@stamhoofd/models 2.62.0 → 2.63.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.
Files changed (38) hide show
  1. package/dist/src/migrations/1733909398-balance-items-due-at.sql +2 -0
  2. package/dist/src/migrations/1733909399-recalculate-at.sql +3 -0
  3. package/dist/src/migrations/1733994454-balance-item-price-open.sql +2 -0
  4. package/dist/src/migrations/1734084689-payment-type.sql +2 -0
  5. package/dist/src/migrations/1734084690-move-payment-column-order.sql +2 -0
  6. package/dist/src/migrations/1734084691-fill-payment-type-refunds.sql +1 -0
  7. package/dist/src/migrations/1734084692-fill-payment-type-reallocations.sql +15 -0
  8. package/dist/src/models/BalanceItem.d.ts +17 -4
  9. package/dist/src/models/BalanceItem.d.ts.map +1 -1
  10. package/dist/src/models/BalanceItem.js +59 -59
  11. package/dist/src/models/BalanceItem.js.map +1 -1
  12. package/dist/src/models/CachedBalance.d.ts +7 -0
  13. package/dist/src/models/CachedBalance.d.ts.map +1 -1
  14. package/dist/src/models/CachedBalance.js +70 -7
  15. package/dist/src/models/CachedBalance.js.map +1 -1
  16. package/dist/src/models/DocumentTemplate.js +1 -1
  17. package/dist/src/models/DocumentTemplate.js.map +1 -1
  18. package/dist/src/models/Order.d.ts +1 -0
  19. package/dist/src/models/Order.d.ts.map +1 -1
  20. package/dist/src/models/Order.js +6 -0
  21. package/dist/src/models/Order.js.map +1 -1
  22. package/dist/src/models/Payment.d.ts +11 -1
  23. package/dist/src/models/Payment.d.ts.map +1 -1
  24. package/dist/src/models/Payment.js +13 -0
  25. package/dist/src/models/Payment.js.map +1 -1
  26. package/package.json +2 -2
  27. package/src/migrations/1733909398-balance-items-due-at.sql +2 -0
  28. package/src/migrations/1733909399-recalculate-at.sql +3 -0
  29. package/src/migrations/1733994454-balance-item-price-open.sql +2 -0
  30. package/src/migrations/1734084689-payment-type.sql +2 -0
  31. package/src/migrations/1734084690-move-payment-column-order.sql +2 -0
  32. package/src/migrations/1734084691-fill-payment-type-refunds.sql +1 -0
  33. package/src/migrations/1734084692-fill-payment-type-reallocations.sql +15 -0
  34. package/src/models/BalanceItem.ts +61 -70
  35. package/src/models/CachedBalance.ts +105 -30
  36. package/src/models/DocumentTemplate.ts +1 -1
  37. package/src/models/Order.ts +7 -0
  38. package/src/models/Payment.ts +13 -1
@@ -1,5 +1,5 @@
1
1
  import { column, Database, Model, SQLResultNamespacedRow } from '@simonbackx/simple-database';
2
- import { BalanceItemPaymentWithPayment, BalanceItemPaymentWithPrivatePayment, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, BalanceItemWithPayments, BalanceItemWithPrivatePayments, OrderStatus, Payment as PaymentStruct, PrivatePayment } from '@stamhoofd/structures';
2
+ import { BalanceItem as BalanceItemStruct, BalanceItemPaymentWithPayment, BalanceItemPaymentWithPrivatePayment, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, BalanceItemWithPayments, BalanceItemWithPrivatePayments, OrderStatus, Payment as PaymentStruct, PrivatePayment } from '@stamhoofd/structures';
3
3
  import { Formatter } from '@stamhoofd/utility';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
 
@@ -90,8 +90,31 @@ export class BalanceItem extends Model {
90
90
  @column({ type: 'integer' })
91
91
  pricePending = 0;
92
92
 
93
+ /**
94
+ * Cached value, for optimizations
95
+ */
96
+ @column({
97
+ type: 'integer',
98
+ beforeSave: function () {
99
+ return this.calculatedPriceOpen;
100
+ },
101
+ skipUpdate: true,
102
+ })
103
+ priceOpen = 0;
104
+
105
+ /**
106
+ * todo: deprecate ('pending' and 'paid') + 'hidden' status and replace with 'due' + 'hidden'
107
+ * -> maybe add 'due' (due if dueAt is null or <= now), 'hidden' (never due), 'future' (= not due until dueAt - but not possible to pay earlier)
108
+ */
93
109
  @column({ type: 'string' })
94
- status = BalanceItemStatus.Pending;
110
+ status = BalanceItemStatus.Due;
111
+
112
+ /**
113
+ * In case the balance item doesn't have to be paid immediately, we can set a due date.
114
+ * When the due date is reached, it is set to null and the cached balance is updated.
115
+ */
116
+ @column({ type: 'datetime', nullable: true })
117
+ dueAt: Date | null = null;
95
118
 
96
119
  @column({
97
120
  type: 'datetime', beforeSave(old?: any) {
@@ -119,14 +142,13 @@ export class BalanceItem extends Model {
119
142
  return this.unitPrice * this.amount;
120
143
  }
121
144
 
122
- get priceOpen() {
145
+ get calculatedPriceOpen() {
146
+ if (this.status !== BalanceItemStatus.Due) {
147
+ return -this.pricePaid - this.pricePending;
148
+ }
123
149
  return this.price - this.pricePaid - this.pricePending;
124
150
  }
125
151
 
126
- updateStatus() {
127
- this.status = (this.pricePaid !== 0 || this.price === 0) && this.pricePaid >= this.price ? BalanceItemStatus.Paid : (this.pricePaid !== 0 ? BalanceItemStatus.Pending : (this.status === BalanceItemStatus.Hidden ? BalanceItemStatus.Hidden : BalanceItemStatus.Pending));
128
- }
129
-
130
152
  static async deleteItems(items: BalanceItem[]) {
131
153
  // Find depending items
132
154
  const dependingItemIds = Formatter.uniqueArray(items.filter(i => !!i.dependingBalanceItemId).map(i => i.dependingBalanceItemId!)).filter(id => !items.some(item => item.id === id));
@@ -137,30 +159,19 @@ export class BalanceItem extends Model {
137
159
  items = [...items, ...dependingItems];
138
160
  }
139
161
 
140
- const { balanceItemPayments } = await BalanceItem.loadPayments(items);
141
-
142
162
  // todo: in the future we could automatically delete payments that are not needed anymore and weren't paid yet -> to prevent leaving ghost payments
143
163
  // for now, an admin can manually cancel those payments
144
164
  let needsUpdate = false;
145
165
 
146
166
  // Set other items to zero (the balance item payments keep the real price)
147
167
  for (const item of items) {
148
- needsUpdate = true;
149
-
150
- // Don't change status of items that are already paid or are partially paid
151
- // Not using item.paidPrice, since this is cached
152
- const bip = balanceItemPayments.filter(p => p.balanceItemId === item.id);
153
-
154
- if (bip.length === 0) {
155
- // No payments associated with this item
156
- item.status = BalanceItemStatus.Hidden;
157
- item.amount = 0;
158
- await item.save();
159
- }
160
- else {
161
- item.amount = 0;
162
- await item.save();
168
+ if (item.status !== BalanceItemStatus.Due) {
169
+ continue;
163
170
  }
171
+
172
+ needsUpdate = true;
173
+ item.status = BalanceItemStatus.Canceled;
174
+ await item.save();
164
175
  }
165
176
 
166
177
  if (needsUpdate) {
@@ -172,7 +183,7 @@ export class BalanceItem extends Model {
172
183
  let needsUpdate = false;
173
184
  for (const item of items) {
174
185
  if (item.status === BalanceItemStatus.Hidden) {
175
- item.status = BalanceItemStatus.Pending;
186
+ item.status = BalanceItemStatus.Due;
176
187
  needsUpdate = true;
177
188
  await item.save();
178
189
  }
@@ -225,7 +236,6 @@ export class BalanceItem extends Model {
225
236
  console.log('Update outstanding balance for', items.length, 'items');
226
237
 
227
238
  await BalanceItem.updatePricePaid(items.map(i => i.id));
228
- await BalanceItem.updatePricePending(items.map(i => i.id));
229
239
 
230
240
  // Deprecated: the member balances have moved to CachedBalance
231
241
  // Update outstanding amount of related members and registrations
@@ -264,7 +274,7 @@ export class BalanceItem extends Model {
264
274
  * Update the outstanding balance of multiple members in one go (or all members)
265
275
  */
266
276
  static async updatePricePaid(balanceItemIds: string[] | 'all') {
267
- if (balanceItemIds !== 'all' && balanceItemIds.length == 0) {
277
+ if (balanceItemIds !== 'all' && balanceItemIds.length === 0) {
268
278
  return;
269
279
  }
270
280
 
@@ -275,13 +285,13 @@ export class BalanceItem extends Model {
275
285
  if (balanceItemIds !== 'all') {
276
286
  firstWhere = ` AND balanceItemId IN (?)`;
277
287
  params.push(balanceItemIds);
288
+ params.push(balanceItemIds);
278
289
 
279
290
  secondWhere = `WHERE balance_items.id IN (?)`;
280
291
  params.push(balanceItemIds);
281
292
  }
282
293
 
283
- // Note: we'll never mark a balance item as 'paid' via the database -> because we need to rigger the markPaid function manually, which will call the appropriate functions
284
- // this method will set the balance item to paid once, and only call the handlers once
294
+ // Note: this query only works in MySQL because of the SET assignment behaviour allowing to reference newly set values
285
295
  const query = `
286
296
  UPDATE
287
297
  balance_items
@@ -296,41 +306,7 @@ export class BalanceItem extends Model {
296
306
  payments.status = 'Succeeded'${firstWhere}
297
307
  GROUP BY
298
308
  balanceItemId
299
- ) i ON i.balanceItemId = balance_items.id
300
- SET balance_items.pricePaid = coalesce(i.price, 0), balance_items.status = (CASE
301
- WHEN balance_items.status = '${BalanceItemStatus.Paid}' AND balance_items.pricePaid != 0 AND balance_items.pricePaid >= (balance_items.unitPrice * balance_items.amount) THEN '${BalanceItemStatus.Paid}'
302
- WHEN balance_items.pricePaid != 0 THEN '${BalanceItemStatus.Pending}'
303
- WHEN balance_items.status = '${BalanceItemStatus.Hidden}' THEN '${BalanceItemStatus.Hidden}'
304
- ELSE '${BalanceItemStatus.Pending}'
305
- END)
306
- ${secondWhere}`;
307
-
308
- await Database.update(query, params);
309
- }
310
-
311
- /**
312
- * Call this after updating the pricePaid
313
- */
314
- static async updatePricePending(balanceItemIds: string[] | 'all') {
315
- if (balanceItemIds !== 'all' && balanceItemIds.length == 0) {
316
- return;
317
- }
318
-
319
- const params: any[] = [];
320
- let firstWhere = '';
321
- let secondWhere = '';
322
-
323
- if (balanceItemIds !== 'all') {
324
- firstWhere = ` AND balanceItemId IN (?)`;
325
- params.push(balanceItemIds);
326
-
327
- secondWhere = `WHERE balance_items.id IN (?)`;
328
- params.push(balanceItemIds);
329
- }
330
-
331
- const query = `
332
- UPDATE
333
- balance_items
309
+ ) paid ON paid.balanceItemId = balance_items.id
334
310
  LEFT JOIN (
335
311
  SELECT
336
312
  balanceItemId,
@@ -342,16 +318,25 @@ export class BalanceItem extends Model {
342
318
  payments.status != 'Succeeded' AND payments.status != 'Failed'${firstWhere}
343
319
  GROUP BY
344
320
  balanceItemId
345
- ) i ON i.balanceItemId = balance_items.id
346
- SET balance_items.pricePending = LEAST(
347
- GREATEST(0, balance_items.unitPrice * balance_items.amount - balance_items.pricePaid),
348
- coalesce(i.price, 0)
349
- )
321
+ ) pending ON pending.balanceItemId = balance_items.id
322
+ SET balance_items.pricePaid = coalesce(paid.price, 0),
323
+ balance_items.pricePending = coalesce(pending.price, 0),
324
+ balance_items.priceOpen = (CASE
325
+ WHEN balance_items.status = '${BalanceItemStatus.Due}' THEN (balance_items.unitPrice * balance_items.amount - balance_items.pricePaid - balance_items.pricePending)
326
+ ELSE (-balance_items.pricePaid - balance_items.pricePending)
327
+ END)
350
328
  ${secondWhere}`;
351
329
 
352
330
  await Database.update(query, params);
353
331
  }
354
332
 
333
+ /**
334
+ * @deprecated
335
+ */
336
+ static async updatePricePending(balanceItemIds: string[] | 'all') {
337
+ // deprecated
338
+ }
339
+
355
340
  static async loadPayments(items: BalanceItem[]) {
356
341
  if (items.length == 0) {
357
342
  return { balanceItemPayments: [], payments: [] };
@@ -366,8 +351,14 @@ export class BalanceItem extends Model {
366
351
  return { payments, balanceItemPayments };
367
352
  }
368
353
 
354
+ getStructure() {
355
+ return BalanceItemStruct.create({
356
+ ...this,
357
+ });
358
+ }
359
+
369
360
  static async getStructureWithPayments(items: BalanceItem[]): Promise<BalanceItemWithPayments[]> {
370
- if (items.length == 0) {
361
+ if (items.length === 0) {
371
362
  return [];
372
363
  }
373
364
 
@@ -1,6 +1,6 @@
1
1
  import { column, Model, SQLResultNamespacedRow } from '@simonbackx/simple-database';
2
- import { SQL, SQLAlias, SQLCalculation, SQLMinusSign, SQLMultiplicationSign, SQLSelect, SQLSelectAs, SQLSum, SQLWhere, SQLWhereSign } from '@stamhoofd/sql';
3
- import { BalanceItemStatus, ReceivableBalanceType } from '@stamhoofd/structures';
2
+ import { SQL, SQLAlias, SQLCalculation, SQLGreatest, SQLMin, SQLMinusSign, SQLMultiplicationSign, SQLSelect, SQLSelectAs, SQLSum, SQLWhere, SQLWhereSign } from '@stamhoofd/sql';
3
+ import { BalanceItem as BalanceItemStruct, BalanceItemStatus, ReceivableBalanceType } from '@stamhoofd/structures';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
  import { BalanceItem } from './BalanceItem';
6
6
 
@@ -35,9 +35,18 @@ export class CachedBalance extends Model {
35
35
  @column({ type: 'integer' })
36
36
  amount = 0;
37
37
 
38
+ /**
39
+ * The sum of unconfirmed payments
40
+ */
38
41
  @column({ type: 'integer' })
39
42
  amountPending = 0;
40
43
 
44
+ /**
45
+ * This is the minimum `dueAt` that lies in the future of all **unpaid** balance items connected to this object.
46
+ */
47
+ @column({ type: 'datetime', nullable: true })
48
+ nextDueAt: Date | null = null;
49
+
41
50
  @column({
42
51
  type: 'datetime', beforeSave(old?: any) {
43
52
  if (old !== undefined) {
@@ -102,17 +111,7 @@ export class CachedBalance extends Model {
102
111
  .where(columnName, objectIds)
103
112
  .whereNot('status', BalanceItemStatus.Hidden)
104
113
  .where(
105
- SQL.where(
106
- new SQLCalculation(
107
- new SQLCalculation(
108
- SQL.column('unitPrice'),
109
- new SQLMultiplicationSign(),
110
- SQL.column('amount'),
111
- ),
112
- new SQLMinusSign(),
113
- SQL.column('pricePaid'),
114
- )
115
- , SQLWhereSign.NotEqual, 0)
114
+ SQL.where(SQL.column('priceOpen'), SQLWhereSign.NotEqual, 0)
116
115
  .or('pricePending', SQLWhereSign.NotEqual, 0),
117
116
  );
118
117
 
@@ -124,21 +123,12 @@ export class CachedBalance extends Model {
124
123
  }
125
124
 
126
125
  private static async fetchForObjects(organizationId: string, objectIds: string[], columnName: string, customWhere?: SQLWhere) {
126
+ const dueOffset = BalanceItemStruct.getDueOffset();
127
127
  const query = SQL.select(
128
128
  SQL.column(columnName),
129
129
  new SQLSelectAs(
130
- new SQLCalculation(
131
- new SQLSum(
132
- new SQLCalculation(
133
- SQL.column('unitPrice'),
134
- new SQLMultiplicationSign(),
135
- SQL.column('amount'),
136
- ),
137
- ),
138
- new SQLMinusSign(),
139
- new SQLSum(
140
- SQL.column('pricePaid'),
141
- ),
130
+ new SQLSum(
131
+ SQL.column('priceOpen'),
142
132
  ),
143
133
  new SQLAlias('data__amount'),
144
134
  ),
@@ -153,6 +143,7 @@ export class CachedBalance extends Model {
153
143
  .where('organizationId', organizationId)
154
144
  .where(columnName, objectIds)
155
145
  .whereNot('status', BalanceItemStatus.Hidden)
146
+ .where(SQL.where('dueAt', null).or('dueAt', SQLWhereSign.LessEqual, dueOffset))
156
147
  .groupBy(SQL.column(columnName));
157
148
 
158
149
  if (customWhere) {
@@ -161,7 +152,40 @@ export class CachedBalance extends Model {
161
152
 
162
153
  const result = await query.fetch();
163
154
 
164
- const results: [string, { amount: number; amountPending: number }][] = [];
155
+ // Calculate future due
156
+ const dueQuery = SQL.select(
157
+ SQL.column(columnName),
158
+ new SQLSelectAs(
159
+ new SQLMin(
160
+ SQL.column('dueAt'),
161
+ ),
162
+ new SQLAlias('data__dueAt'),
163
+ ),
164
+ // If the current amount_due is negative, we can ignore that negative part if there is a future due item
165
+ new SQLSelectAs(
166
+ new SQLSum(
167
+ SQL.column('priceOpen'),
168
+ ),
169
+ new SQLAlias('data__amount'),
170
+ ),
171
+ new SQLSelectAs(
172
+ new SQLSum(
173
+ SQL.column('pricePending'),
174
+ ),
175
+ new SQLAlias('data__amountPending'),
176
+ ),
177
+ )
178
+ .from(BalanceItem.table)
179
+ .where('organizationId', organizationId)
180
+ .where(columnName, objectIds)
181
+ .where('status', BalanceItemStatus.Due)
182
+ .whereNot('dueAt', null)
183
+ .where('dueAt', SQLWhereSign.Greater, dueOffset)
184
+ .groupBy(SQL.column(columnName));
185
+
186
+ const dueResult = await dueQuery.fetch();
187
+
188
+ const results: [string, { amount: number; amountPending: number; nextDueAt: Date | null }][] = [];
165
189
  for (const row of result) {
166
190
  if (!row['data']) {
167
191
  throw new Error('Invalid data namespace');
@@ -187,20 +211,68 @@ export class CachedBalance extends Model {
187
211
  throw new Error('Invalid amountPending');
188
212
  }
189
213
 
190
- results.push([objectId, { amount, amountPending }]);
214
+ results.push([objectId, { amount, amountPending, nextDueAt: null }]);
215
+ }
216
+
217
+ for (const row of dueResult) {
218
+ if (!row['data']) {
219
+ throw new Error('Invalid data namespace');
220
+ }
221
+
222
+ if (!row[BalanceItem.table]) {
223
+ throw new Error('Invalid ' + BalanceItem.table + ' namespace');
224
+ }
225
+
226
+ const objectId = row[BalanceItem.table][columnName];
227
+ const dueAt = row['data']['dueAt'];
228
+ const amount = row['data']['amount'];
229
+ const amountPending = row['data']['amountPending'];
230
+
231
+ if (typeof objectId !== 'string') {
232
+ throw new Error('Invalid objectId');
233
+ }
234
+
235
+ if (!(dueAt instanceof Date)) {
236
+ throw new Error('Invalid dueAt');
237
+ }
238
+
239
+ if (typeof amount !== 'number') {
240
+ throw new Error('Invalid amount');
241
+ }
242
+
243
+ if (typeof amountPending !== 'number') {
244
+ throw new Error('Invalid amountPending');
245
+ }
246
+
247
+ const result = results.find(r => r[0] === objectId);
248
+ if (result) {
249
+ result[1].nextDueAt = dueAt;
250
+
251
+ if (result[1].amount < 0) {
252
+ if (amount > 0) {
253
+ // Let the future due amount fill in the gap until maximum 0
254
+ result[1].amount = Math.min(0, result[1].amount + amount);
255
+ }
256
+ }
257
+
258
+ result[1].amountPending += amountPending;
259
+ }
260
+ else {
261
+ results.push([objectId, { amount: 0, amountPending: amountPending, nextDueAt: dueAt }]);
262
+ }
191
263
  }
192
264
 
193
265
  // Add missing object ids (with 0 amount, otherwise we don't reset the amounts back to zero when all the balance items are hidden)
194
266
  for (const objectId of objectIds) {
195
267
  if (!results.find(([id]) => id === objectId)) {
196
- results.push([objectId, { amount: 0, amountPending: 0 }]);
268
+ results.push([objectId, { amount: 0, amountPending: 0, nextDueAt: null }]);
197
269
  }
198
270
  }
199
271
 
200
272
  return results;
201
273
  }
202
274
 
203
- private static async setForResults(organizationId: string, result: [string, { amount: number; amountPending: number }][], objectType: ReceivableBalanceType) {
275
+ private static async setForResults(organizationId: string, result: [string, { amount: number; amountPending: number; nextDueAt: null | Date }][], objectType: ReceivableBalanceType) {
204
276
  if (result.length === 0) {
205
277
  return;
206
278
  }
@@ -212,10 +284,11 @@ export class CachedBalance extends Model {
212
284
  'objectType',
213
285
  'amount',
214
286
  'amountPending',
287
+ 'nextDueAt',
215
288
  'createdAt',
216
289
  'updatedAt',
217
290
  )
218
- .values(...result.map(([objectId, { amount, amountPending }]) => {
291
+ .values(...result.map(([objectId, { amount, amountPending, nextDueAt }]) => {
219
292
  return [
220
293
  uuidv4(),
221
294
  organizationId,
@@ -223,6 +296,7 @@ export class CachedBalance extends Model {
223
296
  objectType,
224
297
  amount,
225
298
  amountPending,
299
+ nextDueAt,
226
300
  new Date(),
227
301
  new Date(),
228
302
  ];
@@ -231,6 +305,7 @@ export class CachedBalance extends Model {
231
305
  .onDuplicateKeyUpdate(
232
306
  SQL.assignment('amount', SQL.column('v', 'amount')),
233
307
  SQL.assignment('amountPending', SQL.column('v', 'amountPending')),
308
+ SQL.assignment('nextDueAt', SQL.column('v', 'nextDueAt')),
234
309
  SQL.assignment('updatedAt', SQL.column('v', 'updatedAt')),
235
310
  );
236
311
 
@@ -208,7 +208,7 @@ export class DocumentTemplate extends Model {
208
208
  let debtor: Parent | undefined = parentsWithNRN[0] ?? registration.member.details.parents[0];
209
209
  if (parentsWithNRN.length > 1) {
210
210
  for (const balanceItem of balanceItems) {
211
- if (balanceItem && balanceItem.userId && balanceItem.status === BalanceItemStatus.Paid) {
211
+ if (balanceItem && balanceItem.userId && balanceItem.priceOpen === 0 && balanceItem.status === BalanceItemStatus.Due) {
212
212
  const user = await User.getByID(balanceItem.userId);
213
213
  if (user) {
214
214
  const parent = parentsWithNRN.find(p => p.hasEmail(user.email));
@@ -157,6 +157,13 @@ export class Order extends Model {
157
157
  return false;
158
158
  }
159
159
 
160
+ get isDue() {
161
+ if (this.status === OrderStatus.Canceled || this.status === OrderStatus.Deleted) {
162
+ return false;
163
+ }
164
+ return true;
165
+ }
166
+
160
167
  get totalToPay() {
161
168
  if (this.status === OrderStatus.Canceled || this.status === OrderStatus.Deleted) {
162
169
  return 0;
@@ -1,5 +1,5 @@
1
1
  import { column, Model, SQLResultNamespacedRow } from '@simonbackx/simple-database';
2
- import { BalanceItemDetailed, BalanceItemPaymentDetailed, PaymentCustomer, PaymentGeneral, PaymentMethod, PaymentProvider, PaymentStatus, Settlement, TransferSettings, BaseOrganization } from '@stamhoofd/structures';
2
+ import { BalanceItemDetailed, BalanceItemPaymentDetailed, PaymentCustomer, PaymentGeneral, PaymentMethod, PaymentProvider, PaymentStatus, Settlement, TransferSettings, BaseOrganization, PaymentType } from '@stamhoofd/structures';
3
3
  import { Formatter } from '@stamhoofd/utility';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
 
@@ -16,6 +16,18 @@ export class Payment extends Model {
16
16
  })
17
17
  id!: string;
18
18
 
19
+ /**
20
+ * Types of payment:
21
+ * - Payment (default) = positive amount or zero
22
+ * - Refund = negative amount
23
+ * - Rebooking = zero payment due to rebooking
24
+ */
25
+ @column({ type: 'string' })
26
+ type = PaymentType.Payment;
27
+
28
+ /**
29
+ * How the payment is paid out or refunded
30
+ */
19
31
  @column({ type: 'string' })
20
32
  method: PaymentMethod;
21
33