summaryrefslogtreecommitdiff
path: root/sql/modules/Reconciliaton.sql
blob: 2142a1fe448159e6e7f41aee7761b5bc6d87acf0 (plain)
  1. CREATE TABLE pending_reports (
  2. id bigserial primary key not null,
  3. report_id int,
  4. scn int,
  5. their_balance INT,
  6. our_balance INT,
  7. errorcode INT,
  8. user TEXT,
  9. corrections INT NOT NULL DEFAULT 0
  10. clear_time TIMESTAMP NOT NULL,
  11. insert_time TIMESTAMPTZ NOT NULL DEFAULT now(),
  12. ledger_id int REFERENCES acc_trans(entry_id),
  13. overlook boolean not null default 'f'
  14. );
  15. CREATE TABLE report_corrections (
  16. id serial primary key not null,
  17. correction_id int not null default 1,
  18. entry references pending_reports(id) not null,
  19. user text not null,
  20. reason text not null,
  21. insert_time timestamptz not null default now()
  22. );
  23. -- to correct OUR wrong amount.
  24. CREATE OR REPLACE FUNCTION reconciliation_correct_ledger (in_report_id INT, in_id int, in_new_amount NUMERIC, reason TEXT) returns INT AS $$
  25. DECLARE
  26. new_code INT;
  27. current_row RECORD;
  28. l_row acc_trans;
  29. in_user TEXT;
  30. full_reason TEXT;
  31. BEGIN
  32. select into in_user from current_user;
  33. select into current_row from pending_reports where pending_reports.id = in_report_id and pending_reports.id = in_id;
  34. select into l_row from acc_trans where entry_id = current_row.lid;
  35. IF NOT FOUND THEN
  36. RAISE EXCEPTION 'No such id % in this report.', in_scn;
  37. END IF;
  38. IF user <> current_row.user THEN
  39. IF current_row.our_balance <> in_new_amount AND in_new_amount = current_row.their_balance THEN
  40. update pending_reports pr
  41. set pr.corrections = pending_reports.corrections + 1,
  42. pr.new_balance = in_new_amount,
  43. error_code = 0
  44. where id = in_report_id and scn = in_scn;
  45. return 0;
  46. -- After that, it is required to update the general ledger.
  47. full_reason := "User % is filing a reconciliation correction on the general ledger, changing amount % to amount %.
  48. Their reason given is: %", in_user, current_row.our_balance, in_new_amount, reason;
  49. select update_ledger(current_row.lid, in_new_amount, full_reason)
  50. ELSE IF current_row.our_balance = in_new_amount THEN
  51. -- This should be something, does it equal the original
  52. -- balance? If so, there's no change.
  53. return current_row.error_code;
  54. END IF;
  55. END IF;
  56. return current_row.error_code;
  57. END;
  58. $$ language 'plpgsql';
  59. -- to correct an incorrect bank statement value.
  60. CREATE OR REPLACE FUNCTION reconciliation_correct_bank_statement (in_report_id INT, in_id int, in_new_amount NUMERIC) returns INT AS $$
  61. DECLARE
  62. new_code INT;
  63. current_row RECORD;
  64. in_user TEXT;
  65. BEGIN
  66. select into in_user from current_user;
  67. select into current_row from pending_reports where pending_reports.id = in_report_id and pending_reports.scn = in_scn;
  68. IF NOT FOUND THEN
  69. RAISE EXCEPTION 'No such SCN % in this report.', in_scn;
  70. END IF;
  71. IF user <> current_row.user THEN
  72. IF current_row.their_balance <> in_new_amount AND in_new_amount = current_row.our_balance THEN
  73. update pending_reports pr
  74. set pr.corrections = pending_reports.corrections + 1,
  75. pr.new_balance = in_new_amount,
  76. error_code = 0
  77. where id = in_report_id and scn = in_scn;
  78. return 0;
  79. ELSE IF current_row.their_balance = in_new_amount THEN
  80. -- This should be something, does it equal the original
  81. -- balance? If so, there's no change.
  82. return current_row.error_code;
  83. END IF;
  84. END IF;
  85. return current_row.error_code;
  86. END;
  87. $$ language 'plpgsql';
  88. CREATE OR REPLACE reconciliation_correct_passthrough ( in_report_id int, in_id int ) returns INT AS $$
  89. DECLARE
  90. in_user TEXT;
  91. pending_entry pending_reports;
  92. BEGIN
  93. select into in_user from current_user;
  94. select into pending_entry from pending_reports where report_id = in_report_id and id = in_id;
  95. IF NOT FOUND THEN
  96. -- Raise an exception.
  97. RAISE EXCEPTION "Cannot find entry.";
  98. ELSE IF pending_entry.errorcode <> 4 THEN
  99. -- Only error codes of 4 may be "passed through" safely.
  100. RAISE EXCEPTION "Selected entry not permitted to be passed through.";
  101. ELSE
  102. -- Then we mark it passthroughable, and "approve" will overlook it.
  103. update pending_reports set overlook = 't', errorcode = 0 where report_id = in_report_id and id = in_id;
  104. return 0;
  105. END IF;
  106. END;
  107. $$ language 'plpgsql';
  108. CREATE OR REPLACE FUNCTION reconciliation_correct_bank_charge (in_report_id int, in_id int) returns INT AS $$
  109. DECLARE
  110. in_user TEXT;
  111. pending_entry pending_reports;
  112. BEGIN
  113. select into pending_entry from pending_reports where report_id = in_report_id and id = in_id;
  114. IF NOT FOUND THEN
  115. -- Raise an exception.
  116. RAISE EXCEPTION "Cannot find entry with ID % in report %.", in_id, in_report_id;
  117. ELSE IF pending_entry.errorcode <> 2 THEN
  118. -- Only error codes of 4 may be "passed through" safely.
  119. RAISE EXCEPTION "Attempt to retroactively add a non-bank-charge entry to the ledger.";
  120. ELSE
  121. -- Then we mark it passthroughable, and "approve" will overlook it.
  122. select create_entry (pending_entry.their_balance, 'payable', pending_entry.clear_date, 'Bank charge');
  123. update pending_reports set errorcode = 0 where report_id = in_report_id and id = in_id;
  124. return 0;
  125. END IF;
  126. END;
  127. $$ LANGUAGE 'plpgsql';
  128. CREATE OR REPLACE FUNCTION reconciliation_correct_unaccounted_charge (in_report_id int, in_id int, reason TEXT) RETURNS INT AS $$
  129. DECLARE
  130. in_user TEXT;
  131. pending_entry pending_reports;
  132. note TEXT;
  133. BEGIN
  134. select into pending_entry from pending_reports where report_id = in_report_id and id = in_id;
  135. IF NOT FOUND THEN
  136. -- Raise an exception.
  137. RAISE EXCEPTION "Cannot find entry with ID % in report %.", in_id, in_report_id;
  138. ELSE IF pending_entry.errorcode <> 3 THEN
  139. -- Only error codes of 4 may be "passed through" safely.
  140. RAISE EXCEPTION "Not an unaccounted charge; cannot be retroactively added to the ledger.";
  141. ELSE
  142. -- Then we mark it passthroughable, and "approve" will overlook it.
  143. note := 'Retroactive addition of an unaccounted entry, of value %.
  144. Being added by user % with the following explanation: %', pending_entry.their_balance, in_user, in_reason;
  145. select create_entry (pending_entry.their_balance, 'payable', pending_entry.clear_date,note);
  146. update pending_reports set errorcode = 0 where report_id = in_report_id and id = in_id;
  147. return 0;
  148. END IF;
  149. END;
  150. $$ language 'plpgsql';
  151. CREATE OR REPLACE FUNCTION reconciliation_report_approve (in_report_id INT) returns INT as $$
  152. -- Does some basic checks before allowing the approval to go through;
  153. -- moves the approval to "reports", I guess, or some other "final" table.
  154. --
  155. -- Pending may just be a single flag in the database to mark that it is
  156. -- not finalized. Will need to discuss with Chris.
  157. DECLARE
  158. current_row RECORD;
  159. completed pending_reports;
  160. total_errors INT;
  161. in_user TEXT;
  162. BEGIN
  163. select into in_user current_user;
  164. select into current_row distinct on user * from pending_reports where report_id = in_report_id;
  165. IF NOT FOUND THEN
  166. RAISE EXCEPTION "Fatal Error: Pending report % not found", in_report_id;
  167. END IF;
  168. IF current_row.user = in_user THEN
  169. RAISE EXCEPTION "Fatal Error: User % cannot self-approve report!", in_user;
  170. END IF;
  171. SELECT INTO total_errors count(*) from pending_reports where report_id = in_report_id and error_code <> 0;
  172. IF total_errors <> 0 THEN
  173. RAISE EXCEPTION "Fatal Error: Cannot approve while % uncorrected errors remain.", total_errors;
  174. END IF;
  175. -- so far, so good. Different user, and no errors remain. Therefore, we can move it to completed reports.
  176. --
  177. -- User may not be necessary - I would think it better to use the
  178. -- in_user, to note who approved the report, than the user who
  179. -- filed it. This may require clunkier syntax..
  180. insert into reports (report_id, scn, their_balance, our_balance, code, user, correction )
  181. SELECT report_id, scn, their_balance, our_balance, code, user, corrections
  182. FROM pending_reports
  183. WHERE report_id = in_report_id;
  184. return 1;
  185. END;
  186. $$ language 'plpgsql';
  187. CREATE OR REPLACE FUNCTION reconciliation_new_report_id () returns INT as $$
  188. SELECT nextval('pending_report_report_id_seq');
  189. $$ language 'sql';
  190. create or replace function reconciliation_add_entry(in_report_id INT, in_scn INT, in_amount INT, in_account INT, in_user TEXT, in_date TIMESTAMP) RETURNS INT AS $$
  191. DELCARE
  192. la RECORD;
  193. errorcode INT;
  194. our_value NUMERIC;
  195. lid INT;
  196. BEGIN
  197. SELECT INTO la FROM acc_trans gl
  198. WHERE gl.source = in_scn
  199. and gl.account = in_account
  200. and gl.amount = in_amount;
  201. lid := NULL;
  202. IF NOT FOUND THEN
  203. -- they have it, we don't. This is Bad, and implies either a bank
  204. -- charge or an unaccounted cheque.
  205. if in_scn <> '' and in_scn IS NOT NULL THEN
  206. -- It's a bank charge. Approval action will probably be
  207. -- adding it as an entry to the general ledger.
  208. errorcode := 2;
  209. our_value := 0;
  210. ELSE
  211. -- Okay, now this is bad.
  212. -- They have a cheque/sourced charge that we don't.
  213. -- REsolution action is going to be
  214. errorcode := 3;
  215. our_value := 0;
  216. END IF;
  217. ELSE if la.amount <> in_amount THEN
  218. errorcode := 1;
  219. our_value := la.amount;
  220. lid := la.entry_id;
  221. ELSE
  222. -- it reconciles. No problem.
  223. errorcode := 0;
  224. our_value := la.amount;
  225. lid := la.entry_id;
  226. END IF;
  227. INSERT INTO pending_reports (
  228. report_id,
  229. scn,
  230. their_balance,
  231. our_balance,
  232. error_code,
  233. user,
  234. clear_time,
  235. ledger_id
  236. )
  237. VALUES (
  238. in_report_id,
  239. in_scn,
  240. in_amount,
  241. gl.balance,
  242. errorcode,
  243. in_user,
  244. in_date,
  245. lid
  246. );
  247. -- success, basically. This could very likely be collapsed to
  248. -- do the compare check here, instead of in the Perl app. Save us a DB
  249. -- call.
  250. return 1;
  251. END;
  252. $$ language 'plpgsql';
  253. create or replace function reconciliation_pending_transactions (in_report_id INT, in_month TIMESTAMP, in_user INT) RETURNS setof int as $$
  254. DECLARE
  255. gl_row acc_trans;
  256. BEGIN
  257. FOR gl_row IN
  258. select gl.* from acc_trans gl, pending_reports pr
  259. where gl.cleared = 'f'
  260. and date_trunc('month',gl.transdate) <= date_trunc('month', in_month)
  261. and gl.entry_id <> pr.lid
  262. LOOP
  263. INSERT INTO pending_reports (
  264. report_id,
  265. scn,
  266. their_balance,
  267. our_balance,
  268. error_code,
  269. user,
  270. clear_time,
  271. ledger_id
  272. )
  273. VALUES (
  274. in_report_id, -- add it to the report ID
  275. gl_row.source, -- the source control number
  276. 0, -- The banks' amount for the transaction
  277. gl_row.amount, -- our recorded amount
  278. 4, -- The error code, meaning it's uncleared.
  279. in_user, -- the report-generating user
  280. in_month, -- basically, right now.
  281. gl_row.entry_id -- the foreign key to the ledger
  282. );
  283. END LOOP;
  284. END;
  285. $$ language plpgsql;
  286. CREATE OR REPLACE FUNCTION reconciliation_report (in_report_id INT) RETURNS setof pending_reports as $$
  287. DECLARE
  288. row pending_reports;
  289. BEGIN
  290. FOR row IN select * from pending_reports where report_id = in_report_id LOOP
  291. RETURN NEXT row;
  292. END LOOP;
  293. END;
  294. $$ language 'plpgsql';
  295. CREATE OR REPLACE FUNCTION reconciliation_get_total (in_report_id INT) returns setof pending_reports AS $$
  296. DECLARE
  297. row pending_reports;
  298. BEGIN
  299. SELECT INTO row FROM pending_reports
  300. WHERE ledger_id IS NULL
  301. and report_id = in_report_id
  302. AND scn = -1;
  303. IF NOT FOUND THEN -- I think this is a fairly major error condition
  304. RAISE EXCEPTION "No Bank Total found.";
  305. ELSE
  306. return row;
  307. END IF;
  308. END;
  309. $$ language 'plpgsql';
  310. CREATE OR REPLACE FUNCTION reconciliation_corrections (in_report_id INT, in_id INT) returns setof report_corrections AS $$
  311. DECLARE
  312. corr report_corrections;
  313. BEGIN
  314. SELECT INTO corr FROM report_corrections WHERE report_id = in_report_id AND id = in_id LIMIT 1;
  315. IF NOT FOUND THEN
  316. RAISE EXCEPTION "No corrections for selected entry.";
  317. ELSE
  318. FOR corr IN select * from report_corrections WHERE report_id = in_report_id AND id = in_id LOOP
  319. RETURN NEXT corr;
  320. END LOOP;
  321. END IF;
  322. END;
  323. $$ language 'plplsql';
  324. CREATE OR REPLACE FUNCTION reconciliation_single_entry (in_report_id INT, in_id INT) returns setof pending_reports AS
  325. DECLARE
  326. row pending_reports;
  327. BEGIN
  328. SELECT INTO row FROM pending_reports WHERE report_id = in_report_id and id = in_id LIMIT 1;
  329. -- if there's more than one, that's a Bad Thing
  330. IF NOT FOUND THEN
  331. RAISE EXCEPTION "Could not find selected report entry";
  332. ELSE
  333. RETURN row;
  334. END IF;
  335. END;
  336. $$ language 'plpgsql';