Merged in release/2022-08-19 (pull request #562)

Release/2022 08 19
This commit is contained in:
Patrick Fic
2022-08-19 18:25:50 +00:00
17 changed files with 15973 additions and 10558 deletions

View File

@@ -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 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 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!@#' 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@ import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.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"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -58,17 +59,24 @@ export function ChatMediaSelector({
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? ( {selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div> <div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
) : null} ) : null}
{data && ( {!bodyshop.uselocalmediaserver && data && (
<JobDocumentsGalleryExternal <JobDocumentsGalleryExternal
data={data ? data.documents : []} data={data ? data.documents : []}
externalMediaState={[selectedMedia, setSelectedMedia]} externalMediaState={[selectedMedia, setSelectedMedia]}
/> />
)} )}
{bodyshop.uselocalmediaserver && visible && (
<JobDocumentsLocalGalleryExternal
externalMediaState={[selectedMedia, setSelectedMedia]}
jobId={
conversation.job_conversations[0] &&
conversation.job_conversations[0].jobid
}
/>
)}
</div> </div>
); );
if (bodyshop.uselocalmediaserver) return null;
return ( return (
<Popover <Popover
content={ content={

View File

@@ -5,12 +5,15 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries"; import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
import { selectEmailConfig } from "../../redux/email/email.selectors"; import { selectEmailConfig } from "../../redux/email/email.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.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"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
bodyshop: selectBodyshop,
emailConfig: selectEmailConfig, emailConfig: selectEmailConfig,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -25,6 +28,7 @@ export function EmailDocumentsComponent({
emailConfig, emailConfig,
form, form,
selectedMediaState, selectedMediaState,
bodyshop,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -52,12 +56,18 @@ export function EmailDocumentsComponent({
10485760 - new Blob([form.getFieldValue("html")]).size ? ( 10485760 - new Blob([form.getFieldValue("html")]).size ? (
<div style={{ color: "red" }}>{t("general.errors.sizelimit")}</div> <div style={{ color: "red" }}>{t("general.errors.sizelimit")}</div>
) : null} ) : null}
{data && ( {!bodyshop.uselocalmediaserver && data && (
<JobDocumentsGalleryExternal <JobDocumentsGalleryExternal
data={data ? data.documents : []} data={data ? data.documents : []}
externalMediaState={[selectedMedia, setSelectedMedia]} externalMediaState={[selectedMedia, setSelectedMedia]}
/> />
)} )}
{bodyshop.uselocalmediaserver && (
<JobsDocumentsLocalGalleryExternalComponent
externalMediaState={[selectedMedia, setSelectedMedia]}
jobId={emailConfig.jobid}
/>
)}
</div> </div>
); );
} }

View File

@@ -160,14 +160,13 @@ export function EmailOverlayComponent({
</Form.Item> </Form.Item>
<Tabs> <Tabs>
{!bodyshop.uselocalmediaserver && ( <Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents"> <EmailDocumentsComponent
<EmailDocumentsComponent selectedMediaState={selectedMediaState}
selectedMediaState={selectedMediaState} form={form}
form={form} />
/> </Tabs.TabPane>
</Tabs.TabPane>
)}
<Tabs.TabPane tab={t("emails.labels.attachments")} key="attachments"> <Tabs.TabPane tab={t("emails.labels.attachments")} key="attachments">
{bodyshop.uselocalmediaserver && emailConfig.jobid && ( {bodyshop.uselocalmediaserver && emailConfig.jobid && (
<a href={CreateExplorerLinkForJob({ jobid: emailConfig.jobid })}> <a href={CreateExplorerLinkForJob({ jobid: emailConfig.jobid })}>

View File

@@ -95,7 +95,7 @@ mutation UNVOID_JOB($jobId: uuid!) {
insertAuditTrail({ insertAuditTrail({
jobid: job.id, jobid: job.id,
operation: AuditTrailMapping.admin_unvoicejob(), operation: AuditTrailMapping.admin_jobunvoid(),
}); });
} else { } else {
notification["error"]({ notification["error"]({

View File

@@ -205,7 +205,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseLines);
const HasBeenConvertedTolabor = ({ value }) => { const HasBeenConvertedTolabor = ({ value }) => {
const { t } = useTranslation(); const { t } = useTranslation();
console.log(value);
if (!value) return null; if (!value) return null;
return ( return (
<Tooltip title={t("joblines.labels.convertedtolabor")}> <Tooltip title={t("joblines.labels.convertedtolabor")}>

View File

@@ -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>
);
}

View File

@@ -1,12 +1,15 @@
import React from "react"; 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 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"; import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
export default function TechLookupContainer() { export default function TechLookupContainer() {
return ( return (
<div> <div>
<TechLookupJobsList /> <RbacWrapperComponent action="jobs:list-active">
<TechLookupJobsDrawer /> <TechLookupJobsList />
<TechLookupJobsDrawer />
</RbacWrapperComponent>
</div> </div>
); );
} }

View File

@@ -40,6 +40,7 @@ const AuditTrailMapping = {
i18n.t("audit_trail.messages.admin_jobmarkforreexport"), i18n.t("audit_trail.messages.admin_jobmarkforreexport"),
admin_jobmarkexported: () => admin_jobmarkexported: () =>
i18n.t("audit_trail.messages.admin_jobmarkexported"), i18n.t("audit_trail.messages.admin_jobmarkexported"),
}; };
export default AuditTrailMapping; export default AuditTrailMapping;

View File

@@ -1,6 +1,7 @@
const DineroQbFormat = require("./accounting-constants").DineroQbFormat; const DineroQbFormat = require("./accounting-constants").DineroQbFormat;
const Dinero = require("dinero.js"); const Dinero = require("dinero.js");
const { DiscountNotAlreadyCounted } = require("../job/job-totals");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
exports.default = function ({ exports.default = function ({
@@ -37,23 +38,22 @@ exports.default = function ({
amount: Math.round((jobline.act_price || 0) * 100), amount: Math.round((jobline.act_price || 0) * 100),
}).multiply(jobline.part_qty || 1); }).multiply(jobline.part_qty || 1);
if ( // console.log("Have a part discount", jobline);
(jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0) || DineroAmount = DineroAmount.add(
((jobline.db_ref === "900511" || ((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) ||
jobline.db_ref === "900510" || (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) &&
jobline.db_ref === "900500") && DiscountNotAlreadyCounted(jobline, jobs_by_pk.joblines)
jobline.prt_dsmk_m && ? 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
? Dinero({ amount: Math.round(jobline.prt_dsmk_m * 100) }) ? Dinero({ amount: Math.round(jobline.prt_dsmk_m * 100) })
: DineroAmount.percentage( : Dinero({
Math.abs(jobline.prt_dsmk_p || 0) amount: Math.round(jobline.act_price * 100),
).multiply(jobline.prt_dsmk_p > 0 ? 1 : -1) })
); .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( const account = responsibilityCenters.profits.find(
(i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase() (i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase()
); );
@@ -82,7 +82,11 @@ exports.default = function ({
state: state:
jobs_by_pk.state_tax_rate === 0 jobs_by_pk.state_tax_rate === 0
? false ? 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 ? true
: jobline.tax_part, : jobline.tax_part,
}, },

View File

@@ -12,6 +12,7 @@ const CdkBase = require("../web-sockets/web-socket");
const Dinero = require("dinero.js"); const Dinero = require("dinero.js");
const _ = require("lodash"); const _ = require("lodash");
const { DiscountNotAlreadyCounted } = require("../job/job-totals");
exports.default = async function (socket, jobid) { exports.default = async function (socket, jobid) {
try { try {
@@ -70,23 +71,20 @@ exports.default = async function (socket, jobid) {
amount: Math.round(val.act_price * 100), amount: Math.round(val.act_price * 100),
}).multiply(val.part_qty || 1); }).multiply(val.part_qty || 1);
if ( DineroAmount = DineroAmount.add(
(val.prt_dsmk_p && val.prt_dsmk_p !== 0) || ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
((val.db_ref === "900511" || (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
val.db_ref === "900510" || DiscountNotAlreadyCounted(val, job.joblines)
val.db_ref === "900500") && ? val.prt_dsmk_m
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
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
: DineroAmount.percentage(Math.abs(val.prt_dsmk_p || 0)).multiply( : Dinero({
val.prt_dsmk_p > 0 ? 1 : -1 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] =
acc[val.profitcenter_part].add(DineroAmount); acc[val.profitcenter_part].add(DineroAmount);

View File

@@ -431,7 +431,7 @@ async function QueryDmsCustomerById(socket, JobData, CustomerId) {
async function QueryDmsCustomerByName(socket, JobData) { async function QueryDmsCustomerByName(socket, JobData) {
const ownerName = ( const ownerName = (
JobData.ownr_co_nm JobData.ownr_co_nm && JobData.ownr_co_nm !== ""
? JobData.ownr_co_nm ? JobData.ownr_co_nm
: `${JobData.ownr_ln},${JobData.ownr_fn}` : `${JobData.ownr_ln},${JobData.ownr_fn}`
).replace(replaceSpecialRegex, ""); ).replace(replaceSpecialRegex, "");
@@ -725,7 +725,7 @@ async function InsertDmsVehicle(socket) {
manufacturer: {}, manufacturer: {},
vehicle: { vehicle: {
deliveryDate: moment() deliveryDate: moment()
// .tz(socket.JobData.bodyshop.timezone) // .tz(socket.JobData.bodyshop.timezone)
.format("YYYYMMDD"), .format("YYYYMMDD"),
licensePlateNo: socket.JobData.plate_no, licensePlateNo: socket.JobData.plate_no,
make: socket.txEnvelope.dms_make, make: socket.txEnvelope.dms_make,
@@ -854,7 +854,7 @@ async function UpdateDmsVehicle(socket) {
socket.DMSVeh.dealer.inServiceDate || socket.DMSVeh.dealer.inServiceDate ||
socket.txEnvelope.inservicedate socket.txEnvelope.inservicedate
) )
// .tz(socket.JobData.bodyshop.timezone) // .tz(socket.JobData.bodyshop.timezone)
.toISOString(), .toISOString(),
}), }),
}, },

View File

@@ -209,6 +209,9 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
prt_dsmk_p prt_dsmk_p
prt_dsmk_m prt_dsmk_m
tax_part tax_part
line_ref
unq_seq
lbr_op
} }
} }
bodyshops(where: {associations: {active: {_eq: true}}}) { bodyshops(where: {associations: {active: {_eq: true}}}) {
@@ -402,6 +405,8 @@ query QUERY_JOBS_FOR_PBS_EXPORT($id: uuid!) {
profitcenter_part profitcenter_part
db_ref db_ref
prt_dsmk_p 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 } }) { joblines(where: { removed: { _eq: false } }) {
id id
db_ref db_ref
line_ref
unq_seq unq_seq
line_ind line_ind
tax_part tax_part
@@ -1220,6 +1226,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
id id
db_ref db_ref
unq_seq unq_seq
line_ref
line_ind line_ind
tax_part tax_part
line_desc line_desc
@@ -1443,7 +1450,8 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
op_code_desc op_code_desc
profitcenter_labor profitcenter_labor
profitcenter_part profitcenter_part
prt_dsmk_p line_ref
unq_seq
} }
} }
} }

View File

@@ -4,6 +4,7 @@ const queries = require("../graphql-client/queries");
const _ = require("lodash"); const _ = require("lodash");
const GraphQLClient = require("graphql-request").GraphQLClient; const GraphQLClient = require("graphql-request").GraphQLClient;
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const { DiscountNotAlreadyCounted } = require("./job-totals");
// Dinero.defaultCurrency = "USD"; // Dinero.defaultCurrency = "USD";
// Dinero.globalLocale = "en-CA"; // Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_EVEN"; Dinero.globalRoundingMode = "HALF_EVEN";
@@ -308,7 +309,8 @@ function GenerateCostingData(job) {
job.bodyshop.md_responsibility_centers.defaults.profits; job.bodyshop.md_responsibility_centers.defaults.profits;
const allCenters = _.union( const allCenters = _.union(
job.bodyshop.md_responsibility_centers.profits.map((p) => p.name), 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 }; const materialsHours = { mapaHrs: 0, mashHrs: 0 };
@@ -330,12 +332,15 @@ function GenerateCostingData(job) {
} }
if (val.mod_lbr_ty) { if (val.mod_lbr_ty) {
const laborProfitCenter = 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); console.log("Unknown type", val.line_desc, val.mod_lbr_ty);
const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`; const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`;
const laborAmount = Dinero({ const laborAmount = Dinero({
amount: Math.round((job[rateName] || 0) * 100), amount: Math.round((job[rateName] || 0) * 100),
}).multiply(val.mod_lb_hrs || 0); }).multiply(val.mod_lb_hrs || 0);
@@ -344,6 +349,19 @@ function GenerateCostingData(job) {
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter] =
acc.labor[laborProfitCenter].add(laborAmount); 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") { if (val.mod_lbr_ty === "LAR") {
materialsHours.mapaHrs += val.mod_lb_hrs || 0; materialsHours.mapaHrs += val.mod_lb_hrs || 0;
} }
@@ -359,9 +377,9 @@ function GenerateCostingData(job) {
val.part_type !== "PASL" val.part_type !== "PASL"
) { ) {
const partsProfitCenter = 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); console.log("Unknown type", val.line_desc, val.part_type);
if (!partsProfitCenter) if (!partsProfitCenter)
@@ -375,14 +393,18 @@ function GenerateCostingData(job) {
}) })
.multiply(val.part_qty || 1) .multiply(val.part_qty || 1)
.add( .add(
val.prt_dsmk_m && val.prt_dsmk_m !== 0 ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
: Dinero({ DiscountNotAlreadyCounted(val, job.joblines)
amount: Math.round(val.act_price * 100), ? val.prt_dsmk_m
}) ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
.multiply(val.part_qty || 0) : Dinero({
.percentage(Math.abs(val.prt_dsmk_p || 0)) amount: Math.round(val.act_price * 100),
.multiply(val.prt_dsmk_p > 0 ? 1 : -1) })
.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]) if (!acc.parts[partsProfitCenter])
acc.parts[partsProfitCenter] = Dinero(); acc.parts[partsProfitCenter] = Dinero();
@@ -395,9 +417,9 @@ function GenerateCostingData(job) {
(val.part_type === "PAS" || val.part_type === "PASL") (val.part_type === "PAS" || val.part_type === "PASL")
) { ) {
const partsProfitCenter = 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); console.log("Unknown type", val.line_desc, val.part_type);
if (!partsProfitCenter) if (!partsProfitCenter)
@@ -411,14 +433,18 @@ function GenerateCostingData(job) {
}) })
.multiply(val.part_qty || 1) .multiply(val.part_qty || 1)
.add( .add(
val.prt_dsmk_m && val.prt_dsmk_m !== 0 ((val.prt_dsmk_m && val.prt_dsmk_m !== 0) ||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
: Dinero({ DiscountNotAlreadyCounted(val, job.joblines)
amount: Math.round(val.act_price * 100), ? val.prt_dsmk_m
}) ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
.multiply(val.part_qty || 0) : Dinero({
.percentage(Math.abs(val.prt_dsmk_p || 0)) amount: Math.round(val.act_price * 100),
.multiply(val.prt_dsmk_p > 0 ? 1 : -1) })
.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]) if (!acc.sublet[partsProfitCenter])
acc.sublet[partsProfitCenter] = Dinero(); acc.sublet[partsProfitCenter] = Dinero();
@@ -433,17 +459,20 @@ function GenerateCostingData(job) {
const partsProfitCenter = const partsProfitCenter =
val.profitcenter_part || val.profitcenter_part ||
getAdditionalCostCenter(val, defaultProfits) || getAdditionalCostCenter(val, defaultProfits) ||
"?"; "Unknown";
if (partsProfitCenter === "?") { if (partsProfitCenter === "Unknown") {
console.log("Unknown type", val.line_desc, val.part_type); console.log("Unknown type", val.line_desc, val.part_type);
} else { }
const partsAmount = Dinero({ const partsAmount = Dinero({
amount: Math.round((val.act_price || 0) * 100), amount: Math.round((val.act_price || 0) * 100),
}) })
.multiply(val.part_qty || 1) .multiply(val.part_qty || 1)
.add( .add(
val.prt_dsmk_m && val.prt_dsmk_m !== 0 ((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.prt_dsmk_m * 100) })
: Dinero({ : Dinero({
amount: Math.round(val.act_price * 100), amount: Math.round(val.act_price * 100),
@@ -451,13 +480,13 @@ function GenerateCostingData(job) {
.multiply(val.part_qty || 0) .multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0)) .percentage(Math.abs(val.prt_dsmk_p || 0))
.multiply(val.prt_dsmk_p > 0 ? 1 : -1) .multiply(val.prt_dsmk_p > 0 ? 1 : -1)
); : Dinero()
);
if (!acc.additional[partsProfitCenter]) if (!acc.additional[partsProfitCenter])
acc.additional[partsProfitCenter] = Dinero(); acc.additional[partsProfitCenter] = Dinero();
acc.additional[partsProfitCenter] = acc.additional[partsProfitCenter] =
acc.additional[partsProfitCenter].add(partsAmount); acc.additional[partsProfitCenter].add(partsAmount);
}
} }
return acc; return acc;

View File

@@ -362,28 +362,27 @@ function CalculateRatesTotals(ratesList) {
} }
function CalculatePartsTotals(jobLines) { function CalculatePartsTotals(jobLines) {
const ret = jobLines const jl = jobLines.filter((jl) => !jl.removed);
.filter((jl) => !jl.removed)
.reduce( const ret = jl.reduce(
(acc, value) => { (acc, value) => {
switch (value.part_type) { switch (value.part_type) {
case "PAS": case "PAS":
case "PASL": case "PASL":
return { return {
...acc, ...acc,
sublets: { sublets: {
...acc.sublets, ...acc.sublets,
subtotal: acc.sublets.subtotal.add( subtotal: acc.sublets.subtotal.add(
Dinero({ Dinero({
amount: Math.round(value.act_price * 100), amount: Math.round(value.act_price * 100),
}) })
.multiply(value.part_qty || 0) .multiply(value.part_qty || 0)
.add( .add(
(value.db_ref === "900511" || ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) ||
value.db_ref === "900510" || (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) &&
value.db_ref === "900500") && DiscountNotAlreadyCounted(value, jl)
value.prt_dsmk_m && ? value.prt_dsmk_m
value.prt_dsmk_m !== 0
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
: Dinero({ : Dinero({
amount: Math.round(value.act_price * 100), amount: Math.round(value.act_price * 100),
@@ -391,28 +390,28 @@ function CalculatePartsTotals(jobLines) {
.multiply(value.part_qty || 0) .multiply(value.part_qty || 0)
.percentage(Math.abs(value.prt_dsmk_p || 0)) .percentage(Math.abs(value.prt_dsmk_p || 0))
.multiply(value.prt_dsmk_p > 0 ? 1 : -1) .multiply(value.prt_dsmk_p > 0 ? 1 : -1)
) : Dinero()
), )
}, ),
}; },
};
default: default:
if ( if (
!value.part_type && !value.part_type &&
value.db_ref !== "900510" && value.db_ref !== "900510" &&
value.db_ref !== "900511" value.db_ref !== "900511"
) )
return acc; return acc;
return { return {
...acc, ...acc,
parts: { parts: {
...acc.parts, ...acc.parts,
prt_dsmk_total: acc.parts.prt_dsmk_total.add( prt_dsmk_total: acc.parts.prt_dsmk_total.add(
(value.db_ref === "900511" || ((value.prt_dsmk_m && value.prt_dsmk_m !== 0) ||
value.db_ref === "900510" || (value.prt_dsmk_p && value.prt_dsmk_p !== 0)) &&
value.db_ref === "900500") && DiscountNotAlreadyCounted(value, jl)
value.prt_dsmk_m && ? value.prt_dsmk_m
value.prt_dsmk_m !== 0
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
: Dinero({ : Dinero({
amount: Math.round(value.act_price * 100), amount: Math.round(value.act_price * 100),
@@ -420,47 +419,45 @@ function CalculatePartsTotals(jobLines) {
.multiply(value.part_qty || 0) .multiply(value.part_qty || 0)
.percentage(Math.abs(value.prt_dsmk_p || 0)) .percentage(Math.abs(value.prt_dsmk_p || 0))
.multiply(value.prt_dsmk_p > 0 ? 1 : -1) .multiply(value.prt_dsmk_p > 0 ? 1 : -1)
), : Dinero()
...(value.part_type ),
? { ...(value.part_type
list: { ? {
...acc.parts.list, list: {
[value.part_type]: ...acc.parts.list,
acc.parts.list[value.part_type] && [value.part_type]:
acc.parts.list[value.part_type].total acc.parts.list[value.part_type] &&
? { acc.parts.list[value.part_type].total
total: acc.parts.list[ ? {
value.part_type total: acc.parts.list[value.part_type].total.add(
].total.add( Dinero({
Dinero({
amount: Math.round(
(value.act_price || 0) * 100
),
}).multiply(value.part_qty || 0)
),
}
: {
total: Dinero({
amount: Math.round( amount: Math.round(
(value.act_price || 0) * 100 (value.act_price || 0) * 100
), ),
}).multiply(value.part_qty || 0), }).multiply(value.part_qty || 0)
}, ),
}, }
} : {
: {}), total: Dinero({
subtotal: acc.parts.subtotal amount: Math.round(
.add( (value.act_price || 0) * 100
Dinero({ ),
amount: Math.round(value.act_price * 100), }).multiply(value.part_qty || 0),
}).multiply(value.part_qty || 0) },
) },
.add( }
(value.db_ref === "900511" || : {}),
value.db_ref === "900510" || subtotal: acc.parts.subtotal
value.db_ref === "900500") && .add(
value.prt_dsmk_m && Dinero({
value.prt_dsmk_m !== 0 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.prt_dsmk_m * 100) })
: Dinero({ : Dinero({
amount: Math.round(value.act_price * 100), amount: Math.round(value.act_price * 100),
@@ -468,25 +465,26 @@ function CalculatePartsTotals(jobLines) {
.multiply(value.part_qty || 0) .multiply(value.part_qty || 0)
.percentage(Math.abs(value.prt_dsmk_p || 0)) .percentage(Math.abs(value.prt_dsmk_p || 0))
.multiply(value.prt_dsmk_p > 0 ? 1 : -1) .multiply(value.prt_dsmk_p > 0 ? 1 : -1)
), : Dinero()
}, ),
}; },
} };
},
{
parts: {
list: {},
prt_dsmk_total: Dinero(),
subtotal: Dinero({ amount: 0 }),
total: Dinero({ amount: 0 }),
},
sublets: {
subtotal: Dinero({ amount: 0 }),
total: Dinero({ amount: 0 }),
},
} }
); },
{
parts: {
list: {},
prt_dsmk_total: Dinero(),
subtotal: Dinero({ amount: 0 }),
total: Dinero({ amount: 0 }),
},
sublets: {
subtotal: Dinero({ amount: 0 }),
total: Dinero({ amount: 0 }),
},
}
);
return { return {
parts: { parts: {
@@ -706,7 +704,16 @@ function CalculateTaxesTotals(job, otherTotals) {
exports.default = Totals; exports.default = Totals;
function DiscountNotAlreadyCounted(jobline, joblines) { 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); const ParentLine = joblines.find((j) => j.unq_seq === jobline.line_ref);
return ParentLine && !(ParentLine.prt_dsmk_m && ParentLine.prt_dsmk_m !== 0); return ParentLine && !(ParentLine.prt_dsmk_m && ParentLine.prt_dsmk_m !== 0);
} }
exports.DiscountNotAlreadyCounted = DiscountNotAlreadyCounted;