Files
bodyshop/server/data/autohouse.js
2021-12-17 08:35:07 -08:00

764 lines
24 KiB
JavaScript

const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment");
var builder = require("xmlbuilder2");
const _ = require("lodash");
const logger = require("../utils/logger");
const fs = require("fs");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;
const AHDineroFormat = "0.00";
const AhDateFormat = "MMDDYYYY";
const repairOpCodes = ["OP4", "OP9", "OP10"];
const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"];
const ftpSetup = {
host: process.env.AUTOHOUSE_HOST,
port: process.env.AUTOHOUSE_PORT,
username: process.env.AUTOHOUSE_USER,
password: process.env.AUTOHOUSE_PASSWORD,
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
algorithms: {
serverHostKey: ["ssh-rsa", "ssh-dss"],
},
};
exports.default = async (req, res) => {
//Query for the List of Bodyshop Clients.
logger.log("autohouse-start", "DEBUG", "api", null, null);
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
const allxmlsToUpload = [];
const allErrors = [];
try {
for (const bodyshop of bodyshops) {
logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname,
});
const erroredJobs = [];
try {
const { jobs } = await client.request(queries.AUTOHOUSE_QUERY, {
bodyshopid: bodyshop.id,
start: moment().subtract(3, "days").startOf("day"),
});
const autoHouseObject = {
AutoHouseExport: {
RepairOrder: jobs.map((j) =>
CreateRepairOrderTag(
{ ...j, bodyshop },
function ({ job, error }) {
erroredJobs.push({ job: job, error: error.toString() });
}
)
),
},
};
if (erroredJobs.length > 0) {
logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, {
count: erroredJobs.length,
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)),
});
}
var ret = builder
.create(
{
// version: "1.0",
// encoding: "UTF-8",
//keepNullNodes: true,
},
autoHouseObject
)
.end({ allowEmptyTags: true });
allxmlsToUpload.push({
xml: ret,
filename: `IM_${bodyshop.autohouseid}_${moment().format(
"DDMMYYYY_HHMMSS"
)}.xml`,
});
logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname,
});
} catch (error) {
//Error at the shop level.
logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, {
error,
});
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
fatal: true,
errors: [error.toString()],
});
} finally {
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
errors: erroredJobs,
});
}
}
//if (process.env.NODE_ENV !== "production") {
for (const xmlObj of allxmlsToUpload) {
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
}
res.json(allxmlsToUpload);
return;
// }
let sftp = new Client();
sftp.on("error", (errors) =>
logger.log("autohouse-sftp-error", "ERROR", "api", null, {
errors,
})
);
try {
//Connect to the FTP and upload all.
await sftp.connect(ftpSetup);
for (const xmlObj of allxmlsToUpload) {
logger.log("autohouse-sftp-upload", "DEBUG", "api", null, {
filename: xmlObj.filename,
});
const uploadResult = await sftp.put(
Buffer.from(xmlObj.xml),
`/${xmlObj.filename}`
);
logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, {
uploadResult,
});
}
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
} catch (error) {
logger.log("autohouse-sftp-error", "ERROR", "api", null, {
error,
});
} finally {
sftp.end();
}
res.sendStatus(200);
} catch (error) {
res.status(200).json(error);
}
};
const CreateRepairOrderTag = (job, errorCallback) => {
//Level 2
const repairCosts = CreateCosts(job);
try {
const ret = {
RepairOrderInformation: {
ShopInternalName: job.bodyshop.autohouseid,
ID: parseInt(job.ro_number.match(/\d/g).join(""), 10),
RO: job.ro_number,
Est: parseInt(job.ro_number.match(/\d/g).join(""), 10), //We no longer use estimate id.
GUID: job.id,
TransType: StatusMapping(job.status, job.bodyshop.md_ro_statuses),
ShopName: job.bodyshop.shopname,
ShopAddress: job.bodyshop.address1,
ShopCity: job.bodyshop.city,
ShopState: job.bodyshop.state,
ShopZip: job.bodyshop.zip_post,
ShopPhone: job.bodyshop.phone,
EstimatorID: `${job.est_ct_ln ? job.est_ct_ln : ""}${
job.est_ct_ln ? ", " : ""
}${job.est_ct_fn ? job.est_ct_fn : ""}`,
EstimatorName: `${job.est_ct_ln ? job.est_ct_ln : ""}${
job.est_ct_ln ? ", " : ""
}${job.est_ct_fn ? job.est_ct_fn : ""}`,
},
CustomerInformation: {
FirstName: job.ownr_fn || "",
LastName: job.ownr_ln || "",
Street: job.ownr_addr1 || "",
City: job.ownr_city || "",
State: job.ownr_st || "",
Zip: job.ownr_zip || "",
Phone1: job.ownr_ph1 || "",
Phone2: null,
Phone2Extension: null,
Phone3: null,
Phone3Extension: null,
FileComments: null,
Source: null,
Email: job.ownr_ea || "",
RetWhsl: null,
Cat: null,
InsuredorClaimantFlag: null,
},
VehicleInformation: {
Year: job.v_model_yr || "",
Make: job.v_make_desc || "",
Model: job.v_model_desc || "",
VIN: job.v_vin || "",
License: job.plate_no,
MileageIn: job.kmin || 0,
Vehiclecolor: job.v_color,
VehicleProductionDate: null,
VehiclePaintCode: null,
VehicleTrimCode: null,
VehicleBodyStyle: null,
DriveableFlag: job.driveable ? "Y" : "N",
},
InsuranceInformation: {
InsuranceCo: job.ins_co_nm || "",
CompanyName: job.ins_co_nm || "",
Address: job.ins_addr1 || "",
City: job.ins_addr1 || "",
State: job.ins_city || "",
Zip: job.ins_zip || "",
Phone: job.ins_ph1 || "",
Fax: job.ins_fax || "",
ClaimType: null,
LossType: job.loss_type || "",
Policy: job.policy_no || "",
Claim: job.clm_no || "",
InsuredLastName: null,
InsuredFirstName: null,
ClaimantLastName: null,
ClaimantFirstName: null,
Assignment: null,
InsuranceAgentLastName: null,
InsuranceAgentFirstName: null,
InsAgentPhone: null,
InsideAdjuster: null,
OutsideAdjuster: null,
},
Dates: {
DateofLoss:
(job.loss_date && moment(job.loss_date).format(AhDateFormat)) || "",
InitialCustomerContactDate: null,
FirstFollowUpDate: null,
ReferralDate: null,
EstimateAppointmentDate: null,
SecondFollowUpDate: null,
AssignedDate:
(job.asgn_date && moment(job.asgn_date).format(AhDateFormat)) || "",
EstComplete: null,
CustomerAuthorizationDate: null,
InsuranceAuthorizationDate: null,
DateOpened:
(job.date_open && moment(job.date_open).format(AhDateFormat)) || "",
ScheduledArrivalDate:
(job.scheduled_in && moment(job.scheduled_in).format(AhDateFormat)) ||
"",
CarinShop:
(job.actual_in && moment(job.actual_in).format(AhDateFormat)) || "",
InsInspDate: null,
StartDate:
(job.actual_in && moment(job.actual_in).format(AhDateFormat)) || "",
PartsOrder: null,
TeardownHold: null,
SupplementSubmittedDate: null,
SupplementApprovedDate: null,
AssntoBody: null,
AssntoMech: null,
AssntoPaint: null,
AssntoDetail: null,
PromiseDate:
(job.scheduled_completion &&
moment(job.scheduled_completion).format(AhDateFormat)) ||
"",
//InsuranceTargetOut: null,
CarComplete:
(job.actual_completion &&
moment(job.actual_completion).format(AhDateFormat)) ||
"",
DeliveryAppointmentDate:
(job.scheduled_delivery &&
moment(job.scheduled_delivery).format(AhDateFormat)) ||
"",
DateClosed:
(job.date_invoiced &&
moment(job.date_invoiced).format(AhDateFormat)) ||
"",
CustomerPaidInFullDate: null,
InsurancePaidInFullDate: null,
CustPickup:
(job.actual_delivery &&
moment(job.actual_delivery).format(AhDateFormat)) ||
"",
AccountPostedDate:
job.date_exported && moment(job.date_exported).format(AhDateFormat),
CSIProcessedDate: null,
ThankYouLetterSent: null,
AdditionalFollowUpDate: null,
},
Rates: {
BodyRate: job.rate_lab || 0,
RefinishRate: job.rate_lar || 0,
MechanicalRate: job.rate_lam || 0,
StructuralRate: job.rate_las || 0,
PMRate: job.rate_mapa || 0,
BMRate: job.rate_mash || 0,
TaxRate:
(job.parts_tax_rates &&
job.parts_tax_rates.PAN &&
job.parts_tax_rates.PAN.prt_tax_rt) ||
0,
StorageRateperDay: 0,
DaysStored: 0,
},
// EstimateTotals: {
// BodyHours: null,
// RefinishHours: null,
// MechanicalHours: null,
// StructuralHours: null,
// PartsTotal: null,
// PartsOEM: null,
// PartsAM: null,
// PartsReconditioned: null,
// PartsRecycled: null,
// PartsOther: null,
// SubletTotal: null,
// BodyLaborTotal: null,
// RefinishLaborTotal: null,
// MechanicalLaborTotal: null,
// StructuralLaborTotal: null,
// MiscellaneousChargeTotal: null,
// PMTotal: null,
// BMTotal: null,
// MiscTotal: null,
// TowingTotal: null,
// StorageTotal: null,
// DetailTotal: null,
// SalesTaxTotal: null,
// GrossTotal: null,
// DeductibleTotal: null,
// DepreciationTotal: null,
// Discount: null,
// CustomerPay: null,
// InsurancePay: null,
// Deposit: null,
// AmountDue: null,
// },
// SupplementTotals: {
// BodyHours: null,
// RefinishHours: null,
// MechanicalHours: null,
// StructuralHours: null,
// PartsTotal: null,
// PartsOEM: null,
// PartsAM: null,
// PartsReconditioned: null,
// PartsRecycled: null,
// PartsOther: null,
// SubletTotal: null,
// BodyLaborTotal: null,
// RefinishLaborTotal: null,
// MechanicalLaborTotal: null,
// StructuralLaborTotal: null,
// MiscellaneousChargeTotal: null,
// PMTotal: null,
// BMTotal: null,
// MiscTotal: null,
// TowingTotal: null,
// StorageTotal: null,
// DetailTotal: null,
// SalesTaxTotal: null,
// GrossTotal: null,
// DeductibleTotal: null,
// DepreciationTotal: null,
// Discount: null,
// CustomerPay: null,
// InsurancePay: null,
// Deposit: null,
// AmountDue: null,
// },
RevisedTotals: {
BodyHours: job.job_totals.rates.lab.hours,
BodyRepairHours: job.joblines
.filter((line) => repairOpCodes.includes(line.lbr_op))
.reduce((acc, val) => acc + val.mod_lb_hrs, 0),
BodyReplaceHours: job.joblines
.filter((line) => replaceOpCodes.includes(line.lbr_op))
.reduce((acc, val) => acc + val.mod_lb_hrs, 0),
RefinishHours: job.job_totals.rates.lar.hours,
MechanicalHours: job.job_totals.rates.lam.hours,
StructuralHours: job.job_totals.rates.las.hours,
PartsTotal: Dinero(job.job_totals.parts.parts.total).toFormat(
AHDineroFormat
),
PartsTotalCost: repairCosts.PartsTotalCost.toFormat(AHDineroFormat),
PartsOEM: Dinero(
job.job_totals.parts.parts.list.PAN &&
job.job_totals.parts.parts.list.PAN.total
)
.add(
Dinero(
job.job_totals.parts.parts.list.PAP &&
job.job_totals.parts.parts.list.PAP.total
)
)
.toFormat(AHDineroFormat),
PartsOEMCost: repairCosts.PartsOemCost.toFormat(AHDineroFormat),
PartsAM: Dinero(
job.job_totals.parts.parts.list.PAA &&
job.job_totals.parts.parts.list.PAA.total
).toFormat(AHDineroFormat),
PartsAMCost: repairCosts.PartsAMCost.toFormat(AHDineroFormat),
PartsReconditioned:
repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat),
PartsReconditionedCost:
repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat),
PartsRecycled: Dinero(
job.job_totals.parts.parts.list.PAR &&
job.job_totals.parts.parts.list.PAR.total
).toFormat(AHDineroFormat),
PartsRecycledCost:
repairCosts.PartsRecycledCost.toFormat(AHDineroFormat),
PartsOther: Dinero(
job.job_totals.parts.parts.list.PAO &&
job.job_totals.parts.parts.list.PAO.total
).toFormat(AHDineroFormat),
PartsOtherCost: repairCosts.PartsOtherCost.toFormat(AHDineroFormat),
SubletTotal: Dinero(job.job_totals.parts.sublets.total).toFormat(
AHDineroFormat
),
SubletTotalCost: repairCosts.SubletTotalCost.toFormat(AHDineroFormat),
BodyLaborTotal: Dinero(job.job_totals.rates.lab.total).toFormat(
AHDineroFormat
),
BodyLaborTotalCost:
repairCosts.BodyLaborTotalCost.toFormat(AHDineroFormat),
RefinishLaborTotal: Dinero(job.job_totals.rates.lar.total).toFormat(
AHDineroFormat
),
RefinishLaborTotalCost:
repairCosts.RefinishLaborTotalCost.toFormat(AHDineroFormat),
MechanicalLaborTotal: Dinero(job.job_totals.rates.lam.total).toFormat(
AHDineroFormat
),
MechanicalLaborTotalCost:
repairCosts.MechanicalLaborTotalCost.toFormat(AHDineroFormat),
StructuralLaborTotal: Dinero(job.job_totals.rates.las.total).toFormat(
AHDineroFormat
),
StructuralLaborTotalCost:
repairCosts.StructuralLaborTotalCost.toFormat(AHDineroFormat),
MiscellaneousChargeTotal: 0,
MiscellaneousChargeTotalCost: 0,
PMTotal: Dinero(job.job_totals.rates.mapa.total).toFormat(
AHDineroFormat
),
PMTotalCost: 0,
BMTotal: Dinero(job.job_totals.rates.mash.total).toFormat(
AHDineroFormat
),
BMTotalCost: 0,
MiscTotal: 0,
MiscTotalCost: 0,
TowingTotal: Dinero(job.job_totals.additional.towing).toFormat(
AHDineroFormat
),
TowingTotalCost: repairCosts.TowingTotalCost.toFormat(AHDineroFormat),
StorageTotal: Dinero(job.job_totals.additional.storage).toFormat(
AHDineroFormat
),
StorageTotalCost: repairCosts.StorageTotalCost.toFormat(AHDineroFormat),
DetailTotal: 0,
DetailTotalCost: 0,
SalesTaxTotal: Dinero(job.job_totals.totals.local_tax)
.add(Dinero(job.job_totals.totals.state_tax))
.add(Dinero(job.job_totals.totals.federal_tax))
.toFormat(AHDineroFormat),
SalesTaxTotalCost: 0,
GrossTotal: Dinero(job.job_totals.totals.net_repairs).toFormat(
AHDineroFormat
),
DeductibleTotal: job.ded_amt || 0,
DepreciationTotal: Dinero(
job.job_totals.totals.custPayable.dep_taxes
).toFormat(AHDineroFormat),
Discount: Dinero(job.job_totals.additional.adjustments).toFormat(
AHDineroFormat
),
CustomerPay: Dinero(job.job_totals.totals.custPayable.total).toFormat(
AHDineroFormat
),
InsurancePay: 0,
Deposit: 0,
AmountDue: 0,
},
Misc: {
ProductionStatus: null,
StatusDescription: null,
Hub50Comment: null,
DateofChange: null,
BodyTechName: null,
TotalLossYN: job.tlos_ind ? "Y" : "N",
InsScreenCommentsLine1: null,
InsScreenCommentsLine2: null,
AssignmentCaller: null,
AssignmentDivision: null,
LocationofPrimaryImpact:
(job.area_of_damage && job.area_of_damage.impact1) || 0,
LocationofSecondaryImpact:
(job.area_of_damage && job.area_of_damage.impact2) || 0,
PaintTechID: null,
PaintTechName: null,
ImportType: null,
ImportFile: null,
GSTTax: Dinero(job.job_totals.totals.federal_tax).toFormat(
AHDineroFormat
),
RepairDelayStatusCode: null,
RepairDelaycomment: null,
AgentMktgID: null,
AgentCity: null,
Picture1: null,
Picture2: null,
ExtNoteDate: null,
RentalOrdDate: null,
RentalPUDate: null,
RentalDueDate: null,
RentalActRetDate: null,
RentalCompanyID: null,
// CSIID: null,
InsGroupCode: null,
},
DetailLines: {
DetailLine:
job.joblines.length > 0
? job.joblines.map((jl) =>
GenerateDetailLines(jl, job.bodyshop.md_order_statuses)
)
: [generateNullDetailLine()],
},
};
return ret;
} catch (error) {
logger.log("autohouse-job-calculate-error", "ERROR", "api", null, {
error,
});
errorCallback({ job, error });
}
};
const CreateCosts = (job) => {
//Create a mapping based on AH Requirements
const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
//At the bill level.
bill_val.billlines.map((line_val) => {
//At the bill line level.
if (!bill_acc[line_val.cost_center])
bill_acc[line_val.cost_center] = Dinero();
bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
return null;
});
return bill_acc;
}, {});
const materialsHours = { mapaHrs: 0, mashHrs: 0 };
//If the hourly rates for job costing are set, add them in.
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa) {
if (
!billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
]
)
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = Dinero();
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
].add(
Dinero({
amount:
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mapa * 100) ||
0,
}).multiply(materialsHours.mapaHrs)
);
}
const ticketTotalsByCostCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
//At the invoice level.
if (!ticket_acc[ticket_val.cost_center])
ticket_acc[ticket_val.cost_center] = Dinero();
ticket_acc[ticket_val.cost_center] = ticket_acc[
ticket_val.cost_center
].add(
Dinero({
amount: Math.round((ticket_val.rate || 0) * 100),
}).multiply(ticket_val.actualhrs || ticket_val.productivehrs || 0)
);
return ticket_acc;
},
{}
);
const defaultCosts = job.bodyshop.md_responsibility_centers.defaults.costs;
return {
PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
return acc.add(billTotalsByCostCenters[key]);
}, Dinero()),
PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add(
billTotalsByCostCenters[defaultCosts.PAP] || Dinero()
),
PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(),
PartsReconditionedCost:
billTotalsByCostCenters[defaultCosts.PAM] || Dinero(),
PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(),
PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
SubletTotalCost: billTotalsByCostCenters[defaultCosts.PAS] || Dinero(),
BodyLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(),
RefinishLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(),
MechanicalLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(),
StructuralLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(),
PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(),
BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(),
MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(),
StorageTotalCost: Dinero(),
DetailTotal: Dinero(),
DetailTotalCost: Dinero(),
SalesTaxTotalCost: Dinero(),
};
};
const StatusMapping = (status, md_ro_statuses) => {
//EST, SCH, ARR, IPR, RDY, DEL, CLO, CAN, UNDEFINED.
const {
default_imported,
default_open,
default_scheduled,
default_arrived,
default_completed,
default_delivered,
default_invoiced,
default_exported,
default_void,
} = md_ro_statuses;
if (status === default_open || status === default_imported) return "EST";
else if (status === default_scheduled) return "SCH";
else if (status === default_arrived) return "ARR";
else if (status === default_completed) return "RDY";
else if (status === default_delivered) return "DEL";
else if (status === default_invoiced || status === default_exported)
return "CLO";
else if (status === default_void) return "CLO";
else if (md_ro_statuses.production_statuses.includes(status)) return "IPR";
else return "UNDEFINED";
// default: return "UNDEFINED"
};
const GenerateDetailLines = (line, statuses) => {
const ret = {
BackOrdered: line.status === statuses.default_bo ? "1" : "0",
Cost:
(line.billlines[0] &&
(line.billlines[0].actual_cost * line.billlines[0].quantity).toFixed(
2
)) ||
0,
//Critical: null,
Description: line.line_desc || "",
DiscountMarkup: line.prt_dsmk_m || 0,
InvoiceNumber: line.billlines[0] && line.billlines[0].bill.invoice_number,
IOUPart: 0,
LineNumber: line.line_no || 0,
MarkUp: null,
OrderedOn: null,
OriginalCost: null,
OriginalInvoiceNumber: null,
PriceEach: (line.billlines[0] && line.billlines[0].retail_price) || 0,
PartNumber: _.escape(line.oem_partno),
ProfitPercent: null,
PurchaseOrderNumber: null,
Qty: line.part_qty || 0,
Status: line.status || "",
SupplementNumber: line.line_ind || "",
Type: line.part_type || "",
Vendor: (line.billlines[0] && line.billlines[0].bill.vendor.name) || "",
VendorPaid: null,
VendorPrice: (line.billlines[0] && line.billlines[0].actual_price) || 0,
Deleted: null,
ExpectedOn: null,
ReceivedOn: null,
OrderedBy: null,
ShipVia: null,
VendorContact: null,
EstimateAmount: line.act_price * line.part_qty || 0, //Rebecca
};
return ret;
};
const generateNullDetailLine = () => {
return {
BackOrdered: "0",
Cost: 0,
Critical: null,
Description: "No Lines on Estimate",
DiscountMarkup: 0,
InvoiceNumber: null,
IOUPart: 0,
LineNumber: 0,
MarkUp: null,
OrderedOn: null,
OriginalCost: null,
OriginalInvoiceNumber: null,
PriceEach: 0,
PartNumber: 0,
ProfitPercent: null,
PurchaseOrderNumber: null,
Qty: 0,
Status: "",
SupplementNumber: 0,
Type: "",
Vendor: "",
VendorPaid: null,
VendorPrice: 0,
Deleted: 0,
ExpectedOn: "",
ReceivedOn: "",
OrderedBy: "",
ShipVia: "",
VendorContact: "",
EstimateAmount: 0,
};
};