ztechno_core 0.0.126 → 0.0.128

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.
@@ -0,0 +1,12 @@
1
+ import { ZSQLService } from '../..';
2
+ import { ZOrm } from '../../core/orm/orm';
3
+ import { ZInvoiceItemTemplate } from '../types/mollie_types';
4
+ export declare class InvoiceItemTemplatesOrm extends ZOrm {
5
+ constructor(opt: { sqlService: ZSQLService; alias?: string });
6
+ create(template: Omit<ZInvoiceItemTemplate, 'id' | 'created_at' | 'updated_at'>): Promise<ZInvoiceItemTemplate>;
7
+ findById(id: number): Promise<ZInvoiceItemTemplate>;
8
+ findAll(): Promise<ZInvoiceItemTemplate[]>;
9
+ update(id: number, template: Partial<Omit<ZInvoiceItemTemplate, 'id' | 'created_at' | 'updated_at'>>): Promise<void>;
10
+ delete(id: number): Promise<void>;
11
+ createTable(): Promise<void>;
12
+ }
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ exports.InvoiceItemTemplatesOrm = void 0;
4
+ const orm_1 = require('../../core/orm/orm');
5
+ class InvoiceItemTemplatesOrm extends orm_1.ZOrm {
6
+ constructor(opt) {
7
+ super({ sqlService: opt.sqlService, alias: opt.alias ?? 'mollie_invoice_item_templates' });
8
+ }
9
+ async create(template) {
10
+ const res = await this.sqlService.query(
11
+ /*SQL*/ `
12
+ INSERT INTO \`${this.alias}\`
13
+ (name, item_type, description, quantity, unit_price, vat_rate, sort_order)
14
+ VALUES
15
+ (:name, :item_type, :description, :quantity, :unit_price, :vat_rate, :sort_order)
16
+ `,
17
+ {
18
+ name: template.name,
19
+ item_type: template.item_type ?? 'service',
20
+ description: template.description,
21
+ quantity: template.quantity,
22
+ unit_price: template.unit_price,
23
+ vat_rate: template.vat_rate,
24
+ sort_order: template.sort_order ?? 0,
25
+ },
26
+ );
27
+ const insertId = res.insertId;
28
+ return await this.findById(insertId);
29
+ }
30
+ async findById(id) {
31
+ const res = await this.sqlService.exec({
32
+ query: `SELECT * FROM \`${this.alias}\` WHERE id=:id LIMIT 1`,
33
+ params: { id },
34
+ });
35
+ return res[0];
36
+ }
37
+ async findAll() {
38
+ return await this.sqlService.exec({
39
+ query: `SELECT * FROM \`${this.alias}\` ORDER BY sort_order, name`,
40
+ });
41
+ }
42
+ async update(id, template) {
43
+ const sets = [];
44
+ const params = { id };
45
+ const fields = ['name', 'item_type', 'description', 'quantity', 'unit_price', 'vat_rate', 'sort_order'];
46
+ for (const field of fields) {
47
+ if (template[field] !== undefined) {
48
+ sets.push(`${field}=:${field}`);
49
+ params[field] = template[field];
50
+ }
51
+ }
52
+ if (sets.length === 0) return;
53
+ sets.push('updated_at=NOW()');
54
+ await this.sqlService.query(
55
+ /*SQL*/ `
56
+ UPDATE \`${this.alias}\` SET ${sets.join(', ')} WHERE id=:id
57
+ `,
58
+ params,
59
+ );
60
+ }
61
+ async delete(id) {
62
+ await this.sqlService.query(
63
+ /*SQL*/ `
64
+ DELETE FROM \`${this.alias}\` WHERE id=:id
65
+ `,
66
+ { id },
67
+ );
68
+ }
69
+ async createTable() {
70
+ await this.sqlService.query(/*SQL*/ `
71
+ CREATE TABLE IF NOT EXISTS \`${this.alias}\` (
72
+ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
73
+ name VARCHAR(100) NOT NULL,
74
+ item_type ENUM('service','subsidy') NOT NULL DEFAULT 'service',
75
+ description VARCHAR(255) NOT NULL,
76
+ quantity DECIMAL(10,2) NOT NULL DEFAULT 1,
77
+ unit_price DECIMAL(12,2) NOT NULL,
78
+ vat_rate DECIMAL(5,2) NOT NULL DEFAULT 0.00,
79
+ sort_order SMALLINT UNSIGNED NOT NULL DEFAULT 0,
80
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
81
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
82
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
83
+ `);
84
+ }
85
+ }
86
+ exports.InvoiceItemTemplatesOrm = InvoiceItemTemplatesOrm;
@@ -37,5 +37,6 @@ export declare class InvoicesOrm extends ZOrm {
37
37
  },
38
38
  ): Promise<void>;
39
39
  finalizePayToken(id: number): Promise<void>;
40
+ incrementTimesSent(id: number): Promise<void>;
40
41
  createTable(): Promise<void>;
41
42
  }
@@ -10,9 +10,9 @@ class InvoicesOrm extends orm_1.ZOrm {
10
10
  const res = await this.sqlService.query(
11
11
  /*SQL*/ `
12
12
  INSERT INTO \`${this.alias}\`
13
- (invoice_number, customer_id, subscription_id, subscription_period_start, subscription_period_end, mollie_customer_id, mollie_payment_id, pay_token_hash, pay_token_expires_at, pay_token_finalized_at, status, amount_due, amount_paid, currency, description, payment_terms, due_date, issued_at, paid_at, checkout_url, metadata)
13
+ (invoice_number, customer_id, subscription_id, subscription_period_start, subscription_period_end, mollie_customer_id, mollie_payment_id, pay_token_hash, pay_token_expires_at, pay_token_finalized_at, status, amount_due, amount_paid, currency, description, payment_terms, due_date, issued_at, paid_at, checkout_url, times_sent, metadata)
14
14
  VALUES
15
- (:invoice_number, :customer_id, :subscription_id, :subscription_period_start, :subscription_period_end, :mollie_customer_id, :mollie_payment_id, :pay_token_hash, :pay_token_expires_at, :pay_token_finalized_at, :status, :amount_due, :amount_paid, :currency, :description, :payment_terms, :due_date, :issued_at, :paid_at, :checkout_url, :metadata)
15
+ (:invoice_number, :customer_id, :subscription_id, :subscription_period_start, :subscription_period_end, :mollie_customer_id, :mollie_payment_id, :pay_token_hash, :pay_token_expires_at, :pay_token_finalized_at, :status, :amount_due, :amount_paid, :currency, :description, :payment_terms, :due_date, :issued_at, :paid_at, :checkout_url, :times_sent, :metadata)
16
16
  ON DUPLICATE KEY UPDATE
17
17
  subscription_id=VALUES(subscription_id),
18
18
  subscription_period_start=VALUES(subscription_period_start),
@@ -32,10 +32,11 @@ class InvoicesOrm extends orm_1.ZOrm {
32
32
  issued_at=VALUES(issued_at),
33
33
  paid_at=VALUES(paid_at),
34
34
  checkout_url=VALUES(checkout_url),
35
+ times_sent=VALUES(times_sent),
35
36
  metadata=VALUES(metadata),
36
37
  updated_at=NOW()
37
38
  `,
38
- invoice,
39
+ { ...invoice, times_sent: invoice.times_sent ?? 0 },
39
40
  );
40
41
  return res;
41
42
  }
@@ -140,6 +141,16 @@ class InvoicesOrm extends orm_1.ZOrm {
140
141
  { id },
141
142
  );
142
143
  }
144
+ async incrementTimesSent(id) {
145
+ await this.sqlService.query(
146
+ /*SQL*/ `
147
+ UPDATE \`${this.alias}\`
148
+ SET times_sent = times_sent + 1, updated_at=NOW()
149
+ WHERE id=:id
150
+ `,
151
+ { id },
152
+ );
153
+ }
143
154
  async createTable() {
144
155
  await this.sqlService.query(/*SQL*/ `
145
156
  CREATE TABLE IF NOT EXISTS \`${this.alias}\` (
@@ -154,7 +165,7 @@ class InvoicesOrm extends orm_1.ZOrm {
154
165
  pay_token_hash CHAR(64),
155
166
  pay_token_expires_at DATETIME,
156
167
  pay_token_finalized_at DATETIME,
157
- status ENUM('draft','pending','paid','failed','canceled','expired','refunded') NOT NULL DEFAULT 'draft',
168
+ status ENUM('draft','pending','paid','failed','canceled','expired','refunded','archived') NOT NULL DEFAULT 'draft',
158
169
  amount_due DECIMAL(12,2) NOT NULL,
159
170
  amount_paid DECIMAL(12,2) NOT NULL DEFAULT 0,
160
171
  currency CHAR(3) NOT NULL DEFAULT 'EUR',
@@ -164,6 +175,7 @@ class InvoicesOrm extends orm_1.ZOrm {
164
175
  issued_at DATETIME,
165
176
  paid_at DATETIME,
166
177
  checkout_url VARCHAR(512),
178
+ times_sent INT NOT NULL DEFAULT 0,
167
179
  metadata JSON NULL,
168
180
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
169
181
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
@@ -7,16 +7,19 @@ import { ZSQLService } from '../../core/sql_service';
7
7
  import {
8
8
  ZInvoice,
9
9
  CreateInvoiceInput,
10
+ ZInvoiceItem,
10
11
  ZInvoicePayment,
11
12
  CreateInvoiceOverrides,
12
13
  ZIssuedPayToken,
13
14
  ZPayResolveResult,
15
+ ZInvoiceItemTemplate,
14
16
  } from '../types/mollie_types';
15
17
  export declare class InvoiceService {
16
18
  private opt;
17
19
  private invoicesOrm;
18
20
  private itemsOrm;
19
21
  private paymentsOrm;
22
+ private templateOrm;
20
23
  private subscriptionsOrm;
21
24
  private subscriptionItemsOrm;
22
25
  private payTokenSecret;
@@ -40,6 +43,8 @@ export declare class InvoiceService {
40
43
  private ensurePayTokenSchema;
41
44
  private ensureSubscriptionInvoiceSchema;
42
45
  private ensureSubsidyItemTypeSchema;
46
+ private ensureSentCountSchema;
47
+ private ensureArchivedStatusSchema;
43
48
  private ensureInvoicePaymentSchema;
44
49
  private generateInvoiceNumber;
45
50
  private getWebhookUrl;
@@ -69,6 +74,25 @@ export declare class InvoiceService {
69
74
  listInvoices(): Promise<ZInvoice[]>;
70
75
  getInvoiceById(invoiceId: number): Promise<ZInvoice | undefined>;
71
76
  listPayments(invoice_id: number): Promise<ZInvoicePayment[]>;
77
+ getInvoiceItems(invoiceId: number): Promise<ZInvoiceItem[]>;
78
+ archiveInvoice(invoiceId: number): Promise<ZInvoice>;
79
+ duplicateInvoice(
80
+ sourceInvoiceId: number,
81
+ customerId: number,
82
+ ): Promise<{
83
+ invoice: ZInvoice;
84
+ pay: ZIssuedPayToken;
85
+ }>;
86
+ createItemTemplate(
87
+ template: Omit<ZInvoiceItemTemplate, 'id' | 'created_at' | 'updated_at'>,
88
+ ): Promise<ZInvoiceItemTemplate>;
89
+ listItemTemplates(): Promise<ZInvoiceItemTemplate[]>;
90
+ getItemTemplate(id: number): Promise<ZInvoiceItemTemplate>;
91
+ updateItemTemplate(
92
+ id: number,
93
+ template: Partial<Omit<ZInvoiceItemTemplate, 'id' | 'created_at' | 'updated_at'>>,
94
+ ): Promise<ZInvoiceItemTemplate>;
95
+ deleteItemTemplate(id: number): Promise<void>;
72
96
  createInvoiceWithPayment(input: CreateInvoiceInput): Promise<{
73
97
  invoice: ZInvoice;
74
98
  checkoutUrl: string;
@@ -11,6 +11,7 @@ const path_1 = __importDefault(require('path'));
11
11
  const fs_1 = __importDefault(require('fs'));
12
12
  const crypto_1 = __importDefault(require('crypto'));
13
13
  const invoice_items_orm_1 = require('../orm/invoice_items_orm');
14
+ const invoice_item_templates_orm_1 = require('../orm/invoice_item_templates_orm');
14
15
  const invoice_payments_orm_1 = require('../orm/invoice_payments_orm');
15
16
  const invoices_orm_1 = require('../orm/invoices_orm');
16
17
  const subscription_items_orm_1 = require('../orm/subscription_items_orm');
@@ -31,6 +32,7 @@ class InvoiceService {
31
32
  this.invoicesOrm = new invoices_orm_1.InvoicesOrm({ sqlService: opt.sqlService });
32
33
  this.itemsOrm = new invoice_items_orm_1.InvoiceItemsOrm({ sqlService: opt.sqlService });
33
34
  this.paymentsOrm = new invoice_payments_orm_1.InvoicePaymentsOrm({ sqlService: opt.sqlService });
35
+ this.templateOrm = new invoice_item_templates_orm_1.InvoiceItemTemplatesOrm({ sqlService: opt.sqlService });
34
36
  this.subscriptionsOrm = new subscriptions_orm_1.SubscriptionsOrm({ sqlService: opt.sqlService });
35
37
  this.subscriptionItemsOrm = new subscription_items_orm_1.SubscriptionItemsOrm({ sqlService: opt.sqlService });
36
38
  this.mailService = opt.mailService;
@@ -41,10 +43,13 @@ class InvoiceService {
41
43
  await this.invoicesOrm.ensureTableExists();
42
44
  await this.itemsOrm.ensureTableExists();
43
45
  await this.paymentsOrm.ensureTableExists();
46
+ await this.templateOrm.ensureTableExists();
44
47
  await this.ensurePayTokenSchema();
45
48
  await this.ensureSubscriptionInvoiceSchema();
46
49
  await this.ensureInvoicePaymentSchema();
47
50
  await this.ensureSubsidyItemTypeSchema();
51
+ await this.ensureSentCountSchema();
52
+ await this.ensureArchivedStatusSchema();
48
53
  }
49
54
  async ensurePayTokenSchema() {
50
55
  const table = this.invoicesOrm.alias;
@@ -104,6 +109,31 @@ class InvoiceService {
104
109
  );
105
110
  }
106
111
  }
112
+ async ensureSentCountSchema() {
113
+ const table = this.invoicesOrm.alias;
114
+ const rows = await this.opt.sqlService.exec({
115
+ query: `SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=:schema AND TABLE_NAME=:tableName AND COLUMN_NAME='times_sent' LIMIT 1`,
116
+ params: { schema: this.opt.sqlService.database, tableName: table },
117
+ });
118
+ if (!rows?.[0]) {
119
+ await this.opt.sqlService.query(
120
+ `ALTER TABLE \`${table}\` ADD COLUMN times_sent INT NOT NULL DEFAULT 0 AFTER checkout_url`,
121
+ );
122
+ }
123
+ }
124
+ async ensureArchivedStatusSchema() {
125
+ const table = this.invoicesOrm.alias;
126
+ const rows = await this.opt.sqlService.exec({
127
+ query: `SELECT COLUMN_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=:schema AND TABLE_NAME=:tableName AND COLUMN_NAME='status' LIMIT 1`,
128
+ params: { schema: this.opt.sqlService.database, tableName: table },
129
+ });
130
+ const colType = rows?.[0]?.COLUMN_TYPE ?? '';
131
+ if (colType && !colType.includes('archived')) {
132
+ await this.opt.sqlService.query(
133
+ `ALTER TABLE \`${table}\` MODIFY COLUMN status ENUM('draft','pending','paid','failed','canceled','expired','refunded','archived') NOT NULL DEFAULT 'draft'`,
134
+ );
135
+ }
136
+ }
107
137
  async ensureInvoicePaymentSchema() {
108
138
  const table = this.paymentsOrm.alias;
109
139
  const ensureColumn = async (column, sqlType) => {
@@ -389,6 +419,57 @@ class InvoiceService {
389
419
  async listPayments(invoice_id) {
390
420
  return await this.paymentsOrm.findByInvoice(invoice_id);
391
421
  }
422
+ async getInvoiceItems(invoiceId) {
423
+ return await this.itemsOrm.findByInvoice(invoiceId);
424
+ }
425
+ // ==================== Archive ====================
426
+ async archiveInvoice(invoiceId) {
427
+ const invoice = await this.invoicesOrm.findById(invoiceId);
428
+ if (!invoice) {
429
+ throw new Error(`Invoice ${invoiceId} not found`);
430
+ }
431
+ if (invoice.status === 'paid') {
432
+ throw new Error(`Cannot archive a paid invoice (${invoice.invoice_number})`);
433
+ }
434
+ if (invoice.status === 'archived') {
435
+ return invoice;
436
+ }
437
+ await this.invoicesOrm.updateStatus(invoiceId, 'archived');
438
+ return await this.invoicesOrm.findById(invoiceId);
439
+ }
440
+ // ==================== Duplicate ====================
441
+ async duplicateInvoice(sourceInvoiceId, customerId) {
442
+ const items = await this.itemsOrm.findByInvoice(sourceInvoiceId);
443
+ if (items.length === 0) {
444
+ throw new Error(`Source invoice ${sourceInvoiceId} has no items to duplicate`);
445
+ }
446
+ const mappedItems = items.map((it) => ({
447
+ description: it.description,
448
+ quantity: it.quantity,
449
+ unit_price: it.unit_price,
450
+ vat_rate: it.vat_rate,
451
+ item_type: it.item_type,
452
+ sort_order: it.sort_order,
453
+ }));
454
+ return await this.createInvoiceDraft({ customer_id: customerId, items: mappedItems });
455
+ }
456
+ // ==================== Item Templates ====================
457
+ async createItemTemplate(template) {
458
+ return await this.templateOrm.create(template);
459
+ }
460
+ async listItemTemplates() {
461
+ return await this.templateOrm.findAll();
462
+ }
463
+ async getItemTemplate(id) {
464
+ return await this.templateOrm.findById(id);
465
+ }
466
+ async updateItemTemplate(id, template) {
467
+ await this.templateOrm.update(id, template);
468
+ return await this.templateOrm.findById(id);
469
+ }
470
+ async deleteItemTemplate(id) {
471
+ await this.templateOrm.delete(id);
472
+ }
392
473
  async createInvoiceWithPayment(input) {
393
474
  const draft = await this.createInvoiceDraft(input);
394
475
  const payment = await this.createMolliePaymentForInvoice(draft.invoice);
@@ -895,6 +976,7 @@ class InvoiceService {
895
976
  },
896
977
  ],
897
978
  });
979
+ await this.invoicesOrm.incrementTimesSent(invoiceId);
898
980
  return {
899
981
  invoice_number: invoice.invoice_number,
900
982
  recipient: to,
@@ -53,7 +53,7 @@ export type CreateInvoiceOverrides = {
53
53
  mollie_payment_id?: string | null;
54
54
  checkout_url?: string | null;
55
55
  };
56
- export type ZInvoiceStatus = 'draft' | 'pending' | 'paid' | 'failed' | 'canceled' | 'expired' | 'refunded';
56
+ export type ZInvoiceStatus = 'draft' | 'pending' | 'paid' | 'failed' | 'canceled' | 'expired' | 'refunded' | 'archived';
57
57
  export type ZInvoice = {
58
58
  id?: number;
59
59
  invoice_number: string;
@@ -76,6 +76,7 @@ export type ZInvoice = {
76
76
  issued_at?: string | null;
77
77
  paid_at?: string | null;
78
78
  checkout_url?: string | null;
79
+ times_sent?: number;
79
80
  metadata?: any;
80
81
  created_at?: string | Date;
81
82
  updated_at?: string | Date;
@@ -145,6 +146,18 @@ export type ZSubscription = {
145
146
  created_at?: string | Date;
146
147
  updated_at?: string | Date;
147
148
  };
149
+ export type ZInvoiceItemTemplate = {
150
+ id?: number;
151
+ name: string;
152
+ item_type?: ZInvoiceItemType;
153
+ description: string;
154
+ quantity: number;
155
+ unit_price: number;
156
+ vat_rate: number;
157
+ sort_order?: number;
158
+ created_at?: string | Date;
159
+ updated_at?: string | Date;
160
+ };
148
161
  export type ZIssuedPayToken = {
149
162
  token: string;
150
163
  expiresAt: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ztechno_core",
3
- "version": "0.0.126",
3
+ "version": "0.0.128",
4
4
  "description": "Core files for ztechno framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",