@stamhoofd/models 2.63.0 → 2.65.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/src/helpers/EmailBuilder.d.ts +6 -1
- package/dist/src/helpers/EmailBuilder.d.ts.map +1 -1
- package/dist/src/helpers/EmailBuilder.js +60 -20
- package/dist/src/helpers/EmailBuilder.js.map +1 -1
- package/dist/src/helpers/MemberMerger.d.ts.map +1 -1
- package/dist/src/helpers/MemberMerger.js +1 -2
- package/dist/src/helpers/MemberMerger.js.map +1 -1
- package/dist/src/migrations/1605262045-import-postcodes.d.ts +3 -3
- package/dist/src/migrations/1605262045-import-postcodes.js +10 -13
- package/dist/src/migrations/1734429094-registration-trial-until.sql +3 -0
- package/dist/src/migrations/1734429095-membership-trial-until.sql +2 -0
- package/dist/src/migrations/1734535120-registration-period-previous-period-id.sql +3 -0
- package/dist/src/migrations/1734535121-platform-previous-period-id.sql +3 -0
- package/dist/src/migrations/1734626607-cached-balance-amount-open.sql +2 -0
- package/dist/src/migrations/1734698906-cached-balance-amount-paid.sql +2 -0
- package/dist/src/migrations/1735573520-emails-email-type.sql +2 -0
- package/dist/src/migrations/1735573521-email-recipients-email-type.sql +4 -0
- package/dist/src/migrations/1735573522-emails-indexes.sql +3 -0
- package/dist/src/migrations/1735982691-cached-balance-email-reminder-counts.sql +4 -0
- package/dist/src/migrations/1735994471-default-email-templates.sql +5 -0
- package/dist/src/models/AuditLog.d.ts +2 -7
- package/dist/src/models/AuditLog.d.ts.map +1 -1
- package/dist/src/models/AuditLog.js +1 -15
- package/dist/src/models/AuditLog.js.map +1 -1
- package/dist/src/models/BalanceItem.d.ts +4 -13
- package/dist/src/models/BalanceItem.d.ts.map +1 -1
- package/dist/src/models/BalanceItem.js +21 -33
- package/dist/src/models/BalanceItem.js.map +1 -1
- package/dist/src/models/BalanceItemPayment.d.ts +3 -2
- package/dist/src/models/BalanceItemPayment.d.ts.map +1 -1
- package/dist/src/models/BalanceItemPayment.js +2 -1
- package/dist/src/models/BalanceItemPayment.js.map +1 -1
- package/dist/src/models/BuckarooPayment.d.ts +2 -2
- package/dist/src/models/BuckarooPayment.d.ts.map +1 -1
- package/dist/src/models/BuckarooPayment.js +2 -1
- package/dist/src/models/BuckarooPayment.js.map +1 -1
- package/dist/src/models/CachedBalance.d.ts +12 -10
- package/dist/src/models/CachedBalance.d.ts.map +1 -1
- package/dist/src/models/CachedBalance.js +122 -39
- package/dist/src/models/CachedBalance.js.map +1 -1
- package/dist/src/models/Document.d.ts +3 -3
- package/dist/src/models/Document.d.ts.map +1 -1
- package/dist/src/models/Document.js +2 -1
- package/dist/src/models/Document.js.map +1 -1
- package/dist/src/models/DocumentTemplate.d.ts +2 -2
- package/dist/src/models/DocumentTemplate.d.ts.map +1 -1
- package/dist/src/models/DocumentTemplate.js +38 -10
- package/dist/src/models/DocumentTemplate.js.map +1 -1
- package/dist/src/models/Email.d.ts +10 -4
- package/dist/src/models/Email.d.ts.map +1 -1
- package/dist/src/models/Email.js +68 -25
- package/dist/src/models/Email.js.map +1 -1
- package/dist/src/models/EmailRecipient.d.ts +14 -8
- package/dist/src/models/EmailRecipient.d.ts.map +1 -1
- package/dist/src/models/EmailRecipient.js +19 -14
- package/dist/src/models/EmailRecipient.js.map +1 -1
- package/dist/src/models/EmailTemplate.d.ts +2 -7
- package/dist/src/models/EmailTemplate.d.ts.map +1 -1
- package/dist/src/models/EmailTemplate.js +1 -15
- package/dist/src/models/EmailTemplate.js.map +1 -1
- package/dist/src/models/EmailVerificationCode.d.ts +2 -2
- package/dist/src/models/EmailVerificationCode.d.ts.map +1 -1
- package/dist/src/models/EmailVerificationCode.js +2 -1
- package/dist/src/models/EmailVerificationCode.js.map +1 -1
- package/dist/src/models/Event.d.ts +2 -2
- package/dist/src/models/Event.d.ts.map +1 -1
- package/dist/src/models/Event.js +2 -1
- package/dist/src/models/Event.js.map +1 -1
- package/dist/src/models/Group.d.ts +2 -7
- package/dist/src/models/Group.d.ts.map +1 -1
- package/dist/src/models/Group.js +1 -17
- package/dist/src/models/Group.js.map +1 -1
- package/dist/src/models/Image.d.ts +2 -2
- package/dist/src/models/Image.d.ts.map +1 -1
- package/dist/src/models/Image.js +2 -1
- package/dist/src/models/Image.js.map +1 -1
- package/dist/src/models/Member.d.ts +6 -9
- package/dist/src/models/Member.d.ts.map +1 -1
- package/dist/src/models/Member.js +3 -42
- package/dist/src/models/Member.js.map +1 -1
- package/dist/src/models/MemberPlatformMembership.d.ts +12 -8
- package/dist/src/models/MemberPlatformMembership.d.ts.map +1 -1
- package/dist/src/models/MemberPlatformMembership.js +80 -20
- package/dist/src/models/MemberPlatformMembership.js.map +1 -1
- package/dist/src/models/MemberResponsibilityRecord.d.ts +2 -7
- package/dist/src/models/MemberResponsibilityRecord.d.ts.map +1 -1
- package/dist/src/models/MemberResponsibilityRecord.js +1 -15
- package/dist/src/models/MemberResponsibilityRecord.js.map +1 -1
- package/dist/src/models/MemberUser.d.ts +8 -0
- package/dist/src/models/MemberUser.d.ts.map +1 -0
- package/dist/src/models/MemberUser.js +26 -0
- package/dist/src/models/MemberUser.js.map +1 -0
- package/dist/src/models/MergedMember.d.ts +3 -3
- package/dist/src/models/MergedMember.d.ts.map +1 -1
- package/dist/src/models/MergedMember.js +3 -2
- package/dist/src/models/MergedMember.js.map +1 -1
- package/dist/src/models/MolliePayment.d.ts +2 -2
- package/dist/src/models/MolliePayment.d.ts.map +1 -1
- package/dist/src/models/MolliePayment.js +2 -1
- package/dist/src/models/MolliePayment.js.map +1 -1
- package/dist/src/models/MollieToken.d.ts +2 -2
- package/dist/src/models/MollieToken.d.ts.map +1 -1
- package/dist/src/models/MollieToken.js +2 -1
- package/dist/src/models/MollieToken.js.map +1 -1
- package/dist/src/models/OneTimeToken.d.ts +2 -2
- package/dist/src/models/OneTimeToken.d.ts.map +1 -1
- package/dist/src/models/OneTimeToken.js +2 -1
- package/dist/src/models/OneTimeToken.js.map +1 -1
- package/dist/src/models/Order.d.ts +3 -2
- package/dist/src/models/Order.d.ts.map +1 -1
- package/dist/src/models/Order.js +2 -1
- package/dist/src/models/Order.js.map +1 -1
- package/dist/src/models/Organization.d.ts +2 -2
- package/dist/src/models/Organization.d.ts.map +1 -1
- package/dist/src/models/Organization.js +2 -1
- package/dist/src/models/Organization.js.map +1 -1
- package/dist/src/models/OrganizationRegistrationPeriod.d.ts +2 -2
- package/dist/src/models/OrganizationRegistrationPeriod.d.ts.map +1 -1
- package/dist/src/models/OrganizationRegistrationPeriod.js +2 -1
- package/dist/src/models/OrganizationRegistrationPeriod.js.map +1 -1
- package/dist/src/models/PasswordToken.d.ts +3 -2
- package/dist/src/models/PasswordToken.d.ts.map +1 -1
- package/dist/src/models/PasswordToken.js +2 -1
- package/dist/src/models/PasswordToken.js.map +1 -1
- package/dist/src/models/PayconiqPayment.d.ts +2 -2
- package/dist/src/models/PayconiqPayment.d.ts.map +1 -1
- package/dist/src/models/PayconiqPayment.js +2 -1
- package/dist/src/models/PayconiqPayment.js.map +1 -1
- package/dist/src/models/Payment.d.ts +2 -7
- package/dist/src/models/Payment.d.ts.map +1 -1
- package/dist/src/models/Payment.js +1 -15
- package/dist/src/models/Payment.js.map +1 -1
- package/dist/src/models/Platform.d.ts +5 -3
- package/dist/src/models/Platform.d.ts.map +1 -1
- package/dist/src/models/Platform.js +11 -2
- package/dist/src/models/Platform.js.map +1 -1
- package/dist/src/models/RegisterCode.d.ts +2 -2
- package/dist/src/models/RegisterCode.d.ts.map +1 -1
- package/dist/src/models/RegisterCode.js +2 -1
- package/dist/src/models/RegisterCode.js.map +1 -1
- package/dist/src/models/Registration.d.ts +20 -7
- package/dist/src/models/Registration.d.ts.map +1 -1
- package/dist/src/models/Registration.js +25 -61
- package/dist/src/models/Registration.js.map +1 -1
- package/dist/src/models/RegistrationPeriod.d.ts +4 -2
- package/dist/src/models/RegistrationPeriod.d.ts.map +1 -1
- package/dist/src/models/RegistrationPeriod.js +23 -1
- package/dist/src/models/RegistrationPeriod.js.map +1 -1
- package/dist/src/models/STCredit.d.ts +2 -2
- package/dist/src/models/STCredit.d.ts.map +1 -1
- package/dist/src/models/STCredit.js +2 -1
- package/dist/src/models/STCredit.js.map +1 -1
- package/dist/src/models/STInvoice.d.ts +3 -2
- package/dist/src/models/STInvoice.d.ts.map +1 -1
- package/dist/src/models/STInvoice.js +2 -1
- package/dist/src/models/STInvoice.js.map +1 -1
- package/dist/src/models/STPackage.d.ts +2 -2
- package/dist/src/models/STPackage.d.ts.map +1 -1
- package/dist/src/models/STPackage.js +2 -1
- package/dist/src/models/STPackage.js.map +1 -1
- package/dist/src/models/STPendingInvoice.d.ts +3 -2
- package/dist/src/models/STPendingInvoice.d.ts.map +1 -1
- package/dist/src/models/STPendingInvoice.js +2 -1
- package/dist/src/models/STPendingInvoice.js.map +1 -1
- package/dist/src/models/StripeAccount.d.ts +2 -2
- package/dist/src/models/StripeAccount.d.ts.map +1 -1
- package/dist/src/models/StripeAccount.js +2 -1
- package/dist/src/models/StripeAccount.js.map +1 -1
- package/dist/src/models/StripeCheckoutSession.d.ts +2 -2
- package/dist/src/models/StripeCheckoutSession.d.ts.map +1 -1
- package/dist/src/models/StripeCheckoutSession.js +2 -1
- package/dist/src/models/StripeCheckoutSession.js.map +1 -1
- package/dist/src/models/StripePaymentIntent.d.ts +2 -2
- package/dist/src/models/StripePaymentIntent.d.ts.map +1 -1
- package/dist/src/models/StripePaymentIntent.js +2 -1
- package/dist/src/models/StripePaymentIntent.js.map +1 -1
- package/dist/src/models/Ticket.d.ts +3 -2
- package/dist/src/models/Ticket.d.ts.map +1 -1
- package/dist/src/models/Ticket.js +2 -1
- package/dist/src/models/Ticket.js.map +1 -1
- package/dist/src/models/Token.d.ts +3 -2
- package/dist/src/models/Token.d.ts.map +1 -1
- package/dist/src/models/Token.js +2 -1
- package/dist/src/models/Token.js.map +1 -1
- package/dist/src/models/UsedRegisterCode.d.ts +2 -2
- package/dist/src/models/UsedRegisterCode.d.ts.map +1 -1
- package/dist/src/models/UsedRegisterCode.js +2 -1
- package/dist/src/models/UsedRegisterCode.js.map +1 -1
- package/dist/src/models/User.d.ts +7 -2
- package/dist/src/models/User.d.ts.map +1 -1
- package/dist/src/models/User.js +27 -4
- package/dist/src/models/User.js.map +1 -1
- package/dist/src/models/UserPermissions.d.ts +3 -2
- package/dist/src/models/UserPermissions.d.ts.map +1 -1
- package/dist/src/models/UserPermissions.js +2 -1
- package/dist/src/models/UserPermissions.js.map +1 -1
- package/dist/src/models/Webshop.d.ts +3 -2
- package/dist/src/models/Webshop.d.ts.map +1 -1
- package/dist/src/models/Webshop.js +2 -1
- package/dist/src/models/Webshop.js.map +1 -1
- package/dist/src/models/WebshopDiscountCode.d.ts +2 -2
- package/dist/src/models/WebshopDiscountCode.d.ts.map +1 -1
- package/dist/src/models/WebshopDiscountCode.js +2 -1
- package/dist/src/models/WebshopDiscountCode.js.map +1 -1
- package/dist/src/models/addresses/City.d.ts +3 -2
- package/dist/src/models/addresses/City.d.ts.map +1 -1
- package/dist/src/models/addresses/City.js +2 -1
- package/dist/src/models/addresses/City.js.map +1 -1
- package/dist/src/models/addresses/PostalCode.d.ts +3 -2
- package/dist/src/models/addresses/PostalCode.d.ts.map +1 -1
- package/dist/src/models/addresses/PostalCode.js +2 -1
- package/dist/src/models/addresses/PostalCode.js.map +1 -1
- package/dist/src/models/addresses/Province.d.ts +2 -2
- package/dist/src/models/addresses/Province.d.ts.map +1 -1
- package/dist/src/models/addresses/Province.js +2 -1
- package/dist/src/models/addresses/Province.js.map +1 -1
- package/dist/src/models/addresses/Street.d.ts +3 -2
- package/dist/src/models/addresses/Street.d.ts.map +1 -1
- package/dist/src/models/addresses/Street.js +2 -1
- package/dist/src/models/addresses/Street.js.map +1 -1
- package/dist/src/models/index.d.ts +1 -0
- package/dist/src/models/index.d.ts.map +1 -1
- package/dist/src/models/index.js +1 -0
- package/dist/src/models/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/helpers/EmailBuilder.ts +82 -27
- package/src/helpers/MemberMerger.ts +2 -3
- package/src/migrations/1605262045-import-postcodes.ts +6 -9
- package/src/migrations/1734429094-registration-trial-until.sql +3 -0
- package/src/migrations/1734429095-membership-trial-until.sql +2 -0
- package/src/migrations/1734535120-registration-period-previous-period-id.sql +3 -0
- package/src/migrations/1734535121-platform-previous-period-id.sql +3 -0
- package/src/migrations/1734626607-cached-balance-amount-open.sql +2 -0
- package/src/migrations/1734698906-cached-balance-amount-paid.sql +2 -0
- package/src/migrations/1735573520-emails-email-type.sql +2 -0
- package/src/migrations/1735573521-email-recipients-email-type.sql +4 -0
- package/src/migrations/1735573522-emails-indexes.sql +3 -0
- package/src/migrations/1735982691-cached-balance-email-reminder-counts.sql +4 -0
- package/src/migrations/1735994471-default-email-templates.sql +5 -0
- package/src/models/AuditLog.ts +3 -21
- package/src/models/BalanceItem.ts +30 -46
- package/src/models/BalanceItemPayment.ts +3 -2
- package/src/models/BuckarooPayment.ts +3 -2
- package/src/models/CachedBalance.ts +166 -46
- package/src/models/Document.ts +4 -3
- package/src/models/DocumentTemplate.ts +43 -12
- package/src/models/Email.ts +80 -32
- package/src/models/EmailRecipient.ts +20 -20
- package/src/models/EmailTemplate.ts +3 -21
- package/src/models/EmailVerificationCode.ts +3 -2
- package/src/models/Event.ts +3 -2
- package/src/models/Group.ts +4 -23
- package/src/models/Image.ts +3 -2
- package/src/models/Member.ts +6 -52
- package/src/models/MemberPlatformMembership.ts +95 -26
- package/src/models/MemberResponsibilityRecord.ts +3 -21
- package/src/models/MemberUser.ts +18 -0
- package/src/models/MergedMember.ts +4 -3
- package/src/models/MolliePayment.ts +3 -2
- package/src/models/MollieToken.ts +3 -2
- package/src/models/OneTimeToken.ts +3 -2
- package/src/models/Order.ts +3 -2
- package/src/models/Organization.ts +3 -2
- package/src/models/OrganizationRegistrationPeriod.ts +3 -2
- package/src/models/PasswordToken.ts +3 -2
- package/src/models/PayconiqPayment.ts +3 -2
- package/src/models/Payment.ts +3 -21
- package/src/models/Platform.ts +13 -4
- package/src/models/RegisterCode.ts +3 -2
- package/src/models/Registration.ts +24 -68
- package/src/models/RegistrationPeriod.ts +30 -3
- package/src/models/STCredit.ts +3 -2
- package/src/models/STInvoice.ts +3 -2
- package/src/models/STPackage.ts +3 -2
- package/src/models/STPendingInvoice.ts +3 -2
- package/src/models/StripeAccount.ts +3 -2
- package/src/models/StripeCheckoutSession.ts +3 -2
- package/src/models/StripePaymentIntent.ts +3 -2
- package/src/models/Ticket.ts +3 -2
- package/src/models/Token.ts +3 -2
- package/src/models/UsedRegisterCode.ts +3 -2
- package/src/models/User.ts +31 -3
- package/src/models/UserPermissions.ts +3 -2
- package/src/models/Webshop.ts +3 -2
- package/src/models/WebshopDiscountCode.ts +3 -2
- package/src/models/addresses/City.ts +3 -2
- package/src/models/addresses/PostalCode.ts +3 -2
- package/src/models/addresses/Province.ts +3 -2
- package/src/models/addresses/Street.ts +3 -2
- package/src/models/index.ts +1 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { column
|
|
2
|
-
import { SQL, SQLAlias,
|
|
3
|
-
import { BalanceItem as BalanceItemStruct,
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
|
+
import { QueryableModel, SQL, SQLAlias, SQLMin, SQLSelectAs, SQLSum, SQLWhere, SQLWhereSign } from '@stamhoofd/sql';
|
|
3
|
+
import { BalanceItemStatus, BalanceItem as BalanceItemStruct, ReceivableBalanceType } from '@stamhoofd/structures';
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
5
|
import { BalanceItem } from './BalanceItem';
|
|
6
|
+
import { MemberUser } from './MemberUser';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Keeps track of how much a member/user owes or needs to be reimbursed.
|
|
9
10
|
*/
|
|
10
|
-
export class CachedBalance extends
|
|
11
|
+
export class CachedBalance extends QueryableModel {
|
|
11
12
|
static table = 'cached_outstanding_balances';
|
|
12
13
|
|
|
13
14
|
@column({
|
|
@@ -33,7 +34,10 @@ export class CachedBalance extends Model {
|
|
|
33
34
|
objectType: ReceivableBalanceType;
|
|
34
35
|
|
|
35
36
|
@column({ type: 'integer' })
|
|
36
|
-
|
|
37
|
+
amountPaid = 0;
|
|
38
|
+
|
|
39
|
+
@column({ type: 'integer' })
|
|
40
|
+
amountOpen = 0;
|
|
37
41
|
|
|
38
42
|
/**
|
|
39
43
|
* The sum of unconfirmed payments
|
|
@@ -47,6 +51,15 @@ export class CachedBalance extends Model {
|
|
|
47
51
|
@column({ type: 'datetime', nullable: true })
|
|
48
52
|
nextDueAt: Date | null = null;
|
|
49
53
|
|
|
54
|
+
@column({ type: 'datetime', nullable: true })
|
|
55
|
+
lastReminderEmail: Date | null = null;
|
|
56
|
+
|
|
57
|
+
@column({ type: 'integer' })
|
|
58
|
+
lastReminderAmountOpen = 0;
|
|
59
|
+
|
|
60
|
+
@column({ type: 'integer' })
|
|
61
|
+
reminderEmailCount = 0;
|
|
62
|
+
|
|
50
63
|
@column({
|
|
51
64
|
type: 'datetime', beforeSave(old?: any) {
|
|
52
65
|
if (old !== undefined) {
|
|
@@ -70,6 +83,10 @@ export class CachedBalance extends Model {
|
|
|
70
83
|
updatedAt: Date;
|
|
71
84
|
|
|
72
85
|
static async getForObjects(objectIds: string[], limitOrganizationId?: string | null): Promise<CachedBalance[]> {
|
|
86
|
+
if (objectIds.length === 0) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
|
|
73
90
|
const query = this.select()
|
|
74
91
|
.where('objectId', objectIds);
|
|
75
92
|
|
|
@@ -81,6 +98,10 @@ export class CachedBalance extends Model {
|
|
|
81
98
|
}
|
|
82
99
|
|
|
83
100
|
static async updateForObjects(organizationId: string, objectIds: string[], objectType: ReceivableBalanceType) {
|
|
101
|
+
if (objectIds.length === 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
84
105
|
switch (objectType) {
|
|
85
106
|
case ReceivableBalanceType.organization:
|
|
86
107
|
await this.updateForOrganizations(organizationId, objectIds);
|
|
@@ -91,17 +112,26 @@ export class CachedBalance extends Model {
|
|
|
91
112
|
case ReceivableBalanceType.user:
|
|
92
113
|
await this.updateForUsers(organizationId, objectIds);
|
|
93
114
|
break;
|
|
115
|
+
case ReceivableBalanceType.registration:
|
|
116
|
+
await this.updateForRegistrations(organizationId, objectIds);
|
|
117
|
+
break;
|
|
94
118
|
}
|
|
95
119
|
}
|
|
96
120
|
|
|
97
|
-
static async balanceForObjects(organizationId: string, objectIds: string[], objectType: ReceivableBalanceType) {
|
|
121
|
+
static async balanceForObjects(organizationId: string, objectIds: string[], objectType: ReceivableBalanceType, includeUserMembers = false) {
|
|
122
|
+
if (objectIds.length === 0) {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
|
|
98
126
|
switch (objectType) {
|
|
99
127
|
case ReceivableBalanceType.organization:
|
|
100
128
|
return await this.balanceForOrganizations(organizationId, objectIds);
|
|
101
129
|
case ReceivableBalanceType.member:
|
|
102
130
|
return await this.balanceForMembers(organizationId, objectIds);
|
|
103
131
|
case ReceivableBalanceType.user:
|
|
104
|
-
return await this.balanceForUsers(organizationId, objectIds);
|
|
132
|
+
return await this.balanceForUsers(organizationId, objectIds, includeUserMembers);
|
|
133
|
+
case ReceivableBalanceType.registration:
|
|
134
|
+
return await this.balanceForRegistrations(organizationId, objectIds);
|
|
105
135
|
}
|
|
106
136
|
}
|
|
107
137
|
|
|
@@ -122,15 +152,25 @@ export class CachedBalance extends Model {
|
|
|
122
152
|
return await query.fetch();
|
|
123
153
|
}
|
|
124
154
|
|
|
155
|
+
static whereNeedsUpdate() {
|
|
156
|
+
return SQL.where('nextDueAt', SQLWhereSign.LessEqual, BalanceItemStruct.getDueOffset());
|
|
157
|
+
}
|
|
158
|
+
|
|
125
159
|
private static async fetchForObjects(organizationId: string, objectIds: string[], columnName: string, customWhere?: SQLWhere) {
|
|
126
160
|
const dueOffset = BalanceItemStruct.getDueOffset();
|
|
127
161
|
const query = SQL.select(
|
|
128
162
|
SQL.column(columnName),
|
|
163
|
+
new SQLSelectAs(
|
|
164
|
+
new SQLSum(
|
|
165
|
+
SQL.column('pricePaid'),
|
|
166
|
+
),
|
|
167
|
+
new SQLAlias('data__amountPaid'),
|
|
168
|
+
),
|
|
129
169
|
new SQLSelectAs(
|
|
130
170
|
new SQLSum(
|
|
131
171
|
SQL.column('priceOpen'),
|
|
132
172
|
),
|
|
133
|
-
new SQLAlias('
|
|
173
|
+
new SQLAlias('data__amountOpen'),
|
|
134
174
|
),
|
|
135
175
|
new SQLSelectAs(
|
|
136
176
|
new SQLSum(
|
|
@@ -162,11 +202,17 @@ export class CachedBalance extends Model {
|
|
|
162
202
|
new SQLAlias('data__dueAt'),
|
|
163
203
|
),
|
|
164
204
|
// If the current amount_due is negative, we can ignore that negative part if there is a future due item
|
|
205
|
+
new SQLSelectAs(
|
|
206
|
+
new SQLSum(
|
|
207
|
+
SQL.column('pricePaid'),
|
|
208
|
+
),
|
|
209
|
+
new SQLAlias('data__amountPaid'),
|
|
210
|
+
),
|
|
165
211
|
new SQLSelectAs(
|
|
166
212
|
new SQLSum(
|
|
167
213
|
SQL.column('priceOpen'),
|
|
168
214
|
),
|
|
169
|
-
new SQLAlias('
|
|
215
|
+
new SQLAlias('data__amountOpen'),
|
|
170
216
|
),
|
|
171
217
|
new SQLSelectAs(
|
|
172
218
|
new SQLSum(
|
|
@@ -185,7 +231,7 @@ export class CachedBalance extends Model {
|
|
|
185
231
|
|
|
186
232
|
const dueResult = await dueQuery.fetch();
|
|
187
233
|
|
|
188
|
-
const results: [string, {
|
|
234
|
+
const results: [string, { amountPaid: number; amountOpen: number; amountPending: number; nextDueAt: Date | null }][] = [];
|
|
189
235
|
for (const row of result) {
|
|
190
236
|
if (!row['data']) {
|
|
191
237
|
throw new Error('Invalid data namespace');
|
|
@@ -196,22 +242,27 @@ export class CachedBalance extends Model {
|
|
|
196
242
|
}
|
|
197
243
|
|
|
198
244
|
const objectId = row[BalanceItem.table][columnName];
|
|
199
|
-
const
|
|
245
|
+
const amountOpen = row['data']['amountOpen'];
|
|
200
246
|
const amountPending = row['data']['amountPending'];
|
|
247
|
+
const amountPaid = row['data']['amountPaid'];
|
|
201
248
|
|
|
202
249
|
if (typeof objectId !== 'string') {
|
|
203
250
|
throw new Error('Invalid objectId');
|
|
204
251
|
}
|
|
205
252
|
|
|
206
|
-
if (typeof
|
|
207
|
-
throw new Error('Invalid
|
|
253
|
+
if (typeof amountOpen !== 'number') {
|
|
254
|
+
throw new Error('Invalid amountOpen');
|
|
208
255
|
}
|
|
209
256
|
|
|
210
257
|
if (typeof amountPending !== 'number') {
|
|
211
258
|
throw new Error('Invalid amountPending');
|
|
212
259
|
}
|
|
213
260
|
|
|
214
|
-
|
|
261
|
+
if (typeof amountPaid !== 'number') {
|
|
262
|
+
throw new Error('Invalid amountPaid');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
results.push([objectId, { amountPaid, amountOpen, amountPending, nextDueAt: null }]);
|
|
215
266
|
}
|
|
216
267
|
|
|
217
268
|
for (const row of dueResult) {
|
|
@@ -225,8 +276,9 @@ export class CachedBalance extends Model {
|
|
|
225
276
|
|
|
226
277
|
const objectId = row[BalanceItem.table][columnName];
|
|
227
278
|
const dueAt = row['data']['dueAt'];
|
|
228
|
-
const
|
|
279
|
+
const amountOpen = row['data']['amountOpen'];
|
|
229
280
|
const amountPending = row['data']['amountPending'];
|
|
281
|
+
const amountPaid = row['data']['amountPaid'];
|
|
230
282
|
|
|
231
283
|
if (typeof objectId !== 'string') {
|
|
232
284
|
throw new Error('Invalid objectId');
|
|
@@ -236,77 +288,103 @@ export class CachedBalance extends Model {
|
|
|
236
288
|
throw new Error('Invalid dueAt');
|
|
237
289
|
}
|
|
238
290
|
|
|
239
|
-
if (typeof
|
|
240
|
-
throw new Error('Invalid
|
|
291
|
+
if (typeof amountOpen !== 'number') {
|
|
292
|
+
throw new Error('Invalid amountOpen');
|
|
241
293
|
}
|
|
242
294
|
|
|
243
295
|
if (typeof amountPending !== 'number') {
|
|
244
296
|
throw new Error('Invalid amountPending');
|
|
245
297
|
}
|
|
246
298
|
|
|
299
|
+
if (typeof amountPaid !== 'number') {
|
|
300
|
+
throw new Error('Invalid amountPaid');
|
|
301
|
+
}
|
|
302
|
+
|
|
247
303
|
const result = results.find(r => r[0] === objectId);
|
|
248
304
|
if (result) {
|
|
249
305
|
result[1].nextDueAt = dueAt;
|
|
250
306
|
|
|
251
|
-
if (result[1].
|
|
252
|
-
if (
|
|
307
|
+
if (result[1].amountOpen < 0) {
|
|
308
|
+
if (amountOpen > 0) {
|
|
253
309
|
// Let the future due amount fill in the gap until maximum 0
|
|
254
|
-
result[1].
|
|
310
|
+
result[1].amountOpen = Math.min(0, result[1].amountOpen + amountOpen);
|
|
255
311
|
}
|
|
256
312
|
}
|
|
257
313
|
|
|
258
314
|
result[1].amountPending += amountPending;
|
|
315
|
+
result[1].amountPaid += amountPaid;
|
|
259
316
|
}
|
|
260
317
|
else {
|
|
261
|
-
results.push([objectId, {
|
|
318
|
+
results.push([objectId, { amountPaid, amountOpen: 0, amountPending, nextDueAt: dueAt }]);
|
|
262
319
|
}
|
|
263
320
|
}
|
|
264
321
|
|
|
265
322
|
// Add missing object ids (with 0 amount, otherwise we don't reset the amounts back to zero when all the balance items are hidden)
|
|
266
323
|
for (const objectId of objectIds) {
|
|
267
324
|
if (!results.find(([id]) => id === objectId)) {
|
|
268
|
-
results.push([objectId, {
|
|
325
|
+
results.push([objectId, { amountPaid: 0, amountOpen: 0, amountPending: 0, nextDueAt: null }]);
|
|
269
326
|
}
|
|
270
327
|
}
|
|
271
328
|
|
|
272
329
|
return results;
|
|
273
330
|
}
|
|
274
331
|
|
|
275
|
-
private static async setForResults(organizationId: string, result: [string, {
|
|
332
|
+
private static async setForResults(organizationId: string, result: [string, { amountPaid: number; amountOpen: number; amountPending: number; nextDueAt: null | Date }][], objectType: ReceivableBalanceType) {
|
|
276
333
|
if (result.length === 0) {
|
|
277
334
|
return;
|
|
278
335
|
}
|
|
279
|
-
const query =
|
|
336
|
+
const query = this.insert()
|
|
280
337
|
.columns(
|
|
281
338
|
'id',
|
|
282
339
|
'organizationId',
|
|
283
340
|
'objectId',
|
|
284
341
|
'objectType',
|
|
285
|
-
'
|
|
342
|
+
'amountPaid',
|
|
343
|
+
'amountOpen',
|
|
286
344
|
'amountPending',
|
|
287
345
|
'nextDueAt',
|
|
288
346
|
'createdAt',
|
|
289
347
|
'updatedAt',
|
|
348
|
+
'reminderEmailCount',
|
|
290
349
|
)
|
|
291
|
-
.values(...result.map(([objectId, {
|
|
350
|
+
.values(...result.map(([objectId, { amountPaid, amountOpen, amountPending, nextDueAt }]) => {
|
|
292
351
|
return [
|
|
293
352
|
uuidv4(),
|
|
294
353
|
organizationId,
|
|
295
354
|
objectId,
|
|
296
355
|
objectType,
|
|
297
|
-
|
|
356
|
+
amountPaid,
|
|
357
|
+
amountOpen,
|
|
298
358
|
amountPending,
|
|
299
359
|
nextDueAt,
|
|
300
360
|
new Date(),
|
|
301
361
|
new Date(),
|
|
362
|
+
0,
|
|
302
363
|
];
|
|
303
364
|
}))
|
|
304
365
|
.as('v')
|
|
305
366
|
.onDuplicateKeyUpdate(
|
|
306
|
-
SQL.assignment('
|
|
367
|
+
SQL.assignment('amountPaid', SQL.column('v', 'amountPaid')),
|
|
368
|
+
SQL.assignment('amountOpen', SQL.column('v', 'amountOpen')),
|
|
307
369
|
SQL.assignment('amountPending', SQL.column('v', 'amountPending')),
|
|
308
370
|
SQL.assignment('nextDueAt', SQL.column('v', 'nextDueAt')),
|
|
309
371
|
SQL.assignment('updatedAt', SQL.column('v', 'updatedAt')),
|
|
372
|
+
|
|
373
|
+
// Reset email count if amountOpen is zero
|
|
374
|
+
SQL.assignment(
|
|
375
|
+
'reminderEmailCount',
|
|
376
|
+
SQL.if(SQL.column('v', 'amountOpen'), 0)
|
|
377
|
+
.then(0)
|
|
378
|
+
.else(SQL.column('reminderEmailCount')),
|
|
379
|
+
),
|
|
380
|
+
|
|
381
|
+
// Reset lastReminderEmail if amountOpen is zero
|
|
382
|
+
SQL.assignment(
|
|
383
|
+
'lastReminderEmail',
|
|
384
|
+
SQL.if(SQL.column('v', 'amountOpen'), 0)
|
|
385
|
+
.then(null)
|
|
386
|
+
.else(SQL.column('lastReminderEmail')),
|
|
387
|
+
),
|
|
310
388
|
);
|
|
311
389
|
|
|
312
390
|
await query.insert();
|
|
@@ -328,11 +406,55 @@ export class CachedBalance extends Model {
|
|
|
328
406
|
await this.setForResults(organizationId, results, ReceivableBalanceType.member);
|
|
329
407
|
}
|
|
330
408
|
|
|
409
|
+
static async updateForRegistrations(organizationId: string, registrationIds: string[]) {
|
|
410
|
+
if (registrationIds.length === 0) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const results = await this.fetchForObjects(organizationId, registrationIds, 'registrationId');
|
|
414
|
+
await this.setForResults(organizationId, results, ReceivableBalanceType.registration);
|
|
415
|
+
}
|
|
416
|
+
|
|
331
417
|
static async updateForUsers(organizationId: string, userIds: string[]) {
|
|
332
418
|
if (userIds.length === 0) {
|
|
333
419
|
return;
|
|
334
420
|
}
|
|
335
|
-
const results = await this.fetchForObjects(
|
|
421
|
+
const results = await this.fetchForObjects(
|
|
422
|
+
organizationId,
|
|
423
|
+
userIds,
|
|
424
|
+
'userId',
|
|
425
|
+
SQL.where('memberId', null),
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
// We also need to include the balance of the related members for each user
|
|
429
|
+
|
|
430
|
+
// Fetch members of these users
|
|
431
|
+
const memberUsers = await MemberUser.select().where('usersId', userIds).fetch();
|
|
432
|
+
|
|
433
|
+
const memberCachedBalances = await this.getForObjects(memberUsers.map(mu => mu.membersId), organizationId);
|
|
434
|
+
|
|
435
|
+
for (const memberCachedBalance of memberCachedBalances) {
|
|
436
|
+
const userIds = memberUsers.filter(mu => mu.membersId === memberCachedBalance.objectId).map(mu => mu.usersId);
|
|
437
|
+
|
|
438
|
+
for (const userId of userIds) {
|
|
439
|
+
// if already in results: append
|
|
440
|
+
const result = results.find(([objectId]) => objectId === userId);
|
|
441
|
+
|
|
442
|
+
if (result) {
|
|
443
|
+
result[1].amountPaid += memberCachedBalance.amountPaid;
|
|
444
|
+
result[1].amountOpen += memberCachedBalance.amountOpen;
|
|
445
|
+
result[1].amountPending += memberCachedBalance.amountPending;
|
|
446
|
+
if (memberCachedBalance.nextDueAt && (!result[1].nextDueAt || memberCachedBalance.nextDueAt > result[1].nextDueAt)) {
|
|
447
|
+
result[1].nextDueAt = memberCachedBalance.nextDueAt;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
// Not possible
|
|
452
|
+
throw new Error('User not found in results');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Fetch cached balance of these members and merge the results
|
|
336
458
|
await this.setForResults(organizationId, results, ReceivableBalanceType.user);
|
|
337
459
|
}
|
|
338
460
|
|
|
@@ -350,28 +472,26 @@ export class CachedBalance extends Model {
|
|
|
350
472
|
return await this.fetchBalanceItems(organizationId, memberIds, 'memberId');
|
|
351
473
|
}
|
|
352
474
|
|
|
353
|
-
static async balanceForUsers(organizationId: string, userIds: string[]) {
|
|
475
|
+
static async balanceForUsers(organizationId: string, userIds: string[], includeUserMembers = false) {
|
|
354
476
|
if (userIds.length === 0) {
|
|
355
477
|
return [];
|
|
356
478
|
}
|
|
357
|
-
|
|
358
|
-
}
|
|
479
|
+
const base = await this.fetchBalanceItems(organizationId, userIds, 'userId', SQL.where('memberId', null));
|
|
359
480
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
static select() {
|
|
364
|
-
const transformer = (row: SQLResultNamespacedRow): CachedBalance => {
|
|
365
|
-
const d = (this as typeof CachedBalance & typeof Model).fromRow(row[this.table] as any) as CachedBalance | undefined;
|
|
366
|
-
|
|
367
|
-
if (!d) {
|
|
368
|
-
throw new Error('EmailTemplate not found');
|
|
369
|
-
}
|
|
481
|
+
if (!includeUserMembers) {
|
|
482
|
+
return base;
|
|
483
|
+
}
|
|
370
484
|
|
|
371
|
-
|
|
372
|
-
|
|
485
|
+
const memberUsers = await MemberUser.select().where('usersId', userIds).fetch();
|
|
486
|
+
const memberIds = memberUsers.map(mu => mu.membersId);
|
|
487
|
+
const memberCachedBalances = await this.balanceForMembers(organizationId, memberIds);
|
|
488
|
+
return base.concat(memberCachedBalances);
|
|
489
|
+
}
|
|
373
490
|
|
|
374
|
-
|
|
375
|
-
|
|
491
|
+
static async balanceForRegistrations(organizationId: string, registrationIds: string[]) {
|
|
492
|
+
if (registrationIds.length === 0) {
|
|
493
|
+
return [];
|
|
494
|
+
}
|
|
495
|
+
return await this.fetchBalanceItems(organizationId, registrationIds, 'registrationId');
|
|
376
496
|
}
|
|
377
497
|
}
|
package/src/models/Document.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { column
|
|
2
|
-
import { Document as DocumentStruct,
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
|
+
import { DocumentData, DocumentStatus, Document as DocumentStruct, Platform, Version } from '@stamhoofd/structures';
|
|
3
3
|
import { Formatter } from '@stamhoofd/utility';
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
5
|
|
|
6
|
+
import { QueryableModel } from '@stamhoofd/sql';
|
|
6
7
|
import { render } from '../helpers/Handlebars';
|
|
7
8
|
import { RegistrationWithMember } from './Member';
|
|
8
9
|
import { Organization } from './Organization';
|
|
9
10
|
import { Registration } from './Registration';
|
|
10
11
|
|
|
11
|
-
export class Document extends
|
|
12
|
+
export class Document extends QueryableModel {
|
|
12
13
|
static table = 'documents';
|
|
13
14
|
|
|
14
15
|
@column({ primary: true, type: 'string', beforeSave(value) {
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { column
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
2
|
import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
|
|
3
3
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
4
4
|
import { BalanceItemStatus, DocumentData, DocumentPrivateSettings, DocumentSettings, DocumentStatus, DocumentTemplatePrivate, GroupType, NationalRegisterNumberOptOut, Parent, RecordAddressAnswer, RecordAnswer, RecordAnswerDecoder, RecordDateAnswer, RecordPriceAnswer, RecordSettings, RecordTextAnswer, RecordType } from '@stamhoofd/structures';
|
|
5
5
|
import { Sorter } from '@stamhoofd/utility';
|
|
6
6
|
import { v4 as uuidv4 } from 'uuid';
|
|
7
7
|
|
|
8
|
+
import { QueryableModel } from '@stamhoofd/sql';
|
|
8
9
|
import { render } from '../helpers/Handlebars';
|
|
9
10
|
import { BalanceItem } from './BalanceItem';
|
|
10
11
|
import { Document } from './Document';
|
|
11
12
|
import { Group } from './Group';
|
|
12
13
|
import { Member, RegistrationWithMember } from './Member';
|
|
13
14
|
import { Organization } from './Organization';
|
|
14
|
-
import { User } from './User';
|
|
15
15
|
import { Registration } from './Registration';
|
|
16
|
+
import { User } from './User';
|
|
16
17
|
|
|
17
|
-
export class DocumentTemplate extends
|
|
18
|
+
export class DocumentTemplate extends QueryableModel {
|
|
18
19
|
static table = 'document_templates';
|
|
19
20
|
|
|
20
21
|
@column({ primary: true, type: 'string', beforeSave(value) {
|
|
@@ -74,9 +75,11 @@ export class DocumentTemplate extends Model {
|
|
|
74
75
|
let missingData = false;
|
|
75
76
|
|
|
76
77
|
const group = await Group.getByID(registration.groupId);
|
|
77
|
-
const { items: balanceItems, payments } = await BalanceItem.getForRegistration(registration.id);
|
|
78
|
+
const { items: balanceItems, payments } = await BalanceItem.getForRegistration(registration.id, this.organizationId);
|
|
78
79
|
|
|
79
80
|
const paidAtDates = payments.flatMap(p => p.paidAt ? [p.paidAt?.getTime()] : []);
|
|
81
|
+
const price = balanceItems.reduce((sum, item) => sum + item.price, 0);
|
|
82
|
+
const pricePaid = balanceItems.reduce((sum, item) => sum + item.pricePaid, 0);
|
|
80
83
|
|
|
81
84
|
// We take the minimum date here, because there is a highter change of later paymetns to be for other things than the registration itself
|
|
82
85
|
const paidAt = paidAtDates.length ? new Date(Math.min(...paidAtDates)) : null;
|
|
@@ -102,7 +105,7 @@ export class DocumentTemplate extends Model {
|
|
|
102
105
|
id: 'registration.startDate',
|
|
103
106
|
type: RecordType.Date,
|
|
104
107
|
}), // settings will be overwritten
|
|
105
|
-
dateValue: group?.settings?.startDate,
|
|
108
|
+
dateValue: registration.startDate ?? group?.settings?.startDate,
|
|
106
109
|
}),
|
|
107
110
|
'registration.endDate': RecordDateAnswer.create({
|
|
108
111
|
settings: RecordSettings.create({
|
|
@@ -114,16 +117,36 @@ export class DocumentTemplate extends Model {
|
|
|
114
117
|
'registration.price':
|
|
115
118
|
RecordPriceAnswer.create({
|
|
116
119
|
settings: RecordSettings.create({
|
|
120
|
+
id: 'registration.price',
|
|
121
|
+
type: RecordType.Price,
|
|
122
|
+
}), // settings will be overwritten
|
|
123
|
+
value: price,
|
|
124
|
+
}),
|
|
125
|
+
// This one is duplicated in case it got disabled (we need to use it to check if document is included)
|
|
126
|
+
'registration.priceOriginal':
|
|
127
|
+
RecordPriceAnswer.create({
|
|
128
|
+
settings: RecordSettings.create({
|
|
129
|
+
id: 'registration.priceOriginal',
|
|
130
|
+
type: RecordType.Price,
|
|
131
|
+
}), // settings will be overwritten
|
|
132
|
+
value: price,
|
|
133
|
+
}),
|
|
134
|
+
// This one is duplicated in case it got disabled (we need to use it to check if document is included)
|
|
135
|
+
'registration.pricePaidOriginal':
|
|
136
|
+
RecordPriceAnswer.create({
|
|
137
|
+
settings: RecordSettings.create({
|
|
138
|
+
id: 'registration.pricePaidOriginal',
|
|
117
139
|
type: RecordType.Price,
|
|
118
140
|
}), // settings will be overwritten
|
|
119
|
-
value:
|
|
141
|
+
value: pricePaid,
|
|
120
142
|
}),
|
|
121
143
|
'registration.pricePaid':
|
|
122
144
|
RecordPriceAnswer.create({
|
|
123
145
|
settings: RecordSettings.create({
|
|
146
|
+
id: 'registration.pricePaid',
|
|
124
147
|
type: RecordType.Price,
|
|
125
148
|
}), // settings will be overwritten
|
|
126
|
-
value:
|
|
149
|
+
value: pricePaid,
|
|
127
150
|
}),
|
|
128
151
|
'registration.paidAt':
|
|
129
152
|
RecordDateAnswer.create({
|
|
@@ -483,14 +506,22 @@ export class DocumentTemplate extends Model {
|
|
|
483
506
|
}
|
|
484
507
|
}
|
|
485
508
|
|
|
486
|
-
if (this.settings.minPrice !== null) {
|
|
487
|
-
|
|
488
|
-
|
|
509
|
+
if (this.settings.minPrice !== null && this.settings.minPrice > 0) {
|
|
510
|
+
const priceAnswer = fieldAnswers.get('registration.priceOriginal');
|
|
511
|
+
if (priceAnswer && priceAnswer instanceof RecordPriceAnswer) {
|
|
512
|
+
if ((priceAnswer.value ?? 0) < this.settings.minPrice) {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
489
515
|
}
|
|
490
516
|
}
|
|
491
517
|
|
|
492
|
-
if (this.settings.minPricePaid !== null) {
|
|
493
|
-
|
|
518
|
+
if (this.settings.minPricePaid !== null && this.settings.minPricePaid > 0) {
|
|
519
|
+
const pricePaidAnswer = fieldAnswers.get('registration.pricePaidOriginal');
|
|
520
|
+
const priceAnswer = fieldAnswers.get('registration.priceOriginal');
|
|
521
|
+
const price = (priceAnswer instanceof RecordPriceAnswer ? priceAnswer.value : 0) ?? 0;
|
|
522
|
+
const pricePaid = (pricePaidAnswer instanceof RecordPriceAnswer ? pricePaidAnswer.value : 0) ?? 0;
|
|
523
|
+
|
|
524
|
+
if (pricePaid < this.settings.minPricePaid && price > 0) {
|
|
494
525
|
return false;
|
|
495
526
|
}
|
|
496
527
|
}
|