Merged in release/2022-07-29 (pull request #552)

release/2022-07-29

Approved-by: Patrick Fic
This commit is contained in:
Patrick Fic
2022-07-29 23:05:08 +00:00
20 changed files with 4300 additions and 4222 deletions

View File

@@ -40555,6 +40555,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>parts_not_recieved_vendor</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>payments_by_date</name>
<definition_loaded>false</definition_loaded>
@@ -41535,6 +41556,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>entries</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>jobs</name>
<definition_loaded>false</definition_loaded>

View File

@@ -4,18 +4,18 @@
"private": true,
"proxy": "http://localhost:4000",
"dependencies": {
"@apollo/client": "^3.6.6",
"@apollo/client": "^3.6.9",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.3",
"@craco/craco": "^6.4.5",
"@fingerprintjs/fingerprintjs": "^3.3.3",
"@jsreport/browser-client": "^3.1.0",
"@sentry/react": "^7.1.1",
"@sentry/tracing": "^7.1.1",
"@splitsoftware/splitio-react": "^1.4.1",
"@stripe/react-stripe-js": "^1.8.1",
"@stripe/stripe-js": "^1.31.0",
"@tanem/react-nprogress": "^5.0.1",
"antd": "^4.21.0",
"@sentry/react": "^7.7.0",
"@sentry/tracing": "^7.7.0",
"@splitsoftware/splitio-react": "^1.6.0",
"@stripe/react-stripe-js": "^1.9.0",
"@stripe/stripe-js": "^1.32.0",
"@tanem/react-nprogress": "^5.0.8",
"antd": "^4.21.7",
"apollo-link-logger": "^2.0.0",
"axios": "^0.27.2",
"craco-less": "^1.20.0",
@@ -24,33 +24,33 @@
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^9.8.2",
"firebase": "^9.9.1",
"graphql": "^16.5.0",
"i18next": "^21.8.9",
"i18next": "^21.8.14",
"i18next-browser-languagedetector": "^6.1.4",
"jsoneditor": "^9.8.0",
"jsoneditor": "^9.9.0",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.10.6",
"logrocket": "^3.0.0",
"markerjs2": "^2.21.4",
"libphonenumber-js": "^1.10.9",
"logrocket": "^3.0.1",
"markerjs2": "^2.22.0",
"moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34",
"normalize-url": "^7.0.3",
"phone": "^3.1.20",
"phone": "^3.1.23",
"preval.macro": "^5.0.0",
"prop-types": "^15.8.1",
"query-string": "^7.1.1",
"rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6",
"react": "^17.0.2",
"react-big-calendar": "^0.40.1",
"react-big-calendar": "^1.5.0",
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-drag-listview": "^0.2.1",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.4",
"react-i18next": "^11.17.0",
"react-i18next": "^11.18.1",
"react-icons": "^4.4.0",
"react-number-format": "^4.9.3",
"react-redux": "^7.2.8",
@@ -60,13 +60,13 @@
"react-sticky": "^6.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3",
"recharts": "^2.1.10",
"recharts": "^2.1.12",
"redux": "^4.2.0",
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2",
"redux-state-sync": "^3.1.4",
"reselect": "^4.1.6",
"sass": "^1.51.0",
"sass": "^1.54.0",
"socket.io-client": "^4.5.1",
"styled-components": "^5.3.5",
"subscriptions-transport-ws": "^0.11.0",
@@ -119,9 +119,9 @@
"react-error-overlay": "6.0.9"
},
"devDependencies": {
"@sentry/webpack-plugin": "^1.18.9",
"@testing-library/cypress": "^8.0.2",
"cypress": "^9.6.1",
"@sentry/webpack-plugin": "^1.19.0",
"@testing-library/cypress": "^8.0.3",
"cypress": "^10.3.1",
"eslint-plugin-cypress": "^2.12.1",
"react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6",

View File

@@ -27,6 +27,10 @@ const BillLineSearchSelect = (
option.oem_partno
.toLowerCase()
.includes(inputValue.toLowerCase())) ||
(option.alt_partno &&
option.alt_partno
.toLowerCase()
.includes(inputValue.toLowerCase())) ||
(option.act_price &&
option.act_price.toString().startsWith(inputValue.toString()))
);
@@ -48,14 +52,17 @@ const BillLineSearchSelect = (
line_desc={item.line_desc}
part_qty={item.part_qty}
oem_partno={item.oem_partno}
alt_partno={item.alt_partno}
act_price={item.act_price}
style={{
...(item.removed ? { textDecoration: "line-through" } : {}),
}}
>
<span>{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}`}</span>
<span>
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
</span>
<span style={{ float: "right", paddingleft: "1rem" }}>
{item.act_price
? `$${item.act_price && item.act_price.toFixed(2)}`

View File

@@ -45,9 +45,11 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
return acc + generateUpdateQuery(value, idx);
}, "");
const removeQueries = existingLines.reduce((acc, value, idx) => {
return acc + generateRemoveQuery(value, idx);
}, "");
const removeQueries = existingLines
.filter((l) => !l.manual_line)
.reduce((acc, value, idx) => {
return acc + generateRemoveQuery(value, idx);
}, "");
console.log(insertQueries, updateQueries, removeQueries);
if ((insertQueries + updateQueries + removeQueries).trim() === "") {

View File

@@ -1,10 +1,10 @@
import { Card, Col, Row, Space, Tag } from "antd";
import {
WarningFilled,
ExclamationCircleFilled,
PauseCircleOutlined,
WarningFilled,
} from "@ant-design/icons";
import React, { useMemo } from "react";
import { Card, Col, Row, Space, Tag } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
@@ -13,17 +13,17 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component";
import JobAltTransportChange from "../job-at-change/job-at-change.component";
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import "./jobs-detail-header.styles.scss";
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
import "./jobs-detail-header.styles.scss";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -56,12 +56,6 @@ const colSpan = {
export function JobsDetailHeader({ job, bodyshop, disabled }) {
const { t } = useTranslation();
const jobInPostProduction = useMemo(() => {
return bodyshop.md_ro_statuses.post_production_statuses.includes(
job.status
);
}, [job.status, bodyshop.md_ro_statuses.post_production_statuses]);
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
${job.v_make_desc || ""}
${job.v_model_desc || ""}`.trim();
@@ -129,11 +123,10 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
</DataLabel>
)}
{(job.inproduction || jobInPostProduction) && (
<DataLabel label={t("jobs.fields.production_vars.note")}>
<ProductionListColumnProductionNote record={job} />
</DataLabel>
)}
<DataLabel label={t("jobs.fields.production_vars.note")}>
<ProductionListColumnProductionNote record={job} />
</DataLabel>
<Space>
{job.special_coverage_policy && (
<Tag color="tomato">

View File

@@ -6,12 +6,17 @@ export const CalculateAllocationsTotals = (
timetickets,
adjustments = []
) => {
console.log(
"🚀 ~ file: labor-allocations-table.utility.js ~ line 9 ~ adjustments",
adjustments
);
const responsibilitycenters = bodyshop.md_responsibility_centers;
const jobCodes = joblines.map((item) => item.mod_lbr_ty);
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
const ticketCodes = timetickets.map((item) => item.ciecacode);
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
const allCodes = [...jobCodes, ...ticketCodes].filter(
const adjustmentCodes = Object.keys(adjustments);
const allCodes = [...jobCodes, ...ticketCodes, ...adjustmentCodes].filter(
(value, index, self) => self.indexOf(value) === index && !!value
);

View File

@@ -1,42 +1,42 @@
import { useQuery } from "@apollo/client";
import { Button, Card, Input, Modal, Space, Table, Typography } from "antd";
import React, { useState } from "react";
import { Dropdown, Button, Table, Space, Card, Input } from "antd";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component";
import { QUERY_SCOREBOARD_PAGINATED } from "../../graphql/scoreboard.queries";
import { DateFormatter } from "../../utils/DateFormatter";
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component";
import { SyncOutlined } from "@ant-design/icons";
export default function ScoreboardJobsList({ scoreBoardlist }) {
const { t } = useTranslation();
const [searchText, setSearchText] = useState("");
const [state, setState] = useState({
visible: false,
search: "",
current: 1,
pageSize: 10,
});
const jobs = scoreBoardlist
? searchText === ""
? scoreBoardlist
: scoreBoardlist.filter(
(sb) =>
(sb.job.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(sb.job.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(sb.job.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(sb.job.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(sb.job.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(sb.job.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
)
: [];
const { loading, error, data, refetch } = useQuery(
QUERY_SCOREBOARD_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: !state.visible,
variables: {
search: state.search !== "" ? `%${state.search}%` : null,
offset: state.current ? (state.current - 1) * state.pageSize : 0,
limit: state.pageSize,
order: [
{
date: "desc",
},
],
},
}
);
const columns = [
{
@@ -97,35 +97,69 @@ export default function ScoreboardJobsList({ scoreBoardlist }) {
},
];
const overlay = (
<Card
style={{ maxWidth: "90vw", padding: "1rem" }}
onClick={(e) => e.stopPropagation()}
extra={
<Input.Search
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);
}}
value={searchText}
enterButton
onClick={(e) => e.stopPropagation()}
/>
}
>
<Table
pagination={false}
columns={columns}
rowKey="id"
dataSource={jobs}
onClick={(e) => e.stopPropagation()}
/>
</Card>
);
return (
<Dropdown trigger={["click"]} overlay={overlay}>
<Button>Jobs</Button>
</Dropdown>
<>
<Modal
visible={state.visible}
destroyOnClose
width="80%"
cancelButtonProps={{ style: { display: "none" } }}
onCancel={() =>
setState((state) => ({
...state,
visible: false,
current: 1,
search: "",
}))
}
>
{error && (
<AlertComponent type="error" message={JSON.stringify(error)} />
)}
<Card
extra={
<Space align="middle" wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Typography.Title level={4}>
{t("general.labels.searchresults", { search: state.search })}
</Typography.Title>
<Input.Search
placeholder={t("jobs.fields.ro_number")}
allowClear
onSearch={(value) => {
setState((state) => ({ ...state, search: value }));
}}
//value={state.search}
enterButton
/>
</Space>
}
>
<Table
columns={columns}
rowKey="id"
dataSource={data ? data.scoreboard : []}
loading={loading}
onClick={(e) => e.stopPropagation()}
onChange={(tableArgs) =>
setState((state) => ({ ...state, ...tableArgs }))
}
pagination={{
position: "top",
pageSize: state.pageSize || 10,
current: state.current || 1,
total: data ? data.scoreboard_aggregate.aggregate.count : 0,
}}
/>
</Card>
</Modal>
<Button
onClick={() => setState((state) => ({ ...state, visible: true }))}
>
{t("scoreboard.labels.entries")}
</Button>
</>
);
}

View File

@@ -17,6 +17,8 @@ export default function ScoreboardRemoveButton({ scoreboardId }) {
setLoading(true);
const result = await deleteScoreboardEntry({
variables: { sbId: scoreboardId },
awaitRefetchQueries: true,
refetchQueries: ["QUERY_SCOREBOARD_PAGINATED"],
});
if (!!result.errors) {

View File

@@ -185,8 +185,8 @@ export default function ScoreboardTimeTickets() {
ret.employees[ticket.employee.employee_number].totalOverPeriod +
ticket.productivehrs;
ret.employees[ticket.employee.employee_number].actualTotalOverPeriod =
ret.employees[ticket.employee.employee_number].actualTotalOverPeriod +
(ticket.actualhrs || 0);
ret.employees[ticket.employee.employee_number]
.actualTotalOverPeriod + (ticket.actualhrs || 0);
if (!totals.employees[ticket.employee.employee_number])
totals.employees[ticket.employee.employee_number] = {
@@ -236,8 +236,6 @@ export default function ScoreboardTimeTickets() {
};
}, [fixedPeriods, data, startDate, endDate]);
console.log(calculatedData);
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading) return <LoadingSpinner />;
return (

View File

@@ -23,6 +23,7 @@ export const GET_ALL_JOBLINES_BY_PK = gql`
notes
location
tax_part
manual_line
}
}
`;
@@ -197,6 +198,7 @@ export const GET_JOB_LINES_TO_ENTER_BILL = gql`
line_desc
part_type
oem_partno
alt_partno
db_price
act_price
part_qty
@@ -206,6 +208,7 @@ export const GET_JOB_LINES_TO_ENTER_BILL = gql`
lbr_op
lbr_amt
op_code_desc
alt_partno
}
jobs_by_pk(id: $id) {
id

View File

@@ -88,3 +88,41 @@ export const GET_BLOCKED_DAYS = gql`
}
}
`;
export const QUERY_SCOREBOARD_PAGINATED = gql`
query QUERY_SCOREBOARD_PAGINATED(
$search: String
$offset: Int
$limit: Int
$order: [scoreboard_order_by!]
) {
scoreboard(
where: { job: { ro_number: { _ilike: $search } } }
offset: $offset
limit: $limit
order_by: $order
) {
id
jobid
job {
id
ro_number
invoice_date
v_make_desc
v_model_desc
v_model_yr
ownr_fn
ownr_ln
ownr_co_nm
}
date
bodyhrs
painthrs
}
scoreboard_aggregate(where: { job: { ro_number: { _ilike: $search } } }) {
aggregate {
count(distinct: true)
}
}
}
`;

View File

@@ -2406,6 +2406,7 @@
"open_orders_status": "Open Orders by Status",
"parts_backorder": "IOU Parts List",
"parts_not_recieved": "Parts Not Received",
"parts_not_recieved_vendor": "Parts Not Received by Vendor",
"payments_by_date": "Payments by Date",
"payments_by_date_type": "Payments by Date and Type",
"production_by_category": "Production by Category",
@@ -2465,6 +2466,7 @@
"dailyactual": "Actual (D)",
"dailytarget": "Daily",
"efficiencyoverperiod": "Efficiency over Selected Dates",
"entries": "Scoreboard Entries",
"jobs": "Jobs",
"lastmonth": "Last Month",
"lastweek": "Last Week",

View File

@@ -2406,6 +2406,7 @@
"open_orders_status": "",
"parts_backorder": "",
"parts_not_recieved": "",
"parts_not_recieved_vendor": "",
"payments_by_date": "",
"payments_by_date_type": "",
"production_by_category": "",
@@ -2465,6 +2466,7 @@
"dailyactual": "",
"dailytarget": "",
"efficiencyoverperiod": "",
"entries": "",
"jobs": "",
"lastmonth": "",
"lastweek": "",

View File

@@ -2406,6 +2406,7 @@
"open_orders_status": "",
"parts_backorder": "",
"parts_not_recieved": "",
"parts_not_recieved_vendor": "",
"payments_by_date": "",
"payments_by_date_type": "",
"production_by_category": "",
@@ -2465,6 +2466,7 @@
"dailyactual": "",
"dailytarget": "",
"efficiencyoverperiod": "",
"entries": "",
"jobs": "",
"lastmonth": "",
"lastweek": "",

View File

@@ -1448,6 +1448,19 @@ export const TemplateList = (type, context) => {
},
group: "purchases",
},
parts_not_recieved_vendor: {
title: i18n.t("reportcenter.templates.parts_not_recieved_vendor"),
description: "",
subject: i18n.t("reportcenter.templates.parts_not_recieved_vendor"),
key: "parts_not_recieved_vendor",
idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.parts_orders"),
field: i18n.t("parts_orders.fields.order_date"),
},
group: "purchases",
},
scoreboard_detail: {
title: i18n.t("reportcenter.templates.scoreboard_detail"),
description: "",

File diff suppressed because it is too large Load Diff

View File

@@ -17,11 +17,11 @@
"start": "node server.js"
},
"dependencies": {
"aws-sdk": "^2.1136.0",
"aws-sdk": "^2.1181.0",
"axios": "^0.27.2",
"bluebird": "^3.7.2",
"body-parser": "^1.20.0",
"cloudinary": "^1.30.0",
"cloudinary": "^1.30.1",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"cors": "2.8.5",
@@ -29,35 +29,33 @@
"dinero.js": "^1.9.1",
"dotenv": "16.0.1",
"express": "^4.18.1",
"firebase-admin": "^10.2.0",
"firebase-admin": "^11.0.0",
"graphql": "^16.5.0",
"graphql-request": "^4.2.0",
"graylog2": "^0.2.1",
"inline-css": "^3.0.0",
"inline-css": "^4.0.1",
"intuit-oauth": "^4.0.0",
"json-2-csv": "^3.17.1",
"lodash": "^4.17.21",
"moment": "^2.29.3",
"moment": "^2.29.4",
"moment-timezone": "^0.5.34",
"multer": "^1.4.4",
"node-mailjet": "^3.4.1",
"multer": "^1.4.5-lts.1",
"node-mailjet": "^5.1.0",
"node-quickbooks": "^2.0.39",
"nodemailer": "^6.7.5",
"phone": "^3.1.17",
"nodemailer": "^6.7.7",
"phone": "^3.1.23",
"query-string": "^7.1.1",
"soap": "^0.43.0",
"soap": "^0.45.0",
"socket.io": "^4.5.0",
"ssh2-sftp-client": "^8.0.0",
"stripe": "^9.1.0",
"twilio": "^3.77.0",
"ssh2-sftp-client": "^9.0.2",
"stripe": "^9.15.0",
"twilio": "^3.80.0",
"uuid": "^8.3.2",
"xml2js": "^0.4.23",
"xmlbuilder2": "^3.0.2"
},
"devDependencies": {
"concurrently": "^6.3.0",
"eslint": "^7.31.0",
"eslint-plugin-promise": "^5.1.0",
"concurrently": "^7.3.0",
"source-map-explorer": "^2.5.2"
}
}

View File

@@ -172,6 +172,18 @@ app.post(
fb.validateAdmin,
adm.createShop
);
app.post(
"/adm/updateshop",
fb.validateFirebaseIdToken,
fb.validateAdmin,
adm.updateShop
);
app.post(
"/adm/updatecounter",
fb.validateFirebaseIdToken,
fb.validateAdmin,
adm.updateCounter
);
//Stripe Processing
var stripe = require("./server/stripe/payment");

View File

@@ -66,3 +66,55 @@ exports.createShop = async (req, res) => {
res.status(500).json(error);
}
};
exports.updateCounter = async (req, res) => {
logger.log("admin-update-counter", "ADMIN", req.user.email, null, {
request: req.body,
ioadmin: true,
});
const { id, counter } = req.body;
try {
const result = await client.request(
`mutation UPDATE_COUNTER($id: uuid!, $counter: counters_set_input!) {
update_counters_by_pk(pk_columns: { id: $id }, _set: $counter) {
id
countertype
count
prefix
}
}`,
{
id,
counter,
}
);
res.json(result);
} catch (error) {
res.status(500).json(error);
}
};
exports.updateShop = async (req, res) => {
logger.log("admin-update-shop", "ADMIN", req.user.email, null, {
request: req.body,
ioadmin: true,
});
const { id, bodyshop } = req.body;
try {
const result = await client.request(
`mutation UPDATE_BODYSHOP($id: uuid!, $bodyshop: bodyshops_set_input!) {
update_bodyshops_by_pk(pk_columns: { id: $id }, _set: $bodyshop) {
id
}
}`,
{
id,
bodyshop,
}
);
res.json(result);
} catch (error) {
res.status(500).json(error);
}
};

2711
yarn.lock

File diff suppressed because it is too large Load Diff