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