Add/skv salary agi (#423)
* feat: add Bankgirot LB-fil support for salary payments and tax payments - Implemented `generateBgLb` for salary batch payments, producing opening, payment, and closing records. - Added tests for `generateBgLb` to ensure correct record generation and validation. - Created `generateBankgiroPaymentBgLb` for single tax payments to Skatteverket, including validation and formatting. - Added tests for `generateBankgiroPaymentBgLb` to verify record structure and data integrity. - Introduced `generateSkattekontoOcr` for generating valid OCR references for Skattekonto payments, with tests for various input formats. - Updated database schema to track payment file formats and timestamps for salary runs and AGI declarations. - Created a new table for logging salary payslip deliveries to ensure compliance with audit requirements. * feat: add write permission check and company ID validation for payment file generation * feat: add write permission check for salary payment file generation
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
-- Salary payment files: Bankgirot LB-fil support alongside existing pain.001 (SEPA).
|
||||
--
|
||||
-- Most Swedish SMBs upload Bankgirot LB-files to Swedbank/SEB/Handelsbanken/Nordea
|
||||
-- via their corporate portal — pain.001 is also supported but less common.
|
||||
--
|
||||
-- This migration:
|
||||
-- 1. Adds `preferred_payment_format` to `company_settings` so the UI can
|
||||
-- pre-select the right format per company.
|
||||
-- 2. Tracks which format was generated for each salary run, plus the
|
||||
-- generation timestamp (used by deadline reminders).
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- company_settings.preferred_payment_format
|
||||
-- ------------------------------------------------------------------
|
||||
ALTER TABLE public.company_settings
|
||||
ADD COLUMN IF NOT EXISTS preferred_payment_format text NOT NULL DEFAULT 'bg_lb';
|
||||
|
||||
-- Re-apply default + NOT NULL in case ADD COLUMN IF NOT EXISTS skipped them
|
||||
-- (column existed out-of-band).
|
||||
ALTER TABLE public.company_settings
|
||||
ALTER COLUMN preferred_payment_format SET DEFAULT 'bg_lb';
|
||||
|
||||
UPDATE public.company_settings
|
||||
SET preferred_payment_format = 'bg_lb'
|
||||
WHERE preferred_payment_format IS NULL;
|
||||
|
||||
ALTER TABLE public.company_settings
|
||||
ALTER COLUMN preferred_payment_format SET NOT NULL;
|
||||
|
||||
ALTER TABLE public.company_settings
|
||||
DROP CONSTRAINT IF EXISTS company_settings_preferred_payment_format_check;
|
||||
|
||||
ALTER TABLE public.company_settings
|
||||
ADD CONSTRAINT company_settings_preferred_payment_format_check
|
||||
CHECK (preferred_payment_format IN ('bg_lb', 'pain001'));
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- salary_runs payment file tracking
|
||||
-- ------------------------------------------------------------------
|
||||
ALTER TABLE public.salary_runs
|
||||
ADD COLUMN IF NOT EXISTS payment_file_format text,
|
||||
ADD COLUMN IF NOT EXISTS payment_file_generated_at timestamptz;
|
||||
|
||||
ALTER TABLE public.salary_runs
|
||||
DROP CONSTRAINT IF EXISTS salary_runs_payment_file_format_check;
|
||||
|
||||
ALTER TABLE public.salary_runs
|
||||
ADD CONSTRAINT salary_runs_payment_file_format_check
|
||||
CHECK (payment_file_format IS NULL OR payment_file_format IN ('bg_lb', 'pain001'));
|
||||
|
||||
-- Reload PostgREST schema cache.
|
||||
NOTIFY pgrst, 'reload schema';
|
||||
@@ -0,0 +1,26 @@
|
||||
-- AGI tax payment tracking.
|
||||
--
|
||||
-- An AGI declaration represents both the filing obligation and the resulting
|
||||
-- tax liability for the period (skatt + avgifter). We track when the payment
|
||||
-- file (Bankgirot LB to BG 5050-1055) was generated and when the payment
|
||||
-- actually cleared the bank.
|
||||
--
|
||||
-- This avoids a separate `tax_payment_runs` table — the AGI declaration is
|
||||
-- already keyed on (company_id, period_year, period_month) and carries the
|
||||
-- exact amounts owed.
|
||||
|
||||
ALTER TABLE public.agi_declarations
|
||||
ADD COLUMN IF NOT EXISTS tax_payment_file_generated_at timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS tax_payment_file_format text,
|
||||
ADD COLUMN IF NOT EXISTS tax_paid_at timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS tax_payment_journal_entry_id uuid REFERENCES public.journal_entries(id);
|
||||
|
||||
ALTER TABLE public.agi_declarations
|
||||
DROP CONSTRAINT IF EXISTS agi_declarations_tax_payment_format_check;
|
||||
|
||||
ALTER TABLE public.agi_declarations
|
||||
ADD CONSTRAINT agi_declarations_tax_payment_format_check
|
||||
CHECK (tax_payment_file_format IS NULL OR tax_payment_file_format IN ('bg_lb'));
|
||||
|
||||
-- Reload PostgREST schema cache so the columns become visible to the API.
|
||||
NOTIFY pgrst, 'reload schema';
|
||||
@@ -0,0 +1,72 @@
|
||||
-- Salary payslip delivery log.
|
||||
--
|
||||
-- Per BFL 7 kap.: delivery confirmation of lönespecifikationer must be
|
||||
-- retained as part of the audit trail. The send route currently calls Resend
|
||||
-- and returns the count, but doesn't persist a per-employee record — so we
|
||||
-- can't answer "did Anna receive her March payslip?" months later.
|
||||
--
|
||||
-- This table stores one row per (salary_run, employee, attempt) with the
|
||||
-- Resend message_id (if known) so a follow-up webhook can update status to
|
||||
-- delivered/bounced/complained.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.salary_payslip_deliveries (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
company_id uuid NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
|
||||
salary_run_id uuid NOT NULL REFERENCES public.salary_runs(id) ON DELETE CASCADE,
|
||||
employee_id uuid NOT NULL REFERENCES public.employees(id) ON DELETE RESTRICT,
|
||||
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
|
||||
email_address text NOT NULL,
|
||||
status text NOT NULL DEFAULT 'sent'
|
||||
CHECK (status IN ('sent', 'delivered', 'bounced', 'complained', 'failed', 'skipped')),
|
||||
|
||||
-- Resend (or other provider) tracking
|
||||
provider text NOT NULL DEFAULT 'resend',
|
||||
provider_message_id text,
|
||||
provider_event jsonb,
|
||||
|
||||
error_message text,
|
||||
|
||||
sent_at timestamptz NOT NULL DEFAULT now(),
|
||||
delivered_at timestamptz,
|
||||
bounced_at timestamptz,
|
||||
complained_at timestamptz,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.salary_payslip_deliveries ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "salary_payslip_deliveries_select"
|
||||
ON public.salary_payslip_deliveries
|
||||
FOR SELECT
|
||||
USING (company_id IN (SELECT public.user_company_ids()));
|
||||
|
||||
CREATE POLICY "salary_payslip_deliveries_insert"
|
||||
ON public.salary_payslip_deliveries
|
||||
FOR INSERT
|
||||
WITH CHECK (company_id IN (SELECT public.user_company_ids()));
|
||||
|
||||
CREATE POLICY "salary_payslip_deliveries_update"
|
||||
ON public.salary_payslip_deliveries
|
||||
FOR UPDATE
|
||||
USING (company_id IN (SELECT public.user_company_ids()));
|
||||
|
||||
-- No DELETE policy — BFL 7 kap. requires retention of delivery records.
|
||||
|
||||
CREATE INDEX idx_payslip_deliveries_run
|
||||
ON public.salary_payslip_deliveries (salary_run_id);
|
||||
|
||||
CREATE INDEX idx_payslip_deliveries_company
|
||||
ON public.salary_payslip_deliveries (company_id);
|
||||
|
||||
CREATE INDEX idx_payslip_deliveries_provider_msg
|
||||
ON public.salary_payslip_deliveries (provider_message_id)
|
||||
WHERE provider_message_id IS NOT NULL;
|
||||
|
||||
CREATE TRIGGER salary_payslip_deliveries_updated_at
|
||||
BEFORE UPDATE ON public.salary_payslip_deliveries
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
NOTIFY pgrst, 'reload schema';
|
||||
Reference in New Issue
Block a user