Compare commits

...

16 Commits

Author SHA1 Message Date
Allan Carr
cc623b7cbb IO-3627 Courtesy Car Create RO
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-03-24 20:12:26 -07:00
Allan Carr
9b1488ac3b Merged in feature/IO-3609-Bill-Cost-Calculation-Toggle (pull request #3151)
IO-3609 Bill Cost Calculation Toggle

Approved-by: Dave Richer
2026-03-24 17:50:06 +00:00
Allan Carr
7bab9bf4cb Merged in feature/IO-3623-Extend-Vendor-Discount (pull request #3152)
IO-3623 Extend Vendor Discount to Precision 3

Approved-by: Dave Richer
2026-03-24 17:49:40 +00:00
Allan Carr
8278242e6f Merged in feature/IO-3622-Employee-Delete-Rate (pull request #3153)
IO-3622 Employee Delete Rate

Approved-by: Dave Richer
2026-03-24 17:49:29 +00:00
Allan Carr
aa81cddcf1 IO-3622 Employee Delete Rate
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-03-23 16:46:28 -07:00
Allan Carr
85e60dcd6b IO-3623 Extend Vendor Discount to Precision 3
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-03-23 16:00:12 -07:00
Allan Carr
a005f1bb45 IO-3609 Bill Cost Calculation Toggle
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-03-23 15:52:49 -07:00
Dave
f904fa4e18 Merge branch 'feature/IO-3587-Commision-Cut-clean' into release/2026-04-03 2026-03-23 13:04:12 -04:00
Dave Richer
27ffee0c7a Merged in hotfix/2026-03-20 (pull request #3147)
Fix RR
2026-03-20 18:56:08 +00:00
Patrick Fic
187938286d Merged in feature/IO-3515-bill-ocr-feedback (pull request #3145)
IO-3515 Add shopname to bill ai feedback.
2026-03-20 18:15:23 +00:00
Patrick Fic
6ad0272135 Merged in feature/IO-3515-bill-ocr-feedback (pull request #3142)
Feature/IO-3515 bill ocr feedback

Approved-by: Dave Richer
2026-03-19 22:44:36 +00:00
Dave Richer
86db96f47d Merged in feature/IO-3587-Commision-Cut-clean (pull request #3143)
Feature/IO-3587 Commision Cut clean
2026-03-19 22:44:22 +00:00
Allan Carr
ac8c84543a Merged in feature/IO-3616-Production-Phone#-Deeplink (pull request #3139)
IO-3616 Production Phone# Deeplinking

Approved-by: Dave Richer
2026-03-19 18:46:15 +00:00
Dave Richer
78a2ff0fa1 Merged in feature/IO-3587-Commision-Cut-clean (pull request #3140)
Feature/IO-3587 Commision Cut clean
2026-03-19 18:46:02 +00:00
Dave Richer
dac1ed42df Merged in feature/IO-3587-Commision-Cut-clean (pull request #3138)
Feature/IO-3587 Commision Cut clean
2026-03-17 15:20:59 +00:00
Allan Carr
bc5f9f88d1 IO-3616 Production Phone# Deeplinking
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-03-17 00:30:07 -07:00
10 changed files with 183 additions and 108 deletions

View File

@@ -96,6 +96,7 @@ export function BillEnterModalLinesComponent({
// Only fill actual_cost when the user forward-tabs out of Retail (actual_price)
const autofillActualCost = (index) => {
if (bodyshop.accountingconfig?.disableBillCostCalculation) return;
Promise.resolve().then(() => {
const retailRaw = form.getFieldValue(["billlines", index, "actual_price"]);
const actualRaw = form.getFieldValue(["billlines", index, "actual_cost"]);

View File

@@ -10,6 +10,7 @@ import { createStructuredSelector } from "reselect";
import { INSERT_NEW_JOB } from "../../graphql/jobs.queries";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -156,104 +157,127 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled
joblines: {
data: billingLines
},
parts_tax_rates: {
PAA: {
prt_type: "PAA",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
...InstanceRenderManager({
imex: {
parts_tax_rates: {
PAA: {
prt_type: "PAA",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
PAC: {
prt_type: "PAC",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
PAL: {
prt_type: "PAL",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
PAM: {
prt_type: "PAM",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
PAN: {
prt_type: "PAN",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
PAR: {
prt_type: "PAR",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
PAS: {
prt_type: "PAS",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
CCDR: {
prt_type: "CCDR",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
CCF: {
prt_type: "CCF",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
CCM: {
prt_type: "CCM",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
CCC: {
prt_type: "CCC",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
CCD: {
prt_type: "CCD",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
}
}
},
PAC: {
prt_type: "PAC",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
PAL: {
prt_type: "PAL",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
PAM: {
prt_type: "PAM",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
PAN: {
prt_type: "PAN",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
PAR: {
prt_type: "PAR",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
PAS: {
prt_type: "PAS",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
CCDR: {
prt_type: "CCDR",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
CCF: {
prt_type: "CCF",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
CCM: {
prt_type: "CCM",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
CCC: {
prt_type: "CCC",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
},
CCD: {
prt_type: "CCD",
prt_discp: 0,
prt_mktyp: false,
prt_mkupp: 0,
prt_tax_in: true,
prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100
rome: {
cieca_pft: {
...bodyshop.md_responsibility_centers.taxes.tax_ty1,
...bodyshop.md_responsibility_centers.taxes.tax_ty2,
...bodyshop.md_responsibility_centers.taxes.tax_ty3,
...bodyshop.md_responsibility_centers.taxes.tax_ty4,
...bodyshop.md_responsibility_centers.taxes.tax_ty5
},
materials: bodyshop.md_responsibility_centers.cieca_pfm,
cieca_pfl: bodyshop.md_responsibility_centers.cieca_pfl,
parts_tax_rates: bodyshop.md_responsibility_centers.parts_tax_rates,
tax_tow_rt: bodyshop.md_responsibility_centers.tax_tow_rt,
tax_str_rt: bodyshop.md_responsibility_centers.tax_str_rt,
tax_paint_mat_rt: bodyshop.md_responsibility_centers.tax_paint_mat_rt,
tax_shop_mat_rt: bodyshop.md_responsibility_centers.tax_shop_mat_rt,
tax_sub_rt: bodyshop.md_responsibility_centers.tax_sub_rt,
tax_lbr_rt: bodyshop.md_responsibility_centers.tax_lbr_rt,
tax_levies_rt: bodyshop.md_responsibility_centers.tax_levies_rt
}
}
})
};
if (currentUser?.email) {
@@ -287,7 +311,7 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled
notification.success({
title: t("jobs.successes.created"),
onClick: () => {
history.push(`/manage/jobs/${result.data.insert_jobs.returning[0].id}`);
history(`/manage/jobs/${result.data.insert_jobs.returning[0].id}`);
}
});
}

View File

@@ -6,7 +6,7 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import { store } from "../../redux/store";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { TimeFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
@@ -28,6 +28,7 @@ import ProductionListColumnCategory from "./production-list-columns.status.categ
import ProductionListColumnStatus from "./production-list-columns.status.component";
import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component";
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
import ChatOpenButton from "../chat-open-button/chat-open-button.component.jsx";
const getEmployeeName = (employeeId, employees) => {
const employee = employees.find((e) => e.id === employeeId);
@@ -271,14 +272,24 @@ const productionListColumnsData = ({ technician, state, activeStatuses, data, bo
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
render: (text, record) => <PhoneFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneFormatter>
render: (text, record) =>
technician ? (
<PhoneNumberFormatter type={record.ownr_ph1_ty}>{record.ownr_ph1}</PhoneNumberFormatter>
) : (
<ChatOpenButton type={record.ownr_ph1_ty} phone={record.ownr_ph1} jobid={record.id} />
)
},
{
title: i18n.t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
render: (text, record) => <PhoneFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneFormatter>
render: (text, record) =>
technician ? (
<PhoneNumberFormatter type={record.ownr_ph2_ty}>{record.ownr_ph2}</PhoneNumberFormatter>
) : (
<ChatOpenButton type={record.ownr_ph2_ty} phone={record.ownr_ph2} jobid={record.id} />
)
},
{
title: i18n.t("jobs.fields.specialcoveragepolicy"),

View File

@@ -316,9 +316,8 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
<LayoutFormRow grow>
<Form.Item
label={t("employees.fields.cost_center")}
key={`${index}`}
key={`${field.key}-cost_center`}
name={[field.name, "cost_center"]}
valuePropName="value"
rules={[
{
required: true
@@ -343,7 +342,7 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
</Form.Item>
<Form.Item
label={t("employees.fields.rate")}
key={`${index}`}
key={`${field.key}-rate`}
name={[field.name, "rate"]}
rules={[
{

View File

@@ -342,6 +342,14 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
valuePropName="checked"
>
<Switch />
</Form.Item>,
<Form.Item
key="disableBillCostCalculation"
name={["accountingconfig", "disableBillCostCalculation"]}
label={t("bodyshop.fields.disableBillCostCalculation")}
valuePropName="checked"
>
<Switch />
</Form.Item>
]
: []),

View File

@@ -152,7 +152,7 @@ export function VendorsFormComponent({ bodyshop, form, formLoading, handleDelete
{!isPartsEntry && (
<>
<Form.Item label={t("vendors.fields.discount")} name="discount">
<InputNumber min={0} max={1} precision={2} step={0.01} />
<InputNumber min={0} max={1} precision={3} step={0.01} />
</Form.Item>
<Form.Item label={t("vendors.fields.due_date")} name="due_date">

View File

@@ -339,6 +339,7 @@
"require_actual_delivery_date": "Require Actual Delivery",
"templates": "Delivery Templates"
},
"disableBillCostCalculation": "Disable Automatic Bill Cost Calculation",
"dms": {
"apcontrol": "AP Control Number",
"appostingaccount": "AP Posting Account",

View File

@@ -339,6 +339,7 @@
"require_actual_delivery_date": "",
"templates": ""
},
"disableBillCostCalculation": "",
"dms": {
"apcontrol": "",
"appostingaccount": "",

View File

@@ -339,6 +339,7 @@
"require_actual_delivery_date": "",
"templates": ""
},
"disableBillCostCalculation": "",
"dms": {
"apcontrol": "",
"appostingaccount": "",

View File

@@ -885,6 +885,8 @@ function CalculateTaxesTotals(job, otherTotals) {
STOR: Dinero()
};
const pfp = job.parts_tax_rates;
//For each line, determine if it's taxable, and if it is, add the line amount to the taxable amounts total.
job.joblines
.filter((jl) => !jl.removed)
@@ -916,7 +918,17 @@ function CalculateTaxesTotals(job, otherTotals) {
})
.multiply(val.part_qty || 0)
.add(discMarkupAmount);
taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add(partAmount);
if (taxableAmounts[typeOfPart]) {
taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add(partAmount);
} else {
const isTaxableCustomType =
IsTrueOrYes(pfp?.[typeOfPart]?.prt_tax_in) || pfp?.[typeOfPart]?.prt_tax_in === true;
if (isTaxableCustomType) {
if (!taxableAmounts[typeOfPart]) taxableAmounts[typeOfPart] = Dinero();
taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add(partAmount);
}
}
}
});
@@ -969,13 +981,15 @@ function CalculateTaxesTotals(job, otherTotals) {
})
);
const pfp = job.parts_tax_rates;
//For any profile level markups/discounts, add them in now as well.
Object.keys(otherTotals.parts.adjustments).forEach((key) => {
const adjustmentAmount = otherTotals.parts.adjustments[key];
if (adjustmentAmount.getAmount() !== 0 && pfp[key]?.prt_tax_in) {
taxableAmounts[key] = taxableAmounts[key].add(adjustmentAmount);
if (taxableAmounts[key]) {
taxableAmounts[key] = taxableAmounts[key].add(adjustmentAmount);
} else {
taxableAmounts[key] = Dinero().add(adjustmentAmount);
}
}
});
@@ -1072,6 +1086,21 @@ function CalculateTaxesTotals(job, otherTotals) {
);
}
}
} else if (pfp[key]) {
//Custom part types (e.g. CC*) should flow through taxableAmountsByTier too.
let assignedToTier = false;
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
if (IsTrueOrYes(pfp[key][`prt_tx_in${tyCounter}`])) {
taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add(
taxableAmounts[key]
);
assignedToTier = true;
}
}
if (!assignedToTier && (IsTrueOrYes(pfp[key].prt_tax_in) || pfp[key].prt_tax_in === true)) {
taxableAmountsByTier.ty1Tax = taxableAmountsByTier.ty1Tax.add(taxableAmounts[key]);
}
}
} catch (error) {
logger.log("job-totals-USA Key with issue", "warn", null, job.id, {