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

@@ -0,0 +1,28 @@
-- Suppliers get a structured address (street/city/postal_code/country) like
-- customers; the single free-text `address` blob is dropped. Existing data is
-- split best-effort: newlines normalized to ", ", first comma segment ->
-- street, the PSC (3+2 digits) -> postal_code, the remainder -> city.
-- Unparseable one-segment addresses keep their full text in `street`.
-- AlterTable: add the structured columns
ALTER TABLE `sklad_suppliers`
ADD COLUMN `street` VARCHAR(255) NULL AFTER `phone`,
ADD COLUMN `city` VARCHAR(255) NULL AFTER `street`,
ADD COLUMN `postal_code` VARCHAR(20) NULL AFTER `city`,
ADD COLUMN `country` VARCHAR(100) NULL AFTER `postal_code`;
-- Preserve data: best-effort split of the legacy free-text address
UPDATE `sklad_suppliers`
SET
`street` = NULLIF(TRIM(SUBSTRING_INDEX(REPLACE(REPLACE(`address`, '\r', ''), '\n', ', '), ',', 1)), ''),
`postal_code` = REGEXP_SUBSTR(`address`, '[0-9]{3}[ ]?[0-9]{2}'),
`city` = NULLIF(TRIM(BOTH ',' FROM TRIM(REGEXP_REPLACE(
SUBSTRING(
REPLACE(REPLACE(`address`, '\r', ''), '\n', ', '),
CHAR_LENGTH(SUBSTRING_INDEX(REPLACE(REPLACE(`address`, '\r', ''), '\n', ', '), ',', 1)) + 2
),
'[0-9]{3}[ ]?[0-9]{2}', ''))), '')
WHERE `address` IS NOT NULL AND `address` <> '';
-- AlterTable: drop the legacy blob
ALTER TABLE `sklad_suppliers` DROP COLUMN `address`;

View File

@@ -0,0 +1,43 @@
-- Suppliers become a full mirror of the customers model: dedicated
-- contact_person/email/phone/notes columns are replaced by the same
-- custom_fields JSON blob customers use ({"fields":[{name,value,showLabel}],
-- "field_order":[]}). Existing contact data is preserved as custom fields.
-- AlterTable: add the custom_fields blob
ALTER TABLE `sklad_suppliers` ADD COLUMN `custom_fields` LONGTEXT NULL AFTER `country`;
-- Seed an empty container for rows that carry any contact data
UPDATE `sklad_suppliers`
SET `custom_fields` = JSON_OBJECT('fields', JSON_ARRAY(), 'field_order', JSON_ARRAY())
WHERE COALESCE(`contact_person`, '') <> ''
OR COALESCE(`email`, '') <> ''
OR COALESCE(`phone`, '') <> ''
OR COALESCE(`notes`, '') <> '';
-- Preserve data: each legacy column becomes one labeled custom field
UPDATE `sklad_suppliers`
SET `custom_fields` = JSON_ARRAY_APPEND(`custom_fields`, '$.fields',
JSON_OBJECT('name', 'Kontaktní osoba', 'value', `contact_person`, 'showLabel', TRUE))
WHERE COALESCE(`contact_person`, '') <> '';
UPDATE `sklad_suppliers`
SET `custom_fields` = JSON_ARRAY_APPEND(`custom_fields`, '$.fields',
JSON_OBJECT('name', 'E-mail', 'value', `email`, 'showLabel', TRUE))
WHERE COALESCE(`email`, '') <> '';
UPDATE `sklad_suppliers`
SET `custom_fields` = JSON_ARRAY_APPEND(`custom_fields`, '$.fields',
JSON_OBJECT('name', 'Telefon', 'value', `phone`, 'showLabel', TRUE))
WHERE COALESCE(`phone`, '') <> '';
UPDATE `sklad_suppliers`
SET `custom_fields` = JSON_ARRAY_APPEND(`custom_fields`, '$.fields',
JSON_OBJECT('name', 'Poznámky', 'value', `notes`, 'showLabel', TRUE))
WHERE COALESCE(`notes`, '') <> '';
-- AlterTable: drop the dedicated columns
ALTER TABLE `sklad_suppliers`
DROP COLUMN `contact_person`,
DROP COLUMN `email`,
DROP COLUMN `phone`,
DROP COLUMN `notes`;

View File

@@ -804,18 +804,18 @@ model sklad_categories {
}
model sklad_suppliers {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
ico String? @db.VarChar(20)
dic String? @db.VarChar(20)
contact_person String? @db.VarChar(255)
email String? @db.VarChar(255)
phone String? @db.VarChar(50)
address String? @db.Text
notes String? @db.Text
is_active Boolean @default(true)
created_at DateTime? @default(now()) @db.DateTime(0)
modified_at DateTime? @db.DateTime(0)
id Int @id @default(autoincrement())
name String @db.VarChar(255)
ico String? @db.VarChar(20)
dic String? @db.VarChar(20)
street String? @db.VarChar(255)
city String? @db.VarChar(255)
postal_code String? @db.VarChar(20)
country String? @db.VarChar(100)
custom_fields String? @db.LongText
is_active Boolean @default(true)
created_at DateTime? @default(now()) @db.DateTime(0)
modified_at DateTime? @db.DateTime(0)
receipts sklad_receipts[]
issued_orders issued_orders[]