@stamhoofd/models 2.121.0 → 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/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/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 +7 -1
- package/dist/helpers/Handlebars.js.map +1 -1
- 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/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/1778796615-payments-reversing-payment-id.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 +7 -2
- package/dist/models/BalanceItem.d.ts.map +1 -1
- package/dist/models/BalanceItem.js +41 -38
- 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/Invoice.d.ts +1 -0
- package/dist/models/Invoice.d.ts.map +1 -1
- package/dist/models/Invoice.js +8 -0
- 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/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 +23 -25
- package/dist/models/Organization.d.ts.map +1 -1
- package/dist/models/Organization.js +92 -64
- 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 +21 -2
- package/dist/models/Payment.d.ts.map +1 -1
- package/dist/models/Payment.js +43 -2
- package/dist/models/Payment.js.map +1 -1
- package/dist/models/Registration.d.ts.map +1 -1
- package/dist/models/Registration.js +3 -3
- package/dist/models/Registration.js.map +1 -1
- 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/_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 +1 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +1 -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 +32 -13
- package/src/factories/GroupFactory.ts +4 -6
- package/src/factories/OrganizationFactory.ts +12 -4
- package/src/factories/STPackageFactory.ts +2 -2
- package/src/factories/UserFactory.ts +4 -5
- package/src/helpers/EmailBuilder.ts +19 -28
- package/src/helpers/Handlebars.ts +6 -1
- 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/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/1778796615-payments-reversing-payment-id.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 +44 -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/Invoice.ts +9 -0
- package/src/models/MemberPlatformMembership.test.ts +70 -0
- package/src/models/MemberPlatformMembership.ts +16 -12
- package/src/models/Order.ts +14 -26
- package/src/models/Organization.ts +122 -93
- package/src/models/PasswordToken.ts +21 -21
- package/src/models/Payment.ts +42 -4
- package/src/models/Registration.ts +3 -3
- 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/_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 +2 -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
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { ManyToOneRelation } from '@simonbackx/simple-database';
|
|
2
2
|
import { column, Database } from '@simonbackx/simple-database';
|
|
3
|
-
import type {I18n} from '@stamhoofd/backend-i18n';
|
|
3
|
+
import type { I18n } from '@stamhoofd/backend-i18n';
|
|
4
4
|
import { QueryableModel } from '@stamhoofd/sql';
|
|
5
5
|
import basex from 'base-x';
|
|
6
6
|
import crypto from 'crypto';
|
|
7
7
|
|
|
8
|
-
import type {Organization} from './Organization.js';
|
|
8
|
+
import type { Organization } from './Organization.js';
|
|
9
9
|
import { User } from './User.js';
|
|
10
10
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
11
|
+
import { getAppHost } from '@stamhoofd/structures';
|
|
12
|
+
import { Platform } from './Platform.js';
|
|
11
13
|
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
12
14
|
const bs58 = basex(ALPHABET);
|
|
13
15
|
|
|
@@ -110,8 +112,7 @@ export class PasswordToken extends QueryableModel {
|
|
|
110
112
|
|
|
111
113
|
if (validUntil) {
|
|
112
114
|
token.validUntil = new Date(validUntil);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
+
} else {
|
|
115
116
|
token.validUntil = new Date();
|
|
116
117
|
token.validUntil.setTime(token.validUntil.getTime() + 3 * 3600 * 1000);
|
|
117
118
|
}
|
|
@@ -122,29 +123,28 @@ export class PasswordToken extends QueryableModel {
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
static async getPasswordRecoveryUrl(user: User, organization: Organization | null, i18n: I18n, validUntil?: Date) {
|
|
125
|
-
if (user.organizationId !== null && ((user.organizationId ?? null) !== (organization?.id ?? null))) {
|
|
126
|
-
throw new Error('Unexpected mismatch in organization id for PasswordToken');
|
|
127
|
-
}
|
|
128
126
|
// Send an e-mail to say you already have an account + follow password forgot flow
|
|
129
127
|
const token = await PasswordToken.createToken(user, validUntil);
|
|
128
|
+
return token.getPasswordRecoveryUrl(organization, i18n, user);
|
|
129
|
+
}
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Build the password recovery url for this (already created) token.
|
|
133
|
+
* Pass the user to avoid an extra query when it is already loaded.
|
|
134
|
+
*/
|
|
135
|
+
async getPasswordRecoveryUrl(organization: Organization | null, i18n: I18n, user?: User) {
|
|
136
|
+
const tokenUser = user ?? await User.getByID(this.userId);
|
|
137
|
+
if (!tokenUser) {
|
|
138
|
+
throw new Error('PasswordToken without a valid user');
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
if (tokenUser.organizationId !== null && ((tokenUser.organizationId ?? null) !== (organization?.id ?? null))) {
|
|
142
|
+
throw new Error('Unexpected mismatch in organization id for PasswordToken');
|
|
143
|
+
}
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return Promise.resolve(host + '/login' + '?email=' + encodeURIComponent(user.email) + '&hasAccount=' + (user.hasAccount() ? 1 : 0));
|
|
145
|
+
const hasOrganizationPermissions = organization ? tokenUser.permissions?.forOrganization(organization, await Platform.getSharedStruct())?.isEmpty === false : false;
|
|
146
|
+
const host = 'https://' + getAppHost(hasOrganizationPermissions ? 'dashboard' : 'registration', organization, hasOrganizationPermissions, i18n);
|
|
147
|
+
return host + '/reset-password?token=' + encodeURIComponent(this.token);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
static async clearFor(userId: string) {
|
package/src/models/Payment.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { column } from '@simonbackx/simple-database';
|
|
2
|
-
import
|
|
3
|
-
import { BalanceItemDetailed, BalanceItemPaymentDetailed, BaseOrganization, PaymentCustomer, PaymentGeneral, PaymentProvider, PaymentType, Settlement, TransferSettings } from '@stamhoofd/structures';
|
|
2
|
+
import { BalanceItemDetailed, BalanceItemPaymentDetailed, BaseOrganization, PaymentCustomer, PaymentGeneral, PaymentMethod, PaymentProvider, PaymentStatus, PaymentType, Settlement, TransferSettings } from '@stamhoofd/structures';
|
|
4
3
|
import { Formatter } from '@stamhoofd/utility';
|
|
5
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
6
5
|
|
|
7
|
-
import { QueryableModel } from '@stamhoofd/sql';
|
|
6
|
+
import { QueryableModel, SQL } from '@stamhoofd/sql';
|
|
8
7
|
import { CreateMandateSettings } from '@stamhoofd/structures/checkout/CreateMandateSettings.js';
|
|
9
8
|
import type { BalanceItem } from './BalanceItem.js';
|
|
10
9
|
import type { BalanceItemPayment } from './BalanceItemPayment.js';
|
|
@@ -98,10 +97,16 @@ export class Payment extends QueryableModel {
|
|
|
98
97
|
@column({ type: 'integer' })
|
|
99
98
|
price: number;
|
|
100
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Total price refunded or charged back
|
|
102
|
+
*/
|
|
103
|
+
@column({ type: 'integer' })
|
|
104
|
+
refundedAmount = 0;
|
|
105
|
+
|
|
101
106
|
/**
|
|
102
107
|
* The difference between the sum of the balance item payments price and the price of the payment, caused by rounding to 1 cent.
|
|
103
108
|
* This cannot be >= 100 (= 0,01 euro) or <= -100 (=-0,01 euro)
|
|
104
|
-
*
|
|
109
|
+
*
|
|
105
110
|
* For understanding the sign of the value, regard it as an extra balance item to the payment.
|
|
106
111
|
*
|
|
107
112
|
* Just like all prices, this price is stored per ten thousand (1 = 0,0001 ). Storing smaller units is not possible because even in balance items, the price to pay cannot be smaller than 0,0001 euro
|
|
@@ -141,6 +146,20 @@ export class Payment extends QueryableModel {
|
|
|
141
146
|
@column({ type: 'integer' })
|
|
142
147
|
serviceFeeManualCharged = 0;
|
|
143
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Transfer fee - if we need to charge this for a certain provider
|
|
151
|
+
*
|
|
152
|
+
* This EXCLUDES VAT
|
|
153
|
+
*/
|
|
154
|
+
@column({ type: 'integer' })
|
|
155
|
+
transferFeeManual = 0;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Part of the transferFeeManual, that has been invoiced (added to outstanding balance)
|
|
159
|
+
*/
|
|
160
|
+
@column({ type: 'integer' })
|
|
161
|
+
transferFeeManualCharged = 0;
|
|
162
|
+
|
|
144
163
|
/**
|
|
145
164
|
* Included in the total price
|
|
146
165
|
*/
|
|
@@ -156,6 +175,12 @@ export class Payment extends QueryableModel {
|
|
|
156
175
|
@column({ type: 'string', nullable: true })
|
|
157
176
|
invoiceId: string | null = null;
|
|
158
177
|
|
|
178
|
+
/**
|
|
179
|
+
* In case the payment was initiated by an admin or not.
|
|
180
|
+
*/
|
|
181
|
+
@column({ type: 'string', nullable: true })
|
|
182
|
+
adminUserId: string | null = null;
|
|
183
|
+
|
|
159
184
|
@column({
|
|
160
185
|
type: 'datetime', beforeSave(old?: any) {
|
|
161
186
|
if (old !== undefined) {
|
|
@@ -196,11 +221,24 @@ export class Payment extends QueryableModel {
|
|
|
196
221
|
@column({ type: 'string', nullable: true })
|
|
197
222
|
ibanName: string | null = null;
|
|
198
223
|
|
|
224
|
+
get canChangeStatus() {
|
|
225
|
+
return this.price !== 0 && (this.method === PaymentMethod.Transfer || this.method === PaymentMethod.PointOfSale || this.method === PaymentMethod.Unknown);
|
|
226
|
+
}
|
|
227
|
+
|
|
199
228
|
generateDescription(organization: Organization, reference: string, replacements: { [key: string]: string } = {}) {
|
|
200
229
|
const settings = this.transferSettings ?? organization.meta.transferSettings;
|
|
201
230
|
this.transferDescription = settings.generateDescription(reference, organization.address.country, replacements);
|
|
202
231
|
}
|
|
203
232
|
|
|
233
|
+
async updateRefundedAmount() {
|
|
234
|
+
this.refundedAmount = await Payment.select()
|
|
235
|
+
.where('organizationId', this.organizationId)
|
|
236
|
+
.where('reversingPaymentId', this.id)
|
|
237
|
+
.where('status', PaymentStatus.Succeeded)
|
|
238
|
+
.sum(SQL.column('price'));
|
|
239
|
+
await this.save();
|
|
240
|
+
}
|
|
241
|
+
|
|
204
242
|
static roundPrice(price: number) {
|
|
205
243
|
return Math.round(price / 100) * 100;
|
|
206
244
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ManyToOneRelation } from '@simonbackx/simple-database';
|
|
2
2
|
import { column } from '@simonbackx/simple-database';
|
|
3
3
|
import type { RecordAnswer } from '@stamhoofd/structures';
|
|
4
|
-
import { AppliedRegistrationDiscount, GroupPrice,
|
|
4
|
+
import { AppliedRegistrationDiscount, GroupPrice, RecordAnswerMapDecoder, RegisterItemOption, Registration as RegistrationStructure, StockReservation } from '@stamhoofd/structures';
|
|
5
5
|
import { v4 as uuidv4 } from 'uuid';
|
|
6
6
|
|
|
7
7
|
import { ArrayDecoder, MapDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
@@ -39,7 +39,7 @@ export class Registration extends QueryableModel {
|
|
|
39
39
|
@column({ type: 'json', decoder: new ArrayDecoder(RegisterItemOption) })
|
|
40
40
|
options: RegisterItemOption[] = [];
|
|
41
41
|
|
|
42
|
-
@column({ type: 'json', decoder:
|
|
42
|
+
@column({ type: 'json', decoder: RecordAnswerMapDecoder })
|
|
43
43
|
recordAnswers: Map<string, RecordAnswer> = new Map();
|
|
44
44
|
|
|
45
45
|
/**
|
|
@@ -166,6 +166,6 @@ export class Registration extends QueryableModel {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
shouldIncludeStock() {
|
|
169
|
-
return (this.registeredAt !== null && this.deactivatedAt === null) ||
|
|
169
|
+
return (this.registeredAt !== null && this.deactivatedAt === null) || (this.reservedUntil && this.reservedUntil > new Date());
|
|
170
170
|
}
|
|
171
171
|
}
|
package/src/models/STCredit.ts
CHANGED
|
@@ -49,4 +49,36 @@ export class STCredit extends QueryableModel {
|
|
|
49
49
|
skipUpdate: true,
|
|
50
50
|
})
|
|
51
51
|
updatedAt: Date;
|
|
52
|
+
|
|
53
|
+
static async getForOrganization(organizationId: string) {
|
|
54
|
+
return await STCredit.where({ organizationId }, {
|
|
55
|
+
sort: [{
|
|
56
|
+
column: 'createdAt',
|
|
57
|
+
direction: 'DESC',
|
|
58
|
+
}],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static async getBalance(organizationId: string) {
|
|
63
|
+
const now = new Date();
|
|
64
|
+
const credits = await this.getForOrganization(organizationId);
|
|
65
|
+
credits.reverse();
|
|
66
|
+
let balance = 0;
|
|
67
|
+
|
|
68
|
+
for (const credit of credits) {
|
|
69
|
+
if (credit.expireAt !== null && credit.expireAt <= now) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// TODO: we can expire credits here
|
|
73
|
+
balance += credit.change;
|
|
74
|
+
if (balance < 0) {
|
|
75
|
+
// This is needed to make deleting credit and expiring credit work.
|
|
76
|
+
// At no point in time, the credits can get negative.
|
|
77
|
+
// E.g. Getting credits, using them, and later expiring 'getting the credits' won't have impact on future credits
|
|
78
|
+
balance = 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { balance: balance };
|
|
83
|
+
}
|
|
52
84
|
}
|
package/src/models/STInvoice.ts
CHANGED
|
@@ -20,12 +20,14 @@ export class STInvoice extends QueryableModel {
|
|
|
20
20
|
id!: string;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
+
* @deprecated
|
|
23
24
|
* Organization that made the invoice. Can be null if the organization was deleted and for the migration from V1 -> V2
|
|
24
25
|
*/
|
|
25
26
|
@column({ type: 'string', nullable: true })
|
|
26
|
-
organizationId: string | null;
|
|
27
|
+
protected organizationId: string | null;
|
|
27
28
|
|
|
28
29
|
/**
|
|
30
|
+
*
|
|
29
31
|
* Organization that is associated with this invoice (can be null if deleted or unknown)
|
|
30
32
|
*/
|
|
31
33
|
@column({ type: 'string', nullable: true })
|
|
@@ -56,6 +58,12 @@ export class STInvoice extends QueryableModel {
|
|
|
56
58
|
@column({ type: 'datetime', nullable: true })
|
|
57
59
|
paidAt: Date | null = null;
|
|
58
60
|
|
|
61
|
+
/**
|
|
62
|
+
* If a invoice was refunded because of a cancellation, we store the negative invoice id here
|
|
63
|
+
*/
|
|
64
|
+
@column({ type: 'string', nullable: true })
|
|
65
|
+
negativeInvoiceId: string | null = null;
|
|
66
|
+
|
|
59
67
|
@column({
|
|
60
68
|
type: 'datetime', beforeSave(old?: any) {
|
|
61
69
|
if (old !== undefined) {
|
|
@@ -117,14 +125,12 @@ export class STInvoice extends QueryableModel {
|
|
|
117
125
|
cardLabel: ('cardLabel' in details ? details.cardLabel : null),
|
|
118
126
|
}),
|
|
119
127
|
}));
|
|
120
|
-
}
|
|
121
|
-
catch (e) {
|
|
128
|
+
} catch (e) {
|
|
122
129
|
console.error(e);
|
|
123
130
|
}
|
|
124
131
|
}
|
|
125
132
|
}
|
|
126
|
-
}
|
|
127
|
-
catch (e) {
|
|
133
|
+
} catch (e) {
|
|
128
134
|
console.error(e);
|
|
129
135
|
}
|
|
130
136
|
return mandates;
|
package/src/models/STPackage.ts
CHANGED
|
@@ -65,6 +65,19 @@ export class STPackage extends QueryableModel {
|
|
|
65
65
|
@column({ type: 'datetime', nullable: true })
|
|
66
66
|
lastEmailAt: Date | null = null;
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Helper to handle edge cases where validUntil is null but removeAt is set
|
|
70
|
+
*/
|
|
71
|
+
get endDate() {
|
|
72
|
+
if (!this.removeAt) {
|
|
73
|
+
return this.validUntil;
|
|
74
|
+
}
|
|
75
|
+
if (!this.validUntil) {
|
|
76
|
+
return this.removeAt;
|
|
77
|
+
}
|
|
78
|
+
return new Date(Math.min(this.validUntil.getTime(), this.removeAt.getTime()));
|
|
79
|
+
}
|
|
80
|
+
|
|
68
81
|
async activate() {
|
|
69
82
|
if (this.validAt !== null) {
|
|
70
83
|
return;
|
|
@@ -150,8 +163,7 @@ export class STPackage extends QueryableModel {
|
|
|
150
163
|
pack.meta.pricingType = STPricingType.Fixed;
|
|
151
164
|
pack.validUntil = null;
|
|
152
165
|
pack.removeAt = null;
|
|
153
|
-
}
|
|
154
|
-
else if (pack.meta.type === STPackageType.Members) {
|
|
166
|
+
} else if (pack.meta.type === STPackageType.Members) {
|
|
155
167
|
pack.meta.serviceFeeFixed = 0;
|
|
156
168
|
pack.meta.serviceFeePercentage = 0;
|
|
157
169
|
pack.meta.serviceFeeMinimum = 0;
|
|
@@ -165,8 +177,6 @@ export class STPackage extends QueryableModel {
|
|
|
165
177
|
}
|
|
166
178
|
|
|
167
179
|
createStatus(): STPackageStatus {
|
|
168
|
-
// TODO: if payment failed: temporary set valid until to 2 weeks after last/first failed payment
|
|
169
|
-
|
|
170
180
|
return STPackageStatus.create({
|
|
171
181
|
startDate: this.meta.startDate,
|
|
172
182
|
validUntil: this.validUntil,
|
|
@@ -205,20 +215,16 @@ export class STPackage extends QueryableModel {
|
|
|
205
215
|
if (this.meta.type === STPackageType.Members) {
|
|
206
216
|
type = EmailTemplateType.MembersExpirationReminder;
|
|
207
217
|
allowDays = 32;
|
|
208
|
-
}
|
|
209
|
-
else if (this.meta.type === STPackageType.Webshops) {
|
|
218
|
+
} else if (this.meta.type === STPackageType.Webshops) {
|
|
210
219
|
type = EmailTemplateType.WebshopsExpirationReminder;
|
|
211
220
|
allowDays = 32;
|
|
212
|
-
}
|
|
213
|
-
else if (this.meta.type === STPackageType.SingleWebshop) {
|
|
221
|
+
} else if (this.meta.type === STPackageType.SingleWebshop) {
|
|
214
222
|
type = EmailTemplateType.SingleWebshopExpirationReminder;
|
|
215
223
|
allowDays = 7;
|
|
216
|
-
}
|
|
217
|
-
else if (this.meta.type === STPackageType.TrialMembers) {
|
|
224
|
+
} else if (this.meta.type === STPackageType.TrialMembers) {
|
|
218
225
|
type = EmailTemplateType.TrialMembersExpirationReminder;
|
|
219
226
|
allowDays = 3;
|
|
220
|
-
}
|
|
221
|
-
else if (this.meta.type === STPackageType.TrialWebshops) {
|
|
227
|
+
} else if (this.meta.type === STPackageType.TrialWebshops) {
|
|
222
228
|
type = EmailTemplateType.TrialWebshopsExpirationReminder;
|
|
223
229
|
allowDays = 3;
|
|
224
230
|
}
|
|
@@ -237,8 +243,7 @@ export class STPackage extends QueryableModel {
|
|
|
237
243
|
});
|
|
238
244
|
}
|
|
239
245
|
this.lastEmailAt = new Date();
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
246
|
+
} else {
|
|
242
247
|
console.log('Skip sending expiration email for ' + this.id + ' (no type)');
|
|
243
248
|
}
|
|
244
249
|
|
|
@@ -25,6 +25,13 @@ export class UsedRegisterCode extends QueryableModel {
|
|
|
25
25
|
* Set if this has been rewarded
|
|
26
26
|
*/
|
|
27
27
|
@column({ type: 'string', nullable: true })
|
|
28
|
+
balanceItemId: string | null = null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Migrated to balanceItemId
|
|
32
|
+
* Set if this has been rewarded
|
|
33
|
+
*/
|
|
34
|
+
@column({ type: 'string', nullable: true })
|
|
28
35
|
creditId: string | null = null;
|
|
29
36
|
|
|
30
37
|
@column({
|
|
@@ -48,4 +55,31 @@ export class UsedRegisterCode extends QueryableModel {
|
|
|
48
55
|
skipUpdate: true,
|
|
49
56
|
})
|
|
50
57
|
updatedAt: Date;
|
|
58
|
+
|
|
59
|
+
static async getFor(organizationId: string): Promise<UsedRegisterCode | undefined> {
|
|
60
|
+
const code = await this.where({ organizationId }, { limit: 1 });
|
|
61
|
+
return code[0] ?? undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static async getAll(code: string) {
|
|
65
|
+
const used = await UsedRegisterCode.where({
|
|
66
|
+
code,
|
|
67
|
+
});
|
|
68
|
+
return used;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static async getUsed(code: string) {
|
|
72
|
+
const used = await UsedRegisterCode.select()
|
|
73
|
+
.where('code', code)
|
|
74
|
+
.andWhere('balanceItemId', '!=', null)
|
|
75
|
+
.fetch();
|
|
76
|
+
return used;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static async getUsedCount(code: string) {
|
|
80
|
+
return await UsedRegisterCode.select()
|
|
81
|
+
.where('code', code)
|
|
82
|
+
.andWhere('balanceItemId', '!=', null)
|
|
83
|
+
.count();
|
|
84
|
+
}
|
|
51
85
|
}
|
package/src/models/_relations.ts
CHANGED
|
@@ -22,6 +22,10 @@ import { Group } from './Group.js';
|
|
|
22
22
|
import { Registration } from './Registration.js';
|
|
23
23
|
import { Token } from './Token.js';
|
|
24
24
|
import { PasswordToken } from './PasswordToken.js';
|
|
25
|
+
import { City } from './addresses/City.js';
|
|
26
|
+
import { Province } from './addresses/Province.js';
|
|
27
|
+
import { PostalCode } from './addresses/PostalCode.js';
|
|
28
|
+
import { Street } from './addresses/Street.js';
|
|
25
29
|
|
|
26
30
|
if (User === undefined) {
|
|
27
31
|
throw new Error('Unexpected missing User');
|
|
@@ -131,3 +135,28 @@ if (!PasswordToken.relations) {
|
|
|
131
135
|
PasswordToken.user = new ManyToOneRelation(User, 'user');
|
|
132
136
|
PasswordToken.user.foreignKey = 'userId';
|
|
133
137
|
PasswordToken.relations.push(PasswordToken.user);
|
|
138
|
+
|
|
139
|
+
if (!City.relations) {
|
|
140
|
+
City.relations = [];
|
|
141
|
+
}
|
|
142
|
+
City.parentCity = new ManyToOneRelation(City, 'parentCity');
|
|
143
|
+
City.parentCity.foreignKey = 'parentCityId';
|
|
144
|
+
City.relations.push(City.parentCity);
|
|
145
|
+
|
|
146
|
+
City.province = new ManyToOneRelation(Province, 'province');
|
|
147
|
+
City.province.foreignKey = 'provinceId';
|
|
148
|
+
City.relations.push(City.province);
|
|
149
|
+
|
|
150
|
+
if (!PostalCode.relations) {
|
|
151
|
+
PostalCode.relations = [];
|
|
152
|
+
}
|
|
153
|
+
PostalCode.city = new ManyToOneRelation(City, 'city');
|
|
154
|
+
PostalCode.city.foreignKey = 'cityId';
|
|
155
|
+
PostalCode.relations.push(PostalCode.city);
|
|
156
|
+
|
|
157
|
+
if (!Street.relations) {
|
|
158
|
+
Street.relations = [];
|
|
159
|
+
}
|
|
160
|
+
Street.city = new ManyToOneRelation(City, 'city');
|
|
161
|
+
Street.city.foreignKey = 'cityId';
|
|
162
|
+
Street.relations.push(Street.city);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { column
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
|
+
import type { ManyToOneRelation } from '@simonbackx/simple-database';
|
|
2
3
|
import type { Country } from '@stamhoofd/types/Country';
|
|
3
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
5
|
|
|
5
6
|
import { QueryableModel } from '@stamhoofd/sql';
|
|
6
|
-
import { Province } from './Province.js';
|
|
7
|
+
import type { Province } from './Province.js';
|
|
7
8
|
|
|
8
9
|
export class City extends QueryableModel {
|
|
9
10
|
static table = 'cities';
|
|
@@ -18,15 +19,16 @@ export class City extends QueryableModel {
|
|
|
18
19
|
@column({ type: 'string' })
|
|
19
20
|
name: string;
|
|
20
21
|
|
|
21
|
-
@column({ type: 'string'
|
|
22
|
+
@column({ type: 'string' })
|
|
22
23
|
provinceId: string;
|
|
23
24
|
|
|
24
|
-
@column({ type: 'string',
|
|
25
|
+
@column({ type: 'string', nullable: true })
|
|
25
26
|
parentCityId: string | null = null;
|
|
26
27
|
|
|
27
28
|
@column({ type: 'string' })
|
|
28
29
|
country: Country;
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
static
|
|
31
|
+
// Relations are wired up in _relations.ts (after the classes are defined) to avoid circular references.
|
|
32
|
+
static parentCity: ManyToOneRelation<'parentCity', City>;
|
|
33
|
+
static province: ManyToOneRelation<'province', Province>;
|
|
32
34
|
}
|
|
@@ -3,6 +3,7 @@ import { Country } from '@stamhoofd/types/Country';
|
|
|
3
3
|
import { City } from './City.js';
|
|
4
4
|
import { PostalCode } from './PostalCode.js';
|
|
5
5
|
import { Province } from './Province.js';
|
|
6
|
+
import '../_relations.js';
|
|
6
7
|
|
|
7
8
|
describe('Model.PostalCode', () => {
|
|
8
9
|
let oostVlaanderen!: Province;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { column, Database
|
|
1
|
+
import { column, Database } from '@simonbackx/simple-database';
|
|
2
|
+
import type { ManyToOneRelation } from '@simonbackx/simple-database';
|
|
2
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
3
4
|
import { QueryableModel } from '@stamhoofd/sql';
|
|
4
5
|
import { Country } from '@stamhoofd/types/Country';
|
|
@@ -20,13 +21,14 @@ export class PostalCode extends QueryableModel {
|
|
|
20
21
|
@column({ type: 'string' })
|
|
21
22
|
postalCode: string;
|
|
22
23
|
|
|
23
|
-
@column({ type: 'string'
|
|
24
|
+
@column({ type: 'string' })
|
|
24
25
|
cityId: string;
|
|
25
26
|
|
|
26
27
|
@column({ type: 'string' })
|
|
27
28
|
country: Country;
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
// Relation is wired up in _relations.ts (after the classes are defined) to avoid circular references.
|
|
31
|
+
static city: ManyToOneRelation<'city', City>;
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* Search for a given city via it's postal code and country, collecting information about the city (id), parentCity (id) and province (id)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { column
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
|
+
import type { ManyToOneRelation } from '@simonbackx/simple-database';
|
|
2
3
|
import { QueryableModel } from '@stamhoofd/sql';
|
|
3
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
5
|
|
|
5
|
-
import { City } from './City.js';
|
|
6
|
+
import type { City } from './City.js';
|
|
6
7
|
|
|
7
8
|
export class Street extends QueryableModel {
|
|
8
9
|
static table = 'streets';
|
|
@@ -17,8 +18,9 @@ export class Street extends QueryableModel {
|
|
|
17
18
|
@column({ type: 'string' })
|
|
18
19
|
name: string;
|
|
19
20
|
|
|
20
|
-
@column({ type: 'string'
|
|
21
|
+
@column({ type: 'string' })
|
|
21
22
|
cityId: string;
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
// Relation is wired up in _relations.ts (after the classes are defined) to avoid circular references.
|
|
25
|
+
static city: ManyToOneRelation<'city', City>;
|
|
24
26
|
}
|
package/src/models/index.ts
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
|
+
|
|
3
|
+
import { QueryableModel } from '@stamhoofd/sql';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Temporary table for the migration from v1 to v2.
|
|
7
|
+
* Keeps track of which combination of group id and cycle has been migrated to a new group.
|
|
8
|
+
*/
|
|
9
|
+
export class V1GroupMigrationData extends QueryableModel {
|
|
10
|
+
static table = 'v1_groups_migration_data';
|
|
11
|
+
|
|
12
|
+
// new group id (can be same as old group id if no new group was created)
|
|
13
|
+
@column({ primary: true, type: 'string' })
|
|
14
|
+
newGroupId: string;
|
|
15
|
+
|
|
16
|
+
// old group id from v1
|
|
17
|
+
@column({ type: 'string' })
|
|
18
|
+
oldGroupId: string;
|
|
19
|
+
|
|
20
|
+
// old cycle from v1
|
|
21
|
+
@column({ type: 'integer' })
|
|
22
|
+
oldCycle = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Temporary table for the migration from v1 to v2.
|
|
27
|
+
* Keeps track of which combination of waiting list group id and cycle has been migrated to a new group.
|
|
28
|
+
*/
|
|
29
|
+
export class V1WaitingListMigrationData extends QueryableModel {
|
|
30
|
+
static table = 'v1_waiting_list_migration_data';
|
|
31
|
+
|
|
32
|
+
// new group id (can be same as old group id if no new group was created)
|
|
33
|
+
@column({ primary: true, type: 'string' })
|
|
34
|
+
newGroupId: string;
|
|
35
|
+
|
|
36
|
+
// old group id from v1
|
|
37
|
+
@column({ type: 'string' })
|
|
38
|
+
oldGroupId: string;
|
|
39
|
+
|
|
40
|
+
// old cycle from v1
|
|
41
|
+
@column({ type: 'integer' })
|
|
42
|
+
oldCycle = 0;
|
|
43
|
+
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Member } from '../models/index.js';
|
|
2
|
-
export declare function mergeMultipleMembers(members: Member[]): Promise<void>;
|
|
3
|
-
export declare function findEqualMembers({ firstName, lastName, birthDay, }: {
|
|
4
|
-
firstName: string;
|
|
5
|
-
lastName: string;
|
|
6
|
-
birthDay: string;
|
|
7
|
-
}): Promise<Member[]>;
|
|
8
|
-
export declare function mergeTwoMembers(base: Member, other: Member): Promise<void>;
|
|
9
|
-
export declare function mergeMemberDetails(base: Member, other: Member): void;
|
|
10
|
-
export declare function selectBaseMember(members: Member[]): {
|
|
11
|
-
base: Member;
|
|
12
|
-
others: Member[];
|
|
13
|
-
};
|
|
14
|
-
//# sourceMappingURL=MemberMerger.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MemberMerger.d.ts","sourceRoot":"","sources":["../../src/helpers/MemberMerger.ts"],"names":[],"mappings":"AAaA,OAAO,EAGH,MAAM,EAMT,MAAM,oBAAoB,CAAC;AAE5B,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,iBAU3D;AAED,wBAAsB,gBAAgB,CAAC,EACnC,SAAS,EACT,QAAQ,EACR,QAAQ,GACX,EAAE;IACC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAMpB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2ChF;AA8HD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAkGpE;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB,CASA"}
|