Compare commits
30 Commits
release/20
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62d5c17de2 | ||
|
|
87c934c886 | ||
|
|
e4b736d4e9 | ||
|
|
d0673bfcba | ||
|
|
fe6e85e993 | ||
|
|
b744720efe | ||
|
|
d5c27fc9ae | ||
|
|
129c94f066 | ||
|
|
503e901295 | ||
|
|
85caf828ea | ||
|
|
92b89af1c7 | ||
|
|
b03d729af6 | ||
|
|
5c22ce188b | ||
|
|
faef05a95b | ||
|
|
64aa8336b3 | ||
|
|
8700e9a9ae | ||
|
|
d92f706d05 | ||
|
|
a62fd5d9b2 | ||
|
|
9fec6bbe0c | ||
|
|
e379ecfba6 | ||
|
|
4ef68a0e26 | ||
|
|
f36926f228 | ||
|
|
22cd7fcef1 | ||
|
|
2586393b4c | ||
|
|
0b448e043c | ||
|
|
f89e39759a | ||
|
|
ce783fdc5a | ||
|
|
8aa99415b1 | ||
|
|
2ede54b901 | ||
|
|
19329e76b7 |
@@ -19,3 +19,6 @@ npx deadfile ./src/index.js --exclude build templates
|
||||
hasura migrate create "Init" --from-server --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||
hasura migrate apply --version "1620771761757" --skip-execution --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||
hasura migrate status --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||
|
||||
Generate the license file:
|
||||
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite
|
||||
|
||||
@@ -40555,6 +40555,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>parts_not_recieved_vendor</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>payments_by_date</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -41535,6 +41556,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>entries</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>jobs</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.6.6",
|
||||
"@apollo/client": "^3.6.9",
|
||||
"@asseinfo/react-kanban": "^2.2.0",
|
||||
"@craco/craco": "^6.4.3",
|
||||
"@craco/craco": "^6.4.5",
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@sentry/react": "^7.1.1",
|
||||
"@sentry/tracing": "^7.1.1",
|
||||
"@splitsoftware/splitio-react": "^1.4.1",
|
||||
"@stripe/react-stripe-js": "^1.8.1",
|
||||
"@stripe/stripe-js": "^1.31.0",
|
||||
"@tanem/react-nprogress": "^5.0.1",
|
||||
"antd": "^4.21.0",
|
||||
"@sentry/react": "^7.7.0",
|
||||
"@sentry/tracing": "^7.7.0",
|
||||
"@splitsoftware/splitio-react": "^1.6.0",
|
||||
"@stripe/react-stripe-js": "^1.9.0",
|
||||
"@stripe/stripe-js": "^1.32.0",
|
||||
"@tanem/react-nprogress": "^5.0.8",
|
||||
"antd": "^4.22.3",
|
||||
"apollo-link-logger": "^2.0.0",
|
||||
"axios": "^0.27.2",
|
||||
"craco-less": "^1.20.0",
|
||||
@@ -24,33 +24,33 @@
|
||||
"enquire-js": "^0.2.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^9.8.2",
|
||||
"firebase": "^9.9.1",
|
||||
"graphql": "^16.5.0",
|
||||
"i18next": "^21.8.9",
|
||||
"i18next": "^21.8.14",
|
||||
"i18next-browser-languagedetector": "^6.1.4",
|
||||
"jsoneditor": "^9.8.0",
|
||||
"jsoneditor": "^9.9.0",
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"libphonenumber-js": "^1.10.6",
|
||||
"logrocket": "^3.0.0",
|
||||
"markerjs2": "^2.21.4",
|
||||
"libphonenumber-js": "^1.10.9",
|
||||
"logrocket": "^3.0.1",
|
||||
"markerjs2": "^2.22.0",
|
||||
"moment-business-days": "^1.2.0",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"normalize-url": "^7.0.3",
|
||||
"phone": "^3.1.20",
|
||||
"phone": "^3.1.23",
|
||||
"preval.macro": "^5.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^7.1.1",
|
||||
"rc-queue-anim": "^2.0.0",
|
||||
"rc-scroll-anim": "^2.7.6",
|
||||
"react": "^17.0.2",
|
||||
"react-big-calendar": "^0.40.1",
|
||||
"react-big-calendar": "^1.5.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-drag-listview": "^0.2.1",
|
||||
"react-grid-gallery": "^0.5.5",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-i18next": "^11.17.0",
|
||||
"react-i18next": "^11.18.1",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-number-format": "^4.9.3",
|
||||
"react-redux": "^7.2.8",
|
||||
@@ -60,13 +60,13 @@
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-sublime-video": "^0.2.5",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"recharts": "^2.1.10",
|
||||
"recharts": "^2.1.12",
|
||||
"redux": "^4.2.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.1.3",
|
||||
"redux-state-sync": "^3.1.2",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^4.1.6",
|
||||
"sass": "^1.51.0",
|
||||
"sass": "^1.54.0",
|
||||
"socket.io-client": "^4.5.1",
|
||||
"styled-components": "^5.3.5",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
@@ -119,9 +119,9 @@
|
||||
"react-error-overlay": "6.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/webpack-plugin": "^1.18.9",
|
||||
"@testing-library/cypress": "^8.0.2",
|
||||
"cypress": "^9.6.1",
|
||||
"@sentry/webpack-plugin": "^1.19.0",
|
||||
"@testing-library/cypress": "^8.0.3",
|
||||
"cypress": "^10.3.1",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"react-error-overlay": "6.0.11",
|
||||
"redux-logger": "^3.0.6",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,10 @@ const BillLineSearchSelect = (
|
||||
option.oem_partno
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())) ||
|
||||
(option.alt_partno &&
|
||||
option.alt_partno
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())) ||
|
||||
(option.act_price &&
|
||||
option.act_price.toString().startsWith(inputValue.toString()))
|
||||
);
|
||||
@@ -48,14 +52,17 @@ const BillLineSearchSelect = (
|
||||
line_desc={item.line_desc}
|
||||
part_qty={item.part_qty}
|
||||
oem_partno={item.oem_partno}
|
||||
alt_partno={item.alt_partno}
|
||||
act_price={item.act_price}
|
||||
style={{
|
||||
...(item.removed ? { textDecoration: "line-through" } : {}),
|
||||
}}
|
||||
>
|
||||
<span>{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}`}</span>
|
||||
<span>
|
||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
|
||||
</span>
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||
{item.act_price
|
||||
? `$${item.act_price && item.act_price.toFixed(2)}`
|
||||
|
||||
@@ -9,6 +9,7 @@ import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component";
|
||||
import JobDocumentsLocalGalleryExternal from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -58,17 +59,24 @@ export function ChatMediaSelector({
|
||||
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
|
||||
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
|
||||
) : null}
|
||||
{data && (
|
||||
{!bodyshop.uselocalmediaserver && data && (
|
||||
<JobDocumentsGalleryExternal
|
||||
data={data ? data.documents : []}
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
/>
|
||||
)}
|
||||
{bodyshop.uselocalmediaserver && visible && (
|
||||
<JobDocumentsLocalGalleryExternal
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
jobId={
|
||||
conversation.job_conversations[0] &&
|
||||
conversation.job_conversations[0].jobid
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (bodyshop.uselocalmediaserver) return null;
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
|
||||
@@ -5,12 +5,15 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
|
||||
import { selectEmailConfig } from "../../redux/email/email.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component";
|
||||
import JobsDocumentsLocalGalleryExternalComponent from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
emailConfig: selectEmailConfig,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@@ -25,6 +28,7 @@ export function EmailDocumentsComponent({
|
||||
emailConfig,
|
||||
form,
|
||||
selectedMediaState,
|
||||
bodyshop,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -52,12 +56,18 @@ export function EmailDocumentsComponent({
|
||||
10485760 - new Blob([form.getFieldValue("html")]).size ? (
|
||||
<div style={{ color: "red" }}>{t("general.errors.sizelimit")}</div>
|
||||
) : null}
|
||||
{data && (
|
||||
{!bodyshop.uselocalmediaserver && data && (
|
||||
<JobDocumentsGalleryExternal
|
||||
data={data ? data.documents : []}
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
/>
|
||||
)}
|
||||
{bodyshop.uselocalmediaserver && (
|
||||
<JobsDocumentsLocalGalleryExternalComponent
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
jobId={emailConfig.jobid}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -160,14 +160,13 @@ export function EmailOverlayComponent({
|
||||
</Form.Item>
|
||||
|
||||
<Tabs>
|
||||
{!bodyshop.uselocalmediaserver && (
|
||||
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
|
||||
<EmailDocumentsComponent
|
||||
selectedMediaState={selectedMediaState}
|
||||
form={form}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
)}
|
||||
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
|
||||
<EmailDocumentsComponent
|
||||
selectedMediaState={selectedMediaState}
|
||||
form={form}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane tab={t("emails.labels.attachments")} key="attachments">
|
||||
{bodyshop.uselocalmediaserver && emailConfig.jobid && (
|
||||
<a href={CreateExplorerLinkForJob({ jobid: emailConfig.jobid })}>
|
||||
|
||||
@@ -144,11 +144,26 @@ export function JobLinesComponent({
|
||||
filters: [
|
||||
{
|
||||
text: t("jobs.labels.partsfilter"),
|
||||
value: ["PAN", "PAP", "PAL", "PAA", "PAS", "PASL"],
|
||||
value: [
|
||||
"PAN",
|
||||
"PAC",
|
||||
"PAR",
|
||||
"PAL",
|
||||
"PAA",
|
||||
"PAM",
|
||||
"PAP",
|
||||
"PAS",
|
||||
"PASL",
|
||||
"PAG",
|
||||
],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAN"),
|
||||
value: ["PAN", "PAP"],
|
||||
value: ["PAN"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAP"),
|
||||
value: ["PAP"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAL"),
|
||||
@@ -158,9 +173,29 @@ export function JobLinesComponent({
|
||||
text: t("joblines.fields.part_types.PAA"),
|
||||
value: ["PAA"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAG"),
|
||||
value: ["PAG"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAS"),
|
||||
value: ["PAS", "PASL"],
|
||||
value: ["PAS"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PASL"),
|
||||
value: ["PASL"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAC"),
|
||||
value: ["PAC"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAR"),
|
||||
value: ["PAR"],
|
||||
},
|
||||
{
|
||||
text: t("joblines.fields.part_types.PAM"),
|
||||
value: ["PAM"],
|
||||
},
|
||||
],
|
||||
onFilter: (value, record) => value.includes(record.part_type),
|
||||
@@ -346,7 +381,11 @@ export function JobLinesComponent({
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
setState((state) => ({
|
||||
...state,
|
||||
filteredInfo: filters,
|
||||
sortedInfo: sorter,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleMark = (e) => {
|
||||
@@ -482,9 +521,10 @@ export function JobLinesComponent({
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setState({
|
||||
setState((state) => ({
|
||||
...state,
|
||||
filteredInfo: {
|
||||
...state.filteredInfo,
|
||||
part_type: [
|
||||
"PAN",
|
||||
"PAC",
|
||||
@@ -498,7 +538,7 @@ export function JobLinesComponent({
|
||||
"PAG",
|
||||
],
|
||||
},
|
||||
});
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<FilterFilled /> {t("jobs.actions.filterpartsonly")}
|
||||
|
||||
@@ -95,7 +95,7 @@ mutation UNVOID_JOB($jobId: uuid!) {
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.admin_unvoicejob(),
|
||||
operation: AuditTrailMapping.admin_jobunvoid(),
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
|
||||
@@ -45,9 +45,11 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
|
||||
return acc + generateUpdateQuery(value, idx);
|
||||
}, "");
|
||||
|
||||
const removeQueries = existingLines.reduce((acc, value, idx) => {
|
||||
return acc + generateRemoveQuery(value, idx);
|
||||
}, "");
|
||||
const removeQueries = existingLines
|
||||
.filter((l) => !l.manual_line)
|
||||
.reduce((acc, value, idx) => {
|
||||
return acc + generateRemoveQuery(value, idx);
|
||||
}, "");
|
||||
console.log(insertQueries, updateQueries, removeQueries);
|
||||
|
||||
if ((insertQueries + updateQueries + removeQueries).trim() === "") {
|
||||
|
||||
@@ -205,7 +205,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseLines);
|
||||
|
||||
const HasBeenConvertedTolabor = ({ value }) => {
|
||||
const { t } = useTranslation();
|
||||
console.log(value);
|
||||
|
||||
if (!value) return null;
|
||||
return (
|
||||
<Tooltip title={t("joblines.labels.convertedtolabor")}>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Card, Col, Row, Space, Tag } from "antd";
|
||||
import {
|
||||
WarningFilled,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
WarningFilled,
|
||||
} from "@ant-design/icons";
|
||||
import React, { useMemo } from "react";
|
||||
import { Card, Col, Row, Space, Tag } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -13,17 +13,17 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
||||
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
|
||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||
import "./jobs-detail-header.styles.scss";
|
||||
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
import "./jobs-detail-header.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -56,12 +56,6 @@ const colSpan = {
|
||||
export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const jobInPostProduction = useMemo(() => {
|
||||
return bodyshop.md_ro_statuses.post_production_statuses.includes(
|
||||
job.status
|
||||
);
|
||||
}, [job.status, bodyshop.md_ro_statuses.post_production_statuses]);
|
||||
|
||||
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
|
||||
${job.v_make_desc || ""}
|
||||
${job.v_model_desc || ""}`.trim();
|
||||
@@ -129,11 +123,10 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
</DataLabel>
|
||||
)}
|
||||
|
||||
{(job.inproduction || jobInPostProduction) && (
|
||||
<DataLabel label={t("jobs.fields.production_vars.note")}>
|
||||
<ProductionListColumnProductionNote record={job} />
|
||||
</DataLabel>
|
||||
)}
|
||||
<DataLabel label={t("jobs.fields.production_vars.note")}>
|
||||
<ProductionListColumnProductionNote record={job} />
|
||||
</DataLabel>
|
||||
|
||||
<Space>
|
||||
{job.special_coverage_policy && (
|
||||
<Tag color="tomato">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { SyncOutlined, FileExcelFilled } from "@ant-design/icons";
|
||||
import { Button, Card, Space } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import Gallery from "react-grid-gallery";
|
||||
@@ -58,6 +58,24 @@ export function JobsDocumentsLocalGallery({
|
||||
}
|
||||
}, [job, invoice_number, getJobMedia, getBillMedia]);
|
||||
|
||||
const jobMedia =
|
||||
allMedia && allMedia[job.id]
|
||||
? allMedia[job.id].reduce(
|
||||
(acc, val) => {
|
||||
if (
|
||||
val.type &&
|
||||
val.type.mime &&
|
||||
val.type.mime.startsWith("image")
|
||||
) {
|
||||
acc.images.push(val);
|
||||
} else {
|
||||
acc.other.push(val);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ images: [], other: [] }
|
||||
)
|
||||
: { images: [], other: [] };
|
||||
return (
|
||||
<div>
|
||||
<Space wrap>
|
||||
@@ -90,7 +108,7 @@ export function JobsDocumentsLocalGallery({
|
||||
</Card>
|
||||
<Card title={t("jobs.labels.documents-images")}>
|
||||
<Gallery
|
||||
images={(allMedia && allMedia[job.id]) || []}
|
||||
images={jobMedia.images}
|
||||
backdropClosesModal={true}
|
||||
onSelectImage={(index, image) => {
|
||||
toggleMediaSelected({ jobid: job.id, filename: image.filename });
|
||||
@@ -104,6 +122,31 @@ export function JobsDocumentsLocalGallery({
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
<Card title={t("jobs.labels.documents-other")}>
|
||||
<Gallery
|
||||
images={jobMedia.other}
|
||||
backdropClosesModal={true}
|
||||
enableLightbox={false}
|
||||
thumbnailStyle={() => {
|
||||
return {
|
||||
backgroundImage: <FileExcelFilled />,
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
cursor: "pointer",
|
||||
};
|
||||
}}
|
||||
onClickThumbnail={(index) => {
|
||||
window.open(
|
||||
jobMedia.other[index].src,
|
||||
"_blank",
|
||||
"toolbar=0,location=0,menubar=0"
|
||||
);
|
||||
}}
|
||||
onSelectImage={(index, image) => {
|
||||
toggleMediaSelected({ jobid: job.id, filename: image.filename });
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import React, { useEffect } from "react";
|
||||
import Gallery from "react-grid-gallery";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
getJobMedia,
|
||||
toggleMediaSelected,
|
||||
} from "../../redux/media/media.actions";
|
||||
import { selectAllMedia } from "../../redux/media/media.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
allMedia: selectAllMedia,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
getJobMedia: (id) => dispatch(getJobMedia(id)),
|
||||
|
||||
toggleMediaSelected: ({ jobid, filename }) =>
|
||||
dispatch(toggleMediaSelected({ jobid, filename })),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobDocumentsLocalGalleryExternal);
|
||||
|
||||
function JobDocumentsLocalGalleryExternal({
|
||||
jobId,
|
||||
externalMediaState,
|
||||
getJobMedia,
|
||||
toggleMediaSelected,
|
||||
allMedia,
|
||||
}) {
|
||||
const [galleryImages, setgalleryImages] = externalMediaState;
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if ( jobId) {
|
||||
getJobMedia(jobId);
|
||||
}
|
||||
}, [jobId, getJobMedia]);
|
||||
|
||||
useEffect(() => {
|
||||
let documents =
|
||||
allMedia && allMedia[jobId]
|
||||
? allMedia[jobId].reduce((acc, val) => {
|
||||
if (
|
||||
val.type &&
|
||||
val.type.mime &&
|
||||
val.type.mime.startsWith("image")
|
||||
) {
|
||||
acc.push(val);
|
||||
}
|
||||
return acc;
|
||||
}, [])
|
||||
: [];
|
||||
|
||||
setgalleryImages(documents);
|
||||
}, [allMedia, jobId, setgalleryImages, t]);
|
||||
|
||||
return (
|
||||
<div className="clearfix">
|
||||
<Gallery
|
||||
images={galleryImages}
|
||||
backdropClosesModal={true}
|
||||
onSelectImage={(index, image) => {
|
||||
setgalleryImages(
|
||||
galleryImages.map((g, idx) =>
|
||||
index === idx ? { ...g, isSelected: !g.isSelected } : g
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,12 +6,17 @@ export const CalculateAllocationsTotals = (
|
||||
timetickets,
|
||||
adjustments = []
|
||||
) => {
|
||||
console.log(
|
||||
"🚀 ~ file: labor-allocations-table.utility.js ~ line 9 ~ adjustments",
|
||||
adjustments
|
||||
);
|
||||
const responsibilitycenters = bodyshop.md_responsibility_centers;
|
||||
const jobCodes = joblines.map((item) => item.mod_lbr_ty);
|
||||
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
|
||||
const ticketCodes = timetickets.map((item) => item.ciecacode);
|
||||
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
|
||||
const allCodes = [...jobCodes, ...ticketCodes].filter(
|
||||
const adjustmentCodes = Object.keys(adjustments);
|
||||
const allCodes = [...jobCodes, ...ticketCodes, ...adjustmentCodes].filter(
|
||||
(value, index, self) => self.indexOf(value) === index && !!value
|
||||
);
|
||||
|
||||
|
||||
@@ -12,24 +12,24 @@ export default connect(mapStateToProps, mapDispatchToProps)(OwnerNameDisplay);
|
||||
|
||||
export function OwnerNameDisplay({ bodyshop, ownerObject }) {
|
||||
const emptyTest =
|
||||
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
|
||||
ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm;
|
||||
|
||||
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "")
|
||||
return "N/A";
|
||||
|
||||
if (bodyshop.last_name_first)
|
||||
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
|
||||
ownerObject.ownr_co_nm || ""
|
||||
return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${
|
||||
ownerObject?.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
|
||||
return `${ownerObject.ownr_fn || ""} ${ownerObject.ownr_ln || ""} ${
|
||||
return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${
|
||||
ownerObject.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
}
|
||||
|
||||
export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
|
||||
const emptyTest =
|
||||
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
|
||||
ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm;
|
||||
|
||||
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "")
|
||||
return "N/A";
|
||||
@@ -37,11 +37,11 @@ export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
|
||||
const rdxStore = store.getState();
|
||||
|
||||
if (rdxStore.user.bodyshop.last_name_first && !forceFirstLast)
|
||||
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
|
||||
ownerObject.ownr_co_nm || ""
|
||||
return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${
|
||||
ownerObject?.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
|
||||
return `${ownerObject.ownr_fn || ""} ${ownerObject.ownr_ln || ""} ${
|
||||
ownerObject.ownr_co_nm || ""
|
||||
return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${
|
||||
ownerObject?.ownr_co_nm || ""
|
||||
}`.trim();
|
||||
}
|
||||
|
||||
@@ -238,10 +238,8 @@ export function ProductionListTable({
|
||||
sticky
|
||||
pagination={false}
|
||||
size="small"
|
||||
className="production-list-table"
|
||||
onRow={
|
||||
Production_List_Status_Colors.treatment === "on" &&
|
||||
((record, index) => {
|
||||
{...(Production_List_Status_Colors.treatment === "on" && {
|
||||
onRow: (record, index) => {
|
||||
if (!bodyshop.md_ro_statuses.production_colors) return null;
|
||||
|
||||
const color = bodyshop.md_ro_statuses.production_colors.find(
|
||||
@@ -255,8 +253,8 @@ export function ProductionListTable({
|
||||
backgroundColor: `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
|
||||
},
|
||||
};
|
||||
})
|
||||
}
|
||||
},
|
||||
})}
|
||||
components={{
|
||||
header: {
|
||||
cell: ResizeableTitle,
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Button, Card, Input, Modal, Space, Table, Typography } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { Dropdown, Button, Table, Space, Card, Input } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component";
|
||||
import { QUERY_SCOREBOARD_PAGINATED } from "../../graphql/scoreboard.queries";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
|
||||
import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
export default function ScoreboardJobsList({ scoreBoardlist }) {
|
||||
const { t } = useTranslation();
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [state, setState] = useState({
|
||||
visible: false,
|
||||
search: "",
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const jobs = scoreBoardlist
|
||||
? searchText === ""
|
||||
? scoreBoardlist
|
||||
: scoreBoardlist.filter(
|
||||
(sb) =>
|
||||
(sb.job.ro_number || "")
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(sb.job.ownr_co_nm || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(sb.job.ownr_fn || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(sb.job.ownr_ln || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(sb.job.v_model_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(sb.job.v_make_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
const { loading, error, data, refetch } = useQuery(
|
||||
QUERY_SCOREBOARD_PAGINATED,
|
||||
{
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !state.visible,
|
||||
variables: {
|
||||
search: state.search !== "" ? `%${state.search}%` : null,
|
||||
offset: state.current ? (state.current - 1) * state.pageSize : 0,
|
||||
limit: state.pageSize,
|
||||
order: [
|
||||
{
|
||||
date: "desc",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@@ -97,35 +97,69 @@ export default function ScoreboardJobsList({ scoreBoardlist }) {
|
||||
},
|
||||
];
|
||||
|
||||
const overlay = (
|
||||
<Card
|
||||
style={{ maxWidth: "90vw", padding: "1rem" }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
extra={
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
onChange={(e) => {
|
||||
setSearchText(e.target.value);
|
||||
}}
|
||||
value={searchText}
|
||||
enterButton
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
pagination={false}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={jobs}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown trigger={["click"]} overlay={overlay}>
|
||||
<Button>Jobs</Button>
|
||||
</Dropdown>
|
||||
<>
|
||||
<Modal
|
||||
visible={state.visible}
|
||||
destroyOnClose
|
||||
width="80%"
|
||||
cancelButtonProps={{ style: { display: "none" } }}
|
||||
onCancel={() =>
|
||||
setState((state) => ({
|
||||
...state,
|
||||
visible: false,
|
||||
current: 1,
|
||||
search: "",
|
||||
}))
|
||||
}
|
||||
>
|
||||
{error && (
|
||||
<AlertComponent type="error" message={JSON.stringify(error)} />
|
||||
)}
|
||||
<Card
|
||||
extra={
|
||||
<Space align="middle" wrap>
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Typography.Title level={4}>
|
||||
{t("general.labels.searchresults", { search: state.search })}
|
||||
</Typography.Title>
|
||||
<Input.Search
|
||||
placeholder={t("jobs.fields.ro_number")}
|
||||
allowClear
|
||||
onSearch={(value) => {
|
||||
setState((state) => ({ ...state, search: value }));
|
||||
}}
|
||||
//value={state.search}
|
||||
enterButton
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data ? data.scoreboard : []}
|
||||
loading={loading}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(tableArgs) =>
|
||||
setState((state) => ({ ...state, ...tableArgs }))
|
||||
}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: state.pageSize || 10,
|
||||
current: state.current || 1,
|
||||
total: data ? data.scoreboard_aggregate.aggregate.count : 0,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</Modal>
|
||||
<Button
|
||||
onClick={() => setState((state) => ({ ...state, visible: true }))}
|
||||
>
|
||||
{t("scoreboard.labels.entries")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ export default function ScoreboardRemoveButton({ scoreboardId }) {
|
||||
setLoading(true);
|
||||
const result = await deleteScoreboardEntry({
|
||||
variables: { sbId: scoreboardId },
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: ["QUERY_SCOREBOARD_PAGINATED"],
|
||||
});
|
||||
|
||||
if (!!result.errors) {
|
||||
|
||||
@@ -185,8 +185,8 @@ export default function ScoreboardTimeTickets() {
|
||||
ret.employees[ticket.employee.employee_number].totalOverPeriod +
|
||||
ticket.productivehrs;
|
||||
ret.employees[ticket.employee.employee_number].actualTotalOverPeriod =
|
||||
ret.employees[ticket.employee.employee_number].actualTotalOverPeriod +
|
||||
(ticket.actualhrs || 0);
|
||||
ret.employees[ticket.employee.employee_number]
|
||||
.actualTotalOverPeriod + (ticket.actualhrs || 0);
|
||||
|
||||
if (!totals.employees[ticket.employee.employee_number])
|
||||
totals.employees[ticket.employee.employee_number] = {
|
||||
@@ -236,8 +236,6 @@ export default function ScoreboardTimeTickets() {
|
||||
};
|
||||
}, [fixedPeriods, data, startDate, endDate]);
|
||||
|
||||
console.log(calculatedData);
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
if (loading) return <LoadingSpinner />;
|
||||
return (
|
||||
|
||||
@@ -23,6 +23,7 @@ export const GET_ALL_JOBLINES_BY_PK = gql`
|
||||
notes
|
||||
location
|
||||
tax_part
|
||||
manual_line
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -197,6 +198,7 @@ export const GET_JOB_LINES_TO_ENTER_BILL = gql`
|
||||
line_desc
|
||||
part_type
|
||||
oem_partno
|
||||
alt_partno
|
||||
db_price
|
||||
act_price
|
||||
part_qty
|
||||
@@ -206,6 +208,7 @@ export const GET_JOB_LINES_TO_ENTER_BILL = gql`
|
||||
lbr_op
|
||||
lbr_amt
|
||||
op_code_desc
|
||||
alt_partno
|
||||
}
|
||||
jobs_by_pk(id: $id) {
|
||||
id
|
||||
|
||||
@@ -88,3 +88,41 @@ export const GET_BLOCKED_DAYS = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_SCOREBOARD_PAGINATED = gql`
|
||||
query QUERY_SCOREBOARD_PAGINATED(
|
||||
$search: String
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$order: [scoreboard_order_by!]
|
||||
) {
|
||||
scoreboard(
|
||||
where: { job: { ro_number: { _ilike: $search } } }
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
) {
|
||||
id
|
||||
jobid
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
invoice_date
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
}
|
||||
date
|
||||
bodyhrs
|
||||
painthrs
|
||||
}
|
||||
scoreboard_aggregate(where: { job: { ro_number: { _ilike: $search } } }) {
|
||||
aggregate {
|
||||
count(distinct: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React from "react";
|
||||
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
|
||||
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
|
||||
|
||||
export default function TechLookupContainer() {
|
||||
return (
|
||||
<div>
|
||||
<TechLookupJobsList />
|
||||
<TechLookupJobsDrawer />
|
||||
<RbacWrapperComponent action="jobs:list-active">
|
||||
<TechLookupJobsList />
|
||||
<TechLookupJobsDrawer />
|
||||
</RbacWrapperComponent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ const sentryReduxEnhancer = Sentry.createReduxEnhancer({
|
||||
const sagaMiddleWare = createSagaMiddleware();
|
||||
|
||||
const reduxSyncConfig = {
|
||||
whitelist: ["ADD_RECENT_ITEM", "SET_SHOP_DETAILS"],
|
||||
whitelist: [
|
||||
"ADD_RECENT_ITEM", //"SET_SHOP_DETAILS"
|
||||
],
|
||||
};
|
||||
|
||||
const middlewares = [
|
||||
|
||||
@@ -269,7 +269,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
try {
|
||||
const userEmail = yield select((state) => state.user.currentUser.email);
|
||||
try {
|
||||
console.log("Setting shop timezone.");
|
||||
//console.log("Setting shop timezone.");
|
||||
// moment.tz.setDefault(payload.timezone);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
@@ -2406,6 +2406,7 @@
|
||||
"open_orders_status": "Open Orders by Status",
|
||||
"parts_backorder": "IOU Parts List",
|
||||
"parts_not_recieved": "Parts Not Received",
|
||||
"parts_not_recieved_vendor": "Parts Not Received by Vendor",
|
||||
"payments_by_date": "Payments by Date",
|
||||
"payments_by_date_type": "Payments by Date and Type",
|
||||
"production_by_category": "Production by Category",
|
||||
@@ -2465,6 +2466,7 @@
|
||||
"dailyactual": "Actual (D)",
|
||||
"dailytarget": "Daily",
|
||||
"efficiencyoverperiod": "Efficiency over Selected Dates",
|
||||
"entries": "Scoreboard Entries",
|
||||
"jobs": "Jobs",
|
||||
"lastmonth": "Last Month",
|
||||
"lastweek": "Last Week",
|
||||
|
||||
@@ -2406,6 +2406,7 @@
|
||||
"open_orders_status": "",
|
||||
"parts_backorder": "",
|
||||
"parts_not_recieved": "",
|
||||
"parts_not_recieved_vendor": "",
|
||||
"payments_by_date": "",
|
||||
"payments_by_date_type": "",
|
||||
"production_by_category": "",
|
||||
@@ -2465,6 +2466,7 @@
|
||||
"dailyactual": "",
|
||||
"dailytarget": "",
|
||||
"efficiencyoverperiod": "",
|
||||
"entries": "",
|
||||
"jobs": "",
|
||||
"lastmonth": "",
|
||||
"lastweek": "",
|
||||
|
||||
@@ -2406,6 +2406,7 @@
|
||||
"open_orders_status": "",
|
||||
"parts_backorder": "",
|
||||
"parts_not_recieved": "",
|
||||
"parts_not_recieved_vendor": "",
|
||||
"payments_by_date": "",
|
||||
"payments_by_date_type": "",
|
||||
"production_by_category": "",
|
||||
@@ -2465,6 +2466,7 @@
|
||||
"dailyactual": "",
|
||||
"dailytarget": "",
|
||||
"efficiencyoverperiod": "",
|
||||
"entries": "",
|
||||
"jobs": "",
|
||||
"lastmonth": "",
|
||||
"lastweek": "",
|
||||
|
||||
@@ -40,6 +40,7 @@ const AuditTrailMapping = {
|
||||
i18n.t("audit_trail.messages.admin_jobmarkforreexport"),
|
||||
admin_jobmarkexported: () =>
|
||||
i18n.t("audit_trail.messages.admin_jobmarkexported"),
|
||||
|
||||
};
|
||||
|
||||
export default AuditTrailMapping;
|
||||
|
||||
@@ -1448,6 +1448,19 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "purchases",
|
||||
},
|
||||
parts_not_recieved_vendor: {
|
||||
title: i18n.t("reportcenter.templates.parts_not_recieved_vendor"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.parts_not_recieved_vendor"),
|
||||
key: "parts_not_recieved_vendor",
|
||||
idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.parts_orders"),
|
||||
field: i18n.t("parts_orders.fields.order_date"),
|
||||
},
|
||||
group: "purchases",
|
||||
},
|
||||
scoreboard_detail: {
|
||||
title: i18n.t("reportcenter.templates.scoreboard_detail"),
|
||||
description: "",
|
||||
|
||||
5348
client/yarn.lock
5348
client/yarn.lock
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@@ -17,11 +17,11 @@
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.1136.0",
|
||||
"aws-sdk": "^2.1181.0",
|
||||
"axios": "^0.27.2",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.20.0",
|
||||
"cloudinary": "^1.30.0",
|
||||
"cloudinary": "^1.30.1",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "2.8.5",
|
||||
@@ -29,35 +29,33 @@
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "16.0.1",
|
||||
"express": "^4.18.1",
|
||||
"firebase-admin": "^10.2.0",
|
||||
"firebase-admin": "^11.0.0",
|
||||
"graphql": "^16.5.0",
|
||||
"graphql-request": "^4.2.0",
|
||||
"graylog2": "^0.2.1",
|
||||
"inline-css": "^3.0.0",
|
||||
"inline-css": "^4.0.1",
|
||||
"intuit-oauth": "^4.0.0",
|
||||
"json-2-csv": "^3.17.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.3",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"multer": "^1.4.4",
|
||||
"node-mailjet": "^3.4.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"node-mailjet": "^5.1.0",
|
||||
"node-quickbooks": "^2.0.39",
|
||||
"nodemailer": "^6.7.5",
|
||||
"phone": "^3.1.17",
|
||||
"nodemailer": "^6.7.7",
|
||||
"phone": "^3.1.23",
|
||||
"query-string": "^7.1.1",
|
||||
"soap": "^0.43.0",
|
||||
"soap": "^0.45.0",
|
||||
"socket.io": "^4.5.0",
|
||||
"ssh2-sftp-client": "^8.0.0",
|
||||
"stripe": "^9.1.0",
|
||||
"twilio": "^3.77.0",
|
||||
"ssh2-sftp-client": "^9.0.2",
|
||||
"stripe": "^9.15.0",
|
||||
"twilio": "^3.80.0",
|
||||
"uuid": "^8.3.2",
|
||||
"xml2js": "^0.4.23",
|
||||
"xmlbuilder2": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^6.3.0",
|
||||
"eslint": "^7.31.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"concurrently": "^7.3.0",
|
||||
"source-map-explorer": "^2.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
12
server.js
12
server.js
@@ -172,6 +172,18 @@ app.post(
|
||||
fb.validateAdmin,
|
||||
adm.createShop
|
||||
);
|
||||
app.post(
|
||||
"/adm/updateshop",
|
||||
fb.validateFirebaseIdToken,
|
||||
fb.validateAdmin,
|
||||
adm.updateShop
|
||||
);
|
||||
app.post(
|
||||
"/adm/updatecounter",
|
||||
fb.validateFirebaseIdToken,
|
||||
fb.validateAdmin,
|
||||
adm.updateCounter
|
||||
);
|
||||
|
||||
//Stripe Processing
|
||||
var stripe = require("./server/stripe/payment");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const DineroQbFormat = require("./accounting-constants").DineroQbFormat;
|
||||
|
||||
const Dinero = require("dinero.js");
|
||||
const { DiscountNotAlreadyCounted } = require("../job/job-totals");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
exports.default = function ({
|
||||
@@ -37,23 +38,22 @@ exports.default = function ({
|
||||
amount: Math.round((jobline.act_price || 0) * 100),
|
||||
}).multiply(jobline.part_qty || 1);
|
||||
|
||||
if (
|
||||
(jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0) ||
|
||||
((jobline.db_ref === "900511" ||
|
||||
jobline.db_ref === "900510" ||
|
||||
jobline.db_ref === "900500") &&
|
||||
jobline.prt_dsmk_m &&
|
||||
jobline.prt_dsmk_m !== 0)
|
||||
) {
|
||||
// console.log("Have a part discount", jobline);
|
||||
DineroAmount = DineroAmount.add(
|
||||
jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0
|
||||
// console.log("Have a part discount", jobline);
|
||||
DineroAmount = DineroAmount.add(
|
||||
((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) ||
|
||||
(jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(jobline, jobs_by_pk.joblines)
|
||||
? jobline.prt_dsmk_m
|
||||
? Dinero({ amount: Math.round(jobline.prt_dsmk_m * 100) })
|
||||
: DineroAmount.percentage(
|
||||
Math.abs(jobline.prt_dsmk_p || 0)
|
||||
).multiply(jobline.prt_dsmk_p > 0 ? 1 : -1)
|
||||
);
|
||||
}
|
||||
: Dinero({
|
||||
amount: Math.round(jobline.act_price * 100),
|
||||
})
|
||||
.multiply(jobline.part_qty || 0)
|
||||
.percentage(Math.abs(jobline.prt_dsmk_p || 0))
|
||||
.multiply(jobline.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
);
|
||||
|
||||
const account = responsibilityCenters.profits.find(
|
||||
(i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase()
|
||||
);
|
||||
@@ -82,7 +82,11 @@ exports.default = function ({
|
||||
state:
|
||||
jobs_by_pk.state_tax_rate === 0
|
||||
? false
|
||||
: jobline.db_ref === "900511" || jobline.db_ref === "900510"
|
||||
: jobline.db_ref === "900511" ||
|
||||
jobline.db_ref === "900510" ||
|
||||
(jobline.mod_lb_hrs === 0 && //Extending IO-1375 as a part of IO-2023
|
||||
jobline.act_price > 0 &&
|
||||
jobline.lbr_op === "OP14")
|
||||
? true
|
||||
: jobline.tax_part,
|
||||
},
|
||||
|
||||
@@ -66,3 +66,55 @@ exports.createShop = async (req, res) => {
|
||||
res.status(500).json(error);
|
||||
}
|
||||
};
|
||||
exports.updateCounter = async (req, res) => {
|
||||
logger.log("admin-update-counter", "ADMIN", req.user.email, null, {
|
||||
request: req.body,
|
||||
ioadmin: true,
|
||||
});
|
||||
const { id, counter } = req.body;
|
||||
|
||||
try {
|
||||
const result = await client.request(
|
||||
`mutation UPDATE_COUNTER($id: uuid!, $counter: counters_set_input!) {
|
||||
update_counters_by_pk(pk_columns: { id: $id }, _set: $counter) {
|
||||
id
|
||||
countertype
|
||||
count
|
||||
prefix
|
||||
}
|
||||
}`,
|
||||
{
|
||||
id,
|
||||
counter,
|
||||
}
|
||||
);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json(error);
|
||||
}
|
||||
};
|
||||
exports.updateShop = async (req, res) => {
|
||||
logger.log("admin-update-shop", "ADMIN", req.user.email, null, {
|
||||
request: req.body,
|
||||
ioadmin: true,
|
||||
});
|
||||
const { id, bodyshop } = req.body;
|
||||
|
||||
try {
|
||||
const result = await client.request(
|
||||
`mutation UPDATE_BODYSHOP($id: uuid!, $bodyshop: bodyshops_set_input!) {
|
||||
update_bodyshops_by_pk(pk_columns: { id: $id }, _set: $bodyshop) {
|
||||
id
|
||||
|
||||
}
|
||||
}`,
|
||||
{
|
||||
id,
|
||||
bodyshop,
|
||||
}
|
||||
);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ const CdkBase = require("../web-sockets/web-socket");
|
||||
|
||||
const Dinero = require("dinero.js");
|
||||
const _ = require("lodash");
|
||||
const { DiscountNotAlreadyCounted } = require("../job/job-totals");
|
||||
|
||||
exports.default = async function (socket, jobid) {
|
||||
try {
|
||||
@@ -70,23 +71,20 @@ exports.default = async function (socket, jobid) {
|
||||
amount: Math.round(val.act_price * 100),
|
||||
}).multiply(val.part_qty || 1);
|
||||
|
||||
if (
|
||||
(val.prt_dsmk_p && val.prt_dsmk_p !== 0) ||
|
||||
((val.db_ref === "900511" ||
|
||||
val.db_ref === "900510" ||
|
||||
val.db_ref === "900500") &&
|
||||
val.prt_dsmk_m &&
|
||||
val.prt_dsmk_m !== 0)
|
||||
) {
|
||||
// console.log("Have a part discount", val);
|
||||
DineroAmount = DineroAmount.add(
|
||||
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||
DineroAmount = DineroAmount.add(
|
||||
((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
|
||||
(val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(val, job.joblines)
|
||||
? val.prt_dsmk_m
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: DineroAmount.percentage(Math.abs(val.prt_dsmk_p || 0)).multiply(
|
||||
val.prt_dsmk_p > 0 ? 1 : -1
|
||||
)
|
||||
);
|
||||
}
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
);
|
||||
|
||||
acc[val.profitcenter_part] =
|
||||
acc[val.profitcenter_part].add(DineroAmount);
|
||||
|
||||
@@ -431,7 +431,7 @@ async function QueryDmsCustomerById(socket, JobData, CustomerId) {
|
||||
|
||||
async function QueryDmsCustomerByName(socket, JobData) {
|
||||
const ownerName = (
|
||||
JobData.ownr_co_nm
|
||||
JobData.ownr_co_nm && JobData.ownr_co_nm !== ""
|
||||
? JobData.ownr_co_nm
|
||||
: `${JobData.ownr_ln},${JobData.ownr_fn}`
|
||||
).replace(replaceSpecialRegex, "");
|
||||
@@ -725,7 +725,7 @@ async function InsertDmsVehicle(socket) {
|
||||
manufacturer: {},
|
||||
vehicle: {
|
||||
deliveryDate: moment()
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.format("YYYYMMDD"),
|
||||
licensePlateNo: socket.JobData.plate_no,
|
||||
make: socket.txEnvelope.dms_make,
|
||||
@@ -854,7 +854,7 @@ async function UpdateDmsVehicle(socket) {
|
||||
socket.DMSVeh.dealer.inServiceDate ||
|
||||
socket.txEnvelope.inservicedate
|
||||
)
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
// .tz(socket.JobData.bodyshop.timezone)
|
||||
.toISOString(),
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -209,6 +209,9 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
|
||||
prt_dsmk_p
|
||||
prt_dsmk_m
|
||||
tax_part
|
||||
line_ref
|
||||
unq_seq
|
||||
lbr_op
|
||||
}
|
||||
}
|
||||
bodyshops(where: {associations: {active: {_eq: true}}}) {
|
||||
@@ -402,6 +405,8 @@ query QUERY_JOBS_FOR_PBS_EXPORT($id: uuid!) {
|
||||
profitcenter_part
|
||||
db_ref
|
||||
prt_dsmk_p
|
||||
unq_seq
|
||||
line_ref
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1112,6 +1117,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
id
|
||||
db_ref
|
||||
line_ref
|
||||
unq_seq
|
||||
line_ind
|
||||
tax_part
|
||||
@@ -1220,6 +1226,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
|
||||
id
|
||||
db_ref
|
||||
unq_seq
|
||||
line_ref
|
||||
line_ind
|
||||
tax_part
|
||||
line_desc
|
||||
@@ -1443,7 +1450,8 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
|
||||
op_code_desc
|
||||
profitcenter_labor
|
||||
profitcenter_part
|
||||
prt_dsmk_p
|
||||
line_ref
|
||||
unq_seq
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const queries = require("../graphql-client/queries");
|
||||
const _ = require("lodash");
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
const logger = require("../utils/logger");
|
||||
const { DiscountNotAlreadyCounted } = require("./job-totals");
|
||||
// Dinero.defaultCurrency = "USD";
|
||||
// Dinero.globalLocale = "en-CA";
|
||||
Dinero.globalRoundingMode = "HALF_EVEN";
|
||||
@@ -308,7 +309,8 @@ function GenerateCostingData(job) {
|
||||
job.bodyshop.md_responsibility_centers.defaults.profits;
|
||||
const allCenters = _.union(
|
||||
job.bodyshop.md_responsibility_centers.profits.map((p) => p.name),
|
||||
job.bodyshop.md_responsibility_centers.costs.map((p) => p.name)
|
||||
job.bodyshop.md_responsibility_centers.costs.map((p) => p.name),
|
||||
["Unknown"]
|
||||
);
|
||||
|
||||
const materialsHours = { mapaHrs: 0, mashHrs: 0 };
|
||||
@@ -330,12 +332,15 @@ function GenerateCostingData(job) {
|
||||
}
|
||||
if (val.mod_lbr_ty) {
|
||||
const laborProfitCenter =
|
||||
val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "?";
|
||||
val.profitcenter_labor ||
|
||||
defaultProfits[val.mod_lbr_ty] ||
|
||||
"Unknown";
|
||||
|
||||
if (laborProfitCenter === "?")
|
||||
if (laborProfitCenter === "Unknown")
|
||||
console.log("Unknown type", val.line_desc, val.mod_lbr_ty);
|
||||
|
||||
const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;
|
||||
|
||||
const laborAmount = Dinero({
|
||||
amount: Math.round((job[rateName] || 0) * 100),
|
||||
}).multiply(val.mod_lb_hrs || 0);
|
||||
@@ -344,6 +349,19 @@ function GenerateCostingData(job) {
|
||||
acc.labor[laborProfitCenter] =
|
||||
acc.labor[laborProfitCenter].add(laborAmount);
|
||||
|
||||
if (
|
||||
val.mod_lb_hrs === 0 &&
|
||||
val.act_price > 0 &&
|
||||
val.lbr_op === "OP14"
|
||||
) {
|
||||
//Scenario where SGI may pay out hours using a part price.
|
||||
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(
|
||||
Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
}).multiply(val.part_qty)
|
||||
);
|
||||
}
|
||||
|
||||
if (val.mod_lbr_ty === "LAR") {
|
||||
materialsHours.mapaHrs += val.mod_lb_hrs || 0;
|
||||
}
|
||||
@@ -359,9 +377,9 @@ function GenerateCostingData(job) {
|
||||
val.part_type !== "PASL"
|
||||
) {
|
||||
const partsProfitCenter =
|
||||
val.profitcenter_part || defaultProfits[val.part_type] || "?";
|
||||
val.profitcenter_part || defaultProfits[val.part_type] || "Unknown";
|
||||
|
||||
if (partsProfitCenter === "?")
|
||||
if (partsProfitCenter === "Unknown")
|
||||
console.log("Unknown type", val.line_desc, val.part_type);
|
||||
|
||||
if (!partsProfitCenter)
|
||||
@@ -375,14 +393,18 @@ function GenerateCostingData(job) {
|
||||
})
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
|
||||
(val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(val, job.joblines)
|
||||
? val.prt_dsmk_m
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
);
|
||||
if (!acc.parts[partsProfitCenter])
|
||||
acc.parts[partsProfitCenter] = Dinero();
|
||||
@@ -395,9 +417,9 @@ function GenerateCostingData(job) {
|
||||
(val.part_type === "PAS" || val.part_type === "PASL")
|
||||
) {
|
||||
const partsProfitCenter =
|
||||
val.profitcenter_part || defaultProfits[val.part_type] || "?";
|
||||
val.profitcenter_part || defaultProfits[val.part_type] || "Unknown";
|
||||
|
||||
if (partsProfitCenter === "?")
|
||||
if (partsProfitCenter === "Unknown")
|
||||
console.log("Unknown type", val.line_desc, val.part_type);
|
||||
|
||||
if (!partsProfitCenter)
|
||||
@@ -411,14 +433,18 @@ function GenerateCostingData(job) {
|
||||
})
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
|
||||
(val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(val, job.joblines)
|
||||
? val.prt_dsmk_m
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
: Dinero()
|
||||
);
|
||||
if (!acc.sublet[partsProfitCenter])
|
||||
acc.sublet[partsProfitCenter] = Dinero();
|
||||
@@ -433,17 +459,20 @@ function GenerateCostingData(job) {
|
||||
const partsProfitCenter =
|
||||
val.profitcenter_part ||
|
||||
getAdditionalCostCenter(val, defaultProfits) ||
|
||||
"?";
|
||||
"Unknown";
|
||||
|
||||
if (partsProfitCenter === "?") {
|
||||
if (partsProfitCenter === "Unknown") {
|
||||
console.log("Unknown type", val.line_desc, val.part_type);
|
||||
} else {
|
||||
const partsAmount = Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||
}
|
||||
const partsAmount = Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
})
|
||||
.multiply(val.part_qty || 1)
|
||||
.add(
|
||||
((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
|
||||
(val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(val, job.joblines)
|
||||
? val.prt_dsmk_m
|
||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(val.act_price * 100),
|
||||
@@ -451,13 +480,13 @@ function GenerateCostingData(job) {
|
||||
.multiply(val.part_qty || 0)
|
||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||
);
|
||||
: Dinero()
|
||||
);
|
||||
|
||||
if (!acc.additional[partsProfitCenter])
|
||||
acc.additional[partsProfitCenter] = Dinero();
|
||||
acc.additional[partsProfitCenter] =
|
||||
acc.additional[partsProfitCenter].add(partsAmount);
|
||||
}
|
||||
if (!acc.additional[partsProfitCenter])
|
||||
acc.additional[partsProfitCenter] = Dinero();
|
||||
acc.additional[partsProfitCenter] =
|
||||
acc.additional[partsProfitCenter].add(partsAmount);
|
||||
}
|
||||
|
||||
return acc;
|
||||
|
||||
@@ -362,28 +362,27 @@ function CalculateRatesTotals(ratesList) {
|
||||
}
|
||||
|
||||
function CalculatePartsTotals(jobLines) {
|
||||
const ret = jobLines
|
||||
.filter((jl) => !jl.removed)
|
||||
.reduce(
|
||||
(acc, value) => {
|
||||
switch (value.part_type) {
|
||||
case "PAS":
|
||||
case "PASL":
|
||||
return {
|
||||
...acc,
|
||||
sublets: {
|
||||
...acc.sublets,
|
||||
subtotal: acc.sublets.subtotal.add(
|
||||
Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
})
|
||||
.multiply(value.part_qty || 0)
|
||||
.add(
|
||||
(value.db_ref === "900511" ||
|
||||
value.db_ref === "900510" ||
|
||||
value.db_ref === "900500") &&
|
||||
value.prt_dsmk_m &&
|
||||
value.prt_dsmk_m !== 0
|
||||
const jl = jobLines.filter((jl) => !jl.removed);
|
||||
|
||||
const ret = jl.reduce(
|
||||
(acc, value) => {
|
||||
switch (value.part_type) {
|
||||
case "PAS":
|
||||
case "PASL":
|
||||
return {
|
||||
...acc,
|
||||
sublets: {
|
||||
...acc.sublets,
|
||||
subtotal: acc.sublets.subtotal.add(
|
||||
Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
})
|
||||
.multiply(value.part_qty || 0)
|
||||
.add(
|
||||
((value.prt_dsmk_m && value.prt_dsmk_m !== 0) ||
|
||||
(value.prt_dsmk_p && value.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(value, jl)
|
||||
? value.prt_dsmk_m
|
||||
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
@@ -391,28 +390,28 @@ function CalculatePartsTotals(jobLines) {
|
||||
.multiply(value.part_qty || 0)
|
||||
.percentage(Math.abs(value.prt_dsmk_p || 0))
|
||||
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
: Dinero()
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
default:
|
||||
if (
|
||||
!value.part_type &&
|
||||
value.db_ref !== "900510" &&
|
||||
value.db_ref !== "900511"
|
||||
)
|
||||
return acc;
|
||||
return {
|
||||
...acc,
|
||||
parts: {
|
||||
...acc.parts,
|
||||
prt_dsmk_total: acc.parts.prt_dsmk_total.add(
|
||||
(value.db_ref === "900511" ||
|
||||
value.db_ref === "900510" ||
|
||||
value.db_ref === "900500") &&
|
||||
value.prt_dsmk_m &&
|
||||
value.prt_dsmk_m !== 0
|
||||
default:
|
||||
if (
|
||||
!value.part_type &&
|
||||
value.db_ref !== "900510" &&
|
||||
value.db_ref !== "900511"
|
||||
)
|
||||
return acc;
|
||||
return {
|
||||
...acc,
|
||||
parts: {
|
||||
...acc.parts,
|
||||
prt_dsmk_total: acc.parts.prt_dsmk_total.add(
|
||||
((value.prt_dsmk_m && value.prt_dsmk_m !== 0) ||
|
||||
(value.prt_dsmk_p && value.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(value, jl)
|
||||
? value.prt_dsmk_m
|
||||
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
@@ -420,47 +419,45 @@ function CalculatePartsTotals(jobLines) {
|
||||
.multiply(value.part_qty || 0)
|
||||
.percentage(Math.abs(value.prt_dsmk_p || 0))
|
||||
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
|
||||
),
|
||||
...(value.part_type
|
||||
? {
|
||||
list: {
|
||||
...acc.parts.list,
|
||||
[value.part_type]:
|
||||
acc.parts.list[value.part_type] &&
|
||||
acc.parts.list[value.part_type].total
|
||||
? {
|
||||
total: acc.parts.list[
|
||||
value.part_type
|
||||
].total.add(
|
||||
Dinero({
|
||||
amount: Math.round(
|
||||
(value.act_price || 0) * 100
|
||||
),
|
||||
}).multiply(value.part_qty || 0)
|
||||
),
|
||||
}
|
||||
: {
|
||||
total: Dinero({
|
||||
: Dinero()
|
||||
),
|
||||
...(value.part_type
|
||||
? {
|
||||
list: {
|
||||
...acc.parts.list,
|
||||
[value.part_type]:
|
||||
acc.parts.list[value.part_type] &&
|
||||
acc.parts.list[value.part_type].total
|
||||
? {
|
||||
total: acc.parts.list[value.part_type].total.add(
|
||||
Dinero({
|
||||
amount: Math.round(
|
||||
(value.act_price || 0) * 100
|
||||
),
|
||||
}).multiply(value.part_qty || 0),
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
subtotal: acc.parts.subtotal
|
||||
.add(
|
||||
Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
}).multiply(value.part_qty || 0)
|
||||
)
|
||||
.add(
|
||||
(value.db_ref === "900511" ||
|
||||
value.db_ref === "900510" ||
|
||||
value.db_ref === "900500") &&
|
||||
value.prt_dsmk_m &&
|
||||
value.prt_dsmk_m !== 0
|
||||
}).multiply(value.part_qty || 0)
|
||||
),
|
||||
}
|
||||
: {
|
||||
total: Dinero({
|
||||
amount: Math.round(
|
||||
(value.act_price || 0) * 100
|
||||
),
|
||||
}).multiply(value.part_qty || 0),
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
subtotal: acc.parts.subtotal
|
||||
.add(
|
||||
Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
}).multiply(value.part_qty || 0)
|
||||
)
|
||||
.add(
|
||||
((value.prt_dsmk_m && value.prt_dsmk_m !== 0) ||
|
||||
(value.prt_dsmk_p && value.prt_dsmk_p !== 0)) &&
|
||||
DiscountNotAlreadyCounted(value, jl)
|
||||
? value.prt_dsmk_m
|
||||
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
|
||||
: Dinero({
|
||||
amount: Math.round(value.act_price * 100),
|
||||
@@ -468,25 +465,26 @@ function CalculatePartsTotals(jobLines) {
|
||||
.multiply(value.part_qty || 0)
|
||||
.percentage(Math.abs(value.prt_dsmk_p || 0))
|
||||
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
parts: {
|
||||
list: {},
|
||||
prt_dsmk_total: Dinero(),
|
||||
subtotal: Dinero({ amount: 0 }),
|
||||
total: Dinero({ amount: 0 }),
|
||||
},
|
||||
sublets: {
|
||||
subtotal: Dinero({ amount: 0 }),
|
||||
|
||||
total: Dinero({ amount: 0 }),
|
||||
},
|
||||
: Dinero()
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
},
|
||||
{
|
||||
parts: {
|
||||
list: {},
|
||||
prt_dsmk_total: Dinero(),
|
||||
subtotal: Dinero({ amount: 0 }),
|
||||
total: Dinero({ amount: 0 }),
|
||||
},
|
||||
sublets: {
|
||||
subtotal: Dinero({ amount: 0 }),
|
||||
|
||||
total: Dinero({ amount: 0 }),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
parts: {
|
||||
@@ -706,7 +704,16 @@ function CalculateTaxesTotals(job, otherTotals) {
|
||||
exports.default = Totals;
|
||||
|
||||
function DiscountNotAlreadyCounted(jobline, joblines) {
|
||||
if (jobline.db_ref !== "900510") return true;
|
||||
if (
|
||||
//If it's not a discount line, then it definitely hasn't been counted yet.
|
||||
jobline.db_ref !== "900510" &&
|
||||
jobline.db_ref !== "900511"
|
||||
)
|
||||
return true;
|
||||
|
||||
const ParentLine = joblines.find((j) => j.unq_seq === jobline.line_ref);
|
||||
|
||||
return ParentLine && !(ParentLine.prt_dsmk_m && ParentLine.prt_dsmk_m !== 0);
|
||||
}
|
||||
|
||||
exports.DiscountNotAlreadyCounted = DiscountNotAlreadyCounted;
|
||||
|
||||
Reference in New Issue
Block a user