update sql functions
This commit is contained in:
+154
-202
@@ -174,7 +174,6 @@ CREATE TABLE user_patch_tests (
|
||||
user_id CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
patch_test_id CHAR(12) NOT NULL REFERENCES patch_tests(id) ON DELETE CASCADE,
|
||||
tested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
notes TEXT,
|
||||
UNIQUE (user_id, patch_test_id)
|
||||
);
|
||||
|
||||
@@ -453,6 +452,7 @@ GDPR COMPLIANCE NOTES:
|
||||
-- WHY: GDPR Article 17 - users can request data deletion
|
||||
-- WHEN: User requests account deletion
|
||||
-- OUTPUT: Converts personal data to anonymous placeholder
|
||||
-- NOTE: phone/date_of_birth are NOT NULL so we use placeholder values, not NULL
|
||||
CREATE OR REPLACE FUNCTION anonymize_user(target_id CHAR(12))
|
||||
RETURNS VOID AS $$
|
||||
BEGIN
|
||||
@@ -460,12 +460,13 @@ BEGIN
|
||||
SET
|
||||
n_first_name = 'Deleted',
|
||||
n_last_name = 'User',
|
||||
email = CONCAT('deleted+', target_id, '@example.com'),
|
||||
phone = NULL,
|
||||
email = CONCAT('deleted+', target_id, '@deleted.invalid'),
|
||||
phone = '+000000000000', -- NOT NULL column: use placeholder
|
||||
date_of_birth = '1900-01-01', -- NOT NULL column: use placeholder
|
||||
profile_pic_url = NULL,
|
||||
date_of_birth = NULL,
|
||||
account_role = 'guest',
|
||||
loyalty_stamps = 0,
|
||||
referral_code = NULL,
|
||||
data_retention_consent = FALSE,
|
||||
data_consent_updated_at = NOW(),
|
||||
updated_at = NOW(),
|
||||
@@ -475,19 +476,6 @@ BEGIN
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Fully delete guest user
|
||||
-- WHY: Guest accounts have no ongoing business relationship
|
||||
-- WHEN: Cleaning up temporary/incomplete accounts
|
||||
-- OUTPUT: Complete removal from database
|
||||
CREATE OR REPLACE FUNCTION delete_guest_user(target_id CHAR(12))
|
||||
RETURNS VOID AS $$
|
||||
BEGIN
|
||||
DELETE FROM users
|
||||
WHERE id = target_id
|
||||
AND account_role = 'guest';
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Update consent
|
||||
-- WHY: GDPR requires tracking consent changes
|
||||
-- WHEN: User updates privacy preferences
|
||||
@@ -526,6 +514,7 @@ BEGIN
|
||||
'profile_pic_url', profile_pic_url,
|
||||
'account_role', account_role,
|
||||
'loyalty_stamps', loyalty_stamps,
|
||||
'referral_code', referral_code,
|
||||
'data_retention_consent', data_retention_consent,
|
||||
'data_consent_updated_at', data_consent_updated_at,
|
||||
'created_at', created_at,
|
||||
@@ -539,26 +528,25 @@ BEGIN
|
||||
'booking_id', b.id,
|
||||
'start_time', b.start_time,
|
||||
'status', b.status,
|
||||
'notes', b.notes,
|
||||
'created_at', b.created_at,
|
||||
'updated_at', b.updated_at,
|
||||
'created_by', b.created_by,
|
||||
'updated_by', b.updated_by,
|
||||
'services', (
|
||||
SELECT COALESCE(json_agg(
|
||||
json_build_object(
|
||||
'service_id', s.id,
|
||||
'name', s.name,
|
||||
'description', s.description,
|
||||
'price', s.price,
|
||||
'duration_minutes', s.duration_minutes
|
||||
'price', COALESCE(bsvc.override_price, s.price),
|
||||
'duration_minutes', COALESCE(bsvc.override_duration_minutes, s.duration_minutes)
|
||||
)
|
||||
), '[]'::json)
|
||||
FROM booking_services bs
|
||||
JOIN services s ON bs.service_id = s.id
|
||||
WHERE bs.booking_id = b.id
|
||||
FROM booking_services bsvc
|
||||
JOIN services s ON bsvc.service_id = s.id
|
||||
WHERE bsvc.booking_id = b.id
|
||||
)
|
||||
)
|
||||
), '[]'::json)
|
||||
ORDER BY b.start_time DESC), '[]'::json)
|
||||
FROM bookings b WHERE b.user_id = target_user_id
|
||||
),
|
||||
'payments', (
|
||||
@@ -568,19 +556,32 @@ BEGIN
|
||||
'booking_id', p.booking_id,
|
||||
'payment_type', p.payment_type,
|
||||
'payment_method', p.payment_method,
|
||||
'vendor_code', p.vendor_code,
|
||||
'invoice_number', p.invoice_number,
|
||||
'status', p.status,
|
||||
'amount', p.amount,
|
||||
'vat_amount', p.vat_amount,
|
||||
'net_amount', p.net_amount,
|
||||
'created_at', p.created_at,
|
||||
'updated_at', p.updated_at,
|
||||
'created_by', p.created_by,
|
||||
'updated_by', p.updated_by
|
||||
'updated_at', p.updated_at
|
||||
)
|
||||
), '[]'::json)
|
||||
ORDER BY p.created_at DESC), '[]'::json)
|
||||
FROM payments p
|
||||
JOIN bookings b ON p.booking_id = b.id
|
||||
WHERE b.user_id = target_user_id
|
||||
),
|
||||
'patch_tests', (
|
||||
SELECT COALESCE(json_agg(
|
||||
json_build_object(
|
||||
'patch_test_id', upt.patch_test_id,
|
||||
'name', pt.name,
|
||||
'description', pt.description,
|
||||
'tested_at', upt.tested_at
|
||||
)
|
||||
ORDER BY upt.tested_at DESC), '[]'::json)
|
||||
FROM user_patch_tests upt
|
||||
JOIN patch_tests pt ON upt.patch_test_id = pt.id
|
||||
WHERE upt.user_id = target_user_id
|
||||
),
|
||||
'export_metadata', json_build_object(
|
||||
'exported_at', NOW(),
|
||||
'exported_by', 'system',
|
||||
@@ -625,33 +626,28 @@ RETURNS TABLE (
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
start_date as period_start,
|
||||
end_date as period_end,
|
||||
COALESCE(SUM(p.amount), 0) as total_sales,
|
||||
start_date AS period_start,
|
||||
end_date AS period_end,
|
||||
COALESCE(SUM(p.amount), 0) AS total_sales,
|
||||
COALESCE(SUM(
|
||||
CASE
|
||||
-- If VAT is explicitly stored, use it
|
||||
WHEN p.vat_amount IS NOT NULL THEN p.vat_amount
|
||||
-- If business is VAT registered but no VAT breakdown, calculate it
|
||||
WHEN (SELECT is_vat_registered FROM business_settings WHERE id = 1) THEN
|
||||
ROUND(p.amount - (p.amount / (1 + COALESCE(p.vat_rate, (SELECT default_vat_rate FROM business_settings WHERE id = 1)) / 100)), 2)
|
||||
-- Business not VAT registered = no VAT charged
|
||||
ROUND(p.amount - (p.amount / (1 + COALESCE(p.vat_rate,
|
||||
(SELECT default_vat_rate FROM business_settings WHERE id = 1)) / 100)), 2)
|
||||
ELSE 0
|
||||
END
|
||||
), 0) as total_vat_charged,
|
||||
), 0) AS total_vat_charged,
|
||||
COALESCE(SUM(
|
||||
CASE
|
||||
-- If net amount is explicitly stored, use it
|
||||
WHEN p.net_amount IS NOT NULL THEN p.net_amount
|
||||
-- If business is VAT registered but no net amount, calculate it
|
||||
WHEN (SELECT is_vat_registered FROM business_settings WHERE id = 1) THEN
|
||||
ROUND(p.amount / (1 + COALESCE(p.vat_rate,
|
||||
(SELECT default_vat_rate FROM business_settings WHERE id = 1)) / 100), 2)
|
||||
-- Business not VAT registered = gross amount is net amount
|
||||
ELSE p.amount
|
||||
END
|
||||
), 0) as net_sales,
|
||||
COUNT(*) as transaction_count
|
||||
), 0) AS net_sales,
|
||||
COUNT(*) AS transaction_count
|
||||
FROM payments p
|
||||
JOIN bookings b ON p.booking_id = b.id
|
||||
WHERE p.status = 'completed'
|
||||
@@ -665,7 +661,7 @@ $$ LANGUAGE plpgsql;
|
||||
-- WHEN: Monthly/quarterly for accounting software import
|
||||
-- OUTPUT: CSV-compatible transaction list with customer, service, payment details
|
||||
-- USE: Import directly into QuickBooks, Xero, or other accounting software
|
||||
-- PARAMETERS: include_vat=false (default for micro businesses), include_vat=true only when VAT registered
|
||||
-- NOTE: include_vat=false (default for micro businesses); set true only when VAT registered
|
||||
CREATE OR REPLACE FUNCTION export_sales_transactions(
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
@@ -688,43 +684,47 @@ RETURNS TABLE (
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
p.created_at::date as transaction_date,
|
||||
p.created_at::date AS transaction_date,
|
||||
p.invoice_number::text AS invoice_number,
|
||||
COALESCE(u.fn, 'Walk-in Customer') as customer_name,
|
||||
u.email as customer_email,
|
||||
string_agg(s.name, ', ') as service_description,
|
||||
p.payment_method::text as payment_method,
|
||||
p.amount as gross_amount,
|
||||
COALESCE(
|
||||
CASE WHEN u.n_first_name = 'Deleted' THEN NULL ELSE u.fn END,
|
||||
'Walk-in Customer'
|
||||
) AS customer_name,
|
||||
CASE WHEN u.n_first_name = 'Deleted' THEN NULL ELSE u.email END AS customer_email,
|
||||
string_agg(s.name, ', ' ORDER BY s.name) AS service_description,
|
||||
p.payment_method::text AS payment_method,
|
||||
p.amount AS gross_amount,
|
||||
CASE
|
||||
WHEN include_vat AND p.net_amount IS NOT NULL THEN p.net_amount
|
||||
WHEN include_vat THEN ROUND(p.amount / 1.20, 2)
|
||||
ELSE p.amount
|
||||
END as net_amount,
|
||||
END AS net_amount,
|
||||
CASE
|
||||
WHEN include_vat AND p.vat_amount IS NOT NULL THEN p.vat_amount
|
||||
WHEN include_vat THEN ROUND(p.amount - (p.amount / 1.20), 2)
|
||||
ELSE 0
|
||||
END as vat_amount,
|
||||
END AS vat_amount,
|
||||
CASE
|
||||
WHEN include_vat THEN COALESCE(p.vat_rate, 20.00)
|
||||
ELSE NULL
|
||||
END as vat_rate,
|
||||
b.id as booking_id,
|
||||
p.id as payment_id
|
||||
END AS vat_rate,
|
||||
b.id AS booking_id,
|
||||
p.id AS payment_id
|
||||
FROM payments p
|
||||
JOIN bookings b ON p.booking_id = b.id
|
||||
LEFT JOIN users u ON b.user_id = u.id
|
||||
LEFT JOIN booking_services bs ON b.id = bs.booking_id
|
||||
LEFT JOIN services s ON bs.service_id = s.id
|
||||
LEFT JOIN booking_services bsvc ON b.id = bsvc.booking_id
|
||||
LEFT JOIN services s ON bsvc.service_id = s.id
|
||||
WHERE p.status = 'completed'
|
||||
AND p.created_at::date BETWEEN start_date AND end_date
|
||||
AND p.payment_type IN ('full', 'partial', 'balance')
|
||||
GROUP BY p.id, b.id, u.fn, u.email, p.created_at, p.amount, p.net_amount, p.vat_amount, p.vat_rate, p.payment_method, p.invoice_number
|
||||
GROUP BY p.id, b.id, u.fn, u.n_first_name, u.email,
|
||||
p.created_at, p.amount, p.net_amount, p.vat_amount,
|
||||
p.vat_rate, p.payment_method, p.invoice_number
|
||||
ORDER BY p.created_at;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
-- Monthly Business Summary
|
||||
-- WHY: Track business performance trends and payment method preferences
|
||||
-- WHEN: Monthly review of business performance
|
||||
@@ -746,13 +746,13 @@ RETURNS TABLE (
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
TO_CHAR(p.created_at, 'YYYY-MM') as month_year,
|
||||
COUNT(DISTINCT b.id) as total_bookings,
|
||||
SUM(p.amount) as total_revenue,
|
||||
SUM(CASE WHEN p.payment_method = 'cash' THEN p.amount ELSE 0 END) as cash_payments,
|
||||
SUM(CASE WHEN p.payment_method = 'in_person_card' THEN p.amount ELSE 0 END) as card_payments,
|
||||
SUM(CASE WHEN p.payment_method = 'online_square' THEN p.amount ELSE 0 END) as online_payments,
|
||||
ROUND(AVG(p.amount), 2) as avg_transaction
|
||||
TO_CHAR(p.created_at, 'YYYY-MM') AS month_year,
|
||||
COUNT(DISTINCT b.id) AS total_bookings,
|
||||
SUM(p.amount) AS total_revenue,
|
||||
SUM(CASE WHEN p.payment_method = 'cash' THEN p.amount ELSE 0 END) AS cash_payments,
|
||||
SUM(CASE WHEN p.payment_method = 'in_person_card' THEN p.amount ELSE 0 END) AS card_payments,
|
||||
SUM(CASE WHEN p.payment_method = 'online_square' THEN p.amount ELSE 0 END) AS online_payments,
|
||||
ROUND(AVG(p.amount), 2) AS avg_transaction
|
||||
FROM payments p
|
||||
JOIN bookings b ON p.booking_id = b.id
|
||||
WHERE p.status = 'completed'
|
||||
@@ -782,11 +782,11 @@ RETURNS TABLE (
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
COALESCE(SUM(p.amount), 0) as total_sales,
|
||||
COUNT(*) as total_transactions,
|
||||
COALESCE(SUM(CASE WHEN p.payment_method = 'cash' THEN p.amount ELSE 0 END), 0) as cash_total,
|
||||
COALESCE(SUM(CASE WHEN p.payment_method = 'in_person_card' THEN p.amount ELSE 0 END), 0) as card_total,
|
||||
COALESCE(SUM(CASE WHEN p.payment_method = 'online_square' THEN p.amount ELSE 0 END), 0) as online_total
|
||||
COALESCE(SUM(p.amount), 0) AS total_sales,
|
||||
COUNT(*) AS total_transactions,
|
||||
COALESCE(SUM(CASE WHEN p.payment_method = 'cash' THEN p.amount ELSE 0 END), 0) AS cash_total,
|
||||
COALESCE(SUM(CASE WHEN p.payment_method = 'in_person_card' THEN p.amount ELSE 0 END), 0) AS card_total,
|
||||
COALESCE(SUM(CASE WHEN p.payment_method = 'online_square' THEN p.amount ELSE 0 END), 0) AS online_total
|
||||
FROM payments p
|
||||
JOIN bookings b ON p.booking_id = b.id
|
||||
WHERE p.status = 'completed'
|
||||
@@ -799,15 +799,6 @@ $$ LANGUAGE plpgsql;
|
||||
-- UTILITY FUNCTIONS
|
||||
-- =======================================
|
||||
|
||||
-- VAT Registration Transition Function
|
||||
-- WHY: When crossing £90k threshold, must register for VAT and backfill existing data
|
||||
-- WHEN: One-time use when VAT registration becomes mandatory
|
||||
-- OUTPUT: Updates all historical payments with VAT breakdown + sets new defaults
|
||||
-- USE: Call once when we register for VAT - transforms business from non-VAT to VAT
|
||||
-- =======================================
|
||||
-- UTILITY FUNCTIONS
|
||||
-- =======================================
|
||||
|
||||
-- VAT Registration Transition Function
|
||||
-- WHY: When crossing £90k threshold, must register for VAT and backfill existing data
|
||||
-- WHEN: One-time use when VAT registration becomes mandatory
|
||||
@@ -831,7 +822,6 @@ DECLARE
|
||||
total_net NUMERIC(12,2);
|
||||
settings_ok BOOLEAN := FALSE;
|
||||
BEGIN
|
||||
-- Update all existing completed payments with VAT breakdown
|
||||
UPDATE payments
|
||||
SET
|
||||
vat_rate = enable_vat_registration.vat_rate,
|
||||
@@ -845,17 +835,15 @@ BEGIN
|
||||
|
||||
GET DIAGNOSTICS updated_count = ROW_COUNT;
|
||||
|
||||
-- Calculate totals
|
||||
SELECT
|
||||
COALESCE(SUM(vat_amount), 0),
|
||||
COALESCE(SUM(net_amount), 0)
|
||||
COALESCE(SUM(p.vat_amount), 0),
|
||||
COALESCE(SUM(p.net_amount), 0)
|
||||
INTO total_vat, total_net
|
||||
FROM payments
|
||||
FROM payments p
|
||||
WHERE status = 'completed'
|
||||
AND created_at::date >= registration_date
|
||||
AND vat_amount IS NOT NULL;
|
||||
|
||||
-- Update business_settings to reflect VAT registration
|
||||
UPDATE business_settings
|
||||
SET
|
||||
is_vat_registered = TRUE,
|
||||
@@ -877,19 +865,11 @@ BEGIN
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
-- Update payment with VAT (for new payments after VAT registration)
|
||||
-- WHY: After VAT registration, all new payments need VAT calculation
|
||||
-- WHEN: Called automatically when processing new payments (if VAT registered)
|
||||
-- WHEN: Called when processing new payments (if VAT registered)
|
||||
-- OUTPUT: Updates individual payment with VAT breakdown
|
||||
-- USE: Call from Go when processing new payments after VAT registration
|
||||
-- =======================================
|
||||
-- UPDATE PAYMENT WITH VAT
|
||||
-- =======================================
|
||||
-- WHY: After VAT registration, all new payments need VAT calculation
|
||||
-- WHEN: Called automatically when processing new payments (if VAT registered)
|
||||
-- OUTPUT: Updates individual payment with VAT breakdown
|
||||
-- USE: Call from Go or backend when processing new payments
|
||||
CREATE OR REPLACE FUNCTION apply_vat_to_payment(
|
||||
payment_id CHAR(12),
|
||||
vat_rate NUMERIC(5,2) DEFAULT NULL -- NULL = use default from business_settings
|
||||
@@ -898,20 +878,18 @@ RETURNS VOID AS $$
|
||||
DECLARE
|
||||
effective_rate NUMERIC(5,2);
|
||||
BEGIN
|
||||
-- Determine effective VAT rate
|
||||
IF vat_rate IS NULL THEN
|
||||
SELECT default_vat_rate INTO effective_rate
|
||||
FROM business_settings
|
||||
WHERE id = 1 AND is_vat_registered = TRUE;
|
||||
|
||||
IF NOT FOUND OR effective_rate IS NULL THEN
|
||||
RAISE EXCEPTION 'no vat rate provided and business is not registered for vat';
|
||||
RAISE EXCEPTION 'No VAT rate provided and business is not registered for VAT';
|
||||
END IF;
|
||||
ELSE
|
||||
effective_rate := vat_rate;
|
||||
END IF;
|
||||
|
||||
-- Update payment with VAT amounts
|
||||
UPDATE payments
|
||||
SET
|
||||
vat_rate = effective_rate,
|
||||
@@ -924,9 +902,7 @@ BEGIN
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- =======================================
|
||||
-- SIMPLE VAT CALCULATION HELPER
|
||||
-- =======================================
|
||||
-- Simple VAT Calculation Helper
|
||||
-- WHY: Manual VAT calculations or validation
|
||||
-- WHEN: Checking VAT calculations or manual entry corrections
|
||||
-- OUTPUT: net amount and vat amount from gross amount
|
||||
@@ -942,20 +918,18 @@ RETURNS TABLE(
|
||||
DECLARE
|
||||
effective_rate NUMERIC(5,2);
|
||||
BEGIN
|
||||
-- Determine effective VAT rate
|
||||
IF vat_rate IS NULL THEN
|
||||
SELECT default_vat_rate INTO effective_rate
|
||||
FROM business_settings
|
||||
WHERE id = 1 AND is_vat_registered = TRUE;
|
||||
|
||||
IF NOT FOUND OR effective_rate IS NULL THEN
|
||||
RAISE EXCEPTION 'no vat rate provided and business is not registered for vat';
|
||||
RAISE EXCEPTION 'No VAT rate provided and business is not registered for VAT';
|
||||
END IF;
|
||||
ELSE
|
||||
effective_rate := vat_rate;
|
||||
END IF;
|
||||
|
||||
-- Return net and VAT
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
ROUND(gross_amount / (1 + effective_rate / 100), 2) AS net,
|
||||
@@ -963,28 +937,20 @@ BEGIN
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
-- =======================================
|
||||
-- UK RECEIPT COMPLIANCE FUNCTION
|
||||
-- =======================================
|
||||
|
||||
/*
|
||||
UK RECEIPT REQUIREMENTS:
|
||||
- Business name and address (static - provided by application)
|
||||
- Business name and address
|
||||
- Date and time of transaction
|
||||
- Description of goods/services
|
||||
- Amount charged (including VAT if registered)
|
||||
- VAT breakdown (if VAT registered)
|
||||
- VAT breakdown (if VAT registered): VAT number, rate, VAT amount, net amount
|
||||
- Receipt/invoice number
|
||||
- Payment method
|
||||
- Customer details (if requested)
|
||||
|
||||
VAT RECEIPT REQUIREMENTS (if VAT registered):
|
||||
- VAT registration number (static - provided by application)
|
||||
- VAT rate applied
|
||||
- VAT amount
|
||||
- Net amount (excluding VAT)
|
||||
- Total amount (including VAT)
|
||||
- Customer details (if not anonymised)
|
||||
*/
|
||||
|
||||
-- Complete Receipt Data for UK Compliance
|
||||
@@ -1036,23 +1002,21 @@ RETURNS TABLE (
|
||||
currency_code TEXT
|
||||
) AS $$
|
||||
DECLARE
|
||||
bs RECORD; -- To hold business settings
|
||||
bs RECORD;
|
||||
BEGIN
|
||||
-- Fetch current business settings (assume single row, id=1)
|
||||
SELECT INTO bs
|
||||
business_name,
|
||||
business_address,
|
||||
business_phone,
|
||||
business_email,
|
||||
vat_registration_number,
|
||||
is_vat_registered,
|
||||
currency_code
|
||||
FROM business_settings
|
||||
WHERE id = 1;
|
||||
bs2.business_name,
|
||||
bs2.business_address,
|
||||
bs2.business_phone,
|
||||
bs2.business_email,
|
||||
bs2.vat_registration_number,
|
||||
bs2.is_vat_registered,
|
||||
bs2.currency_code
|
||||
FROM business_settings bs2
|
||||
WHERE bs2.id = 1;
|
||||
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
-- Business Info
|
||||
bs.business_name,
|
||||
bs.business_address,
|
||||
bs.business_phone,
|
||||
@@ -1060,68 +1024,67 @@ BEGIN
|
||||
bs.vat_registration_number,
|
||||
bs.is_vat_registered,
|
||||
|
||||
-- Payment Info
|
||||
p.id as payment_id,
|
||||
COALESCE('INV-' || p.invoice_number::text, 'INV-' || p.id) as invoice_number,
|
||||
p.created_at as transaction_date,
|
||||
p.payment_method::text as payment_method,
|
||||
p.status::text as payment_status,
|
||||
p.id AS payment_id,
|
||||
COALESCE('INV-' || p.invoice_number::text, 'INV-' || p.id) AS invoice_number,
|
||||
p.created_at AS transaction_date,
|
||||
p.payment_method::text AS payment_method,
|
||||
p.status::text AS payment_status,
|
||||
|
||||
-- Customer Info
|
||||
u.id as customer_id,
|
||||
u.id AS customer_id,
|
||||
CASE
|
||||
WHEN u.account_role = 'guest' OR u.fn = 'Deleted User' THEN 'Walk-in Customer'
|
||||
WHEN u.id IS NULL THEN 'Walk-in Customer'
|
||||
WHEN u.n_first_name = 'Deleted' THEN 'Walk-in Customer'
|
||||
WHEN u.account_role = 'guest' THEN 'Walk-in Customer'
|
||||
ELSE u.fn
|
||||
END as customer_name,
|
||||
END AS customer_name,
|
||||
CASE
|
||||
WHEN u.account_role = 'guest' OR u.fn = 'Deleted User' THEN NULL
|
||||
WHEN u.id IS NULL THEN NULL
|
||||
WHEN u.n_first_name = 'Deleted' THEN NULL
|
||||
WHEN u.account_role = 'guest' THEN NULL
|
||||
ELSE u.email
|
||||
END as customer_email,
|
||||
END AS customer_email,
|
||||
CASE
|
||||
WHEN u.account_role = 'guest' OR u.fn = 'Deleted User' THEN NULL
|
||||
WHEN u.id IS NULL THEN NULL
|
||||
WHEN u.n_first_name = 'Deleted' THEN NULL
|
||||
WHEN u.account_role = 'guest' THEN NULL
|
||||
ELSE u.phone
|
||||
END as customer_phone,
|
||||
END AS customer_phone,
|
||||
|
||||
-- Booking Info
|
||||
b.id as booking_id,
|
||||
b.start_time as appointment_date,
|
||||
b.status::text as booking_status,
|
||||
b.id AS booking_id,
|
||||
b.start_time AS appointment_date,
|
||||
b.status::text AS booking_status,
|
||||
|
||||
-- Service Details
|
||||
COALESCE((
|
||||
SELECT json_agg(
|
||||
json_build_object(
|
||||
'service_id', s.id,
|
||||
'name', s.name,
|
||||
'description', s.description,
|
||||
'price', s.price,
|
||||
'duration_minutes', s.duration_minutes
|
||||
'price', COALESCE(bsvc.override_price, s.price),
|
||||
'duration_minutes', COALESCE(bsvc.override_duration_minutes, s.duration_minutes)
|
||||
)
|
||||
ORDER BY s.name
|
||||
)
|
||||
FROM booking_services bs_inner
|
||||
JOIN services s ON bs_inner.service_id = s.id
|
||||
WHERE bs_inner.booking_id = b.id
|
||||
), '[]'::json) as services,
|
||||
FROM booking_services bsvc
|
||||
JOIN services s ON bsvc.service_id = s.id
|
||||
WHERE bsvc.booking_id = b.id
|
||||
), '[]'::json) AS services,
|
||||
|
||||
-- Total service duration
|
||||
COALESCE((
|
||||
SELECT SUM(s.duration_minutes)
|
||||
FROM booking_services bs_inner
|
||||
JOIN services s ON bs_inner.service_id = s.id
|
||||
WHERE bs_inner.booking_id = b.id
|
||||
), 0) as total_duration_minutes,
|
||||
SELECT SUM(COALESCE(bsvc.override_duration_minutes, s.duration_minutes))
|
||||
FROM booking_services bsvc
|
||||
JOIN services s ON bsvc.service_id = s.id
|
||||
WHERE bsvc.booking_id = b.id
|
||||
), 0)::INT AS total_duration_minutes,
|
||||
|
||||
-- Financial Info
|
||||
p.amount as gross_amount,
|
||||
COALESCE(p.net_amount, p.amount) as net_amount,
|
||||
COALESCE(p.vat_amount, 0.00) as vat_amount,
|
||||
p.vat_rate as vat_rate,
|
||||
p.is_vat_applicable as is_vat_applicable,
|
||||
p.amount AS gross_amount,
|
||||
COALESCE(p.net_amount, p.amount) AS net_amount,
|
||||
COALESCE(p.vat_amount, 0.00) AS vat_amount,
|
||||
p.vat_rate AS vat_rate,
|
||||
p.is_vat_applicable AS is_vat_applicable,
|
||||
|
||||
-- Receipt Metadata
|
||||
NOW() as receipt_generated_at,
|
||||
bs.currency_code as currency_code
|
||||
NOW() AS receipt_generated_at,
|
||||
bs.currency_code AS currency_code
|
||||
|
||||
FROM payments p
|
||||
JOIN bookings b ON p.booking_id = b.id
|
||||
@@ -1134,68 +1097,57 @@ $$ LANGUAGE plpgsql;
|
||||
-- FUNCTION USAGE SUMMARY
|
||||
-- =======================================
|
||||
/*
|
||||
This summary organizes all database functions by their primary purpose, legal basis, and expected usage frequency—
|
||||
providing a clear guide for integration into our application, compliance workflows, and business operations.
|
||||
This summary organises all database functions by purpose, legal basis, and expected
|
||||
usage frequency — providing a clear guide for application integration.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
1. FINANCIAL & TAX COMPLIANCE
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
REGULAR USE (called from API endpoints or UI):
|
||||
REGULAR USE (called from API endpoints):
|
||||
- get_sales_totals(start_date, end_date)
|
||||
→ Quick dashboard metrics: total revenue, transaction count, and payment method breakdown.
|
||||
→ Dashboard metrics: total revenue, transaction count, payment method breakdown.
|
||||
- get_receipt_data(payment_id)
|
||||
→ Generate a UK-compliant receipt after every completed payment.
|
||||
→ UK-compliant receipt after every completed payment.
|
||||
- calculate_vat(gross_amount, vat_rate)
|
||||
→ Utility for validating or manually computing VAT splits.
|
||||
→ Validate or manually compute VAT splits (only usable when VAT registered).
|
||||
|
||||
PERIODIC USE (monthly/quarterly for accounting or tax filing):
|
||||
PERIODIC USE (monthly/quarterly for accounting or HMRC):
|
||||
- export_sales_transactions(start_date, end_date, include_vat)
|
||||
→ Primary export for accounting software (QuickBooks, Xero). Set include_vat = true only if VAT-registered.
|
||||
→ Primary export for QuickBooks / Xero. Set include_vat=true only if VAT registered.
|
||||
- get_monthly_business_summary(start_date, end_date)
|
||||
→ Analyze trends: bookings, revenue, avg. transaction size, and payment method adoption.
|
||||
→ Revenue trends, avg. transaction, and payment method adoption month by month.
|
||||
- get_vat_return_data(start_date, end_date)
|
||||
→ MTD-ready VAT return summary for HMRC (only if VAT-registered).
|
||||
→ MTD-ready VAT return summary for HMRC (only relevant when VAT registered).
|
||||
|
||||
ONE-TIME OR TRANSITIONAL USE:
|
||||
ONE-TIME / TRANSITIONAL:
|
||||
- enable_vat_registration(registration_date, vat_rate, vat_reg_number)
|
||||
→ Run once when crossing the £90k VAT threshold to backfill historical payments.
|
||||
→ Run once on crossing the £90k VAT threshold; backfills historical payments.
|
||||
- apply_vat_to_payment(payment_id, vat_rate)
|
||||
→ Called automatically for every new payment after VAT registration.
|
||||
→ Called for every new payment after VAT registration.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
2. GDPR & DATA PRIVACY
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
ON-DEMAND USE (triggered by user request or admin action):
|
||||
ON-DEMAND (triggered by user request or admin action):
|
||||
- export_all_user_data(user_id)
|
||||
→ Full Subject Access Request (SAR) export in JSON (GDPR Article 15).
|
||||
→ Full Subject Access Request (SAR) export in JSON — GDPR Article 15.
|
||||
→ Includes profile, bookings (with service overrides), payments, patch tests.
|
||||
- anonymize_user(user_id)
|
||||
→ Right to Erasure for registered users (GDPR Article 17); preserves audit trail.
|
||||
→ Right to Erasure for registered users — GDPR Article 17.
|
||||
→ Preserves booking/payment audit trail; replaces PII with placeholders.
|
||||
→ NOTE: phone and date_of_birth use placeholder values (NOT NULL columns).
|
||||
- delete_guest_user(user_id)
|
||||
→ Complete deletion of guest accounts (no contractual basis).
|
||||
→ Complete deletion of guest accounts (no contractual retention basis).
|
||||
- update_data_consent(user_id, consent)
|
||||
→ Records updated consent preference with timestamp for auditability.
|
||||
→ Records updated consent with timestamp for auditability.
|
||||
|
||||
NOTE: Booking data is retained under contractual necessity (GDPR Art. 6(1)(b)) even if consent is withdrawn.
|
||||
Only optional data (e.g., marketing) is governed by consent.
|
||||
NOTE: Booking/payment data is retained under contractual necessity (GDPR Art. 6(1)(b))
|
||||
even after consent withdrawal. Only optional/marketing data is consent-governed.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
3. BUSINESS OPERATIONS & LOYALTY
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
- Referral Program: Use user_referrals table + referral_code in users (handled in app logic).
|
||||
- Patch Test Tracking: user_service_patch_tests enforces allergen safety (enforced in app based on services.patch_test_duration_hours).
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
4. UTILITY & MAINTENANCE
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
- generate_*_id() functions: Internal use only (DEFAULT in table definitions).
|
||||
- Timestamp triggers (e.g., on business_settings): Automatic—no manual call needed.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
INTEGRATION QUICK REFERENCE
|
||||
3. INTEGRATION QUICK REFERENCE
|
||||
--------------------------------------------------------------------------------
|
||||
• User deletes account → anonymize_user() or delete_guest_user()
|
||||
• "What data do you have?" → export_all_user_data()
|
||||
|
||||
Reference in New Issue
Block a user