fix(crm): PR #603 review follow-ups + Outlook-proof email design
Addresses the maintainer's non-blocking review items + the Outlook email bug:
- invoice create: verify the chosen event belongs to the customer (only when
the event has assignments; legacy unassigned events pass through).
- mark-paid + import: bound paidAt to [2000-01-01, now+30d] so a typo'd year
can't silently drop a payment out of every cash-basis revenue window.
- customer routes: country_code now {min:2,max:2}+isAlpha+uppercase-normalize
(was isString/max:2 — allowed '', '1', '!@'), matching the business-profile
route.
- email transporter: close the previous instance before re-init (leak guard
for a future pooled transport).
- scheduled-email tz: warn loudly when business_hours is set but the profile
timezone is blank (was silently using the server/UTC tz).
- wrapEmailHtml: rebuild the chrome as inline-styled tables + bgcolor and
inline the themed CTA button, so the design survives Outlook/Apple Mail
stripping the head <style> (kept the <style> as progressive enhancement).
This commit is contained in:
@@ -694,6 +694,22 @@ async function createInvoice(payload, adminId, trx = db) {
|
||||
const customer = await trx('customer_accounts').where({ id: payload.customerAccountId }).first();
|
||||
ensureCustomerCanBill(customer);
|
||||
|
||||
// PR #603 review follow-up #1 — when an invoice is attached to an event,
|
||||
// make sure that event actually belongs to the chosen customer. Without
|
||||
// this, a typo'd/copy-pasted eventId silently links the invoice to an
|
||||
// unrelated event, producing misleading reporting links. Only enforced
|
||||
// when the event HAS customer assignments (an event with none — e.g. a
|
||||
// legacy import — is allowed through, since we can't prove a mismatch).
|
||||
if (payload.eventId && await trx.schema.hasTable('event_customer_assignments')) {
|
||||
const assignments = await trx('event_customer_assignments')
|
||||
.where({ event_id: payload.eventId })
|
||||
.select('customer_account_id');
|
||||
if (assignments.length > 0 &&
|
||||
!assignments.some(a => a.customer_account_id === payload.customerAccountId)) {
|
||||
throw new AppError('The selected event is not assigned to this customer', 422, 'EVENT_CUSTOMER_MISMATCH');
|
||||
}
|
||||
}
|
||||
|
||||
// Accumulator intercept (migration 128). For customers in
|
||||
// billing_cadence='monthly' OR 'manual' mode every createInvoice call
|
||||
// APPENDS line items onto a single running draft instead of minting a
|
||||
|
||||
Reference in New Issue
Block a user