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