A local web app for a small room-rental business in Austria: guest register, room ledger, occupancy plan, invoices, the monthly tourist-tax report and the quarterly VAT list. Application code only; it ships no data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
181 lines
8.1 KiB
PL/PgSQL
181 lines
8.1 KiB
PL/PgSQL
-- 001 schema.
|
|
-- * text is COLLATE "C" (byte order), dates are ISO text, money is double precision
|
|
-- * every foreign key is DEFERRABLE (INITIALLY IMMEDIATE): company and client point at each other
|
|
|
|
CREATE TABLE unit (
|
|
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
code text COLLATE "C" NOT NULL UNIQUE, -- short code shown in the lists
|
|
name text COLLATE "C" NOT NULL,
|
|
subtitle text COLLATE "C",
|
|
address text COLLATE "C", -- multi-line issuer address block
|
|
place text COLLATE "C", -- where invoices are issued ('<place>, am <date>')
|
|
contact text COLLATE "C", -- multi-line contact block of the invoice footer
|
|
bank text COLLATE "C",
|
|
iban text COLLATE "C",
|
|
uid text COLLATE "C",
|
|
tvb_number text COLLATE "C", -- Betriebsnummer for the tourist-tax report
|
|
invoice_prefix text COLLATE "C", -- '2026-' | '26-': the year is kept current
|
|
logo text COLLATE "C", -- file in app/static/invoice/, nullable
|
|
photo text COLLATE "C",
|
|
theme text COLLATE "C", -- 'green' | 'blue'
|
|
is_current integer NOT NULL DEFAULT 1,
|
|
succeeded_by_id bigint REFERENCES unit(id) DEFERRABLE -- a former operator reports under its successor
|
|
);
|
|
|
|
CREATE TABLE room (
|
|
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
number text COLLATE "C" NOT NULL UNIQUE, -- floor-coded, e.g. '21'
|
|
category text COLLATE "C", -- EZ | DZ | 3BZ | 4BZ | Garconniere
|
|
capacity integer,
|
|
unit_id bigint REFERENCES unit(id) DEFERRABLE, -- current owner
|
|
long_term integer NOT NULL DEFAULT 0, -- let long-term, left out of the free-bed count
|
|
floor text COLLATE "C"
|
|
);
|
|
|
|
CREATE TABLE company (
|
|
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
name text COLLATE "C" NOT NULL, -- canonical display name
|
|
kind text COLLATE "C" NOT NULL DEFAULT 'company', -- company | private
|
|
uid text COLLATE "C",
|
|
email text COLLATE "C",
|
|
phone text COLLATE "C",
|
|
notes text COLLATE "C",
|
|
current_client_id bigint -- the address version used for NEW invoices
|
|
);
|
|
|
|
CREATE TABLE client (
|
|
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
kind text COLLATE "C" NOT NULL DEFAULT 'company', -- company | private
|
|
name text COLLATE "C" NOT NULL,
|
|
address text COLLATE "C",
|
|
postcode text COLLATE "C",
|
|
city text COLLATE "C",
|
|
country text COLLATE "C",
|
|
company_id bigint REFERENCES company(id) DEFERRABLE
|
|
);
|
|
ALTER TABLE company ADD FOREIGN KEY (current_client_id) REFERENCES client(id) DEFERRABLE;
|
|
|
|
CREATE TABLE company_key (
|
|
key text COLLATE "C" PRIMARY KEY, -- normalize.company_key(spelling)
|
|
company_id bigint NOT NULL REFERENCES company(id) DEFERRABLE
|
|
);
|
|
|
|
CREATE TABLE dismissed_finding (
|
|
kind text COLLATE "C" NOT NULL,
|
|
key text COLLATE "C" NOT NULL,
|
|
note text COLLATE "C",
|
|
PRIMARY KEY (kind, key)
|
|
);
|
|
|
|
CREATE TABLE guest (
|
|
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
last_name text COLLATE "C",
|
|
first_name text COLLATE "C",
|
|
birth_date text COLLATE "C", -- ISO yyyy-mm-dd
|
|
nationality text COLLATE "C",
|
|
doc_type text COLLATE "C",
|
|
doc_number text COLLATE "C",
|
|
profession text COLLATE "C",
|
|
home_street text COLLATE "C",
|
|
home_postcode text COLLATE "C",
|
|
home_city text COLLATE "C",
|
|
home_country text COLLATE "C",
|
|
phone text COLLATE "C"
|
|
);
|
|
|
|
CREATE TABLE invoice (
|
|
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
unit_id bigint REFERENCES unit(id) DEFERRABLE,
|
|
client_id bigint REFERENCES client(id) DEFERRABLE,
|
|
number text COLLATE "C", -- '2026-01'
|
|
date text COLLATE "C",
|
|
paid_date text COLLATE "C",
|
|
payment_method text COLLATE "C",
|
|
notes text COLLATE "C",
|
|
lines_stale integer NOT NULL DEFAULT 0
|
|
);
|
|
|
|
-- One table for three kinds of rows, told apart by source_sheet: 'guestsheet' (a Gästeblatt in the guest
|
|
-- register), 'ledger' (a Zimmerbelegung row; its dates live on the charge) and 'invoice' (an invoice line).
|
|
CREATE TABLE stay (
|
|
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
guest_id bigint REFERENCES guest(id) DEFERRABLE, -- null for ledger and invoice rows
|
|
room_id bigint REFERENCES room(id) DEFERRABLE,
|
|
unit_id bigint REFERENCES unit(id) DEFERRABLE, -- the operator at the time
|
|
employer_client_id bigint REFERENCES client(id) DEFERRABLE,
|
|
arrival text COLLATE "C",
|
|
departure text COLLATE "C",
|
|
sheet_number text COLLATE "C",
|
|
label text COLLATE "C", -- occupant text when guest_id is null
|
|
notes text COLLATE "C",
|
|
source_sheet text COLLATE "C", -- guestsheet | ledger | invoice
|
|
hauptwohnsitz integer,
|
|
tourism_liable integer
|
|
);
|
|
|
|
CREATE TABLE charge (
|
|
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
stay_id bigint REFERENCES stay(id) DEFERRABLE,
|
|
invoice_id bigint REFERENCES invoice(id) DEFERRABLE,
|
|
period_from text COLLATE "C",
|
|
period_to text COLLATE "C",
|
|
quantity double precision,
|
|
unit_type text COLLATE "C", -- week | day | night | month
|
|
gross_per_unit double precision,
|
|
line_net_override double precision, -- null => computed
|
|
source_sheet text COLLATE "C",
|
|
persons numeric,
|
|
paid_date text COLLATE "C",
|
|
paid_status text COLLATE "C",
|
|
tourism_tax double precision,
|
|
art text COLLATE "C",
|
|
gb_ref text COLLATE "C",
|
|
vat_rate double precision NOT NULL DEFAULT 0.10,
|
|
kind text COLLATE "C" NOT NULL DEFAULT 'stay',
|
|
description text COLLATE "C",
|
|
rnr text COLLATE "C",
|
|
payer text COLLATE "C",
|
|
notes text COLLATE "C"
|
|
);
|
|
|
|
CREATE TABLE payment (
|
|
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
charge_id bigint NOT NULL REFERENCES charge(id) ON DELETE CASCADE DEFERRABLE,
|
|
paid_date text COLLATE "C" NOT NULL,
|
|
amount double precision NOT NULL
|
|
);
|
|
|
|
CREATE INDEX idx_charge_invoice ON charge(invoice_id);
|
|
CREATE INDEX idx_charge_stay ON charge(stay_id);
|
|
CREATE INDEX idx_client_company ON client(company_id);
|
|
CREATE INDEX idx_stay_guest ON stay(guest_id);
|
|
CREATE INDEX idx_stay_room ON stay(room_id);
|
|
-- the company pages count stays and invoices per company in correlated subqueries
|
|
CREATE INDEX idx_stay_employer ON stay(employer_client_id);
|
|
CREATE INDEX idx_invoice_client ON invoice(client_id);
|
|
CREATE INDEX payment_charge ON payment(charge_id);
|
|
|
|
-- "Has anything changed?" for the page cache and the browser's frozen column widths: every statement that
|
|
-- writes a data table counts it up.
|
|
CREATE TABLE data_version (
|
|
id integer PRIMARY KEY DEFAULT 1 CHECK (id = 1),
|
|
version bigint NOT NULL DEFAULT 0
|
|
);
|
|
INSERT INTO data_version (id, version) VALUES (1, 0);
|
|
|
|
CREATE FUNCTION bump_data_version() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
EXECUTE format('UPDATE %I.data_version SET version = version + 1', TG_TABLE_SCHEMA);
|
|
RETURN NULL;
|
|
END $$;
|
|
|
|
DO $$
|
|
DECLARE t text;
|
|
BEGIN
|
|
FOREACH t IN ARRAY ARRAY['unit', 'room', 'company', 'client', 'company_key', 'dismissed_finding', 'guest',
|
|
'invoice', 'stay', 'charge', 'payment'] LOOP
|
|
EXECUTE format('CREATE TRIGGER %I AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE ON %I '
|
|
'FOR EACH STATEMENT EXECUTE FUNCTION bump_data_version()', t || '_data_version', t);
|
|
END LOOP;
|
|
END $$;
|