IO-2278 Begin parts dispatching including basic items and schema.

This commit is contained in:
Patrick Fic
2023-06-01 13:27:08 -07:00
parent de62e994bd
commit 68f4237e15
13 changed files with 388 additions and 16 deletions

View File

@@ -19356,6 +19356,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>dispatchparts</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>new</name>
<definition_loaded>false</definition_loaded>
@@ -37411,6 +37432,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>markexported</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>markforreexport</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>new</name>
<definition_loaded>false</definition_loaded>
@@ -37542,6 +37605,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>markreexported</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>payment</name>
<definition_loaded>false</definition_loaded>
@@ -39355,6 +39439,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>mpi_final_repair_acct_sheet</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>paint_grid</name>
<definition_loaded>false</definition_loaded>
@@ -40433,6 +40538,27 @@
<folder_node>
<name>jobs</name>
<children>
<concept_node>
<name>individual_job_note</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>parts_order</name>
<definition_loaded>false</definition_loaded>
@@ -44378,6 +44504,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>estimators</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>ins_co_nm_filter</name>
<definition_loaded>false</definition_loaded>

View File

@@ -45,6 +45,7 @@ import JobLinesExpander from "./job-lines-expander.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
import JobLineDispatchButton from "../job-line-dispatch-button/job-line-dispatch-button.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -440,6 +441,11 @@ export function JobLinesComponent({
</Space>
</Tag>
)}
<JobLineDispatchButton
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
job={job}
/>
<Button
disabled={
(job && !job.converted) ||
@@ -448,15 +454,6 @@ export function JobLinesComponent({
technician
}
onClick={() => {
// setPartsOrderContext({
// actions: { refetch: refetch },
// context: {
// jobId: job.id,
// job: job,
// linesToOrder: selectedLines,
// },
// });
setBillEnterContext({
actions: { refetch: refetch },
context: {

View File

@@ -0,0 +1,152 @@
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { Button, Form, Popover, Select, Space } from "antd";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { useTranslation } from "react-i18next";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import { TemplateList } from "../../utils/TemplateConstants";
import { useMutation } from "@apollo/client";
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobLineDispatchButton);
export function JobLineDispatchButton({
setSelectedLines,
selectedLines,
bodyshop,
jobRO,
job,
currentUser,
}) {
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const Templates = TemplateList("job_special", {
ro_number: job.ro_number,
});
const { t } = useTranslation();
const [dispatchLines] = useMutation(UPDATE_JOB_LINE);
const handleConvert = async (values) => {
try {
setLoading(true);
//THIS HAS NOT YET BEEN TESTED. START BY FINISHING THIS FUNCTION.
const result = await dispatchLines({
variables: {
joblinesids: selectedLines.map((l) => l.id),
employeeid: values.employeeid,
note: {
audit: true,
type: "parts",
jobid: job.id,
created_by: currentUser.email,
text: `${t("joblines.labels.dispatchaudit")}
${selectedLines.map((line) => `line.line_desc \r\n`)}
`,
},
},
});
setVisible(false);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const popMenu = (
<div>
<Form layout="vertical" form={form} onFinish={handleConvert}>
<Form.Item
name={"employeeid"}
label={t("jobs.fields.employeeid")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
showSearch
style={{ width: 200 }}
optionFilterProp="children"
filterOption={(input, option) =>
option.props.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
>
{bodyshop.employees
.filter((emp) => emp.active)
.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item shouldUpdate>
{() => {
return (
<PrintWrapperComponent
disabled={!form.getFieldValue("employeeid")}
templateObject={{
name: Templates.parts_dispatch.key,
variables: { id: job.id },
}}
messageObject={{
subject: Templates.parts_dispatch.subject,
}}
id={job.id}
/>
);
}}
</Form.Item>
<Space wrap>
<Button type="danger" onClick={() => form.submit()} loading={loading}>
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisible(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</div>
);
return (
<Popover open={visible} content={popMenu}>
<Button
disabled={selectedLines.length === 0 || jobRO}
loading={loading}
onClick={() => setVisible(true)}
>
{t("joblines.actions.dispatchparts", { count: selectedLines.length })}
</Button>
</Popover>
);
}

View File

@@ -9,9 +9,11 @@ export default function PrintWrapperComponent({
children,
id,
emailOnly = false,
disabled,
}) {
const [loading, setLoading] = useState(false);
const handlePrint = async (type) => {
if (disabled) return;
setLoading(true);
await GenerateDocument(templateObject, messageObject, type, id);
setLoading(false);
@@ -20,8 +22,18 @@ export default function PrintWrapperComponent({
return (
<Space>
{children || null}
{!emailOnly && <PrinterFilled onClick={() => handlePrint("p")} />}
<MailFilled onClick={() => handlePrint("e")} />
{!emailOnly && (
<PrinterFilled
disabled={disabled}
onClick={() => handlePrint("p")}
style={{ cursor: disabled ? "not-allowed" : null }}
/>
)}
<MailFilled
disabled={disabled}
onClick={() => handlePrint("e")}
style={{ cursor: disabled ? "not-allowed" : null }}
/>
{loading && <Spin />}
</Space>
);

View File

@@ -1208,6 +1208,7 @@
"joblines": {
"actions": {
"converttolabor": "Convert amount to Labor.",
"dispatchparts": "Dispatch Parts ({{count}})",
"new": "New Line"
},
"errors": {
@@ -2214,17 +2215,17 @@
"external": "External",
"findermodal": "ICBC Payment Finder",
"insurance": "Insurance",
"markexported": "Mark Exported",
"markforreexport": "Mark for Re-export",
"new": "New Payment",
"signup": "Please contact support to sign up for electronic payments.",
"title": "Payments",
"totalpayments": "Total Payments",
"markexported": "Mark Exported",
"markforreexport": "Mark for Re-export"
"totalpayments": "Total Payments"
},
"successes": {
"exported": "Payment(s) exported successfully.",
"markreexported": "Payment marked for re-export successfully",
"markexported": "Payment(s) marked exported.",
"markreexported": "Payment marked for re-export successfully",
"payment": "Payment created successfully. ",
"stripe": "Credit card transaction charged successfully."
}
@@ -2623,8 +2624,8 @@
"labels": {
"atssummary": "ATS Summary",
"employeevacation": "Employee Vacations",
"ins_co_nm_filter": "Filter by Insurance Company",
"estimators": "Filter by Writer/Customer Rep.",
"ins_co_nm_filter": "Filter by Insurance Company",
"intake": "Intake Events",
"manual": "Manual Events",
"manualevent": "Add Manual Event"

View File

@@ -1208,6 +1208,7 @@
"joblines": {
"actions": {
"converttolabor": "",
"dispatchparts": "",
"new": ""
},
"errors": {
@@ -2214,6 +2215,8 @@
"external": "",
"findermodal": "",
"insurance": "",
"markexported": "",
"markforreexport": "",
"new": "",
"signup": "",
"title": "",
@@ -2222,6 +2225,7 @@
"successes": {
"exported": "",
"markexported": "",
"markreexported": "",
"payment": "",
"stripe": ""
}
@@ -2620,6 +2624,7 @@
"labels": {
"atssummary": "",
"employeevacation": "",
"estimators": "",
"ins_co_nm_filter": "",
"intake": "",
"manual": "",

View File

@@ -1208,6 +1208,7 @@
"joblines": {
"actions": {
"converttolabor": "",
"dispatchparts": "",
"new": ""
},
"errors": {
@@ -2214,6 +2215,8 @@
"external": "",
"findermodal": "",
"insurance": "",
"markexported": "",
"markforreexport": "",
"new": "",
"signup": "",
"title": "",
@@ -2222,6 +2225,7 @@
"successes": {
"exported": "",
"markexported": "",
"markreexported": "",
"payment": "",
"stripe": ""
}
@@ -2620,6 +2624,7 @@
"labels": {
"atssummary": "",
"employeevacation": "",
"estimators": "",
"ins_co_nm_filter": "",
"intake": "",
"manual": "",

View File

@@ -558,6 +558,15 @@ export const TemplateList = (type, context) => {
}),
disabled: false,
},
parts_dispatch: {
title: i18n.t("printcenter.jobs.parts_dispatch"),
description: "",
key: "parts_dispatch",
subject: i18n.t("printcenter.subjects.jobs.parts_dispatch", {
ro_number: (context && context.ro_number) || "",
}),
disabled: false,
},
}
: {}),
...(!type || type === "appointment"

View File

@@ -4556,6 +4556,12 @@
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2
- table:
name: parts_dispatch
schema: public
- table:
name: parts_dispatch_lines
schema: public
- table:
name: parts_order_lines
schema: public

View File

@@ -0,0 +1 @@
DROP TABLE "public"."parts_dispatch";

View File

@@ -0,0 +1,18 @@
CREATE TABLE "public"."parts_dispatch" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "jobid" uuid NOT NULL, "number" serial NOT NULL, "employeeid" uuid NOT NULL, "dispatched_at" timestamptz NOT NULL, "dispatched_by" text NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("jobid") REFERENCES "public"."jobs"("id") ON UPDATE cascade ON DELETE cascade);
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
RETURNS TRIGGER AS $$
DECLARE
_new record;
BEGIN
_new := NEW;
_new."updated_at" = NOW();
RETURN _new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER "set_public_parts_dispatch_updated_at"
BEFORE UPDATE ON "public"."parts_dispatch"
FOR EACH ROW
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
COMMENT ON TRIGGER "set_public_parts_dispatch_updated_at" ON "public"."parts_dispatch"
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@@ -0,0 +1 @@
DROP TABLE "public"."parts_dispatch_lines";

View File

@@ -0,0 +1,18 @@
CREATE TABLE "public"."parts_dispatch_lines" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "partsdispatchid" UUID NOT NULL, "joblineid" uuid NOT NULL, "quantity" numeric NOT NULL DEFAULT 1, "accepted_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("joblineid") REFERENCES "public"."joblines"("id") ON UPDATE cascade ON DELETE cascade);
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
RETURNS TRIGGER AS $$
DECLARE
_new record;
BEGIN
_new := NEW;
_new."updated_at" = NOW();
RETURN _new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER "set_public_parts_dispatch_lines_updated_at"
BEFORE UPDATE ON "public"."parts_dispatch_lines"
FOR EACH ROW
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
COMMENT ON TRIGGER "set_public_parts_dispatch_lines_updated_at" ON "public"."parts_dispatch_lines"
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
CREATE EXTENSION IF NOT EXISTS pgcrypto;