1 #ident "Kalopa: $Id: postinvoice.c,v 1.33 2008/10/31 15:17:13 dtynan Exp $"
2
3 /*
4 * $Id: postinvoice.c,v 1.33 2008/10/31 15:17:13 dtynan Exp $
5 *
6 * Copyright (c) 2003, Kalopa Media Limited. All rights reserved.
7 * Copyright (c) 2005, Kalopa Research Limited. All rights reserved.
8 * This is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published
10 * by the Free Software Foundation; either version 2, or (at your
11 * option) any later version.
12 *
13 * It is distributed in the hope that it will be useful, but WITHOUT
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
16 * License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this product; see the file COPYING. If not, write to
20 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
21 * USA.
22 *
23 * THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH LIMITED "AS IS" AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
25 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
26 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KALOPA
27 * RESEARCH LIMITED BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
30 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
34 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 *
36 * ABSTRACT
37 *
38 * $Log: postinvoice.c,v $
39 * Revision 1.33 2008/10/31 15:17:13 dtynan
40 * Extensive changes for version 0.8.2.
41 *
42 * Revision 1.32 2008/10/20 17:53:25 dtynan
43 * Added a document for installation (quite basic at this point), removed
44 * some debug output from tpadjust(), and fixed a lingering issue with the
45 * way invoice due-dates were calculated.
46 *
47 * Revision 1.31 2008/10/15 15:11:14 dtynan
48 * Mammoth change to convert from old-style database configuration to new
49 * schema where an RC file defines the database access.
50 *
51 * Revision 1.30 2008/10/13 20:59:38 dtynan
52 * Extensive changes (part 1) for change to database configuration.
53 *
54 * Revision 1.29 2008/07/18 11:07:38 dtynan
55 * Fixed issue with isholiday/skipholiday where it wouldn't actually detect
56 * bank holidays (only weekends). It now interrogates the database.
57 *
58 * Revision 1.28 2008/07/08 17:10:18 dtynan
59 * Extensive changes.
60 *
61 * Revision 1.27 2008/01/23 16:29:21 dtynan
62 * Added the ability to run the apps without effect (-n) and fixed some
63 * effective-date issues with invoice postings.
64 *
65 * Revision 1.26 2008/01/15 18:10:44 dtynan
66 * Changed to use new company name and copyright, also fixed a few old
67 * files with the wrong license.
68 *
69 * Revision 1.25 2007/08/07 07:48:13 dtynan
70 * When posting a credit note, don't add the invoice description to the
71 * Sales Ledger, add "Credit Note" instead.
72 *
73 * Revision 1.24 2006/10/01 10:32:33 dtynan
74 * Major changes for VAT.
75 *
76 * Revision 1.23 2006/07/31 10:17:52 dtynan
77 * Massive changeover to a more Ruby/Rails primary key naming convention.
78 *
79 * Revision 1.22 2005/12/19 22:17:43 dtynan
80 * Added rudimentary support for VAT - still need to sort out some
81 * multicurrency issues.
82 *
83 * Revision 1.21 2005/10/17 15:41:47 dtynan
84 * Tweaked the roundoff routine.
85 *
86 * Revision 1.20 2005/10/17 15:12:42 dtynan
87 * Use the new roundoff function for accurate discount calculations.
88 *
89 * Revision 1.19 2005/10/17 13:35:27 dtynan
90 * First pass at a multicurrency invoicing system.
91 *
92 * Revision 1.18 2005/09/21 18:10:28 dtynan
93 * Extensive changes prior to first public release, including an
94 * import/export mechanism.
95 *
96 * Revision 1.17 2005/08/12 18:57:50 dtynan
97 * Changed to use a slightly different format - now an invoice is
98 * sent with a statement the first time but only a statement is
99 * sent as a reminder.
100 *
101 * Revision 1.16 2005/04/11 13:52:04 dtynan
102 * Added code to post invoice data to sales ledger.
103 *
104 * Revision 1.15 2005/03/22 09:41:25 dtynan
105 * Extensive upgrade to make sure the copyright block was
106 * consistent and GPL'ed.
107 *
108 * Revision 1.14 2005/03/09 11:22:13 dtynan
109 * Removed custcode from the customer table. Now replaced with ccode
110 * from the contact table.
111 *
112 * Revision 1.13 2005/02/17 17:25:28 dtynan
113 * Fixed some compiler warnings.
114 *
115 * Revision 1.12 2005/02/17 16:23:22 dtynan
116 * Extensive changes to support new customer/contact database
117 * format and to add user IDs to journal entries.
118 *
119 * Revision 1.11 2005/01/13 18:19:22 dtynan
120 * Added code to correctly handle prepayments.
121 *
122 * Revision 1.10 2005/01/13 17:50:31 dtynan
123 * Debit the customer record by the invoice amount.
124 *
125 * Revision 1.9 2004/12/09 20:33:57 dtynan
126 * Fixed bug in include files.
127 *
128 * Revision 1.8 2004/12/09 20:29:16 dtynan
129 * Use new mechanism for determining organisation name.
130 *
131 * Revision 1.7 2004/12/08 14:30:37 dtynan
132 * Fixed a bug.
133 *
134 * Revision 1.6 2004/12/08 14:29:42 dtynan
135 * Now adds the invoice # to the journal folio and calculates the invoice
136 * total.
137 *
138 * Revision 1.5 2004/09/07 12:00:24 dtynan
139 * Fixed issue where geninvoice was being called without specifying
140 * the company number (which defaults to 1).
141 *
142 * Revision 1.4 2004/08/26 17:24:10 dtynan
143 * Minor changes and tweaks - nothing serious.
144 *
145 * Revision 1.3 2004/08/05 12:51:53 dtynan
146 * Added code to post an invoice to the journal.
147 *
148 * Revision 1.2 2004/08/04 13:15:24 dtynan
149 * Make sure the company number is handled properly.
150 *
151 * Revision 1.1 2004/07/16 19:23:33 dtynan
152 * Extensive changes to split the invoicing into posting and generating.
153 * Also fixed some bugs in the site billing code, added a default nominal
154 * account (5060) and fixed some bugs in the invoice emailing code. Added
155 * makepath() calls to generate the correct paths.
156 */
157 #include <stdio.h>
158 #include <stdlib.h>
159 #include <unistd.h>
160 #include <string.h>
161 #include <time.h>
162
163 #include "beanie.h"
164 #include "hacks.h"
165
166 struct jitem {
167 int acctno;
168 double amount;
169 } jitem[20];
170
171 int noeffect;
172 int verbose;
173 struct db_company *comp;
174 struct vatamt *vat;
175
176 extern int optind, opterr;
177 extern char *optarg;
178
179 void postinvoice(struct db_invoice *);
180 void usage();
181
182 /*
183 *
184 */
185 int
main(int argc,char * argv[])186 main(int argc, char *argv[])
187 {
188 int i;
189 char *cname;
190 dbow_conn *dbp;
191 struct db_invoice *invp;
192
193 verbose = noeffect = 0;
194 cname = NULL;
195 while ((i = getopt(argc, argv, "c:nv")) != EOF) {
196 switch (i) {
197 case 'c':
198 cname = optarg;
199 break;
200
201 case 'n':
202 noeffect = 1;
203 break;
204
205 case 'v':
206 verbose = 1;
207 break;
208
209 default:
210 usage();
211 break;
212 }
213 }
214 /*
215 * Find the company that we're processing...
216 */
217 loadconfig(cname);
218 dbp = getdbase();
219 if ((comp = db_findcompanyfirst(dbp)) == NULL)
220 berror("postinvoice", "cannot find company");
221 vat = vat_init(dbp);
222 /*
223 * Post all the invoices that aren't complete.
224 */
225 invp = db_findinvoicebystate(dbp, INVOICE_STATE_NEW);
226 while (invp != NULL) {
227 postinvoice(invp);
228 invp = db_findinvoicenext(dbp, invp);
229 }
230 exit(0);
231 }
232
233 /*
234 *
235 */
236 void
postinvoice(struct db_invoice * invp)237 postinvoice(struct db_invoice *invp)
238 {
239 int i, njitem, curr;
240 struct jitem *xp;
241 struct db_journal *jp;
242 struct db_journal_item *jip;
243 struct db_invoice *pip;
244 struct db_invoice_item *iip;
245 struct db_customer *cusp;
246 struct db_contact *cp;
247 struct db_sledger *slp;
248 char descr[256], invid[64];
249 double hval, aval, ttl;
250 dbow_conn *dbp = getdbase();
251
252 if (verbose)
253 printf("Posting invoice/crnote #%d to journal...\n", invp->id);
254 vat_clear(vat);
255 /*
256 * Find the name of the customer and get their default currency.
257 */
258 if (invp->customer_id == 0) {
259 /*
260 * Cash sale.
261 */
262 cusp = NULL;
263 } else if ((cusp = db_findcustomerbyid(dbp, invp->customer_id)) == NULL)
264 berror("postinvoice", "cannot find customer record");
265 if (cusp != NULL && cusp->currency_id > 0)
266 curr = cusp->currency_id;
267 else
268 curr = HOME_CURRENCY;
269 if (invp->currency_id == 0)
270 invp->currency_id = curr;
271 /*
272 * Update the invoice fields, including the 'due date' and the
273 * invoice state (via the dunning system).
274 */
275 if (invp->idate == 0)
276 time((time_t *)&invp->idate);
277 dunning(invp);
278 invp->vatttl = 0.0;
279 invp->shipping = 0.0;
280 if (verbose)
281 printf("Due date: %s", ctime((time_t *)&invp->ddate));
282 /*
283 * Generate the journal entry title
284 */
285 if (cusp != NULL) {
286 cp = contact_findbyid(dbp, cusp->contact_id);
287 sprintf(descr, "%s for %s", invp->cninv_id != 0 ?
288 "Credit Note" : "Invoice", orgname(cp));
289 } else {
290 sprintf(descr, "Cash %s", invp->cninv_id != 0 ?
291 "Credit Note" : "Invoice");
292 }
293 sprintf(invid, "%d", invp->id);
294 if (verbose)
295 printf("Posting title: [%s] (%s)\n", descr, invid);
296 /*
297 * Allocate (and stuff) a journal entry.
298 */
299 if ((jp = db_journalalloc()) == NULL)
300 berror("postinvoice", "cannot create journal entry");
301 jp->jdate = invp->idate;
302 jp->folio = strdup(invid);
303 jp->descr = strdup(descr);
304 jp->user_id = getuid();
305 jp->state = 0;
306 if (!noeffect && db_insertjournal(dbp, jp) < 0)
307 berror("postinvoice", "cannot create journal entry");
308 /*
309 * Find out how many different nominal accounts are needed.
310 * The first is for accounts-receivable which is debited.
311 * 'aval' is the actual value of the invoice item in the
312 * customer currency, and 'hval' is the home value of the
313 * invoice in our local currency. Note that the customer
314 * will be invoiced in their currency, but the posting will
315 * be in our currency. When payment is made (in their
316 * currency), we use N/A "9400; currency gains and losses"
317 * to account for any discrepancy.
318 */
319 njitem = 1;
320 xp = jitem;
321 xp->acctno = (cusp == NULL) ? NA_CASHSALES : NA_DEBTORSCA;
322 xp->amount = ttl = 0.0;
323 #ifdef VAT_CALCULATE
324 xp++;
325 njitem++;
326 xp->acctno = NA_VATACCOUNT;
327 xp->amount = 0.0;
328 #endif
329 iip = db_findinvoice_itembyinvoice_id(dbp, invp->id);
330 while (iip != NULL) {
331 invoice_total(invp, iip, &aval, &hval);
332 ttl += aval;
333 for (i = 0, xp = jitem; i < njitem; i++, xp++)
334 if (xp->acctno == iip->nomacct_id)
335 break;
336 if (i == njitem && ++njitem < 20) {
337 jitem[i].acctno = iip->nomacct_id;
338 jitem[i].amount = 0.0;
339 }
340 jitem[i].amount += hval;
341 jitem[0].amount += hval;
342 vat_lineitem(vat, hval, iip->vcode, jp->id, VATDATA_SALES);
343 iip = db_findinvoice_itemnext(dbp, iip);
344 }
345 /*
346 * Set the invoice total. This is done here because we can
347 * add multiple invoice line items to an invoice over the
348 * course of a business day so we don't need to keep
349 * recomputing the invoice total until we post the invoice.
350 * Also remember what the home value of the invoice is, for
351 * computing currency gains and losses later on.
352 *
353 * HACK: We need to add a journal line item for shipping
354 * charges. They get totalled in the first line item but
355 * don't appear anywhere else.
356 */
357 #ifdef VAT_CALCULATE
358 invp->vatttl = vat_total(vat);
359 jitem[1].amount = currconv(invp->vatttl, invp->currency_id);
360 jitem[0].amount += jitem[1].amount;
361 #endif
362 jitem[0].amount += currconv(invp->shipping, invp->currency_id);
363 invp->total = ttl + invp->vatttl + invp->shipping;
364 invp->htotal = jitem[0].amount;
365 /*
366 * Now allocate (and stuff) each journal item entry.
367 */
368 jip = db_journal_itemalloc();
369 jip->journal_id = jp->id;
370 db_journalfree(jp);
371 for (i = 0, xp = jitem; i < njitem; i++, xp++) {
372 if (verbose)
373 printf("%d - %.2f\n", xp->acctno, xp->amount);
374 jip->item_id = i + 1;
375 jip->nomacct_id = xp->acctno;
376 if (xp->amount < 0.0) {
377 jip->drcrf = (i == 0) ? 1 : 0;
378 jip->amount = -xp->amount;
379 } else {
380 jip->drcrf = (i == 0) ? 0 : 1;
381 jip->amount = xp->amount;
382 }
383 if (!noeffect && db_insertjournal_item(dbp, jip) < 0)
384 berror("postinvoice", "cannot create journal item");
385 }
386 db_journal_itemfree(jip);
387 /*
388 * Update the customer balance. If they are still in credit,
389 * then show the prepayment amount as going away.
390 */
391 if (cusp!=NULL && invp->total>=0.0 && cusp->balance>=invp->total) {
392 /*
393 * Allocate a journal entry for recognising the prepayment.
394 */
395 sprintf(descr, "Prepayment on invoice #%d", invp->id);
396 if (!noeffect) {
397 journal_entry(&invp->idate, NULL, descr, 0,
398 NA_PREPAYMENTS, NA_DEBTORSCA,
399 invp->htotal);
400 }
401 invp->state = INVOICE_STATE_PAID;
402 }
403 if (cusp != NULL) {
404 cusp->balance -= invp->total;
405 if (verbose)
406 printf("New customer balance: %.2f\n", cusp->balance);
407 }
408 /*
409 * If this is a credit note and it covers the full amount of the
410 * credited invoice, close them both out now.
411 */
412 if (invp->cninv_id != 0) {
413 if ((pip = db_findinvoicebyid(dbp, invp->cninv_id)) == NULL)
414 berror("postinvoice", "cannot find parent invoice");
415 if (invp->total <= -pip->total) {
416 pip->state = INVOICE_STATE_PAID;
417 invp->state = INVOICE_STATE_PAID;
418 }
419 }
420 /*
421 * Update the database with the invoice changes.
422 */
423 if (!noeffect) {
424 if (db_updateinvoicebyid(dbp, invp, invp->id) < 0)
425 berror("postinvoice", "cannot update invoice");
426 if (cusp!=NULL && db_updatecustomerbyid(dbp, cusp, cusp->id)<0)
427 berror("postinvoice", "cannot update customer");
428 }
429 /*
430 * Add a record to the sales ledger.
431 */
432 if ((slp = db_sledgeralloc()) == NULL)
433 berror("postinvoice", "cannot generate sales ledger entry");
434 slp->customer_id = invp->customer_id;
435 slp->sldate = invp->idate;
436 slp->folio = strdup(invid);
437 if (invp->cninv_id != 0)
438 slp->descr = strdup("Credit Note");
439 else
440 slp->descr = strdup(invp->descr);
441 slp->amount = -invp->total;
442 if (!noeffect && db_insertsledger(dbp, slp) < 0)
443 berror("postinvoice", "cannot create sales ledger entry");
444 db_sledgerfree(slp);
445 /*
446 * Send a copy of the invoice via the most appropriate method.
447 */
448 if (!noeffect && cusp != NULL) {
449 if (invp->cninv_id != 0)
450 invoicemail(comp, cusp, invp, "crnote");
451 else
452 invoicemail(comp, cusp, invp, "invoice");
453 }
454 }
455
456 /*
457 *
458 */
459 void
usage()460 usage()
461 {
462 fprintf(stderr, "Usage: postinvoice [-c <comp>][-nv]\n");
463 exit(2);
464 }
465