feat: editable billing text on invoices

- Added billing_text column to invoices table (VARCHAR 500)
- Prisma migration: 20260323_add_billing_text
- Form field on invoice create page with placeholder
- PDF uses billing_text, falls back to default translation
- Stored on create and editable on draft invoices

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-03-23 19:47:15 +01:00
parent 2540efbec2
commit c4c4433561
6 changed files with 19 additions and 2 deletions

View File

@@ -0,0 +1,2 @@
-- Add billing_text column to invoices table
ALTER TABLE `invoices` ADD COLUMN `billing_text` VARCHAR(500) NULL AFTER `issued_by`;

View File

@@ -166,6 +166,7 @@ model invoices {
tax_date DateTime? @db.Date tax_date DateTime? @db.Date
paid_date DateTime? @db.Date paid_date DateTime? @db.Date
issued_by String? @db.VarChar(255) issued_by String? @db.VarChar(255)
billing_text String? @db.VarChar(500)
notes String? @db.Text notes String? @db.Text
internal_notes String? @db.Text internal_notes String? @db.Text
created_at DateTime? @default(now()) @db.DateTime(0) created_at DateTime? @default(now()) @db.DateTime(0)

View File

@@ -77,6 +77,7 @@ interface InvoiceForm {
payment_method: string payment_method: string
constant_symbol: string constant_symbol: string
issued_by: string issued_by: string
billing_text: string
notes: string notes: string
bank_account_id: number | string bank_account_id: number | string
bank_name: string bank_name: string
@@ -271,6 +272,7 @@ export default function InvoiceDetail() {
payment_method: 'Příkazem', payment_method: 'Příkazem',
constant_symbol: '0308', constant_symbol: '0308',
issued_by: user?.fullName || '', issued_by: user?.fullName || '',
billing_text: '',
notes: '', notes: '',
bank_account_id: '', bank_account_id: '',
bank_name: '', bank_name: '',
@@ -855,6 +857,16 @@ export default function InvoiceDetail() {
</FormField> </FormField>
</div> </div>
<FormField label="Text fakturace (na PDF)">
<input
type="text"
value={form.billing_text}
onChange={(e) => setForm(prev => ({ ...prev, billing_text: e.target.value }))}
className="admin-form-input"
placeholder="Fakturujeme Vám za: (ponechte prázdné pro výchozí)"
/>
</FormField>
<div className="admin-form-row"> <div className="admin-form-row">
<FormField label="Datum vystavení" error={errors.issue_date} required> <FormField label="Datum vystavení" error={errors.issue_date} required>
<AdminDatePicker mode="date" value={form.issue_date} onChange={(val: string) => { setForm(prev => ({ ...prev, issue_date: val })); setErrors(prev => ({ ...prev, issue_date: '' })) }} /> <AdminDatePicker mode="date" value={form.issue_date} onChange={(val: string) => { setForm(prev => ({ ...prev, issue_date: val })); setErrors(prev => ({ ...prev, issue_date: '' })) }} />

View File

@@ -880,7 +880,7 @@ ${indentCSS}
</div> </div>
<!-- Polozky --> <!-- Polozky -->
<div class="billing-label">${escapeHtml(t.billing)}</div> <div class="billing-label">${escapeHtml(invoice.billing_text || t.billing)}</div>
<table class="items"> <table class="items">
<thead> <thead>
<tr> <tr>

View File

@@ -27,6 +27,7 @@ export const CreateInvoiceSchema = z.object({
due_date: z.string().nullish(), due_date: z.string().nullish(),
tax_date: z.string().nullish(), tax_date: z.string().nullish(),
issued_by: z.string().nullish(), issued_by: z.string().nullish(),
billing_text: z.string().nullish(),
notes: z.string().nullish(), notes: z.string().nullish(),
internal_notes: z.string().nullish(), internal_notes: z.string().nullish(),
items: z.array(InvoiceItemSchema).optional(), items: z.array(InvoiceItemSchema).optional(),

View File

@@ -251,6 +251,7 @@ export async function createInvoice(body: Record<string, any>) {
due_date: body.due_date ? new Date(String(body.due_date)) : null, due_date: body.due_date ? new Date(String(body.due_date)) : null,
tax_date: body.tax_date ? new Date(String(body.tax_date)) : null, tax_date: body.tax_date ? new Date(String(body.tax_date)) : null,
issued_by: body.issued_by ? String(body.issued_by) : null, issued_by: body.issued_by ? String(body.issued_by) : null,
billing_text: body.billing_text ? String(body.billing_text) : null,
notes: body.notes ? String(body.notes) : null, notes: body.notes ? String(body.notes) : null,
internal_notes: body.internal_notes ? String(body.internal_notes) : null, internal_notes: body.internal_notes ? String(body.internal_notes) : null,
}, },
@@ -293,7 +294,7 @@ export async function updateInvoice(id: number, body: Record<string, any>) {
// Only allow full editing in 'issued' state // Only allow full editing in 'issued' state
const isDraft = currentStatus === 'issued'; const isDraft = currentStatus === 'issued';
if (isDraft) { if (isDraft) {
const strFields = ['currency', 'payment_method', 'constant_symbol', 'bank_name', 'bank_swift', 'bank_iban', 'bank_account', 'issued_by']; const strFields = ['currency', 'payment_method', 'constant_symbol', 'bank_name', 'bank_swift', 'bank_iban', 'bank_account', 'issued_by', 'billing_text'];
for (const f of strFields) { for (const f of strFields) {
if (body[f] !== undefined) data[f] = body[f] ? String(body[f]) : null; if (body[f] !== undefined) data[f] = body[f] ? String(body[f]) : null;
} }