@stamhoofd/models 2.106.0 → 2.107.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/factories/GroupFactory.js +1 -1
- package/dist/src/factories/GroupFactory.js.map +1 -1
- package/dist/src/migrations/1762521336-did-send-peppol.sql +6 -0
- package/dist/src/migrations/1763216321-multiply-balance-item-payments.sql +1 -0
- package/dist/src/migrations/1763216322-multiply-balance-items.sql +6 -0
- package/dist/src/migrations/1763216323-bigint-cached-balances.sql +5 -0
- package/dist/src/migrations/1763216324-multiply-cached-balances.sql +6 -0
- package/dist/src/migrations/1763216325-multiply-member-platform-memberships.sql +4 -0
- package/dist/src/migrations/1763216326-multiply-payments.sql +8 -0
- package/dist/src/migrations/1763216327-multiply-register-codes.sql +4 -0
- package/dist/src/migrations/1763216328-multiply-credits.sql +3 -0
- package/dist/src/migrations/1763216329-multiply-orders.sql +3 -0
- package/dist/src/migrations/1763216330-multiply-uitpas-numbers.sql +4 -0
- package/dist/src/migrations/1763216331-balance-item-vat-percentages.sql +4 -0
- package/dist/src/migrations/1763216332-balance-item-price-total.sql +2 -0
- package/dist/src/migrations/1763216333-balance-item-price-total-fill.sql +3 -0
- package/dist/src/models/BalanceItem.d.ts +80 -2
- package/dist/src/models/BalanceItem.d.ts.map +1 -1
- package/dist/src/models/BalanceItem.js +124 -4
- package/dist/src/models/BalanceItem.js.map +1 -1
- package/dist/src/models/EmailVerificationCode.js +1 -1
- package/dist/src/models/EmailVerificationCode.js.map +1 -1
- package/dist/src/models/Event.d.ts.map +1 -1
- package/dist/src/models/Event.js.map +1 -1
- package/dist/src/models/Order.d.ts +6 -0
- package/dist/src/models/Order.d.ts.map +1 -1
- package/dist/src/models/Order.js +6 -2
- package/dist/src/models/Order.js.map +1 -1
- package/dist/src/models/PayconiqPayment.js +2 -2
- package/dist/src/models/PayconiqPayment.js.map +1 -1
- package/dist/src/models/Payment.d.ts +3 -2
- package/dist/src/models/Payment.d.ts.map +1 -1
- package/dist/src/models/Payment.js +3 -0
- package/dist/src/models/Payment.js.map +1 -1
- package/dist/src/models/STInvoice.d.ts +1 -0
- package/dist/src/models/STInvoice.d.ts.map +1 -1
- package/dist/src/models/STInvoice.js +4 -0
- package/dist/src/models/STInvoice.js.map +1 -1
- package/dist/src/models/STPackage.js +2 -2
- package/dist/src/models/STPackage.js.map +1 -1
- package/package.json +4 -4
- package/src/factories/GroupFactory.ts +1 -1
- package/src/migrations/1762521336-did-send-peppol.sql +6 -0
- package/src/migrations/1763216321-multiply-balance-item-payments.sql +1 -0
- package/src/migrations/1763216322-multiply-balance-items.sql +6 -0
- package/src/migrations/1763216323-bigint-cached-balances.sql +5 -0
- package/src/migrations/1763216324-multiply-cached-balances.sql +6 -0
- package/src/migrations/1763216325-multiply-member-platform-memberships.sql +4 -0
- package/src/migrations/1763216326-multiply-payments.sql +8 -0
- package/src/migrations/1763216327-multiply-register-codes.sql +4 -0
- package/src/migrations/1763216328-multiply-credits.sql +3 -0
- package/src/migrations/1763216329-multiply-orders.sql +3 -0
- package/src/migrations/1763216330-multiply-uitpas-numbers.sql +4 -0
- package/src/migrations/1763216331-balance-item-vat-percentages.sql +4 -0
- package/src/migrations/1763216332-balance-item-price-total.sql +2 -0
- package/src/migrations/1763216333-balance-item-price-total-fill.sql +3 -0
- package/src/models/BalanceItem.ts +129 -5
- package/src/models/EmailVerificationCode.ts +1 -1
- package/src/models/Event.ts +2 -1
- package/src/models/Order.ts +6 -2
- package/src/models/PayconiqPayment.ts +2 -2
- package/src/models/Payment.ts +5 -1
- package/src/models/STInvoice.ts +3 -0
- package/src/models/STPackage.ts +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { column, Database } from '@simonbackx/simple-database';
|
|
2
|
-
import { BalanceItemPaymentWithPayment, BalanceItemPaymentWithPrivatePayment, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItem as BalanceItemStruct, BalanceItemType, BalanceItemWithPayments, BalanceItemWithPrivatePayments, Payment as PaymentStruct, PrivatePayment } from '@stamhoofd/structures';
|
|
2
|
+
import { BalanceItemPaymentWithPayment, BalanceItemPaymentWithPrivatePayment, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItem as BalanceItemStruct, BalanceItemType, BalanceItemWithPayments, BalanceItemWithPrivatePayments, Payment as PaymentStruct, PrivatePayment, VATExcemptReason } from '@stamhoofd/structures';
|
|
3
3
|
import { Formatter } from '@stamhoofd/utility';
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
5
|
|
|
@@ -72,25 +72,81 @@ export class BalanceItem extends QueryableModel {
|
|
|
72
72
|
amount = 1;
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* Price per piece
|
|
76
|
+
*
|
|
77
|
+
* NOTE: We store an integer of the price up to 4 digits after the comma.
|
|
78
|
+
* 1 euro = 10000.
|
|
79
|
+
* 0,01 euro = 100
|
|
80
|
+
* 0,0001 euro = 1
|
|
81
|
+
*
|
|
82
|
+
* This is required for correct VAT calculations without intermediate rounding.
|
|
76
83
|
*/
|
|
77
84
|
@column({ type: 'integer' })
|
|
78
85
|
unitPrice: number;
|
|
79
86
|
|
|
87
|
+
@column({ type: 'integer', nullable: true })
|
|
88
|
+
VATPercentage: number | null = null;
|
|
89
|
+
|
|
90
|
+
@column({ type: 'boolean' })
|
|
91
|
+
VATIncluded = true;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Whether there is a VAT excempt reason.
|
|
95
|
+
* Note: keep the original VAT in these cases. On time of payment or invoicing, the VAT excemption will be revalidated.
|
|
96
|
+
* If that fails, we can still charge the VAT.
|
|
97
|
+
*/
|
|
98
|
+
@column({ type: 'string', nullable: true })
|
|
99
|
+
VATExcempt: VATExcemptReason | null = null;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* This is a cached value for storing in the database.
|
|
103
|
+
* It stores the calculated price with VAT.
|
|
104
|
+
*
|
|
105
|
+
* price should = pricePaid + pricePending + priceOpen
|
|
106
|
+
*/
|
|
107
|
+
@column({
|
|
108
|
+
type: 'integer',
|
|
109
|
+
beforeSave: function () {
|
|
110
|
+
return this.priceWithVAT;
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
priceTotal = 0;
|
|
114
|
+
|
|
80
115
|
/**
|
|
81
116
|
* Cached value, for optimizations
|
|
117
|
+
*
|
|
118
|
+
* NOTE: We store an integer of the price up to 4 digits after the comma.
|
|
119
|
+
* 1 euro = 10000.
|
|
120
|
+
* 0,01 euro = 100
|
|
121
|
+
* 0,0001 euro = 1
|
|
122
|
+
*
|
|
123
|
+
* This is required for correct VAT calculations without intermediate rounding.
|
|
82
124
|
*/
|
|
83
125
|
@column({ type: 'integer' })
|
|
84
126
|
pricePaid = 0;
|
|
85
127
|
|
|
86
128
|
/**
|
|
87
129
|
* Cached value, for optimizations
|
|
130
|
+
*
|
|
131
|
+
* NOTE: We store an integer of the price up to 4 digits after the comma.
|
|
132
|
+
* 1 euro = 10000.
|
|
133
|
+
* 0,01 euro = 100
|
|
134
|
+
* 0,0001 euro = 1
|
|
135
|
+
*
|
|
136
|
+
* This is required for correct VAT calculations without intermediate rounding.
|
|
88
137
|
*/
|
|
89
138
|
@column({ type: 'integer' })
|
|
90
139
|
pricePending = 0;
|
|
91
140
|
|
|
92
141
|
/**
|
|
93
142
|
* Cached value, for optimizations
|
|
143
|
+
*
|
|
144
|
+
* NOTE: We store an integer of the price up to 4 digits after the comma.
|
|
145
|
+
* 1 euro = 10000.
|
|
146
|
+
* 0,01 euro = 100
|
|
147
|
+
* 0,0001 euro = 1
|
|
148
|
+
*
|
|
149
|
+
* This is required for correct VAT calculations without intermediate rounding.
|
|
94
150
|
*/
|
|
95
151
|
@column({
|
|
96
152
|
type: 'integer',
|
|
@@ -143,7 +199,67 @@ export class BalanceItem extends QueryableModel {
|
|
|
143
199
|
})
|
|
144
200
|
updatedAt: Date;
|
|
145
201
|
|
|
202
|
+
/**
|
|
203
|
+
* @deprecated: use priceWithVAT
|
|
204
|
+
* NOTE: This contains an integer of the price up to 4 digits after the comma.
|
|
205
|
+
* 1 euro = 10000.
|
|
206
|
+
* 0,01 euro = 100
|
|
207
|
+
* 0,0001 euro = 1
|
|
208
|
+
*
|
|
209
|
+
* This is required for correct VAT calculations without intermediate rounding.
|
|
210
|
+
*/
|
|
146
211
|
get price() {
|
|
212
|
+
return this.priceWithVAT;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Difference here is that when the VAT is excempt, this is still set, while VAT will be zero.
|
|
217
|
+
*/
|
|
218
|
+
get calculatedVAT() {
|
|
219
|
+
if (!this.VATPercentage) {
|
|
220
|
+
// VAT percentage not set, so treat as 0%
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (this.VATIncluded) {
|
|
225
|
+
// Calculate VAT on price incl. VAT, which is not 100% correct and causes roudning issues
|
|
226
|
+
return this.unitPrice * this.amount - Math.round(this.unitPrice * this.amount * 100 / (100 + this.VATPercentage));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Note: the rounding is only to avoid floating point errors in software, this should not cause any actual rounding
|
|
230
|
+
// That is the reason why we store it up to 4 digits after comma
|
|
231
|
+
return Math.round(this.VATPercentage * this.unitPrice * this.amount / 100);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Note, this is not 100% accurate.
|
|
236
|
+
* Legally we most often need to calculate the VAT on invoice level and round it there.
|
|
237
|
+
* Technically we cannot pass infinite accurate numbers around in a system to avoid rounding. The returned number is
|
|
238
|
+
* therefore rounded up to 4 digits after the comma. On normal amounts, with only 2 digits after the comma, this won't lose accuracy.
|
|
239
|
+
* So the VAT calculation needs to happen at the end again before payment.
|
|
240
|
+
*/
|
|
241
|
+
get VAT() {
|
|
242
|
+
if (this.VATExcempt) {
|
|
243
|
+
// Exempt from VAT
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return this.calculatedVAT;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
get priceWithVAT() {
|
|
251
|
+
return this.priceWithoutVAT + this.VAT;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Note: when the VAT is already included, the result of this will be unreliable because of rounding issues.
|
|
256
|
+
* Do not use this in calculations!
|
|
257
|
+
*/
|
|
258
|
+
get priceWithoutVAT() {
|
|
259
|
+
if (this.VATIncluded) {
|
|
260
|
+
return this.unitPrice * this.amount - this.calculatedVAT;
|
|
261
|
+
}
|
|
262
|
+
|
|
147
263
|
return this.unitPrice * this.amount;
|
|
148
264
|
}
|
|
149
265
|
|
|
@@ -168,18 +284,26 @@ export class BalanceItem extends QueryableModel {
|
|
|
168
284
|
return this.isAfterDueDate;
|
|
169
285
|
}
|
|
170
286
|
|
|
287
|
+
/**
|
|
288
|
+
* NOTE: This contains an integer of the price up to 4 digits after the comma.
|
|
289
|
+
* 1 euro = 10000.
|
|
290
|
+
* 0,01 euro = 100
|
|
291
|
+
* 0,0001 euro = 1
|
|
292
|
+
*
|
|
293
|
+
* This is required for correct VAT calculations without intermediate rounding.
|
|
294
|
+
*/
|
|
171
295
|
get calculatedPriceOpen() {
|
|
172
296
|
if (this.status !== BalanceItemStatus.Due) {
|
|
173
297
|
return -this.pricePaid - this.pricePending;
|
|
174
298
|
}
|
|
175
|
-
return this.
|
|
299
|
+
return this.priceWithVAT - this.pricePaid - this.pricePending;
|
|
176
300
|
}
|
|
177
301
|
|
|
178
302
|
/**
|
|
179
303
|
* price minus pricePaid
|
|
180
304
|
*/
|
|
181
305
|
get priceUnpaid() {
|
|
182
|
-
return this.
|
|
306
|
+
return this.priceWithVAT - this.pricePaid;
|
|
183
307
|
}
|
|
184
308
|
|
|
185
309
|
get isPaid() {
|
|
@@ -374,7 +498,7 @@ export class BalanceItem extends QueryableModel {
|
|
|
374
498
|
SET balance_items.pricePaid = coalesce(paid.price, 0),
|
|
375
499
|
balance_items.pricePending = coalesce(pending.price, 0),
|
|
376
500
|
balance_items.priceOpen = (CASE
|
|
377
|
-
WHEN balance_items.status = '${BalanceItemStatus.Due}' THEN (balance_items.
|
|
501
|
+
WHEN balance_items.status = '${BalanceItemStatus.Due}' THEN (balance_items.priceTotal - balance_items.pricePaid - balance_items.pricePending)
|
|
378
502
|
ELSE (-balance_items.pricePaid - balance_items.pricePending)
|
|
379
503
|
END)
|
|
380
504
|
${secondWhere}`;
|
|
@@ -222,7 +222,7 @@ export class EmailVerificationCode extends QueryableModel {
|
|
|
222
222
|
});
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
if (verificationCode.code === code || (code === '111111' && STAMHOOFD.environment === 'development')) {
|
|
225
|
+
if (verificationCode.code === code || (code === '111111' && (STAMHOOFD.environment === 'development' || STAMHOOFD.environment === 'test'))) {
|
|
226
226
|
// Delete all remaining information!
|
|
227
227
|
// To avoid leaving information about the existince of this user (tries)
|
|
228
228
|
await verificationCode.delete();
|
package/src/models/Event.ts
CHANGED
|
@@ -118,7 +118,8 @@ export class Event extends QueryableModel {
|
|
|
118
118
|
if (waitingList) {
|
|
119
119
|
if (group.settings.allowRegistrationsByOrganization) {
|
|
120
120
|
waitingList.settings.allowRegistrationsByOrganization = true;
|
|
121
|
-
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
122
123
|
waitingList.settings.allowRegistrationsByOrganization = false;
|
|
123
124
|
}
|
|
124
125
|
await this.syncGroupRequirements(waitingList);
|
package/src/models/Order.ts
CHANGED
|
@@ -67,12 +67,17 @@ export class Order extends QueryableModel {
|
|
|
67
67
|
@column({ type: 'string' })
|
|
68
68
|
status = OrderStatus.Created;
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Cached value for faster in database filtering
|
|
72
|
+
*/
|
|
71
73
|
@column({ type: 'integer', nullable: true, beforeSave(this: Order) {
|
|
72
74
|
return this.data.totalPrice;
|
|
73
75
|
} })
|
|
74
76
|
totalPrice: number = 0;
|
|
75
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Cached value for faster in database filtering
|
|
80
|
+
*/
|
|
76
81
|
@column({ type: 'integer', nullable: true, beforeSave(this: Order) {
|
|
77
82
|
return this.data.amount;
|
|
78
83
|
} })
|
|
@@ -82,7 +87,6 @@ export class Order extends QueryableModel {
|
|
|
82
87
|
return this.data.timeSlot?.timeIndex ?? null;
|
|
83
88
|
} })
|
|
84
89
|
timeSlotTime: string | null = null;
|
|
85
|
-
// #endregion
|
|
86
90
|
|
|
87
91
|
static webshop = new ManyToOneRelation(Webshop, 'webshop');
|
|
88
92
|
static payment = new ManyToOneRelation(Payment, 'payment');
|
|
@@ -92,7 +92,7 @@ export class PayconiqPayment extends QueryableModel {
|
|
|
92
92
|
|
|
93
93
|
// Check and save amount
|
|
94
94
|
if (typeof response.amount === 'number' && status === PaymentStatus.Succeeded) {
|
|
95
|
-
const amount = response.amount;
|
|
95
|
+
const amount = response.amount * 100; // Convert from integer with 2 decimal places to 4 places
|
|
96
96
|
if (amount < payment.price) {
|
|
97
97
|
// We do NOT allow lower prices
|
|
98
98
|
console.error('Manual price change detected by Payconiq user. Failing the payment');
|
|
@@ -157,7 +157,7 @@ export class PayconiqPayment extends QueryableModel {
|
|
|
157
157
|
|
|
158
158
|
const response = await this.request('POST', '/v3/payments', {
|
|
159
159
|
reference: payment.id.replaceAll('-', ''), // 36 chars, max length is 35.... The actual id is also in the description
|
|
160
|
-
amount: payment.price,
|
|
160
|
+
amount: Math.round(payment.price / 100), // 4 decimals to 2 decimals
|
|
161
161
|
currency: 'EUR',
|
|
162
162
|
callbackUrl: callbackUrl ?? 'https://' + organization.getApiHost() + '/v' + Version + '/payments/' + encodeURIComponent(payment.id) + '?exchange=true',
|
|
163
163
|
returnUrl,
|
package/src/models/Payment.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { BalanceItemDetailed, BalanceItemPaymentDetailed, PaymentCustomer, Payme
|
|
|
3
3
|
import { Formatter } from '@stamhoofd/utility';
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
5
|
|
|
6
|
-
import { Organization } from './';
|
|
6
|
+
import { BalanceItem, Organization } from './';
|
|
7
7
|
import { QueryableModel } from '@stamhoofd/sql';
|
|
8
8
|
|
|
9
9
|
export class Payment extends QueryableModel {
|
|
@@ -165,6 +165,10 @@ export class Payment extends QueryableModel {
|
|
|
165
165
|
this.transferDescription = settings.generateDescription(reference, organization.address.country, replacements);
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
static roundPrice(price: number) {
|
|
169
|
+
return Math.round(price / 100) * 100;
|
|
170
|
+
}
|
|
171
|
+
|
|
168
172
|
static async getGeneralStructure(payments: Payment[], includeSettlements = false): Promise<PaymentGeneral[]> {
|
|
169
173
|
if (payments.length === 0) {
|
|
170
174
|
return [];
|
package/src/models/STInvoice.ts
CHANGED
|
@@ -81,6 +81,9 @@ export class STInvoice extends QueryableModel {
|
|
|
81
81
|
@column({ type: 'string', nullable: true })
|
|
82
82
|
reference: string | null = null;
|
|
83
83
|
|
|
84
|
+
@column({ type: 'boolean' })
|
|
85
|
+
didSendPeppol = false;
|
|
86
|
+
|
|
84
87
|
// static organization = new ManyToOneRelation(Organization, 'organization');
|
|
85
88
|
// static payment = new ManyToOneRelation(Payment, 'payment');
|
|
86
89
|
|
package/src/models/STPackage.ts
CHANGED
|
@@ -194,7 +194,7 @@ export class STPackage extends QueryableModel {
|
|
|
194
194
|
pack.meta.serviceFeeFixed = 0;
|
|
195
195
|
pack.meta.serviceFeePercentage = 2_00;
|
|
196
196
|
pack.meta.serviceFeeMinimum = 0;
|
|
197
|
-
pack.meta.serviceFeeMaximum = 20
|
|
197
|
+
pack.meta.serviceFeeMaximum = 2000; // 20 cent
|
|
198
198
|
|
|
199
199
|
pack.meta.unitPrice = 0;
|
|
200
200
|
pack.meta.pricingType = STPricingType.Fixed;
|
|
@@ -207,7 +207,7 @@ export class STPackage extends QueryableModel {
|
|
|
207
207
|
pack.meta.serviceFeeMinimum = 0;
|
|
208
208
|
pack.meta.serviceFeeMaximum = 0;
|
|
209
209
|
|
|
210
|
-
pack.meta.unitPrice =
|
|
210
|
+
pack.meta.unitPrice = 1_0000; // 1 euro
|
|
211
211
|
pack.meta.pricingType = STPricingType.PerMember;
|
|
212
212
|
}
|
|
213
213
|
|