1 #ident "Kalopa: $Id: statement.c,v 1.15 2008/10/15 15:11:14 dtynan Exp $"
2
3 /*
4 * $Id: statement.c,v 1.15 2008/10/15 15:11:14 dtynan Exp $
5 *
6 * Copyright (c) 2005, Kalopa Research Limited. All rights reserved.
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published
9 * by the Free Software Foundation; either version 2, or (at your
10 * option) any later version.
11 *
12 * It is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
15 * License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this product; see the file COPYING. If not, write to
19 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
20 * USA.
21 *
22 * THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH LIMITED "AS IS" AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
24 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KALOPA
26 * RESEARCH LIMITED BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
29 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
33 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 * ABSTRACT
36 *
37 * $Log: statement.c,v $
38 * Revision 1.15 2008/10/15 15:11:14 dtynan
39 * Mammoth change to convert from old-style database configuration to new
40 * schema where an RC file defines the database access.
41 *
42 * Revision 1.14 2008/10/13 20:59:38 dtynan
43 * Extensive changes (part 1) for change to database configuration.
44 *
45 * Revision 1.13 2008/01/15 18:10:44 dtynan
46 * Changed to use new company name and copyright, also fixed a few old
47 * files with the wrong license.
48 *
49 * Revision 1.12 2008/01/15 15:34:59 dtynan
50 * Extensive changes for new DBOW version.
51 *
52 * Revision 1.11 2007/06/19 22:02:06 dtynan
53 * Updated with extra components.
54 *
55 * Revision 1.10 2006/10/01 10:32:33 dtynan
56 * Major changes for VAT.
57 *
58 * Revision 1.9 2006/09/12 07:42:31 dtynan
59 * Fixed bug in statement generation, where the customer code could not be
60 * retrieved correctly.
61 *
62 * Revision 1.8 2006/07/31 10:17:52 dtynan
63 * Massive changeover to a more Ruby/Rails primary key naming convention.
64 *
65 * Revision 1.7 2005/10/17 13:35:27 dtynan
66 * First pass at a multicurrency invoicing system.
67 *
68 * Revision 1.6 2005/08/15 12:04:10 dtynan
69 * Minor tweaks to fix some MIME issues.
70 *
71 * Revision 1.5 2005/08/15 11:31:40 dtynan
72 * Troff address block was going to the wrong place.
73 *
74 * Revision 1.4 2005/08/12 18:59:03 dtynan
75 * Fixed some minor bugs with the handling of negative numbers.
76 *
77 * Revision 1.3 2005/04/11 21:17:08 dtynan
78 * Added a -p option to generate PostScript (via troff) also made some
79 * cleanup changes to the output.
80 *
81 * Revision 1.2 2005/04/11 20:52:27 dtynan
82 * Deal properly with negative numbers.
83 *
84 * Revision 1.1 2005/04/11 17:51:57 dtynan
85 * New program to produce a customer statement.
86 */
87 #include <stdio.h>
88 #include <stdlib.h>
89 #include <unistd.h>
90 #include <string.h>
91 #include <time.h>
92
93 #include "beanie.h"
94 #include "hacks.h"
95
96 int verbose;
97
98 extern int optind, opterr;
99 extern char *optarg;
100
101 void genstatement(FILE *, struct db_customer *);
102 void usage();
103
104 /*
105 *
106 */
107 int
main(int argc,char * argv[])108 main(int argc, char *argv[])
109 {
110 int i, aflag, psflag, warnf, sawfirst, didfirst, row;
111 struct db_company *comp;
112 struct db_customer *cusp;
113 struct db_contact *conp;
114 struct db_sledger *sp;
115 struct db_invoice *ip;
116 struct db_currency *curp;
117 struct db_bankacct *bap;
118 double total = 0.0;
119 double ac, a3, a6, a9;
120 time_t sdate, edate, now;
121 dbow_conn *dbp;
122 char *cname, cmd[512], *currlabel;
123 FILE *fp;
124
125 time(&now);
126 edate = (now / 86400) * 86400 + 82799;
127 aflag = verbose = psflag = warnf = 0;
128 cname = NULL;
129 sdate = now - DAYS(120);
130 ac = a3 = a6 = a9 = 0.0;
131 while ((i = getopt(argc, argv, "ac:ps:e:v")) != EOF) {
132 switch (i) {
133 case 'a':
134 aflag = 1;
135 break;
136
137 case 'c':
138 cname = optarg;
139 break;
140
141 case 'p':
142 psflag = 1;
143 break;
144
145 case 's':
146 if (parsedate(optarg, &sdate) < 0)
147 usage();
148 break;
149
150 case 'e':
151 if (parsedate(optarg, &edate) < 0)
152 usage();
153 break;
154
155 case 'v':
156 verbose = 1;
157 break;
158
159 default:
160 usage();
161 break;
162 }
163 }
164 if ((argc - optind) != 1)
165 usage();
166 loadconfig(cname);
167 dbp = getdbase();
168 if ((comp = db_findcompanyfirst(dbp)) == NULL)
169 berror("statement", "cannot find company");
170 if (verbose) {
171 printf("Company: %s\n", comp->cname);
172 printf("Start on %s", ctime(&sdate));
173 printf("End on %s", ctime(&edate));
174 }
175 /*
176 * Do the report for each customer...
177 */
178 if ((conp = contact_findcode(dbp, argv[optind])) == NULL) {
179 fprintf(stderr,"?Cannot find code: %s\n", argv[optind]);
180 exit(1);
181 }
182 cusp = db_findcustomerbycontact_id(dbp, conp->id);
183 if (cusp == NULL) {
184 fprintf(stderr, "?Contact is not a customer.\n");
185 exit(1);
186 }
187 if ((curp = findcurrency(dbp, cusp->currency_id)) == NULL)
188 berror("statement", "cannot find customer currency");
189 currlabel = (curp->rofflabel != NULL) ? curp->rofflabel : "";
190 /*
191 * Where is this statement going?
192 */
193 if (psflag) {
194 sprintf(cmd, "groff -s -t -Tps");
195 if ((fp = popen(cmd, "w")) == NULL)
196 berror("geninvoice", "popen");
197 } else
198 fp = stdout;
199 /*
200 * Pull all the appropriate sales ledger records...
201 */
202 fprintf(fp, ".ds StDate %s\n.ds StPX 1\n.ds StPY 1\n", prtime(&now));
203 fprintf(fp, ".ds Curr %s\n", curp->lname);
204 gentroffaddr(fp, dbp, cusp->contact_id, cusp->cperson_id);
205 total = 0.0;
206 sawfirst = didfirst = row = 0;
207 bap = db_findbankacctbyid(dbp, 1);
208 if (bap != NULL && bap->iban != NULL)
209 fprintf(fp, ".ds IBAN %s\n", bap->iban);
210 for (sp = findsledgerentries(dbp, cusp->id);
211 sp != NULL && sp->sldate <= edate;
212 sp = db_findsledgernext(dbp, sp)) {
213 if (!sawfirst) {
214 if (sdate < sp->sldate)
215 sdate = sp->sldate;
216 sawfirst = 1;
217 }
218 total += sp->amount;
219 if (sp->sldate < sdate)
220 continue;
221 if (!didfirst) {
222 fprintf(fp, ".ds ODate %s\n", prabbrev(&sdate));
223 fprintf(fp, ".ds OBal %s\n", bal(total - sp->amount,
224 currlabel));
225 didfirst = 1;
226 }
227 fprintf(fp, ".ds L%02da %s\n", ++row, prabbrev((time_t *)&sp->sldate));
228 if (sp->folio != NULL)
229 fprintf(fp, ".ds L%02db %s\n", row, sp->folio);
230 if (sp->descr != NULL)
231 fprintf(fp, ".ds L%02dc %s\n", row, sp->descr);
232 if (sp->amount < 0.0) {
233 fprintf(fp, ".ds L%02dd %s\n", row, bal(-sp->amount,
234 NULL));
235 } else {
236 fprintf(fp, ".ds L%02de %s\n", row, bal(sp->amount,
237 NULL));
238 }
239 fprintf(fp, ".ds L%02df %s\n", row, bal(total, NULL));
240 }
241 if (!didfirst)
242 fprintf(fp, ".ds ODate %s\n.ds OBal 0.00\n", prabbrev(&sdate));
243 fprintf(fp, ".ds CBal %s\n", bal(total, currlabel));
244 fprintf(fp, ".ds CDate %s\n", prabbrev(&edate));
245 for (ip = db_findinvoicebycustomer_id(dbp, cusp->id);
246 ip != NULL;
247 ip = db_findinvoicenext(dbp, ip)) {
248 switch (ip->state) {
249 case INVOICE_STATE_SENT:
250 ac += ip->total;
251 break;
252
253 case INVOICE_STATE_LATE1:
254 case INVOICE_STATE_LATE2:
255 a3 += ip->total;
256 break;
257
258 case INVOICE_STATE_LATE3:
259 warnf = 1;
260 a6 += ip->total;
261 break;
262
263 case INVOICE_STATE_LATE4:
264 case INVOICE_STATE_LATE5:
265 warnf = 1;
266 a9 += ip->total;
267 break;
268 }
269 }
270 if (ac > 0.0)
271 fprintf(fp, ".ds ACurr %s\n", bal(ac, currlabel));
272 if (a3 > 0.0)
273 fprintf(fp, ".ds A30 %s\n", bal(a3, currlabel));
274 if (a6 > 0.0)
275 fprintf(fp, ".ds A60 %s\n", bal(a6, currlabel));
276 if (a9 > 0.0)
277 fprintf(fp, ".ds A90 %s\n", bal(a9, currlabel));
278 fprintf(fp, ".ds Company %s\n", comp->tag);
279 fprintf(fp, ".nr PrePrint %d\n", psflag ? 0 : 1);
280 fprintf(fp, ".so %s.tmac\n", makepath("roff", "statement", NULL));
281 fprintf(fp, ".TS\nbox;\nlb.\n");
282 if (warnf) {
283 fprintf(fp, "Please Note: Your account is currently delinquen");
284 fprintf(fp, "t.\nPlease pay all outstanding invoices to preve");
285 fprintf(fp, "nt an\ninterruption in service.\n");
286 } else if (bap != NULL && bap->iban != NULL) {
287 fprintf(fp, "Please Note: You can pay by Electronic Funds Tra");
288 fprintf(fp, "nsfer\nto \\fI%s\\fB. Please send an", bap->iban);
289 fprintf(fp, "\nemail to \\fIaccounts@kalopa.net\\fB to confir");
290 fprintf(fp, "m EFT payment.\n");
291 } else {
292 fprintf(fp, "If you wish to pay by Electronic Funds Transfer,");
293 fprintf(fp, " please\ncontact the accounts department at \\fI");
294 fprintf(fp, "accounts@kalopa.net\\fP\nfor further details.\n");
295 }
296 fprintf(fp, ".TE\n");
297 if (psflag)
298 pclose(fp);
299 exit(0);
300 }
301
302 /*
303 *
304 */
305 void
usage()306 usage()
307 {
308 fprintf(stderr, "Usage: statement [-c #][-v][-s DATE][-e DATE] <custcode>\n");
309 exit(2);
310 }
311