@stamhoofd/models 2.55.2 → 2.56.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/helpers/Handlebars.d.ts +1 -1
- package/dist/src/helpers/Handlebars.d.ts.map +1 -1
- package/dist/src/helpers/Handlebars.js +14 -1
- package/dist/src/helpers/Handlebars.js.map +1 -1
- package/dist/src/migrations/1732285080-audit-logs.sql +18 -0
- package/dist/src/models/AuditLog.d.ts +24 -0
- package/dist/src/models/AuditLog.d.ts.map +1 -0
- package/dist/src/models/AuditLog.js +72 -0
- package/dist/src/models/AuditLog.js.map +1 -0
- package/dist/src/models/BalanceItem.d.ts +1 -6
- package/dist/src/models/BalanceItem.d.ts.map +1 -1
- package/dist/src/models/BalanceItem.js +0 -91
- package/dist/src/models/BalanceItem.js.map +1 -1
- package/dist/src/models/BalanceItemPayment.d.ts +1 -10
- package/dist/src/models/BalanceItemPayment.d.ts.map +1 -1
- package/dist/src/models/BalanceItemPayment.js +0 -31
- package/dist/src/models/BalanceItemPayment.js.map +1 -1
- package/dist/src/models/Document.d.ts +1 -7
- package/dist/src/models/Document.d.ts.map +1 -1
- package/dist/src/models/Document.js +19 -8
- package/dist/src/models/Document.js.map +1 -1
- package/dist/src/models/DocumentTemplate.d.ts.map +1 -1
- package/dist/src/models/DocumentTemplate.js +87 -17
- package/dist/src/models/DocumentTemplate.js.map +1 -1
- package/dist/src/models/Member.js +1 -1
- package/dist/src/models/Member.js.map +1 -1
- package/dist/src/models/Registration.d.ts +0 -4
- package/dist/src/models/Registration.d.ts.map +1 -1
- package/dist/src/models/Registration.js +0 -37
- package/dist/src/models/Registration.js.map +1 -1
- package/dist/src/models/index.d.ts +1 -0
- package/dist/src/models/index.d.ts.map +1 -1
- package/dist/src/models/index.js +1 -0
- package/dist/src/models/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/helpers/Handlebars.ts +16 -1
- package/src/migrations/1732285080-audit-logs.sql +18 -0
- package/src/models/AuditLog.ts +58 -0
- package/src/models/BalanceItem.ts +0 -105
- package/src/models/BalanceItemPayment.ts +1 -40
- package/src/models/Document.ts +23 -10
- package/src/models/DocumentTemplate.ts +96 -19
- package/src/models/Member.ts +1 -1
- package/src/models/Registration.ts +0 -46
- package/src/models/index.ts +1 -0
|
@@ -3,6 +3,7 @@ import { Image } from '@stamhoofd/structures';
|
|
|
3
3
|
import { Formatter } from '@stamhoofd/utility';
|
|
4
4
|
import Handlebars from 'handlebars';
|
|
5
5
|
import { Interval } from 'luxon';
|
|
6
|
+
import bwipjs from '@bwip-js/node';
|
|
6
7
|
|
|
7
8
|
Handlebars.registerHelper('eq', (a, b) => a == b);
|
|
8
9
|
Handlebars.registerHelper('neq', (a, b) => a !== b);
|
|
@@ -192,8 +193,22 @@ Handlebars.registerHelper('src-height', (a, options) => {
|
|
|
192
193
|
}
|
|
193
194
|
});
|
|
194
195
|
|
|
196
|
+
Handlebars.registerHelper('datamatrix', (a) => {
|
|
197
|
+
if (typeof a !== 'string') {
|
|
198
|
+
return '';
|
|
199
|
+
}
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
201
|
+
const svgCode = bwipjs.toSVG({
|
|
202
|
+
bcid: 'datamatrix',
|
|
203
|
+
text: a,
|
|
204
|
+
} as any);
|
|
205
|
+
|
|
206
|
+
// Base64 encode the string
|
|
207
|
+
return 'data:image/svg+xml;base64,' + Buffer.from(svgCode).toString('base64');
|
|
208
|
+
});
|
|
209
|
+
|
|
195
210
|
// Rander handlebars template
|
|
196
|
-
export function render(htmlTemplate: string, context: any): string | null {
|
|
211
|
+
export async function render(htmlTemplate: string, context: any): Promise<string | null> {
|
|
197
212
|
try {
|
|
198
213
|
const template = Handlebars.compile(htmlTemplate);
|
|
199
214
|
const renderedHtml = template(context);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
CREATE TABLE `audit_logs` (
|
|
2
|
+
`id` varchar(36) NOT NULL DEFAULT '',
|
|
3
|
+
`type` varchar(36) NOT NULL,
|
|
4
|
+
`organizationId` varchar(36) DEFAULT NULL,
|
|
5
|
+
`userId` varchar(36) DEFAULT NULL,
|
|
6
|
+
`objectId` varchar(36) DEFAULT NULL,
|
|
7
|
+
`description` text,
|
|
8
|
+
`replacements` json NOT NULL,
|
|
9
|
+
`patchList` json NOT NULL,
|
|
10
|
+
`createdAt` datetime NOT NULL,
|
|
11
|
+
PRIMARY KEY (`id`),
|
|
12
|
+
KEY `createdAt` (`createdAt` DESC) USING BTREE,
|
|
13
|
+
KEY `objectId` (`objectId`,`createdAt` DESC) USING BTREE,
|
|
14
|
+
KEY `organizationId` (`organizationId`),
|
|
15
|
+
KEY `userId` (`userId`),
|
|
16
|
+
CONSTRAINT `audit_logs_ibfk_1` FOREIGN KEY (`organizationId`) REFERENCES `organizations` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
|
17
|
+
CONSTRAINT `audit_logs_ibfk_2` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
|
18
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { column, Model } from '@simonbackx/simple-database';
|
|
2
|
+
import { ArrayDecoder, Decoder, MapDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
3
|
+
import { AuditLogPatchItem, AuditLogReplacement, AuditLogType } from '@stamhoofd/structures';
|
|
4
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
+
|
|
6
|
+
export class AuditLog extends Model {
|
|
7
|
+
static table = 'audit_logs';
|
|
8
|
+
|
|
9
|
+
// Columns
|
|
10
|
+
@column({
|
|
11
|
+
primary: true, type: 'string', beforeSave(value) {
|
|
12
|
+
return value ?? uuidv4();
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
id!: string;
|
|
16
|
+
|
|
17
|
+
@column({ type: 'string' })
|
|
18
|
+
type: AuditLogType = AuditLogType.Unknown;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Set to make the log visible for this specific organization - otherwise it is private for the platform
|
|
22
|
+
*/
|
|
23
|
+
@column({ type: 'string', nullable: true })
|
|
24
|
+
organizationId: string | null;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* User who performed the action
|
|
28
|
+
*/
|
|
29
|
+
@column({ type: 'string', nullable: true })
|
|
30
|
+
userId: string | null = null;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Main involved object ID - e.g. the member id
|
|
34
|
+
*/
|
|
35
|
+
@column({ type: 'string', nullable: true })
|
|
36
|
+
objectId: string | null = null;
|
|
37
|
+
|
|
38
|
+
@column({ type: 'string' })
|
|
39
|
+
description: string = '';
|
|
40
|
+
|
|
41
|
+
@column({ type: 'json', decoder: new MapDecoder(StringDecoder, AuditLogReplacement as Decoder<AuditLogReplacement>) })
|
|
42
|
+
replacements: Map<string, AuditLogReplacement> = new Map();
|
|
43
|
+
|
|
44
|
+
@column({ type: 'json', decoder: new ArrayDecoder(AuditLogPatchItem as Decoder<AuditLogPatchItem>) })
|
|
45
|
+
patchList: AuditLogPatchItem[] = [];
|
|
46
|
+
|
|
47
|
+
@column({
|
|
48
|
+
type: 'datetime', beforeSave(old?: any) {
|
|
49
|
+
if (old !== undefined) {
|
|
50
|
+
return old;
|
|
51
|
+
}
|
|
52
|
+
const date = new Date();
|
|
53
|
+
date.setMilliseconds(0);
|
|
54
|
+
return date;
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
createdAt: Date;
|
|
58
|
+
}
|
|
@@ -123,111 +123,6 @@ export class BalanceItem extends Model {
|
|
|
123
123
|
return this.price - this.pricePaid - this.pricePending;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
async markUpdated(payment: Payment, organization: Organization) {
|
|
127
|
-
// For orders: mark order as changed (so they are refetched in front ends)
|
|
128
|
-
if (this.orderId) {
|
|
129
|
-
const { Order } = await import('./Order');
|
|
130
|
-
const order = await Order.getByID(this.orderId);
|
|
131
|
-
if (order) {
|
|
132
|
-
await order.paymentChanged(payment, organization);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async markPaid(payment: Payment | null, organization: Organization) {
|
|
138
|
-
if (this.status === BalanceItemStatus.Hidden) {
|
|
139
|
-
await BalanceItem.reactivateItems([this]);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// status and pricePaid changes are handled inside balanceitempayment
|
|
143
|
-
if (this.dependingBalanceItemId) {
|
|
144
|
-
const depending = await BalanceItem.getByID(this.dependingBalanceItemId);
|
|
145
|
-
if (depending && depending.status === BalanceItemStatus.Hidden) {
|
|
146
|
-
await BalanceItem.reactivateItems([depending]);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// If registration
|
|
151
|
-
if (this.registrationId) {
|
|
152
|
-
const { Registration } = await import('./Registration');
|
|
153
|
-
const registration = await Registration.getByID(this.registrationId);
|
|
154
|
-
|
|
155
|
-
if (registration) {
|
|
156
|
-
// 1. Mark registration as being valid
|
|
157
|
-
if (registration.registeredAt === null || registration.deactivatedAt) {
|
|
158
|
-
await registration.markValid();
|
|
159
|
-
|
|
160
|
-
const { Group } = await import('./Group');
|
|
161
|
-
|
|
162
|
-
// Update group occupancy
|
|
163
|
-
// TODO: maybe we should schedule this, to prevent doing many updates at once
|
|
164
|
-
const group = await Group.getByID(registration.groupId);
|
|
165
|
-
if (group) {
|
|
166
|
-
await group.updateOccupancy();
|
|
167
|
-
await group.save();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// If order
|
|
174
|
-
if (this.orderId) {
|
|
175
|
-
const { Order } = await import('./Order');
|
|
176
|
-
const order = await Order.getByID(this.orderId);
|
|
177
|
-
if (order) {
|
|
178
|
-
await order.markPaid(payment, organization);
|
|
179
|
-
|
|
180
|
-
// Save number in balacance description
|
|
181
|
-
if (order.number !== null) {
|
|
182
|
-
const webshop = await Webshop.getByID(order.webshopId);
|
|
183
|
-
|
|
184
|
-
if (webshop) {
|
|
185
|
-
this.description = order.generateBalanceDescription(webshop);
|
|
186
|
-
await this.save();
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async undoPaid(payment: Payment | null, organization: Organization) {
|
|
194
|
-
// If order
|
|
195
|
-
if (this.orderId) {
|
|
196
|
-
const { Order } = await import('./Order');
|
|
197
|
-
const order = await Order.getByID(this.orderId);
|
|
198
|
-
if (order) {
|
|
199
|
-
await order.undoPaid(payment, organization);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async markFailed(payment: Payment, organization: Organization) {
|
|
205
|
-
// If order
|
|
206
|
-
if (this.orderId) {
|
|
207
|
-
const { Order } = await import('./Order');
|
|
208
|
-
const order = await Order.getByID(this.orderId);
|
|
209
|
-
if (order) {
|
|
210
|
-
await order.onPaymentFailed(payment, organization);
|
|
211
|
-
|
|
212
|
-
if (order.status === OrderStatus.Deleted) {
|
|
213
|
-
this.status = BalanceItemStatus.Hidden;
|
|
214
|
-
await this.save();
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
async undoFailed(payment: Payment, organization: Organization) {
|
|
221
|
-
// If order
|
|
222
|
-
if (this.orderId) {
|
|
223
|
-
const { Order } = await import('./Order');
|
|
224
|
-
const order = await Order.getByID(this.orderId);
|
|
225
|
-
if (order) {
|
|
226
|
-
await order.undoPaymentFailed(payment, organization);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
126
|
updateStatus() {
|
|
232
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));
|
|
233
128
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { column, ManyToOneRelation, Model } from '@simonbackx/simple-database';
|
|
2
|
-
import { BalanceItemStatus } from '@stamhoofd/structures';
|
|
3
2
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
3
|
|
|
5
|
-
import { BalanceItem,
|
|
4
|
+
import { BalanceItem, Payment } from './';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Keeps track of all the created payments of a balance item, which contains the (tries) to pay a balance item.
|
|
@@ -57,42 +56,4 @@ export class BalanceItemPayment extends Model {
|
|
|
57
56
|
|
|
58
57
|
static balanceItem = new ManyToOneRelation(BalanceItem, 'balanceItem');
|
|
59
58
|
static payment = new ManyToOneRelation(Payment, 'payment');
|
|
60
|
-
|
|
61
|
-
async markPaid(this: BalanceItemPayment & Loaded<typeof BalanceItemPayment.balanceItem> & Loaded<typeof BalanceItemPayment.payment>, organization: Organization) {
|
|
62
|
-
// Update cached amountPaid of the balance item (this will get overwritten later, but we need it to calculate the status)
|
|
63
|
-
this.balanceItem.pricePaid += this.price;
|
|
64
|
-
|
|
65
|
-
// Update status
|
|
66
|
-
const old = this.balanceItem.status;
|
|
67
|
-
this.balanceItem.updateStatus();
|
|
68
|
-
await this.balanceItem.save();
|
|
69
|
-
|
|
70
|
-
// Do logic of balance item
|
|
71
|
-
if (this.balanceItem.status === BalanceItemStatus.Paid && old !== BalanceItemStatus.Paid) {
|
|
72
|
-
// Only call markPaid once (if it wasn't (partially) paid before)
|
|
73
|
-
await this.balanceItem.markPaid(this.payment, organization);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
await this.balanceItem.markUpdated(this.payment, organization);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Call this once a earlier succeeded payment is no longer succeeded
|
|
82
|
-
*/
|
|
83
|
-
async undoPaid(this: BalanceItemPayment & Loaded<typeof BalanceItemPayment.balanceItem> & Loaded<typeof BalanceItemPayment.payment>, organization: Organization) {
|
|
84
|
-
await this.balanceItem.undoPaid(this.payment, organization);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async markFailed(this: BalanceItemPayment & Loaded<typeof BalanceItemPayment.balanceItem> & Loaded<typeof BalanceItemPayment.payment>, organization: Organization) {
|
|
88
|
-
// Do logic of balance item
|
|
89
|
-
await this.balanceItem.markFailed(this.payment, organization);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async undoFailed(this: BalanceItemPayment & Loaded<typeof BalanceItemPayment.balanceItem> & Loaded<typeof BalanceItemPayment.payment>, organization: Organization) {
|
|
93
|
-
// Reactivate deleted items
|
|
94
|
-
await this.balanceItem.undoFailed(this.payment, organization);
|
|
95
|
-
}
|
|
96
59
|
}
|
|
97
|
-
|
|
98
|
-
type Loaded<T> = (T) extends ManyToOneRelation<infer Key, infer Model> ? Record<Key, Model> : never;
|
package/src/models/Document.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { column, Model } from '@simonbackx/simple-database';
|
|
2
|
-
import { Document as DocumentStruct, DocumentData, DocumentStatus } from '@stamhoofd/structures';
|
|
2
|
+
import { Document as DocumentStruct, DocumentData, DocumentStatus, Platform, Version } from '@stamhoofd/structures';
|
|
3
3
|
import { Formatter } from '@stamhoofd/utility';
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
5
|
|
|
@@ -67,13 +67,26 @@ export class Document extends Model {
|
|
|
67
67
|
|
|
68
68
|
buildContext(organization: Organization) {
|
|
69
69
|
// Convert the field answers in a simplified javascript object
|
|
70
|
-
const data = {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
'organization.logo': organization.meta.squareLogo,
|
|
70
|
+
const data: Record<string, any> = {
|
|
71
|
+
id: this.id,
|
|
72
|
+
name: this.data.name,
|
|
73
|
+
number: this.number,
|
|
74
|
+
created_at: this.createdAt,
|
|
76
75
|
};
|
|
76
|
+
const platformLogo = Platform.shared.config.logoDocuments ?? Platform.shared.config.horizontalLogo ?? Platform.shared.config.squareLogo;
|
|
77
|
+
const organizationLogo = organization.meta.horizontalLogo ?? organization.meta.squareLogo;
|
|
78
|
+
|
|
79
|
+
if (organizationLogo) {
|
|
80
|
+
data['organization'] = {
|
|
81
|
+
logo: organizationLogo.encode({ version: Version }) ?? null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (platformLogo) {
|
|
86
|
+
data['platform'] = {
|
|
87
|
+
logo: platformLogo.encode({ version: Version }) ?? null,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
77
90
|
|
|
78
91
|
for (const field of this.data.fieldAnswers.values()) {
|
|
79
92
|
const keys = field.settings.id.split('.');
|
|
@@ -188,14 +201,14 @@ export class Document extends Model {
|
|
|
188
201
|
return null;
|
|
189
202
|
}
|
|
190
203
|
|
|
191
|
-
return this.getRenderedHtmlForTemplate(organization, template.html);
|
|
204
|
+
return await this.getRenderedHtmlForTemplate(organization, template.html);
|
|
192
205
|
}
|
|
193
206
|
|
|
194
207
|
// Rander handlebars template
|
|
195
|
-
private getRenderedHtmlForTemplate(organization: Organization, htmlTemplate: string): string | null {
|
|
208
|
+
private async getRenderedHtmlForTemplate(organization: Organization, htmlTemplate: string): Promise<string | null> {
|
|
196
209
|
try {
|
|
197
210
|
const context = this.buildContext(organization);
|
|
198
|
-
const renderedHtml = render(htmlTemplate, context);
|
|
211
|
+
const renderedHtml = await render(htmlTemplate, context);
|
|
199
212
|
return renderedHtml;
|
|
200
213
|
}
|
|
201
214
|
catch (e) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { column, Model } from '@simonbackx/simple-database';
|
|
2
2
|
import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
|
|
3
3
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
4
|
-
import { DocumentData, DocumentPrivateSettings, DocumentSettings, DocumentStatus, DocumentTemplatePrivate, GroupType, RecordAddressAnswer, RecordAnswer, RecordAnswerDecoder, RecordDateAnswer, RecordPriceAnswer, RecordSettings, RecordTextAnswer, RecordType } from '@stamhoofd/structures';
|
|
4
|
+
import { BalanceItemStatus, DocumentData, DocumentPrivateSettings, DocumentSettings, DocumentStatus, DocumentTemplatePrivate, GroupType, Parent, RecordAddressAnswer, RecordAnswer, RecordAnswerDecoder, RecordDateAnswer, RecordPriceAnswer, RecordSettings, RecordTextAnswer, RecordType } from '@stamhoofd/structures';
|
|
5
5
|
import { Sorter } from '@stamhoofd/utility';
|
|
6
6
|
import { v4 as uuidv4 } from 'uuid';
|
|
7
7
|
|
|
@@ -11,6 +11,7 @@ import { Document } from './Document';
|
|
|
11
11
|
import { Group } from './Group';
|
|
12
12
|
import { Member, RegistrationWithMember } from './Member';
|
|
13
13
|
import { Organization } from './Organization';
|
|
14
|
+
import { User } from './User';
|
|
14
15
|
|
|
15
16
|
export class DocumentTemplate extends Model {
|
|
16
17
|
static table = 'document_templates';
|
|
@@ -72,7 +73,7 @@ export class DocumentTemplate extends Model {
|
|
|
72
73
|
let missingData = false;
|
|
73
74
|
|
|
74
75
|
const group = await Group.getByID(registration.groupId);
|
|
75
|
-
const { payments } = await BalanceItem.getForRegistration(registration.id);
|
|
76
|
+
const { items: balanceItems, payments } = await BalanceItem.getForRegistration(registration.id);
|
|
76
77
|
|
|
77
78
|
const paidAtDates = payments.flatMap(p => p.paidAt ? [p.paidAt?.getTime()] : []);
|
|
78
79
|
|
|
@@ -81,13 +82,11 @@ export class DocumentTemplate extends Model {
|
|
|
81
82
|
|
|
82
83
|
// Some fields are supported by default in linked fields
|
|
83
84
|
const defaultData: Record<string, RecordAnswer> = {
|
|
84
|
-
// "registration.startDate": registration.group.settings.startDate,
|
|
85
|
-
// "registration.endDate": registration.group.settings.endDate,
|
|
86
85
|
'group.name': RecordTextAnswer.create({
|
|
87
86
|
settings: RecordSettings.create({
|
|
88
87
|
id: 'group.name',
|
|
89
88
|
type: RecordType.Text,
|
|
90
|
-
}),
|
|
89
|
+
}),
|
|
91
90
|
value: group?.settings?.name ?? '',
|
|
92
91
|
}),
|
|
93
92
|
'group.type': RecordTextAnswer.create({
|
|
@@ -142,13 +141,17 @@ export class DocumentTemplate extends Model {
|
|
|
142
141
|
settings: RecordSettings.create({}), // settings will be overwritten
|
|
143
142
|
value: registration.member.details.lastName,
|
|
144
143
|
}),
|
|
144
|
+
'member.nationalRegisterNumber': RecordTextAnswer.create({
|
|
145
|
+
settings: RecordSettings.create({}), // settings will be overwritten
|
|
146
|
+
value: registration.member.details.nationalRegisterNumber,
|
|
147
|
+
}),
|
|
145
148
|
'member.address': RecordAddressAnswer.create({
|
|
146
149
|
settings: RecordSettings.create({}), // settings will be overwritten
|
|
147
|
-
address: registration.member.details.address ?? null,
|
|
150
|
+
address: registration.member.details.address ?? registration.member.details.getAllAddresses()[0] ?? null,
|
|
148
151
|
}),
|
|
149
152
|
'member.email': RecordTextAnswer.create({
|
|
150
153
|
settings: RecordSettings.create({}), // settings will be overwritten
|
|
151
|
-
value: registration.member.details.
|
|
154
|
+
value: registration.member.details.getMemberEmails()[0] ?? registration.member.details.getParentEmails()[0],
|
|
152
155
|
}),
|
|
153
156
|
'member.birthDay': RecordDateAnswer.create({
|
|
154
157
|
settings: RecordSettings.create({}), // settings will be overwritten
|
|
@@ -162,6 +165,10 @@ export class DocumentTemplate extends Model {
|
|
|
162
165
|
settings: RecordSettings.create({}), // settings will be overwritten
|
|
163
166
|
value: registration.member.details.parents[0]?.lastName,
|
|
164
167
|
}),
|
|
168
|
+
'parents[0].nationalRegisterNumber': RecordTextAnswer.create({
|
|
169
|
+
settings: RecordSettings.create({}), // settings will be overwritten
|
|
170
|
+
value: registration.member.details.parents[0]?.nationalRegisterNumber,
|
|
171
|
+
}),
|
|
165
172
|
'parents[0].address': RecordAddressAnswer.create({
|
|
166
173
|
settings: RecordSettings.create({}), // settings will be overwritten
|
|
167
174
|
address: registration.member.details.parents[0]?.address ?? null,
|
|
@@ -178,6 +185,10 @@ export class DocumentTemplate extends Model {
|
|
|
178
185
|
settings: RecordSettings.create({}), // settings will be overwritten
|
|
179
186
|
value: registration.member.details.parents[1]?.lastName,
|
|
180
187
|
}),
|
|
188
|
+
'parents[1].nationalRegisterNumber': RecordTextAnswer.create({
|
|
189
|
+
settings: RecordSettings.create({}), // settings will be overwritten
|
|
190
|
+
value: registration.member.details.parents[1]?.nationalRegisterNumber,
|
|
191
|
+
}),
|
|
181
192
|
'parents[1].address': RecordAddressAnswer.create({
|
|
182
193
|
settings: RecordSettings.create({}), // settings will be overwritten
|
|
183
194
|
address: registration.member.details.parents[1]?.address ?? null,
|
|
@@ -188,13 +199,83 @@ export class DocumentTemplate extends Model {
|
|
|
188
199
|
}),
|
|
189
200
|
};
|
|
190
201
|
|
|
202
|
+
const allRecords = this.privateSettings.templateDefinition.documentFieldCategories.flatMap(c => c.getAllRecords());
|
|
203
|
+
const hasDebtor = allRecords.find(s => s.id.startsWith('debtor.'));
|
|
204
|
+
|
|
205
|
+
if (hasDebtor) {
|
|
206
|
+
const parentsWithNRN = registration.member.details.parents.filter(p => p.nationalRegisterNumber);
|
|
207
|
+
let debtor: Parent | undefined = parentsWithNRN[0] ?? registration.member.details.parents[0];
|
|
208
|
+
if (parentsWithNRN.length > 1) {
|
|
209
|
+
for (const balanceItem of balanceItems) {
|
|
210
|
+
if (balanceItem && balanceItem.userId && balanceItem.status === BalanceItemStatus.Paid) {
|
|
211
|
+
const user = await User.getByID(balanceItem.userId);
|
|
212
|
+
if (user) {
|
|
213
|
+
const parent = parentsWithNRN.find(p => p.hasEmail(user.email));
|
|
214
|
+
|
|
215
|
+
if (parent) {
|
|
216
|
+
debtor = parent;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!debtor.nationalRegisterNumber) {
|
|
221
|
+
const parent = registration.member.details.parents.find(p => p.hasEmail(user.email));
|
|
222
|
+
if (parent) {
|
|
223
|
+
debtor = parent;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
Object.assign(defaultData, {
|
|
232
|
+
'debtor.firstName': RecordTextAnswer.create({
|
|
233
|
+
settings: RecordSettings.create({}), // settings will be overwritten
|
|
234
|
+
value: debtor?.firstName ?? '',
|
|
235
|
+
}),
|
|
236
|
+
'debtor.lastName': RecordTextAnswer.create({
|
|
237
|
+
settings: RecordSettings.create({}), // settings will be overwritten
|
|
238
|
+
value: debtor?.lastName ?? '',
|
|
239
|
+
}),
|
|
240
|
+
'debtor.nationalRegisterNumber': RecordTextAnswer.create({
|
|
241
|
+
settings: RecordSettings.create({}), // settings will be overwritten
|
|
242
|
+
value: debtor?.nationalRegisterNumber ?? '',
|
|
243
|
+
}),
|
|
244
|
+
'debtor.address': RecordAddressAnswer.create({
|
|
245
|
+
settings: RecordSettings.create({}), // settings will be overwritten
|
|
246
|
+
address: debtor?.address ?? null,
|
|
247
|
+
}),
|
|
248
|
+
'debtor.email': RecordTextAnswer.create({
|
|
249
|
+
settings: RecordSettings.create({}), // settings will be overwritten
|
|
250
|
+
value: debtor?.email ?? null,
|
|
251
|
+
}),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
191
255
|
// Add data that is different for each member
|
|
192
|
-
for (const field of
|
|
256
|
+
for (const field of allRecords) {
|
|
193
257
|
// Where do we need to find the answer to this linked field?
|
|
194
258
|
// - Could either return an id of a recordSetting connected to member
|
|
195
259
|
// - or an idea of defaultData that is supported by default
|
|
196
260
|
// The result is always a recordAnswer whose type should match the type of the linkedField
|
|
197
|
-
|
|
261
|
+
let linkedToMemberAnswerSettingsIds = this.settings.linkedFields.get(field.id) ?? [field.id];
|
|
262
|
+
|
|
263
|
+
if (linkedToMemberAnswerSettingsIds.length === 0) {
|
|
264
|
+
linkedToMemberAnswerSettingsIds = [field.id];
|
|
265
|
+
}
|
|
266
|
+
console.log('Checking', field.id);
|
|
267
|
+
|
|
268
|
+
// Check if this field has been manually disabled by a global checkbox
|
|
269
|
+
const enableField = this.settings.fieldAnswers.get('enable[' + field.id + ']');
|
|
270
|
+
if (enableField && enableField.objectValue === false) {
|
|
271
|
+
field.required = false;
|
|
272
|
+
|
|
273
|
+
const clone = RecordAnswerDecoder.getClassForType(field.type).create({
|
|
274
|
+
settings: field,
|
|
275
|
+
});
|
|
276
|
+
fieldAnswers.set(field.id, clone);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
198
279
|
|
|
199
280
|
let found = false;
|
|
200
281
|
|
|
@@ -375,17 +456,13 @@ export class DocumentTemplate extends Model {
|
|
|
375
456
|
}
|
|
376
457
|
|
|
377
458
|
if (this.settings.minPrice !== null) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const answer = fieldAnswers.get(fieldId);
|
|
382
|
-
if (answer && answer instanceof RecordPriceAnswer) {
|
|
383
|
-
if (answer.value !== null) {
|
|
384
|
-
price = answer.value;
|
|
385
|
-
}
|
|
459
|
+
if ((registration.price ?? 0) < this.settings.minPrice) {
|
|
460
|
+
return false;
|
|
386
461
|
}
|
|
462
|
+
}
|
|
387
463
|
|
|
388
|
-
|
|
464
|
+
if (this.settings.minPricePaid !== null) {
|
|
465
|
+
if ((registration.pricePaid ?? 0) < this.settings.minPricePaid && (registration.price ?? 0) > 0) {
|
|
389
466
|
return false;
|
|
390
467
|
}
|
|
391
468
|
}
|
|
@@ -527,7 +604,7 @@ export class DocumentTemplate extends Model {
|
|
|
527
604
|
|
|
528
605
|
try {
|
|
529
606
|
const context = await this.buildContext(organization);
|
|
530
|
-
const renderedHtml = render(this.privateSettings.templateDefinition.xmlExport, context);
|
|
607
|
+
const renderedHtml = await render(this.privateSettings.templateDefinition.xmlExport, context);
|
|
531
608
|
return renderedHtml;
|
|
532
609
|
}
|
|
533
610
|
catch (e) {
|
package/src/models/Member.ts
CHANGED
|
@@ -188,7 +188,7 @@ export class Member extends Model {
|
|
|
188
188
|
static async getRegistrationWithMembersForGroup(groupId: string): Promise<RegistrationWithMember[]> {
|
|
189
189
|
let query = `SELECT ${Member.getDefaultSelect()}, ${Registration.getDefaultSelect()} from \`${Member.table}\`\n`;
|
|
190
190
|
|
|
191
|
-
query += `JOIN \`${Registration.table}\` ON \`${Registration.table}\`.\`${Member.registrations.foreignKey}\` = \`${Member.table}\`.\`${Member.primary.name}\` AND
|
|
191
|
+
query += `JOIN \`${Registration.table}\` ON \`${Registration.table}\`.\`${Member.registrations.foreignKey}\` = \`${Member.table}\`.\`${Member.primary.name}\` AND \`${Registration.table}\`.\`registeredAt\` is not null AND \`${Registration.table}\`.\`deactivatedAt\` is null\n`;
|
|
192
192
|
|
|
193
193
|
// We do an extra join because we also need to get the other registrations of each member (only one regitration has to match the query)
|
|
194
194
|
query += `where \`${Registration.table}\`.\`groupId\` = ?`;
|
|
@@ -185,52 +185,6 @@ export class Registration extends Model {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
async deactivate() {
|
|
189
|
-
if (this.deactivatedAt !== null) {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Clear the registration
|
|
194
|
-
this.deactivatedAt = new Date();
|
|
195
|
-
await this.save();
|
|
196
|
-
this.scheduleStockUpdate();
|
|
197
|
-
|
|
198
|
-
const { Member } = await import('./Member');
|
|
199
|
-
await Member.updateMembershipsForId(this.memberId);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async markValid(this: Registration, options?: { skipEmail?: boolean }) {
|
|
203
|
-
if (this.registeredAt !== null && this.deactivatedAt === null) {
|
|
204
|
-
await this.save();
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
this.reservedUntil = null;
|
|
209
|
-
this.registeredAt = this.registeredAt ?? new Date();
|
|
210
|
-
this.deactivatedAt = null;
|
|
211
|
-
this.canRegister = false;
|
|
212
|
-
await this.save();
|
|
213
|
-
this.scheduleStockUpdate();
|
|
214
|
-
|
|
215
|
-
const { Member } = await import('./Member');
|
|
216
|
-
await Member.updateMembershipsForId(this.memberId);
|
|
217
|
-
|
|
218
|
-
if (options?.skipEmail !== true) {
|
|
219
|
-
await this.sendEmailTemplate({
|
|
220
|
-
type: EmailTemplateType.RegistrationConfirmation,
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const member = await Member.getByID(this.memberId);
|
|
225
|
-
if (member) {
|
|
226
|
-
const registrationMemberRelation = new ManyToOneRelation(Member, 'member');
|
|
227
|
-
registrationMemberRelation.foreignKey = Member.registrations.foreignKey;
|
|
228
|
-
await Document.updateForRegistration(this.setRelation(registrationMemberRelation, member));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return true;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
188
|
async getRecipients(organization: Organization, group: import('./').Group) {
|
|
235
189
|
const { Member } = await import('./Member');
|
|
236
190
|
|
package/src/models/index.ts
CHANGED