summaryrefslogtreecommitdiff
path: root/sql/modules/Reconciliaton.sql
blob: b8aa2d97986c3772ec27720d6b166ed2017fe131 (plain)
  1. CREATE TABLE reports (
  2. id bigserial primary key not null,
  3. report_id int NOT NULL,
  4. account text not null,
  5. scn text not null, -- What is SCN?
  6. their_balance numeric,
  7. our_balance numeric,
  8. errorcode INT,
  9. user int references entity(id) not null, -- why ois this not an entity reference?
  10. corrections INT NOT NULL DEFAULT 0
  11. clear_time TIMESTAMP NOT NULL,
  12. insert_time TIMESTAMPTZ NOT NULL DEFAULT now(),
  13. ledger_id int REFERENCES acc_trans(entry_id),
  14. overlook boolean not null default 'f',
  15. approved boolean not null default 'f'
  16. );
  17. CREATE TABLE report_corrections (
  18. id serial primary key not null,
  19. correction_id int not null default 1,
  20. entry references reports(id) not null,
  21. user references entity(id) not null, -- why is this not an entity reference?
  22. reason text not null,
  23. insert_time timestamptz not null default now()
  24. );
  25. -- to correct OUR wrong amount.
  26. CREATE OR REPLACE FUNCTION reconciliation__correct_ledger (in_report_id INT, in_id int, in_new_amount NUMERIC, reason TEXT) returns INT AS $$
  27. DECLARE
  28. new_code INT;
  29. current_row RECORD;
  30. l_row acc_trans;
  31. in_user TEXT;
  32. full_reason TEXT;
  33. BEGIN
  34. select into in_user from current_user;
  35. select into current_row from reports where reports.id = in_report_id and reports.id = in_id;
  36. select into l_row from acc_trans where entry_id = current_row.lid;
  37. IF NOT FOUND THEN
  38. RAISE EXCEPTION 'No such id % in this report.', in_scn;
  39. END IF;
  40. IF user <> current_row.user THEN
  41. IF current_row.our_balance <> in_new_amount AND in_new_amount = current_row.their_balance THEN
  42. update reports pr
  43. set pr.corrections = reports.corrections + 1,
  44. pr.new_balance = in_new_amount,
  45. error_code = 0
  46. where id = in_report_id and scn = in_scn;
  47. return 0;
  48. -- After that, it is required to update the general ledger.
  49. full_reason := "User % is filing a reconciliation correction on the general ledger, changing amount % to amount %.
  50. Their reason given is: %", in_user, current_row.our_balance, in_new_amount, reason;
  51. select update_ledger(current_row.lid, in_new_amount, full_reason)
  52. ELSE IF current_row.our_balance = in_new_amount THEN
  53. -- This should be something, does it equal the original
  54. -- balance? If so, there's no change.
  55. return current_row.error_code;
  56. END IF;
  57. END IF;
  58. return current_row.error_code;
  59. END;
  60. $$ language 'plpgsql';
  61. -- to correct an incorrect bank statement value.
  62. CREATE OR REPLACE FUNCTION reconciliation__correct_bank_statement (in_report_id INT, in_id int, in_new_amount NUMERIC) returns INT AS $$
  63. DECLARE
  64. new_code INT;
  65. current_row RECORD;
  66. in_user TEXT;
  67. BEGIN
  68. select into in_user from current_user;
  69. select into current_row from reports where reports.id = in_id and reports.report_id = in_report_id;
  70. IF NOT FOUND THEN
  71. RAISE EXCEPTION 'No such SCN % in this report.', in_scn;
  72. END IF;
  73. IF user <> current_row.user THEN
  74. IF current_row.their_balance <> in_new_amount AND in_new_amount = current_row.our_balance THEN
  75. update reports pr
  76. set pr.corrections = reports.corrections + 1,
  77. pr.new_balance = in_new_amount,
  78. error_code = 0
  79. where id = in_report_id and scn = in_scn;
  80. return 0;
  81. ELSE IF current_row.their_balance = in_new_amount THEN
  82. -- This should be something, does it equal the original
  83. -- balance? If so, there's no change.
  84. return current_row.error_code;
  85. END IF;
  86. END IF;
  87. return current_row.error_code;
  88. END;
  89. $$ language 'plpgsql';
  90. CREATE OR REPLACE reconciliation__correct_passthrough ( in_report_id int, in_id int ) returns INT AS $$
  91. DECLARE
  92. in_user TEXT;
  93. pending_entry reports;
  94. BEGIN
  95. select into in_user from current_user;
  96. select into pending_entry from reports where report_id = in_report_id and id = in_id;
  97. IF NOT FOUND THEN
  98. -- Raise an exception.
  99. RAISE EXCEPTION "Cannot find entry.";
  100. ELSE IF pending_entry.errorcode <> 4 THEN
  101. -- Only error codes of 4 may be "passed through" safely.
  102. RAISE EXCEPTION "Selected entry not permitted to be passed through.";
  103. ELSE
  104. -- Then we mark it passthroughable, and "approve" will overlook it.
  105. update reports set overlook = 't', errorcode = 0 where report_id = in_report_id and id = in_id;
  106. return 0;
  107. END IF;
  108. END;
  109. $$ language 'plpgsql';
  110. CREATE OR REPLACE FUNCTION reconciliation__correct_bank_charge (in_report_id int, in_id int) returns INT AS $$
  111. DECLARE
  112. in_user TEXT;
  113. pending_entry reports;
  114. BEGIN
  115. IF NOT FOUND THEN
  116. -- Raise an exception.
  117. RAISE EXCEPTION "Cannot find entry with ID % in report %.", in_id, in_report_id;
  118. ELSE IF pending_entry.errorcode <> 2 THEN
  119. -- Only error codes of 2 may be "passed through" safely.
  120. RAISE EXCEPTION "Attempt to retroactively add a non-bank-charge entry to the ledger.";
  121. ELSE
  122. -- Then we mark it passthroughable, and "approve" will overlook it.
  123. select create_entry (pending_entry.their_balance, 'payable', pending_entry.clear_date, 'Bank charge');
  124. update reports set errorcode = 0 where report_id = in_report_id and id = in_id;
  125. return 0;
  126. END IF;
  127. END;
  128. $$ LANGUAGE 'plpgsql';
  129. CREATE OR REPLACE FUNCTION reconciliation__correct_unaccounted_charge (in_report_id int, in_id int, reason TEXT) RETURNS INT AS $$
  130. DECLARE
  131. in_user TEXT;
  132. pending_entry reports;
  133. note TEXT;
  134. BEGIN
  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 3 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 reports set errorcode = 0 where report_id = in_report_id and id = in_id;
  147. return in_id;
  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 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 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 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. --
  181. update reports set approved = 't', clear_time = now() where report_id = in_report_id;
  182. return 1;
  183. END;
  184. $$ language 'plpgsql';
  185. CREATE OR REPLACE FUNCTION reconciliation__new_report_id () returns INT as $$
  186. SELECT nextval('pending_report_report_id_seq');
  187. $$ language 'sql';
  188. create or replace function reconciliation__add_entry(
  189. in_report_id INT,
  190. in_scn INT,
  191. in_amount INT,
  192. in_account INT,
  193. in_user TEXT,
  194. in_date TIMESTAMP
  195. ) RETURNS INT AS $$
  196. DELCARE
  197. la RECORD;
  198. errorcode INT;
  199. our_value NUMERIC;
  200. lid INT;
  201. BEGIN
  202. SELECT INTO la FROM acc_trans gl
  203. WHERE gl.source = in_scn
  204. and gl.account = in_account
  205. and gl.amount = in_amount;
  206. lid := NULL;
  207. IF NOT FOUND THEN
  208. -- they have it, we don't. This is Bad, and implies either a bank
  209. -- charge or an unaccounted cheque.
  210. if in_scn <> '' and in_scn IS NOT NULL THEN
  211. -- It's a bank charge. Approval action will probably be
  212. -- adding it as an entry to the general ledger.
  213. errorcode := 2;
  214. our_value := 0;
  215. ELSE
  216. -- Okay, now this is bad.
  217. -- They have a cheque/sourced charge that we don't.
  218. -- REsolution action is going to be
  219. errorcode := 3;
  220. our_value := 0;
  221. END IF;
  222. ELSE if la.amount <> in_amount THEN
  223. errorcode := 1;
  224. our_value := la.amount;
  225. lid := la.entry_id;
  226. ELSE
  227. -- it reconciles. No problem.
  228. errorcode := 0;
  229. our_value := la.amount;
  230. lid := la.entry_id;
  231. END IF;
  232. INSERT INTO reports (
  233. report_id,
  234. scn,
  235. their_balance,
  236. our_balance,
  237. error_code,
  238. user,
  239. clear_time,
  240. ledger_id
  241. )
  242. VALUES (
  243. in_report_id,
  244. in_scn,
  245. in_amount,
  246. gl.amount,
  247. errorcode,
  248. in_user,
  249. in_date,
  250. lid
  251. );
  252. -- success, basically. This could very likely be collapsed to
  253. -- do the compare check here, instead of in the Perl app. Save us a DB
  254. -- call.
  255. return 1;
  256. END;
  257. $$ language 'plpgsql';
  258. create or replace function reconciliation__pending_transactions (in_month DATE) RETURNS setof acc_trans as $$
  259. DECLARE
  260. gl_row acc_trans;
  261. BEGIN
  262. FOR gl_row IN
  263. select gl.* from acc_trans gl, reports pr
  264. where gl.cleared = 'f'
  265. and date_trunc('month',gl.transdate) <= date_trunc('month', in_month)
  266. and gl.entry_id <> pr.ledger_id -- there's no entries in the reports for this
  267. LOOP
  268. RETURN NEXT gl_row;
  269. END LOOP;
  270. END;
  271. $$ LANGUAGE plpgsql;
  272. CREATE OR REPLACE FUNCTION reconciliation__report (in_report_id INT) RETURNS setof reports as $$
  273. DECLARE
  274. row reports;
  275. BEGIN
  276. FOR row IN select * from reports where report_id = in_report_id LOOP
  277. RETURN NEXT row;
  278. END LOOP;
  279. END;
  280. $$ language 'plpgsql';
  281. CREATE OR REPLACE FUNCTION reconciliation__get_total (in_report_id INT) returns setof reports AS $$
  282. DECLARE
  283. row reports;
  284. BEGIN
  285. SELECT INTO row FROM reports
  286. WHERE ledger_id IS NULL
  287. and report_id = in_report_id
  288. AND scn = -1;
  289. IF NOT FOUND THEN -- I think this is a fairly major error condition
  290. RAISE EXCEPTION "No Bank Total found.";
  291. ELSE
  292. return row;
  293. END IF;
  294. END;
  295. $$ language 'plpgsql';
  296. CREATE OR REPLACE FUNCTION reconciliation__corrections (in_report_id INT, in_id INT) returns setof report_corrections AS $$
  297. DECLARE
  298. corr report_corrections;
  299. BEGIN
  300. SELECT INTO corr FROM report_corrections WHERE report_id = in_report_id AND id = in_id LIMIT 1;
  301. IF NOT FOUND THEN
  302. RAISE EXCEPTION "No corrections for selected entry.";
  303. ELSE
  304. FOR corr IN select * from report_corrections WHERE report_id = in_report_id AND id = in_id LOOP
  305. RETURN NEXT corr;
  306. END LOOP;
  307. END IF;
  308. END;
  309. $$ language 'plplsql';
  310. CREATE OR REPLACE FUNCTION reconciliation__single_entry (in_report_id INT, in_id INT) returns setof reports AS $$
  311. DECLARE
  312. row reports;
  313. BEGIN
  314. SELECT INTO row FROM reports WHERE report_id = in_report_id and id = in_id LIMIT 1;
  315. -- if there's more than one, that's a Bad Thing
  316. IF NOT FOUND THEN
  317. RAISE EXCEPTION "Could not find selected report entry";
  318. ELSE
  319. RETURN row;
  320. END IF;
  321. END;
  322. $$ language 'plpgsql';
  323. CREATE OR REPLACE FUNCTION reconciliation__search (
  324. in_date_begin DATE,
  325. in_date_end DATE,
  326. in_account TEXT,
  327. in_status BOOLEAN
  328. ) RETURNS setof reports AS $$
  329. DECLARE
  330. row reports;
  331. statement text;
  332. where_stmt text;
  333. BEGIN
  334. IF in_date_begin IS NOT NULL
  335. or in_date_end IS NOT NULL
  336. or in_account IS NOT NULL
  337. or in_status IS NOT NULL
  338. THEN
  339. statement = "select pr.* from reports pr ";
  340. statement = statement + "join acc_trans at on pr.ledger_id = at.entry_id ";
  341. IF in_account IS NOT NULL THEN
  342. statement = statement + "join chart c on at.chart_id = c.id ";
  343. where_stmt = "c.accno =~ " + quote_literal(in_account) + " AND ";
  344. END IF;
  345. IF in_date_begin IS NOT NULL THEN
  346. where_stmt = where_stmt + "insert_time >= " + quote_literal(in_date_begin) + " AND ";
  347. END IF;
  348. IF in_date_end IS NOT NULL THEN
  349. where_stmt = where_stmt + "insert_time <= " + quote_literal(in_date_end) + " AND ";
  350. END IF;
  351. IF in_status IS NOT NULL THEN
  352. if in_status == 't'::bool THEN
  353. where_stmt = where_stmt + " approved = 't'::bool AND ";
  354. ELSIF in_status == 'f'::bool THEN
  355. where_stmt = where_stmt + " approved = 'f'::bool AND ";
  356. END IF;
  357. END IF;
  358. FOR row in EXECUTE statement LOOP
  359. RETURN NEXT row;
  360. END LOOP;
  361. ELSE
  362. FOR row IN SELECT * FROM reports LOOP
  363. RETURN NEXT row;
  364. END LOOP;
  365. END IF;
  366. END;