@stamhoofd/backend 2.65.0 → 2.65.2

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.0",
3
+ "version": "2.65.2",
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.0",
41
- "@stamhoofd/backend-middleware": "2.65.0",
42
- "@stamhoofd/email": "2.65.0",
43
- "@stamhoofd/models": "2.65.0",
44
- "@stamhoofd/queues": "2.65.0",
45
- "@stamhoofd/sql": "2.65.0",
46
- "@stamhoofd/structures": "2.65.0",
47
- "@stamhoofd/utility": "2.65.0",
40
+ "@stamhoofd/backend-i18n": "2.65.2",
41
+ "@stamhoofd/backend-middleware": "2.65.2",
42
+ "@stamhoofd/email": "2.65.2",
43
+ "@stamhoofd/models": "2.65.2",
44
+ "@stamhoofd/queues": "2.65.2",
45
+ "@stamhoofd/sql": "2.65.2",
46
+ "@stamhoofd/structures": "2.65.2",
47
+ "@stamhoofd/utility": "2.65.2",
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": "16bb6a7ce183c964158cee5d1581de6e7575133d"
67
+ "gitHead": "2db24a133a1f990d69cafc26f0425f473bc42ae2"
68
68
  }
@@ -37,7 +37,6 @@ async function balanceEmails() {
37
37
  const platform = await Platform.getSharedPrivateStruct();
38
38
 
39
39
  if (!platform.config.featureFlags.includes('balance-emails')) {
40
- console.log('Feature flag not enabled, skipping.');
41
40
  return;
42
41
  }
43
42
  const systemUser = await User.getSystem();
@@ -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.priceOpen === 0;
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.priceOpen === 0;
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
- async markPaid(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
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'