- CREATE TABLE reports (
- id bigserial primary key not null,
- report_id int NOT NULL,
- account text not null,
- scn text not null, -- SCN is the check #
- their_balance numeric,
- our_balance numeric,
- errorcode INT,
- user int references entity(id) not null, -- why ois this not an entity reference?
- corrections INT NOT NULL DEFAULT 0
- clear_time TIMESTAMP NOT NULL,
- insert_time TIMESTAMPTZ NOT NULL DEFAULT now(),
- ledger_id int REFERENCES acc_trans(entry_id),
- overlook boolean not null default 'f',
- approved boolean not null default 'f'
- );
- CREATE TABLE coa_to_account (
- chart_id int not null references chart(id),
- account text not null CHECK (account ~ '[0-9]{7}(xxx)')
- );
- CREATE TABLE report_corrections (
- id serial primary key not null,
- correction_id int not null default 1,
- entry references reports(id) not null,
- user references entity(id) not null, -- why is this not an entity reference?
- reason text not null,
- insert_time timestamptz not null default now()
- );
- -- to correct OUR wrong amount.
- CREATE OR REPLACE FUNCTION reconciliation__correct_ledger (in_report_id INT, in_id int, in_new_amount NUMERIC, reason TEXT) returns INT AS $$
- DECLARE
- new_code INT;
- current_row RECORD;
- l_row acc_trans;
- in_user TEXT;
- full_reason TEXT;
- BEGIN
- select into in_user from current_user;
-
- select into current_row from reports where reports.id = in_report_id and reports.id = in_id;
- select into l_row from acc_trans where entry_id = current_row.lid;
-
- IF NOT FOUND THEN
- RAISE EXCEPTION 'No such id % in this report.', in_scn;
- END IF;
-
- IF user <> current_row.user THEN
-
- IF current_row.our_balance <> in_new_amount AND in_new_amount = current_row.their_balance THEN
- update reports pr
- set pr.corrections = reports.corrections + 1,
- pr.new_balance = in_new_amount,
- error_code = 0
- where id = in_report_id and scn = in_scn;
- return 0;
-
- -- After that, it is required to update the general ledger.
- full_reason := "User % is filing a reconciliation correction on the general ledger, changing amount % to amount %.
- Their reason given is: %", in_user, current_row.our_balance, in_new_amount, reason;
- perform select reconciliation__update_ledger(current_row.lid, in_new_amount, full_reason)
- ELSE IF current_row.our_balance = in_new_amount THEN
- -- This should be something, does it equal the original
- -- balance? If so, there's no change.
- return current_row.error_code;
- END IF;
- END IF;
-
- return current_row.error_code;
-
- END;
- $$ language 'plpgsql';
- -- to correct an incorrect bank statement value.
- CREATE OR REPLACE FUNCTION reconciliation__correct_bank_statement (in_report_id INT, in_id int, in_new_amount NUMERIC) returns INT AS $$
- DECLARE
- new_code INT;
- current_row RECORD;
- in_user TEXT;
- BEGIN
- select into in_user from current_user;
- select into current_row from reports where reports.id = in_id and reports.report_id = in_report_id;
- IF NOT FOUND THEN
- RAISE EXCEPTION 'No such SCN % in this report.', in_scn;
- END IF;
-
- IF user <> current_row.user THEN
-
- IF current_row.their_balance <> in_new_amount AND in_new_amount = current_row.our_balance THEN
- update reports pr
- set pr.corrections = reports.corrections + 1,
- pr.new_balance = in_new_amount,
- error_code = 0
- where id = in_report_id and scn = in_scn;
- return 0;
-
- ELSE IF current_row.their_balance = in_new_amount THEN
- -- This should be something, does it equal the original
- -- balance? If so, there's no change.
- return current_row.error_code;
- END IF;
- END IF;
-
- return current_row.error_code;
-
- END;
- $$ language 'plpgsql';
- CREATE OR REPLACE reconciliation__correct_passthrough ( in_report_id int, in_id int ) returns INT AS $$
- DECLARE
- in_user TEXT;
- pending_entry reports;
- BEGIN
- select into in_user from current_user;
-
- select into pending_entry from reports where report_id = in_report_id and id = in_id;
-
- IF NOT FOUND THEN
- -- Raise an exception.
- RAISE EXCEPTION "Cannot find entry.";
- ELSE IF pending_entry.errorcode <> 4 THEN
- -- Only error codes of 4 may be "passed through" safely.
- RAISE EXCEPTION "Selected entry not permitted to be passed through.";
-
- ELSE
- -- Then we mark it passthroughable, and "approve" will overlook it.
- update reports set overlook = 't', errorcode = 0 where report_id = in_report_id and id = in_id;
- return 0;
- END IF;
- END;
- $$ language 'plpgsql';
- CREATE OR REPLACE FUNCTION reconciliation__correct_bank_charge (in_report_id int, in_id int) returns INT AS $$
- DECLARE
- in_user TEXT;
- pending_entry reports;
- BEGIN
-
- IF NOT FOUND THEN
- -- Raise an exception.
- RAISE EXCEPTION "Cannot find entry with ID % in report %.", in_id, in_report_id;
- ELSE IF pending_entry.errorcode <> 2 THEN
- -- Only error codes of 2 may be "passed through" safely.
- RAISE EXCEPTION "Attempt to retroactively add a non-bank-charge entry to the ledger.";
-
- ELSE
- -- Then we mark it passthroughable, and "approve" will overlook it.
-
- select create_entry (pending_entry.their_balance, 'payable', pending_entry.clear_date, 'Bank charge');
-
- update reports set errorcode = 0 where report_id = in_report_id and id = in_id;
- return 0;
- END IF;
- END;
- $$ LANGUAGE 'plpgsql';
- CREATE OR REPLACE FUNCTION reconciliation__correct_unaccounted_charge (in_report_id int, in_id int, reason TEXT) RETURNS INT AS $$
- DECLARE
- in_user TEXT;
- pending_entry reports;
- note TEXT;
- BEGIN
-
- IF NOT FOUND THEN
- -- Raise an exception.
- RAISE EXCEPTION "Cannot find entry with ID % in report %.", in_id, in_report_id;
- ELSE IF pending_entry.errorcode <> 3 THEN
- -- Only error codes of 3 may be "passed through" safely.
- RAISE EXCEPTION "Not an unaccounted charge; cannot be retroactively added to the ledger.";
-
- ELSE
- -- Then we mark it passthroughable, and "approve" will overlook it.
-
- note := 'Retroactive addition of an unaccounted entry, of value %.
- Being added by user % with the following explanation: %', pending_entry.their_balance, in_user, in_reason;
-
- select create_entry (pending_entry.their_balance, 'payable', pending_entry.clear_date,note);
-
- update reports set errorcode = 0 where report_id = in_report_id and id = in_id;
- return in_id;
- END IF;
- END;
- $$ language 'plpgsql';
- CREATE OR REPLACE FUNCTION reconciliation__report_approve (in_report_id INT) returns INT as $$
-
- -- Does some basic checks before allowing the approval to go through;
- -- moves the approval to "reports", I guess, or some other "final" table.
- --
- -- Pending may just be a single flag in the database to mark that it is
- -- not finalized. Will need to discuss with Chris.
-
- DECLARE
- current_row RECORD;
- completed reports;
- total_errors INT;
- in_user TEXT;
- BEGIN
-
- select into in_user current_user;
- select into current_row distinct on user * from reports where report_id = in_report_id;
- IF NOT FOUND THEN
- RAISE EXCEPTION "Fatal Error: Pending report % not found", in_report_id;
- END IF;
-
- IF current_row.user = in_user THEN
- RAISE EXCEPTION "Fatal Error: User % cannot self-approve report!", in_user;
- END IF;
-
- SELECT INTO total_errors count(*) from reports where report_id = in_report_id and error_code <> 0;
-
- IF total_errors <> 0 THEN
- RAISE EXCEPTION "Fatal Error: Cannot approve while % uncorrected errors remain.", total_errors;
- END IF;
-
- -- so far, so good. Different user, and no errors remain. Therefore, we can move it to completed reports.
- --
- -- User may not be necessary - I would think it better to use the
- -- in_user, to note who approved the report, than the user who
- -- filed it. This may require clunkier syntax..
-
- --
-
- update reports set approved = 't', clear_time = now() where report_id = in_report_id;
-
- return 1;
- END;
- $$ language 'plpgsql';
- CREATE OR REPLACE FUNCTION reconciliation__new_report_id () returns INT as $$
- SELECT nextval('pending_report_report_id_seq');
- $$ language 'sql';
- create or replace function reconciliation__add_entry(
- in_report_id INT,
- in_scn INT,
- in_amount numeric,
- in_account INT,
- in_user TEXT,
- in_date TIMESTAMP
- ) RETURNS INT AS $$
-
- DELCARE
- la RECORD;
- errorcode INT;
- our_value NUMERIC;
- lid INT;
- BEGIN
-
- SELECT INTO la FROM acc_trans gl
- JOIN chart c on gl.chart_id = c.id
- JOIN ap ON gl.trans_id = ap.id
- JOIN coa_to_account cta on cta.chart_id = gl.chart_id
- WHERE gl.source ~ in_scn -- does it look like it?
- and cta.account = in_account
- and gl.amount = in_amount
- AND gl.transdate = in_date;
-
- lid := NULL;
- IF NOT FOUND THEN
- -- they have it, we don't. This is Bad, and implies either a bank
- -- charge or an unaccounted cheque.
-
- if in_scn <> '' and in_scn IS NOT NULL THEN
-
- -- It's a bank charge. Approval action will probably be
- -- adding it as an entry to the general ledger.
- errorcode := 2;
- our_value := 0;
- ELSE
- -- Okay, now this is bad.
- -- They have a cheque/sourced charge that we don't.
- -- REsolution action is going to be
- errorcode := 3;
- our_value := 0;
- END IF;
-
- ELSE if la.amount <> in_amount THEN
-
- errorcode := 1;
- our_value := la.amount;
- lid := la.entry_id;
-
- ELSE
- -- it reconciles. No problem.
-
- errorcode := 0;
- our_value := la.amount;
- lid := la.entry_id;
-
- END IF;
-
- INSERT INTO reports (
- report_id,
- scn,
- their_balance,
- our_balance,
- error_code,
- user,
- clear_time,
- ledger_id
- )
- VALUES (
- in_report_id,
- in_scn,
- in_amount,
- gl.amount,
- errorcode,
- in_user,
- in_date,
- lid
- );
-
- -- success, basically. This could very likely be collapsed to
- -- do the compare check here, instead of in the Perl app. Save us a DB
- -- call.
- return 1;
-
- END;
- $$ language 'plpgsql';
- create or replace function reconciliation__pending_transactions (in_month DATE) RETURNS setof acc_trans as $$
-
- DECLARE
- gl_row acc_trans;
- BEGIN
- FOR gl_row IN
- select gl.* from acc_trans gl, reports pr
- where gl.cleared = 'f'
- and date_trunc('month',gl.transdate) <= date_trunc('month', in_month)
- and gl.entry_id <> pr.ledger_id -- there's no entries in the reports for this
- LOOP
- RETURN NEXT gl_row;
- END LOOP;
- END;
- $$ LANGUAGE plpgsql;
- CREATE OR REPLACE FUNCTION reconciliation__report (in_report_id INT) RETURNS setof reports as $$
- DECLARE
- row reports;
- BEGIN
- FOR row IN select * from reports where report_id = in_report_id LOOP
-
- RETURN NEXT row;
-
- END LOOP;
- END;
- $$ language 'plpgsql';
- CREATE OR REPLACE FUNCTION reconciliation__get_total (in_report_id INT) returns setof reports AS $$
- DECLARE
- row reports;
- BEGIN
-
- SELECT INTO row FROM reports
- WHERE ledger_id IS NULL
- and report_id = in_report_id
- AND scn = -1;
-
- IF NOT FOUND THEN -- I think this is a fairly major error condition
- RAISE EXCEPTION "No Bank Total found.";
- ELSE
- return row;
- END IF;
- END;
- $$ language 'plpgsql';
- CREATE OR REPLACE FUNCTION reconciliation__corrections (in_report_id INT, in_id INT) returns setof report_corrections AS $$
- DECLARE
- corr report_corrections;
- BEGIN
-
- SELECT INTO corr FROM report_corrections WHERE report_id = in_report_id AND id = in_id LIMIT 1;
- IF NOT FOUND THEN
- RAISE EXCEPTION "No corrections for selected entry.";
- ELSE
- FOR corr IN select * from report_corrections WHERE report_id = in_report_id AND id = in_id LOOP
- RETURN NEXT corr;
- END LOOP;
- END IF;
- END;
- $$ language 'plplsql';
- CREATE OR REPLACE FUNCTION reconciliation__single_entry (in_report_id INT, in_id INT) returns setof reports AS $$
- DECLARE
- row reports;
- BEGIN
-
- SELECT INTO row FROM reports WHERE report_id = in_report_id and id = in_id LIMIT 1;
- -- if there's more than one, that's a Bad Thing
-
- IF NOT FOUND THEN
- RAISE EXCEPTION "Could not find selected report entry";
- ELSE
- RETURN row;
- END IF;
- END;
- $$ language 'plpgsql';
- CREATE OR REPLACE FUNCTION reconciliation__search (
- in_date_begin DATE,
- in_date_end DATE,
- in_account TEXT,
- in_status TEXT
- ) RETURNS setof reports AS $$
- DECLARE
- row reports;
- statement text;
- where_stmt text;
- v_status BOOLEAN;
- v_accum NUMERIC;
- BEGIN
-
- if in_status = "pending" then
- v_status = 'ft'::bool;
- ELSIF in_status = "approved" THEN
-
- v_status = 't'::bool;
- END IF;
-
- IF in_date_begin IS NOT NULL
- or in_date_end IS NOT NULL
- or in_account IS NOT NULL
- or v_status IS NOT NULL
- THEN
- statement = "select pr.* from reports pr ";
- statement = statement + "join acc_trans at on pr.ledger_id = at.entry_id ";
-
- IF in_account IS NOT NULL THEN
-
- statement = statement + "join chart c on at.chart_id = c.id ";
- where_stmt = "c.accno =~ " + quote_literal(in_account) + " AND ";
- END IF;
-
- IF in_date_begin IS NOT NULL THEN
- where_stmt = where_stmt + "insert_time >= " + quote_literal(in_date_begin) + " AND ";
- END IF;
-
- IF in_date_end IS NOT NULL THEN
- where_stmt = where_stmt + "insert_time <= " + quote_literal(in_date_end) + " AND ";
- END IF;
-
- IF in_status IS NOT NULL THEN
-
- if v_status == 't'::bool THEN
- where_stmt = where_stmt + " approved = 't'::bool AND ";
- ELSIF v_status == 'f'::bool THEN
- where_stmt = where_stmt + " approved = 'f'::bool AND ";
- END IF;
-
- END IF;
-
- FOR row in EXECUTE statement LOOP
- RETURN NEXT row;
- END LOOP;
- ELSE
-
- FOR row IN SELECT * FROM reports LOOP
- RETURN NEXT row;
- END LOOP;
-
- END IF;
- END;
- $$ language 'plpgsql';
- create type recon_accounts as (
- name text,
- id int
- );
- create or replace function reconciliation__account_list () returns setof recon_accounts as $$
- SELECT
- coa.accno || ' ' || coa.description as name,
- coa.id as id
- FROM chart coa, coa_to_account cta
- WHERE cta.chart_id = coa.id;
- $$ language sql;
|