Compare commits
33 Commits
feature/IO
...
bugfix/IO-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
217a0b84ac | ||
|
|
45e143578c | ||
|
|
849d967b56 | ||
|
|
519d7e8d87 | ||
|
|
b08435607e | ||
|
|
ea9e4ffcad | ||
|
|
6c814c7dc6 | ||
|
|
cc9e536059 | ||
|
|
dadc9892d0 | ||
|
|
b05e20ce0d | ||
|
|
eb36b12cb0 | ||
|
|
bf5a099fa6 | ||
|
|
ff3d24c623 | ||
|
|
27b955a701 | ||
|
|
1896c4db59 | ||
|
|
78770ed54e | ||
|
|
9e2ae2cc10 | ||
|
|
f0dfa2717f | ||
|
|
3d9ad799f3 | ||
|
|
6e17ef10bb | ||
|
|
fdc06e79a6 | ||
|
|
66924367fc | ||
|
|
f76165552e | ||
|
|
80fbb847d8 | ||
|
|
ca1703e724 | ||
|
|
163819809c | ||
|
|
42fa85e145 | ||
|
|
0c9f7df9ac | ||
|
|
78d816fa8b | ||
|
|
4a1b1fe905 | ||
|
|
a9fb77189e | ||
|
|
70b6aa63ed | ||
|
|
844a879f1c |
@@ -48,7 +48,7 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
|||||||
// db_price: i.actual_price,
|
// db_price: i.actual_price,
|
||||||
act_price: i.actual_price,
|
act_price: i.actual_price,
|
||||||
cost: i.actual_cost,
|
cost: i.actual_cost,
|
||||||
quantity: i.quantity,
|
part_qty: i.quantity,
|
||||||
joblineid: i.joblineid,
|
joblineid: i.joblineid,
|
||||||
oem_partno: i.jobline && i.jobline.oem_partno,
|
oem_partno: i.jobline && i.jobline.oem_partno,
|
||||||
part_type: i.jobline && i.jobline.part_type
|
part_type: i.jobline && i.jobline.part_type
|
||||||
@@ -104,6 +104,10 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
|||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<tr key={field.key}>
|
<tr key={field.key}>
|
||||||
<td>
|
<td>
|
||||||
|
{/* Hidden field to preserve the id */}
|
||||||
|
<Form.Item name={[field.name, "id"]} hidden>
|
||||||
|
<input type="hidden" />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
// label={t("joblines.fields.selected")}
|
// label={t("joblines.fields.selected")}
|
||||||
key={`${index}selected`}
|
key={`${index}selected`}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Only fill actual_cost when the user forward-tabs out of Retail (actual_price)
|
||||||
const autofillActualCost = (index) => {
|
const autofillActualCost = (index) => {
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
const retailRaw = form.getFieldValue(["billlines", index, "actual_price"]);
|
const retailRaw = form.getFieldValue(["billlines", index, "actual_price"]);
|
||||||
@@ -164,10 +165,9 @@ export function BillEnterModalLinesComponent({
|
|||||||
}}
|
}}
|
||||||
allowRemoved={form.getFieldValue("is_credit_memo") || false}
|
allowRemoved={form.getFieldValue("is_credit_memo") || false}
|
||||||
onSelect={(value, opt) => {
|
onSelect={(value, opt) => {
|
||||||
const d = normalizeDiscount(discount);
|
// IMPORTANT:
|
||||||
const retail = Number(opt.cost);
|
// Do NOT autofill actual_cost here. It should only fill when the user forward-tabs
|
||||||
const computedActual = Number.isFinite(retail) ? round2(retail * (1 - d)) : null;
|
// from Retail (actual_price) -> Actual Cost (actual_cost).
|
||||||
|
|
||||||
setFieldsValue({
|
setFieldsValue({
|
||||||
billlines: (getFieldValue("billlines") || []).map((item, idx) => {
|
billlines: (getFieldValue("billlines") || []).map((item, idx) => {
|
||||||
if (idx !== index) return item;
|
if (idx !== index) return item;
|
||||||
@@ -178,7 +178,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
quantity: opt.part_qty || 1,
|
quantity: opt.part_qty || 1,
|
||||||
actual_price: opt.cost,
|
actual_price: opt.cost,
|
||||||
original_actual_price: opt.cost,
|
original_actual_price: opt.cost,
|
||||||
actual_cost: isBlank(item.actual_cost) ? computedActual : item.actual_cost,
|
// actual_cost intentionally untouched here
|
||||||
cost_center: opt.part_type
|
cost_center: opt.part_type
|
||||||
? bodyshopHasDmsKey(bodyshop)
|
? bodyshopHasDmsKey(bodyshop)
|
||||||
? opt.part_type !== "PAE"
|
? opt.part_type !== "PAE"
|
||||||
@@ -251,9 +251,9 @@ export function BillEnterModalLinesComponent({
|
|||||||
<CurrencyInput
|
<CurrencyInput
|
||||||
min={0}
|
min={0}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onBlur={() => autofillActualCost(index)}
|
// NOTE: Autofill should only happen on forward Tab out of Retail
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Tab") autofillActualCost(index);
|
if (e.key === "Tab" && !e.shiftKey) autofillActualCost(index);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -329,7 +329,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
controls={false}
|
controls={false}
|
||||||
style={{ width: "100%", height: CONTROL_HEIGHT }}
|
style={{ width: "100%", height: CONTROL_HEIGHT }}
|
||||||
onFocus={() => autofillActualCost(index)}
|
// NOTE: No auto-fill on focus/blur; only triggered from Retail on Tab
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -49,12 +49,15 @@ export function DmsCdkVehicles({ form, job }) {
|
|||||||
open={open}
|
open={open}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
form.setFieldsValue({
|
if (selectedModel) {
|
||||||
dms_make: selectedModel.makecode,
|
form.setFieldsValue({
|
||||||
dms_model: selectedModel.modelcode
|
dms_make: selectedModel.makecode,
|
||||||
});
|
dms_model: selectedModel.modelcode
|
||||||
setOpen(false);
|
});
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
|
okButtonProps={{ disabled: !selectedModel }}
|
||||||
>
|
>
|
||||||
{error && <AlertComponent title={error.message} type="error" />}
|
{error && <AlertComponent title={error.message} type="error" />}
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<tr key={field.key}>
|
<tr key={field.key}>
|
||||||
|
{/* Hidden field to preserve jobline ID */}
|
||||||
|
<Form.Item hidden name={[field.name, "id"]}>
|
||||||
|
<input />
|
||||||
|
</Form.Item>
|
||||||
<td>
|
<td>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
// label={t("joblines.fields.line_desc")}
|
// label={t("joblines.fields.line_desc")}
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ import PartsOrderModalPriceChange from "./parts-order-modal-price-change.compone
|
|||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
|
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
|
||||||
|
|
||||||
|
const PriceInputWrapper = ({ value, onChange, form, field }) => (
|
||||||
|
<Space.Compact style={{ width: "100%" }}>
|
||||||
|
<PartsOrderModalPriceChange form={form} field={field} />
|
||||||
|
<CurrencyInput style={{ flex: 1 }} value={value} onChange={onChange} />
|
||||||
|
</Space.Compact>
|
||||||
|
);
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
isPartsEntry: selectIsPartsEntry
|
isPartsEntry: selectIsPartsEntry
|
||||||
@@ -199,10 +206,7 @@ export function PartsOrderModalComponent({
|
|||||||
key={`${index}act_price`}
|
key={`${index}act_price`}
|
||||||
name={[field.name, "act_price"]}
|
name={[field.name, "act_price"]}
|
||||||
>
|
>
|
||||||
<Space.Compact style={{ width: "100%" }}>
|
<PriceInputWrapper form={form} field={field} />
|
||||||
<PartsOrderModalPriceChange form={form} field={field} />
|
|
||||||
<CurrencyInput style={{ flex: 1 }} />
|
|
||||||
</Space.Compact>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{isReturn && (
|
{isReturn && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ export function PartsOrderModalContainer({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...p,
|
...p,
|
||||||
job_line_id: jobLineId
|
job_line_id: jobLineId,
|
||||||
|
...(isReturn && { cm_received: false })
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -356,7 +356,10 @@ export const MUTATION_BACKORDER_PART_LINE = gql`
|
|||||||
export const QUERY_UNRECEIVED_LINES = gql`
|
export const QUERY_UNRECEIVED_LINES = gql`
|
||||||
query QUERY_UNRECEIVED_LINES($jobId: uuid!, $vendorId: uuid!) {
|
query QUERY_UNRECEIVED_LINES($jobId: uuid!, $vendorId: uuid!) {
|
||||||
parts_order_lines(
|
parts_order_lines(
|
||||||
where: { parts_order: { jobid: { _eq: $jobId }, vendorid: { _eq: $vendorId } }, cm_received: { _neq: true } }
|
where: {
|
||||||
|
parts_order: { jobid: { _eq: $jobId }, vendorid: { _eq: $vendorId }, return: { _eq: true } }
|
||||||
|
_or: [{ cm_received: { _neq: true } }, { cm_received: { _is_null: true } }]
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
cm_received
|
cm_received
|
||||||
id
|
id
|
||||||
|
|||||||
@@ -82,10 +82,23 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
|
|||||||
|
|
||||||
const handleFinish = async ({ removefromproduction, ...values }) => {
|
const handleFinish = async ({ removefromproduction, ...values }) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
// Validate that all joblines have valid IDs
|
||||||
|
const joblinesWithIds = values.joblines.filter(jl => jl && jl.id);
|
||||||
|
if (joblinesWithIds.length !== values.joblines.length) {
|
||||||
|
notification.error({
|
||||||
|
title: t("jobs.errors.invalidjoblines"),
|
||||||
|
message: t("jobs.errors.missingjoblineids")
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await client.mutate({
|
const result = await client.mutate({
|
||||||
mutation: generateJobLinesUpdatesForInvoicing(values.joblines)
|
mutation: generateJobLinesUpdatesForInvoicing(values.joblines)
|
||||||
});
|
});
|
||||||
if (result.errors) {
|
if (result.errors) {
|
||||||
|
setLoading(false);
|
||||||
return; // Abandon the rest of the close.
|
return; // Abandon the rest of the close.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1676,7 +1676,9 @@
|
|||||||
"deleted": "Error deleting Job. {{error}}",
|
"deleted": "Error deleting Job. {{error}}",
|
||||||
"exporting": "Error exporting Job. {{error}}",
|
"exporting": "Error exporting Job. {{error}}",
|
||||||
"exporting-partner": "Unable to connect to partner application. Please ensure it is running and logged in.",
|
"exporting-partner": "Unable to connect to partner application. Please ensure it is running and logged in.",
|
||||||
|
"invalidjoblines": "Job has invalid job lines.",
|
||||||
"invoicing": "Error invoicing Job. {{error}}",
|
"invoicing": "Error invoicing Job. {{error}}",
|
||||||
|
"missingjoblineids": "Missing job line IDs for job lines.",
|
||||||
"noaccess": "This Job does not exist or you do not have access to it.",
|
"noaccess": "This Job does not exist or you do not have access to it.",
|
||||||
"nodamage": "No damage points on estimate.",
|
"nodamage": "No damage points on estimate.",
|
||||||
"nodates": "No dates specified for this Job.",
|
"nodates": "No dates specified for this Job.",
|
||||||
|
|||||||
@@ -1674,7 +1674,9 @@
|
|||||||
"deleted": "Error al eliminar el trabajo.",
|
"deleted": "Error al eliminar el trabajo.",
|
||||||
"exporting": "",
|
"exporting": "",
|
||||||
"exporting-partner": "",
|
"exporting-partner": "",
|
||||||
|
"invalidjoblines": "",
|
||||||
"invoicing": "",
|
"invoicing": "",
|
||||||
|
"missingjoblineids": "",
|
||||||
"noaccess": "Este trabajo no existe o no tiene acceso a él.",
|
"noaccess": "Este trabajo no existe o no tiene acceso a él.",
|
||||||
"nodamage": "",
|
"nodamage": "",
|
||||||
"nodates": "No hay fechas especificadas para este trabajo.",
|
"nodates": "No hay fechas especificadas para este trabajo.",
|
||||||
|
|||||||
@@ -1674,7 +1674,9 @@
|
|||||||
"deleted": "Erreur lors de la suppression du travail.",
|
"deleted": "Erreur lors de la suppression du travail.",
|
||||||
"exporting": "",
|
"exporting": "",
|
||||||
"exporting-partner": "",
|
"exporting-partner": "",
|
||||||
|
"invalidjoblines": "",
|
||||||
"invoicing": "",
|
"invoicing": "",
|
||||||
|
"missingjoblineids": "",
|
||||||
"noaccess": "Ce travail n'existe pas ou vous n'y avez pas accès.",
|
"noaccess": "Ce travail n'existe pas ou vous n'y avez pas accès.",
|
||||||
"nodamage": "",
|
"nodamage": "",
|
||||||
"nodates": "Aucune date spécifiée pour ce travail.",
|
"nodates": "Aucune date spécifiée pour ce travail.",
|
||||||
|
|||||||
@@ -306,8 +306,7 @@ async function FortellisSelectedCustomer({ socket, redisHelpers, selectedCustome
|
|||||||
CreateFortellisLogEvent(socket, "ERROR", `{7.1} Error posting vehicle service history. ${error.message}`);
|
CreateFortellisLogEvent(socket, "ERROR", `{7.1} Error posting vehicle service history. ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: IF THE VEHICLE SERVICE HISTORY FAILS, WE NEED TO MARK IT AS SUCH AND NOT DELETE THE TRANSACTION.
|
socket.emit("export-success", JobData.id);
|
||||||
//socket.emit("export-success", JobData.id);
|
|
||||||
} else {
|
} else {
|
||||||
//There was something wrong. Throw an error to trigger clean up.
|
//There was something wrong. Throw an error to trigger clean up.
|
||||||
//throw new Error("Error posting DMS Batch Transaction");
|
//throw new Error("Error posting DMS Batch Transaction");
|
||||||
@@ -431,10 +430,10 @@ async function QueryDmsCustomerByName({ socket, redisHelpers, JobData }) {
|
|||||||
const ownerName =
|
const ownerName =
|
||||||
JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== ""
|
JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== ""
|
||||||
//? [["firstName", JobData.ownr_co_nm.replace(replaceSpecialRegex, "").toUpperCase()]] // Commented out until we receive direction.
|
//? [["firstName", JobData.ownr_co_nm.replace(replaceSpecialRegex, "").toUpperCase()]] // Commented out until we receive direction.
|
||||||
? [["email", JobData.ownr_ea.toUpperCase()]]
|
? [["phone", JobData.ownr_ph1?.replace(replaceSpecialRegex, "")]]
|
||||||
: [
|
: [
|
||||||
["firstName", JobData.ownr_fn.replace(replaceSpecialRegex, "").toUpperCase()],
|
["firstName", JobData.ownr_fn?.replace(replaceSpecialRegex, "").toUpperCase()],
|
||||||
["lastName", JobData.ownr_ln.replace(replaceSpecialRegex, "").toUpperCase()]
|
["lastName", JobData.ownr_ln?.replace(replaceSpecialRegex, "").toUpperCase()]
|
||||||
];
|
];
|
||||||
try {
|
try {
|
||||||
const result = await MakeFortellisCall({
|
const result = await MakeFortellisCall({
|
||||||
|
|||||||
@@ -1725,6 +1725,7 @@ query QUERY_JOB_COSTING_DETAILS($id: uuid!) {
|
|||||||
profitcenter_part
|
profitcenter_part
|
||||||
profitcenter_labor
|
profitcenter_labor
|
||||||
act_price_before_ppc
|
act_price_before_ppc
|
||||||
|
manual_line
|
||||||
}
|
}
|
||||||
bills {
|
bills {
|
||||||
id
|
id
|
||||||
@@ -1842,6 +1843,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
|
|||||||
op_code_desc
|
op_code_desc
|
||||||
profitcenter_part
|
profitcenter_part
|
||||||
profitcenter_labor
|
profitcenter_labor
|
||||||
|
manual_line
|
||||||
}
|
}
|
||||||
bills {
|
bills {
|
||||||
id
|
id
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ function GenerateCostingData(job) {
|
|||||||
if (!acc.labor[laborProfitCenter]) acc.labor[laborProfitCenter] = Dinero();
|
if (!acc.labor[laborProfitCenter]) acc.labor[laborProfitCenter] = Dinero();
|
||||||
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(laborAmount);
|
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(laborAmount);
|
||||||
|
|
||||||
if (val.act_price > 0 && val.lbr_op === "OP14") {
|
if (val.act_price > 0 && val.lbr_op === "OP14" && !val.part_type) {
|
||||||
//Scenario where SGI may pay out hours using a part price.
|
//Scenario where SGI may pay out hours using a part price.
|
||||||
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(
|
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(
|
||||||
Dinero({
|
Dinero({
|
||||||
@@ -363,6 +363,9 @@ function GenerateCostingData(job) {
|
|||||||
if (val.mod_lbr_ty !== "LAR" && mashOpCodes.includes(val.lbr_op)) {
|
if (val.mod_lbr_ty !== "LAR" && mashOpCodes.includes(val.lbr_op)) {
|
||||||
materialsHours.mashHrs += val.mod_lb_hrs || 0;
|
materialsHours.mashHrs += val.mod_lb_hrs || 0;
|
||||||
}
|
}
|
||||||
|
if (val.manual_line === true && !mashOpCodes.includes(val.lbr_op) && val.mod_lbr_ty !== "LAR" ) {
|
||||||
|
materialsHours.mashHrs += val.mod_lb_hrs || 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,7 +502,7 @@ function GenerateCostingData(job) {
|
|||||||
let disc = Dinero(),
|
let disc = Dinero(),
|
||||||
markup = Dinero();
|
markup = Dinero();
|
||||||
const convertedKey = Object.keys(defaultProfits).find((k) => defaultProfits[k] === key);
|
const convertedKey = Object.keys(defaultProfits).find((k) => defaultProfits[k] === key);
|
||||||
if (job.parts_tax_rates && job.parts_tax_rates[convertedKey.toUpperCase()]) {
|
if (convertedKey && job.parts_tax_rates && job.parts_tax_rates[convertedKey.toUpperCase()]) {
|
||||||
if (
|
if (
|
||||||
job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp !== undefined &&
|
job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp !== undefined &&
|
||||||
job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp >= 0
|
job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp >= 0
|
||||||
@@ -523,14 +526,16 @@ function GenerateCostingData(job) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (InstanceManager({ rome: true })) {
|
if (InstanceManager({ rome: true })) {
|
||||||
const correspondingCiecaStlTotalLine = job.cieca_stl?.data.find(
|
if (convertedKey) {
|
||||||
(c) => c.ttl_typecd === convertedKey.toUpperCase()
|
const correspondingCiecaStlTotalLine = job.cieca_stl?.data.find(
|
||||||
);
|
(c) => c.ttl_typecd === convertedKey.toUpperCase()
|
||||||
if (
|
);
|
||||||
correspondingCiecaStlTotalLine &&
|
if (
|
||||||
Math.abs(jobLineTotalsByProfitCenter.parts[key].getAmount() - correspondingCiecaStlTotalLine.ttl_amt * 100) > 1
|
correspondingCiecaStlTotalLine &&
|
||||||
) {
|
Math.abs(jobLineTotalsByProfitCenter.parts[key].getAmount() - correspondingCiecaStlTotalLine.ttl_amt * 100) > 1
|
||||||
jobLineTotalsByProfitCenter.parts[key] = jobLineTotalsByProfitCenter.parts[key].add(disc).add(markup);
|
) {
|
||||||
|
jobLineTotalsByProfitCenter.parts[key] = jobLineTotalsByProfitCenter.parts[key].add(disc).add(markup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user