= Домени = == Парични вредности со деловни граници == === Опис === Доменот `positive_money` дефинира валидни парични вредности со деловни граници — не дозволува негативни суми, ниту неразумно големи износи (над 1.000.000). Се користи за сите парични полиња во базата (цени, плаќања, вкупни износи). === Имплементација === {{{ CREATE DOMAIN project.positive_money AS NUMERIC(12,2) CHECK (VALUE >= 0 AND VALUE < 1000000); }}} == ДДВ со законски дозволени стапки == === Опис === Доменот `vat_percentage` дозволува само законски дозволени стапки на ДДВ во Македонија: 0%, 5%, 10% и 18%. Спречува внесување на невалидни стапки како 17% или 19%. === Имплементација === {{{ CREATE DOMAIN project.vat_percentage AS NUMERIC(5,2) CHECK (VALUE IN (0, 5, 10, 18)); }}} == Статус на маса == === Опис === Доменот `table_status` дозволува само однапред дефинирани статуси на маса: СЛОБОДНА, ЗАФАТЕНА, РЕЗЕРВИРАНА, НЕДОСТАПНА. Овозможува конзистентност на статусите низ целата апликација. === Имплементација === {{{ CREATE DOMAIN project.table_status AS TEXT CHECK (VALUE IN ('СЛОБОДНА', 'ЗАФАТЕНА', 'РЕЗЕРВИРАНА', 'НЕДОСТАПНА')); }}} == Статус на нарачка == === Опис === Доменот `order_status` дозволува само валидни статуси на нарачка во текот на нејзиниот животен циклус: НОВА, ПОРАЧАНО, ПОДГОТВЕНО, СЕРВИРАНО, ПЛАТЕНА, ОТКАЖАНА. === Имплементација === {{{ CREATE DOMAIN project.order_status AS TEXT CHECK (VALUE IN ('НОВА', 'ПОРАЧАНО', 'ПОДГОТВЕНО', 'СЕРВИРАНО', 'ПЛАТЕНА', 'ОТКАЖАНА')); }}} == Тип на инвентарна операција == === Опис === Доменот `inventory_operation` дозволува само валидни типови на инвентарни операции: ПОЧЕТНА, ПРИЕМ, ПРОДАЖБА, ОТПИС, КОРЕКЦИЈА. Овозможува конзистентно евидентирање на движењата на залиха. === Имплементација === {{{ CREATE DOMAIN project.inventory_operation AS TEXT CHECK (VALUE IN ('ПОЧЕТНА', 'ПРИЕМ', 'ПРОДАЖБА', 'ОТПИС', 'КОРЕКЦИЈА')); }}} == Комплексна email валидација == === Опис === Доменот `valid_email` валидира email адреси со комплексен regex и дополнителни услови: * Основен regex за валиден формат * Ограничување на должина (макс 255) * Забрана за последователни точки (`..`) * Забрана за започнување или завршување со точка === Имплементација === {{{ CREATE DOMAIN project.valid_email AS VARCHAR(255) CHECK ( VALUE ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' AND LENGTH(VALUE) <= 255 AND POSITION('..' IN VALUE) = 0 AND VALUE !~ '^\.' AND VALUE !~ '\.$' ); }}} == Македонски мобилен телефонски број == === Опис === Доменот `mkd_phone` валидира македонски мобилни телефонски броеви во три дозволени формати: * `+389 7X XXX XXX` (меѓународен формат) * `0 7X XXX XXX` (домашен формат со водечка нула) * `7X XXX XXX` (краток формат) Дозволува опционални празни места помеѓу групите цифри. === Имплементација === {{{ CREATE DOMAIN project.mkd_phone AS TEXT CHECK ( VALUE ~ '^\+389\s?7[0-9]{2}\s?[0-9]{3}\s?[0-9]{3}$' OR VALUE ~ '^0\s?7[0-9]{2}\s?[0-9]{3}\s?[0-9]{3}$' OR VALUE ~ '^7[0-9]{2}\s?[0-9]{3}\s?[0-9]{3}$' ); }}} == bcrypt password hash валидација == === Опис === Доменот `password_hash` валидира bcrypt hash вредности во стандарден формат `$2a$XX$...`. Спречува внесување на plain-text лозинки или невалидни hash вредности. Форматот е: `$2a$` или `$2b$` или `$2y$`, потоа два броја за cost фактор, потоа `$`, потоа 53 карактери од base64 алфабетот (`./A-Za-z0-9`). === Имплементација === {{{ CREATE DOMAIN project.password_hash AS TEXT CHECK (VALUE ~ '^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$'); }}} == Примена на домените на постоечки колони == === Опис === Домените се применуваат на постоечките колони со `ALTER TABLE ... ALTER COLUMN ... TYPE ...`. Ова автоматски ги валидира сите постоечки вредности против новите ограничувања. === Имплементација === {{{ ALTER TABLE project.payment ALTER COLUMN amount TYPE project.positive_money; ALTER TABLE project.product ALTER COLUMN price TYPE project.positive_money; ALTER TABLE project.order_item ALTER COLUMN unit_price TYPE project.positive_money; ALTER TABLE project.settings ALTER COLUMN vat_percent TYPE project.vat_percentage; ALTER TABLE project.app_settings ALTER COLUMN vat_rate TYPE project.vat_percentage; ALTER TABLE project.restaurant_table ALTER COLUMN status TYPE project.table_status; ALTER TABLE project.orders ALTER COLUMN status TYPE project.order_status; ALTER TABLE project.inventory ALTER COLUMN operation_type TYPE project.inventory_operation; ALTER TABLE project.ingredient_inventory ALTER COLUMN operation_type TYPE project.inventory_operation; ALTER TABLE project.app_user ALTER COLUMN email TYPE project.valid_email, ALTER COLUMN password_hash TYPE project.password_hash; }}} == Тестирање == === Успешно внесување === {{{ INSERT INTO project.payment (order_id, amount, method, payment_date) VALUES (1, 100.00, 'КЕШ', NOW()); }}} === Негативна сума (грешка) === {{{ INSERT INTO project.payment (order_id, amount, method, payment_date) VALUES (1, -50.00, 'КЕШ', NOW()); -- ERROR: value for domain positive_money violates check constraint }}} === Преголема сума (грешка) === {{{ INSERT INTO project.payment (order_id, amount, method, payment_date) VALUES (1, 2000000.00, 'КЕШ', NOW()); -- ERROR: value for domain positive_money violates check constraint }}} === Невалиден ДДВ (грешка) === {{{ UPDATE project.settings SET vat_percent = 17 WHERE id = 1; -- ERROR: value for domain vat_percentage violates check constraint }}} === Невалиден статус на нарачка (грешка) === {{{ UPDATE project.orders SET status = 'НЕПОЗНАТО' WHERE order_id = 1; -- ERROR: value for domain order_status violates check constraint }}} === Невалиден email (грешка) === {{{ INSERT INTO project.app_user (username, password_hash, role_id, first_name, last_name, email, active) VALUES ('test', '$2a$06$abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTU', 1, 'Test', 'User', 'not-an-email', TRUE); -- ERROR: value for domain valid_email violates check constraint }}} == Заклучок == Со 8 кориснички дефинирани домени се постигнува: * Централизирана валидација на деловни правила * Конзистентност на податоците низ целата база * Повторна употреба на исти ограничувања на повеќе колони * Полесно одржување — промена на едно место * Подобрена читливост на шемата