Accounting Software
Small Business Software Estimating Software
Time Tracking SoftwareTime Management SoftwareTime Billing SoftwareContact Management SoftwareCustomer Management SoftwareProject Management SoftwareBusiness Management Software

Cost Breakdown Tables (Source Code)

Link to: header | source 1 | source 2 | tables directory

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

Source Code

This class manages cost breakdown tables for the Goldenseal estimating software,
small business management software, construction project management software and
construction estimating software.

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

ExportSchedule TCS 4/20/04

export data for a schedule.

*********************************************************************************/
void CCostBreakdownTable::ExportSchedule(CTextOutputStream &stream)
{
CTextString outString, taskName;
CDate startDate, endDate;
CMoney crewSize;

TableIndexT itemCol = GetMemberCol(tag_costitem),
crewCol = GetMemberCol(tag_crewsize),
startCol = GetMemberCol(tag_date),
startTimeCol = GetMemberCol(tag_time),
endCol = GetMemberCol(tag_enddate),
endTimeCol = GetMemberCol(tag_endtime);

for ( TableIndexT row = 1 ; row <= GetNumRows() ; row++ )
{
taskName = GetCellText(row, itemCol);
stream.WriteString(taskName);

crewSize = GetCellMoneyValue(row, crewCol);
stream.WriteString(crewSize.GetNumberString());

startDate = CDate(GetCellText(row, startCol));
stream.WriteString(startDate.GetCString());

if (startTimeCol)
stream.WriteString(GetCellText(row, startTimeCol));

endDate = CDate(GetCellText(row, endCol));
stream.WriteString(endDate.GetCString());

if (startTimeCol)
stream.WriteString(GetCellText(row, startTimeCol));

stream.WriteEndOfLine();
}
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************

RecalcBreakdownRow

recalculate the given row. We have special handling for labor/equpment hours,
and also for estimate (which always need to check the unit costs, since it may
include a flat price which results in a variable unit price.

*********************************************************************************/
CMoney CCostBreakdownTable::RecalcBreakdownRow(const TableIndexT row, const TagType changedCol)
{
// some classes need no calculating TCS 8/23/99
if (mOwnerClass == id_LaborHours || mOwnerClass == id_EquipmentHours)
return RecalcHoursBreakdownRow(row, changedCol);
else
return RecalcCostBreakdownRow(row, changedCol);
}
/*********************************************************************************

RecalcCostBreakdownRow

recalculate the given row in a regular cost table

*********************************************************************************/
CMoney CCostBreakdownTable::RecalcCostBreakdownRow(TableIndexT row, const TagType changedCol,
const Boolean fromUpdateButton)
{
CMoney unitCost = 0,
quantity = 0,
discount = 0,
totalCost = 0; // TCS 5/16/02
CTextString taxableString;
Boolean isTaxable = false;


CMoney hoursPerUnit = GetMemberColMoney(row, tag_unithours); // TCS 4/26/02
CMoney laborHours = GetMemberColMoney(row, tag_laborhours);
CMoney crewSize = GetMemberColMoney(row, tag_crewsize);

TableIndexT col;
SInt32 costArea = GetCostArea(row); // TCS rev 4/29/02

// check a few column locations
TableIndexT amountCol = GetMemberCol(tag_amount),
unitCostCol = GetMemberCol(tag_unitcost),
quantityCol = GetMemberCol(tag_quantity);

// if no amount column, we might as well give up now
if (!amountCol)
return 0;

// ditto for missing quantity column
if (quantityCol)
quantity = GetCellText(row, quantityCol);
else
{
SetMemberColMoney(row, tag_amount, totalCost);
return 0;
}

Boolean hasCostItem = HasCostItem(costArea); // TCS 8/7/01

// if doing 'update prices', we only update some items TCS 5/10/01
if (fromUpdateButton && !hasCostItem)
return 0;

// a few cost areas don't cost anything TCS 3/31/03
if (costArea == costtype_delay || costArea == costtype_reminder ||
costArea == costtype_tool)
{
SetCellText(row, amountCol, cTwoDashString);

if (unitCostCol)
SetCellText(row, unitCostCol, cTwoDashString);

// reset the quantity (in case we're changing from %)
SetMemberColNumber(row, tag_quantity, quantity);

if (costArea == costtype_delay)
{
UInt8 value = GetMemberColValue(row, tag_unitsize);

if (value != time_hour && value != time_day)
SetMemberCV(row, tag_unitsize, time_day);
}

// we may be removing an existing cost
RecalcAllPercentRows();

return 0;
}

// it's an item that has a cost, so let's do some updating.
// look for the unit cost column moved TCS 3/20/01
if (unitCostCol)
unitCost = GetCellText(row, unitCostCol);

// for percent costs, compute directly TCS 3/17/99
if (CCostBreakdownEntry::CostItemIsPercent(costArea))
{
// calculate the amount for this row
RecalcPercentRow(row, amountCol);

// reset the number format, in case we're converting
// from some other cost type
SetMemberColPercent(row, tag_quantity, quantity);
SetMemberColString(row, tag_unitcost, cTwoDashString);

// no further processing needed
return totalCost;
}

// reset the number format, in case we're converting from percents TCS 3/21/01
SetMemberColString(row, tag_unitcost, unitCost.GetExtendedCurrencyString());
SetMemberColNumber(row, tag_quantity, quantity);

// how we recalculate depends on the cost item being displayed.
// let's branch through all the possibilities...
if (costArea == costtype_bid) // ******* BID ******** TCS 9/6/00
{
// we need to check the bid to see if it's per unit or flat
DBid bidID = GetMemberColValue(row, tag_costitem);
CBid *bid = TCS_SAFE_CAST(gDBFile->GetOneObject(id_Bid, bidID), CBid);
if (bid)
{
DB_ObjectWatcher watcher(bid);

unitCost = bid->GetProjectPrice();
laborHours = bid->GetLaborHours();
crewSize = bid->GetCrewSize();
SetMemberColMoney(row, tag_unitcost, unitCost); // TCS 12/13/00

if (bid->IsCostPerUnit())
{
totalCost = unitCost * quantity;
}
else
{
totalCost = unitCost;
SetMemberColString(row, tag_quantity, cOneString);
}
}
else
totalCost = 0;

SetMemberColMoney(row, tag_amount, totalCost);
SetMemberColString(row, tag_laborhours, laborHours.GetNumberString()); // TCS 2/11/03
SetMemberColString(row, tag_crewsize, crewSize.GetNumberString());

return totalCost;
}
else if (costArea == costtype_allowance) // ******* ALLOWANCE ******** TCS 9/6/00
{
// we need to check the allowance to see if it's per unit or flat
DBid allowanceID = GetMemberColValue(row, tag_costitem);
CAllowance *allowance =
TCS_SAFE_CAST(gDBFile->GetOneObject(id_Allowance, allowanceID), CAllowance);
if (allowance)
{
DB_ObjectWatcher watcher(allowance);

unitCost = allowance->GetProjectPrice();
laborHours = allowance->GetLaborHours();
crewSize = allowance->GetCrewSize();

if (allowance->IsCostPerUnit())
{
totalCost = unitCost * quantity;
}
else
{
totalCost = unitCost;
SetMemberColString(row, tag_quantity, cOneString);
}
}
else
totalCost = 0;

SetMemberColMoney(row, tag_amount, totalCost);
SetMemberColString(row, tag_laborhours, laborHours.GetNumberString()); // TCS 2/11/03
SetMemberColString(row, tag_crewsize, crewSize.GetNumberString());

return totalCost;
}
else if (costArea == costtype_powo) // ******* PURCHASE ORDER ******** // TCS 2/11/03
{
// fetch info from the order
DBid orderID = GetMemberColValue(row, tag_costitem);
CPurchaseWorkOrder *order =
TCS_SAFE_CAST(gDBFile->GetOneObject(id_PurchaseWorkOrder, orderID),
CPurchaseWorkOrder);
if (order)
{
DB_ObjectWatcher watcher(order);

unitCost = order->GetAmount();
laborHours = order->GetLaborHours();
crewSize = order->GetCrewSize();
SetMemberColMoney(row, tag_unitcost, unitCost);

totalCost = unitCost;
SetMemberColString(row, tag_quantity, cOneString);
}
else
totalCost = 0;

SetMemberColMoney(row, tag_amount, totalCost);
SetMemberColString(row, tag_laborhours, laborHours.GetNumberString()); // TCS 2/11/03
SetMemberColString(row, tag_crewsize, crewSize.GetNumberString());

return totalCost;
}

Boolean hasFixedQuantity = false;

// it's a standard cost item
// check to see if we need to update from the unit cost rev TCS 3/31/03
// we update if we have a cost item and there is no unit bugfix TCS 5/10/01
// cost column, or if user hit the update button, or for rev TCS 1/29/04
// estimates, bids, material purchases && PO's. rev TCS 3/6/04
if (hasCostItem && (!unitCostCol || fromUpdateButton ||
((mOwnerClass == id_Estimate || mOwnerClass == id_MaterialPurchase ||
mOwnerClass == id_Bid || mOwnerClass == id_PurchaseWorkOrder) && changedCol != tag_unitcost)))
{
col = GetMemberCol(tag_costitem);

if (!col)
{ // no cost item displayed, so amount is zero
SetMemberColMoney(row, tag_amount, totalCost);
unitCost = 0;
}
else
{
// we have a cost item column, so let's fetch its price
CUnitCost *costItem = nil;
SInt32 itemID = GetCellValue(row, col);
if (costArea)
{
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));

if (costArea == costtype_assembly || costArea == costtype_assmlabor ||
costArea == costtype_assmmaterial)
{
costItem = TCS_SAFE_CAST(gDBFile->GetOneObject(id_Assembly, itemID),
CUnitCost);
}
else if (costArea == costtype_labor || costArea == costtype_material ||
costArea == costtype_other || costArea == costtype_equipment ||
costArea == costtype_subcontractor)
{
costItem = TCS_SAFE_CAST(gDBFile->GetOneObject(id_CostItem, itemID),
CUnitCost);
}
}

if (costItem)
{
DB_ObjectWatcher watcher(costItem);

if (mOwnerClass == id_Estimate)
{
hasFixedQuantity = costItem->GetEstimateCost(mViewerObject,
costArea, quantity, unitCost,
totalCost, hoursPerUnit, laborHours);
}
/*if (mOwnerClass == id_Estimate)
{
if (costArea == costtype_assembly || costArea == costtype_assmlabor ||
costArea == costtype_assmmaterial) // rev TCS 11/26/03
hasFixedQuantity = CUnitCost::HasFixedQuantity(id_Assembly, itemID);
else
hasFixedQuantity = false;

if (hasFixedQuantity)
{ // rev TCS 3/31/03 rev 11/26/03
CUnitCost::GetAdjustedUnitCosts(itemID, quantity, unitCost, totalCost,
hoursPerUnit, laborHours, costArea);
}
else if (costArea == costtype_assmlabor) // TCS 11/26/03
{
unitCost = costItem->GetLaborPrice();
}
else if (costArea == costtype_assmmaterial)
{
unitCost = costItem->GetMaterialPrice();
}
else
{
unitCost = costItem->GetProjectPrice(); // expanded TCS 9/23/99
}
}*/
else if (mOwnerClass == id_Sale)
{
if (IsOnSale())
unitCost = costItem->GetReducedPrice();
else
unitCost = costItem->GetResalePrice();
}
else if (mOwnerClass == id_MaterialPurchase || mOwnerClass == id_Bid ||
mOwnerClass == id_PurchaseWorkOrder)
{
// if a listed vendor we fetch their price TCS 3/6/04
unitCost = costItem->GetSupplierPrice(mMainAccount);
SetCellText(row, unitCostCol, unitCost.GetCurrencyString());
}
else
unitCost = costItem->GetPurchasePrice();

// now apply the adjustment factor TCS 1/22/04
if (mAdjustPercent.IsNonZero())
unitCost.Multiply(mAdjustPercent.GetPercentMultiplier());

// fetch the labor hours per unit
if (!hasFixedQuantity)
{
if (costArea == costtype_assmmaterial) // TCS 11/26/03
hoursPerUnit = 0;
else
hoursPerUnit = costItem->GetLaborHours();
}
}
else
{
// no cost item, so no cost. TCS bugfix 9/25/02
unitCost = 0;
hoursPerUnit = 0;
}
}
// update the unit cost col TCS 9/23/99
if (unitCostCol && (fromUpdateButton || hasFixedQuantity))
{
CTextString costString = unitCost.GetCurrencyString();

if (hasFixedQuantity) // TCS rev 12/24/01
costString.Prepend(cStarString);

SetCellText(row, unitCostCol, costString);
}
}

// calculate the total cost (if not already done so) TCS moved 12/26/01
if (!hasFixedQuantity)
{
totalCost = unitCost * quantity;
laborHours = hoursPerUnit * quantity;
}

// set the labor hours cells TCS 4/26/02
if (costArea == costtype_labor || costArea == costtype_unlistedlabor)
hoursPerUnit = 1;

SetMemberColString(row, tag_unithours, hoursPerUnit.GetNumberString());
SetMemberColString(row, tag_laborhours, laborHours.GetNumberString());


// check for discount col and calc unit discount price, if necessary BD 6/13/00
TableIndexT discCol = GetMemberCol(tag_discount);
if (discCol)
{
discount = GetMemberColMoney(row, tag_discount); // TCS 1/5/03
discount = totalCost * discount.GetPercentDecimal();
totalCost -= discount;
}
else if (CostItemIsUnitCost(costArea)) // TCS rev 1/5/03
{
// if no discount column, calculate the discount each time
col = GetMemberCol(tag_costitem);
SInt32 itemID = GetCellValue(row, col);

CUnitCost *costItem = nil;

if (costArea == costtype_assembly || costArea == costtype_assmlabor ||
costArea == costtype_assmmaterial)
{
costItem = TCS_SAFE_CAST(gDBFile->GetOneObject(id_Assembly, itemID), CUnitCost);
}
else
{
costItem = TCS_SAFE_CAST(gDBFile->GetOneObject(id_CostItem, itemID), CUnitCost);
}

if (costItem)
{
DB_ObjectWatcher watcher(costItem);
discount = costItem->GetDiscountForCustomer(mCustDiscount);
}

if (discount.IsNonZero())
{
discount = totalCost * discount.GetPercentDecimal();
totalCost -= discount;
}
}

// fill in the total cost column
SetCellText(row, amountCol, totalCost.GetCurrencyString());

// recalculate any other rows that are based on a percent of the total TCS 8/13/02
RecalcAllPercentRows();

return totalCost;
}
/*********************************************************************************

RecalcPercentRow TCS moved & rev 8/13/02

recalculate a row that is a percentage of hard costs (or total cost)

*********************************************************************************/
void CCostBreakdownTable::RecalcPercentRow(const TableIndexT row, const TableIndexT amountCol)
{
UInt8 costArea = GetMemberColValue(row, tag_costarea);

if (CCostBreakdownEntry::CostItemIsPercent(costArea))
{
// first set this item to zero so the
// column total will not include this amount
CMoney totalCost = 0;
SetMemberColMoney(row, tag_amount, totalCost);

CMoney quantity = GetMemberColMoney(row, tag_quantity);

// calculate percent, and display it
if (costArea == costtype_percenthard)
totalCost = GetHardCostTotal() * quantity.GetPercentDecimal();
else if (costArea == costtype_percenttotal)
totalCost = GetColumnMoneyTotal(amountCol) * quantity.GetRetailDecimal();
else if (costArea == costtype_percentlabor)
totalCost = GetLaborTotal() * quantity.GetPercentDecimal(); // TCS 11/8/02
else if (costArea == costtype_percentmaterial)
totalCost = GetMaterialTotal() * quantity.GetPercentDecimal();
else if (costArea == costtype_percentsubs) // TCS 1/23/04
totalCost = GetSubcontractorTotal() * quantity.GetPercentDecimal();

SetMemberColMoney(row, tag_amount, totalCost);

RefreshRow(row); // TCS 3/31/03
}
}
/*********************************************************************************

RecalcAllPercentRows TCS 8/13/02

recalculate all rows that are a percentage of hard costs (or total cost)

*********************************************************************************/
void CCostBreakdownTable::RecalcAllPercentRows()
{
TableIndexT amountCol = GetMemberCol(tag_amount);

for (TableIndexT row = 1 ; row <= GetNumRows() ; row++ )
{
RecalcPercentRow(row, amountCol);
}
}
/*********************************************************************************

RecalcHoursBreakdownRow TCS 9/11/99

recalculate the given row in an hours cost table

*********************************************************************************/
CMoney CCostBreakdownTable::RecalcHoursBreakdownRow(TableIndexT row, const TagType /*changedCol*/)
{
CMoney hours = 0,
result = 0;
TableIndexT col,
amountCol = GetMemberCol(tag_amount);

// if no amount column, we might as well give up now
if (!amountCol)
return 0;

// look for the qty col, which stores the hours
col = GetMemberCol(tag_quantity);
if (col) // there's a quantity column
hours = GetCellText(row, col);

result = hours * mHourlyRate;
SetCellText(row, amountCol, result.GetCurrencyString());

// fill in the latest unit cost per hour TCS 1/17/00
col = GetMemberCol(tag_unitcost);
if (col)
SetCellText(row, col, mHourlyRate.GetCurrencyString());

// the discount column stores the # of unit price items
// finished, so it's not involved in calculations TCS 2/22/02

return result;
}
/*********************************************************************************

RecalculateRows TCS 9/13/99

recalculate each row after the wage rate has changed. This is used when the
values in a table need to be updated from the outside.

WARNING- this should never call anything that might call BreakdownTotalChanged!
That method sometimes calls this one, and you probably don't want infinite
froot loops...

*********************************************************************************/
void CCostBreakdownTable::RecalculateRows()
{
for ( TableIndexT row = 1 ; row <= GetNumRows() ; row++ )
{
RecalcBreakdownRow(row, tag_wagerate);
}
Refresh();
}
#if CAN_USE_MARK
#pragma mark -
#endif
/*********************************************************************************

UpdateSchedule TCS 4/11/02 rev 4/26/02

update schedule dates in an estimate breakdown table, and return the final
completion date.

*********************************************************************************/
CDate CCostBreakdownTable::UpdateSchedule(const CDate startDate,
const TableIndexT startRow)
{
CDate currentDate = startDate;
CMoney workHours, crewSize;

UInt8 startDateCol = GetMemberCol(tag_date);
UInt8 startTimeCol = GetMemberCol(tag_time);
UInt8 endDateCol = GetMemberCol(tag_enddate); // TCS 4/4/04
UInt8 endTimeCol = GetMemberCol(tag_endtime);

CCursorSpinner spinner;
CProgressBar *progressBar = nil;

if (GetNumRows() > 80 || (gIsClient && GetNumRows() > 80)) // TCS 11/25/03
progressBar = CProgressBar::CreateBar(TCS_GetStockString(stockID_UpdatingSchedule));

if (progressBar)
{
progressBar->SetMaxValue(GetNumRows());
progressBar->SetActionType(TCS_GetStockString(stockID_Row));
}

CProgressBarWatcher watcher (progressBar);

// loop through each row and recalculate
for (TableIndexT row = startRow ; row <= GetNumRows() ; row++ )
{
workHours = GetRowWorkHours(row);
crewSize = GetRowCrewSize(row);

if (progressBar)
progressBar->Increment();
else
++spinner;

if (startDateCol)
SetCellText(row, startDateCol, currentDate.GetCString());
if (startTimeCol) // TCS 5/24/02
SetCellText(row, startTimeCol, currentDate.GetTimeString());

// add the time taken for this row's item. We now use a static
// method so we can fetch fewer objects in the MU version.
currentDate = CWorkingHours::AddWorkHours(mWorkingHours, currentDate, workHours, crewSize);

if (endDateCol)
SetCellText(row, endDateCol, currentDate.GetCString());
if (endTimeCol) // TCS 5/24/02
SetCellText(row, endTimeCol, currentDate.GetTimeString());
}
// make sure we get redrawn
Refresh();

// store the completion date TCS 11/5/02
mFinishDate = currentDate;

return currentDate;
}
/*********************************************************************************

GetRowEndDate TCS 8/13/02

get the completion date for work in the give row

*********************************************************************************/
CDate CCostBreakdownTable::GetRowEndDate(const TableIndexT row) const
{
UInt8 startDateCol = GetMemberCol(tag_date);
UInt8 endDateCol = GetMemberCol(tag_enddate); // TCS 4/4/04

if (endDateCol)
return GetMemberColDate(row, tag_enddate);
else if (startDateCol)
{
CMoney workHours = GetRowWorkHours(row);
CMoney crewSize = GetRowCrewSize(row);
CDate currentDate = GetMemberColDate(row, tag_date);

currentDate = CWorkingHours::AddWorkHours(mWorkingHours, currentDate, workHours, crewSize);

return currentDate;
}
else
return mStartDate;
}
/*********************************************************************************

GetRowWorkHours TCS split 5/14/02

get the number of work hours in the given row

*********************************************************************************/
CMoney CCostBreakdownTable::GetRowWorkHours(const TableIndexT row) const
{
UInt8 costArea = GetMemberColValue(row, tag_costarea);
CMoney laborHours = 0;

if (costArea == costtype_delay)
{
UInt8 size = GetMemberColValue(row, tag_unitsize);
laborHours = GetMemberColMoney(row, tag_quantity);

// add the time taken for this row's item
if (size == time_day)
return laborHours * 8;
else
return laborHours;
}
else if (costArea == costtype_labor || costArea == costtype_unlistedlabor)
{
laborHours = GetMemberColMoney(row, tag_quantity);
}
else if (GetMemberCol(tag_laborhours))
{
laborHours = GetMemberColMoney(row, tag_laborhours);
}
else
{
CMoney quantity = GetMemberColMoney(row, tag_quantity);
laborHours = GetMemberColMoney(row, tag_unithours);
laborHours = laborHours * quantity;
}

return laborHours;
}
/*********************************************************************************

GetRowCrewSize TCS split 5/14/02

get the number of work hours in the given row

*********************************************************************************/
CMoney CCostBreakdownTable::GetRowCrewSize(const TableIndexT row) const
{
UInt8 costArea = GetMemberColValue(row, tag_costarea);

if (costArea == costtype_delay)
{
return 1;
}
else
{
CMoney crewSize = GetMemberColMoney(row, tag_crewsize);

if (!crewSize.IsPositive())
crewSize = 1;

return crewSize;
}
}
/*********************************************************************************

RecalcScheduleRow TCS 5/16/02

recalculate the schedule after a change in the given row

*********************************************************************************/
void CCostBreakdownTable::RecalcScheduleRow(const TableIndexT row, const TagType changedCol)
{
if (mOwnerClass != id_Estimate) // TCS 11/5/02
return;

CDate endDate, endTime;

if (changedCol == tag_enddate || changedCol == tag_endtime)
{
// a different end date was entered. Update subsequent rows.
endDate = GetMemberColDate(row, tag_enddate);
endTime = GetMemberColTime(row, tag_endtime); // TCS 4/29/03

if (endTime.IsValidDate())
endDate.SetTime(endTime);

UpdateSchedule(endDate, row + 1);
}
else if (changedCol == tag_date || changedCol == tag_time) // TCS 4/4/04
{
// a different start date was entered. First get
// the starting date and time.
endDate = GetMemberColDate(row, tag_date);
endTime = GetMemberColTime(row, tag_time);

if (endTime.IsValidDate())
endDate.SetTime(endTime);

// figure the end date for this row, and update cells
CMoney crewSize = GetRowCrewSize(row);
CMoney workHours = GetRowWorkHours(row);
endDate = CWorkingHours::AddWorkHours(mWorkingHours, endDate, workHours, crewSize);

SetMemberColDate(row, tag_enddate, endDate);
SetMemberColTime(row, tag_endtime, endDate);

// update subsequent rows
UpdateSchedule(endDate, row + 1);
}
else
{ // a new cost item, quantity or crew size was entered.
// update this row and subsequent rows TCS rewrite 4/29/03
if (row == 1)
{
TCS_FailNILMsg(mRecordViewer, TCS_GetErrString(errID_BadViewer));
endDate = mRecordViewer->GetFieldDateValue(tag_startdate);

endTime = mRecordViewer->GetFieldTimeValue(tag_starttime);
endDate.SetTime(endTime);
}
else
{
endDate = GetMemberColDate(row - 1, tag_enddate);
endTime = GetMemberColTime(row - 1, tag_endtime); // TCS 4/29/03

if (endTime.IsValidDate())
endDate.SetTime(endTime);
}

UpdateSchedule(endDate, row);
}
}