@nuxt-customer-portal/invoices 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +4 -0
  4. package/app/assets/invoices.css +332 -0
  5. package/app/components/InvoicesAdminEmptyState.vue +28 -0
  6. package/app/components/InvoicesAdminInvoices.vue +950 -0
  7. package/app/components/InvoicesAdminListToolbar.vue +19 -0
  8. package/app/components/InvoicesAdminPaginatedList.vue +95 -0
  9. package/app/components/InvoicesClientAccessPanel.vue +63 -0
  10. package/app/components/InvoicesClientSettingsPanel.vue +150 -0
  11. package/app/components/InvoicesDashboardCard.vue +29 -0
  12. package/app/components/InvoicesDashboardReceivedInvoices.vue +1 -0
  13. package/app/components/InvoicesDashboardSalesInvoices.vue +1 -0
  14. package/app/components/InvoicesInvoiceDetail.vue +1109 -0
  15. package/app/components/InvoicesInvoiceEmailModal.vue +223 -0
  16. package/app/components/InvoicesPageShell.vue +7 -0
  17. package/app/components/InvoicesSenderDetailsForm.vue +229 -0
  18. package/app/composables/useInvoiceSources.ts +9 -0
  19. package/app/composables/useInvoices.ts +68 -0
  20. package/app/composables/useInvoicesAdminList.ts +113 -0
  21. package/app/pages/admin/invoices/[id].vue +36 -0
  22. package/app/pages/admin/invoices/index.vue +61 -0
  23. package/app/pages/admin/invoices/new.vue +18 -0
  24. package/app/pages/admin/invoices/settings.vue +86 -0
  25. package/app/pages/invoices/[id].vue +37 -0
  26. package/app/pages/invoices/index.vue +131 -0
  27. package/app/pages/invoices/viewers.vue +115 -0
  28. package/app/plugins/invoices-feature.ts +60 -0
  29. package/i18n/locales/en.json +306 -0
  30. package/i18n/locales/nl.json +306 -0
  31. package/migrations/0000_baseline.sql +235 -0
  32. package/migrations/meta/0000_snapshot.json +1318 -0
  33. package/migrations/meta/_journal.json +13 -0
  34. package/nuxt.config.ts +15 -0
  35. package/package.json +60 -0
  36. package/portal.manifest.mjs +9 -0
  37. package/server/api/invoices/admin/bootstrap.get.ts +17 -0
  38. package/server/api/invoices/admin/clients/[clientId]/access.get.ts +11 -0
  39. package/server/api/invoices/admin/clients/[clientId]/contacts/[id].delete.ts +8 -0
  40. package/server/api/invoices/admin/clients/[clientId]/contacts/[id].patch.ts +13 -0
  41. package/server/api/invoices/admin/clients/[clientId]/contacts/index.get.ts +8 -0
  42. package/server/api/invoices/admin/clients/[clientId]/contacts/index.post.ts +12 -0
  43. package/server/api/invoices/admin/email-domain.get.ts +17 -0
  44. package/server/api/invoices/admin/invoices/[id]/attachments/[attachmentId].delete.ts +23 -0
  45. package/server/api/invoices/admin/invoices/[id]/attachments/[attachmentId].get.ts +30 -0
  46. package/server/api/invoices/admin/invoices/[id]/attachments.post.ts +27 -0
  47. package/server/api/invoices/admin/invoices/[id]/email-preview.get.ts +22 -0
  48. package/server/api/invoices/admin/invoices/[id]/email-status.post.ts +18 -0
  49. package/server/api/invoices/admin/invoices/[id]/email.post.ts +24 -0
  50. package/server/api/invoices/admin/invoices/[id]/index.get.ts +17 -0
  51. package/server/api/invoices/admin/invoices/[id]/issue.post.ts +24 -0
  52. package/server/api/invoices/admin/invoices/[id]/payments.post.ts +24 -0
  53. package/server/api/invoices/admin/invoices/[id]/pdf.get.ts +29 -0
  54. package/server/api/invoices/admin/invoices/[id]/reminder-preview.get.ts +24 -0
  55. package/server/api/invoices/admin/invoices/[id]/reminder.post.ts +25 -0
  56. package/server/api/invoices/admin/invoices/[id].patch.ts +24 -0
  57. package/server/api/invoices/admin/invoices/index.get.ts +20 -0
  58. package/server/api/invoices/admin/invoices/index.post.ts +19 -0
  59. package/server/api/invoices/admin/invoices/next-number.get.ts +16 -0
  60. package/server/api/invoices/admin/settings.put.ts +9 -0
  61. package/server/api/invoices/capabilities.get.ts +25 -0
  62. package/server/api/invoices/client/[accessId]/viewers.get.ts +10 -0
  63. package/server/api/invoices/client/[accessId]/viewers.put.ts +17 -0
  64. package/server/api/invoices/client/invoices/[id]/attachments/[attachmentId].get.ts +20 -0
  65. package/server/api/invoices/client/invoices/[id]/index.get.ts +12 -0
  66. package/server/api/invoices/client/invoices/[id]/pdf.get.ts +23 -0
  67. package/server/api/invoices/client/invoices/index.get.ts +14 -0
  68. package/server/api/invoices/client/suppliers.get.ts +8 -0
  69. package/server/db/schema/invoices.ts +251 -0
  70. package/server/plugins/openapi-contracts.ts +55 -0
  71. package/server/utils/invoice-email-status.ts +64 -0
  72. package/server/utils/invoice-email.ts +240 -0
  73. package/server/utils/invoice-listing.ts +71 -0
  74. package/server/utils/invoice-pdf.ts +358 -0
  75. package/server/utils/invoice-repository.ts +821 -0
  76. package/server/utils/invoice-validation.ts +112 -0
  77. package/shared/email-delivery-status.ts +23 -0
  78. package/shared/feature.ts +148 -0
  79. package/shared/invoice-introduction.ts +21 -0
  80. package/shared/invoice-number.ts +24 -0
  81. package/shared/types/invoice.ts +251 -0
  82. package/tokens.css +25 -0
@@ -0,0 +1,235 @@
1
+ CREATE SCHEMA "invoices";
2
+ --> statement-breakpoint
3
+ CREATE TYPE "invoices"."invoice_email_delivery_status" AS ENUM('PENDING', 'SENT', 'FAILED');--> statement-breakpoint
4
+ CREATE TYPE "invoices"."invoice_email_purpose" AS ENUM('INVOICE', 'REMINDER');--> statement-breakpoint
5
+ CREATE TYPE "invoices"."invoice_status" AS ENUM('DRAFT', 'ISSUED', 'PAID', 'VOID');--> statement-breakpoint
6
+ CREATE TABLE "invoices"."billing_contact" (
7
+ "id" text PRIMARY KEY NOT NULL,
8
+ "organization_id" text NOT NULL,
9
+ "user_id" text,
10
+ "name" text NOT NULL,
11
+ "email" text NOT NULL,
12
+ "phone" text,
13
+ "job_title" text,
14
+ "created_at" timestamp DEFAULT now() NOT NULL,
15
+ "updated_at" timestamp DEFAULT now() NOT NULL
16
+ );
17
+ --> statement-breakpoint
18
+ CREATE TABLE "invoices"."invoice" (
19
+ "id" text PRIMARY KEY NOT NULL,
20
+ "organization_id" text NOT NULL,
21
+ "client_organization_id" text,
22
+ "number" text NOT NULL,
23
+ "status" "invoices"."invoice_status" DEFAULT 'DRAFT' NOT NULL,
24
+ "currency" text NOT NULL,
25
+ "issue_date" date NOT NULL,
26
+ "due_date" date NOT NULL,
27
+ "subject" text,
28
+ "notes" text,
29
+ "sender_name" text NOT NULL,
30
+ "sender_logo" text,
31
+ "sender_address" text NOT NULL,
32
+ "sender_registration" text,
33
+ "sender_vat_number" text,
34
+ "sender_iban" text,
35
+ "sender_bic" text,
36
+ "recipient_name" text NOT NULL,
37
+ "recipient_address" text NOT NULL,
38
+ "recipient_contact_name" text,
39
+ "recipient_email" text,
40
+ "recipient_locale" text DEFAULT 'nl' NOT NULL,
41
+ "created_by_id" text NOT NULL,
42
+ "issued_at" timestamp,
43
+ "created_at" timestamp DEFAULT now() NOT NULL,
44
+ "updated_at" timestamp DEFAULT now() NOT NULL
45
+ );
46
+ --> statement-breakpoint
47
+ CREATE TABLE "invoices"."invoice_attachment" (
48
+ "id" text PRIMARY KEY NOT NULL,
49
+ "invoice_id" text NOT NULL,
50
+ "file_name" text NOT NULL,
51
+ "content_type" text NOT NULL,
52
+ "size" integer NOT NULL,
53
+ "content_base64" text NOT NULL,
54
+ "uploaded_by_id" text NOT NULL,
55
+ "created_at" timestamp DEFAULT now() NOT NULL
56
+ );
57
+ --> statement-breakpoint
58
+ CREATE TABLE "invoices"."client_access" (
59
+ "id" text PRIMARY KEY NOT NULL,
60
+ "provider_organization_id" text NOT NULL,
61
+ "client_organization_id" text NOT NULL,
62
+ "enabled" boolean DEFAULT true NOT NULL,
63
+ "created_at" timestamp DEFAULT now() NOT NULL,
64
+ "updated_at" timestamp DEFAULT now() NOT NULL
65
+ );
66
+ --> statement-breakpoint
67
+ CREATE TABLE "invoices"."client_viewer" (
68
+ "id" text PRIMARY KEY NOT NULL,
69
+ "access_id" text NOT NULL,
70
+ "user_id" text NOT NULL,
71
+ "created_by_id" text NOT NULL,
72
+ "created_at" timestamp DEFAULT now() NOT NULL
73
+ );
74
+ --> statement-breakpoint
75
+ CREATE TABLE "invoices"."invoice_email_delivery" (
76
+ "id" text PRIMARY KEY NOT NULL,
77
+ "invoice_id" text NOT NULL,
78
+ "status" "invoices"."invoice_email_delivery_status" DEFAULT 'PENDING' NOT NULL,
79
+ "purpose" "invoices"."invoice_email_purpose" DEFAULT 'INVOICE' NOT NULL,
80
+ "recipient_email" text NOT NULL,
81
+ "cc_emails" text DEFAULT '[]' NOT NULL,
82
+ "locale" text NOT NULL,
83
+ "subject" text NOT NULL,
84
+ "body" text NOT NULL,
85
+ "actor_user_id" text NOT NULL,
86
+ "provider_message_id" text,
87
+ "provider_last_event" text,
88
+ "provider_status_checked_at" timestamp,
89
+ "error_message" text,
90
+ "payload_hash" text NOT NULL,
91
+ "created_at" timestamp DEFAULT now() NOT NULL,
92
+ "updated_at" timestamp DEFAULT now() NOT NULL,
93
+ "sent_at" timestamp
94
+ );
95
+ --> statement-breakpoint
96
+ CREATE TABLE "invoices"."invoice_history" (
97
+ "id" text PRIMARY KEY NOT NULL,
98
+ "invoice_id" text NOT NULL,
99
+ "action" text NOT NULL,
100
+ "actor_user_id" text NOT NULL,
101
+ "amount_minor" integer,
102
+ "attachment_name" text,
103
+ "created_at" timestamp DEFAULT now() NOT NULL
104
+ );
105
+ --> statement-breakpoint
106
+ CREATE TABLE "invoices"."invoice_line" (
107
+ "id" text PRIMARY KEY NOT NULL,
108
+ "invoice_id" text NOT NULL,
109
+ "position" integer NOT NULL,
110
+ "description" text NOT NULL,
111
+ "quantity_milli" integer NOT NULL,
112
+ "unit" text DEFAULT 'item' NOT NULL,
113
+ "unit_price_minor" integer NOT NULL,
114
+ "vat_rate_basis_points" integer DEFAULT 2100 NOT NULL
115
+ );
116
+ --> statement-breakpoint
117
+ CREATE TABLE "invoices"."invoice_payment" (
118
+ "id" text PRIMARY KEY NOT NULL,
119
+ "invoice_id" text NOT NULL,
120
+ "paid_on" date NOT NULL,
121
+ "amount_minor" integer NOT NULL,
122
+ "reference" text,
123
+ "note" text,
124
+ "created_by_id" text NOT NULL,
125
+ "created_at" timestamp DEFAULT now() NOT NULL
126
+ );
127
+ --> statement-breakpoint
128
+ CREATE TABLE "invoices"."settings" (
129
+ "organization_id" text PRIMARY KEY NOT NULL,
130
+ "enabled" boolean DEFAULT false NOT NULL,
131
+ "currency" text DEFAULT 'EUR' NOT NULL,
132
+ "default_vat_rate_basis_points" integer DEFAULT 2100 NOT NULL,
133
+ "address" text DEFAULT '' NOT NULL,
134
+ "registration_number" text,
135
+ "vat_number" text,
136
+ "iban" text,
137
+ "bic" text,
138
+ "invoice_email" text,
139
+ "invoice_email_template" text,
140
+ "preferred_locale" text DEFAULT 'nl' NOT NULL,
141
+ "created_at" timestamp DEFAULT now() NOT NULL,
142
+ "updated_at" timestamp DEFAULT now() NOT NULL
143
+ );
144
+ --> statement-breakpoint
145
+ ALTER TABLE "invoices"."billing_contact" ADD CONSTRAINT "billing_contact_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
146
+ ALTER TABLE "invoices"."billing_contact" ADD CONSTRAINT "billing_contact_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
147
+ ALTER TABLE "invoices"."invoice" ADD CONSTRAINT "invoice_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
148
+ ALTER TABLE "invoices"."invoice" ADD CONSTRAINT "invoice_client_organization_id_organization_id_fk" FOREIGN KEY ("client_organization_id") REFERENCES "public"."organization"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
149
+ ALTER TABLE "invoices"."invoice" ADD CONSTRAINT "invoice_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
150
+ ALTER TABLE "invoices"."invoice_attachment" ADD CONSTRAINT "invoice_attachment_invoice_id_invoice_id_fk" FOREIGN KEY ("invoice_id") REFERENCES "invoices"."invoice"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
151
+ ALTER TABLE "invoices"."invoice_attachment" ADD CONSTRAINT "invoice_attachment_uploaded_by_id_user_id_fk" FOREIGN KEY ("uploaded_by_id") REFERENCES "public"."user"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
152
+ ALTER TABLE "invoices"."client_access" ADD CONSTRAINT "client_access_provider_organization_id_organization_id_fk" FOREIGN KEY ("provider_organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
153
+ ALTER TABLE "invoices"."client_access" ADD CONSTRAINT "client_access_client_organization_id_organization_id_fk" FOREIGN KEY ("client_organization_id") REFERENCES "public"."organization"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
154
+ ALTER TABLE "invoices"."client_viewer" ADD CONSTRAINT "client_viewer_access_id_client_access_id_fk" FOREIGN KEY ("access_id") REFERENCES "invoices"."client_access"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
155
+ ALTER TABLE "invoices"."client_viewer" ADD CONSTRAINT "client_viewer_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
156
+ ALTER TABLE "invoices"."client_viewer" ADD CONSTRAINT "client_viewer_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
157
+ ALTER TABLE "invoices"."invoice_email_delivery" ADD CONSTRAINT "invoice_email_delivery_invoice_id_invoice_id_fk" FOREIGN KEY ("invoice_id") REFERENCES "invoices"."invoice"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
158
+ ALTER TABLE "invoices"."invoice_email_delivery" ADD CONSTRAINT "invoice_email_delivery_actor_user_id_user_id_fk" FOREIGN KEY ("actor_user_id") REFERENCES "public"."user"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
159
+ ALTER TABLE "invoices"."invoice_history" ADD CONSTRAINT "invoice_history_invoice_id_invoice_id_fk" FOREIGN KEY ("invoice_id") REFERENCES "invoices"."invoice"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
160
+ ALTER TABLE "invoices"."invoice_history" ADD CONSTRAINT "invoice_history_actor_user_id_user_id_fk" FOREIGN KEY ("actor_user_id") REFERENCES "public"."user"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
161
+ ALTER TABLE "invoices"."invoice_line" ADD CONSTRAINT "invoice_line_invoice_id_invoice_id_fk" FOREIGN KEY ("invoice_id") REFERENCES "invoices"."invoice"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
162
+ ALTER TABLE "invoices"."invoice_payment" ADD CONSTRAINT "invoice_payment_invoice_id_invoice_id_fk" FOREIGN KEY ("invoice_id") REFERENCES "invoices"."invoice"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
163
+ ALTER TABLE "invoices"."invoice_payment" ADD CONSTRAINT "invoice_payment_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
164
+ ALTER TABLE "invoices"."settings" ADD CONSTRAINT "settings_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
165
+ CREATE UNIQUE INDEX "billing_contact_org_email_uidx" ON "invoices"."billing_contact" USING btree ("organization_id","email");--> statement-breakpoint
166
+ CREATE INDEX "billing_contact_org_idx" ON "invoices"."billing_contact" USING btree ("organization_id");--> statement-breakpoint
167
+ CREATE UNIQUE INDEX "invoice_org_number_uidx" ON "invoices"."invoice" USING btree ("organization_id","number");--> statement-breakpoint
168
+ CREATE INDEX "invoice_org_status_idx" ON "invoices"."invoice" USING btree ("organization_id","status");--> statement-breakpoint
169
+ CREATE INDEX "invoice_client_idx" ON "invoices"."invoice" USING btree ("client_organization_id");--> statement-breakpoint
170
+ CREATE INDEX "invoice_attachment_invoice_idx" ON "invoices"."invoice_attachment" USING btree ("invoice_id");--> statement-breakpoint
171
+ CREATE UNIQUE INDEX "invoice_client_access_provider_client_uidx" ON "invoices"."client_access" USING btree ("provider_organization_id","client_organization_id");--> statement-breakpoint
172
+ CREATE INDEX "invoice_client_access_client_idx" ON "invoices"."client_access" USING btree ("client_organization_id");--> statement-breakpoint
173
+ CREATE UNIQUE INDEX "invoice_client_viewer_access_user_uidx" ON "invoices"."client_viewer" USING btree ("access_id","user_id");--> statement-breakpoint
174
+ CREATE INDEX "invoice_client_viewer_user_idx" ON "invoices"."client_viewer" USING btree ("user_id");--> statement-breakpoint
175
+ CREATE INDEX "invoice_email_delivery_invoice_idx" ON "invoices"."invoice_email_delivery" USING btree ("invoice_id","created_at");--> statement-breakpoint
176
+ CREATE UNIQUE INDEX "invoice_email_delivery_pending_invoice_uidx" ON "invoices"."invoice_email_delivery" USING btree ("invoice_id") WHERE "invoices"."invoice_email_delivery"."status" = 'PENDING';--> statement-breakpoint
177
+ CREATE INDEX "invoice_history_invoice_idx" ON "invoices"."invoice_history" USING btree ("invoice_id");--> statement-breakpoint
178
+ CREATE INDEX "invoice_line_invoice_idx" ON "invoices"."invoice_line" USING btree ("invoice_id");--> statement-breakpoint
179
+ CREATE INDEX "invoice_payment_invoice_idx" ON "invoices"."invoice_payment" USING btree ("invoice_id");--> statement-breakpoint
180
+ DO $$ BEGIN
181
+ IF to_regclass('timesheets.workspace_settings') IS NOT NULL AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'timesheets' AND table_name = 'workspace_settings' AND column_name = 'invoicing_enabled') THEN
182
+ INSERT INTO invoices.settings (organization_id, enabled, currency, default_vat_rate_basis_points, address, registration_number, vat_number, iban, bic, invoice_email, invoice_email_template, preferred_locale, created_at, updated_at)
183
+ SELECT ws.organization_id, ws.invoicing_enabled, ws.currency, ws.default_vat_rate_basis_points, COALESCE(p.address, ''), p.registration_number, p.vat_number, p.iban, p.bic, p.invoice_email, p.invoice_email_template, COALESCE(p.preferred_locale, 'nl'), ws.created_at, ws.updated_at
184
+ FROM timesheets.workspace_settings ws LEFT JOIN timesheets.organization_invoice_profile p ON p.organization_id = ws.organization_id ON CONFLICT (organization_id) DO NOTHING;
185
+ END IF;
186
+ IF to_regclass('timesheets.organization_contact') IS NOT NULL THEN INSERT INTO invoices.billing_contact SELECT * FROM timesheets.organization_contact ON CONFLICT (id) DO NOTHING; END IF;
187
+ IF to_regclass('timesheets.workspace_client') IS NOT NULL AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'timesheets' AND table_name = 'workspace_client' AND column_name = 'invoice_access_enabled') THEN
188
+ INSERT INTO invoices.client_access (id, provider_organization_id, client_organization_id, enabled, created_at, updated_at) SELECT id, workspace_organization_id, client_organization_id, invoice_access_enabled, created_at, updated_at FROM timesheets.workspace_client WHERE invoice_access_enabled ON CONFLICT (id) DO NOTHING;
189
+ INSERT INTO clients.client_module (id, organization_id, module_id, enabled, enabled_at, created_at, updated_at) SELECT 'invoice-' || id, client_organization_id, 'invoices', true, created_at, created_at, updated_at FROM timesheets.workspace_client WHERE invoice_access_enabled ON CONFLICT (organization_id, module_id) DO UPDATE SET enabled = true, disabled_at = NULL, updated_at = EXCLUDED.updated_at;
190
+ END IF;
191
+ IF to_regclass('timesheets.workspace_client_invoice_viewer') IS NOT NULL THEN INSERT INTO invoices.client_viewer (id, access_id, user_id, created_by_id, created_at) SELECT id, workspace_client_id, user_id, created_by_id, created_at FROM timesheets.workspace_client_invoice_viewer ON CONFLICT (id) DO NOTHING; END IF;
192
+ IF to_regclass('timesheets.invoice') IS NOT NULL THEN
193
+ INSERT INTO invoices.invoice SELECT id, organization_id, client_organization_id, number, status::text::invoices.invoice_status, currency, issue_date, due_date, subject, notes, sender_name, sender_logo, sender_address, sender_registration, sender_vat_number, sender_iban, sender_bic, recipient_name, recipient_address, recipient_contact_name, recipient_email, recipient_locale, created_by_id, issued_at, created_at, updated_at FROM timesheets.invoice ON CONFLICT (id) DO NOTHING;
194
+ INSERT INTO invoices.invoice_line SELECT * FROM timesheets.invoice_line ON CONFLICT (id) DO NOTHING;
195
+ INSERT INTO invoices.invoice_payment SELECT * FROM timesheets.invoice_payment ON CONFLICT (id) DO NOTHING;
196
+ INSERT INTO invoices.invoice_history SELECT * FROM timesheets.invoice_history ON CONFLICT (id) DO NOTHING;
197
+ INSERT INTO invoices.invoice_attachment SELECT * FROM timesheets.invoice_attachment ON CONFLICT (id) DO NOTHING;
198
+ INSERT INTO invoices.invoice_email_delivery SELECT id, invoice_id, status::text::invoices.invoice_email_delivery_status, purpose::text::invoices.invoice_email_purpose, recipient_email, cc_emails, locale, subject, body, actor_user_id, provider_message_id, provider_last_event, provider_status_checked_at, error_message, payload_hash, created_at, updated_at, sent_at FROM timesheets.invoice_email_delivery ON CONFLICT (id) DO NOTHING;
199
+
200
+ CREATE TABLE IF NOT EXISTS invoices._legacy_timesheet_source (id text PRIMARY KEY, invoice_id text NOT NULL, invoice_line_id text NOT NULL, time_entry_id text NOT NULL);
201
+ INSERT INTO invoices._legacy_timesheet_source SELECT * FROM timesheets.invoice_time_entry ON CONFLICT (id) DO NOTHING;
202
+
203
+ IF EXISTS (SELECT id FROM timesheets.invoice EXCEPT SELECT id FROM invoices.invoice)
204
+ OR EXISTS (SELECT id FROM timesheets.invoice_line EXCEPT SELECT id FROM invoices.invoice_line)
205
+ OR EXISTS (SELECT id FROM timesheets.invoice_payment EXCEPT SELECT id FROM invoices.invoice_payment)
206
+ OR EXISTS (SELECT id FROM timesheets.invoice_history EXCEPT SELECT id FROM invoices.invoice_history)
207
+ OR EXISTS (SELECT id FROM timesheets.invoice_attachment EXCEPT SELECT id FROM invoices.invoice_attachment)
208
+ OR EXISTS (SELECT id FROM timesheets.invoice_email_delivery EXCEPT SELECT id FROM invoices.invoice_email_delivery)
209
+ OR EXISTS (SELECT id FROM timesheets.invoice_time_entry EXCEPT SELECT id FROM invoices._legacy_timesheet_source)
210
+ OR EXISTS (SELECT organization_id FROM timesheets.workspace_settings EXCEPT SELECT organization_id FROM invoices.settings)
211
+ OR EXISTS (SELECT id FROM timesheets.organization_contact EXCEPT SELECT id FROM invoices.billing_contact)
212
+ OR EXISTS (SELECT id FROM timesheets.workspace_client WHERE invoice_access_enabled EXCEPT SELECT id FROM invoices.client_access)
213
+ OR EXISTS (SELECT id FROM timesheets.workspace_client_invoice_viewer EXCEPT SELECT id FROM invoices.client_viewer)
214
+ THEN
215
+ RAISE EXCEPTION 'Invoice migration verification failed; legacy tables were preserved';
216
+ END IF;
217
+
218
+ DROP TABLE timesheets.invoice_time_entry;
219
+ DROP TABLE timesheets.invoice_email_delivery;
220
+ DROP TABLE timesheets.invoice_attachment;
221
+ DROP TABLE timesheets.invoice_history;
222
+ DROP TABLE timesheets.invoice_payment;
223
+ DROP TABLE timesheets.invoice_line;
224
+ DROP TABLE timesheets.invoice;
225
+ DROP TABLE IF EXISTS timesheets.workspace_client_invoice_viewer;
226
+ DROP TABLE IF EXISTS timesheets.organization_contact;
227
+ DROP TABLE IF EXISTS timesheets.organization_invoice_profile;
228
+ ALTER TABLE timesheets.workspace_client DROP COLUMN IF EXISTS invoice_access_enabled;
229
+ ALTER TABLE timesheets.workspace_settings DROP COLUMN IF EXISTS invoicing_enabled;
230
+ ALTER TABLE timesheets.workspace_settings DROP COLUMN IF EXISTS default_vat_rate_basis_points;
231
+ DROP TYPE IF EXISTS timesheets.invoice_email_purpose;
232
+ DROP TYPE IF EXISTS timesheets.invoice_email_delivery_status;
233
+ DROP TYPE IF EXISTS timesheets.invoice_status;
234
+ END IF;
235
+ END $$;