IO-3515 set po context, update confidence UI showing
This commit is contained in:
@@ -5,6 +5,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { useNotification } from "../../contexts/Notifications/notificationContext";
|
import { useNotification } from "../../contexts/Notifications/notificationContext";
|
||||||
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { FaWandMagicSparkles } from "react-icons/fa6";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
billEnterModal: selectBillEnterModal,
|
billEnterModal: selectBillEnterModal,
|
||||||
@@ -19,7 +20,8 @@ function BillEnterAiScan({
|
|||||||
form,
|
form,
|
||||||
fileInputRef,
|
fileInputRef,
|
||||||
scanLoading,
|
scanLoading,
|
||||||
setScanLoading
|
setScanLoading,
|
||||||
|
setIsAiScan
|
||||||
}) {
|
}) {
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
|
||||||
@@ -59,8 +61,6 @@ function BillEnterAiScan({
|
|||||||
}
|
}
|
||||||
// If status is IN_PROGRESS, continue polling
|
// If status is IN_PROGRESS, continue polling
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error polling job status:", error);
|
|
||||||
|
|
||||||
// Stop polling on error
|
// Stop polling on error
|
||||||
if (pollingIntervalRef.current) {
|
if (pollingIntervalRef.current) {
|
||||||
clearInterval(pollingIntervalRef.current);
|
clearInterval(pollingIntervalRef.current);
|
||||||
@@ -86,11 +86,12 @@ function BillEnterAiScan({
|
|||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
setScanLoading(true);
|
setScanLoading(true);
|
||||||
|
setIsAiScan(true);
|
||||||
const formdata = new FormData();
|
const formdata = new FormData();
|
||||||
formdata.append("billScan", file);
|
formdata.append("billScan", file);
|
||||||
formdata.append("jobid", billEnterModal.context.job.id);
|
formdata.append("jobid", billEnterModal.context.job.id);
|
||||||
formdata.append("bodyshopid", bodyshop.id);
|
formdata.append("bodyshopid", bodyshop.id);
|
||||||
formdata.append("partsorderid", "3dd26419-a139-4399-af4e-43eeb6f0dbad");
|
formdata.append("partsorderid", billEnterModal.context.parts_order?.id);
|
||||||
//formdata.append("skipTextract", "true"); // For testing purposes
|
//formdata.append("skipTextract", "true"); // For testing purposes
|
||||||
axios
|
axios
|
||||||
.post("/ai/bill-ocr", formdata)
|
.post("/ai/bill-ocr", formdata)
|
||||||
@@ -123,7 +124,6 @@ function BillEnterAiScan({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("*** ~ BillEnterModalContainer ~ error:", error);
|
|
||||||
setScanLoading(false);
|
setScanLoading(false);
|
||||||
notification.error({
|
notification.error({
|
||||||
title: "AI Scan Failed",
|
title: "AI Scan Failed",
|
||||||
@@ -137,9 +137,9 @@ function BillEnterAiScan({
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log("Fields Object", form.getFieldsValue());
|
|
||||||
fileInputRef.current?.click();
|
fileInputRef.current?.click();
|
||||||
}}
|
}}
|
||||||
|
icon={<FaWandMagicSparkles />}
|
||||||
loading={scanLoading}
|
loading={scanLoading}
|
||||||
disabled={scanLoading}
|
disabled={scanLoading}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
|
const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [scanLoading, setScanLoading] = useState(false);
|
const [scanLoading, setScanLoading] = useState(false);
|
||||||
|
const [isAiScan, setIsAiScan] = useState(false);
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const [generateLabel, setGenerateLabel] = useLocalStorage("enter_bill_generate_label", false);
|
const [generateLabel, setGenerateLabel] = useLocalStorage("enter_bill_generate_label", false);
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
@@ -59,12 +60,13 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
const pollingIntervalRef = useRef(null);
|
const pollingIntervalRef = useRef(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treatments: { Enhanced_Payroll, Imgproxy }
|
treatments: { Enhanced_Payroll, Imgproxy, Bill_OCR_AI }
|
||||||
} = useTreatmentsWithConfig({
|
} = useTreatmentsWithConfig({
|
||||||
attributes: {},
|
attributes: {},
|
||||||
names: ["Enhanced_Payroll", "Imgproxy"],
|
names: ["Enhanced_Payroll", "Imgproxy", "Bill_OCR_AI"],
|
||||||
splitKey: bodyshop.imexshopid
|
splitKey: bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
console.log("*** ~ BillEnterModalContainer ~ Bill_OCR_AI:", Bill_OCR_AI);
|
||||||
|
|
||||||
const formValues = useMemo(() => {
|
const formValues = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@@ -382,6 +384,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
vendorid: values.vendorid,
|
vendorid: values.vendorid,
|
||||||
billlines: []
|
billlines: []
|
||||||
});
|
});
|
||||||
|
setIsAiScan(false);
|
||||||
// form.resetFields();
|
// form.resetFields();
|
||||||
} else {
|
} else {
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
@@ -398,6 +401,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
pollingIntervalRef.current = null;
|
pollingIntervalRef.current = null;
|
||||||
}
|
}
|
||||||
setScanLoading(false);
|
setScanLoading(false);
|
||||||
|
setIsAiScan(false);
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -422,6 +426,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
pollingIntervalRef.current = null;
|
pollingIntervalRef.current = null;
|
||||||
}
|
}
|
||||||
setScanLoading(false);
|
setScanLoading(false);
|
||||||
|
setIsAiScan(false);
|
||||||
}
|
}
|
||||||
}, [billEnterModal.open, form, formValues]);
|
}, [billEnterModal.open, form, formValues]);
|
||||||
|
|
||||||
@@ -437,7 +442,22 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t("bills.labels.new")}
|
title={
|
||||||
|
<Space size="large">
|
||||||
|
{t("bills.labels.new")}
|
||||||
|
{Bill_OCR_AI.treatment === "on" && (
|
||||||
|
<BillEnterAiScan
|
||||||
|
fileInputRef={fileInputRef}
|
||||||
|
form={form}
|
||||||
|
pollingIntervalRef={pollingIntervalRef}
|
||||||
|
setPollingIntervalRef={setPollingIntervalRef}
|
||||||
|
scanLoading={scanLoading}
|
||||||
|
setScanLoading={setScanLoading}
|
||||||
|
setIsAiScan={setIsAiScan}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
width={"98%"}
|
width={"98%"}
|
||||||
open={billEnterModal.open}
|
open={billEnterModal.open}
|
||||||
okText={t("general.actions.save")}
|
okText={t("general.actions.save")}
|
||||||
@@ -450,14 +470,6 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
}}
|
}}
|
||||||
footer={
|
footer={
|
||||||
<Space>
|
<Space>
|
||||||
<BillEnterAiScan
|
|
||||||
fileInputRef={fileInputRef}
|
|
||||||
form={form}
|
|
||||||
pollingIntervalRef={pollingIntervalRef}
|
|
||||||
setPollingIntervalRef={setPollingIntervalRef}
|
|
||||||
scanLoading={scanLoading}
|
|
||||||
setScanLoading={setScanLoading}
|
|
||||||
/>
|
|
||||||
<Checkbox checked={generateLabel} onChange={(e) => setGenerateLabel(e.target.checked)}>
|
<Checkbox checked={generateLabel} onChange={(e) => setGenerateLabel(e.target.checked)}>
|
||||||
{t("bills.labels.generatepartslabel")}
|
{t("bills.labels.generatepartslabel")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
@@ -491,7 +503,11 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RbacWrapper action="bills:enter">
|
<RbacWrapper action="bills:enter">
|
||||||
<BillFormContainer form={form} disableInvNumber={billEnterModal.context.disableInvNumber} />
|
<BillFormContainer
|
||||||
|
form={form}
|
||||||
|
isAiScan={isAiScan}
|
||||||
|
disableInvNumber={billEnterModal.context.disableInvNumber}
|
||||||
|
/>
|
||||||
</RbacWrapper>
|
</RbacWrapper>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ export function BillFormComponent({
|
|||||||
loadOutstandingReturns,
|
loadOutstandingReturns,
|
||||||
loadInventory,
|
loadInventory,
|
||||||
preferredMake,
|
preferredMake,
|
||||||
disableInHouse
|
disableInHouse,
|
||||||
|
isAiScan
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -452,6 +453,7 @@ export function BillFormComponent({
|
|||||||
responsibilityCenters={responsibilityCenters}
|
responsibilityCenters={responsibilityCenters}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
billEdit={billEdit}
|
billEdit={billEdit}
|
||||||
|
isAiScan={isAiScan}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Divider titlePlacement="left" style={{ display: billEdit ? "none" : null }}>
|
<Divider titlePlacement="left" style={{ display: billEdit ? "none" : null }}>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber, disableInHouse }) {
|
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber, disableInHouse,isAiScan }) {
|
||||||
const {
|
const {
|
||||||
treatments: { Simple_Inventory }
|
treatments: { Simple_Inventory }
|
||||||
} = useTreatmentsWithConfig({
|
} = useTreatmentsWithConfig({
|
||||||
@@ -50,6 +50,7 @@ export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableI
|
|||||||
loadOutstandingReturns={loadOutstandingReturns}
|
loadOutstandingReturns={loadOutstandingReturns}
|
||||||
loadInventory={loadInventory}
|
loadInventory={loadInventory}
|
||||||
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
||||||
|
isAiScan={isAiScan}
|
||||||
/>
|
/>
|
||||||
{!billEdit && <BillCmdReturnsTableComponent form={form} returnLoading={returnLoading} returnData={returnData} />}
|
{!billEdit && <BillCmdReturnsTableComponent form={form} returnLoading={returnLoading} returnData={returnData} />}
|
||||||
{Simple_Inventory.treatment === "on" && (
|
{Simple_Inventory.treatment === "on" && (
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export function BillEnterModalLinesComponent({
|
|||||||
discount,
|
discount,
|
||||||
form,
|
form,
|
||||||
responsibilityCenters,
|
responsibilityCenters,
|
||||||
billEdit
|
billEdit,
|
||||||
|
isAiScan
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
||||||
@@ -140,6 +141,29 @@ export function BillEnterModalLinesComponent({
|
|||||||
|
|
||||||
const columns = (remove) => {
|
const columns = (remove) => {
|
||||||
return [
|
return [
|
||||||
|
...(isAiScan
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: t("billlines.fields.confidence"),
|
||||||
|
dataIndex: "confidence",
|
||||||
|
editable: true,
|
||||||
|
width: "5rem",
|
||||||
|
formItemProps: (field) => ({
|
||||||
|
key: `${field.index}confidence`,
|
||||||
|
name: [field.name, "confidence"],
|
||||||
|
label: t("billlines.fields.confidence")
|
||||||
|
}),
|
||||||
|
formInput: (record) => {
|
||||||
|
const confidenceValue = getFieldValue(["billlines", record.name, "confidence"]);
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||||
|
<ConfidenceDisplay value={confidenceValue} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.jobline"),
|
title: t("billlines.fields.jobline"),
|
||||||
dataIndex: "joblineid",
|
dataIndex: "joblineid",
|
||||||
@@ -213,25 +237,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
}),
|
}),
|
||||||
formInput: () => <Input.TextArea disabled={disabled} autoSize tabIndex={0} />
|
formInput: () => <Input.TextArea disabled={disabled} autoSize tabIndex={0} />
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t("billlines.fields.confidence"),
|
|
||||||
dataIndex: "confidence",
|
|
||||||
editable: true,
|
|
||||||
width: "5rem",
|
|
||||||
formItemProps: (field) => ({
|
|
||||||
key: `${field.index}confidence`,
|
|
||||||
name: [field.name, "confidence"],
|
|
||||||
label: t("billlines.fields.confidence")
|
|
||||||
}),
|
|
||||||
formInput: (record) => {
|
|
||||||
const confidenceValue = getFieldValue(["billlines", record.name, "confidence"]);
|
|
||||||
return (
|
|
||||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
|
|
||||||
<ConfidenceDisplay value={confidenceValue} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.quantity"),
|
title: t("billlines.fields.quantity"),
|
||||||
dataIndex: "quantity",
|
dataIndex: "quantity",
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
actions: { refetch: refetch },
|
actions: { refetch: refetch },
|
||||||
context: {
|
context: {
|
||||||
job: job,
|
job: job,
|
||||||
|
parts_order: { id: record.id },
|
||||||
bill: {
|
bill: {
|
||||||
vendorid: record.vendor.id,
|
vendorid: record.vendor.id,
|
||||||
is_credit_memo: record.return,
|
is_credit_memo: record.return,
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ export function PartsOrderListTableComponent({
|
|||||||
actions: { refetch: refetch },
|
actions: { refetch: refetch },
|
||||||
context: {
|
context: {
|
||||||
job: job,
|
job: job,
|
||||||
|
parts_order: { id: record.id },
|
||||||
bill: {
|
bill: {
|
||||||
vendorid: record.vendor.id,
|
vendorid: record.vendor.id,
|
||||||
is_credit_memo: record.return,
|
is_credit_memo: record.return,
|
||||||
|
|||||||
@@ -10,8 +10,14 @@ const { Option } = Select;
|
|||||||
const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, preferredMake, showPhone, ref }) => {
|
const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, preferredMake, showPhone, ref }) => {
|
||||||
const [option, setOption] = useState(value);
|
const [option, setOption] = useState(value);
|
||||||
|
|
||||||
|
// Sync internal state when value prop changes (e.g., from form.setFieldsValue)
|
||||||
|
useEffect(() => {
|
||||||
|
if (value !== option) {
|
||||||
|
setOption(value);
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("*** ~ VendorSearchSelect ~ USEEFFECT:", value, option);
|
|
||||||
if (value !== option && onChange) {
|
if (value !== option && onChange) {
|
||||||
if (value && !option) {
|
if (value && !option) {
|
||||||
onChange(value);
|
onChange(value);
|
||||||
|
|||||||
@@ -4,4 +4,6 @@ Required Infrastructure setup
|
|||||||
3. Created 2 roles for SNS. The textract role is the right one, the other was created manually based on incorrect instructions.
|
3. Created 2 roles for SNS. The textract role is the right one, the other was created manually based on incorrect instructions.
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
* Create a rome bucket for uploads, or move to the regular spot.
|
* Create a rome bucket for uploads, or move to the regular spot.
|
||||||
|
* How to implement this across environments.
|
||||||
|
* How to prevent polling for a job that may have errored.
|
||||||
Reference in New Issue
Block a user