From 6ffbf6fa865470b479684441a109b1cc43c5f7ea Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 15 Sep 2026 10:28:48 +0700 Subject: [PATCH] feat(tax): add Singapore GST community tax pack --- main/tax-packs/community-singapore.json | 55 +++++++++++++++++++++++++ tests/community-tax-packs.test.ts | 18 +++++++- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 main/tax-packs/community-singapore.json diff --git a/main/tax-packs/community-singapore.json b/main/tax-packs/community-singapore.json new file mode 100644 index 000000000..285b2249d --- /dev/null +++ b/main/tax-packs/community-singapore.json @@ -0,0 +1,55 @@ +{ + "schemaVersion": 1, + "id": "community-singapore", + "publisher": "FloCafe Community", + "sourceType": "community", + "version": "0.0.1", + "country": "SG", + "jurisdiction": "*", + "currency": "SGD", + "effectiveFrom": "2026-01-01", + "publishedAt": "2026-08-24", + "minFloVersion": "2.4.0", + "taxPoint": "finalized_at", + "inclusivePricingDefault": false, + "registrationNumberLabel": "UEN", + "registrationNumberFormat": { + "pattern": "^(?:\\d{8,9}[A-Z]|[ST]\\d{2}[A-Z]{2}\\d{4}[A-Z])$", + "description": "Singapore UEN (e.g. 12345678A, 200812345A, or T08LL1234A)" + }, + "categories": [ + { "id": "standard", "label": "Standard (GST 9%)", "ruleIds": ["gst"] }, + { "id": "packaging", "label": "Packaging", "ruleIds": ["gst"] }, + { "id": "delivery", "label": "Delivery", "ruleIds": ["gst"] }, + { "id": "service_charge", "label": "Service charge", "ruleIds": ["gst"] }, + { "id": "addon", "label": "Add-on", "ruleIds": ["gst"] }, + { "id": "unclassified", "label": "Unclassified", "ruleIds": ["gst"] } + ], + "defaultCategories": { + "product": "standard", + "packaging": "packaging", + "delivery": "delivery", + "service_charge": "service_charge", + "addon": "addon" + }, + "unclassifiedCategoryId": "unclassified", + "rules": [ + { + "id": "gst", + "label": "GST", + "type": "percent", + "categoryIds": ["standard", "packaging", "delivery", "service_charge", "addon", "unclassified"], + "rate": "9" + } + ], + "taxRounding": { + "scope": "document", + "method": "half_up", + "decimalPlaces": 2, + "remainderAllocation": "largest_remainder" + }, + "payableRounding": { + "increment": "0.01", + "method": "half_up" + } +} diff --git a/tests/community-tax-packs.test.ts b/tests/community-tax-packs.test.ts index d34638924..9e49fb19d 100644 --- a/tests/community-tax-packs.test.ts +++ b/tests/community-tax-packs.test.ts @@ -56,7 +56,7 @@ function main() { } const packs = loadCommunityPacks(); - assert.ok(packs.length >= 13, `expected at least 13 community packs, found ${packs.length}`); + assert.ok(packs.length >= 14, `expected at least 14 community packs, found ${packs.length}`); try { for (const { file, pack } of packs) { @@ -128,6 +128,22 @@ function main() { `${file}/${category.id}: payable total must be finite and non-negative`); } console.log(` ✓ every category produces a sane, non-negative calculation`); + + if (pack.country === 'SG' && pack.registrationNumberFormat?.pattern) { + const regex = new RegExp(pack.registrationNumberFormat.pattern, 'i'); + assert.ok(regex.test('12345678A'), `${file}: must accept 8-digit business UEN`); + assert.ok(regex.test('52812345X'), `${file}: must accept 8-digit business UEN`); + assert.ok(regex.test('200812345A'), `${file}: must accept 9-digit company UEN`); + assert.ok(regex.test('199912345Z'), `${file}: must accept 9-digit company UEN`); + assert.ok(regex.test('T08LL1234A'), `${file}: must accept LLP UEN`); + assert.ok(regex.test('S85FC1234B'), `${file}: must accept foreign company UEN`); + assert.ok(regex.test('T20SS0001D'), `${file}: must accept society UEN`); + assert.equal(regex.test('1234567A'), false, `${file}: must reject 7-digit UEN`); + assert.equal(regex.test('1234567890A'), false, `${file}: must reject 10-digit numeric UEN`); + assert.equal(regex.test('R08LL1234A'), false, `${file}: must reject R prefix`); + assert.equal(regex.test('T08123456A'), false, `${file}: must reject missing entity type letters`); + console.log(` ✓ registrationNumberFormat validates Singapore UEN test vectors`); + } } } finally { closeDatabase();