This commit is contained in:
Tomas Dvorak
2026-01-26 08:13:18 +01:00
parent aa036b6550
commit dfc079288f
505 changed files with 95755 additions and 5712 deletions
@@ -0,0 +1,20 @@
-- Remove unwanted admin navigation sections
-- This SQL removes navigation items with the specified labels from the admin navigation
DELETE FROM navigation_items
WHERE label IN ('Zařízení', 'Správa zařízení', 'Vybavení', 'Údržba')
AND requires_admin = true;
-- Also remove any child items that might be orphaned
DELETE FROM navigation_items
WHERE parent_id IN (
SELECT id FROM (
SELECT id FROM navigation_items
WHERE label IN ('Zařízení', 'Správa zařízení', 'Vybavení', 'Údržba')
AND requires_admin = true
) AS parent_ids
);
-- Verify removal
SELECT label, page_type, requires_admin FROM navigation_items
WHERE label IN ('Zařízení', 'Správa zařízení', 'Vybavení', 'Údržba');
+14
View File
@@ -38,6 +38,13 @@ func MigrateDB(db *gorm.DB) error {
&models.ContactCategory{},
&models.Contact{},
&models.PageElementConfig{},
&models.Invoice{},
&models.InvoiceItem{},
&models.InvoicePayment{},
&models.InvoiceCustomer{},
&models.InvoiceSettings{},
&models.InvoiceSequence{},
&models.InvoiceTemplate{},
)
if err != nil {
return err
@@ -61,6 +68,13 @@ func MigrateDB(db *gorm.DB) error {
&models.ContactCategory{},
&models.Contact{},
&models.PageElementConfig{},
&models.Invoice{},
&models.InvoiceItem{},
&models.InvoicePayment{},
&models.InvoiceCustomer{},
&models.InvoiceSettings{},
&models.InvoiceSequence{},
&models.InvoiceTemplate{},
)
if err != nil {
return err
@@ -0,0 +1,275 @@
-- Create languages table
CREATE TABLE IF NOT EXISTS languages (
id VARCHAR(5) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
native_name VARCHAR(100) NOT NULL,
code VARCHAR(10) NOT NULL UNIQUE,
is_default BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Create translations table
CREATE TABLE IF NOT EXISTS translations (
id SERIAL PRIMARY KEY,
key VARCHAR(200) NOT NULL,
language_code VARCHAR(10) NOT NULL,
value TEXT NOT NULL,
context VARCHAR(100),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (language_code) REFERENCES languages(code) ON DELETE CASCADE
);
-- Create content_translations table
CREATE TABLE IF NOT EXISTS content_translations (
id SERIAL PRIMARY KEY,
content_type VARCHAR(50) NOT NULL,
content_id INTEGER NOT NULL,
language_code VARCHAR(10) NOT NULL,
title VARCHAR(500),
content TEXT,
excerpt TEXT,
meta_title VARCHAR(200),
meta_description VARCHAR(500),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (language_code) REFERENCES languages(code) ON DELETE CASCADE
);
-- Create user_language_preferences table
CREATE TABLE IF NOT EXISTS user_language_preferences (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL UNIQUE,
language_code VARCHAR(10) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (language_code) REFERENCES languages(code) ON DELETE CASCADE
);
-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_translations_key ON translations(key);
CREATE INDEX IF NOT EXISTS idx_translations_language_key ON translations(language_code, key);
CREATE INDEX IF NOT EXISTS idx_translations_context ON translations(context);
CREATE INDEX IF NOT EXISTS idx_content_translations_content ON content_translations(content_type, content_id);
CREATE INDEX IF NOT EXISTS idx_content_translations_language ON content_translations(language_code);
CREATE INDEX IF NOT EXISTS idx_languages_active ON languages(is_active) WHERE is_active = TRUE;
CREATE INDEX IF NOT EXISTS idx_languages_default ON languages(is_default) WHERE is_default = TRUE;
-- Insert default languages
INSERT INTO languages (id, name, native_name, code, is_default, is_active, sort_order) VALUES
('cs', 'Czech', 'Čeština', 'cs', true, true, 1),
('en', 'English', 'English', 'en', false, true, 2)
ON CONFLICT (code) DO UPDATE SET
name = EXCLUDED.name,
native_name = EXCLUDED.native_name,
is_default = EXCLUDED.is_default,
is_active = EXCLUDED.is_active,
sort_order = EXCLUDED.sort_order;
-- Insert basic Czech translations
INSERT INTO translations (key, language_code, value, context) VALUES
-- Common
('common.welcome_message', 'cs', 'Vítejte v našem fotbalovém klubu', 'common'),
('common.welcome_subtitle', 'cs', 'Oficiální stránky fotbalového klubu', 'common'),
-- Navigation
('nav.home', 'cs', 'Domů', 'navbar'),
('nav.news', 'cs', 'Aktuality', 'navbar'),
('nav.matches', 'cs', 'Zápasy', 'navbar'),
('nav.players', 'cs', 'Hráči', 'navbar'),
('nav.gallery', 'cs', 'Galerie', 'navbar'),
('nav.videos', 'cs', 'Videa', 'navbar'),
('nav.contact', 'cs', 'Kontakt', 'navbar'),
('nav.about', 'cs', 'O klubu', 'navbar'),
('nav.activities', 'cs', 'Aktivity', 'navbar'),
('nav.sponsors', 'cs', 'Sponzoři', 'navbar'),
('nav.articles', 'cs', 'Články', 'navbar'),
('nav.tables', 'cs', 'Tabulky', 'navbar'),
('nav.calendar', 'cs', 'Kalendář', 'navbar'),
('nav.shop', 'cs', 'Obchod', 'navbar'),
-- Common actions
('action.save', 'cs', 'Uložit', 'common'),
('action.cancel', 'cs', 'Zrušit', 'common'),
('action.edit', 'cs', 'Upravit', 'common'),
('action.delete', 'cs', 'Smazat', 'common'),
('action.create', 'cs', 'Vytvořit', 'common'),
('action.update', 'cs', 'Aktualizovat', 'common'),
('action.publish', 'cs', 'Publikovat', 'common'),
('action.unpublish', 'cs', 'Skrýt', 'common'),
('action.search', 'cs', 'Hledat', 'common'),
('action.more', 'cs', 'Více', 'common'),
-- Forms
('form.required', 'cs', 'Toto pole je povinné', 'form'),
('form.email', 'cs', 'E-mail', 'form'),
('form.password', 'cs', 'Heslo', 'form'),
('form.name', 'cs', 'Jméno', 'form'),
('form.message', 'cs', 'Zpráva', 'form'),
('form.submit', 'cs', 'Odeslat', 'form'),
-- Messages
('msg.success', 'cs', 'Operace proběhla úspěšně', 'message'),
('msg.error', 'cs', 'Došlo k chybě', 'message'),
('msg.loading', 'cs', 'Načítám...', 'message'),
('msg.no_data', 'cs', 'Žádná data', 'message'),
-- Admin
('admin.dashboard', 'cs', 'Nástěnka', 'admin'),
('admin.articles', 'cs', 'Články', 'admin'),
('admin.matches', 'cs', 'Zápasy', 'admin'),
('admin.players', 'cs', 'Hráči', 'admin'),
('admin.settings', 'cs', 'Nastavení', 'admin'),
('admin.users', 'cs', 'Uživatelé', 'admin'),
-- Content
('content.read_more', 'cs', 'Číst více', 'content'),
('content.published_at', 'cs', 'Publikováno', 'content'),
('content.updated_at', 'cs', 'Aktualizováno', 'content'),
('content.author', 'cs', 'Autor', 'content'),
-- Matches
('match.date', 'cs', 'Datum', 'match'),
('match.time', 'cs', 'Čas', 'match'),
('match.place', 'cs', 'Místo', 'match'),
('match.result', 'cs', 'Výsledek', 'match'),
('match.score', 'cs', 'Skóre', 'match'),
('match.team_home', 'cs', 'Domácí', 'match'),
('match.team_away', 'cs', 'Hosté', 'match'),
-- Teams
('team.name', 'cs', 'Název týmu', 'team'),
('team.players', 'cs', 'Hráči', 'team'),
('team.coach', 'cs', 'Trenér', 'team'),
('team.category', 'cs', 'Kategorie', 'team'),
-- Gallery
('homepage.gallery', 'cs', 'Galerie', 'gallery'),
('gallery.albums', 'cs', 'Alba', 'gallery'),
('gallery.photos', 'cs', 'Fotky', 'gallery'),
('gallery.view_all', 'cs', 'Zobrazit vše', 'gallery'),
-- Search
('search.placeholder', 'cs', 'Hledat...', 'search'),
('search.results', 'cs', 'Výsledky hledání', 'search'),
('search.no_results', 'cs', 'Nebyly nalezeny žádné výsledky', 'search'),
-- Pagination
('pagination.previous', 'cs', 'Předchozí', 'pagination'),
('pagination.next', 'cs', 'Další', 'pagination'),
('pagination.page', 'cs', 'Strana', 'pagination'),
('pagination.of', 'cs', 'z', 'pagination'),
-- Date/Time
('date.today', 'cs', 'Dnes', 'date'),
('date.yesterday', 'cs', 'Včera', 'date'),
('date.tomorrow', 'cs', 'Zítra', 'date'),
('date.format', 'cs', 'DD.MM.YYYY', 'date'),
('time.format', 'cs', 'HH:mm', 'date')
ON CONFLICT (key, language_code) DO NOTHING;
-- Insert basic English translations
INSERT INTO translations (key, language_code, value, context) VALUES
-- Common
('common.welcome_message', 'en', 'Welcome to our football club', 'common'),
('common.welcome_subtitle', 'en', 'Official website of the football club', 'common'),
-- Navigation
('nav.home', 'en', 'Home', 'navbar'),
('nav.news', 'en', 'News', 'navbar'),
('nav.matches', 'en', 'Matches', 'navbar'),
('nav.players', 'en', 'Players', 'navbar'),
('nav.gallery', 'en', 'Gallery', 'navbar'),
('nav.videos', 'en', 'Videos', 'navbar'),
('nav.contact', 'en', 'Contact', 'navbar'),
('nav.about', 'en', 'About', 'navbar'),
('nav.activities', 'en', 'Activities', 'navbar'),
('nav.sponsors', 'en', 'Sponsors', 'navbar'),
('nav.articles', 'en', 'Articles', 'navbar'),
('nav.tables', 'en', 'Tables', 'navbar'),
('nav.calendar', 'en', 'Calendar', 'navbar'),
('nav.shop', 'en', 'Shop', 'navbar'),
-- Common actions
('action.save', 'en', 'Save', 'common'),
('action.cancel', 'en', 'Cancel', 'common'),
('action.edit', 'en', 'Edit', 'common'),
('action.delete', 'en', 'Delete', 'common'),
('action.create', 'en', 'Create', 'common'),
('action.update', 'en', 'Update', 'common'),
('action.publish', 'en', 'Publish', 'common'),
('action.unpublish', 'en', 'Unpublish', 'common'),
('action.search', 'en', 'Search', 'common'),
('action.more', 'en', 'More', 'common'),
-- Forms
('form.required', 'en', 'This field is required', 'form'),
('form.email', 'en', 'Email', 'form'),
('form.password', 'en', 'Password', 'form'),
('form.name', 'en', 'Name', 'form'),
('form.message', 'en', 'Message', 'form'),
('form.submit', 'en', 'Submit', 'form'),
-- Messages
('msg.success', 'en', 'Operation successful', 'message'),
('msg.error', 'en', 'An error occurred', 'message'),
('msg.loading', 'en', 'Loading...', 'message'),
('msg.no_data', 'en', 'No data', 'message'),
-- Admin
('admin.dashboard', 'en', 'Dashboard', 'admin'),
('admin.articles', 'en', 'Articles', 'admin'),
('admin.matches', 'en', 'Matches', 'admin'),
('admin.players', 'en', 'Players', 'admin'),
('admin.settings', 'en', 'Settings', 'admin'),
('admin.users', 'en', 'Users', 'admin'),
-- Content
('content.read_more', 'en', 'Read more', 'content'),
('content.published_at', 'en', 'Published', 'content'),
('content.updated_at', 'en', 'Updated', 'content'),
('content.author', 'en', 'Author', 'content'),
-- Matches
('match.date', 'en', 'Date', 'match'),
('match.time', 'en', 'Time', 'match'),
('match.place', 'en', 'Place', 'match'),
('match.result', 'en', 'Result', 'match'),
('match.score', 'en', 'Score', 'match'),
('match.team_home', 'en', 'Home', 'match'),
('match.team_away', 'en', 'Away', 'match'),
-- Teams
('team.name', 'en', 'Team name', 'team'),
('team.players', 'en', 'Players', 'team'),
('team.coach', 'en', 'Coach', 'team'),
('team.category', 'en', 'Category', 'team'),
-- Gallery
('homepage.gallery', 'en', 'Gallery', 'gallery'),
('gallery.albums', 'en', 'Albums', 'gallery'),
('gallery.photos', 'en', 'Photos', 'gallery'),
('gallery.view_all', 'en', 'View all', 'gallery'),
-- Search
('search.placeholder', 'en', 'Search...', 'search'),
('search.results', 'en', 'Search results', 'search'),
('search.no_results', 'en', 'No results found', 'search'),
-- Pagination
('pagination.previous', 'en', 'Previous', 'pagination'),
('pagination.next', 'en', 'Next', 'pagination'),
('pagination.page', 'en', 'Page', 'pagination'),
('pagination.of', 'en', 'of', 'pagination'),
-- Date/Time
('date.today', 'en', 'Today', 'date'),
('date.yesterday', 'en', 'Yesterday', 'date'),
('date.tomorrow', 'en', 'Tomorrow', 'date'),
('date.format', 'en', 'MM/DD/YYYY', 'date'),
('time.format', 'en', 'HH:mm', 'date')
ON CONFLICT (key, language_code) DO NOTHING;
@@ -0,0 +1,20 @@
-- Ticket system migration rollback
-- Migration: 20250109000001_add_ticket_system.down.sql
-- Drop views
DROP VIEW IF EXISTS available_tickets_view;
-- Remove ticket-related fields from eshop_orders
ALTER TABLE eshop_orders
DROP COLUMN IF EXISTS ticket_order,
DROP COLUMN IF EXISTS ticket_campaign_id;
-- Drop tables in reverse order of creation
DROP TABLE IF EXISTS ticket_availability;
DROP TABLE IF EXISTS tickets;
DROP TABLE IF EXISTS campaign_ticket_types;
DROP TABLE IF EXISTS ticket_campaigns;
DROP TABLE IF EXISTS ticket_types;
-- Disable UUID extension (optional, usually kept)
-- DROP EXTENSION IF EXISTS "uuid-ossp";
@@ -0,0 +1,190 @@
-- Ticket system migration for match ticketing
-- Migration: 20250109000001_add_ticket_system.up.sql
-- Enable UUID extension if not exists
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Ticket types for different pricing tiers
CREATE TABLE ticket_types (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
name VARCHAR(255) NOT NULL,
description TEXT,
price_cents BIGINT NOT NULL,
currency VARCHAR(10) DEFAULT 'CZK',
color VARCHAR(20) DEFAULT 'primary', -- primary, secondary, success, warning, danger
display_order INTEGER DEFAULT 0,
active BOOLEAN DEFAULT true,
-- Constraints and limits
max_tickets_per_order INTEGER DEFAULT 10,
sale_start_time TIMESTAMP WITH TIME ZONE,
sale_end_time TIMESTAMP WITH TIME ZONE,
-- Access control (optional)
requires_membership BOOLEAN DEFAULT false,
min_age INTEGER,
INDEX idx_ticket_types_active (active),
INDEX idx_ticket_types_order (display_order)
);
-- Ticket campaigns for specific matches or events
CREATE TABLE ticket_campaigns (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
title VARCHAR(255) NOT NULL,
description TEXT,
-- Link to match (can be null for non-match events)
external_match_id VARCHAR(255), -- FACR match ID
competition_code VARCHAR(50),
match_date_time TIMESTAMP WITH TIME ZONE,
home_team VARCHAR(255),
away_team VARCHAR(255),
venue VARCHAR(255),
-- Campaign settings
active BOOLEAN DEFAULT true,
sale_start_time TIMESTAMP WITH TIME ZONE NOT NULL,
sale_end_time TIMESTAMP WITH TIME ZONE NOT NULL,
max_total_tickets INTEGER, -- Overall capacity limit
INDEX idx_ticket_campaigns_active (active),
INDEX idx_ticket_campaigns_match (external_match_id),
INDEX idx_ticket_campaigns_dates (sale_start_time, sale_end_time)
);
-- Link ticket types to campaigns (many-to-many with campaign-specific overrides)
CREATE TABLE campaign_ticket_types (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
campaign_id UUID NOT NULL REFERENCES ticket_campaigns(id) ON DELETE CASCADE,
ticket_type_id UUID NOT NULL REFERENCES ticket_types(id) ON DELETE CASCADE,
-- Campaign-specific overrides
price_cents BIGINT, -- Override default price if set
max_quantity INTEGER, -- Campaign-specific quantity limit
UNIQUE(campaign_id, ticket_type_id),
INDEX idx_campaign_ticket_types_campaign (campaign_id),
INDEX idx_campaign_ticket_types_type (ticket_type_id)
);
-- Ticket instances (actual sold tickets)
CREATE TABLE tickets (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
order_id UUID, -- Link to e-shop order when purchased
campaign_id UUID NOT NULL REFERENCES ticket_campaigns(id),
ticket_type_id UUID NOT NULL REFERENCES ticket_types(id),
-- Ticket holder information
holder_name VARCHAR(255) NOT NULL,
holder_email VARCHAR(255) NOT NULL,
holder_phone VARCHAR(50),
-- Ticket details
quantity INTEGER NOT NULL DEFAULT 1,
unit_price_cents BIGINT NOT NULL,
total_price_cents BIGINT NOT NULL,
currency VARCHAR(10) DEFAULT 'CZK',
-- Status tracking
status VARCHAR(20) DEFAULT 'reserved', -- reserved, paid, cancelled, used
barcode VARCHAR(255) UNIQUE, -- Unique barcode/QR code
qr_code_data TEXT, -- JSON with QR code data
-- Usage tracking
used_at TIMESTAMP WITH TIME ZONE,
used_by VARCHAR(255), -- Who validated the ticket
INDEX idx_tickets_order (order_id),
INDEX idx_tickets_campaign (campaign_id),
INDEX idx_tickets_status (status),
INDEX idx_tickets_barcode (barcode),
INDEX idx_tickets_holder_email (holder_email)
);
-- Ticket availability tracking per campaign/type
CREATE TABLE ticket_availability (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
campaign_id UUID NOT NULL REFERENCES ticket_campaigns(id) ON DELETE CASCADE,
ticket_type_id UUID NOT NULL REFERENCES ticket_types(id) ON DELETE CASCADE,
total_capacity INTEGER NOT NULL DEFAULT 0,
sold_quantity INTEGER NOT NULL DEFAULT 0,
reserved_quantity INTEGER NOT NULL DEFAULT 0,
available_quantity INTEGER GENERATED ALWAYS AS (total_capacity - sold_quantity - reserved_quantity) STORED,
UNIQUE(campaign_id, ticket_type_id),
INDEX idx_ticket_availability_campaign (campaign_id),
INDEX idx_ticket_availability_type (ticket_type_id)
);
-- Add ticket-related fields to existing e-shop orders
ALTER TABLE eshop_orders
ADD COLUMN ticket_order BOOLEAN DEFAULT false,
ADD COLUMN ticket_campaign_id UUID REFERENCES ticket_campaigns(id);
-- Create indexes for new order fields
CREATE INDEX idx_eshop_orders_ticket_order ON eshop_orders(ticket_order);
CREATE INDEX idx_eshop_orders_ticket_campaign ON eshop_orders(ticket_campaign_id);
-- Insert default ticket types
INSERT INTO ticket_types (name, description, price_cents, color, display_order) VALUES
('Dospělý', 'Vstupenka pro dospělého', 15000, 'primary', 1),
('Dítě do 15 let', 'Vstupenka pro děti do 15 let', 5000, 'success', 2),
('Student', 'Vstupenka pro studenty (platí ISIC)', 8000, 'warning', 3),
('Senior', 'Vstupenka pro seniory nad 65 let', 8000, 'secondary', 4),
('VIP', 'VIP vstupenka s občerstvením', 50000, 'danger', 5);
-- Create view for available tickets with campaign info
CREATE VIEW available_tickets_view AS
SELECT
c.id as campaign_id,
c.title as campaign_title,
c.description as campaign_description,
c.external_match_id,
c.competition_code,
c.match_date_time,
c.home_team,
c.away_team,
c.venue,
c.sale_start_time,
c.sale_end_time,
tt.id as ticket_type_id,
tt.name as ticket_type_name,
tt.description as ticket_type_description,
COALESCE(ctt.price_cents, tt.price_cents) as price_cents,
COALESCE(ctt.max_quantity, tt.max_tickets_per_order) as max_per_order,
tt.color,
ta.available_quantity,
ta.total_capacity,
CASE
WHEN NOW() < c.sale_start_time THEN 'upcoming'
WHEN NOW() > c.sale_end_time THEN 'ended'
WHEN ta.available_quantity <= 0 THEN 'sold_out'
ELSE 'available'
END as sale_status
FROM ticket_campaigns c
JOIN campaign_ticket_types ctt ON c.id = ctt.campaign_id
JOIN ticket_types tt ON ctt.ticket_type_id = tt.id
LEFT JOIN ticket_availability ta ON c.id = ta.campaign_id AND tt.id = ta.ticket_type_id
WHERE c.active = true
AND tt.active = true
AND c.deleted_at IS NULL
AND tt.deleted_at IS NULL;
@@ -0,0 +1,22 @@
-- Drop financial management tables
-- Migration: 20250110000001_create_financial_tables
DROP TRIGGER IF EXISTS update_financial_settings_updated_at ON financial_settings;
DROP TRIGGER IF EXISTS update_financial_reports_updated_at ON financial_reports;
DROP TRIGGER IF EXISTS update_expense_documents_updated_at ON expense_documents;
DROP TRIGGER IF EXISTS update_expenses_updated_at ON expenses;
DROP TRIGGER IF EXISTS update_sponsorship_documents_updated_at ON sponsorship_documents;
DROP TRIGGER IF EXISTS update_sponsorship_payments_updated_at ON sponsorship_payments;
DROP TRIGGER IF EXISTS update_sponsorships_updated_at ON sponsorships;
DROP TRIGGER IF EXISTS update_budgets_updated_at ON budgets;
DROP FUNCTION IF EXISTS update_updated_at_column();
DROP TABLE IF EXISTS financial_settings;
DROP TABLE IF EXISTS financial_reports;
DROP TABLE IF EXISTS expense_documents;
DROP TABLE IF EXISTS expenses;
DROP TABLE IF EXISTS sponsorship_documents;
DROP TABLE IF EXISTS sponsorship_payments;
DROP TABLE IF EXISTS sponsorships;
DROP TABLE IF EXISTS budgets;
@@ -0,0 +1,259 @@
-- Create financial management tables
-- Migration: 20250110000001_create_financial_tables
-- Budgets table
CREATE TABLE budgets (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
yearly_limit DECIMAL(12,2),
monthly_limit DECIMAL(12,2),
current_spend DECIMAL(12,2) DEFAULT 0,
fiscal_year INTEGER,
start_date TIMESTAMP,
end_date TIMESTAMP,
active BOOLEAN DEFAULT true,
alert_threshold DECIMAL(5,2) DEFAULT 80,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
CREATE INDEX idx_budgets_category ON budgets(category);
CREATE INDEX idx_budgets_fiscal_year ON budgets(fiscal_year);
CREATE INDEX idx_budgets_active ON budgets(active);
-- Sponsorships table
CREATE TABLE sponsorships (
id SERIAL PRIMARY KEY,
sponsor_name VARCHAR(255) NOT NULL,
sponsor_logo VARCHAR(500),
contact_person VARCHAR(255),
contact_email VARCHAR(255),
contact_phone VARCHAR(50),
contract_number VARCHAR(100) UNIQUE,
contract_type VARCHAR(100),
total_value DECIMAL(12,2),
payment_schedule VARCHAR(100),
currency VARCHAR(3) DEFAULT 'CZK',
start_date TIMESTAMP,
end_date TIMESTAMP,
auto_renewal BOOLEAN DEFAULT false,
renewal_notice INTEGER DEFAULT 90,
benefits TEXT,
obligations TEXT,
status VARCHAR(50) DEFAULT 'active',
last_payment_date TIMESTAMP,
next_payment_date TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
CREATE INDEX idx_sponsorships_status ON sponsorships(status);
CREATE INDEX idx_sponsorships_contract_number ON sponsorships(contract_number);
CREATE INDEX idx_sponsorships_end_date ON sponsorships(end_date);
-- Sponsorship payments table
CREATE TABLE sponsorship_payments (
id SERIAL PRIMARY KEY,
sponsorship_id INTEGER NOT NULL REFERENCES sponsorships(id) ON DELETE CASCADE,
amount DECIMAL(12,2),
currency VARCHAR(3) DEFAULT 'CZK',
payment_date TIMESTAMP,
payment_method VARCHAR(100),
reference_number VARCHAR(255),
status VARCHAR(50) DEFAULT 'received',
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
CREATE INDEX idx_sponsorship_payments_sponsorship_id ON sponsorship_payments(sponsorship_id);
CREATE INDEX idx_sponsorship_payments_payment_date ON sponsorship_payments(payment_date);
CREATE INDEX idx_sponsorship_payments_status ON sponsorship_payments(status);
-- Sponsorship documents table
CREATE TABLE sponsorship_documents (
id SERIAL PRIMARY KEY,
sponsorship_id INTEGER NOT NULL REFERENCES sponsorships(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
type VARCHAR(100),
file_name VARCHAR(500),
file_path VARCHAR(500),
file_size BIGINT,
mime_type VARCHAR(100),
description TEXT,
version VARCHAR(20) DEFAULT '1.0',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
CREATE INDEX idx_sponsorship_documents_sponsorship_id ON sponsorship_documents(sponsorship_id);
CREATE INDEX idx_sponsorship_documents_type ON sponsorship_documents(type);
-- Expenses table
CREATE TABLE expenses (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
subcategory VARCHAR(100),
amount DECIMAL(12,2),
currency VARCHAR(3) DEFAULT 'CZK',
vat_rate DECIMAL(5,2) DEFAULT 21,
vat_amount DECIMAL(12,2),
total_amount DECIMAL(12,2),
expense_date TIMESTAMP,
payment_method VARCHAR(100),
has_receipt BOOLEAN DEFAULT false,
receipt_data TEXT,
receipt_image VARCHAR(500),
status VARCHAR(50) DEFAULT 'pending',
approved_by INTEGER,
approved_at TIMESTAMP,
rejection_reason TEXT,
budget_id INTEGER REFERENCES budgets(id),
team_id INTEGER,
project_id INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
CREATE INDEX idx_expenses_category ON expenses(category);
CREATE INDEX idx_expenses_status ON expenses(status);
CREATE INDEX idx_expenses_expense_date ON expenses(expense_date);
CREATE INDEX idx_expenses_budget_id ON expenses(budget_id);
CREATE INDEX idx_expenses_team_id ON expenses(team_id);
CREATE INDEX idx_expenses_created_by ON expenses(created_by);
-- Expense documents table
CREATE TABLE expense_documents (
id SERIAL PRIMARY KEY,
expense_id INTEGER NOT NULL REFERENCES expenses(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
type VARCHAR(100),
file_name VARCHAR(500),
file_path VARCHAR(500),
file_size BIGINT,
mime_type VARCHAR(100),
ocr_data TEXT,
ocr_accuracy DECIMAL(5,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
CREATE INDEX idx_expense_documents_expense_id ON expense_documents(expense_id);
CREATE INDEX idx_expense_documents_type ON expense_documents(type);
-- Financial reports table
CREATE TABLE financial_reports (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
type VARCHAR(100),
period VARCHAR(50),
report_data TEXT,
summary TEXT,
file_path VARCHAR(500),
generated_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
CREATE INDEX idx_financial_reports_type ON financial_reports(type);
CREATE INDEX idx_financial_reports_period ON financial_reports(period);
CREATE INDEX idx_financial_reports_generated_at ON financial_reports(generated_at);
-- Financial settings table
CREATE TABLE financial_settings (
id SERIAL PRIMARY KEY,
default_currency VARCHAR(3) DEFAULT 'CZK',
default_vat_rate DECIMAL(5,2) DEFAULT 21,
fiscal_year_start VARCHAR(10) DEFAULT '01-01',
expense_approval_required BOOLEAN DEFAULT true,
max_expense_auto_approve DECIMAL(12,2) DEFAULT 1000,
budget_alert_enabled BOOLEAN DEFAULT true,
budget_alert_threshold DECIMAL(5,2) DEFAULT 80,
sponsorship_alert_enabled BOOLEAN DEFAULT true,
ocr_service_enabled BOOLEAN DEFAULT true,
ocr_provider VARCHAR(50) DEFAULT 'tesseract',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_by INTEGER
);
-- Insert default financial settings
INSERT INTO financial_settings (
default_currency,
default_vat_rate,
fiscal_year_start,
expense_approval_required,
max_expense_auto_approve,
budget_alert_enabled,
budget_alert_threshold,
sponsorship_alert_enabled,
ocr_service_enabled,
ocr_provider
) VALUES (
'CZK',
21.0,
'01-01',
true,
1000.00,
true,
80.0,
true,
true,
'tesseract'
);
-- Create default budget categories for a typical football club
INSERT INTO budgets (
name,
description,
category,
yearly_limit,
monthly_limit,
fiscal_year,
start_date,
end_date,
active
) VALUES
('Týmové provoz', 'Mzdy hráčů, trenérů a realizačního týmu', 'Týmové provoz', 500000.00, 41667.00, 2025, '2025-01-01 00:00:00', '2025-12-31 23:59:59', true),
('Stadion a zařízení', 'Údržba stadionu, energie, voda, odpady', 'Stadion', 200000.00, 16667.00, 2025, '2025-01-01 00:00:00', '2025-12-31 23:59:59', true),
('Cestování', 'Ubytování, doprava na zápasy a soustředění', 'Cestování', 150000.00, 12500.00, 2025, '2025-01-01 00:00:00', '2025-12-31 23:59:59', true),
('Materiál a vybavení', 'Sportovní vybavení, dresy, míče', 'Materiál', 100000.00, 8333.00, 2025, '2025-01-01 00:00:00', '2025-12-31 23:59:59', true),
('Marketing a reklama', 'Propagace, reklama, sociální sítě', 'Marketing', 80000.00, 6667.00, 2025, '2025-01-01 00:00:00', '2025-12-31 23:59:59', true),
('Administrativa', 'Kancelářské potřeby, software, právní služby', 'Administrativa', 50000.00, 4167.00, 2025, '2025-01-01 00:00:00', '2025-12-31 23:59:59', true);
-- Create trigger to update updated_at column
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
-- Apply the trigger to all financial tables
CREATE TRIGGER update_budgets_updated_at BEFORE UPDATE ON budgets FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_sponsorships_updated_at BEFORE UPDATE ON sponsorships FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_sponsorship_payments_updated_at BEFORE UPDATE ON sponsorship_payments FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_sponsorship_documents_updated_at BEFORE UPDATE ON sponsorship_documents FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_expenses_updated_at BEFORE UPDATE ON expenses FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_expense_documents_updated_at BEFORE UPDATE ON expense_documents FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_financial_reports_updated_at BEFORE UPDATE ON financial_reports FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_financial_settings_updated_at BEFORE UPDATE ON financial_settings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
@@ -0,0 +1,20 @@
-- Drop invoice management tables
-- Migration: 20250110000002_create_invoice_tables
DROP TRIGGER IF EXISTS update_invoice_payments_updated_at ON invoice_payments;
DROP TRIGGER IF EXISTS update_invoice_items_updated_at ON invoice_items;
DROP TRIGGER IF EXISTS update_invoices_updated_at ON invoices;
DROP TRIGGER IF EXISTS update_invoice_templates_updated_at ON invoice_templates;
DROP TRIGGER IF EXISTS update_invoice_sequences_updated_at ON invoice_sequences;
DROP TRIGGER IF EXISTS update_invoice_customers_updated_at ON invoice_customers;
DROP TRIGGER IF EXISTS update_invoice_settings_updated_at ON invoice_settings;
DROP FUNCTION IF EXISTS update_updated_at_column();
DROP TABLE IF EXISTS invoice_payments;
DROP TABLE IF EXISTS invoice_items;
DROP TABLE IF EXISTS invoices;
DROP TABLE IF EXISTS invoice_templates;
DROP TABLE IF EXISTS invoice_sequences;
DROP TABLE IF EXISTS invoice_customers;
DROP TABLE IF EXISTS invoice_settings;
@@ -0,0 +1,406 @@
-- Create invoice management tables
-- Migration: 20250110000002_create_invoice_tables
-- Invoice settings table
CREATE TABLE invoice_settings (
id SERIAL PRIMARY KEY,
company_name VARCHAR(255) NOT NULL,
company_ico VARCHAR(20) NOT NULL,
company_dic VARCHAR(20),
company_address TEXT,
company_city VARCHAR(100),
company_zip VARCHAR(10),
company_country VARCHAR(100) DEFAULT 'Česká republika',
bank_name VARCHAR(255),
bank_account VARCHAR(50),
bank_iban VARCHAR(50),
bank_swift VARCHAR(20),
invoice_number_format VARCHAR(100) DEFAULT 'F{year}{seq:6}',
next_invoice_number INTEGER DEFAULT 1,
current_year INTEGER,
default_payment_term INTEGER DEFAULT 14,
default_vat_rate DECIMAL(5,2) DEFAULT 21.0,
default_currency VARCHAR(3) DEFAULT 'CZK',
email_from VARCHAR(255),
email_subject VARCHAR(255) DEFAULT 'Faktura č. {invoice_number}',
email_body TEXT,
pdf_logo_path VARCHAR(500),
pdf_footer TEXT,
registration_number VARCHAR(50),
tax_registration_number VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_by INTEGER
);
-- Invoice customers table
CREATE TABLE invoice_customers (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
ico VARCHAR(20) UNIQUE,
dic VARCHAR(20),
address TEXT,
city VARCHAR(100),
zip VARCHAR(10),
country VARCHAR(100) DEFAULT 'Česká republika',
email VARCHAR(255),
phone VARCHAR(50),
website VARCHAR(255),
business_type VARCHAR(100),
vat_payer BOOLEAN DEFAULT true,
notes TEXT,
active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
CREATE INDEX idx_invoice_customers_ico ON invoice_customers(ico);
CREATE INDEX idx_invoice_customers_active ON invoice_customers(active);
CREATE INDEX idx_invoice_customers_name ON invoice_customers(name);
-- Invoice sequences table
CREATE TABLE invoice_sequences (
id SERIAL PRIMARY KEY,
type VARCHAR(50) NOT NULL,
year INTEGER NOT NULL,
current_number INTEGER DEFAULT 1,
prefix VARCHAR(20),
suffix VARCHAR(20),
padding INTEGER DEFAULT 6,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(type, year)
);
-- Insert default sequences
INSERT INTO invoice_sequences (type, year, current_number, prefix) VALUES
('faktura', EXTRACT(YEAR FROM CURRENT_DATE), 1, 'F'),
('zalohova_faktura', EXTRACT(YEAR FROM CURRENT_DATE), 1, 'ZF'),
('proforma_faktura', EXTRACT(YEAR FROM CURRENT_DATE), 1, 'PF'),
('dobropis', EXTRACT(YEAR FROM CURRENT_DATE), 1, 'D');
-- Invoice templates table
CREATE TABLE invoice_templates (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
description TEXT,
header_html TEXT,
body_html TEXT,
footer_html TEXT,
css TEXT,
default_vat_rate DECIMAL(5,2) DEFAULT 21.0,
default_payment_term INTEGER DEFAULT 14,
active BOOLEAN DEFAULT true,
default BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
-- Invoices table
CREATE TABLE invoices (
id SERIAL PRIMARY KEY,
invoice_number VARCHAR(50) NOT NULL UNIQUE,
invoice_type VARCHAR(20) DEFAULT 'faktura',
variable_symbol VARCHAR(20),
constant_symbol VARCHAR(20),
specific_symbol VARCHAR(20),
issue_date TIMESTAMP NOT NULL,
due_date TIMESTAMP NOT NULL,
taxable_supply_date TIMESTAMP,
-- Supplier information (auto-filled)
supplier_name VARCHAR(255) NOT NULL,
supplier_ico VARCHAR(20),
supplier_dic VARCHAR(20),
supplier_address TEXT,
supplier_city VARCHAR(100),
supplier_zip VARCHAR(10),
supplier_country VARCHAR(100) DEFAULT 'Česká republika',
-- Supplier bank information
bank_name VARCHAR(255),
bank_account VARCHAR(50),
bank_iban VARCHAR(50),
bank_swift VARCHAR(20),
-- Customer information
customer_id INTEGER REFERENCES invoice_customers(id),
customer_name VARCHAR(255) NOT NULL,
customer_ico VARCHAR(20),
customer_dic VARCHAR(20),
customer_address TEXT,
customer_city VARCHAR(100),
customer_zip VARCHAR(10),
customer_country VARCHAR(100) DEFAULT 'Česká republika',
customer_email VARCHAR(255),
customer_phone VARCHAR(50),
-- Financial summary
total_amount DECIMAL(15,2) NOT NULL,
total_vat DECIMAL(15,2) NOT NULL,
total_amount_vat DECIMAL(15,2) NOT NULL,
total_amount_without_vat DECIMAL(15,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'CZK',
-- Status and workflow
status VARCHAR(20) DEFAULT 'draft',
payment_status VARCHAR(20) DEFAULT 'unpaid',
payment_date TIMESTAMP,
paid_amount DECIMAL(15,2) DEFAULT 0,
-- Additional information
note TEXT,
payment_note TEXT,
internal_note TEXT,
-- PDF and sending
pdf_path VARCHAR(500),
pdf_generated_at TIMESTAMP,
sent_at TIMESTAMP,
sent_to TEXT,
-- Metadata
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
CREATE INDEX idx_invoices_invoice_number ON invoices(invoice_number);
CREATE INDEX idx_invoices_status ON invoices(status);
CREATE INDEX idx_invoices_payment_status ON invoices(payment_status);
CREATE INDEX idx_invoices_customer_id ON invoices(customer_id);
CREATE INDEX idx_invoices_issue_date ON invoices(issue_date);
CREATE INDEX idx_invoices_due_date ON invoices(due_date);
CREATE INDEX idx_invoices_customer_ico ON invoices(customer_ico);
-- Invoice items table
CREATE TABLE invoice_items (
id SERIAL PRIMARY KEY,
invoice_id INTEGER NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
description TEXT NOT NULL,
quantity DECIMAL(12,3) NOT NULL,
unit VARCHAR(20) DEFAULT 'ks',
unit_price DECIMAL(15,2) NOT NULL,
total_price DECIMAL(15,2) NOT NULL,
vat_rate DECIMAL(5,2) NOT NULL,
vat_amount DECIMAL(15,2) NOT NULL,
total_with_vat DECIMAL(15,2) NOT NULL,
code VARCHAR(100),
note TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_invoice_items_invoice_id ON invoice_items(invoice_id);
-- Invoice payments table
CREATE TABLE invoice_payments (
id SERIAL PRIMARY KEY,
invoice_id INTEGER NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
amount DECIMAL(15,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'CZK',
payment_date TIMESTAMP NOT NULL,
payment_method VARCHAR(50),
variable_symbol VARCHAR(20),
constant_symbol VARCHAR(20),
specific_symbol VARCHAR(20),
bank_account VARCHAR(50),
note TEXT,
reference_number VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER
);
CREATE INDEX idx_invoice_payments_invoice_id ON invoice_payments(invoice_id);
CREATE INDEX idx_invoice_payments_payment_date ON invoice_payments(payment_date);
-- Insert default invoice settings
INSERT INTO invoice_settings (
company_name,
company_ico,
company_dic,
company_address,
company_city,
company_zip,
company_country,
bank_name,
bank_account,
bank_iban,
bank_swift,
invoice_number_format,
next_invoice_number,
current_year,
default_payment_term,
default_vat_rate,
default_currency,
email_subject,
registration_number,
tax_registration_number
) VALUES (
'Fotbalový klub',
'12345678',
'CZ12345678',
'Sportovní 1',
'Praha',
'11000',
'Česká republika',
'Česká spořitelna',
'123456789/0800',
'CZ650800000000123456789',
'GIBACZPX',
'F{year}{seq:6}',
1,
EXTRACT(YEAR FROM CURRENT_DATE)::INTEGER,
14,
21.0,
'CZK',
'Faktura č. {invoice_number}',
'Spisová značka: 12345',
'DIČ: CZ12345678'
);
-- Insert default invoice template
INSERT INTO invoice_templates (
name,
type,
description,
header_html,
body_html,
footer_html,
css,
default_vat_rate,
default_payment_term,
active,
default
) VALUES (
'Standardní faktura',
'standard',
'Standardní šablona pro faktury',
'<div class="invoice-header">
<div class="logo">
<img src="{logo_url}" alt="Logo" />
</div>
<div class="supplier-info">
<h2>{supplier_name}</h2>
<p>{supplier_address}</p>
<p>{supplier_zip} {supplier_city}</p>
<p>IČO: {supplier_ico}</p>
{supplier_dic ? `<p>DIČ: {supplier_dic}</p>` : ''}
</div>
<div class="invoice-info">
<h1>Faktura</h1>
<p>Číslo: {invoice_number}</p>
<p>Datum vydání: {issue_date}</p>
<p>Datum splatnosti: {due_date}</p>
<p> Datum zdanitelného plnění: {taxable_supply_date}</p>
</div>
</div>',
'<div class="invoice-body">
<div class="customer-info">
<h3>Odběratel</h3>
<p><strong>{customer_name}</strong></p>
{customer_ico ? `<p>IČO: {customer_ico}</p>` : ''}
{customer_dic ? `<p>DIČ: {customer_dic}</p>` : ''}
<p>{customer_address}</p>
<p>{customer_zip} {customer_city}</p>
{customer_country !== 'Česká republika' ? `<p>{customer_country}</p>` : ''}
</div>
<div class="invoice-items">
<table>
<thead>
<tr>
<th>Položka</th>
<th>Množství</th>
<th>Jedn. cena</th>
<th>DPH %</th>
<th>Celkem</th>
</tr>
</thead>
<tbody>
{items}
</tbody>
</table>
</div>
<div class="invoice-summary">
<table>
<tr>
<td>Celkem bez DPH:</td>
<td class="amount">{total_amount_without_vat}</td>
</tr>
<tr>
<td>DPH {total_vat_rate}%:</td>
<td class="amount">{total_vat}</td>
</tr>
<tr class="total">
<td>Celkem k úhradě:</td>
<td class="amount">{total_amount_vat}</td>
</tr>
</table>
</div>
</div>',
'<div class="invoice-footer">
<div class="payment-info">
<h3>Platební údaje</h3>
<p><strong>Banka:</strong> {bank_name}</p>
<p><strong>Číslo účtu:</strong> {bank_account}</p>
<p><strong>IBAN:</strong> {bank_iban}</p>
<p><strong>SWIFT:</strong> {bank_swift}</p>
{variable_symbol ? `<p><strong>Var. symbol:</strong> {variable_symbol}</p>` : ''}
{constant_symbol ? `<p><strong>Konst. symbol:</strong> {constant_symbol}</p>` : ''}
</div>
<div class="notes">
{payment_note ? `<p><strong>Poznámka:</strong> {payment_note}</p>` : ''}
{note ? `<p>{note}</p>` : ''}
</div>
<div class="signature">
<p>Vystavil:</p>
<div class="signature-line"></div>
</div>
</div>',
'body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
.invoice-header { display: flex; justify-content: space-between; margin-bottom: 40px; }
.logo img { max-height: 80px; }
.invoice-info { text-align: right; }
.invoice-info h1 { margin: 0; font-size: 24px; }
.invoice-body { margin-bottom: 40px; }
.customer-info { margin-bottom: 30px; }
.invoice-items table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
.invoice-items th, .invoice-items td { border: 1px solid #ddd; padding: 8px; text-align: left; }
.invoice-items th { background-color: #f5f5f5; }
.invoice-items .amount { text-align: right; }
.invoice-summary table { width: 300px; margin-left: auto; }
.invoice-summary td { padding: 5px; }
.invoice-summary .total { font-weight: bold; border-top: 2px solid #333; }
.invoice-footer { margin-top: 40px; }
.payment-info { margin-bottom: 20px; }
.signature-line { border-bottom: 1px solid #333; width: 200px; margin-top: 40px; }',
21.0,
14,
true,
true
);
-- Create trigger to update updated_at column
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
-- Apply the trigger to all invoice tables
CREATE TRIGGER update_invoice_settings_updated_at BEFORE UPDATE ON invoice_settings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_invoice_customers_updated_at BEFORE UPDATE ON invoice_customers FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_invoice_sequences_updated_at BEFORE UPDATE ON invoice_sequences FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_invoice_templates_updated_at BEFORE UPDATE ON invoice_templates FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_invoices_updated_at BEFORE UPDATE ON invoices FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_invoice_items_updated_at BEFORE UPDATE ON invoice_items FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_invoice_payments_updated_at BEFORE UPDATE ON invoice_payments FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
@@ -0,0 +1,16 @@
CREATE TABLE qr_codes (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
target_url VARCHAR(500) NOT NULL,
qr_code_url VARCHAR(500),
scan_count INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Indexes for performance
CREATE INDEX idx_qr_codes_created_at ON qr_codes(created_at);
CREATE INDEX idx_qr_codes_is_active ON qr_codes(is_active);
CREATE INDEX idx_qr_codes_target_url ON qr_codes(target_url);
@@ -0,0 +1,147 @@
-- Financial management tables
-- Budgets table
CREATE TABLE budgets (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100) NOT NULL,
yearly_limit DECIMAL(12,2),
monthly_limit DECIMAL(12,2),
current_spend DECIMAL(12,2) DEFAULT 0,
fiscal_year INTEGER NOT NULL,
start_date TIMESTAMP NOT NULL,
end_date TIMESTAMP NOT NULL,
active BOOLEAN DEFAULT true,
alert_threshold DECIMAL(5,2) DEFAULT 80.00,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
-- Sponsorships table
CREATE TABLE sponsorships (
id SERIAL PRIMARY KEY,
sponsor_name VARCHAR(255) NOT NULL,
sponsor_logo VARCHAR(500),
contact_person VARCHAR(255),
contact_email VARCHAR(255),
contact_phone VARCHAR(50),
contract_number VARCHAR(100) UNIQUE,
contract_type VARCHAR(100),
total_value DECIMAL(12,2),
payment_schedule VARCHAR(100) DEFAULT 'Měsíčně',
currency VARCHAR(3) DEFAULT 'CZK',
start_date TIMESTAMP NOT NULL,
end_date TIMESTAMP NOT NULL,
auto_renewal BOOLEAN DEFAULT false,
renewal_notice INTEGER DEFAULT 90,
benefits TEXT,
obligations TEXT,
status VARCHAR(50) DEFAULT 'active',
last_payment_date TIMESTAMP,
next_payment_date TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
-- Sponsorship payments table
CREATE TABLE sponsorship_payments (
id SERIAL PRIMARY KEY,
sponsorship_id INTEGER NOT NULL REFERENCES sponsorships(id) ON DELETE CASCADE,
amount DECIMAL(12,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'CZK',
payment_date TIMESTAMP NOT NULL,
payment_method VARCHAR(100),
reference_number VARCHAR(255),
status VARCHAR(50) DEFAULT 'received',
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
-- Sponsorship documents table
CREATE TABLE sponsorship_documents (
id SERIAL PRIMARY KEY,
sponsorship_id INTEGER NOT NULL REFERENCES sponsorships(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
type VARCHAR(100),
file_name VARCHAR(500),
file_path VARCHAR(500),
file_size BIGINT,
mime_type VARCHAR(100),
description TEXT,
version VARCHAR(20) DEFAULT '1.0',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
-- Expenses table
CREATE TABLE expenses (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100) NOT NULL,
subcategory VARCHAR(100),
amount DECIMAL(12,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'CZK',
vat_rate DECIMAL(5,2) DEFAULT 21.00,
vat_amount DECIMAL(12,2),
total_amount DECIMAL(12,2),
expense_date TIMESTAMP NOT NULL,
payment_method VARCHAR(100),
has_receipt BOOLEAN DEFAULT false,
receipt_data TEXT,
receipt_image VARCHAR(500),
status VARCHAR(50) DEFAULT 'pending',
approved_by INTEGER,
approved_at TIMESTAMP,
rejection_reason TEXT,
budget_id INTEGER REFERENCES budgets(id),
team_id INTEGER,
project_id INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER NOT NULL,
updated_by INTEGER
);
-- Expense documents table
CREATE TABLE expense_documents (
id SERIAL PRIMARY KEY,
expense_id INTEGER NOT NULL REFERENCES expenses(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
type VARCHAR(100),
file_name VARCHAR(500),
file_path VARCHAR(500),
file_size BIGINT,
mime_type VARCHAR(100),
ocr_data TEXT,
ocr_accuracy DECIMAL(5,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
-- Indexes for performance
CREATE INDEX idx_budgets_category ON budgets(category);
CREATE INDEX idx_budgets_fiscal_year ON budgets(fiscal_year);
CREATE INDEX idx_budgets_active ON budgets(active);
CREATE INDEX idx_expenses_category ON expenses(category);
CREATE INDEX idx_expenses_status ON expenses(status);
CREATE INDEX idx_expenses_expense_date ON expenses(expense_date);
CREATE INDEX idx_expenses_budget_id ON expenses(budget_id);
CREATE INDEX idx_expenses_created_by ON expenses(created_by);
CREATE INDEX idx_sponsorships_status ON sponsorships(status);
CREATE INDEX idx_sponsorships_contract_number ON sponsorships(contract_number);
CREATE INDEX idx_sponsorship_payments_sponsorship_id ON sponsorship_payments(sponsorship_id);
CREATE INDEX idx_sponsorship_documents_sponsorship_id ON sponsorship_documents(sponsorship_id);
CREATE INDEX idx_expense_documents_expense_id ON expense_documents(expense_id);
@@ -0,0 +1,200 @@
-- Invoice management tables
-- Customers table
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
ico VARCHAR(20),
dic VARCHAR(20),
address TEXT,
city VARCHAR(100),
zip VARCHAR(10),
country VARCHAR(100) DEFAULT 'Česká republika',
email VARCHAR(255),
phone VARCHAR(50),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
-- Invoices table
CREATE TABLE invoices (
id SERIAL PRIMARY KEY,
invoice_number VARCHAR(50) NOT NULL UNIQUE,
invoice_type VARCHAR(20) DEFAULT 'faktura',
variable_symbol VARCHAR(20),
constant_symbol VARCHAR(20),
specific_symbol VARCHAR(20),
issue_date TIMESTAMP NOT NULL,
due_date TIMESTAMP NOT NULL,
taxable_supply_date TIMESTAMP,
-- Supplier information (auto-filled from club settings)
supplier_name VARCHAR(255) NOT NULL,
supplier_ico VARCHAR(20),
supplier_dic VARCHAR(20),
supplier_address TEXT,
supplier_city VARCHAR(100),
supplier_zip VARCHAR(10),
supplier_country VARCHAR(100) DEFAULT 'Česká republika',
-- Supplier bank information
bank_name VARCHAR(255),
bank_account VARCHAR(50),
bank_iban VARCHAR(50),
bank_swift VARCHAR(20),
-- Customer information
customer_id INTEGER REFERENCES customers(id),
customer_name VARCHAR(255) NOT NULL,
customer_ico VARCHAR(20),
customer_dic VARCHAR(20),
customer_address TEXT,
customer_city VARCHAR(100),
customer_zip VARCHAR(10),
customer_country VARCHAR(100) DEFAULT 'Česká republika',
customer_email VARCHAR(255),
customer_phone VARCHAR(50),
-- Financial summary
subtotal_ex_vat DECIMAL(12,2) DEFAULT 0,
vat_amount DECIMAL(12,2) DEFAULT 0,
total_amount DECIMAL(12,2) NOT NULL,
paid_amount DECIMAL(12,2) DEFAULT 0,
-- Status and dates
status VARCHAR(50) DEFAULT 'draft',
payment_status VARCHAR(50) DEFAULT 'unpaid',
sent_at TIMESTAMP,
paid_at TIMESTAMP,
-- Notes and metadata
notes TEXT,
internal_notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER NOT NULL,
updated_by INTEGER
);
-- Invoice items table
CREATE TABLE invoice_items (
id SERIAL PRIMARY KEY,
invoice_id INTEGER NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
quantity DECIMAL(10,2) NOT NULL DEFAULT 1,
unit VARCHAR(50) DEFAULT 'ks',
unit_price DECIMAL(12,2) NOT NULL,
vat_rate DECIMAL(5,2) DEFAULT 21.00,
vat_amount DECIMAL(12,2),
total_price_ex_vat DECIMAL(12,2),
total_price_with_vat DECIMAL(12,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Invoice payments table
CREATE TABLE invoice_payments (
id SERIAL PRIMARY KEY,
invoice_id INTEGER NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
amount DECIMAL(12,2) NOT NULL,
payment_date TIMESTAMP NOT NULL,
payment_method VARCHAR(100),
reference_number VARCHAR(255),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
-- Invoice documents table
CREATE TABLE invoice_documents (
id SERIAL PRIMARY KEY,
invoice_id INTEGER NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
type VARCHAR(100),
file_name VARCHAR(500),
file_path VARCHAR(500),
file_size BIGINT,
mime_type VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
-- Invoice settings table (singleton)
CREATE TABLE invoice_settings (
id SERIAL PRIMARY KEY,
-- Default supplier information
default_supplier_name VARCHAR(255),
default_supplier_ico VARCHAR(20),
default_supplier_dic VARCHAR(20),
default_supplier_address TEXT,
default_supplier_city VARCHAR(100),
default_supplier_zip VARCHAR(10),
default_supplier_country VARCHAR(100) DEFAULT 'Česká republika',
-- Default bank information
default_bank_name VARCHAR(255),
default_bank_account VARCHAR(50),
default_bank_iban VARCHAR(50),
default_bank_swift VARCHAR(20),
-- Invoice defaults
default_due_days INTEGER DEFAULT 14,
default_vat_rate DECIMAL(5,2) DEFAULT 21.00,
default_payment_method VARCHAR(100),
invoice_number_prefix VARCHAR(20) DEFAULT 'FV',
next_invoice_number INTEGER DEFAULT 1,
-- Email settings
email_subject VARCHAR(255),
email_body TEXT,
send_emails BOOLEAN DEFAULT false,
-- Other settings
currency VARCHAR(3) DEFAULT 'CZK',
language VARCHAR(10) DEFAULT 'cs',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_by INTEGER
);
-- Indexes for performance
CREATE INDEX idx_customers_ico ON customers(ico);
CREATE INDEX idx_customers_email ON customers(email);
CREATE INDEX idx_invoices_number ON invoices(invoice_number);
CREATE INDEX idx_invoices_status ON invoices(status);
CREATE INDEX idx_invoices_payment_status ON invoices(payment_status);
CREATE INDEX idx_invoices_customer_id ON invoices(customer_id);
CREATE INDEX idx_invoices_issue_date ON invoices(issue_date);
CREATE INDEX idx_invoices_due_date ON invoices(due_date);
CREATE INDEX idx_invoice_items_invoice_id ON invoice_items(invoice_id);
CREATE INDEX idx_invoice_payments_invoice_id ON invoice_payments(invoice_id);
CREATE INDEX idx_invoice_documents_invoice_id ON invoice_documents(invoice_id);
-- Insert default invoice settings
INSERT INTO invoice_settings (
default_supplier_name,
default_due_days,
default_vat_rate,
invoice_number_prefix,
next_invoice_number,
currency,
language
) VALUES (
'Fotbalový klub',
14,
21.00,
'FV',
1,
'CZK',
'cs'
);
@@ -0,0 +1 @@
ALTER TABLE comment_reactions DROP CONSTRAINT IF EXISTS unique_comment_user_reaction;
@@ -0,0 +1,11 @@
-- Add unique constraint for comment reactions if it doesn't exist
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'unique_comment_user_reaction'
AND table_name = 'comment_reactions'
) THEN
ALTER TABLE comment_reactions ADD CONSTRAINT unique_comment_user_reaction UNIQUE (comment_id, user_id);
END IF;
END $$;
@@ -89,5 +89,4 @@ CREATE TABLE IF NOT EXISTS comment_reactions (
);
CREATE INDEX idx_comment_reactions_comment ON comment_reactions(comment_id);
CREATE INDEX idx_comment_reactions_user ON comment_reactions(user_id);
CREATE INDEX idx_comment_reactions_type ON comment_reactions(type);
@@ -0,0 +1,11 @@
-- Drop e-shop tables
DROP TABLE IF EXISTS eshop_shipping_labels CASCADE;
DROP TABLE IF EXISTS eshop_payments CASCADE;
DROP TABLE IF EXISTS eshop_order_items CASCADE;
DROP TABLE IF EXISTS eshop_orders CASCADE;
DROP TABLE IF EXISTS eshop_cart_items CASCADE;
DROP TABLE IF EXISTS eshop_carts CASCADE;
DROP TABLE IF EXISTS eshop_product_variants CASCADE;
DROP TABLE IF EXISTS eshop_products CASCADE;
DROP TABLE IF EXISTS eshop_product_categories CASCADE;
DROP TABLE IF EXISTS eshop_settings CASCADE;
@@ -0,0 +1,193 @@
-- E-shop tables for MyClub
-- These tables support product catalog, shopping cart, orders, payments, and shipping
-- Product categories
CREATE TABLE IF NOT EXISTS eshop_product_categories (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
slug VARCHAR(190) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
parent_id INTEGER REFERENCES eshop_product_categories(id),
display_order INTEGER DEFAULT 0,
active BOOLEAN DEFAULT true
);
-- Products
CREATE TABLE IF NOT EXISTS eshop_products (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
slug VARCHAR(190) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
short_description TEXT,
description_html TEXT,
price_cents BIGINT NOT NULL,
currency VARCHAR(10) DEFAULT 'CZK',
vat_rate DECIMAL(5,4),
active BOOLEAN DEFAULT true,
stock_mode VARCHAR(20) DEFAULT 'finite',
default_image_url VARCHAR(500),
gallery_json TEXT,
tags TEXT,
metadata_json TEXT,
category_id INTEGER REFERENCES eshop_product_categories(id)
);
-- Product variants
CREATE TABLE IF NOT EXISTS eshop_product_variants (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
product_id INTEGER NOT NULL REFERENCES eshop_products(id) ON DELETE CASCADE,
sku VARCHAR(64),
name VARCHAR(255),
attributes_json TEXT,
stock_qty INTEGER DEFAULT 0,
barcode VARCHAR(128),
image_url VARCHAR(500)
);
-- Shopping carts
CREATE TABLE IF NOT EXISTS eshop_carts (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
user_id INTEGER REFERENCES users(id),
session_token VARCHAR(64),
currency VARCHAR(10),
completed BOOLEAN DEFAULT false
);
-- Cart items
CREATE TABLE IF NOT EXISTS eshop_cart_items (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
cart_id INTEGER NOT NULL REFERENCES eshop_carts(id) ON DELETE CASCADE,
product_id INTEGER NOT NULL REFERENCES eshop_products(id),
variant_id INTEGER REFERENCES eshop_product_variants(id),
quantity INTEGER NOT NULL DEFAULT 1,
unit_price_cents BIGINT NOT NULL,
currency VARCHAR(10)
);
-- Orders
CREATE TABLE IF NOT EXISTS eshop_orders (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
order_number VARCHAR(32) UNIQUE NOT NULL,
user_id INTEGER REFERENCES users(id),
session_token VARCHAR(64),
email VARCHAR(255),
first_name VARCHAR(100),
last_name VARCHAR(100),
billing_address_json TEXT,
shipping_address_json TEXT,
status VARCHAR(32) DEFAULT 'new',
total_amount_cents BIGINT NOT NULL,
currency VARCHAR(10),
shipping_method VARCHAR(32),
shipping_price_cents BIGINT DEFAULT 0,
shipping_data_json TEXT,
metadata_json TEXT
);
-- Order items
CREATE TABLE IF NOT EXISTS eshop_order_items (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
order_id INTEGER NOT NULL REFERENCES eshop_orders(id) ON DELETE CASCADE,
product_id INTEGER NOT NULL,
variant_id INTEGER REFERENCES eshop_product_variants(id),
name VARCHAR(255) NOT NULL,
sku VARCHAR(64),
quantity INTEGER NOT NULL DEFAULT 1,
unit_price_cents BIGINT NOT NULL,
currency VARCHAR(10),
vat_rate DECIMAL(5,4)
);
-- Payments
CREATE TABLE IF NOT EXISTS eshop_payments (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
order_id INTEGER NOT NULL REFERENCES eshop_orders(id) ON DELETE CASCADE,
provider VARCHAR(32),
provider_payment_id VARCHAR(128),
status VARCHAR(32) DEFAULT 'pending',
amount_cents BIGINT NOT NULL,
currency VARCHAR(10),
raw_payload_json TEXT
);
-- Shipping labels
CREATE TABLE IF NOT EXISTS eshop_shipping_labels (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
order_id INTEGER NOT NULL REFERENCES eshop_orders(id) ON DELETE CASCADE,
carrier VARCHAR(32),
packeta_packet_id VARCHAR(64),
tracking_number VARCHAR(64),
label_url VARCHAR(500),
status VARCHAR(64),
history_json TEXT
);
-- E-shop settings
CREATE TABLE IF NOT EXISTS eshop_settings (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
default_currency VARCHAR(10) DEFAULT 'CZK',
supported_currencies TEXT,
default_country VARCHAR(2) DEFAULT 'CZ',
shipping_options_json TEXT,
terms_url VARCHAR(500),
returns_policy_url VARCHAR(500),
support_email VARCHAR(255),
support_phone VARCHAR(64)
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_eshop_products_active ON eshop_products(active) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_products_slug ON eshop_products(slug) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_products_category ON eshop_products(category_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_variants_product ON eshop_product_variants(product_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_variants_sku ON eshop_product_variants(sku) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_carts_user ON eshop_carts(user_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_carts_session ON eshop_carts(session_token) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_carts_completed ON eshop_carts(completed) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_cart_items_cart ON eshop_cart_items(cart_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_orders_user ON eshop_orders(user_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_orders_session ON eshop_orders(session_token) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_orders_status ON eshop_orders(status) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_orders_number ON eshop_orders(order_number) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_payments_order ON eshop_payments(order_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_payments_provider ON eshop_payments(provider, provider_payment_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_shipping_order ON eshop_shipping_labels(order_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_eshop_shipping_packet ON eshop_shipping_labels(packeta_packet_id) WHERE deleted_at IS NULL;
@@ -0,0 +1,10 @@
-- Down migration for facility management tables
-- Migration: 20260109000001_create_facility_management_tables.down.sql
DROP TABLE IF EXISTS facility_booking_templates;
DROP TABLE IF EXISTS weather_conditions;
DROP TABLE IF EXISTS facility_maintenance;
DROP TABLE IF EXISTS facility_equipment;
DROP TABLE IF EXISTS facility_bookings;
DROP TABLE IF EXISTS facility_availability_rules;
DROP TABLE IF EXISTS facilities;
@@ -0,0 +1,292 @@
-- Create facility management tables
-- Migration: 20260109000001_create_facility_management_tables.up.sql
-- Facilities table
CREATE TABLE IF NOT EXISTS facilities (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE,
name VARCHAR(255) NOT NULL,
description TEXT,
type VARCHAR(20) NOT NULL CHECK (type IN ('field', 'gym', 'locker', 'classroom', 'storage', 'other')),
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'maintenance', 'closed')),
capacity INTEGER,
area DECIMAL(10,2),
location VARCHAR(255),
is_indoor BOOLEAN DEFAULT true,
is_outdoor BOOLEAN DEFAULT false,
image_url VARCHAR(500),
-- Booking settings
requires_approval BOOLEAN DEFAULT false,
min_booking_duration INTEGER DEFAULT 30,
max_booking_duration INTEGER DEFAULT 240,
booking_advance_days INTEGER DEFAULT 30,
-- Pricing
price_per_hour DECIMAL(10,2) DEFAULT 0.00,
CONSTRAINT facilities_name_unique UNIQUE (name, deleted_at)
);
-- Facility availability rules
CREATE TABLE IF NOT EXISTS facility_availability_rules (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE,
facility_id INTEGER NOT NULL REFERENCES facilities(id) ON DELETE CASCADE,
day_of_week INTEGER NOT NULL CHECK (day_of_week BETWEEN 0 AND 6),
start_time VARCHAR(5) NOT NULL CHECK (start_time ~ '^[0-2][0-9]:[0-5][0-9]$'),
end_time VARCHAR(5) NOT NULL CHECK (end_time ~ '^[0-2][0-9]:[0-5][0-9]$'),
is_available BOOLEAN DEFAULT true,
start_date DATE,
end_date DATE
);
-- Facility bookings
CREATE TABLE IF NOT EXISTS facility_bookings (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE,
facility_id INTEGER NOT NULL REFERENCES facilities(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id),
title VARCHAR(255) NOT NULL,
description TEXT,
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
end_time TIMESTAMP WITH TIME ZONE NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'confirmed', 'cancelled', 'completed', 'noshow')),
-- Pricing
total_price DECIMAL(10,2) DEFAULT 0.00,
payment_status VARCHAR(20) DEFAULT 'pending',
-- Attendance tracking
actual_start_time TIMESTAMP WITH TIME ZONE,
actual_end_time TIMESTAMP WITH TIME ZONE,
attendees_count INTEGER DEFAULT 0,
-- Notes
internal_notes TEXT,
public_notes TEXT,
-- Cancellation
cancelled_at TIMESTAMP WITH TIME ZONE,
cancelled_by INTEGER REFERENCES users(id),
cancel_reason TEXT,
CONSTRAINT bookings_no_overlap EXCLUDE (facility_id WITH =, tsrange(start_time, end_time) WITH &&) WHERE (deleted_at IS NULL AND status NOT IN ('cancelled', 'noshow'))
);
-- Facility equipment
CREATE TABLE IF NOT EXISTS facility_equipment (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE,
facility_id INTEGER NOT NULL REFERENCES facilities(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
status VARCHAR(20) NOT NULL DEFAULT 'available' CHECK (status IN ('available', 'in_use', 'maintenance', 'damaged', 'lost', 'retired')),
quantity INTEGER NOT NULL DEFAULT 1,
available INTEGER NOT NULL DEFAULT 1,
-- Purchase info
purchase_date DATE,
purchase_price DECIMAL(10,2),
supplier VARCHAR(255),
serial_number VARCHAR(255),
warranty_expiry DATE,
-- Maintenance
last_maintenance_date DATE,
next_maintenance_date DATE,
-- Location tracking
current_location VARCHAR(255),
image_url VARCHAR(500),
-- Usage tracking
usage_count INTEGER DEFAULT 0,
last_used_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT equipment_name_facility_unique UNIQUE (name, facility_id, deleted_at)
);
-- Facility maintenance
CREATE TABLE IF NOT EXISTS facility_maintenance (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE,
facility_id INTEGER NOT NULL REFERENCES facilities(id) ON DELETE CASCADE,
type VARCHAR(20) NOT NULL CHECK (type IN ('routine', 'repair', 'inspection', 'upgrade')),
title VARCHAR(255) NOT NULL,
description TEXT,
-- Scheduling
scheduled_date TIMESTAMP WITH TIME ZONE,
estimated_duration INTEGER, -- minutes
actual_duration INTEGER, -- minutes
-- Status
status VARCHAR(20) DEFAULT 'scheduled',
started_at TIMESTAMP WITH TIME ZONE,
completed_at TIMESTAMP WITH TIME ZONE,
-- Cost
estimated_cost DECIMAL(10,2),
actual_cost DECIMAL(10,2),
-- Personnel
assigned_to VARCHAR(255),
performed_by VARCHAR(255),
-- Impact
is_facility_unavailable BOOLEAN DEFAULT true,
-- Notes
internal_notes TEXT,
public_notes TEXT,
-- Related equipment (JSON array)
equipment_affected TEXT
);
-- Weather conditions for outdoor facilities
CREATE TABLE IF NOT EXISTS weather_conditions (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE,
facility_id INTEGER NOT NULL REFERENCES facilities(id) ON DELETE CASCADE,
date_time TIMESTAMP WITH TIME ZONE NOT NULL,
temperature DECIMAL(5,2), -- Celsius
humidity DECIMAL(5,2), -- Percentage
precipitation DECIMAL(8,2), -- mm
wind_speed DECIMAL(5,2), -- km/h
wind_direction INTEGER, -- Degrees
weather_code VARCHAR(10), -- OpenWeatherMap condition code
description VARCHAR(255),
is_suitable BOOLEAN DEFAULT false,
recommendations TEXT,
CONSTRAINT weather_facility_datetime_unique UNIQUE (facility_id, date_time)
);
-- Facility booking templates
CREATE TABLE IF NOT EXISTS facility_booking_templates (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE,
facility_id INTEGER NOT NULL REFERENCES facilities(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
-- Default booking settings
duration INTEGER, -- minutes
price_per_hour DECIMAL(10,2),
requires_approval BOOLEAN DEFAULT false,
-- Recurrence pattern
is_recurring BOOLEAN DEFAULT false,
recurrence_pattern TEXT, -- JSON
-- Default settings
default_title VARCHAR(255),
default_description TEXT,
default_attendees INTEGER DEFAULT 1,
is_active BOOLEAN DEFAULT true,
CONSTRAINT template_name_facility_unique UNIQUE (name, facility_id, deleted_at)
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_facilities_type ON facilities(type);
CREATE INDEX IF NOT EXISTS idx_facilities_status ON facilities(status);
CREATE INDEX IF NOT EXISTS idx_facilities_is_outdoor ON facilities(is_outdoor);
CREATE INDEX IF NOT EXISTS idx_facility_bookings_facility_id ON facility_bookings(facility_id);
CREATE INDEX IF NOT EXISTS idx_facility_bookings_user_id ON facility_bookings(user_id);
CREATE INDEX IF NOT EXISTS idx_facility_bookings_start_time ON facility_bookings(start_time);
CREATE INDEX IF NOT EXISTS idx_facility_bookings_end_time ON facility_bookings(end_time);
CREATE INDEX IF NOT EXISTS idx_facility_bookings_status ON facility_bookings(status);
CREATE INDEX IF NOT EXISTS idx_facility_bookings_time_range ON facility_bookings USING gist (tsrange(start_time, end_time));
CREATE INDEX IF NOT EXISTS idx_facility_equipment_facility_id ON facility_equipment(facility_id);
CREATE INDEX IF NOT EXISTS idx_facility_equipment_status ON facility_equipment(status);
CREATE INDEX IF NOT EXISTS idx_facility_equipment_category ON facility_equipment(category);
CREATE INDEX IF NOT EXISTS idx_facility_maintenance_facility_id ON facility_maintenance(facility_id);
CREATE INDEX IF NOT EXISTS idx_facility_maintenance_scheduled_date ON facility_maintenance(scheduled_date);
CREATE INDEX IF NOT EXISTS idx_facility_maintenance_status ON facility_maintenance(status);
CREATE INDEX IF NOT EXISTS idx_weather_conditions_facility_id ON weather_conditions(facility_id);
CREATE INDEX IF NOT EXISTS idx_weather_conditions_date_time ON weather_conditions(date_time);
CREATE INDEX IF NOT EXISTS idx_weather_conditions_is_suitable ON weather_conditions(is_suitable);
CREATE INDEX IF NOT EXISTS idx_facility_booking_templates_facility_id ON facility_booking_templates(facility_id);
CREATE INDEX IF NOT EXISTS idx_facility_booking_templates_is_active ON facility_booking_templates(is_active);
-- Insert sample data
INSERT INTO facilities (name, description, type, status, capacity, area, location, is_indoor, is_outdoor, price_per_hour, min_booking_duration, max_booking_duration, booking_advance_days) VALUES
('Hlavní hřiště', 'Plnoformátové fotbalové hřiště s přírodním trávníkem', 'field', 'active', 22, 7200.00, 'Hlavní sportovní areál', false, true, 500.00, 60, 180, 14),
('Tréninkové hřiště č. 1', 'Menší tréninkové hřiště s umělým trávníkem', 'field', 'active', 16, 4800.00, 'Tréninkový areál - sever', false, true, 300.00, 30, 120, 7),
('Posilovna', 'Plně vybavená posilovna pro hráče', 'gym', 'active', 20, 150.00, 'Hlavní budova - 1. patro', true, false, 100.00, 30, 90, 3),
('Šatna A', 'Šatna pro domácí tým', 'locker', 'active', 25, 80.00, 'Hlavní budova - přízemí', true, false, 0.00, 15, 60, 1),
('Zasedací místnost', 'Místnost pro týmové porady a prezentace', 'classroom', 'active', 30, 60.00, 'Hlavní budova - 2. patro', true, false, 50.00, 30, 120, 7),
('Sklad vybavení', 'Sklad pro tréninkové vybavení', 'storage', 'active', 5, 40.00, 'Hlavní budova - suterén', true, false, 0.00, 15, 30, 1);
-- Insert availability rules for main field (available Mon-Fri 16:00-22:00, Sat-Sun 08:00-22:00)
INSERT INTO facility_availability_rules (facility_id, day_of_week, start_time, end_time, is_available)
SELECT f.id, d.day_of_week, d.start_time, d.end_time, true
FROM facilities f,
(VALUES
(1, 1, '16:00', '22:00'), -- Monday
(1, 2, '16:00', '22:00'), -- Tuesday
(1, 3, '16:00', '22:00'), -- Wednesday
(1, 4, '16:00', '22:00'), -- Thursday
(1, 5, '16:00', '22:00'), -- Friday
(1, 6, '08:00', '22:00'), -- Saturday
(1, 0, '08:00', '22:00') -- Sunday
) AS d(day_of_week, start_time, end_time)
WHERE f.name = 'Hlavní hřiště';
-- Insert sample equipment
INSERT INTO facility_equipment (facility_id, name, description, category, quantity, available, purchase_date, purchase_price)
SELECT f.id, e.name, e.description, e.category, e.quantity, e.quantity, e.purchase_date, e.purchase_price
FROM facilities f,
(VALUES
(1, 'Míče velikosti 5', 'Plnoformátové fotbalové míče', 'balls', 20, 20, '2024-01-15', 800.00),
(1, 'Tréninkové kužely', 'Plastové kužele pro cvičení', 'cones', 50, 50, '2024-01-15', 500.00),
(1, 'Branky', 'Přenosné branky pro trénink', 'goals', 4, 4, '2024-02-01', 2000.00),
(3, 'Činky', 'Sada činek různých vah', 'weights', 10, 10, '2024-01-20', 5000.00),
(3, 'Posilovací lavice', 'Profesionální lavice na bench press', 'benches', 3, 3, '2024-01-20', 3000.00)
) AS e(name, description, category, quantity, available, purchase_date, purchase_price)
WHERE f.name = 'Hlavní hřiště' AND e.category IN ('balls', 'cones', 'goals')
UNION ALL
SELECT f.id, e.name, e.description, e.category, e.quantity, e.quantity, e.purchase_date, e.purchase_price
FROM facilities f,
(VALUES
('Činky', 'Sada činek různých vah', 'weights', 10, 10, '2024-01-20', 5000.00),
('Posilovací lavice', 'Profesionální lavice na bench press', 'benches', 3, 3, '2024-01-20', 3000.00)
) AS e(name, description, category, quantity, available, purchase_date, purchase_price)
WHERE f.name = 'Posilovna' AND e.category IN ('weights', 'benches');
@@ -0,0 +1,2 @@
-- Down migration for reaction constraint fix
ALTER TABLE comment_reactions DROP CONSTRAINT IF EXISTS unique_comment_user_reaction;
@@ -0,0 +1,13 @@
-- Handle existing unique constraint gracefully
DO $$
BEGIN
-- Check if constraint already exists
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'unique_comment_user_reaction'
AND table_name = 'comment_reactions'
) THEN
-- Try to add constraint, ignore if it exists
ALTER TABLE comment_reactions ADD CONSTRAINT unique_comment_user_reaction UNIQUE (comment_id, user_id);
END IF;
END $$;
@@ -0,0 +1,4 @@
-- Remove indexes for comment_reactions table
DROP INDEX IF EXISTS idx_comment_reactions_comment;
DROP INDEX IF EXISTS idx_comment_reactions_user;
DROP INDEX IF EXISTS idx_comment_reactions_type;
@@ -0,0 +1,4 @@
-- Add indexes for comment_reactions table (separate from constraints)
CREATE INDEX idx_comment_reactions_comment ON comment_reactions(comment_id);
CREATE INDEX idx_comment_reactions_user ON comment_reactions(user_id);
CREATE INDEX idx_comment_reactions_type ON comment_reactions(type);