@stamhoofd/models 2.120.6 → 2.122.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/dist/factories/GroupFactory.d.ts.map +1 -1
- package/dist/factories/GroupFactory.js +1 -1
- package/dist/factories/GroupFactory.js.map +1 -1
- package/dist/factories/OrganizationFactory.d.ts +2 -1
- package/dist/factories/OrganizationFactory.d.ts.map +1 -1
- package/dist/factories/OrganizationFactory.js +9 -1
- package/dist/factories/OrganizationFactory.js.map +1 -1
- package/dist/factories/RegistrationInvitationFactory.d.ts +15 -0
- package/dist/factories/RegistrationInvitationFactory.d.ts.map +1 -0
- package/dist/factories/RegistrationInvitationFactory.js +18 -0
- package/dist/factories/RegistrationInvitationFactory.js.map +1 -0
- package/dist/factories/STPackageFactory.js.map +1 -1
- package/dist/factories/UserFactory.d.ts.map +1 -1
- package/dist/factories/UserFactory.js +2 -2
- package/dist/factories/UserFactory.js.map +1 -1
- package/dist/factories/index.d.ts +1 -0
- package/dist/factories/index.d.ts.map +1 -1
- package/dist/factories/index.js +1 -0
- package/dist/factories/index.js.map +1 -1
- package/dist/helpers/EmailBuilder.d.ts.map +1 -1
- package/dist/helpers/EmailBuilder.js +8 -8
- package/dist/helpers/EmailBuilder.js.map +1 -1
- package/dist/helpers/Handlebars.d.ts.map +1 -1
- package/dist/helpers/Handlebars.js +10 -1
- package/dist/helpers/Handlebars.js.map +1 -1
- package/dist/helpers/InvoiceCounter.d.ts +24 -0
- package/dist/helpers/InvoiceCounter.d.ts.map +1 -0
- package/dist/helpers/InvoiceCounter.js +133 -0
- package/dist/helpers/InvoiceCounter.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/migrations/1605262045-import-postcodes.d.ts.map +1 -1
- package/dist/migrations/1605262045-import-postcodes.js +58 -24
- package/dist/migrations/1605262045-import-postcodes.js.map +1 -1
- package/dist/migrations/1605262046-import-postcodes-nl.d.ts.map +1 -1
- package/dist/migrations/1605262046-import-postcodes-nl.js +54 -17
- package/dist/migrations/1605262046-import-postcodes-nl.js.map +1 -1
- package/dist/migrations/1719567881-organization-periodId.sql +2 -0
- package/dist/migrations/1719567882-groups-periodId.sql +2 -0
- package/dist/migrations/1720080975-convert-charset.d.ts +4 -0
- package/dist/migrations/1720080975-convert-charset.d.ts.map +1 -0
- package/dist/migrations/1720080975-convert-charset.js +26 -0
- package/dist/migrations/1720080975-convert-charset.js.map +1 -0
- package/dist/migrations/1720080976-convert-charset-leads.d.ts.map +1 -1
- package/dist/migrations/1720080976-convert-charset-leads.js +11 -10
- package/dist/migrations/1720080976-convert-charset-leads.js.map +1 -1
- package/dist/migrations/1721400546-users-memberId.sql +2 -0
- package/dist/migrations/1722269236-group-waitinglist-id.sql +2 -1
- package/dist/migrations/1722525785-balance-item-paying-organization-id.sql +2 -0
- package/dist/migrations/1722525787-depending-balance-item.sql +2 -0
- package/dist/migrations/1722963554-registration-group-price-and-options.sql +1 -1
- package/dist/migrations/1723652797-payments-paying-organization-id-fk.sql +2 -0
- package/dist/migrations/1733317908-added-missing-organization-fk-on-registrations.sql +2 -0
- package/dist/migrations/1733317910-paying-organization-id-fk.sql +2 -0
- package/dist/migrations/1733504881-negative-invoice-id.sql +6 -0
- package/dist/migrations/1733994455-balance-item-status-open.d.ts +4 -0
- package/dist/migrations/1733994455-balance-item-status-open.d.ts.map +1 -0
- package/dist/migrations/1733994455-balance-item-status-open.js +28 -0
- package/dist/migrations/1733994455-balance-item-status-open.js.map +1 -0
- package/dist/migrations/1763216320-bigint-balance-item-payments.sql +2 -0
- package/dist/migrations/1763216320-bigint-balance-items.sql +5 -0
- package/dist/migrations/1763216320-bigint-orders.sql +2 -0
- package/dist/migrations/1763216320-bigint-payments.sql +2 -0
- package/dist/migrations/1763216332-bigint-balance-item-price-total.sql +2 -0
- package/dist/migrations/1769087808-corrected-invoice-user-agent.sql +2 -0
- package/dist/migrations/1769087809-payments-invoice-id.sql +2 -0
- package/dist/migrations/1772033555-balance-item-package-id.sql +2 -0
- package/dist/migrations/1776873089-create-registration-invitations-table.sql +13 -0
- package/dist/migrations/1778657958-payments-create-mandate.sql +2 -0
- package/dist/migrations/1778657959-payments-mandate-id.sql +2 -0
- package/dist/migrations/1778796615-payments-reversing-payment-id.sql +5 -0
- package/dist/migrations/1778950642-price-invoiced.sql +2 -0
- package/dist/migrations/1779443446-transfer-fees.sql +3 -0
- package/dist/migrations/1779709174-used-register-code-balance-item-id.sql +5 -0
- package/dist/migrations/1779968328-payments-admin-user-id.sql +5 -0
- package/dist/migrations/1779970611-payments-refunded-amount.sql +2 -0
- package/dist/migrations/1779972640-balance-items-failed-at.sql +2 -0
- package/dist/migrations/1780328285-document-template-locked.sql +2 -0
- package/dist/migrations/1780328286-document-locked.sql +2 -0
- package/dist/migrations/1780412083-documents-set-locked.d.ts +4 -0
- package/dist/migrations/1780412083-documents-set-locked.d.ts.map +1 -0
- package/dist/migrations/1780412083-documents-set-locked.js +14 -0
- package/dist/migrations/1780412083-documents-set-locked.js.map +1 -0
- package/dist/migrations/1780928401-v1-groups-migration-data.d.ts +4 -0
- package/dist/migrations/1780928401-v1-groups-migration-data.d.ts.map +1 -0
- package/dist/migrations/1780928401-v1-groups-migration-data.js +44 -0
- package/dist/migrations/1780928401-v1-groups-migration-data.js.map +1 -0
- package/dist/models/BalanceItem.d.ts +14 -1
- package/dist/models/BalanceItem.d.ts.map +1 -1
- package/dist/models/BalanceItem.js +91 -41
- package/dist/models/BalanceItem.js.map +1 -1
- package/dist/models/CachedBalance.d.ts +6 -1
- package/dist/models/CachedBalance.d.ts.map +1 -1
- package/dist/models/CachedBalance.js +3 -2
- package/dist/models/CachedBalance.js.map +1 -1
- package/dist/models/Document.d.ts +4 -0
- package/dist/models/Document.d.ts.map +1 -1
- package/dist/models/Document.js +26 -3
- package/dist/models/Document.js.map +1 -1
- package/dist/models/DocumentTemplate.d.ts +4 -0
- package/dist/models/DocumentTemplate.d.ts.map +1 -1
- package/dist/models/DocumentTemplate.js +37 -1
- package/dist/models/DocumentTemplate.js.map +1 -1
- package/dist/models/Email.d.ts.map +1 -1
- package/dist/models/Email.js +1 -1
- package/dist/models/Email.js.map +1 -1
- package/dist/models/EmailVerificationCode.d.ts.map +1 -1
- package/dist/models/EmailVerificationCode.js +3 -13
- package/dist/models/EmailVerificationCode.js.map +1 -1
- package/dist/models/Event.d.ts +2 -1
- package/dist/models/Event.d.ts.map +1 -1
- package/dist/models/Event.js +3 -0
- package/dist/models/Event.js.map +1 -1
- package/dist/models/EventNotification.d.ts.map +1 -1
- package/dist/models/EventNotification.js +5 -5
- package/dist/models/EventNotification.js.map +1 -1
- package/dist/models/Group.d.ts +4 -0
- package/dist/models/Group.d.ts.map +1 -1
- package/dist/models/Group.js +17 -0
- package/dist/models/Group.js.map +1 -1
- package/dist/models/Invoice.d.ts +1 -0
- package/dist/models/Invoice.d.ts.map +1 -1
- package/dist/models/Invoice.js +8 -8
- package/dist/models/Invoice.js.map +1 -1
- package/dist/models/MemberPlatformMembership.d.ts.map +1 -1
- package/dist/models/MemberPlatformMembership.js +9 -0
- package/dist/models/MemberPlatformMembership.js.map +1 -1
- package/dist/models/MollieToken.d.ts +4 -8
- package/dist/models/MollieToken.d.ts.map +1 -1
- package/dist/models/MollieToken.js +37 -90
- package/dist/models/MollieToken.js.map +1 -1
- package/dist/models/Order.d.ts.map +1 -1
- package/dist/models/Order.js +1 -0
- package/dist/models/Order.js.map +1 -1
- package/dist/models/Organization.d.ts +30 -23
- package/dist/models/Organization.d.ts.map +1 -1
- package/dist/models/Organization.js +113 -61
- package/dist/models/Organization.js.map +1 -1
- package/dist/models/PasswordToken.d.ts +5 -1
- package/dist/models/PasswordToken.d.ts.map +1 -1
- package/dist/models/PasswordToken.js +18 -17
- package/dist/models/PasswordToken.js.map +1 -1
- package/dist/models/Payment.d.ts +35 -3
- package/dist/models/Payment.d.ts.map +1 -1
- package/dist/models/Payment.js +66 -3
- package/dist/models/Payment.js.map +1 -1
- package/dist/models/Registration.d.ts +1 -0
- package/dist/models/Registration.d.ts.map +1 -1
- package/dist/models/Registration.js +4 -3
- package/dist/models/Registration.js.map +1 -1
- package/dist/models/RegistrationInvitation.d.ts +14 -0
- package/dist/models/RegistrationInvitation.d.ts.map +1 -0
- package/dist/models/RegistrationInvitation.js +45 -0
- package/dist/models/RegistrationInvitation.js.map +1 -0
- package/dist/models/STCredit.d.ts +4 -0
- package/dist/models/STCredit.d.ts.map +1 -1
- package/dist/models/STCredit.js +28 -0
- package/dist/models/STCredit.js.map +1 -1
- package/dist/models/STInvoice.d.ts +7 -1
- package/dist/models/STInvoice.d.ts.map +1 -1
- package/dist/models/STInvoice.js +9 -0
- package/dist/models/STInvoice.js.map +1 -1
- package/dist/models/STPackage.d.ts +4 -0
- package/dist/models/STPackage.d.ts.map +1 -1
- package/dist/models/STPackage.js +12 -1
- package/dist/models/STPackage.js.map +1 -1
- package/dist/models/UsedRegisterCode.d.ts +9 -0
- package/dist/models/UsedRegisterCode.d.ts.map +1 -1
- package/dist/models/UsedRegisterCode.js +31 -0
- package/dist/models/UsedRegisterCode.js.map +1 -1
- package/dist/models/User.d.ts +1 -1
- package/dist/models/User.d.ts.map +1 -1
- package/dist/models/User.js +1 -1
- package/dist/models/User.js.map +1 -1
- package/dist/models/_relations.js +25 -0
- package/dist/models/_relations.js.map +1 -1
- package/dist/models/addresses/City.d.ts +4 -4
- package/dist/models/addresses/City.d.ts.map +1 -1
- package/dist/models/addresses/City.js +6 -6
- package/dist/models/addresses/City.js.map +1 -1
- package/dist/models/addresses/PostalCode.d.ts +2 -2
- package/dist/models/addresses/PostalCode.d.ts.map +1 -1
- package/dist/models/addresses/PostalCode.js +4 -3
- package/dist/models/addresses/PostalCode.js.map +1 -1
- package/dist/models/addresses/Street.d.ts +3 -3
- package/dist/models/addresses/Street.d.ts.map +1 -1
- package/dist/models/addresses/Street.js +4 -4
- package/dist/models/addresses/Street.js.map +1 -1
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +2 -0
- package/dist/models/index.js.map +1 -1
- package/dist/models/v1GroupMigrationData.d.ts +22 -0
- package/dist/models/v1GroupMigrationData.d.ts.map +1 -0
- package/dist/models/v1GroupMigrationData.js +48 -0
- package/dist/models/v1GroupMigrationData.js.map +1 -0
- package/package.json +41 -13
- package/src/factories/GroupFactory.ts +4 -6
- package/src/factories/OrganizationFactory.ts +12 -4
- package/src/factories/RegistrationInvitationFactory.ts +24 -0
- package/src/factories/STPackageFactory.ts +2 -2
- package/src/factories/UserFactory.ts +4 -5
- package/src/factories/index.ts +1 -0
- package/src/helpers/EmailBuilder.ts +19 -28
- package/src/helpers/Handlebars.ts +10 -1
- package/src/helpers/InvoiceCounter.test.ts +220 -0
- package/src/helpers/InvoiceCounter.ts +162 -0
- package/src/index.ts +0 -1
- package/src/migrations/1605262045-import-postcodes.ts +62 -25
- package/src/migrations/1605262046-import-postcodes-nl.ts +58 -17
- package/src/migrations/1719567881-organization-periodId.sql +2 -0
- package/src/migrations/1719567882-groups-periodId.sql +2 -0
- package/src/migrations/1720080975-convert-charset.ts +34 -0
- package/src/migrations/1720080976-convert-charset-leads.ts +16 -13
- package/src/migrations/1721400546-users-memberId.sql +2 -0
- package/src/migrations/1722269236-group-waitinglist-id.sql +2 -1
- package/src/migrations/1722525785-balance-item-paying-organization-id.sql +2 -0
- package/src/migrations/1722525787-depending-balance-item.sql +2 -0
- package/src/migrations/1722963554-registration-group-price-and-options.sql +1 -1
- package/src/migrations/1723652797-payments-paying-organization-id-fk.sql +2 -0
- package/src/migrations/1733317908-added-missing-organization-fk-on-registrations.sql +2 -0
- package/src/migrations/1733317910-paying-organization-id-fk.sql +2 -0
- package/src/migrations/1733504881-negative-invoice-id.sql +6 -0
- package/src/migrations/1733994455-balance-item-status-open.ts +30 -0
- package/src/migrations/1763216320-bigint-balance-item-payments.sql +2 -0
- package/src/migrations/1763216320-bigint-balance-items.sql +5 -0
- package/src/migrations/1763216320-bigint-orders.sql +2 -0
- package/src/migrations/1763216320-bigint-payments.sql +2 -0
- package/src/migrations/1763216332-bigint-balance-item-price-total.sql +2 -0
- package/src/migrations/1769087808-corrected-invoice-user-agent.sql +2 -0
- package/src/migrations/1769087809-payments-invoice-id.sql +2 -0
- package/src/migrations/1772033555-balance-item-package-id.sql +2 -0
- package/src/migrations/1776873089-create-registration-invitations-table.sql +13 -0
- package/src/migrations/1778657958-payments-create-mandate.sql +2 -0
- package/src/migrations/1778657959-payments-mandate-id.sql +2 -0
- package/src/migrations/1778796615-payments-reversing-payment-id.sql +5 -0
- package/src/migrations/1778950642-price-invoiced.sql +2 -0
- package/src/migrations/1779443446-transfer-fees.sql +3 -0
- package/src/migrations/1779709174-used-register-code-balance-item-id.sql +5 -0
- package/src/migrations/1779968328-payments-admin-user-id.sql +5 -0
- package/src/migrations/1779970611-payments-refunded-amount.sql +2 -0
- package/src/migrations/1779972640-balance-items-failed-at.sql +2 -0
- package/src/migrations/1780328285-document-template-locked.sql +2 -0
- package/src/migrations/1780328286-document-locked.sql +2 -0
- package/src/migrations/1780412083-documents-set-locked.ts +18 -0
- package/src/migrations/1780928401-v1-groups-migration-data.ts +50 -0
- package/src/models/BalanceItem.ts +98 -43
- package/src/models/CachedBalance.test.ts +46 -46
- package/src/models/CachedBalance.ts +7 -7
- package/src/models/Document.ts +34 -13
- package/src/models/DocumentTemplate.ts +56 -17
- package/src/models/Email.test.ts +3 -3
- package/src/models/Email.ts +28 -49
- package/src/models/EmailVerificationCode.ts +8 -22
- package/src/models/Event.ts +6 -4
- package/src/models/EventNotification.ts +6 -6
- package/src/models/Group.ts +24 -3
- package/src/models/Invoice.ts +10 -9
- package/src/models/MemberPlatformMembership.test.ts +70 -0
- package/src/models/MemberPlatformMembership.ts +16 -12
- package/src/models/MollieToken.ts +42 -102
- package/src/models/Order.ts +14 -26
- package/src/models/Organization.ts +143 -86
- package/src/models/PasswordToken.ts +21 -21
- package/src/models/Payment.ts +61 -4
- package/src/models/Registration.ts +6 -5
- package/src/models/RegistrationInvitation.ts +40 -0
- package/src/models/STCredit.ts +32 -0
- package/src/models/STInvoice.ts +11 -5
- package/src/models/STPackage.ts +19 -14
- package/src/models/UsedRegisterCode.ts +34 -0
- package/src/models/User.ts +6 -7
- package/src/models/_relations.ts +29 -0
- package/src/models/addresses/City.ts +8 -6
- package/src/models/addresses/PostalCode.test.ts +1 -0
- package/src/models/addresses/PostalCode.ts +5 -3
- package/src/models/addresses/Street.ts +6 -4
- package/src/models/index.ts +3 -0
- package/src/models/v1GroupMigrationData.ts +43 -0
- package/dist/helpers/MemberMerger.d.ts +0 -14
- package/dist/helpers/MemberMerger.d.ts.map +0 -1
- package/dist/helpers/MemberMerger.js +0 -364
- package/dist/helpers/MemberMerger.js.map +0 -1
- package/dist/migrations/1720080975-convert-charset.sql +0 -85
- package/dist/migrations/1723202126-member-number-index.sql +0 -2
- package/dist/models/OneTimeToken.d.ts +0 -38
- package/dist/models/OneTimeToken.d.ts.map +0 -1
- package/dist/models/OneTimeToken.js +0 -125
- package/dist/models/OneTimeToken.js.map +0 -1
- package/src/helpers/MemberMerger.test.ts +0 -782
- package/src/helpers/MemberMerger.ts +0 -577
- package/src/migrations/1720080975-convert-charset.sql +0 -85
- package/src/migrations/1723202126-member-number-index.sql +0 -2
- package/src/models/OneTimeToken.ts +0 -133
package/package.json
CHANGED
|
@@ -1,37 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/models",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.122.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"types": "./dist/index.d.ts",
|
|
6
5
|
"main": "./dist/index.js",
|
|
7
|
-
"
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
8
7
|
"sideEffects": [
|
|
9
8
|
"./src/models/_relations.ts",
|
|
10
9
|
"./dist/models/_relations.js"
|
|
11
10
|
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./dist/index.js",
|
|
13
|
+
"./*.ts": "./dist/*.js",
|
|
14
|
+
"./*.js": "./dist/*.js"
|
|
15
|
+
},
|
|
16
|
+
"imports": {
|
|
17
|
+
"#*": "./src/*.ts",
|
|
18
|
+
"#*.ts": "./src/*.ts"
|
|
19
|
+
},
|
|
12
20
|
"files": [
|
|
13
21
|
"dist",
|
|
14
22
|
"src"
|
|
15
23
|
],
|
|
16
24
|
"scripts": {
|
|
17
25
|
"build": "tsc --build tsconfig.build.json && mkdir -p ./dist/migrations && rsync --delete --exclude='*.ts' --exclude='*.js' --exclude='*.map' -r --checksum ./src/migrations/ ./dist/migrations/ && rsync -r --checksum ./src/assets/ ./dist/assets/",
|
|
26
|
+
"lint": "eslint",
|
|
18
27
|
"test": "vitest",
|
|
19
|
-
"
|
|
20
|
-
},
|
|
21
|
-
"peerDependencies": {
|
|
22
|
-
"@simonbackx/simple-database": "1.36.12",
|
|
23
|
-
"@stamhoofd/email": "*",
|
|
24
|
-
"@stamhoofd/queues": "*"
|
|
28
|
+
"test:coverage": "vitest --coverage"
|
|
25
29
|
},
|
|
26
30
|
"dependencies": {
|
|
27
|
-
"@aws-sdk/client-s3": "
|
|
28
|
-
"
|
|
31
|
+
"@aws-sdk/client-s3": "3.1068.0",
|
|
32
|
+
"@aws-sdk/client-sesv2": "3.1068.0",
|
|
33
|
+
"@bwip-js/node": "4.11.1",
|
|
34
|
+
"@mollie/api-client": "4.5.0",
|
|
35
|
+
"@simonbackx/simple-encoding": "2.26.10",
|
|
36
|
+
"@simonbackx/simple-endpoints": "1.21.1",
|
|
37
|
+
"@simonbackx/simple-errors": "1.5.0",
|
|
38
|
+
"argon2": "0.44.0",
|
|
39
|
+
"base-x": "3.0.11",
|
|
40
|
+
"handlebars": "4.7.9",
|
|
41
|
+
"sharp": "0.35.1"
|
|
29
42
|
},
|
|
30
43
|
"devDependencies": {
|
|
31
|
-
"@simonbackx/simple-database": "1.
|
|
44
|
+
"@simonbackx/simple-database": "1.37.1",
|
|
45
|
+
"@stamhoofd/test-utils": "2.122.0",
|
|
46
|
+
"@types/luxon": "3.7.1",
|
|
47
|
+
"@types/uuid": "8.3.4",
|
|
48
|
+
"vitest": "4.1.8"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@simonbackx/simple-database": "1.37.1",
|
|
52
|
+
"@stamhoofd/backend-i18n": "*",
|
|
53
|
+
"@stamhoofd/email": "*",
|
|
54
|
+
"@stamhoofd/queues": "*",
|
|
55
|
+
"@stamhoofd/sql": "*",
|
|
56
|
+
"@stamhoofd/structures": "*",
|
|
57
|
+
"@stamhoofd/types": "*",
|
|
58
|
+
"@stamhoofd/utility": "*"
|
|
32
59
|
},
|
|
60
|
+
"license": "UNLICENCED",
|
|
33
61
|
"publishConfig": {
|
|
34
62
|
"access": "public"
|
|
35
63
|
},
|
|
36
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "3c022b7d7801fb105acfaa401432a8d029abda61"
|
|
37
65
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Factory } from '@simonbackx/simple-database';
|
|
2
|
-
import type { BundleDiscount, GroupPriceDiscount, GroupType} from '@stamhoofd/structures';
|
|
2
|
+
import type { BundleDiscount, GroupPriceDiscount, GroupType } from '@stamhoofd/structures';
|
|
3
3
|
import { BundleDiscountGroupPriceSettings, GroupPrice, GroupSettings, ReduceablePrice, TranslatedString } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
@@ -42,15 +42,14 @@ export class GroupFactory extends Factory<Options, Group> {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
group.periodId = this.options.period.id;
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
45
|
+
} else {
|
|
47
46
|
group.periodId = organization.periodId;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
group.waitingListId = this.options.waitingListId ?? null;
|
|
51
50
|
|
|
52
51
|
group.settings = GroupSettings.create({
|
|
53
|
-
name: this.options.name ?? new TranslatedString('Group
|
|
52
|
+
name: this.options.name ?? new TranslatedString('Group ' + this.randomString(5)),
|
|
54
53
|
startDate: new Date(new Date().getTime() - 10 * 1000),
|
|
55
54
|
endDate: new Date(new Date().getTime() + 10 * 1000),
|
|
56
55
|
registrationStartDate: new Date(new Date().getTime() - 10 * 1000),
|
|
@@ -80,8 +79,7 @@ export class GroupFactory extends Factory<Options, Group> {
|
|
|
80
79
|
for (const discount of this.options.bundleDiscounts) {
|
|
81
80
|
map.set(discount, null);
|
|
82
81
|
}
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
82
|
+
} else {
|
|
85
83
|
map = this.options.bundleDiscounts;
|
|
86
84
|
}
|
|
87
85
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Factory } from '@simonbackx/simple-database';
|
|
2
|
-
import type { PermissionRoleDetailed } from '@stamhoofd/structures';
|
|
2
|
+
import type { PermissionRoleDetailed, STPackageBundle, STPackageType } from '@stamhoofd/structures';
|
|
3
3
|
import { Address, OrganizationMetaData, OrganizationType } from '@stamhoofd/structures';
|
|
4
4
|
import { Formatter } from '@stamhoofd/utility';
|
|
5
5
|
|
|
@@ -7,6 +7,7 @@ import { Organization } from '../models/Organization.js';
|
|
|
7
7
|
import type { RegistrationPeriod } from '../models/RegistrationPeriod.js';
|
|
8
8
|
import { RegistrationPeriodFactory } from './RegistrationPeriodFactory.js';
|
|
9
9
|
import { Country } from '@stamhoofd/types/Country';
|
|
10
|
+
import { STPackageFactory } from './STPackageFactory.js';
|
|
10
11
|
|
|
11
12
|
class Options {
|
|
12
13
|
uri?: string;
|
|
@@ -17,12 +18,13 @@ class Options {
|
|
|
17
18
|
roles?: PermissionRoleDetailed[];
|
|
18
19
|
period?: RegistrationPeriod;
|
|
19
20
|
tags?: string[];
|
|
21
|
+
packages?: STPackageBundle[];
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export class OrganizationFactory extends Factory<Options, Organization> {
|
|
23
25
|
async create(): Promise<Organization> {
|
|
24
26
|
const organization = new Organization();
|
|
25
|
-
organization.name = this.options.name ?? 'Organization ' + (new Date().getTime() + Math.floor(Math.random() * 999999));
|
|
27
|
+
organization.name = this.options.name ?? 'Organization ' + (new Date().getTime() + '-' + Math.floor(Math.random() * 999999) + '-' + Math.floor(Math.random() * 999999));
|
|
26
28
|
organization.website = 'https://domain.com';
|
|
27
29
|
organization.registerDomain = this.options.domain ?? null;
|
|
28
30
|
organization.uri = this.options.uri ?? Formatter.slug(organization.name);
|
|
@@ -45,8 +47,7 @@ export class OrganizationFactory extends Factory<Options, Organization> {
|
|
|
45
47
|
|
|
46
48
|
if (this.options.period) {
|
|
47
49
|
period = this.options.period;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
+
} else {
|
|
50
51
|
period = await new RegistrationPeriodFactory({}).create();
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -68,6 +69,13 @@ export class OrganizationFactory extends Factory<Options, Organization> {
|
|
|
68
69
|
await period.save();
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
for (const bundle of this.options.packages ?? []) {
|
|
73
|
+
await new STPackageFactory({
|
|
74
|
+
organization,
|
|
75
|
+
bundle,
|
|
76
|
+
}).create();
|
|
77
|
+
}
|
|
78
|
+
|
|
71
79
|
return organization;
|
|
72
80
|
}
|
|
73
81
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Factory } from '@simonbackx/simple-database';
|
|
2
|
+
|
|
3
|
+
import type { Group } from '../models/Group.js';
|
|
4
|
+
import type { Member } from '../models/Member.js';
|
|
5
|
+
import type { Organization } from '../models/Organization.js';
|
|
6
|
+
import { RegistrationInvitation } from '../models/RegistrationInvitation.js';
|
|
7
|
+
|
|
8
|
+
class Options {
|
|
9
|
+
group: Group;
|
|
10
|
+
member: Member;
|
|
11
|
+
organization: Organization
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class RegistrationInvitationFactory extends Factory<Options, RegistrationInvitation> {
|
|
15
|
+
async create(): Promise<RegistrationInvitation> {
|
|
16
|
+
const invitation = new RegistrationInvitation();
|
|
17
|
+
invitation.groupId = this.options.group.id;
|
|
18
|
+
invitation.memberId = this.options.member.id;
|
|
19
|
+
invitation.organizationId = this.options.organization.id;
|
|
20
|
+
await invitation.save();
|
|
21
|
+
|
|
22
|
+
return invitation;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -24,8 +24,7 @@ export class STPackageFactory extends Factory<Options, STPackage> {
|
|
|
24
24
|
|
|
25
25
|
if (this.options.removeAt) {
|
|
26
26
|
pack.validAt = new Date(this.options.removeAt.getTime() - 365 * 1000 * 60 * 60 * 24);
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
27
|
+
} else {
|
|
29
28
|
pack.validAt = this.options.validAt !== undefined ? this.options.validAt : new Date();
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -38,6 +37,7 @@ export class STPackageFactory extends Factory<Options, STPackage> {
|
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
await pack.save();
|
|
40
|
+
|
|
41
41
|
return pack;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Factory } from '@simonbackx/simple-database';
|
|
2
|
-
import type { Permissions} from '@stamhoofd/structures';
|
|
2
|
+
import type { Permissions } from '@stamhoofd/structures';
|
|
3
3
|
import { NewUser, UserPermissions } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import type { Organization } from '../models/Organization.js';
|
|
@@ -22,13 +22,12 @@ class Options {
|
|
|
22
22
|
|
|
23
23
|
export class UserFactory extends Factory<Options, User> {
|
|
24
24
|
async create(): Promise<User> {
|
|
25
|
-
let organization: Organization | null
|
|
25
|
+
let organization: Organization | null;
|
|
26
26
|
|
|
27
27
|
if (!this.options.organization && STAMHOOFD.userMode !== 'platform' && !this.options.globalPermissions) {
|
|
28
28
|
const organizationFactory = new OrganizationFactory({});
|
|
29
29
|
organization = await organizationFactory.create();
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
30
|
+
} else {
|
|
32
31
|
organization = this.options.organization ?? null;
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -40,7 +39,7 @@ export class UserFactory extends Factory<Options, User> {
|
|
|
40
39
|
organizationId: organization?.id ?? null,
|
|
41
40
|
password,
|
|
42
41
|
}), {
|
|
43
|
-
allowPlatform: !!this.options.globalPermissions
|
|
42
|
+
allowPlatform: !!this.options.globalPermissions,
|
|
44
43
|
});
|
|
45
44
|
if (!user) {
|
|
46
45
|
throw new Error('Unexpected failure when creating user in factory');
|
package/src/factories/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EmailBuilder, EmailInterfaceRecipient } from '@stamhoofd/email';
|
|
2
2
|
import { Email, EmailAddress } from '@stamhoofd/email';
|
|
3
|
-
import type { EmailRecipient as EmailRecipientStruct, EmailTemplateType, OrganizationEmail, Platform as PlatformStruct, Recipient} from '@stamhoofd/structures';
|
|
4
|
-
import { BalanceItem as BalanceItemStruct, ReceivableBalanceType, replaceEmailHtml, replaceEmailText, Replacement } from '@stamhoofd/structures';
|
|
3
|
+
import type { EmailRecipient as EmailRecipientStruct, EmailTemplateType, OrganizationEmail, Platform as PlatformStruct, Recipient } from '@stamhoofd/structures';
|
|
4
|
+
import { BalanceItem as BalanceItemStruct, getAppHost, ReceivableBalanceType, replaceEmailHtml, replaceEmailText, Replacement } from '@stamhoofd/structures';
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
|
|
7
7
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
@@ -13,7 +13,7 @@ import type { Group } from '../models/Group.js';
|
|
|
13
13
|
import type { Organization } from '../models/Organization.js';
|
|
14
14
|
import { Platform } from '../models/Platform.js';
|
|
15
15
|
import { User } from '../models/User.js';
|
|
16
|
-
import type {Webshop} from '../models/Webshop.js';
|
|
16
|
+
import type { Webshop } from '../models/Webshop.js';
|
|
17
17
|
|
|
18
18
|
export type EmailTemplateOptions = {
|
|
19
19
|
type: EmailTemplateType;
|
|
@@ -314,8 +314,7 @@ export async function getEmailBuilder(organization: Organization | null, email:
|
|
|
314
314
|
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
|
|
315
315
|
};
|
|
316
316
|
cleaned.push(recipient);
|
|
317
|
-
}
|
|
318
|
-
catch (e) {
|
|
317
|
+
} catch (e) {
|
|
319
318
|
console.error(e);
|
|
320
319
|
}
|
|
321
320
|
}
|
|
@@ -499,14 +498,14 @@ export function stripSensitiveRecipientReplacements(recipient: Recipient | Email
|
|
|
499
498
|
}
|
|
500
499
|
|
|
501
500
|
// Add dummy unsubscribeUrl
|
|
502
|
-
const dummyUnsubscribeUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? organization
|
|
501
|
+
const dummyUnsubscribeUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? getAppHost('registration', organization, false) : STAMHOOFD.domains.dashboard) + '/unsubscribe?token=example';
|
|
503
502
|
recipient.replacements.push(Replacement.create({
|
|
504
503
|
token: 'unsubscribeUrl',
|
|
505
504
|
value: dummyUnsubscribeUrl,
|
|
506
505
|
}));
|
|
507
506
|
|
|
508
507
|
// dummy signInUrl
|
|
509
|
-
const dummySignInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? organization
|
|
508
|
+
const dummySignInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? getAppHost('registration', organization, false) : STAMHOOFD.domains.dashboard) + '/login';
|
|
510
509
|
recipient.replacements.push(Replacement.create({
|
|
511
510
|
token: 'signInUrl',
|
|
512
511
|
value: dummySignInUrl,
|
|
@@ -536,7 +535,7 @@ export function stripRecipientReplacementsForWebDisplay(recipient: Recipient | E
|
|
|
536
535
|
recipient.replacements = recipient.replacements.filter(r => r.token !== 'unsubscribeUrl' && r.token !== 'loginDetails' && r.token !== 'greeting');
|
|
537
536
|
|
|
538
537
|
// Add dummy unsubscribeUrl
|
|
539
|
-
const dummyUnsubscribeUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? organization
|
|
538
|
+
const dummyUnsubscribeUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? getAppHost('registration', organization, false) : STAMHOOFD.domains.dashboard);
|
|
540
539
|
recipient.replacements.push(Replacement.create({
|
|
541
540
|
token: 'unsubscribeUrl',
|
|
542
541
|
value: dummyUnsubscribeUrl,
|
|
@@ -575,7 +574,7 @@ export async function fillRecipientReplacements(recipient: Recipient | EmailReci
|
|
|
575
574
|
}
|
|
576
575
|
|
|
577
576
|
if (!recipient.email && !recipient.userId) {
|
|
578
|
-
const signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? organization
|
|
577
|
+
const signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? getAppHost('registration', organization, false) : STAMHOOFD.domains.dashboard) + '/login';
|
|
579
578
|
recipient.replacements.push(Replacement.create({
|
|
580
579
|
token: 'signInUrl',
|
|
581
580
|
value: signInUrl,
|
|
@@ -587,8 +586,7 @@ export async function fillRecipientReplacements(recipient: Recipient | EmailReci
|
|
|
587
586
|
value: '',
|
|
588
587
|
}));
|
|
589
588
|
}
|
|
590
|
-
}
|
|
591
|
-
else {
|
|
589
|
+
} else {
|
|
592
590
|
// Default signInUrl
|
|
593
591
|
recipientUser = recipient.userId ? await User.select().where('id', recipient.userId).first(false) : await User.getForAuthentication(organization?.id ?? null, recipient.email!, { allowWithoutAccount: true });
|
|
594
592
|
if (STAMHOOFD.userMode !== 'platform' && recipientUser && recipientUser.organizationId && recipientUser.organizationId !== (organization?.id ?? null)) {
|
|
@@ -600,14 +598,12 @@ export async function fillRecipientReplacements(recipient: Recipient | EmailReci
|
|
|
600
598
|
if (!recipientUser || !recipientUser.hasAccount()) {
|
|
601
599
|
// We can create a special token
|
|
602
600
|
if (recipientUser) {
|
|
603
|
-
signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? organization
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? organization.getHost() : STAMHOOFD.domains.dashboard) + '/account-aanmaken';
|
|
601
|
+
signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? getAppHost('registration', organization, false) : STAMHOOFD.domains.dashboard) + '/account-aanmaken?email=' + encodeURIComponent(recipientUser?.email);
|
|
602
|
+
} else {
|
|
603
|
+
signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? getAppHost('registration', organization, false) : STAMHOOFD.domains.dashboard) + '/account-aanmaken';
|
|
607
604
|
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? organization.getHost() : STAMHOOFD.domains.dashboard) + '/login?email=' + encodeURIComponent(recipientUser.email);
|
|
605
|
+
} else {
|
|
606
|
+
signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? getAppHost('registration', organization, false) : STAMHOOFD.domains.dashboard) + '/login?email=' + encodeURIComponent(recipientUser.email);
|
|
611
607
|
}
|
|
612
608
|
|
|
613
609
|
recipient.replacements.push(Replacement.create({
|
|
@@ -638,8 +634,7 @@ export async function fillRecipientReplacements(recipient: Recipient | EmailReci
|
|
|
638
634
|
}),
|
|
639
635
|
);
|
|
640
636
|
}
|
|
641
|
-
}
|
|
642
|
-
else {
|
|
637
|
+
} else {
|
|
643
638
|
console.log('No member found for user', recipientUser.id);
|
|
644
639
|
}
|
|
645
640
|
}
|
|
@@ -653,8 +648,7 @@ export async function fillRecipientReplacements(recipient: Recipient | EmailReci
|
|
|
653
648
|
: `<p class="description"><em>${$t('%1EB', { email: emailEscaped })}${suffix}</em></p>`,
|
|
654
649
|
}),
|
|
655
650
|
);
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
651
|
+
} else {
|
|
658
652
|
if (recipient.email) {
|
|
659
653
|
const emailEscaped = `<strong>${Formatter.escapeHtml(recipient.email)}</strong>`;
|
|
660
654
|
console.log('No user found for email', recipient.email);
|
|
@@ -665,8 +659,7 @@ export async function fillRecipientReplacements(recipient: Recipient | EmailReci
|
|
|
665
659
|
html: `<p class="description"><em>${$t('%1EB', { email: emailEscaped })}</em></p>`,
|
|
666
660
|
}),
|
|
667
661
|
);
|
|
668
|
-
}
|
|
669
|
-
else {
|
|
662
|
+
} else {
|
|
670
663
|
recipient.replacements.push(
|
|
671
664
|
Replacement.create({
|
|
672
665
|
token: 'loginDetails',
|
|
@@ -702,8 +695,7 @@ export async function fillRecipientReplacements(recipient: Recipient | EmailReci
|
|
|
702
695
|
html: BalanceItemStruct.getDetailsHTMLTable(balanceItems),
|
|
703
696
|
}),
|
|
704
697
|
);
|
|
705
|
-
}
|
|
706
|
-
else {
|
|
698
|
+
} else {
|
|
707
699
|
recipient.replacements.push(
|
|
708
700
|
Replacement.create({
|
|
709
701
|
token: 'outstandingBalance',
|
|
@@ -739,8 +731,7 @@ export async function fillRecipientReplacements(recipient: Recipient | EmailReci
|
|
|
739
731
|
|
|
740
732
|
if (recipient instanceof EmailRecipient) {
|
|
741
733
|
recipient.replacements.push(...recipient.getRecipient().getDefaultReplacements());
|
|
742
|
-
}
|
|
743
|
-
else {
|
|
734
|
+
} else {
|
|
744
735
|
recipient.replacements.push(...recipient.getDefaultReplacements());
|
|
745
736
|
}
|
|
746
737
|
|
|
@@ -50,7 +50,12 @@ class AsyncResolver {
|
|
|
50
50
|
|
|
51
51
|
Handlebars.registerHelper('eq', (a, b) => a == b);
|
|
52
52
|
Handlebars.registerHelper('neq', (a, b) => a !== b);
|
|
53
|
-
Handlebars.registerHelper('formatPrice', a
|
|
53
|
+
Handlebars.registerHelper('formatPrice', (a, options) => {
|
|
54
|
+
if (typeof a !== 'number') return a;
|
|
55
|
+
// round=true rounds to nearest cent (100 units = 1 cent, where 10000 units = €1)
|
|
56
|
+
const value = options?.hash?.round ? Math.round(a / 100) * 100 : a;
|
|
57
|
+
return Formatter.price(value);
|
|
58
|
+
});
|
|
54
59
|
Handlebars.registerHelper('formatDate', (a, options) => {
|
|
55
60
|
if (!(a instanceof Date)) {
|
|
56
61
|
return '';
|
|
@@ -103,6 +108,10 @@ Handlebars.registerHelper('coalesce', (...args) => {
|
|
|
103
108
|
return args.find(a => a !== null && a !== undefined) ?? null;
|
|
104
109
|
});
|
|
105
110
|
|
|
111
|
+
Handlebars.registerHelper('$t', (t: string) => {
|
|
112
|
+
return $t(t);
|
|
113
|
+
});
|
|
114
|
+
|
|
106
115
|
Handlebars.registerHelper('days', (a, b) => {
|
|
107
116
|
if (!(a instanceof Date) || !(b instanceof Date)) {
|
|
108
117
|
return 0;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { InvoiceCounter } from './InvoiceCounter.js';
|
|
3
|
+
import { OrganizationInvoiceSettings } from '@stamhoofd/structures/OrganizationInvoiceSettings.js';
|
|
4
|
+
import { Invoice } from '../models/Invoice.js';
|
|
5
|
+
import { OrganizationFactory } from '../factories/OrganizationFactory.js';
|
|
6
|
+
|
|
7
|
+
// Minimal settings factory
|
|
8
|
+
function makeSettings(overrides: Partial<OrganizationInvoiceSettings> = {}): OrganizationInvoiceSettings {
|
|
9
|
+
return OrganizationInvoiceSettings.create({
|
|
10
|
+
resetMonth: null,
|
|
11
|
+
prefixYear: false,
|
|
12
|
+
...overrides,
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ─────────────────────────────────────────────
|
|
17
|
+
// parseNumber
|
|
18
|
+
// ─────────────────────────────────────────────
|
|
19
|
+
describe('InvoiceCounter.parseNumber', () => {
|
|
20
|
+
describe('without year prefix', () => {
|
|
21
|
+
const settings = makeSettings({ prefixYear: false });
|
|
22
|
+
|
|
23
|
+
it('parses ABC-123 → 123', () => {
|
|
24
|
+
expect(InvoiceCounter.parseNumber(settings, 'ABC-123')).toBe(123);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('parses XXX0001 → 1', () => {
|
|
28
|
+
expect(InvoiceCounter.parseNumber(settings, 'XXX0001')).toBe(1);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('parses 05STA0001 → 1', () => {
|
|
32
|
+
expect(InvoiceCounter.parseNumber(settings, '05STA0001')).toBe(1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('parses 1234-0011 → 11', () => {
|
|
36
|
+
expect(InvoiceCounter.parseNumber(settings, '1234-0011')).toBe(11);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('with year prefix (prefixYear: true)', () => {
|
|
41
|
+
const settings = makeSettings({ prefixYear: true });
|
|
42
|
+
|
|
43
|
+
it('parses 2012001584 → 1584', () => {
|
|
44
|
+
expect(InvoiceCounter.parseNumber(settings, '2012001584')).toBe(1584);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('parses XXX-2012001584 → 1584', () => {
|
|
48
|
+
expect(InvoiceCounter.parseNumber(settings, 'XXX-2012001584')).toBe(1584);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('InvoiceCounter.formatNumber', () => {
|
|
54
|
+
const date2025 = new Date('2025-06-15T12:00:00Z');
|
|
55
|
+
|
|
56
|
+
it('formats 123 with fixed prefix ABC → "ABC000123"', () => {
|
|
57
|
+
const settings = makeSettings({ fixedPrefix: 'ABC' });
|
|
58
|
+
expect(InvoiceCounter.formatNumber(settings, 123, date2025)).toBe('ABC000123');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('formats 123 with fixed prefix ABC1 → "ABC1-000123"', () => {
|
|
62
|
+
const settings = makeSettings({ fixedPrefix: 'ABC1' });
|
|
63
|
+
expect(InvoiceCounter.formatNumber(settings, 123, date2025)).toBe('ABC1-000123');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('formats 1 with year prefix → "2025000001"', () => {
|
|
67
|
+
const settings = makeSettings({ prefixYear: true });
|
|
68
|
+
expect(InvoiceCounter.formatNumber(settings, 1, date2025)).toBe('2025000001');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('formats 11 with fixed prefix "111-" and no duplicate dash → "111-000011"', () => {
|
|
72
|
+
const settings = makeSettings({ fixedPrefix: '111-' });
|
|
73
|
+
expect(InvoiceCounter.formatNumber(settings, 11, date2025)).toBe('111-000011');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('formats 11 with fixed prefix "test-" and no duplicate dash → "test-000011"', () => {
|
|
77
|
+
const settings = makeSettings({ fixedPrefix: 'test-' });
|
|
78
|
+
expect(InvoiceCounter.formatNumber(settings, 11, date2025)).toBe('test-000011');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('formats 54 with year prefix + fixed prefix → "ABC202500054"', () => {
|
|
82
|
+
// year is prepended first, then fixedPrefix wraps around
|
|
83
|
+
const settings = makeSettings({ prefixYear: true, fixedPrefix: 'ABC' });
|
|
84
|
+
expect(InvoiceCounter.formatNumber(settings, 54, date2025)).toBe('ABC2025000054');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('InvoiceCounter.shouldStartNewSeries', () => {
|
|
89
|
+
it('returns false when resetMonth is null', () => {
|
|
90
|
+
const settings = makeSettings({ resetMonth: null });
|
|
91
|
+
const last = new Date('2024-12-01');
|
|
92
|
+
const now = new Date('2025-06-01');
|
|
93
|
+
expect(InvoiceCounter.shouldStartNewSeries(settings, last, now)).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('returns true when crossing the reset month boundary', () => {
|
|
97
|
+
// reset on January (month 1)
|
|
98
|
+
const settings = makeSettings({ resetMonth: 1 });
|
|
99
|
+
const last = new Date('2024-06-15');
|
|
100
|
+
const now = new Date('2025-01-02');
|
|
101
|
+
expect(InvoiceCounter.shouldStartNewSeries(settings, last, now)).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('returns false when still within the same series period', () => {
|
|
105
|
+
const settings = makeSettings({ resetMonth: 1 });
|
|
106
|
+
const last = new Date('2025-01-05');
|
|
107
|
+
const now = new Date('2025-06-01');
|
|
108
|
+
expect(InvoiceCounter.shouldStartNewSeries(settings, last, now)).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('InvoiceCounter.assignNextNumber', () => {
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
InvoiceCounter.clearAll();
|
|
115
|
+
vitest.useFakeTimers({ toFake: ['Date'] });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
afterEach(() => {
|
|
119
|
+
vitest.useRealTimers();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('assigns 000001 to the first invoice for an org (no cache, no DB)', async () => {
|
|
123
|
+
const org = await new OrganizationFactory({}).create();
|
|
124
|
+
const settings = makeSettings({});
|
|
125
|
+
|
|
126
|
+
const invoice = new Invoice();
|
|
127
|
+
invoice.organizationId = org.id;
|
|
128
|
+
await InvoiceCounter.assignNextNumber(invoice, settings);
|
|
129
|
+
|
|
130
|
+
expect(invoice.number).toBe('000001');
|
|
131
|
+
expect(invoice.invoicedAt).not.toBeNull();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('increments from cache when called twice without a reset boundary', async () => {
|
|
135
|
+
const org = await new OrganizationFactory({}).create();
|
|
136
|
+
const settings = makeSettings({});
|
|
137
|
+
|
|
138
|
+
const invoice1 = new Invoice();
|
|
139
|
+
invoice1.organizationId = org.id;
|
|
140
|
+
await InvoiceCounter.assignNextNumber(invoice1, settings);
|
|
141
|
+
expect(invoice1.number).toBe('000001');
|
|
142
|
+
|
|
143
|
+
const invoice2 = new Invoice();
|
|
144
|
+
invoice2.organizationId = org.id;
|
|
145
|
+
await InvoiceCounter.assignNextNumber(invoice2, settings);
|
|
146
|
+
expect(invoice2.number).toBe('000002');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('continues from DB when cache is absent but a numbered invoice exists', async () => {
|
|
150
|
+
const org = await new OrganizationFactory({}).create();
|
|
151
|
+
const settings = makeSettings({});
|
|
152
|
+
|
|
153
|
+
const existing = new Invoice();
|
|
154
|
+
existing.organizationId = org.id;
|
|
155
|
+
existing.number = '000042';
|
|
156
|
+
existing.invoicedAt = new Date();
|
|
157
|
+
await existing.save();
|
|
158
|
+
|
|
159
|
+
InvoiceCounter.clearAll();
|
|
160
|
+
|
|
161
|
+
const invoice = new Invoice();
|
|
162
|
+
invoice.organizationId = org.id;
|
|
163
|
+
await InvoiceCounter.assignNextNumber(invoice, settings);
|
|
164
|
+
|
|
165
|
+
expect(invoice.number).toBe('000043');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('starts a new series when the reset boundary is crossed (cache hit)', async () => {
|
|
169
|
+
const org = await new OrganizationFactory({}).create();
|
|
170
|
+
const settings = makeSettings({ resetMonth: 1, prefixYear: true });
|
|
171
|
+
|
|
172
|
+
vitest.setSystemTime(new Date('2024-06-15T12:00:00Z'));
|
|
173
|
+
const invoice1 = new Invoice();
|
|
174
|
+
invoice1.organizationId = org.id;
|
|
175
|
+
await InvoiceCounter.assignNextNumber(invoice1, settings);
|
|
176
|
+
expect(invoice1.number).toBe('2024000001');
|
|
177
|
+
|
|
178
|
+
// Advance past the January 2025 reset boundary
|
|
179
|
+
vitest.setSystemTime(new Date('2025-02-01T12:00:00Z'));
|
|
180
|
+
const invoice2 = new Invoice();
|
|
181
|
+
invoice2.organizationId = org.id;
|
|
182
|
+
await InvoiceCounter.assignNextNumber(invoice2, settings);
|
|
183
|
+
|
|
184
|
+
expect(invoice2.number).toBe('2025000001');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('falls back to 1 when the DB invoice number cannot be parsed', async () => {
|
|
188
|
+
const org = await new OrganizationFactory({}).create();
|
|
189
|
+
const settings = makeSettings({});
|
|
190
|
+
|
|
191
|
+
const existing = new Invoice();
|
|
192
|
+
existing.organizationId = org.id;
|
|
193
|
+
existing.number = 'INVALID';
|
|
194
|
+
existing.invoicedAt = new Date();
|
|
195
|
+
await existing.save();
|
|
196
|
+
|
|
197
|
+
const invoice = new Invoice();
|
|
198
|
+
invoice.organizationId = org.id;
|
|
199
|
+
await InvoiceCounter.assignNextNumber(invoice, settings);
|
|
200
|
+
|
|
201
|
+
expect(invoice.number).toBe('000001');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('reads from DB after resetNumbers clears the cache', async () => {
|
|
205
|
+
const org = await new OrganizationFactory({}).create();
|
|
206
|
+
const settings = makeSettings({});
|
|
207
|
+
|
|
208
|
+
const invoice1 = new Invoice();
|
|
209
|
+
invoice1.organizationId = org.id;
|
|
210
|
+
await InvoiceCounter.assignNextNumber(invoice1, settings);
|
|
211
|
+
expect(invoice1.number).toBe('000001');
|
|
212
|
+
|
|
213
|
+
await InvoiceCounter.resetNumbers(org.id);
|
|
214
|
+
|
|
215
|
+
const invoice2 = new Invoice();
|
|
216
|
+
invoice2.organizationId = org.id;
|
|
217
|
+
await InvoiceCounter.assignNextNumber(invoice2, settings);
|
|
218
|
+
expect(invoice2.number).toBe('000002');
|
|
219
|
+
});
|
|
220
|
+
});
|