Compare commits
7 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9f59fcad4 | ||
|
|
edaeb5d77a | ||
|
|
7f8c82b300 | ||
|
|
609ac2bd33 | ||
|
|
0883274320 | ||
|
|
fa33b88632 | ||
|
|
bec32c1d70 |
@@ -12,13 +12,15 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
technician: selectTechnician
|
||||
technician: selectTechnician,
|
||||
isPartsEntry: selectIsPartsEntry
|
||||
});
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
export function JobLinesPartPriceChange({ job, line, refetch, technician }) {
|
||||
export function JobLinesPartPriceChange({ job, line, refetch, technician, isPartsEntry }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updatePartPrice] = useMutation(UPDATE_LINE_PPC);
|
||||
const notification = useNotification();
|
||||
@@ -64,6 +66,7 @@ export function JobLinesPartPriceChange({ job, line, refetch, technician }) {
|
||||
|
||||
const popcontent =
|
||||
!technician &&
|
||||
!isPartsEntry &&
|
||||
InstanceRenderManager({
|
||||
imex: null,
|
||||
rome: (
|
||||
|
||||
@@ -481,48 +481,50 @@ export function JobLinesComponent({
|
||||
{Enhanced_Payroll.treatment === "on" && (
|
||||
<JobLineBulkAssignComponent selectedLines={selectedLines} setSelectedLines={setSelectedLines} job={job} />
|
||||
)}
|
||||
<Button
|
||||
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
|
||||
onClick={() => {
|
||||
setBillEnterContext({
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
disableInvNumber: true,
|
||||
job: { id: job.id },
|
||||
bill: {
|
||||
vendorid: bodyshop.inhousevendorid,
|
||||
invoice_number: "ih",
|
||||
isinhouse: true,
|
||||
date: dayjs(),
|
||||
total: 0,
|
||||
billlines: selectedLines.map((p) => {
|
||||
return {
|
||||
joblineid: p.id,
|
||||
actual_price: p.act_price,
|
||||
actual_cost: 0, //p.act_price,
|
||||
line_desc: p.line_desc,
|
||||
line_remarks: p.line_remarks,
|
||||
part_type: p.part_type,
|
||||
quantity: p.quantity || 1,
|
||||
applicable_taxes: {
|
||||
local: false,
|
||||
state: false,
|
||||
federal: false
|
||||
}
|
||||
};
|
||||
})
|
||||
{!isPartsEntry && (
|
||||
<Button
|
||||
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
|
||||
onClick={() => {
|
||||
setBillEnterContext({
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
disableInvNumber: true,
|
||||
job: { id: job.id },
|
||||
bill: {
|
||||
vendorid: bodyshop.inhousevendorid,
|
||||
invoice_number: "ih",
|
||||
isinhouse: true,
|
||||
date: dayjs(),
|
||||
total: 0,
|
||||
billlines: selectedLines.map((p) => {
|
||||
return {
|
||||
joblineid: p.id,
|
||||
actual_price: p.act_price,
|
||||
actual_cost: 0, //p.act_price,
|
||||
line_desc: p.line_desc,
|
||||
line_remarks: p.line_remarks,
|
||||
part_type: p.part_type,
|
||||
quantity: p.quantity || 1,
|
||||
applicable_taxes: {
|
||||
local: false,
|
||||
state: false,
|
||||
federal: false
|
||||
}
|
||||
};
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//Clear out the selected lines. IO-785
|
||||
setSelectedLines([]);
|
||||
}}
|
||||
>
|
||||
<HomeOutlined />
|
||||
{t("parts.actions.orderinhouse")}
|
||||
{selectedLines.length > 0 && ` (${selectedLines.length})`}
|
||||
</Button>
|
||||
//Clear out the selected lines. IO-785
|
||||
setSelectedLines([]);
|
||||
}}
|
||||
>
|
||||
<HomeOutlined />
|
||||
{t("parts.actions.orderinhouse")}
|
||||
{selectedLines.length > 0 && ` (${selectedLines.length})`}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
id="job-lines-order-parts-button"
|
||||
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
|
||||
@@ -578,7 +580,8 @@ export function JobLinesComponent({
|
||||
{t("joblines.actions.new")}
|
||||
</Button>
|
||||
)}
|
||||
{InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} disabled={technician} /> })}
|
||||
{!isPartsEntry &&
|
||||
InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} disabled={technician} /> })}
|
||||
<JobCreateIOU job={job} selectedJobLines={selectedLines} />
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
|
||||
@@ -20,35 +20,27 @@ export function JobTotalsCashDiscount({ bodyshop, amountDinero }) {
|
||||
const notification = useNotification();
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
if (amountDinero && bodyshop) {
|
||||
setLoading(true);
|
||||
let response;
|
||||
try {
|
||||
response = await axios.post("/intellipay/checkfee", {
|
||||
bodyshop: { id: bodyshop.id, imexshopid: bodyshop.imexshopid, state: bodyshop.state },
|
||||
amount: Dinero(amountDinero).toFormat("0.00")
|
||||
});
|
||||
if (!amountDinero || !bodyshop) return;
|
||||
|
||||
if (response?.data?.error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message:
|
||||
response.data?.error ||
|
||||
"Error encountered when contacting IntelliPay service to determine cash discounted price."
|
||||
});
|
||||
} else {
|
||||
setFee(response.data?.fee || 0);
|
||||
}
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message:
|
||||
error.response?.data?.error ||
|
||||
"Error encountered when contacting IntelliPay service to determine cash discounted price."
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setLoading(true);
|
||||
const errorMessage = "Error encountered when contacting IntelliPay service to determine cash discounted price.";
|
||||
|
||||
try {
|
||||
const { id, imexshopid, state } = bodyshop;
|
||||
const { data } = await axios.post("/intellipay/checkfee", {
|
||||
bodyshop: { id, imexshopid, state },
|
||||
amount: Dinero(amountDinero).toUnit()
|
||||
});
|
||||
|
||||
if (data?.error) {
|
||||
notification.open({ type: "error", message: data.error || errorMessage });
|
||||
} else {
|
||||
setFee(data?.fee ?? 0);
|
||||
}
|
||||
} catch (error) {
|
||||
notification.open({ type: "error", message: error.response?.data?.error || errorMessage });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [amountDinero, bodyshop, notification]);
|
||||
|
||||
|
||||
@@ -139,17 +139,18 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, is
|
||||
<DataLabel label={t("jobs.fields.comment")} valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
<ProductionListColumnComment record={job} />
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.ins_co_nm_short")}>{job.ins_co_nm}</DataLabel>
|
||||
{!isPartsEntry && <DataLabel label={t("jobs.fields.ins_co_nm_short")}>{job.ins_co_nm}</DataLabel>}
|
||||
<DataLabel label={t("jobs.fields.clm_no")}>{job.clm_no}</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.ponumber")} hideIfNull>
|
||||
{job.po_number}
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.repairtotal")}>
|
||||
<CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
|
||||
<span style={{ margin: "0rem .5rem" }}>/</span>
|
||||
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
|
||||
</DataLabel>
|
||||
|
||||
{!isPartsEntry && (
|
||||
<DataLabel label={t("jobs.fields.repairtotal")}>
|
||||
<CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
|
||||
<span style={{ margin: "0rem .5rem" }}>/</span>
|
||||
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
|
||||
</DataLabel>
|
||||
)}
|
||||
{!isPartsEntry && (
|
||||
<>
|
||||
<DataLabel label={t("jobs.fields.alt_transport")}>
|
||||
|
||||
@@ -9,7 +9,6 @@ import { connect } from "react-redux";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
@@ -144,26 +143,6 @@ export function SimplifiedPartsJobsListComponent({
|
||||
sortOrder: sortcolumn === "clm_no" && sortorder,
|
||||
render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}`
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.clm_total"),
|
||||
dataIndex: "clm_total",
|
||||
key: "clm_total",
|
||||
sorter: search?.search ? (a, b) => a.clm_total - b.clm_total : true,
|
||||
sortOrder: sortcolumn === "clm_total" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.clm_total ? (
|
||||
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
|
||||
) : (
|
||||
t("general.labels.unknown")
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.partsstatus"),
|
||||
dataIndex: "partsstatus",
|
||||
|
||||
@@ -173,7 +173,7 @@ export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete
|
||||
|
||||
<Form.Item
|
||||
name="tags"
|
||||
label={t("vendor.fields.tags")}
|
||||
label={t("vendors.fields.tags")}
|
||||
rules={[
|
||||
{
|
||||
//message: t("general.validation.required"),
|
||||
|
||||
197
server/integrations/partsManagement/endpoints/lib/opCodes.json
Normal file
197
server/integrations/partsManagement/endpoints/lib/opCodes.json
Normal file
@@ -0,0 +1,197 @@
|
||||
{
|
||||
"OP0": {
|
||||
"desc": "REMOVE / REPLACE PARTIAL",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAA"
|
||||
},
|
||||
"OP1": {
|
||||
"desc": "REFINISH / REPAIR",
|
||||
"opcode": "OP1",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP2": {
|
||||
"desc": "REMOVE / INSTALL",
|
||||
"opcode": "OP2",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP3": {
|
||||
"desc": "ADDITIONAL LABOR",
|
||||
"opcode": "OP9",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP4": {
|
||||
"desc": "ALIGNMENT",
|
||||
"opcode": "OP4",
|
||||
"partcode": "PAS"
|
||||
},
|
||||
"OP5": {
|
||||
"desc": "OVERHAUL",
|
||||
"opcode": "OP5",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP6": {
|
||||
"desc": "REFINISH",
|
||||
"opcode": "OP6",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP7": {
|
||||
"desc": "INSPECT",
|
||||
"opcode": "OP7",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP8": {
|
||||
"desc": "CHECK / ADJUST",
|
||||
"opcode": "OP8",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP9": {
|
||||
"desc": "REPAIR",
|
||||
"opcode": "OP9",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP10": {
|
||||
"desc": "REPAIR , PARTIAL",
|
||||
"opcode": "OP9",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP11": {
|
||||
"desc": "REMOVE / REPLACE",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAN"
|
||||
},
|
||||
"OP12": {
|
||||
"desc": "REMOVE / REPLACE PARTIAL",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAN"
|
||||
},
|
||||
"OP13": {
|
||||
"desc": "ADDITIONAL COSTS",
|
||||
"opcode": "OP13",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP14": {
|
||||
"desc": "ADDITIONAL OPERATIONS",
|
||||
"opcode": "OP14",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP15": {
|
||||
"desc": "BLEND",
|
||||
"opcode": "OP15",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP16": {
|
||||
"desc": "SUBLET",
|
||||
"opcode": "OP16",
|
||||
"partcode": "PAS"
|
||||
},
|
||||
"OP17": {
|
||||
"desc": "POLICY LIMIT ADJUSTMENT",
|
||||
"opcode": "OP9",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP18": {
|
||||
"desc": "APPEAR ALLOWANCE",
|
||||
"opcode": "OP7",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP20": {
|
||||
"desc": "REMOVE AND REINSTALL",
|
||||
"opcode": "OP20",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP24": {
|
||||
"desc": "CHIPGUARD",
|
||||
"opcode": "OP6",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP25": {
|
||||
"desc": "TWO TONE",
|
||||
"opcode": "OP6",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP26": {
|
||||
"desc": "PAINTLESS DENT REPAIR",
|
||||
"opcode": "OP16",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP100": {
|
||||
"desc": "REPLACE PRE-PRICED",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAA"
|
||||
},
|
||||
"OP101": {
|
||||
"desc": "REMOVE/REPLACE RECYCLED PART",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAL"
|
||||
},
|
||||
"OP103": {
|
||||
"desc": "REMOVE / REPLACE PARTIAL",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAA"
|
||||
},
|
||||
"OP104": {
|
||||
"desc": "REMOVE / REPLACE PARTIAL LABOUR",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAA"
|
||||
},
|
||||
"OP105": {
|
||||
"desc": "!!ADJUST MANUALLY!!",
|
||||
"opcode": "OP99",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP106": {
|
||||
"desc": "REPAIR , PARTIAL",
|
||||
"opcode": "OP9",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP107": {
|
||||
"desc": "CHIPGUARD",
|
||||
"opcode": "OP6",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP108": {
|
||||
"desc": "MULTI TONE",
|
||||
"opcode": "OP6",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP109": {
|
||||
"desc": "REPLACE PRE-PRICED",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAA"
|
||||
},
|
||||
"OP110": {
|
||||
"desc": "REFINISH / REPAIR",
|
||||
"opcode": "OP1",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP111": {
|
||||
"desc": "REMOVE / REPLACE",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAN"
|
||||
},
|
||||
"OP112": {
|
||||
"desc": "REMOVE / REPLACE",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAA"
|
||||
},
|
||||
"OP113": {
|
||||
"desc": "REPLACE PRE-PRICED",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAA"
|
||||
},
|
||||
"OP114": {
|
||||
"desc": "REPLACE PRE-PRICED",
|
||||
"opcode": "OP11",
|
||||
"partcode": "PAA"
|
||||
},
|
||||
"OP120": {
|
||||
"desc": "REPAIR , PARTIAL",
|
||||
"opcode": "OP9",
|
||||
"partcode": "PAE"
|
||||
},
|
||||
"OP260": {
|
||||
"desc": "SUBLET",
|
||||
"opcode": "OP16",
|
||||
"partcode": "PAE"
|
||||
}
|
||||
}
|
||||
@@ -129,15 +129,17 @@ const partsManagementDeprovisioning = async (req, res) => {
|
||||
const deletedUsers = [];
|
||||
for (const user of associatedUsers) {
|
||||
const countResp = await client.request(GET_USER_ASSOCIATIONS_COUNT, { userEmail: user.email });
|
||||
const assocCount = countResp.associations_aggregate.aggregate.count;
|
||||
if (assocCount === 0) {
|
||||
await client.request(DELETE_USER, { email: user.email });
|
||||
await deleteFirebaseUser(user.authId);
|
||||
deletedUsers.push(user.email);
|
||||
}
|
||||
// Determine which users now have zero associations and should be deleted (defer deletion until end)
|
||||
const emailsToAuthId = associatedUsers.reduce((acc, u) => {
|
||||
acc[u.email] = u.authId;
|
||||
return acc;
|
||||
}, {});
|
||||
const emailsToDelete = [];
|
||||
await client.request(DELETE_USER, { email: user.email });
|
||||
await deleteFirebaseUser(user.authId);
|
||||
deletedUsers.push(user.email);
|
||||
}
|
||||
|
||||
// Get all job ids for this shop, then delete joblines and jobs (joblines first)
|
||||
emailsToDelete.push(user.email);
|
||||
const jobIds = await getJobIdsForShop(body.shopId);
|
||||
const joblinesDeleted = await deleteJoblinesForJobs(jobIds);
|
||||
const jobsDeleted = await deleteJobsByIds(jobIds);
|
||||
@@ -174,6 +176,16 @@ const partsManagementDeprovisioning = async (req, res) => {
|
||||
deletedJobsCount: jobsDeleted,
|
||||
deletedAuditTrailCount: auditDeleted
|
||||
});
|
||||
// Now delete users that have no remaining associations and their Firebase accounts
|
||||
const deletedUsers = [];
|
||||
for (const email of emailsToDelete) {
|
||||
await client.request(DELETE_USER, { email });
|
||||
const authId = emailsToAuthId[email];
|
||||
if (authId) {
|
||||
await deleteFirebaseUser(authId);
|
||||
}
|
||||
deletedUsers.push(email);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.log("admin-delete-shop-error", "error", null, null, {
|
||||
message: err.message,
|
||||
|
||||
@@ -216,6 +216,36 @@ const GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ = `
|
||||
}
|
||||
`;
|
||||
|
||||
// Clear task links to parts orders for all jobs in a shop to avoid FK violations when deleting parts orders
|
||||
const CLEAR_TASKS_PARTSORDER_LINKS_BY_JOBIDS = `
|
||||
mutation ClearTasksPartsOrderLinks($jobIds: [uuid!]!) {
|
||||
update_tasks(
|
||||
where: { parts_order: { jobid: { _in: $jobIds } } },
|
||||
_set: { partsorderid: null }
|
||||
) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Delete parts order lines where the parent order belongs to any of the provided job IDs
|
||||
const DELETE_PARTS_ORDER_LINES_BY_JOB_IDS = `
|
||||
mutation DeletePartsOrderLinesByJobIds($jobIds: [uuid!]!) {
|
||||
delete_parts_order_lines(where: { parts_order: { jobid: { _in: $jobIds } } }) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Delete parts orders for the given job IDs
|
||||
const DELETE_PARTS_ORDERS_BY_JOB_IDS = `
|
||||
mutation DeletePartsOrdersByJobIds($jobIds: [uuid!]!) {
|
||||
delete_parts_orders(where: { jobid: { _in: $jobIds } }) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
module.exports = {
|
||||
GET_BODYSHOP_STATUS,
|
||||
GET_VEHICLE_BY_SHOP_VIN,
|
||||
@@ -241,5 +271,9 @@ module.exports = {
|
||||
DELETE_JOBS_BY_IDS,
|
||||
DELETE_AUDIT_TRAIL_BY_SHOP,
|
||||
GET_JOBLINES_NOTES_BY_JOBID_UNQSEQ,
|
||||
GET_JOB_BY_ID
|
||||
GET_JOB_BY_ID,
|
||||
// newly added exports
|
||||
CLEAR_TASKS_PARTSORDER_LINKS_BY_JOBIDS,
|
||||
DELETE_PARTS_ORDER_LINES_BY_JOB_IDS,
|
||||
DELETE_PARTS_ORDERS_BY_JOB_IDS
|
||||
};
|
||||
|
||||
@@ -252,35 +252,27 @@ const generatePaymentUrl = async (req, res) => {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const checkFee = async (req, res) => {
|
||||
const logResponseMeta = {
|
||||
bodyshop: {
|
||||
id: req.body?.bodyshop?.id,
|
||||
imexshopid: req.body?.bodyshop?.imexshopid,
|
||||
name: req.body?.bodyshop?.shopname,
|
||||
state: req.body?.bodyshop?.state
|
||||
},
|
||||
amount: req.body?.amount
|
||||
};
|
||||
const { bodyshop = {}, amount } = req.body || {};
|
||||
const { id, imexshopid, shopname, state } = bodyshop;
|
||||
const logResponseMeta = { bodyshop: { id, imexshopid, name: shopname, state }, amount };
|
||||
|
||||
logger.log("intellipay-checkfee-request-received", "DEBUG", req.user?.email, null, logResponseMeta);
|
||||
|
||||
if (!isNumber(req.body?.amount) || req.body?.amount <= 0) {
|
||||
if (!isNumber(amount) || amount <= 0) {
|
||||
logger.log("intellipay-checkfee-skip", "DEBUG", req.user?.email, null, {
|
||||
message: "Amount is zero or undefined, skipping fee check.",
|
||||
...logResponseMeta
|
||||
});
|
||||
|
||||
return res.json({ fee: 0 });
|
||||
}
|
||||
|
||||
const shopCredentials = await getShopCredentials(req.body.bodyshop);
|
||||
const shopCredentials = await getShopCredentials(bodyshop);
|
||||
|
||||
if (shopCredentials?.error) {
|
||||
logger.log("intellipay-checkfee-credentials-error", "ERROR", req.user?.email, null, {
|
||||
message: shopCredentials.error?.message,
|
||||
...logResponseMeta
|
||||
});
|
||||
|
||||
return res.status(400).json({ error: shopCredentials.error?.message, ...logResponseMeta });
|
||||
}
|
||||
|
||||
@@ -292,13 +284,10 @@ const checkFee = async (req, res) => {
|
||||
{
|
||||
method: "fee",
|
||||
...shopCredentials,
|
||||
amount: req.body.amount,
|
||||
paymenttype: `CC`,
|
||||
amount: String(amount), // Type cast to string as required by API
|
||||
paymenttype: "CC",
|
||||
cardnum: "4111111111111111", // Required for compatibility with API
|
||||
state:
|
||||
req.body.bodyshop?.state && req.body.bodyshop.state.length === 2
|
||||
? req.body.bodyshop.state.toUpperCase()
|
||||
: "ZZ"
|
||||
state: state?.toUpperCase() || "ZZ"
|
||||
},
|
||||
{ sort: false } // Ensure query string order is preserved
|
||||
),
|
||||
@@ -310,46 +299,24 @@ const checkFee = async (req, res) => {
|
||||
...logResponseMeta
|
||||
});
|
||||
|
||||
const response = await axios(options);
|
||||
const { data } = await axios(options);
|
||||
|
||||
if (response.data?.error) {
|
||||
logger.log("intellipay-checkfee-api-error", "ERROR", req.user?.email, null, {
|
||||
message: response.data?.error,
|
||||
...logResponseMeta
|
||||
});
|
||||
|
||||
return res.status(400).json({
|
||||
error: response.data?.error,
|
||||
type: "intellipay-checkfee-api-error",
|
||||
...logResponseMeta
|
||||
});
|
||||
if (data?.error || data < 0) {
|
||||
const errorType = data?.error ? "intellipay-checkfee-api-error" : "intellipay-checkfee-negative-fee";
|
||||
const errorMessage = data?.error
|
||||
? data?.error
|
||||
: "Fee amount negative. Check API credentials & account configuration.";
|
||||
logger.log(errorType, "ERROR", req.user?.email, null, { message: errorMessage, data, ...logResponseMeta });
|
||||
return res.status(400).json({ error: errorMessage, type: errorType, data, ...logResponseMeta });
|
||||
}
|
||||
|
||||
if (response.data < 0) {
|
||||
logger.log("intellipay-checkfee-negative-fee", "ERROR", req.user?.email, null, {
|
||||
message: "Fee amount returned is negative.",
|
||||
...logResponseMeta
|
||||
});
|
||||
|
||||
return res.json({
|
||||
error: "Fee amount negative. Check API credentials & account configuration.",
|
||||
...logResponseMeta,
|
||||
type: "intellipay-checkfee-negative-fee"
|
||||
});
|
||||
}
|
||||
|
||||
logger.log("intellipay-checkfee-success", "DEBUG", req.user?.email, null, {
|
||||
fee: response.data,
|
||||
...logResponseMeta
|
||||
});
|
||||
|
||||
return res.json({ fee: response.data, ...logResponseMeta });
|
||||
logger.log("intellipay-checkfee-success", "DEBUG", req.user?.email, null, { fee: data, ...logResponseMeta });
|
||||
return res.json({ fee: data, ...logResponseMeta });
|
||||
} catch (error) {
|
||||
logger.log("intellipay-checkfee-error", "ERROR", req.user?.email, null, {
|
||||
message: error?.message,
|
||||
...logResponseMeta
|
||||
});
|
||||
|
||||
return res.status(500).json({ error: error?.message, logResponseMeta });
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user