@@ -1,4 +1,4 @@
|
|||||||
<babeledit_project be_version="2.7.1" version="1.2">
|
<babeledit_project version="1.2" be_version="2.7.1">
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
BabelEdit project file
|
BabelEdit project file
|
||||||
@@ -4000,6 +4000,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>ignoreblockeddays</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>
|
<concept_node>
|
||||||
<name>inhousevendorid</name>
|
<name>inhousevendorid</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -4045,6 +4066,27 @@
|
|||||||
<folder_node>
|
<folder_node>
|
||||||
<name>intake</name>
|
<name>intake</name>
|
||||||
<children>
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>next_contact_hours</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>
|
<concept_node>
|
||||||
<name>templates</name>
|
<name>templates</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -9990,6 +10032,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>length</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>
|
<concept_node>
|
||||||
<name>localtax</name>
|
<name>localtax</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -14010,6 +14073,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>print</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>
|
<concept_node>
|
||||||
<name>refresh</name>
|
<name>refresh</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -34091,6 +34175,74 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<folder_node>
|
||||||
|
<name>labels</name>
|
||||||
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>count</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>labels</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>position</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>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>mpi_animal_checklist</name>
|
<name>mpi_animal_checklist</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -1,232 +1 @@
|
|||||||
[
|
[]
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "Leanne Graham",
|
|
||||||
"username": "Bret",
|
|
||||||
"email": "Sincere@april.biz",
|
|
||||||
"address": {
|
|
||||||
"street": "Kulas Light",
|
|
||||||
"suite": "Apt. 556",
|
|
||||||
"city": "Gwenborough",
|
|
||||||
"zipcode": "92998-3874",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-37.3159",
|
|
||||||
"lng": "81.1496"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "1-770-736-8031 x56442",
|
|
||||||
"website": "hildegard.org",
|
|
||||||
"company": {
|
|
||||||
"name": "Romaguera-Crona",
|
|
||||||
"catchPhrase": "Multi-layered client-server neural-net",
|
|
||||||
"bs": "harness real-time e-markets"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"name": "Ervin Howell",
|
|
||||||
"username": "Antonette",
|
|
||||||
"email": "Shanna@melissa.tv",
|
|
||||||
"address": {
|
|
||||||
"street": "Victor Plains",
|
|
||||||
"suite": "Suite 879",
|
|
||||||
"city": "Wisokyburgh",
|
|
||||||
"zipcode": "90566-7771",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-43.9509",
|
|
||||||
"lng": "-34.4618"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "010-692-6593 x09125",
|
|
||||||
"website": "anastasia.net",
|
|
||||||
"company": {
|
|
||||||
"name": "Deckow-Crist",
|
|
||||||
"catchPhrase": "Proactive didactic contingency",
|
|
||||||
"bs": "synergize scalable supply-chains"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"name": "Clementine Bauch",
|
|
||||||
"username": "Samantha",
|
|
||||||
"email": "Nathan@yesenia.net",
|
|
||||||
"address": {
|
|
||||||
"street": "Douglas Extension",
|
|
||||||
"suite": "Suite 847",
|
|
||||||
"city": "McKenziehaven",
|
|
||||||
"zipcode": "59590-4157",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-68.6102",
|
|
||||||
"lng": "-47.0653"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "1-463-123-4447",
|
|
||||||
"website": "ramiro.info",
|
|
||||||
"company": {
|
|
||||||
"name": "Romaguera-Jacobson",
|
|
||||||
"catchPhrase": "Face to face bifurcated interface",
|
|
||||||
"bs": "e-enable strategic applications"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"name": "Patricia Lebsack",
|
|
||||||
"username": "Karianne",
|
|
||||||
"email": "Julianne.OConner@kory.org",
|
|
||||||
"address": {
|
|
||||||
"street": "Hoeger Mall",
|
|
||||||
"suite": "Apt. 692",
|
|
||||||
"city": "South Elvis",
|
|
||||||
"zipcode": "53919-4257",
|
|
||||||
"geo": {
|
|
||||||
"lat": "29.4572",
|
|
||||||
"lng": "-164.2990"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "493-170-9623 x156",
|
|
||||||
"website": "kale.biz",
|
|
||||||
"company": {
|
|
||||||
"name": "Robel-Corkery",
|
|
||||||
"catchPhrase": "Multi-tiered zero tolerance productivity",
|
|
||||||
"bs": "transition cutting-edge web services"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"name": "Chelsey Dietrich",
|
|
||||||
"username": "Kamren",
|
|
||||||
"email": "Lucio_Hettinger@annie.ca",
|
|
||||||
"address": {
|
|
||||||
"street": "Skiles Walks",
|
|
||||||
"suite": "Suite 351",
|
|
||||||
"city": "Roscoeview",
|
|
||||||
"zipcode": "33263",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-31.8129",
|
|
||||||
"lng": "62.5342"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "(254)954-1289",
|
|
||||||
"website": "demarco.info",
|
|
||||||
"company": {
|
|
||||||
"name": "Keebler LLC",
|
|
||||||
"catchPhrase": "User-centric fault-tolerant solution",
|
|
||||||
"bs": "revolutionize end-to-end systems"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"name": "Mrs. Dennis Schulist",
|
|
||||||
"username": "Leopoldo_Corkery",
|
|
||||||
"email": "Karley_Dach@jasper.info",
|
|
||||||
"address": {
|
|
||||||
"street": "Norberto Crossing",
|
|
||||||
"suite": "Apt. 950",
|
|
||||||
"city": "South Christy",
|
|
||||||
"zipcode": "23505-1337",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-71.4197",
|
|
||||||
"lng": "71.7478"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "1-477-935-8478 x6430",
|
|
||||||
"website": "ola.org",
|
|
||||||
"company": {
|
|
||||||
"name": "Considine-Lockman",
|
|
||||||
"catchPhrase": "Synchronised bottom-line interface",
|
|
||||||
"bs": "e-enable innovative applications"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 7,
|
|
||||||
"name": "Kurtis Weissnat",
|
|
||||||
"username": "Elwyn.Skiles",
|
|
||||||
"email": "Telly.Hoeger@billy.biz",
|
|
||||||
"address": {
|
|
||||||
"street": "Rex Trail",
|
|
||||||
"suite": "Suite 280",
|
|
||||||
"city": "Howemouth",
|
|
||||||
"zipcode": "58804-1099",
|
|
||||||
"geo": {
|
|
||||||
"lat": "24.8918",
|
|
||||||
"lng": "21.8984"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "210.067.6132",
|
|
||||||
"website": "elvis.io",
|
|
||||||
"company": {
|
|
||||||
"name": "Johns Group",
|
|
||||||
"catchPhrase": "Configurable multimedia task-force",
|
|
||||||
"bs": "generate enterprise e-tailers"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"name": "Nicholas Runolfsdottir V",
|
|
||||||
"username": "Maxime_Nienow",
|
|
||||||
"email": "Sherwood@rosamond.me",
|
|
||||||
"address": {
|
|
||||||
"street": "Ellsworth Summit",
|
|
||||||
"suite": "Suite 729",
|
|
||||||
"city": "Aliyaview",
|
|
||||||
"zipcode": "45169",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-14.3990",
|
|
||||||
"lng": "-120.7677"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "586.493.6943 x140",
|
|
||||||
"website": "jacynthe.com",
|
|
||||||
"company": {
|
|
||||||
"name": "Abernathy Group",
|
|
||||||
"catchPhrase": "Implemented secondary concept",
|
|
||||||
"bs": "e-enable extensible e-tailers"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 9,
|
|
||||||
"name": "Glenna Reichert",
|
|
||||||
"username": "Delphine",
|
|
||||||
"email": "Chaim_McDermott@dana.io",
|
|
||||||
"address": {
|
|
||||||
"street": "Dayna Park",
|
|
||||||
"suite": "Suite 449",
|
|
||||||
"city": "Bartholomebury",
|
|
||||||
"zipcode": "76495-3109",
|
|
||||||
"geo": {
|
|
||||||
"lat": "24.6463",
|
|
||||||
"lng": "-168.8889"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "(775)976-6794 x41206",
|
|
||||||
"website": "conrad.com",
|
|
||||||
"company": {
|
|
||||||
"name": "Yost and Sons",
|
|
||||||
"catchPhrase": "Switchable contextually-based project",
|
|
||||||
"bs": "aggregate real-time technologies"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"name": "Clementina DuBuque",
|
|
||||||
"username": "Moriah.Stanton",
|
|
||||||
"email": "Rey.Padberg@karina.biz",
|
|
||||||
"address": {
|
|
||||||
"street": "Kattie Turnpike",
|
|
||||||
"suite": "Suite 198",
|
|
||||||
"city": "Lebsackbury",
|
|
||||||
"zipcode": "31428-2261",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-38.2386",
|
|
||||||
"lng": "57.2232"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "024-648-3804",
|
|
||||||
"website": "ambrose.net",
|
|
||||||
"company": {
|
|
||||||
"name": "Hoeger LLC",
|
|
||||||
"catchPhrase": "Centralized empowering task-force",
|
|
||||||
"bs": "target end-to-end models"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -111,11 +111,15 @@
|
|||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"react-error-overlay": "6.0.9"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sentry/webpack-plugin": "^1.18.3",
|
"@sentry/webpack-plugin": "^1.18.3",
|
||||||
"@testing-library/cypress": "^8.0.2",
|
"@testing-library/cypress": "^8.0.2",
|
||||||
"cypress": "^9.1.1",
|
"cypress": "^9.1.1",
|
||||||
"eslint-plugin-cypress": "^2.12.1",
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
|
"react-error-overlay": "6.0.9",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.2"
|
"source-map-explorer": "^2.5.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import { Form, Input, Table } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
import BillFormItemsExtendedFormItem from "./bill-form-lines.extended.formitem.component";
|
||||||
|
export default function BillFormLinesExtended({
|
||||||
|
lineData,
|
||||||
|
discount,
|
||||||
|
form,
|
||||||
|
responsibilityCenters,
|
||||||
|
disabled,
|
||||||
|
}) {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.line_desc"),
|
||||||
|
dataIndex: "line_desc",
|
||||||
|
key: "line_desc",
|
||||||
|
width: "10%",
|
||||||
|
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.oem_partno"),
|
||||||
|
dataIndex: "oem_partno",
|
||||||
|
key: "oem_partno",
|
||||||
|
width: "10%",
|
||||||
|
sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.part_type"),
|
||||||
|
dataIndex: "part_type",
|
||||||
|
key: "part_type",
|
||||||
|
width: "10%",
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: t("jobs.labels.partsfilter"),
|
||||||
|
value: ["PAN", "PAP", "PAL", "PAA", "PAS", "PASL"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t("joblines.fields.part_types.PAN"),
|
||||||
|
value: ["PAN", "PAP"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t("joblines.fields.part_types.PAL"),
|
||||||
|
value: ["PAL"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t("joblines.fields.part_types.PAA"),
|
||||||
|
value: ["PAA"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t("joblines.fields.part_types.PAS"),
|
||||||
|
value: ["PAS", "PASL"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onFilter: (value, record) => value.includes(record.part_type),
|
||||||
|
render: (text, record) =>
|
||||||
|
record.part_type
|
||||||
|
? t(`joblines.fields.part_types.${record.part_type}`)
|
||||||
|
: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.act_price"),
|
||||||
|
dataIndex: "act_price",
|
||||||
|
key: "act_price",
|
||||||
|
width: "10%",
|
||||||
|
sorter: (a, b) => a.act_price - b.act_price,
|
||||||
|
shouldCellUpdate: false,
|
||||||
|
render: (text, record) => (
|
||||||
|
<>
|
||||||
|
<CurrencyFormatter>
|
||||||
|
{record.db_ref === "900510" || record.db_ref === "900511"
|
||||||
|
? record.prt_dsmk_m
|
||||||
|
: record.act_price}
|
||||||
|
</CurrencyFormatter>
|
||||||
|
{record.part_qty ? `(x ${record.part_qty})` : null}
|
||||||
|
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
|
||||||
|
<span
|
||||||
|
style={{ marginLeft: ".2rem" }}
|
||||||
|
>{`(${record.prt_dsmk_p}%)`}</span>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("billlines.fields.posting"),
|
||||||
|
dataIndex: "posting",
|
||||||
|
key: "posting",
|
||||||
|
|
||||||
|
render: (text, record, index) => (
|
||||||
|
<Form.Item noStyle name={["billlineskeys", record.id]}>
|
||||||
|
<BillFormItemsExtendedFormItem
|
||||||
|
form={form}
|
||||||
|
record={record}
|
||||||
|
index={index}
|
||||||
|
responsibilityCenters={responsibilityCenters}
|
||||||
|
discount={discount}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const data =
|
||||||
|
search === ""
|
||||||
|
? lineData
|
||||||
|
: lineData.filter(
|
||||||
|
(l) =>
|
||||||
|
(l.line_desc &&
|
||||||
|
l.line_desc.toLowerCase().includes(search.toLowerCase())) ||
|
||||||
|
(l.oem_partno &&
|
||||||
|
l.oem_partno.toLowerCase().includes(search.toLowerCase())) ||
|
||||||
|
(l.act_price &&
|
||||||
|
l.act_price.toString().startsWith(search.toString()))
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Item noStyle name="billlineskeys">
|
||||||
|
<button onClick={() => console.log(form.getFieldsValue())}>form</button>
|
||||||
|
<Input onChange={(e) => setSearch(e.target.value)} allowClear />
|
||||||
|
<Table
|
||||||
|
pagination={false}
|
||||||
|
size="small"
|
||||||
|
columns={columns}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={data}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
PlusCircleFilled,
|
||||||
|
MinusCircleFilled,
|
||||||
|
WarningOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import { Form, Button, InputNumber, Input, Select, Switch, Space } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
|
import CiecaSelect from "../../utils/Ciecaselect";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(BillFormItemsExtendedFormItem);
|
||||||
|
|
||||||
|
export function BillFormItemsExtendedFormItem({
|
||||||
|
value,
|
||||||
|
bodyshop,
|
||||||
|
form,
|
||||||
|
record,
|
||||||
|
index,
|
||||||
|
disabled,
|
||||||
|
responsibilityCenters,
|
||||||
|
discount,
|
||||||
|
}) {
|
||||||
|
// const { billlineskeys } = form.getFieldsValue("billlineskeys");
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
if (!value)
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const values = form.getFieldsValue("billlineskeys");
|
||||||
|
|
||||||
|
form.setFieldsValue({
|
||||||
|
...values,
|
||||||
|
billlineskeys: {
|
||||||
|
...(values.billlineskeys || {}),
|
||||||
|
[record.id]: {
|
||||||
|
joblineid: record.id,
|
||||||
|
line_desc: record.line_desc,
|
||||||
|
quantity: record.part_qty || 1,
|
||||||
|
actual_price: record.act_price,
|
||||||
|
cost_center: record.part_type
|
||||||
|
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
||||||
|
? record.part_type
|
||||||
|
: responsibilityCenters.defaults &&
|
||||||
|
(responsibilityCenters.defaults.costs[record.part_type] ||
|
||||||
|
null)
|
||||||
|
: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusCircleFilled />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space wrap>
|
||||||
|
<Form.Item
|
||||||
|
label={t("billlines.fields.line_desc")}
|
||||||
|
name={["billlineskeys", record.id, "line_desc"]}
|
||||||
|
>
|
||||||
|
<Input disabled={disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("billlines.fields.quantity")}
|
||||||
|
name={["billlineskeys", record.id, "quantity"]}
|
||||||
|
>
|
||||||
|
<InputNumber precision={0} min={0} disabled={disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("billlines.fields.actual_price")}
|
||||||
|
name={["billlineskeys", record.id, "actual_price"]}
|
||||||
|
>
|
||||||
|
<CurrencyInput
|
||||||
|
min={0}
|
||||||
|
disabled={disabled}
|
||||||
|
onBlur={(e) => {
|
||||||
|
const { billlineskeys } = form.getFieldsValue("billlineskeys");
|
||||||
|
form.setFieldsValue({
|
||||||
|
billlineskeys: {
|
||||||
|
...billlineskeys,
|
||||||
|
[record.id]: {
|
||||||
|
...billlineskeys[billlineskeys],
|
||||||
|
actual_cost: !!billlineskeys[billlineskeys].actual_cost
|
||||||
|
? billlineskeys[billlineskeys].actual_cost
|
||||||
|
: Math.round(
|
||||||
|
(parseFloat(e.target.value) * (1 - discount) +
|
||||||
|
Number.EPSILON) *
|
||||||
|
100
|
||||||
|
) / 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("billlines.fields.actual_cost")}
|
||||||
|
name={["billlineskeys", record.id, "actual_cost"]}
|
||||||
|
>
|
||||||
|
<CurrencyInput min={0} disabled={disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item shouldUpdate>
|
||||||
|
{() => {
|
||||||
|
const line = value;
|
||||||
|
if (!!!line) return null;
|
||||||
|
const lineDiscount = (
|
||||||
|
1 -
|
||||||
|
Math.round((line.actual_cost / line.actual_price) * 100) / 100
|
||||||
|
).toPrecision(2);
|
||||||
|
|
||||||
|
if (lineDiscount - discount === 0) return <div />;
|
||||||
|
return <WarningOutlined style={{ color: "red" }} />;
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("billlines.fields.cost_center")}
|
||||||
|
name={["billlineskeys", record.id, "cost_center"]}
|
||||||
|
>
|
||||||
|
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
|
||||||
|
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||||
|
? CiecaSelect(true, false)
|
||||||
|
: responsibilityCenters.costs.map((item) => (
|
||||||
|
<Select.Option key={item.name}>{item.name}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("billlines.fields.location")}
|
||||||
|
name={["billlineskeys", record.id, "location"]}
|
||||||
|
>
|
||||||
|
<Select disabled={disabled}>
|
||||||
|
{bodyshop.md_parts_locations.map((loc, idx) => (
|
||||||
|
<Select.Option key={idx} value={loc}>
|
||||||
|
{loc}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("billlines.fields.deductedfromlbr")}
|
||||||
|
name={["billlineskeys", record.id, "deductedfromlbr"]}
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch disabled={disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item shouldUpdate style={{ display: "inline-block" }}>
|
||||||
|
{() => {
|
||||||
|
if (
|
||||||
|
form.getFieldsValue("billlineskeys").billlineskeys[record.id]
|
||||||
|
.deductedfromlbr
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={t("joblines.fields.mod_lbr_ty")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
name={[
|
||||||
|
"billlineskeys",
|
||||||
|
record.id,
|
||||||
|
"lbr_adjustment",
|
||||||
|
"mod_lbr_ty",
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select allowClear>
|
||||||
|
<Select.Option value="LAA">
|
||||||
|
{t("joblines.fields.lbr_types.LAA")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LAB">
|
||||||
|
{t("joblines.fields.lbr_types.LAB")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LAD">
|
||||||
|
{t("joblines.fields.lbr_types.LAD")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LAE">
|
||||||
|
{t("joblines.fields.lbr_types.LAE")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LAF">
|
||||||
|
{t("joblines.fields.lbr_types.LAF")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LAG">
|
||||||
|
{t("joblines.fields.lbr_types.LAG")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LAM">
|
||||||
|
{t("joblines.fields.lbr_types.LAM")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LAR">
|
||||||
|
{t("joblines.fields.lbr_types.LAR")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LAS">
|
||||||
|
{t("joblines.fields.lbr_types.LAS")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LAU">
|
||||||
|
{t("joblines.fields.lbr_types.LAU")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LA1">
|
||||||
|
{t("joblines.fields.lbr_types.LA1")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LA2">
|
||||||
|
{t("joblines.fields.lbr_types.LA2")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LA3">
|
||||||
|
{t("joblines.fields.lbr_types.LA3")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="LA4">
|
||||||
|
{t("joblines.fields.lbr_types.LA4")}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.labels.adjustmentrate")}
|
||||||
|
name={["billlineskeys", record.id, "lbr_adjustment", "rate"]}
|
||||||
|
initialValue={bodyshop.default_adjustment_rate}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber precision={2} min={0.01} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return <></>;
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={t("billlines.fields.federal_tax_applicable")}
|
||||||
|
name={["billlineskeys", record.id, "applicable_taxes", "federal"]}
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch disabled={disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("billlines.fields.state_tax_applicable")}
|
||||||
|
name={["billlineskeys", record.id, "applicable_taxes", "state"]}
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch disabled={disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("billlines.fields.local_tax_applicable")}
|
||||||
|
name={["billlineskeys", record.id, "applicable_taxes", "local"]}
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch disabled={disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const values = form.getFieldsValue("billlineskeys");
|
||||||
|
|
||||||
|
form.setFieldsValue({
|
||||||
|
...values,
|
||||||
|
billlineskeys: {
|
||||||
|
...(values.billlineskeys || {}),
|
||||||
|
[record.id]: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MinusCircleFilled />
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -28,6 +28,8 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||||
import BillFormLines from "./bill-form.lines.component";
|
import BillFormLines from "./bill-form.lines.component";
|
||||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
||||||
|
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -49,7 +51,11 @@ export function BillFormComponent({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const [discount, setDiscount] = useState(0);
|
const [discount, setDiscount] = useState(0);
|
||||||
|
const { Extended_Bill_Posting } = useTreatments(
|
||||||
|
["Extended_Bill_Posting"],
|
||||||
|
{},
|
||||||
|
bodyshop.imexshopid
|
||||||
|
);
|
||||||
const handleVendorSelect = (props, opt) => {
|
const handleVendorSelect = (props, opt) => {
|
||||||
setDiscount(opt.discount);
|
setDiscount(opt.discount);
|
||||||
};
|
};
|
||||||
@@ -357,13 +363,24 @@ export function BillFormComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Divider orientation="left">{t("bills.labels.bill_lines")}</Divider>
|
<Divider orientation="left">{t("bills.labels.bill_lines")}</Divider>
|
||||||
<BillFormLines
|
|
||||||
lineData={lineData}
|
{Extended_Bill_Posting.treatment === "on" ? (
|
||||||
discount={discount}
|
<BillFormLinesExtended
|
||||||
form={form}
|
lineData={lineData}
|
||||||
responsibilityCenters={responsibilityCenters}
|
discount={discount}
|
||||||
disabled={disabled}
|
form={form}
|
||||||
/>
|
responsibilityCenters={responsibilityCenters}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<BillFormLines
|
||||||
|
lineData={lineData}
|
||||||
|
discount={discount}
|
||||||
|
form={form}
|
||||||
|
responsibilityCenters={responsibilityCenters}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="upload"
|
name="upload"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
Table
|
Table,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { setModalContext } from "../../redux/modals/modals.actions";
|
|||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import moment from "moment";
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
@@ -86,7 +87,9 @@ export function ContractsList({
|
|||||||
} ${record.courtesycar.make} ${record.courtesycar.model}${
|
} ${record.courtesycar.make} ${record.courtesycar.model}${
|
||||||
record.courtesycar.plate ? ` (${record.courtesycar.plate})` : ""
|
record.courtesycar.plate ? ` (${record.courtesycar.plate})` : ""
|
||||||
}${
|
}${
|
||||||
record.courtesycar.fleetnumber ? ` (${record.courtesycar.fleetnumber})` : ""
|
record.courtesycar.fleetnumber
|
||||||
|
? ` (${record.courtesycar.fleetnumber})`
|
||||||
|
: ""
|
||||||
}`}</Link>
|
}`}</Link>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -133,6 +136,19 @@ export function ContractsList({
|
|||||||
<DateTimeFormatter>{record.actualreturn}</DateTimeFormatter>
|
<DateTimeFormatter>{record.actualreturn}</DateTimeFormatter>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("contracts.fields.length"),
|
||||||
|
dataIndex: "length",
|
||||||
|
key: "length",
|
||||||
|
|
||||||
|
render: (text, record) =>
|
||||||
|
(record.actualreturn &&
|
||||||
|
record.start &&
|
||||||
|
`${moment(record.actualreturn)
|
||||||
|
.diff(moment(record.start), "days", true)
|
||||||
|
.toFixed(1)} days`) ||
|
||||||
|
"",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
|||||||
initialValues={{
|
initialValues={{
|
||||||
story: t("jobs.labels.dms.defaultstory", {
|
story: t("jobs.labels.dms.defaultstory", {
|
||||||
ro_number: job.ro_number,
|
ro_number: job.ro_number,
|
||||||
area_of_damage: job.area_of_damage && job.area_of_damage.impact1,
|
area_of_damage:
|
||||||
|
(job.area_of_damage && job.area_of_damage.impact1) || "UNKNOWN",
|
||||||
}).substr(0, 239),
|
}).substr(0, 239),
|
||||||
inservicedate: moment("2019-01-01"),
|
inservicedate: moment("2019-01-01"),
|
||||||
}}
|
}}
|
||||||
@@ -183,6 +184,22 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Space size="large" wrap align="center">
|
||||||
|
<Statistic
|
||||||
|
title={t("jobs.fields.ded_amt")}
|
||||||
|
value={Dinero(
|
||||||
|
job.job_totals.totals.custPayable.deductible
|
||||||
|
).toFormat()}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("jobs.labels.total_cust_payable")}
|
||||||
|
value={Dinero(job.job_totals.totals.custPayable.total).toFormat()}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("jobs.labels.net_repairs")}
|
||||||
|
value={Dinero(job.job_totals.totals.net_repairs).toFormat()}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
<Form.List name={["payers"]}>
|
<Form.List name={["payers"]}>
|
||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove }) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ export const handleUpload = (ev, context) => {
|
|||||||
|
|
||||||
const fileName = ev.file.name || ev.filename;
|
const fileName = ev.file.name || ev.filename;
|
||||||
|
|
||||||
let key = `${bodyshop.id}/${jobId}/${fileName.replace(
|
let key = `${bodyshop.id}/${jobId}/${replaceAccents(fileName).replace(
|
||||||
/\.[^/.]+$/,
|
/[^A-Z0-9]+/gi,
|
||||||
""
|
"_"
|
||||||
)}-${new Date().getTime()}`;
|
)}-${new Date().getTime()}`;
|
||||||
let extension = fileName.split(".").pop();
|
let extension = fileName.split(".").pop();
|
||||||
uploadToCloudinary(
|
uploadToCloudinary(
|
||||||
@@ -187,3 +187,33 @@ export function DetermineFileType(filetype) {
|
|||||||
|
|
||||||
return "auto";
|
return "auto";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function replaceAccents(str) {
|
||||||
|
// Verifies if the String has accents and replace them
|
||||||
|
if (str.search(/[\xC0-\xFF]/g) > -1) {
|
||||||
|
str = str
|
||||||
|
.replace(/[\xC0-\xC5]/g, "A")
|
||||||
|
.replace(/[\xC6]/g, "AE")
|
||||||
|
.replace(/[\xC7]/g, "C")
|
||||||
|
.replace(/[\xC8-\xCB]/g, "E")
|
||||||
|
.replace(/[\xCC-\xCF]/g, "I")
|
||||||
|
.replace(/[\xD0]/g, "D")
|
||||||
|
.replace(/[\xD1]/g, "N")
|
||||||
|
.replace(/[\xD2-\xD6\xD8]/g, "O")
|
||||||
|
.replace(/[\xD9-\xDC]/g, "U")
|
||||||
|
.replace(/[\xDD]/g, "Y")
|
||||||
|
.replace(/[\xDE]/g, "P")
|
||||||
|
.replace(/[\xE0-\xE5]/g, "a")
|
||||||
|
.replace(/[\xE6]/g, "ae")
|
||||||
|
.replace(/[\xE7]/g, "c")
|
||||||
|
.replace(/[\xE8-\xEB]/g, "e")
|
||||||
|
.replace(/[\xEC-\xEF]/g, "i")
|
||||||
|
.replace(/[\xF1]/g, "n")
|
||||||
|
.replace(/[\xF2-\xF6\xF8]/g, "o")
|
||||||
|
.replace(/[\xF9-\xFC]/g, "u")
|
||||||
|
.replace(/[\xFE]/g, "p")
|
||||||
|
.replace(/[\xFD\xFF]/g, "y");
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|||||||
@@ -79,9 +79,19 @@ export function JobChecklistForm({
|
|||||||
...(type === "intake" && {
|
...(type === "intake" && {
|
||||||
scheduled_completion: values.scheduled_completion,
|
scheduled_completion: values.scheduled_completion,
|
||||||
}),
|
}),
|
||||||
|
...(type === "intake" &&
|
||||||
|
bodyshop.intakechecklist &&
|
||||||
|
bodyshop.intakechecklist.next_contact_hours &&
|
||||||
|
bodyshop.intakechecklist.next_contact_hours > 0 && {
|
||||||
|
date_next_contact: moment().add(
|
||||||
|
bodyshop.intakechecklist.next_contact_hours,
|
||||||
|
"hours"
|
||||||
|
),
|
||||||
|
}),
|
||||||
...(type === "deliver" && {
|
...(type === "deliver" && {
|
||||||
actual_completion: values.actual_completion,
|
actual_completion: values.actual_completion,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
[(type === "intake" && "intakechecklist") ||
|
[(type === "intake" && "intakechecklist") ||
|
||||||
(type === "deliver" && "deliverchecklist")]: {
|
(type === "deliver" && "deliverchecklist")]: {
|
||||||
...values,
|
...values,
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { Button, Card, Form, InputNumber, Popover } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(PrintCenterJobsLabels);
|
||||||
|
|
||||||
|
export function PrintCenterJobsLabels({ bodyshop, jobId }) {
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
form.submit();
|
||||||
|
setIsModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setIsModalVisible(false);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
const handleFinish = async (values) => {
|
||||||
|
const { sendtype, ...restVals } = values;
|
||||||
|
setLoading(true);
|
||||||
|
await GenerateDocument(
|
||||||
|
{
|
||||||
|
name: TemplateList("job_special").folder_label_multiple.key,
|
||||||
|
variables: { id: jobId },
|
||||||
|
context: restVals,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
"p"
|
||||||
|
);
|
||||||
|
setLoading(false);
|
||||||
|
setIsModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<Card>
|
||||||
|
<Form
|
||||||
|
onFinish={handleFinish}
|
||||||
|
autoComplete={"off"}
|
||||||
|
layout="vertical"
|
||||||
|
form={form}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
label={t("printcenter.jobs.labels.position")}
|
||||||
|
name="position"
|
||||||
|
>
|
||||||
|
<InputNumber min={0} precision={0} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("printcenter.jobs.labels.count")} name="count">
|
||||||
|
<InputNumber min={0} precision={0} />
|
||||||
|
</Form.Item>
|
||||||
|
<Button type="primary" loading={loading} onClick={handleOk}>
|
||||||
|
{t("general.actions.print")}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Popover content={content} visible={isModalVisible}>
|
||||||
|
<Button onClick={() => setIsModalVisible(true)}>
|
||||||
|
{t("printcenter.jobs.labels.labels")}
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import Jobd3RdPartyModal from "../job-3rd-party-modal/job-3rd-party-modal.component";
|
import Jobd3RdPartyModal from "../job-3rd-party-modal/job-3rd-party-modal.component";
|
||||||
import PrintCenterItem from "../print-center-item/print-center-item.component";
|
import PrintCenterItem from "../print-center-item/print-center-item.component";
|
||||||
|
import PrintCenterJobsLabels from "../print-center-jobs-labels/print-center-jobs-labels.component";
|
||||||
import PrintCenterSpeedPrint from "../print-center-speed-print/print-center-speed-print.component";
|
import PrintCenterSpeedPrint from "../print-center-speed-print/print-center-speed-print.component";
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
printCenterModal: selectPrintCenter,
|
printCenterModal: selectPrintCenter,
|
||||||
@@ -52,6 +53,7 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
|
|||||||
<Card
|
<Card
|
||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
<PrintCenterJobsLabels jobId={jobId} />
|
||||||
<Jobd3RdPartyModal jobId={jobId} />
|
<Jobd3RdPartyModal jobId={jobId} />
|
||||||
<Input.Search
|
<Input.Search
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export function ProductionColumnsComponent({
|
|||||||
setColumns([
|
setColumns([
|
||||||
...columns,
|
...columns,
|
||||||
...dataSource({
|
...dataSource({
|
||||||
|
bodyshop,
|
||||||
technician,
|
technician,
|
||||||
state: tableState,
|
state: tableState,
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import ProductionListColumnNote from "./production-list-columns.productionnote.c
|
|||||||
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
||||||
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
||||||
|
|
||||||
const r = ({ technician, state, activeStatuses }) => {
|
const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: i18n.t("jobs.actions.viewdetail"),
|
title: i18n.t("jobs.actions.viewdetail"),
|
||||||
@@ -353,6 +353,14 @@ const r = ({ technician, state, activeStatuses }) => {
|
|||||||
title: i18n.t("jobs.fields.employee_body"),
|
title: i18n.t("jobs.fields.employee_body"),
|
||||||
dataIndex: "employee_body",
|
dataIndex: "employee_body",
|
||||||
key: "employee_body",
|
key: "employee_body",
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "employee_body" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
bodyshop.employees.find((e) => e.id === a.employee_body)?.first_name,
|
||||||
|
bodyshop.employees.find((e) => e.id === b.employee_body)?.first_name
|
||||||
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment
|
<ProductionListEmployeeAssignment
|
||||||
record={record}
|
record={record}
|
||||||
@@ -364,6 +372,14 @@ const r = ({ technician, state, activeStatuses }) => {
|
|||||||
title: i18n.t("jobs.fields.employee_prep"),
|
title: i18n.t("jobs.fields.employee_prep"),
|
||||||
dataIndex: "employee_prep",
|
dataIndex: "employee_prep",
|
||||||
key: "employee_prep",
|
key: "employee_prep",
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "employee_prep" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
bodyshop.employees.find((e) => e.id === a.employee_prep)?.first_name,
|
||||||
|
bodyshop.employees.find((e) => e.id === b.employee_prep)?.first_name
|
||||||
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment
|
<ProductionListEmployeeAssignment
|
||||||
record={record}
|
record={record}
|
||||||
@@ -375,6 +391,13 @@ const r = ({ technician, state, activeStatuses }) => {
|
|||||||
title: i18n.t("jobs.fields.employee_csr"),
|
title: i18n.t("jobs.fields.employee_csr"),
|
||||||
dataIndex: "employee_csr",
|
dataIndex: "employee_csr",
|
||||||
key: "employee_csr",
|
key: "employee_csr",
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
bodyshop.employees.find((e) => e.id === a.employee_csr)?.first_name,
|
||||||
|
bodyshop.employees.find((e) => e.id === b.employee_csr)?.first_name
|
||||||
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment record={record} type="employee_csr" />
|
<ProductionListEmployeeAssignment record={record} type="employee_csr" />
|
||||||
),
|
),
|
||||||
@@ -383,6 +406,16 @@ const r = ({ technician, state, activeStatuses }) => {
|
|||||||
title: i18n.t("jobs.fields.employee_refinish"),
|
title: i18n.t("jobs.fields.employee_refinish"),
|
||||||
dataIndex: "employee_refinish",
|
dataIndex: "employee_refinish",
|
||||||
key: "employee_refinish",
|
key: "employee_refinish",
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "employee_refinish" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
bodyshop.employees.find((e) => e.id === a.employee_refinish)
|
||||||
|
?.first_name,
|
||||||
|
bodyshop.employees.find((e) => e.id === b.employee_refinish)
|
||||||
|
?.first_name
|
||||||
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment
|
<ProductionListEmployeeAssignment
|
||||||
record={record}
|
record={record}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export function ProductionListTable({
|
|||||||
.columns.columnKeys.map((k) => {
|
.columns.columnKeys.map((k) => {
|
||||||
return {
|
return {
|
||||||
...ProductionListColumns({
|
...ProductionListColumns({
|
||||||
|
bodyshop,
|
||||||
technician,
|
technician,
|
||||||
state,
|
state,
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export function ProductionListTable({
|
|||||||
matchingColumnConfig.columns.columnKeys.map((k) => {
|
matchingColumnConfig.columns.columnKeys.map((k) => {
|
||||||
return {
|
return {
|
||||||
...ProductionListColumns({
|
...ProductionListColumns({
|
||||||
|
bodyshop,
|
||||||
technician,
|
technician,
|
||||||
state,
|
state,
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
|||||||
@@ -10,13 +10,17 @@ import Event from "../job-at-change/schedule-event.container";
|
|||||||
import HeaderComponent from "./schedule-calendar-header.component";
|
import HeaderComponent from "./schedule-calendar-header.component";
|
||||||
import "./schedule-calendar.styles.scss";
|
import "./schedule-calendar.styles.scss";
|
||||||
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
|
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
|
||||||
|
import { selectProblemJobs } from "../../redux/application/application.selectors";
|
||||||
|
import { Alert } from "antd";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
|
problemJobs: selectProblemJobs,
|
||||||
});
|
});
|
||||||
const localizer = momentLocalizer(moment);
|
const localizer = momentLocalizer(moment);
|
||||||
export function ScheduleCalendarWrapperComponent({
|
export function ScheduleCalendarWrapperComponent({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
|
problemJobs,
|
||||||
data,
|
data,
|
||||||
refetch,
|
refetch,
|
||||||
defaultView,
|
defaultView,
|
||||||
@@ -48,6 +52,15 @@ export function ScheduleCalendarWrapperComponent({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<JobDetailCards />
|
<JobDetailCards />
|
||||||
|
{problemJobs &&
|
||||||
|
problemJobs.map((problem) => (
|
||||||
|
<Alert
|
||||||
|
key={problem.id}
|
||||||
|
type="error"
|
||||||
|
message={`${problem.ro_number} has a data consistency issue. It has been exlcuded for scheduling purposes. CODE: ${problem.code}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
<Calendar
|
<Calendar
|
||||||
events={data}
|
events={data}
|
||||||
defaultView={search.view || defaultView || "week"}
|
defaultView={search.view || defaultView || "week"}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/sched
|
|||||||
import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
|
import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
|
||||||
//import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
//import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
||||||
import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component";
|
import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component";
|
||||||
|
import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component";
|
||||||
|
|
||||||
export default function ScheduleCalendarComponent({ data, refetch }) {
|
export default function ScheduleCalendarComponent({ data, refetch }) {
|
||||||
return (
|
return (
|
||||||
@@ -15,6 +16,7 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
<ScheduleVerifyIntegrity />
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
refetch();
|
refetch();
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { useApolloClient } from "@apollo/client";
|
||||||
|
import { Button } from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
|
||||||
|
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
currentUser: selectCurrentUser,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ScheduleVerifyIntegrity);
|
||||||
|
|
||||||
|
export function ScheduleVerifyIntegrity({ currentUser }) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const client = useApolloClient();
|
||||||
|
const handleVerify = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const {
|
||||||
|
data: { arrJobs, compJobs, prodJobs },
|
||||||
|
} = await client.query({
|
||||||
|
query: QUERY_SCHEDULE_LOAD_DATA,
|
||||||
|
variables: { start: moment(), end: moment().add(180, "days") },
|
||||||
|
});
|
||||||
|
|
||||||
|
//check that the leaving jobs are either in the arriving list, or in production.
|
||||||
|
const issues = [];
|
||||||
|
|
||||||
|
compJobs.forEach((j) => {
|
||||||
|
const inProdJobs = prodJobs.find((p) => p.id === j.id);
|
||||||
|
const inArrJobs = arrJobs.find((p) => p.id === j.id);
|
||||||
|
|
||||||
|
if (!(inProdJobs || inArrJobs)) {
|
||||||
|
// NOT FOUND!
|
||||||
|
issues.push(j);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
"The following completing jobs are not in production, or are arriving within the next 180 days. ",
|
||||||
|
issues
|
||||||
|
);
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentUser.email === "patrick@imex.prod")
|
||||||
|
return (
|
||||||
|
<Button loading={loading} onClick={handleVerify}>
|
||||||
|
Developer Use Only - Verify Schedule Integrity
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
@@ -43,19 +43,28 @@ export function ScoreboardDisplayComponent({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//Update the locals.
|
//Update the locals.
|
||||||
async function setMomentSettings() {
|
async function setMomentSettings() {
|
||||||
const {
|
let appointments;
|
||||||
data: { appointments },
|
|
||||||
} = await client.query({
|
if (!bodyshop.scoreboard_target.ignoreblockeddays) {
|
||||||
query: GET_BLOCKED_DAYS,
|
const { data } = await client.query({
|
||||||
variables: {
|
query: GET_BLOCKED_DAYS,
|
||||||
start: moment().startOf("month"),
|
variables: {
|
||||||
end: moment().endOf("month"),
|
start: moment().startOf("month"),
|
||||||
},
|
end: moment().endOf("month"),
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
appointments = data.appointments;
|
||||||
|
}
|
||||||
|
|
||||||
moment.updateLocale("ca", {
|
moment.updateLocale("ca", {
|
||||||
workingWeekdays: translateSettingsToWorkingDays(bodyshop.workingdays),
|
workingWeekdays: translateSettingsToWorkingDays(bodyshop.workingdays),
|
||||||
holidays: appointments.map((h) => moment(h.start).format("MM-DD-YYYY")),
|
...(appointments
|
||||||
|
? {
|
||||||
|
holidays: appointments.map((h) =>
|
||||||
|
moment(h.start).format("MM-DD-YYYY")
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
holidayFormat: "MM-DD-YYYY",
|
holidayFormat: "MM-DD-YYYY",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -354,6 +354,13 @@ export default function ShopInfoGeneral({ form }) {
|
|||||||
>
|
>
|
||||||
<InputNumber min={0} max={12} precision={0} />
|
<InputNumber min={0} max={12} precision={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.ignoreblockeddays")}
|
||||||
|
name={["scoreboard_target", "ignoreblockeddays"]}
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.prodtargethrs")}
|
label={t("bodyshop.fields.prodtargethrs")}
|
||||||
name={["prodtargethrs"]}
|
name={["prodtargethrs"]}
|
||||||
@@ -472,7 +479,6 @@ export default function ShopInfoGeneral({ form }) {
|
|||||||
label={t("bodyshop.fields.md_email_cc", { template: "parts_order" })}
|
label={t("bodyshop.fields.md_email_cc", { template: "parts_order" })}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
|
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
type: "array",
|
type: "array",
|
||||||
},
|
},
|
||||||
@@ -492,7 +498,6 @@ export default function ShopInfoGeneral({ form }) {
|
|||||||
label={t("bodyshop.fields.md_ded_notes")}
|
label={t("bodyshop.fields.md_ded_notes")}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
|
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
type: "array",
|
type: "array",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -180,6 +180,12 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={["intakechecklist", "next_contact_hours"]}
|
||||||
|
label={t("bodyshop.fields.intake.next_contact_hours")}
|
||||||
|
>
|
||||||
|
<InputNumber min={0} precision={0} />
|
||||||
|
</Form.Item>
|
||||||
</SelectorDiv>
|
</SelectorDiv>
|
||||||
|
|
||||||
<LayoutFormRow header={t("bodyshop.labels.deliverchecklist")}>
|
<LayoutFormRow header={t("bodyshop.labels.deliverchecklist")}>
|
||||||
|
|||||||
@@ -234,11 +234,17 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
compJobs: jobs(
|
compJobs: jobs(
|
||||||
where: { scheduled_completion: { _gte: $start, _lte: $end } }
|
where: {
|
||||||
|
_or: [
|
||||||
|
{ scheduled_completion: { _gte: $start, _lte: $end } }
|
||||||
|
{ actual_completion: { _gte: $start, _lte: $end } }
|
||||||
|
]
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
scheduled_completion
|
scheduled_completion
|
||||||
|
actual_completion
|
||||||
labhrs: joblines_aggregate(
|
labhrs: joblines_aggregate(
|
||||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -2032,6 +2032,8 @@ export const QUERY_JOB_EXPORT_DMS = gql`
|
|||||||
po_number
|
po_number
|
||||||
clm_no
|
clm_no
|
||||||
job_totals
|
job_totals
|
||||||
|
ded_amt
|
||||||
|
ded_status
|
||||||
ownr_fn
|
ownr_fn
|
||||||
ownr_ln
|
ownr_ln
|
||||||
ownr_co_nm
|
ownr_co_nm
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ export const Banner00DataSource = {
|
|||||||
button: {
|
button: {
|
||||||
className: "banner0-button",
|
className: "banner0-button",
|
||||||
children: i18n.t("landing.hero.button"),
|
children: i18n.t("landing.hero.button"),
|
||||||
|
href: "https://imexsystems.ca",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export const Content40DataSource = {
|
export const Content40DataSource = {
|
||||||
|
|||||||
@@ -58,13 +58,6 @@ export default class Home extends React.Component {
|
|||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
/* 如果不是 dva 2.0 请删除 end */
|
/* 如果不是 dva 2.0 请删除 end */
|
||||||
|
|
||||||
window.$crisp.push(["set", "session:segments", [["lead"]]]);
|
|
||||||
window.$crisp.push([
|
|
||||||
"set",
|
|
||||||
"session:event",
|
|
||||||
[[["landing-page", {}, "green"]]],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import queryString from "query-string";
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useHistory, useLocation, Link } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import SocketIO from "socket.io-client";
|
import SocketIO from "socket.io-client";
|
||||||
import AlertComponent from "../../components/alert/alert.component";
|
import AlertComponent from "../../components/alert/alert.component";
|
||||||
@@ -138,13 +138,20 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
|||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col md={24} lg={10}>
|
<Col md={24} lg={10}>
|
||||||
<DmsAllocationsSummary
|
<DmsAllocationsSummary
|
||||||
title={`${data && data.jobs_by_pk && data.jobs_by_pk.ro_number} | ${
|
title={
|
||||||
data.jobs_by_pk.ownr_fn || ""
|
<span>
|
||||||
} ${data.jobs_by_pk.ownr_ln || ""} ${
|
<Link to={`/manage/jobs/${data && data.jobs_by_pk.id}`}>{`${
|
||||||
data.jobs_by_pk.ownr_co_nm || ""
|
data && data.jobs_by_pk && data.jobs_by_pk.ro_number
|
||||||
} | ${data.jobs_by_pk.v_model_yr || ""} ${
|
}`}</Link>
|
||||||
data.jobs_by_pk.v_make_desc || ""
|
{` | ${data.jobs_by_pk.ownr_fn || ""} ${
|
||||||
} ${data.jobs_by_pk.v_model_desc || ""}`}
|
data.jobs_by_pk.ownr_ln || ""
|
||||||
|
} ${data.jobs_by_pk.ownr_co_nm || ""} | ${
|
||||||
|
data.jobs_by_pk.v_model_yr || ""
|
||||||
|
} ${data.jobs_by_pk.v_make_desc || ""} ${
|
||||||
|
data.jobs_by_pk.v_model_desc || ""
|
||||||
|
}`}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
socket={socket}
|
socket={socket}
|
||||||
jobId={jobId}
|
jobId={jobId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -58,3 +58,7 @@ export const insertAuditTrail = ({ jobid, billid, operation }) => ({
|
|||||||
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
|
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
|
||||||
payload: { jobid, billid, operation },
|
payload: { jobid, billid, operation },
|
||||||
});
|
});
|
||||||
|
export const setProblemJobs = (problemJobs) => ({
|
||||||
|
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
|
||||||
|
payload: problemJobs,
|
||||||
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const INITIAL_STATE = {
|
|||||||
breadcrumbs: [],
|
breadcrumbs: [],
|
||||||
recentItems: [],
|
recentItems: [],
|
||||||
selectedHeader: "home",
|
selectedHeader: "home",
|
||||||
|
problemJobs: [],
|
||||||
scheduleLoad: {
|
scheduleLoad: {
|
||||||
load: {},
|
load: {},
|
||||||
calculating: false,
|
calculating: false,
|
||||||
@@ -40,6 +41,7 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
|||||||
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD:
|
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
problemJobs: [],
|
||||||
scheduleLoad: { ...state.scheduleLoad, calculating: true, error: null },
|
scheduleLoad: { ...state.scheduleLoad, calculating: true, error: null },
|
||||||
};
|
};
|
||||||
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS:
|
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS:
|
||||||
@@ -76,7 +78,9 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
|||||||
|
|
||||||
case ApplicationActionTypes.SET_PARTNER_VERSION:
|
case ApplicationActionTypes.SET_PARTNER_VERSION:
|
||||||
return { ...state, partnerVersion: action.payload };
|
return { ...state, partnerVersion: action.payload };
|
||||||
|
case ApplicationActionTypes.SET_PROBLEM_JOBS: {
|
||||||
|
return { ...state, problemJobs: action.payload };
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils";
|
|||||||
import {
|
import {
|
||||||
scheduleLoadFailure,
|
scheduleLoadFailure,
|
||||||
scheduleLoadSuccess,
|
scheduleLoadSuccess,
|
||||||
|
setProblemJobs,
|
||||||
} from "./application.actions";
|
} from "./application.actions";
|
||||||
import ApplicationActionTypes from "./application.types";
|
import ApplicationActionTypes from "./application.types";
|
||||||
|
|
||||||
@@ -53,6 +54,8 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
arrJobs.forEach((item) => {
|
arrJobs.forEach((item) => {
|
||||||
|
if (!item.scheduled_in)
|
||||||
|
console.log("JOB HAS NO SCHEDULED IN DATE.", item);
|
||||||
const itemDate = moment(item.scheduled_in).format("yyyy-MM-DD");
|
const itemDate = moment(item.scheduled_in).format("yyyy-MM-DD");
|
||||||
if (!!load[itemDate]) {
|
if (!!load[itemDate]) {
|
||||||
load[itemDate].hoursIn =
|
load[itemDate].hoursIn =
|
||||||
@@ -71,7 +74,26 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let problemJobs = [];
|
||||||
compJobs.forEach((item) => {
|
compJobs.forEach((item) => {
|
||||||
|
if (!item.scheduled_completion)
|
||||||
|
console.log("JOB HAS NO SCHEDULED COMPLETION DATE.", item);
|
||||||
|
|
||||||
|
const inProdJobs = prodJobs.find((p) => p.id === item.id);
|
||||||
|
const inArrJobs = arrJobs.find((p) => p.id === item.id);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(inProdJobs || inArrJobs) &&
|
||||||
|
!moment(item.scheduled_completion).isSame(moment(), "day")
|
||||||
|
) {
|
||||||
|
// NOT FOUND!
|
||||||
|
problemJobs.push({
|
||||||
|
...item,
|
||||||
|
code: "Job is scheduled for completion, but it is not marked in production nor is it an arriving job in this period. Check the scheduled in and completion dates",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const itemDate = moment(item.scheduled_completion).format("yyyy-MM-DD");
|
const itemDate = moment(item.scheduled_completion).format("yyyy-MM-DD");
|
||||||
if (!!load[itemDate]) {
|
if (!!load[itemDate]) {
|
||||||
load[itemDate].hoursOut =
|
load[itemDate].hoursOut =
|
||||||
@@ -116,7 +138,7 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
yield put(setProblemJobs(problemJobs));
|
||||||
yield put(scheduleLoadSuccess(load));
|
yield put(scheduleLoadSuccess(load));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put(scheduleLoadFailure(error));
|
yield put(scheduleLoadFailure(error));
|
||||||
|
|||||||
@@ -44,3 +44,7 @@ export const selectOnline = createSelector(
|
|||||||
[selectApplication],
|
[selectApplication],
|
||||||
(application) => application.online
|
(application) => application.online
|
||||||
);
|
);
|
||||||
|
export const selectProblemJobs = createSelector(
|
||||||
|
[selectApplication],
|
||||||
|
(application) => application.problemJobs
|
||||||
|
);
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ const ApplicationActionTypes = {
|
|||||||
SET_PARTNER_VERSION: "SET_PARTNER_VERSION",
|
SET_PARTNER_VERSION: "SET_PARTNER_VERSION",
|
||||||
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
|
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
|
||||||
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
|
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
|
||||||
|
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
|
||||||
};
|
};
|
||||||
export default ApplicationActionTypes;
|
export default ApplicationActionTypes;
|
||||||
|
|||||||
@@ -48,3 +48,7 @@ initMessageListener(store);
|
|||||||
export const persistor = persistStore(store);
|
export const persistor = persistStore(store);
|
||||||
const e = { store, persistStore };
|
const e = { store, persistStore };
|
||||||
export default e;
|
export default e;
|
||||||
|
|
||||||
|
if (window.Cypress) {
|
||||||
|
window.store = store;
|
||||||
|
}
|
||||||
|
|||||||
@@ -253,9 +253,11 @@
|
|||||||
"enforce_class": "Enforce Class on Conversion?",
|
"enforce_class": "Enforce Class on Conversion?",
|
||||||
"enforce_referral": "Enforce Referrals",
|
"enforce_referral": "Enforce Referrals",
|
||||||
"federal_tax_id": "Federal Tax ID (GST/HST)",
|
"federal_tax_id": "Federal Tax ID (GST/HST)",
|
||||||
|
"ignoreblockeddays": "Scoreboard - Ignore Blocked Days",
|
||||||
"inhousevendorid": "In House Vendor ID",
|
"inhousevendorid": "In House Vendor ID",
|
||||||
"insurance_vendor_id": "Insurance Vendor ID",
|
"insurance_vendor_id": "Insurance Vendor ID",
|
||||||
"intake": {
|
"intake": {
|
||||||
|
"next_contact_hours": "Automatic Next Contact Date - Hours from Intake",
|
||||||
"templates": "Intake Templates"
|
"templates": "Intake Templates"
|
||||||
},
|
},
|
||||||
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate",
|
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate",
|
||||||
@@ -614,6 +616,7 @@
|
|||||||
"fuelout": "Fuel Out",
|
"fuelout": "Fuel Out",
|
||||||
"kmend": "Mileage End",
|
"kmend": "Mileage End",
|
||||||
"kmstart": "Mileage Start",
|
"kmstart": "Mileage Start",
|
||||||
|
"length": "Length",
|
||||||
"localtax": "Local Taxes",
|
"localtax": "Local Taxes",
|
||||||
"refuelcharge": "Refuel Charge (per liter/gallon)",
|
"refuelcharge": "Refuel Charge (per liter/gallon)",
|
||||||
"scheduledreturn": "Scheduled Return",
|
"scheduledreturn": "Scheduled Return",
|
||||||
@@ -890,6 +893,7 @@
|
|||||||
"deselectall": "Deselect All",
|
"deselectall": "Deselect All",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
|
"print": "Print",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"reset": "Reset your changes.",
|
"reset": "Reset your changes.",
|
||||||
@@ -1628,7 +1632,7 @@
|
|||||||
"slogan": "A whole new kind of shop management system."
|
"slogan": "A whole new kind of shop management system."
|
||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
"button": "Coming Soon",
|
"button": "Learn More",
|
||||||
"title": "A whole new kind of shop management system."
|
"title": "A whole new kind of shop management system."
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -2033,6 +2037,11 @@
|
|||||||
"job_costing_ro": "Job Costing",
|
"job_costing_ro": "Job Costing",
|
||||||
"job_notes": "Job Notes",
|
"job_notes": "Job Notes",
|
||||||
"key_tag": "Key Tag",
|
"key_tag": "Key Tag",
|
||||||
|
"labels": {
|
||||||
|
"count": "Count",
|
||||||
|
"labels": "Labels",
|
||||||
|
"position": "Starting Position"
|
||||||
|
},
|
||||||
"mpi_animal_checklist": "MPI - Animal Checklist",
|
"mpi_animal_checklist": "MPI - Animal Checklist",
|
||||||
"mpi_eglass_auth": "MPI - eGlass Auth",
|
"mpi_eglass_auth": "MPI - eGlass Auth",
|
||||||
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet",
|
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet",
|
||||||
|
|||||||
@@ -253,9 +253,11 @@
|
|||||||
"enforce_class": "",
|
"enforce_class": "",
|
||||||
"enforce_referral": "",
|
"enforce_referral": "",
|
||||||
"federal_tax_id": "",
|
"federal_tax_id": "",
|
||||||
|
"ignoreblockeddays": "",
|
||||||
"inhousevendorid": "",
|
"inhousevendorid": "",
|
||||||
"insurance_vendor_id": "",
|
"insurance_vendor_id": "",
|
||||||
"intake": {
|
"intake": {
|
||||||
|
"next_contact_hours": "",
|
||||||
"templates": ""
|
"templates": ""
|
||||||
},
|
},
|
||||||
"invoice_federal_tax_rate": "",
|
"invoice_federal_tax_rate": "",
|
||||||
@@ -614,6 +616,7 @@
|
|||||||
"fuelout": "",
|
"fuelout": "",
|
||||||
"kmend": "",
|
"kmend": "",
|
||||||
"kmstart": "",
|
"kmstart": "",
|
||||||
|
"length": "",
|
||||||
"localtax": "",
|
"localtax": "",
|
||||||
"refuelcharge": "",
|
"refuelcharge": "",
|
||||||
"scheduledreturn": "",
|
"scheduledreturn": "",
|
||||||
@@ -890,6 +893,7 @@
|
|||||||
"deselectall": "",
|
"deselectall": "",
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"login": "",
|
"login": "",
|
||||||
|
"print": "",
|
||||||
"refresh": "",
|
"refresh": "",
|
||||||
"remove": "",
|
"remove": "",
|
||||||
"reset": " Restablecer a original.",
|
"reset": " Restablecer a original.",
|
||||||
@@ -2033,6 +2037,11 @@
|
|||||||
"job_costing_ro": "",
|
"job_costing_ro": "",
|
||||||
"job_notes": "",
|
"job_notes": "",
|
||||||
"key_tag": "",
|
"key_tag": "",
|
||||||
|
"labels": {
|
||||||
|
"count": "",
|
||||||
|
"labels": "",
|
||||||
|
"position": ""
|
||||||
|
},
|
||||||
"mpi_animal_checklist": "",
|
"mpi_animal_checklist": "",
|
||||||
"mpi_eglass_auth": "",
|
"mpi_eglass_auth": "",
|
||||||
"mpi_final_acct_sheet": "",
|
"mpi_final_acct_sheet": "",
|
||||||
|
|||||||
@@ -253,9 +253,11 @@
|
|||||||
"enforce_class": "",
|
"enforce_class": "",
|
||||||
"enforce_referral": "",
|
"enforce_referral": "",
|
||||||
"federal_tax_id": "",
|
"federal_tax_id": "",
|
||||||
|
"ignoreblockeddays": "",
|
||||||
"inhousevendorid": "",
|
"inhousevendorid": "",
|
||||||
"insurance_vendor_id": "",
|
"insurance_vendor_id": "",
|
||||||
"intake": {
|
"intake": {
|
||||||
|
"next_contact_hours": "",
|
||||||
"templates": ""
|
"templates": ""
|
||||||
},
|
},
|
||||||
"invoice_federal_tax_rate": "",
|
"invoice_federal_tax_rate": "",
|
||||||
@@ -614,6 +616,7 @@
|
|||||||
"fuelout": "",
|
"fuelout": "",
|
||||||
"kmend": "",
|
"kmend": "",
|
||||||
"kmstart": "",
|
"kmstart": "",
|
||||||
|
"length": "",
|
||||||
"localtax": "",
|
"localtax": "",
|
||||||
"refuelcharge": "",
|
"refuelcharge": "",
|
||||||
"scheduledreturn": "",
|
"scheduledreturn": "",
|
||||||
@@ -890,6 +893,7 @@
|
|||||||
"deselectall": "",
|
"deselectall": "",
|
||||||
"edit": "modifier",
|
"edit": "modifier",
|
||||||
"login": "",
|
"login": "",
|
||||||
|
"print": "",
|
||||||
"refresh": "",
|
"refresh": "",
|
||||||
"remove": "",
|
"remove": "",
|
||||||
"reset": " Rétablir l'original.",
|
"reset": " Rétablir l'original.",
|
||||||
@@ -2033,6 +2037,11 @@
|
|||||||
"job_costing_ro": "",
|
"job_costing_ro": "",
|
||||||
"job_notes": "",
|
"job_notes": "",
|
||||||
"key_tag": "",
|
"key_tag": "",
|
||||||
|
"labels": {
|
||||||
|
"count": "",
|
||||||
|
"labels": "",
|
||||||
|
"position": ""
|
||||||
|
},
|
||||||
"mpi_animal_checklist": "",
|
"mpi_animal_checklist": "",
|
||||||
"mpi_eglass_auth": "",
|
"mpi_eglass_auth": "",
|
||||||
"mpi_final_acct_sheet": "",
|
"mpi_final_acct_sheet": "",
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ export const CalculateLoad = (currentLoad, buckets, jobsIn, jobsOut) => {
|
|||||||
const bucketId = CheckJobBucket(buckets, job);
|
const bucketId = CheckJobBucket(buckets, job);
|
||||||
if (bucketId) {
|
if (bucketId) {
|
||||||
newLoad[bucketId].count = newLoad[bucketId].count - 1;
|
newLoad[bucketId].count = newLoad[bucketId].count - 1;
|
||||||
|
if (newLoad[bucketId].count < 0) {
|
||||||
|
console.log("***ERROR: NEGATIVE LOAD", bucketId, job);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
"[Util Out Job]Uh oh, this job doesn't fit in a bucket!",
|
"[Util Out Job]Uh oh, this job doesn't fit in a bucket!",
|
||||||
|
|||||||
@@ -464,6 +464,12 @@ export const TemplateList = (type, context) => {
|
|||||||
key: "special_thirdpartypayer",
|
key: "special_thirdpartypayer",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
|
folder_label_multiple: {
|
||||||
|
title: i18n.t("printcenter.jobs.folder_label_multiple"),
|
||||||
|
description: "Folder Label Multiple",
|
||||||
|
key: "folder_label_multiple",
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
csi_invitation_action: {
|
csi_invitation_action: {
|
||||||
title: i18n.t("printcenter.jobs.csi_invitation_action"),
|
title: i18n.t("printcenter.jobs.csi_invitation_action"),
|
||||||
description: "CSI invite",
|
description: "CSI invite",
|
||||||
|
|||||||
@@ -11842,10 +11842,10 @@ react-draggable@^4.0.0, react-draggable@^4.0.3:
|
|||||||
clsx "^1.1.1"
|
clsx "^1.1.1"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
react-error-overlay@^6.0.9:
|
react-error-overlay@6.0.9, react-error-overlay@^6.0.9:
|
||||||
version "6.0.10"
|
version "6.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
|
||||||
integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==
|
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
||||||
|
|
||||||
react-grid-gallery@^0.5.5:
|
react-grid-gallery@^0.5.5:
|
||||||
version "0.5.5"
|
version "0.5.5"
|
||||||
|
|||||||
@@ -454,15 +454,14 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports.QUERY_UPCOMING_APPOINTMENTS = `
|
exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
||||||
query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
|
||||||
jobs_by_pk(id: $jobId) {
|
jobs_by_pk(id: $jobId) {
|
||||||
bodyshop {
|
bodyshop {
|
||||||
ssbuckets
|
ssbuckets
|
||||||
target_touchtime
|
target_touchtime
|
||||||
workingdays
|
workingdays
|
||||||
}
|
}
|
||||||
jobhrs: joblines_aggregate(where: { removed: { _eq: false } }) {
|
jobhrs: joblines_aggregate(where: {removed: {_eq: false}}) {
|
||||||
aggregate {
|
aggregate {
|
||||||
sum {
|
sum {
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
@@ -470,40 +469,60 @@ query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
appointments(where: {_and: {canceled: {_eq: false}, start: {_gte: $now}}}) {
|
blockedDays: appointments(where: {_and: {canceled: {_eq: false}, block: {_eq: true}, start: {_gte: $now}}}) {
|
||||||
start
|
start
|
||||||
isintake
|
|
||||||
id
|
|
||||||
block
|
block
|
||||||
job {
|
}
|
||||||
joblines_aggregate(where: { removed: { _eq: false } }) {
|
arrJobs: jobs(where: {scheduled_in: {_gte: $now}}) {
|
||||||
aggregate {
|
id
|
||||||
sum {
|
scheduled_in
|
||||||
mod_lb_hrs
|
ro_number
|
||||||
}
|
labhrs: joblines_aggregate(where: {mod_lbr_ty: {_neq: "LAR"}, removed: {_eq: false}}) {
|
||||||
|
aggregate {
|
||||||
|
sum {
|
||||||
|
mod_lb_hrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
larhrs: joblines_aggregate(where: {mod_lbr_ty: {_eq: "LAR"}, removed: {_eq: false}}) {
|
||||||
|
aggregate {
|
||||||
|
sum {
|
||||||
|
mod_lb_hrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jobs(where: {inproduction: {_eq: true}}) {
|
compJobs: jobs(where: {_or: [{scheduled_completion: {_gte: $now}}, {actual_completion: {_gte: $now}}]}) {
|
||||||
|
id
|
||||||
|
ro_number
|
||||||
|
scheduled_completion
|
||||||
|
actual_completion
|
||||||
|
labhrs: joblines_aggregate(where: {mod_lbr_ty: {_neq: "LAR"}, removed: {_eq: false}}) {
|
||||||
|
aggregate {
|
||||||
|
sum {
|
||||||
|
mod_lb_hrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
larhrs: joblines_aggregate(where: {mod_lbr_ty: {_eq: "LAR"}, removed: {_eq: false}}) {
|
||||||
|
aggregate {
|
||||||
|
sum {
|
||||||
|
mod_lb_hrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prodJobs: jobs(where: {inproduction: {_eq: true}}) {
|
||||||
id
|
id
|
||||||
scheduled_completion
|
scheduled_completion
|
||||||
labhrs: joblines_aggregate(
|
labhrs: joblines_aggregate(where: {_and: [{mod_lbr_ty: {_neq: "LAR"}}, {removed: {_eq: false}}]}) {
|
||||||
where: {
|
|
||||||
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
aggregate {
|
aggregate {
|
||||||
sum {
|
sum {
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
larhrs: joblines_aggregate(
|
larhrs: joblines_aggregate(where: {_and: [{mod_lbr_ty: {_eq: "LAR"}}, {removed: {_eq: false}}]}) {
|
||||||
where: {
|
|
||||||
_and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }]
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
aggregate {
|
aggregate {
|
||||||
sum {
|
sum {
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
@@ -512,6 +531,9 @@ query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employeeId: String!) {
|
exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employeeId: String!) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const queries = require("../graphql-client/queries");
|
|||||||
const Dinero = require("dinero.js");
|
const Dinero = require("dinero.js");
|
||||||
const moment = require("moment");
|
const moment = require("moment");
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
|
const _ = require("lodash");
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
@@ -30,7 +31,7 @@ exports.job = async (req, res) => {
|
|||||||
jobId: jobId,
|
jobId: jobId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { appointments, jobs } = result;
|
const { jobs_by_pk, blockedDays, prodJobs, arrJobs, compJobs } = result;
|
||||||
const { ssbuckets, workingdays } = result.jobs_by_pk.bodyshop;
|
const { ssbuckets, workingdays } = result.jobs_by_pk.bodyshop;
|
||||||
const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs;
|
const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
|
||||||
@@ -39,105 +40,164 @@ exports.job = async (req, res) => {
|
|||||||
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
|
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
|
||||||
)[0];
|
)[0];
|
||||||
|
|
||||||
const bucketMatrix = {};
|
const load = {
|
||||||
const yesterday = moment().subtract(1, "day");
|
productionTotal: {},
|
||||||
//Get latest date + add 5 days to allow for back end adding..
|
};
|
||||||
|
//Set the current load.
|
||||||
|
ssbuckets.forEach((bucket) => {
|
||||||
|
load.productionTotal[bucket.id] = { count: 0, label: bucket.label };
|
||||||
|
});
|
||||||
|
|
||||||
const totalMatrixDays = moment
|
const filteredProdJobsList = prodJobs.filter(
|
||||||
.max([
|
(j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
|
||||||
...appointments.map((a) => moment(a.start)),
|
);
|
||||||
...jobs
|
|
||||||
.map((p) => moment(p.scheduled_completion))
|
|
||||||
.filter((p) => p.isValid() && p.isAfter(yesterday)),
|
|
||||||
])
|
|
||||||
.add("5", "days")
|
|
||||||
.diff(moment(), "days");
|
|
||||||
|
|
||||||
//Initialize the bucket matrix
|
filteredProdJobsList.forEach((item) => {
|
||||||
for (var i = 0; i < totalMatrixDays; i++) {
|
//Add all of the jobs currently in production to the buckets so that we have a starting point.
|
||||||
const theDate = moment().add(i, "days").format("yyyy-MM-DD");
|
const bucketId = CheckJobBucket(ssbuckets, item);
|
||||||
//Only need to create a matrix for jobs of the same bucket.
|
if (bucketId) {
|
||||||
bucketMatrix[theDate] = { in: 0, out: 0 };
|
load.productionTotal[bucketId].count =
|
||||||
|
load.productionTotal[bucketId].count + 1;
|
||||||
// ssbuckets.forEach((bucket) => {
|
|
||||||
// bucketMatrix[theDate] = {
|
|
||||||
// ...bucketMatrix[theDate],
|
|
||||||
// [bucket.id]: { in: 0, out: 0 },
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
//Populate the jobs scheduled to come in.
|
|
||||||
appointments.forEach((appointment) => {
|
|
||||||
if (!appointment.block) {
|
|
||||||
const jobHrs =
|
|
||||||
appointment.job.joblines_aggregate.aggregate.sum.mod_lb_hrs;
|
|
||||||
//Is the job in the same bucket?
|
|
||||||
const appointmentBucket = ssbuckets.filter(
|
|
||||||
(bucket) =>
|
|
||||||
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
|
|
||||||
)[0];
|
|
||||||
if (appointmentBucket.id === JobBucket.id) {
|
|
||||||
//Theyre the same classification. Add it to the matrix.
|
|
||||||
const appDate = moment(appointment.start).format("yyyy-MM-DD");
|
|
||||||
bucketMatrix[appDate] = {
|
|
||||||
...bucketMatrix[appDate],
|
|
||||||
in: bucketMatrix[appDate].in + 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
//remove the date from the possible list.
|
console.log("Uh oh, this job doesn't fit in a bucket!", item);
|
||||||
const appDate = moment(appointment.start).format("yyyy-MM-DD");
|
}
|
||||||
bucketMatrix[appDate] = {
|
});
|
||||||
...bucketMatrix[appDate],
|
|
||||||
blocked: true,
|
const filteredArrJobs = arrJobs.filter(
|
||||||
|
(j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
|
||||||
|
);
|
||||||
|
|
||||||
|
filteredArrJobs.forEach((item) => {
|
||||||
|
const itemDate = moment(item.scheduled_in).format("yyyy-MM-DD");
|
||||||
|
if (!!load[itemDate]) {
|
||||||
|
load[itemDate].hoursIn =
|
||||||
|
(load[itemDate].hoursIn || 0) +
|
||||||
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
load[itemDate].jobsIn.push(item);
|
||||||
|
} else {
|
||||||
|
load[itemDate] = {
|
||||||
|
jobsIn: [item],
|
||||||
|
jobsOut: [],
|
||||||
|
hoursIn:
|
||||||
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
|
item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//Populate the jobs that are leaving today.
|
//Get the completing jobs.
|
||||||
const todayIsoString = moment().format("yyyy-MM-DD");
|
let problemJobs = [];
|
||||||
jobs.forEach((pjob) => {
|
const filteredCompJobs = compJobs.filter(
|
||||||
const jobHrs =
|
(j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
|
||||||
pjob.larhrs.aggregate.sum.mod_lb_hrs +
|
);
|
||||||
pjob.labhrs.aggregate.sum.mod_lb_hrs;
|
|
||||||
//Is the job in the same bucket?
|
|
||||||
const pjobBucket = ssbuckets.filter(
|
|
||||||
(bucket) =>
|
|
||||||
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
|
|
||||||
)[0];
|
|
||||||
if (pjobBucket.id === JobBucket.id) {
|
|
||||||
//Theyre the same classification. Add it to the matrix.
|
|
||||||
const compDate = moment(pjob.scheduled_completion);
|
|
||||||
//Is the schedule completion behind today? If so, use today as it.
|
|
||||||
let dateToUse;
|
|
||||||
dateToUse = compDate.isValid()
|
|
||||||
? moment().diff(compDate, "days") < 0
|
|
||||||
? compDate.format("yyyy-MM-DD")
|
|
||||||
: todayIsoString
|
|
||||||
: todayIsoString;
|
|
||||||
|
|
||||||
bucketMatrix[dateToUse] = {
|
filteredCompJobs.forEach((item) => {
|
||||||
...bucketMatrix[dateToUse],
|
const inProdJobs = filteredProdJobsList.find((p) => p.id === item.id);
|
||||||
out: (bucketMatrix[dateToUse].out || 0) + 1,
|
const inArrJobs = filteredArrJobs.find((p) => p.id === item.id);
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Propose the first 5 dates where we are below target.
|
|
||||||
|
|
||||||
const possibleDates = [];
|
|
||||||
const bucketMatrixKeys = Object.keys(bucketMatrix);
|
|
||||||
bucketMatrixKeys.forEach((bmkey) => {
|
|
||||||
const isShopOpen =
|
|
||||||
workingdays[dayOfWeekMapper(moment(bmkey).day())] &&
|
|
||||||
!bucketMatrix[bmkey].blocked;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
JobBucket.target > bucketMatrix[bmkey].in - bucketMatrix[bmkey].out &&
|
!(inProdJobs || inArrJobs) &&
|
||||||
|
!moment(item.actual_completion || item.scheduled_completion).isSame(
|
||||||
|
moment(),
|
||||||
|
"day"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// NOT FOUND!
|
||||||
|
console.log("PROBLEM JOB", item);
|
||||||
|
problemJobs.push({
|
||||||
|
...item,
|
||||||
|
code: "Job is scheduled for completion, but it is not marked in production nor is it an arriving job in this period. Check the scheduled in and completion dates",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const itemDate = moment(
|
||||||
|
item.actual_completion || item.scheduled_completion
|
||||||
|
).format("yyyy-MM-DD");
|
||||||
|
if (!!load[itemDate]) {
|
||||||
|
load[itemDate].hoursOut =
|
||||||
|
(load[itemDate].hoursOut || 0) +
|
||||||
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
load[itemDate].jobsOut.push(item);
|
||||||
|
} else {
|
||||||
|
load[itemDate] = {
|
||||||
|
jobsOut: [item],
|
||||||
|
hoursOut:
|
||||||
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||||
|
item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Propagate the expected load to each day.
|
||||||
|
const yesterday = moment().subtract(1, "day");
|
||||||
|
const today = moment().startOf("day");
|
||||||
|
|
||||||
|
const end = moment.max([
|
||||||
|
...filteredArrJobs.map((a) => moment(a.scheduled_in)),
|
||||||
|
...filteredCompJobs
|
||||||
|
.map((p) => moment(p.actual_completion || p.scheduled_completion))
|
||||||
|
.filter((p) => p.isValid() && p.isAfter(yesterday)),
|
||||||
|
]);
|
||||||
|
const range = Math.round(moment.duration(end.diff(today)).asDays());
|
||||||
|
for (var day = 0; day < range; day++) {
|
||||||
|
const current = moment(today).add(day, "days").format("yyyy-MM-DD");
|
||||||
|
const prev = moment(today)
|
||||||
|
.add(day - 1, "days")
|
||||||
|
.format("yyyy-MM-DD");
|
||||||
|
if (!!!load[current]) {
|
||||||
|
load[current] = {};
|
||||||
|
}
|
||||||
|
if (day === 0) {
|
||||||
|
//Starting on day 1. The load is current.
|
||||||
|
load[current].expectedLoad = CalculateLoad(
|
||||||
|
load.productionTotal,
|
||||||
|
ssbuckets,
|
||||||
|
load[current].jobsIn || [],
|
||||||
|
load[current].jobsOut || []
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
load[current].expectedLoad = CalculateLoad(
|
||||||
|
load[prev].expectedLoad,
|
||||||
|
ssbuckets,
|
||||||
|
load[current].jobsIn || [],
|
||||||
|
load[current].jobsOut || []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add in all of the blocked days.
|
||||||
|
|
||||||
|
blockedDays.forEach((b) => {
|
||||||
|
//Find it in the load, set it as blocked.
|
||||||
|
const startIsoFormat = moment(b.start).format("YYYY-MM-DD");
|
||||||
|
if (load[startIsoFormat]) load[startIsoFormat].blocked = true;
|
||||||
|
else {
|
||||||
|
load[startIsoFormat] = { blocked: true };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// //Propose the first 5 dates where we are below target.
|
||||||
|
|
||||||
|
const possibleDates = [];
|
||||||
|
delete load.productionTotal;
|
||||||
|
const loadKeys = Object.keys(load).sort((a, b) =>
|
||||||
|
moment(a).isAfter(moment(b)) ? 1 : -1
|
||||||
|
);
|
||||||
|
|
||||||
|
loadKeys.forEach((loadKey) => {
|
||||||
|
const isShopOpen =
|
||||||
|
(workingdays[dayOfWeekMapper(moment(loadKey).day())] || false) &&
|
||||||
|
!load[loadKey].blocked;
|
||||||
|
|
||||||
|
if (
|
||||||
|
load[loadKey].expectedLoad &&
|
||||||
|
load[loadKey].expectedLoad[JobBucket.id] &&
|
||||||
|
JobBucket.target > load[loadKey].expectedLoad[JobBucket.id].count &&
|
||||||
isShopOpen
|
isShopOpen
|
||||||
)
|
)
|
||||||
possibleDates.push(new Date(bmkey).toISOString().substr(0, 10));
|
possibleDates.push(new Date(loadKey).toISOString().substr(0, 10));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (possibleDates.length < 6) {
|
if (possibleDates.length < 6) {
|
||||||
@@ -147,7 +207,7 @@ exports.job = async (req, res) => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, {
|
logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, {
|
||||||
error: JSON.stringify(error),
|
error,
|
||||||
});
|
});
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
}
|
}
|
||||||
@@ -171,3 +231,47 @@ const dayOfWeekMapper = (numberOfDay) => {
|
|||||||
return "saturday";
|
return "saturday";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CheckJobBucket = (buckets, job) => {
|
||||||
|
const jobHours =
|
||||||
|
job.labhrs.aggregate.sum.mod_lb_hrs + job.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
|
||||||
|
const matchingBucket = buckets.filter((b) =>
|
||||||
|
b.gte <= jobHours && b.lt ? b.lt > jobHours : true
|
||||||
|
);
|
||||||
|
|
||||||
|
return matchingBucket[0] && matchingBucket[0].id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CalculateLoad = (currentLoad, buckets, jobsIn, jobsOut) => {
|
||||||
|
//Add the jobs coming
|
||||||
|
const newLoad = _.cloneDeep(currentLoad);
|
||||||
|
jobsIn.forEach((job) => {
|
||||||
|
const bucketId = CheckJobBucket(buckets, job);
|
||||||
|
if (bucketId) {
|
||||||
|
newLoad[bucketId].count = newLoad[bucketId].count + 1;
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"[Util Arr Job]Uh oh, this job doesn't fit in a bucket!",
|
||||||
|
job
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
jobsOut.forEach((job) => {
|
||||||
|
const bucketId = CheckJobBucket(buckets, job);
|
||||||
|
if (bucketId) {
|
||||||
|
newLoad[bucketId].count = newLoad[bucketId].count - 1;
|
||||||
|
if (newLoad[bucketId].count < 0) {
|
||||||
|
console.log("***ERROR: NEGATIVE LOAD Bucket =>", bucketId, job);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"[Util Out Job]Uh oh, this job doesn't fit in a bucket!",
|
||||||
|
job
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return newLoad;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user