1 -- APMS Utility Functions
3 -- Returns the name of the entity type
4 CREATE or REPLACE FUNCTION get_entity_type_name( TEXT ) RETURNS TEXT AS '
6 WHEN $1 = ''T'' THEN ''Debtor''
7 WHEN $1 = ''C'' THEN ''Creditor''
8 WHEN $1 = ''L'' THEN ''Company''
9 WHEN $1 = ''P'' THEN ''Property''
10 WHEN $1 = ''J'' THEN ''Project''
11 WHEN $1 = ''A'' THEN ''Asset''
14 ' LANGUAGE SQL IMMUTABLE STRICT;
16 -- Returns the internal name of the entity type
17 CREATE or REPLACE FUNCTION get_entity_type_internal_name( TEXT ) RETURNS TEXT AS '
19 WHEN $1 = ''T'' THEN ''tenant''
20 WHEN $1 = ''C'' THEN ''creditor''
21 WHEN $1 = ''L'' THEN ''company''
22 WHEN $1 = ''P'' THEN ''property''
23 WHEN $1 = ''J'' THEN ''project''
24 WHEN $1 = ''A'' THEN ''asset''
27 ' LANGUAGE SQL IMMUTABLE STRICT;
29 -- Returns a link for the document reference, if possible
30 CREATE or REPLACE FUNCTION get_docref_link( TEXT, TEXT ) RETURNS TEXT AS '
32 WHEN $2 = ''VCHR'' THEN ''<a href="/view.php?t=voucher&id=''||$1||''" title="View the creditor voucher that created this document">''||$1||''</a>''
33 WHEN $2 = ''CHEQ'' THEN ''<a href="/view.php?t=cheque&id=''||$1||''" title="View the cheque that created this document">''||$1||''</a>''
34 WHEN $2 = ''INVC'' THEN ''<a href="/view.php?t=invoice&id=''||$1||''" title="View the debtor invoice that created this document">''||$1||''</a>''
37 ' LANGUAGE SQL IMMUTABLE STRICT;
40 -- Returns the name of the entity, depending on the type
41 CREATE or REPLACE FUNCTION get_entity_name( TEXT, INT4 ) RETURNS TEXT AS '
43 WHEN $1 = ''T'' THEN (SELECT name FROM tenant WHERE tenantcode=$2)
44 WHEN $1 = ''C'' THEN (SELECT name FROM creditor WHERE creditorcode=$2)
45 WHEN $1 = ''L'' THEN (SELECT legalname FROM company WHERE companycode=$2)
46 WHEN $1 = ''P'' THEN (SELECT name FROM property WHERE propertycode=$2)
47 WHEN $1 = ''J'' THEN (SELECT name FROM project WHERE projectcode=$2)
50 ' LANGUAGE SQL STABLE;
52 -- Returns the name of the entity, depending on the type
53 CREATE or REPLACE FUNCTION get_account_name( NUMERIC ) RETURNS TEXT AS $$
54 SELECT name FROM chartofaccount WHERE accountcode = $1
55 $$ LANGUAGE SQL IMMUTABLE STRICT;
58 CREATE or REPLACE FUNCTION previous_balance( TEXT, INT, NUMERIC, INT, INT ) RETURNS NUMERIC AS '
67 SELECT balance INTO result
68 FROM accountbalance ab
69 JOIN month oldm USING ( monthcode )
70 JOIN month newm ON newm.startdate = (oldm.startdate + (years::text||'' years'')::interval)
71 WHERE entitytype = et AND entitycode = ec AND accountcode = ac
72 AND newm.monthcode = mcode;
78 ' LANGUAGE plpgsql STABLE;
82 CREATE or REPLACE FUNCTION previous_summary( TEXT, INT, NUMERIC, INT, INT ) RETURNS NUMERIC AS $$
93 SELECT oldm.monthcode INTO mth FROM month oldm JOIN month newm ON newm.startdate = (oldm.startdate + (years::text||' years')::interval)
94 WHERE newm.monthcode = mcode;
96 RAISE NOTICE 'No month record found (%).', mcode;
100 SELECT sum(balance) INTO result FROM accountbalance ab
101 WHERE entitytype = et AND entitycode = ec AND accountcode = ac AND monthcode <= mth;
102 IF NOT FOUND OR result IS NULL THEN
103 result := 0::numeric;
106 SELECT accountcode INTO year_end FROM chartofaccount LEFT JOIN accountgroup ag USING(accountgroupcode)
107 WHERE accountcode = ac AND grouptype = 'P';
112 SELECT sum(amount) INTO year_end FROM accttran
113 LEFT JOIN document USING (batchcode,documentcode)
114 WHERE entitytype = et AND entitycode = ec AND accountcode = ac AND monthcode = mth
115 AND documenttype = 'YEND';
116 IF FOUND AND year_end IS NOT NULL THEN
117 result := result - year_end;
122 $$ LANGUAGE plpgsql STABLE;
126 CREATE or REPLACE FUNCTION year_end_balance( TEXT, INT, INT ) RETURNS SETOF accountbalance AS $$
133 yend_reversals NUMERIC;
134 result accountbalance%ROWTYPE;
137 SELECT max(monthcode) INTO fy_last_month FROM month WHERE financialyearcode = fy;
139 result.entitytype := et;
140 result.entitycode := ec;
141 result.monthcode := fy_last_month;
142 result.notecode := NULL;
145 SELECT accountcode, sum(balance) AS balance, sum(budget) AS budget, sum(revisedbudget) AS revisedbudget, accountgroup.grouptype
146 FROM month LEFT JOIN accountbalance USING (monthcode)
147 RIGHT JOIN chartofaccount USING(accountcode)
148 LEFT JOIN accountgroup USING(accountgroupcode)
149 WHERE (month.financialyearcode = fy OR (grouptype != 'P' AND month.financialyearcode < fy) )
150 AND accountbalance.entitytype = et AND accountbalance.entitycode = ec
151 GROUP BY accountgroup.grouptype, accountcode
153 SELECT sum(amount) INTO yend_reversals
154 FROM accttran JOIN document USING (batchcode,documentcode)
155 WHERE accttran.entitytype = et AND accttran.entitycode = ec AND accttran.accountcode = abrec.accountcode
156 AND documenttype = 'YEND' AND accttran.monthcode = fy_last_month
157 GROUP BY accountcode;
158 result.balance := coalesce(abrec.balance,0) - coalesce(yend_reversals,0);
159 result.budget := coalesce(abrec.budget,0);
160 result.revisedbudget := coalesce(abrec.revisedbudget,0);
161 IF result.balance != 0 OR result.budget != 0 OR result.revisedbudget != 0 THEN
162 result.accountcode := abrec.accountcode;
167 -- That caught most of them, but for the ones that got zeroed out, and which have no budget
168 -- or revised budget, the accountbalance records get removed by a trigger, so we need a special
169 -- pass through looking at anything which had YEND transactions, and which now doesn't exist!
171 SELECT accttran.accountcode, sum(amount) AS balance, 0::numeric AS budget, 0::numeric AS revisedbudget, ag.grouptype
172 FROM accttran LEFT JOIN document USING (batchcode,documentcode)
173 LEFT JOIN chartofaccount USING(accountcode)
174 LEFT JOIN accountgroup ag USING(accountgroupcode)
175 WHERE entitytype = et AND entitycode = ec
176 AND documenttype = 'YEND' AND monthcode = fy_last_month AND grouptype = 'P'
177 AND NOT EXISTS( SELECT 1 FROM month LEFT JOIN accountbalance USING (monthcode)
178 RIGHT JOIN chartofaccount USING(accountcode)
179 LEFT JOIN accountgroup USING(accountgroupcode)
180 WHERE month.financialyearcode = fy AND accountgroup.grouptype = 'P' AND accountbalance.entitytype = accttran.entitytype
181 AND accountbalance.entitycode = accttran.entitycode AND accountbalance.accountcode = accttran.accountcode )
182 GROUP BY ag.grouptype, accountcode
184 result.balance := 0 - coalesce(abrec.balance,0);
185 IF result.balance != 0 THEN
187 result.revisedbudget := 0;
188 result.accountcode := abrec.accountcode;
195 $$ LANGUAGE plpgsql STABLE;
198 CREATE or REPLACE FUNCTION debtor_statistics( IN tcode INT, IN mfrom INT, IN mto INT,
199 OUT tenant_name TEXT,
200 OUT total_paid NUMERIC,
201 OUT average_month NUMERIC,
203 OUT average_days NUMERIC,
205 OUT balance_days INT )
208 debtors_control NUMERIC;
209 start_balance NUMERIC;
213 group_unpaid_days INT;
214 group_amount NUMERIC;
220 SELECT accountcode INTO debtors_control FROM office JOIN officecontrolaccount USING (officecode) where officecontrolaccount.name = ''DEBTORS'' AND office.thisoffice;
222 SELECT name INTO tenant_name FROM tenant WHERE tenantcode=tcode;
224 SELECT sum(amount), count(amount) INTO total_paid, times FROM accttran
225 WHERE entitytype = ''T'' AND entitycode = tcode
226 AND accountcode = debtors_control
227 AND monthcode >= mfrom AND monthcode <= mto
230 SELECT total_paid / count(*), min(startdate), max(enddate) INTO average_month, period_start, period_end
231 FROM month WHERE monthcode >= mfrom AND monthcode <= mto;
233 SELECT sum(balance) INTO start_balance FROM accountbalance
234 WHERE entitytype = ''T'' AND entitycode = tcode
235 AND accountcode = debtors_control
236 AND monthcode < mfrom;
242 FOR groups IN SELECT DISTINCT closinggroup FROM accttran
243 WHERE entitytype = ''T'' AND entitycode = tcode
244 AND accountcode = debtors_control
245 AND monthcode >= mfrom AND monthcode <= mto
247 group_count := group_count + 1;
249 SELECT date INTO group_earliest FROM accttran
250 WHERE entitytype = ''T'' AND entitycode = tcode
251 AND accountcode = debtors_control
252 AND closinggroup = groups.closinggroup
253 ORDER BY date LIMIT 1;
255 SELECT date INTO group_last FROM accttran
256 WHERE entitytype = ''T'' AND entitycode = tcode
257 AND accountcode = debtors_control
258 AND closinggroup = groups.closinggroup
259 ORDER BY date DESC LIMIT 1;
261 total_days := total_days + (group_last - group_earliest);
262 IF group_last > period_end THEN
263 group_unpaid_days := period_end - group_earliest;
264 IF group_unpaid_days > balance_days THEN
265 balance_days := group_unpaid_days;
271 average_days := CASE WHEN group_count = 0 THEN 0 ELSE total_days::numeric / group_count END;
274 ' LANGUAGE plpgsql STABLE;
276 -- Retrieve all phone numbers and nicely format them into a single string
277 -- FIXME: This assumes local STD dialling is preceded by a '0' and other unwarranted things
278 CREATE or REPLACE FUNCTION get_person_phones( INT, TEXT, TEXT ) RETURNS TEXT AS '
280 in_personcode ALIAS FOR $1;
281 loc_ccode ALIAS FOR $2;
282 loc_cstd ALIAS FOR $3;
287 FOR phone IN SELECT phonetype, ccountrycode, cstdcode, number FROM phonedetail
288 WHERE personcode = in_personcode
290 phonelist := phonelist
291 || (CASE WHEN phonelist = '''' THEN '''' ELSE '', '' END)
292 || phone.phonetype || '': ''
294 WHEN loc_ccode = phone.ccountrycode THEN
295 (CASE WHEN loc_cstd = phone.cstdcode THEN '''' ELSE ''0'' || phone.cstdcode || '' '' END)
297 ''+'' || phone.ccountrycode || ''('' || phone.cstdcode || '')''
303 ' LANGUAGE plpgsql STRICT;
306 CREATE or REPLACE FUNCTION get_office_setting( TEXT ) RETURNS TEXT AS $$
307 SELECT setvalue FROM office JOIN officesettings USING ( officecode ) WHERE setname = $1 AND thisoffice;
308 $$ LANGUAGE sql STRICT IMMUTABLE;
311 CREATE or REPLACE FUNCTION date_series( DATE, INT4, INT4 ) RETURNS SETOF DATE AS $$
313 base_date ALIAS FOR $1;
314 repeats ALIAS FOR $2;
320 this_date := base_date;
322 gap := (period::text || ' days')::interval;
324 WHILE counter < repeats LOOP
325 RETURN NEXT this_date;
326 counter := counter + 1;
327 this_date := this_date + gap;
331 $$ LANGUAGE plpgsql STRICT IMMUTABLE;
334 DROP TYPE day_balance_type CASCADE;
335 CREATE TYPE day_balance_type AS (
341 CREATE or REPLACE FUNCTION daily_balance( CHAR, INT4, NUMERIC, DATE, INT4 ) RETURNS SETOF day_balance_type AS $$
346 base_date ALIAS FOR $4;
348 result day_balance_type%ROWTYPE;
353 SELECT monthcode INTO mth FROM month WHERE month.startdate <= base_date AND month.enddate >= base_date;
355 RAISE NOTICE 'No month record covering start of period (%).', base_date;
359 SELECT sum(balance) INTO starting FROM accountbalance WHERE entitytype = et AND entitycode = ec AND accountcode = ac AND monthcode < mth;
360 IF NOT FOUND OR starting IS NULL THEN
363 result.day := base_date - '1 day'::interval;
364 result.balance := starting;
366 SELECT sum(amount) INTO starting FROM accttran WHERE entitytype = et AND entitycode = ec AND accountcode = ac AND monthcode >= mth AND date < base_date;
368 result.balance := result.balance + COALESCE(starting,0);
372 SELECT date_series, sum(amount) FROM date_series( base_date, days, 1)
373 LEFT JOIN accttran ON (accttran.date = date_series AND entitytype = et AND entitycode = ec AND accountcode = ac AND monthcode >= mth)
374 GROUP BY date_series ORDER BY date_series
376 result.day := delta.date_series;
377 result.balance := result.balance + COALESCE(delta.sum, 0);
384 $$ LANGUAGE plpgsql STRICT IMMUTABLE;