feat(suppliers)!: full customers model - structured address + custom fields

The dodavatele modal is now an exact mirror of the customers modal
(minus the PDF field-order picker): Nazev+ARES, Ulice, Mesto+PSC, Zeme,
ICO+ARES, DIC, and the same "Vlastni pole" editor (maxWidth md).

Two migrations on sklad_suppliers:
- structured street/city/postal_code/country replace the free-text
  address blob (best-effort split: street / PSC regex / city)
- custom_fields LONGTEXT replaces contact_person/email/phone/notes;
  existing values are preserved as labeled custom fields

The encode/decode of the custom_fields blob is shared with customers
via the new utils/custom-fields.ts. The PO PDF supplier block renders
the structured address + custom-field lines like the customer block;
the supplier picker/detail selects and types follow. Legacy clients
sending the dropped keys are silently stripped (tested). Suppliers
list shows Ulice/Mesto instead of the dropped contact columns; search
covers name/ico/city.

BREAKING CHANGE: sklad_suppliers.address, contact_person, email,
phone, notes columns dropped (data migrated); deploy must run
prisma migrate deploy + generate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-10 20:11:22 +02:00
parent f1ce76d21d
commit db7a5c3d15
16 changed files with 578 additions and 232 deletions

View File

@@ -8,7 +8,6 @@ import {
nullableIntIdFromForm,
booleanFromForm,
isoDateString,
emailOrEmpty,
} from "./common";
// === Categories ===
@@ -26,26 +25,30 @@ export type CreateCategoryInput = z.infer<typeof CreateCategorySchema>;
export type UpdateCategoryInput = z.infer<typeof UpdateCategorySchema>;
// === Suppliers ===
// Full mirror of the customers model: structured address + custom_fields
// (the dedicated contact_person/email/phone/notes columns and the free-text
// `address` blob were dropped; legacy clients sending them are silently
// stripped by z.object). Limits are DB-aligned.
export const CreateSupplierSchema = z.object({
name: z.string().min(1, "Název je povinný").max(255),
ico: z.string().max(255).nullish(),
dic: z.string().max(255).nullish(),
contact_person: z.string().max(255).nullish(),
email: emailOrEmpty.nullish(),
phone: z.string().max(50).nullish(),
address: z.string().max(5000).nullish(),
notes: z.string().max(5000).nullish(),
street: z.string().max(255).nullish(),
city: z.string().max(255).nullish(),
postal_code: z.string().max(20).nullish(),
country: z.string().max(100).nullish(),
custom_fields: z.array(z.unknown()).max(100).optional(),
is_active: booleanFromForm.optional().default(true),
});
export const UpdateSupplierSchema = z.object({
name: z.string().min(1, "Název je povinný").max(255).optional(),
ico: z.string().max(255).nullish(),
dic: z.string().max(255).nullish(),
contact_person: z.string().max(255).nullish(),
email: emailOrEmpty.nullish(),
phone: z.string().max(50).nullish(),
address: z.string().max(5000).nullish(),
notes: z.string().max(5000).nullish(),
street: z.string().max(255).nullish(),
city: z.string().max(255).nullish(),
postal_code: z.string().max(20).nullish(),
country: z.string().max(100).nullish(),
custom_fields: z.array(z.unknown()).max(100).optional(),
is_active: booleanFromForm.optional(),
});
export type CreateSupplierInput = z.infer<typeof CreateSupplierSchema>;