@formfeed/mcp 0.3.16 → 0.3.17
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 +5 -3
- package/bin/formfeed-mcp.mjs +1317 -13
- package/package.json +5 -2
package/bin/formfeed-mcp.mjs
CHANGED
|
@@ -6257,9 +6257,9 @@ var require_parser2 = __commonJS({
|
|
|
6257
6257
|
this.yytext = "";
|
|
6258
6258
|
this.match = "";
|
|
6259
6259
|
}
|
|
6260
|
-
var
|
|
6261
|
-
for (var i = 0; i <
|
|
6262
|
-
tempMatch = this._input.match(this.rules[
|
|
6260
|
+
var rules2 = this._currentRules();
|
|
6261
|
+
for (var i = 0; i < rules2.length; i++) {
|
|
6262
|
+
tempMatch = this._input.match(this.rules[rules2[i]]);
|
|
6263
6263
|
if (tempMatch && (!match7 || tempMatch[0].length > match7[0].length)) {
|
|
6264
6264
|
match7 = tempMatch;
|
|
6265
6265
|
index = i;
|
|
@@ -6285,7 +6285,7 @@ var require_parser2 = __commonJS({
|
|
|
6285
6285
|
this._more = false;
|
|
6286
6286
|
this._input = this._input.slice(match7[0].length);
|
|
6287
6287
|
this.matched += match7[0];
|
|
6288
|
-
token = this.performAction.call(this, this.yy, this,
|
|
6288
|
+
token = this.performAction.call(this, this.yy, this, rules2[index], this.conditionStack[this.conditionStack.length - 1]);
|
|
6289
6289
|
if (this.done && this._input) this.done = false;
|
|
6290
6290
|
if (token) return token;
|
|
6291
6291
|
else return;
|
|
@@ -16041,10 +16041,10 @@ var require_nunjucks = __commonJS({
|
|
|
16041
16041
|
return typeof value === "function";
|
|
16042
16042
|
}
|
|
16043
16043
|
exports2.callable = callable;
|
|
16044
|
-
function
|
|
16044
|
+
function defined2(value) {
|
|
16045
16045
|
return value !== void 0;
|
|
16046
16046
|
}
|
|
16047
|
-
exports2.defined =
|
|
16047
|
+
exports2.defined = defined2;
|
|
16048
16048
|
function divisibleby(one, two) {
|
|
16049
16049
|
return one % two === 0;
|
|
16050
16050
|
}
|
|
@@ -70747,6 +70747,14 @@ function epcPayload(input) {
|
|
|
70747
70747
|
""
|
|
70748
70748
|
].join("\n");
|
|
70749
70749
|
}
|
|
70750
|
+
var normaliseIban = (iban2) => iban2.replace(/\s/g, "").toUpperCase();
|
|
70751
|
+
function ibanChecksum(iban2) {
|
|
70752
|
+
const rearranged = normaliseIban(iban2).slice(4) + normaliseIban(iban2).slice(0, 4);
|
|
70753
|
+
const digits = [...rearranged].map((c) => c >= "A" && c <= "Z" ? String(c.charCodeAt(0) - 55) : c).join("");
|
|
70754
|
+
let remainder = 0;
|
|
70755
|
+
for (const digit of digits) remainder = (remainder * 10 + Number(digit)) % 97;
|
|
70756
|
+
return remainder === 1;
|
|
70757
|
+
}
|
|
70750
70758
|
|
|
70751
70759
|
// packages/engine/src/lib/codes/qr.ts
|
|
70752
70760
|
var import_qrcode = __toESM(require_lib());
|
|
@@ -81793,6 +81801,49 @@ var paymentMeansCodes = list2(
|
|
|
81793
81801
|
"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 74 75 76 77 78 91 92 93 94 95 96 97 98 ZZZ"
|
|
81794
81802
|
);
|
|
81795
81803
|
|
|
81804
|
+
// packages/engine/src/lib/einvoice/decimal.ts
|
|
81805
|
+
function decimalText(value) {
|
|
81806
|
+
if (typeof value === "string") {
|
|
81807
|
+
const text5 = value.trim();
|
|
81808
|
+
return /^-?\d+(\.\d+)?$/.test(text5) ? trimZeros(text5) : null;
|
|
81809
|
+
}
|
|
81810
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
81811
|
+
const text4 = value.toPrecision(15);
|
|
81812
|
+
return /^-?\d+(\.\d+)?$/.test(text4) ? trimZeros(text4) : null;
|
|
81813
|
+
}
|
|
81814
|
+
function trimZeros(text4) {
|
|
81815
|
+
const trimmed = text4.includes(".") ? text4.replace(/0+$/, "").replace(/\.$/, "") : text4;
|
|
81816
|
+
return trimmed === "-0" ? "0" : trimmed;
|
|
81817
|
+
}
|
|
81818
|
+
function decimalPlaces(text4) {
|
|
81819
|
+
const dot = text4.indexOf(".");
|
|
81820
|
+
return dot === -1 ? 0 : text4.length - dot - 1;
|
|
81821
|
+
}
|
|
81822
|
+
function toUnits(text4, scale) {
|
|
81823
|
+
const negative = text4.startsWith("-");
|
|
81824
|
+
const [whole = "0", fraction = ""] = (negative ? text4.slice(1) : text4).split(
|
|
81825
|
+
"."
|
|
81826
|
+
);
|
|
81827
|
+
const units = BigInt(whole + fraction.padEnd(scale, "0").slice(0, scale));
|
|
81828
|
+
return negative ? -units : units;
|
|
81829
|
+
}
|
|
81830
|
+
function fromUnits(units, scale, places = scale) {
|
|
81831
|
+
const negative = units < 0n;
|
|
81832
|
+
const digits = (negative ? -units : units).toString().padStart(scale + 1, "0");
|
|
81833
|
+
const whole = digits.slice(0, digits.length - scale);
|
|
81834
|
+
const fraction = digits.slice(digits.length - scale).padEnd(places, "0");
|
|
81835
|
+
const text4 = places > 0 ? `${whole}.${fraction.slice(0, places)}` : whole;
|
|
81836
|
+
return negative && /[1-9]/.test(text4) ? `-${text4}` : text4;
|
|
81837
|
+
}
|
|
81838
|
+
function divideRounded(value, divisor) {
|
|
81839
|
+
const negative = value < 0n !== divisor < 0n;
|
|
81840
|
+
const a = value < 0n ? -value : value;
|
|
81841
|
+
const b2 = divisor < 0n ? -divisor : divisor;
|
|
81842
|
+
const quotient = (a * 2n + b2) / (b2 * 2n);
|
|
81843
|
+
return negative ? -quotient : quotient;
|
|
81844
|
+
}
|
|
81845
|
+
var abs = (value) => value < 0n ? -value : value;
|
|
81846
|
+
|
|
81796
81847
|
// packages/engine/src/lib/einvoice/schema.ts
|
|
81797
81848
|
import { z as z2 } from "zod";
|
|
81798
81849
|
var numeric = z2.union([z2.number(), z2.string()]);
|
|
@@ -81965,6 +82016,1093 @@ var einvoiceOptionsSchema = z2.object({
|
|
|
81965
82016
|
display_check: z2.enum(["warn", "strict"]).optional()
|
|
81966
82017
|
}).strict();
|
|
81967
82018
|
|
|
82019
|
+
// packages/engine/src/lib/einvoice/check.ts
|
|
82020
|
+
var REQUIRED = {
|
|
82021
|
+
number: ["BR-02", "BT-1"],
|
|
82022
|
+
issue_date: ["BR-03", "BT-2"],
|
|
82023
|
+
currency: ["BR-05", "BT-5"],
|
|
82024
|
+
seller: ["BR-06", "BG-4"],
|
|
82025
|
+
"seller.name": ["BR-06", "BT-27"],
|
|
82026
|
+
"seller.address": ["BR-08", "BG-5"],
|
|
82027
|
+
"seller.address.country": ["BR-09", "BT-40"],
|
|
82028
|
+
buyer: ["BR-07", "BG-7"],
|
|
82029
|
+
"buyer.name": ["BR-07", "BT-44"],
|
|
82030
|
+
"buyer.address": ["BR-10", "BG-8"],
|
|
82031
|
+
"buyer.address.country": ["BR-11", "BT-55"],
|
|
82032
|
+
lines: ["BR-16", "BG-25"],
|
|
82033
|
+
"lines[].id": ["BR-21", "BT-126"],
|
|
82034
|
+
"lines[].quantity": ["BR-22", "BT-129"],
|
|
82035
|
+
"lines[].net_amount": ["BR-24", "BT-131"],
|
|
82036
|
+
"lines[].name": ["BR-25", "BT-153"],
|
|
82037
|
+
"lines[].net_price": ["BR-26", "BT-146"],
|
|
82038
|
+
"lines[].tax": ["BR-CO-04", "BT-151"],
|
|
82039
|
+
"lines[].tax.category": ["BR-CO-04", "BT-151"],
|
|
82040
|
+
tax: ["BR-CO-18", "BG-23"],
|
|
82041
|
+
"tax.breakdown": ["BR-CO-18", "BG-23"],
|
|
82042
|
+
"tax.breakdown[].basis": ["BR-45", "BT-116"],
|
|
82043
|
+
"tax.breakdown[].amount": ["BR-46", "BT-117"],
|
|
82044
|
+
"tax.breakdown[].category": ["BR-47", "BT-118"],
|
|
82045
|
+
totals: ["BR-12", "BG-22"],
|
|
82046
|
+
"totals.line_net": ["BR-12", "BT-106"],
|
|
82047
|
+
"totals.tax_basis": ["BR-13", "BT-109"],
|
|
82048
|
+
"totals.tax_total": ["BR-CO-14", "BT-110"],
|
|
82049
|
+
"totals.grand": ["BR-14", "BT-112"],
|
|
82050
|
+
"totals.due": ["BR-15", "BT-115"],
|
|
82051
|
+
"allowances[].amount": ["BR-31", "BT-92"],
|
|
82052
|
+
"allowances[].tax": ["BR-32", "BT-95"],
|
|
82053
|
+
"allowances[].tax.category": ["BR-32", "BT-95"],
|
|
82054
|
+
"charges[].amount": ["BR-36", "BT-99"],
|
|
82055
|
+
"charges[].tax": ["BR-37", "BT-102"],
|
|
82056
|
+
"charges[].tax.category": ["BR-37", "BT-102"],
|
|
82057
|
+
"lines[].allowances[].amount": ["BR-41", "BT-136"],
|
|
82058
|
+
"lines[].charges[].amount": ["BR-43", "BT-141"],
|
|
82059
|
+
"delivery.address.country": ["BR-57", "BT-80"],
|
|
82060
|
+
"preceding_invoices[].number": ["BR-55", "BT-25"]
|
|
82061
|
+
};
|
|
82062
|
+
var RULE_PREFIX = {
|
|
82063
|
+
S: "BR-S",
|
|
82064
|
+
Z: "BR-Z",
|
|
82065
|
+
E: "BR-E",
|
|
82066
|
+
AE: "BR-AE",
|
|
82067
|
+
K: "BR-IC",
|
|
82068
|
+
G: "BR-G",
|
|
82069
|
+
O: "BR-O"
|
|
82070
|
+
};
|
|
82071
|
+
var UNIT_SUGGESTIONS = {
|
|
82072
|
+
piece: "C62",
|
|
82073
|
+
pieces: "C62",
|
|
82074
|
+
pcs: "C62",
|
|
82075
|
+
pc: "C62",
|
|
82076
|
+
stk: "C62",
|
|
82077
|
+
st\u00FCck: "C62",
|
|
82078
|
+
unit: "C62",
|
|
82079
|
+
units: "C62",
|
|
82080
|
+
each: "C62",
|
|
82081
|
+
hour: "HUR",
|
|
82082
|
+
hours: "HUR",
|
|
82083
|
+
h: "HUR",
|
|
82084
|
+
std: "HUR",
|
|
82085
|
+
stunde: "HUR",
|
|
82086
|
+
stunden: "HUR",
|
|
82087
|
+
day: "DAY",
|
|
82088
|
+
days: "DAY",
|
|
82089
|
+
tag: "DAY",
|
|
82090
|
+
tage: "DAY",
|
|
82091
|
+
month: "MON",
|
|
82092
|
+
months: "MON",
|
|
82093
|
+
monat: "MON",
|
|
82094
|
+
year: "ANN",
|
|
82095
|
+
min: "MIN",
|
|
82096
|
+
minute: "MIN",
|
|
82097
|
+
minutes: "MIN",
|
|
82098
|
+
kg: "KGM",
|
|
82099
|
+
g: "GRM",
|
|
82100
|
+
t: "TNE",
|
|
82101
|
+
l: "LTR",
|
|
82102
|
+
litre: "LTR",
|
|
82103
|
+
liter: "LTR",
|
|
82104
|
+
m: "MTR",
|
|
82105
|
+
km: "KMT",
|
|
82106
|
+
m2: "MTK",
|
|
82107
|
+
qm: "MTK",
|
|
82108
|
+
m3: "MTQ",
|
|
82109
|
+
kwh: "KWH",
|
|
82110
|
+
flat: "LS",
|
|
82111
|
+
pauschal: "LS"
|
|
82112
|
+
};
|
|
82113
|
+
var FRENCH_FRAMEWORKS = /* @__PURE__ */ new Set([
|
|
82114
|
+
"B1",
|
|
82115
|
+
"S1",
|
|
82116
|
+
"M1",
|
|
82117
|
+
"B2",
|
|
82118
|
+
"S2",
|
|
82119
|
+
"M2",
|
|
82120
|
+
"S3",
|
|
82121
|
+
"B4",
|
|
82122
|
+
"S4",
|
|
82123
|
+
"M4",
|
|
82124
|
+
"S5",
|
|
82125
|
+
"S6",
|
|
82126
|
+
"B7",
|
|
82127
|
+
"S7",
|
|
82128
|
+
"B8",
|
|
82129
|
+
"S8",
|
|
82130
|
+
"M8"
|
|
82131
|
+
]);
|
|
82132
|
+
var FRENCH_OPERATION = { goods: "B1", services: "S1", mixed: "M1" };
|
|
82133
|
+
var FRENCH_NOTES = [
|
|
82134
|
+
["PMT", "the fixed compensation of \u20AC40 for recovery costs"],
|
|
82135
|
+
["PMD", "the penalties for late payment"],
|
|
82136
|
+
["AAB", "the discount for early payment, or that none is given"]
|
|
82137
|
+
];
|
|
82138
|
+
var CREDIT_TRANSFER = /* @__PURE__ */ new Set(["30", "58"]);
|
|
82139
|
+
function checkInvoice(data, options = {}) {
|
|
82140
|
+
const problems = [];
|
|
82141
|
+
const add2 = (code, path, message2, extra = {}) => {
|
|
82142
|
+
problems.push({
|
|
82143
|
+
code,
|
|
82144
|
+
path,
|
|
82145
|
+
...extra.rule ? { rule: extra.rule } : {},
|
|
82146
|
+
message: extra.rule ? `${message2} (${extra.rule})` : message2,
|
|
82147
|
+
args: {
|
|
82148
|
+
path,
|
|
82149
|
+
...extra.rule ? { rule: extra.rule } : {},
|
|
82150
|
+
...extra.args
|
|
82151
|
+
}
|
|
82152
|
+
});
|
|
82153
|
+
};
|
|
82154
|
+
const parsed = invoiceSchema.safeParse(data);
|
|
82155
|
+
if (!parsed.success) {
|
|
82156
|
+
for (const issue of parsed.error.issues) {
|
|
82157
|
+
const path = pathText(issue.path);
|
|
82158
|
+
const required = REQUIRED[path.replace(/\[\d+\]/g, "[]")];
|
|
82159
|
+
const absent = valueAt(data, issue.path) === void 0;
|
|
82160
|
+
if (absent && required)
|
|
82161
|
+
add2("missing", path, `_invoice.${path} is missing (${required[1]})`, {
|
|
82162
|
+
rule: required[0],
|
|
82163
|
+
args: { term: required[1] }
|
|
82164
|
+
});
|
|
82165
|
+
else if (absent) add2("missing", path, `_invoice.${path} is missing`);
|
|
82166
|
+
else if (issue.code === "too_small" && path === "lines")
|
|
82167
|
+
add2(
|
|
82168
|
+
"missing",
|
|
82169
|
+
path,
|
|
82170
|
+
"_invoice.lines is empty: an invoice has at least one line (BG-25)",
|
|
82171
|
+
{
|
|
82172
|
+
rule: "BR-16",
|
|
82173
|
+
args: { term: "BG-25" }
|
|
82174
|
+
}
|
|
82175
|
+
);
|
|
82176
|
+
else if (issue.code === "too_small" && path === "tax.breakdown")
|
|
82177
|
+
add2(
|
|
82178
|
+
"missing",
|
|
82179
|
+
path,
|
|
82180
|
+
"_invoice.tax.breakdown is empty: an invoice has at least one VAT breakdown (BG-23)",
|
|
82181
|
+
{
|
|
82182
|
+
rule: "BR-CO-18",
|
|
82183
|
+
args: { term: "BG-23" }
|
|
82184
|
+
}
|
|
82185
|
+
);
|
|
82186
|
+
else
|
|
82187
|
+
add2("invalid", path, `_invoice.${path}: ${issue.message}`, {
|
|
82188
|
+
args: { detail: issue.message }
|
|
82189
|
+
});
|
|
82190
|
+
}
|
|
82191
|
+
return { ok: false, invoice: null, problems };
|
|
82192
|
+
}
|
|
82193
|
+
const invoice = normalise(parsed.data, add2, options);
|
|
82194
|
+
if (problems.length > 0) return { ok: false, invoice: null, problems };
|
|
82195
|
+
rules(invoice, add2, options);
|
|
82196
|
+
return problems.length > 0 ? { ok: false, invoice: null, problems } : { ok: true, invoice, problems: [] };
|
|
82197
|
+
}
|
|
82198
|
+
function pathText(path) {
|
|
82199
|
+
let out = "";
|
|
82200
|
+
for (const key of path)
|
|
82201
|
+
out += typeof key === "number" ? `[${key}]` : out ? `.${String(key)}` : String(key);
|
|
82202
|
+
return out;
|
|
82203
|
+
}
|
|
82204
|
+
function valueAt(data, path) {
|
|
82205
|
+
let value = data;
|
|
82206
|
+
for (const key of path) {
|
|
82207
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
82208
|
+
value = value[key];
|
|
82209
|
+
}
|
|
82210
|
+
return value === null ? void 0 : value;
|
|
82211
|
+
}
|
|
82212
|
+
function normalise(input, add2, options) {
|
|
82213
|
+
const amount = (value, path) => {
|
|
82214
|
+
const text4 = decimalText(value);
|
|
82215
|
+
if (text4 === null) {
|
|
82216
|
+
add2("not_a_number", path, `_invoice.${path} is not a number`);
|
|
82217
|
+
return "0";
|
|
82218
|
+
}
|
|
82219
|
+
if (decimalPlaces(text4) > 2) {
|
|
82220
|
+
add2(
|
|
82221
|
+
"decimals",
|
|
82222
|
+
path,
|
|
82223
|
+
`_invoice.${path} has more than 2 decimals; an amount of the invoice is exact to the cent`,
|
|
82224
|
+
{
|
|
82225
|
+
args: { max: 2, value: text4 }
|
|
82226
|
+
}
|
|
82227
|
+
);
|
|
82228
|
+
return "0";
|
|
82229
|
+
}
|
|
82230
|
+
return text4;
|
|
82231
|
+
};
|
|
82232
|
+
const number = (value, path, max) => {
|
|
82233
|
+
const text4 = decimalText(value);
|
|
82234
|
+
if (text4 === null) {
|
|
82235
|
+
add2("not_a_number", path, `_invoice.${path} is not a number`);
|
|
82236
|
+
return "0";
|
|
82237
|
+
}
|
|
82238
|
+
if (decimalPlaces(text4) > max) {
|
|
82239
|
+
add2("decimals", path, `_invoice.${path} has more than ${max} decimals`, {
|
|
82240
|
+
args: { max, value: text4 }
|
|
82241
|
+
});
|
|
82242
|
+
return "0";
|
|
82243
|
+
}
|
|
82244
|
+
return text4;
|
|
82245
|
+
};
|
|
82246
|
+
const optionalAmount = (value, path) => value === void 0 ? void 0 : amount(value, path);
|
|
82247
|
+
const date = (value, path) => {
|
|
82248
|
+
if (value === void 0) return void 0;
|
|
82249
|
+
const [year = 0, month = 0, day = 0] = value.split("-").map(Number);
|
|
82250
|
+
const real = new Date(Date.UTC(year, month - 1, day));
|
|
82251
|
+
if (real.getUTCFullYear() !== year || real.getUTCMonth() !== month - 1 || real.getUTCDate() !== day) {
|
|
82252
|
+
add2("date", path, `_invoice.${path} is not a date of the calendar`, {
|
|
82253
|
+
args: { value }
|
|
82254
|
+
});
|
|
82255
|
+
}
|
|
82256
|
+
return value;
|
|
82257
|
+
};
|
|
82258
|
+
const code = (value, path, list3, name, rule2) => {
|
|
82259
|
+
const upper = value.trim().toUpperCase();
|
|
82260
|
+
if (!list3.has(upper))
|
|
82261
|
+
add2(
|
|
82262
|
+
"code_list",
|
|
82263
|
+
path,
|
|
82264
|
+
`_invoice.${path}: "${value}" is not a code of ${name}`,
|
|
82265
|
+
{
|
|
82266
|
+
rule: rule2,
|
|
82267
|
+
args: { value, list: name }
|
|
82268
|
+
}
|
|
82269
|
+
);
|
|
82270
|
+
return upper;
|
|
82271
|
+
};
|
|
82272
|
+
const period2 = (value, path, rule2) => {
|
|
82273
|
+
if (!value || value.start === void 0 && value.end === void 0)
|
|
82274
|
+
return void 0;
|
|
82275
|
+
const start = date(value.start, `${path}.start`);
|
|
82276
|
+
const end = date(value.end, `${path}.end`);
|
|
82277
|
+
if (start && end && end < start)
|
|
82278
|
+
add2("date_order", path, `_invoice.${path} ends before it starts`, {
|
|
82279
|
+
rule: rule2
|
|
82280
|
+
});
|
|
82281
|
+
return { ...start ? { start } : {}, ...end ? { end } : {} };
|
|
82282
|
+
};
|
|
82283
|
+
const identifier3 = (value) => {
|
|
82284
|
+
if (value === void 0) return void 0;
|
|
82285
|
+
if (typeof value === "string") return { value };
|
|
82286
|
+
return {
|
|
82287
|
+
value: value.value,
|
|
82288
|
+
...value.scheme ? { scheme: value.scheme } : {}
|
|
82289
|
+
};
|
|
82290
|
+
};
|
|
82291
|
+
const address2 = (value, path) => ({
|
|
82292
|
+
...defined({
|
|
82293
|
+
street: value.street,
|
|
82294
|
+
street2: value.street2,
|
|
82295
|
+
street3: value.street3,
|
|
82296
|
+
postcode: value.postcode,
|
|
82297
|
+
city: value.city,
|
|
82298
|
+
subdivision: value.subdivision
|
|
82299
|
+
}),
|
|
82300
|
+
country: code(
|
|
82301
|
+
value.country,
|
|
82302
|
+
`${path}.country`,
|
|
82303
|
+
countryCodes,
|
|
82304
|
+
"ISO 3166-1 (two letters, as in DE)",
|
|
82305
|
+
"BR-CL-14"
|
|
82306
|
+
)
|
|
82307
|
+
});
|
|
82308
|
+
const vatId = (value, path) => {
|
|
82309
|
+
if (value === void 0) return void 0;
|
|
82310
|
+
const id = value.replace(/\s/g, "").toUpperCase();
|
|
82311
|
+
const prefix = id.slice(0, 2);
|
|
82312
|
+
if (!(countryCodes.has(prefix) || prefix === "EL") || !/^[A-Z]{2}/.test(id))
|
|
82313
|
+
add2(
|
|
82314
|
+
"vat_id_prefix",
|
|
82315
|
+
path,
|
|
82316
|
+
`_invoice.${path} must start with the country code of the VAT ID, as in DE123456789`,
|
|
82317
|
+
{
|
|
82318
|
+
rule: "BR-CO-09",
|
|
82319
|
+
args: { value }
|
|
82320
|
+
}
|
|
82321
|
+
);
|
|
82322
|
+
return id;
|
|
82323
|
+
};
|
|
82324
|
+
const electronicAddress = (value, path, rule2) => {
|
|
82325
|
+
const id = identifier3(value);
|
|
82326
|
+
if (!id) return void 0;
|
|
82327
|
+
const scheme = id.scheme?.trim().toUpperCase() ?? (/^[^@\s]+@[^@\s]+$/.test(id.value) ? "EM" : void 0);
|
|
82328
|
+
if (!scheme) {
|
|
82329
|
+
add2(
|
|
82330
|
+
"scheme_required",
|
|
82331
|
+
path,
|
|
82332
|
+
`_invoice.${path} needs its scheme: write { "value": \u2026, "scheme": \u2026 } with a code of the EAS list, or an e-mail address`,
|
|
82333
|
+
{ rule: rule2 }
|
|
82334
|
+
);
|
|
82335
|
+
return void 0;
|
|
82336
|
+
}
|
|
82337
|
+
if (!electronicAddressSchemes.has(scheme))
|
|
82338
|
+
add2(
|
|
82339
|
+
"code_list",
|
|
82340
|
+
`${path}.scheme`,
|
|
82341
|
+
`_invoice.${path}.scheme: "${scheme}" is not a code of the EAS list (EM is an e-mail address)`,
|
|
82342
|
+
{
|
|
82343
|
+
rule: "BR-CL-25",
|
|
82344
|
+
args: { value: scheme, list: "the EAS list" }
|
|
82345
|
+
}
|
|
82346
|
+
);
|
|
82347
|
+
return { value: id.value, scheme };
|
|
82348
|
+
};
|
|
82349
|
+
const party2 = (value, path, addressRule) => {
|
|
82350
|
+
const id = identifier3(value.id);
|
|
82351
|
+
const registration = identifier3(value.legal_registration);
|
|
82352
|
+
const electronic = electronicAddress(
|
|
82353
|
+
value.electronic_address,
|
|
82354
|
+
`${path}.electronic_address`,
|
|
82355
|
+
addressRule
|
|
82356
|
+
);
|
|
82357
|
+
const vat = vatId(value.vat_id, `${path}.vat_id`);
|
|
82358
|
+
return {
|
|
82359
|
+
name: value.name,
|
|
82360
|
+
...defined({ trading_name: value.trading_name, vat_id: vat }),
|
|
82361
|
+
...id ? { id } : {},
|
|
82362
|
+
...registration ? { legal_registration: registration } : {},
|
|
82363
|
+
address: address2(value.address, `${path}.address`),
|
|
82364
|
+
...value.contact && Object.values(value.contact).some((v2) => v2 !== void 0) ? { contact: defined(value.contact) } : {},
|
|
82365
|
+
...electronic ? { electronic_address: electronic } : {}
|
|
82366
|
+
};
|
|
82367
|
+
};
|
|
82368
|
+
const tax = (value, path) => ({
|
|
82369
|
+
category: value.category,
|
|
82370
|
+
...value.rate === void 0 || value.rate === null ? {} : { rate: number(value.rate, `${path}.rate`, 4) }
|
|
82371
|
+
});
|
|
82372
|
+
const allowance = (value, path) => ({
|
|
82373
|
+
amount: amount(value.amount, `${path}.amount`),
|
|
82374
|
+
...defined({
|
|
82375
|
+
reason: value.reason,
|
|
82376
|
+
reason_code: value.reason_code?.trim().toUpperCase(),
|
|
82377
|
+
base_amount: optionalAmount(value.base_amount, `${path}.base_amount`),
|
|
82378
|
+
percent: value.percent === void 0 ? void 0 : number(value.percent, `${path}.percent`, 4)
|
|
82379
|
+
})
|
|
82380
|
+
});
|
|
82381
|
+
const seller = party2(input.seller, "seller", "BR-62");
|
|
82382
|
+
const buyer = party2(input.buyer, "buyer", "BR-63");
|
|
82383
|
+
if (input.buyer.siren !== void 0) {
|
|
82384
|
+
const siren = input.buyer.siren.replace(/\s/g, "");
|
|
82385
|
+
if (buyer.legal_registration && buyer.legal_registration.value.replace(/\s/g, "") !== siren)
|
|
82386
|
+
add2(
|
|
82387
|
+
"siren_mismatch",
|
|
82388
|
+
"buyer.siren",
|
|
82389
|
+
"_invoice.buyer.siren and _invoice.buyer.legal_registration name different registrations; state one of them"
|
|
82390
|
+
);
|
|
82391
|
+
else
|
|
82392
|
+
buyer.legal_registration = {
|
|
82393
|
+
value: siren,
|
|
82394
|
+
scheme: buyer.legal_registration?.scheme ?? "0002"
|
|
82395
|
+
};
|
|
82396
|
+
}
|
|
82397
|
+
const notes = [
|
|
82398
|
+
...input.note ? [{ text: input.note }] : [],
|
|
82399
|
+
...(input.notes ?? []).map((note2) => ({
|
|
82400
|
+
text: note2.text,
|
|
82401
|
+
...note2.subject_code ? { subject_code: note2.subject_code.trim().toUpperCase() } : {}
|
|
82402
|
+
}))
|
|
82403
|
+
];
|
|
82404
|
+
const delivery = input.delivery ? defined({
|
|
82405
|
+
name: input.delivery.name,
|
|
82406
|
+
date: date(input.delivery.date, "delivery.date"),
|
|
82407
|
+
address: input.delivery.address ? address2(input.delivery.address, "delivery.address") : void 0
|
|
82408
|
+
}) : void 0;
|
|
82409
|
+
let businessProcess;
|
|
82410
|
+
if (input.operation_code !== void 0)
|
|
82411
|
+
businessProcess = input.operation_code.trim().toUpperCase();
|
|
82412
|
+
else if (input.operation !== void 0)
|
|
82413
|
+
businessProcess = FRENCH_OPERATION[input.operation];
|
|
82414
|
+
const payment2 = input.payment ? defined({
|
|
82415
|
+
means_code: input.payment.means_code === void 0 ? void 0 : code(
|
|
82416
|
+
String(input.payment.means_code),
|
|
82417
|
+
"payment.means_code",
|
|
82418
|
+
paymentMeansCodes,
|
|
82419
|
+
"UNTDID 4461 (58 is a SEPA credit transfer)",
|
|
82420
|
+
"BR-CL-16"
|
|
82421
|
+
),
|
|
82422
|
+
iban: iban(input.payment.iban, "payment.iban", add2),
|
|
82423
|
+
bic: input.payment.bic?.replace(/\s/g, "").toUpperCase(),
|
|
82424
|
+
account_name: input.payment.account_name,
|
|
82425
|
+
reference: input.payment.reference,
|
|
82426
|
+
terms: input.payment.terms,
|
|
82427
|
+
due_date: date(input.payment.due_date, "payment.due_date"),
|
|
82428
|
+
mandate_id: input.payment.mandate_id,
|
|
82429
|
+
creditor_id: input.payment.creditor_id,
|
|
82430
|
+
debited_iban: iban(
|
|
82431
|
+
input.payment.debited_iban,
|
|
82432
|
+
"payment.debited_iban",
|
|
82433
|
+
add2
|
|
82434
|
+
)
|
|
82435
|
+
}) : void 0;
|
|
82436
|
+
const rounding = optionalAmount(input.totals.rounding, "totals.rounding");
|
|
82437
|
+
if (options.profile === "basic" && rounding !== void 0 && toUnits(rounding, 2) !== 0n)
|
|
82438
|
+
add2(
|
|
82439
|
+
"profile",
|
|
82440
|
+
"totals.rounding",
|
|
82441
|
+
"_invoice.totals.rounding cannot be carried by the profile basic, which has no rounding amount (BT-114); use en16931",
|
|
82442
|
+
{
|
|
82443
|
+
args: { profile: "basic", term: "BT-114" }
|
|
82444
|
+
}
|
|
82445
|
+
);
|
|
82446
|
+
return {
|
|
82447
|
+
number: input.number,
|
|
82448
|
+
issue_date: date(input.issue_date, "issue_date") ?? input.issue_date,
|
|
82449
|
+
type_code: code(
|
|
82450
|
+
String(input.type_code),
|
|
82451
|
+
"type_code",
|
|
82452
|
+
documentTypeCodes,
|
|
82453
|
+
"UNTDID 1001 (380 is an invoice, 381 a credit note)",
|
|
82454
|
+
"BR-CL-01"
|
|
82455
|
+
),
|
|
82456
|
+
currency: code(
|
|
82457
|
+
input.currency,
|
|
82458
|
+
"currency",
|
|
82459
|
+
currencyCodes,
|
|
82460
|
+
"ISO 4217 (three letters, as in EUR)",
|
|
82461
|
+
"BR-CL-04"
|
|
82462
|
+
),
|
|
82463
|
+
...defined({
|
|
82464
|
+
accounting_currency: input.accounting_currency === void 0 ? void 0 : code(
|
|
82465
|
+
input.accounting_currency,
|
|
82466
|
+
"accounting_currency",
|
|
82467
|
+
currencyCodes,
|
|
82468
|
+
"ISO 4217 (three letters, as in EUR)",
|
|
82469
|
+
"BR-CL-05"
|
|
82470
|
+
),
|
|
82471
|
+
buyer_reference: input.buyer_reference,
|
|
82472
|
+
order_reference: input.order_reference,
|
|
82473
|
+
contract_reference: input.contract_reference,
|
|
82474
|
+
period: period2(input.period, "period", "BR-29"),
|
|
82475
|
+
business_process: businessProcess
|
|
82476
|
+
}),
|
|
82477
|
+
notes,
|
|
82478
|
+
preceding_invoices: (input.preceding_invoices ?? []).map(
|
|
82479
|
+
(reference, i) => ({
|
|
82480
|
+
number: reference.number,
|
|
82481
|
+
...defined({
|
|
82482
|
+
issue_date: date(
|
|
82483
|
+
reference.issue_date,
|
|
82484
|
+
`preceding_invoices[${i}].issue_date`
|
|
82485
|
+
)
|
|
82486
|
+
})
|
|
82487
|
+
})
|
|
82488
|
+
),
|
|
82489
|
+
seller: {
|
|
82490
|
+
...seller,
|
|
82491
|
+
...defined({
|
|
82492
|
+
tax_number: input.seller.tax_number,
|
|
82493
|
+
legal_info: input.seller.legal_info,
|
|
82494
|
+
vat_on_debits: input.seller.vat_on_debits
|
|
82495
|
+
})
|
|
82496
|
+
},
|
|
82497
|
+
buyer,
|
|
82498
|
+
...delivery && Object.keys(delivery).length > 0 ? { delivery } : {},
|
|
82499
|
+
lines: input.lines.map((line2, i) => {
|
|
82500
|
+
const path = `lines[${i}]`;
|
|
82501
|
+
const unit = line2.unit_code.trim();
|
|
82502
|
+
const known = unitCodes.has(unit.toUpperCase()) || unitCodes.has(unit);
|
|
82503
|
+
if (!known) {
|
|
82504
|
+
const suggestion = UNIT_SUGGESTIONS[unit.toLowerCase()];
|
|
82505
|
+
add2(
|
|
82506
|
+
"code_list",
|
|
82507
|
+
`${path}.unit_code`,
|
|
82508
|
+
`_invoice.${path}.unit_code: "${unit}" is not a code of UN/ECE Recommendation 20${suggestion ? `; ${suggestion} is the code for it` : " (C62 is a piece, HUR an hour, DAY a day)"}`,
|
|
82509
|
+
{
|
|
82510
|
+
rule: "BR-CL-23",
|
|
82511
|
+
args: {
|
|
82512
|
+
value: unit,
|
|
82513
|
+
list: "UN/ECE Recommendation 20",
|
|
82514
|
+
...suggestion ? { suggestion } : {}
|
|
82515
|
+
}
|
|
82516
|
+
}
|
|
82517
|
+
);
|
|
82518
|
+
}
|
|
82519
|
+
const price = number(line2.net_price, `${path}.net_price`, 6);
|
|
82520
|
+
if (price.startsWith("-"))
|
|
82521
|
+
add2(
|
|
82522
|
+
"negative",
|
|
82523
|
+
`${path}.net_price`,
|
|
82524
|
+
`_invoice.${path}.net_price must not be negative; a credit is a negative quantity`,
|
|
82525
|
+
{ rule: "BR-27" }
|
|
82526
|
+
);
|
|
82527
|
+
const standard = identifier3(line2.standard_item_id);
|
|
82528
|
+
if (standard && !standard.scheme)
|
|
82529
|
+
add2(
|
|
82530
|
+
"scheme_required",
|
|
82531
|
+
`${path}.standard_item_id`,
|
|
82532
|
+
`_invoice.${path}.standard_item_id needs its scheme: write { "value": \u2026, "scheme": \u2026 }, 0160 for a GTIN`,
|
|
82533
|
+
{ rule: "BR-64" }
|
|
82534
|
+
);
|
|
82535
|
+
return {
|
|
82536
|
+
id: String(line2.id),
|
|
82537
|
+
name: line2.name,
|
|
82538
|
+
quantity: number(line2.quantity, `${path}.quantity`, 6),
|
|
82539
|
+
unit_code: unitCodes.has(unit) ? unit : unit.toUpperCase(),
|
|
82540
|
+
net_price: price,
|
|
82541
|
+
net_amount: amount(line2.net_amount, `${path}.net_amount`),
|
|
82542
|
+
tax: tax(line2.tax, `${path}.tax`),
|
|
82543
|
+
allowances: (line2.allowances ?? []).map(
|
|
82544
|
+
(a, j2) => allowance(a, `${path}.allowances[${j2}]`)
|
|
82545
|
+
),
|
|
82546
|
+
charges: (line2.charges ?? []).map(
|
|
82547
|
+
(c, j2) => allowance(c, `${path}.charges[${j2}]`)
|
|
82548
|
+
),
|
|
82549
|
+
...defined({
|
|
82550
|
+
description: line2.description,
|
|
82551
|
+
price_base_quantity: line2.price_base_quantity === void 0 ? void 0 : number(
|
|
82552
|
+
line2.price_base_quantity,
|
|
82553
|
+
`${path}.price_base_quantity`,
|
|
82554
|
+
6
|
|
82555
|
+
),
|
|
82556
|
+
period: period2(line2.period, `${path}.period`, "BR-30"),
|
|
82557
|
+
buyer_accounting_reference: line2.buyer_accounting_reference,
|
|
82558
|
+
note: line2.note,
|
|
82559
|
+
seller_item_id: line2.seller_item_id,
|
|
82560
|
+
buyer_item_id: line2.buyer_item_id,
|
|
82561
|
+
standard_item_id: standard,
|
|
82562
|
+
order_line_reference: line2.order_line_reference
|
|
82563
|
+
})
|
|
82564
|
+
};
|
|
82565
|
+
}),
|
|
82566
|
+
allowances: (input.allowances ?? []).map((a, i) => ({
|
|
82567
|
+
...allowance(a, `allowances[${i}]`),
|
|
82568
|
+
tax: tax(a.tax, `allowances[${i}].tax`)
|
|
82569
|
+
})),
|
|
82570
|
+
charges: (input.charges ?? []).map((c, i) => ({
|
|
82571
|
+
...allowance(c, `charges[${i}]`),
|
|
82572
|
+
tax: tax(c.tax, `charges[${i}].tax`)
|
|
82573
|
+
})),
|
|
82574
|
+
breakdown: input.tax.breakdown.map((row, i) => ({
|
|
82575
|
+
...tax(row, `tax.breakdown[${i}]`),
|
|
82576
|
+
basis: amount(row.basis, `tax.breakdown[${i}].basis`),
|
|
82577
|
+
amount: amount(row.amount, `tax.breakdown[${i}].amount`),
|
|
82578
|
+
...defined({
|
|
82579
|
+
exemption_reason: row.exemption_reason,
|
|
82580
|
+
exemption_reason_code: row.exemption_reason_code?.trim()
|
|
82581
|
+
})
|
|
82582
|
+
})),
|
|
82583
|
+
totals: {
|
|
82584
|
+
line_net: amount(input.totals.line_net, "totals.line_net"),
|
|
82585
|
+
tax_basis: amount(input.totals.tax_basis, "totals.tax_basis"),
|
|
82586
|
+
tax_total: amount(input.totals.tax_total, "totals.tax_total"),
|
|
82587
|
+
grand: amount(input.totals.grand, "totals.grand"),
|
|
82588
|
+
due: amount(input.totals.due, "totals.due"),
|
|
82589
|
+
...defined({
|
|
82590
|
+
allowances: optionalAmount(
|
|
82591
|
+
input.totals.allowances,
|
|
82592
|
+
"totals.allowances"
|
|
82593
|
+
),
|
|
82594
|
+
charges: optionalAmount(input.totals.charges, "totals.charges"),
|
|
82595
|
+
prepaid: optionalAmount(input.totals.prepaid, "totals.prepaid"),
|
|
82596
|
+
tax_total_accounting: optionalAmount(
|
|
82597
|
+
input.totals.tax_total_accounting,
|
|
82598
|
+
"totals.tax_total_accounting"
|
|
82599
|
+
),
|
|
82600
|
+
rounding
|
|
82601
|
+
})
|
|
82602
|
+
},
|
|
82603
|
+
...payment2 && Object.keys(payment2).length > 0 ? { payment: payment2 } : {}
|
|
82604
|
+
};
|
|
82605
|
+
}
|
|
82606
|
+
function iban(value, path, add2) {
|
|
82607
|
+
if (value === void 0) return void 0;
|
|
82608
|
+
const normalised = normaliseIban(value);
|
|
82609
|
+
if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/.test(normalised) || !ibanChecksum(normalised))
|
|
82610
|
+
add2(
|
|
82611
|
+
"iban",
|
|
82612
|
+
path,
|
|
82613
|
+
`_invoice.${path} is not a valid IBAN: its check digits do not match`
|
|
82614
|
+
);
|
|
82615
|
+
return normalised;
|
|
82616
|
+
}
|
|
82617
|
+
function defined(value) {
|
|
82618
|
+
return Object.fromEntries(
|
|
82619
|
+
Object.entries(value).filter(([, v2]) => v2 !== void 0)
|
|
82620
|
+
);
|
|
82621
|
+
}
|
|
82622
|
+
var cents = (text4) => text4 === void 0 ? 0n : toUnits(text4, 2);
|
|
82623
|
+
var money = (value) => fromUnits(value, 2);
|
|
82624
|
+
var rateKey = (tax) => `${tax.category}|${tax.rate ?? ""}`;
|
|
82625
|
+
function rules(invoice, add2, options) {
|
|
82626
|
+
const { totals: totals2 } = invoice;
|
|
82627
|
+
const sum = (path, stated, expected, rule2, formula) => {
|
|
82628
|
+
if (stated !== expected)
|
|
82629
|
+
add2(
|
|
82630
|
+
"sum",
|
|
82631
|
+
path,
|
|
82632
|
+
`_invoice.${path} is ${money(stated)}, but ${formula} ${money(expected)}`,
|
|
82633
|
+
{
|
|
82634
|
+
rule: rule2,
|
|
82635
|
+
args: { value: money(stated), expected: money(expected) }
|
|
82636
|
+
}
|
|
82637
|
+
);
|
|
82638
|
+
};
|
|
82639
|
+
const lineNet = invoice.lines.reduce(
|
|
82640
|
+
(total, line2) => total + cents(line2.net_amount),
|
|
82641
|
+
0n
|
|
82642
|
+
);
|
|
82643
|
+
const allowanceSum = invoice.allowances.reduce(
|
|
82644
|
+
(total, a) => total + cents(a.amount),
|
|
82645
|
+
0n
|
|
82646
|
+
);
|
|
82647
|
+
const chargeSum = invoice.charges.reduce(
|
|
82648
|
+
(total, c) => total + cents(c.amount),
|
|
82649
|
+
0n
|
|
82650
|
+
);
|
|
82651
|
+
const taxSum = invoice.breakdown.reduce(
|
|
82652
|
+
(total, row) => total + cents(row.amount),
|
|
82653
|
+
0n
|
|
82654
|
+
);
|
|
82655
|
+
sum(
|
|
82656
|
+
"totals.line_net",
|
|
82657
|
+
cents(totals2.line_net),
|
|
82658
|
+
lineNet,
|
|
82659
|
+
"BR-CO-10",
|
|
82660
|
+
"the net amounts of the lines add up to"
|
|
82661
|
+
);
|
|
82662
|
+
sum(
|
|
82663
|
+
"totals.allowances",
|
|
82664
|
+
cents(totals2.allowances),
|
|
82665
|
+
allowanceSum,
|
|
82666
|
+
"BR-CO-11",
|
|
82667
|
+
"the allowances of the document add up to"
|
|
82668
|
+
);
|
|
82669
|
+
sum(
|
|
82670
|
+
"totals.charges",
|
|
82671
|
+
cents(totals2.charges),
|
|
82672
|
+
chargeSum,
|
|
82673
|
+
"BR-CO-12",
|
|
82674
|
+
"the charges of the document add up to"
|
|
82675
|
+
);
|
|
82676
|
+
sum(
|
|
82677
|
+
"totals.tax_basis",
|
|
82678
|
+
cents(totals2.tax_basis),
|
|
82679
|
+
cents(totals2.line_net) - cents(totals2.allowances) + cents(totals2.charges),
|
|
82680
|
+
"BR-CO-13",
|
|
82681
|
+
"line_net less allowances plus charges is"
|
|
82682
|
+
);
|
|
82683
|
+
sum(
|
|
82684
|
+
"totals.tax_total",
|
|
82685
|
+
cents(totals2.tax_total),
|
|
82686
|
+
taxSum,
|
|
82687
|
+
"BR-CO-14",
|
|
82688
|
+
"the VAT amounts of the breakdown add up to"
|
|
82689
|
+
);
|
|
82690
|
+
sum(
|
|
82691
|
+
"totals.grand",
|
|
82692
|
+
cents(totals2.grand),
|
|
82693
|
+
cents(totals2.tax_basis) + cents(totals2.tax_total),
|
|
82694
|
+
"BR-CO-15",
|
|
82695
|
+
"tax_basis plus tax_total is"
|
|
82696
|
+
);
|
|
82697
|
+
sum(
|
|
82698
|
+
"totals.due",
|
|
82699
|
+
cents(totals2.due),
|
|
82700
|
+
cents(totals2.grand) - cents(totals2.prepaid) + cents(totals2.rounding),
|
|
82701
|
+
"BR-CO-16",
|
|
82702
|
+
"grand less prepaid plus rounding is"
|
|
82703
|
+
);
|
|
82704
|
+
if (invoice.accounting_currency !== void 0 && totals2.tax_total_accounting === void 0)
|
|
82705
|
+
add2(
|
|
82706
|
+
"missing",
|
|
82707
|
+
"totals.tax_total_accounting",
|
|
82708
|
+
`_invoice.totals.tax_total_accounting is missing: with accounting_currency ${invoice.accounting_currency} the invoice states its VAT total in that currency (BT-111)`,
|
|
82709
|
+
{
|
|
82710
|
+
rule: "BR-53",
|
|
82711
|
+
args: { term: "BT-111" }
|
|
82712
|
+
}
|
|
82713
|
+
);
|
|
82714
|
+
if (invoice.accounting_currency === void 0 && totals2.tax_total_accounting !== void 0)
|
|
82715
|
+
add2(
|
|
82716
|
+
"missing",
|
|
82717
|
+
"accounting_currency",
|
|
82718
|
+
"_invoice.accounting_currency is missing: totals.tax_total_accounting needs the currency it is stated in (BT-6)",
|
|
82719
|
+
{
|
|
82720
|
+
rule: "BR-53",
|
|
82721
|
+
args: { term: "BT-6" }
|
|
82722
|
+
}
|
|
82723
|
+
);
|
|
82724
|
+
const used = /* @__PURE__ */ new Map();
|
|
82725
|
+
const use = (tax, value, path) => {
|
|
82726
|
+
const entry = used.get(rateKey(tax)) ?? {
|
|
82727
|
+
tax,
|
|
82728
|
+
basis: 0n,
|
|
82729
|
+
items: 0,
|
|
82730
|
+
first: path
|
|
82731
|
+
};
|
|
82732
|
+
entry.basis += value;
|
|
82733
|
+
entry.items += 1;
|
|
82734
|
+
used.set(rateKey(tax), entry);
|
|
82735
|
+
};
|
|
82736
|
+
invoice.lines.forEach(
|
|
82737
|
+
(line2, i) => use(line2.tax, cents(line2.net_amount), `lines[${i}]`)
|
|
82738
|
+
);
|
|
82739
|
+
invoice.allowances.forEach(
|
|
82740
|
+
(a, i) => use(a.tax, -cents(a.amount), `allowances[${i}]`)
|
|
82741
|
+
);
|
|
82742
|
+
invoice.charges.forEach(
|
|
82743
|
+
(c, i) => use(c.tax, cents(c.amount), `charges[${i}]`)
|
|
82744
|
+
);
|
|
82745
|
+
const seen = /* @__PURE__ */ new Set();
|
|
82746
|
+
invoice.breakdown.forEach((row, i) => {
|
|
82747
|
+
const path = `tax.breakdown[${i}]`;
|
|
82748
|
+
const prefix = RULE_PREFIX[row.category];
|
|
82749
|
+
const shown = row.rate === void 0 ? row.category : `${row.category} at ${row.rate} %`;
|
|
82750
|
+
if (seen.has(rateKey(row)))
|
|
82751
|
+
add2(
|
|
82752
|
+
"breakdown_duplicate",
|
|
82753
|
+
path,
|
|
82754
|
+
`_invoice.tax.breakdown lists category ${shown} twice; one entry per category and rate`,
|
|
82755
|
+
{
|
|
82756
|
+
args: { category: row.category, rate: row.rate ?? "" }
|
|
82757
|
+
}
|
|
82758
|
+
);
|
|
82759
|
+
seen.add(rateKey(row));
|
|
82760
|
+
const entry = used.get(rateKey(row));
|
|
82761
|
+
const expected = entry?.basis ?? 0n;
|
|
82762
|
+
if (cents(row.basis) !== expected)
|
|
82763
|
+
add2(
|
|
82764
|
+
"sum",
|
|
82765
|
+
`${path}.basis`,
|
|
82766
|
+
`_invoice.${path}.basis is ${money(cents(row.basis))}, but the lines, charges and allowances of category ${shown} come to ${money(expected)}`,
|
|
82767
|
+
{
|
|
82768
|
+
rule: `${prefix}-08`,
|
|
82769
|
+
args: {
|
|
82770
|
+
value: money(cents(row.basis)),
|
|
82771
|
+
expected: money(expected),
|
|
82772
|
+
category: row.category,
|
|
82773
|
+
rate: row.rate ?? ""
|
|
82774
|
+
}
|
|
82775
|
+
}
|
|
82776
|
+
);
|
|
82777
|
+
if (row.rate !== void 0) {
|
|
82778
|
+
const computed = divideRounded(
|
|
82779
|
+
cents(row.basis) * toUnits(row.rate, 4),
|
|
82780
|
+
1000000n
|
|
82781
|
+
);
|
|
82782
|
+
const items = BigInt(Math.max(1, entry?.items ?? 1));
|
|
82783
|
+
const tolerance = (items + 1n) / 2n > 100n ? 100n : (items + 1n) / 2n;
|
|
82784
|
+
if (abs(cents(row.amount) - computed) > tolerance)
|
|
82785
|
+
add2(
|
|
82786
|
+
"sum",
|
|
82787
|
+
`${path}.amount`,
|
|
82788
|
+
`_invoice.${path}.amount is ${money(cents(row.amount))}, but ${row.rate} % of ${money(cents(row.basis))} is ${money(computed)}`,
|
|
82789
|
+
{
|
|
82790
|
+
rule: "BR-CO-17",
|
|
82791
|
+
args: {
|
|
82792
|
+
value: money(cents(row.amount)),
|
|
82793
|
+
expected: money(computed),
|
|
82794
|
+
rate: row.rate,
|
|
82795
|
+
basis: money(cents(row.basis))
|
|
82796
|
+
}
|
|
82797
|
+
}
|
|
82798
|
+
);
|
|
82799
|
+
}
|
|
82800
|
+
});
|
|
82801
|
+
for (const [key, entry] of used) {
|
|
82802
|
+
if (seen.has(key)) continue;
|
|
82803
|
+
const shown = entry.tax.rate === void 0 ? entry.tax.category : `${entry.tax.category} at ${entry.tax.rate} %`;
|
|
82804
|
+
add2(
|
|
82805
|
+
"breakdown_missing",
|
|
82806
|
+
"tax.breakdown",
|
|
82807
|
+
`_invoice.tax.breakdown has no entry for category ${shown}, which _invoice.${entry.first} falls under`,
|
|
82808
|
+
{
|
|
82809
|
+
rule: `${RULE_PREFIX[entry.tax.category]}-01`,
|
|
82810
|
+
args: {
|
|
82811
|
+
category: entry.tax.category,
|
|
82812
|
+
rate: entry.tax.rate ?? "",
|
|
82813
|
+
used: entry.first
|
|
82814
|
+
}
|
|
82815
|
+
}
|
|
82816
|
+
);
|
|
82817
|
+
}
|
|
82818
|
+
const rate = (tax, path, at2) => {
|
|
82819
|
+
const prefix = RULE_PREFIX[tax.category];
|
|
82820
|
+
const number = at2 === "line" || at2 === "breakdown" ? "05" : at2 === "allowance" ? "06" : "07";
|
|
82821
|
+
if (tax.category === "O") {
|
|
82822
|
+
if (tax.rate !== void 0)
|
|
82823
|
+
add2(
|
|
82824
|
+
"rate_forbidden",
|
|
82825
|
+
`${path}.rate`,
|
|
82826
|
+
`_invoice.${path}.rate must be left out: category O is not subject to VAT and has no rate`,
|
|
82827
|
+
{ rule: `${prefix}-${number}` }
|
|
82828
|
+
);
|
|
82829
|
+
return;
|
|
82830
|
+
}
|
|
82831
|
+
if (tax.rate === void 0) {
|
|
82832
|
+
add2(
|
|
82833
|
+
"rate_required",
|
|
82834
|
+
`${path}.rate`,
|
|
82835
|
+
`_invoice.${path}.rate is missing: category ${tax.category} states its rate${tax.category === "S" ? "" : ", which is 0"}`,
|
|
82836
|
+
{
|
|
82837
|
+
rule: at2 === "breakdown" ? "BR-48" : `${prefix}-${number}`,
|
|
82838
|
+
args: { category: tax.category }
|
|
82839
|
+
}
|
|
82840
|
+
);
|
|
82841
|
+
return;
|
|
82842
|
+
}
|
|
82843
|
+
const value = toUnits(tax.rate, 4);
|
|
82844
|
+
if (tax.category === "S" && value <= 0n)
|
|
82845
|
+
add2(
|
|
82846
|
+
"rate_positive",
|
|
82847
|
+
`${path}.rate`,
|
|
82848
|
+
`_invoice.${path}.rate is ${tax.rate}: category S is standard rated and needs a rate above 0; a rate of 0 is category Z or E`,
|
|
82849
|
+
{
|
|
82850
|
+
rule: `${prefix}-${number}`,
|
|
82851
|
+
args: { rate: tax.rate }
|
|
82852
|
+
}
|
|
82853
|
+
);
|
|
82854
|
+
if (tax.category !== "S" && value !== 0n)
|
|
82855
|
+
add2(
|
|
82856
|
+
"rate_zero",
|
|
82857
|
+
`${path}.rate`,
|
|
82858
|
+
`_invoice.${path}.rate is ${tax.rate}: category ${tax.category} carries a rate of 0`,
|
|
82859
|
+
{
|
|
82860
|
+
rule: `${prefix}-${number}`,
|
|
82861
|
+
args: { rate: tax.rate, category: tax.category }
|
|
82862
|
+
}
|
|
82863
|
+
);
|
|
82864
|
+
};
|
|
82865
|
+
invoice.lines.forEach((line2, i) => rate(line2.tax, `lines[${i}].tax`, "line"));
|
|
82866
|
+
invoice.allowances.forEach(
|
|
82867
|
+
(a, i) => rate(a.tax, `allowances[${i}].tax`, "allowance")
|
|
82868
|
+
);
|
|
82869
|
+
invoice.charges.forEach((c, i) => rate(c.tax, `charges[${i}].tax`, "charge"));
|
|
82870
|
+
const categories = new Set(
|
|
82871
|
+
[...used.values()].map((entry) => entry.tax.category)
|
|
82872
|
+
);
|
|
82873
|
+
invoice.breakdown.forEach((row, i) => {
|
|
82874
|
+
const path = `tax.breakdown[${i}]`;
|
|
82875
|
+
const prefix = RULE_PREFIX[row.category];
|
|
82876
|
+
rate(row, path, "breakdown");
|
|
82877
|
+
categories.add(row.category);
|
|
82878
|
+
const reason = row.exemption_reason !== void 0 || row.exemption_reason_code !== void 0;
|
|
82879
|
+
if (row.category === "S" || row.category === "Z") {
|
|
82880
|
+
if (reason)
|
|
82881
|
+
add2(
|
|
82882
|
+
"exemption_reason_forbidden",
|
|
82883
|
+
`${path}.exemption_reason`,
|
|
82884
|
+
`_invoice.${path} must not state an exemption reason: category ${row.category} is not an exemption`,
|
|
82885
|
+
{
|
|
82886
|
+
rule: `${prefix}-10`,
|
|
82887
|
+
args: { category: row.category }
|
|
82888
|
+
}
|
|
82889
|
+
);
|
|
82890
|
+
} else if (!reason) {
|
|
82891
|
+
add2(
|
|
82892
|
+
"exemption_reason_required",
|
|
82893
|
+
`${path}.exemption_reason`,
|
|
82894
|
+
`_invoice.${path}.exemption_reason is missing: category ${row.category} says why no VAT is charged, as text or as exemption_reason_code`,
|
|
82895
|
+
{
|
|
82896
|
+
rule: `${prefix}-10`,
|
|
82897
|
+
args: { category: row.category }
|
|
82898
|
+
}
|
|
82899
|
+
);
|
|
82900
|
+
}
|
|
82901
|
+
if (row.category !== "S" && cents(row.amount) !== 0n)
|
|
82902
|
+
add2(
|
|
82903
|
+
"amount_zero",
|
|
82904
|
+
`${path}.amount`,
|
|
82905
|
+
`_invoice.${path}.amount is ${money(cents(row.amount))}: category ${row.category} carries no VAT, so the amount is 0`,
|
|
82906
|
+
{
|
|
82907
|
+
rule: `${prefix}-09`,
|
|
82908
|
+
args: { value: money(cents(row.amount)), category: row.category }
|
|
82909
|
+
}
|
|
82910
|
+
);
|
|
82911
|
+
});
|
|
82912
|
+
const sellerTaxId = invoice.seller.vat_id !== void 0 || invoice.seller.tax_number !== void 0;
|
|
82913
|
+
for (const category of categories) {
|
|
82914
|
+
const prefix = RULE_PREFIX[category];
|
|
82915
|
+
if (category === "O") {
|
|
82916
|
+
if (invoice.seller.vat_id !== void 0)
|
|
82917
|
+
add2(
|
|
82918
|
+
"vat_id_forbidden",
|
|
82919
|
+
"seller.vat_id",
|
|
82920
|
+
"_invoice.seller.vat_id must be left out: an invoice of category O is not subject to VAT; the seller is named by tax_number or legal_registration",
|
|
82921
|
+
{ rule: "BR-O-02" }
|
|
82922
|
+
);
|
|
82923
|
+
if (invoice.buyer.vat_id !== void 0)
|
|
82924
|
+
add2(
|
|
82925
|
+
"vat_id_forbidden",
|
|
82926
|
+
"buyer.vat_id",
|
|
82927
|
+
"_invoice.buyer.vat_id must be left out: an invoice of category O is not subject to VAT",
|
|
82928
|
+
{ rule: "BR-O-02" }
|
|
82929
|
+
);
|
|
82930
|
+
if (categories.size > 1)
|
|
82931
|
+
add2(
|
|
82932
|
+
"only_category",
|
|
82933
|
+
"tax.breakdown",
|
|
82934
|
+
"_invoice mixes category O with others: an invoice that is not subject to VAT has that one category only",
|
|
82935
|
+
{ rule: "BR-O-11" }
|
|
82936
|
+
);
|
|
82937
|
+
continue;
|
|
82938
|
+
}
|
|
82939
|
+
if (category === "K" || category === "G") {
|
|
82940
|
+
if (invoice.seller.vat_id === void 0)
|
|
82941
|
+
add2(
|
|
82942
|
+
"seller_tax_id_required",
|
|
82943
|
+
"seller.vat_id",
|
|
82944
|
+
`_invoice.seller.vat_id is missing: category ${category} names the seller's VAT ID`,
|
|
82945
|
+
{
|
|
82946
|
+
rule: `${prefix}-02`,
|
|
82947
|
+
args: { category }
|
|
82948
|
+
}
|
|
82949
|
+
);
|
|
82950
|
+
} else if (!sellerTaxId) {
|
|
82951
|
+
add2(
|
|
82952
|
+
"seller_tax_id_required",
|
|
82953
|
+
"seller.vat_id",
|
|
82954
|
+
`_invoice.seller.vat_id is missing: category ${category} names the seller's VAT ID, or tax_number where there is none`,
|
|
82955
|
+
{
|
|
82956
|
+
rule: `${prefix}-02`,
|
|
82957
|
+
args: { category }
|
|
82958
|
+
}
|
|
82959
|
+
);
|
|
82960
|
+
}
|
|
82961
|
+
if (category === "AE" && invoice.buyer.vat_id === void 0 && invoice.buyer.legal_registration === void 0)
|
|
82962
|
+
add2(
|
|
82963
|
+
"buyer_tax_id_required",
|
|
82964
|
+
"buyer.vat_id",
|
|
82965
|
+
"_invoice.buyer.vat_id is missing: with reverse charge the buyer owes the VAT, so the invoice names the buyer's VAT ID or legal_registration",
|
|
82966
|
+
{
|
|
82967
|
+
rule: "BR-AE-02",
|
|
82968
|
+
args: { category }
|
|
82969
|
+
}
|
|
82970
|
+
);
|
|
82971
|
+
if (category === "K") {
|
|
82972
|
+
if (invoice.buyer.vat_id === void 0)
|
|
82973
|
+
add2(
|
|
82974
|
+
"buyer_tax_id_required",
|
|
82975
|
+
"buyer.vat_id",
|
|
82976
|
+
"_invoice.buyer.vat_id is missing: an intra-community supply names the buyer's VAT ID",
|
|
82977
|
+
{
|
|
82978
|
+
rule: "BR-IC-02",
|
|
82979
|
+
args: { category }
|
|
82980
|
+
}
|
|
82981
|
+
);
|
|
82982
|
+
if (invoice.delivery?.date === void 0 && invoice.period === void 0)
|
|
82983
|
+
add2(
|
|
82984
|
+
"delivery_required",
|
|
82985
|
+
"delivery.date",
|
|
82986
|
+
"_invoice.delivery.date is missing: an intra-community supply states when it was delivered, or the invoicing period",
|
|
82987
|
+
{ rule: "BR-IC-11" }
|
|
82988
|
+
);
|
|
82989
|
+
if (invoice.delivery?.address === void 0)
|
|
82990
|
+
add2(
|
|
82991
|
+
"delivery_required",
|
|
82992
|
+
"delivery.address.country",
|
|
82993
|
+
"_invoice.delivery.address.country is missing: an intra-community supply states the country it was delivered to",
|
|
82994
|
+
{ rule: "BR-IC-12" }
|
|
82995
|
+
);
|
|
82996
|
+
}
|
|
82997
|
+
}
|
|
82998
|
+
if (invoice.seller.id === void 0 && invoice.seller.legal_registration === void 0 && invoice.seller.vat_id === void 0)
|
|
82999
|
+
add2(
|
|
83000
|
+
"seller_identifier_required",
|
|
83001
|
+
"seller.vat_id",
|
|
83002
|
+
"_invoice.seller needs an identifier the buyer can match: vat_id, legal_registration or id",
|
|
83003
|
+
{ rule: "BR-CO-26" }
|
|
83004
|
+
);
|
|
83005
|
+
const payment2 = invoice.payment;
|
|
83006
|
+
const account = payment2?.iban !== void 0 || payment2?.bic !== void 0 || payment2?.account_name !== void 0;
|
|
83007
|
+
if (payment2 && payment2.means_code === void 0 && (account || payment2.mandate_id !== void 0 || payment2.debited_iban !== void 0))
|
|
83008
|
+
add2(
|
|
83009
|
+
"means_code_required",
|
|
83010
|
+
"payment.means_code",
|
|
83011
|
+
"_invoice.payment.means_code is missing: payment details say how the invoice is paid, 58 for a SEPA credit transfer, 59 for a SEPA direct debit",
|
|
83012
|
+
{ rule: "BR-49" }
|
|
83013
|
+
);
|
|
83014
|
+
if (payment2?.means_code !== void 0 && CREDIT_TRANSFER.has(payment2.means_code) && payment2.iban === void 0)
|
|
83015
|
+
add2(
|
|
83016
|
+
"account_required",
|
|
83017
|
+
"payment.iban",
|
|
83018
|
+
`_invoice.payment.iban is missing: a payment by credit transfer (code ${payment2.means_code}) names the account`,
|
|
83019
|
+
{
|
|
83020
|
+
rule: "BR-61",
|
|
83021
|
+
args: { means: payment2.means_code }
|
|
83022
|
+
}
|
|
83023
|
+
);
|
|
83024
|
+
if (cents(totals2.due) > 0n && payment2?.due_date === void 0 && payment2?.terms === void 0)
|
|
83025
|
+
add2(
|
|
83026
|
+
"due_or_terms_required",
|
|
83027
|
+
"payment.due_date",
|
|
83028
|
+
"_invoice.payment.due_date is missing: an invoice with an amount due states when it is due, or its payment terms",
|
|
83029
|
+
{ rule: "BR-CO-25" }
|
|
83030
|
+
);
|
|
83031
|
+
if (invoice.seller.address.country === "FR" && invoice.buyer.address.country === "FR") {
|
|
83032
|
+
if (options.profile === "basic")
|
|
83033
|
+
add2(
|
|
83034
|
+
"fr_profile",
|
|
83035
|
+
"",
|
|
83036
|
+
"_invoice is an invoice between French businesses, which France's e-invoicing reform does not accept in the profile basic (AFNOR XP Z12-012 takes EN 16931 and EXTENDED-CTC-FR); use en16931",
|
|
83037
|
+
{ args: { profile: "basic" } }
|
|
83038
|
+
);
|
|
83039
|
+
const digits = (id) => (id?.value ?? "").replace(/\s/g, "");
|
|
83040
|
+
if (!/^\d{9}$/.test(digits(invoice.seller.legal_registration)))
|
|
83041
|
+
add2(
|
|
83042
|
+
"fr_seller_siren",
|
|
83043
|
+
"seller.legal_registration",
|
|
83044
|
+
"_invoice.seller.legal_registration is missing or not nine digits: an invoice between French businesses names the seller's SIREN (BT-30)",
|
|
83045
|
+
{ rule: "BR-FR-10" }
|
|
83046
|
+
);
|
|
83047
|
+
if (!/^\d{9}$/.test(digits(invoice.buyer.legal_registration)))
|
|
83048
|
+
add2(
|
|
83049
|
+
"fr_buyer_siren",
|
|
83050
|
+
"buyer.siren",
|
|
83051
|
+
"_invoice.buyer.siren is missing or not nine digits: an invoice between French businesses names the buyer's SIREN (BT-47)",
|
|
83052
|
+
{ rule: "BR-FR-11" }
|
|
83053
|
+
);
|
|
83054
|
+
if (invoice.business_process === void 0)
|
|
83055
|
+
add2(
|
|
83056
|
+
"fr_operation",
|
|
83057
|
+
"operation",
|
|
83058
|
+
"_invoice.operation is missing: an invoice between French businesses says whether it is for goods, services or mixed (BT-23)",
|
|
83059
|
+
{ rule: "BR-FR-08" }
|
|
83060
|
+
);
|
|
83061
|
+
else if (!FRENCH_FRAMEWORKS.has(invoice.business_process))
|
|
83062
|
+
add2(
|
|
83063
|
+
"code_list",
|
|
83064
|
+
"operation_code",
|
|
83065
|
+
`_invoice.operation_code: "${invoice.business_process}" is not a billing framework of the French specifications (B1, S1, M1 and their variants)`,
|
|
83066
|
+
{
|
|
83067
|
+
rule: "BR-FR-08",
|
|
83068
|
+
args: {
|
|
83069
|
+
value: invoice.business_process,
|
|
83070
|
+
list: "the French billing frameworks"
|
|
83071
|
+
}
|
|
83072
|
+
}
|
|
83073
|
+
);
|
|
83074
|
+
if (invoice.seller.vat_on_debits === void 0)
|
|
83075
|
+
add2(
|
|
83076
|
+
"fr_vat_on_debits",
|
|
83077
|
+
"seller.vat_on_debits",
|
|
83078
|
+
"_invoice.seller.vat_on_debits is missing: an invoice between French businesses says whether the seller pays VAT on invoicing (true) or on payment (false)"
|
|
83079
|
+
);
|
|
83080
|
+
const subjects = new Set(invoice.notes.map((note2) => note2.subject_code));
|
|
83081
|
+
for (const [code, what] of FRENCH_NOTES)
|
|
83082
|
+
if (!subjects.has(code))
|
|
83083
|
+
add2(
|
|
83084
|
+
"fr_note",
|
|
83085
|
+
"notes",
|
|
83086
|
+
`_invoice.notes has no note with the subject_code ${code}: an invoice between French businesses states ${what} as a note (BT-21, BT-22)`,
|
|
83087
|
+
{ rule: "BR-FR-05", args: { subject_code: code } }
|
|
83088
|
+
);
|
|
83089
|
+
if (!invoice.seller.electronic_address)
|
|
83090
|
+
add2(
|
|
83091
|
+
"fr_electronic_address",
|
|
83092
|
+
"seller.electronic_address",
|
|
83093
|
+
"_invoice.seller.electronic_address is missing: an invoice between French businesses names the seller's electronic address (BT-34)",
|
|
83094
|
+
{ rule: "BR-FR-13", args: { term: "BT-34" } }
|
|
83095
|
+
);
|
|
83096
|
+
if (!invoice.buyer.electronic_address)
|
|
83097
|
+
add2(
|
|
83098
|
+
"fr_electronic_address",
|
|
83099
|
+
"buyer.electronic_address",
|
|
83100
|
+
"_invoice.buyer.electronic_address is missing: an invoice between French businesses names the buyer's electronic address (BT-49)",
|
|
83101
|
+
{ rule: "BR-FR-12", args: { term: "BT-49" } }
|
|
83102
|
+
);
|
|
83103
|
+
}
|
|
83104
|
+
}
|
|
83105
|
+
|
|
81968
83106
|
// packages/engine/src/lib/office/errors.ts
|
|
81969
83107
|
var officeLimits = {
|
|
81970
83108
|
/** Entries in one container. */
|
|
@@ -82703,7 +83841,7 @@ var encoder2 = new TextEncoder();
|
|
|
82703
83841
|
// packages/mcp/src/lib/server.ts
|
|
82704
83842
|
import { z as z3 } from "zod";
|
|
82705
83843
|
var CONVERT_LIMIT_BYTES = 20 * 1024 * 1024;
|
|
82706
|
-
var SERVER_INFO = { name: "formfeed", version: "0.3.
|
|
83844
|
+
var SERVER_INFO = { name: "formfeed", version: "0.3.17" };
|
|
82707
83845
|
var READS = {
|
|
82708
83846
|
readOnlyHint: true,
|
|
82709
83847
|
destructiveHint: false,
|
|
@@ -82741,8 +83879,15 @@ var renderSummary = (r) => ({
|
|
|
82741
83879
|
error: r.error,
|
|
82742
83880
|
// the validator's verdict of a render whose template declares PDF/UA-1 (plan 21): whether the
|
|
82743
83881
|
// file passed, and for each failed rule what to change and in which elements
|
|
82744
|
-
...r.accessibility ? { accessibility: r.accessibility } : {}
|
|
83882
|
+
...r.accessibility ? { accessibility: r.accessibility } : {},
|
|
83883
|
+
// the report of an e-invoice (spec 17): valid, the findings, the XML's link, the hashes it checked
|
|
83884
|
+
...r.einvoice ? { einvoice: r.einvoice } : {}
|
|
82745
83885
|
});
|
|
83886
|
+
var EINVOICE_PROBLEMS = /* @__PURE__ */ new Set([
|
|
83887
|
+
"einvoice_data_invalid",
|
|
83888
|
+
"einvoice_display_mismatch",
|
|
83889
|
+
"pdfa_conversion_failed"
|
|
83890
|
+
]);
|
|
82746
83891
|
function text3(data) {
|
|
82747
83892
|
return {
|
|
82748
83893
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
@@ -82752,12 +83897,19 @@ function text3(data) {
|
|
|
82752
83897
|
function failure(e) {
|
|
82753
83898
|
const message2 = e instanceof FormfeedError ? `${e.code}: ${e.message}` : e instanceof Error ? e.message : String(e);
|
|
82754
83899
|
const verdict = e instanceof FormfeedError && e.code === "pdfua_validation_failed" ? e.problem?.["accessibility"] : void 0;
|
|
83900
|
+
const invoice = e instanceof FormfeedError && EINVOICE_PROBLEMS.has(e.code) ? Object.fromEntries(
|
|
83901
|
+
["errors", "einvoice"].filter((key) => e.problem?.[key] !== void 0).map((key) => [key, e.problem?.[key]])
|
|
83902
|
+
) : {};
|
|
83903
|
+
const details = {
|
|
83904
|
+
...verdict ? { accessibility: verdict } : {},
|
|
83905
|
+
...invoice
|
|
83906
|
+
};
|
|
82755
83907
|
return {
|
|
82756
83908
|
content: [
|
|
82757
83909
|
{
|
|
82758
83910
|
type: "text",
|
|
82759
|
-
text:
|
|
82760
|
-
${JSON.stringify(
|
|
83911
|
+
text: Object.keys(details).length > 0 ? `${message2}
|
|
83912
|
+
${JSON.stringify(details, null, 2)}` : message2
|
|
82761
83913
|
}
|
|
82762
83914
|
],
|
|
82763
83915
|
isError: true
|
|
@@ -82775,7 +83927,7 @@ function createFormfeedServer(options) {
|
|
|
82775
83927
|
...options.fetch ? { fetch: options.fetch } : {}
|
|
82776
83928
|
});
|
|
82777
83929
|
const server = new McpServer(SERVER_INFO, {
|
|
82778
|
-
instructions: "Formfeed renders PDFs and images from HTML templates, and fills Word and PowerPoint templates (as DOCX, PPTX or PDF). Start with list_templates, read the data shape with get_template_schema, check data with validate_template, then call render and hand the download_url to the user. convert_to_pdf turns office documents into PDFs. get_workspace says which workspace this server works in and what the period has left."
|
|
83930
|
+
instructions: "Formfeed renders PDFs and images from HTML templates, and fills Word and PowerPoint templates (as DOCX, PPTX or PDF). Start with list_templates, read the data shape with get_template_schema, check data with validate_template, then call render and hand the download_url to the user. For an e-invoice (ZUGFeRD / Factur-X), put the invoice into data._invoice, check it with check_invoice, and call render with einvoice set; the template prints the same block. convert_to_pdf turns office documents into PDFs. get_workspace says which workspace this server works in and what the period has left."
|
|
82779
83931
|
});
|
|
82780
83932
|
server.registerTool(
|
|
82781
83933
|
"list_templates",
|
|
@@ -82911,11 +84063,38 @@ function createFormfeedServer(options) {
|
|
|
82911
84063
|
}
|
|
82912
84064
|
}
|
|
82913
84065
|
);
|
|
84066
|
+
server.registerTool(
|
|
84067
|
+
"check_invoice",
|
|
84068
|
+
{
|
|
84069
|
+
title: "Check an e-invoice",
|
|
84070
|
+
description: "Checks the _invoice block of an e-invoice offline, as the API does before it renders one: the fields EN 16931 asks for, the totals to the cent, VAT categories and code lists, and France's rules for an invoice between two French businesses. Free, no units, nothing sent. Returns ok and problems, each with the field, the rule (BR-\u2026) where one applies and a message. Nothing is computed for you: change the values a problem names, check again, then call render with einvoice and the block as data._invoice. The validator that runs during the render holds the file to the full rule set and to PDF/A-3b as well.",
|
|
84071
|
+
inputSchema: {
|
|
84072
|
+
invoice: z3.record(z3.string(), z3.unknown()).describe(
|
|
84073
|
+
"The _invoice block as it goes into data._invoice: number, issue_date, currency, seller, buyer, lines, tax.breakdown, totals and payment, as https://docs.formfeed.dev/templates/e-invoices#invoice-block describes"
|
|
84074
|
+
),
|
|
84075
|
+
profile: z3.enum(["en16931", "basic"]).optional().describe(
|
|
84076
|
+
"The profile the render will ask for; en16931 when left out. basic cannot carry a rounding amount and is refused between two French businesses"
|
|
84077
|
+
)
|
|
84078
|
+
},
|
|
84079
|
+
annotations: READS
|
|
84080
|
+
},
|
|
84081
|
+
async ({ invoice, profile }) => {
|
|
84082
|
+
const check = checkInvoice(invoice, { profile: profile ?? "en16931" });
|
|
84083
|
+
return text3({
|
|
84084
|
+
ok: check.ok,
|
|
84085
|
+
problems: check.problems.map((problem) => ({
|
|
84086
|
+
path: problem.path ? `_invoice.${problem.path}` : "_invoice",
|
|
84087
|
+
...problem.rule ? { rule: problem.rule } : {},
|
|
84088
|
+
message: problem.message
|
|
84089
|
+
}))
|
|
84090
|
+
});
|
|
84091
|
+
}
|
|
84092
|
+
);
|
|
82914
84093
|
server.registerTool(
|
|
82915
84094
|
"render",
|
|
82916
84095
|
{
|
|
82917
84096
|
title: "Render a document",
|
|
82918
|
-
description: "Creates a document by filling a stored template, or ad-hoc html, with data: a PDF or image, or DOCX, PPTX or PDF from Word and PowerPoint templates. Returns the render with status and download_url. Use it to make a document from data; to turn an existing office file into a PDF, use convert_to_pdf. Stored templates render their published version, so one whose published_version is null fails with template_not_found. Every call is a new render: a live key consumes units, a test key is free (watermarked on the Free plan). A render that fails returns status failed and an error with code and message; validate_template finds most causes beforehand. The link expires at expires_at; get_render issues a fresh one.",
|
|
84097
|
+
description: "Creates a document by filling a stored template, or ad-hoc html, with data: a PDF or image, or DOCX, PPTX or PDF from Word and PowerPoint templates. Returns the render with status and download_url. Use it to make a document from data; to turn an existing office file into a PDF, use convert_to_pdf. Stored templates render their published version, so one whose published_version is null fails with template_not_found. Every call is a new render: a live key consumes units, a test key is free (watermarked on the Free plan). A render that fails returns status failed and an error with code and message; validate_template finds most causes beforehand. With einvoice the PDF becomes a ZUGFeRD / Factur-X e-invoice built from data._invoice (check the block with check_invoice first): the render then carries einvoice, the validator's report, and a refused invoice names the fields and rules to fix. The link expires at expires_at; get_render issues a fresh one.",
|
|
82919
84098
|
inputSchema: {
|
|
82920
84099
|
template: z3.string().optional().describe("Slug or tpl_ id of a stored template; pass this or html"),
|
|
82921
84100
|
html: z3.string().optional().describe(
|
|
@@ -82936,6 +84115,25 @@ function createFormfeedServer(options) {
|
|
|
82936
84115
|
locale: z3.string().optional().describe("BCP 47 tag for helpers and translations, e.g. de-DE"),
|
|
82937
84116
|
wait: z3.boolean().default(true).describe(
|
|
82938
84117
|
"Most renders are finished when the call returns; true also waits for one that continues in the background, false returns it as it is (queued or rendering) to check later with get_render"
|
|
84118
|
+
),
|
|
84119
|
+
einvoice: z3.union([
|
|
84120
|
+
z3.boolean(),
|
|
84121
|
+
z3.object({
|
|
84122
|
+
profile: z3.enum(["en16931", "basic"]).optional().describe(
|
|
84123
|
+
"en16931 (default) carries the whole European standard; basic is the smallest full invoice and is refused between two French businesses"
|
|
84124
|
+
),
|
|
84125
|
+
flavour: z3.enum(["factur-x", "zugferd"]).optional().describe(
|
|
84126
|
+
"One specification under two names; the default factur-x and zugferd make the same file"
|
|
84127
|
+
),
|
|
84128
|
+
xml: z3.enum(["embedded", "both"]).optional().describe(
|
|
84129
|
+
"both also stores the XML as a file of its own, linked as einvoice.xml_url"
|
|
84130
|
+
),
|
|
84131
|
+
display_check: z3.enum(["warn", "strict"]).optional().describe(
|
|
84132
|
+
"What a value of the XML that the PDF does not show does: a warning in the report (default) or a failed render"
|
|
84133
|
+
)
|
|
84134
|
+
})
|
|
84135
|
+
]).optional().describe(
|
|
84136
|
+
"Makes the PDF a ZUGFeRD / Factur-X e-invoice whose XML is built from data._invoice: true for the defaults, an object to choose, false to switch off what the template declares. PDF only; live renders from the Starter plan, test keys on Free make 20 marked ones a day"
|
|
82939
84137
|
)
|
|
82940
84138
|
},
|
|
82941
84139
|
annotations: CREATES
|
|
@@ -82948,7 +84146,8 @@ function createFormfeedServer(options) {
|
|
|
82948
84146
|
output,
|
|
82949
84147
|
filename,
|
|
82950
84148
|
locale,
|
|
82951
|
-
wait
|
|
84149
|
+
wait,
|
|
84150
|
+
einvoice
|
|
82952
84151
|
}) => {
|
|
82953
84152
|
try {
|
|
82954
84153
|
if (!template && !html)
|
|
@@ -82961,6 +84160,7 @@ function createFormfeedServer(options) {
|
|
|
82961
84160
|
...output ? { output } : {},
|
|
82962
84161
|
...filename ? { filename } : {},
|
|
82963
84162
|
...locale ? { locale } : {},
|
|
84163
|
+
...einvoice !== void 0 ? { post: { einvoice: einvoice === true ? {} : einvoice } } : {},
|
|
82964
84164
|
meta: { source: "mcp" }
|
|
82965
84165
|
});
|
|
82966
84166
|
if (wait && render.status !== "succeeded" && render.status !== "failed")
|
|
@@ -83128,11 +84328,110 @@ function createFormfeedServer(options) {
|
|
|
83128
84328
|
|
|
83129
84329
|
// packages/mcp/src/lib/web.ts
|
|
83130
84330
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
84331
|
+
|
|
84332
|
+
// packages/mcp/src/lib/server-card.ts
|
|
84333
|
+
import { SUPPORTED_PROTOCOL_VERSIONS } from "@modelcontextprotocol/sdk/types.js";
|
|
84334
|
+
var SERVER_CARD_TYPE = "application/mcp-server-card+json";
|
|
84335
|
+
var SERVER_CARD_SCHEMA = "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json";
|
|
84336
|
+
function serverCard(origin, signIn) {
|
|
84337
|
+
const endpoint = `${origin.replace(/\/$/, "")}/mcp`;
|
|
84338
|
+
const supportedProtocolVersions = [...SUPPORTED_PROTOCOL_VERSIONS];
|
|
84339
|
+
return {
|
|
84340
|
+
$schema: SERVER_CARD_SCHEMA,
|
|
84341
|
+
name: "io.github.formfeed-dev/formfeed",
|
|
84342
|
+
version: SERVER_INFO.version,
|
|
84343
|
+
description: "Render PDFs and images from templates, validate data and convert Office files to PDF.",
|
|
84344
|
+
title: "Formfeed",
|
|
84345
|
+
websiteUrl: "https://docs.formfeed.dev/integrations/mcp",
|
|
84346
|
+
repository: {
|
|
84347
|
+
url: "https://github.com/formfeed-dev/formfeed",
|
|
84348
|
+
source: "github",
|
|
84349
|
+
subfolder: "packages/mcp"
|
|
84350
|
+
},
|
|
84351
|
+
icons: [
|
|
84352
|
+
{ src: "https://formfeed.dev/favicon.svg", mimeType: "image/svg+xml" },
|
|
84353
|
+
{
|
|
84354
|
+
src: "https://formfeed.dev/brand/formfeed-512.png",
|
|
84355
|
+
mimeType: "image/png",
|
|
84356
|
+
sizes: ["512x512"]
|
|
84357
|
+
}
|
|
84358
|
+
],
|
|
84359
|
+
remotes: [
|
|
84360
|
+
{
|
|
84361
|
+
type: "streamable-http",
|
|
84362
|
+
url: endpoint,
|
|
84363
|
+
headers: [
|
|
84364
|
+
signIn ? {
|
|
84365
|
+
name: "Authorization",
|
|
84366
|
+
description: "Bearer followed by a Formfeed API key (ff_test_\u2026 or ff_live_\u2026); without it the client signs in with your Formfeed account (OAuth) and you choose the workspace",
|
|
84367
|
+
isRequired: false,
|
|
84368
|
+
isSecret: true
|
|
84369
|
+
} : {
|
|
84370
|
+
name: "Authorization",
|
|
84371
|
+
description: "Bearer followed by a Formfeed API key (ff_test_\u2026 or ff_live_\u2026)",
|
|
84372
|
+
isRequired: true,
|
|
84373
|
+
isSecret: true
|
|
84374
|
+
}
|
|
84375
|
+
],
|
|
84376
|
+
supportedProtocolVersions
|
|
84377
|
+
},
|
|
84378
|
+
...signIn ? [
|
|
84379
|
+
{
|
|
84380
|
+
type: "streamable-http",
|
|
84381
|
+
url: `${endpoint}/{workspace_id}`,
|
|
84382
|
+
variables: {
|
|
84383
|
+
workspace_id: {
|
|
84384
|
+
description: "Workspace id from the Formfeed app, for a connection that should work in another workspace than the one chosen at sign-in (OAuth)",
|
|
84385
|
+
isRequired: true
|
|
84386
|
+
}
|
|
84387
|
+
},
|
|
84388
|
+
supportedProtocolVersions
|
|
84389
|
+
}
|
|
84390
|
+
] : []
|
|
84391
|
+
]
|
|
84392
|
+
};
|
|
84393
|
+
}
|
|
84394
|
+
var CORS = {
|
|
84395
|
+
"access-control-allow-origin": "*",
|
|
84396
|
+
"access-control-allow-methods": "GET, HEAD, OPTIONS",
|
|
84397
|
+
"access-control-allow-headers": "Content-Type, If-None-Match",
|
|
84398
|
+
"access-control-expose-headers": "ETag"
|
|
84399
|
+
};
|
|
84400
|
+
async function serverCardResponse(request, card) {
|
|
84401
|
+
if (request.method === "OPTIONS")
|
|
84402
|
+
return new Response(null, { status: 204, headers: CORS });
|
|
84403
|
+
if (request.method !== "GET" && request.method !== "HEAD")
|
|
84404
|
+
return new Response(null, {
|
|
84405
|
+
status: 405,
|
|
84406
|
+
headers: { ...CORS, allow: "GET, HEAD, OPTIONS" }
|
|
84407
|
+
});
|
|
84408
|
+
const body = JSON.stringify(card, null, 2);
|
|
84409
|
+
const digest = await crypto.subtle.digest(
|
|
84410
|
+
"SHA-256",
|
|
84411
|
+
new TextEncoder().encode(body)
|
|
84412
|
+
);
|
|
84413
|
+
const etag = `"${[...new Uint8Array(digest).slice(0, 16)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}"`;
|
|
84414
|
+
const headers = {
|
|
84415
|
+
...CORS,
|
|
84416
|
+
"cache-control": "public, max-age=3600",
|
|
84417
|
+
etag
|
|
84418
|
+
};
|
|
84419
|
+
const known = (request.headers.get("if-none-match") ?? "").split(",").map((tag) => tag.trim().replace(/^W\//, ""));
|
|
84420
|
+
if (known.includes(etag) || known.includes("*"))
|
|
84421
|
+
return new Response(null, { status: 304, headers });
|
|
84422
|
+
return new Response(request.method === "HEAD" ? null : body, {
|
|
84423
|
+
status: 200,
|
|
84424
|
+
headers: { ...headers, "content-type": SERVER_CARD_TYPE }
|
|
84425
|
+
});
|
|
84426
|
+
}
|
|
84427
|
+
|
|
84428
|
+
// packages/mcp/src/lib/web.ts
|
|
83131
84429
|
var WORKSPACE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
83132
84430
|
var API_KEY = /^ff_(live|test)_/;
|
|
83133
84431
|
var JWT = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
83134
84432
|
var MCP_PATH = "/mcp";
|
|
83135
84433
|
var RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
|
|
84434
|
+
var SERVER_CARD_PATH = `${MCP_PATH}/server-card`;
|
|
83136
84435
|
function resourceMetadata(origin, workspaceId, authorizationServer2) {
|
|
83137
84436
|
return {
|
|
83138
84437
|
resource: `${origin}${MCP_PATH}/${workspaceId}`,
|
|
@@ -83208,6 +84507,11 @@ function mcpFetchHandler(options = {}) {
|
|
|
83208
84507
|
200
|
|
83209
84508
|
);
|
|
83210
84509
|
}
|
|
84510
|
+
if (path === SERVER_CARD_PATH)
|
|
84511
|
+
return serverCardResponse(
|
|
84512
|
+
request,
|
|
84513
|
+
serverCard(origin, Boolean(authorizationServer2))
|
|
84514
|
+
);
|
|
83211
84515
|
if (path !== MCP_PATH && !path.startsWith(`${MCP_PATH}/`))
|
|
83212
84516
|
return new Response(null, { status: 404 });
|
|
83213
84517
|
const workspaceId = path === MCP_PATH ? null : path.slice(MCP_PATH.length + 1);
|