= Релационен модел — [wiki:WikiStart СменоПланер] = == Релационен дијаграм == [[Image(RelationalModel-SmenoPlaner.svg)]] ''Прикачени датотеки: `RelationalModel-SmenoPlaner.svg`, `Revised_27032026.vpp(Фајлот ми е 800кб)`'' == Документација и дополнителен опис == === Multi-tenant изолација по organization === Секој ентитет кој припаѓа на одредена организација (location, shift_presets, position, users, shift) содржи `organization_id` странски клуч кон табелата `organization`. Ова е свесна архитектурна одлука: наместо посебна база по тенант, сите организации споделуваат иста шема, а изолацијата се спроведува на апликациско ниво преку `organization_id`. Слагот (`slug`) на организацијата е уникатен и служи за URL-friendly идентификација. === UUID примарни клучеви (varchar 36) === Сите примарни клучеви се UUID вредности чувани како `varchar(36)`, наместо auto-increment integer. Причина: UUID-а дозволуваат генерација на ID на страната на апликацијата (без round-trip до базата), погодни се за дистрибуирани системи и спречуваат откривање на бројот на записи преку секвенцијални ID-а. === position е scoped по организација === Табелата `position` содржи `organization_id` и уникатен индекс на `(organization_id, name)`. Ова значи дека две различни организации можат да имаат позиција со исто име, но во рамки на иста организација имињата мора да бидат уникатни. Позициите се поврзани со корисници преку `user_position` (junction табела) и со шаблони преку `shift_preset_position_requirement`. === shift_preset_position_requirement — барања по позиција === Секој шаблон за смена (`shift_presets`) може да дефинира колку вработени од одредена позиција се потребни (`required_count`). CHECK ограничувањето `required_count > 0` гарантира дека вредноста е секогаш позитивна. Уникатниот индекс на `(shift_preset_id, position_id)` спречува дупли барања за иста комбинација. === swap_request_users — junction табела за замена === Наместо колони `requested_by` и `taken_by` директно во `swap_request`, тие се изолирани во посебна табела `swap_request_users`. Причина: `taken_by` е nullable (вработен сè уште не ја прифатил замената), а оваа структура јасно ги разделува двете страни на процесот — кој побарал и кој прифатил. Примарен клуч е `swap_request_id` (1:1 врска), со три странски клучеви: кон `swap_request`, и двапати кон `users`. === CHECK ограничувања на статус полиња === `leave_request.status` и `swap_request.status` се ограничени на вредностите `('pending', 'approved', 'rejected')` преку CHECK ограничувања. Ова го спречува внесот на невалидни статуси директно на ниво на базата, независно од апликациската логика.