IO-3515 Checkin. Crude form update with some correct values. Pricing still significantly out.

This commit is contained in:
Patrick Fic
2026-01-28 16:20:27 -08:00
parent 55de16281d
commit 83be45a40b
4 changed files with 280 additions and 2772 deletions

View File

@@ -2,7 +2,7 @@ import { useApolloClient, useMutation } from "@apollo/client/react";
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react"; import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
import { Button, Checkbox, Form, Modal, Space } from "antd"; import { Button, Checkbox, Form, Modal, Space } from "antd";
import _ from "lodash"; import _ from "lodash";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -27,6 +27,7 @@ import { handleUpload as handleLocalUpload } from "../documents-local-upload/doc
import { handleUpload } from "../documents-upload/documents-upload.utility"; import { handleUpload } from "../documents-upload/documents-upload.utility";
import { handleUpload as handleUploadToImageProxy } from "../documents-upload-imgproxy/documents-upload-imgproxy.utility"; import { handleUpload as handleUploadToImageProxy } from "../documents-upload-imgproxy/documents-upload-imgproxy.utility";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import axios from "axios";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
billEnterModal: selectBillEnterModal, billEnterModal: selectBillEnterModal,
@@ -53,6 +54,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
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();
const fileInputRef = useRef(null);
const { const {
treatments: { Enhanced_Payroll, Imgproxy } treatments: { Enhanced_Payroll, Imgproxy }
@@ -419,6 +421,44 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
}} }}
footer={ footer={
<Space> <Space>
<input
ref={fileInputRef}
type="file"
accept="image/*,application/pdf"
style={{ display: "none" }}
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
const formdata = new FormData();
formdata.append("billScan", file);
formdata.append("jobid", billEnterModal.context.job.id);
formdata.append("bodyshopid", bodyshop.id);
formdata.append("partsorderid", "3dd26419-a139-4399-af4e-43eeb6f0dbad");
// formdata.append("skipTextract", "true"); // For testing purposes
axios
.post("/ai/bill-ocr", formdata)
.then(({ data }) => {
console.log("*** ~ BillEnterModalContainer ~ response:", data.data.billForm);
//Stored in data.data
form.setFieldsValue(data.data.billForm);
})
.catch((error) => {
console.error("*** ~ BillEnterModalContainer ~ error:", error);
});
}
// Reset the input so the same file can be selected again
e.target.value = "";
}}
/>
<Button
onClick={() => {
console.log("Fields Object", form.getFieldsValue());
fileInputRef.current?.click();
}}
>
AI Scan (1 page only for now)
</Button>
<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>

File diff suppressed because it is too large Load Diff

View File

@@ -39,9 +39,9 @@ function normalizeLabelName(labelText) {
'part_num': standardizedFieldsnames.part_no, 'part_num': standardizedFieldsnames.part_no,
'part_number': standardizedFieldsnames.part_no, 'part_number': standardizedFieldsnames.part_no,
'price': standardizedFieldsnames.actual_price, 'price': standardizedFieldsnames.actual_price,
'unit_price': standardizedFieldsnames.actual_price,
'amount': standardizedFieldsnames.actual_price, 'amount': standardizedFieldsnames.actual_price,
'list_price': standardizedFieldsnames.actual_price, 'list_price': standardizedFieldsnames.actual_price,
'unit_price': standardizedFieldsnames.actual_price,
'list': standardizedFieldsnames.actual_price, 'list': standardizedFieldsnames.actual_price,
'retail_price': standardizedFieldsnames.actual_price, 'retail_price': standardizedFieldsnames.actual_price,
'net': standardizedFieldsnames.actual_cost, 'net': standardizedFieldsnames.actual_cost,
@@ -145,6 +145,7 @@ function extractInvoiceData(textractResponse) {
if (lineItemGroup.LineItems) { if (lineItemGroup.LineItems) {
lineItemGroup.LineItems.forEach(lineItem => { lineItemGroup.LineItems.forEach(lineItem => {
const item = {}; const item = {};
const fieldNameCounts = {}; // Track field name occurrences
if (lineItem.LineItemExpenseFields) { if (lineItem.LineItemExpenseFields) {
lineItem.LineItemExpenseFields.forEach(field => { lineItem.LineItemExpenseFields.forEach(field => {
@@ -155,7 +156,14 @@ function extractInvoiceData(textractResponse) {
if (fieldType && fieldValue) { if (fieldType && fieldValue) {
// Normalize field names // Normalize field names
const normalizedField = normalizeFieldName(fieldType); let normalizedField = normalizeFieldName(fieldType);
// Ensure uniqueness by appending a counter if the field already exists
if (item.hasOwnProperty(normalizedField)) {
fieldNameCounts[normalizedField] = (fieldNameCounts[normalizedField] || 1) + 1;
normalizedField = `${normalizedField}_${fieldNameCounts[normalizedField]}`;
}
item[normalizedField] = { item[normalizedField] = {
value: fieldValue, value: fieldValue,
label: fieldLabel, label: fieldLabel,

View File

@@ -62,7 +62,19 @@ async function handleBillOcr(request, response) {
// The uploaded file is available in request.file // The uploaded file is available in request.file
const uploadedFile = request.file; const uploadedFile = request.file;
const { jobid, bodyshopid, parts_orderid } = request.body; const { jobid, bodyshopid, partsorderid, skipTextract } = request.body;
if (skipTextract === 'true') {
console.log('Skipping Textract processing as per request');
response.status(200).send({
success: true,
status: 'COMPLETED',
data: await generateBillFormData({ processedData: null, jobid, bodyshopid, partsorderid }), //This is broken if the processedData is not overwritten in the function for testing.
message: 'Invoice processing completed'
});
return;
}
try { try {
const fileType = getFileType(uploadedFile); const fileType = getFileType(uploadedFile);
@@ -71,12 +83,12 @@ async function handleBillOcr(request, response) {
// Images are always processed synchronously (single page) // Images are always processed synchronously (single page)
if (fileType === 'image') { if (fileType === 'image') {
console.log('Image => 1 page, processing synchronously'); console.log('Image => 1 page, processing synchronously');
const result = await processSinglePageDocument(uploadedFile.buffer); const processedData = await processSinglePageDocument(uploadedFile.buffer);
const billForm = await generateBillFormData({ processedData: processedData, jobid, bodyshopid, partsorderid });
response.status(200).send({ response.status(200).send({
success: true, success: true,
status: 'COMPLETED', status: 'COMPLETED',
data: result, data: { ...processedData, billForm },
message: 'Invoice processing completed' message: 'Invoice processing completed'
}); });
} else if (fileType === 'pdf') { } else if (fileType === 'pdf') {
@@ -87,13 +99,13 @@ async function handleBillOcr(request, response) {
if (pageCount === 1) { if (pageCount === 1) {
// Process synchronously for single-page documents // Process synchronously for single-page documents
console.log('PDF => 1 page, processing synchronously'); console.log('PDF => 1 page, processing synchronously');
const result = await processSinglePageDocument(uploadedFile.buffer); const processedData = await processSinglePageDocument(uploadedFile.buffer);
const billForm = await generateBillFormData({ processedData: processedData, jobid, bodyshopid, partsorderid });
//const billResult = await generateBillFormData({ result, }); //const billResult = await generateBillFormData({ result, });
response.status(200).send({ response.status(200).send({
success: true, success: true,
status: 'COMPLETED', status: 'COMPLETED',
data: { result, }, data: { ...processedData, billForm },
message: 'Invoice processing completed' message: 'Invoice processing completed'
}); });
} else { } else {
@@ -142,9 +154,13 @@ async function handleBillOcrStatus(request, response) {
} }
if (jobStatus.status === 'COMPLETED') { if (jobStatus.status === 'COMPLETED') {
//TODO: This needs to be stored in the redis cache and pulled when it's processed.
//const billForm = await generateBillFormData({ jobid, bodyshopid, partsorderid });
response.status(200).send({ response.status(200).send({
status: 'COMPLETED', status: 'COMPLETED',
data: jobStatus.data data: jobStatus.data
// data: { ...jobStatus.data, billForm }
}); });
} else if (jobStatus.status === 'FAILED') { } else if (jobStatus.status === 'FAILED') {
response.status(500).send({ response.status(500).send({