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