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