Files
orion/app/modules/billing/static/store/js/invoices.js
Samir Boulahtit e9253fbd84 refactor: rename Wizamart to Orion across entire codebase
Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart
with Orion/orion/ORION across 184 files. This includes database
identifiers, email addresses, domain references, R2 bucket names,
DNS prefixes, encryption salt, Celery app name, config defaults,
Docker configs, CI configs, documentation, seed data, and templates.

Renames homepage-wizamart.html template to homepage-orion.html.
Fixes duplicate file_pattern key in api.yaml architecture rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:46:56 +01:00

405 lines
14 KiB
JavaScript

// app/modules/billing/static/store/js/invoices.js
/**
* Store invoice management page logic
*/
const invoicesLog = window.LogConfig?.createLogger('INVOICES') || console;
invoicesLog.info('[STORE INVOICES] Loading...');
function storeInvoices() {
invoicesLog.info('[STORE INVOICES] storeInvoices() called');
return {
// Inherit base layout state
...data(),
// Set page identifier
currentPage: 'invoices',
// Tab state
activeTab: 'invoices',
// Loading states
loading: false,
savingSettings: false,
creatingInvoice: false,
downloadingPdf: false,
// Messages
error: '',
successMessage: '',
// Settings
hasSettings: false,
settings: null,
settingsForm: {
merchant_name: '',
merchant_address: '',
merchant_city: '',
merchant_postal_code: '',
merchant_country: 'LU',
vat_number: '',
invoice_prefix: 'INV',
default_vat_rate: '17.00',
bank_name: '',
bank_iban: '',
bank_bic: '',
payment_terms: 'Net 30 days',
footer_text: ''
},
// Stats
stats: {
total_invoices: 0,
total_revenue_cents: 0,
draft_count: 0,
issued_count: 0,
paid_count: 0,
cancelled_count: 0
},
// Invoices list
invoices: [],
totalInvoices: 0,
page: 1,
perPage: 20,
filters: {
status: ''
},
// Create invoice modal
showCreateModal: false,
createForm: {
order_id: '',
notes: ''
},
async init() {
// Guard against multiple initialization
if (window._storeInvoicesInitialized) {
return;
}
window._storeInvoicesInitialized = true;
// Call parent init first to set storeCode from URL
const parentInit = data().init;
if (parentInit) {
await parentInit.call(this);
}
await this.loadSettings();
await this.loadStats();
await this.loadInvoices();
},
/**
* Load invoice settings
*/
async loadSettings() {
try {
const response = await apiClient.get('/store/invoices/settings');
if (response) {
this.settings = response;
this.hasSettings = true;
// Populate form with existing settings
this.settingsForm = {
merchant_name: response.merchant_name || '',
merchant_address: response.merchant_address || '',
merchant_city: response.merchant_city || '',
merchant_postal_code: response.merchant_postal_code || '',
merchant_country: response.merchant_country || 'LU',
vat_number: response.vat_number || '',
invoice_prefix: response.invoice_prefix || 'INV',
default_vat_rate: response.default_vat_rate?.toString() || '17.00',
bank_name: response.bank_name || '',
bank_iban: response.bank_iban || '',
bank_bic: response.bank_bic || '',
payment_terms: response.payment_terms || 'Net 30 days',
footer_text: response.footer_text || ''
};
} else {
this.hasSettings = false;
}
} catch (error) {
// 404 means not configured yet, which is fine
if (error.status !== 404) {
invoicesLog.error('[STORE INVOICES] Failed to load settings:', error);
}
this.hasSettings = false;
}
},
/**
* Load invoice statistics
*/
async loadStats() {
try {
const response = await apiClient.get('/store/invoices/stats');
this.stats = {
total_invoices: response.total_invoices || 0,
total_revenue_cents: response.total_revenue_cents || 0,
draft_count: response.draft_count || 0,
issued_count: response.issued_count || 0,
paid_count: response.paid_count || 0,
cancelled_count: response.cancelled_count || 0
};
} catch (error) {
invoicesLog.error('[STORE INVOICES] Failed to load stats:', error);
}
},
/**
* Load invoices list
*/
async loadInvoices() {
this.loading = true;
this.error = '';
try {
const params = new URLSearchParams({
page: this.page.toString(),
per_page: this.perPage.toString()
});
if (this.filters.status) {
params.append('status', this.filters.status);
}
const response = await apiClient.get(`/store/invoices?${params}`);
this.invoices = response.items || [];
this.totalInvoices = response.total || 0;
} catch (error) {
invoicesLog.error('[STORE INVOICES] Failed to load invoices:', error);
this.error = error.message || 'Failed to load invoices';
} finally {
this.loading = false;
}
},
/**
* Refresh all data
*/
async refreshData() {
await this.loadSettings();
await this.loadStats();
await this.loadInvoices();
this.successMessage = 'Data refreshed';
setTimeout(() => this.successMessage = '', 3000);
},
/**
* Save invoice settings
*/
async saveSettings() {
if (!this.settingsForm.merchant_name) {
this.error = 'Merchant name is required';
return;
}
this.savingSettings = true;
this.error = '';
try {
const payload = {
merchant_name: this.settingsForm.merchant_name,
merchant_address: this.settingsForm.merchant_address || null,
merchant_city: this.settingsForm.merchant_city || null,
merchant_postal_code: this.settingsForm.merchant_postal_code || null,
merchant_country: this.settingsForm.merchant_country || 'LU',
vat_number: this.settingsForm.vat_number || null,
invoice_prefix: this.settingsForm.invoice_prefix || 'INV',
default_vat_rate: parseFloat(this.settingsForm.default_vat_rate) || 17.0,
bank_name: this.settingsForm.bank_name || null,
bank_iban: this.settingsForm.bank_iban || null,
bank_bic: this.settingsForm.bank_bic || null,
payment_terms: this.settingsForm.payment_terms || null,
footer_text: this.settingsForm.footer_text || null
};
let response;
if (this.hasSettings) {
// Update existing settings
response = await apiClient.put('/store/invoices/settings', payload);
} else {
// Create new settings
response = await apiClient.post('/store/invoices/settings', payload);
}
this.settings = response;
this.hasSettings = true;
this.successMessage = 'Settings saved successfully';
} catch (error) {
invoicesLog.error('[STORE INVOICES] Failed to save settings:', error);
this.error = error.message || 'Failed to save settings';
} finally {
this.savingSettings = false;
setTimeout(() => this.successMessage = '', 5000);
}
},
/**
* Open create invoice modal
*/
openCreateModal() {
if (!this.hasSettings) {
this.error = 'Please configure invoice settings first';
this.activeTab = 'settings';
return;
}
this.createForm = {
order_id: '',
notes: ''
};
this.showCreateModal = true;
},
/**
* Create invoice from order
*/
async createInvoice() {
if (!this.createForm.order_id) {
this.error = 'Please enter an order ID';
return;
}
this.creatingInvoice = true;
this.error = '';
try {
const payload = {
order_id: parseInt(this.createForm.order_id),
notes: this.createForm.notes || null
};
const response = await apiClient.post('/store/invoices', payload);
this.showCreateModal = false;
this.successMessage = `Invoice ${response.invoice_number} created successfully`;
await this.loadStats();
await this.loadInvoices();
} catch (error) {
invoicesLog.error('[STORE INVOICES] Failed to create invoice:', error);
this.error = error.message || 'Failed to create invoice';
} finally {
this.creatingInvoice = false;
setTimeout(() => this.successMessage = '', 5000);
}
},
/**
* Update invoice status
*/
async updateStatus(invoice, newStatus) {
const statusLabels = {
'issued': 'mark as issued',
'paid': 'mark as paid',
'cancelled': 'cancel'
};
if (!confirm(`Are you sure you want to ${statusLabels[newStatus] || newStatus} this invoice?`)) {
return;
}
try {
await apiClient.put(`/store/invoices/${invoice.id}/status`, {
status: newStatus
});
this.successMessage = `Invoice ${invoice.invoice_number} status updated to ${newStatus}`;
await this.loadStats();
await this.loadInvoices();
} catch (error) {
invoicesLog.error('[STORE INVOICES] Failed to update status:', error);
this.error = error.message || 'Failed to update invoice status';
}
setTimeout(() => this.successMessage = '', 5000);
},
/**
* Download invoice PDF
*/
async downloadPDF(invoice) {
this.downloadingPdf = true;
try {
// Get the token for authentication
const token = localStorage.getItem('orion_token') || localStorage.getItem('store_token');
if (!token) {
throw new Error('Not authenticated');
}
// noqa: js-008 - File download needs response headers for filename
const response = await fetch(`/api/v1/store/invoices/${invoice.id}/pdf`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || 'Failed to download PDF');
}
// Get filename from Content-Disposition header
const contentDisposition = response.headers.get('Content-Disposition');
let filename = `invoice-${invoice.invoice_number}.pdf`;
if (contentDisposition) {
const match = contentDisposition.match(/filename="(.+)"/);
if (match) {
filename = match[1];
}
}
// Download the file
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
this.successMessage = `Downloaded: ${filename}`;
} catch (error) {
invoicesLog.error('[STORE INVOICES] Failed to download PDF:', error);
this.error = error.message || 'Failed to download PDF';
} finally {
this.downloadingPdf = false;
setTimeout(() => this.successMessage = '', 5000);
}
},
/**
* Format date for display
*/
formatDate(dateStr) {
if (!dateStr) return 'N/A';
const date = new Date(dateStr);
const locale = window.STORE_CONFIG?.locale || 'en-GB';
return date.toLocaleDateString(locale, {
day: '2-digit',
month: 'short',
year: 'numeric'
});
},
/**
* Format currency for display
*/
formatCurrency(cents, currency = 'EUR') {
if (cents === null || cents === undefined) return 'N/A';
const amount = cents / 100;
const locale = window.STORE_CONFIG?.locale || 'en-GB';
const currencyCode = window.STORE_CONFIG?.currency || currency;
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currencyCode
}).format(amount);
}
};
}