@stamhoofd/models 2.19.0 → 2.20.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/models/Member.js.map +1 -1
- package/dist/src/models/MemberResponsibilityRecord.d.ts +5 -0
- package/dist/src/models/MemberResponsibilityRecord.d.ts.map +1 -1
- package/dist/src/models/MemberResponsibilityRecord.js +16 -1
- package/dist/src/models/MemberResponsibilityRecord.js.map +1 -1
- package/dist/src/models/Organization.d.ts +2 -0
- package/dist/src/models/Organization.d.ts.map +1 -1
- package/dist/src/models/Organization.js +19 -3
- package/dist/src/models/Organization.js.map +1 -1
- package/dist/src/models/RegisterCode.d.ts +0 -10
- package/dist/src/models/RegisterCode.d.ts.map +1 -1
- package/dist/src/models/RegisterCode.js +0 -86
- package/dist/src/models/RegisterCode.js.map +1 -1
- package/dist/src/models/STCredit.d.ts +0 -7
- package/dist/src/models/STCredit.d.ts.map +1 -1
- package/dist/src/models/STCredit.js +0 -69
- package/dist/src/models/STCredit.js.map +1 -1
- package/dist/src/models/STInvoice.d.ts +2 -20
- package/dist/src/models/STInvoice.d.ts.map +1 -1
- package/dist/src/models/STInvoice.js +0 -364
- package/dist/src/models/STInvoice.js.map +1 -1
- package/dist/src/models/STPendingInvoice.d.ts +1 -21
- package/dist/src/models/STPendingInvoice.d.ts.map +1 -1
- package/dist/src/models/STPendingInvoice.js +0 -213
- package/dist/src/models/STPendingInvoice.js.map +1 -1
- package/dist/src/models/UsedRegisterCode.d.ts +0 -5
- package/dist/src/models/UsedRegisterCode.d.ts.map +1 -1
- package/dist/src/models/UsedRegisterCode.js +0 -101
- package/dist/src/models/UsedRegisterCode.js.map +1 -1
- package/package.json +2 -2
- package/src/models/Member.ts +5 -5
- package/src/models/MemberResponsibilityRecord.ts +21 -2
- package/src/models/Organization.ts +23 -3
- package/src/models/RegisterCode.ts +0 -95
- package/src/models/STCredit.ts +0 -82
- package/src/models/STInvoice.ts +2 -433
- package/src/models/STPendingInvoice.ts +2 -247
- package/src/models/UsedRegisterCode.ts +0 -115
- package/dist/src/helpers/InvoiceBuilder.d.ts +0 -29
- package/dist/src/helpers/InvoiceBuilder.d.ts.map +0 -1
- package/dist/src/helpers/InvoiceBuilder.js +0 -407
- package/dist/src/helpers/InvoiceBuilder.js.map +0 -1
- package/dist/src/helpers/InvoiceBuilder.test.d.ts +0 -2
- package/dist/src/helpers/InvoiceBuilder.test.d.ts.map +0 -1
- package/dist/src/helpers/InvoiceBuilder.test.js +0 -52
- package/dist/src/helpers/InvoiceBuilder.test.js.map +0 -1
- package/src/helpers/InvoiceBuilder.test.ts +0 -57
- package/src/helpers/InvoiceBuilder.ts +0 -501
package/src/models/STCredit.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { column, Model } from "@simonbackx/simple-database";
|
|
2
|
-
import { STInvoiceItem } from "@stamhoofd/structures";
|
|
3
2
|
import { v4 as uuidv4 } from "uuid";
|
|
4
3
|
|
|
5
|
-
import { STInvoice } from "./";
|
|
6
4
|
|
|
7
5
|
export class STCredit extends Model {
|
|
8
6
|
static table = "stamhoofd_credits";
|
|
@@ -51,84 +49,4 @@ export class STCredit extends Model {
|
|
|
51
49
|
skipUpdate: true
|
|
52
50
|
})
|
|
53
51
|
updatedAt: Date
|
|
54
|
-
|
|
55
|
-
static async getForOrganization(organizationId: string) {
|
|
56
|
-
return await STCredit.where({ organizationId }, {
|
|
57
|
-
sort: [{
|
|
58
|
-
column: "createdAt",
|
|
59
|
-
direction: "DESC"
|
|
60
|
-
}]
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
static async getBalance(organizationId: string) {
|
|
65
|
-
const now = new Date()
|
|
66
|
-
const credits = await this.getForOrganization(organizationId)
|
|
67
|
-
credits.reverse()
|
|
68
|
-
let balance = 0
|
|
69
|
-
let balanceTransactions = 0
|
|
70
|
-
|
|
71
|
-
for (const credit of credits) {
|
|
72
|
-
if (credit.expireAt !== null && credit.expireAt <= now) {
|
|
73
|
-
continue
|
|
74
|
-
}
|
|
75
|
-
// TODO: we can expire credits here
|
|
76
|
-
balance += credit.change
|
|
77
|
-
if (balance < 0) {
|
|
78
|
-
// This is needed to make deleting credit and expiring credit work.
|
|
79
|
-
// At no point in time, the credits can get negative.
|
|
80
|
-
// E.g. Getting credits, using them, and later expiring 'getting the credits' won't have impact on future credits
|
|
81
|
-
balance = 0
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (credit.allowTransactions || credit.change < 0) {
|
|
85
|
-
balanceTransactions += credit.change
|
|
86
|
-
|
|
87
|
-
// No point in time we can have more balance for transactions
|
|
88
|
-
balanceTransactions = Math.min(Math.max(balanceTransactions, 0), balance)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return {balance: balance - balanceTransactions, balanceTransactions}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
static async applyCredits(organizationId: string, invoice: STInvoice, dryRun: boolean) {
|
|
96
|
-
// Apply credits
|
|
97
|
-
const {balance, balanceTransactions} = await STCredit.getBalance(organizationId)
|
|
98
|
-
if (balance > 0 || balanceTransactions > 0) {
|
|
99
|
-
// Loop all items where you can use credits for
|
|
100
|
-
const maxCredits = invoice.meta.items.filter(i => i.canUseCredits).reduce((price, item) => price + item.price, 0)
|
|
101
|
-
let applyValue = Math.min(maxCredits, balance)
|
|
102
|
-
|
|
103
|
-
if (balanceTransactions > 0) {
|
|
104
|
-
// Can apply to all items
|
|
105
|
-
const maxTransactionsCredits = invoice.meta.items.reduce((price, item) => price + item.price, 0) - applyValue
|
|
106
|
-
applyValue += Math.min(maxTransactionsCredits, balanceTransactions)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (applyValue > 0) {
|
|
110
|
-
invoice.meta.items.push(STInvoiceItem.create({
|
|
111
|
-
name: "Gebruikt tegoed",
|
|
112
|
-
unitPrice: -applyValue,
|
|
113
|
-
amount: 1,
|
|
114
|
-
date: new Date()
|
|
115
|
-
}))
|
|
116
|
-
|
|
117
|
-
if (!dryRun) {
|
|
118
|
-
const credit = new STCredit()
|
|
119
|
-
credit.organizationId = organizationId
|
|
120
|
-
credit.change = -applyValue
|
|
121
|
-
credit.description = "Tijdelijk vrijgehouden voor lopende betaling"
|
|
122
|
-
|
|
123
|
-
// Reserve for one week (direct debit can take a while)
|
|
124
|
-
credit.expireAt = new Date()
|
|
125
|
-
credit.expireAt.setDate(credit.expireAt.getDate() + 7)
|
|
126
|
-
credit.expireAt.setMilliseconds(0)
|
|
127
|
-
|
|
128
|
-
await credit.save()
|
|
129
|
-
invoice.creditId = credit.id
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
52
|
}
|
package/src/models/STInvoice.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { column, ManyToOneRelation, Model } from "@simonbackx/simple-database";
|
|
2
|
-
import {
|
|
3
|
-
import { QueueHandler } from "@stamhoofd/queues";
|
|
4
|
-
import { calculateVATPercentage, Payment as PaymentStruct, PaymentMethod, STBillingStatus, STCredit as STCreditStruct, STInvoice as STInvoiceStruct, STInvoiceItem, STInvoiceMeta, STPackage as STPackageStruct, STPackageType, STPendingInvoice as STPendingInvoiceStruct } from '@stamhoofd/structures';
|
|
5
|
-
import { Sorter } from "@stamhoofd/utility";
|
|
2
|
+
import { STInvoiceMeta } from '@stamhoofd/structures';
|
|
6
3
|
import { v4 as uuidv4 } from "uuid";
|
|
7
4
|
|
|
8
|
-
import {
|
|
9
|
-
import { Organization, Payment, STCredit, STPackage, STPendingInvoice, UsedRegisterCode } from './';
|
|
5
|
+
import { Organization, Payment } from './';
|
|
10
6
|
|
|
11
7
|
export class STInvoice extends Model {
|
|
12
8
|
static table = "stamhoofd_invoices";
|
|
@@ -79,431 +75,4 @@ export class STInvoice extends Model {
|
|
|
79
75
|
|
|
80
76
|
static organization = new ManyToOneRelation(Organization, "organization");
|
|
81
77
|
static payment = new ManyToOneRelation(Payment, "payment");
|
|
82
|
-
|
|
83
|
-
static createFor(organization: Organization): STInvoice {
|
|
84
|
-
const invoice = new STInvoice()
|
|
85
|
-
invoice.organizationId = organization.id
|
|
86
|
-
|
|
87
|
-
const date = new Date()
|
|
88
|
-
invoice.meta = STInvoiceMeta.create({
|
|
89
|
-
date,
|
|
90
|
-
companyName: organization.meta.companyName ?? organization.name,
|
|
91
|
-
companyContact: organization.privateMeta.billingContact ?? "",
|
|
92
|
-
companyAddress: organization.meta.companyAddress ?? organization.address,
|
|
93
|
-
companyVATNumber: organization.meta.VATNumber,
|
|
94
|
-
companyNumber: organization.meta.companyNumber,
|
|
95
|
-
VATPercentage: calculateVATPercentage(organization.meta.companyAddress ?? organization.address, organization.meta.VATNumber)
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
return invoice
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async getStructure() {
|
|
102
|
-
let payment: Payment | undefined
|
|
103
|
-
if (this.paymentId) {
|
|
104
|
-
payment = await Payment.getByID(this.paymentId)
|
|
105
|
-
}
|
|
106
|
-
return STInvoiceStruct.create(Object.assign({}, this, {
|
|
107
|
-
payment: payment ? PaymentStruct.create(payment) : null
|
|
108
|
-
}))
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async getPackages(): Promise<STPackage[]> {
|
|
112
|
-
const ids = new Set<string>()
|
|
113
|
-
|
|
114
|
-
for (const item of this.meta.items) {
|
|
115
|
-
if (item.package) {
|
|
116
|
-
ids.add(item.package.id)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (ids.size > 0) {
|
|
121
|
-
const packages = await STPackage.getByIDs(...ids)
|
|
122
|
-
|
|
123
|
-
if (packages.length !== ids.size) {
|
|
124
|
-
console.warn("Invoice contains invalid package ids "+this.id)
|
|
125
|
-
}
|
|
126
|
-
return packages
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return []
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async getOrganizationActivePackages(): Promise<STPackage[]> {
|
|
133
|
-
return this.organizationId ? (await STPackage.getForOrganization(this.organizationId)) : []
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async activatePackages(paymentSucceeded = false) {
|
|
137
|
-
const packages = await this.getPackages()
|
|
138
|
-
|
|
139
|
-
// Search for all packages and activate them if needed (might be possible that they are already validated)
|
|
140
|
-
for (const p of packages) {
|
|
141
|
-
// It is possible that the meta of the package has changed in the previous loop call (in pack.activate), so we need to refetch it otherwise we get 'meta' conflicts
|
|
142
|
-
const pack = (await STPackage.getByID(p.id)) ?? p
|
|
143
|
-
console.log("Activating package "+pack.id)
|
|
144
|
-
|
|
145
|
-
// We'll never have multiple invoices for the same package that are awaiting payments
|
|
146
|
-
if (paymentSucceeded) {
|
|
147
|
-
pack.meta.firstFailedPayment = null;
|
|
148
|
-
pack.meta.paymentFailedCount = 0;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
await pack.activate()
|
|
152
|
-
|
|
153
|
-
// Activate doesn't save always, so save if needed:
|
|
154
|
-
await pack.save()
|
|
155
|
-
|
|
156
|
-
// Deactivate demo packages
|
|
157
|
-
const remove: STPackageType[] = []
|
|
158
|
-
if (pack.meta.type === STPackageType.Members) {
|
|
159
|
-
// Remove demo
|
|
160
|
-
remove.push(STPackageType.TrialMembers)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (pack.meta.type === STPackageType.SingleWebshop || pack.meta.type === STPackageType.Webshops) {
|
|
164
|
-
// Remove demo
|
|
165
|
-
remove.push(STPackageType.TrialWebshops)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (remove.length > 0 && this.organizationId) {
|
|
169
|
-
// Get all packages
|
|
170
|
-
const all = await STPackage.getForOrganization(this.organizationId)
|
|
171
|
-
for (const pack of all) {
|
|
172
|
-
if (remove.includes(pack.meta.type)) {
|
|
173
|
-
console.log("Disabling demo package "+pack.id+" because package is bought.")
|
|
174
|
-
// Stop
|
|
175
|
-
pack.removeAt = new Date()
|
|
176
|
-
pack.removeAt.setTime(pack.removeAt.getTime() - 1000)
|
|
177
|
-
await pack.save()
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* WARNING: only call this in the correct queue!
|
|
186
|
-
*/
|
|
187
|
-
async markPaid({sendEmail} = { sendEmail: true }) {
|
|
188
|
-
// Schule on the queue because we are modifying pending invoices
|
|
189
|
-
if (this.paidAt !== null) {
|
|
190
|
-
return
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
this.paidAt = new Date()
|
|
194
|
-
|
|
195
|
-
if (this.meta.priceWithoutVAT > 0) {
|
|
196
|
-
// Only assign a number if it was an non empty invoice
|
|
197
|
-
await this.assignNextNumber()
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (this.creditId !== null) {
|
|
201
|
-
const credit = await STCredit.getByID(this.creditId)
|
|
202
|
-
if (credit) {
|
|
203
|
-
// This credit was used to pay this invoice (partially)
|
|
204
|
-
credit.description = this.number !== null ? "Factuur "+this.number : "Betaling "+this.id
|
|
205
|
-
credit.expireAt = null;
|
|
206
|
-
await credit.save()
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const packages = await this.getPackages()
|
|
211
|
-
|
|
212
|
-
// Increase paid amounts and paid prices
|
|
213
|
-
for (const item of this.meta.items) {
|
|
214
|
-
if (item.package) {
|
|
215
|
-
const pack = packages.find(p => p.id === item.package?.id)
|
|
216
|
-
|
|
217
|
-
if (pack) {
|
|
218
|
-
// Increase paid amount
|
|
219
|
-
pack.meta.paidPrice += item.price
|
|
220
|
-
pack.meta.paidAmount += item.amount
|
|
221
|
-
await pack.save();
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Search for all packages and activate them if needed (might be possible that they are already validated)
|
|
227
|
-
await this.activatePackages(true)
|
|
228
|
-
|
|
229
|
-
if (packages.length === 0) {
|
|
230
|
-
// Mark payments succeeded
|
|
231
|
-
const orgPackages = await this.getOrganizationActivePackages()
|
|
232
|
-
for (const p of orgPackages) {
|
|
233
|
-
if (p.meta.firstFailedPayment) {
|
|
234
|
-
const pack = (await STPackage.getByID(p.id)) ?? p
|
|
235
|
-
pack.meta.firstFailedPayment = null;
|
|
236
|
-
pack.meta.paymentFailedCount = 0;
|
|
237
|
-
await pack.save()
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// If needed: remove the invoice items from the pending invoice
|
|
243
|
-
if (this.organizationId) {
|
|
244
|
-
const pendingInvoice = await STPendingInvoice.getForOrganization(this.organizationId)
|
|
245
|
-
if (pendingInvoice) {
|
|
246
|
-
// Remove all invoice items that were paid
|
|
247
|
-
const newItems: STInvoiceItem[] = []
|
|
248
|
-
for (const item of pendingInvoice.meta.items) {
|
|
249
|
-
const found = this.meta.items.find(i => i.id === item.id)
|
|
250
|
-
if (!found) {
|
|
251
|
-
newItems.push(item)
|
|
252
|
-
} else {
|
|
253
|
-
console.log("Removed invoice item "+item.id+" from pending invoice "+pendingInvoice.id)
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
pendingInvoice.meta.items = newItems
|
|
257
|
-
|
|
258
|
-
if (pendingInvoice.invoiceId === this.id) {
|
|
259
|
-
// Unlock the pending invoice: we allow new invoices to get created
|
|
260
|
-
pendingInvoice.invoiceId = null
|
|
261
|
-
}
|
|
262
|
-
await pendingInvoice.save()
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Force regeneration of organization meta data
|
|
266
|
-
await STPackage.updateOrganizationPackages(this.organizationId)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (this.number !== null && !this.meta.pdf) {
|
|
270
|
-
await this.generatePdf()
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (this.organizationId && this.meta.pdf && this.number !== null && sendEmail) {
|
|
274
|
-
const organization = await Organization.getByID(this.organizationId)
|
|
275
|
-
if (organization) {
|
|
276
|
-
const invoicingTo = await organization.getInvoicingToEmails()
|
|
277
|
-
|
|
278
|
-
if (invoicingTo) {
|
|
279
|
-
// Send the e-mail
|
|
280
|
-
Email.sendInternal({
|
|
281
|
-
to: invoicingTo,
|
|
282
|
-
bcc: "simon@stamhoofd.be",
|
|
283
|
-
subject: "Stamhoofd Factuur " + this.number + " voor " + organization.name,
|
|
284
|
-
text: "Dag "+organization.name+", \n\nBedankt voor jullie vertrouwen in Stamhoofd. In bijlage vinden jullie factuur "+ this.number +" voor jullie administratie. Deze werd al betaald, je hoeft dus geen actie meer te ondernemen. Neem gerust contact met ons op (via "+organization.i18n.$t("shared.emails.general")+") als je denkt dat er iets fout is gegaan of als je nog bijkomende vragen zou hebben.\n\nMet vriendelijke groeten,\nStamhoofd\n\n",
|
|
285
|
-
attachments: [
|
|
286
|
-
{
|
|
287
|
-
filename: "factuur-"+this.number+".pdf",
|
|
288
|
-
href: this.meta.pdf.getPublicPath(),
|
|
289
|
-
contentType: "application/pdf"
|
|
290
|
-
}
|
|
291
|
-
]
|
|
292
|
-
}, organization.i18n)
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Reward if we have an open register code
|
|
298
|
-
if (this.meta.priceWithVAT >= 100 && this.organizationId) {
|
|
299
|
-
// We spend some money
|
|
300
|
-
const code = await UsedRegisterCode.getFor(this.organizationId)
|
|
301
|
-
if (code && !code.creditId) {
|
|
302
|
-
console.log("Rewarding code "+code.id+" for payment")
|
|
303
|
-
|
|
304
|
-
// Deze code werd nog niet beloond
|
|
305
|
-
await code.reward()
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// todo: link member_membership items to this newly created invoice
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
async assignNextNumber() {
|
|
313
|
-
return await QueueHandler.schedule("billing/invoice-numbers", async () => {
|
|
314
|
-
// Get clone
|
|
315
|
-
const refreshed = await STInvoice.getByID(this.id)
|
|
316
|
-
if (!refreshed || refreshed.number !== null) {
|
|
317
|
-
return
|
|
318
|
-
}
|
|
319
|
-
// Removed the cache because not working across multiple instances
|
|
320
|
-
// if (STInvoice.numberCache) {
|
|
321
|
-
// this.number = STInvoice.numberCache + 1
|
|
322
|
-
// await this.save()
|
|
323
|
-
//
|
|
324
|
-
// // If save succeeded: increase it
|
|
325
|
-
// STInvoice.numberCache++;
|
|
326
|
-
// return
|
|
327
|
-
// }
|
|
328
|
-
const lastInvoice = await STInvoice.where({ number: { value: null, sign: "!=" }}, { sort: [{ column: "number", direction: "DESC"}], limit: 1 })
|
|
329
|
-
STInvoice.numberCache = lastInvoice[0]?.number ?? 0
|
|
330
|
-
|
|
331
|
-
this.number = STInvoice.numberCache + 1
|
|
332
|
-
this.meta.date = new Date()
|
|
333
|
-
await this.save()
|
|
334
|
-
|
|
335
|
-
// If save succeeded: increase it
|
|
336
|
-
STInvoice.numberCache++;
|
|
337
|
-
return
|
|
338
|
-
})
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
async generatePdf() {
|
|
342
|
-
const builder = new InvoiceBuilder(this)
|
|
343
|
-
this.meta.pdf = await builder.build()
|
|
344
|
-
await this.save()
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* WARNGING: only call this method in the correct queue!
|
|
349
|
-
*/
|
|
350
|
-
async markFailed(payment: Payment, markFailed = true) {
|
|
351
|
-
console.log("Mark invoice as failed", this.id)
|
|
352
|
-
|
|
353
|
-
const packages = await this.getPackages()
|
|
354
|
-
|
|
355
|
-
if (markFailed && (payment.method === PaymentMethod.DirectDebit || payment.method === PaymentMethod.Transfer)) {
|
|
356
|
-
// Only mark failed payments for background payments
|
|
357
|
-
for (const pack of packages) {
|
|
358
|
-
console.log("Marking package with failed payment "+pack.id)
|
|
359
|
-
|
|
360
|
-
pack.meta.firstFailedPayment = pack.meta.firstFailedPayment ?? new Date()
|
|
361
|
-
pack.meta.paymentFailedCount++;
|
|
362
|
-
await pack.save()
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (packages.length == 0) {
|
|
367
|
-
// Mark all active packages as failed
|
|
368
|
-
const activePackages = await this.getOrganizationActivePackages()
|
|
369
|
-
for (const pack of activePackages) {
|
|
370
|
-
console.log("Marking package with failed payment "+pack.id)
|
|
371
|
-
|
|
372
|
-
pack.meta.firstFailedPayment = pack.meta.firstFailedPayment ?? new Date()
|
|
373
|
-
pack.meta.paymentFailedCount++;
|
|
374
|
-
await pack.save()
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (this.organizationId) {
|
|
381
|
-
const pendingInvoice = await STPendingInvoice.getForOrganization(this.organizationId)
|
|
382
|
-
if (pendingInvoice && pendingInvoice.invoiceId === this.id) {
|
|
383
|
-
pendingInvoice.invoiceId = null
|
|
384
|
-
|
|
385
|
-
// Also update the packages in the pending invoice itself
|
|
386
|
-
for (const item of pendingInvoice.meta.items) {
|
|
387
|
-
const pack = item.package
|
|
388
|
-
if (pack) {
|
|
389
|
-
const pp = packages.find(p => p.id === pack.id)
|
|
390
|
-
if (pp) {
|
|
391
|
-
console.log("Updated package "+pp.id+" in pending invoice")
|
|
392
|
-
// Update reference to include new failed counts
|
|
393
|
-
item.package = STPackageStruct.create(pp)
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
await pendingInvoice.save()
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Force regeneration of organization meta data
|
|
402
|
-
await STPackage.updateOrganizationPackages(this.organizationId)
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (this.creditId !== null) {
|
|
406
|
-
const credit = await STCredit.getByID(this.creditId)
|
|
407
|
-
if (credit && (credit.expireAt === null || credit.expireAt > new Date())) {
|
|
408
|
-
// Expire usage (do not delete to keep the relation for debugging and recovery)
|
|
409
|
-
credit.expireAt = new Date(new Date().getTime() - 1000);
|
|
410
|
-
await credit.save()
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (markFailed && this.organizationId && payment.method === PaymentMethod.DirectDebit) {
|
|
415
|
-
const organization = await Organization.getByID(this.organizationId)
|
|
416
|
-
if (organization) {
|
|
417
|
-
const invoicingTo = await organization.getInvoicingToEmails()
|
|
418
|
-
|
|
419
|
-
if (invoicingTo) {
|
|
420
|
-
// Send the e-mail
|
|
421
|
-
Email.sendInternal({
|
|
422
|
-
to: invoicingTo,
|
|
423
|
-
bcc: "simon@stamhoofd.be",
|
|
424
|
-
subject: "Betaling mislukt voor "+organization.name,
|
|
425
|
-
text: "Dag "+organization.name+", \n\nDe automatische betaling via domiciliëring van jullie openstaande bedrag is mislukt (zie daarvoor onze vorige e-mail). Kijk even na wat er fout ging en betaal het openstaande bedrag manueel om te vermijden dat bepaalde diensten tijdelijk worden uitgeschakeld. Betalen kan via Stamhoofd > Instellingen > Facturen en betalingen > Openstaand bedrag > Afrekenen. Neem gerust contact met ons op als je bijkomende vragen hebt.\n\nMet vriendelijke groeten,\nStamhoofd\n\n",
|
|
426
|
-
}, organization.i18n)
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (markFailed && this.organizationId && payment.method === PaymentMethod.Transfer) {
|
|
432
|
-
const organization = await Organization.getByID(this.organizationId)
|
|
433
|
-
if (organization) {
|
|
434
|
-
const invoicingTo = await organization.getInvoicingToEmails()
|
|
435
|
-
|
|
436
|
-
if (invoicingTo) {
|
|
437
|
-
// Send the e-mail
|
|
438
|
-
Email.sendInternal({
|
|
439
|
-
to: invoicingTo,
|
|
440
|
-
bcc: "simon@stamhoofd.be",
|
|
441
|
-
subject: "Betaling mislukt voor "+organization.name,
|
|
442
|
-
text: "Dag "+organization.name+", \n\nBij nazicht blijkt dat we geen overschrijving hebben ontvangen voor jullie aankoop. Kijk even na wat er fout ging en betaal het openstaande bedrag om te vermijden dat de diensten worden uitgeschakeld. Betalen kan via Stamhoofd > Instellingen > Facturen en betalingen > Openstaand bedrag > Afrekenen. Neem gerust contact met ons op als je bijkomende vragen hebt.\n\nMet vriendelijke groeten,\nStamhoofd\n\n",
|
|
443
|
-
}, organization.i18n)
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
static async getBillingStatus(organization: Organization, hideExpired = true): Promise<STBillingStatus> {
|
|
451
|
-
// Get all packages
|
|
452
|
-
const packages = hideExpired ? (await STPackage.getForOrganization(organization.id)) : (await STPackage.getForOrganizationIncludingExpired(organization.id))
|
|
453
|
-
|
|
454
|
-
// GEt all invoices
|
|
455
|
-
const invoices = await STInvoice.where({ organizationId: organization.id, number: { sign: "!=", value: null }})
|
|
456
|
-
|
|
457
|
-
// Get the pending invoice if it exists
|
|
458
|
-
const pendingInvoice = await STPendingInvoice.getForOrganization(organization.id)
|
|
459
|
-
|
|
460
|
-
// Generate temporary pending invoice items for the current state without adding them IRL
|
|
461
|
-
let notYetCreatedItems: STInvoiceItem[] = []
|
|
462
|
-
try {
|
|
463
|
-
notYetCreatedItems = await STPendingInvoice.createItems(organization.id, pendingInvoice)
|
|
464
|
-
} catch (e) {
|
|
465
|
-
console.error(e)
|
|
466
|
-
}
|
|
467
|
-
const pendingInvoiceStruct = pendingInvoice ? STPendingInvoiceStruct.create(pendingInvoice) : (notYetCreatedItems.length > 0 ? STPendingInvoiceStruct.create({
|
|
468
|
-
meta: STInvoiceMeta.create({
|
|
469
|
-
companyName: organization.meta.companyName ?? organization.name,
|
|
470
|
-
companyContact: organization.privateMeta.billingContact ?? "",
|
|
471
|
-
companyAddress: organization.meta.companyAddress ?? organization.address,
|
|
472
|
-
companyVATNumber: organization.meta.VATNumber,
|
|
473
|
-
VATPercentage: calculateVATPercentage(organization.meta.companyAddress ?? organization.address, organization.meta.VATNumber)
|
|
474
|
-
})
|
|
475
|
-
}) : null)
|
|
476
|
-
|
|
477
|
-
if (notYetCreatedItems.length > 0 && pendingInvoiceStruct) {
|
|
478
|
-
pendingInvoiceStruct.meta.items.push(...notYetCreatedItems)
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (pendingInvoiceStruct) {
|
|
482
|
-
// Compress
|
|
483
|
-
pendingInvoiceStruct.meta.items = STInvoiceItem.compress(pendingInvoiceStruct.meta.items)
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (pendingInvoice?.invoiceId && pendingInvoiceStruct) {
|
|
488
|
-
const invoice = await STInvoice.getByID(pendingInvoice?.invoiceId)
|
|
489
|
-
if (invoice) {
|
|
490
|
-
pendingInvoiceStruct.invoice = await invoice.getStructure()
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
invoices.sort((a, b) => Sorter.byDateValue(a.createdAt, b.createdAt))
|
|
494
|
-
|
|
495
|
-
const invoiceStructures: STInvoiceStruct[] = []
|
|
496
|
-
for (const invoice of invoices) {
|
|
497
|
-
invoiceStructures.push(await invoice.getStructure())
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
const credits = await STCredit.getForOrganization(organization.id)
|
|
501
|
-
|
|
502
|
-
return STBillingStatus.create({
|
|
503
|
-
packages: packages.map(pack => STPackageStruct.create(pack)),
|
|
504
|
-
invoices: invoiceStructures,
|
|
505
|
-
pendingInvoice: pendingInvoiceStruct,
|
|
506
|
-
credits: credits.map(c => STCreditStruct.create(c))
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
78
|
}
|