@@ -1,4 +1,4 @@
|
||||
<babeledit_project version="1.2" be_version="2.7.1">
|
||||
<babeledit_project be_version="2.7.1" version="1.2">
|
||||
<!--
|
||||
|
||||
BabelEdit project file
|
||||
@@ -6898,6 +6898,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>editcommitted</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>enter</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -6963,6 +6984,53 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>ttapprovals</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>approve</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>view</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>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>users</name>
|
||||
<children>
|
||||
@@ -7246,6 +7314,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>invoiceexemptcode</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>itemexemptcode</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>la1</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -8932,6 +9042,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>use_paint_scale_data</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>uselocalmediaserver</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -16464,6 +16595,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>excel</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>exceptiontitle</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -32471,6 +32623,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ttapprovals</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>vehicles</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -33634,6 +33807,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>savetojobnotes</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>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
@@ -37756,6 +37950,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>dms_posting_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>envelope_return_address</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -41409,6 +41624,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>exported_gsr_by_ro</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>exported_gsr_by_ro_labor</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>gsr_by_atp</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -44565,6 +44822,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>committed</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>cost_center</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -44649,6 +44927,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>employee_team</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>flat_rate</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -46270,6 +46569,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ttapprovals</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>vehicle-details</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -47196,6 +47516,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ttapprovals</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>vehicledetail</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -47240,6 +47581,37 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>tt_approvals</name>
|
||||
<children>
|
||||
<folder_node>
|
||||
<name>actions</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>approveselected</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>
|
||||
</children>
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>user</name>
|
||||
<children>
|
||||
|
||||
@@ -143,13 +143,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Update row highlighting on production board.
|
||||
//Update row highlighting on production board.
|
||||
.ant-table-tbody > tr.ant-table-row:hover > td {
|
||||
background: #eaeaea !important;
|
||||
}
|
||||
|
||||
.job-line-manual{
|
||||
.job-line-manual {
|
||||
color: tomato;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
td.ant-table-column-sort {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import {
|
||||
Button, Form,
|
||||
Button,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -466,7 +467,7 @@ export function BillEnterModalLinesComponent({
|
||||
return {
|
||||
key: `${field.index}fedtax`,
|
||||
valuePropName: "checked",
|
||||
initialValue: true,
|
||||
// initialValue: true,
|
||||
name: [field.name, "applicable_taxes", "federal"],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -7,7 +7,9 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import GlobalSearch from "../global-search/global-search.component";
|
||||
import GlobalSearchOs from "../global-search/global-search-os.component";
|
||||
import "./breadcrumbs.styles.scss";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
breadcrumbs: selectBreadcrumbs,
|
||||
@@ -15,6 +17,12 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
||||
const { OpenSearch } = useTreatments(
|
||||
["OpenSearch"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
return (
|
||||
<Row className="breadcrumb-container">
|
||||
<Col xs={24} sm={24} md={16}>
|
||||
@@ -38,7 +46,7 @@ export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
||||
</Breadcrumb>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={8}>
|
||||
<GlobalSearch />
|
||||
{OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,10 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
logImEXEvent("job_ca_bc_pvrt_calculate");
|
||||
form.setFieldsValue({ ca_bc_pvrt: ((values.rate||0) * (values.days||0)).toFixed(2) });
|
||||
form.setFieldsValue({
|
||||
ca_bc_pvrt: ((values.rate || 0) * (values.days || 0)).toFixed(2),
|
||||
});
|
||||
form.setFields([{ name: "ca_bc_pvrt", touched: true }]);
|
||||
setVisibility(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import { AutoComplete, Divider, Input, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useHistory } from "react-router-dom";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay, {
|
||||
OwnerNameDisplayFunction,
|
||||
} from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
|
||||
export default function GlobalSearchOs() {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(false);
|
||||
|
||||
const executeSearch = async (v) => {
|
||||
if (v && v && v !== "" && v.length >= 3) {
|
||||
try {
|
||||
setLoading(true);
|
||||
const searchData = await axios.post("/search", {
|
||||
search: v,
|
||||
});
|
||||
|
||||
const resultsByType = {
|
||||
payments: [],
|
||||
jobs: [],
|
||||
bills: [],
|
||||
owners: [],
|
||||
vehicles: [],
|
||||
};
|
||||
|
||||
searchData.data.hits.hits.forEach((hit) => {
|
||||
resultsByType[hit._index].push(hit._source);
|
||||
});
|
||||
setData([
|
||||
{
|
||||
label: renderTitle(t("menus.header.search.jobs")),
|
||||
options: resultsByType.jobs.map((job) => {
|
||||
return {
|
||||
key: job.id,
|
||||
value: job.ro_number || "N/A",
|
||||
label: (
|
||||
<Link to={`/manage/jobs/${job.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
<strong>{job.ro_number || t("general.labels.na")}</strong>
|
||||
<span>{`${job.status || ""}`}</span>
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={job} />
|
||||
</span>
|
||||
<span>{`${job.v_model_yr || ""} ${
|
||||
job.v_make_desc || ""
|
||||
} ${job.v_model_desc || ""}`}</span>
|
||||
<span>{`${job.clm_no || ""}`}</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: renderTitle(t("menus.header.search.owners")),
|
||||
options: resultsByType.owners.map((owner) => {
|
||||
return {
|
||||
key: owner.id,
|
||||
value: OwnerNameDisplayFunction(owner),
|
||||
label: (
|
||||
<Link to={`/manage/owners/${owner.id}`}>
|
||||
<Space
|
||||
size="small"
|
||||
split={<Divider type="vertical" />}
|
||||
wrap
|
||||
>
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={owner} />
|
||||
</span>
|
||||
<PhoneNumberFormatter>
|
||||
{owner.ownr_ph1}
|
||||
</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter>
|
||||
{owner.ownr_ph2}
|
||||
</PhoneNumberFormatter>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: renderTitle(t("menus.header.search.vehicles")),
|
||||
options: resultsByType.vehicles.map((vehicle) => {
|
||||
return {
|
||||
key: vehicle.id,
|
||||
value: `${vehicle.v_model_yr || ""} ${
|
||||
vehicle.v_make_desc || ""
|
||||
} ${vehicle.v_model_desc || ""}`,
|
||||
label: (
|
||||
<Link to={`/manage/vehicles/${vehicle.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
<span>
|
||||
{`${vehicle.v_model_yr || ""} ${
|
||||
vehicle.v_make_desc || ""
|
||||
} ${vehicle.v_model_desc || ""}`}
|
||||
</span>
|
||||
<span>{vehicle.plate_no || ""}</span>
|
||||
<span>
|
||||
<VehicleVinDisplay>
|
||||
{vehicle.v_vin || ""}
|
||||
</VehicleVinDisplay>
|
||||
</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: renderTitle(t("menus.header.search.payments")),
|
||||
options: resultsByType.payments.map((payment) => {
|
||||
return {
|
||||
key: payment.id,
|
||||
value: `${payment.job?.ro_number} ${payment.amount}`,
|
||||
label: (
|
||||
<Link to={`/manage/jobs/${payment.job?.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
<span>{payment.paymentnum}</span>
|
||||
<span>{payment.job?.ro_number}</span>
|
||||
<span>{payment.memo || ""}</span>
|
||||
<span>{payment.amount || ""}</span>
|
||||
<span>{payment.transactionid || ""}</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: renderTitle(t("menus.header.search.bills")),
|
||||
options: resultsByType.bills.map((bill) => {
|
||||
return {
|
||||
key: bill.id,
|
||||
value: `${bill.invoice_number} - ${bill.vendor.name}`,
|
||||
label: (
|
||||
<Link to={`/manage/bills?billid=${bill.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
<span>{bill.invoice_number}</span>
|
||||
<span>{bill.vendor.name}</span>
|
||||
<span>{bill.date}</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
// {
|
||||
// label: renderTitle(t("menus.header.search.phonebook")),
|
||||
// options: resultsByType.search_phonebook.map((pb) => {
|
||||
// return {
|
||||
// key: pb.id,
|
||||
// value: `${pb.firstname || ""} ${pb.lastname || ""} ${
|
||||
// pb.company || ""
|
||||
// }`,
|
||||
// label: (
|
||||
// <Link to={`/manage/phonebook?phonebookentry=${pb.id}`}>
|
||||
// <Space size="small" split={<Divider type="vertical" />}>
|
||||
// <span>{`${pb.firstname || ""} ${pb.lastname || ""} ${
|
||||
// pb.company || ""
|
||||
// }`}</span>
|
||||
// <PhoneNumberFormatter>{pb.phone1}</PhoneNumberFormatter>
|
||||
// <span>{pb.email}</span>
|
||||
// </Space>
|
||||
// </Link>
|
||||
// ),
|
||||
// };
|
||||
// }),
|
||||
// },
|
||||
]);
|
||||
} catch (error) {
|
||||
console.log("Error while fetching search results", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
debouncedExecuteSearch(value);
|
||||
};
|
||||
|
||||
const renderTitle = (title) => {
|
||||
return <span>{title}</span>;
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoComplete
|
||||
options={data}
|
||||
onSearch={handleSearch}
|
||||
defaultActiveFirstOption
|
||||
onSelect={(val, opt) => {
|
||||
history.push(opt.label.props.to);
|
||||
}}
|
||||
onClear={() => setData([])}
|
||||
>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
enterButton
|
||||
allowClear
|
||||
loading={loading}
|
||||
/>
|
||||
</AutoComplete>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import OwnerNameDisplay, {
|
||||
OwnerNameDisplayFunction
|
||||
OwnerNameDisplayFunction,
|
||||
} from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
export default function GlobalSearch() {
|
||||
@@ -18,11 +18,18 @@ export default function GlobalSearch() {
|
||||
useLazyQuery(GLOBAL_SEARCH_QUERY);
|
||||
|
||||
const executeSearch = (v) => {
|
||||
if (v && v.variables.search && v.variables.search !== "") callSearch(v);
|
||||
if (
|
||||
v &&
|
||||
v.variables.search &&
|
||||
v.variables.search !== "" &&
|
||||
v.variables.search.length >= 3
|
||||
)
|
||||
callSearch(v);
|
||||
};
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
console.log("Handle Search");
|
||||
debouncedExecuteSearch({ variables: { search: value } });
|
||||
};
|
||||
|
||||
@@ -37,7 +44,7 @@ export default function GlobalSearch() {
|
||||
options: data.search_jobs.map((job) => {
|
||||
return {
|
||||
key: job.id,
|
||||
value: job.ro_number,
|
||||
value: job.ro_number || "N/A",
|
||||
label: (
|
||||
<Link to={`/manage/jobs/${job.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
|
||||
@@ -262,6 +262,11 @@ function Header({
|
||||
{t("menus.header.timetickets")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="ttapprovals" icon={<FieldTimeOutlined />}>
|
||||
<Link to="/manage/ttapprovals">
|
||||
{t("menus.header.ttapprovals")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="entertimetickets"
|
||||
icon={<Icon component={GiPlayerTime} />}
|
||||
|
||||
@@ -82,7 +82,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
>
|
||||
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
|
||||
</Form.Item>
|
||||
<Space align="end">
|
||||
<Space align="center">
|
||||
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -34,8 +34,8 @@ function JobsDocumentsComponent({
|
||||
const fileType = DetermineFileType(value.type);
|
||||
if (value.type.startsWith("image")) {
|
||||
acc.images.push({
|
||||
src: GenerateSrcUrl(value),
|
||||
thumbnail: GenerateThumbUrl(value),
|
||||
// src: GenerateSrcUrl(value),
|
||||
src: GenerateThumbUrl(value),
|
||||
// src: GenerateSrcUrl(value),
|
||||
// thumbnail: GenerateThumbUrl(value),
|
||||
fullsize: GenerateSrcUrl(value),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Gallery } from "react-grid-gallery";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
|
||||
import { GenerateThumbUrl } from "./job-documents.utility";
|
||||
|
||||
function JobsDocumentGalleryExternal({
|
||||
data,
|
||||
@@ -15,8 +15,8 @@ function JobsDocumentGalleryExternal({
|
||||
let documents = data.reduce((acc, value) => {
|
||||
if (value.type.startsWith("image")) {
|
||||
acc.push({
|
||||
src: GenerateSrcUrl(value),
|
||||
thumbnail: GenerateThumbUrl(value),
|
||||
//src: GenerateSrcUrl(value),
|
||||
src: GenerateThumbUrl(value),
|
||||
thumbnailHeight: 225,
|
||||
thumbnailWidth: 225,
|
||||
isSelected: false,
|
||||
|
||||
@@ -52,11 +52,15 @@ function JobDocumentsLocalGalleryExternal({
|
||||
val.type.mime &&
|
||||
val.type.mime.startsWith("image")
|
||||
) {
|
||||
acc.push(val);
|
||||
acc.push({ ...val, src: val.thumbnail });
|
||||
}
|
||||
return acc;
|
||||
}, [])
|
||||
: [];
|
||||
console.log(
|
||||
"🚀 ~ file: jobs-documents-local-gallery.external.component.jsx:48 ~ useEffect ~ documents:",
|
||||
documents
|
||||
);
|
||||
|
||||
setgalleryImages(documents);
|
||||
}, [allMedia, jobId, setgalleryImages, t]);
|
||||
|
||||
@@ -34,7 +34,7 @@ export function NoteUpsertModalContainer({
|
||||
const [updateNote] = useMutation(UPDATE_NOTE);
|
||||
|
||||
const { visible, context, actions } = noteUpsertModal;
|
||||
const { jobId, existingNote } = context;
|
||||
const { jobId, existingNote, text } = context;
|
||||
const { refetch } = actions;
|
||||
|
||||
const [form] = Form.useForm();
|
||||
@@ -45,8 +45,12 @@ export function NoteUpsertModalContainer({
|
||||
form.setFieldsValue(existingNote);
|
||||
} else if (!existingNote && visible) {
|
||||
form.resetFields();
|
||||
|
||||
if (text) {
|
||||
form.setFieldValue("text", text);
|
||||
}
|
||||
}
|
||||
}, [existingNote, form, visible]);
|
||||
}, [existingNote, form, visible, text]);
|
||||
|
||||
const handleFinish = async (formValues) => {
|
||||
const { relatedros, ...values } = formValues;
|
||||
@@ -82,6 +86,7 @@ export function NoteUpsertModalContainer({
|
||||
{ ...values, jobid: jobId, created_by: currentUser.email },
|
||||
],
|
||||
},
|
||||
refetchQueries: ["QUERY_NOTES_BY_JOB_PK"],
|
||||
});
|
||||
|
||||
if (AdditionalNoteInserts.length > 0) {
|
||||
|
||||
@@ -201,6 +201,7 @@ export function PartsOrderListTableComponent({
|
||||
subject: record.return
|
||||
? Templates.parts_return_slip.subject
|
||||
: Templates.parts_order.subject,
|
||||
to: record.vendor.email,
|
||||
}}
|
||||
id={job.id}
|
||||
/>
|
||||
@@ -296,7 +297,6 @@ export function PartsOrderListTableComponent({
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
|
||||
},
|
||||
|
||||
{
|
||||
title: t("parts_orders.fields.act_price"),
|
||||
dataIndex: "act_price",
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
import Icon from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Input, Popover } from "antd";
|
||||
import { Button, Input, Popover, Space } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaRegStickyNote } from "react-icons/fa";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
export default function ProductionListColumnProductionNote({ record }) {
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setNoteUpsertContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "noteUpsert" })),
|
||||
});
|
||||
|
||||
function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [note, setNote] = useState(
|
||||
@@ -60,12 +71,26 @@ export default function ProductionListColumnProductionNote({ record }) {
|
||||
// onPressEnter={handleSaveNote}
|
||||
autoFocus
|
||||
allowClear
|
||||
style={{ marginBottom: "1em" }}
|
||||
/>
|
||||
<div>
|
||||
<Button onClick={handleSaveNote}>
|
||||
<Space>
|
||||
<Button onClick={handleSaveNote} type="primary">
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setVisible(false);
|
||||
setNoteUpsertContext({
|
||||
context: {
|
||||
jobId: record.id,
|
||||
text: note,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("notes.actions.savetojobnotes")}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
trigger={["click"]}
|
||||
@@ -85,3 +110,8 @@ export default function ProductionListColumnProductionNote({ record }) {
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProductionListColumnProductionNote);
|
||||
|
||||
@@ -92,7 +92,11 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
|
||||
to: values.to,
|
||||
subject: Templates[values.key]?.subject,
|
||||
},
|
||||
values.sendby === "email" ? "e" : "p",
|
||||
values.sendbyexcel === "excel"
|
||||
? "x"
|
||||
: values.sendby === "email"
|
||||
? "e"
|
||||
: "p",
|
||||
id
|
||||
);
|
||||
setLoading(false);
|
||||
@@ -250,15 +254,38 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
|
||||
ranges={DatePIckerRanges}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("general.labels.sendby")}
|
||||
name="sendby"
|
||||
initialValue="print"
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value="email">{t("general.labels.email")}</Radio>
|
||||
<Radio value="print">{t("general.labels.print")}</Radio>
|
||||
</Radio.Group>
|
||||
<Form.Item style={{ margin: 0, padding: 0 }} dependencies={["key"]}>
|
||||
{() => {
|
||||
const key = form.getFieldValue("key");
|
||||
//Kind of Id
|
||||
const reporttype = Templates[key] && Templates[key].reporttype;
|
||||
|
||||
if (reporttype === "excel")
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("general.labels.sendby")}
|
||||
name="sendbyexcel"
|
||||
initialValue="excel"
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value="excel">{t("general.labels.excel")}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
);
|
||||
if (reporttype !== "excel")
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("general.labels.sendby")}
|
||||
name="sendby"
|
||||
initialValue="print"
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value="email">{t("general.labels.email")}</Radio>
|
||||
<Radio value="print">{t("general.labels.print")}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<div
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
const CustomTooltip = ({ active, payload, label }) => {
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
border: "1px solid gray",
|
||||
padding: "0.5rem",
|
||||
}}
|
||||
>
|
||||
<p style={{ margin: "0" }}>{label}</p>
|
||||
{payload.map((data, index) => {
|
||||
if (data.dataKey === "sales" || data.dataKey === "accSales")
|
||||
return (
|
||||
<p
|
||||
style={{ margin: "10px 0", color: data.color }}
|
||||
key={index}
|
||||
>{`${data.name} : ${Dinero({
|
||||
amount: Math.round(data.value * 100),
|
||||
}).toFormat()}`}</p>
|
||||
);
|
||||
|
||||
return (
|
||||
<p
|
||||
style={{ margin: "10px 0", color: data.color }}
|
||||
key={index}
|
||||
>{`${data.name} : ${data.value}`}</p>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default CustomTooltip;
|
||||
@@ -1,3 +1,4 @@
|
||||
import Dinero from "dinero.js";
|
||||
import { Card } from "antd";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
@@ -18,6 +19,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
import _ from "lodash";
|
||||
import CustomTooltip from "./chart-custom-tooltip";
|
||||
|
||||
const graphProps = {
|
||||
strokeWidth: 3,
|
||||
@@ -44,14 +46,19 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
||||
return {
|
||||
bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs,
|
||||
painthrs: dayAcc.painthrs + dayVal.painthrs,
|
||||
sales:
|
||||
dayAcc.painthrs +
|
||||
dayVal.job.job_totals.totals.subtotal.amount / 100 +
|
||||
2500,
|
||||
};
|
||||
},
|
||||
{ bodyhrs: 0, painthrs: 0 }
|
||||
{ bodyhrs: 0, painthrs: 0, sales: 0 }
|
||||
);
|
||||
} else {
|
||||
dayhrs = {
|
||||
bodyhrs: 0,
|
||||
painthrs: 0,
|
||||
sales: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -73,6 +80,13 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
||||
: dayhrs.painthrs + dayhrs.bodyhrs,
|
||||
1
|
||||
),
|
||||
sales: _.round(dayhrs.sales, 2),
|
||||
accSales: _.round(
|
||||
acc.length > 0
|
||||
? acc[acc.length - 1].accSales + dayhrs.sales
|
||||
: dayhrs.sales,
|
||||
2
|
||||
),
|
||||
};
|
||||
|
||||
return [...acc, theValue];
|
||||
@@ -87,22 +101,27 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
||||
>
|
||||
<CartesianGrid stroke="#f5f5f5" />
|
||||
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
|
||||
<YAxis strokeWidth={graphProps.strokeWidth} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Area
|
||||
type="monotone"
|
||||
name="Accumulated Hours"
|
||||
dataKey="accHrs"
|
||||
fill="lightgreen"
|
||||
stroke="green"
|
||||
<YAxis
|
||||
strokeWidth={graphProps.strokeWidth}
|
||||
// allowDataOverflow
|
||||
dataKey="sales"
|
||||
yAxisId="right"
|
||||
tickFormatter={(value) =>
|
||||
Dinero({ amount: Math.round(value * 100) }).toFormat()
|
||||
}
|
||||
orientation="right"
|
||||
/>
|
||||
<YAxis yAxisId="left" strokeWidth={graphProps.strokeWidth} />
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Legend />
|
||||
|
||||
<Bar
|
||||
name="Body Hours"
|
||||
dataKey="bodyHrs"
|
||||
stackId="day"
|
||||
barSize={20}
|
||||
fill="darkblue"
|
||||
yAxisId="left"
|
||||
/>
|
||||
<Bar
|
||||
name="Paint Hours"
|
||||
@@ -110,12 +129,42 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
||||
stackId="day"
|
||||
barSize={20}
|
||||
fill="darkred"
|
||||
yAxisId="left"
|
||||
/>
|
||||
<Line
|
||||
name="Target Hours"
|
||||
type="monotone"
|
||||
dataKey="accTargetHrs"
|
||||
stroke="#ff7300"
|
||||
yAxisId="left"
|
||||
strokeWidth={graphProps.strokeWidth}
|
||||
/>
|
||||
|
||||
<Area
|
||||
type="monotone"
|
||||
name="MTD Hours"
|
||||
dataKey="accHrs"
|
||||
fill="lightblue"
|
||||
stroke="blue"
|
||||
yAxisId="left"
|
||||
/>
|
||||
{
|
||||
// <Area
|
||||
// type="monotone"
|
||||
// name="MTD Sales"
|
||||
// dataKey="accSales"
|
||||
// fill="lightgreen"
|
||||
// stroke="green"
|
||||
// yAxisId="right"
|
||||
// />
|
||||
}
|
||||
<Bar
|
||||
name="Sales"
|
||||
dataKey="sales"
|
||||
stackId="day"
|
||||
barSize={20}
|
||||
fill="darkgreen"
|
||||
yAxisId="right"
|
||||
strokeWidth={graphProps.strokeWidth}
|
||||
/>
|
||||
</ComposedChart>
|
||||
|
||||
@@ -241,9 +241,11 @@ export default function ScoreboardTimeTickets() {
|
||||
);
|
||||
|
||||
ret.totalEffieciencyOverPeriod =
|
||||
(totalActualAndProductive.totalOverPeriod /
|
||||
totalActualAndProductive.actualTotalOverPeriod) *
|
||||
100;
|
||||
totalActualAndProductive.actualTotalOverPeriod
|
||||
? (totalActualAndProductive.totalOverPeriod /
|
||||
totalActualAndProductive.actualTotalOverPeriod) *
|
||||
100
|
||||
: 0;
|
||||
|
||||
roundObject(ret);
|
||||
roundObject(totals);
|
||||
|
||||
@@ -116,7 +116,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.efficiencyoverperiod")}
|
||||
value={`${data.totalEffieciencyOverPeriod}%`}
|
||||
value={`${data.totalEffieciencyOverPeriod || 0}%`}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -11,13 +11,13 @@ import {
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||
import PhoneFormItem, {
|
||||
PhoneItemFormatterValidation,
|
||||
} from "../form-items-formatted/phone-form-item.component";
|
||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||
|
||||
import momentTZ from "moment-timezone";
|
||||
const timeZonesList = momentTZ.tz.names();
|
||||
@@ -551,6 +551,13 @@ export default function ShopInfoGeneral({ form }) {
|
||||
>
|
||||
<CurrencyInput />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["use_paint_scale_data"]}
|
||||
label={t("bodyshop.fields.use_paint_scale_data")}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["attach_pdf_to_email"]}
|
||||
label={t("bodyshop.fields.attach_pdf_to_email")}
|
||||
|
||||
@@ -532,6 +532,42 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "timetickets:editcommitted"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.ttapprovals.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "ttapprovals:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "ttapprovals:approve"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.vendors")}
|
||||
rules={[
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Card, Col, Space, Statistic, Typography } from "antd";
|
||||
import moment from "moment";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE } from "../../graphql/timetickets.queries";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
const { Title } = Typography;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
technician: selectTechnician,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
const TechJobStatistics = ({ technician }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const startDate = moment().startOf("week");
|
||||
const endDate = moment().endOf("week");
|
||||
|
||||
const { loading, error, data } = useQuery(
|
||||
QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE,
|
||||
{
|
||||
variables: {
|
||||
start: startDate.format("YYYY-MM-DD"),
|
||||
end: endDate.format("YYYY-MM-DD"),
|
||||
fixedStart: moment().startOf("month").format("YYYY-MM-DD"),
|
||||
fixedEnd: moment().endOf("month").format("YYYY-MM-DD"),
|
||||
employeeid: technician.id,
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
}
|
||||
);
|
||||
|
||||
const totals = useMemo(() => {
|
||||
if (data && data.timetickets && data.fixedperiod) {
|
||||
const week = data.timetickets.reduce(
|
||||
(acc, val) => {
|
||||
acc.productivehrs = acc.productivehrs + val.productivehrs;
|
||||
acc.actualhrs = acc.actualhrs + val.actualhrs;
|
||||
return acc;
|
||||
},
|
||||
{ productivehrs: 0, actualhrs: 0 }
|
||||
);
|
||||
|
||||
const month = data.fixedperiod.reduce(
|
||||
(acc, val) => {
|
||||
acc.productivehrs = acc.productivehrs + val.productivehrs;
|
||||
acc.actualhrs = acc.actualhrs + val.actualhrs;
|
||||
return acc;
|
||||
},
|
||||
{ productivehrs: 0, actualhrs: 0 }
|
||||
);
|
||||
|
||||
return {
|
||||
week,
|
||||
month,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
week: { productivehrs: 0, actualhrs: 0 },
|
||||
month: { productivehrs: 0, actualhrs: 0 },
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
if (loading) return <LoadingSpinner />;
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<Card title={t("scoreboard.labels.productivestatistics")}>
|
||||
<Space size={100}>
|
||||
<Col>
|
||||
<Title level={5}>{t("scoreboard.labels.thisweek")}</Title>
|
||||
<Space size={20}>
|
||||
<Statistic
|
||||
title={t("timetickets.fields.productivehrs")}
|
||||
value={totals.week.productivehrs.toFixed(2)}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("timetickets.fields.actualhrs")}
|
||||
value={totals.week.actualhrs.toFixed(2)}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("timetickets.labels.efficiency")}
|
||||
value={
|
||||
totals.week.actualhrs
|
||||
? `${(
|
||||
(totals.week.productivehrs / totals.week.actualhrs) *
|
||||
100
|
||||
).toFixed(2)}%`
|
||||
: "0%"
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col>
|
||||
<Title level={5}>{t("scoreboard.labels.thismonth")}</Title>
|
||||
<Space size={20}>
|
||||
<Statistic
|
||||
title={t("timetickets.fields.productivehrs")}
|
||||
value={totals.month.productivehrs.toFixed(2)}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("timetickets.fields.actualhrs")}
|
||||
value={totals.month.actualhrs.toFixed(2)}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("timetickets.labels.efficiency")}
|
||||
value={
|
||||
totals.month.actualhrs
|
||||
? `${(
|
||||
(totals.month.productivehrs / totals.month.actualhrs) *
|
||||
100
|
||||
).toFixed(2)}%`
|
||||
: "0%"
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TechJobStatistics);
|
||||
@@ -29,7 +29,17 @@ export function TechSider({ technician, techLogout }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse}>
|
||||
<Sider
|
||||
style={{
|
||||
height: "100vh",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
left: 0,
|
||||
}}
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
onCollapse={onCollapse}
|
||||
>
|
||||
<Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline">
|
||||
<Menu.Item
|
||||
key="1"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EditFilled } from "@ant-design/icons";
|
||||
import { Button, Card, Space, Table } from "antd";
|
||||
import { Button, Card, Checkbox, Space, Table } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import moment from "moment";
|
||||
import React, { useMemo, useState } from "react";
|
||||
@@ -62,6 +62,14 @@ export function TimeTicketList({
|
||||
}, [timetickets]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("timetickets.fields.committed"),
|
||||
dataIndex: "committed_at",
|
||||
key: "committed_at",
|
||||
render: (text, record) => (
|
||||
<Checkbox disabled checked={record.committed_at} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.date"),
|
||||
dataIndex: "date",
|
||||
@@ -236,6 +244,11 @@ export function TimeTicketList({
|
||||
timeticket: record,
|
||||
}}
|
||||
disabled={
|
||||
HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:editcommitted",
|
||||
}) &&
|
||||
HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
@@ -271,7 +284,6 @@ export function TimeTicketList({
|
||||
}
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTimeTicketTaskContext();
|
||||
setTimeTicketTaskContext({
|
||||
actions: {},
|
||||
context: { jobid: jobId },
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Space,
|
||||
Table,
|
||||
} from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -19,10 +20,9 @@ import EmployeeSearchSelectComponent from "../employee-search-select/employee-se
|
||||
import EmployeeTeamSearchSelectComponent from "../employee-team-search-select/employee-team-search-select.component";
|
||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component";
|
||||
import TimeTicketsTasksPresets from "../time-ticket-tasks-presets/time-ticket-tasks-presets.component";
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
import _ from "lodash";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -39,7 +39,6 @@ export default connect(
|
||||
export function TimeTicketTaskModalComponent({
|
||||
bodyshop,
|
||||
form,
|
||||
employeeAutoCompleteOptions,
|
||||
lineTicketCalled,
|
||||
calculateTimeTickets,
|
||||
lineTicketLoading,
|
||||
@@ -72,18 +71,6 @@ export function TimeTicketTaskModalComponent({
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="employeeid" label={t("timetickets.fields.employee")}>
|
||||
<EmployeeSearchSelectComponent
|
||||
options={employeeAutoCompleteOptions}
|
||||
allowClear
|
||||
onSelect={(value) => {
|
||||
const emps =
|
||||
employeeAutoCompleteOptions &&
|
||||
employeeAutoCompleteOptions.filter((e) => e.id === value)[0];
|
||||
form.setFieldsValue({ flat_rate: emps && emps.flat_rate });
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="employeeteamid"
|
||||
label={t("timetickets.fields.employee_team")}
|
||||
@@ -243,7 +230,13 @@ export function TimeTicketTaskModalComponent({
|
||||
{errors.map((e, idx) => (
|
||||
<Alert key={idx} message={e} />
|
||||
))}
|
||||
<div style={{ display: "none" }}>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
//display: "none"
|
||||
}
|
||||
}
|
||||
>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item
|
||||
key={field.key}
|
||||
@@ -358,7 +351,7 @@ export function TimeTicketTaskModalComponent({
|
||||
!lineTicketLoading)
|
||||
) {
|
||||
queryJobInfo({ variables: { id: jobid } }).then(() =>
|
||||
calculateTimeTickets()
|
||||
calculateTimeTickets("")
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -15,6 +15,7 @@ import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectTimeTicketTasks } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import TimeTicketTaskModalComponent from "./time-ticket-task-modal.component";
|
||||
import { INSERT_NEW_TT_APPROVALS } from "../../graphql/tt-approvals.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
timeTicketTasksModal: selectTimeTicketTasks,
|
||||
@@ -40,8 +41,10 @@ export function TimeTickeTaskModalContainer({
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [insertTimeTickets] = useMutation(INSERT_NEW_TIME_TICKET);
|
||||
const [insertTimeTicketApproval] = useMutation(INSERT_NEW_TT_APPROVALS);
|
||||
const [queryJobInfo, { called, loading, data: lineTicketData }] =
|
||||
useLazyQuery(GET_JOB_INFO_DRAW_CALCULATIONS, {
|
||||
fetchPolicy: "network-only",
|
||||
@@ -49,31 +52,53 @@ export function TimeTickeTaskModalContainer({
|
||||
});
|
||||
|
||||
async function handleFinish(values) {
|
||||
console.log(
|
||||
"🚀 ~ file: time-ticket-task-modal.container.jsx:52 ~ handleFinish ~ values:",
|
||||
values
|
||||
);
|
||||
try {
|
||||
const result = await insertTimeTickets({
|
||||
variables: {
|
||||
timeTicketInput: values.timetickets.map((ticket) =>
|
||||
_.omit(ticket, "pay")
|
||||
),
|
||||
},
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: JSON.stringify(result.errors),
|
||||
}),
|
||||
if (true) {
|
||||
const result = await insertTimeTicketApproval({
|
||||
variables: {
|
||||
timeTicketInput: values.timetickets.map((ticket) => ({
|
||||
..._.omit(ticket, "pay"),
|
||||
bodyshopid: bodyshop.id,
|
||||
})),
|
||||
},
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.created"),
|
||||
});
|
||||
form.resetFields();
|
||||
toggleModalVisible();
|
||||
}
|
||||
} else {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.created"),
|
||||
const result = await insertTimeTickets({
|
||||
variables: {
|
||||
timeTicketInput: values.timetickets.map((ticket) =>
|
||||
_.omit(ticket, "pay")
|
||||
),
|
||||
},
|
||||
});
|
||||
toggleModalVisible();
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.created"),
|
||||
});
|
||||
toggleModalVisible();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
@@ -81,13 +106,13 @@ export function TimeTickeTaskModalContainer({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (context.jobid) {
|
||||
console.log("UE Fired.");
|
||||
if (visible && context.jobid) {
|
||||
queryJobInfo({ variables: { id: context.jobid } });
|
||||
}
|
||||
}, [context.jobid, queryJobInfo]);
|
||||
}, [context.jobid, queryJobInfo, visible]);
|
||||
|
||||
const calculateTimeTickets = (presetMemo) => {
|
||||
console.log("🚀 ~ file: time-ticket-task-modal.container.jsx:115 ~ calculateTimeTickets ~ presetMemo:", presetMemo)
|
||||
const formData = form.getFieldsValue();
|
||||
if (
|
||||
!formData.jobid ||
|
||||
@@ -99,14 +124,13 @@ export function TimeTickeTaskModalContainer({
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = [];
|
||||
let eligibleHours = 0;
|
||||
|
||||
const theTeam = JSON.parse(formData.employeeteamid);
|
||||
|
||||
if (theTeam) {
|
||||
data = [];
|
||||
|
||||
formData.hourstype.forEach((hourstype) => {
|
||||
eligibleHours =
|
||||
lineTicketData.joblines.reduce(
|
||||
@@ -125,6 +149,7 @@ export function TimeTickeTaskModalContainer({
|
||||
actualhrs: 0,
|
||||
memo: presetMemo,
|
||||
flat_rate: true,
|
||||
ciecacode: hourstype,
|
||||
cost_center:
|
||||
bodyshop.md_responsibility_centers.defaults.costs[hourstype],
|
||||
productivehrs:
|
||||
@@ -152,7 +177,10 @@ export function TimeTickeTaskModalContainer({
|
||||
<Modal
|
||||
destroyOnClose
|
||||
open={visible}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
onCancel={() => {
|
||||
toggleModalVisible();
|
||||
form.resetFields();
|
||||
}}
|
||||
width="80%"
|
||||
onOk={() => form.submit()}
|
||||
>
|
||||
@@ -161,7 +189,6 @@ export function TimeTickeTaskModalContainer({
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
// onFieldsChange={handleFieldsChange}
|
||||
initialValues={context}
|
||||
>
|
||||
<TimeTicketTaskModalComponent
|
||||
|
||||
@@ -15,6 +15,7 @@ export default function TimeTicketsTasksPresets({
|
||||
percent: preset.percent,
|
||||
hourstype: preset.hourstype,
|
||||
});
|
||||
|
||||
calculateTimeTickets(preset.memo);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Space, Table, Tag } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import {
|
||||
selectAuthLevel,
|
||||
selectBodyshop,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import TtApproveButtonComponent from "../tt-approve-button/tt-approve-button.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
authLevel: selectAuthLevel,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setTimeTicketTaskContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TtApprovalsListComponent);
|
||||
|
||||
export function TtApprovalsListComponent({
|
||||
bodyshop,
|
||||
setTimeTicketTaskContext,
|
||||
authLevel,
|
||||
disabled,
|
||||
loading,
|
||||
tt_approval_queue,
|
||||
total,
|
||||
refetch,
|
||||
techConsole,
|
||||
jobId,
|
||||
extra,
|
||||
}) {
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" },
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const { page } = search;
|
||||
const [selectedTickets, setSelectedTickets] = useState([]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("timetickets.fields.date"),
|
||||
dataIndex: "date",
|
||||
key: "date",
|
||||
sorter: (a, b) => dateSort(a.date, b.date),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
|
||||
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.employee"),
|
||||
dataIndex: "employeeid",
|
||||
key: "employeeid",
|
||||
sorter: (a, b) => alphaSort(a.employee.last_name, b.employee.last_name),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
`${record.employee.first_name} ${record.employee.last_name}`,
|
||||
filters:
|
||||
tt_approval_queue
|
||||
.map((l) => l.employeeid)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: (() => {
|
||||
const emp = bodyshop.employees.find((e) => e.id === s);
|
||||
|
||||
return `${emp?.first_name} ${emp?.last_name}`;
|
||||
})(), //
|
||||
value: [s],
|
||||
};
|
||||
}) || [],
|
||||
onFilter: (value, record) => value.includes(record.employeeid),
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.cost_center"),
|
||||
dataIndex: "cost_center",
|
||||
key: "cost_center",
|
||||
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
|
||||
render: (text, record) =>
|
||||
record.cost_center === "timetickets.labels.shift"
|
||||
? t(record.cost_center)
|
||||
: record.cost_center,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
|
||||
filters:
|
||||
tt_approval_queue
|
||||
.map((l) => l.cost_center)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s === "timetickets.labels.shift" ? t(s) : s, //|| "No Status*",
|
||||
value: [s],
|
||||
};
|
||||
}) || [],
|
||||
onFilter: (value, record) => value.includes(record.cost_center),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) =>
|
||||
alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
record.job && (
|
||||
<Link to={"/manage/jobs/" + record.job.id}>
|
||||
<Space wrap>
|
||||
{record.job.ro_number || "N/A"}
|
||||
<Tag>{record.job.status}</Tag>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.productivehrs"),
|
||||
dataIndex: "productivehrs",
|
||||
key: "productivehrs",
|
||||
sorter: (a, b) => a.productivehrs - b.productivehrs,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "productivehrs" &&
|
||||
state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.actualhrs"),
|
||||
dataIndex: "actualhrs",
|
||||
key: "actualhrs",
|
||||
sorter: (a, b) => a.actualhrs - b.actualhrs,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "actualhrs" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.memo"),
|
||||
dataIndex: "memo",
|
||||
key: "memo",
|
||||
sorter: (a, b) => alphaSort(a.memo, b.memo),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
record.clockon || record.clockoff ? t(record.memo) : record.memo,
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.clockon"),
|
||||
dataIndex: "clockon",
|
||||
key: "clockon",
|
||||
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.clockon}</DateTimeFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Pay",
|
||||
dataIndex: "pay",
|
||||
key: "pay",
|
||||
render: (text, record) =>
|
||||
Dinero({ amount: Math.round(record.rate * 100) })
|
||||
.multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||
.toFormat("$0.00"),
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
search.page = pagination.current;
|
||||
if (sorter && sorter.column && sorter.column.sortObject) {
|
||||
search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order));
|
||||
} else {
|
||||
delete search.searchObj;
|
||||
search.sortcolumn = sorter.order ? sorter.columnKey : null;
|
||||
search.sortorder = sorter.order;
|
||||
}
|
||||
|
||||
search.sort = JSON.stringify({ [sorter.columnKey]: sorter.order });
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("timetickets.labels.timetickets")}
|
||||
extra={
|
||||
<Space wrap>
|
||||
{extra}
|
||||
<TtApproveButtonComponent
|
||||
selectedTickets={selectedTickets}
|
||||
disabled={selectedTickets.length === 0}
|
||||
completedCallback={setSelectedTickets}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
scroll={{
|
||||
x: true,
|
||||
}}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: 25,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
}}
|
||||
dataSource={tt_approval_queue}
|
||||
onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelectAll: (selected, selectedRows) =>
|
||||
setSelectedTickets(selectedRows.map((i) => i.id)),
|
||||
onSelect: (record, selected, selectedRows, nativeEvent) => {
|
||||
setSelectedTickets(selectedRows.map((i) => i.id));
|
||||
},
|
||||
selectedRowKeys: selectedTickets,
|
||||
type: "checkbox",
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_ALL_TT_APPROVALS_PAGINATED } from "../../graphql/tt-approvals.queries";
|
||||
import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import TtApprovalsListComponent from "./tt-approvals-list.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
});
|
||||
|
||||
export function TimeTicketsContainer({
|
||||
bodyshop,
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
}) {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { page, sortcolumn, sortorder, search, searchObj } = searchParams;
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(
|
||||
QUERY_ALL_TT_APPROVALS_PAGINATED,
|
||||
{
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
search: search || "",
|
||||
offset: page ? (page - 1) * 25 : 0,
|
||||
limit: 25,
|
||||
order: [
|
||||
searchObj
|
||||
? JSON.parse(searchObj)
|
||||
: {
|
||||
[sortcolumn || "date"]: sortorder
|
||||
? sortorder === "descend"
|
||||
? "desc"
|
||||
: "asc"
|
||||
: "desc",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<TtApprovalsListComponent
|
||||
loading={loading}
|
||||
tt_approval_queue={data ? data.tt_approval_queue : []}
|
||||
total={data ? data.tt_approval_queue_aggregate.aggregate.count : 0}
|
||||
refetch={refetch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TimeTicketsContainer);
|
||||
@@ -0,0 +1,92 @@
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_TIME_TICKET_AND_APPROVE } from "../../graphql/timetickets.queries";
|
||||
import { QUERY_TT_APPROVALS_BY_IDS } from "../../graphql/tt-approvals.queries";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
|
||||
export function TtApproveButton({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
selectedTickets,
|
||||
disabled,
|
||||
loadingCallback,
|
||||
completedCallback,
|
||||
refetch,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleQbxml = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await client.query({
|
||||
query: QUERY_TT_APPROVALS_BY_IDS,
|
||||
variables: { ids: selectedTickets },
|
||||
});
|
||||
|
||||
const insertResponse = await client.mutate({
|
||||
mutation: INSERT_TIME_TICKET_AND_APPROVE,
|
||||
variables: {
|
||||
timeTicketInput: data.tt_approval_queue.map((tta) => ({
|
||||
..._.omit(tta, ["id", "__typename"]),
|
||||
ttapprovalqueueid: tta.id,
|
||||
})),
|
||||
approvalIds: selectedTickets,
|
||||
approvalUpdate: {
|
||||
approved_at: moment(),
|
||||
approved_by: currentUser.email,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (insertResponse.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: JSON.stringify(insertResponse.errors),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.createdg"),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: error.message,
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
// if (!!completedCallback) completedCallback([]);
|
||||
// if (!!loadingCallback) loadingCallback(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
{t("tt_approvals.actions.approveselected")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(TtApproveButton);
|
||||
@@ -69,6 +69,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
|
||||
vendor {
|
||||
id
|
||||
name
|
||||
email
|
||||
}
|
||||
order_date
|
||||
deliver_by
|
||||
@@ -104,6 +105,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
|
||||
vendor {
|
||||
id
|
||||
name
|
||||
email
|
||||
}
|
||||
total
|
||||
invoice_number
|
||||
|
||||
@@ -117,6 +117,7 @@ export const QUERY_BODYSHOP = gql`
|
||||
md_parts_scan
|
||||
enforce_conversion_category
|
||||
tt_enforce_hours_for_tech_console
|
||||
use_paint_scale_data
|
||||
employees {
|
||||
user_email
|
||||
id
|
||||
|
||||
@@ -51,6 +51,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
op_code_desc
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
|
||||
}
|
||||
timetickets(where: { jobid: { _eq: $id } }) {
|
||||
actualhrs
|
||||
@@ -65,6 +66,8 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
clockon
|
||||
clockoff
|
||||
rate
|
||||
committed_at
|
||||
committed_by
|
||||
employee {
|
||||
id
|
||||
first_name
|
||||
|
||||
@@ -1279,7 +1279,7 @@ export const SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE = gql`
|
||||
|
||||
export const SEARCH_FOR_JOBS = gql`
|
||||
query SEARCH_FOR_JOBS($search: String!) {
|
||||
search_jobs(args: { search: $search }) {
|
||||
search_jobs(args: { search: $search }, limit: 25) {
|
||||
id
|
||||
ro_number
|
||||
ownr_fn
|
||||
|
||||
@@ -4,7 +4,15 @@ export const INSERT_NEW_NOTE = gql`
|
||||
mutation INSERT_NEW_NOTE($noteInput: [notes_insert_input!]!) {
|
||||
insert_notes(objects: $noteInput) {
|
||||
returning {
|
||||
created_at
|
||||
created_by
|
||||
critical
|
||||
id
|
||||
jobid
|
||||
private
|
||||
text
|
||||
updated_at
|
||||
audit
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +23,8 @@ export const QUERY_NOTES_BY_JOB_PK = gql`
|
||||
jobs_by_pk(id: $id) {
|
||||
id
|
||||
ro_number
|
||||
vehicle{
|
||||
jobs{
|
||||
vehicle {
|
||||
jobs {
|
||||
id
|
||||
ro_number
|
||||
status
|
||||
|
||||
@@ -19,6 +19,7 @@ export const QUERY_SCOREBOARD = gql`
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
job_totals
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,20 @@ import { gql } from "@apollo/client";
|
||||
|
||||
export const GLOBAL_SEARCH_QUERY = gql`
|
||||
query GLOBAL_SEARCH_QUERY($search: String) {
|
||||
search_jobs(args: { search: $search }) {
|
||||
search_jobs(args: { search: $search }, limit: 25) {
|
||||
id
|
||||
ro_number
|
||||
status
|
||||
|
||||
clm_no
|
||||
v_model_yr
|
||||
v_model_desc
|
||||
v_make_desc
|
||||
v_color
|
||||
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
}
|
||||
search_owners(args: { search: $search }) {
|
||||
search_owners(args: { search: $search }, limit: 25) {
|
||||
id
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
@@ -25,7 +23,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
}
|
||||
search_vehicles(args: { search: $search }) {
|
||||
search_vehicles(args: { search: $search }, limit: 25) {
|
||||
id
|
||||
v_model_yr
|
||||
v_model_desc
|
||||
@@ -34,7 +32,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
|
||||
v_vin
|
||||
plate_no
|
||||
}
|
||||
search_payments(args: { search: $search }) {
|
||||
search_payments(args: { search: $search }, limit: 25) {
|
||||
id
|
||||
amount
|
||||
paymentnum
|
||||
@@ -45,7 +43,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
|
||||
memo
|
||||
transactionid
|
||||
}
|
||||
search_bills(args: { search: $search }) {
|
||||
search_bills(args: { search: $search }, limit: 25) {
|
||||
id
|
||||
date
|
||||
invoice_number
|
||||
@@ -54,7 +52,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
|
||||
name
|
||||
}
|
||||
}
|
||||
search_phonebook(args: { search: $search }) {
|
||||
search_phonebook(args: { search: $search }, limit: 25) {
|
||||
id
|
||||
firstname
|
||||
lastname
|
||||
|
||||
@@ -59,6 +59,81 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
|
||||
query QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE(
|
||||
$employeeid: uuid!
|
||||
$start: date!
|
||||
$end: date!
|
||||
$fixedStart: date!
|
||||
$fixedEnd: date!
|
||||
) {
|
||||
timetickets(
|
||||
where: {
|
||||
date: { _gte: $start, _lte: $end }
|
||||
employeeid: { _eq: $employeeid }
|
||||
}
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
clockoff
|
||||
clockon
|
||||
cost_center
|
||||
created_at
|
||||
date
|
||||
id
|
||||
rate
|
||||
productivehrs
|
||||
memo
|
||||
jobid
|
||||
flat_rate
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
}
|
||||
employeeid
|
||||
employee {
|
||||
id
|
||||
employee_number
|
||||
first_name
|
||||
last_name
|
||||
}
|
||||
}
|
||||
fixedperiod: timetickets(
|
||||
where: {
|
||||
date: { _gte: $fixedStart, _lte: $fixedEnd }
|
||||
employeeid: { _eq: $employeeid }
|
||||
}
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
clockoff
|
||||
clockon
|
||||
cost_center
|
||||
created_at
|
||||
date
|
||||
id
|
||||
rate
|
||||
productivehrs
|
||||
memo
|
||||
jobid
|
||||
flat_rate
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
}
|
||||
employeeid
|
||||
employee {
|
||||
id
|
||||
employee_number
|
||||
first_name
|
||||
last_name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
|
||||
query QUERY_TIME_TICKETS_IN_RANGE_SB(
|
||||
$start: date!
|
||||
@@ -83,6 +158,8 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
|
||||
productivehrs
|
||||
memo
|
||||
jobid
|
||||
committed_at
|
||||
committed_by
|
||||
flat_rate
|
||||
job {
|
||||
id
|
||||
@@ -149,6 +226,39 @@ export const INSERT_NEW_TIME_TICKET = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const INSERT_TIME_TICKET_AND_APPROVE = gql`
|
||||
mutation INSERT_TIME_TICKET_AND_APPROVE(
|
||||
$timeTicketInput: [timetickets_insert_input!]!
|
||||
$approvalIds: [uuid!]!
|
||||
$approvalUpdate: tt_approval_queue_set_input
|
||||
) {
|
||||
insert_timetickets(objects: $timeTicketInput) {
|
||||
returning {
|
||||
id
|
||||
clockon
|
||||
clockoff
|
||||
employeeid
|
||||
productivehrs
|
||||
actualhrs
|
||||
ciecacode
|
||||
date
|
||||
memo
|
||||
flat_rate
|
||||
}
|
||||
}
|
||||
update_tt_approval_queue(
|
||||
where: { id: { _in: $approvalIds } }
|
||||
_set: $approvalUpdate
|
||||
) {
|
||||
returning {
|
||||
id
|
||||
approved_at
|
||||
approved_at
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_TIME_TICKET = gql`
|
||||
mutation UPDATE_TIME_TICKET(
|
||||
$timeticketId: uuid!
|
||||
|
||||
98
client/src/graphql/tt-approvals.queries.js
Normal file
98
client/src/graphql/tt-approvals.queries.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const QUERY_ALL_TT_APPROVALS_PAGINATED = gql`
|
||||
query QUERY_ALL_TT_APPROVALS_PAGINATED(
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$order: [tt_approval_queue_order_by!]!
|
||||
) {
|
||||
tt_approval_queue(
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: { approved_at: { _is_null: true } }
|
||||
) {
|
||||
id
|
||||
jobid
|
||||
bodyshopid
|
||||
employeeid
|
||||
employee {
|
||||
first_name
|
||||
last_name
|
||||
id
|
||||
}
|
||||
job {
|
||||
ro_number
|
||||
status
|
||||
id
|
||||
}
|
||||
employeeid
|
||||
actualhrs
|
||||
productivehrs
|
||||
ciecacode
|
||||
cost_center
|
||||
date
|
||||
rate
|
||||
}
|
||||
tt_approval_queue_aggregate {
|
||||
aggregate {
|
||||
count(distinct: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const INSERT_NEW_TT_APPROVALS = gql`
|
||||
mutation INSERT_NEW_TT_APPROVALS(
|
||||
$timeTicketInput: [tt_approval_queue_insert_input!]!
|
||||
) {
|
||||
insert_tt_approval_queue(objects: $timeTicketInput) {
|
||||
returning {
|
||||
id
|
||||
employeeid
|
||||
productivehrs
|
||||
actualhrs
|
||||
ciecacode
|
||||
date
|
||||
memo
|
||||
flat_rate
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_TT_APPROVALS_BY_IDS = gql`
|
||||
query QUERY_TT_APPROVALS_BY_IDS($ids: [uuid!]!) {
|
||||
tt_approval_queue(where: { id: { _in: $ids } }) {
|
||||
id
|
||||
productivehrs
|
||||
actualhrs
|
||||
rate
|
||||
memo
|
||||
jobid
|
||||
flat_rate
|
||||
employeeid
|
||||
date
|
||||
ciecacode
|
||||
bodyshopid
|
||||
cost_center
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_TT_BY_APPROVAL = gql`
|
||||
mutation UPDATE_TT_BY_APPROVAL(
|
||||
$ttApprovalUpdates: [tt_approval_queue_updates!]!
|
||||
) {
|
||||
update_tt_approval_queue_many(updates: $ttApprovalUpdates) {
|
||||
returning {
|
||||
id
|
||||
approved_at
|
||||
approved_by
|
||||
timeticket {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -39,7 +39,7 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
|
||||
...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}),
|
||||
order: [
|
||||
{
|
||||
[sortcolumn || "created_at"]:
|
||||
[sortcolumn || "ro_number"]:
|
||||
sortorder && sortorder !== "false"
|
||||
? sortorder === "descend"
|
||||
? "desc"
|
||||
|
||||
@@ -52,6 +52,7 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -97,7 +98,11 @@ export function JobsDetailPage({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: {
|
||||
...UndefinedToNull(values, ["alt_transport", "category", "referral_source"]),
|
||||
...UndefinedToNull(values, [
|
||||
"alt_transport",
|
||||
"category",
|
||||
"referral_source",
|
||||
]),
|
||||
parts_tax_rates: {
|
||||
...job.parts_tax_rates,
|
||||
...values.parts_tax_rates,
|
||||
@@ -231,6 +236,7 @@ export function JobsDetailPage({
|
||||
<ScheduleJobModalContainer />
|
||||
<JobReconciliationModal />
|
||||
<JobLineUpsertModalContainer />
|
||||
<NoteUpsertModalComponent />
|
||||
<Form
|
||||
form={form}
|
||||
name="JobDetailForm"
|
||||
|
||||
@@ -174,6 +174,9 @@ const Dms = lazy(() => import("../dms/dms.container"));
|
||||
const DmsPayables = lazy(() =>
|
||||
import("../dms-payables/dms-payables.container")
|
||||
);
|
||||
const TtApprovals = lazy(() =>
|
||||
import("../tt-approvals/tt-approvals.page.container")
|
||||
);
|
||||
|
||||
const { Content, Footer } = Layout;
|
||||
|
||||
@@ -376,6 +379,7 @@ export function Manage({ match, conflict, bodyshop }) {
|
||||
path={`${match.path}/accounting/exportlogs`}
|
||||
component={ExportLogs}
|
||||
/>
|
||||
<Route exact path={`${match.path}/ttapprovals`} component={TtApprovals} />
|
||||
<Route exact path={`${match.path}/partsqueue`} component={PartsQueue} />
|
||||
<Route exact path={`${match.path}/phonebook`} component={Phonebook} />
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ import { Divider } from "antd";
|
||||
import React from "react";
|
||||
import TechClockInFormContainer from "../../components/tech-job-clock-in-form/tech-job-clock-in-form.container";
|
||||
import TechClockedInList from "../../components/tech-job-clocked-in-list/tech-job-clocked-in-list.component";
|
||||
import TechJobStatistics from "../../components/tech-job-statistics/tech-job-statistics.component";
|
||||
|
||||
export default function TechClockComponent() {
|
||||
return (
|
||||
<div>
|
||||
<TechJobStatistics />
|
||||
<TechClockInFormContainer />
|
||||
<Divider />
|
||||
<TechClockedInList />
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
.tech-content-container {
|
||||
overflow-y: auto;
|
||||
overflow-y: visible;
|
||||
padding: 1rem;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.tech-layout-container {
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import TtApprovalsList from "../../components/tt-approvals-list/tt-approvals-list.container";
|
||||
import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
});
|
||||
|
||||
export function TtApprovalsPage({ setBreadcrumbs, setSelectedHeader }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t("titles.ttapprovals");
|
||||
setSelectedHeader("ttapprovals");
|
||||
setBreadcrumbs([
|
||||
{
|
||||
link: "/manage/ttapprovals",
|
||||
label: t("titles.bc.ttapprovals"),
|
||||
},
|
||||
]);
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
return (
|
||||
<RbacWrapper action="ttapprovals:view">
|
||||
<TtApprovalsList />
|
||||
</RbacWrapper>
|
||||
);
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TtApprovalsPage);
|
||||
@@ -435,10 +435,15 @@
|
||||
},
|
||||
"timetickets": {
|
||||
"edit": "Time Tickets -> Edit",
|
||||
"editcommitted": "Time Tickets -> Edit Committed",
|
||||
"enter": "Time Tickets -> Enter",
|
||||
"list": "Time Tickets -> List",
|
||||
"shiftedit": "Time Tickets -> Shift Edit"
|
||||
},
|
||||
"ttapprovals": {
|
||||
"approve": "Time Ticket Approval -> Approve",
|
||||
"view": "Time Ticket Approval -> View"
|
||||
},
|
||||
"users": {
|
||||
"editaccess": "Users -> Edit access"
|
||||
}
|
||||
@@ -456,6 +461,8 @@
|
||||
"federal_tax": "Federal Tax",
|
||||
"federal_tax_itc": "Federal Tax Credit",
|
||||
"gst_override": "GST Override Account #",
|
||||
"invoiceexemptcode": "QuickBooks US - Invoice Tax Exempt Code",
|
||||
"itemexemptcode": "QuickBooks US - Line Item Tax Exempt Code",
|
||||
"la1": "LA1",
|
||||
"la2": "LA2",
|
||||
"la3": "LA3",
|
||||
@@ -546,6 +553,7 @@
|
||||
"tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs",
|
||||
"tt_enforce_hours_for_tech_console": "Restrict Claimable hours from Tech Console",
|
||||
"use_fippa": "Use FIPPA for Names on Generated Documents?",
|
||||
"use_paint_scale_data": "Use Paint Scale Data for Job Costing?",
|
||||
"uselocalmediaserver": "Use Local Media Server?",
|
||||
"website": "Website",
|
||||
"zip_post": "Zip/Postal Code"
|
||||
@@ -1028,6 +1036,7 @@
|
||||
"created_at": "Created At",
|
||||
"email": "Email",
|
||||
"errors": "Errors",
|
||||
"excel": "Excel",
|
||||
"exceptiontitle": "An error has occurred.",
|
||||
"friday": "Friday",
|
||||
"globalsearch": "Global Search",
|
||||
@@ -1903,6 +1912,7 @@
|
||||
"shop_vendors": "Vendors",
|
||||
"temporarydocs": "Temporary Documents",
|
||||
"timetickets": "Time Tickets",
|
||||
"ttapprovals": "Time Ticket Approvals",
|
||||
"vehicles": "Vehicles"
|
||||
},
|
||||
"jobsactions": {
|
||||
@@ -1975,7 +1985,8 @@
|
||||
"actions": "Actions",
|
||||
"deletenote": "Delete Note",
|
||||
"edit": "Edit Note",
|
||||
"new": "New Note"
|
||||
"new": "New Note",
|
||||
"savetojobnotes": "Save to Job Notes"
|
||||
},
|
||||
"errors": {
|
||||
"inserting": "Error inserting note. {{error}}"
|
||||
@@ -2241,6 +2252,7 @@
|
||||
"csi_invitation": "CSI Invitation",
|
||||
"csi_invitation_action": "CSI Invite",
|
||||
"diagnostic_authorization": "Diagnostic Authorization",
|
||||
"dms_posting_sheet": "DMS Posting Sheet",
|
||||
"envelope_return_address": "#10 Envelope Return Address Label",
|
||||
"estimate": "Estimate Only",
|
||||
"estimate_detail": "Estimate Details",
|
||||
@@ -2459,6 +2471,8 @@
|
||||
"export_payables": "Export Log - Payables",
|
||||
"export_payments": "Export Log - Payments",
|
||||
"export_receivables": "Export Log - Receivables",
|
||||
"exported_gsr_by_ro": "Exported Gross Sales - Excel",
|
||||
"exported_gsr_by_ro_labor": "Exported Gross Sales (Labor) - Excel",
|
||||
"gsr_by_atp": "",
|
||||
"gsr_by_ats": "Gross Sales by ATS",
|
||||
"gsr_by_category": "Gross Sales by Category",
|
||||
@@ -2641,10 +2655,12 @@
|
||||
"clockhours": "Clock Hours",
|
||||
"clockoff": "Clock Off",
|
||||
"clockon": "Clocked In",
|
||||
"committed": "Committed",
|
||||
"cost_center": "Cost Center",
|
||||
"date": "Ticket Date",
|
||||
"efficiency": "Efficiency",
|
||||
"employee": "Employee",
|
||||
"employee_team": "Employee Team",
|
||||
"flat_rate": "Flat Rate?",
|
||||
"memo": "Memo",
|
||||
"productivehrs": "Productive Hours",
|
||||
@@ -2731,6 +2747,7 @@
|
||||
"shop-vendors": "Vendors",
|
||||
"temporarydocs": "Temporary Documents",
|
||||
"timetickets": "Time Tickets",
|
||||
"ttapprovals": "Time Ticket Approvals",
|
||||
"vehicle-details": "Vehicle: {{vehicle}}",
|
||||
"vehicles": "Vehicles"
|
||||
},
|
||||
@@ -2776,9 +2793,15 @@
|
||||
"shop_vendors": "Vendors | $t(titles.app)",
|
||||
"temporarydocs": "Temporary Documents | $t(titles.app)",
|
||||
"timetickets": "Time Tickets | $t(titles.app)",
|
||||
"ttapprovals": "Time Ticket Approvals | $t(titles.app)",
|
||||
"vehicledetail": "Vehicle Details {{vehicle}} | $t(titles.app)",
|
||||
"vehicles": "All Vehicles | $t(titles.app)"
|
||||
},
|
||||
"tt_approvals": {
|
||||
"actions": {
|
||||
"approveselected": "Approve Selected"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"changepassword": "Change Password",
|
||||
|
||||
@@ -435,10 +435,15 @@
|
||||
},
|
||||
"timetickets": {
|
||||
"edit": "",
|
||||
"editcommitted": "",
|
||||
"enter": "",
|
||||
"list": "",
|
||||
"shiftedit": ""
|
||||
},
|
||||
"ttapprovals": {
|
||||
"approve": "",
|
||||
"view": ""
|
||||
},
|
||||
"users": {
|
||||
"editaccess": ""
|
||||
}
|
||||
@@ -456,6 +461,8 @@
|
||||
"federal_tax": "",
|
||||
"federal_tax_itc": "",
|
||||
"gst_override": "",
|
||||
"invoiceexemptcode": "",
|
||||
"itemexemptcode": "",
|
||||
"la1": "",
|
||||
"la2": "",
|
||||
"la3": "",
|
||||
@@ -546,6 +553,7 @@
|
||||
"tt_allow_post_to_invoiced": "",
|
||||
"tt_enforce_hours_for_tech_console": "",
|
||||
"use_fippa": "",
|
||||
"use_paint_scale_data": "",
|
||||
"uselocalmediaserver": "",
|
||||
"website": "",
|
||||
"zip_post": ""
|
||||
@@ -1028,6 +1036,7 @@
|
||||
"created_at": "",
|
||||
"email": "",
|
||||
"errors": "",
|
||||
"excel": "",
|
||||
"exceptiontitle": "",
|
||||
"friday": "",
|
||||
"globalsearch": "",
|
||||
@@ -1903,6 +1912,7 @@
|
||||
"shop_vendors": "Vendedores",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicles": "Vehículos"
|
||||
},
|
||||
"jobsactions": {
|
||||
@@ -1975,7 +1985,8 @@
|
||||
"actions": "Comportamiento",
|
||||
"deletenote": "Borrar nota",
|
||||
"edit": "Editar nota",
|
||||
"new": "Nueva nota"
|
||||
"new": "Nueva nota",
|
||||
"savetojobnotes": ""
|
||||
},
|
||||
"errors": {
|
||||
"inserting": ""
|
||||
@@ -2241,6 +2252,7 @@
|
||||
"csi_invitation": "",
|
||||
"csi_invitation_action": "",
|
||||
"diagnostic_authorization": "",
|
||||
"dms_posting_sheet": "",
|
||||
"envelope_return_address": "",
|
||||
"estimate": "",
|
||||
"estimate_detail": "",
|
||||
@@ -2459,6 +2471,8 @@
|
||||
"export_payables": "",
|
||||
"export_payments": "",
|
||||
"export_receivables": "",
|
||||
"exported_gsr_by_ro": "",
|
||||
"exported_gsr_by_ro_labor": "",
|
||||
"gsr_by_atp": "",
|
||||
"gsr_by_ats": "",
|
||||
"gsr_by_category": "",
|
||||
@@ -2641,10 +2655,12 @@
|
||||
"clockhours": "",
|
||||
"clockoff": "",
|
||||
"clockon": "",
|
||||
"committed": "",
|
||||
"cost_center": "",
|
||||
"date": "",
|
||||
"efficiency": "",
|
||||
"employee": "",
|
||||
"employee_team": "",
|
||||
"flat_rate": "",
|
||||
"memo": "",
|
||||
"productivehrs": "",
|
||||
@@ -2731,6 +2747,7 @@
|
||||
"shop-vendors": "",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicle-details": "",
|
||||
"vehicles": ""
|
||||
},
|
||||
@@ -2776,9 +2793,15 @@
|
||||
"shop_vendors": "Vendedores | $t(titles.app)",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicledetail": "Detalles del vehículo {{vehicle}} | $t(titles.app)",
|
||||
"vehicles": "Todos los vehiculos | $t(titles.app)"
|
||||
},
|
||||
"tt_approvals": {
|
||||
"actions": {
|
||||
"approveselected": ""
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"changepassword": "",
|
||||
|
||||
@@ -435,10 +435,15 @@
|
||||
},
|
||||
"timetickets": {
|
||||
"edit": "",
|
||||
"editcommitted": "",
|
||||
"enter": "",
|
||||
"list": "",
|
||||
"shiftedit": ""
|
||||
},
|
||||
"ttapprovals": {
|
||||
"approve": "",
|
||||
"view": ""
|
||||
},
|
||||
"users": {
|
||||
"editaccess": ""
|
||||
}
|
||||
@@ -456,6 +461,8 @@
|
||||
"federal_tax": "",
|
||||
"federal_tax_itc": "",
|
||||
"gst_override": "",
|
||||
"invoiceexemptcode": "",
|
||||
"itemexemptcode": "",
|
||||
"la1": "",
|
||||
"la2": "",
|
||||
"la3": "",
|
||||
@@ -546,6 +553,7 @@
|
||||
"tt_allow_post_to_invoiced": "",
|
||||
"tt_enforce_hours_for_tech_console": "",
|
||||
"use_fippa": "",
|
||||
"use_paint_scale_data": "",
|
||||
"uselocalmediaserver": "",
|
||||
"website": "",
|
||||
"zip_post": ""
|
||||
@@ -1028,6 +1036,7 @@
|
||||
"created_at": "",
|
||||
"email": "",
|
||||
"errors": "",
|
||||
"excel": "",
|
||||
"exceptiontitle": "",
|
||||
"friday": "",
|
||||
"globalsearch": "",
|
||||
@@ -1903,6 +1912,7 @@
|
||||
"shop_vendors": "Vendeurs",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicles": "Véhicules"
|
||||
},
|
||||
"jobsactions": {
|
||||
@@ -1975,7 +1985,8 @@
|
||||
"actions": "actes",
|
||||
"deletenote": "Supprimer la note",
|
||||
"edit": "Note éditée",
|
||||
"new": "Nouvelle note"
|
||||
"new": "Nouvelle note",
|
||||
"savetojobnotes": ""
|
||||
},
|
||||
"errors": {
|
||||
"inserting": ""
|
||||
@@ -2241,6 +2252,7 @@
|
||||
"csi_invitation": "",
|
||||
"csi_invitation_action": "",
|
||||
"diagnostic_authorization": "",
|
||||
"dms_posting_sheet": "",
|
||||
"envelope_return_address": "",
|
||||
"estimate": "",
|
||||
"estimate_detail": "",
|
||||
@@ -2459,6 +2471,8 @@
|
||||
"export_payables": "",
|
||||
"export_payments": "",
|
||||
"export_receivables": "",
|
||||
"exported_gsr_by_ro": "",
|
||||
"exported_gsr_by_ro_labor": "",
|
||||
"gsr_by_atp": "",
|
||||
"gsr_by_ats": "",
|
||||
"gsr_by_category": "",
|
||||
@@ -2641,10 +2655,12 @@
|
||||
"clockhours": "",
|
||||
"clockoff": "",
|
||||
"clockon": "",
|
||||
"committed": "",
|
||||
"cost_center": "",
|
||||
"date": "",
|
||||
"efficiency": "",
|
||||
"employee": "",
|
||||
"employee_team": "",
|
||||
"flat_rate": "",
|
||||
"memo": "",
|
||||
"productivehrs": "",
|
||||
@@ -2731,6 +2747,7 @@
|
||||
"shop-vendors": "",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicle-details": "",
|
||||
"vehicles": ""
|
||||
},
|
||||
@@ -2776,9 +2793,15 @@
|
||||
"shop_vendors": "Vendeurs | $t(titles.app)",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicledetail": "Détails du véhicule {{vehicle} | $t(titles.app)",
|
||||
"vehicles": "Tous les véhicules | $t(titles.app)"
|
||||
},
|
||||
"tt_approvals": {
|
||||
"actions": {
|
||||
"approveselected": ""
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"changepassword": "",
|
||||
|
||||
@@ -20,7 +20,6 @@ export const TemplateList = (type, context) => {
|
||||
disabled: false,
|
||||
group: "authorization",
|
||||
},
|
||||
|
||||
fippa_authorization: {
|
||||
title: i18n.t("printcenter.jobs.fippa_authorization"),
|
||||
description: "CASL Authorization",
|
||||
@@ -101,7 +100,6 @@ export const TemplateList = (type, context) => {
|
||||
disabled: false,
|
||||
group: "ro",
|
||||
},
|
||||
|
||||
job_notes: {
|
||||
title: i18n.t("printcenter.jobs.job_notes"),
|
||||
description: "All Jobs Notes",
|
||||
@@ -330,7 +328,6 @@ export const TemplateList = (type, context) => {
|
||||
disabled: false,
|
||||
group: "post",
|
||||
},
|
||||
|
||||
vehicle_delivery_check: {
|
||||
title: i18n.t("printcenter.jobs.vehicle_delivery_check"),
|
||||
description: "All Jobs Notes",
|
||||
@@ -339,7 +336,6 @@ export const TemplateList = (type, context) => {
|
||||
disabled: false,
|
||||
group: "post",
|
||||
},
|
||||
|
||||
guarantee: {
|
||||
title: i18n.t("printcenter.jobs.guarantee"),
|
||||
description: "All Jobs Notes",
|
||||
@@ -496,6 +492,14 @@ export const TemplateList = (type, context) => {
|
||||
disabled: false,
|
||||
group: "financial",
|
||||
},
|
||||
dms_posting_sheet: {
|
||||
title: i18n.t("printcenter.jobs.dms_posting_sheet"),
|
||||
description: "DMS Posting Sheet",
|
||||
subject: i18n.t("printcenter.jobs.dms_posting_sheet"),
|
||||
key: "dms_posting_sheet",
|
||||
disabled: false,
|
||||
group: "financial",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(!type || type === "job_special"
|
||||
@@ -622,7 +626,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
|
||||
hours_sold_detail_closed_ins_co: {
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.hours_sold_detail_closed_ins_co"
|
||||
@@ -640,7 +643,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
|
||||
hours_sold_summary_closed: {
|
||||
title: i18n.t("reportcenter.templates.hours_sold_summary_closed"),
|
||||
description: "",
|
||||
@@ -654,7 +656,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
|
||||
hours_sold_summary_closed_ins_co: {
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.hours_sold_summary_closed_ins_co"
|
||||
@@ -672,7 +673,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
|
||||
hours_sold_detail_open: {
|
||||
title: i18n.t("reportcenter.templates.hours_sold_detail_open"),
|
||||
description: "",
|
||||
@@ -686,7 +686,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
|
||||
hours_sold_detail_open_ins_co: {
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.hours_sold_detail_open_ins_co"
|
||||
@@ -704,7 +703,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
|
||||
hours_sold_summary_open: {
|
||||
title: i18n.t("reportcenter.templates.hours_sold_summary_open"),
|
||||
description: "",
|
||||
@@ -718,7 +716,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
|
||||
hours_sold_summary_open_ins_co: {
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.hours_sold_summary_open_ins_co"
|
||||
@@ -736,7 +733,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
|
||||
hours_sold_detail_closed_csr: {
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.hours_sold_detail_closed_csr"
|
||||
@@ -1090,7 +1086,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "customers",
|
||||
},
|
||||
|
||||
schedule: {
|
||||
title: i18n.t("reportcenter.templates.schedule"),
|
||||
subject: i18n.t("reportcenter.templates.schedule"),
|
||||
@@ -1102,7 +1097,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "customers",
|
||||
},
|
||||
|
||||
timetickets: {
|
||||
title: i18n.t("reportcenter.templates.timetickets"),
|
||||
subject: i18n.t("reportcenter.templates.timetickets"),
|
||||
@@ -1126,7 +1120,6 @@ export const TemplateList = (type, context) => {
|
||||
title: i18n.t("reportcenter.templates.attendance_detail"),
|
||||
subject: i18n.t("reportcenter.templates.attendance_detail"),
|
||||
key: "attendance_detail",
|
||||
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.timetickets"),
|
||||
@@ -1138,7 +1131,6 @@ export const TemplateList = (type, context) => {
|
||||
title: i18n.t("reportcenter.templates.attendance_summary"),
|
||||
subject: i18n.t("reportcenter.templates.attendance_summary"),
|
||||
key: "attendance_summary",
|
||||
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.timetickets"),
|
||||
@@ -1158,7 +1150,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "payroll",
|
||||
},
|
||||
|
||||
timetickets_summary: {
|
||||
title: i18n.t("reportcenter.templates.timetickets_summary"),
|
||||
subject: i18n.t("reportcenter.templates.timetickets_summary"),
|
||||
@@ -1171,7 +1162,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "payroll",
|
||||
},
|
||||
|
||||
estimator_detail: {
|
||||
title: i18n.t("reportcenter.templates.estimator_detail"),
|
||||
description: "",
|
||||
@@ -1224,7 +1214,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "purchases",
|
||||
},
|
||||
|
||||
void_ros: {
|
||||
title: i18n.t("reportcenter.templates.void_ros"),
|
||||
description: "",
|
||||
@@ -1329,7 +1318,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
|
||||
gsr_by_estimator: {
|
||||
title: i18n.t("reportcenter.templates.gsr_by_estimator"),
|
||||
description: "",
|
||||
@@ -1814,7 +1802,9 @@ export const TemplateList = (type, context) => {
|
||||
group: "jobs",
|
||||
},
|
||||
purchase_return_ratio_grouped_by_vendor_detail: {
|
||||
title: i18n.t("reportcenter.templates.purchase_return_ratio_grouped_by_vendor_detail"),
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.purchase_return_ratio_grouped_by_vendor_detail"
|
||||
),
|
||||
subject: i18n.t(
|
||||
"reportcenter.templates.purchase_return_ratio_grouped_by_vendor_detail"
|
||||
),
|
||||
@@ -1828,7 +1818,9 @@ export const TemplateList = (type, context) => {
|
||||
group: "purchases",
|
||||
},
|
||||
purchase_return_ratio_grouped_by_vendor_summary: {
|
||||
title: i18n.t("reportcenter.templates.purchase_return_ratio_grouped_by_vendor_summary"),
|
||||
title: i18n.t(
|
||||
"reportcenter.templates.purchase_return_ratio_grouped_by_vendor_summary"
|
||||
),
|
||||
subject: i18n.t(
|
||||
"reportcenter.templates.purchase_return_ratio_grouped_by_vendor_summary"
|
||||
),
|
||||
@@ -1843,9 +1835,7 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
production_over_time: {
|
||||
title: i18n.t("reportcenter.templates.production_over_time"),
|
||||
subject: i18n.t(
|
||||
"reportcenter.templates.production_over_time"
|
||||
),
|
||||
subject: i18n.t("reportcenter.templates.production_over_time"),
|
||||
key: "production_over_time",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
@@ -1857,9 +1847,7 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
customer_list: {
|
||||
title: i18n.t("reportcenter.templates.customer_list"),
|
||||
subject: i18n.t(
|
||||
"reportcenter.templates.customer_list"
|
||||
),
|
||||
subject: i18n.t("reportcenter.templates.customer_list"),
|
||||
key: "customer_list",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
@@ -1869,6 +1857,32 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "customers",
|
||||
},
|
||||
exported_gsr_by_ro: {
|
||||
title: i18n.t("reportcenter.templates.exported_gsr_by_ro"),
|
||||
subject: i18n.t("reportcenter.templates.exported_gsr_by_ro"),
|
||||
key: "exported_gsr_by_ro",
|
||||
//idtype: "vendor",
|
||||
reporttype: "excel",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_exported"),
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
exported_gsr_by_ro_labor: {
|
||||
title: i18n.t("reportcenter.templates.exported_gsr_by_ro_labor"),
|
||||
subject: i18n.t("reportcenter.templates.exported_gsr_by_ro_labor"),
|
||||
key: "exported_gsr_by_ro_labor",
|
||||
//idtype: "vendor",
|
||||
reporttype: "excel",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_exported"),
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(!type || type === "courtesycarcontract"
|
||||
@@ -1963,7 +1977,6 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
||||
...(!type || type === "production"
|
||||
? {
|
||||
production_by_last_name: {
|
||||
|
||||
@@ -673,6 +673,33 @@
|
||||
_eq: true
|
||||
- exported:
|
||||
_eq: false
|
||||
event_triggers:
|
||||
- name: os_bills
|
||||
definition:
|
||||
delete:
|
||||
columns: '*'
|
||||
enable_manual: false
|
||||
insert:
|
||||
columns: '*'
|
||||
update:
|
||||
columns:
|
||||
- jobid
|
||||
- invoice_number
|
||||
- due_date
|
||||
- vendorid
|
||||
- id
|
||||
- date
|
||||
retry_conf:
|
||||
interval_sec: 10
|
||||
num_retries: 3
|
||||
timeout_sec: 60
|
||||
webhook_from_env: HASURA_API_URL
|
||||
request_transform:
|
||||
method: POST
|
||||
query_params: {}
|
||||
template_engine: Kriti
|
||||
url: '{{$base_url}}/opensearch'
|
||||
version: 2
|
||||
- table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
@@ -831,6 +858,13 @@
|
||||
table:
|
||||
name: transitions
|
||||
schema: public
|
||||
- name: tt_approval_queues
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: bodyshopid
|
||||
table:
|
||||
name: tt_approval_queue
|
||||
schema: public
|
||||
- name: vehicles
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
@@ -938,6 +972,7 @@
|
||||
- tt_enforce_hours_for_tech_console
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- use_paint_scale_data
|
||||
- uselocalmediaserver
|
||||
- website
|
||||
- workingdays
|
||||
@@ -1027,6 +1062,7 @@
|
||||
- tt_enforce_hours_for_tech_console
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- use_paint_scale_data
|
||||
- uselocalmediaserver
|
||||
- website
|
||||
- workingdays
|
||||
@@ -2262,6 +2298,13 @@
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
- name: tt_approval_queues
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: employeeid
|
||||
table:
|
||||
name: tt_approval_queue
|
||||
schema: public
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
@@ -3145,6 +3188,13 @@
|
||||
table:
|
||||
name: transitions
|
||||
schema: public
|
||||
- name: tt_approval_queues
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: jobid
|
||||
table:
|
||||
name: tt_approval_queue
|
||||
schema: public
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
@@ -4033,6 +4083,41 @@
|
||||
template_engine: Kriti
|
||||
url: '{{$base_url}}/record-handler/arms'
|
||||
version: 2
|
||||
- name: os_jobs
|
||||
definition:
|
||||
delete:
|
||||
columns: '*'
|
||||
enable_manual: false
|
||||
insert:
|
||||
columns: '*'
|
||||
update:
|
||||
columns:
|
||||
- v_color
|
||||
- ownerid
|
||||
- ownr_fn
|
||||
- v_model_desc
|
||||
- ownr_ln
|
||||
- id
|
||||
- v_make_desc
|
||||
- ownr_st
|
||||
- clm_no
|
||||
- voided
|
||||
- status
|
||||
- ownr_co_nm
|
||||
- v_model_yr
|
||||
- v_vin
|
||||
- converted
|
||||
retry_conf:
|
||||
interval_sec: 10
|
||||
num_retries: 3
|
||||
timeout_sec: 60
|
||||
webhook_from_env: HASURA_API_URL
|
||||
request_transform:
|
||||
method: POST
|
||||
query_params: {}
|
||||
template_engine: Kriti
|
||||
url: '{{$base_url}}/opensearch'
|
||||
version: 2
|
||||
- table:
|
||||
name: masterdata
|
||||
schema: public
|
||||
@@ -4457,6 +4542,32 @@
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
event_triggers:
|
||||
- name: os_owners
|
||||
definition:
|
||||
delete:
|
||||
columns: '*'
|
||||
enable_manual: false
|
||||
insert:
|
||||
columns: '*'
|
||||
update:
|
||||
columns:
|
||||
- shopid
|
||||
- ownr_fn
|
||||
- id
|
||||
- ownr_co_nm
|
||||
- ownr_ln
|
||||
retry_conf:
|
||||
interval_sec: 10
|
||||
num_retries: 3
|
||||
timeout_sec: 60
|
||||
webhook_from_env: HASURA_API_URL
|
||||
request_transform:
|
||||
method: POST
|
||||
query_params: {}
|
||||
template_engine: Kriti
|
||||
url: '{{$base_url}}/opensearch'
|
||||
version: 2
|
||||
- table:
|
||||
name: parts_order_lines
|
||||
schema: public
|
||||
@@ -4880,6 +4991,36 @@
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
event_triggers:
|
||||
- name: os_payments
|
||||
definition:
|
||||
delete:
|
||||
columns: '*'
|
||||
enable_manual: false
|
||||
insert:
|
||||
columns: '*'
|
||||
update:
|
||||
columns:
|
||||
- paymentnum
|
||||
- type
|
||||
- amount
|
||||
- date
|
||||
- transactionid
|
||||
- memo
|
||||
- payer
|
||||
- id
|
||||
- jobid
|
||||
retry_conf:
|
||||
interval_sec: 10
|
||||
num_retries: 3
|
||||
timeout_sec: 60
|
||||
webhook_from_env: HASURA_API_URL
|
||||
request_transform:
|
||||
method: POST
|
||||
query_params: {}
|
||||
template_engine: Kriti
|
||||
url: '{{$base_url}}/opensearch'
|
||||
version: 2
|
||||
- table:
|
||||
name: phonebook
|
||||
schema: public
|
||||
@@ -5210,6 +5351,12 @@
|
||||
- name: job
|
||||
using:
|
||||
foreign_key_constraint_on: jobid
|
||||
- name: tt_approval_queue
|
||||
using:
|
||||
foreign_key_constraint_on: ttapprovalqueueid
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: commited_by
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
@@ -5228,6 +5375,8 @@
|
||||
- ciecacode
|
||||
- clockoff
|
||||
- clockon
|
||||
- commited_by
|
||||
- committed_at
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
@@ -5238,6 +5387,7 @@
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- ttapprovalqueueid
|
||||
- updated_at
|
||||
select_permissions:
|
||||
- role: user
|
||||
@@ -5248,6 +5398,8 @@
|
||||
- ciecacode
|
||||
- clockoff
|
||||
- clockon
|
||||
- commited_by
|
||||
- committed_at
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
@@ -5258,6 +5410,7 @@
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- ttapprovalqueueid
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
@@ -5277,6 +5430,8 @@
|
||||
- ciecacode
|
||||
- clockoff
|
||||
- clockon
|
||||
- commited_by
|
||||
- committed_at
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
@@ -5287,6 +5442,7 @@
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- ttapprovalqueueid
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
@@ -5397,6 +5553,119 @@
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
check: {}
|
||||
- table:
|
||||
name: tt_approval_queue
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: bodyshop
|
||||
using:
|
||||
foreign_key_constraint_on: bodyshopid
|
||||
- name: employee
|
||||
using:
|
||||
foreign_key_constraint_on: employeeid
|
||||
- name: job
|
||||
using:
|
||||
foreign_key_constraint_on: jobid
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: approved_by
|
||||
array_relationships:
|
||||
- name: timetickets
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: ttapprovalqueueid
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
check:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- active:
|
||||
_eq: true
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
columns:
|
||||
- actualhrs
|
||||
- approved_at
|
||||
- approved_by
|
||||
- bodyshopid
|
||||
- ciecacode
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- flat_rate
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- updated_at
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- actualhrs
|
||||
- approved_at
|
||||
- approved_by
|
||||
- bodyshopid
|
||||
- ciecacode
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- flat_rate
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- active:
|
||||
_eq: true
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
allow_aggregations: true
|
||||
update_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- actualhrs
|
||||
- approved_at
|
||||
- approved_by
|
||||
- bodyshopid
|
||||
- ciecacode
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- flat_rate
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- active:
|
||||
_eq: true
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
check: null
|
||||
- table:
|
||||
name: users
|
||||
schema: public
|
||||
@@ -5467,6 +5736,20 @@
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
- name: timetickets
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: commited_by
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
- name: tt_approval_queues
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: approved_by
|
||||
table:
|
||||
name: tt_approval_queue
|
||||
schema: public
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
@@ -5652,6 +5935,34 @@
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
event_triggers:
|
||||
- name: os_vehicles
|
||||
definition:
|
||||
delete:
|
||||
columns: '*'
|
||||
enable_manual: false
|
||||
insert:
|
||||
columns: '*'
|
||||
update:
|
||||
columns:
|
||||
- v_model_yr
|
||||
- plate_no
|
||||
- id
|
||||
- v_vin
|
||||
- v_model_desc
|
||||
- plate_st
|
||||
- shopid
|
||||
retry_conf:
|
||||
interval_sec: 10
|
||||
num_retries: 3
|
||||
timeout_sec: 60
|
||||
webhook_from_env: HASURA_API_URL
|
||||
request_transform:
|
||||
method: POST
|
||||
query_params: {}
|
||||
template_engine: Kriti
|
||||
url: '{{$base_url}}/opensearch'
|
||||
version: 2
|
||||
- table:
|
||||
name: vendors
|
||||
schema: public
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."timetickets" add column "committed_at" timestamptz
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."timetickets" add column "committed_at" timestamptz
|
||||
null;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "public"."tt_approval_queue";
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE "public"."tt_approval_queue" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "bodyshopid" uuid NOT NULL, "jobid" uuid NOT NULL, "employeeid" uuid NOT NULL, "timeticketid" uuid, "approved_by" text, "approved_at" timestamptz NOT NULL, "actualhrs" numeric NOT NULL DEFAULT 0, "productivehrs" numeric NOT NULL DEFAULT 0, "rate" numeric NOT NULL DEFAULT 0, "flat_rate" boolean NOT NULL DEFAULT true, "ciecacode" text, "cost_center" text NOT NULL, "date" date NOT NULL DEFAULT now(), "memo" text NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("bodyshopid") REFERENCES "public"."bodyshops"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("jobid") REFERENCES "public"."jobs"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("employeeid") REFERENCES "public"."employees"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("timeticketid") REFERENCES "public"."timetickets"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("approved_by") REFERENCES "public"."users"("email") ON UPDATE restrict ON DELETE restrict);
|
||||
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_tt_approval_queue_updated_at"
|
||||
BEFORE UPDATE ON "public"."tt_approval_queue"
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||
COMMENT ON TRIGGER "set_public_tt_approval_queue_updated_at" ON "public"."tt_approval_queue"
|
||||
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."tt_approval_queue" alter column "approved_at" set not null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."tt_approval_queue" alter column "approved_at" drop not null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."tt_approval_queue" alter column "memo" set not null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."tt_approval_queue" alter column "memo" drop not null;
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."tt_approval_queue"
|
||||
add constraint "tt_approval_queue_timeticketid_fkey"
|
||||
foreign key ("timeticketid")
|
||||
references "public"."timetickets"
|
||||
("id") on update cascade on delete cascade;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."tt_approval_queue" drop constraint "tt_approval_queue_timeticketid_fkey";
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."tt_approval_queue" alter column "timeticketid" drop not null;
|
||||
alter table "public"."tt_approval_queue" add column "timeticketid" uuid;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."tt_approval_queue" drop column "timeticketid" cascade;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."timetickets" add column "ttapprovalqueueid" uuid
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."timetickets" add column "ttapprovalqueueid" uuid
|
||||
null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."timetickets" drop constraint "timetickets_ttapprovalqueueid_fkey";
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."timetickets"
|
||||
add constraint "timetickets_ttapprovalqueueid_fkey"
|
||||
foreign key ("ttapprovalqueueid")
|
||||
references "public"."tt_approval_queue"
|
||||
("id") on update cascade on delete set null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."timetickets" add column "commited_by" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."timetickets" add column "commited_by" text
|
||||
null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."timetickets" drop constraint "timetickets_commited_by_fkey";
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."timetickets"
|
||||
add constraint "timetickets_commited_by_fkey"
|
||||
foreign key ("commited_by")
|
||||
references "public"."users"
|
||||
("email") on update restrict on delete restrict;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "use_paint_scale_data" boolean
|
||||
-- not null default 'false';
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "use_paint_scale_data" boolean
|
||||
not null default 'false';
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."jobs_idx_status_hash";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "jobs_idx_status_hash" on
|
||||
"public"."jobs" using hash ("status");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."idx_jobs_ronumber_btree";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "idx_jobs_ronumber_btree" on
|
||||
"public"."jobs" using btree ("ro_number");
|
||||
237
os-loader.js
Normal file
237
os-loader.js
Normal file
@@ -0,0 +1,237 @@
|
||||
const Dinero = require("dinero.js");
|
||||
|
||||
//const client = require("../graphql-client/graphql-client").client;
|
||||
const _ = require("lodash");
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
const logger = require("./server/utils/logger");
|
||||
|
||||
const path = require("path");
|
||||
const client = require("./server/graphql-client/graphql-client").client;
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
`.env.${process.env.NODE_ENV || "development"}`
|
||||
),
|
||||
});
|
||||
const { Client, Connection } = require("@opensearch-project/opensearch");
|
||||
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||
const aws4 = require("aws4");
|
||||
const { gql } = require("graphql-request");
|
||||
const gqlclient = require("./server/graphql-client/graphql-client").client;
|
||||
// const osClient = new Client({
|
||||
// node: `https://imex:Wl0d8k@!@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`,
|
||||
// });
|
||||
|
||||
var host = process.env.OPEN_SEARCH_HOST; // e.g. https://my-domain.region.es.amazonaws.com
|
||||
const createAwsConnector = (credentials, region) => {
|
||||
class AmazonConnection extends Connection {
|
||||
buildRequestObject(params) {
|
||||
const request = super.buildRequestObject(params);
|
||||
request.service = "es";
|
||||
request.region = region;
|
||||
request.headers = request.headers || {};
|
||||
request.headers["host"] = request.hostname;
|
||||
|
||||
return aws4.sign(request, credentials);
|
||||
}
|
||||
}
|
||||
return {
|
||||
Connection: AmazonConnection,
|
||||
};
|
||||
};
|
||||
|
||||
const getClient = async () => {
|
||||
const credentials = await defaultProvider()();
|
||||
return new Client({
|
||||
...createAwsConnector(credentials, "ca-central-1"),
|
||||
node: host,
|
||||
});
|
||||
};
|
||||
|
||||
async function OpenSearchUpdateHandler(req, res) {
|
||||
try {
|
||||
var osClient = await getClient();
|
||||
// const osClient = new Client({
|
||||
// node: `https://imex:password@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com`,
|
||||
// });
|
||||
|
||||
//Clear out all current documents
|
||||
// const deleteResult = await osClient.deleteByQuery({
|
||||
// index: ["*"], // ["jobs", "payments", "bills", "vehicles", "owners"],
|
||||
// body: {
|
||||
// query: {
|
||||
// match_all: {},
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
// return;
|
||||
|
||||
var batchSize = 1000;
|
||||
var promiseQueue = [];
|
||||
|
||||
//Jobs Load.
|
||||
const jobsData = await gqlclient.request(`query{jobs{
|
||||
id
|
||||
bodyshopid:shopid
|
||||
ro_number
|
||||
clm_no
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
status
|
||||
ownr_co_nm
|
||||
v_model_yr
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
}}`);
|
||||
for (let i = 0; i <= jobsData.jobs.length / batchSize; i++) {
|
||||
const slicedArray = jobsData.jobs.slice(
|
||||
i * batchSize,
|
||||
i * batchSize + batchSize
|
||||
);
|
||||
const bulkOperation = [];
|
||||
slicedArray.forEach((job) => {
|
||||
bulkOperation.push({ index: { _index: "jobs", _id: job.id } });
|
||||
bulkOperation.push(job);
|
||||
});
|
||||
promiseQueue.push(bulkOperation);
|
||||
}
|
||||
|
||||
//Owner Load
|
||||
const ownersData = await gqlclient.request(`{
|
||||
owners {
|
||||
id
|
||||
bodyshopid: shopid
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
}
|
||||
}
|
||||
`);
|
||||
for (let i = 0; i <= ownersData.owners.length / batchSize; i++) {
|
||||
const slicedArray = ownersData.owners.slice(
|
||||
i * batchSize,
|
||||
i * batchSize + batchSize
|
||||
);
|
||||
const bulkOperation = [];
|
||||
slicedArray.forEach((owner) => {
|
||||
bulkOperation.push({ index: { _index: "owners", _id: owner.id } });
|
||||
bulkOperation.push(owner);
|
||||
});
|
||||
promiseQueue.push(bulkOperation);
|
||||
}
|
||||
|
||||
//Vehicles
|
||||
const vehiclesData = await gqlclient.request(`{
|
||||
vehicles {
|
||||
id
|
||||
bodyshopid: shopid
|
||||
v_model_yr
|
||||
v_model_desc
|
||||
v_make_desc
|
||||
v_color
|
||||
v_vin
|
||||
plate_no
|
||||
}
|
||||
}
|
||||
`);
|
||||
for (let i = 0; i <= vehiclesData.vehicles.length / batchSize; i++) {
|
||||
const slicedArray = vehiclesData.vehicles.slice(
|
||||
i * batchSize,
|
||||
i * batchSize + batchSize
|
||||
);
|
||||
const bulkOperation = [];
|
||||
slicedArray.forEach((vehicle) => {
|
||||
bulkOperation.push({ index: { _index: "vehicles", _id: vehicle.id } });
|
||||
bulkOperation.push(vehicle);
|
||||
});
|
||||
promiseQueue.push(bulkOperation);
|
||||
}
|
||||
|
||||
//payments
|
||||
const paymentsData = await gqlclient.request(`{
|
||||
payments {
|
||||
id
|
||||
amount
|
||||
paymentnum
|
||||
memo
|
||||
transactionid
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
bodyshopid: shopid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
`);
|
||||
for (let i = 0; i <= paymentsData.payments.length / batchSize; i++) {
|
||||
const slicedArray = paymentsData.payments.slice(
|
||||
i * batchSize,
|
||||
i * batchSize + batchSize
|
||||
);
|
||||
const bulkOperation = [];
|
||||
slicedArray.forEach((payment) => {
|
||||
bulkOperation.push({ index: { _index: "payments", _id: payment.id } });
|
||||
bulkOperation.push({
|
||||
..._.omit(payment, ["job"]),
|
||||
bodyshopid: payment.job.bodyshopid,
|
||||
});
|
||||
});
|
||||
promiseQueue.push(bulkOperation);
|
||||
}
|
||||
|
||||
//bills
|
||||
const billsData = await gqlclient.request(`{
|
||||
bills {
|
||||
id
|
||||
total
|
||||
invoice_number
|
||||
date
|
||||
vendor {
|
||||
name
|
||||
id
|
||||
}
|
||||
job {
|
||||
ro_number
|
||||
id
|
||||
bodyshopid: shopid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
`);
|
||||
for (let i = 0; i <= billsData.bills.length / batchSize; i++) {
|
||||
const slicedArray = billsData.bills.slice(
|
||||
i * batchSize,
|
||||
i * batchSize + batchSize
|
||||
);
|
||||
const bulkOperation = [];
|
||||
slicedArray.forEach((bill) => {
|
||||
bulkOperation.push({ index: { _index: "bills", _id: bill.id } });
|
||||
bulkOperation.push({
|
||||
..._.omit(bill, ["job"]),
|
||||
bodyshopid: bill.job.bodyshopid,
|
||||
});
|
||||
});
|
||||
promiseQueue.push(bulkOperation);
|
||||
}
|
||||
|
||||
//Load the entire queue.
|
||||
for (const queueItem of promiseQueue) {
|
||||
const insertJobsBulk = await osClient.bulk({ body: queueItem });
|
||||
|
||||
console.log(
|
||||
` ${insertJobsBulk.body.items.length} Records inserted in ${insertJobsBulk.body.took}.`
|
||||
);
|
||||
if (insertJobsBulk.body.errors)
|
||||
console.error("*** Error while inserting.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
OpenSearchUpdateHandler();
|
||||
52
package-lock.json
generated
52
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "0.0.1",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@opensearch-project/opensearch": "^2.2.1",
|
||||
"aws-sdk": "^2.1326.0",
|
||||
"axios": "^0.27.2",
|
||||
"bluebird": "^3.7.2",
|
||||
@@ -362,6 +363,22 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@opensearch-project/opensearch": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-2.2.1.tgz",
|
||||
"integrity": "sha512-8zfQX1acL9eWG+ohIc9nJVT9LSqXCdbVEJs0rCPRtji3XF6ahzsiKmGNTeWLxCPDxWCjAIWq9t95xP3Y5Egi6Q==",
|
||||
"dependencies": {
|
||||
"aws4": "^1.11.0",
|
||||
"debug": "^4.3.1",
|
||||
"hpagent": "^1.2.0",
|
||||
"ms": "^2.1.3",
|
||||
"secure-json-parse": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"yarn": "^1.22.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
@@ -3066,6 +3083,14 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/hpagent": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz",
|
||||
"integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/href-content": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/href-content/-/href-content-2.0.2.tgz",
|
||||
@@ -4972,6 +4997,11 @@
|
||||
"integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/secure-json-parse": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
|
||||
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
@@ -6580,6 +6610,18 @@
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz",
|
||||
"integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ=="
|
||||
},
|
||||
"@opensearch-project/opensearch": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-2.2.1.tgz",
|
||||
"integrity": "sha512-8zfQX1acL9eWG+ohIc9nJVT9LSqXCdbVEJs0rCPRtji3XF6ahzsiKmGNTeWLxCPDxWCjAIWq9t95xP3Y5Egi6Q==",
|
||||
"requires": {
|
||||
"aws4": "^1.11.0",
|
||||
"debug": "^4.3.1",
|
||||
"hpagent": "^1.2.0",
|
||||
"ms": "^2.1.3",
|
||||
"secure-json-parse": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
@@ -8622,6 +8664,11 @@
|
||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g=="
|
||||
},
|
||||
"hpagent": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz",
|
||||
"integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA=="
|
||||
},
|
||||
"href-content": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/href-content/-/href-content-2.0.2.tgz",
|
||||
@@ -10042,6 +10089,11 @@
|
||||
"resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz",
|
||||
"integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q=="
|
||||
},
|
||||
"secure-json-parse": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
|
||||
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-provider-node": "^3.319.0",
|
||||
"@opensearch-project/opensearch": "^2.2.1",
|
||||
"aws-sdk": "^2.1326.0",
|
||||
"aws4": "^1.12.0",
|
||||
"axios": "^0.27.2",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.20.2",
|
||||
|
||||
@@ -264,6 +264,13 @@ app.post("/newlog", (req, res) => {
|
||||
logger.log(message, type, user, record, object);
|
||||
});
|
||||
|
||||
var os = require("./server/opensearch/os-handler");
|
||||
app.post(
|
||||
"/opensearch", //fb.validateFirebaseIdToken,
|
||||
os.handler
|
||||
);
|
||||
app.post("/search", fb.validateFirebaseIdToken, os.search);
|
||||
|
||||
var cdkGetMake = require("./server/cdk/cdk-get-makes");
|
||||
app.post("/cdk/getvehicles", fb.validateFirebaseIdToken, cdkGetMake.default);
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ exports.default = function ({
|
||||
const taxAccountCode = findTaxCode(
|
||||
{
|
||||
local: false,
|
||||
federal: true,
|
||||
federal: process.env.COUNTRY === "USA" ? false : true,
|
||||
state:
|
||||
jobs_by_pk.state_tax_rate === 0
|
||||
? false
|
||||
@@ -93,7 +93,14 @@ exports.default = function ({
|
||||
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||
);
|
||||
|
||||
const QboTaxId = taxCodes[taxAccountCode];
|
||||
const QboTaxId =
|
||||
process.env.COUNTRY === "USA"
|
||||
? CheckQBOUSATaxID({
|
||||
jobline: jobline,
|
||||
type: "part",
|
||||
job: jobs_by_pk,
|
||||
})
|
||||
: taxCodes[taxAccountCode];
|
||||
if (!invoiceLineHash[account.name]) invoiceLineHash[account.name] = {};
|
||||
if (!invoiceLineHash[account.name][QboTaxId]) {
|
||||
invoiceLineHash[account.name][QboTaxId] = {
|
||||
@@ -158,13 +165,16 @@ exports.default = function ({
|
||||
const taxAccountCode = findTaxCode(
|
||||
{
|
||||
local: false,
|
||||
federal: true,
|
||||
federal: process.env.COUNTRY === "USA" ? false : true,
|
||||
state: jobs_by_pk.state_tax_rate === 0 ? false : true,
|
||||
},
|
||||
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||
);
|
||||
|
||||
const QboTaxId = taxCodes[taxAccountCode];
|
||||
const QboTaxId =
|
||||
process.env.COUNTRY === "USA"
|
||||
? CheckQBOUSATaxID({ jobline: jobline, type: "labor" })
|
||||
: taxCodes[taxAccountCode];
|
||||
if (!invoiceLineHash[account.name]) invoiceLineHash[account.name] = {};
|
||||
if (!invoiceLineHash[account.name][QboTaxId]) {
|
||||
invoiceLineHash[account.name][QboTaxId] = {
|
||||
@@ -225,13 +235,20 @@ exports.default = function ({
|
||||
const taxAccountCode = findTaxCode(
|
||||
{
|
||||
local: false,
|
||||
federal: true,
|
||||
federal: process.env.COUNTRY === "USA" ? false : true,
|
||||
state: jobs_by_pk.state_tax_rate === 0 ? false : true,
|
||||
},
|
||||
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||
);
|
||||
|
||||
const QboTaxId = taxCodes[taxAccountCode];
|
||||
const QboTaxId =
|
||||
process.env.COUNTRY === "USA"
|
||||
? CheckQBOUSATaxID({
|
||||
// jobline: jobline,
|
||||
job: jobs_by_pk,
|
||||
type: "materials",
|
||||
})
|
||||
: taxCodes[taxAccountCode];
|
||||
if (!invoiceLineHash[mapaAccount.name])
|
||||
invoiceLineHash[mapaAccount.name] = {};
|
||||
if (!invoiceLineHash[mapaAccount.name][QboTaxId]) {
|
||||
@@ -292,13 +309,20 @@ exports.default = function ({
|
||||
const taxAccountCode = findTaxCode(
|
||||
{
|
||||
local: false,
|
||||
federal: true,
|
||||
federal: process.env.COUNTRY === "USA" ? false : true,
|
||||
state: jobs_by_pk.state_tax_rate === 0 ? false : true,
|
||||
},
|
||||
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||
);
|
||||
|
||||
const QboTaxId = taxCodes[taxAccountCode];
|
||||
const QboTaxId =
|
||||
process.env.COUNTRY === "USA"
|
||||
? CheckQBOUSATaxID({
|
||||
// jobline: jobline,
|
||||
job: jobs_by_pk,
|
||||
type: "materials",
|
||||
})
|
||||
: taxCodes[taxAccountCode];
|
||||
if (!invoiceLineHash[mashAccount.name])
|
||||
invoiceLineHash[mashAccount.name] = {};
|
||||
if (!invoiceLineHash[mashAccount.name][QboTaxId]) {
|
||||
@@ -375,7 +399,7 @@ exports.default = function ({
|
||||
const taxAccountCode = findTaxCode(
|
||||
{
|
||||
local: false,
|
||||
federal: true,
|
||||
federal: process.env.COUNTRY === "USA" ? false : true,
|
||||
state: jobs_by_pk.state_tax_rate === 0 ? false : true,
|
||||
},
|
||||
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||
@@ -383,7 +407,14 @@ exports.default = function ({
|
||||
const account = responsibilityCenters.profits.find(
|
||||
(c) => c.name === responsibilityCenters.defaults.profits["TOW"]
|
||||
);
|
||||
const QboTaxId = taxCodes[taxAccountCode];
|
||||
const QboTaxId =
|
||||
process.env.COUNTRY === "USA"
|
||||
? CheckQBOUSATaxID({
|
||||
// jobline: jobline,
|
||||
job: jobs_by_pk,
|
||||
type: "towing",
|
||||
})
|
||||
: taxCodes[taxAccountCode];
|
||||
InvoiceLineAdd.push({
|
||||
DetailType: "SalesItemLineDetail",
|
||||
Amount: Dinero({
|
||||
@@ -427,7 +458,7 @@ exports.default = function ({
|
||||
const taxAccountCode = findTaxCode(
|
||||
{
|
||||
local: false,
|
||||
federal: true,
|
||||
federal: process.env.COUNTRY === "USA" ? false : true,
|
||||
state: jobs_by_pk.state_tax_rate === 0 ? false : true,
|
||||
},
|
||||
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||
@@ -435,7 +466,14 @@ exports.default = function ({
|
||||
const account = responsibilityCenters.profits.find(
|
||||
(c) => c.name === responsibilityCenters.defaults.profits["TOW"]
|
||||
);
|
||||
const QboTaxId = taxCodes[taxAccountCode];
|
||||
const QboTaxId =
|
||||
process.env.COUNTRY === "USA"
|
||||
? CheckQBOUSATaxID({
|
||||
// jobline: jobline,
|
||||
job: jobs_by_pk,
|
||||
type: "storage",
|
||||
})
|
||||
: taxCodes[taxAccountCode];
|
||||
InvoiceLineAdd.push({
|
||||
DetailType: "SalesItemLineDetail",
|
||||
Amount: Dinero({
|
||||
@@ -482,13 +520,20 @@ exports.default = function ({
|
||||
const taxAccountCode = findTaxCode(
|
||||
{
|
||||
local: false,
|
||||
federal: true,
|
||||
federal: process.env.COUNTRY === "USA" ? false : true,
|
||||
state: jobs_by_pk.state_tax_rate === 0 ? false : true,
|
||||
},
|
||||
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||
);
|
||||
|
||||
const QboTaxId = taxCodes[taxAccountCode];
|
||||
const QboTaxId =
|
||||
process.env.COUNTRY === "USA"
|
||||
? CheckQBOUSATaxID({
|
||||
// jobline: jobline,
|
||||
type: "adjustment",
|
||||
job: jobs_by_pk,
|
||||
})
|
||||
: taxCodes[taxAccountCode];
|
||||
InvoiceLineAdd.push({
|
||||
DetailType: "SalesItemLineDetail",
|
||||
Amount: Dinero({
|
||||
@@ -598,7 +643,7 @@ exports.default = function ({
|
||||
findTaxCode(
|
||||
{
|
||||
local: false,
|
||||
federal: true,
|
||||
federal: process.env.COUNTRY === "USA" ? false : true,
|
||||
state: false,
|
||||
},
|
||||
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||
@@ -622,29 +667,29 @@ exports.default = function ({
|
||||
|
||||
//QB USA with GST
|
||||
//This was required for the No. 1 Collision Group.
|
||||
if (
|
||||
bodyshop.accountingconfig &&
|
||||
bodyshop.accountingconfig.qbo &&
|
||||
bodyshop.accountingconfig.qbo_usa &&
|
||||
bodyshop.region_config.includes("CA_")
|
||||
) {
|
||||
InvoiceLineAdd.push({
|
||||
DetailType: "SalesItemLineDetail",
|
||||
Amount: Dinero(jobs_by_pk.job_totals.totals.federal_tax).toFormat(
|
||||
DineroQbFormat
|
||||
),
|
||||
SalesItemLineDetail: {
|
||||
...(jobs_by_pk.class
|
||||
? { ClassRef: { value: classes[jobs_by_pk.class] } }
|
||||
: {}),
|
||||
ItemRef: {
|
||||
value:
|
||||
items[bodyshop.md_responsibility_centers.taxes.federal.accountitem],
|
||||
},
|
||||
Qty: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
// if (
|
||||
// bodyshop.accountingconfig &&
|
||||
// bodyshop.accountingconfig.qbo &&
|
||||
// bodyshop.accountingconfig.qbo_usa &&
|
||||
// bodyshop.region_config.includes("CA_")
|
||||
// ) {
|
||||
// InvoiceLineAdd.push({
|
||||
// DetailType: "SalesItemLineDetail",
|
||||
// Amount: Dinero(jobs_by_pk.job_totals.totals.federal_tax).toFormat(
|
||||
// DineroQbFormat
|
||||
// ),
|
||||
// SalesItemLineDetail: {
|
||||
// ...(jobs_by_pk.class
|
||||
// ? { ClassRef: { value: classes[jobs_by_pk.class] } }
|
||||
// : {}),
|
||||
// ItemRef: {
|
||||
// value:
|
||||
// items[bodyshop.md_responsibility_centers.taxes.federal.accountitem],
|
||||
// },
|
||||
// Qty: 1,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
if (!qbo && InvoiceLineAdd.length === 0) {
|
||||
//Handle the scenario where there is a $0 sale invoice.
|
||||
@@ -785,3 +830,19 @@ exports.createMultiQbPayerLines = function ({
|
||||
|
||||
return InvoiceLineAdd;
|
||||
};
|
||||
|
||||
function CheckQBOUSATaxID({ jobline, job, type }) {
|
||||
if (type === "labor") {
|
||||
return jobline.lbr_tax ? "TAX" : "NON";
|
||||
} else if (type === "part") {
|
||||
return jobline.tax_part ? "TAX" : "NON";
|
||||
} else if (type === "materials") {
|
||||
return job.tax_paint_mat_rt > 0 ? "TAX" : "NON";
|
||||
} else if (type === " towing") {
|
||||
return true ? "TAX" : "NON";
|
||||
} else if (type === "adjustment") {
|
||||
return false ? "TAX" : "NON";
|
||||
} else {
|
||||
throw new Error(`Unknown type to calculate tax id: ${type} `);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ const {
|
||||
const OAuthClient = require("intuit-oauth");
|
||||
const CreateInvoiceLines = require("../qb-receivables-lines").default;
|
||||
const moment = require("moment-timezone");
|
||||
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
const { generateOwnerTier } = require("../qbxml/qbxml-utils");
|
||||
const { createMultiQbPayerLines } = require("../qb-receivables-lines");
|
||||
@@ -635,8 +634,7 @@ async function InsertInvoice(
|
||||
],
|
||||
...(bodyshop.accountingconfig &&
|
||||
bodyshop.accountingconfig.qbo &&
|
||||
bodyshop.accountingconfig.qbo_usa &&
|
||||
bodyshop.region_config.includes("CA_") && {
|
||||
bodyshop.accountingconfig.qbo_usa && {
|
||||
TxnTaxDetail: {
|
||||
TxnTaxCodeRef: {
|
||||
value:
|
||||
|
||||
@@ -124,9 +124,16 @@ const generateBillLine = (billLine, responsibilityCenters, jobClass) => {
|
||||
.multiply(billLine.quantity || 1)
|
||||
.toFormat(DineroQbFormat),
|
||||
...(jobClass ? { ClassRef: { FullName: jobClass } } : {}),
|
||||
SalesTaxCodeRef: {
|
||||
FullName: findTaxCode(billLine, responsibilityCenters.sales_tax_codes),
|
||||
},
|
||||
...(process.env.COUNTRY !== "USA"
|
||||
? {
|
||||
SalesTaxCodeRef: {
|
||||
FullName: findTaxCode(
|
||||
billLine,
|
||||
responsibilityCenters.sales_tax_codes
|
||||
),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -767,18 +767,41 @@ const CreateCosts = (job) => {
|
||||
billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = Dinero();
|
||||
billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
].add(
|
||||
Dinero({
|
||||
amount:
|
||||
(job.bodyshop.jc_hourly_rates &&
|
||||
job.bodyshop.jc_hourly_rates.mapa * 100) ||
|
||||
0,
|
||||
}).multiply(job.job_totals.rates.mapa.hours)
|
||||
);
|
||||
if (job.bodyshop.use_paint_scale_data === true) {
|
||||
if (job.mixdata.length > 0) {
|
||||
billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = Dinero({
|
||||
amount: (job.mixdata[0] && job.mixdata[0].totalliquidcost * 100) || 0,
|
||||
});
|
||||
} else {
|
||||
billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
].add(
|
||||
Dinero({
|
||||
amount:
|
||||
(job.bodyshop.jc_hourly_rates &&
|
||||
job.bodyshop.jc_hourly_rates.mapa * 100) ||
|
||||
0,
|
||||
}).multiply(job.job_totals.rates.mapa.hours)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = billTotalsByCostCenters[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
].add(
|
||||
Dinero({
|
||||
amount:
|
||||
(job.bodyshop.jc_hourly_rates &&
|
||||
job.bodyshop.jc_hourly_rates.mapa * 100) ||
|
||||
0,
|
||||
}).multiply(materialsHours.mapaHrs)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
|
||||
if (
|
||||
|
||||
@@ -225,6 +225,8 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
|
||||
adjustment_bottom_line
|
||||
state_tax_rate
|
||||
qb_multiple_payers
|
||||
tax_paint_mat_rt
|
||||
tax_lbr_rt
|
||||
owner {
|
||||
accountingid
|
||||
}
|
||||
@@ -243,6 +245,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
|
||||
prt_dsmk_p
|
||||
prt_dsmk_m
|
||||
tax_part
|
||||
lbr_tax
|
||||
line_ref
|
||||
unq_seq
|
||||
lbr_op
|
||||
@@ -658,7 +661,6 @@ exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employee
|
||||
|
||||
exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
|
||||
bodyshops_by_pk(id: $bodyshopid){
|
||||
|
||||
id
|
||||
shopname
|
||||
address1
|
||||
@@ -674,6 +676,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
|
||||
jc_hourly_rates
|
||||
cdk_dealerid
|
||||
pbs_serialnumber
|
||||
use_paint_scale_data
|
||||
timezone
|
||||
}
|
||||
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {updated_at: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}]}) {
|
||||
@@ -829,6 +832,10 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
|
||||
employee_number
|
||||
id
|
||||
}
|
||||
mixdata(limit: 1, order_by: {updated_at: desc}) {
|
||||
jobid
|
||||
totalliquidcost
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -1224,12 +1231,17 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
|
||||
flat_rate
|
||||
ciecacode
|
||||
}
|
||||
mixdata(limit: 1, order_by: {updated_at: desc}) {
|
||||
jobid
|
||||
totalliquidcost
|
||||
}
|
||||
bodyshop{
|
||||
id
|
||||
md_responsibility_centers
|
||||
jc_hourly_rates
|
||||
cdk_dealerid
|
||||
pbs_serialnumber
|
||||
use_paint_scale_data
|
||||
}
|
||||
}
|
||||
}`;
|
||||
@@ -1332,12 +1344,17 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
|
||||
flat_rate
|
||||
ciecacode
|
||||
}
|
||||
mixdata(limit: 1, order_by: {updated_at: desc}) {
|
||||
jobid
|
||||
totalliquidcost
|
||||
}
|
||||
bodyshop {
|
||||
id
|
||||
md_responsibility_centers
|
||||
jc_hourly_rates
|
||||
cdk_dealerid
|
||||
pbs_serialnumber
|
||||
pbs_serialnumber
|
||||
use_paint_scale_data
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1770,8 +1787,8 @@ exports.GET_JOB_FOR_PPC = `query GET_JOB_FOR_PPC($jobid: uuid!) {
|
||||
timezone
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
`;
|
||||
|
||||
exports.QUERY_PARTS_SCAN = `query QUERY_PARTS_SCAN ($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
bodyshop {
|
||||
@@ -1795,3 +1812,11 @@ exports.UPDATE_PARTS_CRITICAL = `mutation UPDATE_PARTS_CRITICAL ($IdsToMarkCriti
|
||||
affected_rows
|
||||
}
|
||||
}`;
|
||||
|
||||
exports.ACTIVE_SHOP_BY_USER = `query ACTIVE_SHOP_BY_USER($user: String) {
|
||||
associations(where: {active: {_eq: true}, useremail: {_eq: $user}}) {
|
||||
id
|
||||
shopid
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -621,18 +621,41 @@ function GenerateCostingData(job) {
|
||||
billTotalsByCostCenters.additionalCosts[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = Dinero();
|
||||
billTotalsByCostCenters.additionalCosts[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = billTotalsByCostCenters.additionalCosts[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
].add(
|
||||
Dinero({
|
||||
amount:
|
||||
(job.bodyshop.jc_hourly_rates &&
|
||||
job.bodyshop.jc_hourly_rates.mapa * 100) ||
|
||||
0,
|
||||
}).multiply(materialsHours.mapaHrs)
|
||||
);
|
||||
if (job.bodyshop.use_paint_scale_data === true) {
|
||||
if (job.mixdata.length > 0) {
|
||||
billTotalsByCostCenters.additionalCosts[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = Dinero({
|
||||
amount: (job.mixdata[0] && job.mixdata[0].totalliquidcost * 100) || 0,
|
||||
});
|
||||
} else {
|
||||
billTotalsByCostCenters.additionalCosts[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = billTotalsByCostCenters.additionalCosts[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
].add(
|
||||
Dinero({
|
||||
amount:
|
||||
(job.bodyshop.jc_hourly_rates &&
|
||||
job.bodyshop.jc_hourly_rates.mapa * 100) ||
|
||||
0,
|
||||
}).multiply(materialsHours.mapaHrs)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
billTotalsByCostCenters.additionalCosts[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
] = billTotalsByCostCenters.additionalCosts[
|
||||
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
|
||||
].add(
|
||||
Dinero({
|
||||
amount:
|
||||
(job.bodyshop.jc_hourly_rates &&
|
||||
job.bodyshop.jc_hourly_rates.mapa * 100) ||
|
||||
0,
|
||||
}).multiply(materialsHours.mapaHrs)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
|
||||
|
||||
@@ -20,7 +20,8 @@ async function StatusTransition(req, res) {
|
||||
res.status(403).send("Unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
return;
|
||||
const {
|
||||
id: jobid,
|
||||
status: value,
|
||||
@@ -78,7 +79,7 @@ async function StatusTransition(req, res) {
|
||||
|
||||
res.sendStatus(200); //.json(ret);
|
||||
} catch (error) {
|
||||
logger.log("job-costing-error", "ERROR", req.user.email, jobid, {
|
||||
logger.log("job-status-transition-error", "ERROR", req.user?.email, jobid, {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
|
||||
252
server/opensearch/os-handler.js
Normal file
252
server/opensearch/os-handler.js
Normal file
@@ -0,0 +1,252 @@
|
||||
const Dinero = require("dinero.js");
|
||||
const queries = require("../graphql-client/queries");
|
||||
//const client = require("../graphql-client/graphql-client").client;
|
||||
const _ = require("lodash");
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
const path = require("path");
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
`.env.${process.env.NODE_ENV || "development"}`
|
||||
),
|
||||
});
|
||||
const { Client, Connection } = require("@opensearch-project/opensearch");
|
||||
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||
const aws4 = require("aws4");
|
||||
const { gql } = require("graphql-request");
|
||||
|
||||
var host = process.env.OPEN_SEARCH_HOST;
|
||||
const createAwsConnector = (credentials, region) => {
|
||||
class AmazonConnection extends Connection {
|
||||
buildRequestObject(params) {
|
||||
const request = super.buildRequestObject(params);
|
||||
request.service = "es";
|
||||
request.region = region;
|
||||
request.headers = request.headers || {};
|
||||
request.headers["host"] = request.hostname;
|
||||
|
||||
return aws4.sign(request, credentials);
|
||||
}
|
||||
}
|
||||
return {
|
||||
Connection: AmazonConnection,
|
||||
};
|
||||
};
|
||||
|
||||
const getClient = async () => {
|
||||
const credentials = await defaultProvider()();
|
||||
return new Client({
|
||||
...createAwsConnector(credentials, "ca-central-1"),
|
||||
node: host,
|
||||
});
|
||||
};
|
||||
|
||||
async function OpenSearchUpdateHandler(req, res) {
|
||||
try {
|
||||
var osClient = await getClient();
|
||||
// const osClient = new Client({
|
||||
// node: `https://imex:<password>@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`,
|
||||
// });
|
||||
|
||||
if (req.body.event.op === "DELETE") {
|
||||
let response;
|
||||
response = await osClient.delete({
|
||||
id: req.body.event.data.old.id,
|
||||
index: req.body.table.name,
|
||||
});
|
||||
res.status(200).json(response.body);
|
||||
} else {
|
||||
let document;
|
||||
|
||||
switch (req.body.table.name) {
|
||||
case "jobs":
|
||||
document = _.pick(req.body.event.data.new, [
|
||||
"id",
|
||||
"bodyshopid",
|
||||
"ro_number",
|
||||
"clm_no",
|
||||
"ownr_fn",
|
||||
"ownr_ln",
|
||||
"status",
|
||||
"ownr_co_nm",
|
||||
"v_model_yr",
|
||||
"v_make_desc",
|
||||
"v_model_desc",
|
||||
]);
|
||||
document.bodyshopid = req.body.event.data.new.shopid;
|
||||
break;
|
||||
case "vehicles":
|
||||
document = _.pick(req.body.event.data.new, [
|
||||
"id",
|
||||
"v_model_yr",
|
||||
"v_model_desc",
|
||||
"v_make_desc",
|
||||
"v_color",
|
||||
"v_vin",
|
||||
"plate_no",
|
||||
]);
|
||||
document.bodyshopid = req.body.event.data.new.shopid;
|
||||
break;
|
||||
case "owners":
|
||||
document = _.pick(req.body.event.data.new, [
|
||||
"id",
|
||||
"ownr_fn",
|
||||
"ownr_ln",
|
||||
"ownr_co_nm",
|
||||
"ownr_ph1",
|
||||
"ownr_ph2",
|
||||
]);
|
||||
document.bodyshopid = req.body.event.data.new.shopid;
|
||||
break;
|
||||
case "bills":
|
||||
const bill = await client.request(
|
||||
`query ADMIN_GET_BILL_BY_ID($billId: uuid!) {
|
||||
bills_by_pk(id: $billId) {
|
||||
id
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
shopid
|
||||
}
|
||||
vendor {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ billId: req.body.event.data.new.id }
|
||||
);
|
||||
|
||||
document = {
|
||||
..._.pick(req.body.event.data.new, [
|
||||
"id",
|
||||
"invoice_number",
|
||||
"date",
|
||||
]),
|
||||
...bill.bills_by_pk,
|
||||
bodyshopid: bill.bills_by_pk.job.shopid,
|
||||
};
|
||||
|
||||
break;
|
||||
case "payments":
|
||||
//Query to get the job and RO number
|
||||
const payment = await client.request(
|
||||
`query ADMIN_GET_PAYMENT_BY_ID($paymentId: uuid!) {
|
||||
payments_by_pk(id: $paymentId) {
|
||||
id
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
shopid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
`,
|
||||
{ paymentId: req.body.event.data.new.id }
|
||||
);
|
||||
document = {
|
||||
..._.pick(req.body.event.data.new, ["id", "invoice_number"]),
|
||||
...payment.payments_by_pk,
|
||||
bodyshopid: bill.payments_by_pk.job.shopid,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
id: req.body.event.data.new.id,
|
||||
index: req.body.table.name,
|
||||
body: document,
|
||||
};
|
||||
|
||||
let response;
|
||||
response = await osClient.index(payload);
|
||||
console.log(response.body);
|
||||
res.status(200).json(response.body);
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(400).json(JSON.stringify(error));
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
async function OpensearchSearchHandler(req, res) {
|
||||
try {
|
||||
const { search, bodyshopid } = req.body;
|
||||
if (!req.user) {
|
||||
res.sendStatus(401);
|
||||
return;
|
||||
}
|
||||
logger.log("os-search", "DEBUG", req.user.email, null, {
|
||||
search,
|
||||
});
|
||||
|
||||
const BearerToken = req.headers.authorization;
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||
headers: {
|
||||
Authorization: BearerToken,
|
||||
},
|
||||
});
|
||||
|
||||
const assocs = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
.request(queries.ACTIVE_SHOP_BY_USER, {
|
||||
user: req.user.email,
|
||||
});
|
||||
|
||||
if (assocs.length === 0) {
|
||||
res.sendStatus(401);
|
||||
}
|
||||
|
||||
var osClient = await getClient();
|
||||
|
||||
const { body } = await osClient.search({
|
||||
body: {
|
||||
size: 100,
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
multi_match: {
|
||||
query: search,
|
||||
type: "phrase_prefix",
|
||||
//fields: ["*"],
|
||||
// fuzziness: "5",
|
||||
//prefix_length: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
match: {
|
||||
bodyshopid: assocs.associations[0].shopid,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
_score: {
|
||||
order: "desc",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
res.json(body);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.log("os-search-error", "ERROR", req.user.email, null, {
|
||||
error: JSON.stringify(error),
|
||||
});
|
||||
res.status(400).json(error);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
exports.handler = OpenSearchUpdateHandler;
|
||||
exports.search = OpensearchSearchHandler;
|
||||
Reference in New Issue
Block a user