Accounting Software
Small Business Software Estimating Software
Inventory SoftwareInventory Tracking SoftwareInventory Control SoftwareInventory Management SoftwareConstruction Management SoftwareProject Management SoftwareBusiness Management Software

Labor Hours (Source Code 2)

Link to: header | source code | transactions directory

Copyright Turtle Creek Software 1996-2006. All Rights Reserved.

This class manages employee hours for the Goldenseal accounting software,
time tracking software, construction accounting software and
time billing software.

Source Code

/*********************************************************************************

HandlePayrollRecordMade TCS 5/8/00 renamed & rev 4/25/01

this labor log has been included in a payroll record (or cancelled). We
update payables, and also adjust billing and job cost rates if they are based
on the actual payroll cost

*********************************************************************************/
void CLaborLog::HandlePayrollRecordMade(const Boolean removeItem, const CMoney &wageTotal,
const CMoney &costTotal, const CMoney &hoursTotal,
CPayrollRecord *source, const CDate periodStart)
{
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));
TCS_FailNILMsg(source, TCS_GetErrString(errID_BadObject));

// adjust payables TCS 4/25/01
CUtilityAccount::AddToActiveArray(account_UnpaidLaborHrs, this, !removeItem);

// adjust the employee account's unpaid array TCS 8/2/01
DB_Account *employee = TCS_SAFE_CAST(gDBFile->GetOneObject(id_EmployeeAccount, mMainAccount),
DB_Account);
if (employee)
{
DB_ObjectWatcher watcher(employee);
employee->AddToUnpaidCostArray(GetDBID(), !removeItem);
}


if (!removeItem) // we don't adjust job cost or billing rates if cancelling
{ // a payroll record. We might as well still use the prior
// values as a 'best guess' of actual payroll cost
UInt8 saveType = PrepareViewerDisplay(); // TCS 8/22/00

// update the job cost amount and the billing amount
CMoney oldAmount = GetJobCostAmount();
CMoney catTaxAmount = source->GetCategoryTaxAmount(0, mTimeUsed, mAmount);

FeedbackPayrollAmount(mJobCostRate, wageTotal, costTotal, hoursTotal, catTaxAmount, mJobCostAmount);
FeedbackPayrollAmount(mTMBillingRate, wageTotal, costTotal, hoursTotal, catTaxAmount, mBillingAmount);

// update job costs
AdjustJobCosts(GetJobCostAmount() - oldAmount, cDontAdjustArrays);

// if we're visible, update the display rev TCS 10/23/00
UpdateViewerDisplay(saveType);
}

// we also pass it along to the wage schedule, so it can mark
// this pay period as paid TCS 9/14/01
if (mWageSchedule)
{
CWageSchedule *schedule =
TCS_SAFE_CAST(gDBFile->GetOneObject(id_WageSchedule, mWageSchedule),
CWageSchedule);

if (schedule)
{
DB_ObjectWatcher watcher(schedule);

if (!removeItem)
{
schedule->HandlePayrollRecordMade(periodStart);
}
}
}
}
/*********************************************************************************

SetHasPayRecord TCS 6/5/00

set whether this log item has a pay record

*********************************************************************************/
void CLaborLog::SetHasPayRecord(const Boolean inValue)
{
// set the flag bit
SetIsMarked(inValue);

// set the status. We only need to change it if unbilled and unpaid
if (!HasBeenTandMBilled() && !HasBeenPaid())
{
UInt8 saveType = PrepareViewerDisplay(); // TCS 8/22/00

if (inValue)
mStatus = status_PayRecord;
else
mStatus = status_Entered;

UpdateViewerDisplay(saveType); // TCS rev 10/23/00
}
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************

CalcWageRateAmount

figure the current wage rate amount

*********************************************************************************/
CMoney CLaborLog::CalcWageRateAmount(const DBid wageRateID, const UInt8 timeUnit,
const DBid accountID) const
{
CMoney amount(0,0),
accountRate;

// sanity check
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));

if (wageRateID)
{
// find the wage rate
CWageSchedule *rate = TCS_SAFE_CAST(gDBFile->GetOneObject(id_WageSchedule,
wageRateID), CWageSchedule);
if (rate)
{
DB_ObjectWatcher watcher(rate);

// we have a wage rate, so it can calculate
if (rate->HasVariableRate())
{
// it's a variable rate, so the account provides the value
accountRate = FetchAccountRate(tag_wageamount, id_EmployeeAccount, accountID);
amount = rate->ComputeWageRate(timeUnit, accountRate);
}
else // it's a simple rate, so the rate can compute things
amount = rate->ComputeWageRate(timeUnit);
}
}
// return the computed amount
return amount;
}
/*********************************************************************************

GetWageType TCS 10/11/00

get the type of payroll this item is (hourly, salary, owner draw, etc).

*********************************************************************************/
UInt8 CLaborLog::GetWageType() const
{
// sanity check
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));

if (mWageSchedule)
{
// find the wage rate
CWageSchedule *rate = TCS_SAFE_CAST(gDBFile->GetOneObject(id_WageSchedule,
mWageSchedule), CWageSchedule);
if (rate)
{
DB_ObjectWatcher watcher(rate);

// fetch the wage type from the wage schedule
return rate->GetWageType();
}
}
// return the computed amount
return 0;
}
/*********************************************************************************

GetDefaultExpenseAccount TCS rev 12/5/00

get the expense account to use if none has been assigned. That depends on
whether this is an owner draw

*********************************************************************************/
DBid CLaborLog::GetDefaultExpenseAccount() const
{
UInt8 wageType = GetWageType();
if (wageType == wage_ownerhourly || wageType == wage_ownersalary || wageType == wage_ownerdraw)
return account_OwnerPayDraws;
else if (wageType == wage_hourlyequity || wageType == wage_salaryequity)
return account_SweatEquity;
else
return account_LaborExpenses;
}
/*********************************************************************************

GetPayPeriod TCS 7/28/99

return the pay period interval for these hours

*********************************************************************************/
UInt8 CLaborLog::GetPayPeriod()
{
// sanity check
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));

if (mWageSchedule)
{
// find the wage rate
CWageSchedule *rate = TCS_SAFE_CAST(gDBFile->GetOneObject(id_WageSchedule,
mWageSchedule), CWageSchedule);
if (rate)
{
DB_ObjectWatcher watcher(rate);
return rate->GetPayPeriod();
}
}
// problems, so return blank value
return 0;
}
/*********************************************************************************

GetStarterStatus TCS 6/21/00

return the starting status. That depends on whether we're in a pay record

*********************************************************************************/
UInt8 CLaborLog::GetStarterStatus() const
{
if (IsMarked())
return status_PayRecord;
else
return status_Entered;
}
/*********************************************************************************

GetOvertimeRate

figure the current overtime rate

*********************************************************************************/
CMoney CLaborLog::GetOvertimeRate(const UInt8 timeUnit) const
{
CMoney amount(0,0),
accountRate;

// sanity check
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));

if (mWageSchedule)
{
// find the wage rate
CWageSchedule *rate = TCS_SAFE_CAST(gDBFile->GetOneObject(id_WageSchedule,
mWageSchedule), CWageSchedule);
if (rate)
{
DB_ObjectWatcher watcher(rate);

// we have a wage rate, so it can calculate
if (rate->HasVariableRate())
{
// it's a variable rate, so the account provides the value
accountRate = FetchAccountRate(tag_wageamount, id_EmployeeAccount, mMainAccount);
amount = rate->GetOvertimeRate(mOvertimeType, timeUnit, accountRate);
}
else // it's a simple rate, so the rate can compute things
amount = rate->GetOvertimeRate(mOvertimeType, timeUnit);
}
}
// return the computed amount
return amount;
}
/*********************************************************************************

GetOvertimeAmount

get the overtime wage amount

*********************************************************************************/
CMoney CLaborLog::GetOvertimeAmount() const
{
CMoney amount(0,0),
accountRate;

// sanity check
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));

if (mWageSchedule)
{
// find the wage rate
CWageSchedule *rate = TCS_SAFE_CAST(gDBFile->GetOneObject(id_WageSchedule,
mWageSchedule), CWageSchedule);
if (rate)
{
DB_ObjectWatcher watcher(rate);

// we have a wage rate, so it can calculate
if (rate->HasVariableRate())
{ // it's a variable rate, so the account provides the value
accountRate = FetchAccountRate(tag_wageamount, id_EmployeeAccount, mMainAccount);
amount = rate->GetWagesWithOvertime(mTimeUsed, mTimeUnit, accountRate, mOvertimeType, mDaysWorked);
}
else // it's a simple rate, so the rate can compute things
amount = rate->GetWagesWithOvertime(mTimeUsed, mTimeUnit, 0, mOvertimeType, mDaysWorked);
}
}
// return the computed amount
return amount;
}/*********************************************************************************

GetOvertimeHours

calculate the number of overtime hours, based on the input cumulative hours

*********************************************************************************/
CMoney CLaborLog::GetOvertimeHours(CMoney *cumHours, const CMoney &weeklyOTCutoff,
const CMoney &dailyOTCutoff) const
{
TCS_FailNILMsg(cumHours, TCS_GetErrString(errID_BadPointer));

CMoney netHours = CDate::GetTimeInWorkHours(mTimeUsed, mTimeUnit); // TCS 4/1/00

return CalculateOvertimeHours(cumHours, netHours, weeklyOTCutoff, dailyOTCutoff,
mDaysWorked, mOvertimeType); // bugfix TCS 12/27/00
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************

HasUnpaidStatus TCS 6/6/00

return whether this log is still unpaid

*********************************************************************************/
Boolean CLaborLog::HasUnpaidStatus() const
{
return (mStatus == status_Entered || mStatus == status_TandMBilled
|| mStatus == status_PayRecord);
}
/*********************************************************************************

IsIrregularDraw TCS 12/5/00

return whether this log is for work that gets an irregular draw payment

*********************************************************************************/
Boolean CLaborLog::IsIrregularDraw() const
{
if (mWageSchedule)
{
CWageSchedule *schedule =
TCS_SAFE_CAST(gDBFile->GetOneObject(id_WageSchedule, mWageSchedule),
CWageSchedule);

if (schedule)
{
DB_ObjectWatcher watcher(schedule);
return schedule->IsIrregularDraw();
}
}

// if we get this far, it's not an owner draw
return false;
}
/*********************************************************************************

IsSalary TCS 5/19/00

return whether this labor log is based on a salary wage rate

*********************************************************************************/
Boolean CLaborLog::IsSalary() const
{
CMoney amount(0,0),
accountRate;

// sanity check
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));

if (mWageSchedule)
{
// find the wage schedule
CWageSchedule *rate = TCS_SAFE_CAST(gDBFile->GetOneObject(id_WageSchedule,
mWageSchedule), CWageSchedule);
if (rate)
{
DB_ObjectWatcher watcher(rate);
return rate->IsSalary();
}
}
// if we got this far, there's no salary
return false;
}
/*********************************************************************************

HasUnusedStatus TCS 9/26/00

return whether this log hasn't been used in a payroll record yet

*********************************************************************************/
Boolean CLaborLog::HasUnusedStatus() const
{
return (mStatus == status_Entered || mStatus == status_TandMBilled);
}
/*********************************************************************************

WageIsDue rev TCS 9/23/00

return whether the wage for these hours is due as of the given dates. The
end date must be valid, but the start date doesn't need to be.

*********************************************************************************/
Boolean CLaborLog::WageIsDue(const CDate startDate, const CDate endDate)
{
TCS_ASSERTMsg(endDate.IsValidDate(), TCS_GetErrString(errID_BadDate));

if (startDate.IsValidDate())
return ((mDate >= startDate) && (mDate <= endDate));
else
return (mDate <= endDate);
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************

CalculateWageAmount

calculate the wage amount

*********************************************************************************/
CMoney CLaborLog::CalculateWageAmount(const CMoney &billedUnits, const DBid accountID,
const UInt8 timeUnit, const DBid wageRateID,
const UInt8 overtimeType, const CMoney &daysWorked) const
{
CMoney amount(0,0),
accountRate;

// sanity check
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));

if (wageRateID)
{
// find the wage rate
CWageSchedule *rate = TCS_SAFE_CAST(gDBFile->GetOneObject(id_WageSchedule,
wageRateID), CWageSchedule);
if (rate)
{
DB_ObjectWatcher watcher(rate);

// we have a wage rate, so it can calculate
if (rate->HasVariableRate())
{ // it's a variable rate, so the account provides the value
accountRate = FetchAccountRate(tag_wageamount, id_EmployeeAccount, accountID);
amount = rate->ComputeWageAmount(billedUnits, timeUnit, accountRate,
overtimeType, daysWorked);
}
else
{ // it's a simple rate, so pass zero so the stock rate is used
amount = rate->ComputeWageAmount(billedUnits, timeUnit, 0,
overtimeType, daysWorked);
}
}
}
// return the computed amount
return amount;
}
/*********************************************************************************

CalculateOvertimeHours (static) rev TCS 4/1/00

calculate the number of overtime hours for the given parameters. This is a static
method that can be called by viewers as well as labor log objects.

*********************************************************************************/
CMoney CLaborLog::CalculateOvertimeHours(CMoney *cumHours, const CMoney &inHours,
const CMoney &weeklyOTCutoff, const CMoney &dailyOTCutoff,
const CMoney &inDays, const UInt8 overtimeType) const
{
TCS_FailNILMsg(cumHours, TCS_GetErrString(errID_BadPointer));

switch (overtimeType)
{
case condition_SaturdayRate:
case condition_SundayRate:
case condition_Overtime:
return inHours; // it's all overtime and we don't increment cum hrs
break;

case condition_NoOvertime:
return 0; // not overtime and we don't increment cum hrs
break;

default: // maybe overtime and we do increment cum hrs TCS 12/10/98
{
CMoney dailyExcess, weeklyExcess;

if (dailyOTCutoff.IsPositive() && inDays.IsPositive()) // rev TCS 10/10/00
dailyExcess = inHours - (dailyOTCutoff * inDays);
else
dailyExcess = 0;

if (weeklyOTCutoff.IsPositive())
weeklyExcess = (inHours + *cumHours) - weeklyOTCutoff;
else
weeklyExcess = 0;

// overtime is the greater of the daily and weekly excess amounts
CMoney OTHours = TCS_MAX(dailyExcess, weeklyExcess);

if (OTHours.IsPositive())
{ // we only add straight-time hours to cum total
*cumHours += (inHours - OTHours);
return OTHours;
}
else
{ // add all hours to the cum total
*cumHours += inHours;
return 0;
}
}
break;
}
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************

AddToDeductionBases TCS 11/21/98 minor revs TCS 5/5/00

add the wage amount from this set of hours to all tax items it uses.

This is not a posting- it's just a temporary calculation to fill in an
employeeInfo struct for use in the Payroll command or a Payroll Record's
breakdowns.

*********************************************************************************/
void CLaborLog::AddToDeductionBases(SEmployeeInfo &employeeInfo,
const SLaborHoursInfo &hoursInfo)
{
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));

// first let's get the tax package used for these hours
// we fetch it from the wage rate
CWageSchedule *wageSchedule =
TCS_SAFE_CAST(gDBFile->GetOneObject(id_WageSchedule, mWageSchedule),
CWageSchedule);
if (wageSchedule)
{
DB_ObjectWatcher watcher(wageSchedule);

SInt32 daysInPeriod = wageSchedule->GetDaysInPeriod(mDate); // TCS 5/5/00

DBid packageID;
CTaxPackage *taxPackage = nil;
CBenefitPackage *benefitPackage = nil;
CVacationPackage *vacationPackage = nil;

// fetch a tax package from the wage rate
packageID = wageSchedule->GetTaxPackage(mMainAccount);

taxPackage = TCS_SAFE_CAST(gDBFile->GetOneObject(id_TaxPackage, packageID),
CTaxPackage);

// if we found a tax package, it can adjust amounts as needed
if (taxPackage)
{
DB_ObjectWatcher watcher(taxPackage);

// easy to assign regular deductions
taxPackage->AddToDeductionBases(employeeInfo, hoursInfo, mTimeUsed, daysInPeriod);

// for category taxes, we need to do more work, since category
// comes from the breakdowns, if there is any rev TCS 7/11/02
if (mBreakdownArray.GetCount() > 0)
{
TObjectIDArrayIterator iterator(mBreakdownArray);
DBid breakdownID;
CCostBreakdownEntry *breakdown;
DBid catID, subcatID;
CMoney hours;
SLaborHoursInfo tempInfo;
tempInfo.overtimeHrs = 0;
tempInfo.daysWorked = hoursInfo.daysWorked;

// loop thru breakdowns and figure cat taxes on each
while (iterator.Next(breakdownID))
{
breakdown =
TCS_SAFE_CAST(gDBFile->GetOneObject(mBreakdownClassID, breakdownID),
CCostBreakdownEntry);

if (breakdown)
{
DB_ObjectWatcher watcher (breakdown);

catID = breakdown->GetCategory();
subcatID = breakdown->GetSubcategory();
tempInfo.baseHours = breakdown->GetQuantity();
tempInfo.totalDueAmount = breakdown->GetAmount();

taxPackage->AddToCatTaxBases(employeeInfo, tempInfo, hours, daysInPeriod,
GetCategorySystem(), catID, subcatID);
}
}
}
else
{ // no breakdown, so get cat taxes on the whole amount
taxPackage->AddToCatTaxBases(employeeInfo, hoursInfo, mTimeUsed, daysInPeriod,
GetCategorySystem(), mCategory, mSubcat);
}
}
// next let's get the benefit package used for these hours
// we also fetch it from the wage rate // TCS added 2/25/99
packageID = wageSchedule->GetBenefitPackage(mMainAccount);
benefitPackage = TCS_SAFE_CAST(gDBFile->GetOneObject(id_BenefitPackage, packageID),
CBenefitPackage);

// if we found a benefit package, it can adjust amounts as needed
if (benefitPackage)
{
DB_ObjectWatcher watcher(benefitPackage);
benefitPackage->AddToDeductionBases(employeeInfo, hoursInfo, mTimeUsed, daysInPeriod);
}

// finally let's get the vacation package used for these hours
// we also fetch it from the wage rate // TCS added 2/25/99 TCS bugfix 12/4/00
packageID = wageSchedule->GetVacationPackage(mMainAccount);
vacationPackage = TCS_SAFE_CAST(gDBFile->GetOneObject(id_VacationPackage, packageID),
CVacationPackage);

// if we found a vacation package, it can adjust amounts as needed
if (vacationPackage)
{
DB_ObjectWatcher watcher(vacationPackage);
vacationPackage->AddToDeductionBases(employeeInfo, hoursInfo, mTimeUsed, daysInPeriod,
mOvertimeType);
}
}
}
/*********************************************************************************

FeedbackPayrollAmount TCS 5/19/00 rev 5/3/01 rev 7/5/01

update the billing or job cost amount, using actual payroll info passed
in from a payroll report

*********************************************************************************/
void CLaborLog::FeedbackPayrollAmount(const DBid rateID, const CMoney &wageTotal,
const CMoney &costTotal, const CMoney &hoursTotal,
const CMoney &catTaxTotal, CMoney &outAmount)
{
CMoney burdenPercent;
CMoney burdenAmount = 0,
baseAmount = mAmount,
totalAmount = 0;

// first fetch the billing rate
CLaborBillingRate *rate = TCS_SAFE_CAST(gDBFile->GetOneObject(id_LaborBillingRate, rateID),
CLaborBillingRate);

if (rate)
{
DB_ObjectWatcher watcher(rate);
UInt8 calcMethod = rate->GetCalcMethod();

TCS_Real hoursRatio, usedReal, hoursReal; // TCS 5/25/00

if (hoursTotal.IsPositive())
{
usedReal = mTimeUsed.GetReal();
hoursReal = hoursTotal.GetReal();
hoursRatio = usedReal / hoursReal; // BD changed 6/1/00
}
else
hoursRatio = 0;

if (calcMethod == rate_Pay || calcMethod == rate_PayPlusPercent)
{
if (IsSalary())
{
// if it's a salary, we need to adjust to an apportioned part
// of the total wage amount
baseAmount = wageTotal * hoursRatio; // BD 6/1/00

}
}
else if (calcMethod == rate_Net || calcMethod == rate_NetPlusPercent)
{
if (IsSalary())
{
// if a salary, we use an apportioned part of the total wage
baseAmount = wageTotal * hoursRatio; // BD 6/1/00
burdenAmount = costTotal * hoursRatio;
}
else if (wageTotal.IsPositive())
{
burdenPercent = (costTotal + catTaxTotal) / wageTotal; // rev TCS 5/3/01
burdenAmount = mAmount * burdenPercent;
}
else
{ // if no wage but costs, we'll at least use the cost
baseAmount = costTotal;
}

}
else // for other types, no updating is needed. TCS 7/5/01
return;

outAmount = CalculateJobCostAmount(id_LaborHours, mTimeUsed, mMainAccount,
mTimeUnit, rateID, baseAmount, burdenAmount);
}
}
/*********************************************************************************

FillDataReport TCS 9/6/02

fill in a diagnostic table that shows data field values.

*********************************************************************************/
void CLaborLog::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);

NeoVersion version = GetVersion();

FillFieldObjectIDRow(table, stream, tag_wagerate, mWageSchedule, id_WageSchedule);
FillFieldObjectIDRow(table, stream, tag_billingrate, mTMBillingRate, id_LaborBillingRate);
FillFieldObjectIDRow(table, stream, tag_jobcostrate, mJobCostRate, id_LaborBillingRate);

FillFieldTagRow(table, stream, tag_billingamount, cMoneySize, mBillingAmount.GetCurrencyString());
FillFieldTagRow(table, stream, tag_daysworked, cMoneySize, mDaysWorked.GetNumberString());
FillFieldTagRow(table, stream, tag_wagerateamount, cMoneySize, mWageRateAmount.GetCurrencyString());

FillFieldEnumRow(table, stream, tag_overtimetype, mOvertimeType, MENU_LaborLogOvertime);
FillFieldEnumRow(table, stream, tag_status, mStatus, MENU_LaborLogStatus);

if (version > 3) // TCS 4/20/04
{
FillFieldTagRow(table, stream, tag_starttime, cDateSize, mStartDate.GetTimeString());
FillFieldTagRow(table, stream, tag_endtime, cDateSize, mEndDate.GetTimeString());
}

FillFieldTagRow(table, stream, tag_burdenamount, cMoneySize, mBurdenAmount.GetCurrencyString());

FillFieldObjectIDRow(table, stream, tag_payrecord, mPayrollRecord, id_PayrollRecord);
FillFieldTableRow(table, stream, "mWageBreakdownID", cLongSize, mWageBreakdownID);

FillEndSafetyTag(table, stream, mEndSafetyTag);
}