Merged in release/2022-02-11 (pull request #375)

Release/2022 02 11
This commit is contained in:
Patrick Fic
2022-02-09 17:46:15 +00:00
41 changed files with 803 additions and 142 deletions

View File

@@ -2863,6 +2863,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>markexported</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>markforreexport</name>
<definition_loaded>false</definition_loaded>
@@ -3120,6 +3141,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>markexported</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>reexport</name>
<definition_loaded>false</definition_loaded>
@@ -7794,6 +7836,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>timezone</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>tt_allow_post_to_invoiced</name>
<definition_loaded>false</definition_loaded>
@@ -20329,6 +20392,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>comment</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>customerowing</name>
<definition_loaded>false</definition_loaded>
@@ -36104,6 +36188,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>comment</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>compact</name>
<definition_loaded>false</definition_loaded>
@@ -38903,6 +39008,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>dailyactual</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>dailytarget</name>
<definition_loaded>false</definition_loaded>
@@ -38966,6 +39092,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>todateactual</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>weeklyactual</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>weeklytarget</name>
<definition_loaded>false</definition_loaded>

View File

@@ -33,6 +33,7 @@
"logrocket": "^2.1.2",
"markerjs2": "^2.17.2",
"moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34",
"phone": "^3.1.10",
"preval.macro": "^5.0.0",
"prop-types": "^15.7.2",

View File

@@ -28,6 +28,7 @@ import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -234,6 +235,7 @@ export function BillDetailEditcontainer({
</Button>
</Popconfirm>
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
<BillMarkExportedButton bill={data && data.bills_by_pk} />
</Space>
}
/>

View File

@@ -0,0 +1,82 @@
import { useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import { gql } from "@apollo/client";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
selectBodyshop,
} from "../../redux/user/user.selectors";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillMarkExportedButton);
export function BillMarkExportedButton({ bodyshop, authLevel, bill }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [updateBill] = useMutation(gql`
mutation UPDATE_BILL($billId: uuid!) {
update_bills(where: { id: { _eq: $billId } }, _set: { exported: true }) {
returning {
id
exported
exported_at
}
}
}
`);
const handleUpdate = async () => {
setLoading(true);
const result = await updateBill({
variables: { billId: bill.id },
});
if (!result.errors) {
notification["success"]({
message: t("bills.successes.markexported"),
});
} else {
notification["error"]({
message: t("bills.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
//Get the owner details, populate it all back into the job.
};
const hasAccess = HasRbacAccess({
bodyshop,
authLevel,
action: "bills:reexport",
});
if (hasAccess)
return (
<Button
loading={loading}
disabled={bill.exported}
onClick={handleUpdate}
>
{t("bills.labels.markexported")}
</Button>
);
return <></>;
}

View File

@@ -1,11 +1,24 @@
import { DatePicker } from "antd";
import moment from "moment";
import moment from "moment-timezone";
import React, { useRef } from "react";
//To be used as a form element only.
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(FormDatePicker);
const dateFormat = "MM/DD/YYYY";
export default function FormDatePicker({
export function FormDatePicker({
bodyshop,
value,
onChange,
onBlur,
@@ -23,7 +36,7 @@ export default function FormDatePicker({
const handleKeyDown = (e) => {
if (e.key.toLowerCase() === "t") {
if (onChange) {
onChange(new moment());
onChange(moment());
// if (ref.current && ref.current.blur) ref.current.blur();
}
} else if (e.key.toLowerCase() === "enter") {

View File

@@ -72,7 +72,7 @@ export default function JobBillsTotalComponent({
const discrepWithLbrAdj = discrepancy.add(lbrAdjustments);
const discrepWithCms = discrepWithLbrAdj.add(billCms);
const discrepWithCms = discrepWithLbrAdj.add(totalReturns);
const creditsNotReceived = totalReturns.subtract(billCms); //billCms is tracked as a negative number.
return (
@@ -171,8 +171,8 @@ export default function JobBillsTotalComponent({
}
>
<Statistic
title={t("bills.labels.billcmtotal")}
value={billCms.toFormat()}
title={t("bills.labels.totalreturns")}
value={totalReturns.toFormat()}
/>
</Tooltip>
<Typography.Title>=</Typography.Title>

View File

@@ -21,6 +21,7 @@ import ProductionListColumnProductionNote from "../production-list-columns/produ
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";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -86,6 +87,12 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
) : null}
</Space>
</DataLabel>
<DataLabel
label={t("jobs.fields.comment")}
valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}
>
<ProductionListColumnComment record={job} />
</DataLabel>
<DataLabel label={t("jobs.fields.ins_co_nm_short")}>
{job.ins_co_nm}
</DataLabel>

View File

@@ -30,7 +30,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
width: "8%",
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder,
@@ -47,7 +47,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
key: "ownr_ln",
ellipsis: true,
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
width: "25%",
//sortOrder: sortcolumn === "ownr_ln" && sortorder,
render: (text, record) => {
return record.ownerid ? (
@@ -67,7 +67,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
width: "12%",
ellipsis: true,
render: (text, record) => (
<StartChatButton phone={record.ownr_ph1} jobid={record.id} />
@@ -77,7 +77,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
width: "12%",
ellipsis: true,
render: (text, record) => (
<StartChatButton phone={record.ownr_ph2} jobid={record.id} />
@@ -87,7 +87,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
width: "10%",
ellipsis: true,
sorter: true, // (a, b) => alphaSort(a.status, b.status),
sortOrder: sortcolumn === "status" && sortorder,
@@ -104,7 +104,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
width: "15%",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
@@ -124,7 +124,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
width: "8%",
ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder: sortcolumn === "plate_no" && sortorder,
@@ -136,7 +136,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
width: "12%",
ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortcolumn === "clm_no" && sortorder,
@@ -155,7 +155,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
width: "10%",
sorter: true, //(a, b) => a.clm_total - b.clm_total,
sortOrder: sortcolumn === "clm_total" && sortorder,
render: (text, record) => {
@@ -170,11 +170,17 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.owner_owing"),
dataIndex: "owner_owing",
key: "owner_owing",
width: "8%",
render: (text, record) => (
<CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
},
];
const handleTableChange = (pagination, filters, sorter) => {
@@ -224,7 +230,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
>
<Table
loading={loading}
scroll={{ x: true }}
pagination={{
position: "top",
pageSize: 25,

View File

@@ -60,6 +60,9 @@ export function JobsList({ bodyshop }) {
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.comments || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
@@ -262,6 +265,13 @@ export function JobsList({ bodyshop }) {
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
responsive: ["md"],
},
// {
// title: t("jobs.fields.owner_owing"),
// dataIndex: "owner_owing",

View File

@@ -0,0 +1,79 @@
import Icon from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Input, Popover } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { FaRegStickyNote } from "react-icons/fa";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
export default function ProductionListColumnComment({ record }) {
const { t } = useTranslation();
const [note, setNote] = useState(record.comment || "");
const [visible, setVisible] = useState(false);
const [updateAlert] = useMutation(UPDATE_JOB);
const handleSaveNote = (e) => {
e.stopPropagation();
setVisible(false);
updateAlert({
variables: {
jobId: record.id,
job: {
comment: note,
},
},
}).then(() => {
if (record.refetch) record.refetch();
});
};
const handleChange = (e) => {
e.stopPropagation();
setNote(e.target.value);
};
const handleVisibleChange = (flag) => {
setVisible(flag);
if (flag) setNote(record.comment || "");
};
return (
<Popover
onVisibleChange={handleVisibleChange}
visible={visible}
content={
<div style={{ width: "30em" }}>
<Input.TextArea
rows={5}
value={note}
onChange={handleChange}
// onPressEnter={handleSaveNote}
autoFocus
allowClear
/>
<div>
<Button onClick={handleSaveNote}>
{t("general.actions.save")}
</Button>
</div>
</div>
}
trigger={["click"]}
>
<div
style={{
width: "100%",
height: "19px",
cursor: "pointer",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
<Icon component={FaRegStickyNote} style={{ marginRight: ".2rem" }} />
{record.comment || " "}
</div>
</Popover>
);
}

View File

@@ -20,6 +20,7 @@ import ProductionListColumnNote from "./production-list-columns.productionnote.c
import ProductionListColumnStatus from "./production-list-columns.status.component";
import ProductionListColumnCategory from "./production-list-columns.status.category";
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
import ProductionListColumnComment from "./production-list-columns.comment.component";
const r = ({ technician, state, activeStatuses, bodyshop }) => {
return [
@@ -109,7 +110,11 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.columnKey === "scheduled_completion" &&
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="scheduled_completion" pastIndicator />
<ProductionListDate
record={record}
field="scheduled_completion"
pastIndicator
/>
),
},
{
@@ -156,7 +161,11 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.columnKey === "scheduled_delivery" &&
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="scheduled_delivery" pastIndicator/>
<ProductionListDate
record={record}
field="scheduled_delivery"
pastIndicator
/>
),
},
{
@@ -325,6 +334,13 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
ellipsis: true,
render: (text, record) => <ProductionListColumnNote record={record} />,
},
{
title: i18n.t("production.labels.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
render: (text, record) => <ProductionListColumnComment record={record} />,
},
{
title: i18n.t("production.labels.touchtime"),
dataIndex: "tt",

View File

@@ -65,6 +65,7 @@ export function ScheduleCalendarContainer({ calculateScheduleLoad }) {
color: "red",
start: moment(e.start).startOf("day").toDate(),
end: moment(e.end).startOf("day").toDate(),
allDay: true,
vacation: true,
};
}),

View File

@@ -1,12 +1,14 @@
import { CalendarOutlined } from "@ant-design/icons";
import { Card, Col, Row, Statistic } from "antd";
import React from "react";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ScoreboardJobsList from "../scoreboard-jobs-list/scoreboard-jobs-list.component";
import * as Util from "./scoreboard-targets-table.util";
import _ from "lodash";
import moment from "moment";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -16,25 +18,78 @@ const mapDispatchToProps = (dispatch) => ({
});
const rowGutter = [16, 16];
const statSpans = { xs: 24, sm: 6 };
const statSpans = { xs: 24, sm: 3 };
export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
const { t } = useTranslation();
const values = useMemo(() => {
const dateHash = _.groupBy(scoreBoardlist, "date");
console.log(
"🚀 ~ file: scoreboard-targets-table.component.jsx ~ line 31 ~ values ~ dateHash",
dateHash
);
let ret = {
todayBody: 0,
todayPaint: 0,
weeklyPaint: 0,
weeklyBody: 0,
toDateBody: 0,
toDatePaint: 0,
};
const today = moment().tz(bodyshop.timezone);
if (dateHash[today.format("YYYY-MM-DD")]) {
dateHash[today.format("YYYY-MM-DD")].forEach((d) => {
ret.todayBody = ret.todayBody + d.bodyhrs;
ret.todayPaint = ret.todayPaint + d.painthrs;
});
}
let StartOfWeek = moment().tz(bodyshop.timezone).startOf("week");
while (StartOfWeek.isSameOrBefore(today)) {
if (dateHash[StartOfWeek.format("YYYY-MM-DD")]) {
dateHash[StartOfWeek.format("YYYY-MM-DD")].forEach((d) => {
ret.weeklyBody = ret.weeklyBody + d.bodyhrs;
ret.weeklyPaint = ret.weeklyPaint + d.painthrs;
});
}
StartOfWeek = StartOfWeek.add(1, "day");
}
let startOfMonth = moment().tz(bodyshop.timezone).startOf("month");
while (startOfMonth.isSameOrBefore(today)) {
if (dateHash[startOfMonth.format("YYYY-MM-DD")]) {
dateHash[startOfMonth.format("YYYY-MM-DD")].forEach((d) => {
ret.toDateBody = ret.toDateBody + d.bodyhrs;
ret.toDatePaint = ret.toDatePaint + d.painthrs;
});
}
startOfMonth = startOfMonth.add(1, "day");
}
return ret;
}, [scoreBoardlist, bodyshop.timezone]);
console.log(
"🚀 ~ file: scoreboard-targets-table.component.jsx ~ line 51 ~ values ~ values",
values
);
return (
<Card
title={t("scoreboard.labels.targets")}
extra={<ScoreboardJobsList scoreBoardlist={scoreBoardlist} />}
>
<Row gutter={rowGutter}>
<Col xs={24} sm={{ offset: 0, span: 4 }} lg={{ offset: 5, span: 4 }}>
<Col xs={24} sm={{ offset: 0, span: 4 }} lg={{ span: 4 }}>
<Statistic
title={t("scoreboard.labels.workingdays")}
value={Util.CalculateWorkingDaysThisMonth()}
prefix={<CalendarOutlined />}
/>
</Col>
<Col xs={24} sm={{ offset: 0, span: 20 }} lg={{ offset: 0, span: 13 }}>
<Col xs={24} sm={{ offset: 0, span: 20 }} lg={{ offset: 0, span: 20 }}>
<Row>
<Col {...statSpans}>
<Statistic
@@ -43,6 +98,12 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
prefix="B"
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.dailyactual")}
value={values.todayBody}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.weeklytarget")}
@@ -52,6 +113,12 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.weeklyactual")}
value={values.weeklyBody}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.monthlytarget")}
@@ -70,6 +137,12 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.todateactual")}
value={values.toDateBody}
/>
</Col>
</Row>
<Row>
<Col {...statSpans}>
@@ -78,6 +151,9 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
prefix="P"
/>
</Col>
<Col {...statSpans}>
<Statistic value={values.todayPaint} />
</Col>
<Col {...statSpans}>
<Statistic
value={Util.WeeklyTargetHrs(
@@ -86,6 +162,9 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic value={values.weeklyPaint} />
</Col>
<Col {...statSpans}>
<Statistic
value={Util.MonthlyTargetHrs(
@@ -102,6 +181,9 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic value={values.toDatePaint} />
</Col>
</Row>
</Col>
</Row>

View File

@@ -19,6 +19,9 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import momentTZ from "moment-timezone";
const timeZonesList = momentTZ.tz.names();
export default function ShopInfoGeneral({ form }) {
const { t } = useTranslation();
return (
@@ -84,6 +87,7 @@ export default function ShopInfoGeneral({ form }) {
<Form.Item label={t("bodyshop.fields.email")} name="email">
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.phone")}
name="phone"
@@ -97,6 +101,23 @@ export default function ShopInfoGeneral({ form }) {
<Form.Item label={t("bodyshop.fields.website")} name="website">
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.timezone")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name="timezone"
>
<Select
showSearch
options={timeZonesList.map((z) => {
return { label: z, value: z };
})}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.insurance_vendor_id")}
name="insurance_vendor_id"

View File

@@ -158,7 +158,7 @@ export default function VendorsFormComponent({
<InputNumber min={0} max={1} precision={2} step={0.01} />
</Form.Item>
<Form.Item label={t("vendors.fields.due_date")} name="due_date">
<InputNumber />
<InputNumber min={0} />
</Form.Item>
{
// <Form.Item

View File

@@ -100,6 +100,7 @@ export const QUERY_BODYSHOP = gql`
pbs_serialnumber
md_filehandlers
md_email_cc
timezone
employees {
user_email
id
@@ -197,6 +198,7 @@ export const UPDATE_SHOP = gql`
pbs_serialnumber
md_filehandlers
md_email_cc
timezone
employees {
id
first_name

View File

@@ -12,11 +12,7 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
ownr_ph1
ownr_ph2
ownr_ea
owner {
id
allow_text_message
preferred_contact
}
comment
plate_no
plate_st
v_vin
@@ -121,6 +117,7 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
id
status
ro_number
comment
ownr_fn
ownr_ln
category
@@ -193,6 +190,7 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
id
status
ro_number
comment
ownr_fn
category
ownr_ln
@@ -264,6 +262,7 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
jobs(where: { inproduction: { _eq: true } }) {
id
updated_at
comment
status
category
ro_number
@@ -489,6 +488,7 @@ export const GET_JOB_BY_PK = gql`
alt_transport
intakechecklist
invoice_final_note
comment
loss_desc
kmin
kmout
@@ -830,6 +830,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
ownr_co_nm
ownr_ph1
ownr_ph2
comment
ownr_ea
ca_gst_registrant
owner_owing
@@ -1034,7 +1035,7 @@ export const UPDATE_JOB = gql`
update_jobs(where: { id: { _eq: $jobId } }, _set: $job) {
returning {
id
comment
date_exported
status
alt_transport
@@ -1766,13 +1767,12 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
order_by: $order
where: { status: { _in: $statusList } }
) {
comment
ownr_fn
ownr_ln
ownr_co_nm
ownerid
ownr_ph1
ownr_ph2
ownr_ea
plate_no
plate_st
v_vin
@@ -1781,32 +1781,16 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
v_make_desc
v_color
vehicleid
actual_completion
actual_delivery
actual_in
id
ins_co_nm
ins_ct_fn
ins_ct_ln
ins_ph1
ins_ea
est_co_nm
est_ph1
est_ea
est_ct_fn
est_ct_ln
clm_no
clm_total
owner_owing
ro_number
po_number
scheduled_completion
scheduled_in
scheduled_delivery
status
updated_at
ded_amt
vehicleid
}
search_jobs_aggregate(
args: { search: $search }

View File

@@ -41,6 +41,9 @@ import UserActionTypes from "./user.types";
import axios from "axios";
import { messaging } from "../../firebase/firebase.utils";
import { getToken } from "firebase/messaging";
import moment from "moment-timezone";
export function* onEmailSignInStart() {
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
}
@@ -266,6 +269,12 @@ export function* onSetShopDetails() {
export function* SetAuthLevelFromShopDetails({ payload }) {
try {
const userEmail = yield select((state) => state.user.currentUser.email);
try {
console.log("Setting shop timezone.");
moment.tz.setDefault(payload.timezone);
} catch (error) {
console.log(error);
}
factory.client(payload.imexshopid);

View File

@@ -183,6 +183,7 @@
"federal_tax": "Federal Tax",
"iouexists": "An IOU exists that is associated to this RO.",
"local_tax": "Local Tax",
"markexported": "Mark Exported",
"markforreexport": "Mark for Re-export",
"new": "New Bill",
"noneselected": "No bill selected.",
@@ -197,6 +198,7 @@
"created": "Invoice added successfully.",
"deleted": "Bill deleted successfully.",
"exported": "Bill(s) exported successfully.",
"markexported": "Bill marked as exported.",
"reexport": "Bill marked for re-export."
},
"validation": {
@@ -483,6 +485,7 @@
"production_statuses": "Production Statuses"
},
"target_touchtime": "Target Touch Time",
"timezone": "Timezone",
"tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs",
"use_fippa": "Use FIPPA for Names on Generated Documents?",
"website": "Website",
@@ -1242,6 +1245,7 @@
"class": "Class",
"clm_no": "Claim #",
"clm_total": "Claim Total",
"comment": "Comment",
"customerowing": "Customer Owing",
"date_estimated": "Date Estimated",
"date_exported": "Exported",
@@ -1553,7 +1557,7 @@
"partstotal": "Parts Total (ex. Taxes)",
"plitooltips": {
"billtotal": "The total amount of all bill lines that have been posted against this RO (not including credits, taxes, or labor adjustments).",
"creditmemos": "The total amount of all credit memos entered. This amount does not reflect any parts returns created.",
"creditmemos": "The total amount of all returns created. This amount does not reflect credit memos that have been posted.",
"creditsnotreceived": "The total amount of returns created for this job that do not have a corresponding credit memo posted. An amount greater than $0 indicates that vendors have not provided requested credit memos.",
"discrep1": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Too many bills/bill lines that have been posted against this RO. Check to make sure every bill posted on this RO is correctly posted and assigned.</li>\n<li>You do not have the latest supplement imported, or, a supplement must be submitted and then imported.</li>\n<li>You have posted a bill line to labor.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>",
"discrep2": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Used an incorrect rate when deducting from labor.</li>\n<li>An outstanding imbalance higher in the reconciliation process.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>",
@@ -2150,6 +2154,7 @@
"bodypriority": "B/P",
"cardsettings": "Card Settings",
"clm_no": "Claim Number",
"comment": "Comment",
"compact": "Compact Cards",
"detailpriority": "D/P",
"employeeassignments": "Employee Assignments",
@@ -2315,9 +2320,12 @@
},
"labels": {
"asoftodaytarget": "As of Today",
"dailyactual": "Actual (D)",
"dailytarget": "Daily",
"monthlytarget": "Monthly",
"targets": "Targets",
"todateactual": "Actual (MTD)",
"weeklyactual": "Actual (W)",
"weeklytarget": "Weekly",
"workingdays": "Working Days / Month"
},
@@ -2588,7 +2596,7 @@
"country": "Country",
"discount": "Discount % (as decimal)",
"display_name": "Display Name",
"due_date": "Payment Due Date",
"due_date": "Payment Due Date (# of days)",
"email": "Contact Email",
"favorite": "Favorite?",
"lkq": "LKQ",

View File

@@ -183,6 +183,7 @@
"federal_tax": "",
"iouexists": "",
"local_tax": "",
"markexported": "",
"markforreexport": "",
"new": "",
"noneselected": "",
@@ -197,6 +198,7 @@
"created": "",
"deleted": "",
"exported": "",
"markexported": "",
"reexport": ""
},
"validation": {
@@ -483,6 +485,7 @@
"production_statuses": ""
},
"target_touchtime": "",
"timezone": "",
"tt_allow_post_to_invoiced": "",
"use_fippa": "",
"website": "",
@@ -1242,6 +1245,7 @@
"class": "",
"clm_no": "Reclamación #",
"clm_total": "Reclamar total",
"comment": "",
"customerowing": "Cliente debido",
"date_estimated": "Fecha estimada",
"date_exported": "Exportado",
@@ -2150,6 +2154,7 @@
"bodypriority": "",
"cardsettings": "",
"clm_no": "",
"comment": "",
"compact": "",
"detailpriority": "",
"employeeassignments": "",
@@ -2315,9 +2320,12 @@
},
"labels": {
"asoftodaytarget": "",
"dailyactual": "",
"dailytarget": "",
"monthlytarget": "",
"targets": "",
"todateactual": "",
"weeklyactual": "",
"weeklytarget": "",
"workingdays": ""
},

View File

@@ -183,6 +183,7 @@
"federal_tax": "",
"iouexists": "",
"local_tax": "",
"markexported": "",
"markforreexport": "",
"new": "",
"noneselected": "",
@@ -197,6 +198,7 @@
"created": "",
"deleted": "",
"exported": "",
"markexported": "",
"reexport": ""
},
"validation": {
@@ -483,6 +485,7 @@
"production_statuses": ""
},
"target_touchtime": "",
"timezone": "",
"tt_allow_post_to_invoiced": "",
"use_fippa": "",
"website": "",
@@ -1242,6 +1245,7 @@
"class": "",
"clm_no": "Prétendre #",
"clm_total": "Total réclamation",
"comment": "",
"customerowing": "Client propriétaire",
"date_estimated": "Date estimée",
"date_exported": "Exportés",
@@ -2150,6 +2154,7 @@
"bodypriority": "",
"cardsettings": "",
"clm_no": "",
"comment": "",
"compact": "",
"detailpriority": "",
"employeeassignments": "",
@@ -2315,9 +2320,12 @@
},
"labels": {
"asoftodaytarget": "",
"dailyactual": "",
"dailytarget": "",
"monthlytarget": "",
"targets": "",
"todateactual": "",
"weeklyactual": "",
"weeklytarget": "",
"workingdays": ""
},

View File

@@ -9460,7 +9460,14 @@ moment-business-days@^1.2.0:
resolved "https://registry.yarnpkg.com/moment-business-days/-/moment-business-days-1.2.0.tgz#6172f9f38dbf443c2f859baabeabbd2935f63d65"
integrity sha512-QJlceLfMSxy/jZSOgJYCKeKw+qGYHj8W0jMa/fYruyoJ85+bJuLRiYv5DIaflyuRipmYRfD4kDlSwVYteLN+Jw==
moment@^2.24.0, moment@^2.25.3:
moment-timezone@^0.5.34:
version "0.5.34"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0", moment@^2.24.0, moment@^2.25.3:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==

View File

@@ -867,6 +867,7 @@
- target_touchtime
- template_header
- textid
- timezone
- tt_allow_post_to_invoiced
- updated_at
- use_fippa
@@ -939,6 +940,7 @@
- state
- state_tax_id
- target_touchtime
- timezone
- tt_allow_post_to_invoiced
- updated_at
- use_fippa
@@ -2707,6 +2709,7 @@
- clm_title
- clm_total
- clm_zip
- comment
- converted
- created_at
- cust_pr
@@ -2963,6 +2966,7 @@
- clm_title
- clm_total
- clm_zip
- comment
- converted
- created_at
- cust_pr
@@ -3229,6 +3233,7 @@
- clm_title
- clm_total
- clm_zip
- comment
- converted
- created_at
- cust_pr

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "timezone" text
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "timezone" text
null;

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."jobs" add column "comment" text
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."jobs" add column "comment" text
null;

View File

@@ -37,6 +37,7 @@
"intuit-oauth": "^4.0.0",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",
"node-mailjet": "^3.3.4",
"node-quickbooks": "^2.0.39",
"nodemailer": "^6.7.1",

View File

@@ -14,9 +14,10 @@ const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants");
const CalculateAllocations =
require("../../cdk/cdk-calculate-allocations").default;
const CdkBase = require("../../web-sockets/web-socket");
const moment = require("moment");
const moment = require("moment-timezone");
const Dinero = require("dinero.js");
const axios = AxiosLib.create();
axios.interceptors.request.use((x) => {
const socket = x.socket;
@@ -553,7 +554,9 @@ async function InsertAccountPostingData(socket) {
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).toISOString(),
InvoiceDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
};
wips.push(item);
}
@@ -567,7 +570,9 @@ async function InsertAccountPostingData(socket) {
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).toISOString(),
InvoiceDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
};
wips.push(item);
@@ -578,7 +583,9 @@ async function InsertAccountPostingData(socket) {
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).toISOString(),
InvoiceDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
};
wips.push(itemWip);
//Add to the WIP account.
@@ -593,7 +600,9 @@ async function InsertAccountPostingData(socket) {
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).toISOString(),
InvoiceDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
};
wips.push(item2);
}
@@ -609,7 +618,9 @@ async function InsertAccountPostingData(socket) {
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).toISOString(),
InvoiceDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
};
wips.push(item);
@@ -622,7 +633,9 @@ async function InsertAccountPostingData(socket) {
Posting: {
Reference: socket.JobData.ro_number,
JournalCode: socket.txEnvelope.journal,
TransactionDate: moment(socket.JobData.date_invoiced).toISOString(), //"0001-01-01T00:00:00.0000000Z",
TransactionDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(), //"0001-01-01T00:00:00.0000000Z",
Description: socket.txEnvelope.story,
//AdditionalInfo: "String",
Source: "ImEX Online",

View File

@@ -17,7 +17,7 @@ const {
setNewRefreshToken,
} = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const moment = require("moment");
const moment = require("moment-timezone");
const GraphQLClient = require("graphql-request").GraphQLClient;
const findTaxCode = require("../qb-receivables-lines").findTaxCode;
@@ -178,8 +178,15 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
VendorRef: {
value: vendor.Id,
},
TxnDate: moment(bill.date).format("YYYY-MM-DD"),
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
TxnDate: moment(bill.date)
.tz(bill.job.bodyshop.timezone)
.format("YYYY-MM-DD"),
...(bill.vendor.due_date && {
DueDate: moment(bill.date)
.tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD"),
}),
DocNumber: bill.invoice_number,
//...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),

View File

@@ -15,7 +15,7 @@ const {
setNewRefreshToken,
} = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const moment = require("moment");
const moment = require("moment-timezone");
const GraphQLClient = require("graphql-request").GraphQLClient;
const {
QueryInsuranceCo,
@@ -137,14 +137,22 @@ exports.default = async (req, res) => {
}
if (payment.amount > 0) {
await InsertPayment(oauthClient, qbo_realmId, req, payment, jobTier);
await InsertPayment(
oauthClient,
qbo_realmId,
req,
payment,
jobTier,
bodyshop
);
} else {
await InsertCreditMemo(
oauthClient,
qbo_realmId,
req,
payment,
jobTier
jobTier,
bodyshop
);
}
ret.push({ paymentid: payment.id, success: true });
@@ -178,7 +186,8 @@ async function InsertPayment(
qbo_realmId,
req,
payment,
parentRef
parentRef,
bodyshop
) {
const { paymentMethods, invoices } = await QueryMetaData(
oauthClient,
@@ -198,7 +207,7 @@ async function InsertPayment(
CustomerRef: {
value: parentRef.Id,
},
TxnDate: moment(payment.date).format("YYYY-MM-DD"),
TxnDate: moment(payment.date).tz(bodyshop.timezone).format("YYYY-MM-DD"),
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
DocNumber: payment.paymentnum,
TotalAmt: Dinero({
@@ -362,7 +371,8 @@ async function InsertCreditMemo(
qbo_realmId,
req,
payment,
parentRef
parentRef,
bodyshop
) {
const { paymentMethods, invoices, items, taxCodes } = await QueryMetaData(
oauthClient,
@@ -382,7 +392,7 @@ async function InsertCreditMemo(
CustomerRef: {
value: parentRef.Id,
},
TxnDate: moment(payment.date).format("YYYY-MM-DD"),
TxnDate: moment(payment.date).tz(bodyshop.timezone).format("YYYY-MM-DD"),
DocNumber: payment.paymentnum,
...(invoices && invoices[0]
? { InvoiceRef: { value: invoices[0].Id } }

View File

@@ -17,7 +17,7 @@ const {
} = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const CreateInvoiceLines = require("../qb-receivables-lines").default;
const moment = require("moment");
const moment = require("moment-timezone");
const GraphQLClient = require("graphql-request").GraphQLClient;
const { generateOwnerTier } = require("../qbxml/qbxml-utils");
@@ -440,7 +440,9 @@ async function InsertInvoice(
const invoiceObj = {
Line: InvoiceLineAdd,
TxnDate: moment(job.date_invoiced).format("YYYY-MM-DD"),
TxnDate: moment(job.date_invoiced)
.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
DocNumber: job.ro_number,
...(job.class ? { ClassRef: { value: classes[job.class] } } : {}),
CustomerMemo: {

View File

@@ -5,7 +5,7 @@ const queries = require("../../graphql-client/queries");
const Dinero = require("dinero.js");
var builder = require("xmlbuilder2");
const QbXmlUtils = require("./qbxml-utils");
const moment = require("moment");
const moment = require("moment-timezone");
const logger = require("../../utils/logger");
require("dotenv").config({
path: path.resolve(
@@ -72,9 +72,15 @@ const generateBill = (bill) => {
VendorRef: {
FullName: bill.vendor.name,
},
TxnDate: moment(bill.date).format("YYYY-MM-DD"),
DueDate:
bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
TxnDate: moment(bill.date)
.tz(bill.job.bodyshop.timezone)
.format("YYYY-MM-DD"),
...(bill.vendor.due_date && {
DueDate: moment(bill.date)
.tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD"),
}),
RefNumber: bill.invoice_number,
Memo: `RO ${bill.job.ro_number || ""}`,
ExpenseLineAdd: bill.billlines.map((il) =>

View File

@@ -4,7 +4,7 @@ const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
const queries = require("../../graphql-client/queries");
const Dinero = require("dinero.js");
var builder = require("xmlbuilder2");
const moment = require("moment");
const moment = require("moment-timezone");
const QbXmlUtils = require("./qbxml-utils");
const QbxmlReceivables = require("./qbxml-receivables");
const logger = require("../../utils/logger");
@@ -84,7 +84,7 @@ exports.default = async (req, res) => {
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0"],
qbxml: generatePayment(i, isThreeTier, twoTierPref),
qbxml: generatePayment(i, isThreeTier, twoTierPref, bodyshop),
});
});
@@ -101,7 +101,7 @@ exports.default = async (req, res) => {
}
};
const generatePayment = (payment, isThreeTier, twoTierPref) => {
const generatePayment = (payment, isThreeTier, twoTierPref, bodyshop) => {
let paymentQbxmlObj;
if (payment.amount > 0) {
paymentQbxmlObj = {
@@ -128,7 +128,9 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
FullName:
payment.job.bodyshop.md_responsibility_centers.ar.accountname,
},
TxnDate: moment(payment.date).format("YYYY-MM-DD"), //Trim String
TxnDate: moment(payment.date)
.tz(bodyshop.timezone)
.format("YYYY-MM-DD"), //Trim String
RefNumber: payment.paymentnum || payment.transactionid,
TotalAmount: Dinero({
amount: Math.round(payment.amount * 100),
@@ -172,7 +174,9 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
FullName:
payment.job.bodyshop.md_responsibility_centers.ar.accountname,
},
TxnDate: moment(payment.date).format("YYYY-MM-DD"), //Trim String
TxnDate: moment(payment.date)
.tz(bodyshop.timezone)
.format("YYYY-MM-DD"), //Trim String
RefNumber:
payment.paymentnum || payment.stripeid || payment.transactionid,

View File

@@ -3,7 +3,7 @@ const path = require("path");
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
const queries = require("../../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment");
const moment = require("moment-timezone");
var builder = require("xmlbuilder2");
const QbXmlUtils = require("./qbxml-utils");
const logger = require("../../utils/logger");
@@ -254,7 +254,9 @@ const generateInvoiceQbxml = (
? { ClassRef: { FullName: jobs_by_pk.class } }
: {}),
TxnDate: moment(jobs_by_pk.date_invoiced).format("YYYY-MM-DD"),
TxnDate: moment(jobs_by_pk.date_invoiced)
.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
RefNumber: jobs_by_pk.ro_number,
BillAddress: {
Addr1: jobs_by_pk.ownr_co_nm

View File

@@ -13,7 +13,7 @@ const CdkWsdl = require("./cdk-wsdl").default;
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
const CalcualteAllocations = require("./cdk-calculate-allocations").default;
const moment = require("moment");
const moment = require("moment-timezone");
const replaceSpecialRegex = /[^a-zA-Z0-9 .,\n #]+/g;
@@ -716,6 +716,7 @@ async function InsertDmsVehicle(socket) {
dealerNumber: socket.JobData.bodyshop.cdk_dealerid,
...(socket.txEnvelope.inservicedate && {
inServiceDate: moment(socket.txEnvelope.inservicedate)
.tz(socket.JobData.bodyshop.timezone)
.startOf("day")
.toISOString(),
}),
@@ -723,7 +724,9 @@ async function InsertDmsVehicle(socket) {
},
manufacturer: {},
vehicle: {
deliveryDate: moment().format("YYYYMMDD"),
deliveryDate: moment()
.tz(socket.JobData.bodyshop.timezone)
.format("YYYYMMDD"),
licensePlateNo: socket.JobData.plate_no,
make: socket.txEnvelope.dms_make,
modelAbrev: socket.txEnvelope.dms_model,
@@ -850,14 +853,16 @@ async function UpdateDmsVehicle(socket) {
inServiceDate: moment(
socket.DMSVeh.dealer.inServiceDate ||
socket.txEnvelope.inservicedate
).toISOString(),
)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
}),
},
vehicle: {
...socket.DMSVeh.vehicle,
deliveryDate: moment(
socket.DMSVeh.vehicle.deliveryDate
).toISOString(),
deliveryDate: moment(socket.DMSVeh.vehicle.deliveryDate)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
},
owners: ids,
},
@@ -912,10 +917,18 @@ async function InsertServiceVehicleHistory(socket) {
vehID: socket.DMSVid.vehiclesVehId,
roNumber: socket.JobData.ro_number.match(/\d+/g),
mileage: socket.txEnvelope.kmout,
openDate: moment(socket.JobData.actual_in).format("YYYY-MM-DD"),
openTime: moment(socket.JobData.actual_in).format("HH:mm:ss"),
closeDate: moment(socket.JobData.invoice_date).format("YYYY-MM-DD"),
closeTime: moment(socket.JobData.invoice_date).format("HH:mm:ss"),
openDate: moment(socket.JobData.actual_in)
.tz(socket.JobData.bodyshop.timezone)
.format("YYYY-MM-DD"),
openTime: moment(socket.JobData.actual_in)
.tz(socket.JobData.bodyshop.timezone)
.format("HH:mm:ss"),
closeDate: moment(socket.JobData.invoice_date)
.tz(socket.JobData.bodyshop.timezone)
.format("YYYY-MM-DD"),
closeTime: moment(socket.JobData.invoice_date)
.tz(socket.JobData.bodyshop.timezone)
.format("HH:mm:ss"),
comments: socket.txEnvelope.story,
cashierID: socket.JobData.bodyshop.cdk_configuration.cashierid,
},
@@ -966,7 +979,9 @@ async function InsertDmsStartWip(socket) {
arg0: CDK_CREDENTIALS,
arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
arg2: {
acctgDate: moment().format("YYYY-MM-DD"),
acctgDate: moment()
.tz(socket.JobData.bodyshop.timezone)
.format("YYYY-MM-DD"),
//socket.JobData.invoice_date
desc:
socket.txEnvelope.story &&

View File

@@ -1,7 +1,7 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment");
const moment = require("moment-timezone");
const fs = require("fs");
const _ = require("lodash");
@@ -66,24 +66,35 @@ exports.default = async (req, res) => {
// },
RepairEvent: {
CreatedDateTime: (job.date_open
? moment(job.date_open)
? moment(job.date_open).tz(bodyshop.timezone)
: moment()
).format(momentFormat),
ArrivalDateTime:
job.actual_in && moment(job.actual_in).format(momentFormat),
job.actual_in &&
moment(job.actual_in)
.tz(bodyshop.timezone)
.format(momentFormat),
ArrivalOdometerReading: job.kmin,
TargetCompletionDateTime:
job.scheduled_completion &&
moment(job.scheduled_completion).format(momentFormat),
moment(job.scheduled_completion)
.tz(bodyshop.timezone)
.format(momentFormat),
ActualCompletionDateTime:
job.actual_completion &&
moment(job.actual_completion).format(momentFormat),
moment(job.actual_completion)
.tz(bodyshop.timezone)
.format(momentFormat),
ActualPickUpDateTime:
job.actual_delivery &&
moment(job.actual_delivery).format(momentFormat),
moment(job.actual_delivery)
.tz(bodyshop.timezone)
.format(momentFormat),
CloseDateTime:
job.date_exported &&
moment(job.date_exported).format(momentFormat),
moment(job.date_exported)
.tz(bodyshop.timezone)
.format(momentFormat),
},
},
RepairOrderHeader: {
@@ -254,7 +265,8 @@ exports.default = async (req, res) => {
//ProductionDate: "2009-10",
ModelYear:
parseInt(job.v_model_yr) < 1900
? parseInt(job.v_model_yr) < moment().format("YY")
? parseInt(job.v_model_yr) <
moment().tz(bodyshop.timezone).format("YY")
? `20${job.v_model_yr}`
: `19${job.v_model_yr}`
: job.v_model_yr,
@@ -288,7 +300,9 @@ exports.default = async (req, res) => {
Facts: {
LossDateTime:
job.loss_date &&
moment(job.loss_date).format(momentFormat),
moment(job.loss_date)
.tz(bodyshop.timezone)
.format(momentFormat),
LossDescCode: "Collision",
PrimaryPOI: {
POICode: job.area_of_damage && job.area_of_damage.impact1,
@@ -750,13 +764,17 @@ exports.default = async (req, res) => {
ProductionStatus: {
ProductionStage: {
ProductionStageCode: GetProductionStageCode(job, bodyshop),
ProductionStageDateTime: moment().format(momentFormat),
ProductionStageDateTime: moment()
.tz(bodyshop.timezone)
.format(momentFormat),
// ProductionStageStatusComment:
// "Going to be painted this afternoon",
},
RepairStatus: {
RepairStatusCode: GetRepairStatusCode(job),
RepairStatusDateTime: moment().format(momentFormat),
RepairStatusDateTime: moment()
.tz(bodyshop.timezone)
.format(momentFormat),
// RepairStatusMemo: "Waiting on back ordered parts",
},
},

View File

@@ -1,7 +1,7 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment");
const moment = require("moment-timezone");
var builder = require("xmlbuilder2");
const _ = require("lodash");
const logger = require("../utils/logger");
@@ -267,27 +267,50 @@ const CreateRepairOrderTag = (job, errorCallback) => {
},
Dates: {
DateofLoss:
(job.loss_date && moment(job.loss_date).format(AhDateFormat)) || "",
(job.loss_date &&
moment(job.loss_date)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
InitialCustomerContactDate: null,
FirstFollowUpDate: null,
ReferralDate: null,
EstimateAppointmentDate: null,
SecondFollowUpDate: null,
AssignedDate:
(job.asgn_date && moment(job.asgn_date).format(AhDateFormat)) || "",
(job.asgn_date &&
moment(job.asgn_date)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
EstComplete: null,
CustomerAuthorizationDate: null,
InsuranceAuthorizationDate: null,
DateOpened:
(job.date_open && moment(job.date_open).format(AhDateFormat)) || "",
(job.date_open &&
moment(job.date_open)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
ScheduledArrivalDate:
(job.scheduled_in && moment(job.scheduled_in).format(AhDateFormat)) ||
(job.scheduled_in &&
moment(job.scheduled_in)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
CarinShop:
(job.actual_in && moment(job.actual_in).format(AhDateFormat)) || "",
(job.actual_in &&
moment(job.actual_in)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
InsInspDate: null,
StartDate:
(job.actual_in && moment(job.actual_in).format(AhDateFormat)) || "",
(job.actual_in &&
moment(job.actual_in)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
PartsOrder: null,
TeardownHold: null,
SupplementSubmittedDate: null,
@@ -303,24 +326,35 @@ const CreateRepairOrderTag = (job, errorCallback) => {
//InsuranceTargetOut: null,
CarComplete:
(job.actual_completion &&
moment(job.actual_completion).format(AhDateFormat)) ||
moment(job.actual_completion)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
DeliveryAppointmentDate:
(job.scheduled_delivery &&
moment(job.scheduled_delivery).format(AhDateFormat)) ||
moment(job.scheduled_delivery)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
DateClosed:
(job.date_invoiced &&
moment(job.date_invoiced).format(AhDateFormat)) ||
moment(job.date_invoiced)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
CustomerPaidInFullDate: null,
InsurancePaidInFullDate: null,
CustPickup:
(job.actual_delivery &&
moment(job.actual_delivery).format(AhDateFormat)) ||
moment(job.actual_delivery)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
AccountPostedDate:
job.date_exported && moment(job.date_exported).format(AhDateFormat),
job.date_exported &&
moment(job.date_exported)
.tz(job.bodyshop.timezone)
.format(AhDateFormat),
CSIProcessedDate: null,
ThankYouLetterSent: null,
AdditionalFollowUpDate: null,
@@ -760,9 +794,9 @@ const GenerateDetailLines = (line, statuses) => {
MarkUp: null,
OrderedOn:
(line.parts_order_lines[0] &&
moment(line.parts_order_lines[0].parts_order.order_date).format(
AhDateFormat
)) ||
moment(line.parts_order_lines[0].parts_order.order_date)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
OriginalCost: null,
OriginalInvoiceNumber: null,
@@ -786,7 +820,9 @@ const GenerateDetailLines = (line, statuses) => {
ExpectedOn: null,
ReceivedOn:
line.billlines[0] &&
moment(line.billlines[0].bill.date).format(AhDateFormat),
moment(line.billlines[0].bill.date)
.tz(job.bodyshop.timezone)
.format(AhDateFormat),
OrderedBy: null,
ShipVia: null,
VendorContact: null,

View File

@@ -178,6 +178,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
md_responsibility_centers
accountingconfig
md_ins_cos
timezone
}
}
`;
@@ -247,6 +248,7 @@ query QUERY_JOBS_FOR_CDK_EXPORT($id: uuid!) {
accountingconfig
cdk_dealerid
cdk_configuration
timezone
}
owner {
accountingid
@@ -341,6 +343,7 @@ query QUERY_JOBS_FOR_PBS_EXPORT($id: uuid!) {
accountingconfig
pbs_serialnumber
pbs_configuration
timezone
}
owner {
id
@@ -385,6 +388,7 @@ query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) {
class
bodyshop{
md_responsibility_centers
timezone
}
}
billlines{
@@ -400,6 +404,7 @@ query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) {
vendor{
id
name
due_date
}
}
}
@@ -411,6 +416,7 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
id
md_responsibility_centers
accountingconfig
timezone
}
payments(where: {id: {_in: $payments}}) {
id
@@ -460,6 +466,7 @@ exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: t
ssbuckets
target_touchtime
workingdays
timezone
}
jobhrs: joblines_aggregate(where: {removed: {_eq: false}}) {
aggregate {
@@ -558,7 +565,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
autohouseid
md_responsibility_centers
jc_hourly_rates
timezone
}
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {shopid: {_eq: $bodyshopid}}]}) {
id
@@ -828,6 +835,7 @@ exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) {
loss_desc
kmin
kmout
comment
referral_source
referral_source_extra
unit_number
@@ -1234,6 +1242,7 @@ exports.GET_AUTOHOUSE_SHOPS = `query GET_AUTOHOUSE_SHOPS {
md_responsibility_centers
jc_hourly_rates
imexshopid
timezone
}
}
`;

View File

@@ -2,7 +2,7 @@ const GraphQLClient = require("graphql-request").GraphQLClient;
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment");
const moment = require("moment-timezone");
const logger = require("../utils/logger");
const _ = require("lodash");
require("dotenv").config({
@@ -32,7 +32,7 @@ exports.job = async (req, res) => {
});
const { jobs_by_pk, blockedDays, prodJobs, arrJobs, compJobs } = result;
const { ssbuckets, workingdays } = result.jobs_by_pk.bodyshop;
const { ssbuckets, workingdays, timezone } = result.jobs_by_pk.bodyshop;
const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs;
const JobBucket = ssbuckets.filter(
@@ -68,7 +68,9 @@ exports.job = async (req, res) => {
);
filteredArrJobs.forEach((item) => {
const itemDate = moment(item.scheduled_in).format("yyyy-MM-DD");
const itemDate = moment(item.scheduled_in)
.tz(timezone)
.format("yyyy-MM-DD");
if (!!load[itemDate]) {
load[itemDate].hoursIn =
(load[itemDate].hoursIn || 0) +
@@ -100,18 +102,17 @@ exports.job = async (req, res) => {
//Job isn't found in production or coming in.
//is it going today or scheduled to go today?
if (
moment(item.actual_completion || item.scheduled_completion).isSame(
moment(),
"day"
)
moment(item.actual_completion || item.scheduled_completion)
.tz(timezone)
.isSame(moment().tz(timezone), "day")
) {
console.log("Job is going today anyways, ignore it.", item);
return;
}
if (
moment(item.actual_completion || item.scheduled_completion).isBefore(
moment(),
moment(item.actual_completion || item.scheduled_completion).tz(timezone).isBefore(
moment().tz(timezone),
"day"
)
) {
@@ -127,7 +128,7 @@ exports.job = async (req, res) => {
} else {
const itemDate = moment(
item.actual_completion || item.scheduled_completion
).format("yyyy-MM-DD");
).tz(timezone).format("yyyy-MM-DD");
if (!!load[itemDate]) {
load[itemDate].hoursOut =
(load[itemDate].hoursOut || 0) +
@@ -146,20 +147,20 @@ exports.job = async (req, res) => {
});
//Propagate the expected load to each day.
const yesterday = moment().subtract(1, "day");
const today = moment().startOf("day");
const yesterday = moment().tz(timezone).subtract(1, "day");
const today = moment().tz(timezone).startOf("day");
const end = moment.max([
...filteredArrJobs.map((a) => moment(a.scheduled_in)),
const end = moment.tz(timezone).max([
...filteredArrJobs.map((a) => moment(a.scheduled_in).tz(timezone)),
...filteredCompJobs
.map((p) => moment(p.actual_completion || p.scheduled_completion))
.map((p) => moment(p.actual_completion || p.scheduled_completion).tz(timezone))
.filter((p) => p.isValid() && p.isAfter(yesterday)),
moment().add(5, "days"),
moment().tz(timezone).add(5, "days"),
]);
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)
const current = moment(today).tz(timezone).add(day, "days").format("yyyy-MM-DD");
const prev = moment(today).tz(timezone)
.add(day - 1, "days")
.format("yyyy-MM-DD");
if (!!!load[current]) {
@@ -187,7 +188,7 @@ exports.job = async (req, res) => {
blockedDays.forEach((b) => {
//Find it in the load, set it as blocked.
const startIsoFormat = moment(b.start).format("YYYY-MM-DD");
const startIsoFormat = moment(b.start).tz(timezone).format("YYYY-MM-DD");
if (load[startIsoFormat]) load[startIsoFormat].blocked = true;
else {
load[startIsoFormat] = { blocked: true };
@@ -198,12 +199,12 @@ exports.job = async (req, res) => {
const possibleDates = [];
delete load.productionTotal;
const loadKeys = Object.keys(load).sort((a, b) =>
moment(a).isAfter(moment(b)) ? 1 : -1
moment(a).tz(timezone).isAfter(moment(b).tz(timezone)) ? 1 : -1
);
loadKeys.forEach((loadKey) => {
const isShopOpen =
(workingdays[dayOfWeekMapper(moment(loadKey).day())] || false) &&
(workingdays[dayOfWeekMapper(moment(loadKey).tz(timezone).day())] || false) &&
!load[loadKey].blocked;
if (

View File

@@ -2740,7 +2740,14 @@ mkdirp@^0.5.1:
dependencies:
minimist "^1.2.5"
moment@^2.29.1:
moment-timezone@^0.5.34:
version "0.5.34"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0", moment@^2.29.1:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==