@stamhoofd/backend 2.65.0 → 2.65.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.65.
|
|
3
|
+
"version": "2.65.1",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
"@simonbackx/simple-encoding": "2.19.0",
|
|
38
38
|
"@simonbackx/simple-endpoints": "1.15.0",
|
|
39
39
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
40
|
-
"@stamhoofd/backend-i18n": "2.65.
|
|
41
|
-
"@stamhoofd/backend-middleware": "2.65.
|
|
42
|
-
"@stamhoofd/email": "2.65.
|
|
43
|
-
"@stamhoofd/models": "2.65.
|
|
44
|
-
"@stamhoofd/queues": "2.65.
|
|
45
|
-
"@stamhoofd/sql": "2.65.
|
|
46
|
-
"@stamhoofd/structures": "2.65.
|
|
47
|
-
"@stamhoofd/utility": "2.65.
|
|
40
|
+
"@stamhoofd/backend-i18n": "2.65.1",
|
|
41
|
+
"@stamhoofd/backend-middleware": "2.65.1",
|
|
42
|
+
"@stamhoofd/email": "2.65.1",
|
|
43
|
+
"@stamhoofd/models": "2.65.1",
|
|
44
|
+
"@stamhoofd/queues": "2.65.1",
|
|
45
|
+
"@stamhoofd/sql": "2.65.1",
|
|
46
|
+
"@stamhoofd/structures": "2.65.1",
|
|
47
|
+
"@stamhoofd/utility": "2.65.1",
|
|
48
48
|
"archiver": "^7.0.1",
|
|
49
49
|
"aws-sdk": "^2.885.0",
|
|
50
50
|
"axios": "1.6.8",
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"publishConfig": {
|
|
65
65
|
"access": "public"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "7817159827eba1f565f35785d1700333ab613aef"
|
|
68
68
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
+
import { logger } from '@simonbackx/simple-logging';
|
|
3
|
+
import { BalanceItem, BalanceItemPayment, Organization, Payment } from '@stamhoofd/models';
|
|
4
|
+
import { QueueHandler } from '@stamhoofd/queues';
|
|
5
|
+
import { AuditLogSource, PaymentStatus } from '@stamhoofd/structures';
|
|
6
|
+
import { AuditLogService } from '../services/AuditLogService';
|
|
7
|
+
import { BalanceItemPaymentService } from '../services/BalanceItemPaymentService';
|
|
8
|
+
|
|
9
|
+
export default new Migration(async () => {
|
|
10
|
+
if (STAMHOOFD.environment == 'test') {
|
|
11
|
+
console.log('skipped in tests');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
process.stdout.write('\n');
|
|
16
|
+
let c = 0;
|
|
17
|
+
|
|
18
|
+
await logger.setContext({ tags: ['silent-seed', 'seed'] }, async () => {
|
|
19
|
+
const q = Payment.select()
|
|
20
|
+
.where('status', PaymentStatus.Succeeded)
|
|
21
|
+
.where('createdAt', '>=', new Date('2024-12-12'))
|
|
22
|
+
.limit(100);
|
|
23
|
+
for await (const payment of q.all()) {
|
|
24
|
+
await fix(payment);
|
|
25
|
+
|
|
26
|
+
c += 1;
|
|
27
|
+
|
|
28
|
+
if (c % 1000 === 0) {
|
|
29
|
+
process.stdout.write('.');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log('Updated ' + c + ' payments');
|
|
35
|
+
|
|
36
|
+
// Do something here
|
|
37
|
+
return Promise.resolve();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
async function fix(payment: Payment) {
|
|
41
|
+
if (payment.status !== PaymentStatus.Succeeded) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!payment.organizationId) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const organization = await Organization.getByID(payment.organizationId);
|
|
50
|
+
|
|
51
|
+
if (!organization) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await AuditLogService.setContext({ fallbackUserId: payment.payingUserId, source: AuditLogSource.Payment, fallbackOrganizationId: payment.organizationId }, async () => {
|
|
56
|
+
// Prevent concurrency issues
|
|
57
|
+
await QueueHandler.schedule('balance-item-update/' + organization.id, async () => {
|
|
58
|
+
const unloaded = (await BalanceItemPayment.where({ paymentId: payment.id })).map(r => r.setRelation(BalanceItemPayment.payment, payment));
|
|
59
|
+
const balanceItemPayments = await BalanceItemPayment.balanceItem.load(
|
|
60
|
+
unloaded,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
for (const balanceItemPayment of balanceItemPayments) {
|
|
64
|
+
await BalanceItemPaymentService.markPaidRepeated(balanceItemPayment, organization);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await BalanceItem.updateOutstanding(balanceItemPayments.map(p => p.balanceItem));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -7,7 +7,7 @@ type Loaded<T> = (T) extends ManyToOneRelation<infer Key, infer Model> ? Record<
|
|
|
7
7
|
|
|
8
8
|
export const BalanceItemPaymentService = {
|
|
9
9
|
async markPaid(balanceItemPayment: BalanceItemPayment & Loaded<typeof BalanceItemPayment.balanceItem> & Loaded<typeof BalanceItemPayment.payment>, organization: Organization) {
|
|
10
|
-
const wasPaid = balanceItemPayment.balanceItem.
|
|
10
|
+
const wasPaid = balanceItemPayment.balanceItem.isPaid;
|
|
11
11
|
|
|
12
12
|
// Update cached amountPaid of the balance item (balanceItemPayment will get overwritten later, but we need it to calculate the status)
|
|
13
13
|
balanceItemPayment.balanceItem.pricePaid += balanceItemPayment.price;
|
|
@@ -17,7 +17,7 @@ export const BalanceItemPaymentService = {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
await balanceItemPayment.balanceItem.save();
|
|
20
|
-
const isPaid = balanceItemPayment.balanceItem.
|
|
20
|
+
const isPaid = balanceItemPayment.balanceItem.isPaid;
|
|
21
21
|
|
|
22
22
|
// Do logic of balance item
|
|
23
23
|
if (isPaid && !wasPaid && balanceItemPayment.price >= 0 && balanceItemPayment.balanceItem.status === BalanceItemStatus.Due) {
|
|
@@ -29,6 +29,18 @@ export const BalanceItemPaymentService = {
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Safe method to correct balance items that missed a markPaid call, but avoid double marking an order as valid.
|
|
34
|
+
*/
|
|
35
|
+
async markPaidRepeated(balanceItemPayment: BalanceItemPayment & Loaded<typeof BalanceItemPayment.balanceItem> & Loaded<typeof BalanceItemPayment.payment>, organization: Organization) {
|
|
36
|
+
const isPaid = balanceItemPayment.balanceItem.isPaid;
|
|
37
|
+
|
|
38
|
+
// Do logic of balance item
|
|
39
|
+
if (isPaid && balanceItemPayment.price >= 0 && balanceItemPayment.balanceItem.status === BalanceItemStatus.Due) {
|
|
40
|
+
await BalanceItemService.markPaidRepeated(balanceItemPayment.balanceItem, balanceItemPayment.payment, organization);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
32
44
|
/**
|
|
33
45
|
* Call balanceItemPayment once a earlier succeeded payment is no longer succeeded
|
|
34
46
|
*/
|
|
@@ -6,7 +6,43 @@ import { PaymentReallocationService } from './PaymentReallocationService';
|
|
|
6
6
|
import { Formatter } from '@stamhoofd/utility';
|
|
7
7
|
|
|
8
8
|
export const BalanceItemService = {
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Safe method to correct balance items that missed a markPaid call, but avoid double marking an order as valid.
|
|
11
|
+
*/
|
|
12
|
+
async markPaidRepeated(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
|
|
13
|
+
if (balanceItem.pricePaid <= 0) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
await this.markDue(balanceItem);
|
|
18
|
+
|
|
19
|
+
// Registrations are safe to mark valid multiple times
|
|
20
|
+
if (balanceItem.registrationId) {
|
|
21
|
+
await RegistrationService.markValid(balanceItem.registrationId);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Orders aren't safe to mark paid twice - so only mark paid if not yet valid
|
|
25
|
+
// The only downside of this is that we won't send a paid email for transfer orders
|
|
26
|
+
// we should fix that in the future by introducing a paidAt timestamp for orders
|
|
27
|
+
if (balanceItem.orderId) {
|
|
28
|
+
const order = await Order.getByID(balanceItem.orderId);
|
|
29
|
+
if (order && !order.validAt) {
|
|
30
|
+
await order.markPaid(payment, organization);
|
|
31
|
+
|
|
32
|
+
// Save number in balance description
|
|
33
|
+
if (order.number !== null) {
|
|
34
|
+
const webshop = await Webshop.getByID(order.webshopId);
|
|
35
|
+
|
|
36
|
+
if (webshop) {
|
|
37
|
+
balanceItem.description = order.generateBalanceDescription(webshop);
|
|
38
|
+
await balanceItem.save();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async markDue(balanceItem: BalanceItem) {
|
|
10
46
|
if (balanceItem.status === BalanceItemStatus.Hidden) {
|
|
11
47
|
await BalanceItem.reactivateItems([balanceItem]);
|
|
12
48
|
}
|
|
@@ -18,6 +54,10 @@ export const BalanceItemService = {
|
|
|
18
54
|
await BalanceItem.reactivateItems([depending]);
|
|
19
55
|
}
|
|
20
56
|
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
async markPaid(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
|
|
60
|
+
await this.markDue(balanceItem);
|
|
21
61
|
|
|
22
62
|
// It is possible this balance item was earlier paid
|
|
23
63
|
// and later the regigstration / order has been canceled and it became a negative balance item - which as some point has been reembursed and marked as 'paid'
|