/* $Id: clsAuditAccountingApp.cpp,v 1.2 1999/02/21 02:20:57 josh Exp $ */ // // File: clsAuditAccountingApp.cc // // Class: clsAuditAccountingApp // // Author: Michael Wilson (michael@ebay.com) // // Function: // // // Modifications: // #include "eBayDebug.h" #include "eBayTypes.h" #include "clsAuditAccountingApp.h" #include "clsEnvironment.h" #include "clsDatabase.h" #include "clsMarketPlaces.h" #include "clsMarketPlace.h" #include "clsUsers.h" #include "clsUser.h" #include "clsAccount.h" #include "clsAccountDetail.h" #include #include #include #define FLUSH_TO_END \ do \ { \ if (!fgets(buf, sizeof(buf), pFile)) \ { \ done = true; \ break; \ } \ recCount++; \ if (strncmp(buf, BeginRec, BeginRecLen) == 0) \ break; \ } while (1==1); static float RoundToCents(float it) { float itCents; int itInt; itCents = it * 100; itCents = itCents + .5; itInt = (int)itCents; it = (float)itInt / 100; return it; } char * split(register char *s, register const char *delim) { register char *spanp; register int c, sc; char *tok; static char *last; if (s == NULL && (s = last) == NULL) return (NULL); /* * Skip (span) leading delimiters (s += strspn(s, delim), sort of). */ /*cont:*/ c = *s++; if (c == 0) { /* no non-delimiter characters */ last = NULL; return (NULL); } tok = s - 1; /* * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). * Note that delim must have one NUL; we stop if we see that, too. */ for (;;) { /* c = *s++; ** pmo - move to bottom of loop */ spanp = (char *)delim; do { if ((sc = *spanp++) == c) { if (c == 0) s = NULL; else s[-1] = 0; last = s; return (tok); } } while (sc != 0); c = *s++; /* pmo - move to bottom of loop */ } /* NOTREACHED */ } // // Sort routine // static bool sort_account_detail_time(clsAccountDetail *pA, clsAccountDetail *pB) { if (pA->mTime < pB->mTime) return true; return false; } clsAuditAccountingApp::clsAuditAccountingApp(unsigned char *pRequest) { mpDatabase = (clsDatabase *)0; mpMarketPlaces = (clsMarketPlaces *)0; mpMarketPlace = (clsMarketPlace *)0; mpUsers = (clsUsers *)0; return; } clsAuditAccountingApp::~clsAuditAccountingApp() { return; } // Important Defines #define TIME_SIZE 32 #define MEMO_SIZE 80 #define ITEM_SIZE 12 #define ARRAY_SIZE 1 const char *BeginRec = "BEGIN\t"; const int BeginRecLen = 6; const char *EndRec = "END\t"; const int EndRecLen = 4; void clsAuditAccountingApp::Run(int BatchId) { // File stuff char *pFileName = "accounts.txt"; FILE *pFile; char buf[1024]; char buf2[1024]; char *s; // Counters and stuff bool done; bool badRecord; int recCount; int rejectCount = 0; int acceptCount = 0; int userCount = 0; // Things about the current user char *pUserName; int userNameLen; int theAWId; char *pItemId; char *pComma; // The real current user and things about them. clsUser *pUser; clsAccount *pAccount; AccountDetailVector vAccountDetail; AccountDetailVector::iterator vADi; int userRecCount; int userRecFoundCount; int userRecMissingCount; int userRejectCount; int userAcceptCount; char *pEndUserName; // These fields represent the individual accounting // detail records. We don't use array insert because // recovery is SUCH a pain; int theUserId; time_t theTime; const struct tm *pTheTimeAsTM; char cTheTime[32]; AccountDetailTypeEnum theType; double theAmount; int memoLen; char theMemo[MEMO_SIZE + 1]; char theItem[ITEM_SIZE + 1]; // Date conversion struct tm *pTheTime; // Arrays of things int id[ARRAY_SIZE]; char date[ARRAY_SIZE][32]; int action[ARRAY_SIZE]; float amount[ARRAY_SIZE]; int transactionId[ARRAY_SIZE]; char memo[ARRAY_SIZE][MEMO_SIZE + 1]; int migrationBatchId[ARRAY_SIZE]; int iItem; unsigned int itemTransactionId[ARRAY_SIZE]; char item[ARRAY_SIZE][ITEM_SIZE +1]; if (!mpMarketPlaces) mpMarketPlaces = gApp->GetMarketPlaces(); if (!mpMarketPlace) mpMarketPlace = mpMarketPlaces->GetCurrentMarketPlace(); if (!mpUsers) mpUsers = mpMarketPlace->GetUsers(); if (!mpDatabase) mpDatabase = gApp->GetDatabase(); // File shenanigins pFile = fopen(pFileName, "r"); if (!pFile) { fprintf(stderr, "Error %s opening %s\n", strerror(errno), pFileName); return; } // Ok, here we gooooo done = false; recCount = 0; // Prime the pump if (!fgets(buf, sizeof(buf), pFile)) { done = true; } recCount++; // Ok, here we go do { // Here, we expect a user record to be in the buffer, // which tells us which user(s) the feedback is for. // Figure out who the user is... if (memcmp(buf, BeginRec, sizeof(BeginRec)) != 0) { fprintf(stderr, "Begin record missing in <%s>\n", buf); exit(1); } pUserName = buf + BeginRecLen; if ((s = split(pUserName, "\t")) && *s) { pUserName = s; userNameLen = strlen(pUserName); } else { fprintf(stderr, "No User Name found in <%s>\n", buf); FLUSH_TO_END; continue; } if (strlen(pUserName) > EBAY_MAX_USERID_SIZE) { fprintf(stderr, "User Name <%s> too long\n", pUserName); FLUSH_TO_END; continue; } // Let's find the user pUser = mpUsers->GetUser(pUserName); if (!pUser) { printf("User <%s> NOT found. Flushing\n", pUserName); FLUSH_TO_END; continue; } theUserId = pUser->GetId(); printf("Begin processing User <%s>/<%s>\n", pUserName, pUser->GetUserId()); // Ok, we're ready. Let's get the user's account information pAccount = pUser->GetAccount(); // And the detail... pAccount->GetAccountDetail(&vAccountDetail); // Sort it, and position the iterator sort(vAccountDetail.begin(), vAccountDetail.end(), sort_account_detail_time); vADi = vAccountDetail.begin(); // Now, let's fetch, decode, and handle those records userRecCount = 0; userRecFoundCount = 0; userRecMissingCount = 0; userRejectCount = 0; userAcceptCount = 0; iItem = 0; do { // Fetch the next record. If we're at EOF, // then just break out. badRecord = false; if (!fgets(buf, sizeof(buf), pFile)) { done = true; break; } recCount++; strcpy(buf2, buf); // See if it's a signal record if (strncmp(buf, EndRec, EndRecLen) == 0) { pEndUserName = buf + EndRecLen; if ((s = split(pEndUserName, "\t")) && *s) { pEndUserName = s; } else { fprintf(stderr, "No Ending User Name found in <%s>\n", buf); continue; } if (strcmp(pEndUserName, pUser->GetUserId()) != 0) { fprintf(stderr, "Begin/End User name mismatch! Begin <%s>, End <%s>\n", pUser->GetUserId(), pEndUserName); } // Print out interesting things printf( "User <%s>, AW Account %d: %d/%d/%d/%d/%d\n", pUser->GetUserId(), theAWId, userRecCount, userRecFoundCount, userRecMissingCount, userAcceptCount, userRejectCount ); // Bump the user count userCount++; // Look for the next Begin if (!fgets(buf, sizeof(buf), pFile)) { done = true; break; } // If we find a begin without an end, then // something's up. It's ok, we can recover // though if (strncmp(buf, BeginRec, BeginRecLen) == 0) { break; } else { fprintf(stderr, "End without Begin!\n"); exit(1); } } // Make sure we didn't miss something if (strncmp(buf, BeginRec, BeginRecLen) == 0) { fprintf(stderr, "Warning! Begin found without End!\n"); exit(1); } // We know this isn't a delimiting record, so let's // count it. userRecCount++; // Now the time if ((s = split(buf, "\t")) && *s) { theTime = atol(s); } else { fprintf(stderr, "Can\'t find time! in <%s>\n", buf2); badRecord = true; } // Now, the "type". AuctionWeb only had 3 types -- // "New", "C" and "D". We'll start with that, and // hope we can do better. if (!badRecord && ((s = split(NULL, "\t")) && *s)) { // If it's a "new" record, just ignore it if (strcmp(s, "NEW") == 0) { continue; } else if (strcmp(s, "C") == 0) { theType = AccountDetailAWCredit; } else if (strcmp(s, "D") == 0) { theType = AccountDetailAWDebit; } else if (strcmp(s, "M") == 0) { theType = AccountDetailAWMemo; } else { fprintf(stderr, "Bad Type in <%s>!\n", buf2); badRecord = true; } } else { fprintf(stderr, "Can\'t find type in <%s>!!!\n", buf2); badRecord = true; } // We assume the input records are sorted by time, // and we know the account records are. for (; vADi != vAccountDetail.end(); vADi++) { // If we got it, we're gold! if ((*vADi)->mTime == theTime) break; if ((*vADi)->mTime > theTime) break; } // Ok, if the times are equal, we found the // item, Just count it and be done! if ((*vADi)->mTime == theTime) { userRecFoundCount++; continue; } // Otherwise, we must not have found it, and // have to make a new one. userRecMissingCount++; pTheTimeAsTM = localtime(&theTime); strftime(cTheTime, sizeof(cTheTime), "%m/%d/%y %H:%M:%S", pTheTimeAsTM); printf("%s %s missing %s\n", pUser->GetUserId(), cTheTime, buf2); // Now, the amount if (theType != AccountDetailAWMemo) { if (!badRecord) { if ((s = split(NULL,"\t")) && *s) { theAmount = atof(s); } else { fprintf(stderr, "Can\'t find amount in <%s>!!!\n", buf2); badRecord = true; } } } else theAmount = 0; if (theAmount > 99999999.99 || theAmount < -99999999.99) { fprintf(stderr, "Amount %f too large in <%s>. Skipped!\n", theAmount, buf); badRecord = true; } // Finally...zee comment! if (!badRecord) { if ((s = split(NULL, "\t")) && *s) { memoLen = strlen(s); if (*(s + memoLen - 1) == '\n') *(s + memoLen - 1) = '\0'; if (strlen(s) > MEMO_SIZE) { fprintf(stderr, "Memo <%s> too big in <%s>!\n", s, buf2); badRecord = true; } else if (strlen(s) > 0) { strcpy(theMemo, s); } else { fprintf(stderr, "Zero Len Comment!\n"); strcpy(s, " "); } } else { fprintf(stderr, "No Memo!\n"); strcpy(theMemo, " "); } } // All done! If we don't have a "bad" record, // then we should advance our pointers if (badRecord) { rejectCount++; userRejectCount++; printf("Record Skipped!!!\n"); continue; } else { acceptCount++; userAcceptCount++; } // Let's see if we can figure out anything else about // this record if (strncmp(theMemo, "Insertion fee item ", 19) == 0) { theType = AccountDetailFeeInsertion; pItemId = theMemo + 19; if (strlen(pItemId) <= ITEM_SIZE) strcpy(theItem, pItemId); else theItem[0] = '\0'; // No need for a memo. theMemo[0] = '\0'; } else if (strncmp(theMemo, "Item ", 5) == 0) { pItemId = theMemo + 5; pComma = strchr(pItemId, ','); if (pComma != NULL) { if (((pComma - pItemId) <= ITEM_SIZE) && strncmp(pComma, ", final value $", 15) == 0) { // SOMETIMEs the amount is -ve, which means this // is _really_ a listing fee credit! if (theAmount < 0) { theType = AccountDetailCreditNoSale; theAmount = -theAmount; } else { theType = AccountDetailFeeFinalValue; } memset(theItem, 0x00, sizeof(theItem)); memcpy(theItem, pItemId, pComma - pItemId); // Memo is only final value now sprintf(theMemo, "F%s", pComma + 3); } } } else if (strncmp(theMemo, "courtesy credit ", 16) == 0) { theType = AccountDetailCreditCourtesy; } else if (strncmp(theMemo, "credit card payment -- thank you!", 33) == 0) { theType = AccountDetailPaymentCC; theMemo[0] = '\0'; } else if (strncmp(theMemo, "payment -- thank you!", 20) == 0) { theType = AccountDetailPaymentCheck; theMemo[0] = '\0'; } else if (strncmp(theMemo, "finance charge", 14) == 0) { theType = AccountDetailFinanceCharge; theMemo[0] = '\0'; } // For some reason, some old records come in with -ve signs in the // amounts, some don't. Let's make sure it's consistent : All debits // are negative. if (theType == AccountDetailFeeInsertion || theType == AccountDetailFeeBold || theType == AccountDetailFeeFeatured || theType == AccountDetailFeeCategoryFeatured || theType == AccountDetailFeeFinalValue || theType == AccountDetailFinanceCharge || theType == AccountDetailAWDebit ) { if (theAmount > 0) theAmount = -theAmount; } else { if (theAmount < 0) theAmount = -theAmount; } // One more check if (theAmount > 99999999.99 || theAmount < -99999999.99) { fprintf(stderr, "Amount %f too large in <%s>. Skipped!\n", theAmount, buf); badRecord = true; continue; } // Ok, let's start filling in the array id[0] = pUser->GetId(); pTheTime = localtime(&theTime); sprintf(date[0], "%02d-%02d-%02d %02d:%02d:%02d", pTheTime->tm_year + 1900, pTheTime->tm_mon + 1, pTheTime->tm_mday, pTheTime->tm_hour, pTheTime->tm_min, pTheTime->tm_sec); action[0] = theType; amount[0] = theAmount; transactionId[0] = 0; strcpy(memo[0], theMemo); migrationBatchId[0] = BatchId; itemTransactionId[0] = gApp->GetDatabase()->XAddRawAccountDetail(1, &id[0], (char *)&date[0], &action[0], &amount[0], (char *)&memo[0], &transactionId[0], &migrationBatchId[0]); // Now, construct an account detail record if (theType == AccountDetailFeeInsertion || theType == AccountDetailFeeFinalValue) { strcpy(item[iItem], theItem); if (item[iItem][0] == '\0') strcpy(item[0], "Unknown"); gApp->GetDatabase()->XAddAccountAWItemXref(iItem, &itemTransactionId[0], (char *)&item[0]); } } while (1==1); delete pUser; pUser = NULL; // Dump them out // Now we'll be looping around here... } while (!done); printf("Done! %d Users, %d Records, %d accepted, %d rejected\n", userCount, recCount, acceptCount, rejectCount); // Clean up fclose(pFile); return; } static clsAuditAccountingApp *pTestApp = NULL; int main(int argc, char* argv[]) { int batchId; batchId = atoi(argv[1]); if (batchId == 0) { printf("Bad Batch id <%s>\n", argv[1]); } else { printf("**** Begin Batch %d ****\n", batchId); } if (!pTestApp) { pTestApp = new clsAuditAccountingApp(0); } pTestApp->InitShell(); pTestApp->Run(batchId); return 0; }