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.
- package/lib/helpers/FiscalReportHelpers.d.ts +17 -0
- package/lib/helpers/FiscalReportHelpers.js +183 -0
- package/lib/helpers/OrderHelpers.js +7 -4
- package/lib/helpers/ReceiptXmlHelpers.d.ts +3 -0
- package/lib/helpers/ReceiptXmlHelpers.js +128 -65
- package/lib/index.d.ts +2 -1
- package/lib/index.js +4 -2
- package/package.json +1 -1
|
@@ -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
|
-
|
|
683
|
-
|
|
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[
|
|
687
|
-
acc[
|
|
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, '"')
|
|
19
19
|
.replace(/'/g, ''');
|
|
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 =
|
|
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
|
-
//
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
.
|
|
246
|
-
const label = isZ ? 'Z-
|
|
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
|
-
|
|
252
|
-
receipt += this.escapeXML(d.merchantName || '')
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
receipt +=
|
|
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>${
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
296
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
receipt +=
|
|
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.
|
|
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");
|