feature/IO-999-Part-Tax-rate-Label

This commit is contained in:
Dave Richer
2025-01-17 09:52:35 -08:00
65 changed files with 12074 additions and 11298 deletions

View File

@@ -85,6 +85,17 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
sortOrder: state.sortedInfo.columnKey === "amount" && state.sortedInfo.order,
render: (text, record) => <CurrencyFormatter>{record.amount}</CurrencyFormatter>
},
{
title: t("payments.fields.type"),
dataIndex: "type",
key: "type",
sorter: (a, b) => a.type.localeCompare(b.type),
sortOrder: state.sortedInfo.columnKey === "type" && state.sortedInfo.order,
filters: bodyshop.md_payment_types.map((s) => {
return { text: s, value: [s] };
}),
onFilter: (value, record) => value.includes(record.type)
},
{
title: t("payments.fields.memo"),
dataIndex: "memo",

View File

@@ -1,30 +1,30 @@
import { AlertFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Divider, Dropdown, Form, Input, notification, Popover, Select, Space } from "antd";
import parsePhoneNumber from "libphonenumber-js";
import dayjs from "../../utils/day";
import queryString from "query-string";
import React, { useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import dayjs from "../../utils/day";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.component";
import ScheduleEventNote from "./schedule-event.note.component";
import { useMutation } from "@apollo/client";
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -127,6 +127,7 @@ export function ScheduleEventComponent({
<DataLabel label={t("jobs.fields.ownr_ph2")}>
<ChatOpenButton phone={event.job && event.job.ownr_ph2} jobid={event.job.id} />
</DataLabel>
<DataLabel hideIfNull label={t("jobs.fields.loss_of_use")}>{(event.job && event.job.loss_of_use) || ""}</DataLabel>
<DataLabel label={t("jobs.fields.alt_transport")}>
{(event.job && event.job.alt_transport) || ""}
<ScheduleAtChange job={event && event.job} />

View File

@@ -192,6 +192,23 @@ export function JobLinesUpsertModalComponent({ bodyshop, open, jobLine, handleCa
<Form.Item label={t("joblines.fields.tax_part")} name="tax_part" valuePropName="checked" initialValue={true}>
<Switch />
</Form.Item>
<Form.Item dependencies={[["act_price"]]} noStyle>
{() => {
if (form.getFieldValue("act_price") === 0) {
return (
<Form.Item
label={t("joblines.fields.include_in_part_cnt")}
name="include_in_part_cnt"
valuePropName="checked"
>
<Switch />
</Form.Item>
);
} else {
return null;
}
}}
</Form.Item>
</LayoutFormRow>
</Form>
</Modal>

View File

@@ -173,7 +173,7 @@ export function JobsCloseExportButton({
}
});
if (!!!jobUpdateResponse.errors) {
if (!jobUpdateResponse.errors) {
notification.open({
type: "success",
key: "jobsuccessexport",
@@ -222,7 +222,7 @@ export function JobsCloseExportButton({
};
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
{t("jobs.actions.export")}
</Button>
);

View File

@@ -10,8 +10,8 @@ import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_JOBS } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({
@@ -165,7 +165,7 @@ export function JobsExportAllButton({
}
});
if (!!!jobUpdateResponse.errors) {
if (!jobUpdateResponse.errors) {
notification.open({
type: "success",
key: "jobsuccessexport",
@@ -213,13 +213,13 @@ export function JobsExportAllButton({
})
);
if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false);
if (completedCallback) completedCallback([]);
if (loadingCallback) loadingCallback(false);
setLoading(false);
};
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10} type="primary">
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -1,6 +1,5 @@
import { EditFilled } from "@ant-design/icons";
import { Alert, Card, Col, Row, Space, Table, Typography } from "antd";
import _ from "lodash";
import React, { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -9,11 +8,11 @@ import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
import "./labor-allocations-table.styles.scss";
import { CalculateAllocationsTotals } from "./labor-allocations-table.utility";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician
@@ -65,6 +64,7 @@ export function LaborAllocationsTable({
key: "total",
sorter: (a, b) => a.total - b.total,
sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
align: "right",
render: (text, record) => record.total.toFixed(1)
},
{
@@ -73,6 +73,7 @@ export function LaborAllocationsTable({
key: "hrs_claimed",
sorter: (a, b) => a.claimed - b.claimed,
sortOrder: state.sortedInfo.columnKey === "claimed" && state.sortedInfo.order,
align: "right",
render: (text, record) => record.claimed && record.claimed.toFixed(1)
},
{
@@ -81,6 +82,7 @@ export function LaborAllocationsTable({
key: "adjustments",
sorter: (a, b) => a.adjustments - b.adjustments,
sortOrder: state.sortedInfo.columnKey === "adjustments" && state.sortedInfo.order,
align: "right",
render: (text, record) => (
<Space wrap>
{record.adjustments.toFixed(1)}
@@ -100,17 +102,17 @@ export function LaborAllocationsTable({
{
title: t("jobs.labels.difference"),
dataIndex: "difference",
key: "difference",
sorter: (a, b) => a.difference - b.difference,
sortOrder: state.sortedInfo.columnKey === "difference" && state.sortedInfo.order,
align: "right",
render: (text, record) => (
<strong
style={{
color: record.difference >= 0 ? "green" : "red"
color: record.difference.toFixed(1) >= 0 ? "green" : "red"
}}
>
{_.round(record.difference, 1)}
{(Math.abs(record.difference) < 0.05 ? 0 : record.difference).toFixed(1)}
</strong>
)
}
@@ -129,7 +131,6 @@ export function LaborAllocationsTable({
ellipsis: true,
render: (text, record) => `${record.op_code_desc || ""}${record.alt_partm ? ` ${record.alt_partm}` : ""}`
},
{
title: t("joblines.fields.act_price"),
dataIndex: "act_price",
@@ -187,7 +188,7 @@ export function LaborAllocationsTable({
{ hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 }
);
if (summary.difference !== 0 && typeof warningCallback === "function") {
if (Math.abs(summary.difference.toFixed(1)) !== 0 && typeof warningCallback === "function") {
warningCallback({ key: "labor", warning: t("jobs.labels.outstandinghours") });
}
@@ -217,19 +218,21 @@ export function LaborAllocationsTable({
summary={() => (
<Table.Summary.Row>
<Table.Summary.Cell>
<Typography.Title level={4}>{t("general.labels.totals")}</Typography.Title>
<Typography.Title level={4} style={{ margin: 0, lineHeight: 1 }}>
{t("general.labels.totals")}
</Typography.Title>
</Table.Summary.Cell>
<Table.Summary.Cell>{summary.hrs_total.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell>{summary.hrs_claimed.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell>{summary.adjustments.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell>
<Table.Summary.Cell align="right">{summary.hrs_total.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell align="right">{summary.hrs_claimed.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell align="right">{summary.adjustments.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell align="right">
<Typography.Text
style={{
fontWeight: "bold",
color: summary.difference >= 0 ? "green" : "red"
color: summary.difference.toFixed(1) >= 0 ? "green" : "red"
}}
>
{summary.difference.toFixed(1)}
{(Math.abs(summary.difference) < 0.05 ? 0 : summary.difference).toFixed(1)}
</Typography.Text>
</Table.Summary.Cell>
</Table.Summary.Row>
@@ -261,11 +264,10 @@ export function LaborAllocationsTable({
</Card>
</Col>
)}
{showWarning && summary.difference !== 0 && (
{showWarning && Math.abs(summary.difference.toFixed(1)) !== 0 && (
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstandinghours")} />
)}
</Row>
);
}
export default connect(mapStateToProps, null)(LaborAllocationsTable);

View File

@@ -48,7 +48,7 @@ export function PayableExportAll({
let PartnerResponse;
setLoading(true);
if (!!loadingCallback) loadingCallback(true);
if (loadingCallback) loadingCallback(true);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/payables`, {
bills: billids,
@@ -85,7 +85,7 @@ export function PayableExportAll({
notification["error"]({
message: t("bills.errors.exporting-partner")
});
if (!!loadingCallback) loadingCallback(false);
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
@@ -152,7 +152,7 @@ export function PayableExportAll({
}
}
});
if (!!!billUpdateResponse.errors) {
if (!billUpdateResponse.errors) {
notification.open({
type: "success",
key: "billsuccessexport",
@@ -187,8 +187,8 @@ export function PayableExportAll({
});
await Promise.all(proms);
if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false);
if (completedCallback) completedCallback([]);
if (loadingCallback) loadingCallback(false);
setLoading(false);
};
@@ -200,7 +200,7 @@ export function PayableExportAll({
);
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10} type="primary">
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -46,7 +46,7 @@ export function PayableExportButton({
logImEXEvent("accounting_export_payable");
setLoading(true);
if (!!loadingCallback) loadingCallback(true);
if (loadingCallback) loadingCallback(true);
//Check if it's a QBO Setup.
let PartnerResponse;
@@ -88,7 +88,7 @@ export function PayableExportButton({
notification["error"]({
message: t("bills.errors.exporting-partner")
});
if (!!loadingCallback) loadingCallback(false);
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
@@ -149,7 +149,7 @@ export function PayableExportButton({
}
}
});
if (!!!billUpdateResponse.errors) {
if (!billUpdateResponse.errors) {
notification.open({
type: "success",
key: "billsuccessexport",
@@ -186,7 +186,7 @@ export function PayableExportButton({
}
}
if (!!loadingCallback) loadingCallback(false);
if (loadingCallback) loadingCallback(false);
setLoading(false);
};
@@ -198,7 +198,7 @@ export function PayableExportButton({
);
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
{t("jobs.actions.export")}
</Button>
);

View File

@@ -55,7 +55,7 @@ export function PaymentExportButton({
} else {
//Default is QBD
if (!!loadingCallback) loadingCallback(true);
if (loadingCallback) loadingCallback(true);
let QbXmlResponse;
try {
@@ -88,7 +88,7 @@ export function PaymentExportButton({
notification["error"]({
message: t("payments.errors.exporting-partner")
});
if (!!loadingCallback) loadingCallback(false);
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
@@ -148,7 +148,7 @@ export function PaymentExportButton({
}
}
});
if (!!!paymentUpdateResponse.errors) {
if (!paymentUpdateResponse.errors) {
notification.open({
type: "success",
key: "paymentsuccessexport",
@@ -184,12 +184,12 @@ export function PaymentExportButton({
)
]);
}
if (!!loadingCallback) loadingCallback(false);
if (loadingCallback) loadingCallback(false);
setLoading(false);
};
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
{t("jobs.actions.export")}
</Button>
);

View File

@@ -44,7 +44,7 @@ export function PaymentsExportAllButton({
const handleQbxml = async () => {
setLoading(true);
if (!!loadingCallback) loadingCallback(true);
if (loadingCallback) loadingCallback(true);
let PartnerResponse;
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/payments`, {
@@ -76,7 +76,7 @@ export function PaymentsExportAllButton({
notification["error"]({
message: t("payments.errors.exporting-partner")
});
if (!!loadingCallback) loadingCallback(false);
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
@@ -140,7 +140,7 @@ export function PaymentsExportAllButton({
}
}
});
if (!!!paymentUpdateResponse.errors) {
if (!paymentUpdateResponse.errors) {
notification.open({
type: "success",
key: "paymentsuccessexport",
@@ -174,13 +174,13 @@ export function PaymentsExportAllButton({
);
});
await Promise.all(proms);
if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false);
if (completedCallback) completedCallback([]);
if (loadingCallback) loadingCallback(false);
setLoading(false);
};
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10} type="primary">
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -555,7 +555,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
</Form.Item>
<Form.Item
name={["md_email_cc", "parts_order"]}
label={t("bodyshop.fields.md_email_cc", { template: "parts_order" })}
label={t("bodyshop.fields.md_email_cc", { template: "parts_orders" })}
rules={[
{
//message: t("general.validation.required"),
@@ -567,9 +567,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
</Form.Item>
<Form.Item
name={["md_email_cc", "parts_return_slip"]}
label={t("bodyshop.fields.md_email_cc", {
template: "parts_return_slip"
})}
label={t("bodyshop.fields.md_email_cc", { template: "parts_returns" })}
rules={[
{
//message: t("general.validation.required"),
@@ -1261,7 +1259,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
key={`${index}prt_dsmk_p`}
name={[field.name, "prt_dsmk_p"]}
>
<InputNumber precision={0} min={0} max={100} />
<InputNumber precision={0} min={-100} max={100} />
</Form.Item>
<Form.Item
label={t("joblines.fields.ah_detail_line")}

View File

@@ -73,6 +73,7 @@ export const QUERY_PAYMENTS_FOR_EXPORT = gql`
transactionid
paymentnum
date
type
exportlogs {
id
successful

View File

@@ -49,6 +49,7 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
est_ct_fn
est_ct_ln
comment
loss_of_use
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
aggregate {
sum {

View File

@@ -238,6 +238,7 @@ export const UPDATE_JOB_LINE = gql`
convertedtolbr
convertedtolbr_data
assigned_team
include_in_part_cnt
}
}
}

View File

@@ -581,6 +581,7 @@ export const GET_JOB_BY_PK = gql`
status
tax_part
unq_seq
include_in_part_cnt
}
kmin
kmout

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1444,7 +1444,8 @@ export const TemplateList = (type, context) => {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_exported")
},
group: "sales"
group: "sales",
featureNameRestricted: "export"
},
gsr_by_estimator: {
title: i18n.t("reportcenter.templates.gsr_by_estimator"),
@@ -1865,7 +1866,8 @@ export const TemplateList = (type, context) => {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open")
},
group: "jobs"
group: "jobs",
featureNameRestricted: "bills"
},
psr_by_make: {
title: i18n.t("reportcenter.templates.psr_by_make"),
@@ -1901,7 +1903,8 @@ export const TemplateList = (type, context) => {
object: i18n.t("reportcenter.labels.objects.parts_orders"),
field: i18n.t("parts_orders.fields.order_date")
},
group: "jobs"
group: "jobs",
featureNameRestricted: "bills"
},
returns_grouped_by_vendor_detailed: {
title: i18n.t("reportcenter.templates.returns_grouped_by_vendor_detailed"),
@@ -1913,7 +1916,8 @@ export const TemplateList = (type, context) => {
object: i18n.t("reportcenter.labels.objects.parts_orders"),
field: i18n.t("parts_orders.fields.order_date")
},
group: "jobs"
group: "jobs",
featureNameRestricted: "bills"
},
scheduled_parts_list: {
title: i18n.t("reportcenter.templates.scheduled_parts_list"),
@@ -2225,7 +2229,19 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_open")
},
group: "jobs"
}
},
purchases_by_date_excel: {
title: i18n.t("reportcenter.templates.purchases_by_date_excel"),
subject: i18n.t("reportcenter.templates.purchases_by_date_excel"),
key: "purchases_by_date_excel",
reporttype: "excel",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.bills"),
field: i18n.t("bills.fields.date")
},
group: "purchases"
},
}
: {}),
...(!type || type === "courtesycarcontract"