This is Invoice.m in view mode; [Download] [Up]
/* * For legal stuff see the file COPYRIGHT */ #import <stdio.h> #import <time.h> #import <bsd/libc.h> #import <sys/file.h> #import <sys/types.h> #import <sys/stat.h> #import <objc/NXBundle.h> #import <appkit/appkit.h> #import "Controller.h" #import "Invoice.h" #import "Session.h" #import "Expense.h" #import "Preferences.h" #import "createPath.h" #define EXPENSE "Expense" #define DETAIL "Detail" #define INVOICE "Invoice" #define TEMPLATE_TYPE "rtf" #define MONTH_SWITCH_DAY 20 @interface Invoice(PRIVATE) - (void)copyTemplateIfNeeded:(const char *)template; - (FILE *)openTemplate:(const char *)template; - (FILE *)openOutput:(const char *)path; - (void)filterExpenses:(FILE *)in to:(FILE *)out; - (void)filterDetail:(FILE *)in to:(FILE *)out; - (void)filter:(FILE *)in to:(FILE *)out; - (const char *)translate:(const char *)token; - (BOOL)filterBuffer:(char *)ptr to:(FILE *)out match:(const char *)target; - (const char *)templatePath:(const char *)template; - (const char *)createFilename:(const char *)type; - (int)nextInvoiceNumber; - generateInvoice; - generateDetail; - generateExpenses; - (int)likelyMonth:(struct tm *)tm; - (const char *)number; /* invoice number */ - (const char *)today; /* today's date */ - (const char *)thisMonth; /* the likely current invoicing month */ - (const char *)contact; /* person at client */ - (const char *)client; /* client name */ - (const char *)street; /* street address */ - (const char *)city; - (const char *)state; - (const char *)zip; - (const char *)enddate; /* end of invoice period */ - (const char *)startdate; /* start of invoice period */ - (const char *)billings; /* total hourly billings */ - (const char *)expenses; /* total expenses */ - (const char *)total; /* billings + expenses */ - (const char *)hours; /* total hours worked for invoice period */ - (const char *)rate; /* hourly rate */ - (const char *)date; /* date of a session or expense */ - (const char *)length; /* length of a session (in hours) */ - (const char *)amount; /* expense amount */ - (const char *)description; /* description of session task or expense */ @end @implementation Invoice - initTemplateDir:(const char *)dir { [super init]; templateDir = NXCopyStringBuffer(dir); if ( createPath( templateDir, DIRMODE ) != PathCreationOk ) { NXRunAlertPanel( [NXApp name], "Cannot create path `%s'", "Harumph.", NULL, NULL, templateDir ); return nil; } /* * If the templates are not found in the templateDir, * copy them there from the app wrapper. */ [self copyTemplateIfNeeded:EXPENSE]; [self copyTemplateIfNeeded:INVOICE]; [self copyTemplateIfNeeded:DETAIL]; return self; } - free { if ( templateDir ) { free(templateDir); templateDir = NULL; } return [super free]; } - (BOOL)editTemplate:(const char *)template { const char *path = [self templatePath:template]; return [[Application workspace] openFile:path]; } - (void)generate:(List *)clientList { int i, count = [clientList count]; int sessionCount, expenseCount; for ( i = 0; i < count; i++ ) { info = [clientList objectAt:i]; sessionCount = [info sessionCount]; expenseCount = [info expenseCount]; /* Don't generate an invoice if no work was done */ if ( sessionCount == 0 && expenseCount == 0 ) continue; /* get (and increment) the invoice number */ number = [self nextInvoiceNumber]; [self generateInvoice]; if ( sessionCount > 0 ) [self generateDetail]; if ( expenseCount > 0 ) [self generateExpenses]; } } @end @implementation Invoice(PRIVATE) static char *Tokens[] = { "number", "today", "contact", "client", "street", "city", "state", "zip", "startdate", "enddate", "billings", "expenses", "total", "hours", "rate", "date", "length", "amount", "description", 0, }; #define START '[' #define END ']' #define MAX_LINE 500 #define MAX_TOKEN 20 /* * Determine the value of the token, or return NULL if * not a known token. */ - (const char *)translate:(const char *)token { char **ptr; for ( ptr = Tokens; *ptr; ptr++ ) { if ( strcmp( *ptr, token ) == 0 ) { SEL sel = sel_getUid(token); if ( [self respondsTo:sel] ) return (const char *)[self perform:sel]; else return NULL; /* found string, but don't respond */ } } return NULL; } /* * Translate the line pointed to by 'ptr', writing the result to the * stream 'out'. Return 1 if the given token occurred in this line. */ - (BOOL)filterBuffer:(char *)ptr to:(FILE *)output match:(const char *)target { char c, *tok, token[MAX_TOKEN + 1]; const char *value; int count; BOOL matched = NO; for ( ; *ptr ; ptr++ ) { if ( *ptr == START ) { for (count = 0, tok = token; count < MAX_TOKEN && (c = *++ptr) && NXIsLower(c); count++ ) *tok++ = c; *tok = '\0'; if ( c == END ) { /* got a token */ if ( (value = [self translate:token]) == NULL ) /* it was unknown */ fprintf(output, "%c%s%c", START, token, END); /* pass it thru */ else { fprintf(output, "%s", value); /* use its value */ if ( target && strcmp( token, target ) == 0 ) matched = YES; } } else if ( !NXIsLower(c) ) /* bad character */ fprintf(output, "%c%s%c", START, token, c); /* pass it thru */ else /* it was too long */ fprintf(output, "%c%s", START, token); /* pass it thru */ } else fputc( *ptr, output ); } return matched; } /* * Straight translation - used to generate invoices. */ - (void)filter:(FILE *)input to:(FILE *)output { char buf[MAX_LINE + 1]; while ( fgets( buf, MAX_LINE, input ) ) [self filterBuffer:buf to:output match:NULL]; } /* * Expense generator - loops over Expense records when it * finds the {description} token in a line. */ - (void)filterExpenses:(FILE *)input to:(FILE *)output { char buf[MAX_LINE + 1]; while ( fgets( buf, MAX_LINE, input ) ) { /* * When we find {description}, treat the current buf as * the template for all expenses. */ if ( [self filterBuffer:buf to:output match:"description"] ) { int i, count = [info expenseCount]; for ( i = 0; i < count; i++ ) { expense = (Expense *)[info expenseAt:i]; [self filterBuffer:buf to:output match:NULL]; } expense = nil; } } } /* * Detail generator - loops over Session records when it * finds the {description} token in a line. */ - (void)filterDetail:(FILE *)input to:(FILE *)output { char buf[MAX_LINE + 1]; while ( fgets( buf, MAX_LINE, input ) ) { /* * When we find {description}, treat the current buf as * the template for all sessions. */ if ( [self filterBuffer:buf to:output match:"description"] ) { int i, count = [info sessionCount]; for ( i = 0; i < count; i++ ) { session = (Session *)[info sessionAt:i]; [self filterBuffer:buf to:output match:NULL]; } session = nil; } } } /* * Intuit the most likely month that this invoice is for. If it is before * the 20th day of the month, assume it is for the prior month, otherwise * assume it is for the current month. */ - (int)likelyMonth:(struct tm *)tm { int month = tm->tm_mon; if ( tm->tm_mday < MONTH_SWITCH_DAY ) month = ( month == 0 ? 11 : month - 1 ); /* special for January */ return month; } /* * Return the (likely) current invoicing month. */ - (const char *)thisMonth { time_t now; struct tm *tm; static char *months[] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; time(&now); tm = localtime(&now); return months[[self likelyMonth:tm]]; } /*********************************************************************** * Token Translation Methods ***********************************************************************/ - (const char *)number { static char buf[20]; sprintf( buf, "%d", number ); return buf; } /* * Return today's date as a string */ - (const char *)today { time_t now; struct tm *tm; static char *months[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; static char buf[80]; time(&now); tm = localtime(&now); sprintf(buf, "%s %d, 19%d", months[tm->tm_mon], tm->tm_mday, tm->tm_year ); return buf; } - (const char *)contact { return info ? [info contactName] : ""; } - (const char *)client { return info ? [info clientName] : ""; } - (const char *)street { return info ? [info street] : ""; } - (const char *)city { return info ? [info city] : ""; } - (const char *)state { return info ? [info addrState] : ""; } - (const char *)zip { return info ? [info zipCode] : ""; } - (const char *)enddate { int month; time_t now; struct tm *tm; static char buf[80]; static int lengths[] = { /* number of days per month */ 31, 28, 31, 30, /* gloss over leap years... */ 31, 30, 31, 31, 30, 31, 30, 31 }; time(&now); tm = localtime(&now); month = [self likelyMonth:tm]; sprintf( buf, "%02d/%02d/%02d", month + 1, lengths[month], tm->tm_year ); return buf; } /* * Intuit the most likely start date for this invoice. If it is before * the 20th day of the month, assume it is for the prior month, otherwise * assume it is for the current month. */ - (const char *)startdate { int month; time_t now; struct tm *tm; static char buf[80]; time(&now); tm = localtime(&now); month = [self likelyMonth:tm]; sprintf( buf, "%02d/01/%02d", month + 1, tm->tm_year ); return buf; } - (const char *)billings { static char buf[20]; if ( info ) { sprintf( buf, "%1.2f", [info totalBillable] ); return buf; } return ""; } - (const char *)expenses { static char buf[20]; if ( info ) { sprintf( buf, "%1.2f", [info totalExpenses] ); return buf; } return ""; } - (const char *)total { static char buf[20]; if ( info ) { sprintf( buf, "%1.2f", [info totalBillable] + [info totalExpenses]); return buf; } return ""; } - (const char *)hours { static char buf[20]; if ( info ) { sprintf( buf, "%1.2f", [info totalHours] ); return buf; } return ""; } - (const char *)rate { static char buf[20]; if ( info ) { sprintf( buf, "%1.2f", [info hourlyRate] ); return buf; } return ""; } - (const char *)date { if ( session ) return [session startDateString]; if ( expense ) return [expense dateString]; return ""; } - (const char *)amount { static char buf[20]; if ( !expense ) return ""; sprintf( buf, "%1.2f", [expense amount] ); return buf; } - (const char *)length { static char buf[20]; if ( !session ) return ""; sprintf( buf, "%1.2f", [session hours] ); return buf; } - (const char *)description { if ( expense ) return [expense description]; if ( session ) return [session description]; return ""; } - (const char *)templatePath:(const char *)template { static char path[FILENAME_MAX + 1]; sprintf( path, "%s/%sTemplate.%s", templateDir, template, TEMPLATE_TYPE ); return path; } /* * If the named template file is not found in the templateDir, * copy the one from the app wrapper over there. */ - (void)copyTemplateIfNeeded:(const char *)template { FILE *fp; const char *templatePath; char templateSrc[FILENAME_MAX + 1]; char name[FILENAME_MAX + 1]; templatePath = [self templatePath:template]; /* assume it's ok if we can open it for reading */ if ( (fp = fopen( templatePath, "r" )) != NULL ) { fclose(fp); return; /* the file is already there */ } sprintf( name, "%sTemplate", template ); if ( ! [[NXBundle mainBundle] getPath:templateSrc forResource:name ofType:TEMPLATE_TYPE] ) { NXRunAlertPanel( [NXApp name], "Can't find %s.%s in app wrapper", "What Bogosity!", NULL, NULL, name, TEMPLATE_TYPE ); return; } copyFile( templateSrc, templatePath ); } /* * Open a template file for reading. */ - (FILE *)openTemplate:(const char *)template { FILE *fp; const char *path = [self templatePath:template]; if ( ! (fp = fopen( path, "r" )) ) NXRunAlertPanel( [NXApp name], "Can't open `%s' for reading", "Dang!", NULL, NULL, path ); return fp; } /* * Take out blanks and punctuation characters to create a reasonable * file name. NB: Returns a pointer to a local static buffer */ - (const char *)createFilename:(const char *)type { char c, *ptr; const char *dir = GET_DEFAULT( INVOICE_DIR ); const char *src = [info shortName]; static char buf[MAXPATHLEN]; sprintf( buf, "%s/", dir ) ; ptr = buf + strlen(buf); while ( (c = *src++) != '\0' ) if ( NXIsAlpha(c) ) /* don't copy punctuation */ *ptr++ = ( NXIsUpper(c) ? NXToLower(c) : c ); sprintf( ptr, ".%d.%s.%s.rtf", number, [self thisMonth], type ); return buf; } /* * Open a file of the given type for writing */ - (FILE *)openOutput:(const char *)type { FILE *fp; const char *path = [self createFilename:type]; if ( ! (fp = fopen( path, "w" )) ) NXRunAlertPanel( [NXApp name], "Can't open `%s' for writing", "Drat.", NULL, NULL, path ); return fp; } /* * Read the current "next invoice number" from the defaults database * increment and return the number, after storing the value back. */ - (int)nextInvoiceNumber { char num[10]; int value; value = atoi(GET_DEFAULT( INVOICE_NUM )); sprintf( num, "%d", ++value ); PUT_DEFAULT( INVOICE_NUM, num ); return value; /* return the incremented value */ } /* * Generate an expense report data file for any customer that had * expenses for this period. */ - generateExpenses { FILE *in, *out; if ( !( in = [self openTemplate:EXPENSE]) || !( out = [self openOutput:"exp"] )) return nil; [self filterExpenses:in to:out]; fclose( in ); fclose( out ); return self; } - generateDetail { FILE *in, *out; if ( !( in = [self openTemplate:DETAIL]) || !( out = [self openOutput:"dtl"] )) return nil; [self filterDetail:in to:out]; fclose( in ); fclose( out ); return self; } - generateInvoice { FILE *in, *out; if ( !( in = [self openTemplate:INVOICE]) || !( out = [self openOutput:"inv"] )) return nil; [self filter:in to:out]; fclose( in ); fclose( out ); return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.