Link to: header | prefs
directory
Copyright Turtle Creek Software 1996-2006. All Rights Reserved.
Comments
CRecurringTransaction
This class manages recurring accounting transations for the Goldenseal accounting software,
small business management software, construction
project management software and
construction estimating software.
It's the class for a periodically recurring transactions. This class allows users
to automatically enter repeating account transactions such as sales, expenses
or bank transfers.
Superclass: DB_ArrayOwner
Constructor
/******************************************************************************
constructor v1.0, 06-05-97
*******************************************************************************/
CRecurringTransaction::CRecurringTransaction()
{
mFrequency = time_month;
mAction = action_ask;
mDate.SetToToday(); // TCS 10/24/00
mDate.AddDays(1);
mTemplateID = 0;
mTemplateClass = 0;
mAutoEnterAmounts = 0;
mAutoEnterBreakdowns = 0;
mAutoEnterJobCosts = 0;
filler = 0;
mEndSafetyTag = tag_endsafetytag; // TCS 9/8/02
}
Source Code
/******************************************************************************
CopyFrom
Duplicate another object v1.0, 06-05-97
*******************************************************************************/
void CRecurringTransaction::CopyFrom(DB_PersistentObject *source, const UInt8 copyFlags)
{
THE_SUPERCLASS::CopyFrom(source, copyFlags);
CRecurringTransaction *src = TCS_SAFE_CAST(source,
CRecurringTransaction);
TCS_FailNILMsg(src, TCS_GetErrString(errID_BadObject));
NeoVersion sourceVersion = src->GetVersion(); // TCS 2/5/01
TCS_BlockMove(&src->mTemplateID, &mTemplateID, cCopyFileLength);
}
/*********************************************************************************
GetFileLength rev TCS 2/5/01
return the file length used by this object
*********************************************************************************/
NeoSize CRecurringTransaction::GetFileLength(const CNeoFormat *aFormat) const
{
return THE_SUPERCLASS::GetFileLength(aFormat) + cFileLength;
}
/******************************************************************************
GetMemberValue
Return the value of a member v1.0, 06-05-97
*******************************************************************************/
Boolean CRecurringTransaction::GetMemberValue(const TagType aTag,
const TagType aType,
void *aValue) const
{
switch (aTag)
{
case tag_templateid : // treated as a number only
return ConvertMember(&mTemplateID, type_long, aValue, aType);
break;
case tag_templateclass :
return ConvertEnumMember(mTemplateClass, MENU_IOTransactionTypes, aValue, aType);
break;
case tag_frequency:
return ConvertEnumMember(mFrequency, MENU_RecurringPeriods, aValue, aType);
break;
case tag_schedaction:
return ConvertEnumMember(mAction, MENU_RecurringActions, aValue, aType);
break;
case tag_date:
return ConvertMember(&mDate, type_date, aValue, aType);
break;
case tag_time:
return ConvertMember(&mDate, type_time, aValue, aType);
break;
case tag_transaction: // transaction name TCS 8/13/01
{
CTextString outName = gDBFile->GetObjectName(mTemplateClass, mTemplateID);
return ConvertMember(&outName, type_cstring, aValue, aType);
}
break;
case tag_autoenteramounts:
return ConvertBitFieldMember(mAutoEnterAmounts, aValue, aType);
break;
case tag_autoenterbreakdowns:
return ConvertBitFieldMember(mAutoEnterBreakdowns, aValue, aType);
break;
case tag_autoenterjobcosts:
return ConvertBitFieldMember(mAutoEnterJobCosts, aValue, aType);
break;
case tag_multiplier: // TCS 8/11/01
return ConvertBitFieldMember(mEnterMultipleItems, aValue, aType);
break;
default:
return THE_SUPERCLASS::GetMemberValue(aTag, aType, aValue);
break;
}
}/******************************************************************************
SetMemberValue
Set the value of a member v1.0, 06-05-97
*******************************************************************************/
Boolean CRecurringTransaction::SetMemberValue(const TagType aTag, const TagType aType,
const void *aValue)
{
CDate currentDate;
switch (aTag)
{
case tag_templateid :
return ConvertMember(aValue, aType, &mTemplateID, type_objectid);
break;
case tag_templateclass :
return ConvertMember(aValue, aType, &mTemplateClass, type_objclass);
break;
case tag_frequency:
return ConvertMember(aValue, aType, &mFrequency, type_enum);
break;
case tag_schedaction:
return ConvertMember(aValue, aType, &mAction, type_enum);
break;
case tag_date: // we fetch just the date component TCS 8/15/01
if (ConvertMember(aValue, aType, ¤tDate, type_date))
{
mDate.SetDate(currentDate);
return true;
}
else
return false;
break;
case tag_time: // we fetch just the time component TCS 8/15/01
if (ConvertMember(aValue, aType, ¤tDate, type_time))
{
mDate.SetTime(currentDate);
return true;
}
else
return false;
break;
case tag_nextdate:
case tag_nexttime: // we fetch just the time component TCS 8/15/01
if (ConvertMember(aValue, aType, ¤tDate, type_time))
{
mDate.SetTime(currentDate);
return true;
}
else
return false;
break;
case tag_autoenteramounts:
mAutoEnterAmounts = ConvertDataToBitField(aValue, aType);
return true;
break;
case tag_autoenterbreakdowns:
mAutoEnterBreakdowns = ConvertDataToBitField(aValue, aType);
return true;
break;
case tag_autoenterjobcosts:
mAutoEnterJobCosts = ConvertDataToBitField(aValue, aType);
return true;
break;
case tag_multiplier: // TCS 8/11/01
mEnterMultipleItems = ConvertDataToBitField(aValue, aType);
return true;
break;
case tag_transaction: // calculated, no need to set TCS 8/13/01
return true;
break;
default:
return THE_SUPERCLASS::SetMemberValue(aTag, aType, aValue);
break;
}
}/******************************************************************************
ReadObject
Read the object's data from a stream v1.0.1, 06-05-97, 07-24-97
*******************************************************************************/
void CRecurringTransaction::ReadObject(CNeoStream *aStream, const TagType aTag)
{
TCS_FailNILMsg(aStream, TCS_GetErrString(errID_BadStream));
CNeoDebugImport checker(aStream, this, cCheckTooSmall); // TCS 2/24/00
// Read inherited members
THE_SUPERCLASS::ReadObject(aStream, aTag);
if (!IsIOValid()) // TCS 2/5/02
return;
ReadDateArrayFromStream(aStream, mEntries, cHasSafetyTag); // TCS 5/31/03
mTemplateID = aStream->ReadID(); // mfs_sa 20feb2k3
mDate.ReadFromStream(aStream);
mTemplateClass = aStream->ReadChar();
mFrequency = aStream->ReadChar();
mAction = aStream->ReadChar();
*((UInt8*)&mAction + sizeof(mAction)) = aStream->ReadBits(4); // --Bitfield
mExtraDate.ReadFromStream(aStream);
mEndSafetyTag = aStream->ReadEndSafetyTag(this);
if (!IsValidEndTag(mEndSafetyTag)) // TCS 9/8/02
ReportDamagedObject(GetDBClassID(), GetDBID());
}
/******************************************************************************
WriteObject
Write the object's data to a stream v1.0.1, 06-05-97, 07-24-97
*******************************************************************************/
void CRecurringTransaction::WriteObject(CNeoStream *aStream, const TagType aTag)
{
TCS_FailNILMsg(aStream, TCS_GetErrString(errID_BadStream));
// make sure we have valid data to write TCS 9/8/02
if (!IsValidEndTag(mEndSafetyTag))
{
ReportDamagedObject(GetDBClassID(), GetDBID());
mEndSafetyTag = tag_endsafetytag; // TCS 11/26/02
}
CNeoDebugExport checker(aStream, this, cCheckTooSmall);
// Write inherited members
THE_SUPERCLASS::WriteObject(aStream, aTag);
WriteDateArrayToStream(aStream, mEntries, cHasSafetyTag); // TCS 5/31/03
// Write frequency
NeoVersion version = GetVersion();
aStream->WriteID(mTemplateID); // mfs_sa 20feb2k3
mDate.WriteToStream(aStream);
aStream->WriteChar(mTemplateClass);
aStream->WriteChar(mFrequency);
aStream->WriteChar(mAction);
aStream->WriteChar(*((UInt8*)&mAction + sizeof(mAction))); // --Bitfield
mExtraDate.WriteToStream(aStream);
aStream->WriteEndSafetyTag(mEndSafetyTag, this);
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************
GetEntryFromRow
retrieve a date entry from the given table.
*********************************************************************************/
Boolean CRecurringTransaction::GetEntryFromRow(const CTCS_Table &aTable,
const TableIndexT row,
CDate *date)
{
*date = CDate(aTable.GetCellText(row, col_date));
return true;
}/*********************************************************************************
FillRowFromEntry
fill the table row from the given entry
*********************************************************************************/
Boolean CRecurringTransaction::FillRowFromEntry(CTCS_Table *aTable,
const TableIndexT row,
const CDate &date)
{
aTable->SetCellText(row, col_date, date.GetCString());
return true; // TCS 1/13/02
}
/*********************************************************************************
ExportColumnCString
return the value of a column element from our struct TCS 5/12/98
*********************************************************************************/
CTextString CRecurringTransaction::ExportColumnCString(const TableIndexT col,
const CDate &date)
{
switch (col)
{
case col_date:
return date.GetCString();
break;
default:
return cEmptyString;
break;
}
}
/*********************************************************************************
ImportColumnCString
fill a column element in our struct TCS 6/10/98
*********************************************************************************/
Boolean CRecurringTransaction::ImportColumnCString(const TableIndexT col,
const CTextString &inString, CDate *date)
{
switch (col)
{
case col_date:
*date = CDate(inString);
return true;
break;
default:
return false;
break;
}
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************
PostDeletion TCS 4/24/01
post deletion of a template. We'll reset the flag in the transaction
referenced by this template.
*********************************************************************************/
void CRecurringTransaction::PostDeletion(const Boolean postAudit)
{
THE_SUPERCLASS::PostDeletion(postAudit);
// right now we are lazy about this- we just turn off the flag
// without looking for other templates that use the same record.
// There aren't any really awful consequences from that.
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));
DB_PersistentObject *transaction = gDBFile->GetOneObject(mTemplateClass, mTemplateID);
if (transaction)
{
DB_ObjectWatcher watcher(transaction);
transaction->SetUsedAsTemplate(false);
}
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************
GetTableColInfo
return information about table columns
*********************************************************************************/
Boolean CRecurringTransaction::CRecurring_Desc::GetTableColInfo(const TagType tag,
const TableIndexT col,
STableColInfo *colInfo)
{
if (tag == tag_recurtable)
{
switch (col)
{
case col_date:
colInfo->colType = coltype_caption;
colInfo->fieldType = fieldtype_date;
colInfo->colData = 0;
TCS_BufferFromText(colInfo->colName,
TCS_GetStockString(stockID_Date), cTableColNameLength);
return true;
default:
return false;
break;
}
}
else
return false;
}
/*********************************************************************************
FillDateTable TCS 3/4/99
fill an array of dates into a table
*********************************************************************************/
void CRecurringTransaction::FillDateTable(const UInt8 period, const CDate &inDate,
CMemberTable *table)
{
CDate date = CDate::FirstOfThisYear();
date = inDate.CalcNextPeriod(period, date);
CDate lastOfYear = CDate::LastOfThisYear();
SInt32 periodCount = CDate::GetTimesPerYear(period);
switch (period)
{
case time_custom:
// no need to do anything
break;
case time_irregular:
case time_minute: // TCS rev 8/13/01
case time_hour:
table->MakeEmptyTable();
break;
case time_day:
case time_weekdays:
case time_week: // periods that may need an extra row
case time_twoweeks:
case time_fourweeks:
table->SetNumRows(periodCount);
for (TableIndexT row = 1; row <= periodCount; row++)
{
table->SetCellText(row,1, date.GetCString());
date.IncrementPeriod(period);
}
if (date <= lastOfYear) // oops, we need an extra row
{
table->SetNumRows(periodCount + 1);
table->SetCellText(periodCount + 1, 1, date.GetCString());
}
break;
case time_halfmonth: // periods with a fixed row count
case time_month:
case time_monthfromend:
case time_twomonths:
case time_quarter:
case time_halfyear:
table->SetNumRows(periodCount);
for (TableIndexT row = 1; row <= periodCount; row++)
{
table->SetCellText(row, 1, date.GetCString());
date.IncrementPeriod(period);
}
break;
case time_year:
table->SetNumRows(1);
table->SetCellText(1, 1, inDate.GetCString());
break;
default:
TCS_DebugAlert("Oops, bad case in CRecurringTransaction::FillDateTable!");
break;
}
}
/*********************************************************************************
UpdateDateArray TCS 3/5/99
update our array of billing dates for the current year
*********************************************************************************/
void CRecurringTransaction::UpdateDateArray()
{
// this changes file length, so the object should
// not be in the database rev TCS 2/6/04
TCS_ASSERTMsg(!IsInDatabase(), TCS_GetErrString(errID_BadLengthChange));
SInt32 entryCount = mEntries.GetCount();
CDate date;
SInt32 thisYear = CDate::ThisYear();
// check the first entry.
// if this year, no need to do anything
if (entryCount)
{
mEntries.FetchItemAt(1, date);
if (date.GetYear() == thisYear)
return;
}
// sanity check and prepare to update
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));
SInt32 periodCount = CDate::GetTimesPerYear(mFrequency);
CDate lastOfYear = CDate::LastOfThisYear();
SInt32 index;
// figure the first billing date
CDate nextDate = CDate::FirstOfThisYear();
nextDate = mDate.CalcNextPeriod(mFrequency, nextDate);
// how we proceed depends on the period
switch (mFrequency)
{
case time_custom:
// for custom period we just update the year in existing dates
for (index = 1; index <= entryCount; index++)
{
mEntries.FetchItemAt(index, date);
date.SetYear(thisYear);
mEntries.AssignItemAt(index, date);
}
MakeDirty();
break;
case time_irregular:
// no need to do anything
break;
case time_day:
case time_week: // periods with variable item count
case time_twoweeks:
case time_fourweeks:
mEntries.RemoveAllItems();
for (index = 1; index <= periodCount; index++)
{
date = nextDate;
mEntries.Append(date);
nextDate.IncrementPeriod(mFrequency);
}
if (date <= lastOfYear) // oops, we need an extra row
{
date = nextDate;
mEntries.Append(date);
}
break;
case time_halfmonth: // periods with a fixed item count
case time_month:
case time_monthfromend:
case time_twomonths:
case time_quarter:
case time_halfyear:
case time_year:
mEntries.RemoveAllItems();
for (TableIndexT row = 1; row <= periodCount; row++)
{
date = nextDate;
mEntries.Append(date);
nextDate.IncrementPeriod(mFrequency);
}
break;
default:
TCS_DebugAlert("Oops, bad case in CRecurringTransaction::UpdateDateArray!");
break;
}
}
/*********************************************************************************
UpdateNextRecurDate TCS 8/15/01
we've just made a recurring transaction, so update the stored date to the next
appropriate date, and return whether to keep making more items.
currentMatchDate is either the date of the transaction just made(if making
multiple items) or the current date/time(if making just one item)
*********************************************************************************/
Boolean CRecurringTransaction::UpdateNextRecurDate(CDate ¤tMatchDate)
{
Boolean keepGoing = false;
if (mFrequency == time_custom)
{
// we look thru our array and grab the first date that is after the
// match date
TDateArrayIterator iterator(mEntries);
CDate date;
SInt32 thisYear = CDate::ThisYear();
while (iterator.Next(date))
{
if (date.GetYear() != thisYear)
{
// fill in data for the current year
date.SetYear(thisYear);
DB_ObjectTempRemover remover (this); // rev TCS 8/26/03
if (remover.WasRemoved())
{
mEntries.AssignItemAt(iterator.GetCurrentIndex(), date);
}
}
if (date.IsAfter(currentMatchDate))
{
mDate = date;
}
}
// if we got this far, grab the first date next year
mEntries.FetchFirstItem(date);
mDate = date;
mDate.SetYear(thisYear + 1);
}
else
{
currentMatchDate = mDate.CalcNextPeriod(mFrequency, currentMatchDate, cMustBeAfter, cKeepTimes);
mDate = currentMatchDate;
}
// mark for a save
MakeDirty();
// make sure the time array is updated
DB_ListManager::ChangeInMenus(this);
return keepGoing;
}
#if CAN_USE_MARK
#pragma mark -
#endif
/******************************************************************************
MakeRecurringTransaction (static)
This involves duplicating the object referenced by the template
%%% Note: we need to check the auto-enter flags
*******************************************************************************/
/*void CRecurringTransaction::MakeRecurringTransaction(const DBid templateID)
{
// retrieve the template from which to create the new object
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));
CRecurringTransaction *tmpl =
TCS_SAFE_CAST(gDBFile->GetOneObject(id_RecurringTransaction, templateID),
CRecurringTransaction);
if (tmpl)
{
DB_ObjectWatcher tmplWatcher(tmpl);
tmpl->MakeRecurringTransaction();
}
}*/
/******************************************************************************
MakeRecurringTransactions TCS split 2/5/01
create one or more recurring transaction and reset our timing.
Return whether any transactions were made
*******************************************************************************/
Boolean CRecurringTransaction::MakeRecurringTransactions(const CDate &matchDate, const CDate now)
{
if (!matchDate.IsBefore(now, cCompareTimes))
return false;
if (mAction == action_todolist) // we don't do anything with this yet
return false;
// first ask the user whether to make an object now rev TCS 8/17/01
SInt32 newRecordCount;
CDate currentMatchDate;
if (mEnterMultipleItems) // TCS 3/3/03 rev 5/5/03
{
newRecordCount = CDate::GetPeriodCount(mFrequency, matchDate, now) + 1;
currentMatchDate = now;
}
else
{
newRecordCount = 1;
currentMatchDate = matchDate;
}
if (mAction == action_ask)
{
CTextString askString;
if (newRecordCount > 1)
{
askString = TCS_GetMsgString(msgID_AskRecurringItems);
askString.ReplaceStdTokens(newRecordCount,
DB_ClassDescriptor::GetClassName(mTemplateClass), GetName());
}
else
{
askString = TCS_GetMsgString(msgID_AskRecurringItem);
askString.ReplaceStdTokens(DB_ClassDescriptor::GetClassName(mTemplateClass), GetName());
}
SInt32 result = TCS_YesNoCancelDialog(askString, true, TCS_GetStockString(stockID_Later),
TCS_GetStockString(stockID_Create)); // TCS 5/5/03
if (result == msg_DontSave)
{
return false; // cancel but try later
}
else if (result == msg_Cancel)
{
UpdateNextRecurDate(currentMatchDate); // skip this one entirely
return false;
}
}
// fetch the source object
DB_PersistentObject *sourceObj = gDBFile->GetOneObject(mTemplateClass,
mTemplateID);
if (sourceObj)
{
// we found a source object so duplicate it
DB_ObjectWatcher sourceWatcher(sourceObj);
DB_PersistentObject *createdObject = nil;
// if the window is already open, better close it first
Boolean windowIsOpen = DB_WindowManager::EditorWindowIsOpen(mTemplateClass);
Boolean keepGoing = true;
if (windowIsOpen)
{
if (!DB_WindowManager::TryCloseEditorWindow(mTemplateClass))
return false;
}
while (keepGoing)
{
if (mAutoEnterBreakdowns) // rev TCS 8/10/01 rev TCS 4/2/03
createdObject = gDBFile->CreateObjectFromAnother(sourceObj);
else
createdObject = gDBFile->CreateObjectFromAnother(sourceObj, flag_shallowcopy);
// Check if the duplication was successful
if (createdObject != nil)
{
createdObject->SetCreationType(record_fromtemplate); // TCS 2/14/01
DB_ObjectWatcher creationWatcher(createdObject);
// Clear unwanted information according to the flags
if (!mAutoEnterAmounts)
{
CMoney zeroMoney = 0;
createdObject->SetMemberValue(tag_amount, type_money, &zeroMoney);
createdObject->SetMemberValue(tag_taxamount, type_money, &zeroMoney);
createdObject->SetMemberValue(tag_grossprice, type_money, &zeroMoney);
}
if (!mAutoEnterJobCosts)
{
DBid zeroID = 0;
createdObject->SetMemberValue(tag_job, type_objectid, &zeroID);
createdObject->SetMemberValue(tag_category, type_objectid, &zeroID);
createdObject->SetMemberValue(tag_subcategory, type_objectid, &zeroID);
createdObject->SetMemberValue(tag_location, type_objectid, &zeroID);
}
createdObject->SetDate(currentMatchDate); // TCS 8/17/01
// allow the object to do any tidying
createdObject->FinishTemplateCreate();
// add the new transaction to the database TCS 3/3/03
gDBFile->AddNewTransaction(createdObject);
// post the object creation TCS 3/3/03
createdObject->PostNewRecord(record_fromprocess);
createdObject->MakeDirty();
}
// update the next recurring date.
keepGoing = UpdateNextRecurDate(currentMatchDate);
if (!mEnterMultipleItems) // TCS 3/3/03
keepGoing = false;
}
// we've created one or more transactions. Time to save changes
gDBFile->SaveAllChanges();
// and then show the window
if (windowIsOpen)
DB_Editor::ShowEditorWindow(mTemplateClass, nil);
return true;
}
else
{
ReportMissingObject(mTemplateClass, mTemplateID);
return false;
}
}
/*********************************************************************************
FillDataReport TCS 9/6/02
fill in a diagnostic table that shows data field values.
*********************************************************************************/
void CRecurringTransaction::FillDataReport(CTCS_Table *table, CNeoStream *stream) const
{
TCS_FailNILMsg(table, TCS_GetErrString(errID_BadTable));
TCS_FailNILMsg(stream, TCS_GetErrString(errID_BadStream));
THE_SUPERCLASS::FillDataReport(table, stream);
FillFieldArrayRow(table, stream, "mEntries", mEntries);
FillFieldObjectIDRow(table, stream, tag_templateid, mTemplateID, mTemplateClass);
FillFieldTagRow(table, stream, tag_date, cDateSize, mDate.GetCString());
FillFieldEnumRow(table, stream, tag_templateclass, mTemplateClass, MENU_IOTransactionTypes);
FillFieldEnumRow(table, stream, tag_frequency, mFrequency, MENU_RecurringPeriods);
FillFieldEnumRow(table, stream, tag_schedaction, mAction, MENU_RecurringActions);
FillFieldBitRow(table, stream, "mAutoEnterAmounts", mAutoEnterAmounts, true);
FillFieldBitRow(table, stream, "mAutoEnterBreakdowns", mAutoEnterBreakdowns);
FillFieldBitRow(table, stream, "mAutoEnterJobCosts", mAutoEnterJobCosts);
FillFieldBitRow(table, stream, "mEnterMultipleItems", mEnterMultipleItems);
FillFieldStockRow(table, stream, stockID_Padding, -4, SInt32(filler));
FillFieldTableRow(table, stream, "mExtraDate", cDateSize, mDate.GetCString());
FillEndSafetyTag(table, stream, mEndSafetyTag);
}
|