+
+
+
>}>
-
+ } disabled={bill.exported} loading={loading} />
);
diff --git a/client/src/components/bill-detail-edit/bill-detail-edit-return.component.jsx b/client/src/components/bill-detail-edit/bill-detail-edit-return.component.jsx
index 775f4d32b..ed9743c2d 100644
--- a/client/src/components/bill-detail-edit/bill-detail-edit-return.component.jsx
+++ b/client/src/components/bill-detail-edit/bill-detail-edit-return.component.jsx
@@ -48,7 +48,7 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
// db_price: i.actual_price,
act_price: i.actual_price,
cost: i.actual_cost,
- quantity: i.quantity,
+ part_qty: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type
@@ -104,6 +104,10 @@ export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
{fields.map((field, index) => (
|
+ {/* Hidden field to preserve the id */}
+
+
+
}
onClick={() => {
const values = form.getFieldsValue("billlineskeys");
@@ -53,9 +54,7 @@ export function BillFormItemsExtendedFormItem({
}
});
}}
- >
-
-
+ />
);
return (
@@ -196,6 +195,7 @@ export function BillFormItemsExtendedFormItem({
}
onClick={() => {
const values = form.getFieldsValue("billlineskeys");
@@ -207,9 +207,7 @@ export function BillFormItemsExtendedFormItem({
}
});
}}
- >
-
-
+ />
);
}
diff --git a/client/src/components/bill-form/bill-form.component.jsx b/client/src/components/bill-form/bill-form.component.jsx
index d46035805..51ce7bee1 100644
--- a/client/src/components/bill-form/bill-form.component.jsx
+++ b/client/src/components/bill-form/bill-form.component.jsx
@@ -373,9 +373,11 @@ export function BillFormComponent({
"local_tax_rate"
]);
let totals;
- if (!!values.total && !!values.billlines && values.billlines.length > 0)
+ if (!!values.total && !!values.billlines && values.billlines.length > 0) {
totals = CalculateBillTotal(values);
- if (totals)
+ }
+
+ if (totals) {
return (
// TODO: Align is not correct
// eslint-disable-next-line react/no-unknown-property
@@ -414,7 +416,7 @@ export function BillFormComponent({
);
+ }
return null;
}}
diff --git a/client/src/components/bill-form/bill-form.lines.component.jsx b/client/src/components/bill-form/bill-form.lines.component.jsx
index c6b2c8d0e..bc36b172c 100644
--- a/client/src/components/bill-form/bill-form.lines.component.jsx
+++ b/client/src/components/bill-form/bill-form.lines.component.jsx
@@ -1,6 +1,7 @@
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
import { Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip } from "antd";
+import { useRef } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -32,14 +33,14 @@ export function BillEnterModalLinesComponent({
}) {
const { t } = useTranslation();
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
+ const firstFieldRefs = useRef({});
- // Keep input row heights consistent with the rest of the table controls.
const CONTROL_HEIGHT = 32;
const normalizeDiscount = (d) => {
const n = Number(d);
if (!Number.isFinite(n) || n <= 0) return 0;
- return n > 1 ? n / 100 : n; // supports 15 or 0.15
+ return n > 1 ? n / 100 : n;
};
const round2 = (v) => Math.round((v + Number.EPSILON) * 100) / 100;
@@ -79,7 +80,6 @@ export function BillEnterModalLinesComponent({
return NaN;
};
- // safe per-field setter (supports AntD 6+ setFieldValue, falls back to setFieldsValue)
const setLineField = (index, field, value) => {
if (typeof form.setFieldValue === "function") {
form.setFieldValue(["billlines", index, field], value);
@@ -92,6 +92,7 @@ export function BillEnterModalLinesComponent({
});
};
+ // Only fill actual_cost when the user forward-tabs out of Retail (actual_price)
const autofillActualCost = (index) => {
Promise.resolve().then(() => {
const retailRaw = form.getFieldValue(["billlines", index, "actual_price"]);
@@ -115,7 +116,6 @@ export function BillEnterModalLinesComponent({
};
const getIndicatorShellStyles = (statusColor) => {
- // bring back the “colored shell” feel around the $ indicator while keeping row height stable
if (isDarkMode) {
if (statusColor === "green")
return { borderColor: "rgba(82, 196, 26, 0.75)", background: "rgba(82, 196, 26, 0.10)" };
@@ -145,7 +145,7 @@ export function BillEnterModalLinesComponent({
editable: true,
minWidth: "10rem",
formItemProps: (field) => ({
- key: `${field.index}joblinename`,
+ key: `${field.name}joblinename`,
name: [field.name, "joblineid"],
label: t("billlines.fields.jobline"),
rules: [{ required: true }]
@@ -157,6 +157,9 @@ export function BillEnterModalLinesComponent({
),
formInput: (record, index) => (
{
+ firstFieldRefs.current[index] = el;
+ }}
disabled={disabled}
options={lineData}
style={{
@@ -167,10 +170,9 @@ export function BillEnterModalLinesComponent({
}}
allowRemoved={form.getFieldValue("is_credit_memo") || false}
onSelect={(value, opt) => {
- const d = normalizeDiscount(discount);
- const retail = Number(opt.cost);
- const computedActual = Number.isFinite(retail) ? round2(retail * (1 - d)) : null;
-
+ // IMPORTANT:
+ // Do NOT autofill actual_cost here. It should only fill when the user forward-tabs
+ // from Retail (actual_price) -> Actual Cost (actual_cost).
setFieldsValue({
billlines: (getFieldValue("billlines") || []).map((item, idx) => {
if (idx !== index) return item;
@@ -181,7 +183,7 @@ export function BillEnterModalLinesComponent({
quantity: opt.part_qty || 1,
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
? bodyshopHasDmsKey(bodyshop)
? opt.part_type !== "PAE"
@@ -203,12 +205,12 @@ export function BillEnterModalLinesComponent({
editable: true,
minWidth: "10rem",
formItemProps: (field) => ({
- key: `${field.index}line_desc`,
+ key: `${field.name}line_desc`,
name: [field.name, "line_desc"],
label: t("billlines.fields.line_desc"),
rules: [{ required: true }]
}),
- formInput: () =>
+ formInput: () =>
},
{
title: t("billlines.fields.confidence"),
@@ -228,17 +230,19 @@ export function BillEnterModalLinesComponent({
editable: true,
width: "4rem",
formItemProps: (field) => ({
- key: `${field.index}quantity`,
+ key: `${field.name}quantity`,
name: [field.name, "quantity"],
label: t("billlines.fields.quantity"),
rules: [
{ required: true },
({ getFieldValue: gf }) => ({
- validator(rule, value) {
- if (value && gf("billlines")[field.fieldKey]?.inventories?.length > value) {
+ validator(_, value) {
+ const invLen = gf(["billlines", field.name, "inventories"])?.length ?? 0;
+
+ if (value && invLen > value) {
return Promise.reject(
t("bills.validation.inventoryquantity", {
- number: gf("billlines")[field.fieldKey]?.inventories?.length
+ number: invLen
})
);
}
@@ -247,7 +251,7 @@ export function BillEnterModalLinesComponent({
})
]
}),
- formInput: () =>
+ formInput: () =>
},
{
title: t("billlines.fields.actual_price"),
@@ -255,7 +259,7 @@ export function BillEnterModalLinesComponent({
width: "8rem",
editable: true,
formItemProps: (field) => ({
- key: `${field.index}actual_price`,
+ key: `${field.name}actual_price`,
name: [field.name, "actual_price"],
label: t("billlines.fields.actual_price"),
rules: [{ required: true }]
@@ -264,9 +268,10 @@ export function BillEnterModalLinesComponent({
autofillActualCost(index)}
+ tabIndex={0}
+ // NOTE: Autofill should only happen on forward Tab out of Retail
onKeyDown={(e) => {
- if (e.key === "Tab") autofillActualCost(index);
+ if (e.key === "Tab" && !e.shiftKey) autofillActualCost(index);
}}
/>
),
@@ -307,7 +312,7 @@ export function BillEnterModalLinesComponent({
width: "10rem",
skipFormItem: true,
formItemProps: (field) => ({
- key: `${field.index}actual_cost`,
+ key: `${field.name}actual_cost`,
name: [field.name, "actual_cost"],
label: t("billlines.fields.actual_cost"),
rules: [{ required: true }]
@@ -341,6 +346,7 @@ export function BillEnterModalLinesComponent({
min={0}
disabled={disabled}
controls={false}
+ tabIndex={0}
style={{ width: "100%", height: CONTROL_HEIGHT }}
onFocus={() => autofillActualCost(index)}
/>
@@ -398,14 +404,14 @@ export function BillEnterModalLinesComponent({
dataIndex: "cost_center",
editable: true,
formItemProps: (field) => ({
- key: `${field.index}cost_center`,
+ key: `${field.name}cost_center`,
name: [field.name, "cost_center"],
label: t("billlines.fields.cost_center"),
valuePropName: "value",
rules: [{ required: true }]
}),
formInput: () => (
- | |