Major expansion: HR module, CRM, integrations, packages, validation pipeline
Validate / validate (push) Successful in 34s
Validate / validate (push) Successful in 34s
HR module: - Multi-role per company (admin/manager/user/viewer/hr orthogonal) - Employees with salary history, terminate/reactivate - Per-company public holidays (seeded from ppraserts/thailand-open-data with manual fallback for unsupported years) - Leave types (editable defaults), leave requests with approve/reject - Per-employee leave balances (auto-seeded), remaining-days hint on request form, HR balance summary on requests page - Thai-compliant payroll: SSO 5% capped, PND1 brackets, monthly WHT - Payslip generation with editable line items, finalize/mark-paid, pdf-lib PDF download - CSV export of leave per employee or company-wide CRM & invoicing: - Customer/supplier party database with archive - Invoice line items, VAT 7%, status transitions, PDF generation - Outgoing/incoming direction; incoming auto-creates linked expense Package tracking: - packages + package_events + shipping_accounts tables - 8 carrier stubs (UPS/FedEx/DHL/USPS/Flash Express/Kerry/J&T/TH Post) with API doc references for future implementation - Manual status updates with timeline - Customs duty invoice flow on delivery - Per-company carrier credentials (admin only) Integrations scaffold: - external_accounts + external_transactions (Kasikorn K-Biz, Ether.fi) - Manual transaction matching to expenses Infrastructure: - APP_NAME env var for branding - Soft-delete for companies and parties - Light/dark mode toggle, dark-mode classes throughout - pre-push hook (husky) + Gitea/GitHub Actions running svelte-check with --threshold warning + vite build - npm run validate combines both checks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Tiny CSV serializer. RFC 4180 — quotes fields that contain commas, quotes, or newlines.
|
||||
*/
|
||||
export function csvEscape(value: unknown): string {
|
||||
if (value === null || value === undefined) return '';
|
||||
const s = String(value);
|
||||
if (/[",\n\r]/.test(s)) {
|
||||
return `"${s.replace(/"/g, '""')}"`;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
export function csvRow(values: unknown[]): string {
|
||||
return values.map(csvEscape).join(',');
|
||||
}
|
||||
|
||||
export function csvBuild(rows: unknown[][]): string {
|
||||
return rows.map(csvRow).join('\r\n');
|
||||
}
|
||||
|
||||
/** Build a Response with a CSV download. */
|
||||
export function csvResponse(csv: string, filename: string): Response {
|
||||
// Prepend BOM so Excel detects UTF-8 (important for Thai characters)
|
||||
const body = '\uFEFF' + csv;
|
||||
return new Response(body, {
|
||||
headers: {
|
||||
'Content-Type': 'text/csv; charset=utf-8',
|
||||
'Content-Disposition': `attachment; filename="${filename}"`
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user