@teleporthq/teleport-plugin-next-workflows 0.44.4 → 0.44.6

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 (29) hide show
  1. package/__tests__/account-delete-owned-rows.test.ts +488 -0
  2. package/__tests__/invoice-paid-status.test.ts +382 -0
  3. package/__tests__/invoice-tender.test.ts +5 -3
  4. package/dist/cjs/account-delete-route-generator.d.ts +9 -6
  5. package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
  6. package/dist/cjs/account-delete-route-generator.js +131 -1
  7. package/dist/cjs/account-delete-route-generator.js.map +1 -1
  8. package/dist/cjs/invoice/invoice-assembly-code.d.ts.map +1 -1
  9. package/dist/cjs/invoice/invoice-assembly-code.js +19 -2
  10. package/dist/cjs/invoice/invoice-assembly-code.js.map +1 -1
  11. package/dist/cjs/invoice/invoice-html-code.d.ts.map +1 -1
  12. package/dist/cjs/invoice/invoice-html-code.js +100 -71
  13. package/dist/cjs/invoice/invoice-html-code.js.map +1 -1
  14. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  15. package/dist/esm/account-delete-route-generator.d.ts +9 -6
  16. package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
  17. package/dist/esm/account-delete-route-generator.js +131 -1
  18. package/dist/esm/account-delete-route-generator.js.map +1 -1
  19. package/dist/esm/invoice/invoice-assembly-code.d.ts.map +1 -1
  20. package/dist/esm/invoice/invoice-assembly-code.js +19 -2
  21. package/dist/esm/invoice/invoice-assembly-code.js.map +1 -1
  22. package/dist/esm/invoice/invoice-html-code.d.ts.map +1 -1
  23. package/dist/esm/invoice/invoice-html-code.js +100 -71
  24. package/dist/esm/invoice/invoice-html-code.js.map +1 -1
  25. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +3 -3
  27. package/src/account-delete-route-generator.ts +140 -7
  28. package/src/invoice/invoice-assembly-code.ts +19 -2
  29. package/src/invoice/invoice-html-code.ts +100 -71
@@ -6,15 +6,18 @@
6
6
  * 1. resolves the current user from the NextAuth session (never trusting a
7
7
  * client-supplied id),
8
8
  * 2. runs a single DB transaction that detaches + anonymises business/legal
9
- * records (orders, withdrawal requests) so tracking survives, deletes the
10
- * user's personal data (reviews, cart, favourites, chat history) and the
11
- * user row, and
9
+ * records (orders, withdrawal requests, bookings) so tracking survives,
10
+ * deletes the user's personal data (reviews, cart, favourites, chat
11
+ * history), deletes the rows the user owns in the project's own tables —
12
+ * the ones the AI made for the site, each through the one column the
13
+ * site's "My …" pages read it by (teleport-shared's resolveOwnerColumn,
14
+ * shipped into the route as text) — and then the user row, and
12
15
  * 3. best-effort sends the farewell email (a failed send never fails the
13
16
  * deletion).
14
17
  *
15
- * Every table statement is guarded by a runtime `information_schema` column
16
- * check, so the route is a no-op for feature tables a given project never
17
- * provisioned.
18
+ * Every platform-table statement is guarded by a runtime `information_schema`
19
+ * column check, so the route is a no-op for feature tables a given project
20
+ * never provisioned.
18
21
  */
19
22
  import { UIDLLocalizedEmailTemplates } from '@teleporthq/teleport-types';
20
23
  export interface AccountDeleteRouteOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"account-delete-route-generator.d.ts","sourceRoot":"","sources":["../../src/account-delete-route-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAA;AAYxE,MAAM,WAAW,yBAAyB;IAExC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAI3B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IAGtB,kBAAkB,CAAC,EAAE,2BAA2B,CAAA;IAEhD,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAID,eAAO,MAAM,8BAA8B,+CAAiC,CAAA;AAE5E,eAAO,MAAM,0BAA0B,aAAa,yBAAyB,KAAQ,MAoPpF,CAAA"}
1
+ {"version":3,"file":"account-delete-route-generator.d.ts","sourceRoot":"","sources":["../../src/account-delete-route-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAA;AAaxE,MAAM,WAAW,yBAAyB;IAExC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAI3B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IAGtB,kBAAkB,CAAC,EAAE,2BAA2B,CAAA;IAEhD,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAID,eAAO,MAAM,8BAA8B,+CAAiC,CAAA;AAE5E,eAAO,MAAM,0BAA0B,aAAa,yBAAyB,KAAQ,MAqXpF,CAAA"}
@@ -1,3 +1,4 @@
1
+ import { OwnerColumns } from '@teleporthq/teleport-shared';
1
2
  import { generatePgClientCode } from './pg-client-code';
2
3
  import { generateCommonJsSessionTokenResolverCode } from './session-cookie-resolver';
3
4
  import { SUPPORTED_EMAIL_PROVIDERS, transactionalEmailDependencies, generateProviderSendFunction, generateFillTemplateFn, generateEmailLocaleRequire, generateLocalizedEmailCopyFn, } from './transactional-email-code';
@@ -32,11 +33,22 @@ const DELETE_TABLES = [
32
33
  'teleport_cart',
33
34
  'teleport_ai_chat_messages',
34
35
  'teleport_ai_chat_conversations',
36
+ // A push subscription is the capability to notify the user's device.
37
+ 'teleport_push_subscriptions',
35
38
  ];
36
39
  // Business / legal records that must survive: the user link is nulled and any
37
40
  // stored personal email is replaced with a synthetic, self-describing address.
38
- const ANONYMIZE_TABLES = ['teleport_orders', 'teleport_withdrawal_requests'];
41
+ const ANONYMIZE_TABLES = [
42
+ 'teleport_orders',
43
+ 'teleport_withdrawal_requests',
44
+ 'teleport_bookings',
45
+ 'teleport_subscriptions',
46
+ 'teleport_abandoned_carts',
47
+ 'teleport_voucher_redemptions',
48
+ ];
39
49
  const EMAIL_COLUMNS = ['billing_email', 'shipping_email', 'customer_email', 'email'];
50
+ // A booking also names the customer and carries their phone; both are cleared.
51
+ const CLEARED_COLUMNS = { teleport_bookings: ['customer_name', 'customer_phone'] };
40
52
 
41
53
  async function tableColumns(client, table) {
42
54
  var r = await client.query(
@@ -59,6 +71,10 @@ async function anonymizeUserRows(client, table, userId, syntheticEmail) {
59
71
  sets.push(EMAIL_COLUMNS[i] + ' = $' + params.length);
60
72
  }
61
73
  }
74
+ var cleared = CLEARED_COLUMNS[table] || [];
75
+ for (var c = 0; c < cleared.length; c++) {
76
+ if (cols.indexOf(cleared[c]) !== -1) { sets.push(cleared[c] + ' = NULL'); }
77
+ }
62
78
  await client.query('UPDATE ' + table + ' SET ' + sets.join(', ') + ' WHERE user_id = $1', params);
63
79
  }
64
80
 
@@ -75,6 +91,119 @@ async function deleteRowsByEmail(client, table, column, email) {
75
91
  await client.query('DELETE FROM ' + table + ' WHERE ' + column + ' = $1', [email]);
76
92
  }
77
93
 
94
+ // Rows the user owns in the project's OWN tables — the ones the AI made for
95
+ // this site (saved items, requests, reviews …). Their link to the user is
96
+ // ON DELETE SET NULL, so they outlived the account with a blank owner, and the
97
+ // "My …" pages read "owner = me OR owner IS NULL": every other signed-in
98
+ // visitor saw them. Platform tables keep the explicit handling above —
99
+ // deleted, anonymised, or kept as the business's records.
100
+ //
101
+ // Each table is attributed through exactly ONE column, the one its "My …"
102
+ // pages read it by: resolveOwnerColumn below is the AI pipeline's own
103
+ // decision, shipped here as text, so the two sides cannot disagree. Rows go
104
+ // only through an "owner" column (whose the row is: author, submitter,
105
+ // sender); an "actor" column (who approved, reviewed, was assigned or
106
+ // addressed) names somebody else's row and leaves the table alone.
107
+ ${OwnerColumns.generateOwnerColumnResolverCode()}
108
+
109
+ const PLATFORM_TABLE_PREFIX = 'teleport_';
110
+ const PLATFORM_TABLES = [AUTH_USERS_TABLE, 'password_reset_tokens'];
111
+
112
+ function isPlatformTable(table) {
113
+ return PLATFORM_TABLES.indexOf(table) !== -1 || table.indexOf(PLATFORM_TABLE_PREFIX) === 0;
114
+ }
115
+
116
+ function quoteIdentifier(name) {
117
+ return '"' + String(name).split('"').join('""') + '"';
118
+ }
119
+
120
+ // The (table, column) pairs through which the project's own tables name their
121
+ // owner: the resolved owner column when it references the users table — or
122
+ // references nothing, in older projects whose link was dropped. One that
123
+ // references another table (account_id -> accounts) is a domain link and the
124
+ // table is left alone. Links are read from pg_constraint rather than
125
+ // information_schema: constraint names are only unique per table, so a join
126
+ // by name can pair the wrong tables.
127
+ async function ownedRowLinks(client) {
128
+ var columns = await client.query(
129
+ "SELECT c.table_name, c.column_name, c.ordinal_position FROM information_schema.columns c" +
130
+ " JOIN information_schema.tables t ON t.table_schema = c.table_schema AND t.table_name = c.table_name" +
131
+ " WHERE c.table_schema = 'public' AND t.table_type = 'BASE TABLE'" +
132
+ " ORDER BY c.table_name, c.ordinal_position"
133
+ );
134
+ var links = await client.query(
135
+ "SELECT cl.relname AS table_name, a.attname AS column_name," +
136
+ " rcl.relname AS referenced_table, ra.attname AS referenced_column" +
137
+ " FROM pg_constraint k" +
138
+ " JOIN pg_class cl ON cl.oid = k.conrelid" +
139
+ " JOIN pg_namespace n ON n.oid = cl.relnamespace" +
140
+ " JOIN pg_attribute a ON a.attrelid = k.conrelid AND a.attnum = k.conkey[1]" +
141
+ " JOIN pg_class rcl ON rcl.oid = k.confrelid" +
142
+ " JOIN pg_attribute ra ON ra.attrelid = k.confrelid AND ra.attnum = k.confkey[1]" +
143
+ " WHERE k.contype = 'f' AND n.nspname = 'public' AND array_length(k.conkey, 1) = 1"
144
+ );
145
+ var referenced = {};
146
+ for (var i = 0; i < links.rows.length; i++) {
147
+ var link = links.rows[i];
148
+ referenced[link.table_name + '.' + link.column_name] = link.referenced_table + '.' + link.referenced_column;
149
+ }
150
+ var tables = [];
151
+ var columnsOf = {};
152
+ for (var j = 0; j < columns.rows.length; j++) {
153
+ var row = columns.rows[j];
154
+ if (!columnsOf[row.table_name]) {
155
+ columnsOf[row.table_name] = [];
156
+ tables.push(row.table_name);
157
+ }
158
+ columnsOf[row.table_name].push({ name: row.column_name, ordinalPosition: Number(row.ordinal_position) });
159
+ }
160
+ var owned = [];
161
+ for (var t = 0; t < tables.length; t++) {
162
+ var table = tables[t];
163
+ if (isPlatformTable(table)) { continue; }
164
+ var resolved = resolveOwnerColumn(columnsOf[table]);
165
+ if (!resolved || resolved.kind !== 'owner') { continue; }
166
+ var target = referenced[table + '.' + resolved.column];
167
+ if (target && target !== AUTH_USERS_TABLE + '.id') { continue; }
168
+ owned.push({ table: table, column: resolved.column });
169
+ }
170
+ return owned;
171
+ }
172
+
173
+ // Each table is deleted from under its own savepoint, so one that refuses
174
+ // (another user's row still points at this user's, through a link without
175
+ // ON DELETE CASCADE) stops neither the other tables nor the account from being
176
+ // deleted. A refused table is retried once the rest are done — the blocker may
177
+ // have been this user's own row in another table; what is still refused stays
178
+ // as it always did, and the log names it.
179
+ async function deleteOwnedRows(client, userId) {
180
+ var pending = await ownedRowLinks(client);
181
+ while (pending.length > 0) {
182
+ var refused = [];
183
+ for (var i = 0; i < pending.length; i++) {
184
+ var link = pending[i];
185
+ await client.query('SAVEPOINT owned_rows');
186
+ try {
187
+ await client.query(
188
+ 'DELETE FROM ' + quoteIdentifier(link.table) + ' WHERE ' + quoteIdentifier(link.column) + ' = $1',
189
+ [userId]
190
+ );
191
+ await client.query('RELEASE SAVEPOINT owned_rows');
192
+ } catch (err) {
193
+ await client.query('ROLLBACK TO SAVEPOINT owned_rows');
194
+ await client.query('RELEASE SAVEPOINT owned_rows');
195
+ link.error = err && err.message ? err.message : String(err);
196
+ refused.push(link);
197
+ }
198
+ }
199
+ if (refused.length === pending.length) { break; }
200
+ pending = refused;
201
+ }
202
+ for (var k = 0; k < pending.length; k++) {
203
+ console.error('[account-delete] rows kept in ' + pending[k].table + ' (' + pending[k].column + '): ' + pending[k].error);
204
+ }
205
+ }
206
+
78
207
  // Invoices are a financial/tax record that must survive, but they store the
79
208
  // customer's PII denormalised and link to the user only through order_id (no
80
209
  // user_id). Scrub the PII in place, matching invoices whose order belongs to the
@@ -194,6 +323,7 @@ module.exports = async function handler(req, res) {
194
323
  for (var d = 0; d < DELETE_TABLES.length; d++) {
195
324
  await deleteUserRows(client, DELETE_TABLES[d], userId);
196
325
  }
326
+ await deleteOwnedRows(client, userId);
197
327
  // password_reset_tokens has no user_id — it is keyed by the email string.
198
328
  await deleteRowsByEmail(client, 'password_reset_tokens', 'email', userEmail);
199
329
  await client.query('DELETE FROM ' + AUTH_USERS_TABLE + ' WHERE id = $1', [userId]);
@@ -1 +1 @@
1
- {"version":3,"file":"account-delete-route-generator.js","sourceRoot":"","sources":["../../src/account-delete-route-generator.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,wCAAwC,EAAE,MAAM,2BAA2B,CAAA;AACpF,OAAO,EACL,yBAAyB,EACzB,8BAA8B,EAC9B,4BAA4B,EAC5B,sBAAsB,EACtB,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,4BAA4B,CAAA;AAuBnC,+EAA+E;AAC/E,oFAAoF;AACpF,MAAM,CAAC,MAAM,8BAA8B,GAAG,8BAA8B,CAAA;AAE5E,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,UAAqC,EAAE,EAAU,EAAE;IAC5F,MAAM,cAAc,GAAG,OAAO,CAAC,kBAAkB,IAAI,OAAO,CAAA;IAC5D,MAAM,QAAQ,GACZ,OAAO,CAAC,aAAa,IAAI,yBAAyB,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;QAC3E,CAAC,CAAC,OAAO,CAAC,aAAa;QACvB,CAAC,CAAC,IAAI,CAAA;IACV,MAAM,mBAAmB,GACvB,OAAO,CAAC,mBAAmB,IAAI,yCAAyC,CAAA;IAE1E,OAAO,GAAG,oBAAoB,EAAE;EAChC,wCAAwC,EAAE;2BACjB,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;oBACrC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;gCAC1B,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC;yBAC1C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;qBAC5B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;0BAClC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;gCAChC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,kBAAkB,IAAI,EAAE,CAAC;wBACxD,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,YAAY,IAAI,+BAA+B,CAAC;0BACrE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;0BAC3C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,kBAAkB,IAAI,EAAE,CAAC;EACxE,0BAA0B,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6FtC,sBAAsB,EAAE;;EAExB,4BAA4B,CAAC;QAC7B,MAAM,EAAE,0BAA0B;QAClC,YAAY,EAAE,eAAe;QAC7B,SAAS,EAAE,iBAAiB;QAC5B,cAAc,EAAE,iBAAiB;KAClC,CAAC;;EAEA,4BAA4B,CAAC,QAAQ,EAAE;QACvC,SAAS,EAAE,iBAAiB;QAC5B,MAAM,EAAE,gBAAgB;QACxB,SAAS,EAAE,4BAA4B;QACvC,cAAc,EAAE,UAAU;KAC3B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2GA,QAAQ;QACN,CAAC,CAAC;;CAEL;QACG,CAAC,CAAC,EACN;;;CAGC,CAAA;AACD,CAAC,CAAA"}
1
+ {"version":3,"file":"account-delete-route-generator.js","sourceRoot":"","sources":["../../src/account-delete-route-generator.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,wCAAwC,EAAE,MAAM,2BAA2B,CAAA;AACpF,OAAO,EACL,yBAAyB,EACzB,8BAA8B,EAC9B,4BAA4B,EAC5B,sBAAsB,EACtB,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,4BAA4B,CAAA;AAuBnC,+EAA+E;AAC/E,oFAAoF;AACpF,MAAM,CAAC,MAAM,8BAA8B,GAAG,8BAA8B,CAAA;AAE5E,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,UAAqC,EAAE,EAAU,EAAE;IAC5F,MAAM,cAAc,GAAG,OAAO,CAAC,kBAAkB,IAAI,OAAO,CAAA;IAC5D,MAAM,QAAQ,GACZ,OAAO,CAAC,aAAa,IAAI,yBAAyB,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;QAC3E,CAAC,CAAC,OAAO,CAAC,aAAa;QACvB,CAAC,CAAC,IAAI,CAAA;IACV,MAAM,mBAAmB,GACvB,OAAO,CAAC,mBAAmB,IAAI,yCAAyC,CAAA;IAE1E,OAAO,GAAG,oBAAoB,EAAE;EAChC,wCAAwC,EAAE;2BACjB,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;oBACrC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;gCAC1B,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC;yBAC1C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;qBAC5B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;0BAClC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;gCAChC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,kBAAkB,IAAI,EAAE,CAAC;wBACxD,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,YAAY,IAAI,+BAA+B,CAAC;0BACrE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;0BAC3C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,kBAAkB,IAAI,EAAE,CAAC;EACxE,0BAA0B,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiFtC,YAAY,CAAC,+BAA+B,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4I9C,sBAAsB,EAAE;;EAExB,4BAA4B,CAAC;QAC7B,MAAM,EAAE,0BAA0B;QAClC,YAAY,EAAE,eAAe;QAC7B,SAAS,EAAE,iBAAiB;QAC5B,cAAc,EAAE,iBAAiB;KAClC,CAAC;;EAEA,4BAA4B,CAAC,QAAQ,EAAE;QACvC,SAAS,EAAE,iBAAiB;QAC5B,MAAM,EAAE,gBAAgB;QACxB,SAAS,EAAE,4BAA4B;QACvC,cAAc,EAAE,UAAU;KAC3B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4GA,QAAQ;QACN,CAAC,CAAC;;CAEL;QACG,CAAC,CAAC,EACN;;;CAGC,CAAA;AACD,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"invoice-assembly-code.d.ts","sourceRoot":"","sources":["../../../src/invoice/invoice-assembly-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAIhE;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,aAAc,mBAAmB,KAAG,MAgiB3E,CAAA"}
1
+ {"version":3,"file":"invoice-assembly-code.d.ts","sourceRoot":"","sources":["../../../src/invoice/invoice-assembly-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAIhE;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,aAAc,mBAAmB,KAAG,MAijB3E,CAAA"}
@@ -91,6 +91,16 @@ var CURRENCY_SYMBOLS = {
91
91
  'CLP': 'CLP$', 'PEN': 'S/.', 'VND': '\\u20AB', 'UAH': '\\u20B4',
92
92
  };
93
93
 
94
+ // \`teleport_orders.payment_status\` values that mean the order's money was
95
+ // collected — the editor's PAID_PAYMENT_STATUS_VALUES (teleport-gui
96
+ // e-commerce/constants/order-statuses.ts), provider spellings included.
97
+ var PAID_ORDER_PAYMENT_STATUSES = ['paid', 'partially_refunded', 'succeeded', 'success', 'completed', 'captured'];
98
+
99
+ function isPaidOrder(order) {
100
+ var paymentStatus = String((order && order.payment_status) || '').trim().toLowerCase();
101
+ return PAID_ORDER_PAYMENT_STATUSES.indexOf(paymentStatus) !== -1;
102
+ }
103
+
94
104
  function isLoopbackHost(host) {
95
105
  var name = String(host || '').replace(/:[0-9]+$/, '').toLowerCase();
96
106
  return name === 'localhost' || name === '127.0.0.1' || name === '[::1]';
@@ -471,15 +481,22 @@ function assembleInvoiceData(body, hydratedOrder, hydratedItems) {
471
481
  var fallbackPaymentIntentId = orderRow.payment_intent_id || '';
472
482
  var fallbackNotes = orderRow.notes || '';
473
483
 
484
+ // Paid or not, when the caller does not say: the order's money state. The
485
+ // payment webhook marks an order paid and then asks for its invoice with just
486
+ // the order id; a cash-on-delivery order is invoiced unpaid when it is placed.
487
+ // The orders table keeps no payment time of its own — the mark-paid write
488
+ // stamps \`updated_at\`, just before the webhook asks.
489
+ var paidByOrder = !body.status && isPaidOrder(orderRow);
490
+
474
491
  var invoiceData = {
475
492
  id: body.id || require('crypto').randomUUID(),
476
493
  // Numbered by the route: an invoice issued at payment takes the next
477
494
  // number, a regenerated one keeps its own.
478
495
  invoiceNumber: '',
479
- status: body.status || 'issued',
496
+ status: body.status || (paidByOrder ? 'paid' : 'issued'),
480
497
  issueDate: issueDate,
481
498
  dueDate: dueDate,
482
- paidAt: body.paidAt || null,
499
+ paidAt: body.paidAt || (paidByOrder ? orderRow.updated_at || new Date().toISOString() : null),
483
500
  customerName: body.customerName || fallbackCustomerName || '',
484
501
  customerEmail: body.customerEmail || fallbackCustomerEmail || '',
485
502
  customerAddress: body.customerAddress || fallbackCustomerAddress || '',
@@ -1 +1 @@
1
- {"version":3,"file":"invoice-assembly-code.js","sourceRoot":"","sources":["../../../src/invoice/invoice-assembly-code.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAA;AAEvE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,QAA6B,EAAU,EAAE;;IACnF,MAAM,cAAc,GAAG,QAAQ,CAAC,cAAc,IAAI,CAAC,CAAA;IACnD,MAAM,kBAAkB,GAAG,QAAQ,CAAC,kBAAkB,KAAK,IAAI,CAAA;IAC/D,MAAM,oBAAoB,GAAG,CAAA,MAAA,QAAQ,CAAC,QAAQ,0CAAE,QAAQ;QACtD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC5C,CAAC,CAAC,MAAM,CAAA;IAEV,OAAO;;;;;;;;yBAQgB,cAAc;;8BAET,kBAAkB;0BACtB,oBAAoB;EAC5C,yBAAyB;EACzB,cAAc,CAAC,gCAAgC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2gBlD,CAAA;AACD,CAAC,CAAA"}
1
+ {"version":3,"file":"invoice-assembly-code.js","sourceRoot":"","sources":["../../../src/invoice/invoice-assembly-code.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAA;AAEvE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,QAA6B,EAAU,EAAE;;IACnF,MAAM,cAAc,GAAG,QAAQ,CAAC,cAAc,IAAI,CAAC,CAAA;IACnD,MAAM,kBAAkB,GAAG,QAAQ,CAAC,kBAAkB,KAAK,IAAI,CAAA;IAC/D,MAAM,oBAAoB,GAAG,CAAA,MAAA,QAAQ,CAAC,QAAQ,0CAAE,QAAQ;QACtD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC5C,CAAC,CAAC,MAAM,CAAA;IAEV,OAAO;;;;;;;;yBAQgB,cAAc;;8BAET,kBAAkB;0BACtB,oBAAoB;EAC5C,yBAAyB;EACzB,cAAc,CAAC,gCAAgC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4hBlD,CAAA;AACD,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"invoice-html-code.d.ts","sourceRoot":"","sources":["../../../src/invoice/invoice-html-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AA4HhE,eAAO,MAAM,uBAAuB,aACxB,mBAAmB,uBACR,OAAO,iBACb,OAAO,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,KACxD,MA8gCF,CAAA"}
1
+ {"version":3,"file":"invoice-html-code.d.ts","sourceRoot":"","sources":["../../../src/invoice/invoice-html-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AA4HhE,eAAO,MAAM,uBAAuB,aACxB,mBAAmB,uBACR,OAAO,iBACb,OAAO,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,KACxD,MA2iCF,CAAA"}
@@ -10,12 +10,12 @@
10
10
  //
11
11
  // The UIDL tree has several features that need runtime handling:
12
12
  //
13
- // 1. `<context>` elements carry default invoice data on their attributes
14
- // (Company, Invoice, Customer, Products, Payment). Children reference
15
- // that data through expressions like `invoiceData?.Company?.name`. We
16
- // build a per-render scope from the attrs merged with real DB data
17
- // (real wins unless empty) so every expression resolves against the
18
- // buyer's actual order.
13
+ // 1. `<context>` elements carry the design canvas's sample invoice on
14
+ // their attributes (Company, Invoice, Customer, Products, Payment).
15
+ // Children reference that data through expressions like
16
+ // `invoiceData?.Company?.name`. Every render builds the scope from the
17
+ // real invoice alone, so every expression resolves against the buyer's
18
+ // actual order and an empty field prints nothing — never the sample.
19
19
  //
20
20
  // 2. `cms-list-repeater` iterates `invoiceData.Products` with a
21
21
  // per-iteration variable (the `renderPropIdentifier`). Each line item
@@ -133,11 +133,12 @@ var COMPONENT_DEFINITIONS = ${componentDefinitionsJson};
133
133
  var MAX_COMPONENT_DEPTH = 12;
134
134
 
135
135
  // ---------------------------------------------------------------------------
136
- // Scope construction — merges real DB data with defaults the user set on
137
- // the <context> element in the editor. Per-field fallback: if the real
138
- // field is empty/null, the UIDL default wins. Covers the common dev case
139
- // where COMPANY_DETAILS from invoiceSettings isn't filled in but the user
140
- // dropped "Acme Corporation" into the template preview.
136
+ // Scope construction — the real invoice, the store's company details and the
137
+ // buyer, never the sample on the template's <context>: an empty notes field,
138
+ // VAT number or paid date printed the design canvas's "Payment is due within
139
+ // 30 days", "US123456789" or "Jan 20, 2025" on a real invoice. The one value
140
+ // read from the context is the company logo URL, which the editor writes there
141
+ // from the store's own logo (the sample's is empty).
141
142
  // ---------------------------------------------------------------------------
142
143
 
143
144
  function extractStaticAttrsFromContext(uidl) {
@@ -174,23 +175,9 @@ function findFirstContextNode(node) {
174
175
  return null;
175
176
  }
176
177
 
177
- function nonEmpty(v) {
178
- if (v == null) return false;
179
- if (typeof v === 'string') return v.length > 0;
180
- if (typeof v === 'number') return !isNaN(v);
181
- return true;
182
- }
183
-
184
- function mergeWithDefaults(primary, defaults) {
185
- var out = Object.assign({}, defaults || {});
186
- if (primary) {
187
- Object.keys(primary).forEach(function (k) {
188
- if (nonEmpty(primary[k])) out[k] = primary[k];
189
- });
190
- }
191
- return out;
192
- }
193
-
178
+ // Money on the invoice, in the PDF and the email alike. A symbol that ends in
179
+ // a letter is a word ("lei", "CHF", "kr") and stands apart from the number
180
+ // ("lei 119.00"); a sign sits against it ("$119.00", "\u20AC119.00").
194
181
  function formatCurrencyValue(amount, sym, decimals) {
195
182
  var num = Number(amount);
196
183
  if (!isFinite(num)) num = 0;
@@ -198,7 +185,17 @@ function formatCurrencyValue(amount, sym, decimals) {
198
185
  var fixed = num.toFixed(dec);
199
186
  var parts = fixed.split('.');
200
187
  parts[0] = parts[0].replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');
201
- return (sym || '') + parts.join('.');
188
+ var symbol = String(sym == null ? '' : sym).trim();
189
+ var separator = /\\p{L}$/u.test(symbol) ? ' ' : '';
190
+ return symbol + separator + parts.join('.');
191
+ }
192
+
193
+ // Invoice statuses of money that was collected: a refund later moves a paid
194
+ // invoice to one of the last two, and it was paid all the same.
195
+ var SETTLED_INVOICE_STATUSES = ['paid', 'partially_refunded', 'refunded'];
196
+
197
+ function isSettledInvoice(status) {
198
+ return SETTLED_INVOICE_STATUSES.indexOf(String(status || '').toLowerCase()) !== -1;
202
199
  }
203
200
 
204
201
  // Per-line VAT formulas. These MIRROR the GUI helpers in
@@ -249,7 +246,7 @@ function formatDateValue(raw) {
249
246
  }
250
247
 
251
248
  function buildInvoiceDataScope(invoiceData) {
252
- var defaults = extractStaticAttrsFromContext(TEMPLATE_COMPONENT_UIDL);
249
+ var contextCompany = extractStaticAttrsFromContext(TEMPLATE_COMPONENT_UIDL).Company || {};
253
250
  var sym = invoiceData.currencySymbol || '$';
254
251
 
255
252
  var realCompany = {
@@ -263,7 +260,7 @@ function buildInvoiceDataScope(invoiceData) {
263
260
  registrationNumber: COMPANY_DETAILS.companyRegNumber,
264
261
  email: COMPANY_DETAILS.companyEmail,
265
262
  phone: COMPANY_DETAILS.companyPhone,
266
- logoUrl: invoiceData.companyLogoUrl || '',
263
+ logoUrl: invoiceData.companyLogoUrl || contextCompany.logoUrl || '',
267
264
  website: COMPANY_DETAILS.companyWebsite,
268
265
  };
269
266
 
@@ -299,12 +296,20 @@ function buildInvoiceDataScope(invoiceData) {
299
296
  amountDueNumber = Math.max(0, totalNumber - giftCardAmountNumber);
300
297
  }
301
298
 
299
+ // A paid invoice asks for nothing: where the due date would print, it says
300
+ // when it was paid. The order keeps no payment method finer than its
301
+ // provider, so the line names none.
302
+ var paid = isSettledInvoice(invoiceData.status);
303
+ var paidDate = formatDateValue(invoiceData.paidAt || (paid ? invoiceData.issueDate : ''));
304
+ var paidLine = paid ? 'Paid' + (paidDate ? ' on ' + paidDate : '') : '';
305
+
302
306
  var realInvoice = {
303
307
  invoiceNumber: invoiceData.invoiceNumber ? '#' + invoiceData.invoiceNumber : '',
304
308
  status: invoiceData.status || '',
305
309
  issueDate: formatDateValue(invoiceData.issueDate),
306
- dueDate: formatDateValue(invoiceData.dueDate),
307
- paidDate: formatDateValue(invoiceData.paidAt),
310
+ dueDate: paidLine || formatDateValue(invoiceData.dueDate),
311
+ paidDate: paidDate,
312
+ paidLine: paidLine,
308
313
  subtotal: formatCurrencyValue(invoiceData.subtotal, sym),
309
314
  taxRate: invoiceData.taxRate != null ? String(invoiceData.taxRate) : '',
310
315
  taxAmount: formatCurrencyValue(invoiceData.taxAmount, sym),
@@ -331,13 +336,6 @@ function buildInvoiceDataScope(invoiceData) {
331
336
  notes: invoiceData.notes || '',
332
337
  pdfUrl: invoiceData.pdfUrl || '',
333
338
  };
334
- // The merge below lets an EMPTY real value fall back to the template's
335
- // sample — right for a company address, wrong for a code or a card tail: an
336
- // order without a voucher must not print the designer's "SAVE20".
337
- var realInvoiceNeverSampled = { voucherCode: voucherCode, giftCardLast4: giftCardLast4 };
338
- // The buyer and their payment are never sampled either: an order with no
339
- // city (a booking, a pickup, a download) must not print the designer's
340
- // sample customer's, nor a card it was not paid with.
341
339
 
342
340
  var realCustomer = {
343
341
  name: invoiceData.customerName || '',
@@ -450,11 +448,11 @@ function buildInvoiceDataScope(invoiceData) {
450
448
 
451
449
  return {
452
450
  invoiceData: {
453
- Company: mergeWithDefaults(realCompany, defaults.Company),
454
- Invoice: Object.assign(mergeWithDefaults(realInvoice, defaults.Invoice), realInvoiceNeverSampled),
451
+ Company: realCompany,
452
+ Invoice: realInvoice,
455
453
  Customer: realCustomer,
456
454
  Payment: realPayment,
457
- Products: realProducts.length > 0 ? realProducts : (defaults.Products || []),
455
+ Products: realProducts,
458
456
  },
459
457
  };
460
458
  }
@@ -955,7 +953,12 @@ function buildFallbackInvoiceHtml(scope) {
955
953
  var parts = [];
956
954
  parts.push('<div style="padding:32px;font-family:Helvetica,Arial,sans-serif;color:#333;font-size:10pt;">');
957
955
  parts.push('<h1 style="text-align:right;font-size:24pt;margin:0 0 4px;">INVOICE</h1>');
958
- parts.push('<p style="text-align:right;color:#666;margin:0 0 24px;">' + escapeHtml(inv.invoiceNumber || '') + '</p>');
956
+ if (inv.paidLine) {
957
+ parts.push('<p style="text-align:right;color:#666;margin:0 0 4px;">' + escapeHtml(inv.invoiceNumber || '') + '</p>');
958
+ parts.push('<p style="text-align:right;color:#666;margin:0 0 24px;">' + escapeHtml(inv.paidLine) + '</p>');
959
+ } else {
960
+ parts.push('<p style="text-align:right;color:#666;margin:0 0 24px;">' + escapeHtml(inv.invoiceNumber || '') + '</p>');
961
+ }
959
962
  parts.push('<div style="display:flex;gap:40px;margin-bottom:24px;">');
960
963
  parts.push('<div style="flex:1;"><strong>' + escapeHtml(comp.name || '') + '</strong><br/>' +
961
964
  escapeHtml(comp.address || '') + '<br/>' +
@@ -1054,25 +1057,47 @@ function resolveTextSpans(spans, scope) {
1054
1057
  }).join('');
1055
1058
  }
1056
1059
 
1057
- // Amount tokens whose line means nothing at zero: "Delivery $0.00" on a
1058
- // pickup order, "Discount −$0.00" without a voucher. The table row (or list
1059
- // item / paragraph) that carries such a token is dropped before the tokens are
1060
- // filled, so the merchant's summary only lists the figures that applied.
1061
- // Matched innermost — a row inside a row keeps its parent.
1062
- var OPTIONAL_AMOUNT_TOKENS = ['shippingAmount', 'discountAmount', 'giftCardAmount'];
1063
-
1064
- function stripZeroAmountLines(template, inv) {
1065
- var out = template;
1066
- for (var i = 0; i < OPTIONAL_AMOUNT_TOKENS.length; i++) {
1067
- var key = OPTIONAL_AMOUNT_TOKENS[i];
1068
- if (Number(inv[key] || 0) > 0) continue;
1069
- var line = new RegExp(
1070
- '<(tr|li|p)\\\\b(?:(?!<\\\\/?\\\\1\\\\b)[\\\\s\\\\S])*?\\\\{\\\\{' + key + '\\\\}\\\\}(?:(?!<\\\\/?\\\\1\\\\b)[\\\\s\\\\S])*?<\\\\/\\\\1\\\\s*>',
1071
- 'g'
1072
- );
1073
- out = out.replace(line, '');
1074
- }
1075
- return out;
1060
+ // The summary lines the email leaves out. An amount whose line means nothing at
1061
+ // zero: "Delivery $0.00" on a pickup order, "Discount −$0.00" without a
1062
+ // voucher, "Tax $0.00" in an untaxed store. And what a paid invoice no longer
1063
+ // asks for: its due date and the amount due. As on the PDF, the amount due is
1064
+ // printed only beside a gift card; without one it is the total again.
1065
+ var OPTIONAL_AMOUNT_TOKENS = ['shippingAmount', 'discountAmount', 'taxAmount', 'giftCardAmount'];
1066
+
1067
+ function omittedLineTokens(inv) {
1068
+ var tokens = OPTIONAL_AMOUNT_TOKENS.filter(function (key) {
1069
+ return !(Number(inv[key] || 0) > 0);
1070
+ });
1071
+ var paid = isSettledInvoice(inv.status);
1072
+ if (paid) tokens.push('dueDate');
1073
+ if (paid || !(Number(inv.giftCardAmount || 0) > 0)) tokens.push('amountDue');
1074
+ return tokens;
1075
+ }
1076
+
1077
+ // A line is the table row, list item or paragraph carrying the token, matched
1078
+ // innermost — a row inside a row keeps its parent. The editor's designs publish
1079
+ // each line as a <div> of <span>s instead, so a token outside those drops the
1080
+ // innermost <div> holding it, and only when that <div> is the line alone: one
1081
+ // that also holds a block or another token is the merchant's own, and stays.
1082
+ var NO_BLOCK_OR_TOKEN = '(?!<\\\\/?(?:div|p|tr|li|table|ul|ol|h[1-6])\\\\b|\\\\{\\\\{)';
1083
+
1084
+ function stripLinesHolding(template, key) {
1085
+ var token = '\\\\{\\\\{' + key + '\\\\}\\\\}';
1086
+ var row = new RegExp(
1087
+ '<(tr|li|p)\\\\b(?:(?!<\\\\/?\\\\1\\\\b)[\\\\s\\\\S])*?' + token + '(?:(?!<\\\\/?\\\\1\\\\b)[\\\\s\\\\S])*?<\\\\/\\\\1\\\\s*>',
1088
+ 'g'
1089
+ );
1090
+ var lineDiv = new RegExp(
1091
+ '<div\\\\b(?:' + NO_BLOCK_OR_TOKEN + '[\\\\s\\\\S])*?' + token + '(?:' + NO_BLOCK_OR_TOKEN + '[\\\\s\\\\S])*?<\\\\/div\\\\s*>',
1092
+ 'g'
1093
+ );
1094
+ return template.replace(row, '').replace(lineDiv, '');
1095
+ }
1096
+
1097
+ function stripOmittedLines(template, inv) {
1098
+ return omittedLineTokens(inv).reduce(function (out, key) {
1099
+ return stripLinesHolding(out, key);
1100
+ }, template);
1076
1101
  }
1077
1102
 
1078
1103
  function replacePlaceholders(template, data) {
@@ -1080,7 +1105,8 @@ function replacePlaceholders(template, data) {
1080
1105
  var inv = data.invoice || {};
1081
1106
  var cust = data.customer || {};
1082
1107
  var comp = data.company || {};
1083
- return stripZeroAmountLines(template, inv).replace(/\\{\\{(\\w+)\\}\\}/g, function (match, key) {
1108
+ var sym = inv.currencySymbol;
1109
+ return stripOmittedLines(template, inv).replace(/\\{\\{(\\w+)\\}\\}/g, function (match, key) {
1084
1110
  var map = {
1085
1111
  invoiceNumber: inv.number || '',
1086
1112
  customerName: cust.name || '',
@@ -1091,14 +1117,14 @@ function replacePlaceholders(template, data) {
1091
1117
  // so the mail and its attachment agree.
1092
1118
  invoiceDate: formatDateValue(inv.issueDate),
1093
1119
  dueDate: formatDateValue(inv.dueDate),
1094
- totalAmount: (inv.currencySymbol || '') + Number(inv.total || 0).toFixed(2),
1095
- subtotal: (inv.currencySymbol || '') + Number(inv.subtotal || 0).toFixed(2),
1096
- taxAmount: (inv.currencySymbol || '') + Number(inv.taxAmount || 0).toFixed(2),
1097
- shippingAmount: (inv.currencySymbol || '') + Number(inv.shippingAmount || 0).toFixed(2),
1098
- discountAmount: (inv.currencySymbol || '') + Number(inv.discountAmount || 0).toFixed(2),
1099
- giftCardAmount: (inv.currencySymbol || '') + Number(inv.giftCardAmount || 0).toFixed(2),
1120
+ totalAmount: formatCurrencyValue(inv.total, sym),
1121
+ subtotal: formatCurrencyValue(inv.subtotal, sym),
1122
+ taxAmount: formatCurrencyValue(inv.taxAmount, sym),
1123
+ shippingAmount: formatCurrencyValue(inv.shippingAmount, sym),
1124
+ discountAmount: formatCurrencyValue(inv.discountAmount, sym),
1125
+ giftCardAmount: formatCurrencyValue(inv.giftCardAmount, sym),
1100
1126
  // What is left after a gift card; the whole total when none was used.
1101
- amountDue: (inv.currencySymbol || '') + Number(inv.amountDue != null ? inv.amountDue : (inv.total || 0)).toFixed(2),
1127
+ amountDue: formatCurrencyValue(inv.amountDue != null ? inv.amountDue : inv.total, sym),
1102
1128
  companyName: comp.name || '',
1103
1129
  companyEmail: comp.email || '',
1104
1130
  invoiceUrl: inv.pdfUrl || '',
@@ -1116,6 +1142,9 @@ function buildDataContext(invoiceData) {
1116
1142
  return {
1117
1143
  invoice: {
1118
1144
  number: invoiceData.invoiceNumber || '',
1145
+ // Paid or not decides which lines the email keeps (see omittedLineTokens).
1146
+ status: invoiceData.status || '',
1147
+ paidAt: invoiceData.paidAt || null,
1119
1148
  issueDate: invoiceData.issueDate || '',
1120
1149
  dueDate: invoiceData.dueDate || '',
1121
1150
  subtotal: invoiceData.subtotal || 0,
@@ -1 +1 @@
1
- {"version":3,"file":"invoice-html-code.js","sourceRoot":"","sources":["../../../src/invoice/invoice-html-code.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"invoice-html-code.js","sourceRoot":"","sources":["../../../src/invoice/invoice-html-code.ts"],"names":[],"mappings":"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"}