facturas 0.21.0 → 0.22.1
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/README.md +4 -2
- package/dist/{chunk-C2KX652X.mjs → chunk-5CGIRDZY.mjs} +5 -2
- package/dist/chunk-5CGIRDZY.mjs.map +1 -0
- package/dist/{chunk-FQD2PZXE.mjs → chunk-67ISWKGE.mjs} +3 -2
- package/dist/{chunk-WEEV76IC.mjs → chunk-6BLEIM5F.mjs} +2824 -299
- package/dist/chunk-6BLEIM5F.mjs.map +1 -0
- package/dist/{chunk-4PSLYA2B.mjs → chunk-7MNVZSW6.mjs} +12 -3
- package/dist/chunk-7MNVZSW6.mjs.map +1 -0
- package/dist/{chunk-QHNI4ZIA.mjs → chunk-FSJHE4VS.mjs} +2 -2
- package/dist/{chunk-HSGTCGUX.mjs → chunk-GQGYO2Z2.mjs} +2 -2
- package/dist/cli.d.ts +4 -4
- package/dist/cli.mjs +9 -9
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-CP1PQD_j.d.ts → client-ghHzZdu8.d.ts} +613 -73
- package/dist/constants.d.ts +3 -0
- package/dist/constants.mjs +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/{fiscal-evidence-Cp-ntg5p.d.ts → fiscal-evidence-DjG8zB3o.d.ts} +2 -2
- package/dist/{index-abxokwjJ.d.ts → index-BSTxy3nN.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.mjs +10 -10
- package/dist/index.mjs.map +1 -1
- package/dist/padron.d.ts +2 -2
- package/dist/padron.mjs +2 -2
- package/dist/{types-DhrPnj3r.d.ts → types-CbvsjIhf.d.ts} +2 -2
- package/dist/types.d.ts +1 -1
- package/dist/wsfe.d.ts +3 -3
- package/dist/wsfe.mjs +2 -2
- package/dist/wsmtxca.d.ts +3 -3
- package/dist/wsmtxca.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-4PSLYA2B.mjs.map +0 -1
- package/dist/chunk-C2KX652X.mjs.map +0 -1
- package/dist/chunk-WEEV76IC.mjs.map +0 -1
- /package/dist/{chunk-FQD2PZXE.mjs.map → chunk-67ISWKGE.mjs.map} +0 -0
- /package/dist/{chunk-QHNI4ZIA.mjs.map → chunk-FSJHE4VS.mjs.map} +0 -0
- /package/dist/{chunk-HSGTCGUX.mjs.map → chunk-GQGYO2Z2.mjs.map} +0 -0
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
ARCA_RECEIVER_CONDITION_IDS,
|
|
8
8
|
ARCA_VAT_RATES,
|
|
9
9
|
ARCA_VOUCHER_TYPES
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5CGIRDZY.mjs";
|
|
11
11
|
import {
|
|
12
12
|
createPadronService
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-FSJHE4VS.mjs";
|
|
14
14
|
import {
|
|
15
15
|
arcaMinorUnitsToNumber,
|
|
16
16
|
assertArcaMinorUnits,
|
|
@@ -20,12 +20,19 @@ import {
|
|
|
20
20
|
normalizeWsfeDateInput,
|
|
21
21
|
normalizeWsfeVoucherInput,
|
|
22
22
|
roundHalfEvenRatio,
|
|
23
|
+
serializeArcaAmount,
|
|
23
24
|
serializeArcaExchangeRate,
|
|
24
25
|
toIsoDate
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-67ISWKGE.mjs";
|
|
26
27
|
import {
|
|
27
28
|
createWsmtxcaService
|
|
28
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-GQGYO2Z2.mjs";
|
|
30
|
+
import {
|
|
31
|
+
classifyArcaAuthenticationError,
|
|
32
|
+
classifyArcaAuthenticationIssues,
|
|
33
|
+
createArcaAuthenticationEvidence,
|
|
34
|
+
executeWithAuthenticationRecovery
|
|
35
|
+
} from "./chunk-7MNVZSW6.mjs";
|
|
29
36
|
import {
|
|
30
37
|
ArcaConfigurationError,
|
|
31
38
|
ArcaError,
|
|
@@ -155,6 +162,16 @@ var ARCA_SERVICE_CONFIG = {
|
|
|
155
162
|
soapActionBase: "http://ar.gov.afip.dif.FEV1/",
|
|
156
163
|
useLegacyTlsSecurityLevel0: true
|
|
157
164
|
},
|
|
165
|
+
wsfex: {
|
|
166
|
+
namespace: "http://ar.gov.afip.dif.fexv1/",
|
|
167
|
+
endpoint: {
|
|
168
|
+
production: "https://servicios1.afip.gov.ar/wsfexv1/service.asmx",
|
|
169
|
+
test: "https://wswhomo.afip.gov.ar/wsfexv1/service.asmx"
|
|
170
|
+
},
|
|
171
|
+
soapVersion: "1.1",
|
|
172
|
+
soapActionBase: "http://ar.gov.afip.dif.fexv1/",
|
|
173
|
+
useLegacyTlsSecurityLevel0: true
|
|
174
|
+
},
|
|
158
175
|
wsmtxca: {
|
|
159
176
|
namespace: "http://impl.service.wsmtxca.afip.gov.ar/service/",
|
|
160
177
|
endpoint: {
|
|
@@ -303,7 +320,16 @@ function voucherFamily(type) {
|
|
|
303
320
|
});
|
|
304
321
|
}
|
|
305
322
|
var KINDS = ["invoice", "debit_note", "credit_note"];
|
|
323
|
+
var EXPORT_TYPES = [19, 20, 21];
|
|
306
324
|
function describeVoucherType(voucherType) {
|
|
325
|
+
const exportIndex = EXPORT_TYPES.indexOf(voucherType);
|
|
326
|
+
if (exportIndex !== -1) {
|
|
327
|
+
return {
|
|
328
|
+
family: "export",
|
|
329
|
+
voucherClass: "E",
|
|
330
|
+
kind: KINDS[exportIndex]
|
|
331
|
+
};
|
|
332
|
+
}
|
|
307
333
|
const { family, voucherClass, types } = voucherFamily(voucherType);
|
|
308
334
|
return {
|
|
309
335
|
family,
|
|
@@ -699,20 +725,17 @@ function receiverDocument(document) {
|
|
|
699
725
|
invalid("document.type", "an ARCA document type");
|
|
700
726
|
}
|
|
701
727
|
const number = digits(document.number, "document.number", 0, 20);
|
|
702
|
-
if (document.type === 99 || number === 0) {
|
|
703
|
-
return {};
|
|
704
|
-
}
|
|
705
728
|
return { tipoDocRec: document.type, nroDocRec: number };
|
|
706
729
|
}
|
|
707
730
|
function digits(value, field, min, max) {
|
|
708
|
-
const
|
|
709
|
-
if (!/^\d*$/.test(
|
|
731
|
+
const text2 = String(value).trim();
|
|
732
|
+
if (!/^\d*$/.test(text2) || text2.length < min || text2.length > max) {
|
|
710
733
|
invalid(field, min === max ? `${min} digits` : `${min} to ${max} digits`);
|
|
711
734
|
}
|
|
712
|
-
if (
|
|
735
|
+
if (text2 === "") {
|
|
713
736
|
return 0;
|
|
714
737
|
}
|
|
715
|
-
const parsed = Number(
|
|
738
|
+
const parsed = Number(text2);
|
|
716
739
|
if (!Number.isSafeInteger(parsed)) {
|
|
717
740
|
invalid(field, "a safe integer");
|
|
718
741
|
}
|
|
@@ -1175,10 +1198,10 @@ function deriveWsfeInvoice(input, now = /* @__PURE__ */ new Date()) {
|
|
|
1175
1198
|
const headerTotal = Number(
|
|
1176
1199
|
normalizeArcaAmountToMinorUnits(data.netAmount, "net") + normalizeArcaAmountToMinorUnits(data.vatAmount, "vat") + normalizeArcaAmountToMinorUnits(data.exemptAmount, "exempt") + normalizeArcaAmountToMinorUnits(data.nonTaxableAmount, "untaxed") + normalizeArcaAmountToMinorUnits(data.taxAmount, "tax")
|
|
1177
1200
|
);
|
|
1178
|
-
const
|
|
1179
|
-
data.totalAmount =
|
|
1201
|
+
const reviewedTotal2 = input.amounts ? input.total : void 0;
|
|
1202
|
+
data.totalAmount = reviewedTotal2 === void 0 ? headerTotal / 100 : minor(reviewedTotal2, "total");
|
|
1180
1203
|
amounts.computedTotal += headerTotal - amounts.sentTotal;
|
|
1181
|
-
amounts.sentTotal =
|
|
1204
|
+
amounts.sentTotal = reviewedTotal2 ?? headerTotal;
|
|
1182
1205
|
if (receiver.receiverVatConditionId === 5 && receiver.documentType === ARCA_DOCUMENT_TYPES.CONSUMIDOR_FINAL) {
|
|
1183
1206
|
const [whole, fraction = ""] = currency.exchangeRate.split(".");
|
|
1184
1207
|
const rate = BigInt(whole) * 1000000n + BigInt(fraction.padEnd(6, "0"));
|
|
@@ -1290,11 +1313,11 @@ function issueDocumentNumber(value, field, min, max) {
|
|
|
1290
1313
|
if (typeof value !== "number" && typeof value !== "string") {
|
|
1291
1314
|
invalid2(field, `a positive document number with ${min} to ${max} digits`);
|
|
1292
1315
|
}
|
|
1293
|
-
const
|
|
1294
|
-
if (!/^\d+$/.test(
|
|
1316
|
+
const text2 = String(value);
|
|
1317
|
+
if (!/^\d+$/.test(text2) || text2.length < min || text2.length > max || !Number.isSafeInteger(Number(text2)) || Number(text2) <= 0) {
|
|
1295
1318
|
invalid2(field, `a positive document number with ${min} to ${max} digits`);
|
|
1296
1319
|
}
|
|
1297
|
-
return Number(
|
|
1320
|
+
return Number(text2);
|
|
1298
1321
|
}
|
|
1299
1322
|
function deriveCurrency(input) {
|
|
1300
1323
|
const currency = input.currency === void 0 ? "ARS" : input.currency;
|
|
@@ -1407,6 +1430,16 @@ function voucherDateWindow(input) {
|
|
|
1407
1430
|
if (!(now instanceof Date) || Number.isNaN(now.getTime())) {
|
|
1408
1431
|
invalid2("now", "a valid Date");
|
|
1409
1432
|
}
|
|
1433
|
+
if (EXPORT_TYPES.includes(input.voucherType)) {
|
|
1434
|
+
if (input.service !== void 0) {
|
|
1435
|
+
invalid2("service", "absent: export vouchers always use WSFEX");
|
|
1436
|
+
}
|
|
1437
|
+
const { from: from2, to: to2 } = exportDateWindow(
|
|
1438
|
+
concept === "services",
|
|
1439
|
+
buenosAiresDate(now)
|
|
1440
|
+
);
|
|
1441
|
+
return { from: isoDay(from2), to: isoDay(to2) };
|
|
1442
|
+
}
|
|
1410
1443
|
voucherFamily(input.voucherType);
|
|
1411
1444
|
const { from, to } = dateWindow(
|
|
1412
1445
|
{ concept: CONCEPT_IDS[concept], voucherType: input.voucherType },
|
|
@@ -1434,18 +1467,32 @@ function dateWindow(data, service, today) {
|
|
|
1434
1467
|
function assertVoucherDateWindow(data, service, today) {
|
|
1435
1468
|
const { from, to } = dateWindow(data, service, today);
|
|
1436
1469
|
if (data.voucherDate < from || data.voucherDate > to) {
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
window
|
|
1445
|
-
}
|
|
1446
|
-
);
|
|
1470
|
+
throwOutsideWindow(from, to, today);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
function assertExportDateWindow(voucherDate, services, today) {
|
|
1474
|
+
const { from, to } = exportDateWindow(services, today);
|
|
1475
|
+
if (voucherDate < from || voucherDate > to) {
|
|
1476
|
+
throwOutsideWindow(from, to, today);
|
|
1447
1477
|
}
|
|
1448
1478
|
}
|
|
1479
|
+
function exportDateWindow(services, today) {
|
|
1480
|
+
const from = addDays(today, -5);
|
|
1481
|
+
const to = addDays(today, 5);
|
|
1482
|
+
return { from, to: services ? minDate(to, endOfMonth(today)) : to };
|
|
1483
|
+
}
|
|
1484
|
+
function throwOutsideWindow(from, to, today) {
|
|
1485
|
+
const window = { from: isoDay(from), to: isoDay(to) };
|
|
1486
|
+
throw new ArcaInputError(
|
|
1487
|
+
`date must be from ${window.from} through ${window.to}, the window ARCA accepts on ${isoDay(today)}.`,
|
|
1488
|
+
{
|
|
1489
|
+
code: "ARCA_INPUT_DATE_OUTSIDE_WINDOW",
|
|
1490
|
+
field: "date",
|
|
1491
|
+
expected: `a date from ${window.from} through ${window.to}`,
|
|
1492
|
+
window
|
|
1493
|
+
}
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1449
1496
|
function isoDay(date) {
|
|
1450
1497
|
return toIsoDate(date) ?? date;
|
|
1451
1498
|
}
|
|
@@ -1546,74 +1593,1146 @@ function deriveNumericReceiver(to) {
|
|
|
1546
1593
|
};
|
|
1547
1594
|
}
|
|
1548
1595
|
|
|
1549
|
-
// src/
|
|
1550
|
-
|
|
1551
|
-
function
|
|
1552
|
-
|
|
1596
|
+
// src/services/wsfex.ts
|
|
1597
|
+
function createWsfexService(options) {
|
|
1598
|
+
async function executeWsfexRawOperation(operation, input, body = {}, authFields = {}) {
|
|
1599
|
+
const auth2 = await options.auth.login("wsfex", {
|
|
1600
|
+
representedTaxId: input.representedTaxId,
|
|
1601
|
+
forceRefresh: input.forceRefresh,
|
|
1602
|
+
abortSignal: input.abortSignal
|
|
1603
|
+
});
|
|
1604
|
+
const response = await options.soap.execute({
|
|
1605
|
+
service: "wsfex",
|
|
1606
|
+
operation,
|
|
1607
|
+
signal: input.abortSignal,
|
|
1608
|
+
body: {
|
|
1609
|
+
Auth: {
|
|
1610
|
+
...createWsfexAuth(
|
|
1611
|
+
input.representedTaxId ?? options.config.taxId,
|
|
1612
|
+
auth2.token,
|
|
1613
|
+
auth2.sign
|
|
1614
|
+
),
|
|
1615
|
+
...authFields
|
|
1616
|
+
},
|
|
1617
|
+
...body
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
return unwrapWsfexOperationEnvelope(operation, response.result);
|
|
1621
|
+
}
|
|
1622
|
+
function executeWsfexOperation(operation, input, body = {}, authFields = {}) {
|
|
1623
|
+
return executeWithAuthenticationRecovery({
|
|
1624
|
+
service: "wsfex",
|
|
1625
|
+
operation,
|
|
1626
|
+
forceRefresh: input.forceRefresh,
|
|
1627
|
+
async execute(forceRefresh) {
|
|
1628
|
+
const result = await executeWsfexRawOperation(
|
|
1629
|
+
operation,
|
|
1630
|
+
{
|
|
1631
|
+
representedTaxId: input.representedTaxId,
|
|
1632
|
+
forceRefresh,
|
|
1633
|
+
abortSignal: input.abortSignal
|
|
1634
|
+
},
|
|
1635
|
+
body,
|
|
1636
|
+
authFields
|
|
1637
|
+
);
|
|
1638
|
+
throwForWsfexOperationError(operation, result);
|
|
1639
|
+
return result;
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
async function getWsfexResultRows(operation, rowKey, input) {
|
|
1644
|
+
const result = await executeWsfexOperation(operation, {
|
|
1645
|
+
representedTaxId: input.representedTaxId,
|
|
1646
|
+
forceRefresh: input.forceRefresh
|
|
1647
|
+
});
|
|
1648
|
+
return getWsfexRows(result.FEXResultGet, rowKey);
|
|
1649
|
+
}
|
|
1650
|
+
async function getWsfexCatalog(operation, rowKey, fields, input) {
|
|
1651
|
+
const rows2 = await getWsfexResultRows(operation, rowKey, input);
|
|
1652
|
+
return rows2.flatMap((row) => {
|
|
1653
|
+
const id = readWsfexText(row[fields.id]);
|
|
1654
|
+
return id === void 0 ? [] : [{ id, description: readWsfexText(row[fields.description]) ?? "" }];
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
function issue(input) {
|
|
1658
|
+
const request = createWsfexRequest(input);
|
|
1659
|
+
return executeWsfexAuthorization(input, request);
|
|
1660
|
+
}
|
|
1661
|
+
async function executeWsfexAuthorization(input, request) {
|
|
1662
|
+
try {
|
|
1663
|
+
const result = await executeWsfexRawOperation(
|
|
1664
|
+
"FEXAuthorize",
|
|
1665
|
+
{
|
|
1666
|
+
representedTaxId: input.representedTaxId,
|
|
1667
|
+
forceRefresh: input.forceRefresh,
|
|
1668
|
+
abortSignal: input.abortSignal
|
|
1669
|
+
},
|
|
1670
|
+
{ Cmp: request }
|
|
1671
|
+
);
|
|
1672
|
+
return classifyWsfexAuthorization(result);
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
return createWsfexIndeterminateOutcome(error);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
function lookupVoucher2(input) {
|
|
1678
|
+
return executeWithAuthenticationRecovery({
|
|
1679
|
+
service: "wsfex",
|
|
1680
|
+
operation: "FEXGetCMP",
|
|
1681
|
+
forceRefresh: input.forceRefresh,
|
|
1682
|
+
execute: (forceRefresh) => lookupVoucherOnce({ ...input, forceRefresh })
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
async function lookupVoucherOnce(input) {
|
|
1686
|
+
const operation = "FEXGetCMP";
|
|
1687
|
+
const result = await executeWsfexRawOperation(operation, input, {
|
|
1688
|
+
// ClsFEXGetCMP spells it `Cbte_tipo`; the manual's `Cbte_Tipo` would
|
|
1689
|
+
// be dropped by the server and consult type 0.
|
|
1690
|
+
Cmp: {
|
|
1691
|
+
Cbte_tipo: input.voucherType,
|
|
1692
|
+
Punto_vta: input.salesPoint,
|
|
1693
|
+
Cbte_nro: input.number
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
const errors = extractWsfexErrors(result, operation);
|
|
1697
|
+
if (errors.length > 0 && errors.every((issue2) => issue2.code === "1020")) {
|
|
1698
|
+
return {
|
|
1699
|
+
kind: "not_found",
|
|
1700
|
+
service: "wsfex",
|
|
1701
|
+
operation,
|
|
1702
|
+
errors,
|
|
1703
|
+
observations: [],
|
|
1704
|
+
raw: result
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
if (errors.length > 0) {
|
|
1708
|
+
throw createWsfexServiceError(operation, errors);
|
|
1709
|
+
}
|
|
1710
|
+
const raw = toWsfexRecord(result.FEXResultGet);
|
|
1711
|
+
if (!raw) {
|
|
1712
|
+
throw new ArcaServiceError("WSFEX did not return the consulted voucher", {
|
|
1713
|
+
service: "wsfex",
|
|
1714
|
+
operation
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
return {
|
|
1718
|
+
kind: "found",
|
|
1719
|
+
service: "wsfex",
|
|
1720
|
+
operation,
|
|
1721
|
+
voucher: mapWsfexVoucherInfo(raw),
|
|
1722
|
+
observations: extractWsfexReasons(
|
|
1723
|
+
raw.Motivos_Obs,
|
|
1724
|
+
operation,
|
|
1725
|
+
"observation"
|
|
1726
|
+
),
|
|
1727
|
+
raw: result
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
return {
|
|
1731
|
+
issue,
|
|
1732
|
+
async getLastRequestId(input = {}) {
|
|
1733
|
+
const operation = "FEXGetLast_ID";
|
|
1734
|
+
const result = await executeWsfexOperation(operation, input);
|
|
1735
|
+
return parseWsfexInteger(toWsfexRecord(result.FEXResultGet)?.Id, {
|
|
1736
|
+
operation,
|
|
1737
|
+
field: "Id",
|
|
1738
|
+
min: 0,
|
|
1739
|
+
max: WSFEX_MAX_REQUEST_ID
|
|
1740
|
+
});
|
|
1741
|
+
},
|
|
1742
|
+
async getLastVoucherNumber({ salesPoint, voucherType, ...input }) {
|
|
1743
|
+
const operation = "FEXGetLast_CMP";
|
|
1744
|
+
const result = await executeWsfexOperation(
|
|
1745
|
+
operation,
|
|
1746
|
+
input,
|
|
1747
|
+
{},
|
|
1748
|
+
{ Pto_venta: salesPoint, Cbte_Tipo: voucherType }
|
|
1749
|
+
);
|
|
1750
|
+
return parseWsfexInteger(
|
|
1751
|
+
toWsfexRecord(result.FEXResult_LastCMP)?.Cbte_nro,
|
|
1752
|
+
{ operation, field: "Cbte_nro", min: 0, max: 99999999 }
|
|
1753
|
+
);
|
|
1754
|
+
},
|
|
1755
|
+
lookupVoucher: lookupVoucher2,
|
|
1756
|
+
async getSalesPoints(input = {}) {
|
|
1757
|
+
const rows2 = await getWsfexResultRows(
|
|
1758
|
+
"FEXGetPARAM_PtoVenta",
|
|
1759
|
+
"ClsFEXResponse_PtoVenta",
|
|
1760
|
+
input
|
|
1761
|
+
);
|
|
1762
|
+
return rows2.map(mapWsfexSalesPoint);
|
|
1763
|
+
},
|
|
1764
|
+
getCountries(input = {}) {
|
|
1765
|
+
return getWsfexCatalog(
|
|
1766
|
+
"FEXGetPARAM_DST_pais",
|
|
1767
|
+
"ClsFEXResponse_DST_pais",
|
|
1768
|
+
{ id: "DST_Codigo", description: "DST_Ds" },
|
|
1769
|
+
input
|
|
1770
|
+
);
|
|
1771
|
+
},
|
|
1772
|
+
async getCountryTaxIds(input = {}) {
|
|
1773
|
+
const rows2 = await getWsfexResultRows(
|
|
1774
|
+
"FEXGetPARAM_DST_CUIT",
|
|
1775
|
+
"ClsFEXResponse_DST_cuit",
|
|
1776
|
+
input
|
|
1777
|
+
);
|
|
1778
|
+
return rows2.flatMap((row) => {
|
|
1779
|
+
const taxId = readWsfexText(row.DST_CUIT);
|
|
1780
|
+
return taxId === void 0 ? [] : [{ taxId, description: readWsfexText(row.DST_Ds) ?? "" }];
|
|
1781
|
+
});
|
|
1782
|
+
},
|
|
1783
|
+
getCurrencies(input = {}) {
|
|
1784
|
+
return getWsfexCatalog(
|
|
1785
|
+
"FEXGetPARAM_MON",
|
|
1786
|
+
"ClsFEXResponse_Mon",
|
|
1787
|
+
{ id: "Mon_Id", description: "Mon_Ds" },
|
|
1788
|
+
input
|
|
1789
|
+
);
|
|
1790
|
+
},
|
|
1791
|
+
getIncoterms(input = {}) {
|
|
1792
|
+
return getWsfexCatalog(
|
|
1793
|
+
"FEXGetPARAM_Incoterms",
|
|
1794
|
+
"ClsFEXResponse_Inc",
|
|
1795
|
+
{ id: "Inc_Id", description: "Inc_Ds" },
|
|
1796
|
+
input
|
|
1797
|
+
);
|
|
1798
|
+
},
|
|
1799
|
+
getUnits(input = {}) {
|
|
1800
|
+
return getWsfexCatalog(
|
|
1801
|
+
"FEXGetPARAM_UMed",
|
|
1802
|
+
"ClsFEXResponse_UMed",
|
|
1803
|
+
{ id: "Umed_Id", description: "Umed_Ds" },
|
|
1804
|
+
input
|
|
1805
|
+
);
|
|
1806
|
+
},
|
|
1807
|
+
getLanguages(input = {}) {
|
|
1808
|
+
return getWsfexCatalog(
|
|
1809
|
+
"FEXGetPARAM_Idiomas",
|
|
1810
|
+
"ClsFEXResponse_Idi",
|
|
1811
|
+
{ id: "Idi_Id", description: "Idi_Ds" },
|
|
1812
|
+
input
|
|
1813
|
+
);
|
|
1814
|
+
},
|
|
1815
|
+
getVoucherTypes(input = {}) {
|
|
1816
|
+
return getWsfexCatalog(
|
|
1817
|
+
"FEXGetPARAM_Cbte_Tipo",
|
|
1818
|
+
"ClsFEXResponse_Cbte_Tipo",
|
|
1819
|
+
{ id: "Cbte_Id", description: "Cbte_Ds" },
|
|
1820
|
+
input
|
|
1821
|
+
);
|
|
1822
|
+
},
|
|
1823
|
+
getExportTypes(input = {}) {
|
|
1824
|
+
return getWsfexCatalog(
|
|
1825
|
+
"FEXGetPARAM_Tipo_Expo",
|
|
1826
|
+
"ClsFEXResponse_Tex",
|
|
1827
|
+
{ id: "Tex_Id", description: "Tex_Ds" },
|
|
1828
|
+
input
|
|
1829
|
+
);
|
|
1830
|
+
},
|
|
1831
|
+
async getActivities(input = {}) {
|
|
1832
|
+
const rows2 = await getWsfexResultRows(
|
|
1833
|
+
"FEXGetPARAM_Actividades",
|
|
1834
|
+
"ClsFEXResponse_ActividadTipo",
|
|
1835
|
+
input
|
|
1836
|
+
);
|
|
1837
|
+
return rows2.flatMap((row) => {
|
|
1838
|
+
const id = toWsfexActivityId(row.Id);
|
|
1839
|
+
return id === void 0 ? [] : [{ id, description: readWsfexText(row.Desc) ?? "" }];
|
|
1840
|
+
});
|
|
1841
|
+
},
|
|
1842
|
+
async getExchangeRate({ currencyId, date, ...input }) {
|
|
1843
|
+
const operation = "FEXGetPARAM_Ctz";
|
|
1844
|
+
const quotationDate = date === void 0 ? void 0 : toIsoDate(normalizeWsfexDate(date, "date"));
|
|
1845
|
+
const result = await executeWsfexOperation(operation, input, {
|
|
1846
|
+
Mon_id: currencyId,
|
|
1847
|
+
...optionalWsfexField("FchCotiz", quotationDate)
|
|
1848
|
+
});
|
|
1849
|
+
const raw = toWsfexRecord(result.FEXResultGet);
|
|
1850
|
+
const rate = canonicalizeWsfexDecimal(raw?.Mon_ctz);
|
|
1851
|
+
const rateDate = toIsoDate(readWsfexText(raw?.Mon_fecha));
|
|
1852
|
+
if (rate === void 0 || rateDate === void 0) {
|
|
1853
|
+
throw new ArcaInvalidSoapResponseError("Invalid WSFEX exchange rate", {
|
|
1854
|
+
service: "wsfex",
|
|
1855
|
+
operation
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
return { currencyId, rate, date: rateDate };
|
|
1859
|
+
},
|
|
1860
|
+
async checkPermit({ id, destination, ...input }) {
|
|
1861
|
+
const operation = "FEXCheck_Permiso";
|
|
1862
|
+
const result = await executeWsfexOperation(operation, input, {
|
|
1863
|
+
ID_Permiso: id,
|
|
1864
|
+
Dst_merc: destination
|
|
1865
|
+
});
|
|
1866
|
+
const status = readWsfexText(
|
|
1867
|
+
toWsfexRecord(result.FEXResultGet)?.Status
|
|
1868
|
+
)?.toUpperCase();
|
|
1869
|
+
if (status === "OK" || status === "NO") {
|
|
1870
|
+
return status === "OK";
|
|
1871
|
+
}
|
|
1872
|
+
throw new ArcaInvalidSoapResponseError("Invalid WSFEX permit status", {
|
|
1873
|
+
service: "wsfex",
|
|
1874
|
+
operation
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1553
1878
|
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1879
|
+
var WSFEX_MAX_REQUEST_ID = 999999999999999;
|
|
1880
|
+
var WSFEX_VOUCHER_TYPES = [19, 20, 21];
|
|
1881
|
+
var WSFEX_EXPORT_TYPES = [1, 2, 4];
|
|
1882
|
+
var WSFEX_LANGUAGES = [1, 2, 3];
|
|
1883
|
+
var WSFEX_ITEM_DECIMAL_PATTERN = /^(\d{1,12})(?:\.(\d{1,6}))?$/;
|
|
1884
|
+
var WSFEX_TAX_ID_PATTERN = /^\d{11}$/;
|
|
1885
|
+
function assertWsfexVoucherInput(voucher) {
|
|
1886
|
+
createWsfexRequest({ ...voucher, id: 0, number: 1 });
|
|
1556
1887
|
}
|
|
1557
|
-
function
|
|
1558
|
-
return
|
|
1888
|
+
function createWsfexRequest(input) {
|
|
1889
|
+
return {
|
|
1890
|
+
Id: assertWsfexInteger(input.id, "id", 0, WSFEX_MAX_REQUEST_ID),
|
|
1891
|
+
...optionalWsfexField(
|
|
1892
|
+
"Fecha_cbte",
|
|
1893
|
+
normalizeOptionalWsfexDate(input.voucherDate, "voucherDate")
|
|
1894
|
+
),
|
|
1895
|
+
Cbte_Tipo: assertWsfexCode(
|
|
1896
|
+
input.voucherType,
|
|
1897
|
+
"voucherType",
|
|
1898
|
+
WSFEX_VOUCHER_TYPES
|
|
1899
|
+
),
|
|
1900
|
+
Punto_vta: assertWsfexInteger(input.salesPoint, "salesPoint", 1, 99999),
|
|
1901
|
+
Cbte_nro: assertWsfexInteger(input.number, "number", 1, 99999999),
|
|
1902
|
+
Tipo_expo: assertWsfexCode(
|
|
1903
|
+
input.exportType,
|
|
1904
|
+
"exportType",
|
|
1905
|
+
WSFEX_EXPORT_TYPES
|
|
1906
|
+
),
|
|
1907
|
+
// ARCA expects the element present and empty for notes and services.
|
|
1908
|
+
Permiso_existente: assertOptionalWsfexFlag(input.permitExists, "permitExists") ?? "",
|
|
1909
|
+
...optionalWsfexField(
|
|
1910
|
+
"Permisos",
|
|
1911
|
+
createWsfexList(input.permits, "Permiso", createWsfexPermit)
|
|
1912
|
+
),
|
|
1913
|
+
Dst_cmp: assertWsfexInteger(input.destination, "destination", 1, 999),
|
|
1914
|
+
Cliente: requireWsfexText(input.receiverName, "receiverName"),
|
|
1915
|
+
...optionalWsfexField(
|
|
1916
|
+
"Cuit_pais_cliente",
|
|
1917
|
+
assertOptionalWsfexTaxId(
|
|
1918
|
+
input.receiverCountryTaxId,
|
|
1919
|
+
"receiverCountryTaxId"
|
|
1920
|
+
)
|
|
1921
|
+
),
|
|
1922
|
+
Domicilio_cliente: requireWsfexText(
|
|
1923
|
+
input.receiverAddress,
|
|
1924
|
+
"receiverAddress"
|
|
1925
|
+
),
|
|
1926
|
+
...optionalWsfexField(
|
|
1927
|
+
"Id_impositivo",
|
|
1928
|
+
optionalWsfexText(input.receiverTaxId, "receiverTaxId")
|
|
1929
|
+
),
|
|
1930
|
+
Moneda_Id: requireWsfexText(input.currencyId, "currencyId"),
|
|
1931
|
+
...optionalWsfexField(
|
|
1932
|
+
"Moneda_ctz",
|
|
1933
|
+
input.exchangeRate === void 0 ? void 0 : serializeArcaExchangeRate(input.exchangeRate, "exchangeRate")
|
|
1934
|
+
),
|
|
1935
|
+
...optionalWsfexField(
|
|
1936
|
+
"CanMisMonExt",
|
|
1937
|
+
assertOptionalWsfexFlag(
|
|
1938
|
+
input.sameCurrencyForeignCancellation,
|
|
1939
|
+
"sameCurrencyForeignCancellation"
|
|
1940
|
+
)
|
|
1941
|
+
),
|
|
1942
|
+
...optionalWsfexField(
|
|
1943
|
+
"Obs_comerciales",
|
|
1944
|
+
optionalWsfexText(input.commercialObservations, "commercialObservations")
|
|
1945
|
+
),
|
|
1946
|
+
Imp_total: serializeArcaAmount(input.totalAmount, "totalAmount"),
|
|
1947
|
+
...optionalWsfexField(
|
|
1948
|
+
"Obs",
|
|
1949
|
+
optionalWsfexText(input.observations, "observations")
|
|
1950
|
+
),
|
|
1951
|
+
...optionalWsfexField(
|
|
1952
|
+
"Cmps_asoc",
|
|
1953
|
+
createWsfexList(
|
|
1954
|
+
input.associatedVouchers,
|
|
1955
|
+
"Cmp_asoc",
|
|
1956
|
+
createWsfexAssociatedVoucher
|
|
1957
|
+
)
|
|
1958
|
+
),
|
|
1959
|
+
...optionalWsfexField(
|
|
1960
|
+
"Forma_pago",
|
|
1961
|
+
optionalWsfexText(input.paymentTerms, "paymentTerms")
|
|
1962
|
+
),
|
|
1963
|
+
...optionalWsfexField(
|
|
1964
|
+
"Incoterms",
|
|
1965
|
+
optionalWsfexText(input.incoterms, "incoterms")
|
|
1966
|
+
),
|
|
1967
|
+
...optionalWsfexField(
|
|
1968
|
+
"Incoterms_Ds",
|
|
1969
|
+
optionalWsfexText(input.incotermsDetail, "incotermsDetail")
|
|
1970
|
+
),
|
|
1971
|
+
Idioma_cbte: assertWsfexCode(input.language, "language", WSFEX_LANGUAGES),
|
|
1972
|
+
Items: createWsfexItems(input.items),
|
|
1973
|
+
...optionalWsfexField(
|
|
1974
|
+
"Fecha_pago",
|
|
1975
|
+
normalizeOptionalWsfexDate(input.paymentDate, "paymentDate")
|
|
1976
|
+
),
|
|
1977
|
+
...optionalWsfexField(
|
|
1978
|
+
"Actividades",
|
|
1979
|
+
createWsfexList(input.activities, "Actividad", createWsfexActivity)
|
|
1980
|
+
)
|
|
1981
|
+
};
|
|
1559
1982
|
}
|
|
1560
|
-
function
|
|
1561
|
-
|
|
1983
|
+
function createWsfexList(rows2, key, map) {
|
|
1984
|
+
if (rows2 === void 0 || rows2.length === 0) {
|
|
1985
|
+
return void 0;
|
|
1986
|
+
}
|
|
1987
|
+
return { [key]: rows2.map(map) };
|
|
1562
1988
|
}
|
|
1563
|
-
function
|
|
1564
|
-
return
|
|
1989
|
+
function createWsfexPermit(permit, index) {
|
|
1990
|
+
return {
|
|
1991
|
+
Id_permiso: requireWsfexText(permit.id, `permits[${index}].id`),
|
|
1992
|
+
Dst_merc: assertWsfexInteger(
|
|
1993
|
+
permit.destination,
|
|
1994
|
+
`permits[${index}].destination`,
|
|
1995
|
+
1,
|
|
1996
|
+
999
|
|
1997
|
+
)
|
|
1998
|
+
};
|
|
1565
1999
|
}
|
|
1566
|
-
function
|
|
1567
|
-
|
|
1568
|
-
|
|
2000
|
+
function createWsfexAssociatedVoucher(voucher, index) {
|
|
2001
|
+
const field = `associatedVouchers[${index}]`;
|
|
2002
|
+
return {
|
|
2003
|
+
Cbte_tipo: assertWsfexInteger(
|
|
2004
|
+
voucher.voucherType,
|
|
2005
|
+
`${field}.voucherType`,
|
|
2006
|
+
1,
|
|
2007
|
+
999
|
|
2008
|
+
),
|
|
2009
|
+
Cbte_punto_vta: assertWsfexInteger(
|
|
2010
|
+
voucher.salesPoint,
|
|
2011
|
+
`${field}.salesPoint`,
|
|
2012
|
+
1,
|
|
2013
|
+
99999
|
|
2014
|
+
),
|
|
2015
|
+
Cbte_nro: assertWsfexInteger(
|
|
2016
|
+
voucher.number,
|
|
2017
|
+
`${field}.number`,
|
|
2018
|
+
1,
|
|
2019
|
+
999999999
|
|
2020
|
+
),
|
|
2021
|
+
...optionalWsfexField(
|
|
2022
|
+
"Cbte_cuit",
|
|
2023
|
+
assertOptionalWsfexTaxId(voucher.issuerTaxId, `${field}.issuerTaxId`)
|
|
2024
|
+
)
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
function createWsfexItems(items) {
|
|
2028
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
2029
|
+
throw new ArcaInputError("items must contain at least one item.", {
|
|
2030
|
+
code: "ARCA_INPUT_MISSING_FIELD",
|
|
2031
|
+
field: "items",
|
|
2032
|
+
expected: "from 1 to 9999 items"
|
|
2033
|
+
});
|
|
1569
2034
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
2035
|
+
return { Item: items.map(createWsfexItem) };
|
|
2036
|
+
}
|
|
2037
|
+
function createWsfexItem(item, index) {
|
|
2038
|
+
const field = `items[${index}]`;
|
|
2039
|
+
return {
|
|
2040
|
+
...optionalWsfexField(
|
|
2041
|
+
"Pro_codigo",
|
|
2042
|
+
optionalWsfexText(item.code, `${field}.code`)
|
|
2043
|
+
),
|
|
2044
|
+
Pro_ds: requireWsfexText(item.description, `${field}.description`),
|
|
2045
|
+
...optionalWsfexField(
|
|
2046
|
+
"Pro_qty",
|
|
2047
|
+
serializeWsfexItemDecimal(item.quantity, `${field}.quantity`)
|
|
2048
|
+
),
|
|
2049
|
+
Pro_umed: assertWsfexInteger(item.unit, `${field}.unit`, 0, 99),
|
|
2050
|
+
...optionalWsfexField(
|
|
2051
|
+
"Pro_precio_uni",
|
|
2052
|
+
serializeWsfexItemDecimal(item.unitPrice, `${field}.unitPrice`)
|
|
2053
|
+
),
|
|
2054
|
+
...optionalWsfexField(
|
|
2055
|
+
"Pro_bonificacion",
|
|
2056
|
+
serializeWsfexItemDecimal(item.discount, `${field}.discount`)
|
|
2057
|
+
),
|
|
2058
|
+
Pro_total_item: serializeWsfexSignedAmount(item.amount, `${field}.amount`)
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
function createWsfexActivity(activity, index) {
|
|
2062
|
+
const field = `activities[${index}].id`;
|
|
2063
|
+
if (typeof activity.id !== "string" || !/^\d{1,6}$/.test(activity.id)) {
|
|
2064
|
+
throw new ArcaInputError(`${field} must be an activity code.`, {
|
|
2065
|
+
code: "ARCA_INPUT_INVALID_VALUE",
|
|
2066
|
+
field,
|
|
2067
|
+
expected: "a code of up to 6 digits from getActivities()"
|
|
2068
|
+
});
|
|
1572
2069
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
2070
|
+
return { Id: Number.parseInt(activity.id, 10) };
|
|
2071
|
+
}
|
|
2072
|
+
function serializeWsfexSignedAmount(value, field) {
|
|
2073
|
+
return typeof value === "number" && value < 0 ? `-${serializeArcaAmount(-value, field)}` : serializeArcaAmount(value, field);
|
|
2074
|
+
}
|
|
2075
|
+
function serializeWsfexItemDecimal(value, field) {
|
|
2076
|
+
if (value === void 0) {
|
|
2077
|
+
return void 0;
|
|
2078
|
+
}
|
|
2079
|
+
const text2 = typeof value === "number" && Number.isFinite(value) ? String(value) : value;
|
|
2080
|
+
const match = typeof text2 === "string" ? WSFEX_ITEM_DECIMAL_PATTERN.exec(text2.trim()) : null;
|
|
2081
|
+
if (!match) {
|
|
2082
|
+
throw new ArcaInputError(
|
|
2083
|
+
`${field} must be a non-negative decimal with at most 12 integer and 6 fractional digits.`,
|
|
2084
|
+
{
|
|
2085
|
+
code: "ARCA_INPUT_INVALID_AMOUNT",
|
|
2086
|
+
field,
|
|
2087
|
+
expected: "a non-negative decimal with up to 12 integer and 6 fractional digits"
|
|
2088
|
+
}
|
|
1576
2089
|
);
|
|
1577
2090
|
}
|
|
1578
|
-
return
|
|
2091
|
+
return formatWsfexDecimal(match[1] ?? "0", match[2]);
|
|
1579
2092
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
2093
|
+
function formatWsfexDecimal(integerPart, fractionPart = "") {
|
|
2094
|
+
const integer = integerPart.replace(/^0+(?=\d)/, "");
|
|
2095
|
+
const fraction = fractionPart.replace(/0+$/, "");
|
|
2096
|
+
return fraction ? `${integer}.${fraction}` : integer;
|
|
2097
|
+
}
|
|
2098
|
+
function canonicalizeWsfexDecimal(value) {
|
|
2099
|
+
const match = /^(\d+)(?:\.(\d+))?$/.exec(readWsfexText(value) ?? "");
|
|
2100
|
+
return match ? formatWsfexDecimal(match[1] ?? "0", match[2]) : void 0;
|
|
2101
|
+
}
|
|
2102
|
+
function optionalWsfexField(key, value) {
|
|
2103
|
+
return value === void 0 ? {} : { [key]: value };
|
|
2104
|
+
}
|
|
2105
|
+
function assertWsfexInteger(value, field, min, max) {
|
|
2106
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= min && value <= max) {
|
|
2107
|
+
return value;
|
|
1585
2108
|
}
|
|
2109
|
+
return throwWsfexInvalidInput(
|
|
2110
|
+
value,
|
|
2111
|
+
field,
|
|
2112
|
+
`an integer from ${min} to ${max}`
|
|
2113
|
+
);
|
|
1586
2114
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
2115
|
+
function assertWsfexCode(value, field, allowed) {
|
|
2116
|
+
if (allowed.includes(value)) {
|
|
2117
|
+
return value;
|
|
2118
|
+
}
|
|
2119
|
+
return throwWsfexInvalidInput(value, field, `one of ${allowed.join(", ")}`);
|
|
2120
|
+
}
|
|
2121
|
+
function assertOptionalWsfexFlag(value, field) {
|
|
2122
|
+
if (value === void 0 || value === "S" || value === "N") {
|
|
2123
|
+
return value;
|
|
2124
|
+
}
|
|
2125
|
+
return throwWsfexInvalidInput(value, field, "S or N");
|
|
2126
|
+
}
|
|
2127
|
+
function assertOptionalWsfexTaxId(value, field) {
|
|
2128
|
+
if (value === void 0 || typeof value === "string" && WSFEX_TAX_ID_PATTERN.test(value)) {
|
|
2129
|
+
return value;
|
|
2130
|
+
}
|
|
2131
|
+
return throwWsfexInvalidInput(value, field, "an 11-digit CUIT string");
|
|
2132
|
+
}
|
|
2133
|
+
function requireWsfexText(value, field) {
|
|
2134
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
2135
|
+
return value;
|
|
2136
|
+
}
|
|
2137
|
+
return throwWsfexInvalidInput(value, field, "a non-empty string");
|
|
2138
|
+
}
|
|
2139
|
+
function optionalWsfexText(value, field) {
|
|
2140
|
+
if (value === void 0 || typeof value === "string") {
|
|
2141
|
+
return value;
|
|
2142
|
+
}
|
|
2143
|
+
return throwWsfexInvalidInput(value, field, "a string");
|
|
2144
|
+
}
|
|
2145
|
+
function throwWsfexInvalidInput(value, field, expected) {
|
|
2146
|
+
throw new ArcaInputError(
|
|
2147
|
+
value === void 0 ? `${field} is required.` : `${field} must be ${expected}.`,
|
|
2148
|
+
{
|
|
2149
|
+
code: value === void 0 ? "ARCA_INPUT_MISSING_FIELD" : "ARCA_INPUT_INVALID_VALUE",
|
|
2150
|
+
field,
|
|
2151
|
+
expected
|
|
1595
2152
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
function normalizeOptionalWsfexDate(value, field) {
|
|
2156
|
+
return value === void 0 ? void 0 : normalizeWsfexDate(value, field);
|
|
2157
|
+
}
|
|
2158
|
+
function normalizeWsfexDate(value, field) {
|
|
2159
|
+
const text2 = typeof value === "string" ? value.trim() : "";
|
|
2160
|
+
const [, year = "", month = "", day = ""] = /^(\d{4})(\d{2})(\d{2})$/.exec(text2) ?? /^(\d{4})-(\d{2})-(\d{2})$/.exec(text2) ?? [];
|
|
2161
|
+
const candidate = new Date(
|
|
2162
|
+
Date.UTC(Number(year), Number(month) - 1, Number(day))
|
|
2163
|
+
);
|
|
2164
|
+
if (year !== "" && candidate.getUTCFullYear() === Number(year) && candidate.getUTCMonth() === Number(month) - 1 && candidate.getUTCDate() === Number(day)) {
|
|
2165
|
+
return `${year}${month}${day}`;
|
|
2166
|
+
}
|
|
2167
|
+
throw new ArcaInputError(
|
|
2168
|
+
`Invalid WSFEX ${field}: expected an existing YYYYMMDD or YYYY-MM-DD date`,
|
|
2169
|
+
{
|
|
2170
|
+
code: "ARCA_INPUT_INVALID_DATE",
|
|
2171
|
+
field,
|
|
2172
|
+
expected: "an existing YYYYMMDD or YYYY-MM-DD calendar date"
|
|
1608
2173
|
}
|
|
1609
|
-
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
function createWsfexAuth(representedTaxId, token, sign) {
|
|
2177
|
+
return {
|
|
2178
|
+
Token: token,
|
|
2179
|
+
Sign: sign,
|
|
2180
|
+
Cuit: Number.parseInt(String(representedTaxId), 10)
|
|
1610
2181
|
};
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
2182
|
+
}
|
|
2183
|
+
function unwrapWsfexOperationEnvelope(operation, response) {
|
|
2184
|
+
const operationResponse = response[`${operation}Response`];
|
|
2185
|
+
const result = operationResponse?.[`${operation}Result`] ?? response[`${operation}Result`] ?? response;
|
|
2186
|
+
return result;
|
|
2187
|
+
}
|
|
2188
|
+
function throwForWsfexOperationError(operation, result) {
|
|
2189
|
+
const errors = extractWsfexErrors(result, operation);
|
|
2190
|
+
if (errors.length > 0) {
|
|
2191
|
+
throw createWsfexServiceError(operation, errors);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
function createWsfexServiceError(operation, issues) {
|
|
2195
|
+
const authenticationError = classifyArcaAuthenticationIssues(issues, {
|
|
2196
|
+
service: "wsfex",
|
|
2197
|
+
operation
|
|
2198
|
+
});
|
|
2199
|
+
if (authenticationError) {
|
|
2200
|
+
return authenticationError;
|
|
2201
|
+
}
|
|
2202
|
+
const firstIssue = issues[0];
|
|
2203
|
+
return new ArcaServiceError(
|
|
2204
|
+
firstIssue ? formatWsfexIssue(firstIssue) : "WSFEX returned a service error",
|
|
2205
|
+
{
|
|
2206
|
+
service: "wsfex",
|
|
2207
|
+
operation,
|
|
2208
|
+
...firstIssue?.code === void 0 ? {} : { serviceCode: firstIssue.code },
|
|
2209
|
+
issues
|
|
2210
|
+
}
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
function formatWsfexIssue(issue) {
|
|
2214
|
+
return issue.code ? `(${issue.code}) ${issue.message}` : issue.message;
|
|
2215
|
+
}
|
|
2216
|
+
function extractWsfexErrors(result, operation, resultLevel) {
|
|
2217
|
+
const error = toWsfexRecord(result.FEXErr);
|
|
2218
|
+
const code = readWsfexText(error?.ErrCode);
|
|
2219
|
+
if (code === void 0 || /^0+$/.test(code)) {
|
|
2220
|
+
return [];
|
|
2221
|
+
}
|
|
2222
|
+
return [
|
|
2223
|
+
{
|
|
2224
|
+
service: "wsfex",
|
|
2225
|
+
operation,
|
|
2226
|
+
source: "error",
|
|
2227
|
+
category: getWsfexErrorCategory(operation, code),
|
|
2228
|
+
code,
|
|
2229
|
+
message: readWsfexText(error?.ErrMsg) ?? readWsfexText(error?.Errmsg) ?? "Unknown WSFEX error",
|
|
2230
|
+
...resultLevel === void 0 ? {} : { resultLevel }
|
|
2231
|
+
}
|
|
2232
|
+
];
|
|
2233
|
+
}
|
|
2234
|
+
function getWsfexErrorCategory(operation, code) {
|
|
2235
|
+
if (operation !== "FEXAuthorize") {
|
|
2236
|
+
return "unknown";
|
|
2237
|
+
}
|
|
2238
|
+
return WSFEX_AUTHORIZATION_INFRASTRUCTURE_CODES.has(code) ? "infrastructure" : "business";
|
|
2239
|
+
}
|
|
2240
|
+
var WSFEX_AUTHORIZATION_INFRASTRUCTURE_CODES = /* @__PURE__ */ new Set([
|
|
2241
|
+
"500",
|
|
2242
|
+
"501",
|
|
2243
|
+
"502",
|
|
2244
|
+
"505",
|
|
2245
|
+
"1000",
|
|
2246
|
+
"1001"
|
|
2247
|
+
]);
|
|
2248
|
+
function extractWsfexReasons(value, operation, category) {
|
|
2249
|
+
const text2 = readWsfexText(value);
|
|
2250
|
+
if (text2 === void 0) {
|
|
2251
|
+
return [];
|
|
2252
|
+
}
|
|
2253
|
+
const base = {
|
|
2254
|
+
service: "wsfex",
|
|
2255
|
+
operation,
|
|
2256
|
+
source: "observation",
|
|
2257
|
+
category,
|
|
2258
|
+
resultLevel: "header"
|
|
2259
|
+
};
|
|
2260
|
+
if (!/^\d+(?:\s*[,;|\s]\s*\d+)*$/.test(text2)) {
|
|
2261
|
+
return [{ ...base, message: text2 }];
|
|
2262
|
+
}
|
|
2263
|
+
return text2.split(/\D+/).filter(Boolean).map((token) => {
|
|
2264
|
+
const code = String(Number.parseInt(token, 10));
|
|
2265
|
+
return { ...base, code, message: code };
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
function classifyWsfexAuthorization(result) {
|
|
2269
|
+
const operation = "FEXAuthorize";
|
|
2270
|
+
const authorization = toWsfexRecord(result.FEXResultAuth);
|
|
2271
|
+
const resultCode = normalizeWsfexResult(authorization?.Resultado);
|
|
2272
|
+
const echo = readWsfexEcho(authorization);
|
|
2273
|
+
const reprocessed = readWsfexReprocessed(authorization?.Reproceso);
|
|
2274
|
+
const base = {
|
|
2275
|
+
service: "wsfex",
|
|
2276
|
+
operation,
|
|
2277
|
+
results: resultCode === void 0 ? {} : { header: resultCode },
|
|
2278
|
+
errors: extractWsfexErrors(result, operation, "header"),
|
|
2279
|
+
observations: extractWsfexReasons(
|
|
2280
|
+
authorization?.Motivos_Obs,
|
|
2281
|
+
operation,
|
|
2282
|
+
resultCode === "R" ? "business" : "observation"
|
|
2283
|
+
),
|
|
2284
|
+
raw: result
|
|
2285
|
+
};
|
|
2286
|
+
assignWsfexValue(base, "reprocessed", reprocessed);
|
|
2287
|
+
assignWsfexValue(base, "echo", echo);
|
|
2288
|
+
const context = {
|
|
2289
|
+
base,
|
|
2290
|
+
resultCode,
|
|
2291
|
+
cae: readWsfexText(authorization?.Cae),
|
|
2292
|
+
caeExpiry: readWsfexText(authorization?.Fch_venc_Cae),
|
|
2293
|
+
voucherNumber: echo?.number
|
|
2294
|
+
};
|
|
2295
|
+
return base.errors.length > 0 ? classifyWsfexErrorAnswer(context) : classifyWsfexResultAnswer(context);
|
|
2296
|
+
}
|
|
2297
|
+
function classifyWsfexErrorAnswer(context) {
|
|
2298
|
+
if (context.cae || context.resultCode === "A") {
|
|
2299
|
+
return createWsfexStructuredIndeterminate(
|
|
2300
|
+
context,
|
|
2301
|
+
"contradictory_response"
|
|
2302
|
+
);
|
|
2303
|
+
}
|
|
2304
|
+
const authenticationError = classifyArcaAuthenticationIssues(
|
|
2305
|
+
context.base.errors,
|
|
2306
|
+
{ service: "wsfex", operation: context.base.operation }
|
|
2307
|
+
);
|
|
2308
|
+
if (authenticationError) {
|
|
2309
|
+
return {
|
|
2310
|
+
...createWsfexStructuredIndeterminate(context, "authentication_rejected"),
|
|
2311
|
+
authentication: createArcaAuthenticationEvidence(authenticationError)
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
if (context.base.errors.some((issue) => issue.category === "infrastructure")) {
|
|
2315
|
+
return createWsfexStructuredIndeterminate(context, "incomplete_response");
|
|
2316
|
+
}
|
|
2317
|
+
return {
|
|
2318
|
+
...context.base,
|
|
2319
|
+
kind: "rejected",
|
|
2320
|
+
result: "R",
|
|
2321
|
+
resultLevel: "header"
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
function classifyWsfexResultAnswer(context) {
|
|
2325
|
+
const { cae, caeExpiry, voucherNumber } = context;
|
|
2326
|
+
if (context.resultCode === "A") {
|
|
2327
|
+
return cae && caeExpiry && voucherNumber !== void 0 ? {
|
|
2328
|
+
...context.base,
|
|
2329
|
+
kind: "authorized",
|
|
2330
|
+
result: "A",
|
|
2331
|
+
resultLevel: "header",
|
|
2332
|
+
cae,
|
|
2333
|
+
caeExpiry,
|
|
2334
|
+
voucherNumber
|
|
2335
|
+
} : createWsfexStructuredIndeterminate(context, "incomplete_response");
|
|
2336
|
+
}
|
|
2337
|
+
if (context.resultCode === "R") {
|
|
2338
|
+
return cae ? createWsfexStructuredIndeterminate(context, "contradictory_response") : {
|
|
2339
|
+
...context.base,
|
|
2340
|
+
kind: "rejected",
|
|
2341
|
+
result: "R",
|
|
2342
|
+
resultLevel: "header"
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
return createWsfexStructuredIndeterminate(context, "incomplete_response");
|
|
2346
|
+
}
|
|
2347
|
+
function createWsfexStructuredIndeterminate(context, reason) {
|
|
2348
|
+
const outcome = {
|
|
2349
|
+
...context.base,
|
|
2350
|
+
kind: "indeterminate",
|
|
2351
|
+
reason
|
|
2352
|
+
};
|
|
2353
|
+
assignWsfexValue(outcome, "result", context.resultCode);
|
|
2354
|
+
if (context.resultCode !== void 0) {
|
|
2355
|
+
outcome.resultLevel = "header";
|
|
2356
|
+
}
|
|
2357
|
+
assignWsfexValue(outcome, "cae", context.cae);
|
|
2358
|
+
assignWsfexValue(outcome, "caeExpiry", context.caeExpiry);
|
|
2359
|
+
return outcome;
|
|
2360
|
+
}
|
|
2361
|
+
function createWsfexIndeterminateOutcome(error) {
|
|
2362
|
+
const operation = "FEXAuthorize";
|
|
2363
|
+
const authenticationError = classifyArcaAuthenticationError(error, {
|
|
2364
|
+
service: "wsfex",
|
|
2365
|
+
operation
|
|
2366
|
+
});
|
|
2367
|
+
return {
|
|
2368
|
+
kind: "indeterminate",
|
|
2369
|
+
service: "wsfex",
|
|
2370
|
+
operation,
|
|
2371
|
+
results: {},
|
|
2372
|
+
reason: authenticationError ? "authentication_rejected" : getWsfexIndeterminateReason(error),
|
|
2373
|
+
...authenticationError ? {
|
|
2374
|
+
authentication: createArcaAuthenticationEvidence(authenticationError)
|
|
2375
|
+
} : {},
|
|
2376
|
+
errors: [],
|
|
2377
|
+
observations: []
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
function getWsfexIndeterminateReason(error) {
|
|
2381
|
+
if (error instanceof ArcaTransportError) {
|
|
2382
|
+
return "transport_error";
|
|
2383
|
+
}
|
|
2384
|
+
if (error instanceof ArcaSoapFaultError) {
|
|
2385
|
+
return "soap_fault";
|
|
2386
|
+
}
|
|
2387
|
+
if (error instanceof ArcaInvalidSoapResponseError) {
|
|
2388
|
+
return "invalid_response";
|
|
2389
|
+
}
|
|
2390
|
+
return "unexpected_error";
|
|
2391
|
+
}
|
|
2392
|
+
function readWsfexEcho(authorization) {
|
|
2393
|
+
if (!authorization) {
|
|
2394
|
+
return void 0;
|
|
2395
|
+
}
|
|
2396
|
+
const echo = {};
|
|
2397
|
+
assignWsfexValue(
|
|
2398
|
+
echo,
|
|
2399
|
+
"id",
|
|
2400
|
+
readWsfexInteger(authorization.Id, 0, WSFEX_MAX_REQUEST_ID)
|
|
2401
|
+
);
|
|
2402
|
+
assignWsfexValue(
|
|
2403
|
+
echo,
|
|
2404
|
+
"salesPoint",
|
|
2405
|
+
readWsfexInteger(authorization.Punto_vta, 1, 99999)
|
|
2406
|
+
);
|
|
2407
|
+
assignWsfexValue(
|
|
2408
|
+
echo,
|
|
2409
|
+
"voucherType",
|
|
2410
|
+
readWsfexInteger(authorization.Cbte_tipo ?? authorization.Cbte_Tipo, 1, 999)
|
|
2411
|
+
);
|
|
2412
|
+
assignWsfexValue(
|
|
2413
|
+
echo,
|
|
2414
|
+
"number",
|
|
2415
|
+
readWsfexInteger(authorization.Cbte_nro, 1, 99999999)
|
|
2416
|
+
);
|
|
2417
|
+
assignWsfexValue(echo, "date", readWsfexText(authorization.Fch_cbte));
|
|
2418
|
+
return Object.keys(echo).length > 0 ? echo : void 0;
|
|
2419
|
+
}
|
|
2420
|
+
function readWsfexReprocessed(value) {
|
|
2421
|
+
const flag = normalizeWsfexResult(value);
|
|
2422
|
+
return flag === "S" || flag === "N" ? flag === "S" : void 0;
|
|
2423
|
+
}
|
|
2424
|
+
function mapWsfexVoucherInfo(raw) {
|
|
2425
|
+
const operation = "FEXGetCMP";
|
|
2426
|
+
const voucher = {
|
|
2427
|
+
voucherType: parseWsfexInteger(raw.Cbte_tipo ?? raw.Cbte_Tipo, {
|
|
2428
|
+
operation,
|
|
2429
|
+
field: "Cbte_tipo",
|
|
2430
|
+
min: 1,
|
|
2431
|
+
max: 999
|
|
2432
|
+
}),
|
|
2433
|
+
salesPoint: parseWsfexInteger(raw.Punto_vta, {
|
|
2434
|
+
operation,
|
|
2435
|
+
field: "Punto_vta",
|
|
2436
|
+
min: 1,
|
|
2437
|
+
max: 99999
|
|
2438
|
+
}),
|
|
2439
|
+
number: parseWsfexInteger(raw.Cbte_nro, {
|
|
2440
|
+
operation,
|
|
2441
|
+
field: "Cbte_nro",
|
|
2442
|
+
min: 1,
|
|
2443
|
+
max: 99999999
|
|
2444
|
+
}),
|
|
2445
|
+
destination: parseWsfexNumber(raw.Dst_cmp, operation, "Dst_cmp"),
|
|
2446
|
+
receiverName: readWsfexText(raw.Cliente) ?? "",
|
|
2447
|
+
receiverAddress: readWsfexText(raw.Domicilio_cliente) ?? "",
|
|
2448
|
+
currencyId: readWsfexText(raw.Moneda_Id) ?? "",
|
|
2449
|
+
totalAmount: parseWsfexNumber(raw.Imp_total, operation, "Imp_total"),
|
|
2450
|
+
items: getWsfexRows(raw.Items, "Item").map(mapWsfexLookupItem)
|
|
2451
|
+
};
|
|
2452
|
+
assignWsfexValue(
|
|
2453
|
+
voucher,
|
|
2454
|
+
"id",
|
|
2455
|
+
readWsfexInteger(raw.Id, 0, WSFEX_MAX_REQUEST_ID)
|
|
2456
|
+
);
|
|
2457
|
+
assignWsfexValue(voucher, "voucherDate", readWsfexText(raw.Fecha_cbte));
|
|
2458
|
+
assignWsfexValue(voucher, "exportType", readWsfexNumber(raw.Tipo_expo));
|
|
2459
|
+
assignWsfexValue(
|
|
2460
|
+
voucher,
|
|
2461
|
+
"permitExists",
|
|
2462
|
+
readWsfexFlag(raw.Permiso_existente)
|
|
2463
|
+
);
|
|
2464
|
+
assignWsfexValue(
|
|
2465
|
+
voucher,
|
|
2466
|
+
"permits",
|
|
2467
|
+
mapWsfexLookupList(raw.Permisos, "Permiso", mapWsfexLookupPermit)
|
|
2468
|
+
);
|
|
2469
|
+
assignWsfexValue(
|
|
2470
|
+
voucher,
|
|
2471
|
+
"receiverCountryTaxId",
|
|
2472
|
+
readWsfexTaxId(raw.Cuit_pais_cliente)
|
|
2473
|
+
);
|
|
2474
|
+
assignWsfexValue(voucher, "receiverTaxId", readWsfexText(raw.Id_impositivo));
|
|
2475
|
+
assignWsfexValue(
|
|
2476
|
+
voucher,
|
|
2477
|
+
"exchangeRate",
|
|
2478
|
+
canonicalizeWsfexDecimal(raw.Moneda_ctz)
|
|
2479
|
+
);
|
|
2480
|
+
assignWsfexValue(
|
|
2481
|
+
voucher,
|
|
2482
|
+
"sameCurrencyForeignCancellation",
|
|
2483
|
+
readWsfexFlag(raw.CanMisMonExt)
|
|
2484
|
+
);
|
|
2485
|
+
assignWsfexValue(
|
|
2486
|
+
voucher,
|
|
2487
|
+
"commercialObservations",
|
|
2488
|
+
readWsfexText(raw.Obs_comerciales)
|
|
2489
|
+
);
|
|
2490
|
+
assignWsfexValue(voucher, "observations", readWsfexText(raw.Obs));
|
|
2491
|
+
assignWsfexValue(
|
|
2492
|
+
voucher,
|
|
2493
|
+
"associatedVouchers",
|
|
2494
|
+
mapWsfexLookupList(
|
|
2495
|
+
raw.Cmps_asoc,
|
|
2496
|
+
"Cmp_asoc",
|
|
2497
|
+
mapWsfexLookupAssociatedVoucher
|
|
2498
|
+
)
|
|
2499
|
+
);
|
|
2500
|
+
assignWsfexValue(voucher, "paymentTerms", readWsfexText(raw.Forma_pago));
|
|
2501
|
+
assignWsfexValue(voucher, "incoterms", readWsfexText(raw.Incoterms));
|
|
2502
|
+
assignWsfexValue(
|
|
2503
|
+
voucher,
|
|
2504
|
+
"incotermsDetail",
|
|
2505
|
+
readWsfexIncotermsDetail(raw.Incoterms_Ds)
|
|
2506
|
+
);
|
|
2507
|
+
assignWsfexValue(voucher, "language", readWsfexNumber(raw.Idioma_cbte));
|
|
2508
|
+
assignWsfexValue(voucher, "paymentDate", readWsfexText(raw.Fecha_pago));
|
|
2509
|
+
assignWsfexValue(
|
|
2510
|
+
voucher,
|
|
2511
|
+
"activities",
|
|
2512
|
+
mapWsfexLookupList(raw.Actividades, "Actividad", (row) => {
|
|
2513
|
+
const id = toWsfexActivityId(row.Id);
|
|
2514
|
+
return id === void 0 ? void 0 : { id };
|
|
2515
|
+
})
|
|
2516
|
+
);
|
|
2517
|
+
assignWsfexValue(voucher, "result", normalizeWsfexResult(raw.Resultado));
|
|
2518
|
+
assignWsfexValue(voucher, "cae", readWsfexText(raw.Cae));
|
|
2519
|
+
assignWsfexValue(voucher, "caeExpiry", readWsfexText(raw.Fch_venc_Cae));
|
|
2520
|
+
assignWsfexValue(voucher, "authorizedAt", readWsfexText(raw.Fecha_cbte_cae));
|
|
2521
|
+
return voucher;
|
|
2522
|
+
}
|
|
2523
|
+
function mapWsfexLookupItem(row) {
|
|
2524
|
+
const operation = "FEXGetCMP";
|
|
2525
|
+
const code = readWsfexText(row.Pro_codigo);
|
|
2526
|
+
const quantity = canonicalizeWsfexDecimal(row.Pro_qty);
|
|
2527
|
+
const unitPrice = canonicalizeWsfexDecimal(row.Pro_precio_uni);
|
|
2528
|
+
const discount = canonicalizeWsfexDecimal(row.Pro_bonificacion);
|
|
2529
|
+
return {
|
|
2530
|
+
...code === void 0 ? {} : { code },
|
|
2531
|
+
description: readWsfexText(row.Pro_ds) ?? "",
|
|
2532
|
+
...quantity === void 0 ? {} : { quantity },
|
|
2533
|
+
unit: parseWsfexNumber(row.Pro_umed, operation, "Pro_umed"),
|
|
2534
|
+
...unitPrice === void 0 ? {} : { unitPrice },
|
|
2535
|
+
...discount === void 0 ? {} : { discount },
|
|
2536
|
+
amount: parseWsfexNumber(row.Pro_total_item, operation, "Pro_total_item")
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
function mapWsfexLookupPermit(row) {
|
|
2540
|
+
const id = readWsfexText(row.Id_permiso);
|
|
2541
|
+
const destination = readWsfexNumber(row.Dst_merc);
|
|
2542
|
+
return id === void 0 || destination === void 0 ? void 0 : { id, destination };
|
|
2543
|
+
}
|
|
2544
|
+
function mapWsfexLookupAssociatedVoucher(row) {
|
|
2545
|
+
const voucherType = readWsfexNumber(row.Cbte_tipo ?? row.Cbte_Tipo);
|
|
2546
|
+
const salesPoint = readWsfexNumber(row.Cbte_punto_vta);
|
|
2547
|
+
const number = readWsfexNumber(row.Cbte_nro);
|
|
2548
|
+
if (voucherType === void 0 || salesPoint === void 0 || number === void 0) {
|
|
2549
|
+
return void 0;
|
|
2550
|
+
}
|
|
2551
|
+
const issuerTaxId2 = readWsfexTaxId(row.Cbte_cuit);
|
|
2552
|
+
return {
|
|
2553
|
+
voucherType,
|
|
2554
|
+
salesPoint,
|
|
2555
|
+
number,
|
|
2556
|
+
...issuerTaxId2 === void 0 ? {} : { issuerTaxId: issuerTaxId2 }
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
function mapWsfexLookupList(container, key, map) {
|
|
2560
|
+
const rows2 = getWsfexRows(container, key);
|
|
2561
|
+
if (rows2.length === 0) {
|
|
2562
|
+
return void 0;
|
|
2563
|
+
}
|
|
2564
|
+
const result = [];
|
|
2565
|
+
for (const row of rows2) {
|
|
2566
|
+
const mapped = map(row);
|
|
2567
|
+
if (mapped === void 0) {
|
|
2568
|
+
return void 0;
|
|
2569
|
+
}
|
|
2570
|
+
result.push(mapped);
|
|
2571
|
+
}
|
|
2572
|
+
return result;
|
|
2573
|
+
}
|
|
2574
|
+
function mapWsfexSalesPoint(row) {
|
|
2575
|
+
const deactivatedAt = toIsoDate(readWsfexText(row.Pve_FchBaja));
|
|
2576
|
+
return {
|
|
2577
|
+
number: parseWsfexInteger(row.Pve_Nro, {
|
|
2578
|
+
operation: "FEXGetPARAM_PtoVenta",
|
|
2579
|
+
field: "Pve_Nro",
|
|
2580
|
+
min: 1,
|
|
2581
|
+
max: 99999
|
|
2582
|
+
}),
|
|
2583
|
+
blocked: normalizeWsfexResult(row.Pve_Bloqueado) === "S",
|
|
2584
|
+
...deactivatedAt === void 0 ? {} : { deactivatedAt }
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
function toWsfexActivityId(value) {
|
|
2588
|
+
const text2 = readWsfexText(value);
|
|
2589
|
+
return text2 !== void 0 && /^\d+$/.test(text2) && Number(text2) !== 0 ? text2.padStart(6, "0") : void 0;
|
|
2590
|
+
}
|
|
2591
|
+
function readWsfexTaxId(value) {
|
|
2592
|
+
const text2 = readWsfexText(value);
|
|
2593
|
+
return text2 !== void 0 && /^\d+$/.test(text2) && Number(text2) !== 0 ? text2 : void 0;
|
|
2594
|
+
}
|
|
2595
|
+
function readWsfexIncotermsDetail(value) {
|
|
2596
|
+
const text2 = readWsfexText(value);
|
|
2597
|
+
return text2 === "0" ? void 0 : text2;
|
|
2598
|
+
}
|
|
2599
|
+
function readWsfexFlag(value) {
|
|
2600
|
+
const flag = normalizeWsfexResult(value);
|
|
2601
|
+
return flag === "S" || flag === "N" ? flag : void 0;
|
|
2602
|
+
}
|
|
2603
|
+
function getWsfexRows(container, key) {
|
|
2604
|
+
const value = toWsfexRecord(container)?.[key];
|
|
2605
|
+
if (value === void 0) {
|
|
2606
|
+
return [];
|
|
2607
|
+
}
|
|
2608
|
+
return (Array.isArray(value) ? value : [value]).flatMap((row) => {
|
|
2609
|
+
const record = toWsfexRecord(row);
|
|
2610
|
+
return record ? [record] : [];
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
function toWsfexRecord(value) {
|
|
2614
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2615
|
+
return void 0;
|
|
2616
|
+
}
|
|
2617
|
+
const record = value;
|
|
2618
|
+
return Object.keys(record).some((key) => !key.startsWith("@_")) ? record : void 0;
|
|
2619
|
+
}
|
|
2620
|
+
function readWsfexText(value) {
|
|
2621
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
2622
|
+
return void 0;
|
|
2623
|
+
}
|
|
2624
|
+
const text2 = String(value).trim();
|
|
2625
|
+
return text2 || void 0;
|
|
2626
|
+
}
|
|
2627
|
+
function readWsfexNumber(value) {
|
|
2628
|
+
const text2 = readWsfexText(value);
|
|
2629
|
+
return text2 !== void 0 && /^-?\d+(?:\.\d+)?$/.test(text2) ? Number(text2) : void 0;
|
|
2630
|
+
}
|
|
2631
|
+
function readWsfexInteger(value, min, max) {
|
|
2632
|
+
const text2 = readWsfexText(value);
|
|
2633
|
+
if (text2 === void 0 || !/^\d+$/.test(text2)) {
|
|
2634
|
+
return void 0;
|
|
2635
|
+
}
|
|
2636
|
+
const parsed = Number(text2);
|
|
2637
|
+
return Number.isSafeInteger(parsed) && parsed >= min && parsed <= max ? parsed : void 0;
|
|
2638
|
+
}
|
|
2639
|
+
function parseWsfexInteger(value, options) {
|
|
2640
|
+
const parsed = readWsfexInteger(value, options.min, options.max);
|
|
2641
|
+
if (parsed === void 0) {
|
|
2642
|
+
throw new ArcaInvalidSoapResponseError(`Invalid WSFEX ${options.field}`, {
|
|
2643
|
+
service: "wsfex",
|
|
2644
|
+
operation: options.operation
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
return parsed;
|
|
2648
|
+
}
|
|
2649
|
+
function parseWsfexNumber(value, operation, field) {
|
|
2650
|
+
const parsed = readWsfexNumber(value);
|
|
2651
|
+
if (parsed === void 0) {
|
|
2652
|
+
throw new ArcaInvalidSoapResponseError(`Invalid WSFEX ${field}`, {
|
|
2653
|
+
service: "wsfex",
|
|
2654
|
+
operation
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
return parsed;
|
|
2658
|
+
}
|
|
2659
|
+
function normalizeWsfexResult(value) {
|
|
2660
|
+
return readWsfexText(value)?.toUpperCase();
|
|
2661
|
+
}
|
|
2662
|
+
function assignWsfexValue(target, key, value) {
|
|
2663
|
+
if (value !== void 0) {
|
|
2664
|
+
target[key] = value;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
// src/store/types.ts
|
|
2669
|
+
import { createHash } from "crypto";
|
|
2670
|
+
function attemptKey(environment, taxId, key) {
|
|
2671
|
+
return `arca:v1:attempt:${environment}:${taxId}:${key}`;
|
|
2672
|
+
}
|
|
2673
|
+
function sequenceKey(environment, taxId, salesPoint, voucherType) {
|
|
2674
|
+
return `arca:v1:sequence:${environment}:${taxId}:${salesPoint}:${voucherType}`;
|
|
2675
|
+
}
|
|
2676
|
+
function sequenceLockKey(environment, taxId, salesPoint, voucherType) {
|
|
2677
|
+
return `arca:v1:lock:sequence:${environment}:${taxId}:${salesPoint}:${voucherType}`;
|
|
2678
|
+
}
|
|
2679
|
+
function settledKey(environment, taxId, key) {
|
|
2680
|
+
return `arca:v1:settled:${environment}:${taxId}:${key}`;
|
|
2681
|
+
}
|
|
2682
|
+
function canonicalHash(input) {
|
|
2683
|
+
return createHash("sha256").update(JSON.stringify(canonical(input))).digest("hex");
|
|
2684
|
+
}
|
|
2685
|
+
function canonical(value) {
|
|
2686
|
+
if (Array.isArray(value)) {
|
|
2687
|
+
return value.map(canonical);
|
|
2688
|
+
}
|
|
2689
|
+
if (value instanceof Date) {
|
|
2690
|
+
return value.toJSON();
|
|
2691
|
+
}
|
|
2692
|
+
if (value !== null && typeof value === "object") {
|
|
2693
|
+
return Object.fromEntries(
|
|
2694
|
+
Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, canonical(item)])
|
|
2695
|
+
);
|
|
2696
|
+
}
|
|
2697
|
+
return value;
|
|
2698
|
+
}
|
|
2699
|
+
async function storeCall(fn) {
|
|
2700
|
+
try {
|
|
2701
|
+
return await fn();
|
|
2702
|
+
} catch (cause) {
|
|
2703
|
+
throw new ArcaConfigurationError("ARCA store operation failed.", { cause });
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
// src/services/wsfe-identity.ts
|
|
2708
|
+
function matchWsfeVoucherIdentity(sent, number, found) {
|
|
2709
|
+
let missing;
|
|
2710
|
+
const compare = (field, expected, actual, normalize) => {
|
|
2711
|
+
if (actual === void 0 || actual === null || expected === void 0) {
|
|
2712
|
+
missing ??= field;
|
|
2713
|
+
return void 0;
|
|
2714
|
+
}
|
|
2715
|
+
try {
|
|
2716
|
+
const left = normalize ? normalize(expected) : expected;
|
|
2717
|
+
const right = normalize ? normalize(actual) : actual;
|
|
2718
|
+
if (left !== right) {
|
|
2719
|
+
return {
|
|
2720
|
+
matches: false,
|
|
2721
|
+
evidence: "conflict",
|
|
2722
|
+
reason: `${field} differs from the sent input`
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
} catch {
|
|
2726
|
+
missing ??= field;
|
|
2727
|
+
}
|
|
2728
|
+
return void 0;
|
|
2729
|
+
};
|
|
2730
|
+
const checks = [
|
|
2731
|
+
["voucherType", sent.voucherType, found.voucherType],
|
|
2732
|
+
["salesPoint", sent.salesPoint, found.salesPoint],
|
|
2733
|
+
["number", number, found.voucherNumber, normalizeVoucherNumber],
|
|
2734
|
+
[
|
|
2735
|
+
"date",
|
|
1617
2736
|
sent.voucherDate,
|
|
1618
2737
|
found.voucherDate,
|
|
1619
2738
|
(value) => normalizeWsfeDateInput(value, "date")
|
|
@@ -1719,11 +2838,11 @@ function normalizeVoucherNumber(value) {
|
|
|
1719
2838
|
return value;
|
|
1720
2839
|
}
|
|
1721
2840
|
function normalizeDocument(value) {
|
|
1722
|
-
const
|
|
1723
|
-
if (!/^\d+$/.test(
|
|
2841
|
+
const text2 = String(value);
|
|
2842
|
+
if (!/^\d+$/.test(text2)) {
|
|
1724
2843
|
throw new Error("Invalid document number");
|
|
1725
2844
|
}
|
|
1726
|
-
return BigInt(
|
|
2845
|
+
return BigInt(text2);
|
|
1727
2846
|
}
|
|
1728
2847
|
function compareVatRates(expected, actual) {
|
|
1729
2848
|
if (actual === void 0) {
|
|
@@ -2145,19 +3264,19 @@ async function postXmlOnce({
|
|
|
2145
3264
|
});
|
|
2146
3265
|
}
|
|
2147
3266
|
return await new Promise((resolve, reject) => {
|
|
2148
|
-
let
|
|
3267
|
+
let settled2 = false;
|
|
2149
3268
|
const settleResolve = (response) => {
|
|
2150
|
-
if (
|
|
3269
|
+
if (settled2) {
|
|
2151
3270
|
return;
|
|
2152
3271
|
}
|
|
2153
|
-
|
|
3272
|
+
settled2 = true;
|
|
2154
3273
|
resolve(response);
|
|
2155
3274
|
};
|
|
2156
3275
|
const settleReject = (error) => {
|
|
2157
|
-
if (
|
|
3276
|
+
if (settled2) {
|
|
2158
3277
|
return;
|
|
2159
3278
|
}
|
|
2160
|
-
|
|
3279
|
+
settled2 = true;
|
|
2161
3280
|
reject(error);
|
|
2162
3281
|
};
|
|
2163
3282
|
const request = https.request(
|
|
@@ -2805,25 +3924,1344 @@ function createArcaLogger(config) {
|
|
|
2805
3924
|
}
|
|
2806
3925
|
};
|
|
2807
3926
|
}
|
|
2808
|
-
function resolveArcaLogLevel(level) {
|
|
2809
|
-
if (isArcaLogLevel(level)) {
|
|
2810
|
-
return level;
|
|
3927
|
+
function resolveArcaLogLevel(level) {
|
|
3928
|
+
if (isArcaLogLevel(level)) {
|
|
3929
|
+
return level;
|
|
3930
|
+
}
|
|
3931
|
+
const envLevel = process.env.ARCA_LOG_LEVEL?.trim().toLowerCase();
|
|
3932
|
+
if (isArcaLogLevel(envLevel)) {
|
|
3933
|
+
return envLevel;
|
|
3934
|
+
}
|
|
3935
|
+
return "warn";
|
|
3936
|
+
}
|
|
3937
|
+
function shouldLog(threshold, messageLevel) {
|
|
3938
|
+
return ARCA_LOG_LEVELS.indexOf(messageLevel) >= ARCA_LOG_LEVELS.indexOf(threshold);
|
|
3939
|
+
}
|
|
3940
|
+
function isArcaLogLevel(value) {
|
|
3941
|
+
return ARCA_LOG_LEVELS.includes(value);
|
|
3942
|
+
}
|
|
3943
|
+
function defaultArcaLog(level, message, ...args) {
|
|
3944
|
+
const method = level === "debug" ? console.debug : level === "info" ? console.info : level === "warn" ? console.warn : console.error;
|
|
3945
|
+
method(message, ...args);
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
// src/store/lock.ts
|
|
3949
|
+
import { randomUUID } from "crypto";
|
|
3950
|
+
var ARCA_LEASE_MS = 6e4;
|
|
3951
|
+
var RENEW_MS = 2e4;
|
|
3952
|
+
var POLL_MS = 50;
|
|
3953
|
+
var MAX_WAIT_MS = 2 * ARCA_LEASE_MS;
|
|
3954
|
+
async function withLease(driver, fn, { signal } = {}) {
|
|
3955
|
+
const owner = randomUUID();
|
|
3956
|
+
const deadline = Date.now() + MAX_WAIT_MS;
|
|
3957
|
+
throwIfAborted(signal);
|
|
3958
|
+
let held = await driver.acquire(owner);
|
|
3959
|
+
while (!held) {
|
|
3960
|
+
if (Date.now() >= deadline) {
|
|
3961
|
+
throw new ArcaLockTimeoutError(
|
|
3962
|
+
"ARCA store lock stayed held; no work was attempted.",
|
|
3963
|
+
{ reason: "held" }
|
|
3964
|
+
);
|
|
3965
|
+
}
|
|
3966
|
+
await delay2(POLL_MS + Math.floor(Math.random() * POLL_MS), signal);
|
|
3967
|
+
throwIfAborted(signal);
|
|
3968
|
+
held = await driver.acquire(owner);
|
|
3969
|
+
}
|
|
3970
|
+
if (signal?.aborted) {
|
|
3971
|
+
await driver.release(owner).catch(() => void 0);
|
|
3972
|
+
throwIfAborted(signal);
|
|
3973
|
+
}
|
|
3974
|
+
const renewal = setInterval(() => {
|
|
3975
|
+
driver.renew(owner).catch(() => void 0);
|
|
3976
|
+
}, RENEW_MS);
|
|
3977
|
+
renewal.unref?.();
|
|
3978
|
+
try {
|
|
3979
|
+
return await fn();
|
|
3980
|
+
} finally {
|
|
3981
|
+
clearInterval(renewal);
|
|
3982
|
+
await driver.release(owner).catch(() => void 0);
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
function throwIfAborted(signal) {
|
|
3986
|
+
if (signal?.aborted) {
|
|
3987
|
+
throw new ArcaLockTimeoutError(
|
|
3988
|
+
"Stopped waiting for an ARCA store lock: the caller's signal aborted; no work was attempted.",
|
|
3989
|
+
{ reason: "aborted", cause: signal.reason }
|
|
3990
|
+
);
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
function delay2(ms, signal) {
|
|
3994
|
+
if (signal?.aborted) {
|
|
3995
|
+
return Promise.resolve();
|
|
3996
|
+
}
|
|
3997
|
+
return new Promise((resolve) => {
|
|
3998
|
+
const wake = () => {
|
|
3999
|
+
clearTimeout(timer);
|
|
4000
|
+
resolve();
|
|
4001
|
+
};
|
|
4002
|
+
const timer = setTimeout(() => {
|
|
4003
|
+
signal?.removeEventListener("abort", wake);
|
|
4004
|
+
resolve();
|
|
4005
|
+
}, ms);
|
|
4006
|
+
timer.unref?.();
|
|
4007
|
+
signal?.addEventListener("abort", wake, { once: true });
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
|
|
4011
|
+
// src/services/issuance-wsfex.ts
|
|
4012
|
+
var LANGUAGES = { es: 1, en: 2, pt: 3 };
|
|
4013
|
+
var EXPORT_KINDS = { goods: 1, services: 2, other: 4 };
|
|
4014
|
+
var UNPRICED_UNITS = [0, 97, 99];
|
|
4015
|
+
function isExportVoucherType(voucherType) {
|
|
4016
|
+
return typeof voucherType === "number" && EXPORT_TYPES.includes(voucherType);
|
|
4017
|
+
}
|
|
4018
|
+
function isExportInput(input) {
|
|
4019
|
+
return input !== null && typeof input === "object" && "export" in input;
|
|
4020
|
+
}
|
|
4021
|
+
function isExportNoteInput(input) {
|
|
4022
|
+
if (input === null || typeof input !== "object" || !("for" in input)) {
|
|
4023
|
+
return false;
|
|
4024
|
+
}
|
|
4025
|
+
const target = input.for;
|
|
4026
|
+
return target !== null && typeof target === "object" && !Array.isArray(target) && isExportVoucherType(target.voucherType);
|
|
4027
|
+
}
|
|
4028
|
+
function deriveExportInvoice(input, now = /* @__PURE__ */ new Date()) {
|
|
4029
|
+
assertIssueObject(input, "input");
|
|
4030
|
+
assertIssueKeys(
|
|
4031
|
+
input,
|
|
4032
|
+
[
|
|
4033
|
+
"salesPoint",
|
|
4034
|
+
"to",
|
|
4035
|
+
"export",
|
|
4036
|
+
"items",
|
|
4037
|
+
"total",
|
|
4038
|
+
"date",
|
|
4039
|
+
"currency",
|
|
4040
|
+
"exchangeRate",
|
|
4041
|
+
"paidInForeignCurrency",
|
|
4042
|
+
"paymentTerms",
|
|
4043
|
+
"language",
|
|
4044
|
+
"observations",
|
|
4045
|
+
"commercialObservations"
|
|
4046
|
+
],
|
|
4047
|
+
"input"
|
|
4048
|
+
);
|
|
4049
|
+
assertSalesPoint2(input.salesPoint, "salesPoint");
|
|
4050
|
+
const receiver = deriveReceiver2(input.to);
|
|
4051
|
+
const exported = deriveExport(input.export);
|
|
4052
|
+
const voucherDate = normalizeWsfeDateInput(
|
|
4053
|
+
input.date ?? buenosAiresDate(now),
|
|
4054
|
+
"date"
|
|
4055
|
+
);
|
|
4056
|
+
if (exported.paymentDate !== void 0 && exported.paymentDate < voucherDate) {
|
|
4057
|
+
invalid3("export.paymentDate", "on or after the voucher date");
|
|
4058
|
+
}
|
|
4059
|
+
const currency = deriveCurrency2(input.currency, input.exchangeRate);
|
|
4060
|
+
if (input.paidInForeignCurrency !== void 0) {
|
|
4061
|
+
if (typeof input.paidInForeignCurrency !== "boolean") {
|
|
4062
|
+
invalid3("paidInForeignCurrency", "a boolean");
|
|
4063
|
+
}
|
|
4064
|
+
if (currency.currencyId === ARCA_CURRENCY_IDS.ARS) {
|
|
4065
|
+
invalid3("paidInForeignCurrency", "absent for an invoice in pesos");
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
const { items, total } = deriveItems(input.items, "items");
|
|
4069
|
+
const reviewed = reviewedTotal(input.total, total);
|
|
4070
|
+
const data = {
|
|
4071
|
+
voucherType: ARCA_VOUCHER_TYPES.FACTURA_E,
|
|
4072
|
+
salesPoint: input.salesPoint,
|
|
4073
|
+
voucherDate,
|
|
4074
|
+
exportType: exported.exportType,
|
|
4075
|
+
...exported.permitExists === void 0 ? {} : { permitExists: exported.permitExists },
|
|
4076
|
+
...exported.permits === void 0 ? {} : { permits: exported.permits },
|
|
4077
|
+
...receiver,
|
|
4078
|
+
...currency,
|
|
4079
|
+
...input.paidInForeignCurrency === void 0 ? {} : {
|
|
4080
|
+
sameCurrencyForeignCancellation: input.paidInForeignCurrency ? "S" : "N"
|
|
4081
|
+
},
|
|
4082
|
+
...optionalText(
|
|
4083
|
+
input.commercialObservations,
|
|
4084
|
+
"commercialObservations",
|
|
4085
|
+
4e3
|
|
4086
|
+
),
|
|
4087
|
+
totalAmount: reviewed / 100,
|
|
4088
|
+
...optionalText(input.observations, "observations", 1e3),
|
|
4089
|
+
paymentTerms: text(input.paymentTerms, "paymentTerms", 50),
|
|
4090
|
+
...exported.incoterms === void 0 ? {} : { incoterms: exported.incoterms },
|
|
4091
|
+
...exported.incotermsDetail === void 0 ? {} : { incotermsDetail: exported.incotermsDetail },
|
|
4092
|
+
language: deriveLanguage(input.language),
|
|
4093
|
+
items,
|
|
4094
|
+
...exported.paymentDate === void 0 ? {} : { paymentDate: exported.paymentDate }
|
|
4095
|
+
};
|
|
4096
|
+
assertWsfexVoucherInput(data);
|
|
4097
|
+
return {
|
|
4098
|
+
data,
|
|
4099
|
+
amounts: { computedTotal: total, sentTotal: reviewed, vatAdjustment: 0 }
|
|
4100
|
+
};
|
|
4101
|
+
}
|
|
4102
|
+
function deriveExportNote(original, input, kind, now = /* @__PURE__ */ new Date()) {
|
|
4103
|
+
assertIssueKeys(
|
|
4104
|
+
input,
|
|
4105
|
+
[
|
|
4106
|
+
"for",
|
|
4107
|
+
"salesPoint",
|
|
4108
|
+
"date",
|
|
4109
|
+
"exchangeRate",
|
|
4110
|
+
"observations",
|
|
4111
|
+
"commercialObservations",
|
|
4112
|
+
"items",
|
|
4113
|
+
...kind === "creditNote" ? ["all"] : []
|
|
4114
|
+
],
|
|
4115
|
+
"input",
|
|
4116
|
+
kind === "creditNote" ? "issueCreditNote()" : "issueDebitNote()"
|
|
4117
|
+
);
|
|
4118
|
+
const exportType = noteExportType(original, kind);
|
|
4119
|
+
const salesPoint = input.salesPoint ?? original.salesPoint;
|
|
4120
|
+
assertSalesPoint2(salesPoint, "salesPoint");
|
|
4121
|
+
const voucherDate = normalizeWsfeDateInput(
|
|
4122
|
+
input.date ?? buenosAiresDate(now),
|
|
4123
|
+
"date"
|
|
4124
|
+
);
|
|
4125
|
+
if (exportType === 2 && original.voucherDate !== void 0 && voucherDate < original.voucherDate) {
|
|
4126
|
+
invalid3("date", "on or after the original's date for services");
|
|
4127
|
+
}
|
|
4128
|
+
const exchangeRate2 = noteRate(original, input.exchangeRate, exportType);
|
|
4129
|
+
const lines = noteLines(original, input);
|
|
4130
|
+
const data = {
|
|
4131
|
+
voucherType: kind === "creditNote" ? ARCA_VOUCHER_TYPES.NOTA_CREDITO_E : ARCA_VOUCHER_TYPES.NOTA_DEBITO_E,
|
|
4132
|
+
salesPoint,
|
|
4133
|
+
voucherDate,
|
|
4134
|
+
exportType,
|
|
4135
|
+
...inheritedReceiver(original),
|
|
4136
|
+
currencyId: original.currencyId,
|
|
4137
|
+
...exchangeRate2 === void 0 ? {} : { exchangeRate: exchangeRate2 },
|
|
4138
|
+
...optionalText(
|
|
4139
|
+
input.commercialObservations,
|
|
4140
|
+
"commercialObservations",
|
|
4141
|
+
4e3
|
|
4142
|
+
),
|
|
4143
|
+
totalAmount: lines.total / 100,
|
|
4144
|
+
...optionalText(input.observations, "observations", 1e3),
|
|
4145
|
+
associatedVouchers: [
|
|
4146
|
+
{
|
|
4147
|
+
voucherType: original.voucherType,
|
|
4148
|
+
salesPoint: original.salesPoint,
|
|
4149
|
+
number: original.number
|
|
4150
|
+
}
|
|
4151
|
+
],
|
|
4152
|
+
...original.incoterms === void 0 ? {} : { incoterms: original.incoterms },
|
|
4153
|
+
language: original.language === 2 || original.language === 3 ? original.language : 1,
|
|
4154
|
+
items: lines.items
|
|
4155
|
+
};
|
|
4156
|
+
assertWsfexVoucherInput(data);
|
|
4157
|
+
return {
|
|
4158
|
+
data,
|
|
4159
|
+
amounts: {
|
|
4160
|
+
computedTotal: lines.total,
|
|
4161
|
+
sentTotal: lines.total,
|
|
4162
|
+
vatAdjustment: 0
|
|
4163
|
+
}
|
|
4164
|
+
};
|
|
4165
|
+
}
|
|
4166
|
+
function noteExportType(original, kind) {
|
|
4167
|
+
if (original.result !== void 0 && original.result !== "A") {
|
|
4168
|
+
invalid3("for", "an authorized voucher");
|
|
4169
|
+
}
|
|
4170
|
+
const allowed = kind === "creditNote" ? [ARCA_VOUCHER_TYPES.FACTURA_E, ARCA_VOUCHER_TYPES.NOTA_DEBITO_E] : [ARCA_VOUCHER_TYPES.FACTURA_E];
|
|
4171
|
+
if (!allowed.includes(original.voucherType)) {
|
|
4172
|
+
invalid3(
|
|
4173
|
+
"for.voucherType",
|
|
4174
|
+
kind === "creditNote" ? "19 or 20" : "19, a Factura E"
|
|
4175
|
+
);
|
|
4176
|
+
}
|
|
4177
|
+
const exportType = original.exportType;
|
|
4178
|
+
if (exportType !== 1 && exportType !== 2 && exportType !== 4) {
|
|
4179
|
+
throw new ArcaConfigurationError(
|
|
4180
|
+
"The original voucher reports no export type."
|
|
4181
|
+
);
|
|
4182
|
+
}
|
|
4183
|
+
return exportType;
|
|
4184
|
+
}
|
|
4185
|
+
function noteRate(original, rate, exportType) {
|
|
4186
|
+
if (rate === void 0) {
|
|
4187
|
+
return original.exchangeRate;
|
|
4188
|
+
}
|
|
4189
|
+
if (exportType === 2) {
|
|
4190
|
+
invalid3("exchangeRate", "absent: services notes keep the original's");
|
|
4191
|
+
}
|
|
4192
|
+
return deriveCurrency2(
|
|
4193
|
+
original.currencyId === ARCA_CURRENCY_IDS.ARS ? "ARS" : { id: original.currencyId },
|
|
4194
|
+
rate
|
|
4195
|
+
).exchangeRate;
|
|
4196
|
+
}
|
|
4197
|
+
function noteLines(original, input) {
|
|
4198
|
+
const all = "all" in input ? input.all : void 0;
|
|
4199
|
+
if (input.items === void 0 === (all === void 0)) {
|
|
4200
|
+
throw new ArcaInputError(
|
|
4201
|
+
"issueCreditNote needs exactly one mode: items for a partial note, or all: true for the whole original.",
|
|
4202
|
+
{
|
|
4203
|
+
code: "ARCA_INPUT_INVALID_VALUE",
|
|
4204
|
+
field: input.items === void 0 ? "items" : "all",
|
|
4205
|
+
expected: "exactly one of items or all: true"
|
|
4206
|
+
}
|
|
4207
|
+
);
|
|
4208
|
+
}
|
|
4209
|
+
if (all === void 0) {
|
|
4210
|
+
return deriveItems(input.items, "items");
|
|
4211
|
+
}
|
|
4212
|
+
if (all !== true) {
|
|
4213
|
+
invalid3("all", "true");
|
|
4214
|
+
}
|
|
4215
|
+
if (original.items.length === 0) {
|
|
4216
|
+
throw new ArcaInputError(
|
|
4217
|
+
"ARCA reports no lines for the original, so all: true has nothing to credit; list the items instead.",
|
|
4218
|
+
{
|
|
4219
|
+
code: "ARCA_INPUT_INVALID_VALUE",
|
|
4220
|
+
field: "all",
|
|
4221
|
+
expected: "items, since the original reports no lines"
|
|
4222
|
+
}
|
|
4223
|
+
);
|
|
4224
|
+
}
|
|
4225
|
+
return { items: original.items, total: originalTotal(original) };
|
|
4226
|
+
}
|
|
4227
|
+
function inheritedReceiver(original) {
|
|
4228
|
+
return {
|
|
4229
|
+
destination: original.destination,
|
|
4230
|
+
receiverName: original.receiverName,
|
|
4231
|
+
...original.receiverCountryTaxId === void 0 ? {} : { receiverCountryTaxId: original.receiverCountryTaxId },
|
|
4232
|
+
receiverAddress: original.receiverAddress,
|
|
4233
|
+
...original.receiverTaxId === void 0 ? {} : { receiverTaxId: original.receiverTaxId }
|
|
4234
|
+
};
|
|
4235
|
+
}
|
|
4236
|
+
function assertExportWindow(data, now = /* @__PURE__ */ new Date()) {
|
|
4237
|
+
assertExportDateWindow(
|
|
4238
|
+
data.voucherDate ?? buenosAiresDate(now),
|
|
4239
|
+
data.exportType === 2,
|
|
4240
|
+
buenosAiresDate(now)
|
|
4241
|
+
);
|
|
4242
|
+
}
|
|
4243
|
+
function exportPreview(prepared, originals) {
|
|
4244
|
+
return {
|
|
4245
|
+
voucherClass: "E",
|
|
4246
|
+
voucherType: prepared.data.voucherType,
|
|
4247
|
+
date: isoDate(prepared.data.voucherDate),
|
|
4248
|
+
header: exportHeader(prepared.data),
|
|
4249
|
+
amounts: { ...prepared.amounts },
|
|
4250
|
+
request: structuredClone(prepared.data),
|
|
4251
|
+
service: "wsfex",
|
|
4252
|
+
...originals === void 0 ? {} : { originals }
|
|
4253
|
+
};
|
|
4254
|
+
}
|
|
4255
|
+
function createExportIssuance(wsfex, context) {
|
|
4256
|
+
return {
|
|
4257
|
+
issue: (prepare, operation, hashInput, options) => issueExport(wsfex, context, prepare, operation, hashInput, options),
|
|
4258
|
+
async original(coordinates, options) {
|
|
4259
|
+
const result = await wsfex.lookupVoucher({
|
|
4260
|
+
...auth(options),
|
|
4261
|
+
...coordinates
|
|
4262
|
+
});
|
|
4263
|
+
return result.kind === "found" ? result.voucher : null;
|
|
4264
|
+
},
|
|
4265
|
+
async lookup(coordinates, options) {
|
|
4266
|
+
const result = await wsfex.lookupVoucher({
|
|
4267
|
+
...auth(options),
|
|
4268
|
+
...coordinates
|
|
4269
|
+
});
|
|
4270
|
+
return result.kind === "found" ? exportSummary(result.voucher) : null;
|
|
4271
|
+
},
|
|
4272
|
+
lastAuthorized: (sequence, options) => wsfex.getLastVoucherNumber({ ...auth(options), ...sequence }),
|
|
4273
|
+
recover: (key, json, options) => recoverExport(wsfex, context, key, json, options)
|
|
4274
|
+
};
|
|
4275
|
+
}
|
|
4276
|
+
async function recoverExport(wsfex, context, key, json, options) {
|
|
4277
|
+
const first = readExportRecord(json);
|
|
4278
|
+
const issuer = first.representedTaxId ?? context.taxId;
|
|
4279
|
+
if (options.representedTaxId !== void 0 && String(options.representedTaxId) !== issuer) {
|
|
4280
|
+
throw new ArcaInputError(
|
|
4281
|
+
"Reservation belongs to another represented taxpayer",
|
|
4282
|
+
{ code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH" }
|
|
4283
|
+
);
|
|
4284
|
+
}
|
|
4285
|
+
const consulted = {
|
|
4286
|
+
...options,
|
|
4287
|
+
...first.representedTaxId === void 0 ? {} : { representedTaxId: first.representedTaxId }
|
|
4288
|
+
};
|
|
4289
|
+
return await exportLock(
|
|
4290
|
+
context,
|
|
4291
|
+
issuer,
|
|
4292
|
+
options
|
|
4293
|
+
)(async () => {
|
|
4294
|
+
const store = context.store;
|
|
4295
|
+
const latest = store ? await storeCall(
|
|
4296
|
+
() => store.get(attemptKey(context.environment, context.taxId, key))
|
|
4297
|
+
) : null;
|
|
4298
|
+
const record = latest === null ? first : readExportRecord(latest);
|
|
4299
|
+
const outcome = await consultReservation(wsfex, record, consulted, issuer);
|
|
4300
|
+
return {
|
|
4301
|
+
...outcome,
|
|
4302
|
+
service: "wsfex",
|
|
4303
|
+
...options.include?.request ? {
|
|
4304
|
+
request: {
|
|
4305
|
+
...structuredClone(record.sent),
|
|
4306
|
+
id: record.requestId,
|
|
4307
|
+
number: record.number
|
|
4308
|
+
}
|
|
4309
|
+
} : {}
|
|
4310
|
+
};
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
async function consultReservation(wsfex, record, options, issuer) {
|
|
4314
|
+
const attempted = {
|
|
4315
|
+
salesPoint: record.salesPoint,
|
|
4316
|
+
voucherType: record.voucherType,
|
|
4317
|
+
number: record.number
|
|
4318
|
+
};
|
|
4319
|
+
const attempt = {
|
|
4320
|
+
service: "wsfex",
|
|
4321
|
+
operation: "FEXAuthorize",
|
|
4322
|
+
kind: "indeterminate",
|
|
4323
|
+
reason: "incomplete_response",
|
|
4324
|
+
results: {},
|
|
4325
|
+
errors: [],
|
|
4326
|
+
observations: []
|
|
4327
|
+
};
|
|
4328
|
+
const total = Math.round(record.sent.totalAmount * 100);
|
|
4329
|
+
const amounts = { computedTotal: total, sentTotal: total, vatAdjustment: 0 };
|
|
4330
|
+
let found;
|
|
4331
|
+
try {
|
|
4332
|
+
const result = await wsfex.lookupVoucher({
|
|
4333
|
+
...auth(options),
|
|
4334
|
+
...attempted
|
|
4335
|
+
});
|
|
4336
|
+
found = result.kind === "found" ? result.voucher : null;
|
|
4337
|
+
} catch (error) {
|
|
4338
|
+
return {
|
|
4339
|
+
kind: "indeterminate",
|
|
4340
|
+
attempted,
|
|
4341
|
+
attempt,
|
|
4342
|
+
lookup: options.abortSignal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) }
|
|
4343
|
+
};
|
|
4344
|
+
}
|
|
4345
|
+
return settled(
|
|
4346
|
+
attempt,
|
|
4347
|
+
attempt,
|
|
4348
|
+
found,
|
|
4349
|
+
record.requestId,
|
|
4350
|
+
attempted,
|
|
4351
|
+
record.sent,
|
|
4352
|
+
(authorization) => issuedVoucher(
|
|
4353
|
+
record.sent,
|
|
4354
|
+
attempted,
|
|
4355
|
+
record.requestId,
|
|
4356
|
+
authorization,
|
|
4357
|
+
amounts,
|
|
4358
|
+
issuer
|
|
4359
|
+
)
|
|
4360
|
+
);
|
|
4361
|
+
}
|
|
4362
|
+
function exportLock(context, issuer, options) {
|
|
4363
|
+
const key = `arca:v1:lock:wsfex:${context.environment}:${issuer}`;
|
|
4364
|
+
return (fn) => context.store?.withLock ? context.store.withLock(key, fn, { signal: options.abortSignal }) : withLocalLock(key, fn, options.abortSignal);
|
|
4365
|
+
}
|
|
4366
|
+
function auth(options) {
|
|
4367
|
+
return {
|
|
4368
|
+
...options.representedTaxId === void 0 ? {} : { representedTaxId: options.representedTaxId },
|
|
4369
|
+
...options.forceRefresh === void 0 ? {} : { forceRefresh: options.forceRefresh },
|
|
4370
|
+
...options.abortSignal === void 0 ? {} : { abortSignal: options.abortSignal }
|
|
4371
|
+
};
|
|
4372
|
+
}
|
|
4373
|
+
async function issueExport(wsfex, context, prepare, operation, hashInput, options) {
|
|
4374
|
+
if (options.service !== void 0) {
|
|
4375
|
+
invalid3("options.service", "absent: export vouchers always use WSFEX");
|
|
4376
|
+
}
|
|
4377
|
+
if (options.number !== void 0) {
|
|
4378
|
+
invalid3("options.number", "absent: WSFEX numbers are read under a lock");
|
|
4379
|
+
}
|
|
4380
|
+
const representedTaxId = options.representedTaxId === void 0 ? void 0 : String(options.representedTaxId);
|
|
4381
|
+
const issuer = representedTaxId ?? context.taxId;
|
|
4382
|
+
const lock = exportLock(context, issuer, options);
|
|
4383
|
+
const idempotencyKey = options.idempotencyKey;
|
|
4384
|
+
if (idempotencyKey === void 0 || !context.store) {
|
|
4385
|
+
const prepared2 = await prepare();
|
|
4386
|
+
assertExportWindow(prepared2.data);
|
|
4387
|
+
return lock(() => fresh(prepared2));
|
|
4388
|
+
}
|
|
4389
|
+
const store = context.store;
|
|
4390
|
+
const recordKey = attemptKey(
|
|
4391
|
+
context.environment,
|
|
4392
|
+
context.taxId,
|
|
4393
|
+
idempotencyKey
|
|
4394
|
+
);
|
|
4395
|
+
const inputHash = canonicalHash({
|
|
4396
|
+
input: hashInput,
|
|
4397
|
+
representedTaxId,
|
|
4398
|
+
service: "wsfex"
|
|
4399
|
+
});
|
|
4400
|
+
const existing = await storeCall(() => store.get(recordKey));
|
|
4401
|
+
if (existing !== null) {
|
|
4402
|
+
return lock(
|
|
4403
|
+
async () => replay(await storeCall(() => store.get(recordKey)) ?? existing)
|
|
4404
|
+
);
|
|
4405
|
+
}
|
|
4406
|
+
const prepared = await prepare();
|
|
4407
|
+
assertExportWindow(prepared.data);
|
|
4408
|
+
return lock(async () => {
|
|
4409
|
+
const prior = await storeCall(() => store.get(recordKey));
|
|
4410
|
+
return prior === null ? fresh(prepared) : replay(prior);
|
|
4411
|
+
});
|
|
4412
|
+
async function fresh(prepared2) {
|
|
4413
|
+
const { data } = prepared2;
|
|
4414
|
+
const number = await wsfex.getLastVoucherNumber({
|
|
4415
|
+
...auth(options),
|
|
4416
|
+
salesPoint: data.salesPoint,
|
|
4417
|
+
voucherType: data.voucherType
|
|
4418
|
+
}) + 1;
|
|
4419
|
+
const id = await nextRequestId();
|
|
4420
|
+
if (idempotencyKey === void 0 || !context.store) {
|
|
4421
|
+
return submit({ id, number, data, fresh: true });
|
|
4422
|
+
}
|
|
4423
|
+
const record = {
|
|
4424
|
+
v: 2,
|
|
4425
|
+
operation,
|
|
4426
|
+
service: "wsfex",
|
|
4427
|
+
...representedTaxId === void 0 ? {} : { representedTaxId },
|
|
4428
|
+
salesPoint: data.salesPoint,
|
|
4429
|
+
voucherType: data.voucherType,
|
|
4430
|
+
number,
|
|
4431
|
+
requestId: id,
|
|
4432
|
+
inputHash,
|
|
4433
|
+
sent: data,
|
|
4434
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4435
|
+
};
|
|
4436
|
+
const store2 = context.store;
|
|
4437
|
+
if (await storeCall(() => store2.add(recordKey, JSON.stringify(record)))) {
|
|
4438
|
+
return submit({ id, number, data, fresh: true, record });
|
|
4439
|
+
}
|
|
4440
|
+
const winner = await storeCall(() => store2.get(recordKey));
|
|
4441
|
+
if (winner === null) {
|
|
4442
|
+
throw new ArcaConfigurationError(
|
|
4443
|
+
"ARCA reservation disappeared after atomic creation lost."
|
|
4444
|
+
);
|
|
4445
|
+
}
|
|
4446
|
+
return replay(winner);
|
|
4447
|
+
}
|
|
4448
|
+
function replay(json) {
|
|
4449
|
+
const stored = readExportRecord(json);
|
|
4450
|
+
if (stored.operation !== operation || stored.inputHash !== inputHash || stored.representedTaxId !== representedTaxId) {
|
|
4451
|
+
throw new ArcaInputError(
|
|
4452
|
+
"The idempotency key was already used with different input or operation.",
|
|
4453
|
+
{
|
|
4454
|
+
code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH",
|
|
4455
|
+
field: "options.idempotencyKey"
|
|
4456
|
+
}
|
|
4457
|
+
);
|
|
4458
|
+
}
|
|
4459
|
+
return submit({
|
|
4460
|
+
id: stored.requestId,
|
|
4461
|
+
number: stored.number,
|
|
4462
|
+
data: stored.sent,
|
|
4463
|
+
fresh: false,
|
|
4464
|
+
record: stored
|
|
4465
|
+
});
|
|
4466
|
+
}
|
|
4467
|
+
async function submit(claim) {
|
|
4468
|
+
const first = await attemptOnce(claim, claim.id, claim.fresh);
|
|
4469
|
+
if (first !== "collided") {
|
|
4470
|
+
return first;
|
|
4471
|
+
}
|
|
4472
|
+
const id = await nextRequestId();
|
|
4473
|
+
await moveRecord(claim.record, id);
|
|
4474
|
+
const second = await attemptOnce(claim, id, true);
|
|
4475
|
+
if (second === "collided") {
|
|
4476
|
+
throw new ArcaConfigurationError("WSFEX answered a reused id twice.");
|
|
4477
|
+
}
|
|
4478
|
+
return second;
|
|
4479
|
+
}
|
|
4480
|
+
async function attemptOnce(claim, id, mayCollide) {
|
|
4481
|
+
const { number, data } = claim;
|
|
4482
|
+
const attempted = {
|
|
4483
|
+
salesPoint: data.salesPoint,
|
|
4484
|
+
voucherType: data.voucherType,
|
|
4485
|
+
number
|
|
4486
|
+
};
|
|
4487
|
+
const finish = (outcome2) => ({
|
|
4488
|
+
...outcome2,
|
|
4489
|
+
service: "wsfex",
|
|
4490
|
+
...options.include?.request ? { request: { ...structuredClone(data), id, number } } : {}
|
|
4491
|
+
});
|
|
4492
|
+
const outcome = await send(data, id, number);
|
|
4493
|
+
const attempt = evidence(outcome);
|
|
4494
|
+
const direct = answered(outcome, attempt, id, attempted);
|
|
4495
|
+
if (direct !== void 0) {
|
|
4496
|
+
return finish(
|
|
4497
|
+
direct.kind === "authorized" ? { ...direct, voucher: issued(data, attempted, id, direct.voucher) } : direct
|
|
4498
|
+
);
|
|
4499
|
+
}
|
|
4500
|
+
const consulted = await consult(outcome, attempt, attempted);
|
|
4501
|
+
if ("outcome" in consulted) {
|
|
4502
|
+
return finish(consulted.outcome);
|
|
4503
|
+
}
|
|
4504
|
+
const { found } = consulted;
|
|
4505
|
+
if (mayCollide && outcome.kind === "authorized" && outcome.reprocessed === true && found === null) {
|
|
4506
|
+
return "collided";
|
|
4507
|
+
}
|
|
4508
|
+
return finish(
|
|
4509
|
+
settled(
|
|
4510
|
+
outcome,
|
|
4511
|
+
attempt,
|
|
4512
|
+
found,
|
|
4513
|
+
id,
|
|
4514
|
+
attempted,
|
|
4515
|
+
data,
|
|
4516
|
+
(voucher) => issued(data, attempted, id, voucher)
|
|
4517
|
+
)
|
|
4518
|
+
);
|
|
4519
|
+
}
|
|
4520
|
+
async function consult(outcome, attempt, attempted) {
|
|
4521
|
+
const aborted = () => ({
|
|
4522
|
+
outcome: {
|
|
4523
|
+
kind: "indeterminate",
|
|
4524
|
+
attempted,
|
|
4525
|
+
attempt,
|
|
4526
|
+
lookup: { kind: "aborted" }
|
|
4527
|
+
}
|
|
4528
|
+
});
|
|
4529
|
+
if (options.abortSignal?.aborted && outcome.kind !== "authorized") {
|
|
4530
|
+
return aborted();
|
|
4531
|
+
}
|
|
4532
|
+
try {
|
|
4533
|
+
const result = await wsfex.lookupVoucher({
|
|
4534
|
+
...auth(options),
|
|
4535
|
+
...attempted
|
|
4536
|
+
});
|
|
4537
|
+
return { found: result.kind === "found" ? result.voucher : null };
|
|
4538
|
+
} catch (error) {
|
|
4539
|
+
return options.abortSignal?.aborted ? aborted() : {
|
|
4540
|
+
outcome: {
|
|
4541
|
+
kind: "indeterminate",
|
|
4542
|
+
attempted,
|
|
4543
|
+
attempt,
|
|
4544
|
+
lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
|
|
4545
|
+
}
|
|
4546
|
+
};
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
async function nextRequestId() {
|
|
4550
|
+
const received = await wsfex.getLastRequestId(auth(options));
|
|
4551
|
+
const key = requestIdKey(context.environment, issuer);
|
|
4552
|
+
const store2 = context.store;
|
|
4553
|
+
const issued2 = store2 ? readRequestIdMarker(await storeCall(() => store2.get(key))) : localRequestIds.get(key) ?? 0;
|
|
4554
|
+
const id = Math.max(received, issued2) + 1;
|
|
4555
|
+
if (id > MAX_REQUEST_ID) {
|
|
4556
|
+
throw new ArcaConfigurationError("WSFEX request ids are exhausted.");
|
|
4557
|
+
}
|
|
4558
|
+
if (store2) {
|
|
4559
|
+
const marker = { v: 1, id };
|
|
4560
|
+
await storeCall(() => store2.set(key, JSON.stringify(marker)));
|
|
4561
|
+
} else {
|
|
4562
|
+
localRequestIds.set(key, id);
|
|
4563
|
+
}
|
|
4564
|
+
return id;
|
|
4565
|
+
}
|
|
4566
|
+
function send(data, id, number) {
|
|
4567
|
+
const once = (forceRefresh) => wsfex.issue({
|
|
4568
|
+
...data,
|
|
4569
|
+
id,
|
|
4570
|
+
number,
|
|
4571
|
+
...auth({
|
|
4572
|
+
...options,
|
|
4573
|
+
...forceRefresh ? { forceRefresh: true } : {}
|
|
4574
|
+
})
|
|
4575
|
+
});
|
|
4576
|
+
return once().then(
|
|
4577
|
+
(outcome) => outcome.kind === "indeterminate" && outcome.reason === "authentication_rejected" && options.forceRefresh !== true ? once(true) : outcome
|
|
4578
|
+
);
|
|
4579
|
+
}
|
|
4580
|
+
async function moveRecord(record, id) {
|
|
4581
|
+
const store2 = context.store;
|
|
4582
|
+
if (record === void 0 || store2 === void 0) {
|
|
4583
|
+
return;
|
|
4584
|
+
}
|
|
4585
|
+
const moved = { ...record, requestId: id };
|
|
4586
|
+
await storeCall(() => store2.set(recordKey, JSON.stringify(moved)));
|
|
4587
|
+
}
|
|
4588
|
+
function issued(data, attempted, id, authorization) {
|
|
4589
|
+
const total = Math.round(data.totalAmount * 100);
|
|
4590
|
+
return issuedVoucher(
|
|
4591
|
+
data,
|
|
4592
|
+
attempted,
|
|
4593
|
+
id,
|
|
4594
|
+
authorization,
|
|
4595
|
+
{ computedTotal: total, sentTotal: total, vatAdjustment: 0 },
|
|
4596
|
+
issuer
|
|
4597
|
+
);
|
|
4598
|
+
}
|
|
4599
|
+
function evidence(outcome) {
|
|
4600
|
+
const { raw, ...rest } = outcome;
|
|
4601
|
+
return options.include?.rawResponse && raw !== void 0 ? { ...rest, rawResponse: raw } : rest;
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
function issuedVoucher(data, attempted, id, authorization, amounts, issuer) {
|
|
4605
|
+
const date = isoDate(data.voucherDate);
|
|
4606
|
+
return {
|
|
4607
|
+
...attempted,
|
|
4608
|
+
voucherClass: "E",
|
|
4609
|
+
requestId: id,
|
|
4610
|
+
date,
|
|
4611
|
+
header: exportHeader(data),
|
|
4612
|
+
cae: authorization.cae,
|
|
4613
|
+
caeExpiry: isoDate(authorization.caeExpiry) ?? "",
|
|
4614
|
+
amounts: { ...amounts },
|
|
4615
|
+
...exportQr(issuer, attempted, data, date, authorization.cae)
|
|
4616
|
+
};
|
|
4617
|
+
}
|
|
4618
|
+
function answered(outcome, attempt, id, attempted) {
|
|
4619
|
+
if (outcome.kind === "authorized" && !outcome.reprocessed && echoes(outcome, id, attempted)) {
|
|
4620
|
+
return {
|
|
4621
|
+
kind: "authorized",
|
|
4622
|
+
recoveredByMatch: false,
|
|
4623
|
+
voucher: { cae: outcome.cae, caeExpiry: outcome.caeExpiry },
|
|
4624
|
+
authorization: attempt
|
|
4625
|
+
};
|
|
4626
|
+
}
|
|
4627
|
+
if (outcome.kind === "rejected" && !hasError(outcome, "1535")) {
|
|
4628
|
+
return {
|
|
4629
|
+
kind: "rejected",
|
|
4630
|
+
attempted,
|
|
4631
|
+
issues: [...outcome.errors, ...outcome.observations],
|
|
4632
|
+
authorization: attempt
|
|
4633
|
+
};
|
|
4634
|
+
}
|
|
4635
|
+
return void 0;
|
|
4636
|
+
}
|
|
4637
|
+
function settled(outcome, attempt, found, id, attempted, sent, issue) {
|
|
4638
|
+
if (found !== null && found.id === id && sameVoucher(found, sent)) {
|
|
4639
|
+
const authorized = outcome.kind === "authorized" ? outcome : void 0;
|
|
4640
|
+
const cae = found.cae ?? authorized?.cae;
|
|
4641
|
+
const caeExpiry = found.caeExpiry ?? authorized?.caeExpiry;
|
|
4642
|
+
if (found.result !== "A" || !cae || !caeExpiry) {
|
|
4643
|
+
return {
|
|
4644
|
+
kind: "indeterminate",
|
|
4645
|
+
attempted,
|
|
4646
|
+
attempt,
|
|
4647
|
+
lookup: {
|
|
4648
|
+
kind: "incomplete",
|
|
4649
|
+
reason: "ARCA reports no approved result, CAE and CAE expiry"
|
|
4650
|
+
}
|
|
4651
|
+
};
|
|
4652
|
+
}
|
|
4653
|
+
const voucher = issue({ cae, caeExpiry });
|
|
4654
|
+
return authorized ? {
|
|
4655
|
+
kind: "authorized",
|
|
4656
|
+
recoveredByMatch: false,
|
|
4657
|
+
voucher,
|
|
4658
|
+
authorization: attempt
|
|
4659
|
+
} : {
|
|
4660
|
+
kind: "authorized",
|
|
4661
|
+
recoveredByMatch: true,
|
|
4662
|
+
voucher,
|
|
4663
|
+
attempt,
|
|
4664
|
+
lookup: exportSummary(found)
|
|
4665
|
+
};
|
|
4666
|
+
}
|
|
4667
|
+
if (found !== null) {
|
|
4668
|
+
return {
|
|
4669
|
+
kind: "conflict",
|
|
4670
|
+
attempted,
|
|
4671
|
+
attempt,
|
|
4672
|
+
found: exportSummary(found),
|
|
4673
|
+
reason: found.id === id ? "ARCA stored different fiscal fields under this request id" : "Another request holds this voucher number"
|
|
4674
|
+
};
|
|
4675
|
+
}
|
|
4676
|
+
if (outcome.kind === "rejected") {
|
|
4677
|
+
return {
|
|
4678
|
+
kind: "rejected",
|
|
4679
|
+
attempted,
|
|
4680
|
+
issues: [...outcome.errors, ...outcome.observations],
|
|
4681
|
+
authorization: attempt
|
|
4682
|
+
};
|
|
4683
|
+
}
|
|
4684
|
+
return {
|
|
4685
|
+
kind: "indeterminate",
|
|
4686
|
+
attempted,
|
|
4687
|
+
attempt,
|
|
4688
|
+
lookup: { kind: "not_found" }
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
4691
|
+
function echoes(outcome, id, attempted) {
|
|
4692
|
+
const echo = outcome.echo ?? {};
|
|
4693
|
+
return (echo.id === void 0 || echo.id === id) && (echo.salesPoint === void 0 || echo.salesPoint === attempted.salesPoint) && (echo.voucherType === void 0 || echo.voucherType === attempted.voucherType) && (echo.number === void 0 || echo.number === attempted.number);
|
|
4694
|
+
}
|
|
4695
|
+
function hasError(outcome, code) {
|
|
4696
|
+
return outcome.errors.some((issue) => issue.code === code);
|
|
4697
|
+
}
|
|
4698
|
+
function sameVoucher(found, sent) {
|
|
4699
|
+
return canonicalHash(fiscalIdentity(found)) === canonicalHash(fiscalIdentity(sent));
|
|
4700
|
+
}
|
|
4701
|
+
function fiscalIdentity(voucher) {
|
|
4702
|
+
return {
|
|
4703
|
+
voucherType: voucher.voucherType,
|
|
4704
|
+
salesPoint: voucher.salesPoint,
|
|
4705
|
+
voucherDate: trimmed(voucher.voucherDate),
|
|
4706
|
+
exportType: voucher.exportType,
|
|
4707
|
+
permitExists: trimmed(voucher.permitExists),
|
|
4708
|
+
permits: (voucher.permits ?? []).map((permit) => [
|
|
4709
|
+
trimmed(permit.id),
|
|
4710
|
+
permit.destination
|
|
4711
|
+
]),
|
|
4712
|
+
destination: voucher.destination,
|
|
4713
|
+
receiverName: trimmed(voucher.receiverName),
|
|
4714
|
+
receiverCountryTaxId: trimmed(voucher.receiverCountryTaxId),
|
|
4715
|
+
receiverAddress: trimmed(voucher.receiverAddress),
|
|
4716
|
+
receiverTaxId: trimmed(voucher.receiverTaxId),
|
|
4717
|
+
currencyId: trimmed(voucher.currencyId),
|
|
4718
|
+
exchangeRate: decimal(voucher.exchangeRate),
|
|
4719
|
+
sameCurrencyForeignCancellation: trimmed(
|
|
4720
|
+
voucher.sameCurrencyForeignCancellation
|
|
4721
|
+
),
|
|
4722
|
+
commercialObservations: trimmed(voucher.commercialObservations),
|
|
4723
|
+
totalAmount: decimal(voucher.totalAmount),
|
|
4724
|
+
observations: trimmed(voucher.observations),
|
|
4725
|
+
// ARCA may fill the issuer's CUIT; the original is named by coordinates.
|
|
4726
|
+
associatedVouchers: (voucher.associatedVouchers ?? []).map((original) => [
|
|
4727
|
+
original.voucherType,
|
|
4728
|
+
original.salesPoint,
|
|
4729
|
+
original.number
|
|
4730
|
+
]),
|
|
4731
|
+
paymentTerms: trimmed(voucher.paymentTerms),
|
|
4732
|
+
incoterms: trimmed(voucher.incoterms),
|
|
4733
|
+
// ARCA stores no detail as "0", and the lookup reads "0" as absent.
|
|
4734
|
+
incotermsDetail: trimmed(voucher.incotermsDetail) === "0" ? void 0 : trimmed(voucher.incotermsDetail),
|
|
4735
|
+
language: voucher.language,
|
|
4736
|
+
items: voucher.items.map((item) => [
|
|
4737
|
+
trimmed(item.code),
|
|
4738
|
+
trimmed(item.description),
|
|
4739
|
+
decimal(item.quantity ?? 0),
|
|
4740
|
+
item.unit,
|
|
4741
|
+
decimal(item.unitPrice ?? "0"),
|
|
4742
|
+
decimal(item.discount ?? 0),
|
|
4743
|
+
decimal(item.amount)
|
|
4744
|
+
]),
|
|
4745
|
+
paymentDate: trimmed(voucher.paymentDate),
|
|
4746
|
+
activities: (voucher.activities ?? []).map(
|
|
4747
|
+
(activity) => trimmed(activity.id)?.padStart(6, "0")
|
|
4748
|
+
)
|
|
4749
|
+
};
|
|
4750
|
+
}
|
|
4751
|
+
function trimmed(value) {
|
|
4752
|
+
return value?.replace(/\r\n?/g, "\n").trim() || void 0;
|
|
4753
|
+
}
|
|
4754
|
+
function decimal(value) {
|
|
4755
|
+
if (value === void 0) {
|
|
4756
|
+
return;
|
|
4757
|
+
}
|
|
4758
|
+
const written = typeof value === "number" ? value.toFixed(6) : value.trim();
|
|
4759
|
+
const match = /^(-?)(\d+)(?:\.(\d+))?$/.exec(written);
|
|
4760
|
+
if (!match) {
|
|
4761
|
+
return written;
|
|
4762
|
+
}
|
|
4763
|
+
const whole = (match[2] ?? "0").replace(/^0+(?=\d)/, "");
|
|
4764
|
+
const fraction = (match[3] ?? "").replace(/0+$/, "");
|
|
4765
|
+
const sign = whole === "0" && fraction === "" ? "" : match[1] ?? "";
|
|
4766
|
+
return `${sign}${whole}${fraction ? `.${fraction}` : ""}`;
|
|
4767
|
+
}
|
|
4768
|
+
var MAX_REQUEST_ID = 999999999999999;
|
|
4769
|
+
var localRequestIds = /* @__PURE__ */ new Map();
|
|
4770
|
+
function requestIdKey(environment, issuer) {
|
|
4771
|
+
return `arca:v1:wsfex:request-id:${environment}:${issuer}`;
|
|
4772
|
+
}
|
|
4773
|
+
function readRequestIdMarker(json) {
|
|
4774
|
+
if (json === null) {
|
|
4775
|
+
return 0;
|
|
4776
|
+
}
|
|
4777
|
+
try {
|
|
4778
|
+
const marker = JSON.parse(json);
|
|
4779
|
+
if (marker?.v === 1 && Number.isSafeInteger(marker.id) && marker.id >= 0) {
|
|
4780
|
+
return marker.id;
|
|
4781
|
+
}
|
|
4782
|
+
} catch {
|
|
4783
|
+
}
|
|
4784
|
+
throw new ArcaConfigurationError(
|
|
4785
|
+
"Invalid WSFEX request id marker; preserve it for reconciliation."
|
|
4786
|
+
);
|
|
4787
|
+
}
|
|
4788
|
+
var localLocks = /* @__PURE__ */ new Map();
|
|
4789
|
+
async function withLocalLock(key, fn, signal) {
|
|
4790
|
+
const previous = localLocks.get(key) ?? Promise.resolve();
|
|
4791
|
+
let release = () => void 0;
|
|
4792
|
+
const current = new Promise((resolve) => {
|
|
4793
|
+
release = resolve;
|
|
4794
|
+
});
|
|
4795
|
+
const chained = previous.then(() => current);
|
|
4796
|
+
localLocks.set(key, chained);
|
|
4797
|
+
chained.then(() => {
|
|
4798
|
+
if (localLocks.get(key) === chained) {
|
|
4799
|
+
localLocks.delete(key);
|
|
4800
|
+
}
|
|
4801
|
+
});
|
|
4802
|
+
try {
|
|
4803
|
+
await waitForTurn(previous, signal);
|
|
4804
|
+
} catch (error) {
|
|
4805
|
+
release();
|
|
4806
|
+
throw error;
|
|
4807
|
+
}
|
|
4808
|
+
try {
|
|
4809
|
+
return await fn();
|
|
4810
|
+
} finally {
|
|
4811
|
+
release();
|
|
4812
|
+
}
|
|
4813
|
+
}
|
|
4814
|
+
function waitForTurn(previous, signal) {
|
|
4815
|
+
throwIfAborted(signal);
|
|
4816
|
+
let timer;
|
|
4817
|
+
let onAbort;
|
|
4818
|
+
const stopped = new Promise((_, reject) => {
|
|
4819
|
+
timer = setTimeout(
|
|
4820
|
+
() => reject(
|
|
4821
|
+
new ArcaLockTimeoutError(
|
|
4822
|
+
"ARCA store lock stayed held; no work was attempted.",
|
|
4823
|
+
{ reason: "held" }
|
|
4824
|
+
)
|
|
4825
|
+
),
|
|
4826
|
+
MAX_WAIT_MS
|
|
4827
|
+
);
|
|
4828
|
+
timer.unref?.();
|
|
4829
|
+
onAbort = () => {
|
|
4830
|
+
try {
|
|
4831
|
+
throwIfAborted(signal);
|
|
4832
|
+
} catch (error) {
|
|
4833
|
+
reject(error);
|
|
4834
|
+
}
|
|
4835
|
+
};
|
|
4836
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4837
|
+
});
|
|
4838
|
+
return Promise.race([previous, stopped]).finally(() => {
|
|
4839
|
+
clearTimeout(timer);
|
|
4840
|
+
if (onAbort) {
|
|
4841
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4842
|
+
}
|
|
4843
|
+
});
|
|
4844
|
+
}
|
|
4845
|
+
function readExportRecord(json) {
|
|
4846
|
+
let record;
|
|
4847
|
+
try {
|
|
4848
|
+
record = JSON.parse(json);
|
|
4849
|
+
} catch (cause) {
|
|
4850
|
+
throw new ArcaConfigurationError(
|
|
4851
|
+
"Invalid ARCA reservation record; preserve it for reconciliation.",
|
|
4852
|
+
{ cause }
|
|
4853
|
+
);
|
|
4854
|
+
}
|
|
4855
|
+
if (record?.service !== "wsfex") {
|
|
4856
|
+
throw new ArcaInputError(
|
|
4857
|
+
"The idempotency key was already used with different input or operation.",
|
|
4858
|
+
{
|
|
4859
|
+
code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH",
|
|
4860
|
+
field: "options.idempotencyKey"
|
|
4861
|
+
}
|
|
4862
|
+
);
|
|
4863
|
+
}
|
|
4864
|
+
if (record.v !== 2 || !["issue", "creditNote", "debitNote"].includes(record.operation) || typeof record.inputHash !== "string" || !record.sent || !Number.isSafeInteger(record.number) || !Number.isSafeInteger(record.requestId) || record.sent.salesPoint !== record.salesPoint || record.sent.voucherType !== record.voucherType) {
|
|
4865
|
+
throw new ArcaConfigurationError(
|
|
4866
|
+
"Invalid ARCA reservation record; preserve it for reconciliation."
|
|
4867
|
+
);
|
|
4868
|
+
}
|
|
4869
|
+
return record;
|
|
4870
|
+
}
|
|
4871
|
+
function exportHeader(data) {
|
|
4872
|
+
const header = {
|
|
4873
|
+
exportType: data.exportType,
|
|
4874
|
+
destination: data.destination,
|
|
4875
|
+
receiverName: data.receiverName,
|
|
4876
|
+
receiverAddress: data.receiverAddress,
|
|
4877
|
+
currencyId: data.currencyId,
|
|
4878
|
+
language: data.language
|
|
4879
|
+
};
|
|
4880
|
+
assign(header, "receiverTaxId", data.receiverTaxId);
|
|
4881
|
+
assign(header, "receiverCountryTaxId", data.receiverCountryTaxId);
|
|
4882
|
+
assign(header, "exchangeRate", data.exchangeRate);
|
|
4883
|
+
assign(
|
|
4884
|
+
header,
|
|
4885
|
+
"paidInForeignCurrency",
|
|
4886
|
+
data.sameCurrencyForeignCancellation === void 0 ? void 0 : data.sameCurrencyForeignCancellation === "S"
|
|
4887
|
+
);
|
|
4888
|
+
assign(header, "incoterms", data.incoterms);
|
|
4889
|
+
assign(header, "incotermsDetail", data.incotermsDetail);
|
|
4890
|
+
assign(
|
|
4891
|
+
header,
|
|
4892
|
+
"permitExists",
|
|
4893
|
+
data.permitExists === void 0 ? void 0 : data.permitExists === "S"
|
|
4894
|
+
);
|
|
4895
|
+
assign(header, "paymentTerms", data.paymentTerms);
|
|
4896
|
+
assign(header, "paymentDate", isoDate(data.paymentDate));
|
|
4897
|
+
return header;
|
|
4898
|
+
}
|
|
4899
|
+
function exportSummary(voucher) {
|
|
4900
|
+
const summary = {
|
|
4901
|
+
voucherClass: "E",
|
|
4902
|
+
number: voucher.number
|
|
4903
|
+
};
|
|
4904
|
+
assign(summary, "salesPoint", voucher.salesPoint);
|
|
4905
|
+
assign(summary, "voucherType", voucher.voucherType);
|
|
4906
|
+
assign(summary, "requestId", voucher.id);
|
|
4907
|
+
assign(summary, "date", isoDate(voucher.voucherDate));
|
|
4908
|
+
assign(summary, "exportType", voucher.exportType);
|
|
4909
|
+
assign(summary, "destination", voucher.destination);
|
|
4910
|
+
assign(summary, "receiverName", voucher.receiverName);
|
|
4911
|
+
assign(summary, "receiverAddress", voucher.receiverAddress);
|
|
4912
|
+
assign(summary, "receiverTaxId", voucher.receiverTaxId);
|
|
4913
|
+
assign(summary, "receiverCountryTaxId", voucher.receiverCountryTaxId);
|
|
4914
|
+
assign(summary, "currencyId", voucher.currencyId);
|
|
4915
|
+
assign(summary, "exchangeRate", voucher.exchangeRate);
|
|
4916
|
+
assign(
|
|
4917
|
+
summary,
|
|
4918
|
+
"totalAmount",
|
|
4919
|
+
voucher.totalAmount === void 0 ? void 0 : Math.round(voucher.totalAmount * 100)
|
|
4920
|
+
);
|
|
4921
|
+
assign(summary, "result", voucher.result);
|
|
4922
|
+
assign(summary, "cae", voucher.cae);
|
|
4923
|
+
assign(summary, "caeExpiry", isoDate(voucher.caeExpiry));
|
|
4924
|
+
return summary;
|
|
4925
|
+
}
|
|
4926
|
+
function exportQr(taxId, attempted, data, date, cae) {
|
|
4927
|
+
try {
|
|
4928
|
+
const qrPayload = arcaQrPayload({
|
|
4929
|
+
taxId,
|
|
4930
|
+
...attempted,
|
|
4931
|
+
date,
|
|
4932
|
+
total: Math.round(data.totalAmount * 100),
|
|
4933
|
+
currency: data.currencyId,
|
|
4934
|
+
...data.exchangeRate === void 0 ? {} : { exchangeRate: data.exchangeRate },
|
|
4935
|
+
cae
|
|
4936
|
+
});
|
|
4937
|
+
return { qr: qrUrlForPayload(qrPayload), qrPayload };
|
|
4938
|
+
} catch {
|
|
4939
|
+
return {};
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
function originalTotal(original) {
|
|
4943
|
+
return Math.round(original.totalAmount * 100);
|
|
4944
|
+
}
|
|
4945
|
+
function deriveReceiver2(to) {
|
|
4946
|
+
assertIssueObject(to, "to");
|
|
4947
|
+
assertIssueKeys(
|
|
4948
|
+
to,
|
|
4949
|
+
["country", "name", "address", "taxId", "countryTaxId"],
|
|
4950
|
+
"to"
|
|
4951
|
+
);
|
|
4952
|
+
const receiver = to;
|
|
4953
|
+
if (!(Number.isSafeInteger(receiver.country) && receiver.country >= 1 && receiver.country <= 999)) {
|
|
4954
|
+
invalid3("to.country", "an ARCA destination country code");
|
|
4955
|
+
}
|
|
4956
|
+
if (receiver.taxId === void 0 && receiver.countryTaxId === void 0) {
|
|
4957
|
+
throw new ArcaInputError("to.taxId or to.countryTaxId is required.", {
|
|
4958
|
+
code: "ARCA_INPUT_MISSING_FIELD",
|
|
4959
|
+
field: "to.taxId",
|
|
4960
|
+
expected: "the receiver's tax ID abroad, ARCA's country CUIT, or both"
|
|
4961
|
+
});
|
|
4962
|
+
}
|
|
4963
|
+
if (receiver.countryTaxId !== void 0 && !(typeof receiver.countryTaxId === "string" && /^\d{11}$/.test(receiver.countryTaxId))) {
|
|
4964
|
+
invalid3("to.countryTaxId", "an 11-digit string");
|
|
4965
|
+
}
|
|
4966
|
+
return {
|
|
4967
|
+
destination: receiver.country,
|
|
4968
|
+
receiverName: text(receiver.name, "to.name", 200),
|
|
4969
|
+
...receiver.countryTaxId === void 0 ? {} : { receiverCountryTaxId: receiver.countryTaxId },
|
|
4970
|
+
receiverAddress: text(receiver.address, "to.address", 300),
|
|
4971
|
+
...optionalText(receiver.taxId, "receiverTaxId", 50, "to.taxId")
|
|
4972
|
+
};
|
|
4973
|
+
}
|
|
4974
|
+
function deriveExport(value) {
|
|
4975
|
+
assertIssueObject(value, "export");
|
|
4976
|
+
const exported = value;
|
|
4977
|
+
if (exported.kind === "goods") {
|
|
4978
|
+
assertIssueKeys(
|
|
4979
|
+
exported,
|
|
4980
|
+
["kind", "incoterms", "incotermsDetail", "permits"],
|
|
4981
|
+
"export"
|
|
4982
|
+
);
|
|
4983
|
+
if (typeof exported.incoterms !== "string" || !/^[A-Z]{3}$/.test(exported.incoterms)) {
|
|
4984
|
+
invalid3("export.incoterms", "a three-letter Incoterm such as FOB");
|
|
4985
|
+
}
|
|
4986
|
+
let permits;
|
|
4987
|
+
if (exported.permits !== void 0) {
|
|
4988
|
+
if (!Array.isArray(exported.permits) || exported.permits.length === 0) {
|
|
4989
|
+
invalid3("export.permits", "a non-empty list, or absent while pending");
|
|
4990
|
+
}
|
|
4991
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4992
|
+
permits = exported.permits.map((entry, index) => {
|
|
4993
|
+
const field = `export.permits[${index}]`;
|
|
4994
|
+
const permit = entry;
|
|
4995
|
+
assertIssueObject(permit, field);
|
|
4996
|
+
assertIssueKeys(permit, ["id", "destination"], field);
|
|
4997
|
+
if (typeof permit.id !== "string" || !/^\d{5}[A-Z0-9]{4}\d{6}[A-Z]$/.test(permit.id)) {
|
|
4998
|
+
invalid3(`${field}.id`, "a shipping permit such as 09052EC01006154G");
|
|
4999
|
+
}
|
|
5000
|
+
if (!(typeof permit.destination === "number" && Number.isSafeInteger(permit.destination) && permit.destination >= 1 && permit.destination <= 999)) {
|
|
5001
|
+
invalid3(`${field}.destination`, "an ARCA destination country code");
|
|
5002
|
+
}
|
|
5003
|
+
const pair = `${permit.id}:${permit.destination}`;
|
|
5004
|
+
if (seen.has(pair)) {
|
|
5005
|
+
invalid3(field, "a permit and destination listed once");
|
|
5006
|
+
}
|
|
5007
|
+
seen.add(pair);
|
|
5008
|
+
return {
|
|
5009
|
+
id: permit.id,
|
|
5010
|
+
destination: permit.destination
|
|
5011
|
+
};
|
|
5012
|
+
});
|
|
5013
|
+
}
|
|
5014
|
+
return {
|
|
5015
|
+
exportType: 1,
|
|
5016
|
+
permitExists: permits === void 0 ? "N" : "S",
|
|
5017
|
+
...permits === void 0 ? {} : { permits },
|
|
5018
|
+
incoterms: exported.incoterms,
|
|
5019
|
+
...optionalText(
|
|
5020
|
+
exported.incotermsDetail,
|
|
5021
|
+
"incotermsDetail",
|
|
5022
|
+
20,
|
|
5023
|
+
"export.incotermsDetail"
|
|
5024
|
+
)
|
|
5025
|
+
};
|
|
5026
|
+
}
|
|
5027
|
+
if (exported.kind === "services" || exported.kind === "other") {
|
|
5028
|
+
assertIssueKeys(exported, ["kind", "paymentDate"], "export");
|
|
5029
|
+
if (exported.paymentDate === void 0) {
|
|
5030
|
+
throw new ArcaInputError("export.paymentDate is required.", {
|
|
5031
|
+
code: "ARCA_INPUT_MISSING_FIELD",
|
|
5032
|
+
field: "export.paymentDate",
|
|
5033
|
+
expected: "the date the receiver pays"
|
|
5034
|
+
});
|
|
5035
|
+
}
|
|
5036
|
+
return {
|
|
5037
|
+
exportType: EXPORT_KINDS[exported.kind],
|
|
5038
|
+
paymentDate: normalizeWsfeDateInput(
|
|
5039
|
+
exported.paymentDate,
|
|
5040
|
+
"export.paymentDate"
|
|
5041
|
+
)
|
|
5042
|
+
};
|
|
5043
|
+
}
|
|
5044
|
+
return invalid3("export.kind", "goods, services or other");
|
|
5045
|
+
}
|
|
5046
|
+
function deriveCurrency2(currency, rate) {
|
|
5047
|
+
const value = currency ?? "ARS";
|
|
5048
|
+
let currencyId;
|
|
5049
|
+
if (typeof value === "object" && value !== null) {
|
|
5050
|
+
assertIssueKeys(value, ["id"], "currency");
|
|
5051
|
+
if (typeof value.id !== "string" || !/^[A-Z0-9]{3}$/.test(value.id)) {
|
|
5052
|
+
invalid3("currency.id", "a three-character ARCA currency code");
|
|
5053
|
+
}
|
|
5054
|
+
currencyId = value.id;
|
|
5055
|
+
} else if (value === "ARS" || value === "USD") {
|
|
5056
|
+
currencyId = ARCA_CURRENCY_IDS[value];
|
|
5057
|
+
} else {
|
|
5058
|
+
return invalid3("currency", "ARS, USD or { id }");
|
|
5059
|
+
}
|
|
5060
|
+
if (rate !== void 0 && typeof rate !== "string") {
|
|
5061
|
+
invalid3("exchangeRate", "a decimal string");
|
|
5062
|
+
}
|
|
5063
|
+
if (currencyId === ARCA_CURRENCY_IDS.ARS) {
|
|
5064
|
+
const exchangeRate2 = serializeArcaExchangeRate(rate ?? "1", "exchangeRate");
|
|
5065
|
+
if (Number(exchangeRate2) !== 1) {
|
|
5066
|
+
invalid3("exchangeRate", "1 for pesos");
|
|
5067
|
+
}
|
|
5068
|
+
return { currencyId, exchangeRate: exchangeRate2 };
|
|
5069
|
+
}
|
|
5070
|
+
if (rate === void 0) {
|
|
5071
|
+
throw new ArcaInputError("exchangeRate is required for this currency.", {
|
|
5072
|
+
code: "ARCA_INPUT_MISSING_FIELD",
|
|
5073
|
+
field: "exchangeRate",
|
|
5074
|
+
expected: "ARCA's rate, from wsfex.getExchangeRate()"
|
|
5075
|
+
});
|
|
5076
|
+
}
|
|
5077
|
+
return {
|
|
5078
|
+
currencyId,
|
|
5079
|
+
exchangeRate: serializeArcaExchangeRate(rate, "exchangeRate")
|
|
5080
|
+
};
|
|
5081
|
+
}
|
|
5082
|
+
function deriveLanguage(language) {
|
|
5083
|
+
if (language === void 0) {
|
|
5084
|
+
return 1;
|
|
5085
|
+
}
|
|
5086
|
+
if (typeof language !== "string" || !Object.hasOwn(LANGUAGES, language)) {
|
|
5087
|
+
return invalid3("language", "es, en or pt");
|
|
5088
|
+
}
|
|
5089
|
+
return LANGUAGES[language];
|
|
5090
|
+
}
|
|
5091
|
+
var SCALE_6 = 1000000n;
|
|
5092
|
+
function deriveItems(value, path) {
|
|
5093
|
+
if (!Array.isArray(value) || value.length < 1 || value.length > 9999) {
|
|
5094
|
+
invalid3(path, "a list of 1 to 9999 items");
|
|
5095
|
+
}
|
|
5096
|
+
let total = 0n;
|
|
5097
|
+
const items = value.map((item, index) => {
|
|
5098
|
+
const line = deriveItem(item, `${path}[${index}]`);
|
|
5099
|
+
total += BigInt(item.amount);
|
|
5100
|
+
return line;
|
|
5101
|
+
});
|
|
5102
|
+
if (total < 0n) {
|
|
5103
|
+
invalid3(path, "lines whose total is zero or positive");
|
|
5104
|
+
}
|
|
5105
|
+
return { items, total: Number(total) };
|
|
5106
|
+
}
|
|
5107
|
+
function deriveItem(item, field) {
|
|
5108
|
+
assertIssueObject(item, field);
|
|
5109
|
+
assertIssueKeys(
|
|
5110
|
+
item,
|
|
5111
|
+
[
|
|
5112
|
+
"description",
|
|
5113
|
+
"unit",
|
|
5114
|
+
"quantity",
|
|
5115
|
+
"unitPrice",
|
|
5116
|
+
"discount",
|
|
5117
|
+
"code",
|
|
5118
|
+
"amount"
|
|
5119
|
+
],
|
|
5120
|
+
field
|
|
5121
|
+
);
|
|
5122
|
+
if (!(Number.isSafeInteger(item.unit) && item.unit >= 0 && item.unit <= 99)) {
|
|
5123
|
+
invalid3(`${field}.unit`, "an ARCA unit of measure");
|
|
5124
|
+
}
|
|
5125
|
+
assertItemAmount(item, field);
|
|
5126
|
+
const line = {
|
|
5127
|
+
...optionalText(item.code, "code", 50, `${field}.code`),
|
|
5128
|
+
description: text(item.description, `${field}.description`, 4e3),
|
|
5129
|
+
unit: item.unit,
|
|
5130
|
+
amount: item.amount / 100
|
|
5131
|
+
};
|
|
5132
|
+
if (!UNPRICED_UNITS.includes(item.unit)) {
|
|
5133
|
+
return { ...line, ...pricedLine(item, field) };
|
|
5134
|
+
}
|
|
5135
|
+
for (const key of ["quantity", "unitPrice", "discount"]) {
|
|
5136
|
+
if (item[key] !== void 0 && Number(item[key]) !== 0) {
|
|
5137
|
+
invalid3(`${field}.${key}`, `absent for unit ${item.unit}`);
|
|
5138
|
+
}
|
|
5139
|
+
}
|
|
5140
|
+
return line;
|
|
5141
|
+
}
|
|
5142
|
+
function assertItemAmount(item, field) {
|
|
5143
|
+
if (!Number.isSafeInteger(item.amount)) {
|
|
5144
|
+
invalid3(`${field}.amount`, "an integer amount in minor units");
|
|
5145
|
+
}
|
|
5146
|
+
if (item.unit === 99 && item.amount >= 0) {
|
|
5147
|
+
invalid3(`${field}.amount`, "negative for a discount line");
|
|
5148
|
+
}
|
|
5149
|
+
if (item.unit !== 97 && item.unit !== 99 && item.amount < 0) {
|
|
5150
|
+
invalid3(`${field}.amount`, "zero or positive for this unit");
|
|
5151
|
+
}
|
|
5152
|
+
if (Math.abs(item.amount) > 999999999999999) {
|
|
5153
|
+
invalid3(`${field}.amount`, "at most 13 integer digits");
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
function pricedLine(item, field) {
|
|
5157
|
+
const quantity = scaledQuantity(item.quantity, `${field}.quantity`);
|
|
5158
|
+
const price = scaledPrice(item.unitPrice, `${field}.unitPrice`);
|
|
5159
|
+
const discount = item.discount ?? 0;
|
|
5160
|
+
if (!(Number.isSafeInteger(discount) && discount >= 0)) {
|
|
5161
|
+
invalid3(`${field}.discount`, "a non-negative amount in minor units");
|
|
5162
|
+
}
|
|
5163
|
+
const exact = quantity * price - BigInt(discount) * 10000000000n;
|
|
5164
|
+
if (exact < 0n) {
|
|
5165
|
+
invalid3(`${field}.discount`, "at most the line's quantity \xD7 unitPrice");
|
|
5166
|
+
}
|
|
5167
|
+
if (!withinLineTolerance(exact, BigInt(item.amount))) {
|
|
5168
|
+
throw new ArcaInputError(
|
|
5169
|
+
`${field}.amount must equal quantity \xD7 unitPrice less discount.`,
|
|
5170
|
+
{
|
|
5171
|
+
code: "ARCA_INPUT_AMOUNT_MISMATCH",
|
|
5172
|
+
field: `${field}.amount`,
|
|
5173
|
+
expected: String(roundHalfEvenRatio(exact, 10000000000n))
|
|
5174
|
+
}
|
|
5175
|
+
);
|
|
5176
|
+
}
|
|
5177
|
+
return {
|
|
5178
|
+
quantity: item.quantity,
|
|
5179
|
+
unitPrice: item.unitPrice,
|
|
5180
|
+
...discount > 0 ? { discount: discount / 100 } : {}
|
|
5181
|
+
};
|
|
5182
|
+
}
|
|
5183
|
+
function withinLineTolerance(exact, amountMinorUnits) {
|
|
5184
|
+
const computed = roundHalfEvenRatio(exact, 10000000n);
|
|
5185
|
+
const amount = amountMinorUnits * 1000n;
|
|
5186
|
+
const error = computed > amount ? computed - amount : amount - computed;
|
|
5187
|
+
if (error <= 1000n) {
|
|
5188
|
+
return true;
|
|
2811
5189
|
}
|
|
2812
|
-
const
|
|
2813
|
-
|
|
2814
|
-
|
|
5190
|
+
const base = computed < amount ? computed : amount;
|
|
5191
|
+
return base > 0n && error * 10000n <= base;
|
|
5192
|
+
}
|
|
5193
|
+
function scaledQuantity(value, field) {
|
|
5194
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
5195
|
+
return invalid3(field, "a positive number");
|
|
2815
5196
|
}
|
|
2816
|
-
|
|
5197
|
+
const fixed = value.toFixed(6);
|
|
5198
|
+
if (Number(fixed) !== value || fixed.split(".")[0].length > 12) {
|
|
5199
|
+
invalid3(field, "at most 12 integer digits and 6 decimals");
|
|
5200
|
+
}
|
|
5201
|
+
const [whole, fraction = ""] = fixed.split(".");
|
|
5202
|
+
return BigInt(whole) * SCALE_6 + BigInt(fraction.padEnd(6, "0"));
|
|
2817
5203
|
}
|
|
2818
|
-
function
|
|
2819
|
-
|
|
5204
|
+
function scaledPrice(value, field) {
|
|
5205
|
+
if (typeof value !== "string" || !/^\d{1,12}(\.\d{1,6})?$/.test(value)) {
|
|
5206
|
+
return invalid3(field, "a decimal string with at most 6 decimals");
|
|
5207
|
+
}
|
|
5208
|
+
const [whole, fraction = ""] = value.split(".");
|
|
5209
|
+
return BigInt(whole) * SCALE_6 + BigInt(fraction.padEnd(6, "0"));
|
|
2820
5210
|
}
|
|
2821
|
-
function
|
|
2822
|
-
|
|
5211
|
+
function reviewedTotal(total, computed) {
|
|
5212
|
+
if (total === void 0) {
|
|
5213
|
+
return computed;
|
|
5214
|
+
}
|
|
5215
|
+
if (!Number.isSafeInteger(total) || total < 0) {
|
|
5216
|
+
invalid3("total", "a non-negative integer amount in minor units");
|
|
5217
|
+
}
|
|
5218
|
+
if (total !== computed) {
|
|
5219
|
+
throw new ArcaInputError("total must equal the sum of items.", {
|
|
5220
|
+
code: "ARCA_INPUT_AMOUNT_MISMATCH",
|
|
5221
|
+
field: "total",
|
|
5222
|
+
expected: String(computed)
|
|
5223
|
+
});
|
|
5224
|
+
}
|
|
5225
|
+
return computed;
|
|
2823
5226
|
}
|
|
2824
|
-
function
|
|
2825
|
-
|
|
2826
|
-
|
|
5227
|
+
function assertSalesPoint2(value, field) {
|
|
5228
|
+
if (!(typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 99998)) {
|
|
5229
|
+
invalid3(field, "an integer from 1 through 99998");
|
|
5230
|
+
}
|
|
5231
|
+
}
|
|
5232
|
+
function text(value, field, max) {
|
|
5233
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
5234
|
+
throw new ArcaInputError(`${field} is required.`, {
|
|
5235
|
+
code: "ARCA_INPUT_MISSING_FIELD",
|
|
5236
|
+
field,
|
|
5237
|
+
expected: `a non-empty string of at most ${max} characters`
|
|
5238
|
+
});
|
|
5239
|
+
}
|
|
5240
|
+
if (value.length > max) {
|
|
5241
|
+
invalid3(field, `at most ${max} characters`);
|
|
5242
|
+
}
|
|
5243
|
+
return value;
|
|
5244
|
+
}
|
|
5245
|
+
function optionalText(value, key, max, field = key) {
|
|
5246
|
+
if (value === void 0) {
|
|
5247
|
+
return {};
|
|
5248
|
+
}
|
|
5249
|
+
return { [key]: text(value, field, max) };
|
|
5250
|
+
}
|
|
5251
|
+
function isoDate(value) {
|
|
5252
|
+
return value === void 0 ? void 0 : toIsoDate(value) ?? value;
|
|
5253
|
+
}
|
|
5254
|
+
function assign(target, key, value) {
|
|
5255
|
+
if (value !== void 0) {
|
|
5256
|
+
target[key] = value;
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
function invalid3(field, expected) {
|
|
5260
|
+
throw new ArcaInputError(`${field} must be ${expected}.`, {
|
|
5261
|
+
code: "ARCA_INPUT_INVALID_VALUE",
|
|
5262
|
+
field,
|
|
5263
|
+
expected
|
|
5264
|
+
});
|
|
2827
5265
|
}
|
|
2828
5266
|
|
|
2829
5267
|
// src/services/issuance-wsmtxca.ts
|
|
@@ -2839,7 +5277,7 @@ var iso = (value) => {
|
|
|
2839
5277
|
};
|
|
2840
5278
|
function assertWsmtxcaVoucherType(voucherType) {
|
|
2841
5279
|
if (voucherFamily(voucherType).voucherClass === "C") {
|
|
2842
|
-
|
|
5280
|
+
invalid4(
|
|
2843
5281
|
"voucherType",
|
|
2844
5282
|
"WSMTXCA authorizes class A and B vouchers only; issue class C through WSFE"
|
|
2845
5283
|
);
|
|
@@ -2860,7 +5298,7 @@ function wsmtxcaRequest(data, number) {
|
|
|
2860
5298
|
(source) => source !== void 0
|
|
2861
5299
|
).length;
|
|
2862
5300
|
if (!lines?.length || sources !== 1) {
|
|
2863
|
-
|
|
5301
|
+
invalid4("items", "WSMTXCA requires items with line detail");
|
|
2864
5302
|
}
|
|
2865
5303
|
const items = lines.map((line) => ({
|
|
2866
5304
|
unidadesMtx: line.matrixUnits,
|
|
@@ -2887,7 +5325,7 @@ function wsmtxcaRequest(data, number) {
|
|
|
2887
5325
|
}
|
|
2888
5326
|
const tributes = data.taxes?.length ? data.taxes : void 0;
|
|
2889
5327
|
if (tributes === void 0 && normalizeArcaAmountToMinorUnits(data.taxAmount, "taxes") !== 0n) {
|
|
2890
|
-
|
|
5328
|
+
invalid4("taxes", "Tribute amount requires tribute details");
|
|
2891
5329
|
}
|
|
2892
5330
|
return {
|
|
2893
5331
|
comprobanteCAERequest: {
|
|
@@ -2956,7 +5394,7 @@ function wsmtxcaRequest(data, number) {
|
|
|
2956
5394
|
}
|
|
2957
5395
|
};
|
|
2958
5396
|
}
|
|
2959
|
-
function
|
|
5397
|
+
function invalid4(field, message) {
|
|
2960
5398
|
throw new ArcaInputError(message, {
|
|
2961
5399
|
code: "ARCA_INPUT_INVALID_VALUE",
|
|
2962
5400
|
field
|
|
@@ -3187,7 +5625,7 @@ function wsmtxcaAdditionalData(fields) {
|
|
|
3187
5625
|
}
|
|
3188
5626
|
|
|
3189
5627
|
// src/services/wsfe-credit-note.ts
|
|
3190
|
-
function
|
|
5628
|
+
function invalid5(reason) {
|
|
3191
5629
|
throw new ArcaInputError(`issueCreditNote cannot proceed: ${reason}.`, {
|
|
3192
5630
|
code: "ARCA_INPUT_INVALID_VALUE"
|
|
3193
5631
|
});
|
|
@@ -3207,7 +5645,7 @@ function originalReceiverDocument(original) {
|
|
|
3207
5645
|
}
|
|
3208
5646
|
function required(value, field) {
|
|
3209
5647
|
if (value === void 0 || value === null) {
|
|
3210
|
-
|
|
5648
|
+
invalid5(`original is missing ${field}`);
|
|
3211
5649
|
}
|
|
3212
5650
|
return value;
|
|
3213
5651
|
}
|
|
@@ -3236,7 +5674,7 @@ function deriveWsfeFullCreditNote(original, input, now = /* @__PURE__ */ new Dat
|
|
|
3236
5674
|
}
|
|
3237
5675
|
function deriveWsfePartialCreditNote(originals, input, now = /* @__PURE__ */ new Date(), kind = "creditNote") {
|
|
3238
5676
|
if (input.items === void 0 && input.amounts === void 0) {
|
|
3239
|
-
|
|
5677
|
+
invalid5("items is required to credit chosen lines");
|
|
3240
5678
|
}
|
|
3241
5679
|
const requestedTotal = input.total === void 0 ? void 0 : assertArcaMinorUnits(input.total, "total");
|
|
3242
5680
|
const { note, header } = prepareCreditNote(originals, input, now, kind);
|
|
@@ -3249,7 +5687,7 @@ function deriveWsfePartialCreditNote(originals, input, now = /* @__PURE__ */ new
|
|
|
3249
5687
|
input.amounts,
|
|
3250
5688
|
input.taxes
|
|
3251
5689
|
) : calculateWsfeAmounts(lineSource);
|
|
3252
|
-
const
|
|
5690
|
+
const originalTotal2 = originals.reduce(
|
|
3253
5691
|
(sum, original) => sum + normalizeArcaAmountToMinorUnits(
|
|
3254
5692
|
required(original.totalAmount, "totalAmount"),
|
|
3255
5693
|
"totalAmount"
|
|
@@ -3257,8 +5695,8 @@ function deriveWsfePartialCreditNote(originals, input, now = /* @__PURE__ */ new
|
|
|
3257
5695
|
0n
|
|
3258
5696
|
);
|
|
3259
5697
|
const ceiling = originals.length === 1 ? "the original" : "the sum of the originals";
|
|
3260
|
-
if (kind === "creditNote" && (requestedTotal ?? BigInt(amounts.sentTotal)) >
|
|
3261
|
-
|
|
5698
|
+
if (kind === "creditNote" && (requestedTotal ?? BigInt(amounts.sentTotal)) > originalTotal2) {
|
|
5699
|
+
invalid5(
|
|
3262
5700
|
`the note total is greater than ${ceiling}; the SDK does not track earlier notes against an original`
|
|
3263
5701
|
);
|
|
3264
5702
|
}
|
|
@@ -3281,8 +5719,8 @@ function deriveWsfePartialCreditNote(originals, input, now = /* @__PURE__ */ new
|
|
|
3281
5719
|
)
|
|
3282
5720
|
);
|
|
3283
5721
|
const sentTotal = input.amounts && requestedTotal !== void 0 ? Number(requestedTotal) : total;
|
|
3284
|
-
if (kind === "creditNote" && BigInt(sentTotal) >
|
|
3285
|
-
|
|
5722
|
+
if (kind === "creditNote" && BigInt(sentTotal) > originalTotal2) {
|
|
5723
|
+
invalid5(`the note total is greater than ${ceiling}`);
|
|
3286
5724
|
}
|
|
3287
5725
|
data.totalAmount = minor(sentTotal, "total");
|
|
3288
5726
|
amounts.computedTotal += total - amounts.sentTotal;
|
|
@@ -3301,7 +5739,7 @@ function prepareCreditNote(originals, input, now, kind) {
|
|
|
3301
5739
|
);
|
|
3302
5740
|
const [first, ...rest] = derived;
|
|
3303
5741
|
if (first === void 0) {
|
|
3304
|
-
|
|
5742
|
+
invalid5("for must name at least one original");
|
|
3305
5743
|
}
|
|
3306
5744
|
for (const other of rest) {
|
|
3307
5745
|
assertInheritedHeader(first.header, other.header);
|
|
@@ -3324,7 +5762,7 @@ function assertInheritedHeader(first, other) {
|
|
|
3324
5762
|
const left = first[field];
|
|
3325
5763
|
const right = other[field];
|
|
3326
5764
|
if (JSON.stringify(left) !== JSON.stringify(right)) {
|
|
3327
|
-
|
|
5765
|
+
invalid5(
|
|
3328
5766
|
`the originals disagree on ${field}, which the note inherits from them`
|
|
3329
5767
|
);
|
|
3330
5768
|
}
|
|
@@ -3335,14 +5773,14 @@ function resolveReceiverCondition(original, input) {
|
|
|
3335
5773
|
const reported = original.receiverVatConditionId;
|
|
3336
5774
|
if (reported === void 0) {
|
|
3337
5775
|
if (supplied === void 0) {
|
|
3338
|
-
|
|
5776
|
+
invalid5(
|
|
3339
5777
|
"original is missing receiverVatConditionId; pass to.condition with the receiver's condition"
|
|
3340
5778
|
);
|
|
3341
5779
|
}
|
|
3342
5780
|
return supplied;
|
|
3343
5781
|
}
|
|
3344
5782
|
if (supplied !== void 0 && supplied !== reported) {
|
|
3345
|
-
|
|
5783
|
+
invalid5(
|
|
3346
5784
|
`to.condition (${supplied}) does not match the original's receiver condition (${reported})`
|
|
3347
5785
|
);
|
|
3348
5786
|
}
|
|
@@ -3352,14 +5790,14 @@ function prepareOneCreditNote(original, input, now, kind) {
|
|
|
3352
5790
|
assertOriginalExtensions(original);
|
|
3353
5791
|
const family = voucherFamily(original.voucherType ?? 0);
|
|
3354
5792
|
if (family.types[2] === original.voucherType) {
|
|
3355
|
-
|
|
5793
|
+
invalid5("original must be an invoice or debit note");
|
|
3356
5794
|
}
|
|
3357
5795
|
const note = {
|
|
3358
5796
|
voucherClass: family.voucherClass,
|
|
3359
5797
|
voucherType: family.types[kind === "creditNote" ? 2 : 1]
|
|
3360
5798
|
};
|
|
3361
5799
|
if (!(["A", "O"].includes(original.result ?? "") && original.cae?.trim() && original.caeExpiry?.trim())) {
|
|
3362
|
-
|
|
5800
|
+
invalid5("original is not authorized");
|
|
3363
5801
|
}
|
|
3364
5802
|
const voucherDate = normalizeWsfeDateInput(
|
|
3365
5803
|
input.date ?? buenosAiresDate(now),
|
|
@@ -3370,7 +5808,7 @@ function prepareOneCreditNote(original, input, now, kind) {
|
|
|
3370
5808
|
"original.voucherDate"
|
|
3371
5809
|
);
|
|
3372
5810
|
if (originalDate > voucherDate && originalDate.slice(0, 6) !== voucherDate.slice(0, 6)) {
|
|
3373
|
-
|
|
5811
|
+
invalid5(
|
|
3374
5812
|
"original date is later than the note and outside its month (10210)"
|
|
3375
5813
|
);
|
|
3376
5814
|
}
|
|
@@ -3447,7 +5885,7 @@ function assertNoteReceiver(to) {
|
|
|
3447
5885
|
try {
|
|
3448
5886
|
receiverConditionId(to.condition, "to.condition");
|
|
3449
5887
|
} catch {
|
|
3450
|
-
|
|
5888
|
+
invalid5("to.condition must be a supported receiver condition");
|
|
3451
5889
|
}
|
|
3452
5890
|
}
|
|
3453
5891
|
function assertCreditNoteInput(input) {
|
|
@@ -3480,7 +5918,7 @@ function assertCreditNoteInput(input) {
|
|
|
3480
5918
|
...input.to === void 0 ? {} : { to: { condition: input.to.condition } }
|
|
3481
5919
|
};
|
|
3482
5920
|
if (input.items !== void 0 && input.amounts !== void 0) {
|
|
3483
|
-
|
|
5921
|
+
invalid5("use items or amounts, never both");
|
|
3484
5922
|
}
|
|
3485
5923
|
if ((input.items === void 0 && input.amounts === void 0) === (input.all === void 0)) {
|
|
3486
5924
|
throw new ArcaInputError(
|
|
@@ -3617,7 +6055,7 @@ function assertOriginalExtensions(original) {
|
|
|
3617
6055
|
["associatedPeriod", "PeriodoAsoc"]
|
|
3618
6056
|
]) {
|
|
3619
6057
|
if (original.raw[rawField] && original[field] === void 0) {
|
|
3620
|
-
|
|
6058
|
+
invalid5(`original ${field} could not be decoded`);
|
|
3621
6059
|
}
|
|
3622
6060
|
}
|
|
3623
6061
|
try {
|
|
@@ -3648,7 +6086,7 @@ function assertOriginalExtensions(original) {
|
|
|
3648
6086
|
);
|
|
3649
6087
|
}
|
|
3650
6088
|
} catch {
|
|
3651
|
-
|
|
6089
|
+
invalid5("original extension fields are incomplete or malformed");
|
|
3652
6090
|
}
|
|
3653
6091
|
}
|
|
3654
6092
|
function assertFullMode(input) {
|
|
@@ -3685,7 +6123,7 @@ function assertFullMode(input) {
|
|
|
3685
6123
|
}
|
|
3686
6124
|
|
|
3687
6125
|
// src/services/vouchers.ts
|
|
3688
|
-
function createVouchersService(wsfe, context, wsmtxca) {
|
|
6126
|
+
function createVouchersService(wsfe, context, wsmtxca, wsfex) {
|
|
3689
6127
|
const select = (options = {}) => {
|
|
3690
6128
|
validateOptions(options);
|
|
3691
6129
|
if (options.service !== "wsmtxca") {
|
|
@@ -3696,63 +6134,192 @@ function createVouchersService(wsfe, context, wsmtxca) {
|
|
|
3696
6134
|
}
|
|
3697
6135
|
return createWsmtxcaIssuanceService(wsmtxca);
|
|
3698
6136
|
};
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
)
|
|
3708
|
-
|
|
6137
|
+
const exportIssuance = wsfex && context ? createExportIssuance(wsfex, context) : void 0;
|
|
6138
|
+
const exporting = () => {
|
|
6139
|
+
if (!exportIssuance) {
|
|
6140
|
+
throw new ArcaConfigurationError("WSFEX service is not configured");
|
|
6141
|
+
}
|
|
6142
|
+
return exportIssuance;
|
|
6143
|
+
};
|
|
6144
|
+
const exportOptions = (options) => {
|
|
6145
|
+
const cloned = cloneOptions(options ?? {});
|
|
6146
|
+
validateOptions(cloned);
|
|
6147
|
+
validateKeyStore(cloned, context);
|
|
6148
|
+
return cloned;
|
|
6149
|
+
};
|
|
6150
|
+
async function prepareExportNote(input, options, kind) {
|
|
6151
|
+
assertIssueObject(input, "input");
|
|
6152
|
+
assertIssueObject(input.for, "for");
|
|
6153
|
+
assertIssueKeys(input.for, ["salesPoint", "voucherType", "number"], "for");
|
|
6154
|
+
for (const [field, max] of [
|
|
6155
|
+
["salesPoint", 99998],
|
|
6156
|
+
["number", 99999999]
|
|
6157
|
+
]) {
|
|
6158
|
+
const value = input.for[field];
|
|
6159
|
+
if (!(Number.isSafeInteger(value) && value >= 1 && value <= max)) {
|
|
6160
|
+
throw new ArcaInputError(
|
|
6161
|
+
`for.${field} must be an integer from 1 through ${max}.`,
|
|
6162
|
+
{
|
|
6163
|
+
code: "ARCA_INPUT_INVALID_VALUE",
|
|
6164
|
+
field: `for.${field}`
|
|
6165
|
+
}
|
|
6166
|
+
);
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
const original = await exporting().original(input.for, options);
|
|
6170
|
+
if (original === null) {
|
|
6171
|
+
throw new ArcaInputError("ARCA has no voucher at for.", {
|
|
6172
|
+
code: "ARCA_INPUT_INVALID_VALUE",
|
|
6173
|
+
field: "for",
|
|
6174
|
+
expected: "an authorized export voucher"
|
|
6175
|
+
});
|
|
6176
|
+
}
|
|
6177
|
+
return { original, prepared: deriveExportNote(original, input, kind) };
|
|
6178
|
+
}
|
|
6179
|
+
async function issueExportNote(input, inputOptions, kind) {
|
|
6180
|
+
const options = exportOptions(inputOptions);
|
|
6181
|
+
const snapshot = structuredClone(input);
|
|
6182
|
+
return await exporting().issue(
|
|
6183
|
+
async () => (await prepareExportNote(snapshot, options, kind)).prepared,
|
|
6184
|
+
kind,
|
|
6185
|
+
snapshot,
|
|
6186
|
+
options
|
|
6187
|
+
);
|
|
6188
|
+
}
|
|
6189
|
+
async function previewExportNote(input, inputOptions, kind) {
|
|
6190
|
+
const options = cloneOptions(inputOptions ?? {});
|
|
6191
|
+
assertIssueObject(options, "options");
|
|
6192
|
+
assertIssueKeys(
|
|
6193
|
+
options,
|
|
6194
|
+
["representedTaxId", "forceRefresh", "abortSignal"],
|
|
6195
|
+
"options",
|
|
6196
|
+
kind === "creditNote" ? "previewCreditNote()" : "previewDebitNote()"
|
|
6197
|
+
);
|
|
6198
|
+
validateOptions(options);
|
|
6199
|
+
const { original, prepared } = await prepareExportNote(
|
|
6200
|
+
structuredClone(input),
|
|
6201
|
+
options,
|
|
6202
|
+
kind
|
|
6203
|
+
);
|
|
6204
|
+
assertExportWindow(prepared.data);
|
|
6205
|
+
return exportPreview(prepared, [exportSummary(original)]);
|
|
6206
|
+
}
|
|
6207
|
+
async function readReservation(key, inputOptions) {
|
|
6208
|
+
const store = context?.store;
|
|
6209
|
+
if (exportIssuance === void 0 || store === void 0 || !context) {
|
|
6210
|
+
return;
|
|
6211
|
+
}
|
|
6212
|
+
const options = cloneOptions(inputOptions ?? {});
|
|
6213
|
+
assertIssueObject(options, "options");
|
|
6214
|
+
assertIssueKeys(
|
|
6215
|
+
options,
|
|
6216
|
+
["representedTaxId", "forceRefresh", "include", "abortSignal"],
|
|
6217
|
+
"options"
|
|
6218
|
+
);
|
|
6219
|
+
validateOptions(options);
|
|
6220
|
+
validateKeyStore({ ...options, idempotencyKey: key }, context);
|
|
6221
|
+
const json = await storeCall(
|
|
6222
|
+
() => store.get(attemptKey(context.environment, context.taxId, key))
|
|
6223
|
+
);
|
|
6224
|
+
return { json, options };
|
|
6225
|
+
}
|
|
6226
|
+
const service = {
|
|
6227
|
+
lookup: (async (voucher, options) => {
|
|
6228
|
+
if (!isExportVoucherType(voucher?.voucherType)) {
|
|
6229
|
+
return await lookupVoucher(select(options), voucher, options ?? {});
|
|
6230
|
+
}
|
|
6231
|
+
const checked = cloneOptions(options ?? {});
|
|
6232
|
+
assertLookup(voucher, checked);
|
|
6233
|
+
return await exporting().lookup(voucher, checked);
|
|
6234
|
+
}),
|
|
6235
|
+
lastAuthorized: (async (sequence, options) => {
|
|
6236
|
+
if (!isExportVoucherType(sequence?.voucherType)) {
|
|
6237
|
+
return await lastAuthorizedNumber(
|
|
6238
|
+
select(options),
|
|
6239
|
+
sequence,
|
|
6240
|
+
options ?? {}
|
|
6241
|
+
);
|
|
6242
|
+
}
|
|
6243
|
+
const checked = cloneOptions(options ?? {});
|
|
6244
|
+
assertSequence(sequence, checked);
|
|
6245
|
+
return await exporting().lastAuthorized(sequence, checked);
|
|
6246
|
+
}),
|
|
6247
|
+
recover: (async (key, options) => {
|
|
6248
|
+
const read = await readReservation(key, options);
|
|
6249
|
+
if (typeof read?.json === "string" && isExportRecord(read.json)) {
|
|
6250
|
+
return await exporting().recover(key, read.json, read.options);
|
|
6251
|
+
}
|
|
6252
|
+
return await recoverOperation(
|
|
6253
|
+
select,
|
|
6254
|
+
key,
|
|
6255
|
+
options === void 0 ? {} : options,
|
|
6256
|
+
context,
|
|
6257
|
+
read?.json
|
|
6258
|
+
);
|
|
6259
|
+
}),
|
|
6260
|
+
issueDebitNote: (async (input, options) => isExportNoteInput(input) ? issueExportNote(input, options, "debitNote") : issueCreditNote(
|
|
3709
6261
|
select(options),
|
|
3710
6262
|
input,
|
|
3711
6263
|
options ?? {},
|
|
3712
6264
|
context,
|
|
3713
6265
|
"debitNote",
|
|
3714
6266
|
select
|
|
3715
|
-
),
|
|
3716
|
-
previewCreditNote: async (input, options) => previewNote(
|
|
6267
|
+
)),
|
|
6268
|
+
previewCreditNote: (async (input, options) => isExportNoteInput(input) ? previewExportNote(input, options, "creditNote") : previewNote(
|
|
3717
6269
|
select(options),
|
|
3718
6270
|
input,
|
|
3719
6271
|
options ?? {},
|
|
3720
6272
|
context,
|
|
3721
6273
|
"creditNote"
|
|
3722
|
-
),
|
|
3723
|
-
previewDebitNote: async (input, options) => previewNote(
|
|
6274
|
+
)),
|
|
6275
|
+
previewDebitNote: (async (input, options) => isExportNoteInput(input) ? previewExportNote(input, options, "debitNote") : previewNote(
|
|
3724
6276
|
select(options),
|
|
3725
6277
|
input,
|
|
3726
6278
|
options ?? {},
|
|
3727
6279
|
context,
|
|
3728
6280
|
"debitNote"
|
|
3729
|
-
),
|
|
3730
|
-
issueCreditNote: async (input, options) =>
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
6281
|
+
)),
|
|
6282
|
+
issueCreditNote: (async (input, options) => isExportNoteInput(input) ? issueExportNote(input, options, "creditNote") : issueCreditNote(
|
|
6283
|
+
select(options),
|
|
6284
|
+
input,
|
|
6285
|
+
options === void 0 ? {} : options,
|
|
6286
|
+
context,
|
|
6287
|
+
"creditNote",
|
|
6288
|
+
select
|
|
6289
|
+
)),
|
|
6290
|
+
issue: (async (input, options) => {
|
|
6291
|
+
if (isExportInput(input)) {
|
|
6292
|
+
const checked = exportOptions(options);
|
|
6293
|
+
const snapshot = structuredClone(input);
|
|
6294
|
+
return await exporting().issue(
|
|
6295
|
+
() => deriveExportInvoice(snapshot),
|
|
6296
|
+
"issue",
|
|
6297
|
+
snapshot,
|
|
6298
|
+
checked
|
|
6299
|
+
);
|
|
6300
|
+
}
|
|
6301
|
+
return await issueInvoice(
|
|
3743
6302
|
select(options),
|
|
3744
6303
|
input,
|
|
3745
6304
|
options === void 0 ? {} : options,
|
|
3746
6305
|
context,
|
|
3747
6306
|
select
|
|
3748
6307
|
);
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
6308
|
+
}),
|
|
6309
|
+
preview: ((input, options) => {
|
|
6310
|
+
if (isExportInput(input)) {
|
|
6311
|
+
const checked = options ?? {};
|
|
6312
|
+
assertIssueObject(checked, "options");
|
|
6313
|
+
assertIssueKeys(checked, ["representedTaxId"], "options");
|
|
6314
|
+
validateOptions(checked);
|
|
6315
|
+
const prepared = deriveExportInvoice(input);
|
|
6316
|
+
assertExportWindow(prepared.data);
|
|
6317
|
+
return exportPreview(prepared);
|
|
6318
|
+
}
|
|
6319
|
+
return previewInvoice(input, options === void 0 ? {} : options);
|
|
6320
|
+
})
|
|
3755
6321
|
};
|
|
6322
|
+
return service;
|
|
3756
6323
|
}
|
|
3757
6324
|
function previewInvoice(input, options) {
|
|
3758
6325
|
assertIssueObject(options, "options");
|
|
@@ -3831,15 +6398,14 @@ async function issueInvoice(wsfe, input, inputOptions, context, select) {
|
|
|
3831
6398
|
const options = cloneOptions(inputOptions);
|
|
3832
6399
|
validateOptions(options);
|
|
3833
6400
|
validateKeyStore(options, context);
|
|
3834
|
-
const
|
|
6401
|
+
const snapshot = structuredClone(input);
|
|
3835
6402
|
return await runOperation(
|
|
3836
6403
|
wsfe,
|
|
3837
6404
|
"issue",
|
|
3838
|
-
|
|
3839
|
-
() =>
|
|
6405
|
+
snapshot,
|
|
6406
|
+
async () => prepareInvoice(snapshot, options),
|
|
3840
6407
|
options,
|
|
3841
6408
|
context,
|
|
3842
|
-
prepared.amounts,
|
|
3843
6409
|
select
|
|
3844
6410
|
);
|
|
3845
6411
|
}
|
|
@@ -3850,7 +6416,7 @@ function withIssuerName(input) {
|
|
|
3850
6416
|
const name = issuerConditionName(input.issuer);
|
|
3851
6417
|
return name === void 0 ? input : { ...input, issuer: name };
|
|
3852
6418
|
}
|
|
3853
|
-
async function runOperation(wsfe, operation, input, prepare, options, context,
|
|
6419
|
+
async function runOperation(wsfe, operation, input, prepare, options, context, select) {
|
|
3854
6420
|
const issuer = issuerTaxId(options, context);
|
|
3855
6421
|
if (options.idempotencyKey === void 0 || !context?.store) {
|
|
3856
6422
|
return runAuthorization(
|
|
@@ -3871,7 +6437,7 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
|
|
|
3871
6437
|
...options.service === "wsmtxca" ? { service: "wsmtxca" } : {},
|
|
3872
6438
|
...options.number === void 0 ? {} : { number: options.number }
|
|
3873
6439
|
});
|
|
3874
|
-
const
|
|
6440
|
+
const settled2 = settledKey(environment, taxId, idempotencyKey);
|
|
3875
6441
|
const existing = await storeCall(() => store.get(key));
|
|
3876
6442
|
if (existing !== null) {
|
|
3877
6443
|
return await replay(existing);
|
|
@@ -3994,10 +6560,7 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
|
|
|
3994
6560
|
current,
|
|
3995
6561
|
runAuthorization(
|
|
3996
6562
|
wsfe,
|
|
3997
|
-
|
|
3998
|
-
...preparedFromRecord(current),
|
|
3999
|
-
...replayAmounts ? { amounts: replayAmounts } : {}
|
|
4000
|
-
},
|
|
6563
|
+
preparedFromRecord(current),
|
|
4001
6564
|
options,
|
|
4002
6565
|
issuer,
|
|
4003
6566
|
current.number,
|
|
@@ -4015,7 +6578,7 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
|
|
|
4015
6578
|
const uncoordinated = !store.withLock;
|
|
4016
6579
|
const outcome = await recordConflict(
|
|
4017
6580
|
store,
|
|
4018
|
-
|
|
6581
|
+
settled2,
|
|
4019
6582
|
await running,
|
|
4020
6583
|
!(presumed && uncoordinated)
|
|
4021
6584
|
);
|
|
@@ -4039,7 +6602,7 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
|
|
|
4039
6602
|
async function clearRejection(record) {
|
|
4040
6603
|
const checked = await settle2(
|
|
4041
6604
|
record,
|
|
4042
|
-
|
|
6605
|
+
consultReservation2(select ?? (() => wsfe), record, options, taxId, true),
|
|
4043
6606
|
{ presumed: true }
|
|
4044
6607
|
);
|
|
4045
6608
|
if (checked.kind !== "indeterminate" || checked.lookup.kind !== "not_found") {
|
|
@@ -4155,11 +6718,11 @@ function readSettledRecord(json) {
|
|
|
4155
6718
|
);
|
|
4156
6719
|
}
|
|
4157
6720
|
}
|
|
4158
|
-
async function settledOutcome(select, reservation,
|
|
4159
|
-
if (
|
|
4160
|
-
return settledConflict(
|
|
6721
|
+
async function settledOutcome(select, reservation, settled2, options, taxId, settledFor) {
|
|
6722
|
+
if (settled2.kind === "conflict") {
|
|
6723
|
+
return settledConflict(settled2, reservation, options);
|
|
4161
6724
|
}
|
|
4162
|
-
const outcome = await
|
|
6725
|
+
const outcome = await consultReservation2(
|
|
4163
6726
|
select,
|
|
4164
6727
|
reservation,
|
|
4165
6728
|
options,
|
|
@@ -4167,14 +6730,14 @@ async function settledOutcome(select, reservation, settled, options, taxId, sett
|
|
|
4167
6730
|
true
|
|
4168
6731
|
);
|
|
4169
6732
|
const empty = outcome.kind === "indeterminate" && outcome.lookup.kind === "not_found";
|
|
4170
|
-
if (!empty && (outcome.kind !== "conflict" || await successionDisputed(
|
|
6733
|
+
if (!empty && (outcome.kind !== "conflict" || await successionDisputed(settled2, taxId, settledFor))) {
|
|
4171
6734
|
return outcome;
|
|
4172
6735
|
}
|
|
4173
6736
|
return withheld(
|
|
4174
6737
|
reservation,
|
|
4175
|
-
{ kind: "superseded", by:
|
|
6738
|
+
{ kind: "superseded", by: settled2.by, byTaxId: settled2.byTaxId ?? taxId },
|
|
4176
6739
|
options,
|
|
4177
|
-
|
|
6740
|
+
settled2.number
|
|
4178
6741
|
);
|
|
4179
6742
|
}
|
|
4180
6743
|
function withheld(reservation, lookup, options, number = reservation.number) {
|
|
@@ -4214,16 +6777,16 @@ async function successionDisputed(superseded, taxId, settledFor) {
|
|
|
4214
6777
|
}
|
|
4215
6778
|
return true;
|
|
4216
6779
|
}
|
|
4217
|
-
function settledConflict(
|
|
6780
|
+
function settledConflict(settled2, reservation, options) {
|
|
4218
6781
|
return {
|
|
4219
6782
|
kind: "conflict",
|
|
4220
6783
|
attempted: {
|
|
4221
6784
|
salesPoint: reservation.salesPoint,
|
|
4222
6785
|
voucherType: reservation.voucherType,
|
|
4223
|
-
number:
|
|
6786
|
+
number: settled2.number
|
|
4224
6787
|
},
|
|
4225
6788
|
attempt: replayEvidence(reservation.service),
|
|
4226
|
-
found:
|
|
6789
|
+
found: settled2.found,
|
|
4227
6790
|
reason: "This key already recorded another voucher at the reserved number. Reconcile before issuing under a new key.",
|
|
4228
6791
|
...reservationEvidence(reservation, options)
|
|
4229
6792
|
};
|
|
@@ -4245,6 +6808,15 @@ function validateKeyStore(options, context) {
|
|
|
4245
6808
|
}
|
|
4246
6809
|
}
|
|
4247
6810
|
function readRecord(json) {
|
|
6811
|
+
if (isExportRecord(json)) {
|
|
6812
|
+
throw new ArcaInputError(
|
|
6813
|
+
"The idempotency key was already used for an export voucher.",
|
|
6814
|
+
{
|
|
6815
|
+
code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH",
|
|
6816
|
+
field: "options.idempotencyKey"
|
|
6817
|
+
}
|
|
6818
|
+
);
|
|
6819
|
+
}
|
|
4248
6820
|
try {
|
|
4249
6821
|
const record = JSON.parse(json);
|
|
4250
6822
|
if (!record || record.v !== 1 && record.v !== 2 || record.v === 2 && record.service === void 0 || !["issue", "creditNote", "debitNote"].includes(record.operation) || typeof record.inputHash !== "string" || !record.sent || record.rejectedAt !== void 0 && typeof record.rejectedAt !== "string" || !Number.isSafeInteger(record.number) || record.number < 1 || record.number > 99999999 || record.sent.salesPoint !== record.salesPoint || record.sent.voucherType !== record.voucherType) {
|
|
@@ -4265,6 +6837,13 @@ function readRecord(json) {
|
|
|
4265
6837
|
);
|
|
4266
6838
|
}
|
|
4267
6839
|
}
|
|
6840
|
+
function isExportRecord(json) {
|
|
6841
|
+
try {
|
|
6842
|
+
return JSON.parse(json)?.service === "wsfex";
|
|
6843
|
+
} catch {
|
|
6844
|
+
return false;
|
|
6845
|
+
}
|
|
6846
|
+
}
|
|
4268
6847
|
function preparedFromRecord(record) {
|
|
4269
6848
|
const sentTotal = Number(
|
|
4270
6849
|
normalizeArcaAmountToMinorUnits(record.sent.totalAmount, "totalAmount")
|
|
@@ -4298,7 +6877,7 @@ async function nextNumber(wsfe, data, options) {
|
|
|
4298
6877
|
return number;
|
|
4299
6878
|
}
|
|
4300
6879
|
async function runAuthorization(wsfe, { data, voucherClass, amounts }, options, taxId, reservedNumber, { lookupFirst = false, strangerAtNumber = false } = {}) {
|
|
4301
|
-
const
|
|
6880
|
+
const auth2 = {
|
|
4302
6881
|
representedTaxId: options.representedTaxId,
|
|
4303
6882
|
forceRefresh: options.forceRefresh,
|
|
4304
6883
|
...options.abortSignal === void 0 ? {} : { abortSignal: options.abortSignal }
|
|
@@ -4311,7 +6890,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
|
|
|
4311
6890
|
number
|
|
4312
6891
|
};
|
|
4313
6892
|
const includedRequest = requestEvidence(data, number, options);
|
|
4314
|
-
const voucher = (cae, caeExpiry, lookup) =>
|
|
6893
|
+
const voucher = (cae, caeExpiry, lookup) => issuedVoucher2(
|
|
4315
6894
|
{ attempted, voucherClass, data, amounts, taxId },
|
|
4316
6895
|
cae,
|
|
4317
6896
|
caeExpiry,
|
|
@@ -4319,7 +6898,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
|
|
|
4319
6898
|
);
|
|
4320
6899
|
const recovery = {
|
|
4321
6900
|
wsfe,
|
|
4322
|
-
auth,
|
|
6901
|
+
auth: auth2,
|
|
4323
6902
|
data,
|
|
4324
6903
|
attempted,
|
|
4325
6904
|
includeRawResponse,
|
|
@@ -4331,7 +6910,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
|
|
|
4331
6910
|
const attempt2 = replayEvidence(options.service);
|
|
4332
6911
|
let lookup;
|
|
4333
6912
|
try {
|
|
4334
|
-
lookup = await wsfe.lookupVoucher({ ...
|
|
6913
|
+
lookup = await wsfe.lookupVoucher({ ...auth2, ...attempted });
|
|
4335
6914
|
} catch (error) {
|
|
4336
6915
|
return {
|
|
4337
6916
|
kind: "indeterminate",
|
|
@@ -4346,13 +6925,13 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
|
|
|
4346
6925
|
}
|
|
4347
6926
|
}
|
|
4348
6927
|
let authorization = await wsfe.issue({
|
|
4349
|
-
...
|
|
6928
|
+
...auth2,
|
|
4350
6929
|
data,
|
|
4351
6930
|
voucherNumber: number
|
|
4352
6931
|
});
|
|
4353
|
-
if (authorization.kind === "indeterminate" && authorization.reason === "authentication_rejected" &&
|
|
6932
|
+
if (authorization.kind === "indeterminate" && authorization.reason === "authentication_rejected" && auth2.forceRefresh !== true) {
|
|
4354
6933
|
authorization = await wsfe.issue({
|
|
4355
|
-
...
|
|
6934
|
+
...auth2,
|
|
4356
6935
|
forceRefresh: true,
|
|
4357
6936
|
data,
|
|
4358
6937
|
voucherNumber: number
|
|
@@ -4385,7 +6964,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
|
|
|
4385
6964
|
return recoverInvoice({
|
|
4386
6965
|
wsfe,
|
|
4387
6966
|
service: options.service,
|
|
4388
|
-
auth,
|
|
6967
|
+
auth: auth2,
|
|
4389
6968
|
data,
|
|
4390
6969
|
attempted,
|
|
4391
6970
|
attempt,
|
|
@@ -4427,7 +7006,7 @@ function mayHoldNumber(issues, service = "wsfe") {
|
|
|
4427
7006
|
}
|
|
4428
7007
|
async function recoverInvoice({
|
|
4429
7008
|
wsfe,
|
|
4430
|
-
auth,
|
|
7009
|
+
auth: auth2,
|
|
4431
7010
|
data,
|
|
4432
7011
|
attempted,
|
|
4433
7012
|
attempt,
|
|
@@ -4438,7 +7017,7 @@ async function recoverInvoice({
|
|
|
4438
7017
|
service,
|
|
4439
7018
|
strangerAtNumber = false
|
|
4440
7019
|
}) {
|
|
4441
|
-
if (suppliedLookup === void 0 &&
|
|
7020
|
+
if (suppliedLookup === void 0 && auth2.abortSignal?.aborted) {
|
|
4442
7021
|
return {
|
|
4443
7022
|
kind: "indeterminate",
|
|
4444
7023
|
attempted,
|
|
@@ -4449,13 +7028,13 @@ async function recoverInvoice({
|
|
|
4449
7028
|
}
|
|
4450
7029
|
let lookup;
|
|
4451
7030
|
try {
|
|
4452
|
-
lookup = suppliedLookup ?? await wsfe.lookupVoucher({ ...
|
|
7031
|
+
lookup = suppliedLookup ?? await wsfe.lookupVoucher({ ...auth2, ...attempted });
|
|
4453
7032
|
} catch (error) {
|
|
4454
7033
|
return {
|
|
4455
7034
|
kind: "indeterminate",
|
|
4456
7035
|
attempted,
|
|
4457
7036
|
attempt,
|
|
4458
|
-
lookup:
|
|
7037
|
+
lookup: auth2.abortSignal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) },
|
|
4459
7038
|
...includedRequest
|
|
4460
7039
|
};
|
|
4461
7040
|
}
|
|
@@ -4675,7 +7254,6 @@ async function issueCreditNote(wsfe, input, inputOptions, context, kind = "credi
|
|
|
4675
7254
|
() => prepareNote(wsfe, note, options, context, kind),
|
|
4676
7255
|
options,
|
|
4677
7256
|
context,
|
|
4678
|
-
void 0,
|
|
4679
7257
|
select
|
|
4680
7258
|
);
|
|
4681
7259
|
}
|
|
@@ -4792,8 +7370,7 @@ function assertBounds(value, bounds, field, method) {
|
|
|
4792
7370
|
}
|
|
4793
7371
|
}
|
|
4794
7372
|
}
|
|
4795
|
-
|
|
4796
|
-
const options = cloneOptions(inputOptions);
|
|
7373
|
+
function assertLookup(voucher, options) {
|
|
4797
7374
|
assertIssueKeys(
|
|
4798
7375
|
options,
|
|
4799
7376
|
["representedTaxId", "service", "forceRefresh", "abortSignal"],
|
|
@@ -4808,6 +7385,26 @@ async function lookupVoucher(wsfe, voucher, inputOptions) {
|
|
|
4808
7385
|
"lookup()"
|
|
4809
7386
|
);
|
|
4810
7387
|
assertBounds(voucher, LOOKUP_BOUNDS, "voucher", "lookup()");
|
|
7388
|
+
}
|
|
7389
|
+
function assertSequence(sequence, options) {
|
|
7390
|
+
assertIssueKeys(
|
|
7391
|
+
options,
|
|
7392
|
+
["representedTaxId", "service", "forceRefresh", "abortSignal"],
|
|
7393
|
+
"options",
|
|
7394
|
+
"lastAuthorized()"
|
|
7395
|
+
);
|
|
7396
|
+
assertIssueObject(sequence, "sequence");
|
|
7397
|
+
assertIssueKeys(
|
|
7398
|
+
sequence,
|
|
7399
|
+
["salesPoint", "voucherType"],
|
|
7400
|
+
"sequence",
|
|
7401
|
+
"lastAuthorized()"
|
|
7402
|
+
);
|
|
7403
|
+
assertBounds(sequence, SEQUENCE_BOUNDS, "sequence", "lastAuthorized()");
|
|
7404
|
+
}
|
|
7405
|
+
async function lookupVoucher(wsfe, voucher, inputOptions) {
|
|
7406
|
+
const options = cloneOptions(inputOptions);
|
|
7407
|
+
assertLookup(voucher, options);
|
|
4811
7408
|
const found = await wsfe.lookupVoucher({
|
|
4812
7409
|
representedTaxId: options.representedTaxId,
|
|
4813
7410
|
forceRefresh: options.forceRefresh,
|
|
@@ -4829,20 +7426,7 @@ async function lookupVoucher(wsfe, voucher, inputOptions) {
|
|
|
4829
7426
|
}
|
|
4830
7427
|
async function lastAuthorizedNumber(wsfe, sequence, inputOptions) {
|
|
4831
7428
|
const options = cloneOptions(inputOptions);
|
|
4832
|
-
|
|
4833
|
-
options,
|
|
4834
|
-
["representedTaxId", "service", "forceRefresh", "abortSignal"],
|
|
4835
|
-
"options",
|
|
4836
|
-
"lastAuthorized()"
|
|
4837
|
-
);
|
|
4838
|
-
assertIssueObject(sequence, "sequence");
|
|
4839
|
-
assertIssueKeys(
|
|
4840
|
-
sequence,
|
|
4841
|
-
["salesPoint", "voucherType"],
|
|
4842
|
-
"sequence",
|
|
4843
|
-
"lastAuthorized()"
|
|
4844
|
-
);
|
|
4845
|
-
assertBounds(sequence, SEQUENCE_BOUNDS, "sequence", "lastAuthorized()");
|
|
7429
|
+
assertSequence(sequence, options);
|
|
4846
7430
|
const next = await wsfe.getNextVoucherNumber({
|
|
4847
7431
|
representedTaxId: options.representedTaxId,
|
|
4848
7432
|
forceRefresh: options.forceRefresh,
|
|
@@ -4905,7 +7489,7 @@ function requestEvidence(data, number, options) {
|
|
|
4905
7489
|
request: options.service === "wsmtxca" ? wsmtxcaRequest(data, number) : data
|
|
4906
7490
|
} : {};
|
|
4907
7491
|
}
|
|
4908
|
-
async function recoverOperation(select, key, inputOptions, context) {
|
|
7492
|
+
async function recoverOperation(select, key, inputOptions, context, read) {
|
|
4909
7493
|
const options = cloneOptions(inputOptions);
|
|
4910
7494
|
assertIssueObject(options, "options");
|
|
4911
7495
|
assertIssueKeys(
|
|
@@ -4917,9 +7501,7 @@ async function recoverOperation(select, key, inputOptions, context) {
|
|
|
4917
7501
|
validateKeyStore({ ...options, idempotencyKey: key }, context);
|
|
4918
7502
|
const scope = keyScope(context, key);
|
|
4919
7503
|
const { store, environment, taxId } = scope;
|
|
4920
|
-
const json = await storeCall(
|
|
4921
|
-
() => store.get(attemptKey(environment, taxId, key))
|
|
4922
|
-
);
|
|
7504
|
+
const json = read === void 0 ? await storeCall(() => store.get(attemptKey(environment, taxId, key))) : read;
|
|
4923
7505
|
if (json === null) {
|
|
4924
7506
|
throw new ArcaInputError("No reservation exists for this idempotency key", {
|
|
4925
7507
|
code: "ARCA_INPUT_RESERVATION_NOT_FOUND",
|
|
@@ -4944,7 +7526,7 @@ async function recoverOperation(select, key, inputOptions, context) {
|
|
|
4944
7526
|
return await recordConflict(
|
|
4945
7527
|
store,
|
|
4946
7528
|
settledKey(environment, taxId, key),
|
|
4947
|
-
await
|
|
7529
|
+
await consultReservation2(select, current, options, taxId, rejected),
|
|
4948
7530
|
!rejected || store.withLock !== void 0
|
|
4949
7531
|
);
|
|
4950
7532
|
}
|
|
@@ -4961,7 +7543,7 @@ function keyScope(context, idempotencyKey) {
|
|
|
4961
7543
|
idempotencyKey
|
|
4962
7544
|
};
|
|
4963
7545
|
}
|
|
4964
|
-
function
|
|
7546
|
+
function consultReservation2(select, record, options, taxId, strangerAtNumber = false) {
|
|
4965
7547
|
const storedOptions = {
|
|
4966
7548
|
...options,
|
|
4967
7549
|
service: record.service ?? "wsfe",
|
|
@@ -4987,7 +7569,7 @@ function consultReservation(select, record, options, taxId, strangerAtNumber = f
|
|
|
4987
7569
|
record.number,
|
|
4988
7570
|
storedOptions
|
|
4989
7571
|
),
|
|
4990
|
-
voucher: (cae, caeExpiry, lookup) =>
|
|
7572
|
+
voucher: (cae, caeExpiry, lookup) => issuedVoucher2(
|
|
4991
7573
|
{
|
|
4992
7574
|
attempted,
|
|
4993
7575
|
voucherClass: prepared.voucherClass,
|
|
@@ -5004,7 +7586,7 @@ function consultReservation(select, record, options, taxId, strangerAtNumber = f
|
|
|
5004
7586
|
function issuerTaxId(options, context) {
|
|
5005
7587
|
return options.representedTaxId === void 0 ? context?.taxId : String(options.representedTaxId);
|
|
5006
7588
|
}
|
|
5007
|
-
function
|
|
7589
|
+
function issuedVoucher2({
|
|
5008
7590
|
attempted,
|
|
5009
7591
|
voucherClass,
|
|
5010
7592
|
data,
|
|
@@ -5100,8 +7682,8 @@ async function runSequenceBarrier({
|
|
|
5100
7682
|
return {};
|
|
5101
7683
|
}
|
|
5102
7684
|
const owner = claimed.issuerTaxId ?? taxId;
|
|
5103
|
-
const
|
|
5104
|
-
if (await storeCall(() => store.get(
|
|
7685
|
+
const settled2 = settledKey(environment, owner, claimed.key);
|
|
7686
|
+
if (await storeCall(() => store.get(settled2)) !== null) {
|
|
5105
7687
|
return {};
|
|
5106
7688
|
}
|
|
5107
7689
|
const reservation = await storeCall(
|
|
@@ -5123,8 +7705,8 @@ async function runSequenceBarrier({
|
|
|
5123
7705
|
};
|
|
5124
7706
|
const outcome = await recordConflict(
|
|
5125
7707
|
store,
|
|
5126
|
-
|
|
5127
|
-
await
|
|
7708
|
+
settled2,
|
|
7709
|
+
await consultReservation2(
|
|
5128
7710
|
select,
|
|
5129
7711
|
// This call's certificate may not be the one that claimed. Without a
|
|
5130
7712
|
// represented CUIT, it would read its own numbering instead of the one
|
|
@@ -5155,7 +7737,7 @@ async function runSequenceBarrier({
|
|
|
5155
7737
|
}
|
|
5156
7738
|
await storeCall(
|
|
5157
7739
|
() => store.add(
|
|
5158
|
-
|
|
7740
|
+
settled2,
|
|
5159
7741
|
JSON.stringify({
|
|
5160
7742
|
v: 1,
|
|
5161
7743
|
kind: "superseded",
|
|
@@ -5385,7 +7967,7 @@ function createArcaClient(config = {}) {
|
|
|
5385
7967
|
);
|
|
5386
7968
|
}
|
|
5387
7969
|
const logger = createArcaLogger(normalizedConfig.logger);
|
|
5388
|
-
const
|
|
7970
|
+
const auth2 = createWsaaAuthModule({ config: normalizedConfig, logger });
|
|
5389
7971
|
const soap = createSoapTransport({ config: normalizedConfig, logger });
|
|
5390
7972
|
const publicConfig = Object.freeze({
|
|
5391
7973
|
taxId: normalizedConfig.taxId,
|
|
@@ -5394,13 +7976,19 @@ function createArcaClient(config = {}) {
|
|
|
5394
7976
|
retries: normalizedConfig.retries,
|
|
5395
7977
|
retryDelay: normalizedConfig.retryDelay
|
|
5396
7978
|
});
|
|
5397
|
-
const wsfe = createWsfeService({ config: normalizedConfig, auth, soap });
|
|
7979
|
+
const wsfe = createWsfeService({ config: normalizedConfig, auth: auth2, soap });
|
|
5398
7980
|
const wsmtxca = createWsmtxcaService({
|
|
5399
7981
|
config: normalizedConfig,
|
|
5400
|
-
auth,
|
|
7982
|
+
auth: auth2,
|
|
5401
7983
|
soap
|
|
5402
7984
|
});
|
|
5403
|
-
const
|
|
7985
|
+
const wsfex = createWsfexService({ config: normalizedConfig, auth: auth2, soap });
|
|
7986
|
+
const vouchers = createVouchersService(
|
|
7987
|
+
wsfe,
|
|
7988
|
+
normalizedConfig,
|
|
7989
|
+
wsmtxca,
|
|
7990
|
+
wsfex
|
|
7991
|
+
);
|
|
5404
7992
|
return {
|
|
5405
7993
|
config: publicConfig,
|
|
5406
7994
|
issue: vouchers.issue,
|
|
@@ -5413,8 +8001,9 @@ function createArcaClient(config = {}) {
|
|
|
5413
8001
|
lookup: vouchers.lookup,
|
|
5414
8002
|
lastAuthorized: vouchers.lastAuthorized,
|
|
5415
8003
|
wsfe,
|
|
8004
|
+
wsfex,
|
|
5416
8005
|
wsmtxca,
|
|
5417
|
-
padron: createPadronService({ config: normalizedConfig, auth, soap })
|
|
8006
|
+
padron: createPadronService({ config: normalizedConfig, auth: auth2, soap })
|
|
5418
8007
|
};
|
|
5419
8008
|
}
|
|
5420
8009
|
|
|
@@ -5432,71 +8021,6 @@ import {
|
|
|
5432
8021
|
writeFile
|
|
5433
8022
|
} from "fs/promises";
|
|
5434
8023
|
import { join } from "path";
|
|
5435
|
-
|
|
5436
|
-
// src/store/lock.ts
|
|
5437
|
-
import { randomUUID } from "crypto";
|
|
5438
|
-
var ARCA_LEASE_MS = 6e4;
|
|
5439
|
-
var RENEW_MS = 2e4;
|
|
5440
|
-
var POLL_MS = 50;
|
|
5441
|
-
var MAX_WAIT_MS = 2 * ARCA_LEASE_MS;
|
|
5442
|
-
async function withLease(key, driver, fn, { signal } = {}) {
|
|
5443
|
-
const owner = randomUUID();
|
|
5444
|
-
const deadline = Date.now() + MAX_WAIT_MS;
|
|
5445
|
-
throwIfAborted(key, signal);
|
|
5446
|
-
let held = await driver.acquire(owner);
|
|
5447
|
-
while (!held) {
|
|
5448
|
-
if (Date.now() >= deadline) {
|
|
5449
|
-
throw new ArcaLockTimeoutError(
|
|
5450
|
-
`ARCA store lock ${key} stayed held; no work was attempted.`,
|
|
5451
|
-
{ reason: "held" }
|
|
5452
|
-
);
|
|
5453
|
-
}
|
|
5454
|
-
await delay2(POLL_MS + Math.floor(Math.random() * POLL_MS), signal);
|
|
5455
|
-
throwIfAborted(key, signal);
|
|
5456
|
-
held = await driver.acquire(owner);
|
|
5457
|
-
}
|
|
5458
|
-
if (signal?.aborted) {
|
|
5459
|
-
await driver.release(owner).catch(() => void 0);
|
|
5460
|
-
throwIfAborted(key, signal);
|
|
5461
|
-
}
|
|
5462
|
-
const renewal = setInterval(() => {
|
|
5463
|
-
driver.renew(owner).catch(() => void 0);
|
|
5464
|
-
}, RENEW_MS);
|
|
5465
|
-
renewal.unref?.();
|
|
5466
|
-
try {
|
|
5467
|
-
return await fn();
|
|
5468
|
-
} finally {
|
|
5469
|
-
clearInterval(renewal);
|
|
5470
|
-
await driver.release(owner).catch(() => void 0);
|
|
5471
|
-
}
|
|
5472
|
-
}
|
|
5473
|
-
function throwIfAborted(key, signal) {
|
|
5474
|
-
if (signal?.aborted) {
|
|
5475
|
-
throw new ArcaLockTimeoutError(
|
|
5476
|
-
`Stopped waiting for ARCA store lock ${key}: the caller's signal aborted; no work was attempted.`,
|
|
5477
|
-
{ reason: "aborted", cause: signal.reason }
|
|
5478
|
-
);
|
|
5479
|
-
}
|
|
5480
|
-
}
|
|
5481
|
-
function delay2(ms, signal) {
|
|
5482
|
-
if (signal?.aborted) {
|
|
5483
|
-
return Promise.resolve();
|
|
5484
|
-
}
|
|
5485
|
-
return new Promise((resolve) => {
|
|
5486
|
-
const wake = () => {
|
|
5487
|
-
clearTimeout(timer);
|
|
5488
|
-
resolve();
|
|
5489
|
-
};
|
|
5490
|
-
const timer = setTimeout(() => {
|
|
5491
|
-
signal?.removeEventListener("abort", wake);
|
|
5492
|
-
resolve();
|
|
5493
|
-
}, ms);
|
|
5494
|
-
timer.unref?.();
|
|
5495
|
-
signal?.addEventListener("abort", wake, { once: true });
|
|
5496
|
-
});
|
|
5497
|
-
}
|
|
5498
|
-
|
|
5499
|
-
// src/store/file.ts
|
|
5500
8024
|
function createFileStore(directory) {
|
|
5501
8025
|
const path = (key) => join(directory, createHash3("sha256").update(key).digest("hex"));
|
|
5502
8026
|
const ensure = () => mkdir(directory, { recursive: true, mode: 448 });
|
|
@@ -5545,7 +8069,7 @@ function createFileStore(directory) {
|
|
|
5545
8069
|
withLock: (key, fn, options) => (
|
|
5546
8070
|
// A lock directory: mkdir is the atomic claim on every POSIX filesystem
|
|
5547
8071
|
// and on Windows, and the holder file inside carries the lease.
|
|
5548
|
-
withLease(
|
|
8072
|
+
withLease(fileLease(`${path(key)}.lock`, ensure), fn, options)
|
|
5549
8073
|
)
|
|
5550
8074
|
};
|
|
5551
8075
|
}
|
|
@@ -5716,19 +8240,20 @@ export {
|
|
|
5716
8240
|
discoverArcaClientConfig,
|
|
5717
8241
|
storeCall,
|
|
5718
8242
|
describeVoucherType,
|
|
8243
|
+
ARCA_LEASE_MS,
|
|
8244
|
+
withLease,
|
|
8245
|
+
throwIfAborted,
|
|
5719
8246
|
ARCA_QR_URL,
|
|
5720
8247
|
arcaQrUrl,
|
|
5721
8248
|
arcaQrPayload,
|
|
5722
8249
|
voucherDateWindow,
|
|
8250
|
+
createWsfexService,
|
|
5723
8251
|
matchWsfeVoucherIdentity,
|
|
5724
8252
|
abortable,
|
|
5725
8253
|
createMemoryWsaaSessionStore,
|
|
5726
8254
|
createWsaaAuthModule,
|
|
5727
8255
|
createWsaaStoreAdapter,
|
|
5728
8256
|
createArcaClient,
|
|
5729
|
-
ARCA_LEASE_MS,
|
|
5730
|
-
withLease,
|
|
5731
|
-
throwIfAborted,
|
|
5732
8257
|
createFileStore
|
|
5733
8258
|
};
|
|
5734
|
-
//# sourceMappingURL=chunk-
|
|
8259
|
+
//# sourceMappingURL=chunk-6BLEIM5F.mjs.map
|