@zentring/zinvoice 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,3274 @@
1
+ import { createHash } from 'crypto';
2
+
3
+ // src/Provider.ts
4
+ var Provider = /* @__PURE__ */ ((Provider2) => {
5
+ Provider2["AMEGO"] = "amego";
6
+ return Provider2;
7
+ })(Provider || {});
8
+ var Capability = /* @__PURE__ */ ((Capability3) => {
9
+ Capability3["B2C"] = "b2c";
10
+ Capability3["B2B"] = "b2b";
11
+ Capability3["CARRIER"] = "carrier";
12
+ Capability3["DONATION"] = "donation";
13
+ Capability3["ALLOWANCE"] = "allowance";
14
+ Capability3["VOID"] = "void";
15
+ Capability3["EXCHANGE"] = "exchange";
16
+ Capability3["PRINT"] = "print";
17
+ Capability3["QUERY"] = "query";
18
+ Capability3["LIST"] = "list";
19
+ return Capability3;
20
+ })(Capability || {});
21
+ var PROVIDER_CAPABILITIES = {
22
+ ["amego" /* AMEGO */]: /* @__PURE__ */ new Set([
23
+ "b2c" /* B2C */,
24
+ "b2b" /* B2B */,
25
+ "carrier" /* CARRIER */,
26
+ "donation" /* DONATION */,
27
+ "allowance" /* ALLOWANCE */,
28
+ "void" /* VOID */,
29
+ "exchange" /* EXCHANGE */,
30
+ "print" /* PRINT */,
31
+ "query" /* QUERY */,
32
+ "list" /* LIST */
33
+ ])
34
+ };
35
+ var PROVIDER_NAMES = {
36
+ ["amego" /* AMEGO */]: "\u5149\u8CBF\u8CC7\u8A0A"
37
+ };
38
+
39
+ // src/errors/index.ts
40
+ var ZinvoiceError = class extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = "ZinvoiceError";
44
+ Object.setPrototypeOf(this, new.target.prototype);
45
+ }
46
+ };
47
+ var InvalidTaxIdError = class extends ZinvoiceError {
48
+ constructor(value) {
49
+ super(`Invalid tax ID (\u7D71\u4E00\u7DE8\u865F): ${value}`);
50
+ this.value = value;
51
+ this.name = "InvalidTaxIdError";
52
+ }
53
+ };
54
+ var InvalidCarrierCodeError = class extends ZinvoiceError {
55
+ constructor(value, carrierType) {
56
+ super(`Invalid carrier code for type ${carrierType}: ${value}`);
57
+ this.value = value;
58
+ this.carrierType = carrierType;
59
+ this.name = "InvalidCarrierCodeError";
60
+ }
61
+ };
62
+ var InvalidInvoiceNumberError = class extends ZinvoiceError {
63
+ constructor(value) {
64
+ super(`Invalid invoice number (\u767C\u7968\u865F\u78BC): ${value}`);
65
+ this.value = value;
66
+ this.name = "InvalidInvoiceNumberError";
67
+ }
68
+ };
69
+ var InvalidMoneyError = class extends ZinvoiceError {
70
+ constructor(value, reason) {
71
+ super(`Invalid money amount: ${value} - ${reason}`);
72
+ this.value = value;
73
+ this.name = "InvalidMoneyError";
74
+ }
75
+ };
76
+ var ProviderApiError = class extends ZinvoiceError {
77
+ constructor(provider, code, message) {
78
+ super(`[${provider}] API Error (${code}): ${message}`);
79
+ this.provider = provider;
80
+ this.code = code;
81
+ this.name = "ProviderApiError";
82
+ }
83
+ };
84
+ var NetworkError = class extends ZinvoiceError {
85
+ constructor(message, cause) {
86
+ super(`Network error: ${message}`);
87
+ this.cause = cause;
88
+ this.name = "NetworkError";
89
+ }
90
+ };
91
+ var ValidationError = class extends ZinvoiceError {
92
+ constructor(field, message) {
93
+ super(`Validation error on ${field}: ${message}`);
94
+ this.field = field;
95
+ this.name = "ValidationError";
96
+ }
97
+ };
98
+ var UnsupportedCapabilityError = class extends ZinvoiceError {
99
+ constructor(capability, provider) {
100
+ super(`Provider "${provider}" does not support capability: ${capability}`);
101
+ this.capability = capability;
102
+ this.provider = provider;
103
+ this.name = "UnsupportedCapabilityError";
104
+ }
105
+ };
106
+ var ProviderNotImplementedError = class extends ZinvoiceError {
107
+ constructor(provider) {
108
+ super(`Provider "${provider}" is not yet implemented`);
109
+ this.provider = provider;
110
+ this.name = "ProviderNotImplementedError";
111
+ }
112
+ };
113
+
114
+ // src/infrastructure/amego/AmegoConfig.ts
115
+ var AMEGO_ENDPOINTS = {
116
+ // Invoice endpoints (發票)
117
+ INVOICE_ISSUE: "/json/f0401",
118
+ // 開立發票 (自動配號)
119
+ INVOICE_ISSUE_CUSTOM: "/json/f0401_custom",
120
+ // 開立發票 (API 配號)
121
+ INVOICE_VOID: "/json/f0501",
122
+ // 作廢發票
123
+ INVOICE_QUERY: "/json/invoice_query",
124
+ // 查詢單張發票
125
+ INVOICE_LIST: "/json/invoice_list",
126
+ // 查詢發票列表
127
+ INVOICE_STATUS: "/json/invoice_status",
128
+ // 查詢發票狀態
129
+ INVOICE_FILE: "/json/invoice_file",
130
+ // 下載發票檔案 (PDF)
131
+ INVOICE_PRINT: "/json/invoice_print",
132
+ // 產出列印格式字串
133
+ // Allowance endpoints (折讓)
134
+ ALLOWANCE_ISSUE: "/json/g0401",
135
+ // 開立折讓
136
+ ALLOWANCE_VOID: "/json/g0501",
137
+ // 作廢折讓
138
+ ALLOWANCE_QUERY: "/json/allowance_query",
139
+ // 查詢單張折讓
140
+ ALLOWANCE_LIST: "/json/allowance_list",
141
+ // 查詢折讓列表
142
+ ALLOWANCE_STATUS: "/json/allowance_status",
143
+ // 查詢折讓狀態
144
+ ALLOWANCE_FILE: "/json/allowance_file",
145
+ // 下載折讓檔案 (PDF)
146
+ ALLOWANCE_PRINT: "/json/allowance_print",
147
+ // 產出折讓列印格式字串
148
+ // Utility endpoints (其他)
149
+ BARCODE_CHECK: "/json/barcode",
150
+ // 手機條碼查詢
151
+ BAN_QUERY: "/json/ban_query",
152
+ // 公司名稱查詢
153
+ TRACK_ALL: "/json/track_all",
154
+ // 所有字軌資料
155
+ TRACK_GET: "/json/track_get",
156
+ // 字軌取號 (API 配號專用)
157
+ TRACK_STATUS: "/json/track_status",
158
+ // 字軌狀態 (API 配號專用)
159
+ LOTTERY_TYPE: "/json/lottery_type",
160
+ // 獎項定義
161
+ LOTTERY_STATUS: "/json/lottery_status"
162
+ // 中獎發票
163
+ };
164
+ var AMEGO_DEFAULTS = {
165
+ TIMEOUT: 3e4,
166
+ BASE_URL: "https://invoice-api.amego.tw"
167
+ };
168
+ var AmegoSigner = class {
169
+ constructor(apiKey) {
170
+ this.apiKey = apiKey;
171
+ }
172
+ /**
173
+ * Generate signature for API request
174
+ *
175
+ * @param data - Request data as JSON string
176
+ * @param timestamp - Unix timestamp in seconds
177
+ * @returns MD5 signature in lowercase hex
178
+ */
179
+ sign(data, timestamp) {
180
+ const payload = `${data}${timestamp}${this.apiKey}`;
181
+ return createHash("md5").update(payload, "utf8").digest("hex").toLowerCase();
182
+ }
183
+ /**
184
+ * Generate current timestamp (Unix seconds)
185
+ */
186
+ static getTimestamp() {
187
+ return Math.floor(Date.now() / 1e3);
188
+ }
189
+ };
190
+
191
+ // src/infrastructure/amego/AmegoClient.ts
192
+ var AmegoClient = class {
193
+ baseUrl;
194
+ timeout;
195
+ signer;
196
+ sellerTaxId;
197
+ constructor(config) {
198
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
199
+ this.timeout = config.timeout ?? AMEGO_DEFAULTS.TIMEOUT;
200
+ this.signer = new AmegoSigner(config.apiKey);
201
+ this.sellerTaxId = config.sellerTaxId;
202
+ }
203
+ /**
204
+ * Send POST request to Amego API
205
+ *
206
+ * 請求格式:application/x-www-form-urlencoded
207
+ * 參數:
208
+ * - invoice: 統一編號
209
+ * - data: URL encoded JSON string
210
+ * - time: Unix timestamp
211
+ * - sign: MD5(data + time + apiKey)
212
+ */
213
+ async post(endpoint, data) {
214
+ const timestamp = AmegoSigner.getTimestamp();
215
+ const jsonData = JSON.stringify(data);
216
+ const signature = this.signer.sign(jsonData, timestamp);
217
+ const url = `${this.baseUrl}${endpoint}`;
218
+ const formData = new URLSearchParams();
219
+ formData.append("invoice", this.sellerTaxId);
220
+ formData.append("data", jsonData);
221
+ formData.append("time", timestamp.toString());
222
+ formData.append("sign", signature);
223
+ try {
224
+ const controller = new AbortController();
225
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
226
+ const response = await fetch(url, {
227
+ method: "POST",
228
+ headers: {
229
+ "Content-Type": "application/x-www-form-urlencoded"
230
+ },
231
+ body: formData.toString(),
232
+ signal: controller.signal
233
+ });
234
+ clearTimeout(timeoutId);
235
+ if (!response.ok) {
236
+ throw new ProviderApiError(
237
+ "amego",
238
+ `HTTP ${response.status}`,
239
+ `HTTP error: ${response.status} ${response.statusText}`
240
+ );
241
+ }
242
+ const result = await response.json();
243
+ if (result.code !== 0) {
244
+ throw new ProviderApiError(
245
+ "amego",
246
+ String(result.code),
247
+ result.msg || "Unknown error"
248
+ );
249
+ }
250
+ return result;
251
+ } catch (error) {
252
+ if (error instanceof ProviderApiError) {
253
+ throw error;
254
+ }
255
+ if (error instanceof Error) {
256
+ if (error.name === "AbortError") {
257
+ throw new NetworkError(`Request timeout after ${this.timeout}ms`);
258
+ }
259
+ throw new NetworkError(error.message);
260
+ }
261
+ throw new NetworkError("Unknown network error");
262
+ }
263
+ }
264
+ /**
265
+ * Send POST request and return raw response (for debugging)
266
+ */
267
+ async postRaw(endpoint, data) {
268
+ const timestamp = AmegoSigner.getTimestamp();
269
+ const jsonData = JSON.stringify(data);
270
+ const signature = this.signer.sign(jsonData, timestamp);
271
+ const url = `${this.baseUrl}${endpoint}`;
272
+ const formData = new URLSearchParams();
273
+ formData.append("invoice", this.sellerTaxId);
274
+ formData.append("data", jsonData);
275
+ formData.append("time", timestamp.toString());
276
+ formData.append("sign", signature);
277
+ const controller = new AbortController();
278
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
279
+ const response = await fetch(url, {
280
+ method: "POST",
281
+ headers: {
282
+ "Content-Type": "application/x-www-form-urlencoded"
283
+ },
284
+ body: formData.toString(),
285
+ signal: controller.signal
286
+ });
287
+ clearTimeout(timeoutId);
288
+ const headers = {};
289
+ response.headers.forEach((value, key) => {
290
+ headers[key] = value;
291
+ });
292
+ const body = await response.text();
293
+ let parsed;
294
+ try {
295
+ parsed = JSON.parse(body);
296
+ } catch {
297
+ }
298
+ return {
299
+ status: response.status,
300
+ statusText: response.statusText,
301
+ headers,
302
+ body,
303
+ parsed
304
+ };
305
+ }
306
+ };
307
+
308
+ // src/domain/shared/Money.ts
309
+ var Money = class _Money {
310
+ constructor(amount) {
311
+ this.amount = amount;
312
+ }
313
+ /**
314
+ * Create Money from a number
315
+ * @throws InvalidMoneyError if the amount is invalid
316
+ */
317
+ static create(amount) {
318
+ if (!Number.isFinite(amount)) {
319
+ throw new InvalidMoneyError(amount, "Amount must be a finite number");
320
+ }
321
+ return new _Money(amount);
322
+ }
323
+ /**
324
+ * Create Money representing zero
325
+ */
326
+ static zero() {
327
+ return new _Money(0);
328
+ }
329
+ /**
330
+ * Create Money from cents (integer)
331
+ */
332
+ static fromCents(cents) {
333
+ if (!Number.isInteger(cents)) {
334
+ throw new InvalidMoneyError(cents, "Cents must be an integer");
335
+ }
336
+ return new _Money(cents / 100);
337
+ }
338
+ /**
339
+ * Get the numeric value
340
+ */
341
+ toNumber() {
342
+ return this.amount;
343
+ }
344
+ /**
345
+ * Get value rounded to integer
346
+ */
347
+ toInteger() {
348
+ return Math.round(this.amount);
349
+ }
350
+ /**
351
+ * Get value in cents (integer)
352
+ */
353
+ toCents() {
354
+ return Math.round(this.amount * 100);
355
+ }
356
+ /**
357
+ * Add another Money value
358
+ */
359
+ add(other) {
360
+ return new _Money(this.amount + other.amount);
361
+ }
362
+ /**
363
+ * Subtract another Money value
364
+ */
365
+ subtract(other) {
366
+ return new _Money(this.amount - other.amount);
367
+ }
368
+ /**
369
+ * Multiply by a factor
370
+ */
371
+ multiply(factor) {
372
+ return new _Money(this.amount * factor);
373
+ }
374
+ /**
375
+ * Divide by a divisor
376
+ */
377
+ divide(divisor) {
378
+ if (divisor === 0) {
379
+ throw new InvalidMoneyError(this.amount, "Cannot divide by zero");
380
+ }
381
+ return new _Money(this.amount / divisor);
382
+ }
383
+ /**
384
+ * Check if this amount is zero
385
+ */
386
+ isZero() {
387
+ return this.amount === 0;
388
+ }
389
+ /**
390
+ * Check if this amount is positive
391
+ */
392
+ isPositive() {
393
+ return this.amount > 0;
394
+ }
395
+ /**
396
+ * Check if this amount is negative
397
+ */
398
+ isNegative() {
399
+ return this.amount < 0;
400
+ }
401
+ /**
402
+ * Get absolute value
403
+ */
404
+ abs() {
405
+ return new _Money(Math.abs(this.amount));
406
+ }
407
+ /**
408
+ * Round to specified decimal places
409
+ */
410
+ round(decimals = 0) {
411
+ const factor = Math.pow(10, decimals);
412
+ return new _Money(Math.round(this.amount * factor) / factor);
413
+ }
414
+ /**
415
+ * Format as string with specified decimal places
416
+ */
417
+ format(decimals = 2) {
418
+ return this.amount.toFixed(decimals);
419
+ }
420
+ /**
421
+ * Check equality with another Money
422
+ */
423
+ equals(other) {
424
+ return this.amount === other.amount;
425
+ }
426
+ /**
427
+ * Compare with another Money
428
+ * Returns: -1 if less, 0 if equal, 1 if greater
429
+ */
430
+ compareTo(other) {
431
+ if (this.amount < other.amount) return -1;
432
+ if (this.amount > other.amount) return 1;
433
+ return 0;
434
+ }
435
+ toString() {
436
+ return this.amount.toString();
437
+ }
438
+ };
439
+
440
+ // src/domain/shared/Carrier.ts
441
+ var Carrier = class _Carrier {
442
+ constructor(_type, _id) {
443
+ this._type = _type;
444
+ this._id = _id;
445
+ }
446
+ /**
447
+ * Create a "no carrier" instance
448
+ */
449
+ static none() {
450
+ return new _Carrier("" /* NONE */, "");
451
+ }
452
+ /**
453
+ * Create a mobile barcode carrier (手機條碼)
454
+ *
455
+ * @param barcode The mobile barcode (format: /XXXXXXX)
456
+ */
457
+ static mobile(barcode) {
458
+ const trimmed = barcode?.trim();
459
+ if (!trimmed) {
460
+ throw new ValidationError("carrier", "Mobile barcode is required");
461
+ }
462
+ const pattern = /^\/[0-9A-Z.+-]{7}$/;
463
+ if (!pattern.test(trimmed.toUpperCase())) {
464
+ throw new ValidationError(
465
+ "carrier",
466
+ "Invalid mobile barcode format. Expected: /XXXXXXX"
467
+ );
468
+ }
469
+ return new _Carrier("3J0002" /* MOBILE */, trimmed.toUpperCase());
470
+ }
471
+ /**
472
+ * Create a certificate carrier (自然人憑證)
473
+ *
474
+ * @param certId The certificate ID (16 characters)
475
+ */
476
+ static certificate(certId) {
477
+ const trimmed = certId?.trim();
478
+ if (!trimmed) {
479
+ throw new ValidationError("carrier", "Certificate ID is required");
480
+ }
481
+ if (!/^[A-Z0-9]{16}$/i.test(trimmed)) {
482
+ throw new ValidationError(
483
+ "carrier",
484
+ "Invalid certificate ID format. Expected: 16 alphanumeric characters"
485
+ );
486
+ }
487
+ return new _Carrier("CQ0001" /* CERTIFICATE */, trimmed.toUpperCase());
488
+ }
489
+ /**
490
+ * Create a custom carrier
491
+ */
492
+ static custom(type, id) {
493
+ if (type === "" /* NONE */) {
494
+ return _Carrier.none();
495
+ }
496
+ return new _Carrier(type, id?.trim() ?? "");
497
+ }
498
+ /**
499
+ * Get the carrier type
500
+ */
501
+ get type() {
502
+ return this._type;
503
+ }
504
+ /**
505
+ * Get the carrier type code (for API)
506
+ */
507
+ get typeCode() {
508
+ return this._type;
509
+ }
510
+ /**
511
+ * Get the carrier ID
512
+ */
513
+ get id() {
514
+ return this._id;
515
+ }
516
+ /**
517
+ * Check if this is an empty carrier
518
+ */
519
+ get isEmpty() {
520
+ return this._type === "" /* NONE */;
521
+ }
522
+ /**
523
+ * Check if this is a mobile barcode
524
+ */
525
+ get isMobile() {
526
+ return this._type === "3J0002" /* MOBILE */;
527
+ }
528
+ /**
529
+ * Check if this is a certificate
530
+ */
531
+ get isCertificate() {
532
+ return this._type === "CQ0001" /* CERTIFICATE */;
533
+ }
534
+ };
535
+
536
+ // src/domain/shared/Donation.ts
537
+ var Donation = class _Donation {
538
+ constructor(_code) {
539
+ this._code = _code;
540
+ }
541
+ /**
542
+ * Create a "no donation" instance
543
+ */
544
+ static none() {
545
+ return new _Donation("");
546
+ }
547
+ /**
548
+ * Create a donation with code (愛心碼)
549
+ *
550
+ * @param code The donation code (3-7 digits)
551
+ */
552
+ static code(code) {
553
+ const trimmed = code?.trim();
554
+ if (!trimmed) {
555
+ throw new ValidationError("donation", "Donation code is required");
556
+ }
557
+ if (!/^\d{3,7}$/.test(trimmed)) {
558
+ throw new ValidationError(
559
+ "donation",
560
+ "Invalid donation code format. Expected: 3-7 digits"
561
+ );
562
+ }
563
+ return new _Donation(trimmed);
564
+ }
565
+ /**
566
+ * Try to create a Donation, returns none() if invalid
567
+ */
568
+ static tryCreate(code) {
569
+ try {
570
+ return _Donation.code(code);
571
+ } catch {
572
+ return _Donation.none();
573
+ }
574
+ }
575
+ /**
576
+ * Get the donation code
577
+ */
578
+ get code() {
579
+ return this._code;
580
+ }
581
+ /**
582
+ * Check if this is an empty donation (not donating)
583
+ */
584
+ get isEmpty() {
585
+ return this._code.length === 0;
586
+ }
587
+ /**
588
+ * Check if this invoice is being donated
589
+ */
590
+ get isDonating() {
591
+ return this._code.length > 0;
592
+ }
593
+ /**
594
+ * Get the string value
595
+ */
596
+ toString() {
597
+ return this._code;
598
+ }
599
+ };
600
+
601
+ // src/domain/shared/TaxType.ts
602
+ var TaxType = /* @__PURE__ */ ((TaxType2) => {
603
+ TaxType2[TaxType2["TAXABLE"] = 1] = "TAXABLE";
604
+ TaxType2[TaxType2["ZERO_RATED"] = 2] = "ZERO_RATED";
605
+ TaxType2[TaxType2["TAX_EXEMPT"] = 3] = "TAX_EXEMPT";
606
+ TaxType2[TaxType2["SPECIAL_TAX"] = 4] = "SPECIAL_TAX";
607
+ TaxType2[TaxType2["MIXED"] = 9] = "MIXED";
608
+ return TaxType2;
609
+ })(TaxType || {});
610
+ var ZeroTaxRateReason = /* @__PURE__ */ ((ZeroTaxRateReason3) => {
611
+ ZeroTaxRateReason3[ZeroTaxRateReason3["EXPORT_GOODS"] = 71] = "EXPORT_GOODS";
612
+ ZeroTaxRateReason3[ZeroTaxRateReason3["EXPORT_SERVICES"] = 72] = "EXPORT_SERVICES";
613
+ ZeroTaxRateReason3[ZeroTaxRateReason3["DUTY_FREE_SHOP"] = 73] = "DUTY_FREE_SHOP";
614
+ ZeroTaxRateReason3[ZeroTaxRateReason3["BONDED_AREA"] = 74] = "BONDED_AREA";
615
+ ZeroTaxRateReason3[ZeroTaxRateReason3["INTERNATIONAL_TRANSPORT"] = 75] = "INTERNATIONAL_TRANSPORT";
616
+ ZeroTaxRateReason3[ZeroTaxRateReason3["INTERNATIONAL_VESSELS"] = 76] = "INTERNATIONAL_VESSELS";
617
+ ZeroTaxRateReason3[ZeroTaxRateReason3["VESSEL_SUPPLIES"] = 77] = "VESSEL_SUPPLIES";
618
+ ZeroTaxRateReason3[ZeroTaxRateReason3["BONDED_DIRECT_EXPORT"] = 78] = "BONDED_DIRECT_EXPORT";
619
+ ZeroTaxRateReason3[ZeroTaxRateReason3["BONDED_WAREHOUSE"] = 79] = "BONDED_WAREHOUSE";
620
+ return ZeroTaxRateReason3;
621
+ })(ZeroTaxRateReason || {});
622
+ var CustomsClearanceMark = /* @__PURE__ */ ((CustomsClearanceMark3) => {
623
+ CustomsClearanceMark3[CustomsClearanceMark3["NON_CUSTOMS"] = 1] = "NON_CUSTOMS";
624
+ CustomsClearanceMark3[CustomsClearanceMark3["CUSTOMS"] = 2] = "CUSTOMS";
625
+ return CustomsClearanceMark3;
626
+ })(CustomsClearanceMark || {});
627
+ var TAX_RATE = 0.05;
628
+
629
+ // src/domain/invoice/Invoice.ts
630
+ var InvoiceStatus = /* @__PURE__ */ ((InvoiceStatus2) => {
631
+ InvoiceStatus2[InvoiceStatus2["PENDING"] = 1] = "PENDING";
632
+ InvoiceStatus2[InvoiceStatus2["UPLOADING"] = 2] = "UPLOADING";
633
+ InvoiceStatus2[InvoiceStatus2["UPLOADED"] = 3] = "UPLOADED";
634
+ InvoiceStatus2[InvoiceStatus2["PROCESSING"] = 31] = "PROCESSING";
635
+ InvoiceStatus2[InvoiceStatus2["AWAITING_CONFIRMATION"] = 32] = "AWAITING_CONFIRMATION";
636
+ InvoiceStatus2[InvoiceStatus2["ERROR"] = 91] = "ERROR";
637
+ InvoiceStatus2[InvoiceStatus2["COMPLETED"] = 99] = "COMPLETED";
638
+ return InvoiceStatus2;
639
+ })(InvoiceStatus || {});
640
+ var InvoiceType = /* @__PURE__ */ ((InvoiceType2) => {
641
+ InvoiceType2["B2C_ISSUE"] = "C0401";
642
+ InvoiceType2["B2C_VOID"] = "C0501";
643
+ InvoiceType2["B2C_CANCEL"] = "C0701";
644
+ InvoiceType2["B2B_ISSUE"] = "A0401";
645
+ InvoiceType2["B2B_VOID"] = "A0501";
646
+ return InvoiceType2;
647
+ })(InvoiceType || {});
648
+ var Invoice = class _Invoice {
649
+ constructor(_orderId, _buyer, _items, _carrier, _donation, _remark, _trackApiCode, _taxType, _customsClearanceMark, _zeroTaxRateReason, _brandName, _pricesIncludeTax = true, _status = 1 /* PENDING */, _type = "C0401" /* B2C_ISSUE */) {
650
+ this._orderId = _orderId;
651
+ this._buyer = _buyer;
652
+ this._items = _items;
653
+ this._carrier = _carrier;
654
+ this._donation = _donation;
655
+ this._remark = _remark;
656
+ this._trackApiCode = _trackApiCode;
657
+ this._taxType = _taxType;
658
+ this._customsClearanceMark = _customsClearanceMark;
659
+ this._zeroTaxRateReason = _zeroTaxRateReason;
660
+ this._brandName = _brandName;
661
+ this._pricesIncludeTax = _pricesIncludeTax;
662
+ this._status = _status;
663
+ this._type = _type;
664
+ }
665
+ _invoiceNumber;
666
+ _invoiceTime;
667
+ _randomNumber;
668
+ /**
669
+ * Create a new Invoice
670
+ */
671
+ static create(props) {
672
+ if (!props.items || props.items.length === 0) {
673
+ throw new ValidationError("items", "At least one item is required");
674
+ }
675
+ if (props.items.length > 9999) {
676
+ throw new ValidationError("items", "Maximum 9999 items allowed");
677
+ }
678
+ if (props.remark && props.remark.length > 200) {
679
+ throw new ValidationError(
680
+ "remark",
681
+ "Remark must not exceed 200 characters"
682
+ );
683
+ }
684
+ const taxType = _Invoice.determineTaxType(props.items);
685
+ if (taxType === 2 /* ZERO_RATED */) {
686
+ if (!props.customsClearanceMark) {
687
+ throw new ValidationError(
688
+ "customsClearanceMark",
689
+ "Customs clearance mark is required for zero-rated invoices"
690
+ );
691
+ }
692
+ if (!props.zeroTaxRateReason) {
693
+ throw new ValidationError(
694
+ "zeroTaxRateReason",
695
+ "Zero tax rate reason is required for zero-rated invoices"
696
+ );
697
+ }
698
+ }
699
+ const hasCarrier = props.carrier && !props.carrier.isEmpty;
700
+ const hasDonation = props.donation && props.donation.isDonating;
701
+ if (hasCarrier && hasDonation) {
702
+ throw new ValidationError(
703
+ "carrier",
704
+ "Cannot have both carrier and donation on the same invoice"
705
+ );
706
+ }
707
+ const isB2B = props.buyer.isCompany;
708
+ return new _Invoice(
709
+ props.orderId,
710
+ props.buyer,
711
+ props.items,
712
+ props.carrier ?? Carrier.none(),
713
+ props.donation ?? Donation.none(),
714
+ props.remark?.trim() ?? "",
715
+ props.trackApiCode?.trim() ?? "",
716
+ taxType,
717
+ props.customsClearanceMark,
718
+ props.zeroTaxRateReason,
719
+ props.brandName?.trim(),
720
+ props.pricesIncludeTax ?? true,
721
+ 1 /* PENDING */,
722
+ isB2B ? "A0401" /* B2B_ISSUE */ : "C0401" /* B2C_ISSUE */
723
+ );
724
+ }
725
+ /**
726
+ * Determine overall tax type from items
727
+ */
728
+ static determineTaxType(items) {
729
+ const taxTypes = new Set(items.map((item) => item.taxType));
730
+ if (taxTypes.size === 1) {
731
+ return items[0].taxType;
732
+ }
733
+ return 9 /* MIXED */;
734
+ }
735
+ // --- Calculated amounts ---
736
+ /**
737
+ * Calculate sales amount (應稅銷售額)
738
+ */
739
+ calculateSalesAmount() {
740
+ return this._items.filter((item) => item.taxType === 1 /* TAXABLE */).reduce((sum, item) => sum.add(item.amount), Money.zero()).round();
741
+ }
742
+ /**
743
+ * Calculate free tax sales amount (免稅銷售額)
744
+ */
745
+ calculateFreeTaxSalesAmount() {
746
+ return this._items.filter((item) => item.taxType === 3 /* TAX_EXEMPT */).reduce((sum, item) => sum.add(item.amount), Money.zero()).round();
747
+ }
748
+ /**
749
+ * Calculate zero tax sales amount (零稅率銷售額)
750
+ */
751
+ calculateZeroTaxSalesAmount() {
752
+ return this._items.filter((item) => item.taxType === 2 /* ZERO_RATED */).reduce((sum, item) => sum.add(item.amount), Money.zero()).round();
753
+ }
754
+ /**
755
+ * Calculate tax amount (營業稅額)
756
+ */
757
+ calculateTaxAmount() {
758
+ if (this._buyer.isAnonymous) {
759
+ return Money.zero();
760
+ }
761
+ const salesAmount = this.calculateSalesAmount();
762
+ if (this._pricesIncludeTax) {
763
+ const beforeTax = salesAmount.divide(1.05).round();
764
+ return salesAmount.subtract(beforeTax);
765
+ } else {
766
+ return salesAmount.multiply(0.05).round();
767
+ }
768
+ }
769
+ /**
770
+ * Calculate total amount (總計)
771
+ */
772
+ calculateTotalAmount() {
773
+ const salesAmount = this.calculateSalesAmount();
774
+ const freeTaxAmount = this.calculateFreeTaxSalesAmount();
775
+ const zeroTaxAmount = this.calculateZeroTaxSalesAmount();
776
+ const taxAmount = this.calculateTaxAmount();
777
+ if (this._pricesIncludeTax) {
778
+ return salesAmount.add(freeTaxAmount).add(zeroTaxAmount);
779
+ } else {
780
+ return salesAmount.add(freeTaxAmount).add(zeroTaxAmount).add(taxAmount);
781
+ }
782
+ }
783
+ // --- State mutations ---
784
+ /**
785
+ * Set invoice number after issuing
786
+ */
787
+ setInvoiceNumber(number, time, randomNumber) {
788
+ this._invoiceNumber = number;
789
+ this._invoiceTime = time;
790
+ this._randomNumber = randomNumber;
791
+ }
792
+ /**
793
+ * Update status
794
+ */
795
+ updateStatus(status) {
796
+ this._status = status;
797
+ }
798
+ /**
799
+ * Mark as voided
800
+ */
801
+ markAsVoided() {
802
+ this._type = this.isB2B ? "A0501" /* B2B_VOID */ : "C0501" /* B2C_VOID */;
803
+ }
804
+ // --- Getters ---
805
+ get orderId() {
806
+ return this._orderId;
807
+ }
808
+ get invoiceNumber() {
809
+ return this._invoiceNumber;
810
+ }
811
+ get invoiceTime() {
812
+ return this._invoiceTime;
813
+ }
814
+ get randomNumber() {
815
+ return this._randomNumber;
816
+ }
817
+ get buyer() {
818
+ return this._buyer;
819
+ }
820
+ get carrier() {
821
+ return this._carrier;
822
+ }
823
+ get donation() {
824
+ return this._donation;
825
+ }
826
+ get remark() {
827
+ return this._remark;
828
+ }
829
+ get trackApiCode() {
830
+ return this._trackApiCode;
831
+ }
832
+ get items() {
833
+ return this._items;
834
+ }
835
+ get taxType() {
836
+ return this._taxType;
837
+ }
838
+ get customsClearanceMark() {
839
+ return this._customsClearanceMark;
840
+ }
841
+ get zeroTaxRateReason() {
842
+ return this._zeroTaxRateReason;
843
+ }
844
+ get brandName() {
845
+ return this._brandName;
846
+ }
847
+ get pricesIncludeTax() {
848
+ return this._pricesIncludeTax;
849
+ }
850
+ get status() {
851
+ return this._status;
852
+ }
853
+ get type() {
854
+ return this._type;
855
+ }
856
+ /**
857
+ * Check if this is a B2B invoice (has buyer tax ID)
858
+ */
859
+ get isB2B() {
860
+ return this._buyer.isCompany;
861
+ }
862
+ /**
863
+ * Check if invoice has been issued
864
+ */
865
+ get isIssued() {
866
+ return this._invoiceNumber !== void 0;
867
+ }
868
+ /**
869
+ * Check if invoice has carrier
870
+ */
871
+ get hasCarrier() {
872
+ return !this._carrier.isEmpty;
873
+ }
874
+ /**
875
+ * Check if invoice is donated
876
+ */
877
+ get isDonated() {
878
+ return this._donation.isDonating;
879
+ }
880
+ // --- Backward compatibility getters (deprecated) ---
881
+ /** @deprecated Use buyer.taxId instead */
882
+ get buyerTaxId() {
883
+ return this._buyer.taxId;
884
+ }
885
+ /** @deprecated Use buyer.name instead */
886
+ get buyerName() {
887
+ return this._buyer.name;
888
+ }
889
+ /** @deprecated Use buyer.address instead */
890
+ get buyerAddress() {
891
+ return this._buyer.address;
892
+ }
893
+ /** @deprecated Use buyer.phone instead */
894
+ get buyerPhone() {
895
+ return this._buyer.phone;
896
+ }
897
+ /** @deprecated Use buyer.email instead */
898
+ get buyerEmail() {
899
+ return this._buyer.email;
900
+ }
901
+ /** @deprecated Use donation.code instead */
902
+ get donationCode() {
903
+ return this._donation.code;
904
+ }
905
+ };
906
+
907
+ // src/domain/allowance/Allowance.ts
908
+ var AllowanceStatus = /* @__PURE__ */ ((AllowanceStatus2) => {
909
+ AllowanceStatus2[AllowanceStatus2["PENDING"] = 1] = "PENDING";
910
+ AllowanceStatus2[AllowanceStatus2["UPLOADING"] = 2] = "UPLOADING";
911
+ AllowanceStatus2[AllowanceStatus2["UPLOADED"] = 3] = "UPLOADED";
912
+ AllowanceStatus2[AllowanceStatus2["PROCESSING"] = 31] = "PROCESSING";
913
+ AllowanceStatus2[AllowanceStatus2["AWAITING_CONFIRMATION"] = 32] = "AWAITING_CONFIRMATION";
914
+ AllowanceStatus2[AllowanceStatus2["ERROR"] = 91] = "ERROR";
915
+ AllowanceStatus2[AllowanceStatus2["COMPLETED"] = 99] = "COMPLETED";
916
+ return AllowanceStatus2;
917
+ })(AllowanceStatus || {});
918
+ var AllowanceType = /* @__PURE__ */ ((AllowanceType2) => {
919
+ AllowanceType2["B2C_ISSUE"] = "C0701";
920
+ AllowanceType2["B2C_VOID"] = "C0801";
921
+ AllowanceType2["B2B_ISSUE"] = "A0701";
922
+ AllowanceType2["B2B_VOID"] = "A0801";
923
+ return AllowanceType2;
924
+ })(AllowanceType || {});
925
+ var Allowance = class _Allowance {
926
+ constructor(_originalInvoiceNumber, _originalInvoiceDate, _buyerTaxId, _sellerTaxId, _buyerName, _sellerName, _items, _taxType, _pricesIncludeTax = true, _status = 1 /* PENDING */, _type = "C0701" /* B2C_ISSUE */) {
927
+ this._originalInvoiceNumber = _originalInvoiceNumber;
928
+ this._originalInvoiceDate = _originalInvoiceDate;
929
+ this._buyerTaxId = _buyerTaxId;
930
+ this._sellerTaxId = _sellerTaxId;
931
+ this._buyerName = _buyerName;
932
+ this._sellerName = _sellerName;
933
+ this._items = _items;
934
+ this._taxType = _taxType;
935
+ this._pricesIncludeTax = _pricesIncludeTax;
936
+ this._status = _status;
937
+ this._type = _type;
938
+ }
939
+ _allowanceNumber;
940
+ _allowanceDate;
941
+ /**
942
+ * Create a new Allowance
943
+ */
944
+ static create(props) {
945
+ if (!props.items || props.items.length === 0) {
946
+ throw new ValidationError("items", "At least one item is required");
947
+ }
948
+ if (props.items.length > 9999) {
949
+ throw new ValidationError("items", "Maximum 9999 items allowed");
950
+ }
951
+ const taxType = _Allowance.determineTaxType(props.items);
952
+ const isB2B = !props.buyerTaxId.isNone();
953
+ return new _Allowance(
954
+ props.originalInvoiceNumber,
955
+ props.originalInvoiceDate,
956
+ props.buyerTaxId,
957
+ props.sellerTaxId,
958
+ props.buyerName?.trim() ?? "",
959
+ props.sellerName?.trim() ?? "",
960
+ props.items,
961
+ taxType,
962
+ props.pricesIncludeTax ?? true,
963
+ 1 /* PENDING */,
964
+ isB2B ? "A0701" /* B2B_ISSUE */ : "C0701" /* B2C_ISSUE */
965
+ );
966
+ }
967
+ /**
968
+ * Determine overall tax type from items
969
+ */
970
+ static determineTaxType(items) {
971
+ const taxTypes = new Set(items.map((item) => item.taxType));
972
+ if (taxTypes.size === 1) {
973
+ return items[0].taxType;
974
+ }
975
+ return 9 /* MIXED */;
976
+ }
977
+ // --- Calculated amounts ---
978
+ /**
979
+ * Calculate taxable sales amount (應稅銷售額)
980
+ */
981
+ calculateTaxableAmount() {
982
+ return this._items.filter((item) => item.taxType === 1 /* TAXABLE */).reduce((sum, item) => sum.add(item.amount), Money.zero()).round();
983
+ }
984
+ /**
985
+ * Calculate free tax sales amount (免稅銷售額)
986
+ */
987
+ calculateFreeTaxAmount() {
988
+ return this._items.filter((item) => item.taxType === 3 /* TAX_EXEMPT */).reduce((sum, item) => sum.add(item.amount), Money.zero()).round();
989
+ }
990
+ /**
991
+ * Calculate zero tax sales amount (零稅率銷售額)
992
+ */
993
+ calculateZeroTaxAmount() {
994
+ return this._items.filter((item) => item.taxType === 2 /* ZERO_RATED */).reduce((sum, item) => sum.add(item.amount), Money.zero()).round();
995
+ }
996
+ /**
997
+ * Calculate tax amount (營業稅額)
998
+ */
999
+ calculateTaxAmount() {
1000
+ const taxableAmount = this.calculateTaxableAmount();
1001
+ if (this._pricesIncludeTax) {
1002
+ const beforeTax = taxableAmount.divide(1 + TAX_RATE).round();
1003
+ return taxableAmount.subtract(beforeTax);
1004
+ } else {
1005
+ return taxableAmount.multiply(TAX_RATE).round();
1006
+ }
1007
+ }
1008
+ /**
1009
+ * Calculate total amount (總計)
1010
+ */
1011
+ calculateTotalAmount() {
1012
+ const taxableAmount = this.calculateTaxableAmount();
1013
+ const freeTaxAmount = this.calculateFreeTaxAmount();
1014
+ const zeroTaxAmount = this.calculateZeroTaxAmount();
1015
+ const taxAmount = this.calculateTaxAmount();
1016
+ if (this._pricesIncludeTax) {
1017
+ return taxableAmount.add(freeTaxAmount).add(zeroTaxAmount);
1018
+ } else {
1019
+ return taxableAmount.add(freeTaxAmount).add(zeroTaxAmount).add(taxAmount);
1020
+ }
1021
+ }
1022
+ // --- State mutations ---
1023
+ /**
1024
+ * Set allowance number after issuing
1025
+ */
1026
+ setAllowanceNumber(number, date) {
1027
+ this._allowanceNumber = number;
1028
+ this._allowanceDate = date;
1029
+ }
1030
+ /**
1031
+ * Update status
1032
+ */
1033
+ updateStatus(status) {
1034
+ this._status = status;
1035
+ }
1036
+ /**
1037
+ * Mark as voided
1038
+ */
1039
+ markAsVoided() {
1040
+ this._type = this.isB2B ? "A0801" /* B2B_VOID */ : "C0801" /* B2C_VOID */;
1041
+ }
1042
+ // --- Getters ---
1043
+ get originalInvoiceNumber() {
1044
+ return this._originalInvoiceNumber;
1045
+ }
1046
+ get originalInvoiceDate() {
1047
+ return this._originalInvoiceDate;
1048
+ }
1049
+ get allowanceNumber() {
1050
+ return this._allowanceNumber;
1051
+ }
1052
+ get allowanceDate() {
1053
+ return this._allowanceDate;
1054
+ }
1055
+ get buyerTaxId() {
1056
+ return this._buyerTaxId;
1057
+ }
1058
+ get sellerTaxId() {
1059
+ return this._sellerTaxId;
1060
+ }
1061
+ get buyerName() {
1062
+ return this._buyerName;
1063
+ }
1064
+ get sellerName() {
1065
+ return this._sellerName;
1066
+ }
1067
+ get items() {
1068
+ return this._items;
1069
+ }
1070
+ get taxType() {
1071
+ return this._taxType;
1072
+ }
1073
+ get pricesIncludeTax() {
1074
+ return this._pricesIncludeTax;
1075
+ }
1076
+ get status() {
1077
+ return this._status;
1078
+ }
1079
+ get type() {
1080
+ return this._type;
1081
+ }
1082
+ /**
1083
+ * Check if this is a B2B allowance (has buyer tax ID)
1084
+ */
1085
+ get isB2B() {
1086
+ return !this._buyerTaxId.isNone();
1087
+ }
1088
+ /**
1089
+ * Check if allowance has been issued
1090
+ */
1091
+ get isIssued() {
1092
+ return this._allowanceNumber !== void 0;
1093
+ }
1094
+ };
1095
+
1096
+ // src/infrastructure/amego/AmegoMapper.ts
1097
+ var AmegoMapper = class _AmegoMapper {
1098
+ /**
1099
+ * Convert Invoice domain object to API payload
1100
+ */
1101
+ static toInvoicePayload(invoice) {
1102
+ const salesAmount = invoice.calculateSalesAmount().toNumber();
1103
+ const freeTaxSalesAmount = invoice.calculateFreeTaxSalesAmount().toNumber();
1104
+ const zeroTaxSalesAmount = invoice.calculateZeroTaxSalesAmount().toNumber();
1105
+ const taxAmount = invoice.calculateTaxAmount().toNumber();
1106
+ const totalAmount = invoice.calculateTotalAmount().toNumber();
1107
+ const payload = {
1108
+ OrderId: invoice.orderId.toString(),
1109
+ BuyerIdentifier: invoice.buyer.taxId.isNone() ? "0000000000" : invoice.buyer.taxId.toString(),
1110
+ BuyerName: invoice.buyer.name,
1111
+ ProductItem: invoice.items.map(_AmegoMapper.toInvoiceItemPayload),
1112
+ SalesAmount: salesAmount,
1113
+ FreeTaxSalesAmount: freeTaxSalesAmount,
1114
+ ZeroTaxSalesAmount: zeroTaxSalesAmount,
1115
+ TaxType: _AmegoMapper.toApiTaxType(invoice.taxType),
1116
+ TaxRate: "0.05",
1117
+ TaxAmount: taxAmount,
1118
+ TotalAmount: totalAmount,
1119
+ DetailVat: invoice.pricesIncludeTax ? 1 : 0
1120
+ };
1121
+ if (invoice.trackApiCode) {
1122
+ payload.TrackApiCode = invoice.trackApiCode;
1123
+ }
1124
+ if (invoice.buyer.address) {
1125
+ payload.BuyerAddress = invoice.buyer.address;
1126
+ }
1127
+ if (invoice.buyer.phone) {
1128
+ payload.BuyerTelephoneNumber = invoice.buyer.phone;
1129
+ }
1130
+ if (invoice.buyer.email) {
1131
+ payload.BuyerEmailAddress = invoice.buyer.email;
1132
+ }
1133
+ if (invoice.remark) {
1134
+ payload.MainRemark = invoice.remark;
1135
+ }
1136
+ if (invoice.hasCarrier) {
1137
+ payload.CarrierType = invoice.carrier.typeCode;
1138
+ payload.CarrierId1 = invoice.carrier.id;
1139
+ payload.CarrierId2 = invoice.carrier.id;
1140
+ }
1141
+ if (invoice.isDonated) {
1142
+ payload.NPOBAN = invoice.donation.code;
1143
+ }
1144
+ if (invoice.customsClearanceMark !== void 0) {
1145
+ payload.CustomsClearanceMark = invoice.customsClearanceMark;
1146
+ }
1147
+ if (invoice.zeroTaxRateReason !== void 0) {
1148
+ payload.ZeroTaxRateReason = invoice.zeroTaxRateReason;
1149
+ }
1150
+ if (invoice.brandName) {
1151
+ payload.BrandName = invoice.brandName;
1152
+ }
1153
+ return payload;
1154
+ }
1155
+ /**
1156
+ * Convert InvoiceItem to API payload (ProductItem)
1157
+ */
1158
+ static toInvoiceItemPayload(item) {
1159
+ const payload = {
1160
+ Description: item.description,
1161
+ Quantity: item.quantity,
1162
+ UnitPrice: item.unitPrice.toNumber(),
1163
+ Amount: item.amount.toNumber(),
1164
+ TaxType: _AmegoMapper.toApiTaxType(item.taxType)
1165
+ };
1166
+ if (item.unit) {
1167
+ payload.Unit = item.unit;
1168
+ }
1169
+ if (item.remark) {
1170
+ payload.Remark = item.remark;
1171
+ }
1172
+ return payload;
1173
+ }
1174
+ /**
1175
+ * Convert Allowance domain object to API payload
1176
+ */
1177
+ static toAllowancePayload(allowance, allowanceNumber) {
1178
+ let totalAmount = 0;
1179
+ let taxAmount = 0;
1180
+ const items = allowance.items.map((item) => {
1181
+ const amount = item.amount.toNumber();
1182
+ const tax = Math.round(amount * 0.05);
1183
+ totalAmount += amount;
1184
+ taxAmount += tax;
1185
+ return {
1186
+ OriginalInvoiceNumber: allowance.originalInvoiceNumber.toString(),
1187
+ OriginalInvoiceDate: _AmegoMapper.formatDateNumber(
1188
+ allowance.originalInvoiceDate
1189
+ ),
1190
+ OriginalDescription: item.originalDescription,
1191
+ Quantity: item.quantity,
1192
+ UnitPrice: item.unitPrice.toNumber(),
1193
+ Amount: amount,
1194
+ Tax: tax,
1195
+ TaxType: _AmegoMapper.toApiTaxType(item.taxType),
1196
+ Unit: item.unit
1197
+ };
1198
+ });
1199
+ const payload = {
1200
+ AllowanceNumber: allowanceNumber,
1201
+ AllowanceDate: _AmegoMapper.formatDate(/* @__PURE__ */ new Date()),
1202
+ AllowanceType: 2,
1203
+ // 賣方折讓證明通知單
1204
+ BuyerIdentifier: allowance.buyerTaxId.isNone() ? "0000000000" : allowance.buyerTaxId.toString(),
1205
+ BuyerName: allowance.buyerName ?? "",
1206
+ ProductItem: items,
1207
+ TaxAmount: taxAmount,
1208
+ TotalAmount: totalAmount
1209
+ };
1210
+ return payload;
1211
+ }
1212
+ /**
1213
+ * Convert AllowanceItem to API payload
1214
+ */
1215
+ static toAllowanceItemPayload(item, originalInvoiceNumber, originalInvoiceDate) {
1216
+ const amount = item.amount.toNumber();
1217
+ const tax = Math.round(amount * 0.05);
1218
+ return {
1219
+ OriginalInvoiceNumber: originalInvoiceNumber,
1220
+ OriginalInvoiceDate: _AmegoMapper.formatDateNumber(originalInvoiceDate),
1221
+ OriginalDescription: item.originalDescription,
1222
+ Quantity: item.quantity,
1223
+ UnitPrice: item.unitPrice.toNumber(),
1224
+ Amount: amount,
1225
+ Tax: tax,
1226
+ TaxType: _AmegoMapper.toApiTaxType(item.taxType),
1227
+ Unit: item.unit
1228
+ };
1229
+ }
1230
+ /**
1231
+ * Convert domain TaxType to API tax type number
1232
+ */
1233
+ static toApiTaxType(taxType) {
1234
+ switch (taxType) {
1235
+ case 1 /* TAXABLE */:
1236
+ return 1;
1237
+ case 2 /* ZERO_RATED */:
1238
+ return 2;
1239
+ case 3 /* TAX_EXEMPT */:
1240
+ return 3;
1241
+ case 9 /* MIXED */:
1242
+ return 9;
1243
+ default:
1244
+ return 1;
1245
+ }
1246
+ }
1247
+ /**
1248
+ * Convert API tax type number to domain TaxType
1249
+ */
1250
+ static fromApiTaxType(apiTaxType) {
1251
+ switch (apiTaxType) {
1252
+ case 1:
1253
+ return 1 /* TAXABLE */;
1254
+ case 2:
1255
+ return 2 /* ZERO_RATED */;
1256
+ case 3:
1257
+ return 3 /* TAX_EXEMPT */;
1258
+ case 4:
1259
+ return 1 /* TAXABLE */;
1260
+ // 特種稅率 -> 應稅
1261
+ case 9:
1262
+ return 9 /* MIXED */;
1263
+ default:
1264
+ return 1 /* TAXABLE */;
1265
+ }
1266
+ }
1267
+ /**
1268
+ * Convert API status code to InvoiceStatus
1269
+ */
1270
+ static toInvoiceStatus(status) {
1271
+ switch (status) {
1272
+ case 1:
1273
+ return 1 /* PENDING */;
1274
+ case 2:
1275
+ return 2 /* UPLOADING */;
1276
+ case 3:
1277
+ return 3 /* UPLOADED */;
1278
+ case 31:
1279
+ return 31 /* PROCESSING */;
1280
+ case 32:
1281
+ return 32 /* AWAITING_CONFIRMATION */;
1282
+ case 91:
1283
+ return 91 /* ERROR */;
1284
+ case 99:
1285
+ return 99 /* COMPLETED */;
1286
+ default:
1287
+ return 1 /* PENDING */;
1288
+ }
1289
+ }
1290
+ /**
1291
+ * Convert API status code to AllowanceStatus
1292
+ */
1293
+ static toAllowanceStatus(status) {
1294
+ switch (status) {
1295
+ case 1:
1296
+ return 1 /* PENDING */;
1297
+ case 2:
1298
+ return 2 /* UPLOADING */;
1299
+ case 3:
1300
+ return 3 /* UPLOADED */;
1301
+ case 31:
1302
+ return 31 /* PROCESSING */;
1303
+ case 32:
1304
+ return 32 /* AWAITING_CONFIRMATION */;
1305
+ case 91:
1306
+ return 91 /* ERROR */;
1307
+ case 99:
1308
+ return 99 /* COMPLETED */;
1309
+ default:
1310
+ return 1 /* PENDING */;
1311
+ }
1312
+ }
1313
+ /**
1314
+ * Format date to YYYYMMDD string
1315
+ */
1316
+ static formatDate(date) {
1317
+ const year = date.getFullYear();
1318
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1319
+ const day = String(date.getDate()).padStart(2, "0");
1320
+ return `${year}${month}${day}`;
1321
+ }
1322
+ /**
1323
+ * Format date to YYYYMMDD number
1324
+ */
1325
+ static formatDateNumber(date) {
1326
+ return parseInt(_AmegoMapper.formatDate(date), 10);
1327
+ }
1328
+ /**
1329
+ * Parse YYYYMMDD (string or number) to Date
1330
+ */
1331
+ static parseDate(dateValue) {
1332
+ const dateStr = String(dateValue);
1333
+ const year = parseInt(dateStr.substring(0, 4), 10);
1334
+ const month = parseInt(dateStr.substring(4, 6), 10) - 1;
1335
+ const day = parseInt(dateStr.substring(6, 8), 10);
1336
+ return new Date(year, month, day);
1337
+ }
1338
+ /**
1339
+ * Parse Unix timestamp to Date
1340
+ */
1341
+ static parseUnixTimestamp(timestamp) {
1342
+ return new Date(timestamp * 1e3);
1343
+ }
1344
+ /**
1345
+ * Parse ISO date string to Date
1346
+ */
1347
+ static parseIsoDate(isoStr) {
1348
+ return new Date(isoStr);
1349
+ }
1350
+ };
1351
+
1352
+ // src/domain/invoice/InvoiceNumber.ts
1353
+ var InvoiceNumber = class _InvoiceNumber {
1354
+ constructor(value) {
1355
+ this.value = value;
1356
+ }
1357
+ static PATTERN = /^[A-Z]{2}\d{8}$/;
1358
+ /**
1359
+ * Create an InvoiceNumber from string
1360
+ * @throws InvalidInvoiceNumberError if format is invalid
1361
+ */
1362
+ static create(value) {
1363
+ const normalized = value.trim().toUpperCase();
1364
+ if (!_InvoiceNumber.isValid(normalized)) {
1365
+ throw new InvalidInvoiceNumberError(value);
1366
+ }
1367
+ return new _InvoiceNumber(normalized);
1368
+ }
1369
+ /**
1370
+ * Try to create an InvoiceNumber, return null if invalid
1371
+ */
1372
+ static tryCreate(value) {
1373
+ try {
1374
+ return _InvoiceNumber.create(value);
1375
+ } catch {
1376
+ return null;
1377
+ }
1378
+ }
1379
+ /**
1380
+ * Validate invoice number format
1381
+ */
1382
+ static isValid(value) {
1383
+ return _InvoiceNumber.PATTERN.test(value);
1384
+ }
1385
+ /**
1386
+ * Get the track (字軌) - first 2 letters
1387
+ */
1388
+ getTrack() {
1389
+ return this.value.substring(0, 2);
1390
+ }
1391
+ /**
1392
+ * Get the number part - last 8 digits
1393
+ */
1394
+ getNumber() {
1395
+ return this.value.substring(2);
1396
+ }
1397
+ /**
1398
+ * Get the full invoice number
1399
+ */
1400
+ toString() {
1401
+ return this.value;
1402
+ }
1403
+ /**
1404
+ * Check equality
1405
+ */
1406
+ equals(other) {
1407
+ return this.value === other.value;
1408
+ }
1409
+ };
1410
+
1411
+ // src/domain/invoice/InvoiceItem.ts
1412
+ var InvoiceItem = class _InvoiceItem {
1413
+ constructor(_description, _quantity, _unitPrice, _amount, _unit, _remark, _taxType) {
1414
+ this._description = _description;
1415
+ this._quantity = _quantity;
1416
+ this._unitPrice = _unitPrice;
1417
+ this._amount = _amount;
1418
+ this._unit = _unit;
1419
+ this._remark = _remark;
1420
+ this._taxType = _taxType;
1421
+ }
1422
+ /**
1423
+ * Create an InvoiceItem
1424
+ */
1425
+ static create(props) {
1426
+ if (!props.description || props.description.trim().length === 0) {
1427
+ throw new ValidationError("description", "Description is required");
1428
+ }
1429
+ if (props.description.length > 256) {
1430
+ throw new ValidationError(
1431
+ "description",
1432
+ "Description must not exceed 256 characters"
1433
+ );
1434
+ }
1435
+ if (props.quantity === 0) {
1436
+ throw new ValidationError("quantity", "Quantity cannot be zero");
1437
+ }
1438
+ if (props.unit && props.unit.length > 6) {
1439
+ throw new ValidationError("unit", "Unit must not exceed 6 characters");
1440
+ }
1441
+ if (props.remark && props.remark.length > 40) {
1442
+ throw new ValidationError("remark", "Remark must not exceed 40 characters");
1443
+ }
1444
+ return new _InvoiceItem(
1445
+ props.description.trim(),
1446
+ props.quantity,
1447
+ props.unitPrice,
1448
+ props.amount,
1449
+ props.unit?.trim() ?? "",
1450
+ props.remark?.trim() ?? "",
1451
+ props.taxType ?? 1 /* TAXABLE */
1452
+ );
1453
+ }
1454
+ /**
1455
+ * Create an InvoiceItem with auto-calculated amount
1456
+ */
1457
+ static createWithAutoAmount(props) {
1458
+ const amount = props.unitPrice.multiply(props.quantity);
1459
+ return _InvoiceItem.create({ ...props, amount });
1460
+ }
1461
+ // Getters
1462
+ get description() {
1463
+ return this._description;
1464
+ }
1465
+ get quantity() {
1466
+ return this._quantity;
1467
+ }
1468
+ get unitPrice() {
1469
+ return this._unitPrice;
1470
+ }
1471
+ get amount() {
1472
+ return this._amount;
1473
+ }
1474
+ get unit() {
1475
+ return this._unit;
1476
+ }
1477
+ get remark() {
1478
+ return this._remark;
1479
+ }
1480
+ get taxType() {
1481
+ return this._taxType;
1482
+ }
1483
+ /**
1484
+ * Check if this is a discount item (negative amount)
1485
+ */
1486
+ isDiscount() {
1487
+ return this._amount.isNegative();
1488
+ }
1489
+ };
1490
+
1491
+ // src/domain/shared/TaxId.ts
1492
+ var TaxId = class _TaxId {
1493
+ constructor(value) {
1494
+ this.value = value;
1495
+ }
1496
+ static WEIGHTS = [1, 2, 1, 2, 1, 2, 4, 1];
1497
+ static NO_TAX_ID = "0000000000";
1498
+ /**
1499
+ * Create a TaxId from string
1500
+ * @throws InvalidTaxIdError if the tax ID is invalid
1501
+ */
1502
+ static create(value) {
1503
+ const normalized = value.trim();
1504
+ if (!_TaxId.isValid(normalized)) {
1505
+ throw new InvalidTaxIdError(value);
1506
+ }
1507
+ return new _TaxId(normalized);
1508
+ }
1509
+ /**
1510
+ * Create a "no tax ID" placeholder (0000000000)
1511
+ * Used for B2C invoices without buyer's tax ID
1512
+ */
1513
+ static none() {
1514
+ return new _TaxId(_TaxId.NO_TAX_ID);
1515
+ }
1516
+ /**
1517
+ * Try to create a TaxId, return null if invalid
1518
+ */
1519
+ static tryCreate(value) {
1520
+ try {
1521
+ return _TaxId.create(value);
1522
+ } catch {
1523
+ return null;
1524
+ }
1525
+ }
1526
+ /**
1527
+ * Validate a tax ID string
1528
+ */
1529
+ static isValid(value) {
1530
+ if (value === _TaxId.NO_TAX_ID) {
1531
+ return true;
1532
+ }
1533
+ if (!/^\d{8}$/.test(value)) {
1534
+ return false;
1535
+ }
1536
+ return _TaxId.validateChecksum(value);
1537
+ }
1538
+ /**
1539
+ * Validate checksum using the official algorithm
1540
+ *
1541
+ * Algorithm:
1542
+ * 1. Multiply each digit by its weight [1,2,1,2,1,2,4,1]
1543
+ * 2. For each product, sum its digits (e.g., 18 -> 1+8=9)
1544
+ * 3. Sum all results
1545
+ * 4. If 7th digit is 7, check both (sum % 10 === 0) or ((sum + 1) % 10 === 0)
1546
+ * 5. Otherwise, check (sum % 10 === 0)
1547
+ */
1548
+ static validateChecksum(value) {
1549
+ const digits = value.split("").map(Number);
1550
+ let sum = 0;
1551
+ for (let i = 0; i < 8; i++) {
1552
+ const product = digits[i] * _TaxId.WEIGHTS[i];
1553
+ sum += Math.floor(product / 10) + product % 10;
1554
+ }
1555
+ if (digits[6] === 7) {
1556
+ return sum % 10 === 0 || (sum + 1) % 10 === 0;
1557
+ }
1558
+ return sum % 10 === 0;
1559
+ }
1560
+ /**
1561
+ * Check if this is a "no tax ID" placeholder
1562
+ */
1563
+ isNone() {
1564
+ return this.value === _TaxId.NO_TAX_ID;
1565
+ }
1566
+ /**
1567
+ * Get the string value
1568
+ */
1569
+ toString() {
1570
+ return this.value;
1571
+ }
1572
+ /**
1573
+ * Check equality with another TaxId
1574
+ */
1575
+ equals(other) {
1576
+ return this.value === other.value;
1577
+ }
1578
+ };
1579
+
1580
+ // src/domain/shared/OrderId.ts
1581
+ var OrderId = class _OrderId {
1582
+ constructor(value) {
1583
+ this.value = value;
1584
+ }
1585
+ static MAX_LENGTH = 40;
1586
+ /**
1587
+ * Create an OrderId
1588
+ * @throws ValidationError if invalid
1589
+ */
1590
+ static create(value) {
1591
+ const trimmed = value?.trim();
1592
+ if (!trimmed || trimmed.length === 0) {
1593
+ throw new ValidationError("orderId", "Order ID is required");
1594
+ }
1595
+ if (trimmed.length > _OrderId.MAX_LENGTH) {
1596
+ throw new ValidationError(
1597
+ "orderId",
1598
+ `Order ID must not exceed ${_OrderId.MAX_LENGTH} characters`
1599
+ );
1600
+ }
1601
+ return new _OrderId(trimmed);
1602
+ }
1603
+ /**
1604
+ * Try to create an OrderId, returns null if invalid
1605
+ */
1606
+ static tryCreate(value) {
1607
+ try {
1608
+ return _OrderId.create(value);
1609
+ } catch {
1610
+ return null;
1611
+ }
1612
+ }
1613
+ /**
1614
+ * Get the string value
1615
+ */
1616
+ toString() {
1617
+ return this.value;
1618
+ }
1619
+ /**
1620
+ * Check equality
1621
+ */
1622
+ equals(other) {
1623
+ return this.value === other.value;
1624
+ }
1625
+ };
1626
+
1627
+ // src/domain/shared/Buyer.ts
1628
+ var Buyer = class _Buyer {
1629
+ constructor(_type, _taxId, _name, _address, _phone, _email) {
1630
+ this._type = _type;
1631
+ this._taxId = _taxId;
1632
+ this._name = _name;
1633
+ this._address = _address;
1634
+ this._phone = _phone;
1635
+ this._email = _email;
1636
+ }
1637
+ /**
1638
+ * Create an anonymous buyer (B2C)
1639
+ * @param name Buyer name (default: '消費者')
1640
+ */
1641
+ static anonymous(name) {
1642
+ const trimmedName = name === void 0 ? "\u6D88\u8CBB\u8005" : name.trim();
1643
+ _Buyer.validateName(trimmedName);
1644
+ return new _Buyer(
1645
+ "anonymous" /* ANONYMOUS */,
1646
+ TaxId.none(),
1647
+ trimmedName,
1648
+ "",
1649
+ "",
1650
+ ""
1651
+ );
1652
+ }
1653
+ /**
1654
+ * Create a company buyer (B2B)
1655
+ */
1656
+ static company(props) {
1657
+ const name = props.name?.trim();
1658
+ _Buyer.validateName(name);
1659
+ if (props.taxId.isNone()) {
1660
+ throw new ValidationError("taxId", "Company buyer must have a tax ID");
1661
+ }
1662
+ return new _Buyer(
1663
+ "company" /* COMPANY */,
1664
+ props.taxId,
1665
+ name,
1666
+ props.address?.trim() ?? "",
1667
+ props.phone?.trim() ?? "",
1668
+ props.email?.trim() ?? ""
1669
+ );
1670
+ }
1671
+ /**
1672
+ * Validate buyer name
1673
+ */
1674
+ static validateName(name) {
1675
+ if (!name || name.length === 0) {
1676
+ throw new ValidationError("buyerName", "Buyer name is required");
1677
+ }
1678
+ const invalidNames = ["0", "00", "000", "0000"];
1679
+ if (invalidNames.includes(name)) {
1680
+ throw new ValidationError(
1681
+ "buyerName",
1682
+ "Buyer name cannot be 0, 00, 000, or 0000"
1683
+ );
1684
+ }
1685
+ }
1686
+ /**
1687
+ * Get the buyer type
1688
+ */
1689
+ get type() {
1690
+ return this._type;
1691
+ }
1692
+ /**
1693
+ * Get the tax ID
1694
+ */
1695
+ get taxId() {
1696
+ return this._taxId;
1697
+ }
1698
+ /**
1699
+ * Get the name
1700
+ */
1701
+ get name() {
1702
+ return this._name;
1703
+ }
1704
+ /**
1705
+ * Get the address
1706
+ */
1707
+ get address() {
1708
+ return this._address;
1709
+ }
1710
+ /**
1711
+ * Get the phone
1712
+ */
1713
+ get phone() {
1714
+ return this._phone;
1715
+ }
1716
+ /**
1717
+ * Get the email
1718
+ */
1719
+ get email() {
1720
+ return this._email;
1721
+ }
1722
+ /**
1723
+ * Check if this is a B2B buyer (company)
1724
+ */
1725
+ get isCompany() {
1726
+ return this._type === "company" /* COMPANY */;
1727
+ }
1728
+ /**
1729
+ * Check if this is a B2C buyer (anonymous)
1730
+ */
1731
+ get isAnonymous() {
1732
+ return this._type === "anonymous" /* ANONYMOUS */;
1733
+ }
1734
+ };
1735
+
1736
+ // src/infrastructure/amego/AmegoInvoiceRepository.ts
1737
+ var AmegoInvoiceRepository = class {
1738
+ client;
1739
+ constructor(config) {
1740
+ this.client = new AmegoClient(config);
1741
+ }
1742
+ /**
1743
+ * Issue a new invoice (開立發票)
1744
+ *
1745
+ * 使用 /json/f0401 endpoint
1746
+ */
1747
+ async issue(invoice) {
1748
+ const payload = AmegoMapper.toInvoicePayload(invoice);
1749
+ const response = await this.client.post(
1750
+ AMEGO_ENDPOINTS.INVOICE_ISSUE,
1751
+ payload
1752
+ );
1753
+ const invoiceNumber = InvoiceNumber.create(response.invoice_number);
1754
+ const invoiceTime = AmegoMapper.parseUnixTimestamp(response.invoice_time);
1755
+ invoice.setInvoiceNumber(
1756
+ invoiceNumber,
1757
+ invoiceTime,
1758
+ response.random_number
1759
+ );
1760
+ invoice.updateStatus(3 /* UPLOADED */);
1761
+ return {
1762
+ invoiceNumber,
1763
+ invoiceTime,
1764
+ randomNumber: response.random_number,
1765
+ barcode: response.barcode ?? "",
1766
+ qrcodeLeft: response.qrcode_left ?? "",
1767
+ qrcodeRight: response.qrcode_right ?? "",
1768
+ printData: response.base64_data
1769
+ };
1770
+ }
1771
+ /**
1772
+ * Void an invoice (作廢發票)
1773
+ *
1774
+ * 使用 /json/f0501 endpoint
1775
+ */
1776
+ async void(invoiceNumber) {
1777
+ const payload = {
1778
+ CancelInvoiceNumber: invoiceNumber.toString()
1779
+ };
1780
+ await this.client.post(AMEGO_ENDPOINTS.INVOICE_VOID, payload);
1781
+ }
1782
+ /**
1783
+ * Query invoice by invoice number (查詢發票 - 依發票號碼)
1784
+ *
1785
+ * 使用 /json/invoice_query endpoint
1786
+ */
1787
+ async findByInvoiceNumber(invoiceNumber) {
1788
+ try {
1789
+ const payload = {
1790
+ type: "invoice",
1791
+ invoice_number: invoiceNumber.toString()
1792
+ };
1793
+ const response = await this.client.post(
1794
+ AMEGO_ENDPOINTS.INVOICE_QUERY,
1795
+ payload
1796
+ );
1797
+ if (!response.data) {
1798
+ return null;
1799
+ }
1800
+ return this.mapQueryResponse(response.data);
1801
+ } catch {
1802
+ return null;
1803
+ }
1804
+ }
1805
+ /**
1806
+ * Query invoice by order ID (查詢發票 - 依訂單編號)
1807
+ *
1808
+ * 使用 /json/invoice_query endpoint
1809
+ */
1810
+ async findByOrderId(orderId) {
1811
+ try {
1812
+ const payload = {
1813
+ type: "order",
1814
+ order_id: orderId
1815
+ };
1816
+ const response = await this.client.post(
1817
+ AMEGO_ENDPOINTS.INVOICE_QUERY,
1818
+ payload
1819
+ );
1820
+ if (!response.data) {
1821
+ return null;
1822
+ }
1823
+ return this.mapQueryResponse(response.data);
1824
+ } catch {
1825
+ return null;
1826
+ }
1827
+ }
1828
+ /**
1829
+ * Check invoice status (查詢發票狀態)
1830
+ *
1831
+ * 使用 /json/invoice_status endpoint
1832
+ */
1833
+ async getStatus(invoiceNumbers) {
1834
+ const results = [];
1835
+ for (const invoiceNumber of invoiceNumbers) {
1836
+ try {
1837
+ const payload = {
1838
+ InvoiceNumber: invoiceNumber.toString()
1839
+ };
1840
+ const response = await this.client.post(
1841
+ AMEGO_ENDPOINTS.INVOICE_STATUS,
1842
+ payload
1843
+ );
1844
+ if (response.data && response.data.length > 0) {
1845
+ for (const item of response.data) {
1846
+ results.push({
1847
+ invoiceNumber: item.invoice_number,
1848
+ type: item.type,
1849
+ status: AmegoMapper.toInvoiceStatus(item.status),
1850
+ totalAmount: item.total_amount
1851
+ });
1852
+ }
1853
+ }
1854
+ } catch {
1855
+ }
1856
+ }
1857
+ return results;
1858
+ }
1859
+ /**
1860
+ * List invoices (發票列表)
1861
+ *
1862
+ * 使用 /json/invoice_list endpoint
1863
+ */
1864
+ async list(options) {
1865
+ const dateStart = parseInt(options.startDate, 10);
1866
+ const dateEnd = parseInt(options.endDate, 10);
1867
+ const payload = {
1868
+ date_select: options.dateType,
1869
+ date_start: dateStart,
1870
+ date_end: dateEnd,
1871
+ limit: options.limit ?? 20,
1872
+ page: options.page ?? 1
1873
+ };
1874
+ const response = await this.client.post(
1875
+ AMEGO_ENDPOINTS.INVOICE_LIST,
1876
+ payload
1877
+ );
1878
+ const invoices = (response.data ?? []).map(
1879
+ (item) => this.mapListItemResponse(item)
1880
+ );
1881
+ return {
1882
+ invoices,
1883
+ totalPages: response.page_total ?? 1,
1884
+ currentPage: response.page_now ?? 1,
1885
+ totalCount: response.data_total ?? 0
1886
+ };
1887
+ }
1888
+ /**
1889
+ * Map API query response to domain query result
1890
+ */
1891
+ mapQueryResponse(response) {
1892
+ const items = (response.product_item ?? []).map(
1893
+ (item) => InvoiceItem.create({
1894
+ description: item.description,
1895
+ quantity: item.quantity,
1896
+ unitPrice: Money.create(item.unit_price),
1897
+ amount: Money.create(item.amount),
1898
+ unit: item.unit || void 0,
1899
+ remark: item.remark || void 0,
1900
+ taxType: AmegoMapper.fromApiTaxType(item.tax_type)
1901
+ })
1902
+ );
1903
+ const buyerTaxId = TaxId.tryCreate(response.buyer_identifier) ?? TaxId.none();
1904
+ const buyer = buyerTaxId.isNone() ? Buyer.anonymous(response.buyer_name || "\u6D88\u8CBB\u8005") : Buyer.company({
1905
+ taxId: buyerTaxId,
1906
+ name: response.buyer_name || "",
1907
+ address: response.buyer_address || void 0,
1908
+ phone: response.buyer_telephone_number || void 0,
1909
+ email: response.buyer_email_address || void 0
1910
+ });
1911
+ const carrier = this.buildCarrier(
1912
+ response.carrier_type,
1913
+ response.carrier_id1
1914
+ );
1915
+ const donation = response.npoban ? Donation.tryCreate(response.npoban) : Donation.none();
1916
+ const invoice = Invoice.create({
1917
+ orderId: OrderId.create(response.order_id || response.invoice_number),
1918
+ buyer,
1919
+ items,
1920
+ carrier,
1921
+ donation,
1922
+ remark: response.main_remark || void 0
1923
+ });
1924
+ const invoiceNumber = InvoiceNumber.create(response.invoice_number);
1925
+ const invoiceTime = AmegoMapper.parseIsoDate(response.invoice_time);
1926
+ invoice.setInvoiceNumber(invoiceNumber, invoiceTime, response.random_number);
1927
+ invoice.updateStatus(AmegoMapper.toInvoiceStatus(response.invoice_status));
1928
+ return {
1929
+ invoice,
1930
+ status: AmegoMapper.toInvoiceStatus(response.invoice_status)
1931
+ };
1932
+ }
1933
+ /**
1934
+ * Map API list item response to domain query result
1935
+ */
1936
+ mapListItemResponse(item) {
1937
+ const placeholderItem = InvoiceItem.create({
1938
+ description: "\uFF08\u8A73\u7D30\u8ACB\u67E5\u8A62\u55AE\u5F35\u767C\u7968\uFF09",
1939
+ quantity: 1,
1940
+ unitPrice: Money.create(item.total_amount),
1941
+ amount: Money.create(item.total_amount),
1942
+ taxType: AmegoMapper.fromApiTaxType(item.tax_type)
1943
+ });
1944
+ const buyerTaxId = TaxId.tryCreate(item.buyer_identifier) ?? TaxId.none();
1945
+ const buyer = buyerTaxId.isNone() ? Buyer.anonymous(item.buyer_name || "\u6D88\u8CBB\u8005") : Buyer.company({
1946
+ taxId: buyerTaxId,
1947
+ name: item.buyer_name || "",
1948
+ address: item.buyer_address || void 0,
1949
+ phone: item.buyer_telephone_number || void 0,
1950
+ email: item.buyer_email_address || void 0
1951
+ });
1952
+ const carrier = this.buildCarrier(item.carrier_type, item.carrier_id1);
1953
+ const donation = item.npoban ? Donation.tryCreate(item.npoban) : Donation.none();
1954
+ const invoice = Invoice.create({
1955
+ orderId: OrderId.create(item.order_id || item.invoice_number),
1956
+ buyer,
1957
+ items: [placeholderItem],
1958
+ carrier,
1959
+ donation,
1960
+ remark: item.main_remark || void 0
1961
+ });
1962
+ const invoiceNumber = InvoiceNumber.create(item.invoice_number);
1963
+ const invoiceTime = AmegoMapper.parseIsoDate(item.invoice_time);
1964
+ invoice.setInvoiceNumber(invoiceNumber, invoiceTime, item.random_number);
1965
+ invoice.updateStatus(AmegoMapper.toInvoiceStatus(item.invoice_status));
1966
+ return {
1967
+ invoice,
1968
+ status: AmegoMapper.toInvoiceStatus(item.invoice_status)
1969
+ };
1970
+ }
1971
+ /**
1972
+ * Build Carrier from API response fields
1973
+ */
1974
+ buildCarrier(carrierType, carrierId) {
1975
+ if (!carrierType || carrierType === "") {
1976
+ return Carrier.none();
1977
+ }
1978
+ switch (carrierType) {
1979
+ case "3J0002" /* MOBILE */:
1980
+ return Carrier.mobile(carrierId);
1981
+ case "CQ0001" /* CERTIFICATE */:
1982
+ return Carrier.certificate(carrierId);
1983
+ default:
1984
+ return Carrier.custom(carrierType, carrierId);
1985
+ }
1986
+ }
1987
+ };
1988
+
1989
+ // src/domain/shared/Pagination.ts
1990
+ var Pagination = class _Pagination {
1991
+ constructor(_page, _limit) {
1992
+ this._page = _page;
1993
+ this._limit = _limit;
1994
+ }
1995
+ static DEFAULT_LIMIT = 20;
1996
+ static MIN_LIMIT = 20;
1997
+ static MAX_LIMIT = 500;
1998
+ /**
1999
+ * Create pagination with specified page and limit
2000
+ */
2001
+ static create(options = {}) {
2002
+ const page = options.page ?? 1;
2003
+ const limit = options.limit ?? _Pagination.DEFAULT_LIMIT;
2004
+ if (page < 1) {
2005
+ throw new ValidationError("page", "Page must be at least 1");
2006
+ }
2007
+ if (limit < _Pagination.MIN_LIMIT || limit > _Pagination.MAX_LIMIT) {
2008
+ throw new ValidationError(
2009
+ "limit",
2010
+ `Limit must be between ${_Pagination.MIN_LIMIT} and ${_Pagination.MAX_LIMIT}`
2011
+ );
2012
+ }
2013
+ return new _Pagination(page, limit);
2014
+ }
2015
+ /**
2016
+ * Create first page with default limit
2017
+ */
2018
+ static first(limit) {
2019
+ return _Pagination.create({ page: 1, limit });
2020
+ }
2021
+ /**
2022
+ * Get the page number (1-based)
2023
+ */
2024
+ get page() {
2025
+ return this._page;
2026
+ }
2027
+ /**
2028
+ * Get the limit (items per page)
2029
+ */
2030
+ get limit() {
2031
+ return this._limit;
2032
+ }
2033
+ /**
2034
+ * Get the offset (0-based, for SQL queries)
2035
+ */
2036
+ get offset() {
2037
+ return (this._page - 1) * this._limit;
2038
+ }
2039
+ /**
2040
+ * Create next page pagination
2041
+ */
2042
+ next() {
2043
+ return new _Pagination(this._page + 1, this._limit);
2044
+ }
2045
+ /**
2046
+ * Create previous page pagination (min page 1)
2047
+ */
2048
+ previous() {
2049
+ return new _Pagination(Math.max(1, this._page - 1), this._limit);
2050
+ }
2051
+ };
2052
+ function createPaginatedResult(items, totalCount, pagination) {
2053
+ const totalPages = Math.ceil(totalCount / pagination.limit);
2054
+ return {
2055
+ items,
2056
+ totalCount,
2057
+ totalPages,
2058
+ currentPage: pagination.page,
2059
+ pageSize: pagination.limit,
2060
+ hasNextPage: pagination.page < totalPages,
2061
+ hasPreviousPage: pagination.page > 1
2062
+ };
2063
+ }
2064
+
2065
+ // src/domain/shared/LocalDate.ts
2066
+ var LocalDate = class _LocalDate {
2067
+ constructor(_year, _month, _day) {
2068
+ this._year = _year;
2069
+ this._month = _month;
2070
+ this._day = _day;
2071
+ }
2072
+ /**
2073
+ * Create a LocalDate from year, month, day
2074
+ */
2075
+ static of(year, month, day) {
2076
+ if (month < 1 || month > 12) {
2077
+ throw new ValidationError("month", "Month must be between 1 and 12");
2078
+ }
2079
+ if (day < 1 || day > 31) {
2080
+ throw new ValidationError("day", "Day must be between 1 and 31");
2081
+ }
2082
+ return new _LocalDate(year, month, day);
2083
+ }
2084
+ /**
2085
+ * Create a LocalDate from a JavaScript Date
2086
+ */
2087
+ static fromDate(date) {
2088
+ return new _LocalDate(
2089
+ date.getFullYear(),
2090
+ date.getMonth() + 1,
2091
+ date.getDate()
2092
+ );
2093
+ }
2094
+ /**
2095
+ * Create a LocalDate from YYYYMMDD string or number
2096
+ */
2097
+ static parse(value) {
2098
+ const str = String(value);
2099
+ if (str.length !== 8) {
2100
+ throw new ValidationError("date", "Date must be in YYYYMMDD format");
2101
+ }
2102
+ const year = parseInt(str.substring(0, 4), 10);
2103
+ const month = parseInt(str.substring(4, 6), 10);
2104
+ const day = parseInt(str.substring(6, 8), 10);
2105
+ return _LocalDate.of(year, month, day);
2106
+ }
2107
+ /**
2108
+ * Get today's date
2109
+ */
2110
+ static today() {
2111
+ return _LocalDate.fromDate(/* @__PURE__ */ new Date());
2112
+ }
2113
+ /**
2114
+ * Get the year
2115
+ */
2116
+ get year() {
2117
+ return this._year;
2118
+ }
2119
+ /**
2120
+ * Get the month (1-12)
2121
+ */
2122
+ get month() {
2123
+ return this._month;
2124
+ }
2125
+ /**
2126
+ * Get the day of month
2127
+ */
2128
+ get day() {
2129
+ return this._day;
2130
+ }
2131
+ /**
2132
+ * Convert to YYYYMMDD number (for API calls)
2133
+ */
2134
+ toNumber() {
2135
+ return this._year * 1e4 + this._month * 100 + this._day;
2136
+ }
2137
+ /**
2138
+ * Convert to YYYYMMDD string
2139
+ */
2140
+ toString() {
2141
+ const month = String(this._month).padStart(2, "0");
2142
+ const day = String(this._day).padStart(2, "0");
2143
+ return `${this._year}${month}${day}`;
2144
+ }
2145
+ /**
2146
+ * Convert to JavaScript Date (at midnight)
2147
+ */
2148
+ toDate() {
2149
+ return new Date(this._year, this._month - 1, this._day);
2150
+ }
2151
+ /**
2152
+ * Convert to ISO date string (YYYY-MM-DD)
2153
+ */
2154
+ toISOString() {
2155
+ const month = String(this._month).padStart(2, "0");
2156
+ const day = String(this._day).padStart(2, "0");
2157
+ return `${this._year}-${month}-${day}`;
2158
+ }
2159
+ /**
2160
+ * Check if this date is before another
2161
+ */
2162
+ isBefore(other) {
2163
+ return this.toNumber() < other.toNumber();
2164
+ }
2165
+ /**
2166
+ * Check if this date is after another
2167
+ */
2168
+ isAfter(other) {
2169
+ return this.toNumber() > other.toNumber();
2170
+ }
2171
+ /**
2172
+ * Check equality
2173
+ */
2174
+ equals(other) {
2175
+ return this.toNumber() === other.toNumber();
2176
+ }
2177
+ };
2178
+ var DateRange = class _DateRange {
2179
+ constructor(_start, _end) {
2180
+ this._start = _start;
2181
+ this._end = _end;
2182
+ }
2183
+ /**
2184
+ * Create a date range between two dates
2185
+ */
2186
+ static between(start, end) {
2187
+ if (start.isAfter(end)) {
2188
+ throw new ValidationError(
2189
+ "dateRange",
2190
+ "Start date must be before or equal to end date"
2191
+ );
2192
+ }
2193
+ return new _DateRange(start, end);
2194
+ }
2195
+ /**
2196
+ * Create a date range for a single day
2197
+ */
2198
+ static on(date) {
2199
+ return new _DateRange(date, date);
2200
+ }
2201
+ /**
2202
+ * Get the start date
2203
+ */
2204
+ get start() {
2205
+ return this._start;
2206
+ }
2207
+ /**
2208
+ * Get the end date
2209
+ */
2210
+ get end() {
2211
+ return this._end;
2212
+ }
2213
+ /**
2214
+ * Check if a date is within this range (inclusive)
2215
+ */
2216
+ contains(date) {
2217
+ return !date.isBefore(this._start) && !date.isAfter(this._end);
2218
+ }
2219
+ };
2220
+ var DateType = /* @__PURE__ */ ((DateType2) => {
2221
+ DateType2[DateType2["INVOICE_DATE"] = 1] = "INVOICE_DATE";
2222
+ DateType2[DateType2["CREATE_DATE"] = 2] = "CREATE_DATE";
2223
+ return DateType2;
2224
+ })(DateType || {});
2225
+
2226
+ // src/infrastructure/amego/AmegoInvoiceService.ts
2227
+ var AmegoInvoiceService = class {
2228
+ repository;
2229
+ constructor(config) {
2230
+ this.repository = new AmegoInvoiceRepository(config);
2231
+ }
2232
+ /**
2233
+ * Issue a new invoice (開立發票)
2234
+ */
2235
+ async issue(invoice) {
2236
+ const result = await this.repository.issue(invoice);
2237
+ return {
2238
+ invoiceNumber: result.invoiceNumber,
2239
+ invoiceTime: result.invoiceTime,
2240
+ randomNumber: result.randomNumber,
2241
+ barcode: result.barcode,
2242
+ qrcodeLeft: result.qrcodeLeft,
2243
+ qrcodeRight: result.qrcodeRight,
2244
+ printData: result.printData
2245
+ };
2246
+ }
2247
+ /**
2248
+ * Void an invoice (作廢發票)
2249
+ */
2250
+ async void(invoiceNumber) {
2251
+ await this.repository.void(invoiceNumber);
2252
+ }
2253
+ /**
2254
+ * Find invoice by invoice number
2255
+ */
2256
+ async findByNumber(invoiceNumber) {
2257
+ const result = await this.repository.findByInvoiceNumber(invoiceNumber);
2258
+ return result?.invoice ?? null;
2259
+ }
2260
+ /**
2261
+ * Find invoice by order ID
2262
+ */
2263
+ async findByOrderId(orderId) {
2264
+ const result = await this.repository.findByOrderId(orderId);
2265
+ return result?.invoice ?? null;
2266
+ }
2267
+ /**
2268
+ * List invoices with query
2269
+ */
2270
+ async list(query) {
2271
+ const result = await this.repository.list({
2272
+ dateType: query.dateType === 2 /* CREATE_DATE */ ? 2 : 1,
2273
+ startDate: query.dateRange.start.toNumber().toString(),
2274
+ endDate: query.dateRange.end.toNumber().toString(),
2275
+ limit: query.pagination.limit,
2276
+ page: query.pagination.page
2277
+ });
2278
+ const invoices = result.invoices.map((r) => r.invoice);
2279
+ return createPaginatedResult(invoices, result.totalCount, query.pagination);
2280
+ }
2281
+ /**
2282
+ * Get invoice status
2283
+ */
2284
+ async getStatus(invoiceNumbers) {
2285
+ const results = await this.repository.getStatus(invoiceNumbers);
2286
+ return results.map((r) => ({
2287
+ invoiceNumber: r.invoiceNumber,
2288
+ type: r.type,
2289
+ status: r.status,
2290
+ totalAmount: r.totalAmount
2291
+ }));
2292
+ }
2293
+ };
2294
+
2295
+ // src/domain/allowance/AllowanceItem.ts
2296
+ var AllowanceItem = class _AllowanceItem {
2297
+ constructor(_originalDescription, _quantity, _unitPrice, _amount, _unit, _taxType) {
2298
+ this._originalDescription = _originalDescription;
2299
+ this._quantity = _quantity;
2300
+ this._unitPrice = _unitPrice;
2301
+ this._amount = _amount;
2302
+ this._unit = _unit;
2303
+ this._taxType = _taxType;
2304
+ }
2305
+ /**
2306
+ * Create an AllowanceItem
2307
+ */
2308
+ static create(props) {
2309
+ if (!props.originalDescription || props.originalDescription.trim().length === 0) {
2310
+ throw new ValidationError(
2311
+ "originalDescription",
2312
+ "Original description is required"
2313
+ );
2314
+ }
2315
+ if (props.originalDescription.length > 256) {
2316
+ throw new ValidationError(
2317
+ "originalDescription",
2318
+ "Original description must not exceed 256 characters"
2319
+ );
2320
+ }
2321
+ if (props.quantity === 0) {
2322
+ throw new ValidationError("quantity", "Quantity cannot be zero");
2323
+ }
2324
+ if (props.unit && props.unit.length > 6) {
2325
+ throw new ValidationError("unit", "Unit must not exceed 6 characters");
2326
+ }
2327
+ return new _AllowanceItem(
2328
+ props.originalDescription.trim(),
2329
+ props.quantity,
2330
+ props.unitPrice,
2331
+ props.amount,
2332
+ props.unit?.trim() ?? "",
2333
+ props.taxType ?? 1 /* TAXABLE */
2334
+ );
2335
+ }
2336
+ /**
2337
+ * Create an AllowanceItem with auto-calculated amount
2338
+ */
2339
+ static createWithAutoAmount(props) {
2340
+ const amount = props.unitPrice.multiply(props.quantity);
2341
+ return _AllowanceItem.create({ ...props, amount });
2342
+ }
2343
+ // Getters
2344
+ get originalDescription() {
2345
+ return this._originalDescription;
2346
+ }
2347
+ get quantity() {
2348
+ return this._quantity;
2349
+ }
2350
+ get unitPrice() {
2351
+ return this._unitPrice;
2352
+ }
2353
+ get amount() {
2354
+ return this._amount;
2355
+ }
2356
+ get unit() {
2357
+ return this._unit;
2358
+ }
2359
+ get taxType() {
2360
+ return this._taxType;
2361
+ }
2362
+ };
2363
+
2364
+ // src/infrastructure/amego/AmegoAllowanceRepository.ts
2365
+ var allowanceCounter = 0;
2366
+ function generateAllowanceNumber() {
2367
+ const now = /* @__PURE__ */ new Date();
2368
+ const datePart = AmegoMapper.formatDate(now);
2369
+ const seq = String(++allowanceCounter % 1e6).padStart(6, "0");
2370
+ return `AL${datePart}${seq}`;
2371
+ }
2372
+ var AmegoAllowanceRepository = class {
2373
+ client;
2374
+ constructor(config) {
2375
+ this.client = new AmegoClient(config);
2376
+ }
2377
+ /**
2378
+ * Issue a new allowance (開立折讓)
2379
+ *
2380
+ * 使用 /json/g0401 endpoint
2381
+ */
2382
+ async issue(allowance) {
2383
+ const allowanceNumber = generateAllowanceNumber();
2384
+ const payload = AmegoMapper.toAllowancePayload(allowance, allowanceNumber);
2385
+ await this.client.post(
2386
+ AMEGO_ENDPOINTS.ALLOWANCE_ISSUE,
2387
+ payload
2388
+ );
2389
+ const allowanceDate = AmegoMapper.parseDate(payload.AllowanceDate);
2390
+ allowance.setAllowanceNumber(allowanceNumber, allowanceDate);
2391
+ allowance.updateStatus(3 /* UPLOADED */);
2392
+ return {
2393
+ allowanceNumber,
2394
+ allowanceDate
2395
+ };
2396
+ }
2397
+ /**
2398
+ * Void an allowance (作廢折讓)
2399
+ *
2400
+ * 使用 /json/g0501 endpoint
2401
+ */
2402
+ async void(allowanceNumber) {
2403
+ const payload = {
2404
+ CancelAllowanceNumber: allowanceNumber
2405
+ };
2406
+ await this.client.post(AMEGO_ENDPOINTS.ALLOWANCE_VOID, payload);
2407
+ }
2408
+ /**
2409
+ * Query allowance by allowance number (查詢折讓 - 依折讓單編號)
2410
+ *
2411
+ * 使用 /json/allowance_query endpoint
2412
+ */
2413
+ async findByAllowanceNumber(allowanceNumber) {
2414
+ try {
2415
+ const payload = {
2416
+ type: "allowance",
2417
+ allowance_number: allowanceNumber
2418
+ };
2419
+ const response = await this.client.post(
2420
+ AMEGO_ENDPOINTS.ALLOWANCE_QUERY,
2421
+ payload
2422
+ );
2423
+ if (!response.data) {
2424
+ return null;
2425
+ }
2426
+ return this.mapQueryResponse(response.data);
2427
+ } catch {
2428
+ return null;
2429
+ }
2430
+ }
2431
+ /**
2432
+ * Query allowances by original invoice number (查詢折讓 - 依原發票號碼)
2433
+ *
2434
+ * 使用 /json/allowance_query endpoint
2435
+ */
2436
+ async findByInvoiceNumber(invoiceNumber) {
2437
+ try {
2438
+ const payload = {
2439
+ type: "invoice",
2440
+ invoice_number: invoiceNumber.toString()
2441
+ };
2442
+ const response = await this.client.post(
2443
+ AMEGO_ENDPOINTS.ALLOWANCE_QUERY,
2444
+ payload
2445
+ );
2446
+ if (!response.data) {
2447
+ return [];
2448
+ }
2449
+ return [this.mapQueryResponse(response.data)];
2450
+ } catch {
2451
+ return [];
2452
+ }
2453
+ }
2454
+ /**
2455
+ * Check allowance status (查詢折讓狀態)
2456
+ *
2457
+ * 使用 /json/allowance_status endpoint
2458
+ */
2459
+ async getStatus(allowanceNumbers) {
2460
+ const results = [];
2461
+ for (const allowanceNumber of allowanceNumbers) {
2462
+ try {
2463
+ const payload = {
2464
+ AllowanceNumber: allowanceNumber
2465
+ };
2466
+ const response = await this.client.post(
2467
+ AMEGO_ENDPOINTS.ALLOWANCE_STATUS,
2468
+ payload
2469
+ );
2470
+ if (response.data && response.data.length > 0) {
2471
+ for (const item of response.data) {
2472
+ results.push({
2473
+ allowanceNumber: item.allowance_number,
2474
+ status: AmegoMapper.toAllowanceStatus(item.status),
2475
+ totalAmount: item.total_amount
2476
+ });
2477
+ }
2478
+ }
2479
+ } catch {
2480
+ }
2481
+ }
2482
+ return results;
2483
+ }
2484
+ /**
2485
+ * List allowances (折讓列表)
2486
+ *
2487
+ * 使用 /json/allowance_list endpoint
2488
+ */
2489
+ async list(options) {
2490
+ const dateStart = parseInt(options.startDate, 10);
2491
+ const dateEnd = parseInt(options.endDate, 10);
2492
+ const payload = {
2493
+ date_select: options.dateType,
2494
+ date_start: dateStart,
2495
+ date_end: dateEnd,
2496
+ limit: options.limit ?? 20,
2497
+ page: options.page ?? 1
2498
+ };
2499
+ const response = await this.client.post(
2500
+ AMEGO_ENDPOINTS.ALLOWANCE_LIST,
2501
+ payload
2502
+ );
2503
+ const allowances = (response.data ?? []).map(
2504
+ (item) => this.mapListItemResponse(item)
2505
+ );
2506
+ return {
2507
+ allowances,
2508
+ totalPages: response.page_total ?? 1,
2509
+ currentPage: response.page_now ?? 1,
2510
+ totalCount: response.data_total ?? 0
2511
+ };
2512
+ }
2513
+ /**
2514
+ * Map API query response to domain query result
2515
+ */
2516
+ mapQueryResponse(data) {
2517
+ const items = (data.product_item ?? []).map(
2518
+ (item) => AllowanceItem.create({
2519
+ originalDescription: item.description,
2520
+ quantity: item.quantity,
2521
+ unitPrice: Money.create(item.unit_price),
2522
+ amount: Money.create(item.amount),
2523
+ unit: item.unit || void 0,
2524
+ taxType: AmegoMapper.fromApiTaxType(item.tax_type)
2525
+ })
2526
+ );
2527
+ const firstItem = data.product_item?.[0];
2528
+ const originalInvoiceNumber = firstItem?.original_invoice_number ?? "";
2529
+ const originalInvoiceDate = firstItem?.original_invoice_date ? AmegoMapper.parseDate(firstItem.original_invoice_date) : /* @__PURE__ */ new Date();
2530
+ const allowance = Allowance.create({
2531
+ originalInvoiceNumber: InvoiceNumber.create(originalInvoiceNumber),
2532
+ originalInvoiceDate,
2533
+ buyerTaxId: TaxId.tryCreate(data.buyer_identifier) ?? TaxId.none(),
2534
+ sellerTaxId: TaxId.none(),
2535
+ // API 回應沒有賣方資訊,使用空值
2536
+ buyerName: data.buyer_name || "",
2537
+ items
2538
+ });
2539
+ const allowanceDate = AmegoMapper.parseDate(data.allowance_date);
2540
+ allowance.setAllowanceNumber(data.allowance_number, allowanceDate);
2541
+ allowance.updateStatus(AmegoMapper.toAllowanceStatus(data.invoice_status));
2542
+ return {
2543
+ allowance,
2544
+ status: AmegoMapper.toAllowanceStatus(data.invoice_status)
2545
+ };
2546
+ }
2547
+ /**
2548
+ * Map API list item response to domain query result
2549
+ */
2550
+ mapListItemResponse(item) {
2551
+ const firstProductItem = item.product_item?.[0];
2552
+ const originalInvoiceNumber = firstProductItem?.original_invoice_number ?? "";
2553
+ const originalInvoiceDate = firstProductItem?.original_invoice_date ? AmegoMapper.parseDate(firstProductItem.original_invoice_date) : /* @__PURE__ */ new Date();
2554
+ const items = (item.product_item ?? []).map(
2555
+ (productItem) => AllowanceItem.create({
2556
+ originalDescription: productItem.description,
2557
+ quantity: productItem.quantity,
2558
+ unitPrice: Money.create(productItem.unit_price),
2559
+ amount: Money.create(productItem.amount),
2560
+ unit: productItem.unit || void 0,
2561
+ taxType: AmegoMapper.fromApiTaxType(productItem.tax_type)
2562
+ })
2563
+ );
2564
+ const allowanceItems = items.length > 0 ? items : [
2565
+ AllowanceItem.create({
2566
+ originalDescription: "\uFF08\u8A73\u7D30\u8ACB\u67E5\u8A62\u55AE\u5F35\u6298\u8B93\uFF09",
2567
+ quantity: 1,
2568
+ unitPrice: Money.create(item.total_amount),
2569
+ amount: Money.create(item.total_amount),
2570
+ taxType: AmegoMapper.fromApiTaxType(1)
2571
+ // Default to taxable
2572
+ })
2573
+ ];
2574
+ const allowance = Allowance.create({
2575
+ originalInvoiceNumber: InvoiceNumber.create(originalInvoiceNumber),
2576
+ originalInvoiceDate,
2577
+ buyerTaxId: TaxId.tryCreate(item.buyer_identifier) ?? TaxId.none(),
2578
+ sellerTaxId: TaxId.none(),
2579
+ buyerName: item.buyer_name || "",
2580
+ items: allowanceItems
2581
+ });
2582
+ const allowanceDate = AmegoMapper.parseDate(item.allowance_date);
2583
+ allowance.setAllowanceNumber(item.allowance_number, allowanceDate);
2584
+ allowance.updateStatus(AmegoMapper.toAllowanceStatus(item.invoice_status));
2585
+ return {
2586
+ allowance,
2587
+ status: AmegoMapper.toAllowanceStatus(item.invoice_status)
2588
+ };
2589
+ }
2590
+ };
2591
+
2592
+ // src/infrastructure/amego/AmegoAllowanceService.ts
2593
+ var AmegoAllowanceService = class {
2594
+ repository;
2595
+ constructor(config) {
2596
+ this.repository = new AmegoAllowanceRepository(config);
2597
+ }
2598
+ /**
2599
+ * Issue a new allowance (開立折讓)
2600
+ */
2601
+ async issue(allowance) {
2602
+ const result = await this.repository.issue(allowance);
2603
+ return {
2604
+ allowanceNumber: result.allowanceNumber,
2605
+ allowanceDate: result.allowanceDate
2606
+ };
2607
+ }
2608
+ /**
2609
+ * Void an allowance (作廢折讓)
2610
+ */
2611
+ async void(allowanceNumber) {
2612
+ await this.repository.void(allowanceNumber);
2613
+ }
2614
+ /**
2615
+ * Find allowance by allowance number
2616
+ */
2617
+ async findByNumber(allowanceNumber) {
2618
+ const result = await this.repository.findByAllowanceNumber(allowanceNumber);
2619
+ return result?.allowance ?? null;
2620
+ }
2621
+ /**
2622
+ * Find allowances by original invoice number
2623
+ */
2624
+ async findByInvoiceNumber(invoiceNumber) {
2625
+ const results = await this.repository.findByInvoiceNumber(invoiceNumber);
2626
+ return results.map((r) => r.allowance);
2627
+ }
2628
+ /**
2629
+ * List allowances with query
2630
+ */
2631
+ async list(query) {
2632
+ const result = await this.repository.list({
2633
+ dateType: query.dateType === 2 /* CREATE_DATE */ ? 2 : 1,
2634
+ startDate: query.dateRange.start.toNumber().toString(),
2635
+ endDate: query.dateRange.end.toNumber().toString(),
2636
+ limit: query.pagination.limit,
2637
+ page: query.pagination.page
2638
+ });
2639
+ const allowances = result.allowances.map((r) => r.allowance);
2640
+ return createPaginatedResult(allowances, result.totalCount, query.pagination);
2641
+ }
2642
+ /**
2643
+ * Get allowance status
2644
+ */
2645
+ async getStatus(allowanceNumbers) {
2646
+ const results = await this.repository.getStatus(allowanceNumbers);
2647
+ return results.map((r) => ({
2648
+ allowanceNumber: r.allowanceNumber,
2649
+ status: r.status,
2650
+ totalAmount: r.totalAmount
2651
+ }));
2652
+ }
2653
+ };
2654
+
2655
+ // src/Zinvoice.ts
2656
+ var Zinvoice = class _Zinvoice {
2657
+ _provider;
2658
+ _invoiceService;
2659
+ _allowanceService;
2660
+ _capabilities;
2661
+ constructor(provider, invoiceService, allowanceService) {
2662
+ this._provider = provider;
2663
+ this._invoiceService = invoiceService;
2664
+ this._allowanceService = allowanceService;
2665
+ this._capabilities = PROVIDER_CAPABILITIES[provider];
2666
+ }
2667
+ /**
2668
+ * Create a Zinvoice client
2669
+ */
2670
+ static create(config) {
2671
+ switch (config.provider) {
2672
+ case "amego" /* AMEGO */:
2673
+ return _Zinvoice.createAmego(config);
2674
+ default:
2675
+ throw new ProviderNotImplementedError(config.provider);
2676
+ }
2677
+ }
2678
+ /**
2679
+ * Create a Zinvoice client for Amego provider
2680
+ */
2681
+ static createAmego(config) {
2682
+ const amegoConfig = {
2683
+ baseUrl: config.baseUrl ?? "https://invoice-api.amego.tw",
2684
+ sellerTaxId: config.sellerTaxId,
2685
+ apiKey: config.apiKey,
2686
+ timeout: config.timeout
2687
+ };
2688
+ const invoiceService = new AmegoInvoiceService(amegoConfig);
2689
+ const allowanceService = new AmegoAllowanceService(amegoConfig);
2690
+ return new _Zinvoice("amego" /* AMEGO */, invoiceService, allowanceService);
2691
+ }
2692
+ /**
2693
+ * Get the invoice service
2694
+ */
2695
+ get invoices() {
2696
+ return this._invoiceService;
2697
+ }
2698
+ /**
2699
+ * Get the allowance service
2700
+ */
2701
+ get allowances() {
2702
+ return this._allowanceService;
2703
+ }
2704
+ /**
2705
+ * Get the current provider
2706
+ */
2707
+ get provider() {
2708
+ return this._provider;
2709
+ }
2710
+ /**
2711
+ * Get the provider display name
2712
+ */
2713
+ get providerName() {
2714
+ return PROVIDER_NAMES[this._provider];
2715
+ }
2716
+ /**
2717
+ * Check if a capability is supported
2718
+ */
2719
+ supports(capability) {
2720
+ return this._capabilities.has(capability);
2721
+ }
2722
+ /**
2723
+ * Get all supported capabilities
2724
+ */
2725
+ getCapabilities() {
2726
+ return Array.from(this._capabilities);
2727
+ }
2728
+ /**
2729
+ * Assert that a capability is supported, throw if not
2730
+ */
2731
+ requireCapability(capability) {
2732
+ if (!this.supports(capability)) {
2733
+ throw new UnsupportedCapabilityError(capability, this._provider);
2734
+ }
2735
+ }
2736
+ };
2737
+
2738
+ // src/domain/shared/CarrierCode.ts
2739
+ var CarrierType2 = /* @__PURE__ */ ((CarrierType3) => {
2740
+ CarrierType3["MOBILE"] = "3J0002";
2741
+ CarrierType3["CERTIFICATE"] = "CQ0001";
2742
+ CarrierType3["AMEGO"] = "amego";
2743
+ CarrierType3["NONE"] = "";
2744
+ return CarrierType3;
2745
+ })(CarrierType2 || {});
2746
+ var CarrierCode = class _CarrierCode {
2747
+ constructor(_type, _code1, _code2) {
2748
+ this._type = _type;
2749
+ this._code1 = _code1;
2750
+ this._code2 = _code2;
2751
+ }
2752
+ /**
2753
+ * Create a CarrierCode for mobile barcode
2754
+ * Format: /[A-Z0-9.+-]{7}
2755
+ */
2756
+ static mobile(code) {
2757
+ const normalized = code.trim().toUpperCase();
2758
+ if (!_CarrierCode.isValidMobileBarcode(normalized)) {
2759
+ throw new InvalidCarrierCodeError(code, "3J0002" /* MOBILE */);
2760
+ }
2761
+ return new _CarrierCode("3J0002" /* MOBILE */, normalized, normalized);
2762
+ }
2763
+ /**
2764
+ * Create a CarrierCode for natural person certificate
2765
+ * Format: 2 letters + 14 digits
2766
+ */
2767
+ static certificate(code) {
2768
+ const normalized = code.trim().toUpperCase();
2769
+ if (!_CarrierCode.isValidCertificate(normalized)) {
2770
+ throw new InvalidCarrierCodeError(code, "CQ0001" /* CERTIFICATE */);
2771
+ }
2772
+ return new _CarrierCode("CQ0001" /* CERTIFICATE */, normalized, normalized);
2773
+ }
2774
+ /**
2775
+ * Create a CarrierCode for Amego member
2776
+ * Format: a+phone number (a0911222333) or email
2777
+ */
2778
+ static amego(code) {
2779
+ const normalized = code.trim().toLowerCase();
2780
+ if (!_CarrierCode.isValidAmego(normalized)) {
2781
+ throw new InvalidCarrierCodeError(code, "amego" /* AMEGO */);
2782
+ }
2783
+ return new _CarrierCode("amego" /* AMEGO */, normalized, normalized);
2784
+ }
2785
+ /**
2786
+ * Create a CarrierCode for custom carrier type
2787
+ */
2788
+ static custom(type, code1, code2) {
2789
+ return new _CarrierCode(type, code1.trim(), code2.trim());
2790
+ }
2791
+ /**
2792
+ * Create an empty carrier (no carrier)
2793
+ */
2794
+ static none() {
2795
+ return new _CarrierCode("" /* NONE */, "", "");
2796
+ }
2797
+ /**
2798
+ * Validate mobile barcode format
2799
+ * Format: starts with "/" followed by 7 characters (A-Z, 0-9, +, -, .)
2800
+ */
2801
+ static isValidMobileBarcode(code) {
2802
+ return /^\/[A-Z0-9.+-]{7}$/.test(code);
2803
+ }
2804
+ /**
2805
+ * Validate natural person certificate format
2806
+ * Format: 2 uppercase letters + 14 digits
2807
+ */
2808
+ static isValidCertificate(code) {
2809
+ return /^[A-Z]{2}\d{14}$/.test(code);
2810
+ }
2811
+ /**
2812
+ * Validate Amego member carrier format
2813
+ * Format: "a" + phone number or email
2814
+ */
2815
+ static isValidAmego(code) {
2816
+ if (/^a09\d{8}$/.test(code)) {
2817
+ return true;
2818
+ }
2819
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(code)) {
2820
+ return true;
2821
+ }
2822
+ return false;
2823
+ }
2824
+ /**
2825
+ * Get carrier type (alias for type property)
2826
+ */
2827
+ getType() {
2828
+ return this._type;
2829
+ }
2830
+ /**
2831
+ * Get carrier code 1 (顯碼)
2832
+ */
2833
+ getCode1() {
2834
+ return this._code1;
2835
+ }
2836
+ /**
2837
+ * Get carrier code 2 (隱碼)
2838
+ */
2839
+ getCode2() {
2840
+ return this._code2;
2841
+ }
2842
+ /**
2843
+ * Carrier type for API usage
2844
+ */
2845
+ get type() {
2846
+ return this._type;
2847
+ }
2848
+ /**
2849
+ * Carrier value (code1) for API usage
2850
+ */
2851
+ get value() {
2852
+ return this._code1;
2853
+ }
2854
+ /**
2855
+ * Check if this is an empty carrier
2856
+ */
2857
+ isEmpty() {
2858
+ return this._type === "" /* NONE */;
2859
+ }
2860
+ /**
2861
+ * Check if this is a mobile barcode carrier
2862
+ */
2863
+ isMobile() {
2864
+ return this._type === "3J0002" /* MOBILE */;
2865
+ }
2866
+ /**
2867
+ * Check equality
2868
+ */
2869
+ equals(other) {
2870
+ return this._type === other._type && this._code1 === other._code1 && this._code2 === other._code2;
2871
+ }
2872
+ };
2873
+
2874
+ // src/domain/invoice/InvoiceQuery.ts
2875
+ var InvoiceQuery = class _InvoiceQuery {
2876
+ constructor(_dateRange, _dateType, _pagination) {
2877
+ this._dateRange = _dateRange;
2878
+ this._dateType = _dateType;
2879
+ this._pagination = _pagination;
2880
+ }
2881
+ /**
2882
+ * Create a query with all parameters
2883
+ */
2884
+ static create(options) {
2885
+ return new _InvoiceQuery(
2886
+ options.dateRange,
2887
+ options.dateType ?? 1 /* INVOICE_DATE */,
2888
+ options.pagination ?? Pagination.first()
2889
+ );
2890
+ }
2891
+ /**
2892
+ * Create a query by invoice date
2893
+ */
2894
+ static byInvoiceDate(dateRange, pagination) {
2895
+ return _InvoiceQuery.create({
2896
+ dateRange,
2897
+ dateType: 1 /* INVOICE_DATE */,
2898
+ pagination
2899
+ });
2900
+ }
2901
+ /**
2902
+ * Create a query by create date
2903
+ */
2904
+ static byCreateDate(dateRange, pagination) {
2905
+ return _InvoiceQuery.create({
2906
+ dateRange,
2907
+ dateType: 2 /* CREATE_DATE */,
2908
+ pagination
2909
+ });
2910
+ }
2911
+ /**
2912
+ * Create a query for a specific month
2913
+ */
2914
+ static forMonth(year, month, pagination) {
2915
+ const start = LocalDate.of(year, month, 1);
2916
+ const nextMonth = month === 12 ? 1 : month + 1;
2917
+ const nextYear = month === 12 ? year + 1 : year;
2918
+ const lastDay = new Date(nextYear, nextMonth - 1, 0).getDate();
2919
+ const end = LocalDate.of(year, month, lastDay);
2920
+ return _InvoiceQuery.create({
2921
+ dateRange: DateRange.between(start, end),
2922
+ dateType: 1 /* INVOICE_DATE */,
2923
+ pagination
2924
+ });
2925
+ }
2926
+ /**
2927
+ * Create a query for a specific year
2928
+ */
2929
+ static forYear(year, pagination) {
2930
+ return _InvoiceQuery.create({
2931
+ dateRange: DateRange.between(
2932
+ LocalDate.of(year, 1, 1),
2933
+ LocalDate.of(year, 12, 31)
2934
+ ),
2935
+ dateType: 1 /* INVOICE_DATE */,
2936
+ pagination
2937
+ });
2938
+ }
2939
+ /**
2940
+ * Get the date range
2941
+ */
2942
+ get dateRange() {
2943
+ return this._dateRange;
2944
+ }
2945
+ /**
2946
+ * Get the date type
2947
+ */
2948
+ get dateType() {
2949
+ return this._dateType;
2950
+ }
2951
+ /**
2952
+ * Get the pagination
2953
+ */
2954
+ get pagination() {
2955
+ return this._pagination;
2956
+ }
2957
+ /**
2958
+ * Create a new query with different pagination
2959
+ */
2960
+ withPagination(pagination) {
2961
+ return new _InvoiceQuery(this._dateRange, this._dateType, pagination);
2962
+ }
2963
+ /**
2964
+ * Create a query for the next page
2965
+ */
2966
+ nextPage() {
2967
+ return this.withPagination(this._pagination.next());
2968
+ }
2969
+ };
2970
+
2971
+ // src/domain/services/TaxCalculator.ts
2972
+ var TaxCalculator = class _TaxCalculator {
2973
+ /**
2974
+ * Calculate tax for invoice items
2975
+ */
2976
+ static calculateForInvoice(items, pricesIncludeTax, hasB2BTaxId) {
2977
+ const taxableSales = _TaxCalculator.sumByTaxType(items, 1 /* TAXABLE */);
2978
+ const taxExemptSales = _TaxCalculator.sumByTaxType(items, 3 /* TAX_EXEMPT */);
2979
+ const zeroRatedSales = _TaxCalculator.sumByTaxType(items, 2 /* ZERO_RATED */);
2980
+ const taxAmount = hasB2BTaxId ? _TaxCalculator.calculateTaxAmount(taxableSales, pricesIncludeTax) : Money.zero();
2981
+ const totalAmount = _TaxCalculator.calculateTotal(
2982
+ taxableSales,
2983
+ taxExemptSales,
2984
+ zeroRatedSales,
2985
+ taxAmount,
2986
+ pricesIncludeTax
2987
+ );
2988
+ return {
2989
+ taxableSales,
2990
+ taxExemptSales,
2991
+ zeroRatedSales,
2992
+ taxAmount,
2993
+ totalAmount
2994
+ };
2995
+ }
2996
+ /**
2997
+ * Calculate tax for allowance items
2998
+ */
2999
+ static calculateForAllowance(items, pricesIncludeTax) {
3000
+ const taxableSales = _TaxCalculator.sumAllowanceByTaxType(
3001
+ items,
3002
+ 1 /* TAXABLE */
3003
+ );
3004
+ const taxExemptSales = _TaxCalculator.sumAllowanceByTaxType(
3005
+ items,
3006
+ 3 /* TAX_EXEMPT */
3007
+ );
3008
+ const zeroRatedSales = _TaxCalculator.sumAllowanceByTaxType(
3009
+ items,
3010
+ 2 /* ZERO_RATED */
3011
+ );
3012
+ const taxAmount = _TaxCalculator.calculateTaxAmount(
3013
+ taxableSales,
3014
+ pricesIncludeTax
3015
+ );
3016
+ const totalAmount = _TaxCalculator.calculateTotal(
3017
+ taxableSales,
3018
+ taxExemptSales,
3019
+ zeroRatedSales,
3020
+ taxAmount,
3021
+ pricesIncludeTax
3022
+ );
3023
+ return {
3024
+ taxableSales,
3025
+ taxExemptSales,
3026
+ zeroRatedSales,
3027
+ taxAmount,
3028
+ totalAmount
3029
+ };
3030
+ }
3031
+ /**
3032
+ * Calculate tax amount from taxable sales
3033
+ */
3034
+ static calculateTaxAmount(taxableSales, pricesIncludeTax) {
3035
+ if (pricesIncludeTax) {
3036
+ const beforeTax = taxableSales.divide(1 + TAX_RATE).round();
3037
+ return taxableSales.subtract(beforeTax);
3038
+ } else {
3039
+ return taxableSales.multiply(TAX_RATE).round();
3040
+ }
3041
+ }
3042
+ /**
3043
+ * Calculate net amount (before tax) from tax-included amount
3044
+ */
3045
+ static extractNetAmount(taxIncludedAmount) {
3046
+ return taxIncludedAmount.divide(1 + TAX_RATE).round();
3047
+ }
3048
+ /**
3049
+ * Calculate gross amount (after tax) from tax-excluded amount
3050
+ */
3051
+ static addTax(taxExcludedAmount) {
3052
+ const tax = taxExcludedAmount.multiply(TAX_RATE).round();
3053
+ return taxExcludedAmount.add(tax);
3054
+ }
3055
+ /**
3056
+ * Sum invoice items by tax type
3057
+ */
3058
+ static sumByTaxType(items, taxType) {
3059
+ return items.filter((item) => item.taxType === taxType).reduce((sum, item) => sum.add(item.amount), Money.zero()).round();
3060
+ }
3061
+ /**
3062
+ * Sum allowance items by tax type
3063
+ */
3064
+ static sumAllowanceByTaxType(items, taxType) {
3065
+ return items.filter((item) => item.taxType === taxType).reduce((sum, item) => sum.add(item.amount), Money.zero()).round();
3066
+ }
3067
+ /**
3068
+ * Calculate total amount
3069
+ */
3070
+ static calculateTotal(taxableSales, taxExemptSales, zeroRatedSales, taxAmount, pricesIncludeTax) {
3071
+ if (pricesIncludeTax) {
3072
+ return taxableSales.add(taxExemptSales).add(zeroRatedSales);
3073
+ } else {
3074
+ return taxableSales.add(taxExemptSales).add(zeroRatedSales).add(taxAmount);
3075
+ }
3076
+ }
3077
+ };
3078
+
3079
+ // src/application/InvoiceService.ts
3080
+ var InvoiceService = class {
3081
+ constructor(repository) {
3082
+ this.repository = repository;
3083
+ }
3084
+ /**
3085
+ * Issue a new invoice
3086
+ */
3087
+ async issue(input) {
3088
+ const items = input.items.map((item) => {
3089
+ const unitPrice = Money.create(item.unitPrice);
3090
+ if (item.amount !== void 0) {
3091
+ return InvoiceItem.create({
3092
+ description: item.description,
3093
+ quantity: item.quantity,
3094
+ unitPrice,
3095
+ amount: Money.create(item.amount),
3096
+ unit: item.unit,
3097
+ remark: item.remark,
3098
+ taxType: item.taxType
3099
+ });
3100
+ }
3101
+ return InvoiceItem.createWithAutoAmount({
3102
+ description: item.description,
3103
+ quantity: item.quantity,
3104
+ unitPrice,
3105
+ unit: item.unit,
3106
+ remark: item.remark,
3107
+ taxType: item.taxType
3108
+ });
3109
+ });
3110
+ const buyerTaxId = input.buyerTaxId ? TaxId.tryCreate(input.buyerTaxId) ?? TaxId.none() : TaxId.none();
3111
+ const buyer = buyerTaxId.isNone() ? Buyer.anonymous(input.buyerName) : Buyer.company({
3112
+ taxId: buyerTaxId,
3113
+ name: input.buyerName,
3114
+ address: input.buyerAddress,
3115
+ phone: input.buyerPhone,
3116
+ email: input.buyerEmail
3117
+ });
3118
+ let carrier;
3119
+ if (input.carrier) {
3120
+ switch (input.carrier.type) {
3121
+ case "mobile":
3122
+ carrier = Carrier.mobile(input.carrier.value);
3123
+ break;
3124
+ case "certificate":
3125
+ carrier = Carrier.certificate(input.carrier.value);
3126
+ break;
3127
+ case "amego":
3128
+ carrier = Carrier.custom("amego", input.carrier.value);
3129
+ break;
3130
+ }
3131
+ }
3132
+ const donation = input.donationCode ? Donation.tryCreate(input.donationCode) : void 0;
3133
+ const invoice = Invoice.create({
3134
+ orderId: OrderId.create(input.orderId),
3135
+ buyer,
3136
+ items,
3137
+ carrier,
3138
+ donation,
3139
+ remark: input.remark,
3140
+ trackApiCode: input.trackApiCode,
3141
+ customsClearanceMark: input.customsClearanceMark,
3142
+ zeroTaxRateReason: input.zeroTaxRateReason,
3143
+ brandName: input.brandName,
3144
+ pricesIncludeTax: input.pricesIncludeTax
3145
+ });
3146
+ return this.repository.issue(invoice);
3147
+ }
3148
+ /**
3149
+ * Void an invoice
3150
+ */
3151
+ async void(invoiceNumber) {
3152
+ const number = InvoiceNumber.create(invoiceNumber);
3153
+ return this.repository.void(number);
3154
+ }
3155
+ /**
3156
+ * Query invoice by invoice number
3157
+ */
3158
+ async findByInvoiceNumber(invoiceNumber) {
3159
+ const number = InvoiceNumber.create(invoiceNumber);
3160
+ return this.repository.findByInvoiceNumber(number);
3161
+ }
3162
+ /**
3163
+ * Query invoice by order ID
3164
+ */
3165
+ async findByOrderId(orderId) {
3166
+ return this.repository.findByOrderId(orderId);
3167
+ }
3168
+ /**
3169
+ * Check invoice status
3170
+ */
3171
+ async getStatus(invoiceNumbers) {
3172
+ const numbers = invoiceNumbers.map((n) => InvoiceNumber.create(n));
3173
+ return this.repository.getStatus(numbers);
3174
+ }
3175
+ /**
3176
+ * List invoices
3177
+ */
3178
+ async list(options) {
3179
+ return this.repository.list(options);
3180
+ }
3181
+ };
3182
+
3183
+ // src/application/AllowanceService.ts
3184
+ var AllowanceService = class {
3185
+ constructor(repository) {
3186
+ this.repository = repository;
3187
+ }
3188
+ /**
3189
+ * Issue a new allowance
3190
+ */
3191
+ async issue(input) {
3192
+ const items = input.items.map((item) => {
3193
+ const unitPrice = Money.create(item.unitPrice);
3194
+ if (item.amount !== void 0) {
3195
+ return AllowanceItem.create({
3196
+ originalDescription: item.originalDescription,
3197
+ quantity: item.quantity,
3198
+ unitPrice,
3199
+ amount: Money.create(item.amount),
3200
+ unit: item.unit,
3201
+ taxType: item.taxType
3202
+ });
3203
+ }
3204
+ return AllowanceItem.createWithAutoAmount({
3205
+ originalDescription: item.originalDescription,
3206
+ quantity: item.quantity,
3207
+ unitPrice,
3208
+ unit: item.unit,
3209
+ taxType: item.taxType
3210
+ });
3211
+ });
3212
+ let invoiceDate;
3213
+ if (typeof input.originalInvoiceDate === "string") {
3214
+ const dateStr = input.originalInvoiceDate;
3215
+ if (dateStr.length === 8) {
3216
+ const year = parseInt(dateStr.substring(0, 4), 10);
3217
+ const month = parseInt(dateStr.substring(4, 6), 10) - 1;
3218
+ const day = parseInt(dateStr.substring(6, 8), 10);
3219
+ invoiceDate = new Date(year, month, day);
3220
+ } else {
3221
+ invoiceDate = new Date(dateStr);
3222
+ }
3223
+ } else {
3224
+ invoiceDate = input.originalInvoiceDate;
3225
+ }
3226
+ const props = {
3227
+ originalInvoiceNumber: InvoiceNumber.create(input.originalInvoiceNumber),
3228
+ originalInvoiceDate: invoiceDate,
3229
+ buyerTaxId: input.buyerTaxId ? TaxId.tryCreate(input.buyerTaxId) ?? TaxId.none() : TaxId.none(),
3230
+ sellerTaxId: TaxId.create(input.sellerTaxId),
3231
+ items,
3232
+ buyerName: input.buyerName,
3233
+ sellerName: input.sellerName,
3234
+ pricesIncludeTax: input.pricesIncludeTax
3235
+ };
3236
+ const allowance = Allowance.create(props);
3237
+ return this.repository.issue(allowance);
3238
+ }
3239
+ /**
3240
+ * Void an allowance
3241
+ */
3242
+ async void(allowanceNumber) {
3243
+ return this.repository.void(allowanceNumber);
3244
+ }
3245
+ /**
3246
+ * Query allowance by allowance number
3247
+ */
3248
+ async findByAllowanceNumber(allowanceNumber) {
3249
+ return this.repository.findByAllowanceNumber(allowanceNumber);
3250
+ }
3251
+ /**
3252
+ * Query allowances by original invoice number
3253
+ */
3254
+ async findByInvoiceNumber(invoiceNumber) {
3255
+ const number = InvoiceNumber.create(invoiceNumber);
3256
+ return this.repository.findByInvoiceNumber(number);
3257
+ }
3258
+ /**
3259
+ * Check allowance status
3260
+ */
3261
+ async getStatus(allowanceNumbers) {
3262
+ return this.repository.getStatus(allowanceNumbers);
3263
+ }
3264
+ /**
3265
+ * List allowances
3266
+ */
3267
+ async list(options) {
3268
+ return this.repository.list(options);
3269
+ }
3270
+ };
3271
+
3272
+ export { AMEGO_DEFAULTS, AMEGO_ENDPOINTS, Allowance, AllowanceItem, AllowanceStatus, AllowanceType, AmegoAllowanceRepository, AmegoAllowanceService, AmegoClient, AmegoInvoiceRepository, AmegoInvoiceService, AmegoMapper, AmegoSigner, Buyer, Capability, Carrier, CarrierCode, CarrierType2 as CarrierType, CustomsClearanceMark, DateRange, DateType, Donation, InvalidCarrierCodeError, InvalidInvoiceNumberError, InvalidMoneyError, InvalidTaxIdError, Invoice, InvoiceItem, InvoiceNumber, InvoiceQuery, InvoiceStatus, InvoiceType, AllowanceService as LegacyAllowanceService, InvoiceService as LegacyInvoiceService, LocalDate, Money, NetworkError, OrderId, PROVIDER_CAPABILITIES, PROVIDER_NAMES, Pagination, Provider, ProviderApiError, ProviderNotImplementedError, TAX_RATE, TaxCalculator, TaxId, TaxType, UnsupportedCapabilityError, ValidationError, ZeroTaxRateReason, Zinvoice, ZinvoiceError, createPaginatedResult };
3273
+ //# sourceMappingURL=index.js.map
3274
+ //# sourceMappingURL=index.js.map