Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions main/tax-packs/community-singapore.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
18 changes: 17 additions & 1 deletion tests/community-tax-packs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down
Loading