summaryrefslogtreecommitdiff
path: root/sql/modules/Payment.sql
blob: 1bcba008738ed711b324b4b3358a7ead382ff394 (plain)
  1. CREATE TYPE payment_vc_info AS (
  2. id int,
  3. name text,
  4. entity_class int,
  5. discount int
  6. );
  7. CREATE OR REPLACE FUNCTION payment_get_entity_accounts
  8. (in_account_class int,
  9. in_vc_name text,
  10. in_vc_idn int)
  11. returns SETOF payment_vc_info AS
  12. $$
  13. DECLARE out_entity payment_vc_info;
  14. BEGIN
  15. FOR out_entity IN
  16. SELECT ec.id, cp.legal_name as name, e.entity_class, ec.discount_account_id
  17. FROM entity e
  18. JOIN entity_credit_account ec ON (ec.entity_id = e.id)
  19. JOIN company cp ON (cp.entity_id = e.id)
  20. WHERE ec.entity_class = in_account_class
  21. AND (cp.legal_name ilike coalesce('%'||in_vc_name||'%','%%') OR cp.tax_id = in_vc_idn)
  22. LOOP
  23. RETURN NEXT out_entity;
  24. END LOOP;
  25. END;
  26. $$ LANGUAGE PLPGSQL;
  27. -- payment_get_open_accounts and the option to get all accounts need to be
  28. -- refactored and redesigned. -- CT
  29. CREATE OR REPLACE FUNCTION payment_get_open_accounts(in_account_class int)
  30. returns SETOF entity AS
  31. $$
  32. DECLARE out_entity entity%ROWTYPE;
  33. BEGIN
  34. FOR out_entity IN
  35. SELECT ec.id, cp.legal_name as name, e.entity_class, e.created
  36. FROM entity e
  37. JOIN entity_credit_account ec ON (ec.entity_id = e.id)
  38. JOIN company cp ON (cp.entity_id = e.id)
  39. WHERE ec.entity_class = in_account_class
  40. -- AND CASE WHEN in_account_class = 1 THEN
  41. -- e.id IN (SELECT entity_id FROM ap
  42. -- WHERE amount <> paid
  43. -- GROUP BY entity_id)
  44. -- WHEN in_account_class = 2 THEN
  45. -- e.id IN (SELECT entity_id FROM ar
  46. -- WHERE amount <> paid
  47. -- GROUP BY entity_id)
  48. -- END
  49. LOOP
  50. RETURN NEXT out_entity;
  51. END LOOP;
  52. END;
  53. $$ LANGUAGE PLPGSQL;
  54. COMMENT ON FUNCTION payment_get_open_accounts(int) IS
  55. $$ This function takes a single argument (1 for vendor, 2 for customer as
  56. always) and returns all entities with open accounts of the appropriate type. $$;
  57. CREATE OR REPLACE FUNCTION payment_get_all_accounts(in_account_class int)
  58. RETURNS SETOF entity AS
  59. $$
  60. DECLARE out_entity entity%ROWTYPE;
  61. BEGIN
  62. FOR out_entity IN
  63. SELECT ec.id,
  64. e.name, e.entity_class, e.created
  65. FROM entity e
  66. JOIN entity_credit_account ec ON (ec.entity_id = e.id)
  67. WHERE e.entity_class = in_account_class
  68. LOOP
  69. RETURN NEXT out_entity;
  70. END LOOP;
  71. END;
  72. $$ LANGUAGE PLPGSQL;
  73. COMMENT ON FUNCTION payment_get_open_accounts(int) IS
  74. $$ This function takes a single argument (1 for vendor, 2 for customer as
  75. always) and returns all entities with accounts of the appropriate type. $$;
  76. CREATE TYPE payment_invoice AS (
  77. invoice_id int,
  78. invnumber text,
  79. invoice_date date,
  80. amount numeric,
  81. amount_fx numeric,
  82. discount numeric,
  83. discount_fx numeric,
  84. due numeric,
  85. due_fx numeric,
  86. exchangerate numeric
  87. );
  88. CREATE OR REPLACE FUNCTION payment_get_open_invoices
  89. (in_account_class int,
  90. in_entity_credit_id int,
  91. in_curr char(3),
  92. in_datefrom date,
  93. in_dateto date,
  94. in_amountfrom numeric,
  95. in_amountto numeric,
  96. in_department_id int)
  97. RETURNS SETOF payment_invoice AS
  98. $$
  99. DECLARE payment_inv payment_invoice;
  100. BEGIN
  101. FOR payment_inv IN
  102. SELECT a.id AS invoice_id, a.invnumber AS invnumber,
  103. a.transdate AS invoice_date, a.amount AS amount,
  104. a.amount/
  105. (CASE WHEN a.curr = (SELECT * from defaults_get_defaultcurrency())
  106. THEN 1
  107. ELSE
  108. (CASE WHEN in_account_class =1
  109. THEN ex.buy
  110. ELSE ex.sell END)
  111. END) as amount_fx,
  112. (CASE WHEN c.discount_terms < extract('days' FROM age(a.transdate))
  113. THEN 0
  114. ELSE (coalesce(ac.due, a.amount)) * coalesce(c.discount, 0) / 100
  115. END) AS discount,
  116. (CASE WHEN c.discount_terms < extract('days' FROM age(a.transdate))
  117. THEN 0
  118. ELSE (coalesce(ac.due, a.amount)) * coalesce(c.discount, 0) / 100
  119. END)/
  120. (CASE WHEN a.curr = (SELECT * from defaults_get_defaultcurrency())
  121. THEN 1
  122. ELSE
  123. (CASE WHEN in_account_class =1
  124. THEN ex.buy
  125. ELSE ex.sell END)
  126. END) as discount_fx,
  127. ac.due - (CASE WHEN c.discount_terms < extract('days' FROM age(a.transdate))
  128. THEN 0
  129. ELSE (coalesce(ac.due, a.amount)) * coalesce(c.discount, 0) / 100
  130. END) AS due,
  131. (ac.due - (CASE WHEN c.discount_terms < extract('days' FROM age(a.transdate))
  132. THEN 0
  133. ELSE (coalesce(ac.due, a.amount)) * coalesce(c.discount, 0) / 100
  134. END))/
  135. (CASE WHEN a.curr = (SELECT * from defaults_get_defaultcurrency())
  136. THEN 1
  137. ELSE
  138. (CASE WHEN in_account_class =1
  139. THEN ex.buy
  140. ELSE ex.sell END)
  141. END) AS due_fx,
  142. (CASE WHEN a.curr = (SELECT * from defaults_get_defaultcurrency())
  143. THEN 1
  144. ELSE
  145. (CASE WHEN in_account_class =1
  146. THEN ex.buy
  147. ELSE ex.sell END)
  148. END) AS exchangerate
  149. FROM (SELECT id, invnumber, transdate, amount, entity_id,
  150. 1 as invoice_class, paid, curr,
  151. entity_credit_account, department_id
  152. FROM ap
  153. UNION
  154. SELECT id, invnumber, transdate, amount, entity_id,
  155. 2 AS invoice_class, paid, curr,
  156. entity_credit_account, department_id
  157. FROM ar
  158. ) a
  159. JOIN (SELECT trans_id, chart_id, sum(CASE WHEN in_account_class = 1 THEN amount
  160. WHEN in_account_class = 2
  161. THEN amount * -1
  162. END) as due
  163. FROM acc_trans
  164. GROUP BY trans_id, chart_id) ac ON (ac.trans_id = a.id)
  165. JOIN chart ON (chart.id = ac.chart_id)
  166. LEFT JOIN exchangerate ex ON ( ex.transdate = a.transdate AND ex.curr = a.curr )
  167. JOIN entity_credit_account c ON (c.id = a.entity_credit_account
  168. OR (a.entity_credit_account IS NULL and a.entity_id = c.entity_id))
  169. WHERE ((chart.link = 'AP' AND in_account_class = 1)
  170. OR (chart.link = 'AR' AND in_account_class = 2))
  171. AND a.invoice_class = in_account_class
  172. AND c.entity_class = in_account_class
  173. AND c.id = in_entity_credit_id
  174. AND a.amount - a.paid <> 0
  175. AND a.curr = in_curr
  176. AND (a.transdate >= in_datefrom
  177. OR in_datefrom IS NULL)
  178. AND (a.transdate <= in_dateto
  179. OR in_dateto IS NULL)
  180. AND (a.amount >= in_amountfrom
  181. OR in_amountfrom IS NULL)
  182. AND (a.amount <= in_amountto
  183. OR in_amountto IS NULL)
  184. AND (a.department_id = in_department_id
  185. OR in_department_id IS NULL)
  186. AND due <> 0
  187. GROUP BY a.invnumber, a.transdate, a.amount, amount_fx, discount, discount_fx, ac.due, a.id, c.discount_terms, ex.buy, ex.sell, a.curr
  188. LOOP
  189. RETURN NEXT payment_inv;
  190. END LOOP;
  191. END;
  192. $$ LANGUAGE PLPGSQL;
  193. COMMENT ON FUNCTION payment_get_open_invoices(int, int, char(3), date, date, numeric, numeric, int) IS
  194. $$ This function takes three arguments:
  195. Type: 1 for vendor, 2 for customer
  196. Entity_id: The entity_id of the customer or vendor
  197. Currency: 3 characters for currency ('USD' for example).
  198. Returns all open invoices for the entity in question. $$;
  199. CREATE TYPE payment_contact_invoice AS (
  200. contact_id int,
  201. econtrol_code text,
  202. eca_description text,
  203. contact_name text,
  204. account_number text,
  205. total_due numeric,
  206. invoices text[],
  207. has_vouchers int
  208. );
  209. CREATE OR REPLACE FUNCTION payment_get_all_contact_invoices
  210. (in_account_class int, in_business_id int, in_currency char(3),
  211. in_date_from date, in_date_to date, in_batch_id int,
  212. in_ar_ap_accno text, in_meta_number text)
  213. RETURNS SETOF payment_contact_invoice AS
  214. $$
  215. DECLARE payment_item payment_contact_invoice;
  216. BEGIN
  217. FOR payment_item IN
  218. SELECT c.id AS contact_id, e.control_code as econtrol_code,
  219. c.description as eca_description,
  220. e.name AS contact_name,
  221. c.meta_number AS account_number,
  222. sum( case when u.username IS NULL or
  223. u.username = SESSION_USER
  224. THEN
  225. coalesce(p.due::numeric, 0) -
  226. CASE WHEN c.discount_terms
  227. > extract('days' FROM age(a.transdate))
  228. THEN 0
  229. ELSE (coalesce(p.due::numeric, 0)) *
  230. coalesce(c.discount::numeric, 0) / 100
  231. END
  232. ELSE 0::numeric
  233. END) AS total_due,
  234. compound_array(ARRAY[[
  235. a.id::text, a.invnumber, a.transdate::text,
  236. a.amount::text, (a.amount - p.due)::text,
  237. (CASE WHEN c.discount_terms
  238. > extract('days' FROM age(a.transdate))
  239. THEN 0
  240. ELSE (a.amount - coalesce((a.amount - p.due), 0)) * coalesce(c.discount, 0) / 100
  241. END)::text,
  242. (coalesce(p.due, 0) -
  243. (CASE WHEN c.discount_terms
  244. > extract('days' FROM age(a.transdate))
  245. THEN 0
  246. ELSE (coalesce(p.due, 0)) * coalesce(c.discount, 0) / 100
  247. END))::text,
  248. case when u.username IS NOT NULL
  249. and u.username <> SESSION_USER
  250. THEN 0::text
  251. ELSE 1::text
  252. END,
  253. COALESCE(u.username, 0::text)
  254. ]]),
  255. sum(case when a.batch_id = in_batch_id then 1
  256. else 0 END),
  257. bool_and(lock_record(a.id, (select max(session_id) FROM "session" where users_id = (
  258. select id from users WHERE username =
  259. SESSION_USER))))
  260. FROM entity e
  261. JOIN entity_credit_account c ON (e.id = c.entity_id)
  262. JOIN (SELECT ap.id, invnumber, transdate, amount, entity_id,
  263. paid, curr, 1 as invoice_class,
  264. entity_credit_account, on_hold, v.batch_id,
  265. approved
  266. FROM ap
  267. LEFT JOIN (select * from voucher where batch_class = 1) v
  268. ON (ap.id = v.trans_id)
  269. WHERE in_account_class = 1
  270. AND (v.batch_class = 1 or v.batch_id IS NULL)
  271. UNION
  272. SELECT ar.id, invnumber, transdate, amount, entity_id,
  273. paid, curr, 2 as invoice_class,
  274. entity_credit_account, on_hold, v.batch_id,
  275. approved
  276. FROM ar
  277. LEFT JOIN (select * from voucher where batch_class = 2) v
  278. ON (ar.id = v.trans_id)
  279. WHERE in_account_class = 2
  280. AND (v.batch_class = 2 or v.batch_id IS NULL)
  281. ORDER BY transdate
  282. ) a ON (a.entity_credit_account = c.id)
  283. JOIN transactions t ON (a.id = t.id)
  284. JOIN (SELECT trans_id,
  285. sum(CASE WHEN in_account_class = 1 THEN amount
  286. WHEN in_account_class = 2
  287. THEN amount * -1
  288. END) AS due
  289. FROM acc_trans
  290. JOIN chart ON (chart.id = acc_trans.chart_id)
  291. WHERE ((chart.link = 'AP' AND in_account_class = 1)
  292. OR (chart.link = 'AR' AND in_account_class = 2))
  293. GROUP BY trans_id) p ON (a.id = p.trans_id)
  294. LEFT JOIN "session" s ON (s."session_id" = t.locked_by)
  295. LEFT JOIN users u ON (u.id = s.users_id)
  296. WHERE a.batch_id = in_batch_id
  297. OR (a.invoice_class = in_account_class
  298. AND a.approved
  299. AND c.business_id =
  300. coalesce(in_business_id, c.business_id)
  301. AND ((a.transdate >= COALESCE(in_date_from, a.transdate)
  302. AND a.transdate <= COALESCE(in_date_to, a.transdate)))
  303. AND c.entity_class = in_account_class
  304. AND a.curr = in_currency
  305. AND a.entity_credit_account = c.id
  306. AND (in_meta_number IS NULL OR
  307. in_meta_number = c.meta_number)
  308. AND p.due <> 0
  309. AND a.amount <> a.paid
  310. AND NOT a.on_hold
  311. AND EXISTS (select trans_id FROM acc_trans
  312. WHERE trans_id = a.id AND
  313. chart_id = (SELECT id frOM chart
  314. WHERE accno
  315. = in_ar_ap_accno)
  316. ))
  317. GROUP BY c.id, e.name, c.meta_number, c.threshold,
  318. e.control_code, c.description
  319. HAVING (sum(p.due) >= c.threshold
  320. OR sum(case when a.batch_id = in_batch_id then 1
  321. else 0 END) > 0)
  322. ORDER BY c.meta_number ASC
  323. LOOP
  324. RETURN NEXT payment_item;
  325. END LOOP;
  326. END;
  327. $$ LANGUAGE plpgsql;
  328. COMMENT ON FUNCTION payment_get_all_contact_invoices
  329. (in_account_class int, in_business_id int, in_currency char(3),
  330. in_date_from date, in_date_to date, in_batch_id int,
  331. in_ar_ap_accno text, in_meta_number text) IS
  332. $$
  333. This function takes the following arguments (all prefaced with in_ in the db):
  334. account_class: 1 for vendor, 2 for customer
  335. business_type: integer of business.id.
  336. currency: char(3) of currency (for example 'USD')
  337. date_from, date_to: These dates are inclusive.
  338. 1;3B
  339. batch_id: For payment batches, where fees are concerned.
  340. ar_ap_accno: The AR/AP account number.
  341. This then returns a set of contact information with a 2 dimensional array
  342. cnsisting of outstanding invoices.
  343. $$;
  344. CREATE OR REPLACE FUNCTION payment_bulk_queue
  345. (in_transactions numeric[], in_batch_id int, in_source text, in_total numeric,
  346. in_ar_ap_accno text, in_cash_accno text,
  347. in_payment_date date, in_account_class int)
  348. returns int as
  349. $$
  350. BEGIN
  351. INSERT INTO payments_queue
  352. (transactions, batch_id, source, total, ar_ap_accno, cash_accno,
  353. payment_date, account_class)
  354. VALUES
  355. (in_transactions, in_batch_id, in_source, in_total, in_ar_ap_accno,
  356. in_cash_accno, in_payment_date, in_account_class);
  357. RETURN array_upper(in_transactions, 1) -
  358. array_lower(in_transactions, 1);
  359. END;
  360. $$ LANGUAGE PLPGSQL;
  361. CREATE OR REPLACE FUNCTION job__process_payment(in_job_id int)
  362. RETURNS bool AS $$
  363. DECLARE
  364. queue_record RECORD;
  365. t_auth_name text;
  366. t_counter int;
  367. BEGIN
  368. -- TODO: Move the set session authorization into a utility function
  369. SELECT entered_by INTO t_auth_name FROM pending_job
  370. WHERE id = in_job_id;
  371. EXECUTE 'SET SESSION AUTHORIZATION ' || quote_ident(t_auth_name);
  372. t_counter := 0;
  373. FOR queue_record IN
  374. SELECT *
  375. FROM payments_queue WHERE job_id = in_job_id
  376. LOOP
  377. PERFORM payment_bulk_post
  378. (queue_record.transactions, queue_record.batch_id,
  379. queue_record.source, queue_record.total,
  380. queue_record.ar_ap_accno,
  381. queue_record.cash_accno,
  382. queue_record.payment_date,
  383. queue_record.account_class);
  384. t_counter := t_counter + 1;
  385. RAISE NOTICE 'Processed record %, starting transaction %',
  386. t_counter, queue_record.transactions[1][1];
  387. END LOOP;
  388. DELETE FROM payments_queue WHERE job_id = in_job_id;
  389. UPDATE pending_job
  390. SET completed_at = timeofday()::timestamp,
  391. success = true
  392. WHERE id = in_job_id;
  393. RETURN TRUE;
  394. END;
  395. $$ language plpgsql;
  396. CREATE OR REPLACE FUNCTION job__create(in_batch_class int, in_batch_id int)
  397. RETURNS int AS
  398. $$
  399. BEGIN
  400. INSERT INTO pending_job (batch_class, batch_id)
  401. VALUES (coalesce(in_batch_class, 3), in_batch_id);
  402. RETURN currval('pending_job_id_seq');
  403. END;
  404. $$ LANGUAGE PLPGSQL;
  405. CREATE TYPE job__status AS (
  406. completed int, -- 1 for completed, 0 for no
  407. success int, -- 1 for success, 0 for no
  408. completed_at timestamp,
  409. error_condition text -- error if not successful
  410. );
  411. CREATE OR REPLACE FUNCTION job__status(in_job_id int) RETURNS job__status AS
  412. $$
  413. DECLARE out_row job__status;
  414. BEGIN
  415. SELECT (completed_at IS NULL)::INT, success::int, completed_at,
  416. error_condition
  417. INTO out_row
  418. FROM pending_job
  419. WHERE id = in_job_id;
  420. RETURN out_row;
  421. END;
  422. $$ language plpgsql;
  423. CREATE OR REPLACE FUNCTION payment_bulk_post
  424. (in_transactions numeric[], in_batch_id int, in_source text, in_total numeric,
  425. in_ar_ap_accno text, in_cash_accno text,
  426. in_payment_date date, in_account_class int)
  427. RETURNS int AS
  428. $$
  429. DECLARE
  430. out_count int;
  431. t_voucher_id int;
  432. t_trans_id int;
  433. t_amount numeric;
  434. t_ar_ap_id int;
  435. t_cash_id int;
  436. BEGIN
  437. IF in_batch_id IS NULL THEN
  438. -- t_voucher_id := NULL;
  439. RAISE EXCEPTION 'Bulk Post Must be from Batch!';
  440. ELSE
  441. INSERT INTO voucher (batch_id, batch_class, trans_id)
  442. values (in_batch_id, 3, in_transactions[1][1]);
  443. t_voucher_id := currval('voucher_id_seq');
  444. END IF;
  445. CREATE TEMPORARY TABLE bulk_payments_in (id int, amount numeric);
  446. select id into t_ar_ap_id from chart where accno = in_ar_ap_accno;
  447. select id into t_cash_id from chart where accno = in_cash_accno;
  448. FOR out_count IN
  449. array_lower(in_transactions, 1) ..
  450. array_upper(in_transactions, 1)
  451. LOOP
  452. EXECUTE $E$
  453. INSERT INTO bulk_payments_in(id, amount)
  454. VALUES ($E$ || quote_literal(in_transactions[out_count][1])
  455. || $E$, $E$ ||
  456. quote_literal(in_transactions[out_count][2])
  457. || $E$)$E$;
  458. END LOOP;
  459. EXECUTE $E$
  460. INSERT INTO acc_trans
  461. (trans_id, chart_id, amount, approved, voucher_id, transdate,
  462. source)
  463. SELECT id,
  464. case when $E$ || quote_literal(in_account_class) || $E$ = 1
  465. THEN $E$ || t_cash_id || $E$
  466. WHEN $E$ || quote_literal(in_account_class) || $E$ = 2
  467. THEN $E$ || t_ar_ap_id || $E$
  468. ELSE -1 END,
  469. amount,
  470. CASE
  471. WHEN $E$|| t_voucher_id || $E$ IS NULL THEN true
  472. ELSE false END,
  473. $E$ || t_voucher_id || $E$, $E$|| quote_literal(in_payment_date)
  474. ||$E$ , $E$ ||COALESCE(quote_literal(in_source), 'NULL') ||$E$
  475. FROM bulk_payments_in $E$;
  476. EXECUTE $E$
  477. INSERT INTO acc_trans
  478. (trans_id, chart_id, amount, approved, voucher_id, transdate,
  479. source)
  480. SELECT id,
  481. case when $E$ || quote_literal(in_account_class) || $E$ = 1
  482. THEN $E$ || t_ar_ap_id || $E$
  483. WHEN $E$ || quote_literal(in_account_class) || $E$ = 2
  484. THEN $E$ || t_cash_id || $E$
  485. ELSE -1 END,
  486. amount * -1,
  487. CASE
  488. WHEN $E$|| t_voucher_id || $E$ IS NULL THEN true
  489. ELSE false END,
  490. $E$ || t_voucher_id || $E$, $E$|| quote_literal(in_payment_date)
  491. ||$E$ , $E$ ||COALESCE(quote_literal(in_source), 'null') ||$E$
  492. FROM bulk_payments_in $E$;
  493. EXECUTE $E$
  494. UPDATE ap
  495. set paid = paid + (select amount from bulk_payments_in b
  496. where b.id = ap.id)
  497. where id in (select id from bulk_payments_in) $E$;
  498. EXECUTE $E$ DROP TABLE bulk_payments_in $E$;
  499. perform unlock_all();
  500. return out_count;
  501. END;
  502. $$ language plpgsql;
  503. COMMENT ON FUNCTION payment_bulk_post
  504. (in_transactions numeric[], in_batch_id int, in_source text, in_total numeric,
  505. in_ar_ap_accno text, in_cash_accno text,
  506. in_payment_date date, in_account_class int)
  507. IS
  508. $$ Note that in_transactions is a two-dimensional numeric array. Of each
  509. sub-array, the first element is the (integer) transaction id, and the second
  510. is the amount for that transaction. $$;
  511. --
  512. -- WE NEED A PAYMENT TABLE
  513. --
  514. CREATE TABLE payment (
  515. id serial primary key,
  516. reference text NOT NULL,
  517. gl_id integer references gl(id),
  518. payment_class integer NOT NULL,
  519. payment_date date default current_date,
  520. closed bool default FALSE,
  521. entity_credit_id integer references entity_credit_account(id),
  522. employee_id integer references entity_employee(entity_id),
  523. currency char(3),
  524. notes text,
  525. department_id integer default 0);
  526. COMMENT ON TABLE payment IS $$ This table will store the main data on a payment, prepayment, overpayment, et$$;
  527. COMMENT ON COLUMN payment.reference IS $$ This field will store the code for both receipts and payment order $$;
  528. COMMENT ON COLUMN payment.closed IS $$ This will store the current state of a payment/receipt order $$;
  529. COMMENT ON COLUMN payment.gl_id IS $$ A payment should always be linked to a GL movement $$;
  530. CREATE INDEX payment_id_idx ON payment(id);
  531. CREATE TABLE payment_links (
  532. payment_id integer references Payment(id),
  533. entry_id integer references acc_trans(entry_id),
  534. type integer);
  535. COMMENT ON TABLE payment_links IS $$
  536. An explanation to the type field.
  537. * A type 0 means the link is referencing an ar/ap and was created
  538. using an overpayment movement after the receipt was created
  539. * A type 1 means the link is referencing an ar/ap and was made
  540. on the payment creation, its not the product of an overpayment movement
  541. * A type 2 means the link is not referencing an ar/ap and its the product
  542. of the overpayment logic
  543. With this ideas in order we can do the following
  544. To get the payment amount we will sum the entries with type > 0.
  545. To get the linked amount we will sum the entries with type < 2.
  546. The overpayment account can be obtained from the entries with type = 2.
  547. This reasoning is hacky and i hope it can dissapear when we get to 1.4 - D.M.
  548. $$;
  549. CREATE OR REPLACE FUNCTION payment_post
  550. (in_datepaid date,
  551. in_account_class int,
  552. in_entity_credit_id int,
  553. in_curr char(3),
  554. in_notes text,
  555. in_department_id int,
  556. in_gl_description text,
  557. in_cash_account_id int[],
  558. in_amount numeric[],
  559. in_cash_approved bool[],
  560. in_source text[],
  561. in_memo text[],
  562. in_transaction_id int[],
  563. in_op_amount numeric[],
  564. in_op_cash_account_id int[],
  565. in_op_source text[],
  566. in_op_memo text[],
  567. in_op_account_id int[],
  568. in_approved bool)
  569. RETURNS INT AS
  570. $$
  571. DECLARE var_payment_id int;
  572. DECLARE var_gl_id int;
  573. DECLARE var_entry record;
  574. DECLARE var_entry_id int[];
  575. DECLARE out_count int;
  576. DECLARE coa_id record;
  577. DECLARE var_employee int;
  578. DECLARE var_account_id int;
  579. DECLARE default_currency char(3);
  580. DECLARE current_exchangerate numeric;
  581. DECLARE old_exchangerate numeric;
  582. DECLARE tmp_amount numeric;
  583. BEGIN
  584. SELECT * INTO default_currency FROM defaults_get_defaultcurrency();
  585. SELECT * INTO current_exchangerate FROM currency_get_exchangerate(in_curr, in_datepaid, in_account_class);
  586. SELECT INTO var_employee entity_id FROM users WHERE username = SESSION_USER LIMIT 1;
  587. --
  588. -- WE HAVE TO INSERT THE PAYMENT, USING THE GL INFORMATION
  589. -- THE ID IS GENERATED BY payment_id_seq
  590. --
  591. INSERT INTO payment (reference, payment_class, payment_date,
  592. employee_id, currency, notes, department_id, entity_credit_id)
  593. VALUES ((CASE WHEN in_account_class = 1 THEN
  594. setting_increment('rcptnumber') -- I FOUND THIS ON sql/modules/Settings.sql
  595. ELSE -- and it is very usefull
  596. setting_increment('paynumber')
  597. END),
  598. in_account_class, in_datepaid, var_employee,
  599. in_curr, in_notes, in_department_id, in_entity_credit_id);
  600. SELECT currval('payment_id_seq') INTO var_payment_id; -- WE'LL NEED THIS VALUE TO USE payment_link table
  601. -- WE'LL NEED THIS VALUE TO JOIN WITH PAYMENT
  602. -- NOW COMES THE HEAVY PART, STORING ALL THE POSSIBLE TRANSACTIONS...
  603. --
  604. -- FIRST WE SHOULD INSERT THE CASH ACCOUNTS
  605. --
  606. -- WE SHOULD HAVE THE DATA STORED AS (ACCNO, AMOUNT), SO
  607. FOR out_count IN
  608. array_lower(in_cash_account_id, 1) ..
  609. array_upper(in_cash_account_id, 1)
  610. LOOP
  611. INSERT INTO acc_trans (chart_id, amount,
  612. trans_id, transdate, approved, source, memo)
  613. VALUES (in_cash_account_id[out_count],
  614. CASE WHEN in_account_class = 1 THEN in_amount[out_count]*current_exchangerate
  615. ELSE (in_amount[out_count]*current_exchangerate)* - 1
  616. END,
  617. in_transaction_id[out_count], in_datepaid, coalesce(in_approved, true),
  618. in_source[out_count], in_memo[out_count]);
  619. INSERT INTO payment_links
  620. VALUES (var_payment_id, currval('acc_trans_entry_id_seq'), 1);
  621. END LOOP;
  622. -- NOW LETS HANDLE THE AR/AP ACCOUNTS
  623. -- WE RECEIVED THE TRANSACTIONS_ID AND WE CAN OBTAIN THE ACCOUNT FROM THERE
  624. FOR out_count IN
  625. array_lower(in_transaction_id, 1) ..
  626. array_upper(in_transaction_id, 1)
  627. LOOP
  628. SELECT INTO var_account_id chart_id FROM acc_trans as ac
  629. JOIN chart as c ON (c.id = ac.chart_id)
  630. WHERE
  631. trans_id = in_transaction_id[out_count] AND
  632. ( c.link = 'AP' OR c.link = 'AR' );
  633. -- We need to know the exchangerate of this transaction
  634. IF (current_exchangerate = 1 ) THEN
  635. old_exchangerate := 1;
  636. ELSIF (in_account_class = 1) THEN
  637. SELECT buy INTO old_exchangerate
  638. FROM exchangerate e
  639. JOIN ap a on (a.transdate = e.transdate )
  640. WHERE a.id = in_transaction_id[out_count];
  641. ELSE
  642. SELECT sell INTO old_exchangerate
  643. FROM exchangerate e
  644. JOIN ar a on (a.transdate = e.transdate )
  645. WHERE a.id = in_transaction_id[out_count];
  646. END IF;
  647. -- Now we post the AP/AR transaction
  648. INSERT INTO acc_trans (chart_id, amount,
  649. trans_id, transdate, approved, source, memo)
  650. VALUES (var_account_id,
  651. CASE WHEN in_account_class = 1 THEN
  652. (in_amount[out_count]*old_exchangerate) * -1
  653. ELSE in_amount[out_count]*old_exchangerate
  654. END,
  655. in_transaction_id[out_count], in_datepaid, coalesce(in_approved, true),
  656. in_source[out_count], in_memo[out_count]);
  657. -- Lets set the gain/loss, if tmp_amount equals zero then we dont need to post
  658. -- any transaction
  659. tmp_amount := in_amount[out_count]*current_exchangerate - in_amount[out_count]*old_exchangerate;
  660. IF (tmp_amount < 0) THEN
  661. IF (in_account_class = 1) THEN
  662. INSERT INTO acc_trans (chart_id, amount, trans_id, transdate, approved, source)
  663. VALUES (CAST((select value from defaults where setting_key like 'fxloss_accno_id') AS INT),
  664. tmp_amount, in_transaction_id[out_count], in_datepaid, coalesce(in_approved, true),
  665. in_source[out_count]);
  666. ELSE
  667. INSERT INTO acc_trans (chart_id, amount, trans_id, transdate, approved, source)
  668. VALUES (CAST((select value from defaults where setting_key like 'fxgain_accno_id') AS INT),
  669. tmp_amount, in_transaction_id[out_count], in_datepaid, coalesce(in_approved, true),
  670. in_source[out_count]);
  671. END IF;
  672. ELSIF (tmp_amount > 0) THEN
  673. IF (in_account_class = 1) THEN
  674. INSERT INTO acc_trans (chart_id, amount, trans_id, transdate, approved, source)
  675. VALUES (CAST((select value from defaults where setting_key like 'fxgain_accno_id') AS INT),
  676. tmp_amount, in_transaction_id[out_count], in_datepaid, coalesce(in_approved, true),
  677. in_source[out_count]);
  678. ELSE
  679. INSERT INTO acc_trans (chart_id, amount, trans_id, transdate, approved, source)
  680. VALUES (CAST((select value from defaults where setting_key like 'fxloss_accno_id') AS INT),
  681. tmp_amount, in_transaction_id[out_count], in_datepaid, coalesce(in_approved, true),
  682. in_source[out_count]);
  683. END IF;
  684. END IF;
  685. -- Now we set the links
  686. INSERT INTO payment_links
  687. VALUES (var_payment_id, currval('acc_trans_entry_id_seq'), 1);
  688. END LOOP;
  689. --
  690. -- WE NEED TO HANDLE THE OVERPAYMENTS NOW
  691. --
  692. --
  693. -- FIRST WE HAVE TO MAKE THE GL TO HOLD THE OVERPAYMENT TRANSACTIONS
  694. -- THE ID IS GENERATED BY gl_id_seq
  695. --
  696. IF (array_upper(in_op_cash_account_id, 1) > 0) THEN
  697. INSERT INTO gl (reference, description, transdate,
  698. person_id, notes, approved, department_id)
  699. VALUES (setting_increment('glnumber'),
  700. in_gl_description, in_datepaid, var_employee,
  701. in_notes, in_approved, in_department_id);
  702. SELECT currval('id') INTO var_gl_id;
  703. --
  704. -- WE NEED TO SET THE GL_ID FIELD ON PAYMENT'S TABLE
  705. --
  706. UPDATE payment SET gl_id = var_gl_id
  707. WHERE id = var_payment_id;
  708. -- NOW COMES THE HEAVY PART, STORING ALL THE POSSIBLE TRANSACTIONS...
  709. --
  710. -- FIRST WE SHOULD INSERT THE OVERPAYMENT CASH ACCOUNTS
  711. --
  712. FOR out_count IN
  713. array_lower(in_op_cash_account_id, 1) ..
  714. array_upper(in_op_cash_account_id, 1)
  715. LOOP
  716. INSERT INTO acc_trans (chart_id, amount,
  717. trans_id, transdate, approved, source, memo)
  718. VALUES (in_op_cash_account_id[out_count],
  719. CASE WHEN in_account_class = 2 THEN in_op_amount[out_count]
  720. ELSE in_op_amount[out_count] * - 1
  721. END,
  722. var_gl_id, in_datepaid, coalesce(in_approved, true),
  723. in_op_source[out_count], in_op_memo[out_count]);
  724. INSERT INTO payment_links
  725. VALUES (var_payment_id, currval('acc_trans_entry_id_seq'), 2);
  726. END LOOP;
  727. -- NOW LETS HANDLE THE OVERPAYMENT ACCOUNTS
  728. FOR out_count IN
  729. array_lower(in_op_account_id, 1) ..
  730. array_upper(in_op_account_id, 1)
  731. LOOP
  732. INSERT INTO acc_trans (chart_id, amount,
  733. trans_id, transdate, approved, source, memo)
  734. VALUES (in_op_account_id[out_count],
  735. CASE WHEN in_account_class = 2 THEN in_op_amount[out_count] * -1
  736. ELSE in_op_amount[out_count]
  737. END,
  738. var_gl_id, in_datepaid, coalesce(in_approved, true),
  739. in_op_source[out_count], in_op_memo[out_count]);
  740. INSERT INTO payment_links
  741. VALUES (var_payment_id, currval('acc_trans_entry_id_seq'), 2);
  742. END LOOP;
  743. END IF;
  744. return var_payment_id;
  745. END;
  746. $$ LANGUAGE PLPGSQL;
  747. -- I HAVE TO MAKE A COMMENT ON THIS FUNCTION
  748. -- Move this to the projects module when we start on that. CT
  749. CREATE OR REPLACE FUNCTION project_list_open(in_date date)
  750. RETURNS SETOF project AS
  751. $$
  752. DECLARE out_project project%ROWTYPE;
  753. BEGIN
  754. FOR out_project IN
  755. SELECT * from project
  756. WHERE startdate <= in_date AND enddate >= in_date
  757. AND completed = 0
  758. LOOP
  759. return next out_project;
  760. END LOOP;
  761. END;
  762. $$ language plpgsql;
  763. comment on function project_list_open(in_date date) is
  764. $$ This function returns all projects that were open as on the date provided as
  765. the argument.$$;
  766. -- Move this to the projects module when we start on that. CT
  767. CREATE OR REPLACE FUNCTION department_list(in_role char)
  768. RETURNS SETOF department AS
  769. $$
  770. DECLARE out_department department%ROWTYPE;
  771. BEGIN
  772. FOR out_department IN
  773. SELECT * from department
  774. WHERE role = coalesce(in_role, role)
  775. LOOP
  776. return next out_department;
  777. END LOOP;
  778. END;
  779. $$ language plpgsql;
  780. -- Move this into another module.
  781. comment on function department_list(in_role char) is
  782. $$ This function returns all department that match the role provided as
  783. the argument.$$;
  784. CREATE OR REPLACE FUNCTION payments_get_open_currencies(in_account_class int)
  785. RETURNS SETOF char(3) AS
  786. $$
  787. DECLARE resultrow record;
  788. BEGIN
  789. FOR resultrow IN
  790. SELECT DISTINCT curr FROM ar
  791. UNION
  792. SELECT DISTINCT curr FROM ap
  793. ORDER BY curr
  794. LOOP
  795. return next resultrow.curr;
  796. END LOOP;
  797. END;
  798. $$ language plpgsql;
  799. CREATE OR REPLACE FUNCTION currency_get_exchangerate(in_currency char(3), in_date date, in_account_class int)
  800. RETURNS NUMERIC AS
  801. $$
  802. DECLARE
  803. out_exrate exchangerate.buy%TYPE;
  804. default_currency char(3);
  805. BEGIN
  806. SELECT * INTO default_currency FROM defaults_get_defaultcurrency();
  807. IF default_currency = in_currency THEN
  808. RETURN 1;
  809. END IF;
  810. IF in_account_class = 1 THEN
  811. SELECT buy INTO out_exrate
  812. FROM exchangerate
  813. WHERE transdate = in_date AND curr = in_currency;
  814. ELSE
  815. SELECT sell INTO out_exrate
  816. FROM exchangerate
  817. WHERE transdate = in_date AND curr = in_currency;
  818. END IF;
  819. RETURN out_exrate;
  820. END;
  821. $$ language plpgsql;
  822. COMMENT ON FUNCTION currency_get_exchangerate(in_currency char(3), in_date date, in_account_class int) IS
  823. $$ This function return the exchange rate of a given currency, date and exchange rate class (buy or sell). $$;
  824. --
  825. -- payment_location_result has the same arch as location_result, except for one field
  826. -- This should be unified on the API when we get things working - David Mora
  827. --
  828. CREATE TYPE payment_location_result AS (
  829. id int,
  830. line_one text,
  831. line_two text,
  832. line_three text,
  833. city text,
  834. state text,
  835. mail_code text,
  836. country text,
  837. class text
  838. );
  839. --
  840. -- payment_get_vc_info has the same arch as company__list_locations, except for the filtering capabilities
  841. -- This should be unified on the API when we get things working - David Mora
  842. --
  843. CREATE OR REPLACE FUNCTION payment_get_vc_info(in_entity_credit_id int, in_location_class_id int)
  844. RETURNS SETOF payment_location_result AS
  845. $$
  846. DECLARE out_row payment_location_result;
  847. BEGIN
  848. FOR out_row IN
  849. SELECT l.id, l.line_one, l.line_two, l.line_three, l.city,
  850. l.state, l.mail_code, c.name, lc.class
  851. FROM location l
  852. JOIN company_to_location ctl ON (ctl.location_id = l.id)
  853. JOIN company cp ON (ctl.company_id = cp.id)
  854. JOIN location_class lc ON (ctl.location_class = lc.id)
  855. JOIN country c ON (c.id = l.country_id)
  856. JOIN entity_credit_account ec ON (ec.entity_id = cp.entity_id)
  857. WHERE ec.id = in_entity_credit_id AND
  858. lc.id = in_location_class_id
  859. ORDER BY lc.id, l.id, c.name
  860. LOOP
  861. RETURN NEXT out_row;
  862. END LOOP;
  863. END;
  864. $$ LANGUAGE PLPGSQL;
  865. COMMENT ON FUNCTION payment_get_vc_info(in_entity_id int, in_location_class_id int) IS
  866. $$ This function returns vendor or customer info $$;
  867. CREATE TYPE payment_record AS (
  868. amount numeric,
  869. meta_number text,
  870. credit_id int,
  871. company_paid text,
  872. accounts text[],
  873. source text,
  874. date_paid date
  875. );
  876. CREATE OR REPLACE FUNCTION payment__search
  877. (in_source text, in_date_from date, in_date_to date, in_credit_id int,
  878. in_cash_accno text, in_account_class int)
  879. RETURNS SETOF payment_record AS
  880. $$
  881. DECLARE
  882. out_row payment_record;
  883. BEGIN
  884. FOR out_row IN
  885. select sum(CASE WHEN c.entity_class = 1 then a.amount
  886. ELSE a.amount * -1 END), c.meta_number,
  887. c.id, co.legal_name,
  888. compound_array(ARRAY[ARRAY[ch.id::text, ch.accno,
  889. ch.description]]), a.source, a.transdate
  890. FROM entity_credit_account c
  891. JOIN ( select entity_credit_account, id
  892. FROM ar WHERE in_account_class = 2
  893. UNION
  894. SELECT entity_credit_account, id
  895. FROM ap WHERE in_account_class = 1
  896. ) arap ON (arap.entity_credit_account = c.id)
  897. JOIN acc_trans a ON (arap.id = a.trans_id)
  898. JOIN chart ch ON (ch.id = a.chart_id)
  899. JOIN company co ON (c.entity_id = co.entity_id)
  900. WHERE (ch.accno = in_cash_accno)
  901. AND (c.id = in_credit_id OR in_credit_id IS NULL)
  902. AND (a.transdate >= in_date_from
  903. OR in_date_from IS NULL)
  904. AND (a.transdate <= in_date_to OR in_date_to IS NULL)
  905. AND (source = in_source OR in_source IS NULL)
  906. GROUP BY c.meta_number, c.id, co.legal_name, a.transdate,
  907. a.source
  908. ORDER BY a.transdate, c.meta_number, a.source
  909. LOOP
  910. RETURN NEXT out_row;
  911. END LOOP;
  912. END;
  913. $$ language plpgsql;
  914. CREATE OR REPLACE FUNCTION payment__reverse
  915. (in_source text, in_date_paid date, in_credit_id int, in_cash_accno text,
  916. in_date_reversed date, in_account_class int, in_batch_id int)
  917. RETURNS INT
  918. AS $$
  919. DECLARE
  920. pay_row record;
  921. t_voucher_id int;
  922. t_voucher_inserted bool;
  923. BEGIN
  924. IF in_batch_id IS NOT NULL THEN
  925. t_voucher_id := nextval('voucher_id_seq');
  926. t_voucher_inserted := FALSE;
  927. END IF;
  928. FOR pay_row IN
  929. SELECT a.*, c.ar_ap_account_id
  930. FROM acc_trans a
  931. JOIN (select id, entity_credit_account
  932. FROM ar WHERE in_account_class = 2
  933. UNION
  934. SELECT id, entity_credit_account
  935. FROM ap WHERE in_account_class = 1
  936. ) arap ON (a.trans_id = arap.id)
  937. JOIN entity_credit_account c
  938. ON (arap.entity_credit_account = c.id)
  939. JOIN chart ch ON (a.chart_id = ch.id)
  940. WHERE coalesce(source, '') = coalesce(in_source, '')
  941. AND transdate = in_date_paid
  942. AND in_credit_id = c.id
  943. AND in_cash_accno = ch.accno
  944. LOOP
  945. IF in_batch_id IS NOT NULL
  946. AND t_voucher_inserted IS NOT TRUE
  947. THEN
  948. INSERT INTO voucher
  949. (id, trans_id, batch_id, batch_class)
  950. VALUES
  951. (t_voucher_id, pay_row.trans_id, in_batch_id,
  952. CASE WHEN in_account_class = 1 THEN 4
  953. WHEN in_account_class = 2 THEN 7
  954. END);
  955. t_voucher_inserted := TRUE;
  956. END IF;
  957. INSERT INTO acc_trans
  958. (trans_id, chart_id, amount, transdate, source, memo, approved,
  959. voucher_id)
  960. VALUES
  961. (pay_row.trans_id, pay_row.chart_id, pay_row.amount * -1,
  962. in_date_reversed, in_source, 'Reversing ' ||
  963. COALESCE(in_source, ''),
  964. case when in_batch_id is not null then false
  965. else true end, t_voucher_id);
  966. INSERT INTO acc_trans
  967. (trans_id, chart_id, amount, transdate, source, memo, approved,
  968. voucher_id)
  969. VALUES
  970. (pay_row.trans_id, pay_row.ar_ap_account_id, pay_row.amount,
  971. in_date_reversed, in_source, 'Reversing ' ||
  972. COALESCE(in_source, ''),
  973. case when in_batch_id is not null then false
  974. else true end, t_voucher_id);
  975. IF in_account_class = 1 THEN
  976. UPDATE ap SET paid = amount -
  977. (SELECT sum(a.amount)
  978. FROM acc_trans a
  979. JOIN chart c ON (a.chart_id = c.id)
  980. WHERE c.link = 'AP'
  981. AND trans_id = pay_row.trans_id
  982. )
  983. WHERE id = pay_row.trans_id;
  984. ELSIF in_account_class = 2 THEN
  985. update ar SET paid = amount -
  986. (SELECT sum(a.amount)
  987. FROM acc_trans a
  988. JOIN chart c ON (a.chart_id = c.id)
  989. WHERE c.link = 'AR'
  990. AND trans_id = pay_row.trans_id
  991. ) * -1
  992. WHERE id = pay_row.trans_id;
  993. ELSE
  994. RAISE EXCEPTION 'Unknown account class for payments %',
  995. in_account_class;
  996. END IF;
  997. END LOOP;
  998. RETURN 1;
  999. END;
  1000. $$ LANGUAGE PLPGSQL;
  1001. CREATE OR REPLACE FUNCTION payments_set_exchangerate(in_account_class int,
  1002. in_exchangerate numeric, in_curr char(3), in_datepaid date )
  1003. RETURNS INT
  1004. AS $$
  1005. DECLARE current_exrate exchangerate%ROWTYPE;
  1006. BEGIN
  1007. select * INTO current_exrate
  1008. FROM exchangerate
  1009. WHERE transdate = in_date;
  1010. IF current_exrate.transdate = in_date THEN
  1011. IF in_account_class = 1 THEN
  1012. UPDATE exchangerate set buy = in_exchangerate where transdate = in_date;
  1013. ELSE
  1014. UPDATE exchangerate set sell = in_exchangerate where transdate = in_date;
  1015. END IF;
  1016. RETURN 0;
  1017. ELSE
  1018. IF in_account_class = 1 THEN
  1019. INSERT INTO exchangerate (curr, transdate, buy) values (in_currency, in_date, in_exchangerate);
  1020. ELSE
  1021. INSERT INTO exchangerate (curr, transdate, sell) values (in_currency, in_date, in_exchangerate);
  1022. END IF;
  1023. RETURN 0;
  1024. END IF;
  1025. END;
  1026. $$ language plpgsql;
  1027. CREATE TYPE payment_header_item AS (
  1028. payment_id int,
  1029. payment_reference int,
  1030. payment_date date,
  1031. legal_name text,
  1032. amount numeric,
  1033. employee_first_name text,
  1034. employee_last_name text,
  1035. currency char(3),
  1036. notes text
  1037. );
  1038. -- I NEED TO PLACE THE COMPANY TELEPHONE AND ALL THAT STUFF
  1039. CREATE OR REPLACE FUNCTION payment_gather_header_info(in_account_class int, in_payment_id int)
  1040. RETURNS SETOF payment_header_item AS
  1041. $$
  1042. DECLARE out_payment payment_header_item;
  1043. BEGIN
  1044. FOR out_payment IN
  1045. SELECT p.id as payment_id, p.reference as payment_reference, p.payment_date,
  1046. c.legal_name as legal_name, am.amount as amount, em.first_name, em.last_name, p.currency, p.notes
  1047. FROM payment p
  1048. JOIN employee em ON (em.entity_id = p.employee_id)
  1049. JOIN company c ON (c.entity_id = p.entity_id)
  1050. JOIN ( SELECT sum(a.amount) as amount
  1051. FROM acc_trans a
  1052. JOIN chart c ON (a.chart_id = c.id)
  1053. JOIN payment_links pl ON (pl.entry_id=a.entry_id)
  1054. WHERE
  1055. ( ((c.link like '%AP_paid%' OR c.link like '%AP_discount%') AND in_account_class = 1)
  1056. OR ((c.link like '%AR_paid%' OR c.link like '%AR_discount%') AND in_account_class = 2))
  1057. AND pl.payment_id = in_payment_id ) am ON (1=1)
  1058. WHERE p.id = in_payment_id
  1059. LOOP
  1060. RETURN NEXT out_payment;
  1061. END LOOP;
  1062. END;
  1063. $$ language plpgsql;
  1064. COMMENT ON FUNCTION payment_gather_header_info(int,int) IS
  1065. $$ This function finds a payment based on the id and retrieves the record,
  1066. it is usefull for printing payments :) $$;
  1067. CREATE TYPE payment_line_item AS (
  1068. payment_id int,
  1069. entry_id int,
  1070. link_type int,
  1071. trans_id int,
  1072. invoice_number int,
  1073. chart_id int,
  1074. chart_accno int,
  1075. chart_description text,
  1076. chart_link text,
  1077. amount int,
  1078. trans_date date,
  1079. source text,
  1080. cleared bool,
  1081. fx_transaction bool,
  1082. project_id int,
  1083. memo text,
  1084. invoice_id int,
  1085. approved bool,
  1086. cleared_on date,
  1087. reconciled_on date
  1088. );
  1089. CREATE OR REPLACE FUNCTION payment_gather_line_info(in_account_class int, in_payment_id int)
  1090. RETURNS SETOF payment_line_item AS
  1091. $$
  1092. DECLARE out_payment_line payment_line_item;
  1093. BEGIN
  1094. FOR out_payment_line IN
  1095. SELECT pl.payment_id, ac.entry_id, pl.type as link_type, ac.trans_id, a.invnumber as invoice_number,
  1096. ac.chart_id, ch.accno as chart_accno, ch.description as chart_description, ch.link as chart_link,
  1097. ac.amount, ac.transdate as trans_date, ac.source, ac.cleared_on, ac.fx_transaction, ac.project_id,
  1098. ac.memo, ac.invoice_id, ac.approved, ac.cleared_on, ac.reconciled_on
  1099. FROM acc_trans ac
  1100. JOIN payment_links pl ON (pl.entry_id = ac.entry_id )
  1101. JOIN chart ch ON (ch.id = ac.chart_id)
  1102. LEFT JOIN (SELECT id,invnumber
  1103. FROM ar WHERE in_account_class = 2
  1104. UNION
  1105. SELECT id,invnumber
  1106. FROM ap WHERE in_account_class = 1
  1107. ) a ON (ac.trans_id = a.id)
  1108. WHERE pl.payment_id = in_payment_id
  1109. LOOP
  1110. RETURN NEXT out_payment_line;
  1111. END LOOP;
  1112. END;
  1113. $$ language plpgsql;
  1114. COMMENT ON FUNCTION payment_gather_line_info(int,int) IS
  1115. $$ This function finds a payment based on the id and retrieves all the line records,
  1116. it is usefull for printing payments and build reports :) $$;