rerobe-js-orm 4.9.12 → 4.9.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ export declare function formatSekOre(ore: number): string;
2
+ export declare function paymentTypeLabel(paymentType: string): string;
3
+ export type GrandTotalsOre = {
4
+ salesOre?: number;
5
+ returnsOre?: number;
6
+ } | null | undefined;
7
+ export type DailyReportInput = {
8
+ orders: any[];
9
+ reportType?: string;
10
+ grandTotalsBeforeOre?: GrandTotalsOre;
11
+ copiesCount?: number;
12
+ trainingCount?: number;
13
+ incompleteCount?: number;
14
+ drawerOpenCount?: number;
15
+ certified?: boolean;
16
+ };
17
+ export declare function buildDailyReport(input: DailyReportInput): any;
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ // Swedish "gold standard" daily fiscal report (X-rapport / Z-dagrapport) aggregation.
3
+ //
4
+ // Pure + dependency-light: turns a day's COMPLETED orders for a register into the
5
+ // data object a kassaregister dagrapport must show per Skatteverket SKVFS 2014:9
6
+ // (7 kap. 2-3 §; grand totals added by SKVFS 2021:17). The renderer
7
+ // (ReceiptXmlHelpers.buildReportReceiptXML) is a dumb consumer of this shape, so
8
+ // print + simulator stay in lockstep.
9
+ //
10
+ // Money model — consistent with MarketplaceReceiptHelpers.lineItemGrossOre: a
11
+ // line's originalTotalPrice is the NET (ex-VAT) amount; gross = net * (1 + taxRate);
12
+ // line VAT = gross - net. Everything reconciles: salesGross = netExVat + vatTotal.
13
+ //
14
+ // Scope/date correctness is the CALLER's responsibility — it decides which orders
15
+ // belong to this register and this day; this function aggregates exactly what it
16
+ // is given. (Returns are taken from those orders' refunds[]; a refund processed on
17
+ // a different day than its order is the caller's concern to scope.)
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.formatSekOre = formatSekOre;
20
+ exports.paymentTypeLabel = paymentTypeLabel;
21
+ exports.buildDailyReport = buildDailyReport;
22
+ const OrderHelpers_1 = require("./OrderHelpers");
23
+ const MarketplaceReceiptHelpers_1 = require("./marketplace/MarketplaceReceiptHelpers");
24
+ const order_constants_1 = require("../constants/order-constants");
25
+ // The three Swedish VAT rates always shown on a dagrapport; 0% only when present.
26
+ const VAT_RATES_SHOWN = [25, 12, 6];
27
+ // öre -> Swedish currency string: "1 250,00 kr", "-160,00 kr", "0,00 kr".
28
+ function formatSekOre(ore) {
29
+ const n = Math.round(Number(ore) || 0);
30
+ const sign = n < 0 ? '-' : '';
31
+ const abs = Math.abs(n);
32
+ const kronor = Math.floor(abs / 100);
33
+ const dec = String(abs % 100).padStart(2, '0');
34
+ const grouped = String(kronor).replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
35
+ return `${sign}${grouped},${dec} kr`;
36
+ }
37
+ // SKVFS-friendly Swedish label per payment type ("försäljning per betalsätt").
38
+ function paymentTypeLabel(paymentType) {
39
+ switch (paymentType) {
40
+ case order_constants_1.PAYMENT_TYPES.creditCard:
41
+ return 'Kort';
42
+ case order_constants_1.PAYMENT_TYPES.swish:
43
+ return 'Swish';
44
+ case order_constants_1.PAYMENT_TYPES.klarna:
45
+ return 'Klarna';
46
+ case order_constants_1.PAYMENT_TYPES.cash:
47
+ return 'Kontant';
48
+ case order_constants_1.PAYMENT_TYPES.mobilePayment:
49
+ return 'Mobil';
50
+ case order_constants_1.PAYMENT_TYPES.shoppingCredit:
51
+ return 'Tillgodo';
52
+ case order_constants_1.PAYMENT_TYPES.shopifyPayment:
53
+ return 'Shopify';
54
+ case order_constants_1.PAYMENT_TYPES.traderaPayment:
55
+ return 'Tradera';
56
+ default:
57
+ return paymentType || 'Ovrigt';
58
+ }
59
+ }
60
+ // VAT (öre) for one line, by integer rate percent. Net model: VAT = gross - net.
61
+ function lineVat(lineItem) {
62
+ const li = lineItem || {};
63
+ const rate = Number(li.taxRate || 0);
64
+ const taxable = li.isTaxable !== false && rate > 0;
65
+ const ratePercent = taxable ? Math.round(rate * 100) : 0;
66
+ const netOre = Math.abs((0, MarketplaceReceiptHelpers_1.toOre)((li.originalTotalPrice && li.originalTotalPrice.amount) || 0));
67
+ const grossOre = taxable ? Math.round(netOre * (1 + rate)) : netOre;
68
+ return { ratePercent, grossOre, vatOre: grossOre - netOre };
69
+ }
70
+ // Gross (öre) returned across an order's refunds, and the count of return events.
71
+ function orderReturns(order) {
72
+ const refunds = (order && order.refunds) || [];
73
+ let grossOre = 0;
74
+ refunds.forEach((refund) => {
75
+ (refund.refundLineItems || []).forEach((rli) => {
76
+ const returned = OrderHelpers_1.default.getReturnedLineAmounts(rli);
77
+ grossOre += Math.abs((0, MarketplaceReceiptHelpers_1.toOre)(returned.net));
78
+ });
79
+ });
80
+ return { count: refunds.length, grossOre };
81
+ }
82
+ // Aggregate a day's orders into the full SKVFS dagrapport data object. Amounts are
83
+ // returned both as Swedish-formatted strings (for the renderer) and as raw öre
84
+ // (under `ore`, for accumulator persistence / cross-checks).
85
+ function buildDailyReport(input) {
86
+ const params = input || { orders: [] };
87
+ const orders = params.orders || [];
88
+ const reportType = params.reportType || 'X';
89
+ const before = params.grandTotalsBeforeOre || {};
90
+ const copiesCount = params.copiesCount || 0;
91
+ const trainingCount = params.trainingCount || 0;
92
+ const incompleteCount = params.incompleteCount || 0;
93
+ const drawerOpenCount = params.drawerOpenCount || 0;
94
+ const certified = !!params.certified;
95
+ let receiptCount = 0;
96
+ let goodsCount = 0;
97
+ let salesGrossOre = 0;
98
+ let discountsOre = 0;
99
+ let returnsCount = 0;
100
+ let returnsGrossOre = 0;
101
+ const vatByRate = {};
102
+ const grossByRate = {};
103
+ const payByType = {};
104
+ orders.forEach((order) => {
105
+ receiptCount += 1;
106
+ discountsOre += Math.abs((0, MarketplaceReceiptHelpers_1.toOre)((order.totalDiscount && order.totalDiscount.amount) || 0));
107
+ let orderGrossOre = 0;
108
+ (order.lineItems || []).forEach((li) => {
109
+ goodsCount += Number(li.quantity || 0);
110
+ const { ratePercent, grossOre, vatOre } = lineVat(li);
111
+ orderGrossOre += grossOre;
112
+ vatByRate[ratePercent] = (vatByRate[ratePercent] || 0) + vatOre;
113
+ grossByRate[ratePercent] = (grossByRate[ratePercent] || 0) + grossOre;
114
+ });
115
+ salesGrossOre += orderGrossOre;
116
+ const pt = order.paymentType || order_constants_1.PAYMENT_TYPES.creditCard;
117
+ if (!payByType[pt])
118
+ payByType[pt] = { count: 0, ore: 0 };
119
+ payByType[pt].count += 1;
120
+ payByType[pt].ore += orderGrossOre;
121
+ const ret = orderReturns(order);
122
+ returnsCount += ret.count;
123
+ returnsGrossOre += ret.grossOre;
124
+ });
125
+ const vatTotalOre = Object.keys(vatByRate).reduce((sum, r) => sum + vatByRate[Number(r)], 0);
126
+ const netExVatOre = salesGrossOre - vatTotalOre;
127
+ // VAT lines: the standard Swedish rates always, plus any other rate present;
128
+ // 0% only when there is 0-rated turnover. Descending by rate.
129
+ const rateSet = VAT_RATES_SHOWN.slice();
130
+ Object.keys(grossByRate)
131
+ .map(Number)
132
+ .forEach((r) => {
133
+ if (r > 0 && rateSet.indexOf(r) === -1)
134
+ rateSet.push(r);
135
+ });
136
+ if ((grossByRate[0] || 0) > 0)
137
+ rateSet.push(0);
138
+ const vatLines = rateSet.sort((a, b) => b - a).map((rate) => ({ rate, amount: formatSekOre(vatByRate[rate] || 0) }));
139
+ const paymentBreakdown = Object.keys(payByType)
140
+ .map((type) => ({
141
+ type,
142
+ label: paymentTypeLabel(type),
143
+ count: payByType[type].count,
144
+ amount: formatSekOre(payByType[type].ore),
145
+ ore: payByType[type].ore,
146
+ }))
147
+ .sort((a, b) => b.ore - a.ore);
148
+ const gtSalesOre = (before.salesOre || 0) + salesGrossOre;
149
+ const gtReturnsOre = (before.returnsOre || 0) + returnsGrossOre;
150
+ const gtNetOre = gtSalesOre - gtReturnsOre;
151
+ return {
152
+ reportType: String(reportType).toUpperCase().indexOf('Z') !== -1 ? 'Z' : 'X',
153
+ receiptCount,
154
+ goodsCount,
155
+ copiesCount,
156
+ trainingCount,
157
+ incompleteCount,
158
+ drawerOpenCount,
159
+ paymentBreakdown,
160
+ salesGross: formatSekOre(salesGrossOre),
161
+ discounts: formatSekOre(discountsOre),
162
+ returns: { count: returnsCount, amount: formatSekOre(returnsGrossOre) },
163
+ vatLines,
164
+ vatTotal: formatSekOre(vatTotalOre),
165
+ netExVat: formatSekOre(netExVatOre),
166
+ grandTotals: {
167
+ sales: formatSekOre(gtSalesOre),
168
+ returns: formatSekOre(gtReturnsOre),
169
+ net: formatSekOre(gtNetOre),
170
+ },
171
+ certified,
172
+ ore: {
173
+ salesGross: salesGrossOre,
174
+ discounts: discountsOre,
175
+ returns: returnsGrossOre,
176
+ vatTotal: vatTotalOre,
177
+ netExVat: netExVatOre,
178
+ grandTotalSales: gtSalesOre,
179
+ grandTotalReturns: gtReturnsOre,
180
+ grandTotalNet: gtNetOre,
181
+ },
182
+ };
183
+ }
@@ -679,12 +679,15 @@ class OrderHelpers {
679
679
  })
680
680
  .reduce((acc, cur) => {
681
681
  if (cur.taxRate) {
682
- if (!acc[cur.taxRate]) {
683
- acc[cur.taxRate] = 0;
682
+ // Key by whole percent (25), matching the backend ReceiptBuilder and the
683
+ // canonical "Moms 25%" display format, so every receipt path is consistent.
684
+ const ratePercent = Math.round(Number(cur.taxRate) * 100);
685
+ if (!acc[ratePercent]) {
686
+ acc[ratePercent] = 0;
684
687
  }
685
688
  const amount = Number(cur.taxAmount);
686
- acc[cur.taxRate] += isRefund ? -Math.abs(amount) : amount;
687
- acc[cur.taxRate] = parseFloat(acc[cur.taxRate].toFixed(2));
689
+ acc[ratePercent] += isRefund ? -Math.abs(amount) : amount;
690
+ acc[ratePercent] = parseFloat(acc[ratePercent].toFixed(2));
688
691
  }
689
692
  return acc;
690
693
  }, {});
@@ -7,9 +7,12 @@ export type ReceiptPreviewLine = {
7
7
  };
8
8
  export default class ReceiptXmlHelpers {
9
9
  static escapeXML(text: any): string;
10
+ static vatRatePercent(rate: any): number;
10
11
  private static variantLabelForReceipt;
11
12
  static formatLineItemXML(lineItem: any): string;
12
13
  static buildReceiptContentXML(receiptData: any): string;
14
+ private static padRow;
15
+ private static negateAmount;
13
16
  static buildReportReceiptXML(data: any): string;
14
17
  private static decodeXML;
15
18
  private static parseAttrs;
@@ -18,6 +18,17 @@ class ReceiptXmlHelpers {
18
18
  .replace(/"/g, '&quot;')
19
19
  .replace(/'/g, '&#39;');
20
20
  }
21
+ // Canonical VAT-rate-for-display normalizer — the single source of truth for how
22
+ // a VAT rate is shown on EVERY receipt/report ("Moms 25%"). Accepts a fraction
23
+ // (0.25), a whole percent (25), or their string forms, and always returns the
24
+ // integer percent (25). A value in (0,1) is treated as a fraction; >=1 is already
25
+ // a percent. Covers every Swedish rate (0, 6, 12, 25) from either representation.
26
+ static vatRatePercent(rate) {
27
+ const n = Math.abs(Number(rate));
28
+ if (!n || Number.isNaN(n))
29
+ return 0;
30
+ return Math.round(n < 1 ? n * 100 : n);
31
+ }
21
32
  // Variant label for a receipt line (e.g. "M · R"), delegated to the shared
22
33
  // helper so cart, order detail, and receipt render the same rule.
23
34
  static variantLabelForReceipt(lineItem) {
@@ -131,7 +142,7 @@ class ReceiptXmlHelpers {
131
142
  if (receiptData.taxRates && typeof receiptData.taxRates === 'object') {
132
143
  receipt += '<text font="font_a" width="1" height="1">';
133
144
  Object.entries(receiptData.taxRates).forEach(([taxRate, value]) => {
134
- const rate = parseFloat(String(taxRate).trim());
145
+ const rate = this.vatRatePercent(taxRate);
135
146
  receipt += `Moms ${this.escapeXML(String(rate))}%: ${this.escapeXML(value)}`;
136
147
  receipt += '</text><feed/><text font="font_a" width="1" height="1">';
137
148
  });
@@ -231,83 +242,135 @@ class ReceiptXmlHelpers {
231
242
  receipt += '</epos-print>';
232
243
  return receipt;
233
244
  }
234
- // Render an X/Z daily report (X-rapport / Z-rapport) as ePOS-Print XML, so the
235
- // report prints on the same thermal printer and renders in the same receipt
236
- // simulator (ReceiptPaper) — as sales receipts. Pure; driven entirely by `data`:
237
- // { reportType: 'X'|'Z', merchantName, organizationNumber, storeAddress,
238
- // terminalName, date, sequenceNumber, receiptCount, totalSales, netTotal,
239
- // vatRates: { '25': '32,00 kr', ... }, controlCode?, controlUnitSerial? }
245
+ // Pad a label (left) and value (right) into a fixed 42-col row, so amounts line
246
+ // up on the right edge of the paper. Label is truncated if it would collide.
247
+ static padRow(label, value) {
248
+ const width = 42;
249
+ let l = String(label);
250
+ const v = String(value);
251
+ let space = width - l.length - v.length;
252
+ if (space < 1) {
253
+ l = l.slice(0, Math.max(0, width - v.length - 1));
254
+ space = Math.max(1, width - l.length - v.length);
255
+ }
256
+ return l + ' '.repeat(space) + v;
257
+ }
258
+ // Prefix a formatted amount with a minus (for discounts/returns), unless it is
259
+ // already negative. Callers guard against zero amounts.
260
+ static negateAmount(amount) {
261
+ const a = String(amount || '');
262
+ return a.indexOf('-') === 0 ? a : `-${a}`;
263
+ }
264
+ // Render a Swedish "gold standard" X/Z daily report (X-rapport / Z-dagrapport)
265
+ // as ePOS-Print XML — prints on the same thermal printer and renders in the same
266
+ // simulator (ReceiptPaper) as sales receipts. Pure; a dumb renderer of the data
267
+ // object produced by FiscalReportHelpers.buildDailyReport plus identity fields:
268
+ // { reportType:'X'|'Z', merchantName, organizationNumber, storeAddress,
269
+ // cashRegisterName|terminalName, date, sequenceNumber,
270
+ // receiptCount, goodsCount, copiesCount, trainingCount, incompleteCount,
271
+ // drawerOpenCount,
272
+ // paymentBreakdown:[{label,count,amount}],
273
+ // salesGross, discounts, returns:{count,amount},
274
+ // vatLines:[{rate,amount}], netExVat,
275
+ // grandTotals:{sales,returns,net},
276
+ // certified, controlUnitSerial?, controlCode? }
277
+ // Covers SKVFS 2014:9 (7 kap. 2-3 §) + the 2021:17 grand totals. When certified
278
+ // is falsy (no srv4pos control unit yet) it stamps a PRELIMINÄR RAPPORT notice so
279
+ // the report never masquerades as a registered kassaregister dagrapport.
280
+ // Back-compat: older callers passing totalSales/netTotal/vatRates still render.
240
281
  static buildReportReceiptXML(data) {
241
- var _a;
242
282
  const d = data || {};
243
283
  const isZ = String(d.reportType || d.type || '')
244
284
  .toUpperCase()
245
- .includes('Z');
246
- const label = isZ ? 'Z-RAPPORT' : 'X-RAPPORT';
285
+ .indexOf('Z') !== -1;
286
+ const label = isZ ? 'Z-DAGRAPPORT' : 'X-DAGRAPPORT';
247
287
  const divider = '----------------------------------------';
248
288
  const hasSeq = d.sequenceNumber !== undefined && d.sequenceNumber !== null && d.sequenceNumber !== '';
289
+ const kassabeteckning = d.cashRegisterName || d.terminalName || '';
290
+ // Back-compat mapping for older inputs.
291
+ const salesGross = d.salesGross || d.totalSales || '0,00 kr';
292
+ const netExVat = d.netExVat || d.netTotal || '';
293
+ let vatLines = d.vatLines;
294
+ if (!vatLines && d.vatRates && typeof d.vatRates === 'object') {
295
+ vatLines = Object.keys(d.vatRates).map((rate) => ({
296
+ rate: parseFloat(String(rate)),
297
+ amount: d.vatRates[rate],
298
+ }));
299
+ }
300
+ vatLines = vatLines || [];
301
+ // em is sticky in ePOS-Print, so always emit it explicitly (true/false) to stop
302
+ // a bold line bleeding into the rows that follow; width="1" likewise resets the
303
+ // sticky double-width left by the title.
304
+ const row = (lbl, value, em) => `<text font="font_a" width="1" height="1" em="${em ? 'true' : 'false'}">` +
305
+ `${this.escapeXML(this.padRow(lbl, value))}</text><feed/>`;
306
+ const line = (text, em) => `<text font="font_a" width="1" height="1" em="${em ? 'true' : 'false'}">` +
307
+ `${this.escapeXML(text)}</text><feed/>`;
308
+ const dividerLine = `<text font="font_a" width="1" height="1" em="false">${divider}</text><feed/>`;
249
309
  let receipt = '<epos-print xmlns="http://www.epson-pos.com/schemas/2011/03/epos-print">';
250
310
  receipt += '<text align="center"/>';
251
- receipt += '<text font="font_a" width="2" height="2">';
252
- receipt += this.escapeXML(d.merchantName || '');
253
- receipt += '</text><feed/>';
254
- if (d.organizationNumber) {
255
- receipt += '<text font="font_a" width="1" height="1">';
256
- receipt += `Org.nr: ${this.escapeXML(d.organizationNumber)}`;
257
- receipt += '</text><feed/>';
258
- }
259
- receipt += '<feed/>';
260
- if (d.storeAddress) {
261
- receipt += '<text font="font_a" width="1" height="1">';
262
- receipt += this.escapeXML(d.storeAddress);
263
- receipt += '</text><feed/>';
264
- }
265
- receipt += '<text font="font_a" width="2" height="2">';
266
- receipt += hasSeq ? `${label} # ${this.escapeXML(d.sequenceNumber)}` : label;
267
- receipt += '</text><feed/>';
268
- if (d.date) {
269
- receipt += '<text font="font_a" width="1" height="1">';
270
- receipt += this.escapeXML(d.date);
271
- receipt += '</text><feed/>';
272
- }
311
+ // --- Header ---
312
+ receipt += `<text font="font_a" width="2" height="2">${this.escapeXML(d.merchantName || '')}</text><feed/>`;
313
+ if (d.organizationNumber)
314
+ receipt += line(`Org.nr: ${d.organizationNumber}`);
315
+ if (d.storeAddress)
316
+ receipt += line(String(d.storeAddress));
273
317
  receipt += '<feed/>';
274
- receipt += `<text>${divider}</text><feed/>`;
275
- receipt += '<text font="font_a" width="1" height="1">';
276
- receipt += `Antal kvitton: ${this.escapeXML(String((_a = d.receiptCount) !== null && _a !== void 0 ? _a : 0))}`;
277
- receipt += '</text><feed/>';
278
- receipt += `<text>${divider}</text><feed/>`;
279
- receipt += '<text font="font_a" width="1" height="1" em="true">';
280
- receipt += `Forsaljning: ${this.escapeXML(d.totalSales || '0,00 kr')}`;
281
- receipt += '</text><feed/>';
282
- if (d.vatRates && typeof d.vatRates === 'object') {
283
- Object.entries(d.vatRates).forEach(([rate, value]) => {
284
- const r = parseFloat(String(rate).trim());
285
- receipt += '<text font="font_a" width="1" height="1">';
286
- receipt += `Moms ${this.escapeXML(String(r))}%: ${this.escapeXML(String(value))}`;
287
- receipt += '</text><feed/>';
318
+ receipt += `<text font="font_a" width="2" height="2">${this.escapeXML(label)}</text><feed/>`;
319
+ if (isZ && hasSeq)
320
+ receipt += line(`Z-nummer: ${d.sequenceNumber}`);
321
+ if (kassabeteckning)
322
+ receipt += line(`Kassabeteckning: ${kassabeteckning}`);
323
+ if (d.date)
324
+ receipt += line(String(d.date));
325
+ // --- Counts ---
326
+ receipt += '<feed/>' + dividerLine;
327
+ receipt += row('Antal kvitton:', String(d.receiptCount || 0));
328
+ if (d.goodsCount !== undefined)
329
+ receipt += row('Antal varor:', String(d.goodsCount));
330
+ receipt += row('Antal kvittokopior:', String(d.copiesCount || 0));
331
+ receipt += row('Antal övningskvitton:', String(d.trainingCount || 0));
332
+ receipt += row('Antal ofullständiga köp:', String(d.incompleteCount || 0));
333
+ receipt += row('Lådöppningar:', String(d.drawerOpenCount || 0));
334
+ // --- Sales per payment method ---
335
+ if (Array.isArray(d.paymentBreakdown) && d.paymentBreakdown.length) {
336
+ receipt += dividerLine;
337
+ receipt += line('Försäljning per betalsätt', true);
338
+ d.paymentBreakdown.forEach((p) => {
339
+ receipt += row(` ${p.label}:`, String(p.amount));
288
340
  });
289
341
  }
290
- if (d.netTotal) {
291
- receipt += '<text font="font_a" width="1" height="1">';
292
- receipt += `Netto: ${this.escapeXML(d.netTotal)}`;
293
- receipt += '</text><feed/>';
342
+ // --- Totals ---
343
+ receipt += dividerLine;
344
+ receipt += row('Försäljning brutto:', salesGross, true);
345
+ if (d.discounts && d.discounts !== '0,00 kr') {
346
+ receipt += row('Rabatter:', this.negateAmount(d.discounts));
294
347
  }
295
- receipt += '<feed/>';
296
- receipt += `<text>${divider}</text><feed/>`;
297
- if (d.controlCode) {
298
- receipt += '<text font="font_a" width="1" height="1">';
299
- receipt += `Kontrollkod: ${this.escapeXML(d.controlCode)}`;
300
- receipt += '</text><feed/>';
301
- }
302
- if (d.terminalName) {
303
- receipt += '<text font="font_a" width="1" height="1">';
304
- receipt += `Terminal: ${this.escapeXML(d.terminalName)}`;
305
- receipt += '</text><feed/>';
348
+ if (d.returns && (d.returns.count || (d.returns.amount && d.returns.amount !== '0,00 kr'))) {
349
+ receipt += row(`Returer (${d.returns.count || 0} st):`, this.negateAmount(d.returns.amount));
306
350
  }
307
- if (d.controlUnitSerial) {
308
- receipt += '<text font="font_a" width="1" height="1">';
309
- receipt += `Kontrollenhet: ${this.escapeXML(d.controlUnitSerial)}`;
310
- receipt += '</text><feed/>';
351
+ // --- VAT ---
352
+ receipt += dividerLine;
353
+ vatLines.forEach((v) => {
354
+ receipt += row(`Moms ${this.escapeXML(String(this.vatRatePercent(v.rate)))}%:`, String(v.amount));
355
+ });
356
+ if (netExVat)
357
+ receipt += row('Netto (exkl. moms):', netExVat, true);
358
+ // --- Grand totals (ackumulerande; SKVFS 2021:17) ---
359
+ if (d.grandTotals) {
360
+ receipt += dividerLine;
361
+ receipt += row('Grand total försäljning:', String(d.grandTotals.sales || '0,00 kr'));
362
+ receipt += row('Grand total retur:', String(d.grandTotals.returns || '0,00 kr'));
363
+ receipt += row('Grand total netto:', String(d.grandTotals.net || '0,00 kr'));
364
+ }
365
+ // --- Control unit ---
366
+ receipt += dividerLine;
367
+ receipt += row('Kontrollenhet:', d.controlUnitSerial || '-');
368
+ receipt += row('Kontrollkod:', d.controlCode || '-');
369
+ // --- Pre-approval notice ---
370
+ if (!d.certified) {
371
+ receipt += '<feed/>';
372
+ receipt += line('PRELIMINÄR RAPPORT');
373
+ receipt += line('Kassaregister ej aktiverat');
311
374
  }
312
375
  receipt += '<cut type="feed"/>';
313
376
  receipt += '</epos-print>';
package/lib/index.d.ts CHANGED
@@ -49,6 +49,7 @@ import * as MarketplaceLineDisplayHelpers from './helpers/marketplace/Marketplac
49
49
  import * as MarketplaceOrderHelpers from './helpers/marketplace/MarketplaceOrderHelpers';
50
50
  import * as MarketplaceReceiptHelpers from './helpers/marketplace/MarketplaceReceiptHelpers';
51
51
  import * as FiscalHelpers from './helpers/FiscalHelpers';
52
+ import * as FiscalReportHelpers from './helpers/FiscalReportHelpers';
52
53
  import TaxHelpers from './helpers/TaxHelpers';
53
54
  import PricingHelpers from './helpers/PricingHelpers';
54
55
  import ProductSkuHelpers, { DEFAULT_SKU_LENGTH, SKU_ALPHABET } from './helpers/ProductSkuHelpers';
@@ -59,7 +60,7 @@ import ProductInventoryHelpers, { PLACEHOLDER_VARIANT_ID } from './helpers/Produ
59
60
  import RetailProductFactory from './factories/Product/RetailProductFactory';
60
61
  import { FALLBACK_TAX_COUNTRY_CODE, TAX_CATEGORIES, TAX_CATEGORY_ALIASES, TAX_CATEGORY_BASE_LABELS, TAX_CATEGORY_RATES } from './constants/tax-constants';
61
62
  import { ADMIN_SIMPLIFIED_STATES, ADMIN_STATES, PRODUCT_CLASS_VALUES, PRODUCT_STATES, PRODUCT_STATE_LABELS, RETAIL_EDITABLE_PRODUCT_STATES, getProductStateLabel, getRetailEditableProductStateOptions } from './constants/product-constants';
62
- export { RibbnFile, ProductFromAlgoliaJSONDoc, ProductFromShopifyWebhookJSONDoc, ProductFromShopifyJSClientJSONDoc, ProductFormStateFactory, ProductCollectionFormStateFactory, ProductCollectionHelpers, SellRequestFormStateFactory, PickUpFormStateFactory, AddressFormStateFactory, UserFormStateFactory, UserFromShopifyWebhookJSONDoc, UserFromAuthTemplateMethod, DraftOrderFromMirakl, DraftOrderFromApp, ChatRoomFromNewUserSignUp, KlarnaSessionFactory, Product, ProductStateManager, Order, OrderFromShopifyWebhook, OrderFromShopifyAdminApi, OrderFromShopifyStorefrontApi, OrderFromApp, OrderFromTraderaEmail, OrderFormStateFactory, RefundFormStateFactory, PayoutAccount, PayoutAccountFormState, Merchant, MERCHANT_ACCOUNT_MODES, MERCHANT_ACCOUNT_CLASSES, MERCHANT_RELATIONSHIP_TYPES, MARKETPLACE_SELLER_CAPABILITIES, DEFAULT_MARKETPLACE_SELLER_CAPABILITIES, MerchantFormState, MerchantFormStateFactory, MerchantConfigAutomatedPayouts, MerchantConfigAutomatedPayoutsFormState, ReRobeProductHelpers, MarketplaceProductHelpers, MarketplaceLedgerHelpers, MarketplaceLegacyAdapters, MarketplaceLineDisplayHelpers, MarketplaceOrderHelpers, MarketplaceReceiptHelpers, FiscalHelpers, MARKETPLACE_LISTING_STATUSES, MARKETPLACE_COMMISSION_BASES, MARKETPLACE_RELATIONSHIP_TYPES, MARKETPLACE_FEE_TYPES, MARKETPLACE_FEE_ALLOCATION_BASES, MARKETPLACE_LEDGER_STATUSES, MARKETPLACE_INVENTORY_POLICY_KINDS, MARKETPLACE_SELLER_DEFAULT_COMMISSION, STOCK_STATUSES, DEFAULT_MARKETPLACE_LISTING_STATUS, DEFAULT_MARKETPLACE_COMMISSION_BASIS, DEFAULT_MARKETPLACE_RELATIONSHIP_TYPE, DEFAULT_STRIPE_FEE_ALLOCATION_BASIS, DEFAULT_RIBBN_FEE_ALLOCATION_BASIS, DEFAULT_MARKETPLACE_LEDGER_STATUS, User, AnalyticsHelpers, OrderHelpers, WebhookFormState, MerchantWebPage, ShopifyMerchantAccount, CustomerNotification, TaxHelpers, PricingHelpers, ProductSkuHelpers, ProductVariantHelpers, ReturnPolicyHelpers, ReceiptXmlHelpers, ProductInventoryHelpers, RetailProductFactory, SKU_ALPHABET, DEFAULT_SKU_LENGTH, PLACEHOLDER_VARIANT_ID, TAX_CATEGORIES, TAX_CATEGORY_RATES, TAX_CATEGORY_BASE_LABELS, TAX_CATEGORY_ALIASES, FALLBACK_TAX_COUNTRY_CODE, PRODUCT_STATES, PRODUCT_STATE_LABELS, PRODUCT_CLASS_VALUES, ADMIN_STATES, ADMIN_SIMPLIFIED_STATES, RETAIL_EDITABLE_PRODUCT_STATES, getProductStateLabel, getRetailEditableProductStateOptions, };
63
+ export { RibbnFile, ProductFromAlgoliaJSONDoc, ProductFromShopifyWebhookJSONDoc, ProductFromShopifyJSClientJSONDoc, ProductFormStateFactory, ProductCollectionFormStateFactory, ProductCollectionHelpers, SellRequestFormStateFactory, PickUpFormStateFactory, AddressFormStateFactory, UserFormStateFactory, UserFromShopifyWebhookJSONDoc, UserFromAuthTemplateMethod, DraftOrderFromMirakl, DraftOrderFromApp, ChatRoomFromNewUserSignUp, KlarnaSessionFactory, Product, ProductStateManager, Order, OrderFromShopifyWebhook, OrderFromShopifyAdminApi, OrderFromShopifyStorefrontApi, OrderFromApp, OrderFromTraderaEmail, OrderFormStateFactory, RefundFormStateFactory, PayoutAccount, PayoutAccountFormState, Merchant, MERCHANT_ACCOUNT_MODES, MERCHANT_ACCOUNT_CLASSES, MERCHANT_RELATIONSHIP_TYPES, MARKETPLACE_SELLER_CAPABILITIES, DEFAULT_MARKETPLACE_SELLER_CAPABILITIES, MerchantFormState, MerchantFormStateFactory, MerchantConfigAutomatedPayouts, MerchantConfigAutomatedPayoutsFormState, ReRobeProductHelpers, MarketplaceProductHelpers, MarketplaceLedgerHelpers, MarketplaceLegacyAdapters, MarketplaceLineDisplayHelpers, MarketplaceOrderHelpers, MarketplaceReceiptHelpers, FiscalHelpers, FiscalReportHelpers, MARKETPLACE_LISTING_STATUSES, MARKETPLACE_COMMISSION_BASES, MARKETPLACE_RELATIONSHIP_TYPES, MARKETPLACE_FEE_TYPES, MARKETPLACE_FEE_ALLOCATION_BASES, MARKETPLACE_LEDGER_STATUSES, MARKETPLACE_INVENTORY_POLICY_KINDS, MARKETPLACE_SELLER_DEFAULT_COMMISSION, STOCK_STATUSES, DEFAULT_MARKETPLACE_LISTING_STATUS, DEFAULT_MARKETPLACE_COMMISSION_BASIS, DEFAULT_MARKETPLACE_RELATIONSHIP_TYPE, DEFAULT_STRIPE_FEE_ALLOCATION_BASIS, DEFAULT_RIBBN_FEE_ALLOCATION_BASIS, DEFAULT_MARKETPLACE_LEDGER_STATUS, User, AnalyticsHelpers, OrderHelpers, WebhookFormState, MerchantWebPage, ShopifyMerchantAccount, CustomerNotification, TaxHelpers, PricingHelpers, ProductSkuHelpers, ProductVariantHelpers, ReturnPolicyHelpers, ReceiptXmlHelpers, ProductInventoryHelpers, RetailProductFactory, SKU_ALPHABET, DEFAULT_SKU_LENGTH, PLACEHOLDER_VARIANT_ID, TAX_CATEGORIES, TAX_CATEGORY_RATES, TAX_CATEGORY_BASE_LABELS, TAX_CATEGORY_ALIASES, FALLBACK_TAX_COUNTRY_CODE, PRODUCT_STATES, PRODUCT_STATE_LABELS, PRODUCT_CLASS_VALUES, ADMIN_STATES, ADMIN_SIMPLIFIED_STATES, RETAIL_EDITABLE_PRODUCT_STATES, getProductStateLabel, getRetailEditableProductStateOptions, };
63
64
  export type { PlaceholderVariantInput, PlaceholderVariantInventory, PlaceholderVariantLocation, } from './helpers/ProductInventoryHelpers';
64
65
  export type { RetailOwnershipInput, RetailAccountingInput, RetailLocationInput, ClothingDetailsInput, NonClothingDetailsInput, OneOfAKindClothingInput, MultivariantClothingInput, TrackedRetailInput, UntrackedRetailInput, } from './factories/Product/RetailProductFactory';
65
66
  export type { TaxCategoryValue } from './constants/tax-constants';
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MARKETPLACE_RELATIONSHIP_TYPES = exports.MARKETPLACE_COMMISSION_BASES = exports.MARKETPLACE_LISTING_STATUSES = exports.FiscalHelpers = exports.MarketplaceReceiptHelpers = exports.MarketplaceOrderHelpers = exports.MarketplaceLineDisplayHelpers = exports.MarketplaceLegacyAdapters = exports.MarketplaceLedgerHelpers = exports.MarketplaceProductHelpers = exports.ReRobeProductHelpers = exports.MerchantConfigAutomatedPayoutsFormState = exports.MerchantConfigAutomatedPayouts = exports.MerchantFormStateFactory = exports.MerchantFormState = exports.DEFAULT_MARKETPLACE_SELLER_CAPABILITIES = exports.MARKETPLACE_SELLER_CAPABILITIES = exports.MERCHANT_RELATIONSHIP_TYPES = exports.MERCHANT_ACCOUNT_CLASSES = exports.MERCHANT_ACCOUNT_MODES = exports.Merchant = exports.PayoutAccountFormState = exports.PayoutAccount = exports.RefundFormStateFactory = exports.OrderFormStateFactory = exports.OrderFromTraderaEmail = exports.OrderFromApp = exports.OrderFromShopifyStorefrontApi = exports.OrderFromShopifyAdminApi = exports.OrderFromShopifyWebhook = exports.Order = exports.ProductStateManager = exports.Product = exports.KlarnaSessionFactory = exports.ChatRoomFromNewUserSignUp = exports.DraftOrderFromApp = exports.DraftOrderFromMirakl = exports.UserFromAuthTemplateMethod = exports.UserFromShopifyWebhookJSONDoc = exports.UserFormStateFactory = exports.AddressFormStateFactory = exports.PickUpFormStateFactory = exports.SellRequestFormStateFactory = exports.ProductCollectionHelpers = exports.ProductCollectionFormStateFactory = exports.ProductFormStateFactory = exports.ProductFromShopifyJSClientJSONDoc = exports.ProductFromShopifyWebhookJSONDoc = exports.ProductFromAlgoliaJSONDoc = exports.RibbnFile = void 0;
4
- exports.getRetailEditableProductStateOptions = exports.getProductStateLabel = exports.RETAIL_EDITABLE_PRODUCT_STATES = exports.ADMIN_SIMPLIFIED_STATES = exports.ADMIN_STATES = exports.PRODUCT_CLASS_VALUES = exports.PRODUCT_STATE_LABELS = exports.PRODUCT_STATES = exports.FALLBACK_TAX_COUNTRY_CODE = exports.TAX_CATEGORY_ALIASES = exports.TAX_CATEGORY_BASE_LABELS = exports.TAX_CATEGORY_RATES = exports.TAX_CATEGORIES = exports.PLACEHOLDER_VARIANT_ID = exports.DEFAULT_SKU_LENGTH = exports.SKU_ALPHABET = exports.RetailProductFactory = exports.ProductInventoryHelpers = exports.ReceiptXmlHelpers = exports.ReturnPolicyHelpers = exports.ProductVariantHelpers = exports.ProductSkuHelpers = exports.PricingHelpers = exports.TaxHelpers = exports.CustomerNotification = exports.ShopifyMerchantAccount = exports.MerchantWebPage = exports.WebhookFormState = exports.OrderHelpers = exports.AnalyticsHelpers = exports.User = exports.DEFAULT_MARKETPLACE_LEDGER_STATUS = exports.DEFAULT_RIBBN_FEE_ALLOCATION_BASIS = exports.DEFAULT_STRIPE_FEE_ALLOCATION_BASIS = exports.DEFAULT_MARKETPLACE_RELATIONSHIP_TYPE = exports.DEFAULT_MARKETPLACE_COMMISSION_BASIS = exports.DEFAULT_MARKETPLACE_LISTING_STATUS = exports.STOCK_STATUSES = exports.MARKETPLACE_SELLER_DEFAULT_COMMISSION = exports.MARKETPLACE_INVENTORY_POLICY_KINDS = exports.MARKETPLACE_LEDGER_STATUSES = exports.MARKETPLACE_FEE_ALLOCATION_BASES = exports.MARKETPLACE_FEE_TYPES = void 0;
3
+ exports.MARKETPLACE_COMMISSION_BASES = exports.MARKETPLACE_LISTING_STATUSES = exports.FiscalReportHelpers = exports.FiscalHelpers = exports.MarketplaceReceiptHelpers = exports.MarketplaceOrderHelpers = exports.MarketplaceLineDisplayHelpers = exports.MarketplaceLegacyAdapters = exports.MarketplaceLedgerHelpers = exports.MarketplaceProductHelpers = exports.ReRobeProductHelpers = exports.MerchantConfigAutomatedPayoutsFormState = exports.MerchantConfigAutomatedPayouts = exports.MerchantFormStateFactory = exports.MerchantFormState = exports.DEFAULT_MARKETPLACE_SELLER_CAPABILITIES = exports.MARKETPLACE_SELLER_CAPABILITIES = exports.MERCHANT_RELATIONSHIP_TYPES = exports.MERCHANT_ACCOUNT_CLASSES = exports.MERCHANT_ACCOUNT_MODES = exports.Merchant = exports.PayoutAccountFormState = exports.PayoutAccount = exports.RefundFormStateFactory = exports.OrderFormStateFactory = exports.OrderFromTraderaEmail = exports.OrderFromApp = exports.OrderFromShopifyStorefrontApi = exports.OrderFromShopifyAdminApi = exports.OrderFromShopifyWebhook = exports.Order = exports.ProductStateManager = exports.Product = exports.KlarnaSessionFactory = exports.ChatRoomFromNewUserSignUp = exports.DraftOrderFromApp = exports.DraftOrderFromMirakl = exports.UserFromAuthTemplateMethod = exports.UserFromShopifyWebhookJSONDoc = exports.UserFormStateFactory = exports.AddressFormStateFactory = exports.PickUpFormStateFactory = exports.SellRequestFormStateFactory = exports.ProductCollectionHelpers = exports.ProductCollectionFormStateFactory = exports.ProductFormStateFactory = exports.ProductFromShopifyJSClientJSONDoc = exports.ProductFromShopifyWebhookJSONDoc = exports.ProductFromAlgoliaJSONDoc = exports.RibbnFile = void 0;
4
+ exports.getRetailEditableProductStateOptions = exports.getProductStateLabel = exports.RETAIL_EDITABLE_PRODUCT_STATES = exports.ADMIN_SIMPLIFIED_STATES = exports.ADMIN_STATES = exports.PRODUCT_CLASS_VALUES = exports.PRODUCT_STATE_LABELS = exports.PRODUCT_STATES = exports.FALLBACK_TAX_COUNTRY_CODE = exports.TAX_CATEGORY_ALIASES = exports.TAX_CATEGORY_BASE_LABELS = exports.TAX_CATEGORY_RATES = exports.TAX_CATEGORIES = exports.PLACEHOLDER_VARIANT_ID = exports.DEFAULT_SKU_LENGTH = exports.SKU_ALPHABET = exports.RetailProductFactory = exports.ProductInventoryHelpers = exports.ReceiptXmlHelpers = exports.ReturnPolicyHelpers = exports.ProductVariantHelpers = exports.ProductSkuHelpers = exports.PricingHelpers = exports.TaxHelpers = exports.CustomerNotification = exports.ShopifyMerchantAccount = exports.MerchantWebPage = exports.WebhookFormState = exports.OrderHelpers = exports.AnalyticsHelpers = exports.User = exports.DEFAULT_MARKETPLACE_LEDGER_STATUS = exports.DEFAULT_RIBBN_FEE_ALLOCATION_BASIS = exports.DEFAULT_STRIPE_FEE_ALLOCATION_BASIS = exports.DEFAULT_MARKETPLACE_RELATIONSHIP_TYPE = exports.DEFAULT_MARKETPLACE_COMMISSION_BASIS = exports.DEFAULT_MARKETPLACE_LISTING_STATUS = exports.STOCK_STATUSES = exports.MARKETPLACE_SELLER_DEFAULT_COMMISSION = exports.MARKETPLACE_INVENTORY_POLICY_KINDS = exports.MARKETPLACE_LEDGER_STATUSES = exports.MARKETPLACE_FEE_ALLOCATION_BASES = exports.MARKETPLACE_FEE_TYPES = exports.MARKETPLACE_RELATIONSHIP_TYPES = void 0;
5
5
  const ProductFromShopifyJSClientJSONDoc_1 = require("./factories/Product/ProductFromShopifyJSClientJSONDoc");
6
6
  exports.ProductFromShopifyJSClientJSONDoc = ProductFromShopifyJSClientJSONDoc_1.default;
7
7
  const ProductFromAlgoliaJSONDoc_1 = require("./factories/Product/ProductFromAlgoliaJSONDoc");
@@ -122,6 +122,8 @@ const MarketplaceReceiptHelpers = require("./helpers/marketplace/MarketplaceRece
122
122
  exports.MarketplaceReceiptHelpers = MarketplaceReceiptHelpers;
123
123
  const FiscalHelpers = require("./helpers/FiscalHelpers");
124
124
  exports.FiscalHelpers = FiscalHelpers;
125
+ const FiscalReportHelpers = require("./helpers/FiscalReportHelpers");
126
+ exports.FiscalReportHelpers = FiscalReportHelpers;
125
127
  const TaxHelpers_1 = require("./helpers/TaxHelpers");
126
128
  exports.TaxHelpers = TaxHelpers_1.default;
127
129
  const PricingHelpers_1 = require("./helpers/PricingHelpers");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rerobe-js-orm",
3
- "version": "4.9.12",
3
+ "version": "4.9.14",
4
4
  "description": "ReRobe's Javascript ORM Framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",