1 #ident "Kalopa: $Id: sendinvoice.c,v 1.20 2008/10/31 15:17:13 dtynan Exp $"
2
3 /*
4 * $Id: sendinvoice.c,v 1.20 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: sendinvoice.c,v $
39 * Revision 1.20 2008/10/31 15:17:13 dtynan
40 * Extensive changes for version 0.8.2.
41 *
42 * Revision 1.19 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.18 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.17 2008/10/13 20:59:38 dtynan
52 * Extensive changes (part 1) for change to database configuration.
53 *
54 * Revision 1.16 2008/07/18 13:05:53 dtynan
55 * Added the 'noeffect' flag and some additional verbose output.
56 *
57 * Revision 1.15 2008/07/18 11:07:38 dtynan
58 * Fixed issue with isholiday/skipholiday where it wouldn't actually detect
59 * bank holidays (only weekends). It now interrogates the database.
60 *
61 * Revision 1.14 2008/01/15 18:10:44 dtynan
62 * Changed to use new company name and copyright, also fixed a few old
63 * files with the wrong license.
64 *
65 * Revision 1.13 2006/10/01 10:32:33 dtynan
66 * Major changes for VAT.
67 *
68 * Revision 1.12 2006/07/31 10:17:52 dtynan
69 * Massive changeover to a more Ruby/Rails primary key naming convention.
70 *
71 * Revision 1.11 2005/08/12 18:57:50 dtynan
72 * Changed to use a slightly different format - now an invoice is
73 * sent with a statement the first time but only a statement is
74 * sent as a reminder.
75 *
76 * Revision 1.10 2005/03/22 09:41:25 dtynan
77 * Extensive upgrade to make sure the copyright block was
78 * consistent and GPL'ed.
79 *
80 * Revision 1.9 2005/02/17 16:23:22 dtynan
81 * Extensive changes to support new customer/contact database
82 * format and to add user IDs to journal entries.
83 *
84 * Revision 1.8 2004/12/09 20:23:36 dtynan
85 * Cleaned up include mechanisms.
86 *
87 * Revision 1.7 2004/09/07 12:00:24 dtynan
88 * Fixed issue where geninvoice was being called without specifying
89 * the company number (which defaults to 1).
90 *
91 * Revision 1.6 2004/08/04 13:15:25 dtynan
92 * Make sure the company number is handled properly.
93 *
94 * Revision 1.5 2004/07/16 19:23:33 dtynan
95 * Extensive changes to split the invoicing into posting and generating.
96 * Also fixed some bugs in the site billing code, added a default nominal
97 * account (5060) and fixed some bugs in the invoice emailing code. Added
98 * makepath() calls to generate the correct paths.
99 *
100 * Revision 1.4 2004/05/12 15:21:05 dtynan
101 * Massive check-in of various changes, mostly around DBOW and directory
102 * reorganization (moved to /u/beanie).
103 *
104 * Revision 1.3 2004/01/05 18:09:17 dtynan
105 * Lots of changes to help automate invoice generation and emailing.
106 *
107 * Revision 1.2 2004/01/05 11:53:44 dtynan
108 * Various changes around the filesystem layout and other minor details.
109 *
110 * Revision 1.1 2003/12/09 15:43:02 dtynan
111 * Placeholder.
112 */
113
114 #include <stdio.h>
115 #include <stdlib.h>
116 #include <unistd.h>
117 #include <string.h>
118 #include <time.h>
119
120 #include "beanie.h"
121 #include "hacks.h"
122
123 /*
124 * Linked-list of customers with late invoices.
125 */
126 struct cuslist {
127 struct cuslist *next;
128 struct db_customer *cusp;
129 int howlate;
130 };
131
132 /*
133 * List of reminder files.
134 */
135 char *reminder[9] = {
136 NULL, NULL, NULL,
137 "reminder1",
138 "reminder2",
139 "reminder3",
140 "reminder4",
141 "reminder5",
142 "reminder5"
143 };
144
145 int noeffect;
146 int verbose;
147 struct cuslist *head;
148 struct cuslist *tail;
149
150 void usage();
151
152 /*
153 * Send reminder emails (and .PDF statement attachments) to customers who
154 * are overdue.
155 */
156 int
main(int argc,char * argv[])157 main(int argc, char *argv[])
158 {
159 int i, terms;
160 char *cp, *cname;
161 dbow_conn *dbp;
162 struct cuslist *clp;
163 struct db_company *comp;
164 struct db_invoice *invp;
165
166 opterr = verbose = noeffect = 0;
167 cname = NULL;
168 head = tail = NULL;
169 while ((i = getopt(argc, argv, "c:nv")) != EOF) {
170 switch (i) {
171 case 'c':
172 cname = optarg;
173 break;
174
175 case 'n':
176 noeffect = 1;
177 break;
178
179 case 'v':
180 verbose = 1;
181 break;
182
183 default:
184 usage();
185 }
186 }
187 loadconfig(cname);
188 dbp = getdbase();
189 /*
190 * Find the company that we're processing...
191 */
192 if ((comp = db_findcompanyfirst(dbp)) == NULL)
193 berror("remind", "cannot find company");
194 invp = findoverdueinvoice(dbp);
195 while (invp != NULL) {
196 /*
197 * Hard-code the payment terms to thirty days (should
198 * really come from the customer record). Search the
199 * local list to see if we've found an invoice for this
200 * customer already.
201 */
202 terms = 30;
203 for (clp = head; clp != NULL; clp = clp->next)
204 if (clp->cusp->id == invp->customer_id)
205 break;
206 if (clp == NULL) {
207 /*
208 * First invoice for this guy.
209 */
210 clp = (struct cuslist *)malloc(sizeof(clp));
211 if (clp == NULL)
212 berror("remind", "cannot allocate memory");
213 clp->next = NULL;
214 clp->cusp = db_findcustomerbyid(dbp, invp->customer_id);
215 if (clp->cusp == NULL)
216 berror("remind", "cannot find customer");
217 clp->howlate = 0;
218 /*
219 * Add this guy.
220 */
221 if (head == NULL)
222 head = clp;
223 else
224 tail->next = clp;
225 tail = clp;
226 }
227 if (verbose) {
228 printf("ID: %d (state=%d), ", invp->id, invp->state);
229 printf("due date: %s", ctime((time_t *)&invp->ddate));
230 }
231 if (clp->howlate < invp->state)
232 clp->howlate = invp->state;
233 dunning(invp);
234 if (invp->state == INVOICE_STATE_LATE5 &&
235 (clp->cusp->cusflag & CUST_CREDITHOLD) == 0) {
236 /*
237 * Put this fecker on Credit Hold.
238 */
239 clp->cusp->cusflag |= CUST_CREDITHOLD;
240 if (!noeffect && db_updatecustomerbyid(dbp, clp->cusp,
241 clp->cusp->id) < 0) {
242 berror("remind", "cannot update customer");
243 }
244 if (verbose) {
245 printf("Customer ID %d on CREDIT HOLD.\n",
246 clp->cusp->id);
247 }
248 }
249 if (verbose) {
250 printf("New state: %d, due: %s", invp->state,
251 ctime((time_t *)&invp->ddate));
252 }
253 if (!noeffect && db_updateinvoicebyid(dbp, invp, invp->id) < 0)
254 berror("remind", "cannot update invoice");
255 invp = db_findinvoicenext(dbp, invp);
256 }
257 for (clp = head; clp != NULL; clp = clp->next) {
258 if ((cp = reminder[clp->howlate]) == NULL)
259 continue;
260 /*
261 * Send a reminder email with a copy of the account
262 * statement.
263 */
264 if (!noeffect)
265 invoicemail(comp, clp->cusp, NULL, cp);
266 }
267 exit(0);
268 }
269
270 /*
271 *
272 */
273 void
usage()274 usage()
275 {
276 fprintf(stderr, "Usage: remind [-c <comp>][-v]\n");
277 exit(2);
278 }
279