@fin.cx/einvoice 10.0.0 → 10.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/formats/base/base.decoder.d.ts +9 -1
- package/dist_ts/formats/base/base.decoder.js +24 -12
- package/dist_ts/formats/utils/date.value.d.ts +24 -0
- package/dist_ts/formats/utils/date.value.js +77 -12
- package/package.json +4 -4
- package/readme.md +6 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/formats/base/base.decoder.ts +24 -17
- package/ts/formats/utils/date.value.ts +84 -11
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@fin.cx/einvoice',
|
|
6
|
-
version: '10.0.
|
|
6
|
+
version: '10.0.1',
|
|
7
7
|
description: 'A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxrQkFBa0I7SUFDeEIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLDJKQUEySjtDQUN6SyxDQUFBIn0=
|
|
@@ -49,7 +49,15 @@ export declare abstract class BaseDecoder {
|
|
|
49
49
|
*/
|
|
50
50
|
protected parseCIIDate(dateStr: string, format?: string): number;
|
|
51
51
|
/**
|
|
52
|
-
* Parses a required CII date without inventing a fallback value
|
|
52
|
+
* Parses a required CII date without inventing a fallback value: the
|
|
53
|
+
* calendar day it states, as the UTC midnight of that day. Format 102
|
|
54
|
+
* (`YYYYMMDD`) is the one EN 16931 asks for; a date stated with a time of
|
|
55
|
+
* day in format 203 (`YYYYMMDDhhmm`) or 204 (`YYYYMMDDhhmmss`), or without a
|
|
56
|
+
* format code as an ISO 8601 date or date and time (`parseIsoDateTimeDay`),
|
|
57
|
+
* is read as the day it states, whatever the time of day and the zone.
|
|
58
|
+
* Format 610 (`YYYYMM`) is read as the 1st of the month. Any other value is
|
|
59
|
+
* refused with an `EInvoiceParsingError`: no day is guessed, and none is read
|
|
60
|
+
* in the zone of the machine.
|
|
53
61
|
*/
|
|
54
62
|
protected parseRequiredCIIDate(dateStr: string, format?: string): number;
|
|
55
63
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ValidationLevel } from '../../interfaces/common.js';
|
|
2
2
|
import { EInvoiceParsingError } from '../../errors.js';
|
|
3
|
-
import { parseXsdDateDay } from '../utils/date.value.js';
|
|
3
|
+
import { parseCiiDateDay, parseIsoDateTimeDay, parseXsdDateDay } from '../utils/date.value.js';
|
|
4
4
|
import { statedValueProblemMessage } from '../utils/stated.values.js';
|
|
5
5
|
/**
|
|
6
6
|
* Base decoder class that defines common decoding functionality
|
|
@@ -82,7 +82,15 @@ export class BaseDecoder {
|
|
|
82
82
|
return isNaN(parsed) ? Date.now() : parsed;
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
|
-
* Parses a required CII date without inventing a fallback value
|
|
85
|
+
* Parses a required CII date without inventing a fallback value: the
|
|
86
|
+
* calendar day it states, as the UTC midnight of that day. Format 102
|
|
87
|
+
* (`YYYYMMDD`) is the one EN 16931 asks for; a date stated with a time of
|
|
88
|
+
* day in format 203 (`YYYYMMDDhhmm`) or 204 (`YYYYMMDDhhmmss`), or without a
|
|
89
|
+
* format code as an ISO 8601 date or date and time (`parseIsoDateTimeDay`),
|
|
90
|
+
* is read as the day it states, whatever the time of day and the zone.
|
|
91
|
+
* Format 610 (`YYYYMM`) is read as the 1st of the month. Any other value is
|
|
92
|
+
* refused with an `EInvoiceParsingError`: no day is guessed, and none is read
|
|
93
|
+
* in the zone of the machine.
|
|
86
94
|
*/
|
|
87
95
|
parseRequiredCIIDate(dateStr, format) {
|
|
88
96
|
const value = dateStr.trim();
|
|
@@ -91,25 +99,29 @@ export class BaseDecoder {
|
|
|
91
99
|
format: 'cii',
|
|
92
100
|
});
|
|
93
101
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
const formatCode = (format ?? '').trim();
|
|
103
|
+
if (formatCode === '102' || formatCode === '203' || formatCode === '204') {
|
|
104
|
+
const day = parseCiiDateDay(value, formatCode);
|
|
105
|
+
if (day === undefined) {
|
|
106
|
+
throw this.createInvalidDateError(value, `CII format ${formatCode}`);
|
|
98
107
|
}
|
|
99
|
-
return
|
|
108
|
+
return day;
|
|
100
109
|
}
|
|
101
|
-
if (
|
|
110
|
+
if (formatCode === '610') {
|
|
102
111
|
const match = /^(\d{4})(\d{2})$/.exec(value);
|
|
103
112
|
if (!match) {
|
|
104
113
|
throw this.createInvalidDateError(value, 'CII format 610');
|
|
105
114
|
}
|
|
106
115
|
return this.createStrictUtcTimestamp(Number(match[1]), Number(match[2]), 1, value, 'CII format 610');
|
|
107
116
|
}
|
|
108
|
-
|
|
109
|
-
|
|
117
|
+
if (formatCode) {
|
|
118
|
+
throw this.createInvalidDateError(value, `CII format ${formatCode}`);
|
|
119
|
+
}
|
|
120
|
+
const day = parseIsoDateTimeDay(value);
|
|
121
|
+
if (day === undefined) {
|
|
110
122
|
throw this.createInvalidDateError(value, 'CII');
|
|
111
123
|
}
|
|
112
|
-
return
|
|
124
|
+
return day;
|
|
113
125
|
}
|
|
114
126
|
/**
|
|
115
127
|
* Parses a required UBL xsd:date value strictly: the calendar day it states,
|
|
@@ -142,4 +154,4 @@ export class BaseDecoder {
|
|
|
142
154
|
return new EInvoiceParsingError(`Invalid required invoice issue date "${value}" for ${format}`, { format });
|
|
143
155
|
}
|
|
144
156
|
}
|
|
145
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
157
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFzZS5kZWNvZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvZm9ybWF0cy9iYXNlL2Jhc2UuZGVjb2Rlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFN0QsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDdkQsT0FBTyxFQUFFLGVBQWUsRUFBRSxtQkFBbUIsRUFBRSxlQUFlLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUUvRixPQUFPLEVBQUUseUJBQXlCLEVBQTBCLE1BQU0sMkJBQTJCLENBQUM7QUFFOUY7OztHQUdHO0FBQ0gsTUFBTSxPQUFnQixXQUFXO0lBQ3JCLEdBQUcsQ0FBUztJQUNaLGNBQWMsQ0FBVTtJQUVsQyxZQUFZLEdBQVcsRUFBRSxpQkFBMEIsS0FBSztRQUN0RCxJQUFJLENBQUMsR0FBRyxHQUFHLEdBQUcsQ0FBQztRQUNmLElBQUksQ0FBQyxjQUFjLEdBQUcsY0FBYyxDQUFDO0lBQ3ZDLENBQUM7SUFRRDs7O09BR0c7SUFDSSxLQUFLLENBQUMsYUFBYTtRQUN4QixPQUFPLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztJQUN2QixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksTUFBTTtRQUNYLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQztJQUNsQixDQUFDO0lBRUQ7O09BRUc7SUFDSSxpQkFBaUI7UUFDdEIsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxlQUFlO1FBQ3BCLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNPLDBCQUEwQixDQUFDLElBQXVCLEVBQUUsTUFBYztRQUMxRSxNQUFNLENBQUMsT0FBTyxDQUFDLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQztRQUNoQyxJQUFJLE9BQU8sRUFBRSxDQUFDO1lBQ1osTUFBTSxJQUFJ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
|
|
@@ -37,3 +37,27 @@ export declare const getWritableDueDate: (issueTimestamp: unknown, dueInDays: un
|
|
|
37
37
|
* @param value The date as the document states it
|
|
38
38
|
*/
|
|
39
39
|
export declare const parseXsdDateDay: (value: string) => number | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* The calendar day an ISO 8601 date, or date and time, names, as the UTC
|
|
42
|
+
* midnight of that day; undefined when the value is no such date. A CII date
|
|
43
|
+
* without a format code may be stated so. The value is `YYYY-MM-DD`,
|
|
44
|
+
* optionally followed by a time of day `Thh:mm`, `Thh:mm:ss` or
|
|
45
|
+
* `Thh:mm:ss.fff`, and then optionally by a time zone: `Z`, or `+hh:mm` /
|
|
46
|
+
* `-hh:mm` of at most 14:00. Neither the time of day nor the zone moves the
|
|
47
|
+
* day the document states: `2026-09-11T00:30:00+02:00` is 22:30 UTC on
|
|
48
|
+
* 10 September, but the document states 11 September, and that is the day an
|
|
49
|
+
* invoice is dated with (§ 14 Abs. 4 Satz 1 Nr. 3 UStG asks for the issue date,
|
|
50
|
+
* a calendar day).
|
|
51
|
+
* @param value The date as the document states it
|
|
52
|
+
*/
|
|
53
|
+
export declare const parseIsoDateTimeDay: (value: string) => number | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* The calendar day a CII date in format 102 (`YYYYMMDD`), 203
|
|
56
|
+
* (`YYYYMMDDhhmm`) or 204 (`YYYYMMDDhhmmss`) names, as the UTC midnight of that
|
|
57
|
+
* day; undefined when the value is no such date in that format. A time of day
|
|
58
|
+
* in formats 203 and 204 does not move the day stated; the formats carry no
|
|
59
|
+
* time zone.
|
|
60
|
+
* @param value The date as the document states it
|
|
61
|
+
* @param format The format code: '102', '203' or '204'
|
|
62
|
+
*/
|
|
63
|
+
export declare const parseCiiDateDay: (value: string, format: "102" | "203" | "204") => number | undefined;
|
|
@@ -47,6 +47,37 @@ export const getWritableDueDate = (issueTimestamp, dueInDays, targetFormat) => {
|
|
|
47
47
|
dueDate.setUTCDate(dueDate.getUTCDate() + dueInDays);
|
|
48
48
|
return dueDate;
|
|
49
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* The UTC midnight of a calendar day, or undefined when there is no such day
|
|
52
|
+
* @param year The year, 1 or later
|
|
53
|
+
* @param month The month, 1 to 12
|
|
54
|
+
* @param day The day of the month
|
|
55
|
+
*/
|
|
56
|
+
const utcMidnightOf = (year, month, day) => {
|
|
57
|
+
if (year < 1) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const date = new Date(0);
|
|
61
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
62
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return date.getTime();
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Whether a time of day is one: hours 0 to 23, minutes and seconds 0 to 59
|
|
69
|
+
* @param hours The hours
|
|
70
|
+
* @param minutes The minutes
|
|
71
|
+
* @param seconds The seconds, 0 when not stated
|
|
72
|
+
*/
|
|
73
|
+
const isTimeOfDay = (hours, minutes, seconds) => hours <= 23 && minutes <= 59 && seconds <= 59;
|
|
74
|
+
/**
|
|
75
|
+
* Whether a time zone offset is one an `xsd:date` or `xsd:dateTime` may carry:
|
|
76
|
+
* at most 14:00
|
|
77
|
+
* @param hours The hours of the offset
|
|
78
|
+
* @param minutes The minutes of the offset
|
|
79
|
+
*/
|
|
80
|
+
const isZoneOffset = (hours, minutes) => hours < 14 ? minutes <= 59 : hours === 14 && minutes === 0;
|
|
50
81
|
/**
|
|
51
82
|
* The calendar day a UBL `xsd:date` names (BT-2, BT-9, BT-26, BT-72, BT-73,
|
|
52
83
|
* BT-74), as the UTC midnight of that day; undefined when the value is no such
|
|
@@ -62,21 +93,55 @@ export const parseXsdDateDay = (value) => {
|
|
|
62
93
|
if (!match) {
|
|
63
94
|
return undefined;
|
|
64
95
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const [zoneHours, zoneMinutes] = [Number(match[4]), Number(match[5])];
|
|
68
|
-
if (zoneHours > 14 || zoneMinutes > 59 || (zoneHours === 14 && zoneMinutes !== 0)) {
|
|
69
|
-
return undefined;
|
|
70
|
-
}
|
|
96
|
+
if (match[4] !== undefined && !isZoneOffset(Number(match[4]), Number(match[5]))) {
|
|
97
|
+
return undefined;
|
|
71
98
|
}
|
|
72
|
-
|
|
99
|
+
return utcMidnightOf(Number(match[1]), Number(match[2]), Number(match[3]));
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* The calendar day an ISO 8601 date, or date and time, names, as the UTC
|
|
103
|
+
* midnight of that day; undefined when the value is no such date. A CII date
|
|
104
|
+
* without a format code may be stated so. The value is `YYYY-MM-DD`,
|
|
105
|
+
* optionally followed by a time of day `Thh:mm`, `Thh:mm:ss` or
|
|
106
|
+
* `Thh:mm:ss.fff`, and then optionally by a time zone: `Z`, or `+hh:mm` /
|
|
107
|
+
* `-hh:mm` of at most 14:00. Neither the time of day nor the zone moves the
|
|
108
|
+
* day the document states: `2026-09-11T00:30:00+02:00` is 22:30 UTC on
|
|
109
|
+
* 10 September, but the document states 11 September, and that is the day an
|
|
110
|
+
* invoice is dated with (§ 14 Abs. 4 Satz 1 Nr. 3 UStG asks for the issue date,
|
|
111
|
+
* a calendar day).
|
|
112
|
+
* @param value The date as the document states it
|
|
113
|
+
*/
|
|
114
|
+
export const parseIsoDateTimeDay = (value) => {
|
|
115
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?)?(?:Z|[+-](\d{2}):(\d{2}))?$/.exec(value.trim());
|
|
116
|
+
if (!match) {
|
|
73
117
|
return undefined;
|
|
74
118
|
}
|
|
75
|
-
|
|
76
|
-
date.setUTCFullYear(year, month - 1, day);
|
|
77
|
-
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
|
|
119
|
+
if (match[4] !== undefined && !isTimeOfDay(Number(match[4]), Number(match[5]), Number(match[6] ?? 0))) {
|
|
78
120
|
return undefined;
|
|
79
121
|
}
|
|
80
|
-
|
|
122
|
+
if (match[7] !== undefined && !isZoneOffset(Number(match[7]), Number(match[8]))) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
return utcMidnightOf(Number(match[1]), Number(match[2]), Number(match[3]));
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* The calendar day a CII date in format 102 (`YYYYMMDD`), 203
|
|
129
|
+
* (`YYYYMMDDhhmm`) or 204 (`YYYYMMDDhhmmss`) names, as the UTC midnight of that
|
|
130
|
+
* day; undefined when the value is no such date in that format. A time of day
|
|
131
|
+
* in formats 203 and 204 does not move the day stated; the formats carry no
|
|
132
|
+
* time zone.
|
|
133
|
+
* @param value The date as the document states it
|
|
134
|
+
* @param format The format code: '102', '203' or '204'
|
|
135
|
+
*/
|
|
136
|
+
export const parseCiiDateDay = (value, format) => {
|
|
137
|
+
const timeDigits = { '102': '', '203': '(\\d{2})(\\d{2})', '204': '(\\d{2})(\\d{2})(\\d{2})' }[format];
|
|
138
|
+
const match = new RegExp(`^(\\d{4})(\\d{2})(\\d{2})${timeDigits}$`).exec(value.trim());
|
|
139
|
+
if (!match) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
if (format !== '102' && !isTimeOfDay(Number(match[4]), Number(match[5]), Number(match[6] ?? 0))) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
return utcMidnightOf(Number(match[1]), Number(match[2]), Number(match[3]));
|
|
81
146
|
};
|
|
82
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
147
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0ZS52YWx1ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL2Zvcm1hdHMvdXRpbHMvZGF0ZS52YWx1ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUV0RDs7Ozs7OztHQU9HO0FBRUg7Ozs7Ozs7O0dBUUc7QUFDSCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQUcsQ0FBQyxTQUFrQixFQUFFLEtBQWEsRUFBRSxZQUFvQixFQUFRLEVBQUU7SUFDL0YsTUFBTSxJQUFJLEdBQUcsT0FBTyxTQUFTLEtBQUssUUFBUSxJQUFJLE1BQU0sQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDM0csSUFBSSxDQUFDLElBQUksSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxFQUFFLENBQUM7UUFDMUMsTUFBTSxJQUFJLG1CQUFtQixDQUFDLEdBQUcsS0FBSyxzQkFBc0IsTUFBTSxDQUFDLFNBQVMsQ0FBQyxFQUFFLEVBQUU7WUFDL0UsWUFBWTtZQUNaLG1CQUFtQixFQUFFLENBQUMsS0FBSyxDQUFDO1NBQzdCLENBQUMsQ0FBQztJQUNMLENBQUM7SUFDRCxPQUFPLElBQUksQ0FBQztBQUNkLENBQUMsQ0FBQztBQUVGOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsQ0FBQyxjQUF1QixFQUFFLFNBQWtCLEVBQUUsWUFBb0IsRUFBUSxFQUFFO0lBQzVHLE1BQU0sT0FBTyxHQUFHLGVBQWUsQ0FBQyxjQUFjLEVBQUUseUJBQXlCLEVBQUUsWUFBWSxDQUFDLENBQUM7SUFDekYsSUFBSSxPQUFPLFNBQVMsS0FBSyxRQUFRLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFD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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fin.cx/einvoice",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"@git.zone/tsbuild": "^5.0.0",
|
|
17
17
|
"@git.zone/tsbundle": "^2.15.0",
|
|
18
18
|
"@git.zone/tsrun": "^3.0.0",
|
|
19
|
-
"@git.zone/tstest": "^6.
|
|
19
|
+
"@git.zone/tstest": "^6.3.1",
|
|
20
20
|
"@push.rocks/smartfile": "^13.1.3",
|
|
21
|
-
"@types/node": "^26.6.
|
|
21
|
+
"@types/node": "^26.6.2"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@push.rocks/smartxml": "^2.1.0",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
],
|
|
68
68
|
"scripts": {
|
|
69
69
|
"test": "pnpm run test:unit",
|
|
70
|
-
"test:unit": "tstest test/test.cii-xrechnung.node.ts --verbose --logfile --timeout 60 && tstest test/test.einvoice.ts --verbose --logfile --timeout 60 && tstest test/test.decimal.ts --verbose --logfile --timeout 60 && tstest test/test.decimal-currency-calculator.ts --verbose --logfile --timeout 60 && tstest test/test.currency-utils.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-validators.ts --verbose --logfile --timeout 60 && tstest test/test.schematron-validator.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-creditnote.node.ts --verbose --logfile --timeout 60 && tstest test/test.accountingdoc-types.node.ts --verbose --logfile --timeout 60 && tstest test/test.corrected-invoice.node.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-3-due-date.node.ts --verbose --logfile --timeout 60 && tstest test/test.calendar-dates.node.ts --verbose --logfile --timeout 60 && tstest test/test.document-totals.node.ts --verbose --logfile --timeout 60 && tstest test/test.reverse-charge.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-schema-order.node.ts --verbose --logfile --timeout 60 && tstest test/test.tax-number.node.ts --verbose --logfile --timeout 60 && tstest test/test.country-codes.node.ts --verbose --logfile --timeout 60 && tstest test/test.paid-amount.node.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-unitcodes.node.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-lineunit.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-profile.node.ts --verbose --logfile --timeout 60 && tstest test/test.validator-rule-ids.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-period-address.node.ts --verbose --logfile --timeout 60 && tstest test/test.amount-due.node.ts --verbose --logfile --timeout 60 && tstest test/test.ubl-zoned-dates.node.ts --verbose --logfile --timeout 60 && tstest test/test.vat-categories.node.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-buyer-reference.node.ts --verbose --logfile --timeout 60 && tstest test/test.specification-identifier.node.ts --verbose --logfile --timeout 60 && tstest test/test.seller-identifier.node.ts --verbose --logfile --timeout 60 && tstest test/test.stated-values.node.ts --verbose --logfile --timeout 60 && tstest test/test.vat-category-limits.node.ts --verbose --logfile --timeout 60 && tstest test/test.received-conformance.node.ts --verbose --logfile --timeout 60",
|
|
70
|
+
"test:unit": "tstest test/test.cii-xrechnung.node.ts --verbose --logfile --timeout 60 && tstest test/test.einvoice.ts --verbose --logfile --timeout 60 && tstest test/test.decimal.ts --verbose --logfile --timeout 60 && tstest test/test.decimal-currency-calculator.ts --verbose --logfile --timeout 60 && tstest test/test.currency-utils.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-validators.ts --verbose --logfile --timeout 60 && tstest test/test.schematron-validator.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-creditnote.node.ts --verbose --logfile --timeout 60 && tstest test/test.accountingdoc-types.node.ts --verbose --logfile --timeout 60 && tstest test/test.corrected-invoice.node.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-3-due-date.node.ts --verbose --logfile --timeout 60 && tstest test/test.calendar-dates.node.ts --verbose --logfile --timeout 60 && tstest test/test.document-totals.node.ts --verbose --logfile --timeout 60 && tstest test/test.reverse-charge.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-schema-order.node.ts --verbose --logfile --timeout 60 && tstest test/test.tax-number.node.ts --verbose --logfile --timeout 60 && tstest test/test.country-codes.node.ts --verbose --logfile --timeout 60 && tstest test/test.paid-amount.node.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-unitcodes.node.ts --verbose --logfile --timeout 60 && tstest test/test.en16931-lineunit.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-profile.node.ts --verbose --logfile --timeout 60 && tstest test/test.validator-rule-ids.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-period-address.node.ts --verbose --logfile --timeout 60 && tstest test/test.amount-due.node.ts --verbose --logfile --timeout 60 && tstest test/test.ubl-zoned-dates.node.ts --verbose --logfile --timeout 60 && tstest test/test.vat-categories.node.ts --verbose --logfile --timeout 60 && tstest test/test.xrechnung-buyer-reference.node.ts --verbose --logfile --timeout 60 && tstest test/test.specification-identifier.node.ts --verbose --logfile --timeout 60 && tstest test/test.seller-identifier.node.ts --verbose --logfile --timeout 60 && tstest test/test.stated-values.node.ts --verbose --logfile --timeout 60 && tstest test/test.vat-category-limits.node.ts --verbose --logfile --timeout 60 && tstest test/test.received-conformance.node.ts --verbose --logfile --timeout 60 && tstest test/test.cii-stated-dates.node.ts --verbose --logfile --timeout 60",
|
|
71
71
|
"test:format": "tstest test/test.format-detection.ts --verbose --logfile --timeout 60",
|
|
72
72
|
"test:corpus": "tstest test/suite/einvoice_corpus-validation/ --verbose --logfile --timeout 60",
|
|
73
73
|
"test:performance": "NODE_OPTIONS=--expose-gc tstest test/suite/einvoice_performance/ --verbose --logfile --timeout 60",
|
package/readme.md
CHANGED
|
@@ -509,7 +509,12 @@ the UTC midnight of that day, `Date.UTC(2026, 8, 20)`.
|
|
|
509
509
|
- The decoders return that timestamp. A UBL date may carry a time zone
|
|
510
510
|
(`2026-09-20+02:00`, `2026-09-20Z`); the zone does not move the day, so the
|
|
511
511
|
decoders return the UTC midnight of the stated day, and every format writes
|
|
512
|
-
that day.
|
|
512
|
+
that day. A CII date stated with a time of day, in format 203
|
|
513
|
+
(`202609202330`) or 204 (`20260920233000`), or without a format code as an
|
|
514
|
+
ISO 8601 date or date and time (`2026-09-20T00:30:00+02:00`), is read the
|
|
515
|
+
same way: the day it states, whatever the time of day and the zone. A CII
|
|
516
|
+
date in another form, or in a format that names no day, is refused with an
|
|
517
|
+
`EInvoiceParsingError`; no day is guessed.
|
|
513
518
|
- The encoders write the day of that timestamp in UTC and count the payment
|
|
514
519
|
term in UTC days, so what they write does not depend on the time zone of the
|
|
515
520
|
machine that writes it.
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@fin.cx/einvoice',
|
|
6
|
-
version: '10.0.
|
|
6
|
+
version: '10.0.1',
|
|
7
7
|
description: 'A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.'
|
|
8
8
|
}
|
|
@@ -2,7 +2,7 @@ import type { TAccountingDoc } from '../../interfaces/common.js';
|
|
|
2
2
|
import { ValidationLevel } from '../../interfaces/common.js';
|
|
3
3
|
import type { ValidationResult } from '../../interfaces/common.js';
|
|
4
4
|
import { EInvoiceParsingError } from '../../errors.js';
|
|
5
|
-
import { parseXsdDateDay } from '../utils/date.value.js';
|
|
5
|
+
import { parseCiiDateDay, parseIsoDateTimeDay, parseXsdDateDay } from '../utils/date.value.js';
|
|
6
6
|
import type { IEInvoiceStatedValues } from '../../interfaces/stated.values.js';
|
|
7
7
|
import { statedValueProblemMessage, type IStatedValuesRead } from '../utils/stated.values.js';
|
|
8
8
|
|
|
@@ -102,7 +102,15 @@ export abstract class BaseDecoder {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
* Parses a required CII date without inventing a fallback value
|
|
105
|
+
* Parses a required CII date without inventing a fallback value: the
|
|
106
|
+
* calendar day it states, as the UTC midnight of that day. Format 102
|
|
107
|
+
* (`YYYYMMDD`) is the one EN 16931 asks for; a date stated with a time of
|
|
108
|
+
* day in format 203 (`YYYYMMDDhhmm`) or 204 (`YYYYMMDDhhmmss`), or without a
|
|
109
|
+
* format code as an ISO 8601 date or date and time (`parseIsoDateTimeDay`),
|
|
110
|
+
* is read as the day it states, whatever the time of day and the zone.
|
|
111
|
+
* Format 610 (`YYYYMM`) is read as the 1st of the month. Any other value is
|
|
112
|
+
* refused with an `EInvoiceParsingError`: no day is guessed, and none is read
|
|
113
|
+
* in the zone of the machine.
|
|
106
114
|
*/
|
|
107
115
|
protected parseRequiredCIIDate(dateStr: string, format?: string): number {
|
|
108
116
|
const value = dateStr.trim();
|
|
@@ -111,22 +119,17 @@ export abstract class BaseDecoder {
|
|
|
111
119
|
format: 'cii',
|
|
112
120
|
});
|
|
113
121
|
}
|
|
122
|
+
const formatCode = (format ?? '').trim();
|
|
114
123
|
|
|
115
|
-
if (
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
throw this.createInvalidDateError(value,
|
|
124
|
+
if (formatCode === '102' || formatCode === '203' || formatCode === '204') {
|
|
125
|
+
const day = parseCiiDateDay(value, formatCode);
|
|
126
|
+
if (day === undefined) {
|
|
127
|
+
throw this.createInvalidDateError(value, `CII format ${formatCode}`);
|
|
119
128
|
}
|
|
120
|
-
return
|
|
121
|
-
Number(match[1]),
|
|
122
|
-
Number(match[2]),
|
|
123
|
-
Number(match[3]),
|
|
124
|
-
value,
|
|
125
|
-
'CII format 102',
|
|
126
|
-
);
|
|
129
|
+
return day;
|
|
127
130
|
}
|
|
128
131
|
|
|
129
|
-
if (
|
|
132
|
+
if (formatCode === '610') {
|
|
130
133
|
const match = /^(\d{4})(\d{2})$/.exec(value);
|
|
131
134
|
if (!match) {
|
|
132
135
|
throw this.createInvalidDateError(value, 'CII format 610');
|
|
@@ -140,11 +143,15 @@ export abstract class BaseDecoder {
|
|
|
140
143
|
);
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
if (formatCode) {
|
|
147
|
+
throw this.createInvalidDateError(value, `CII format ${formatCode}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const day = parseIsoDateTimeDay(value);
|
|
151
|
+
if (day === undefined) {
|
|
145
152
|
throw this.createInvalidDateError(value, 'CII');
|
|
146
153
|
}
|
|
147
|
-
return
|
|
154
|
+
return day;
|
|
148
155
|
}
|
|
149
156
|
|
|
150
157
|
/**
|
|
@@ -51,6 +51,42 @@ export const getWritableDueDate = (issueTimestamp: unknown, dueInDays: unknown,
|
|
|
51
51
|
return dueDate;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* The UTC midnight of a calendar day, or undefined when there is no such day
|
|
56
|
+
* @param year The year, 1 or later
|
|
57
|
+
* @param month The month, 1 to 12
|
|
58
|
+
* @param day The day of the month
|
|
59
|
+
*/
|
|
60
|
+
const utcMidnightOf = (year: number, month: number, day: number): number | undefined => {
|
|
61
|
+
if (year < 1) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const date = new Date(0);
|
|
65
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
66
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return date.getTime();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Whether a time of day is one: hours 0 to 23, minutes and seconds 0 to 59
|
|
74
|
+
* @param hours The hours
|
|
75
|
+
* @param minutes The minutes
|
|
76
|
+
* @param seconds The seconds, 0 when not stated
|
|
77
|
+
*/
|
|
78
|
+
const isTimeOfDay = (hours: number, minutes: number, seconds: number): boolean =>
|
|
79
|
+
hours <= 23 && minutes <= 59 && seconds <= 59;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether a time zone offset is one an `xsd:date` or `xsd:dateTime` may carry:
|
|
83
|
+
* at most 14:00
|
|
84
|
+
* @param hours The hours of the offset
|
|
85
|
+
* @param minutes The minutes of the offset
|
|
86
|
+
*/
|
|
87
|
+
const isZoneOffset = (hours: number, minutes: number): boolean =>
|
|
88
|
+
hours < 14 ? minutes <= 59 : hours === 14 && minutes === 0;
|
|
89
|
+
|
|
54
90
|
/**
|
|
55
91
|
* The calendar day a UBL `xsd:date` names (BT-2, BT-9, BT-26, BT-72, BT-73,
|
|
56
92
|
* BT-74), as the UTC midnight of that day; undefined when the value is no such
|
|
@@ -66,20 +102,57 @@ export const parseXsdDateDay = (value: string): number | undefined => {
|
|
|
66
102
|
if (!match) {
|
|
67
103
|
return undefined;
|
|
68
104
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const [zoneHours, zoneMinutes] = [Number(match[4]), Number(match[5])];
|
|
72
|
-
if (zoneHours > 14 || zoneMinutes > 59 || (zoneHours === 14 && zoneMinutes !== 0)) {
|
|
73
|
-
return undefined;
|
|
74
|
-
}
|
|
105
|
+
if (match[4] !== undefined && !isZoneOffset(Number(match[4]), Number(match[5]))) {
|
|
106
|
+
return undefined;
|
|
75
107
|
}
|
|
76
|
-
|
|
108
|
+
return utcMidnightOf(Number(match[1]), Number(match[2]), Number(match[3]));
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The calendar day an ISO 8601 date, or date and time, names, as the UTC
|
|
113
|
+
* midnight of that day; undefined when the value is no such date. A CII date
|
|
114
|
+
* without a format code may be stated so. The value is `YYYY-MM-DD`,
|
|
115
|
+
* optionally followed by a time of day `Thh:mm`, `Thh:mm:ss` or
|
|
116
|
+
* `Thh:mm:ss.fff`, and then optionally by a time zone: `Z`, or `+hh:mm` /
|
|
117
|
+
* `-hh:mm` of at most 14:00. Neither the time of day nor the zone moves the
|
|
118
|
+
* day the document states: `2026-09-11T00:30:00+02:00` is 22:30 UTC on
|
|
119
|
+
* 10 September, but the document states 11 September, and that is the day an
|
|
120
|
+
* invoice is dated with (§ 14 Abs. 4 Satz 1 Nr. 3 UStG asks for the issue date,
|
|
121
|
+
* a calendar day).
|
|
122
|
+
* @param value The date as the document states it
|
|
123
|
+
*/
|
|
124
|
+
export const parseIsoDateTimeDay = (value: string): number | undefined => {
|
|
125
|
+
const match =
|
|
126
|
+
/^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?)?(?:Z|[+-](\d{2}):(\d{2}))?$/.exec(value.trim());
|
|
127
|
+
if (!match) {
|
|
77
128
|
return undefined;
|
|
78
129
|
}
|
|
79
|
-
|
|
80
|
-
date.setUTCFullYear(year, month - 1, day);
|
|
81
|
-
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
|
|
130
|
+
if (match[4] !== undefined && !isTimeOfDay(Number(match[4]), Number(match[5]), Number(match[6] ?? 0))) {
|
|
82
131
|
return undefined;
|
|
83
132
|
}
|
|
84
|
-
|
|
133
|
+
if (match[7] !== undefined && !isZoneOffset(Number(match[7]), Number(match[8]))) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
return utcMidnightOf(Number(match[1]), Number(match[2]), Number(match[3]));
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The calendar day a CII date in format 102 (`YYYYMMDD`), 203
|
|
141
|
+
* (`YYYYMMDDhhmm`) or 204 (`YYYYMMDDhhmmss`) names, as the UTC midnight of that
|
|
142
|
+
* day; undefined when the value is no such date in that format. A time of day
|
|
143
|
+
* in formats 203 and 204 does not move the day stated; the formats carry no
|
|
144
|
+
* time zone.
|
|
145
|
+
* @param value The date as the document states it
|
|
146
|
+
* @param format The format code: '102', '203' or '204'
|
|
147
|
+
*/
|
|
148
|
+
export const parseCiiDateDay = (value: string, format: '102' | '203' | '204'): number | undefined => {
|
|
149
|
+
const timeDigits = { '102': '', '203': '(\\d{2})(\\d{2})', '204': '(\\d{2})(\\d{2})(\\d{2})' }[format];
|
|
150
|
+
const match = new RegExp(`^(\\d{4})(\\d{2})(\\d{2})${timeDigits}$`).exec(value.trim());
|
|
151
|
+
if (!match) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
if (format !== '102' && !isTimeOfDay(Number(match[4]), Number(match[5]), Number(match[6] ?? 0))) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
return utcMidnightOf(Number(match[1]), Number(match[2]), Number(match[3]));
|
|
85
158
|
};
|