diff --git a/app/api/suppliers/route.ts b/app/api/suppliers/route.ts
index 06bfd085..5ea4f21d 100644
--- a/app/api/suppliers/route.ts
+++ b/app/api/suppliers/route.ts
@@ -70,7 +70,8 @@ export const POST = withRouteContext(
clearing_number: body.clearing_number,
account_number: body.account_number,
default_expense_account: body.default_expense_account,
- default_payment_terms: body.default_payment_terms || 30,
+ // ?? not ||: 0 days (betalning direkt) is a value, not a missing one (#2070).
+ default_payment_terms: body.default_payment_terms ?? 30,
default_currency: body.default_currency || 'SEK',
notes: body.notes,
})
diff --git a/components/customers/CustomerForm.tsx b/components/customers/CustomerForm.tsx
index f429162c..5fff4596 100644
--- a/components/customers/CustomerForm.tsx
+++ b/components/customers/CustomerForm.tsx
@@ -86,7 +86,14 @@ export default function CustomerForm({
.optional()
.or(z.literal('')),
language: z.enum(['sv', 'en']).optional(),
- default_payment_terms: z.number().min(1).optional(),
+ // 0 is a real value (betalning direkt). The old min(1) made the form
+ // unsavable on "0" with no message at all (issue #2070); now the rule is
+ // whole days 0-365 and the field says so when it does not hold.
+ default_payment_terms: z
+ .number({ message: t('payment_terms_invalid') })
+ .int(t('payment_terms_invalid'))
+ .min(0, t('payment_terms_invalid'))
+ .max(365, t('payment_terms_invalid')),
notes: z.string().optional(),
}).superRefine((customer, ctx) => {
// Country vs customer type vs VAT prefix (#2025): an EU customer with
@@ -170,7 +177,8 @@ export default function CustomerForm({
vat_number: initialData?.vat_number || '',
personal_number: initialData?.personal_number || '',
language: initialData?.language || 'sv',
- default_payment_terms: initialData?.default_payment_terms || 30,
+ // ?? not ||: a stored 0 (betalning direkt) must not reopen as 30.
+ default_payment_terms: initialData?.default_payment_terms ?? 30,
notes: initialData?.notes || '',
},
})
@@ -538,8 +546,15 @@ export default function CustomerForm({
+
{t('payment_terms_help')}
+ {errors.default_payment_terms && (
+
{errors.default_payment_terms.message}
+ )}
{/* Invoice language */}
diff --git a/components/suppliers/SupplierForm.tsx b/components/suppliers/SupplierForm.tsx
index ad1d5e28..dbf0e865 100644
--- a/components/suppliers/SupplierForm.tsx
+++ b/components/suppliers/SupplierForm.tsx
@@ -70,7 +70,12 @@ export default function SupplierForm({
clearing_number: z.string().optional(),
account_number: z.string().optional(),
default_expense_account: z.string().optional(),
- default_payment_terms: z.number().min(1).optional(),
+ // Whole days 0-365; 0 = betalning direkt (issue #2070, same as CustomerForm).
+ default_payment_terms: z
+ .number({ message: t('default_payment_terms_invalid') })
+ .int(t('default_payment_terms_invalid'))
+ .min(0, t('default_payment_terms_invalid'))
+ .max(365, t('default_payment_terms_invalid')),
default_currency: z.string().optional(),
notes: z.string().optional(),
}), [t])
@@ -103,7 +108,8 @@ export default function SupplierForm({
clearing_number: initialData?.clearing_number || '',
account_number: initialData?.account_number || '',
default_expense_account: initialData?.default_expense_account || '',
- default_payment_terms: initialData?.default_payment_terms || 30,
+ // ?? not ||: a stored 0 (betalning direkt) must not reopen as 30.
+ default_payment_terms: initialData?.default_payment_terms ?? 30,
default_currency: initialData?.default_currency || 'SEK',
notes: initialData?.notes || '',
},
@@ -332,8 +338,15 @@ export default function SupplierForm({
+
{t('default_payment_terms_help')}
+ {errors.default_payment_terms && (
+
{errors.default_payment_terms.message}
+ )}
diff --git a/lib/api/__tests__/schemas.test.ts b/lib/api/__tests__/schemas.test.ts
index 70fc699e..43343440 100644
--- a/lib/api/__tests__/schemas.test.ts
+++ b/lib/api/__tests__/schemas.test.ts
@@ -704,8 +704,13 @@ describe('CreateCustomerSchema', () => {
expect(result.success).toBe(false)
})
- it('rejects zero payment terms', () => {
+ it('accepts zero payment terms (betalning direkt, issue #2070)', () => {
const result = CreateCustomerSchema.safeParse(validCustomer({ default_payment_terms: 0 }))
+ expect(result.success).toBe(true)
+ })
+
+ it('rejects payment terms above 365 days', () => {
+ const result = CreateCustomerSchema.safeParse(validCustomer({ default_payment_terms: 366 }))
expect(result.success).toBe(false)
})
diff --git a/lib/api/schemas.ts b/lib/api/schemas.ts
index aef68368..ab2eca74 100644
--- a/lib/api/schemas.ts
+++ b/lib/api/schemas.ts
@@ -1003,7 +1003,8 @@ export const CreateCustomerSchema = z.object({
.optional()
.nullable(),
language: z.enum(['sv', 'en']).optional(),
- default_payment_terms: z.number().int().positive().optional(),
+ // Whole days 0-365; 0 = betalning direkt / vid mottagande (issue #2070).
+ default_payment_terms: z.number().int().min(0).max(365).optional(),
notes: z.string().optional(),
}).superRefine((customer, ctx) => {
if (customer.personal_number && customer.customer_type !== 'individual') {
@@ -1145,7 +1146,8 @@ export const UpdateCustomerSchema = z.object({
.nullable()
.optional(),
language: z.enum(['sv', 'en']).optional(),
- default_payment_terms: z.number().int().positive().optional(),
+ // Whole days 0-365; 0 = betalning direkt / vid mottagande (issue #2070).
+ default_payment_terms: z.number().int().min(0).max(365).optional(),
notes: z.string().optional(),
}).superRefine((customer, ctx) => {
if (
@@ -1200,7 +1202,8 @@ export const CreateSupplierSchema = z.object({
clearing_number: z.string().optional(),
account_number: z.string().optional(),
default_expense_account: emptyStringAsUndefined(accountNumber),
- default_payment_terms: z.number().int().positive().optional(),
+ // Whole days 0-365; 0 = betalning direkt / vid mottagande (issue #2070).
+ default_payment_terms: z.number().int().min(0).max(365).optional(),
default_currency: CurrencySchema.nullable().optional(),
notes: z.string().optional(),
})
diff --git a/messages/en.json b/messages/en.json
index 75456b3b..6fdbf82f 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -1278,6 +1278,8 @@
"vat_failed_default": "The VAT number could not be verified",
"vat_error_title": "Could not verify VAT number",
"payment_terms_label": "Payment terms (days)",
+ "payment_terms_help": "Days from invoice date to due date. 0 means payment on receipt.",
+ "payment_terms_invalid": "Enter payment terms in whole days, 0 to 365. 0 means payment on receipt.",
"language_label": "Invoice language",
"language_sv": "Swedish",
"language_en": "English",
@@ -1324,6 +1326,8 @@
"clearing_label": "Clearing number",
"account_number_label": "Account number",
"default_payment_terms_label": "Payment terms (days)",
+ "default_payment_terms_help": "Days from invoice date to due date. 0 means payment on receipt.",
+ "default_payment_terms_invalid": "Enter payment terms in whole days, 0 to 365. 0 means payment on receipt.",
"default_account_label": "Default account",
"default_account_clear": "Clear default account",
"default_currency_label": "Default currency",
diff --git a/messages/sv.json b/messages/sv.json
index d9367cdb..f65a7f92 100644
--- a/messages/sv.json
+++ b/messages/sv.json
@@ -1278,6 +1278,8 @@
"vat_failed_default": "VAT-numret kunde inte verifieras",
"vat_error_title": "Kunde inte verifiera VAT-nummer",
"payment_terms_label": "Betalningsvillkor (dagar)",
+ "payment_terms_help": "Dagar från fakturadatum till förfallodag. 0 betyder betalning direkt.",
+ "payment_terms_invalid": "Ange betalningsvillkor i hela dagar, 0 till 365. 0 betyder betalning direkt.",
"language_label": "Fakturaspråk",
"language_sv": "Svenska",
"language_en": "Engelska",
@@ -1324,6 +1326,8 @@
"clearing_label": "Clearingnummer",
"account_number_label": "Kontonummer",
"default_payment_terms_label": "Betalningsvillkor (dagar)",
+ "default_payment_terms_help": "Dagar från fakturadatum till förfallodag. 0 betyder betalning direkt.",
+ "default_payment_terms_invalid": "Ange betalningsvillkor i hela dagar, 0 till 365. 0 betyder betalning direkt.",
"default_account_label": "Standardkonto",
"default_account_clear": "Rensa standardkonto",
"default_currency_label": "Standardvaluta",