@ekwo-ai/myinvois 0.0.0-stage → 0.15.0
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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +360 -2
- package/dist/codelists.d.ts +32 -0
- package/dist/codelists.d.ts.map +1 -0
- package/dist/codelists.js +549 -0
- package/dist/codelists.js.map +1 -0
- package/dist/decimal.d.ts +52 -0
- package/dist/decimal.d.ts.map +1 -0
- package/dist/decimal.js +121 -0
- package/dist/decimal.js.map +1 -0
- package/dist/document.d.ts +16 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +135 -0
- package/dist/document.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +113 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +360 -0
- package/dist/model.js.map +1 -0
- package/dist/rules.d.ts +22 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +274 -0
- package/dist/rules.js.map +1 -0
- package/dist/tree.d.ts +45 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +95 -0
- package/dist/tree.js.map +1 -0
- package/dist/types.d.ts +268 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- First version: the MyInvois e-Invoice of a posted document — invoice,
|
|
11
|
+
credit note, debit note and refund note, the supplier's (`01`–`04`) or
|
|
12
|
+
self-billed (`11`–`14`), versions 1.0 and 1.1 — as UBL 2.1 in JSON or XML,
|
|
13
|
+
with LHDNM's code tables, and every rule of the SDK the document breaks by
|
|
14
|
+
LHDNM's error code where it publishes one. Unsigned, and submitted by the
|
|
15
|
+
host.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EKWO OÜ, trading as Ekwo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,361 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@ekwo-ai/myinvois`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Malaysia's **MyInvois e-Invoice** — the UBL 2.1 document the Inland Revenue
|
|
4
|
+
Board of Malaysia (Lembaga Hasil Dalam Negeri Malaysia, LHDNM) validates
|
|
5
|
+
before an invoice is the taxpayer's — in TypeScript, with no dependencies.
|
|
6
|
+
It writes the eight document types of the MyInvois SDK: the invoice, the
|
|
7
|
+
credit note, the debit note and the refund note, each as the supplier issues
|
|
8
|
+
it or **self-billed** by the buyer, in the JSON syntax or the XML one.
|
|
9
|
+
|
|
10
|
+
Give it a posted document as the books hold it, with what MyInvois asks and
|
|
11
|
+
the books do not hold — the LHDNM identifiers of both parties, the supplier's
|
|
12
|
+
MSIC code, the moment of issuance — and it gives you the document, a name for
|
|
13
|
+
it, and every rule of the SDK the document breaks, named by LHDNM's own error
|
|
14
|
+
code where it publishes one.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { MYINVOIS } from '@ekwo-ai/myinvois';
|
|
18
|
+
|
|
19
|
+
const input = {
|
|
20
|
+
header, lines, taxes, // the three views of a posted document
|
|
21
|
+
version: '1.1', // the default: to be signed by the host; 1.0 unsigned
|
|
22
|
+
issuedAt: '2026-01-15T10:30:00+08:00', // written in UTC: 2026-01-15, 02:30:00Z
|
|
23
|
+
supplier: { tin, sst: 'NA', tourismTax: 'NA', activity: { msic: '46510', description } },
|
|
24
|
+
buyer: { tin, phone: '+60123456789' },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const { content, filename, typeCode, violations } = MYINVOIS.write(input); // JSON
|
|
28
|
+
const xml = MYINVOIS.write(input, 'xml').content; // XML
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`header`, `lines` and `taxes` are one row of `document_header`, the rows of
|
|
32
|
+
`document_line_items` and the rows of `document_tax_summary` in
|
|
33
|
+
[Ekwo OS](https://github.com/Ekwo-ai/ekwo-os) — the same three reads
|
|
34
|
+
`@ekwo-ai/peppol-ubl` and `@ekwo-ai/verifactu` take. They are declared in
|
|
35
|
+
this package's own types, so any invoicing system that can produce the same
|
|
36
|
+
rows can use it.
|
|
37
|
+
|
|
38
|
+
Writing the document is all it does. **It signs nothing**: version 1.1 of
|
|
39
|
+
every document type is the same structure as 1.0 with the signature
|
|
40
|
+
validated, and the signature — an XAdES enveloped signature in
|
|
41
|
+
`UBLExtensions` and `cac:Signature`, with a certificate issued by a
|
|
42
|
+
certification authority licensed by the MCMC — is added by the host after
|
|
43
|
+
this package and before the submission. **It submits nothing**: the login,
|
|
44
|
+
the `documentHash` and the Base64 encoding of the submission, the API call,
|
|
45
|
+
LHDNM's validation, the Unique Identifier Number and the QR code it returns,
|
|
46
|
+
the 72-hour window in which a document may be rejected or cancelled — all of
|
|
47
|
+
it belongs to a transport. In Ekwo, the signature and the transport are for
|
|
48
|
+
Ekwo Cloud. **It reads no clock and no network**: the moment of issuance is
|
|
49
|
+
given, and every code list is in the package.
|
|
50
|
+
|
|
51
|
+
## The interface
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
interface MyInvoisInput {
|
|
55
|
+
header: Row; lines: Row[]; taxes: Row[];
|
|
56
|
+
version?: '1.0' | '1.1'; // 1.1 when left out
|
|
57
|
+
kind?: 'invoice' | 'credit-note' | 'debit-note' | 'refund-note';
|
|
58
|
+
issuedAt: string; // ISO 8601 with its offset
|
|
59
|
+
supplier: PartyIdentifiers & { activity?: { msic: string; description: string } };
|
|
60
|
+
buyer: PartyIdentifiers;
|
|
61
|
+
adjusts?: { number: string; uuid: string }[]; // what a note adjusts
|
|
62
|
+
exchangeRate?: Numeric; // ringgit per unit, where the currency is not MYR
|
|
63
|
+
paymentMode?: string; // a code of LHDNM's payment modes
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface PartyIdentifiers {
|
|
67
|
+
tin?: string; // the TIN LHDNM assigned
|
|
68
|
+
registration?: { scheme: 'BRN' | 'NRIC' | 'PASSPORT' | 'ARMY'; id: string };
|
|
69
|
+
sst?: string; // one or two numbers, or 'NA'
|
|
70
|
+
tourismTax?: string; // the supplier's, or 'NA'
|
|
71
|
+
phone?: string; // E.164
|
|
72
|
+
state?: string; // a state code, where the header's region does not give it
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
MYINVOIS.write(input, syntax = 'json'): {
|
|
76
|
+
content; filename; mediaType; syntax;
|
|
77
|
+
typeCode; // 01–04, 11–14
|
|
78
|
+
version; requiresSignature; // true for 1.1
|
|
79
|
+
violations; // { code, message, line? }
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- **Which document.** A `sale_invoice` is an invoice (`01`) and a
|
|
84
|
+
`sale_credit_note` a credit note (`02`) without being told. The books keep
|
|
85
|
+
no separate type for a debit note or a refund note — a debit note is booked
|
|
86
|
+
as an invoice that adds to an earlier one, a refund note as a credit note
|
|
87
|
+
whose money went back — so `kind: 'debit-note'` (`03`) and
|
|
88
|
+
`kind: 'refund-note'` (`04`) are asked for by name, and refused on a
|
|
89
|
+
document of the other side.
|
|
90
|
+
- **Self-billed.** A `purchase_invoice` or `purchase_credit_note` is a
|
|
91
|
+
document the company issues as the buyer: `11` to `14`. In the views
|
|
92
|
+
`seller_*` is always the company and `buyer_*` its contact, so on a
|
|
93
|
+
purchase the two change places — the contact is written as
|
|
94
|
+
`AccountingSupplierParty`, the company as `AccountingCustomerParty` — and
|
|
95
|
+
`supplier` and `buyer` in the input always mean the parties of the
|
|
96
|
+
document, as the SDK names them.
|
|
97
|
+
- **The version is 1.1 unless 1.0 is asked for.** LHDNM recommends 1.1
|
|
98
|
+
(FAQ) and accepts version 1.0 without a signature "until such time as
|
|
99
|
+
\[it\] issues an official notice concerning the retirement of version 1.0".
|
|
100
|
+
A 1.1 document written here is complete once signed, and
|
|
101
|
+
`requiresSignature` says so.
|
|
102
|
+
- **The moment of issuance** is written in UTC, which is what the SDK asks
|
|
103
|
+
for `IssueDate` and `IssueTime`. It is not the date of the document in the
|
|
104
|
+
books: a document issued at 07:00 in Kuala Lumpur is issued the day before
|
|
105
|
+
in UTC.
|
|
106
|
+
|
|
107
|
+
`write` throws a `MyInvoisError` for what makes a document absurd: no
|
|
108
|
+
document, a quote or an order, a document never numbered, no currency, no
|
|
109
|
+
line, an amount that is not a number, a moment that is not ISO 8601 with an
|
|
110
|
+
offset, a kind the document cannot be. Everything else comes back in
|
|
111
|
+
`violations`, beside a document that says what it was given.
|
|
112
|
+
|
|
113
|
+
## The figures are the books' figures
|
|
114
|
+
|
|
115
|
+
Nothing is recomputed. Totals, bases and taxes are written as they were
|
|
116
|
+
posted, and where they do not add up the mathematical mapping of the SDK they
|
|
117
|
+
break is named rather than the figure corrected. No figure passes through a
|
|
118
|
+
`number`: amounts arrive as the text Postgres prints a `numeric` as, are
|
|
119
|
+
compared as exact decimals on `bigint` — `src/decimal.ts` is the file of
|
|
120
|
+
`@ekwo-ai/peppol-ubl`, byte for byte, and a test compares the two — and are
|
|
121
|
+
written in the JSON as the same digits, unquoted: a JSON number is a decimal
|
|
122
|
+
in text.
|
|
123
|
+
|
|
124
|
+
Three figures are derived, each without rounding:
|
|
125
|
+
|
|
126
|
+
- **The subtotal of a line** (`ItemPriceExtension`) is its price times its
|
|
127
|
+
quantity, exactly, and **its discount** the subtotal less the net amount
|
|
128
|
+
the books posted, with the rate (`MultiplierFactorNumeric`) the keyed
|
|
129
|
+
percentage over a hundred.
|
|
130
|
+
- **The tax of a line.** The books round the tax of all the lines under one
|
|
131
|
+
tax once, so the tax of a line is a figure they hold only where the line is
|
|
132
|
+
alone under its tax, or where the row carries it (`tax_amount`). Otherwise
|
|
133
|
+
it is base times rate, written only where that is exact to the cent for
|
|
134
|
+
every line of the tax and adds up to what was posted; anywhere else it is
|
|
135
|
+
reported, `input:line_tax_amount`.
|
|
136
|
+
- **The amount payable** is the total including tax. A payment received
|
|
137
|
+
after the document was issued is a payment, not the prepayment
|
|
138
|
+
`PrepaidPayment` records, and the books do not say when it was received.
|
|
139
|
+
|
|
140
|
+
A credit note's figures are positive, as the books hold them and as LHDNM's
|
|
141
|
+
samples write them: its type code says what it is.
|
|
142
|
+
|
|
143
|
+
## Violations
|
|
144
|
+
|
|
145
|
+
Each violation is `{ code, message, line? }`, the shape of
|
|
146
|
+
`EinvoiceViolation` in the core:
|
|
147
|
+
|
|
148
|
+
| Code | What it is |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `CF…`, `CV…` | **LHDNM's own error code**, where the SDK publishes one for the rule, with LHDNM's own sentence |
|
|
151
|
+
| `required:<element>` | A field the SDK's data structure marks mandatory, and the document does not hold |
|
|
152
|
+
| `length:<element>` | A value longer than the SDK's "Number of Chars" allows |
|
|
153
|
+
| `format:<element>` | A value not shaped as the SDK describes it, where it publishes no code for it |
|
|
154
|
+
| `math:<element>` | A total that is not what the SDK's mathematical mappings make of the figures beside it |
|
|
155
|
+
| `input:<what>` | What the books do not hold and the caller gives: `input:supplier_tin`, `input:tax_type`, `input:classification`… |
|
|
156
|
+
|
|
157
|
+
**Empty `violations` is the only state to submit in.** A document that breaks
|
|
158
|
+
a rule leaves out what it does not know, and the schema may refuse it too.
|
|
159
|
+
|
|
160
|
+
| Code | What is checked |
|
|
161
|
+
|---|---|
|
|
162
|
+
| CF345, CF346 | An address line of the supplier, of the buyer, longer than 150 characters |
|
|
163
|
+
| CF404 | A city longer than 50 characters |
|
|
164
|
+
| CF405 | A postcode of more than five digits in Malaysia, or longer than 50 characters elsewhere |
|
|
165
|
+
| CF406 | An SST registration number: letters, digits and dashes, at most two separated by `;`, at most 35 characters, or `NA` |
|
|
166
|
+
| CF407, CF412 | A tourism tax registration number: letters, digits and dashes, at most 17 characters, or `NA` |
|
|
167
|
+
| CF408, CF409, CF410 | A telephone: digits and a leading `+`, between 8 and 20 characters |
|
|
168
|
+
| CF400, CF401, CF411 | An e-mail address: its shape, and at most 320 characters |
|
|
169
|
+
| CV302 | A code outside its list: currency, payment mode, MSIC, classification, unit, tax type, state, country |
|
|
170
|
+
| CV316 | A state code 01–16 on an address outside Malaysia |
|
|
171
|
+
| CV317 | State code 17 on an address in Malaysia (it is for a foreign address, and for a consolidated e-Invoice, which this package does not write) |
|
|
172
|
+
| `required:` | The names of both parties, their registration or identification number, address line, city, state, country and telephone; the supplier's business activity description; each line's description, unit price and subtotal; the reason of a tax exemption (`E`); the number and UUID of an adjusted document |
|
|
173
|
+
| `length:` | The document number (50), the names (300), the TIN (14), a BRN (20), an NRIC, passport or army number (12), the MSIC code (five digits), the activity description (300), the bank account (150), the payment terms (300), the bill reference (150), a line's description (300), a reason of exemption (300), a UUID (26) |
|
|
174
|
+
| `format:` | A TIN: `IG` and digits for an individual; for a non-individual one of the prefixes `C`, `CS`, `D`, `F`, `FA`, `PT`, `TA`, `TC`, `TN`, `TR`, `TP`, `J`, `LE`, then digits that start with no zero and end with one (FAQ); a general TIN, `EI…`, is not checked |
|
|
175
|
+
| `math:` | The total including tax against the total excluding tax plus the tax; the total tax against its tax types; the net total against the lines; per tax type, the tax against the taxes of its lines and the base against their net amounts (except for an exemption, `E`); per line, the net against the subtotal less the discount |
|
|
176
|
+
| `input:` | The TIN of each party; the supplier's SST and tourism tax numbers (the supplier alone knows whether it is registered); its MSIC code; the buyer's telephone, which the views do not publish; the tax type of each tax; the classification of each line; the tax of a line the books did not keep; the net price of a line keyed with its tax; the exchange rate of a foreign currency; the payment mode of an account to pay into; the adjusted document of a note; a country with no three-letter code in LHDNM's list |
|
|
177
|
+
|
|
178
|
+
What only LHDNM can check is not checked here: that a TIN exists and goes
|
|
179
|
+
with the identification beside it (the taxpayer validator, `ERR…`), that a
|
|
180
|
+
referenced UUID is a valid document of the same supplier and buyer (`DR…`),
|
|
181
|
+
that a document is not a duplicate, and the signature.
|
|
182
|
+
|
|
183
|
+
## From the rows to the document
|
|
184
|
+
|
|
185
|
+
### `document_header`
|
|
186
|
+
|
|
187
|
+
| Column | Becomes |
|
|
188
|
+
|---|---|
|
|
189
|
+
| `number` | `cbc:ID`, the e-Invoice code or number |
|
|
190
|
+
| `doc_type` | `cbc:InvoiceTypeCode`, with `kind`: see above |
|
|
191
|
+
| `currency_code` | `cbc:DocumentCurrencyCode`, and `cac:TaxExchangeRate` from it to MYR where it is not MYR |
|
|
192
|
+
| `amount_untaxed` | `LineExtensionAmount` and `TaxExclusiveAmount` |
|
|
193
|
+
| `amount_tax` | `cac:TaxTotal/cbc:TaxAmount` |
|
|
194
|
+
| `amount_total` | `TaxInclusiveAmount` and `PayableAmount` |
|
|
195
|
+
| `payment_terms` | `cac:PaymentTerms/cbc:Note` |
|
|
196
|
+
| `payment_reference` | `cac:BillingReference/cac:AdditionalDocumentReference/cbc:ID`, the bill reference number |
|
|
197
|
+
| `payee_iban` | `cac:PayeeFinancialAccount/cbc:ID`, written with a `paymentMode` |
|
|
198
|
+
| `{side}_legal_name`, else `{side}_name` | `cac:PartyLegalEntity/cbc:RegistrationName` |
|
|
199
|
+
| `{side}_registration_number` | The BRN, where no `registration` is given: the SSM number, ISO 6523 scheme `0230` in the Malaysian pack |
|
|
200
|
+
| `{side}_address_line1`, `_line2` | `cac:AddressLine/cbc:Line` |
|
|
201
|
+
| `{side}_city`, `_postal_code` | `cbc:CityName`, `cbc:PostalZone` |
|
|
202
|
+
| `{side}_region` | `cbc:CountrySubentityCode`: a state code as it is, the name of a state of LHDNM's list as its code, a foreign region as it is, `17` for a foreign address with none |
|
|
203
|
+
| `{side}_country` | `cac:Country/cbc:IdentificationCode`, ISO 3166-1 alpha-3 |
|
|
204
|
+
| `seller_phone`, `{side}_email` | `cac:Contact` |
|
|
205
|
+
|
|
206
|
+
### `document_line_items`: one `cac:InvoiceLine` per `product` line
|
|
207
|
+
|
|
208
|
+
| Column | Becomes |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `sequence` | `cbc:ID` |
|
|
211
|
+
| `quantity`, `unit_code` | `cbc:InvoicedQuantity` and its `@unitCode` |
|
|
212
|
+
| `amount_untaxed` | `cbc:LineExtensionAmount`, and the base of its tax |
|
|
213
|
+
| `unit_price` (or `net_unit_price` where the price holds its tax) | `cac:Price/cbc:PriceAmount` |
|
|
214
|
+
| `discount_percent` | `cac:AllowanceCharge`, with `ChargeIndicator` false |
|
|
215
|
+
| `item_name`, else `item_description` | `cac:Item/cbc:Description` |
|
|
216
|
+
| `vat_rate` | `cac:TaxSubtotal/cbc:Percent` |
|
|
217
|
+
| `tax_code`, else `vat_category` and `vat_rate` | Which tax of the breakdown the line was booked under |
|
|
218
|
+
|
|
219
|
+
### `document_tax_summary`: the taxes, one `cac:TaxSubtotal` per tax type
|
|
220
|
+
|
|
221
|
+
`base_amount` and `tax_charged` of every row of one LHDNM tax type are added
|
|
222
|
+
into one `TaxableAmount` and one `TaxAmount`; `exemption_reason` is the
|
|
223
|
+
`TaxExemptionReason` of an exemption (`E`). `cac:TaxScheme/cbc:ID` is `OTH`
|
|
224
|
+
in the scheme `UN/ECE 5153`, agency `6`, as every sample of the SDK writes
|
|
225
|
+
it: Malaysia's taxes are not a value added tax.
|
|
226
|
+
|
|
227
|
+
### What the rows must carry for the document to be complete
|
|
228
|
+
|
|
229
|
+
None of it is in the three views alone. In Ekwo OS the core gives it, from
|
|
230
|
+
the country pack and from the books (`EINVOICE_FORMATS` in `@ekwo-ai/core`,
|
|
231
|
+
profile `myinvois`): the pack's fiscalisation regime gives each tax its
|
|
232
|
+
`myinvois_tax_type` and every line its `myinvois_classification`, and
|
|
233
|
+
`companies.identifiers` and `contacts.identifiers` give each party its TIN,
|
|
234
|
+
its SST and tourism tax registrations by scheme. Any other caller gives it:
|
|
235
|
+
|
|
236
|
+
- **`myinvois_tax_type`** on each row of `document_tax_summary` (or on a
|
|
237
|
+
line): `01` Sales Tax, `02` Service Tax, `03` Tourism Tax, `04` High-Value
|
|
238
|
+
Goods Tax, `05` Sales Tax on Low Value Goods, `06` Not Applicable, `E` Tax
|
|
239
|
+
exemption. Nothing in the books decides it: a category `S` at 10 % is the
|
|
240
|
+
Sales Tax in the Malaysian pack and could be any tax in another, and
|
|
241
|
+
whether an export or a sale outside the scope of the tax is `06` or `E` is
|
|
242
|
+
a reading of the law this package does not make.
|
|
243
|
+
- **`myinvois_classification`** on each line: one code of LHDNM's 45, or
|
|
244
|
+
several. It describes what was sold for LHDNM's purposes — a computer, a
|
|
245
|
+
donation, a disbursement, `022` Others — which the books do not record.
|
|
246
|
+
- **`tax_amount`** on a line that shares its tax with others, where base
|
|
247
|
+
times rate is not exact to the cent.
|
|
248
|
+
- **`tax_code`** on a line, where two taxes of the document share a category
|
|
249
|
+
and a rate.
|
|
250
|
+
|
|
251
|
+
## Named gaps
|
|
252
|
+
|
|
253
|
+
What this package does not write, because it could not be sourced, because
|
|
254
|
+
the books do not hold it, or because it is not a document's to do:
|
|
255
|
+
|
|
256
|
+
- **The signature, the submission, the UIN and the QR code**, as said above.
|
|
257
|
+
- **The consolidated e-Invoice** (classification `004`, the general TIN
|
|
258
|
+
`EI00000000010`, state `17`) and every **general TIN** for a buyer or a
|
|
259
|
+
supplier that has none — foreign buyers, foreign suppliers of a self-billed
|
|
260
|
+
invoice, individuals. The taxpayer validator's codes name them (`010`,
|
|
261
|
+
`020`, `030`, `040`), but the SDK pages read for this package publish
|
|
262
|
+
neither the full numbers nor the rules of their use beyond the consolidated
|
|
263
|
+
sample, and none is invented here: a TIN is always given.
|
|
264
|
+
- **Fixed-rate taxes**: `PerUnitAmount` and `BaseUnitMeasure`, the way the
|
|
265
|
+
tourism tax is charged per room per night. The books hold a tax as a rate.
|
|
266
|
+
- **Invoice-level allowances and charges**, `PrepaidPayment`,
|
|
267
|
+
`PayableRoundingAmount`, `InvoicePeriod` (frequency and period of billing),
|
|
268
|
+
the shipping recipient (`cac:Delivery`), the customs forms No. 1, 2 and 9,
|
|
269
|
+
the Incoterms, the free trade agreement, the certified exporter's
|
|
270
|
+
authorisation, the product tariff code (`PTC`) and the country of origin:
|
|
271
|
+
the views carry none of them.
|
|
272
|
+
- **The tax in ringgit of a document in another currency.** LHDNM's foreign
|
|
273
|
+
currency sample writes `TaxCurrencyCode` `MYR` and its tax amounts in
|
|
274
|
+
ringgit; its currency validator also says that every amount is in the
|
|
275
|
+
document's currency (DC502), and that tax amounts are in `TaxCurrencyCode`
|
|
276
|
+
where one is given (DC501). The books hold the tax of a document in that
|
|
277
|
+
document's currency, so this package writes every amount in it, gives the
|
|
278
|
+
exchange rate, and leaves `TaxCurrencyCode`, which is optional, out. That
|
|
279
|
+
reading has not been checked against the sandbox.
|
|
280
|
+
- **The special characters of a reason of exemption.** The SDK limits the
|
|
281
|
+
text to letters, digits and `. - , ( )`, and its own example, "Reference
|
|
282
|
+
No: ABC.TAX(S)012-3/4/56(7)", holds a colon and slashes. The rule is not
|
|
283
|
+
checked until the two agree.
|
|
284
|
+
- **Five countries of LHDNM's list** have no pair in the UN table this
|
|
285
|
+
package maps two-letter codes from — `ANT`, `CRM`, `SCT`, `TWN`, `XKX` — and
|
|
286
|
+
a document to or from them is reported (`input:<role>_country`).
|
|
287
|
+
- **The shape of a BRN, an NRIC, a passport or an army number**: the SDK
|
|
288
|
+
publishes their lengths and not their form, and only the lengths are
|
|
289
|
+
checked. A TIN is checked against the shape the FAQ describes, and whether
|
|
290
|
+
it exists is LHDNM's to say.
|
|
291
|
+
- **SVDP 1.2 and 1.3**, the document versions of the Special Voluntary
|
|
292
|
+
Disclosure Programme, which allow a backdated moment of issuance during
|
|
293
|
+
the programme only.
|
|
294
|
+
|
|
295
|
+
## Sources
|
|
296
|
+
|
|
297
|
+
Read on 19 October 2026 on the MyInvois SDK of LHDNM,
|
|
298
|
+
<https://sdk.myinvois.hasil.gov.my/>, last updated 25 September 2026
|
|
299
|
+
(release notes, SDK 1.0):
|
|
300
|
+
|
|
301
|
+
- **The document types**, each with its data structure — field, UBL path,
|
|
302
|
+
mandatory or not, number of characters, cardinality: Invoice
|
|
303
|
+
(<https://sdk.myinvois.hasil.gov.my/documents/invoice-v1-1/>, and
|
|
304
|
+
`invoice-v1-0`), Credit Note (`credit-v1-1`), Debit Note (`debit-v1-1`),
|
|
305
|
+
Refund Note (`refund-v1-1`), and the self-billed four
|
|
306
|
+
(`self-billed-invoice-v1-1`, `self-billed-credit-v1-1`,
|
|
307
|
+
`self-billed-debit-v1-1`, `self-billed-refund-v1-1`), under
|
|
308
|
+
`https://sdk.myinvois.hasil.gov.my/documents/`.
|
|
309
|
+
- **The samples** of each document type, in JSON and XML,
|
|
310
|
+
`https://sdk.myinvois.hasil.gov.my/files/sdksamples/`; the JSON ones are
|
|
311
|
+
kept under `test/fixtures/sdk/`, with their checksums.
|
|
312
|
+
- **The mathematical mappings**,
|
|
313
|
+
<https://sdk.myinvois.hasil.gov.my/mathematical-mappings/>.
|
|
314
|
+
- **The validators and their error codes**,
|
|
315
|
+
<https://sdk.myinvois.hasil.gov.my/document-validation-rules/>, and the
|
|
316
|
+
pages of the core fields (`CF…`), code (`CV…`), currency (`DC…`),
|
|
317
|
+
referenced documents (`DR…`) and taxpayer (`ERR…`) validators under
|
|
318
|
+
`https://sdk.myinvois.hasil.gov.my/documentvalidationrules/`.
|
|
319
|
+
- **The FAQ**, <https://sdk.myinvois.hasil.gov.my/faq/> — the shape of a
|
|
320
|
+
TIN, versions 1.0 and 1.1, the signature, the size of a submission.
|
|
321
|
+
- **The signature**, <https://sdk.myinvois.hasil.gov.my/signature/>, which
|
|
322
|
+
this package leaves to the host.
|
|
323
|
+
|
|
324
|
+
**The code tables**, each from its page and the JSON file LHDNM publishes
|
|
325
|
+
beside it, kept unmodified under `test/codelist/` and compiled into
|
|
326
|
+
`src/codelists.ts` by `scripts/build-codelists.mjs`:
|
|
327
|
+
|
|
328
|
+
| Table | Page | File | Exported as |
|
|
329
|
+
|---|---|---|---|
|
|
330
|
+
| e-Invoice types (8) | <https://sdk.myinvois.hasil.gov.my/codes/e-invoice-types/> | `EInvoiceTypes.json` | `EINVOICE_TYPES` |
|
|
331
|
+
| Tax types (7) | <https://sdk.myinvois.hasil.gov.my/codes/tax-types/> | `TaxTypes.json` | `TAX_TYPES` |
|
|
332
|
+
| Classification codes (45) | <https://sdk.myinvois.hasil.gov.my/codes/classification-codes/> | `ClassificationCodes.json` | `CLASSIFICATION_CODES` |
|
|
333
|
+
| MSIC codes (1,175 rows, 1,174 codes: `16211` is listed twice) | <https://sdk.myinvois.hasil.gov.my/codes/msic-codes/> | `MSICSubCategoryCodes.json` | `MSIC_CODES` |
|
|
334
|
+
| State codes (17) | <https://sdk.myinvois.hasil.gov.my/codes/state-codes/> | `StateCodes.json` | `STATE_CODES` |
|
|
335
|
+
| Country codes (253, ISO 3166-1 alpha-3) | <https://sdk.myinvois.hasil.gov.my/codes/countries/> | `CountryCodes.json` | `COUNTRY_CODES` |
|
|
336
|
+
| Currency codes (180, ISO 4217) | <https://sdk.myinvois.hasil.gov.my/codes/currencies/> | `CurrencyCodes.json` | `CURRENCY_CODES` |
|
|
337
|
+
| Unit types (2,162, UN/ECE Recommendation 20) | <https://sdk.myinvois.hasil.gov.my/codes/unit-types/> | `UnitTypes.json` | `UNIT_CODES` |
|
|
338
|
+
| Payment modes (8) | <https://sdk.myinvois.hasil.gov.my/codes/payment-methods/> | `PaymentMethods.json` | `PAYMENT_MODES` |
|
|
339
|
+
|
|
340
|
+
**ISO 3166-1, two letters to three.** The books hold a country as its
|
|
341
|
+
two-letter code and MyInvois wants the three-letter one; the pairs are the
|
|
342
|
+
columns "ISO-alpha2 Code" and "ISO-alpha3 Code" of the M49 table of the
|
|
343
|
+
United Nations Statistics Division, *Standard country or area codes for
|
|
344
|
+
statistical use (M49)*, <https://unstats.un.org/unsd/methodology/m49/overview/>,
|
|
345
|
+
read on 19 October 2026 and kept as `test/codelist/m49-iso-alpha.csv`. A
|
|
346
|
+
pair is kept where its three-letter code is in LHDNM's list (`ALPHA3`, 248
|
|
347
|
+
pairs).
|
|
348
|
+
|
|
349
|
+
**The schema.** OASIS UBL 2.1, the schemas `@ekwo-ai/peppol-ubl` keeps under
|
|
350
|
+
its `test/xsd/`: `test/schema.test.ts` validates every XML document against
|
|
351
|
+
them from there.
|
|
352
|
+
|
|
353
|
+
**The law.** Income Tax Act 1967, section 82C, and the Income Tax (Issuance
|
|
354
|
+
of Electronic Invoice) Rules 2024 \[P.U. (A) 265/2024\], as the Malaysian
|
|
355
|
+
pack of Ekwo OS cites them (`packs/my/pack.json`).
|
|
356
|
+
|
|
357
|
+
## Licence
|
|
358
|
+
|
|
359
|
+
MIT. The files under `test/codelist/` and `test/fixtures/sdk/` are LHDNM's
|
|
360
|
+
and the United Nations', kept as published, are fixtures of the tests, and
|
|
361
|
+
are not part of the published package.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `scripts/build-codelists.mjs` — do not edit.
|
|
3
|
+
*
|
|
4
|
+
* Read from the JSON files LHDNM publishes with the MyInvois SDK,
|
|
5
|
+
* <https://sdk.myinvois.hasil.gov.my/codes/>, kept under `test/codelist/`.
|
|
6
|
+
* `test/codelists.test.ts` compares every list here with those files.
|
|
7
|
+
*/
|
|
8
|
+
/** e-Invoice types (`cbc:InvoiceTypeCode`): 8 codes. From `EInvoiceTypes.json`, <https://sdk.myinvois.hasil.gov.my/codes/e-invoice-types/>. */
|
|
9
|
+
export declare const EINVOICE_TYPES: Readonly<Record<string, string>>;
|
|
10
|
+
/** Tax types (`cac:TaxCategory/cbc:ID`): 7 codes. From `TaxTypes.json`, <https://sdk.myinvois.hasil.gov.my/codes/tax-types/>. */
|
|
11
|
+
export declare const TAX_TYPES: Readonly<Record<string, string>>;
|
|
12
|
+
/** Classification codes (`cbc:ItemClassificationCode[@listID="CLASS"]`): 45 codes. From `ClassificationCodes.json`, <https://sdk.myinvois.hasil.gov.my/codes/classification-codes/>. */
|
|
13
|
+
export declare const CLASSIFICATION_CODES: Readonly<Record<string, string>>;
|
|
14
|
+
/** State codes (`cbc:CountrySubentityCode`): 17 codes. From `StateCodes.json`, <https://sdk.myinvois.hasil.gov.my/codes/state-codes/>. */
|
|
15
|
+
export declare const STATE_CODES: Readonly<Record<string, string>>;
|
|
16
|
+
/** Payment modes (`cbc:PaymentMeansCode`): 8 codes. From `PaymentMethods.json`, <https://sdk.myinvois.hasil.gov.my/codes/payment-methods/>. */
|
|
17
|
+
export declare const PAYMENT_MODES: Readonly<Record<string, string>>;
|
|
18
|
+
/** Country codes, ISO 3166-1 alpha-3 (`cac:Country/cbc:IdentificationCode`): 253 codes. From `CountryCodes.json`, <https://sdk.myinvois.hasil.gov.my/codes/countries/>. */
|
|
19
|
+
export declare const COUNTRY_CODES: ReadonlySet<string>;
|
|
20
|
+
/** Currency codes, ISO 4217 (`cbc:DocumentCurrencyCode`): 180 codes. From `CurrencyCodes.json`, <https://sdk.myinvois.hasil.gov.my/codes/currencies/>. */
|
|
21
|
+
export declare const CURRENCY_CODES: ReadonlySet<string>;
|
|
22
|
+
/** Unit types, UN/ECE Recommendation 20 (`cbc:InvoicedQuantity/@unitCode`): 2162 codes. From `UnitTypes.json`, <https://sdk.myinvois.hasil.gov.my/codes/unit-types/>. */
|
|
23
|
+
export declare const UNIT_CODES: ReadonlySet<string>;
|
|
24
|
+
/** MSIC codes (`cbc:IndustryClassificationCode`): 1175 codes. From `MSICSubCategoryCodes.json`, <https://sdk.myinvois.hasil.gov.my/codes/msic-codes/>. */
|
|
25
|
+
export declare const MSIC_CODES: ReadonlySet<string>;
|
|
26
|
+
/**
|
|
27
|
+
* ISO 3166-1 alpha-2 to alpha-3: 248 pairs. From the M49 table of the UN Statistics
|
|
28
|
+
* Division (`m49-iso-alpha.csv`), <https://unstats.un.org/unsd/methodology/m49/overview/>,
|
|
29
|
+
* kept where the alpha-3 code is in COUNTRY_CODES.
|
|
30
|
+
*/
|
|
31
|
+
export declare const ALPHA3: Readonly<Record<string, string>>;
|
|
32
|
+
//# sourceMappingURL=codelists.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codelists.d.ts","sourceRoot":"","sources":["../src/codelists.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,+IAA+I;AAC/I,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS3D,CAAC;AAEF,iIAAiI;AACjI,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQtD,CAAC;AAEF,wLAAwL;AACxL,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA8CjE,CAAC;AAEF,0IAA0I;AAC1I,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBxD,CAAC;AAEF,+IAA+I;AAC/I,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS1D,CAAC;AAEF,2KAA2K;AAC3K,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,CAY7C,CAAC;AAEF,0JAA0J;AAC1J,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,MAAM,CAS9C,CAAC;AAEF,yKAAyK;AACzK,eAAO,MAAM,UAAU,EAAE,WAAW,CAAC,MAAM,CAyF1C,CAAC;AAEF,0JAA0J;AAC1J,eAAO,MAAM,UAAU,EAAE,WAAW,CAAC,MAAM,CA2E1C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAyPnD,CAAC"}
|