Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions directus-cms/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,26 @@ EMAIL_SENDMAIL_NEW_LINE="unix"
# EMAIL_SMTP_USER="noreply@programmier.bar"
# EMAIL_SMTP_PASSWORD=""

# Accounting mailbox: every invoice document (Rechnung, Rechnungsberichtigung, Stornorechnung)
# is mailed here as a PDF attachment, one per mail, for timely recording in the books. Leave empty
# to skip the accounting copy (e.g. locally). See docs/tickets.md.
ACCOUNTING_INVOICE_EMAIL=""

# Dedicated SMTP transport for that copy, NOT the EMAIL_* transport above: the accounting mailbox
# only accepts mail whose envelope sender (MAIL FROM) is its registered sender address, and the
# transactional mail provider always uses its own return path. ACCOUNTING_MAIL_FROM must be that
# registered address (sender and envelope sender). ACCOUNTING_SMTP_SECURE works like
# EMAIL_SMTP_SECURE: "true" for port 465 (implicit TLS), "false" for 587 (STARTTLS); unset, it
# follows the port. Google Workspace: host smtp.gmail.com, port 465, secure true. Use an app
# password and never commit it. If ACCOUNTING_INVOICE_EMAIL is set but any of these is missing, no
# copy is sent and Slack is alerted.
ACCOUNTING_SMTP_HOST=""
ACCOUNTING_SMTP_PORT=""
ACCOUNTING_SMTP_SECURE=""
ACCOUNTING_SMTP_USER=""
ACCOUNTING_SMTP_PASSWORD=""
ACCOUNTING_MAIL_FROM=""

# Password Brevo's transactional webhook sends via Basic auth (POST /brevo-webhook, see
# docs/newsletter.md). Without it the endpoint refuses every event (503). `openssl rand -hex 32`
BREVO_WEBHOOK_SECRET=""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
* Some dependencies (pdfkit, passkit-generator) use Node.js globals like
* `__dirname` or native modules that don't work when bundled into ESM.
* mjml is CommonJS with a large dependency tree (juice, cssnano, html-minifier)
* that is not meant to be bundled either.
* that is not meant to be bundled either; the same goes for nodemailer (CommonJS, used for
* the accounting-mail SMTP transport).
* By marking them as external, they load from node_modules at runtime.
*/
export default {
plugins: [
{
name: 'externalize-native-modules',
resolveId(id) {
if (id === 'pdfkit' || id === 'passkit-generator' || id === 'mjml') {
if (id === 'pdfkit' || id === 'passkit-generator' || id === 'mjml' || id === 'nodemailer') {
return { id, external: true }
}
return null
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@
},
"dependencies": {
"mjml": "^5.4.1",
"nodemailer": "^8.0.5",
"passkit-generator": "^3.5.0",
"pdfkit": "^0.16.0",
"qrcode": "^1.5.4"
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { describe, expect, test } from '@jest/globals'
import { deriveInvoiceActionsState, isExistingItemKey, type InvoiceDocumentRow } from '../invoice-actions-state.ts'
import {
deriveInvoiceActionsState,
describeActionResult,
isExistingItemKey,
type InvoiceDocumentRow,
} from '../invoice-actions-state.ts'

function doc(overrides: Partial<InvoiceDocumentRow> & Pick<InvoiceDocumentRow, 'id' | 'type'>): InvoiceDocumentRow {
return {
Expand Down Expand Up @@ -106,3 +111,42 @@ describe('deriveInvoiceActionsState', () => {
expect(state.showPostIssuanceActions).toBe(true)
})
})

describe('describeActionResult', () => {
test('a correction that reached the customer is a success', () => {
const result = describeActionResult('correction', {
invoice_number: 'PB-CON26-002',
corrects: 'PB-CON26-001',
customer_mail: 'sent',
accounting_mail: 'sent',
})
expect(result.type).toBe('success')
expect(result.text).toContain('Rechnungsberichtigung PB-CON26-002')
expect(result.text).toContain('An den Kunden verschickt.')
expect(result.text).toContain('Kopie an die Buchhaltung verschickt.')
})

test.each(['failed', 'template_missing', undefined])(
'a cancellation whose customer mail is %s is a warning asking for manual dispatch',
(customerMail) => {
const result = describeActionResult('cancellation', {
invoice_number: 'PB-CON26-003',
cancels: 'PB-CON26-001',
customer_mail: customerMail,
accounting_mail: 'sent',
})
expect(result.type).toBe('warning')
expect(result.text).toContain('Stornorechnung PB-CON26-003')
expect(result.text).toContain('manuell an den Kunden')
}
)

test('regenerate never reports a customer mail', () => {
const result = describeActionResult('regenerate', {
invoice_number: 'PB-CON26-001',
accounting_mail: 'skipped',
})
expect(result.type).toBe('success')
expect(result.text).not.toContain('Kunden')
})
})
Loading
Loading