@gabrielerandelli/minus-tracker 0.7.0 → 0.8.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 +168 -32
- package/dist/cli/index.js +224 -82
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +179 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +179 -69
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +1885 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +18 -5
package/dist/cli/index.js
CHANGED
|
@@ -9,6 +9,9 @@ var it = {
|
|
|
9
9
|
errorInvalidCsv: "CSV non valido: impossibile analizzare il file",
|
|
10
10
|
errorMissingColumn: (col) => `Colonna obbligatoria mancante: ${col}`,
|
|
11
11
|
errorNoOpenLots: (isin, date) => `Nessun lotto aperto per ISIN ${isin} in data ${date}`,
|
|
12
|
+
errorCannotReadFile: (path7) => `Impossibile leggere il file: ${path7}`,
|
|
13
|
+
errorCannotLoadSidecar: (path7) => `Impossibile caricare il sidecar: ${path7}`,
|
|
14
|
+
errorCannotWriteExport: (path7) => `Impossibile scrivere l'export della dichiarazione: ${path7}`,
|
|
12
15
|
warnMissingIsin: (row) => `Riga ${row}: ISIN mancante \u2014 riga ignorata`,
|
|
13
16
|
warnUnsupportedCurrency: (row, currency) => `Riga ${row}: valuta non supportata ${currency} \u2014 riga ignorata`,
|
|
14
17
|
warnNoEcbRate: (row, currency, date) => `Riga ${row}: nessun tasso BCE per ${currency} in data ${date} \u2014 riga ignorata`,
|
|
@@ -88,7 +91,7 @@ var it = {
|
|
|
88
91
|
quadroRMRitenuta: "Ritenuta estera (credito)",
|
|
89
92
|
quadroRMCedole: "[RM-C] Cedole obbligazionarie",
|
|
90
93
|
dichiarazioneDisclaimer: "minus-tracker \xE8 un ausilio al calcolo, non consulenza fiscale.",
|
|
91
|
-
warnNoDichiarazioneSidecar: "
|
|
94
|
+
warnNoDichiarazioneSidecar: "Suggerimento: esegui prima 'minus-tracker classify <file>' per abilitare il Quadro RT/RM.",
|
|
92
95
|
warnNoCarryForwardProvided: "(eventuali perdite pregresse non applicate)"
|
|
93
96
|
};
|
|
94
97
|
|
|
@@ -98,6 +101,9 @@ var en = {
|
|
|
98
101
|
errorInvalidCsv: "Invalid CSV: unable to parse",
|
|
99
102
|
errorMissingColumn: (col) => `Missing required column: ${col}`,
|
|
100
103
|
errorNoOpenLots: (isin, date) => `No open lots for ISIN ${isin} on ${date}`,
|
|
104
|
+
errorCannotReadFile: (path7) => `Cannot read file: ${path7}`,
|
|
105
|
+
errorCannotLoadSidecar: (path7) => `Cannot load sidecar: ${path7}`,
|
|
106
|
+
errorCannotWriteExport: (path7) => `Cannot write dichiarazione export: ${path7}`,
|
|
101
107
|
warnMissingIsin: (row) => `Row ${row}: missing ISIN \u2014 skipped`,
|
|
102
108
|
warnUnsupportedCurrency: (row, currency) => `Row ${row}: unsupported currency ${currency} \u2014 skipped`,
|
|
103
109
|
warnNoEcbRate: (row, currency, date) => `Row ${row}: no ECB rate for ${currency} on ${date} \u2014 skipped`,
|
|
@@ -177,7 +183,7 @@ var en = {
|
|
|
177
183
|
quadroRMRitenuta: "Foreign withholding (credit)",
|
|
178
184
|
quadroRMCedole: "[RM-C] Bond coupons",
|
|
179
185
|
dichiarazioneDisclaimer: "minus-tracker \xE8 un ausilio al calcolo, non consulenza fiscale.",
|
|
180
|
-
warnNoDichiarazioneSidecar: "
|
|
186
|
+
warnNoDichiarazioneSidecar: "Tip: run 'minus-tracker classify <file>' first to enable Quadro RT/RM.",
|
|
181
187
|
warnNoCarryForwardProvided: "(prior-year losses not applied)"
|
|
182
188
|
};
|
|
183
189
|
|
|
@@ -1246,7 +1252,7 @@ function classifyByType(isin, securityType, product, warnings) {
|
|
|
1246
1252
|
};
|
|
1247
1253
|
}
|
|
1248
1254
|
warnings.push(
|
|
1249
|
-
`Unrecognized type: ${securityType}. Please classify manually.`
|
|
1255
|
+
`Unrecognized type for ${isin}: ${securityType}. Please classify manually.`
|
|
1250
1256
|
);
|
|
1251
1257
|
return {
|
|
1252
1258
|
product,
|
|
@@ -1259,6 +1265,84 @@ function classifyByType(isin, securityType, product, warnings) {
|
|
|
1259
1265
|
source: "user"
|
|
1260
1266
|
};
|
|
1261
1267
|
}
|
|
1268
|
+
var ASSET_CLASS_DEFAULTS = {
|
|
1269
|
+
ETF: {
|
|
1270
|
+
assetClass: "ETF",
|
|
1271
|
+
bucketGain: "A",
|
|
1272
|
+
bucketLoss: "B",
|
|
1273
|
+
taxRate: 0.26,
|
|
1274
|
+
whiteListed: null
|
|
1275
|
+
},
|
|
1276
|
+
Stock: {
|
|
1277
|
+
assetClass: "Stock",
|
|
1278
|
+
bucketGain: "B",
|
|
1279
|
+
bucketLoss: "B",
|
|
1280
|
+
taxRate: 0,
|
|
1281
|
+
whiteListed: null
|
|
1282
|
+
},
|
|
1283
|
+
ETC: {
|
|
1284
|
+
assetClass: "ETC",
|
|
1285
|
+
bucketGain: "B",
|
|
1286
|
+
bucketLoss: "B",
|
|
1287
|
+
taxRate: 0,
|
|
1288
|
+
whiteListed: null
|
|
1289
|
+
},
|
|
1290
|
+
GovtBondWL: {
|
|
1291
|
+
assetClass: "GovtBondWL",
|
|
1292
|
+
bucketGain: "A",
|
|
1293
|
+
bucketLoss: "B",
|
|
1294
|
+
taxRate: 0.125,
|
|
1295
|
+
whiteListed: true
|
|
1296
|
+
},
|
|
1297
|
+
GovtBondOther: {
|
|
1298
|
+
assetClass: "GovtBondOther",
|
|
1299
|
+
bucketGain: "A",
|
|
1300
|
+
bucketLoss: "B",
|
|
1301
|
+
taxRate: 0.26,
|
|
1302
|
+
whiteListed: false
|
|
1303
|
+
},
|
|
1304
|
+
CorpBond: {
|
|
1305
|
+
assetClass: "CorpBond",
|
|
1306
|
+
bucketGain: "B",
|
|
1307
|
+
bucketLoss: "B",
|
|
1308
|
+
taxRate: 0,
|
|
1309
|
+
whiteListed: null
|
|
1310
|
+
},
|
|
1311
|
+
Derivative: {
|
|
1312
|
+
assetClass: "Derivative",
|
|
1313
|
+
bucketGain: "B",
|
|
1314
|
+
bucketLoss: "B",
|
|
1315
|
+
taxRate: 0,
|
|
1316
|
+
whiteListed: null
|
|
1317
|
+
},
|
|
1318
|
+
LeverageCert: {
|
|
1319
|
+
assetClass: "LeverageCert",
|
|
1320
|
+
bucketGain: "B",
|
|
1321
|
+
bucketLoss: "B",
|
|
1322
|
+
taxRate: 0,
|
|
1323
|
+
whiteListed: null
|
|
1324
|
+
},
|
|
1325
|
+
CapProtectedCert: {
|
|
1326
|
+
assetClass: "CapProtectedCert",
|
|
1327
|
+
bucketGain: "A",
|
|
1328
|
+
bucketLoss: "B",
|
|
1329
|
+
taxRate: 0.26,
|
|
1330
|
+
whiteListed: null
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
function buildEntryFromAssetClass(assetClass, product) {
|
|
1334
|
+
const mapped = ASSET_CLASS_DEFAULTS[assetClass];
|
|
1335
|
+
return {
|
|
1336
|
+
product,
|
|
1337
|
+
assetClass: mapped.assetClass,
|
|
1338
|
+
bucketGain: mapped.bucketGain,
|
|
1339
|
+
bucketLoss: mapped.bucketLoss,
|
|
1340
|
+
taxRate: mapped.taxRate,
|
|
1341
|
+
whiteListed: mapped.whiteListed,
|
|
1342
|
+
confirmedByUser: true,
|
|
1343
|
+
source: "user"
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1262
1346
|
var Classifier = class {
|
|
1263
1347
|
interactive;
|
|
1264
1348
|
constructor(options) {
|
|
@@ -1280,7 +1364,7 @@ var Classifier = class {
|
|
|
1280
1364
|
}
|
|
1281
1365
|
return parsed.classifications;
|
|
1282
1366
|
}
|
|
1283
|
-
async classify(transactions, sidecarPath, _httpPost = httpsPost) {
|
|
1367
|
+
async classify(transactions, sidecarPath, options, _httpPost = httpsPost) {
|
|
1284
1368
|
const isinToProduct = /* @__PURE__ */ new Map();
|
|
1285
1369
|
for (const tx of transactions) {
|
|
1286
1370
|
if (tx.isin && !isinToProduct.has(tx.isin)) {
|
|
@@ -1288,13 +1372,21 @@ var Classifier = class {
|
|
|
1288
1372
|
}
|
|
1289
1373
|
}
|
|
1290
1374
|
const confirmed = {};
|
|
1291
|
-
if (
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1375
|
+
if (sidecarPath !== void 0) {
|
|
1376
|
+
if (fs3.existsSync(sidecarPath)) {
|
|
1377
|
+
const existingMap = await this.load(sidecarPath);
|
|
1378
|
+
for (const [isin, entry] of Object.entries(existingMap)) {
|
|
1379
|
+
if (entry.confirmedByUser) {
|
|
1380
|
+
confirmed[isin] = entry;
|
|
1381
|
+
}
|
|
1296
1382
|
}
|
|
1297
1383
|
}
|
|
1384
|
+
} else if (options?.existingClassification) {
|
|
1385
|
+
Object.assign(confirmed, options.existingClassification);
|
|
1386
|
+
}
|
|
1387
|
+
for (const [isin, assetClass] of Object.entries(options?.overrides ?? {})) {
|
|
1388
|
+
const product = isinToProduct.get(isin) ?? isin;
|
|
1389
|
+
confirmed[isin] = buildEntryFromAssetClass(assetClass, product);
|
|
1298
1390
|
}
|
|
1299
1391
|
const toProcess = [];
|
|
1300
1392
|
for (const isin of isinToProduct.keys()) {
|
|
@@ -1304,76 +1396,100 @@ var Classifier = class {
|
|
|
1304
1396
|
}
|
|
1305
1397
|
const warnings = [];
|
|
1306
1398
|
const newEntries = {};
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1399
|
+
if (options?.offline) {
|
|
1400
|
+
for (const isin of toProcess) {
|
|
1401
|
+
const product = isinToProduct.get(isin) ?? isin;
|
|
1402
|
+
warnings.push(
|
|
1403
|
+
`Unrecognized type for ${isin}: unknown. Please classify manually.`
|
|
1404
|
+
);
|
|
1405
|
+
newEntries[isin] = {
|
|
1406
|
+
product,
|
|
1407
|
+
assetClass: "Stock",
|
|
1408
|
+
bucketGain: "B",
|
|
1409
|
+
bucketLoss: "B",
|
|
1410
|
+
taxRate: 0,
|
|
1411
|
+
whiteListed: null,
|
|
1412
|
+
confirmedByUser: false,
|
|
1413
|
+
source: "user"
|
|
1414
|
+
};
|
|
1311
1415
|
}
|
|
1312
|
-
|
|
1313
|
-
const
|
|
1314
|
-
|
|
1315
|
-
)
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1416
|
+
} else {
|
|
1417
|
+
const BATCH_SIZE = 10;
|
|
1418
|
+
const totalBatches = Math.ceil(toProcess.length / BATCH_SIZE);
|
|
1419
|
+
for (let i = 0; i < toProcess.length; i += BATCH_SIZE) {
|
|
1420
|
+
if (i > 0) {
|
|
1421
|
+
await new Promise((resolve2) => setTimeout(resolve2, 6e3));
|
|
1422
|
+
}
|
|
1423
|
+
const batch = toProcess.slice(i, i + BATCH_SIZE);
|
|
1424
|
+
const requestBody = JSON.stringify(
|
|
1425
|
+
batch.map((isin) => ({ idType: "ID_ISIN", idValue: isin }))
|
|
1426
|
+
);
|
|
1427
|
+
let response = null;
|
|
1428
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1429
|
+
try {
|
|
1430
|
+
const r = await _httpPost(
|
|
1431
|
+
"https://api.openfigi.com/v3/mapping",
|
|
1432
|
+
requestBody,
|
|
1433
|
+
1e4
|
|
1434
|
+
);
|
|
1435
|
+
if (r.status < 500) {
|
|
1436
|
+
response = r;
|
|
1437
|
+
break;
|
|
1438
|
+
}
|
|
1439
|
+
} catch {
|
|
1440
|
+
throw new ClassificationError("NETWORK_ERROR");
|
|
1327
1441
|
}
|
|
1328
|
-
}
|
|
1442
|
+
}
|
|
1443
|
+
if (response === null) {
|
|
1329
1444
|
throw new ClassificationError("NETWORK_ERROR");
|
|
1330
1445
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1446
|
+
const results = JSON.parse(response.data);
|
|
1447
|
+
for (let j = 0; j < batch.length; j++) {
|
|
1448
|
+
const isin = batch[j];
|
|
1449
|
+
const result = results[j];
|
|
1450
|
+
const product = isinToProduct.get(isin) ?? isin;
|
|
1451
|
+
if (!result || result.error || !result.data || result.data.length === 0) {
|
|
1452
|
+
warnings.push(
|
|
1453
|
+
`Unrecognized type for ${isin}: unknown. Please classify manually.`
|
|
1454
|
+
);
|
|
1455
|
+
newEntries[isin] = {
|
|
1456
|
+
product,
|
|
1457
|
+
assetClass: "Stock",
|
|
1458
|
+
bucketGain: "B",
|
|
1459
|
+
bucketLoss: "B",
|
|
1460
|
+
taxRate: 0,
|
|
1461
|
+
whiteListed: null,
|
|
1462
|
+
confirmedByUser: false,
|
|
1463
|
+
source: "user"
|
|
1464
|
+
};
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1467
|
+
const st = result.data[0]?.securityType;
|
|
1468
|
+
const st2 = result.data[0]?.securityType2;
|
|
1469
|
+
const typeToUse = st && isKnownType(st) ? st : st2 && isKnownType(st2) ? st2 : st ?? st2 ?? "Unknown";
|
|
1470
|
+
newEntries[isin] = classifyByType(isin, typeToUse, product, warnings);
|
|
1355
1471
|
}
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
const typeToUse = st && isKnownType(st) ? st : st2 && isKnownType(st2) ? st2 : st ?? st2 ?? "Unknown";
|
|
1359
|
-
newEntries[isin] = classifyByType(isin, typeToUse, product, warnings);
|
|
1472
|
+
const batchIndex = i / BATCH_SIZE;
|
|
1473
|
+
options?.onBatchProgress?.(batchIndex + 1, totalBatches);
|
|
1360
1474
|
}
|
|
1361
1475
|
}
|
|
1362
1476
|
const mergedMap = { ...newEntries, ...confirmed };
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1477
|
+
if (sidecarPath !== void 0) {
|
|
1478
|
+
const sidecarContent = JSON.stringify(
|
|
1479
|
+
{
|
|
1480
|
+
version: 1,
|
|
1481
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1482
|
+
classifications: mergedMap,
|
|
1483
|
+
warnings
|
|
1484
|
+
},
|
|
1485
|
+
null,
|
|
1486
|
+
2
|
|
1487
|
+
);
|
|
1488
|
+
try {
|
|
1489
|
+
fs3.writeFileSync(sidecarPath, sidecarContent, "utf-8");
|
|
1490
|
+
} catch {
|
|
1491
|
+
throw new ClassificationError("WRITE_ERROR");
|
|
1492
|
+
}
|
|
1377
1493
|
}
|
|
1378
1494
|
return mergedMap;
|
|
1379
1495
|
}
|
|
@@ -1579,8 +1695,7 @@ async function runCalc(positional, flags, s, stdout, stderr) {
|
|
|
1579
1695
|
try {
|
|
1580
1696
|
csv = fs4.readFileSync(filePath, "utf8");
|
|
1581
1697
|
} catch {
|
|
1582
|
-
stderr.write(
|
|
1583
|
-
`);
|
|
1698
|
+
stderr.write(s.errorCannotReadFile(filePath) + "\n");
|
|
1584
1699
|
return 1;
|
|
1585
1700
|
}
|
|
1586
1701
|
const method = flags["method"] ?? "LIFO";
|
|
@@ -1595,8 +1710,7 @@ async function runCalc(positional, flags, s, stdout, stderr) {
|
|
|
1595
1710
|
const classifier = new Classifier({ interactive: false });
|
|
1596
1711
|
classification = await classifier.load(sidecarPath);
|
|
1597
1712
|
} catch {
|
|
1598
|
-
stderr.write(
|
|
1599
|
-
`);
|
|
1713
|
+
stderr.write(s.errorCannotLoadSidecar(sidecarPath) + "\n");
|
|
1600
1714
|
return 1;
|
|
1601
1715
|
}
|
|
1602
1716
|
}
|
|
@@ -1666,8 +1780,7 @@ async function runCalc(positional, flags, s, stdout, stderr) {
|
|
|
1666
1780
|
try {
|
|
1667
1781
|
await report.dichiarazione.exportTo(exportPath);
|
|
1668
1782
|
} catch {
|
|
1669
|
-
stderr.write(
|
|
1670
|
-
`);
|
|
1783
|
+
stderr.write(s.errorCannotWriteExport(exportPath) + "\n");
|
|
1671
1784
|
return 1;
|
|
1672
1785
|
}
|
|
1673
1786
|
}
|
|
@@ -1759,18 +1872,45 @@ function getSnapshotPath() {
|
|
|
1759
1872
|
}
|
|
1760
1873
|
return path4.join(configDir, "minus-tracker", "ecb-rates.json");
|
|
1761
1874
|
}
|
|
1875
|
+
function countMissingBusinessDays(startDate, endDate, dates) {
|
|
1876
|
+
let missing = 0;
|
|
1877
|
+
const cursor = /* @__PURE__ */ new Date(startDate + "T00:00:00Z");
|
|
1878
|
+
const end = /* @__PURE__ */ new Date(endDate + "T00:00:00Z");
|
|
1879
|
+
while (cursor <= end) {
|
|
1880
|
+
const day = cursor.getUTCDay();
|
|
1881
|
+
const iso = cursor.toISOString().slice(0, 10);
|
|
1882
|
+
if (day !== 0 && day !== 6 && dates[iso] === void 0) missing++;
|
|
1883
|
+
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
|
1884
|
+
}
|
|
1885
|
+
return missing;
|
|
1886
|
+
}
|
|
1762
1887
|
function getCoverage(snapshot) {
|
|
1763
1888
|
let start = "9999-12-31";
|
|
1764
1889
|
let end = "0000-01-01";
|
|
1765
1890
|
const currencies = [];
|
|
1891
|
+
const gapEntries = [];
|
|
1766
1892
|
for (const [ccy, dates] of Object.entries(snapshot)) {
|
|
1767
1893
|
currencies.push(ccy);
|
|
1768
|
-
|
|
1894
|
+
const keys = Object.keys(dates).sort();
|
|
1895
|
+
for (const d of keys) {
|
|
1769
1896
|
if (d < start) start = d;
|
|
1770
1897
|
if (d > end) end = d;
|
|
1771
1898
|
}
|
|
1899
|
+
if (keys.length > 0) {
|
|
1900
|
+
const missing = countMissingBusinessDays(
|
|
1901
|
+
keys[0],
|
|
1902
|
+
keys[keys.length - 1],
|
|
1903
|
+
dates
|
|
1904
|
+
);
|
|
1905
|
+
if (missing > 0) gapEntries.push(`${ccy}: ${missing}`);
|
|
1906
|
+
}
|
|
1772
1907
|
}
|
|
1773
|
-
return {
|
|
1908
|
+
return {
|
|
1909
|
+
start,
|
|
1910
|
+
end,
|
|
1911
|
+
currencies: currencies.sort().join(", "),
|
|
1912
|
+
gaps: gapEntries.sort().join(", ")
|
|
1913
|
+
};
|
|
1774
1914
|
}
|
|
1775
1915
|
async function fetchEcbData(currency) {
|
|
1776
1916
|
return new Promise((resolve2, reject) => {
|
|
@@ -1834,9 +1974,9 @@ async function updateRates(snapshotPath, stdout, stderr, s) {
|
|
|
1834
1974
|
async function runRates(positional, flags, s, stdout, stderr) {
|
|
1835
1975
|
if (flags["check"]) {
|
|
1836
1976
|
const snapshot = getActiveSnapshot();
|
|
1837
|
-
const { start, end, currencies } = getCoverage(snapshot);
|
|
1977
|
+
const { start, end, currencies, gaps } = getCoverage(snapshot);
|
|
1838
1978
|
stdout.write(s.ratesCoverage(start, end, currencies) + "\n");
|
|
1839
|
-
stdout.write(s.ratesGapsNone + "\n");
|
|
1979
|
+
stdout.write((gaps ? s.ratesGaps(gaps) : s.ratesGapsNone) + "\n");
|
|
1840
1980
|
return 0;
|
|
1841
1981
|
}
|
|
1842
1982
|
if (flags["update"]) {
|
|
@@ -2402,7 +2542,9 @@ async function main() {
|
|
|
2402
2542
|
range: { type: "string" },
|
|
2403
2543
|
keep: { type: "boolean", default: false },
|
|
2404
2544
|
"output-dir": { type: "string" },
|
|
2405
|
-
offline: { type: "boolean", default: false }
|
|
2545
|
+
offline: { type: "boolean", default: false },
|
|
2546
|
+
"carry-forward": { type: "string", multiple: true },
|
|
2547
|
+
"export-dichiarazione": { type: "string" }
|
|
2406
2548
|
},
|
|
2407
2549
|
allowPositionals: true,
|
|
2408
2550
|
strict: false
|