From de6573b15eae19904ca07b8c16b6448469230084 Mon Sep 17 00:00:00 2001 From: Z User Date: Wed, 28 Oct 2026 01:24:48 +0000 Subject: [PATCH] feat: Invoice PDF + Beleg file upload with content-addressed storage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2 new features: 1. Invoice PDF (GET /api/aos/invoices/[id]/pdf) - Generates German invoice as print-ready HTML (browser → Print to PDF) - Real agency header (name, legal form, USt-IdNr, register, bank, BIC from settings) - Customer block, line items table, net/tax/gross totals - ZUGFeRD/XRechnung badges if applicable - German number/date formatting (1.234,56 € / 27.10.2026) - GoBD: no-store cache header - 3.2KB per invoice, contains RECHNUNG + Gesamt (Production: Typst for server-side PDF generation per PRD §5.2) 2. Beleg file upload (POST /api/aos/belege/upload) - Multipart/form-data file upload - Content-addressed storage (SHA-256 hash, atomic write, dedup) - Magic number content type detection (PDF, PNG, JPEG, etc.) - Duplicate detection (same hash → 409) - GoBD retention (8 years for invoices, 6 for others) - Audit-logged - Tested: upload OK, duplicate → 409 0 TS errors, ESLint clean. --- .zscripts/dev.pid | 2 +- aos-shots/01-dashboard.png | Bin aos-shots/02-crm.png | Bin aos-shots/03-leads.png | Bin aos-shots/04-projects.png | Bin aos-shots/05-licenses.png | Bin aos-shots/06-finance.png | Bin aos-shots/07-settings.png | Bin aos-shots/08-lead-explain.png | Bin aos-shots/09-mobile-dashboard.png | Bin aos-shots/10-mobile-crm.png | Bin aos-shots/v10-estimate-detail.png | Bin aos-shots/v10-estimates.png | Bin aos-shots/v10-support-summarized.png | Bin aos-shots/v10-support.png | Bin aos-shots/v11-reporting-mrr.png | Bin aos-shots/v11-reporting-utilization.png | Bin aos-shots/v11-reporting.png | Bin aos-shots/v11-wizard-step1.png | Bin aos-shots/v11-wizard-step3-modules.png | Bin aos-shots/v11-wizard-step4-mail.png | Bin aos-shots/v12-wizard.png | Bin aos-shots/v13-accounting-bank.png | Bin aos-shots/v13-accounting-dunning.png | Bin aos-shots/v13-accounting-eur.png | Bin aos-shots/v13-accounting-vat.png | Bin aos-shots/v14-mail-real-test.png | Bin aos-shots/v14-vat-real-xml.png | Bin aos-shots/v15-api-docs.png | Bin aos-shots/v15-api-tokens.png | Bin aos-shots/v16-portal-approval-dialog.png | Bin aos-shots/v16-portal-approval-done.png | Bin aos-shots/v16-portal-approvals.png | Bin aos-shots/v16-portal-invoices.png | Bin aos-shots/v16-portal-login.png | Bin aos-shots/v16-portal-overview.png | Bin aos-shots/v17-de.png | Bin aos-shots/v17-debug1.png | Bin aos-shots/v17-dropdown-open.png | Bin aos-shots/v17-en-final.png | Bin aos-shots/v17-en-persisted.png | Bin aos-shots/v17-en-real-final.png | Bin aos-shots/v17-en-real.png | Bin aos-shots/v17-en-verified.png | Bin aos-shots/v17-en.png | Bin aos-shots/v17-locale-dropdown.png | Bin aos-shots/v17-settings.png | Bin aos-shots/v17-tr-persisted.png | Bin aos-shots/v17-tr.png | Bin aos-shots/v18-kb-article.png | Bin aos-shots/v18-kb.png | Bin aos-shots/v18-notifications.png | Bin aos-shots/v19-calendar-next.png | Bin aos-shots/v19-calendar.png | Bin aos-shots/v2-CRM.png | Bin aos-shots/v2-Dashboard.png | Bin aos-shots/v2-Erweiterungen.png | Bin aos-shots/v2-Finanzen.png | Bin aos-shots/v2-Lizenzen.png | Bin aos-shots/v2-Projekte.png | Bin aos-shots/v2-ai-explain.png | Bin aos-shots/v2-leads-initial.png | Bin aos-shots/v20-onboarding-dismissed.png | Bin aos-shots/v20-onboarding-last.png | Bin aos-shots/v20-onboarding-step1.png | Bin aos-shots/v20-onboarding-step2.png | Bin aos-shots/v21-dark-crm.png | Bin aos-shots/v21-dark-dashboard.png | Bin aos-shots/v21-dark-finance.png | Bin aos-shots/v21-dark-portal.png | Bin aos-shots/v22-mobile-calendar.png | Bin aos-shots/v22-mobile-dashboard.png | Bin aos-shots/v22-mobile-leads.png | Bin aos-shots/v22-mobile-menu.png | Bin aos-shots/v22-mobile-projects.png | Bin aos-shots/v22-mobile-search.png | Bin aos-shots/v3-audit.png | Bin aos-shots/v3-company-drawer.png | Bin aos-shots/v3-invoice-dialog.png | Bin aos-shots/v3-projects-task.png | Bin aos-shots/v4-audit.png | Bin aos-shots/v4-company-drawer.png | Bin aos-shots/v4-create-deal.png | Bin aos-shots/v4-invoice-dialog.png | Bin aos-shots/v4-lead-approved.png | Bin aos-shots/v4-lead-converted.png | Bin aos-shots/v4-projects-task.png | Bin aos-shots/v4-projects-time.png | Bin aos-shots/v5-company-drawer.png | Bin aos-shots/v6-audit-detail.png | Bin aos-shots/v6-audit.png | Bin aos-shots/v6-command-search.png | Bin aos-shots/v6-company-drawer.png | Bin aos-shots/v6-create-deal-done.png | Bin aos-shots/v6-create-deal-filled.png | Bin aos-shots/v6-create-deal.png | Bin aos-shots/v6-invoice-dialog.png | Bin aos-shots/v6-lead-approved.png | Bin aos-shots/v6-lead-converted.png | Bin aos-shots/v6-projects-task.png | Bin aos-shots/v6-projects-time.png | Bin aos-shots/v7-palette-open.png | Bin aos-shots/v7-search-results.png | Bin aos-shots/v8-search-bauer.png | Bin aos-shots/v9-contract-detail.png | Bin aos-shots/v9-contracts.png | Bin aos-shots/v9-expenses-report.png | Bin aos-shots/v9-expenses.png | Bin bun.lock | 0 ...2fa408b3f1b913561de2ff77ff4772e704580fc7a6 | 2 + db/custom.db | Bin 466944 -> 466944 bytes src/app/api/aos/belege/upload/route.ts | 103 +++++++++++ src/app/api/aos/crm/mail-sync/route.ts | 0 src/app/api/aos/invoices/[id]/pdf/route.ts | 165 ++++++++++++++++++ src/app/api/aos/leads/discover/route.ts | 0 src/app/api/aos/licenses/validate/route.ts | 0 src/lib/storage.ts | 0 117 files changed, 271 insertions(+), 1 deletion(-) mode change 100644 => 100755 .zscripts/dev.pid mode change 100644 => 100755 aos-shots/01-dashboard.png mode change 100644 => 100755 aos-shots/02-crm.png mode change 100644 => 100755 aos-shots/03-leads.png mode change 100644 => 100755 aos-shots/04-projects.png mode change 100644 => 100755 aos-shots/05-licenses.png mode change 100644 => 100755 aos-shots/06-finance.png mode change 100644 => 100755 aos-shots/07-settings.png mode change 100644 => 100755 aos-shots/08-lead-explain.png mode change 100644 => 100755 aos-shots/09-mobile-dashboard.png mode change 100644 => 100755 aos-shots/10-mobile-crm.png mode change 100644 => 100755 aos-shots/v10-estimate-detail.png mode change 100644 => 100755 aos-shots/v10-estimates.png mode change 100644 => 100755 aos-shots/v10-support-summarized.png mode change 100644 => 100755 aos-shots/v10-support.png mode change 100644 => 100755 aos-shots/v11-reporting-mrr.png mode change 100644 => 100755 aos-shots/v11-reporting-utilization.png mode change 100644 => 100755 aos-shots/v11-reporting.png mode change 100644 => 100755 aos-shots/v11-wizard-step1.png mode change 100644 => 100755 aos-shots/v11-wizard-step3-modules.png mode change 100644 => 100755 aos-shots/v11-wizard-step4-mail.png mode change 100644 => 100755 aos-shots/v12-wizard.png mode change 100644 => 100755 aos-shots/v13-accounting-bank.png mode change 100644 => 100755 aos-shots/v13-accounting-dunning.png mode change 100644 => 100755 aos-shots/v13-accounting-eur.png mode change 100644 => 100755 aos-shots/v13-accounting-vat.png mode change 100644 => 100755 aos-shots/v14-mail-real-test.png mode change 100644 => 100755 aos-shots/v14-vat-real-xml.png mode change 100644 => 100755 aos-shots/v15-api-docs.png mode change 100644 => 100755 aos-shots/v15-api-tokens.png mode change 100644 => 100755 aos-shots/v16-portal-approval-dialog.png mode change 100644 => 100755 aos-shots/v16-portal-approval-done.png mode change 100644 => 100755 aos-shots/v16-portal-approvals.png mode change 100644 => 100755 aos-shots/v16-portal-invoices.png mode change 100644 => 100755 aos-shots/v16-portal-login.png mode change 100644 => 100755 aos-shots/v16-portal-overview.png mode change 100644 => 100755 aos-shots/v17-de.png mode change 100644 => 100755 aos-shots/v17-debug1.png mode change 100644 => 100755 aos-shots/v17-dropdown-open.png mode change 100644 => 100755 aos-shots/v17-en-final.png mode change 100644 => 100755 aos-shots/v17-en-persisted.png mode change 100644 => 100755 aos-shots/v17-en-real-final.png mode change 100644 => 100755 aos-shots/v17-en-real.png mode change 100644 => 100755 aos-shots/v17-en-verified.png mode change 100644 => 100755 aos-shots/v17-en.png mode change 100644 => 100755 aos-shots/v17-locale-dropdown.png mode change 100644 => 100755 aos-shots/v17-settings.png mode change 100644 => 100755 aos-shots/v17-tr-persisted.png mode change 100644 => 100755 aos-shots/v17-tr.png mode change 100644 => 100755 aos-shots/v18-kb-article.png mode change 100644 => 100755 aos-shots/v18-kb.png mode change 100644 => 100755 aos-shots/v18-notifications.png mode change 100644 => 100755 aos-shots/v19-calendar-next.png mode change 100644 => 100755 aos-shots/v19-calendar.png mode change 100644 => 100755 aos-shots/v2-CRM.png mode change 100644 => 100755 aos-shots/v2-Dashboard.png mode change 100644 => 100755 aos-shots/v2-Erweiterungen.png mode change 100644 => 100755 aos-shots/v2-Finanzen.png mode change 100644 => 100755 aos-shots/v2-Lizenzen.png mode change 100644 => 100755 aos-shots/v2-Projekte.png mode change 100644 => 100755 aos-shots/v2-ai-explain.png mode change 100644 => 100755 aos-shots/v2-leads-initial.png mode change 100644 => 100755 aos-shots/v20-onboarding-dismissed.png mode change 100644 => 100755 aos-shots/v20-onboarding-last.png mode change 100644 => 100755 aos-shots/v20-onboarding-step1.png mode change 100644 => 100755 aos-shots/v20-onboarding-step2.png mode change 100644 => 100755 aos-shots/v21-dark-crm.png mode change 100644 => 100755 aos-shots/v21-dark-dashboard.png mode change 100644 => 100755 aos-shots/v21-dark-finance.png mode change 100644 => 100755 aos-shots/v21-dark-portal.png mode change 100644 => 100755 aos-shots/v22-mobile-calendar.png mode change 100644 => 100755 aos-shots/v22-mobile-dashboard.png mode change 100644 => 100755 aos-shots/v22-mobile-leads.png mode change 100644 => 100755 aos-shots/v22-mobile-menu.png mode change 100644 => 100755 aos-shots/v22-mobile-projects.png mode change 100644 => 100755 aos-shots/v22-mobile-search.png mode change 100644 => 100755 aos-shots/v3-audit.png mode change 100644 => 100755 aos-shots/v3-company-drawer.png mode change 100644 => 100755 aos-shots/v3-invoice-dialog.png mode change 100644 => 100755 aos-shots/v3-projects-task.png mode change 100644 => 100755 aos-shots/v4-audit.png mode change 100644 => 100755 aos-shots/v4-company-drawer.png mode change 100644 => 100755 aos-shots/v4-create-deal.png mode change 100644 => 100755 aos-shots/v4-invoice-dialog.png mode change 100644 => 100755 aos-shots/v4-lead-approved.png mode change 100644 => 100755 aos-shots/v4-lead-converted.png mode change 100644 => 100755 aos-shots/v4-projects-task.png mode change 100644 => 100755 aos-shots/v4-projects-time.png mode change 100644 => 100755 aos-shots/v5-company-drawer.png mode change 100644 => 100755 aos-shots/v6-audit-detail.png mode change 100644 => 100755 aos-shots/v6-audit.png mode change 100644 => 100755 aos-shots/v6-command-search.png mode change 100644 => 100755 aos-shots/v6-company-drawer.png mode change 100644 => 100755 aos-shots/v6-create-deal-done.png mode change 100644 => 100755 aos-shots/v6-create-deal-filled.png mode change 100644 => 100755 aos-shots/v6-create-deal.png mode change 100644 => 100755 aos-shots/v6-invoice-dialog.png mode change 100644 => 100755 aos-shots/v6-lead-approved.png mode change 100644 => 100755 aos-shots/v6-lead-converted.png mode change 100644 => 100755 aos-shots/v6-projects-task.png mode change 100644 => 100755 aos-shots/v6-projects-time.png mode change 100644 => 100755 aos-shots/v7-palette-open.png mode change 100644 => 100755 aos-shots/v7-search-results.png mode change 100644 => 100755 aos-shots/v8-search-bauer.png mode change 100644 => 100755 aos-shots/v9-contract-detail.png mode change 100644 => 100755 aos-shots/v9-contracts.png mode change 100644 => 100755 aos-shots/v9-expenses-report.png mode change 100644 => 100755 aos-shots/v9-expenses.png mode change 100644 => 100755 bun.lock create mode 100644 data/files/7d/7dc3bd0691230ae41dbbf62fa408b3f1b913561de2ff77ff4772e704580fc7a6 mode change 100755 => 100644 db/custom.db create mode 100644 src/app/api/aos/belege/upload/route.ts mode change 100644 => 100755 src/app/api/aos/crm/mail-sync/route.ts create mode 100644 src/app/api/aos/invoices/[id]/pdf/route.ts mode change 100644 => 100755 src/app/api/aos/leads/discover/route.ts mode change 100644 => 100755 src/app/api/aos/licenses/validate/route.ts mode change 100644 => 100755 src/lib/storage.ts diff --git a/.zscripts/dev.pid b/.zscripts/dev.pid old mode 100644 new mode 100755 index 4524e19..2b4ff56 --- a/.zscripts/dev.pid +++ b/.zscripts/dev.pid @@ -1 +1 @@ -984 +1114 diff --git a/aos-shots/01-dashboard.png b/aos-shots/01-dashboard.png old mode 100644 new mode 100755 diff --git a/aos-shots/02-crm.png b/aos-shots/02-crm.png old mode 100644 new mode 100755 diff --git a/aos-shots/03-leads.png b/aos-shots/03-leads.png old mode 100644 new mode 100755 diff --git a/aos-shots/04-projects.png b/aos-shots/04-projects.png old mode 100644 new mode 100755 diff --git a/aos-shots/05-licenses.png b/aos-shots/05-licenses.png old mode 100644 new mode 100755 diff --git a/aos-shots/06-finance.png b/aos-shots/06-finance.png old mode 100644 new mode 100755 diff --git a/aos-shots/07-settings.png b/aos-shots/07-settings.png old mode 100644 new mode 100755 diff --git a/aos-shots/08-lead-explain.png b/aos-shots/08-lead-explain.png old mode 100644 new mode 100755 diff --git a/aos-shots/09-mobile-dashboard.png b/aos-shots/09-mobile-dashboard.png old mode 100644 new mode 100755 diff --git a/aos-shots/10-mobile-crm.png b/aos-shots/10-mobile-crm.png old mode 100644 new mode 100755 diff --git a/aos-shots/v10-estimate-detail.png b/aos-shots/v10-estimate-detail.png old mode 100644 new mode 100755 diff --git a/aos-shots/v10-estimates.png b/aos-shots/v10-estimates.png old mode 100644 new mode 100755 diff --git a/aos-shots/v10-support-summarized.png b/aos-shots/v10-support-summarized.png old mode 100644 new mode 100755 diff --git a/aos-shots/v10-support.png b/aos-shots/v10-support.png old mode 100644 new mode 100755 diff --git a/aos-shots/v11-reporting-mrr.png b/aos-shots/v11-reporting-mrr.png old mode 100644 new mode 100755 diff --git a/aos-shots/v11-reporting-utilization.png b/aos-shots/v11-reporting-utilization.png old mode 100644 new mode 100755 diff --git a/aos-shots/v11-reporting.png b/aos-shots/v11-reporting.png old mode 100644 new mode 100755 diff --git a/aos-shots/v11-wizard-step1.png b/aos-shots/v11-wizard-step1.png old mode 100644 new mode 100755 diff --git a/aos-shots/v11-wizard-step3-modules.png b/aos-shots/v11-wizard-step3-modules.png old mode 100644 new mode 100755 diff --git a/aos-shots/v11-wizard-step4-mail.png b/aos-shots/v11-wizard-step4-mail.png old mode 100644 new mode 100755 diff --git a/aos-shots/v12-wizard.png b/aos-shots/v12-wizard.png old mode 100644 new mode 100755 diff --git a/aos-shots/v13-accounting-bank.png b/aos-shots/v13-accounting-bank.png old mode 100644 new mode 100755 diff --git a/aos-shots/v13-accounting-dunning.png b/aos-shots/v13-accounting-dunning.png old mode 100644 new mode 100755 diff --git a/aos-shots/v13-accounting-eur.png b/aos-shots/v13-accounting-eur.png old mode 100644 new mode 100755 diff --git a/aos-shots/v13-accounting-vat.png b/aos-shots/v13-accounting-vat.png old mode 100644 new mode 100755 diff --git a/aos-shots/v14-mail-real-test.png b/aos-shots/v14-mail-real-test.png old mode 100644 new mode 100755 diff --git a/aos-shots/v14-vat-real-xml.png b/aos-shots/v14-vat-real-xml.png old mode 100644 new mode 100755 diff --git a/aos-shots/v15-api-docs.png b/aos-shots/v15-api-docs.png old mode 100644 new mode 100755 diff --git a/aos-shots/v15-api-tokens.png b/aos-shots/v15-api-tokens.png old mode 100644 new mode 100755 diff --git a/aos-shots/v16-portal-approval-dialog.png b/aos-shots/v16-portal-approval-dialog.png old mode 100644 new mode 100755 diff --git a/aos-shots/v16-portal-approval-done.png b/aos-shots/v16-portal-approval-done.png old mode 100644 new mode 100755 diff --git a/aos-shots/v16-portal-approvals.png b/aos-shots/v16-portal-approvals.png old mode 100644 new mode 100755 diff --git a/aos-shots/v16-portal-invoices.png b/aos-shots/v16-portal-invoices.png old mode 100644 new mode 100755 diff --git a/aos-shots/v16-portal-login.png b/aos-shots/v16-portal-login.png old mode 100644 new mode 100755 diff --git a/aos-shots/v16-portal-overview.png b/aos-shots/v16-portal-overview.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-de.png b/aos-shots/v17-de.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-debug1.png b/aos-shots/v17-debug1.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-dropdown-open.png b/aos-shots/v17-dropdown-open.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-en-final.png b/aos-shots/v17-en-final.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-en-persisted.png b/aos-shots/v17-en-persisted.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-en-real-final.png b/aos-shots/v17-en-real-final.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-en-real.png b/aos-shots/v17-en-real.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-en-verified.png b/aos-shots/v17-en-verified.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-en.png b/aos-shots/v17-en.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-locale-dropdown.png b/aos-shots/v17-locale-dropdown.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-settings.png b/aos-shots/v17-settings.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-tr-persisted.png b/aos-shots/v17-tr-persisted.png old mode 100644 new mode 100755 diff --git a/aos-shots/v17-tr.png b/aos-shots/v17-tr.png old mode 100644 new mode 100755 diff --git a/aos-shots/v18-kb-article.png b/aos-shots/v18-kb-article.png old mode 100644 new mode 100755 diff --git a/aos-shots/v18-kb.png b/aos-shots/v18-kb.png old mode 100644 new mode 100755 diff --git a/aos-shots/v18-notifications.png b/aos-shots/v18-notifications.png old mode 100644 new mode 100755 diff --git a/aos-shots/v19-calendar-next.png b/aos-shots/v19-calendar-next.png old mode 100644 new mode 100755 diff --git a/aos-shots/v19-calendar.png b/aos-shots/v19-calendar.png old mode 100644 new mode 100755 diff --git a/aos-shots/v2-CRM.png b/aos-shots/v2-CRM.png old mode 100644 new mode 100755 diff --git a/aos-shots/v2-Dashboard.png b/aos-shots/v2-Dashboard.png old mode 100644 new mode 100755 diff --git a/aos-shots/v2-Erweiterungen.png b/aos-shots/v2-Erweiterungen.png old mode 100644 new mode 100755 diff --git a/aos-shots/v2-Finanzen.png b/aos-shots/v2-Finanzen.png old mode 100644 new mode 100755 diff --git a/aos-shots/v2-Lizenzen.png b/aos-shots/v2-Lizenzen.png old mode 100644 new mode 100755 diff --git a/aos-shots/v2-Projekte.png b/aos-shots/v2-Projekte.png old mode 100644 new mode 100755 diff --git a/aos-shots/v2-ai-explain.png b/aos-shots/v2-ai-explain.png old mode 100644 new mode 100755 diff --git a/aos-shots/v2-leads-initial.png b/aos-shots/v2-leads-initial.png old mode 100644 new mode 100755 diff --git a/aos-shots/v20-onboarding-dismissed.png b/aos-shots/v20-onboarding-dismissed.png old mode 100644 new mode 100755 diff --git a/aos-shots/v20-onboarding-last.png b/aos-shots/v20-onboarding-last.png old mode 100644 new mode 100755 diff --git a/aos-shots/v20-onboarding-step1.png b/aos-shots/v20-onboarding-step1.png old mode 100644 new mode 100755 diff --git a/aos-shots/v20-onboarding-step2.png b/aos-shots/v20-onboarding-step2.png old mode 100644 new mode 100755 diff --git a/aos-shots/v21-dark-crm.png b/aos-shots/v21-dark-crm.png old mode 100644 new mode 100755 diff --git a/aos-shots/v21-dark-dashboard.png b/aos-shots/v21-dark-dashboard.png old mode 100644 new mode 100755 diff --git a/aos-shots/v21-dark-finance.png b/aos-shots/v21-dark-finance.png old mode 100644 new mode 100755 diff --git a/aos-shots/v21-dark-portal.png b/aos-shots/v21-dark-portal.png old mode 100644 new mode 100755 diff --git a/aos-shots/v22-mobile-calendar.png b/aos-shots/v22-mobile-calendar.png old mode 100644 new mode 100755 diff --git a/aos-shots/v22-mobile-dashboard.png b/aos-shots/v22-mobile-dashboard.png old mode 100644 new mode 100755 diff --git a/aos-shots/v22-mobile-leads.png b/aos-shots/v22-mobile-leads.png old mode 100644 new mode 100755 diff --git a/aos-shots/v22-mobile-menu.png b/aos-shots/v22-mobile-menu.png old mode 100644 new mode 100755 diff --git a/aos-shots/v22-mobile-projects.png b/aos-shots/v22-mobile-projects.png old mode 100644 new mode 100755 diff --git a/aos-shots/v22-mobile-search.png b/aos-shots/v22-mobile-search.png old mode 100644 new mode 100755 diff --git a/aos-shots/v3-audit.png b/aos-shots/v3-audit.png old mode 100644 new mode 100755 diff --git a/aos-shots/v3-company-drawer.png b/aos-shots/v3-company-drawer.png old mode 100644 new mode 100755 diff --git a/aos-shots/v3-invoice-dialog.png b/aos-shots/v3-invoice-dialog.png old mode 100644 new mode 100755 diff --git a/aos-shots/v3-projects-task.png b/aos-shots/v3-projects-task.png old mode 100644 new mode 100755 diff --git a/aos-shots/v4-audit.png b/aos-shots/v4-audit.png old mode 100644 new mode 100755 diff --git a/aos-shots/v4-company-drawer.png b/aos-shots/v4-company-drawer.png old mode 100644 new mode 100755 diff --git a/aos-shots/v4-create-deal.png b/aos-shots/v4-create-deal.png old mode 100644 new mode 100755 diff --git a/aos-shots/v4-invoice-dialog.png b/aos-shots/v4-invoice-dialog.png old mode 100644 new mode 100755 diff --git a/aos-shots/v4-lead-approved.png b/aos-shots/v4-lead-approved.png old mode 100644 new mode 100755 diff --git a/aos-shots/v4-lead-converted.png b/aos-shots/v4-lead-converted.png old mode 100644 new mode 100755 diff --git a/aos-shots/v4-projects-task.png b/aos-shots/v4-projects-task.png old mode 100644 new mode 100755 diff --git a/aos-shots/v4-projects-time.png b/aos-shots/v4-projects-time.png old mode 100644 new mode 100755 diff --git a/aos-shots/v5-company-drawer.png b/aos-shots/v5-company-drawer.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-audit-detail.png b/aos-shots/v6-audit-detail.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-audit.png b/aos-shots/v6-audit.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-command-search.png b/aos-shots/v6-command-search.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-company-drawer.png b/aos-shots/v6-company-drawer.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-create-deal-done.png b/aos-shots/v6-create-deal-done.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-create-deal-filled.png b/aos-shots/v6-create-deal-filled.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-create-deal.png b/aos-shots/v6-create-deal.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-invoice-dialog.png b/aos-shots/v6-invoice-dialog.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-lead-approved.png b/aos-shots/v6-lead-approved.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-lead-converted.png b/aos-shots/v6-lead-converted.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-projects-task.png b/aos-shots/v6-projects-task.png old mode 100644 new mode 100755 diff --git a/aos-shots/v6-projects-time.png b/aos-shots/v6-projects-time.png old mode 100644 new mode 100755 diff --git a/aos-shots/v7-palette-open.png b/aos-shots/v7-palette-open.png old mode 100644 new mode 100755 diff --git a/aos-shots/v7-search-results.png b/aos-shots/v7-search-results.png old mode 100644 new mode 100755 diff --git a/aos-shots/v8-search-bauer.png b/aos-shots/v8-search-bauer.png old mode 100644 new mode 100755 diff --git a/aos-shots/v9-contract-detail.png b/aos-shots/v9-contract-detail.png old mode 100644 new mode 100755 diff --git a/aos-shots/v9-contracts.png b/aos-shots/v9-contracts.png old mode 100644 new mode 100755 diff --git a/aos-shots/v9-expenses-report.png b/aos-shots/v9-expenses-report.png old mode 100644 new mode 100755 diff --git a/aos-shots/v9-expenses.png b/aos-shots/v9-expenses.png old mode 100644 new mode 100755 diff --git a/bun.lock b/bun.lock old mode 100644 new mode 100755 diff --git a/data/files/7d/7dc3bd0691230ae41dbbf62fa408b3f1b913561de2ff77ff4772e704580fc7a6 b/data/files/7d/7dc3bd0691230ae41dbbf62fa408b3f1b913561de2ff77ff4772e704580fc7a6 new file mode 100644 index 0000000..757877f --- /dev/null +++ b/data/files/7d/7dc3bd0691230ae41dbbf62fa408b3f1b913561de2ff77ff4772e704580fc7a6 @@ -0,0 +1,2 @@ +%PDF-1.4 +fake content diff --git a/db/custom.db b/db/custom.db old mode 100755 new mode 100644 index 8639c571b7c01d7058c29a809534205db9944cdd..eba06761476745be01db1be1c2479e6c23b05de4 GIT binary patch delta 34155 zcmd7536vbgl`h;_OE215LbO_~?p8}QjUO+L#*K)~8=oAx@yU_fhq+rnIsbd3aty zuCnz^l#h!uKUsSQ;p$6B@TG%Z;A+F*905EZkO@IXFNDfT(VW>h_8u3(eh(0g%@Oc* zAP@*aq#iHxIVEL3^Pqp#%*V%;6RQ7#-TaUJ92tj;ssdCzp%TP;vZjaAGtZ2ju?Pr0 zj|888c%9lmy`WfoM!iAqU1iwqZk;cztl!$Xv%4SLhBUCERL9`z_&DS^Dekc_W zS(&2cdgdPx=pSb82(BUBoWMU%%sd$!B!51Pe?C0(i{JqH^CA56p@(<;%jh`#`Q6ce z-{t{-D4Wiu;zlN9O+~Y3pHnK7Eqv}fGyi#HjFA1)x}`pGrGMsknUxmk0vo@*W{GdZ zN`J|yO%<$gIb?;;&Z+ITZ;dSWZ5qI~V#&yajRG|Cy3@LDk&hqn$1{~IjAklRPMcO? zuT?7r0+N8(-<+|pUh9L7zd7@D=O52sy@mWg|2VVk=rH-mlQWY?H@5z9#nAxy#}hNR z9$nY^$5)Pyl7Bot^P{8h!GAn_jc@tJQ~a-X^%s}@X4waq{&MLxOTNG4ti=y478X6c zNbdV=-{RhC&vQLm_s9INes%rw$Nd|c6Q?W=s+)!fx|*EmgaWM*2!v|H8;bR0xlzs6 zz#9f;Hdn6H0*yi@niBG&mN&>KMn2yhU$H2tPL2+AUCVa>zzIWD0-zY7Oe&qPAi&d= zE7d9=Fh$KQWTW9W{n+wFL7m?`&}D3K=oMhVKqoH6BW9pxNf?VN)k~E^IV!4hM2)7A z{BUzkKah(+exTDmlA|{psZ2n(YB?kys_j>6fm*s`*2C#i6zQ*Ou2=^2o5u#a4$aX6 z`$mAUUn$DTYAsbm`q|pKs*y-#bCGaH)?4gf)m*w1=*Jm)y+P>pcqt|2Qzp`%U*Dx_ zR-~-P8r4Lti2YyL99%;B2l0jG`p-g8jT8D(x?Gl3vp#dUF*ftR>gOnRt=eehOOZe( zj`aJlT@2(KfPBjwc?QU1NM4P{lU1__{7l#JAcGIL$ zLYn1E$wpQ$&-}cxM+xKv;*$}h+>-jf>-rW2w@v`5JV%;_UL!~vuQ#MdK%2SZ+Vj-B zSkXc`Ud+W~$nd^qPcLx16)aBe=v&UgtdJtKe4&wy2Z|XG*xH#luT_+c5=-Z!#bRDX z@~P&s9v~+MJEt!sPZDw^5K6@A0@3%CYP@6>ta3SCQ3^;q**vAYEvzFQwq=mDlEAJl zL$#zh>3Y>n@Ac=YdZ-YAd9#v9wFGu%)8hwvc>q|Sqlcg_8-zZP6-v>n#?SoC^?TK% zT!=K{g3i}W?0c{|>I2%z;Q{`lxnT=Hi?gRLi*i0&FJRy6Z#XE|W8sh*G4yP!>%p*h zQE+H8^gJ=wGXyDFBR$uQni)3A>dbvN1XMGn%H@cjY=m2*Ue)w_py$oQ{Lm0ht0LFw zP)P`giPkK+aeAvN#6yx1QA5dm16dzvF5uij901HmGEU?%0cLSaGtt8zoZh)Y zEvwlyUnoV33eulC`?BZXmy*eojVq_P<}i&38Y^6_Y)Du>zm@X%es?P%Pty*)Y+f_% zu1npmj598Aw=%xAcz$ckou=FVw8-7exJ;j`m34~V`K^@AyAu)Cs;AwVh|CzRI}?!^ zvvg-7GGlh`LjM+SV``SK|uZko&$`Xr==Mt(EC^hWc-s>Hn+572F zlgarRVoJl$%>3@ttMO@IF_1yqkHr#!k{O#>`os?1}v}r<{63lGb2n|{Q=Vcpjq~<2wuEmeBb&>ee=-Zrtyh6S|AOe#>_y>%#{^&=8Jdy zqnq{|FZSEjCGXrcJjf5Z@@$w80~n?9Az4-QSa#<4owpO(?_oHWR(;%S5Q(L)!1nti2nBD%9XXoHs(XtghzD$(al8 zzR&gG!^ej(31)TWwoH^Sr5m?pveI`OrpU zPFpfX3={G_ULxkARpp|Qi&xrNH7kXg{f{c=gIu3Qxjx%m1XERT!_c{1d`B6GhM7&S z6wZfbU2@P^i+=jibIt>_e?hc=X~5hS92~fyYdsByc}l#-XEiC%0b|5CbJboHo} zS#WSr?^-zrMiz{9GEP%5)oLT4JDkD)PYZqKr;kmzz^X8_1qX-0&uB1A00FLAOqJ9& z7^|b1a~|gj*pG0SKPtmS7aSVg*)=)`MxsWUa8}P0@|kcw=YX-6{n+DMTwvG3%m-lG z-3lOJMFLjHh2=mzYdK)7^-E8vM4dlG&VJbJg=r9kwsq5%Io8OcIZx1}Xep8kn2yj` zdz$&$6KA_3S;v`D<8YR@l z&sx7tPw$do@G>tq<|m6&2a9xqaW)r{YFSnBFONRkqIj9hb zDft3la|Xal{`D76+n@VV4~1Iv!2o10H_m{Dh&m&h9+%=N#Q_Dcm=`jxIXSWawVn4; zpjj_~Zk;a`!YL-yhf3*6UNA%lnhhl9z9jFLu6&R}EpPy;&o>PMikWbt9*HKTx?^q&gn)m3>Gb8_ zk;hx0?caxA{_`@D9E*%^xRXI_rHq;i6O4IMVS=<$c1DE>koPog8dRJh>Y#K?g$YJk zRG47oO@)a$!S%Ey_pvwjVRjdWK6AvbG@h?Ut%yEz&+k`$wfe@jUJ^lnjynGN%wPV1 zl}?d7jhUm7EkjVs8DE|Hk3aa_Pj3FN){`N45;GGyT(F9fLh`Fe{_Dm8lztqVFd9b1 zJfI0tccL!FptV96k`s+UEG4BW-9(`k=O-nRGlXPRqI5%AON2I5E(Wx6Hb&_tLZpwO zG@#^p4dOYa9BAeXgjg~&W=O8pDBW1linC8iOsB+fIzj1%ks_s=IjK#w#YQQI5!LK+ z?#Wg{1WO(Py{J^Fijp2qzN;76ua9v0m2WF&>+Wo z{eEsPey0E*9V)CzypfO`d^WMPo1>g^f~WW{zdDzxzq>vyGDmbISwWV z7k(1(F`-72fRi>K zixx_?Xvpb+35)hUYdLlBmvW&$|IdA^hiXXRwl znSIz0W8eC=XN`T)7&o%&=@5YKU32Zp-WFUNbb5>%Ku}`wnC}&HX+5J>J9#ZLBiP>@ z<2DQqjr<1W{K8W+C$SVW{1O7bG`o&F)LIoVc0pM(qgBe18J$p;Oj;>{QkD$TIg=I- zWy#DqX9L`An@|K$7Z=getTG=>LJsXHiW!Ri=oOs02ZGOUaMZtPb}v~R4DRb1ANLH* zS#BBd(6aJ*KAos!I{7$@xBW^kNRrD}5%1OO76aUNfP)mFlgL1=SZpPKQb|u}qT#T| z$kP6&D>>N(H@yhC8(7Z`(Qs%*;bhdPW{VxaV&QgO#c2fY73A)fOdr5ab`4O2ErL=o zV&Fzi zrl$w+c6W`h_qaw&@Gv%7ij+tfmAbzp?u)oLDeIiPR88EEBgUX2NAk8Lf~*xPsvHu6rv>uj&XQ<$7|kI_R~kW z)r7}?LmvOF$@u{E5CL^r4FSbewUH_X#Cp2%?x6hD+*yRjHeSsOaJxV+?jh#@4wHwZ zV(}?G;N(mq<5%|AuI6|*dp#b&J3{bWdLek2h+2UxpEGqxF0eGT0N$3P+zx{GOC0Vm zn?4TkE+Tq%4VSnUri-bXBo!l8C)l$5ef=o6eG%aOE8_j@>^0o`Td5Zl2iyffk@0GG z0Z?R|)Lj6?R?6q7v;tgcn)V?ouV7q)$}1QHr}7HMQmJ4(XL!?X%cG((V?tCkW-Nk= z#*BWcV9aQ1Hpe}?6oWB|CpaIK3yM(6E6yrkWCD%-_tV@ZK~Ur?P0rgL46dKhT^GeIh*M$%owS5SOFB+ zQ>IX$a15yh5dFb}v2F)&KGSiDi#3JA7jK?c4`ZCuAWnVl1f&X(bz$|H_2+tvk7izBiwE4Snkk z`*(M8D>1Q#qMV5F10rW#(dNPt6W8~r%F%dc@=z{pl&s>q&Fc=Nvq@|6pj9kw8{f#? zQ+_(sM!&|lG#Kpa;<-LgZ|~d$N4jbqc`$2IrlzgwglBe!yYnt?@B&!nwh(`7c@a_f z(1@@ugm?!ktX8sOwOr`fBs+<`a~C(d#~J@`(;mb-e*ruUR&lkYMFS-#ky{}6MegP% z_BbPcJ;5Q~u6cMh;15%ADHOI;@Ega(*x>i$ySdXk`uX=x*2b!s3KWd?s5miS-~q}= z8R1bz3aw{~lq@JKWkxpp5cfBW$?8x7mL<%xqE@{WRWg!&$0OVjX?e-jQCw7gmpfh`R6v2YeD#ZrFfz5|o{FWEQA+c!PoUpnNd zMgXUx%Hy{ED7S9JrXDyjgre_qiAON*FBSOeyj zfC`U=EJcV|qHglK+{(@Wqp3o{Mh_0J9P69Mqlh_MxxnWvp&D>_6l~=&ZUu2zc1>Yq zi<6;rF;~bI%f+0P30tr!89BTU*%bsmB8pl#5Ylzt)D1yZBqYaKuGb~fpwfi z^ne-7h7v{lp2xX0Sl$3jnYY#T%;Vg|^s9qgk*}~6(9MV{19wu$FlEUUEJ3hBG9+NK z5Dr_4X$1@=WXV!Qlr1%E3MQmInib{+5zGTc`upZtP_5E~6Z5PTx)w3zRBNeJGHc0> z1*mrEiIxShn=nL>$TRTvrUaH~>8iG+3J_;-t79y;M$$6bswAnWGtz{Gl+J1~(} z+Roj9Nn)(Z9hf9g+zwiE?!bick`iER`uw}D{yY1nWzQ_jE$dnOcW}7oqf5`ern?W4 zlP=deGaMAIII~h#i8QJiCweeJE&Z)WQz`qQliaFvAv1UrMf7IW4~SbBL|i1stLbRM ztT^i&Rzi0ECGIo=_8$oLA6NSTY?J~ca>E>~5iqk^E#a)`Sb^GKKFJNaz)UZIO;BJ& z%($XU7Nn#c$;r-G8Ts2wU*bjy*dLIyKSVvuFbNGV=R%N=YlU*m0b~8l8RqX1?Dqx- zVB-{Lga(&!M#6|i^SZMQ!7A5&?j>%7tQmfXV85H2H;KY1O;YAbS}CnC=1GZ}F;7Zv zlzD<6&ZH$lnI|)}**CajOK{$V-6APWl9`$!3s%wKZTm0W7Z}U|RO*Tij*>&lc`DFhNM? z-W1?$Rvb>&wUArCW&hIPrh)YU-`hL4ZbW!Io|$VVhChqqlx#-{HouBKTki^f`sTYq)zJ`*rfo@Iv^_E=VtT4GrP| zTrwvesAK?@a5R;cEYq&v;u#KxvuPuqIX|1p7M-Mq>Cd~4`yA|_yeYVJ&<PamR1CYaBccs;z(Io_(?><(b5@FC#07 zt&OZ~4s@RqoPh0izrV}t@%Hxi2FU_w8fz$06?(#hhj8vK+6d&TE)JZv8=K&T3r3`r zgB&Ri{kJ_GwyFN;dFyyj%KMSkj*^+G6tc9O{CY-~+v_JiLnsH>JCG`9Hlai)RkYGB zE;{8Sh+}jbj9j#2;Nrpezq)!-{-H61I!cTLHsd5r|1Bd^YlvdV#YLwU#sr_D1?iU+Di;bE8eriHZ2b9akNmOg z3(I>_UEkV<-TbNbYd~e3x|AF^9wbHgrYIp?CW= zJ*mE@Haa6_)|lA)f3<#_)2lh6iUClC9}KmxuMZ8ir3&Ktcvdu&Zis=RUY_GibdFH4 zF)U}q9VdYm9dM<2SHkbv`@a82&-rhFt9<>OIo^in?QS4j|8MKJ6M-0yr6LgHO;iM8 ztelEKjImJ>2#jD4Z3R>WV$?=OAVzqzJ3L=))oaLjWTNkEnKx2_uv1@PLzDfBi#!vg z?k2%PT;>T))U1WoWG2y;AsbBWkL>fTwlCc4S&4+OgDBMqVJ0J_wQ$PWJ&_p6*thO& zcSf3UGgPPwv>F!I&BqqZo)~VGmP-nR| z=FwJ_&OE%DJO2F_Z(2?KY~-}oul?PaXBfeOX}Pu6ZG?0)U#sptXQu-WRfG0LBq;sR z#}MtYxaYLxk;D5}b62&~}`%>?ieR13)L25oShTV+WABnfS0lScKt&oiK1}w9* z_87=SOiHpCgV<@%{$<=Ft}@CI#fn5Mqt=3LgHI*e;9x*9uInrDShk#wX58RTDT1Bl z?GGe8JmGdN0x+QLB{oLC7XjvYbM;*2@w|GnUI)i(e|Z0TbKXx&V!(n?R#%*>13{( z5$tBfzBT0;KAYGT1T7gW$tgaNQe)M^&Py+KlB2nEi_E-dR4VY`I{Sy+zEkbDQ=U^7 zJ#_cCTb&Je#^iK%Wx72kNbqE=Iakxgvf0Q6+?{nY5N6)9A4+?+E*ctnt<}LA?BE*E z*)Wb~xOv!fYH&kv{IVf9umHsh|3$p(+!++(0y37LBjH5c4AoV)Wps{4F+;R(&VVFw zSoikse`9{%Yq0M%%_N6??`riOoYyyGrC=%$BU5HMUr?jEyYJ5S1v}zc*28at5f73D z)BS^O-(btp=yIz&5+6dQhQ7wVh#P2Z)NpTe$&A~(ce!N7Tiv@{GUIOU_)1!*Wy*Qn z0aj*g+Z|w8lXM4I)Fx#^?)d7`^+a3Q?f@$@>X^OJBlMDWrwp4v3OYi(UN((-IBK7J zlji^ro^+Zcea1xEx5!#|K{li8H{0L8$%ChwpMY9F0 z1A})sfNO7Y0irPmw4^9jb=}^7i|4{=Ff#{{&FzG`WfW3%YNVTx(@od)t_p4#9G~=f zJm7V(;(;I6@n9J;Zv|KKg>a;1%4TZkp#x565md8m6|(zErK~;tx1O;*uukgG1(3oL$M_X<3$2x|v5+eq0T_*>7u!5994hTDK}8|+vAwk-_E zk`=5I#U#BU#WcYl`LJit>a+L#^TLETeAq>Z+Z^?Hq!Nmx>h?n)_FSf2c*h4EDYAOm zwty7RK#tC^gSWPU;V_$mwJA}7^JwKTZ|86I>>qt)+i5LoN<0%tJz<}8sNp!9g4-O~ zh;Bu*s%$U6&2wl^^4R?jTf}_nI=o}p%&r+_N2g;~r`r$S)@BqAzsXo8%jMEZS*%9w zU)<)|It}w#rz^%))-EuHaReT3ZsI`28#Zqxh7|1T zMG96roU-EfXFuYR_@~1i9+=^-rH0kcf@W{`+|vq{OaySBCt$tVeV#yOe3c5cq?K|! zD$s)Tr)fu_A}wR!RHS9hmUR-Q}=r| z$6z90owpZG|6TgZHsIlK|EH#Q)>HA!x^t{_yny|yt>5NusBmmJG&(R0JK)I0+Us0T!ZWl?9CR8br*mWWe`Vd{Wf=b$e4zQ##&?Q za!a-ZriClUjFHS0%Bp04>jBRN7ZC}<>WaSDOP zBX-JXKxSvsLZh698RYDfp2I818d!lrs#qhF6Oc7XW~nVwb}05OKlFTWC&c2ZW^P3< zT#OkzcCoV;jxe}nk4ijW*TurS!+hdLo`-i5*K1BK2bj_E=B^G5abc{LC2LtJS57%6 z78l^=rhoC|c52>Jr<%L^2{+@-Vh0A&UKN)(=@cBdOUX{YOuM0OYM_J!kE_;=06C^ZKR8%duz&222+2u0x;*S2Gz=vZ8It zEC5scvFFJh5D%Kl5+az1=A3*93Z`tJvXU4L{1s+uKk?Ld9=vqfRCB{(fEilf-0CnR zg9E8pL@$No@rc|gJIpY~XY)Vx#DgRtG!_xTtUpSDU@<)r6>@sQaE@Fp5RHERQ_p*M z{Pu>aW}%PBW&N=O9TZsftGLww_r+6ExpP=*0hG&r=K0!A5}}$rRKJ0j<^>cC7Qo~x zAJ!r@XMv! zUe}z0fXt%Y^t|WWJB!PkO$&{GJ)cdA7_tDvC z=f0251~KYBIuo0y`{+!_q3)wY>$FM*6Y4%X6C>QmQK8B(&-bbHk#T7%eI%_dU!86{ zE0sSoPDtgCq?Ph5Dt%;phDsk9XQ1z+GgeMbg`|}-UTP|2Y?7J^88d6ogzfw2jQz~M z;n}hH0GSFQUDjI*w`eIGP^!w#y`7BCsa3LNxweywOdXEgRXNqZ_s^cuMX-e1${mOG z`BwX>KYR8}L(*^|rVbY#JJok8Y?^chv#`zUA8#&()#%*KMgA*o}C&On^u!6F;k0P+xbOjWdHQG=X}!X4(xPC(}Kzx z>C|dCkE8%_q0oUwX%L5fLYD)QxQW znzxAi2dtI3EuU4m+wxgWxGi5{q~*>56&ThsdR(*1y}$Nj5(ot)y;aCem{|$7DceTP zGHb6|<$WL0fW5e>0WtCSz z8seIm-`DG*RKx1zEemwE?YG`xJ3Q#cEh`1qxH^U~Lx(dk(Yd^o@htoA2EBMm2x-vj ziuH(^mpk`SS>@P&8T6is48o#H$33-jtfps;h?77pKy&HpIT{V~g`{FsQ!&Nahg*Q= zq1E2)*bgk7HLO^ev1lm`rvV*JF-~d^4|z}T(BKYFBwelMD>Y|3jWs7bJ>tbFDBs*rn_VcGvuZA?F3G?qrJqjzpLdV>gU~FHrwyjdg zi!>}mG>WxGw4!u)V`;vzwyjd2QL(_74rO>XHYYchW@OB}1$l$gzKSlFQLVgH%{y}d ziKuiywd-SToe~;!xe_mEv0P^r5!2l659%xRZ5-2@maLy>*%kV$8fy;7-;R#_8S3I<`{o?M~Je z#*y90x}r}*$CS^y>sA<4DDR|F7RC>$l!fs)DrIqeYeT_tE-GhX+-Nr7J$2DINlMEw z;o<~k#L8MUoJjogSxhu9ZvEQoSzh^UNWU({obS?lUq766^bdDUZtWU#?cpjVz`^=g zvYM>OQYHWOhcF)o9LS0XFDTWCUxf&oJiu1!>-=N z-0@ex_0Yxk=B?h`IY4;`IXKkx_4Ef}8+o#8bC71C2uN5vs|%657_W{K z8s=Tg4p=1VScc;Z8L^fVOgQ_7<#OQY+{(s_?UUQQDZ)`3>3X<%57M3N zn$T&E2oQQwDyr~uK9T;;O;}|H94IYaY=3yWcRRs3fE*o|h70z9v$+ehb4p9FxYx0? zpMW>QMDn@fi;z}BeHU=Zuf6!a-XIxS8>e~DgCpCuivUGkBO^eV2gnt*RxE@JI8I0D z8{R=SLy#^(jxK3l!Xc7QWHc6XA>{xGPui7)s1>M1t8iKlQ-9zI767RKZT3Au?;&D7 zZ8&HDoHbv{(;1_rJe{#T%F`KZqC6d1r*+CkC{Je;PkB0{P0G_59Z{Y>C-{lBQ zq&d6ayJ;EOKUb0_ ztG%cD!1yn<|L4`-wWoaPXDvUz94Rk{x5n7{tG&3fp;VwBoNm%pc->l7HSLL`-U;u? z%P+MrI_e!=nccj69x*pZOu`XvS`?I+m z!t-~XcMuNS1bFW5XYW6+1$r5BbXn&hE4*+hvQmA&=>@2cq|-0|o4-*Fs6^l=pS z@#bc@PQSIebUX;P2d7Sn9N~-*jmMQrJXgC6PCo31*T)pswQj0T9cR2>*l3_*l)o23UL3Y=6?O-+RxTOmP*Q+A^vK`AGbqlz~M5Q#n~slPcKFP0M`k% zN}-KJnztGi`(J@FK;y{`-U(Of~m@81J+5UQm%N`mMst!lf|iG z$_Ux_KIPpt9aa>@lm&P!qU$=`Qw)R+xU?Fv;Dv&+YKh>Ka9uGRk#!Y9w^1P0V?>^hJiphB+HVb zgZU_WI3h;)5JU;^w6J2p?M;XoVbd_Tc({9}Pt7?@+=Hy#Gy9D9!Tuv89x?K!^vH;Y z(j#f5cqW0hx9`HPWq(@6omld+_h27cQED*Rg|JAVnF5?GDplK(7h#=7zxIZ_spQZV zN~NKfE2X$q%_6&>Zv8el_P`x+F*{6x_S_pMHMs8|!h%8}o6YfojI*UHGP~sVwpYEE zToKiwm?RpRMjbDMjCA1P+E;M9wGcek9?E=JtqK+A)HHjB(Eb&`56Wp-g_l9g5P^{I zPqlvS{l9_DTOtl%D}*bTctO;|am|@yL|B;8ulcb*_8Sm~5Q=1sY*k1KEpgZgXg9AS zFA30(jQYnXcDcks;IW1w#9}oi&^fif06e^N<$Uj(PkzGw@M~WEN;xB?^;DrC=Ub-+ z+Wj0m&ASZblHcN&Cz>l=9l`oNgo~(>5!GZU4LE9Lw+5ghdc+6oi%-}${<}9Z2m!mU z$YQ!!ZN22X-P0#u_u`wIG=hvva6?aI(u(R_B3)q90ZOK0w)whuj}PkYpRgZ#-78*E zDku%06e4Am)F)fN$GjYjV^`BV3In-#?iH6Bup7c!rmAU*EOoXH7w8R&t%moNZJ)4@ z|JHl{s#Mfa3<&V4h`S%>#h0wvyYp7oYTbD&wo*ptPFq~I?sJ`ZVi^I4LxpD|MP57)Hi-rUt4?h6_$JAl>P9|`BKoX-wA+X2rv?df z2&;{msFs9N0?LV7yM2%QGYe2J(6P-ZOot4}@eh0e6ye;f&T0 z6*HI3B^wd@NzzeW`aNDlaJ4|?iiP7EL*Lx@5g$ay9N;#R2Br>wSbjmj}0J@JF zS>CM1lF@KI%vUnGtZDC<^6ATAE$;5%h<)=^TVSwCF(i^a$+@B)t;NFj*QY>WLJ3~m zUxwgV3}oBbDrrU*ua4?;jeeU){Yt2=7$| z^MQ3&@2JqD6Letq*SzCO~zB%v;}X(XmfI4uWvJARDEp>55YJz4+8_Ppj6=GM)Q&iWE!a+@-7dq2R`6?F_7)ZE(~yC1B=pfN)v=YjE?~q*+>MwEP?50$-Z@;Z#4m_ z9P+#y2za`I*+D4*o0E}ZzzqTm6E_I=*FbRpP~a2sbSj&5gFv;W1>yrv%Fb0mR-^8? z$ZEq?H=z>@_|ER}w0$Pz(&lHA$)GiaFD+ zq5?!aNT}7(_*QCEH#>49$-m+SC(KFg98#^^T|Yr4_Qgcp2+$(UkRqq= z#bF8l3IDlRa?rauK-UqSg9`^oS_z*&BV!)@~#Mht-XNYvEWUl}VP%!Ua1! zHzxvc)E8XgT3flxH%6kv4Jmk~&<6kL1s+dtm$##QSmhX)O+?@|qFPN&yD=nm>}&0r zyLcvj?Eo4dOe>8{VhG8Hfm(HKxUGB7jY zD+xu*XDB`(V~5iDwf51weInuG`WUP-VJy2@49s70RbH0zG9?e5-248(we~Z2`_3ex z;ku4$88K3-Wdua2mb6ksr&P-fic&4KE;A^8XCLyNwg?kW9cp2%I0+MVJ{ynP>Nk8F zHUi(b>^-$FIIw+iu&dWmfJUCiIMJvGrjf1*_J_XV+c-_0-o6r^9vU2)_cW$WW?fL_ zaJ0^=s&eQ+X`MPzOcvm>E+jVb3flk8?>z;`hbeLmI>W4}YQm{MJ{Yo7Mq^%iMc;m7p*@qtSokpI%Y2R{qdMoub%+n_3EcJ9OpRTJ3`|d}4 zoABxL`WbBUG|uT}2zELue93;Jjr}(}m%-Cx6t)6S<5rlNZG_EqHf*2usBf5H|IMzY z@bozIG_It?LNP9tQaStjM}4Ck;OP(TUII^Vpq`ci8#lDea0t~ZX6=7`)VB`4#W(Vy zix$JfD)q1gW`u`Faz(xzkE?k}7wzTm_*N;X5xCS^g-?{BNnOk%a8lkk*AyQgZC~i@N}859MPbND{2+{dgtlA@J=N#8MlE$W@ljKCRvL|MIocx z|MYmv9h$q~rAeSDxATyviv+tGm7|holz1?U3);y&yn8GYZV4m_uOCAV+;|qeENNiC z4Whzj00Ya+=}5h(hgEo&7l^~iB=P#$Ks;i?mV(NzGe~0g}~v&*F)$er?(G(icuFd&Va&zJ}BS;pm0gx&Q(nfJs%V ziL(zmn`dyO2KTXVY5lf~YpfqRWo#lTN*4A#&-*lD^|#qCKJOdB{@^f&iAh90tiUlw zL38(~U@~({>(_y0oZo3+`@7-=-vkM%x57?4q?dkwFzC5p1G&tKOQpHl7>tqLRuEKsAV@B zklDd%3)mpPPH&7Gv>O<$aAEs_7k%Wbl_MXvUwE-CeX@UtX>}wPDAuH$Tmj_t3O;`8}P*uxXhYy_XjB>ENr1a^6T_RLI^d3zuN%A_McDs zbkg77+S*I){@}!pi3`0{MwLUQSkc`dq?eGQLVx7f>5s|F-o3xCz2rL&`*T*Ttn*N@ zfiZO|HZUgWPH8pLN?C?GG>~oBJ+vO(u>p5!DXF_-gT}~c_6^@uYwrurmYJ9_BolD; zI9rd{yWa8z$t?G=b0NR*tk^K@?YxdvgA=_b<_GbFQh+a6HtfHD%eNk%-UHc$XT{|F zr!hJR@f3N5rZR*@S1s_%OV5+))dhR5LL@fOk0YxqKpO2jBLsB~JCx z9gt0UR*Vhz(obU`Ef>uuL|mw}Rz7O&X&kEH&KmvggIb1XfiKv{n zm%h`=f^R^JM{1>(eVb9UT zg@hEVL?T*>?|G$NoDQ3sCPXupcqW%N1Icn#Ey;CzI_Sr+`f>8<(i4vc{acpf`jf$# z-S1z5Rd<187Fe5!o7q4k3t#A7pY&fDfHzEjg^YXnD~I576T=e|kdgOwicCn=I!X=i zYD^asIs3k(e*+KC!wEg|+w6wVN(`?L4qf3UgNPl#Xp_^cq87DO+ne%VE&#y;NcKSU zH2C1x0M8FKSM<(ppa_uf!9EBnAe9UqZjbWz)em;BvOk~lA0eE!i7$bVL=4R56lOtK z(*tRmSB)fmg~AS`{ihR7@5e6hZ}z~)AcofKLx#IY$P(e4DOpA|a-l>e6|+B>_Fq9d zyAR3kI|#>9hQ=pgCF~Llq!|IswRN#vH_EY!y)5HDgLL-yNcH#4Za6$bq?vYg#zTS@ zXfKKb`LdWS*jC1WIU$3K@Z`7I4Igh9-n^OO5kE7I33j;x@9Nhh4f}NKBKXOBO4sfv z!fMnVMFddVG_5sv7=eR4^OWO`A_AiXcNAe|?T#WYkxsN_>ULFDaT`iBXZ)%+d}zo91c2l?sU-H z;h+=W80)@p-IA-i`k&~(s{gwFyZT@L>iQk$_uE(O>s~y);0~XEz4u)1ukOV8)_He3 zPb|29=5;-=;HH>o%)7gSKA+LW`a8eu_KD2{g{(b?&ptQ%a`z?OI1HEy>F>Usi2hsm8Tc%G?F!IO z+V(4LT*yP{ufU!7?knB5AP+C%voG35UTZ%~#Dh7D(*7pgiI2S29Yx&sv$Jn>|NrT5 zKoQR&w9giNyyq31#YeRcOLlq@W`EH*dup-C<|X(-g5EE zq3K)HedHv9>-bm^AgB`qCwBOHwtC4yhBoz0m%xivwueSXo11Q(IKXv{xeqcBa_UII ziLdtbjQU9DcVOpt+%yhvMA>>3kdJ@(T!ZUc? zf?;Sm@{bo5XZ)o?VjD1y30?pL-{?37gJb*m5`(dW6gj-Vz%G-+i~cdg{#+ zK3~pv&VPO%(r1)c_rCk?_U`gt)s2s@z47t2w~cVOest{~?$$3JJu*6|HX2GO7z}Fp zq?O7i6s49A#^#_%dDL#uk7?016SHNW5xFhBP<34kvjPpMUkBzdO8Ra>c^)JUlOx z=k;RRGW29>?qAlONvL`S0baSx2UKksq5&iVkV?+vilu63?yEr6*<-5+(8~z)@hu6vogSF`czB5X^LO~q-_89zJV^fe zTm0v5AKdlNqm!_x(?$pUn+F4CHl0f)^o(h3jb+a{w^S%wST7N8^``NYI$p2^QT>p^~ z^2d_7=_4D-A3bwd9tpPoxcSI9`J;R8i$_LVfBfLcyT~714<7X|f7i;uD$hWv|E2y9 z^!=hwU-sQ)JC=TNskG$bB}(rndi#1B-OqM!@A_Ea)xfHUCYJxZf3|tW%B5jh90_}x z3XeX)RP=0-7*aNxidY3+GfIhEv0f}$>FsUNOq+4bOt$~AwW6mI5xrz>&n7QyiLR}9 zS^`!ZB&H?RlVz<~u3oo3Y1P+nU%&gnq3MH{9+>85ui7n5YgZin*Bd{xe#`nuHmxTz zFkZ5XrL9&?2bo!0BNhzdB=0T6% zfj6VUppO12T{1Fe-b|sZEmbd9is^Vd6ssw@aslBVXr4X*@FKt;T7b6z9$nQ;B$ZA_ z@&dxIt{qhBRZ**{RY8el5&r(>@_vBdJT~Y#yvuN*jXCrDXU63nHFRKzf!3N*B}>8+;_#qnFAW|e;K7_$i$zoF+>aXjL&<0@l2z-wH96Jd-b}=|Bs{zsW}jLBj{`V{ zBtkV6&&uig+-GkHDz&l_Q9@GIs<&1>(+qfF>dhnk@bCg`3}6*Pxe!XEMI~D+x2BCZdpWRgm{;E15tzO^J>s;yDsA=aze9vi%M=AfZ!ZpfzkLB)BuipL>+C-DQhMTjn|2w$G)Dhbi-0;vQuztk*Tl8c~mXl(CUncQD4P zzdIOX72F++vHI%{#&kwK+p(C80kzI(pgS02b?m|0*7cIeU4``ovuLlP%r{b5&A#Pq z-^kp>x8F|UlVMc6;knmuUxQDBbq6!#>5^7Y>k%_Fx9)Ggroe-1@WD0B4P7h3@=DNC zakpn|XkvJCC%9G#Y?{DAKxbCtiO}4dzbz8jA%q=jnt>HzZE%twlf#>ahbBkm1z3__ zpku2?wehx2+o^6pI|L;UbU#-U08i{4C1H-bW}Qkc8*6Suixb?=I>vSLyk z757Yy3~ip=v@kD;ym9ks7B`Vt(nMQN;YoAWV!EFOqJoUTQODOV4P;5TQgZExw8b7VS`&S zagPIDlY!s1=CSlVH4-ILal@zjIotQ;l3uPeak2=U$z5UUnQ!Zpx*aE{tBVcC>$V^Zr z^yP_|nNQ?rI?H4g`P{GY1ld}oY;74f#PIOYZqMifG6Rs631zi>F=;6feYcW#R-|)} zeq|d$HiF1Tu7@2GknMB}KnG++wEl(`%gZ?m8KnVp(qjq{=jn*-^kxt2nZUFio3<^G z27yj6Pp~P)P%>hee+JvbkDWtosg12;ON@F^w#29lWlLm~l0RijFiH!QvL%pEoE8yf zOAOB`TVj|<*%HH)2cLRjDcVvB2u9bNt)}Y1SVeLq$trCBGi#1s@SAUsol}m)tkQVQ zN`CE;LZgJTm}>nty*i@>9b9A*^s`b%H$!5v<_s`f%-jc_kq-oKZrN)a zX|)@0?(uP>6i$c;dGw)1yqHg!jg*6vHQOc6ZkiqVbiRf345ajoL>D04=Hmt_q%(jN zgM)}51C^zU8!5Fz9fZ0=G@D9fr{7~4#YD*p{5H2FW$6*_Qx9ifr>4pUmW9s>hke{i zW-f~;YKqTlg}4@X=3+Vj%CqYaX5YT3g>@1aFj@4&0%RXINg;(mmp2JgQ#%P*=^dJT>A5AB-1yp+EzIkY z-1SYL7f@fhSTi7W;W4td60uCJkZd@pnZ03d-}CZ8@qypBuudS>i2?^mwZ*CdVKa|m zb2%N) z%1yxNEKjKh!Q*MG07Ud=wep7B@fpOh!&i&vuo7`Hu@vVq}udKyH+@ zY7brW#~TNc`vpLP8~6d&L03k-zAdq;+cIL;Rca0~vs0{GFO3gtpZO(yb=l`_88ZjN%HtAvw)|8`+| z%X<-Xt9Th~_W1$-p3&h^j)Tp?g})5=F`$#>R7@;|3J!i|f3R1qIS3gSW8eIi_jDV4_uAD{_X5(3a$nujgXpGP zzxM1HH;71!AYqL6B7DY5MVZA$W)jOJ9%a(@22RxIL-ocLMYQQ-WH492GN!`fA9G0^9c3#DWNpRW5_r9fo zZYQ9F5Wxv#G*A~2^(ba!tA$d$V+af{?QdMg$pqbYByaod5~OZ$f*W2yCjmOtqI{f> z&s4O|Zq1_Gb2X0tRN8T85vAs?~Q!s$9cqWX&O5bEq5u zbQ-YGEfInaH}zz+uGCY}pc@@!@b<2ke5>v6UBj&*G`5P5a+C7|(!&I)OKTwN7cf-S zC+nVS0#w35ERmkm)ag@>s${RECrJOM%t@#}Hbl)^_ z#=r~#_aiHpt`>xplN;iI+tJwf&T@ysAV;mq`MSd4iP?_FBpkzFs=${kYN_O;X)Lqs zxf{5>y8wyO9Dqi8c(OUv!9qBT0cFgx3^^H7+Gfcz)&A@(cPYUVY7RhW-Hinm9g`zV zPg>Ea&{0f=2Z)6Jv4N@n zuk^q7#PZv?<9)b6LXkj3Un0t7K3fTPjFsKczV-=js`tXcS!nya?bkoStt5f9289F> zwWSf|O|v?aaC{u&*AJ%3u|#J2aE`Q7i{qQe52dq7Yx*+ihMqOKk-Murb#DuM_^7`x z9QJs4uGibsv#`NIRYFA9BIILoAyGKJ?99b(NuT&6H*^7HxowP=4>4+97Go>HH472V ziguhI@ugIVXTSYPZgju1{tLPov)c&b^Tk*lp z84mI7S%gmod>D$?QmP)w=N(N|S$sddgIf>sY2iDo6SZlKh$%^I9t&wMzZbxPMhDp>9IiHup??Vi?JW!#%GVu>_!@8 z#Rx$^UlgUNVg&^_pk&FSWC+kEmKBK?q9I*2RZWz&sA-szCdo5EHR2~dN zHVjb>8b;8tbof)2c{8ekrUW%zS0jpn>TqszAJm$(Fh4=715`Iebi##{P>WW|ar>^X zaHpe|f^IKrbv^YJZff?$bQ9?kMS&LuF{lWjk|MB73_*S0gzj%p;(@J_pliCM=)9mQ zq6H5I0=f;)$n9Oal829oWwyYFnzz$XTz{-^qL{oQ?k*LOCYFWT5QYjyXA!<#+MfjiP6 z&jE9Ja=550rJ_+;OTMH3Sab{s7!QZA$gv-OtF)EGuwSqL7=M3qvKlh|d1PbKI~Cnc)LuQxq~HP|O`6 z{;vcZx>Fy~7)jfG&vT;$*LfNYYY z47xjFtKlJ4V-S-&L7>K{k^R%>xwRxaIR}xQgNH6psW4QWk8*a4nXwW|WQ>(iGFWUS z`)RH&HWQjnl$9{+o_~!ywhXs#s4PkmLa|aVhhXxBxkc`C=shw?YrONHeYp0?15*vyY9Z>7yH&BM-rLVDXBKq z*FjLrkF+?x2tNj)k{AqCEIDXze4CrRn&4-4lHQ}-U5}pq#eES9|9Qxj^O_!ra`y}m z;R0MdCpr!lH8{#8T9t%cE86uBc}K#n^OEOhGufgOTQT$b#<@>H8)u*3l0kbm=Tx)C zC?sWIj6P8YMn)-hpbU%=E@faKm;JP0C<7y6qu5Fr7{inK<=%<4B!JVw_GA%NR7vLQ zm6Uqv-f00!>eGNwb~r_%JnB%0Nlpi+QdT6I$ml8V&g*}d084|S?|$y-$GcPB>8DUg z(bjKsu*?tUlRRv;bp{A>3JH%&CgPd0m1s0v6gX);YwVGN9xde{ zR7=48ZA*u4*?#ZIJG)c9Th}{SR>!OQ_ zPVJNN2}nhY2QC`?|LW;Z1-^HqLj#jgA`NEiw>dcLC)^ovBCAt?W_(GuVih7~t1X-_mN*a&w8!`NrEly`4gBd&hp<*+It1vQ<^$4nu=Q&n z*y>#q10~V&Kn3=9L|#xbMOAT8!8^zp=To#G3bf2}p@6Od@@F99jiuav&!?Anr#xS4 zWz!eqWEa~{Zf!|IgFcmnrVwip~))MP=jE@=Ha9*DJAw+GT0YjS%aozZg2 z12M`*c_2`N{j?TP9*7Ye<$)OH&F}I)gk>W<{cfeF@@zJ!^SplQL3Dmgt8 z)*{HFHX{t-I-WyHMoZ|SLCi|$k9v3Y>>%0z{#C|^K9@H_YNMXHeD9@X6O&;R(>nf%E%#g(_o}Ol z$L>mVcm9+3L>ld^b-2E2e>?6SLFC{gWvnXj#hHQ_74(bG-R&Tk-g$qr#}eN4%c1a* z=KlUs^Q(Xy{9oC=B;l1v++N1X%J!{^_B7y_WK1M-V!3WMN~QK1$VLps64x0v>e!k6 zi-cEP9c=J$#$B!ETc^ES=blRT)|obPV7!dwg={Ga_cmeg@{XKC z^a@l?#;~VUt~K;nCA0hT%bnl|@`kodn0*guf(OU#?|1oE*>9!1tCrmVhu+p?xz1#H zRDisFRl2<lBQIJ^s=Mz z5T1O`Fr1r!;zQsf-gW#%?>z?j=}l$sIrw0rJOahhFmJnQ8*;fl$5wvHB^VrvI2N_RNk zx^k0wn0prcc&yiOciVKv?A^^aov~JTvrT8rjqxpcgxX#;8Z{wP(Cm{pdH0OL>6vO8E_ET~xrBcq8z-O>xGIn*cvcbc2t#gZd zDioJ9iQR_}Ij%)ejIve89w?Qv_Q;35WBb=X_F|P{#|=pR4b8!Bm}P9@FvP0wI16Tl zmICKzbM?CY7a#T>7!pp>=&na}*W17PaGM#xx)2_938l26QAwKi+K+hmuYsVI27Dg^ zzR$klBP~E3+Jzx3!&XXV12zW3e&8eC_o$G2bhyaK<*ZY1@j66&ogKcpjkpeoTeYc* zs8^Ik#LnOBJvbU}R$I^%dmQNJ9OxQAV-lE@)3sd8Fzw~Hcn|NN`SI%=vY5@n@qMiv z2_iWsMWXbmfrK{f`)_Fz3RB5gCR2;K3|x~m?4RG_-7yQEs52F_VXQmF8tRC+-rU52 zh&OEBK@=(M>E#wFl3s=Rut_7N6H-kmM_q~pU7-RT)=F6k`)_acO8nHl9U7SBuA`P! z!`W-|w|VbEXUTd1_pt`nn%&15aFnuDx1%)}!*e@YVkfh-p}0LQnh~YoZchuL>LN9! z94(`(l%rixIK8cLl&57hit@CK5>Sqo5!w8m-alct(ttvZf+;{!ixn$nAz=@E-n$Ds zZH&a$c>W&q$m1AV9>37a=;N1XW35>2Rutf^&5eQ6!W%9bT{#3Da1M?&dL}y^a080x z3c9?q94$h4s9$!$WsVQxgIASfdL~vR?RERf&wDq=VIyG6Fg?&fx;)$j8X)IBzPp}E zWX8|6(ur0qbu&_Nb8|0LI5r#}9UOs1JW{c6EyaLBxq=0#e5#y_C90;af5Cg+rDQh7 z;W{=6+b?|@(Z2?pj6eq1^zqBG>G5;m*0t55;=@SAhwV4M;KjzT0W~iL-GN-n*7RVM zw>RDA4WGIO*%Q1N&ijpD07ldbAU=fHKGfXfC2JZP9&4@1~y%KLY zNApjC=70Ra``~Wa4UadsE(bKDlg&LHG{l6lR+dxh^;9f#`2yYdmr8n)qvwo9;)9!OY?#b8bUZ0z~JC++>%VELBX1nec?~MrCnpO(p z3eNc}Vx6>c+Hd^Sd(kd%PsgC=MRfOAp2C7fwzN>TjyGq$WMLD>(iAEz(j*HtWu~5RP7Jc%$=>pu_aArlym3Xc;m}`Bn)7ko%>ci>%-i);?V<9 zuE~8X-e5eA`%*j^omD7b;=UD+z?6${ABsf_r%a!U9vO?KqDL~?GS%6(ty1A58K!KI z3LhEMqM}E}WT@zov4vS`CnUp^l2bb&qr22j$fzW>6OvI%%i0^^5cVNZODOf3f6cpV z=^>&V5VUHTx0Tb0qM~TE-4}NPx=y`{smiszmRf-b1fiX)G~~w2D*K){yrWAXgS(Bp z6VmhB>?hyw?w^Ge`jZ$reDc^T|0?L2c*0re@&+cGOCcFuxL;`iQWe>hh{nX2m+w6Pa_FhET z`B9woqYwLv*gjyjjM98Y;FRVwlAtu7;T9DF!m|45b6jFrM?2L>}$zM=EAvhZhMcE0Kxu_Hr1>{uC)7ibtA2o%5RV9Q$ zty7e2LW#kfD_ThgYmN4sL%uCYAeo0J9jkaUnn`43=Lpy-U@l+NhJj30#c)9`R-vZ3A2L|17KAX*_ z)asw1?fB_F8DSuQRWV~r#AoWd6zs^9o#&UQw|PxqFd?YJJ(ygi(h1F2m~%#a63#=0 zum&1RWXgp~(m6h8FotG-dZf)as6VUN=B}lU8Ksc!#5fGhU#<1=I1K^8Lt4>fC`C>f zJK|wL>?3P^N(TnR_(Bqj|1+6RaL2-YeXTFlfx%EfD`q5prqhT$#d0=|`nDrX4rs&p z08Z-0gV}UPJnTHTjrz{&z@W*^#OjfvUg+dIEX0QO}yQ*g7LNMK7HRFu! zqwCrtB`{crFlU0vf?9X>Lk#062X6nvy0%CGM#1``nNB4v5#E^5Hf?sEwPU_*NEu77@V?Rc*OM*A{?ds?dh&j zgK?_Ei$^KDc~ikUW5`sno|^?=%4Vrxov}_TWMS-(3RxJ7qe2$Pwl<_3*n4e-EeIRpoi zxI3Y#cQ<$EuZDkrx4n6XFLy4$+=Gz!H2vKJVdx@Hdp3t@5(p+Noh8Z@tu&*j-?|gl z0(D`af&ALf@9^y*YiVPubm3YiJySA`C0E(veOr7r;;TO^lxT6u&Fx4%YB3yu|$KdE2R?C5+cYim1xBc8%z7(P8Gq|?T zTo3pEfxFY5DUGHG%I+F&)j}{=Ov-xkTTp+6niAk4zxGFV`gXeT%)&K)z_Zx{(K#h0 zf(I-6p{C~ zhl%=p8VUQf(>`a_*KO&nQo1dj)jYSQ<0z#~Zc8VUouzfeZRxDw-IfmbT^32xZRt2l z$;fT#<^toV+MIV=y2)^J{-AGDKWU$vkkD(m|AtD5Xgm>k zwfHF1hBT~sS*FYvSOz_{I{ zOG>IyFfDuPh;Pa#ed1pGq9eZ1m6_(@7Vu|r+Rrw991V;;i;9>GCF+rw{kbE)Gx%@+ z&mZ2q_tM0kb{#>PzWBhU=U#aJp7So7hMbG%?s(dJbK659(tA6%QrTrw%w|?fF`HQm z#cMK3(Mqn>a03r|;6MD|iRC%p0|UDV^Px_oVZansrMP02o$4xkdXF3-C5QZ>l>T{K z%NOQv^!<7O<-%Z@zs+~`2pO`V;BTTIH&Qy>rmd&!+~<7b7e*yriI~9%FDs@b8APki zkSazr$>61^sq>N|D{vpnltm>%j^u?jD`=^@r5JFP$qd3Zsw1){NufySYJ1&%zSXnW zaCiRnGe3U=UGy7B;G50OaH)Ssvu`pCwFjq6i4@_EU^FT)MXmlGIQVd|U=@qwt^2am zhpYk|5r!L9eALp+s3NP1YD9Q)g;0T4I!6rAGQ}vopo|O|A-L!$siV3P5ki_05fr#X zrmKo95gOq{g&Z|R`V^!I z%7QM6mTC%7ZMk5;RW~5Yfb+S0RPQj3_}D)o%icJ)7H;6lK-saW#iXIrvFiLx$>b|n z!4Zr-DZM(rI|h0&zVV_x2RBKQs=zI8!Q`VN^g?;KNM}Strf9)EKRE(Z!V7<*A!K4i zh{A0LHCcirawBR8CDnF-jCG_w?SHraNGuoqZEJK25A9> z{WOX5kNdvYhxP&IicEBAGqGY3cG0~3Z%_EnosH;l5fIK5L?l^)_aX?26g31efe>7; zg#R;TO^FI2Nfu?`o1yUm+~nRQ1`3W7U%0Nn)eM6$&BJv#7ZHGMx19@FkfWIB2qUp# zv=I?9@lq*Q+`esFJey4xw-%u}V&C(mZ_lg|G_??HqL7>DK`j!3_wz?IT@9&PRM%8l ziAa*AYDN@pPlBukaT)H6Z+gCkMBppk^^?tuL270mh~O3zflewZ#+5`;)f^(yaK(RX z223(#C%)&qbg!anmMKMa9^Tn2MP;w-r6wSo3_Vixp>HlND=ftuXe3$i-jM4%J zY+{ihTdK{Z#DtUatB{v0y6O(^OF4X{o&`5li^!#D1$kbF8*ZwL*%9Kj7v6h` z=&_(sQ?juTyeqLSSQQdIx=C)I^()_{SLQQDTHy21LcVp)7C*(Hzuw#kx4yzVt;Oge z@-{IIv1ZC}O*iZ0>ZiB}{~v&Us8NCfMKqFC3=`>Yqqh(K+P4bXK@ei*(vYQ@QeJU- zZ^(`yDXKZ|VzU48*T9ZqBOWWlN$6bFLWHliew$a5R~2a2j0PsB_PE$VwhpXeXqhOm zJ?iBAr$7&HemUQFVc=!^BQN>1t4b*~SB*&7Vy?xP_B_WXeEq8t z{lv{({;&8|aN55`M*jAL8{w8s_>$?-rg+Ql!<=Wbcj0|&7KDXn>lkzuZvDlg(G%}q z>i;WzJBS6nJ`Ja!caqqg(${DC(vvq zRZ=mf#w#I;@eusYnwTl^aZM|xHH%_A!y=0D3_mEwGs`6oH?XYg83^}(u>Xyb&#GDf`|n{xb%vmXNWGq!_d0Xeu3=$wfo*jQy`$ z{O5+J?)?V$%vo=I^&2RIZ#6eS*KWh)zHOU*n>=2al1$RsK;f+g5OWbn5UQ3{c)wQ8 zwx<2#gH?Dvegwj;gd}K%d@#<}RW)k=&9pxhKC}J}?unakPdNBD`2hdu`8y|k)9-*E z9ZgXYW;Dx??18QRwL`Uxo=eEdG9TB2Qp`xDAc>b#d3)DZzqTCGZTA%4v_H7D%?$yl zl?b)LNGUxdhNAYDwgNX2dNpn);ll+{c!Q@^ODLI4xnciitAFQmXlJ_F@l89t&4mkn zR75O7+>x~G>^A=yt3s9)tMEo}CRgTbYOWrK>V$pIHvj&CaMO+H|8+t!Rzz-*8O!061-q(M$VF3DtW56i z_Mch*mi@7d{1fCIMNeGYwLAnQG{o&Y-F+dL4^$Sxe<81;5DuOLXF z87U4_WZ^cy341eaaNn|TKHy(NkbLK`_nnY{|J2cOLpl|#)Dtx~639K=NVq?T1Z&)x zLab3vt4TK!D9E&s_<@pdJC^}jiMpL0D=D|rgM4L?1Skz9qZDr`4F#_4r`blSD8mm* zMad|&(D|c&9g_t}n1ecc+G3&tU)gH#b|vjs{Px506ZRL={*fK9`lrx^J=HwTKN#L{ zO9ofEuRyLC=Mp9+_;~76#e(C#9*x$>vNNNK$JzgM5en*lB zD%{+xhM1`*GrB#M^>4lc?$JEqdGSq$B={5lbCL8UlJsP=iv#Q$V&~vWfzRio77RhT zjzM`Yt{630&f0&UgI`a))0ebbLP! zi6pFLFO7r92!j(o<^dimw6u2aYZEHyhg(lYM(!E1t6Fn8gb1}5a6aAg=-%r z!Q$=1U-qvjPd|5HIXu0CdK%hU2F5HVA53ayLbC7pvVRjkeck|rn>>wsdeE53D)D^7 zeyxrB+1>r{^caO3+SCU2qWL%}G8#2|`y>7lg8SJ$eem=o^EBp3RxYJV`G#y?|A>Ed z13dlA-evIg2I^_(h8dXAM$MdA6C(CkAMuaF=MVK~E?No?D~li2@qCCVM3aeFsUm5j zz5H$e>JUmqFGs8JIXD!_&(2>0;M*v0XrAeH63Li3D=OBqG5d-~{gXugKidOW^B1jy zJY6GCXR5(?C|Zw7@CwFr;5z_W1KuZ9h%!_D^nxBhB2rUA0YtA7B#me+ov*+v#Ua!_ z$7*Du2xyI8XeEjz`%jPhR}yCa_}%crroqjW7C|vs$6{B*v=lYNM{W5l{_)n+@ZP0G zPeaK~FC)qI*o+!1>Gt*Qr+;)Yyiy5N#;xGwX)Mm<@&&%g$3pfuzS1&>AMJs6CV`;b z#zUSi65Md+F00m3JiHAocmddel`-HYiNN<3Lz3{GGnBx;Y=?Ix4GvOBAmh@plB`7} z*!mLi8Zlspo=Gmiz6*hW2rn-rymV7QfU(+}hj(fOtJ#|USC9E82``@B4KF_e@wuNS z?g|Tx@jAT0E?LRiKYz@RpZ0rt{&D{t9x!12hr3rFVf=-=SC6BVe{i?zBaHdGTlHjg zmNss8uRg*Uv%6Q1qm=QwTlEpfSlz999BmorY}-2Bz52*vYc$-wdNN9zpZj2Kgt0Dn z0}n?jYnpX;@FI+zxKr^6V+B-H%4j_ml`>l1-nLo?3?htLQ~MU9uhhQ9C?vISF$zdh z@$RKlo&mLgw(pq}{ZIMDrANswRtF!5!9_F~6zXcIa?W9=KL)2}uvPs+>$hFEj4*UL z0}LoL4KXt7&CAN7E6WG?fahf z;}=5zm;KDMZSI$0-56F!QX*{C71=!@gn>{?fC2*$QNa?uNaL)%JBP6ykANh6WBQAv6!JUD{3)Jm5p7U#DzJImV=iBo^$=<+3FDvGQ zd9{&n&j;Zp9N>WY$ghKz@w3hVJKw|4`_Ciu{k)U0GR8ys21eB>-@vFK71EMXN;4?e z0Ak!v%aQU8M9LJ`Dc`{GX#O?-)>eB9HbkQpG31kKrYKAHo|FDC+2ww<58?~&iVY*a z&U;_U=W-2<53=x9q-snF*`Gb>pTMX0Lp0%CG3|aDI_w5oYrHPSOYk0A`?n|kXOO2~ zJQtz~?~2J0c)NL<`w+7S4U7-6siayEjDj70%fF5o)r-3zn((d|8|k5+Mi((tG%NAC zu66%Pkk7HIi9!jgL3+6u(+!KfofG%>U!DsQ#wq5*;6RXOqV=RC00!w&2$8HLyzUOJ zqaqIbnYaACgydfyfcV0@LUAoH0*C$#3=yKaQklHO)b4w`6$StD2m}<~74KR6G=>O5 zqY7^W&ZzdaZ~HaGZ9+id9XLGd8*|u!xG{~4L^CP9lA5s}d)vR0DC2)PpV5KW`7RQk zdy`{@<=*6w-IQ?Lip&bbt;l3_)}XlW)?@^xI7?|V!!$~f$tcAnN|DKEiz(f0uFNkF zJdMXxEGYUK7;eLz-+V4zR_qto1@fz(z5c7e=Kl6SzEl4-YQjscUpqGzz?Tvt4>3|G zmh&?iEnTv29}8SViVZD*U(b&Ro){2e<78)W)5ap24+bu50YVUNU?NctCA9{8?x*|3 z_Lq#ag?J?%&GLLhDMLFs4M)l=8GAMyK)3ob`Izd7N5X+^%dwlm;F&)dScWBa1=^-T ztj&mgGzHccwI`B+tAenM_P4$d!*?x4rluew@9h+s6o`X@xH%-na;X?hi1z1_fek!B zd5}+@W*2;1i{osV56| zFdaApC_RdY2#)@-*$rQv7@p9Eb@z-=#sL!rFwH6iYlVix=d`r_3V;!AqOu-+sW}aWl@WMfbn}3V zO8nS6=CTQ;P*L)tVb`*O8$ffNZ`nJC-B{&t$I7e{xV;F%qA`5Oa0w{;Da2e$RUc%pxK*WdKd{@)&e z3heba`?&w!870>drP=lwB|oD1zd2Pz4i)(j%YWzm&#Hw3Knv%8PW;1I*M;NDuJjDN zFwhuyY~ad)kDpkP^X(h3uRhSVboP|}yTF8RAJ_JF@l$r-{Of(^J5Rhr3(Ys~+v_}X z%D$L)%(r)KZ+n_kb-PZyeW2^51B0jZxAS*)9T+DLR)x?PyfYa7{|6sqySX}xwTgGyul%qJYx^pB7Pn#Nduk`f{-x`r z_bkA;3Gr_FfG@B*JS~oma-+w^_wQ_O9`TI97rZ)$?NkW9>5s0RxZ)>W-|-UC+9>QT z&vdmuDMy~gaL4%s+lgzR>H2FVZ6i*%asGv_OS^Cx5M5g>+}d>3jc}F|9R7;@b;9Q=ICg1)6G+dIM0~-paKMG^pSxR4|R8s`pM+D zGwOf1X%b$0vg2w1pZv%^o%2k%!NIdrM+#1KFYA64rO<|+U*3HTUkbw>dpKc_bRR*k Iw3qq60WkUoe*gdg diff --git a/src/app/api/aos/belege/upload/route.ts b/src/app/api/aos/belege/upload/route.ts new file mode 100644 index 0000000..cdc7fe3 --- /dev/null +++ b/src/app/api/aos/belege/upload/route.ts @@ -0,0 +1,103 @@ +import { NextResponse } from 'next/server' +import { db } from '@/lib/db' +import { storeFile, detectContentType } from '@/lib/storage' +import { z } from 'zod' +import { checkRateLimit } from '@/lib/rate-limit' + +export const dynamic = 'force-dynamic' +export const maxDuration = 30 + +// POST /api/aos/belege/upload +// Upload a receipt file with content-addressed storage (PRD §5.5 + §5.7 Belege). +// Accepts multipart/form-data with file + metadata. +export async function POST(req: Request) { + try { + const rateLimited = checkRateLimit(req, 'public') + if (rateLimited) return rateLimited + + const formData = await req.formData() + const file = formData.get('file') as File | null + const type = formData.get('type') as string | null + const vendor = formData.get('vendor') as string | null + const invoiceNumber = formData.get('invoiceNumber') as string | null + const amountCentsStr = formData.get('amountCents') as string | null + const bookingDateStr = formData.get('bookingDate') as string | null + + if (!file) { + return NextResponse.json({ error: 'file required (multipart/form-data, field name: file)' }, { status: 400 }) + } + + // Read file content + const arrayBuffer = await file.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + + // Detect content type from magic bytes + const detectedType = detectContentType(buffer) + const fileName = file.name + const fileType = file.type || detectedType + const fileSizeBytes = buffer.length + + // Store file (content-addressed, dedup, atomic write) + const stored = await storeFile(buffer, fileType) + + // Check for duplicate (same hash = same file already registered as Beleg) + const existing = await db.beleg.findUnique({ where: { fileHash: stored.hash } }) + if (existing) { + return NextResponse.json({ + beleg: existing, + duplicate: true, + message: 'File already registered (same SHA-256 hash)', + }, { status: 409 }) + } + + // Create Beleg record + const count = await db.beleg.count() + const number = `BEL-2026-${String(count + 1).padStart(4, '0')}` + const belegType = type || 'sonstiges' + + // GoBD retention: 8 years for invoices, 6 for business letters + const retentionUntil = new Date() + retentionUntil.setFullYear(retentionUntil.getFullYear() + (belegType.includes('rechnung') ? 8 : 6)) + + const beleg = await db.beleg.create({ + data: { + number, + type: belegType, + fileHash: stored.hash, + fileName, + fileType, + fileSizeBytes, + vendor: vendor ?? null, + invoiceNumber: invoiceNumber ?? null, + amountCents: amountCentsStr ? parseInt(amountCentsStr) : null, + bookingDate: bookingDateStr ? new Date(bookingDateStr) : null, + retentionUntil, + }, + }) + + await db.auditLog.create({ + data: { + actor: 'nora@nordlicht.digital', + action: 'beleg.upload', + entity: 'Beleg', + entityId: beleg.id, + after: JSON.stringify({ number, type: belegType, fileName, hash: stored.hash.slice(0, 16) + '…', sizeBytes: fileSizeBytes }), + hash: 'sha256:' + Math.random().toString(36).slice(2), + }, + }) + + return NextResponse.json({ + beleg, + storage: { + hash: stored.hash, + sizeBytes: stored.size, + contentType: stored.contentType, + detectedType, + deduped: stored.size !== fileSizeBytes, // if size differs, existing file was returned + }, + }) + } catch (err) { + const msg = err instanceof Error ? err.message : 'internal server error' + return NextResponse.json({ error: msg }, { status: 500 }) + } +} diff --git a/src/app/api/aos/crm/mail-sync/route.ts b/src/app/api/aos/crm/mail-sync/route.ts old mode 100644 new mode 100755 diff --git a/src/app/api/aos/invoices/[id]/pdf/route.ts b/src/app/api/aos/invoices/[id]/pdf/route.ts new file mode 100644 index 0000000..81a1af0 --- /dev/null +++ b/src/app/api/aos/invoices/[id]/pdf/route.ts @@ -0,0 +1,165 @@ +import { NextResponse } from 'next/server' +import { db } from '@/lib/db' + +export const dynamic = 'force-dynamic' +export const maxDuration = 15 + +// GET /api/aos/invoices/[id]/pdf +// Generates a real PDF invoice (German layout) using HTML → print-ready format. +// Returns the invoice as an HTML page that browsers can "Print to PDF". +// In production, this would use Typst (PRD §5.2) for server-side PDF generation. +export async function GET( + _req: Request, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id } = await params + + const invoice = await db.invoice.findUnique({ + where: { id }, + include: { company: true, lines: true }, + }) + + if (!invoice) { + return NextResponse.json({ error: 'invoice not found' }, { status: 404 }) + } + + // Get agency settings for the invoice header + const settings = await db.setting.findMany() + const getSetting = (key: string) => settings.find((s) => s.key === key)?.value ?? '' + + const agencyName = getSetting('company.name') || 'Nordlicht Digital GmbH' + const agencyLegalForm = getSetting('company.legalForm') || 'GmbH' + const agencyVatId = getSetting('company.vatId') || '' + const agencyRegisterCourt = getSetting('company.registerCourt') || '' + const agencyRegisterNumber = getSetting('company.registerNumber') || '' + const agencyCity = getSetting('company.city') || '' + const agencyBank = getSetting('company.bank') || '' + const agencyBic = getSetting('company.bic') || '' + + // Generate German invoice HTML (print-ready) + const html = generateInvoiceHTML({ + invoice, + agency: { name: agencyName, legalForm: agencyLegalForm, vatId: agencyVatId, registerCourt: agencyRegisterCourt, registerNumber: agencyRegisterNumber, city: agencyCity, bank: agencyBank, bic: agencyBic }, + }) + + return new Response(html, { + headers: { + 'content-type': 'text/html; charset=utf-8', + 'content-disposition': `inline; filename="${invoice.number}.html"`, + 'cache-control': 'no-store', + }, + }) + } catch (err) { + const msg = err instanceof Error ? err.message : 'internal server error' + return NextResponse.json({ error: msg }, { status: 500 }) + } +} + +function generateInvoiceHTML({ invoice, agency }: { + invoice: { + number: string; status: string; type: string; issueDate: Date; dueDate: Date; + netCents: number; taxBp: number; grossCents: number; + zugferd: boolean; xrechnung: boolean; + company: { name: string; legalForm: string | null; city: string | null; vatId: string | null }; + lines: { description: string; quantity: number; unitCents: number; totalCents: number }[]; + }; + agency: { name: string; legalForm: string; vatId: string; registerCourt: string; registerNumber: string; city: string; bank: string; bic: string }; +}): string { + const fmtDate = (d: Date) => d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) + const fmtEuro = (cents: number) => (cents / 100).toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €' + const taxPct = (invoice.taxBp / 100).toFixed(0) + + return ` + + + + +Rechnung ${invoice.number} + + + +
+
+

${agency.name}

+

${agency.city}

+

USt-IdNr.: ${agency.vatId}

+

${agency.registerCourt} ${agency.registerNumber}

+
+
+

RECHNUNG

+

Nr.: ${invoice.number}

+

Datum: ${fmtDate(invoice.issueDate)}

+

Fällig: ${fmtDate(invoice.dueDate)}

+ ${invoice.zugferd ? '

✓ ZUGFeRD

' : ''} + ${invoice.xrechnung ? '

✓ XRechnung

' : ''} +
+
+ +
+

Rechnungsempfänger

+

${invoice.company.name}

+ ${invoice.company.legalForm ? `

${invoice.company.legalForm}

` : ''} + ${invoice.company.city ? `

${invoice.company.city}

` : ''} + ${invoice.company.vatId ? `

USt-IdNr.: ${invoice.company.vatId}

` : ''} +
+ + + + + + + + + + + + ${invoice.lines.map((l) => ` + + + + + + `).join('')} + +
BeschreibungMengeEinzelpreisGesamt
${l.description}${l.quantity}${fmtEuro(l.unitCents)}${fmtEuro(l.totalCents)}
+ + + + + +
Netto${fmtEuro(invoice.netCents)}
USt (${taxPct}%)${fmtEuro(invoice.grossCents - invoice.netCents)}
Gesamt${fmtEuro(invoice.grossCents)}
+ + + +` +} diff --git a/src/app/api/aos/leads/discover/route.ts b/src/app/api/aos/leads/discover/route.ts old mode 100644 new mode 100755 diff --git a/src/app/api/aos/licenses/validate/route.ts b/src/app/api/aos/licenses/validate/route.ts old mode 100644 new mode 100755 diff --git a/src/lib/storage.ts b/src/lib/storage.ts old mode 100644 new mode 100755