Merged in release/2022-04-02 (pull request #439)

Test
This commit is contained in:
Patrick Fic
2022-04-01 21:05:31 +00:00
30 changed files with 32161 additions and 872 deletions

View File

@@ -2354,6 +2354,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>is_credit_memo_short</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>local_tax_rate</name>
<definition_loaded>false</definition_loaded>
@@ -26589,6 +26610,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>parts_received</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>parts_tax_rates</name>
<definition_loaded>false</definition_loaded>
@@ -31189,6 +31231,37 @@
</folder_node>
</children>
</folder_node>
<folder_node>
<name>owner</name>
<children>
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>noownerinfo</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>owners</name>
<children>
@@ -34925,6 +34998,27 @@
</concept_node>
</children>
</folder_node>
<concept_node>
<name>lag_time_ro</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>mechanical_authorization</name>
<definition_loaded>false</definition_loaded>
@@ -36487,6 +36581,58 @@
</translation>
</translations>
</concept_node>
<folder_node>
<name>bodyshop</name>
<children>
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>qbo_departmentid</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>qbo_usa</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<concept_node>
<name>cardsettings</name>
<definition_loaded>false</definition_loaded>
@@ -36844,6 +36990,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>stickyheader</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>sublets</name>
<definition_loaded>false</definition_loaded>

View File

@@ -4,40 +4,40 @@
"private": true,
"proxy": "http://localhost:4000",
"dependencies": {
"@apollo/client": "^3.5.6",
"@apollo/client": "^3.5.10",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.3",
"@fingerprintjs/fingerprintjs": "^3.3.1",
"@sentry/react": "^6.16.1",
"@sentry/tracing": "^6.16.1",
"@splitsoftware/splitio-react": "^1.3.0",
"@fingerprintjs/fingerprintjs": "^3.3.3",
"@sentry/react": "^6.19.3",
"@sentry/tracing": "^6.19.3",
"@splitsoftware/splitio-react": "^1.3.1-rc.1",
"@stripe/react-stripe-js": "^1.7.0",
"@stripe/stripe-js": "^1.22.0",
"@tanem/react-nprogress": "^3.0.82",
"antd": "^4.17.4",
"@stripe/stripe-js": "^1.26.0",
"@tanem/react-nprogress": "^4.0.12",
"antd": "^4.19.3",
"apollo-link-logger": "^2.0.0",
"axios": "^0.24.0",
"axios": "^0.26.1",
"craco-less": "^1.20.0",
"dinero.js": "^1.9.1",
"dotenv": "^10.0.0",
"dotenv": "^16.0.0",
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^9.6.1",
"graphql": "^16.2.0",
"i18next": "^21.6.3",
"i18next-browser-languagedetector": "^6.1.2",
"jsoneditor": "^9.5.8",
"firebase": "^9.6.10",
"graphql": "^16.3.0",
"i18next": "^21.6.14",
"i18next-browser-languagedetector": "^6.1.4",
"jsoneditor": "^9.7.4",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.44",
"logrocket": "^2.1.2",
"markerjs2": "^2.17.2",
"libphonenumber-js": "^1.9.50",
"logrocket": "^2.2.1",
"markerjs2": "^2.20.0",
"moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34",
"phone": "^3.1.10",
"phone": "^3.1.14",
"preval.macro": "^5.0.0",
"prop-types": "^15.7.2",
"query-string": "^7.0.1",
"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",
@@ -45,41 +45,42 @@
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-drag-listview": "^0.1.8",
"react-drag-listview": "^0.1.9",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.0",
"react-i18next": "^11.15.1",
"react-grid-layout": "^1.3.4",
"react-i18next": "^11.16.2",
"react-icons": "^4.3.1",
"react-number-format": "^4.9.0",
"react-redux": "^7.2.6",
"react-number-format": "^4.9.1",
"react-redux": "^7.2.7",
"react-resizable": "^3.0.4",
"react-router-dom": "^5.3.0",
"react-scripts": "^4.0.3",
"react-sticky": "^6.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3",
"recharts": "^2.1.8",
"recharts": "^2.1.9",
"redux": "^4.1.2",
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2",
"reselect": "^4.1.5",
"sass": "^1.45.0",
"socket.io-client": "^4.4.0",
"styled-components": "^5.3.3",
"sass": "^1.49.10",
"socket.io-client": "^4.4.1",
"styled-components": "^5.3.5",
"subscriptions-transport-ws": "^0.11.0",
"web-vitals": "^2.1.2",
"workbox-background-sync": "^6.4.2",
"workbox-broadcast-update": "^6.4.2",
"workbox-cacheable-response": "^6.4.2",
"workbox-core": "^6.4.2",
"workbox-expiration": "^6.4.2",
"workbox-google-analytics": "^6.4.2",
"workbox-navigation-preload": "^6.4.2",
"workbox-precaching": "^6.4.2",
"workbox-range-requests": "^6.4.2",
"workbox-routing": "^6.4.2",
"workbox-strategies": "^6.4.2",
"workbox-streams": "^6.4.2",
"web-vitals": "^2.1.4",
"workbox-background-sync": "^6.5.2",
"workbox-broadcast-update": "^6.5.2",
"workbox-cacheable-response": "^6.5.2",
"workbox-core": "^6.5.2",
"workbox-expiration": "^6.5.2",
"workbox-google-analytics": "^6.5.2",
"workbox-navigation-preload": "^6.5.2",
"workbox-precaching": "^6.5.2",
"workbox-range-requests": "^6.5.2",
"workbox-routing": "^6.5.2",
"workbox-strategies": "^6.5.2",
"workbox-streams": "^6.5.2",
"yauzl": "^2.10.0"
},
"scripts": {
@@ -116,11 +117,11 @@
"react-error-overlay": "6.0.9"
},
"devDependencies": {
"@sentry/webpack-plugin": "^1.18.3",
"@sentry/webpack-plugin": "^1.18.8",
"@testing-library/cypress": "^8.0.2",
"cypress": "^9.1.1",
"cypress": "^9.5.3",
"eslint-plugin-cypress": "^2.12.1",
"react-error-overlay": "6.0.9",
"react-error-overlay": "6.0.10",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.2"
}

View File

@@ -162,10 +162,19 @@ export function AccountingPayablesTableComponent({
const dataSource = state.search
? payments.filter(
(v) =>
(v.vendor.name || "")
(v.paymentnum || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(v.invoice_number || "")
((v.job && v.job.ro_number) || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
((v.job && v.job.ownr_fn) || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
((v.job && v.job.ownr_ln) || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
((v.job && v.job.ownr_co_nm) || "")
.toLowerCase()
.includes(state.search.toLowerCase())
)

View File

@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { logImEXEvent } from "../../firebase/firebase.utils";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import { alphaSort, dateSort } from "../../utils/sorters";
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
@@ -12,6 +12,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { DateFormatter } from "../../utils/DateFormatter";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -62,6 +63,18 @@ export function AccountingReceivablesTableComponent({
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
},
{
title: t("jobs.fields.date_invoiced"),
dataIndex: "date_invoiced",
key: "date_invoiced",
sorter: (a, b) => dateSort(a.date_invoiced, b.date_invoiced),
sortOrder:
state.sortedInfo.columnKey === "date_invoiced" &&
state.sortedInfo.order,
render: (text, record) => (
<DateFormatter>{record.date_invoiced}</DateFormatter>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",

View File

@@ -22,7 +22,7 @@ export default function JobReconciliationBillsTable({
dataIndex: "line_desc",
key: "line_desc",
ellipsis: true,
minWidth: "65rem",
width: "10rem",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
@@ -72,11 +72,11 @@ export default function JobReconciliationBillsTable({
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
},
{
title: t("bills.fields.is_credit_memo"),
title: t("bills.fields.is_credit_memo_short"),
dataIndex: "is_credit_memo",
key: "is_credit_memo",
sorter: (a, b) => a.bill.is_credit_memo - b.bill.is_credit_memo,
width: "8rem",
width: "3rem",
sortOrder:
state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order,

View File

@@ -1,6 +1,5 @@
import {
Col,
Divider,
Form,
Input,
InputNumber,
@@ -23,8 +22,8 @@ import FormItemPhone, {
} from "../form-items-formatted/phone-form-item.component";
import Car from "../job-damage-visual/job-damage-visual.component";
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
import FormRow from "../layout-form-row/layout-form-row.component";
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
import FormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
@@ -220,15 +219,8 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
)}
</Col>
</Row>
<Divider
orientation="left"
type="horizontal"
style={{ marginTop: ".8rem", float: "right" }}
>
{t("jobs.forms.appraiserinfo")}
</Divider>
<FormRow noDivider>
<FormRow header={t("jobs.forms.appraiserinfo")}>
<Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm">
<Input disabled={jobRO} />
</Form.Item>

View File

@@ -63,10 +63,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
${job.v_make_desc || ""}
${job.v_model_desc || ""}`.trim();
console.log(
"🚀 ~ file: jobs-detail-header.component.jsx ~ line 64 ~ vehicleTitle",
vehicleTitle.length
);
const ownerTitle = `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`.trim();
return (
<Row gutter={[16, 16]} style={{ alignItems: "stretch" }}>
<Col {...colSpan}>
@@ -159,9 +160,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
style={{ height: "100%" }}
title={
<Link to={disabled ? "#" : `/manage/owners/${job.owner.id}`}>
{`${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`}
{ownerTitle.length > 0
? ownerTitle
: t("owner.labels.noownerinfo")}
</Link>
}
>

View File

@@ -425,8 +425,6 @@ export function PartsOrderListTableComponent({
placement="right"
onClose={() => handleOnRowClick(null)}
visible={selectedpartsorder}
//getContainer={false}
style={{ position: "absolute" }}
closable
width={drawerPercentage}
>

View File

@@ -61,20 +61,22 @@ export default function ProductionBoardCard(
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
<span style={{ fontWeight: "bolder" }}>
{card.ro_number || t("general.labels.na")}
<Link
to={
technician
? `/tech/joblookup?selected=${card.id}`
: `/manage/jobs/${card.id}`
}
>
{card.ro_number || t("general.labels.na")}
</Link>
</span>
</Space>
}
extra={
technician ? (
<Link to={`/tech/joblookup?selected=${card.id}`}>
<EyeFilled />
</Link>
) : (
<Link to={`/manage/jobs/${card.id}`}>
<EyeFilled />
</Link>
)
<Link to={{ search: `?selected=${card.id}` }}>
<EyeFilled />
</Link>
}
>
<Row>

View File

@@ -131,6 +131,13 @@ export default function ProductionBoardKanbanCardSettings({
>
<Switch />
</Form.Item>
<Form.Item
valuePropName="checked"
label={t("production.labels.stickyheader")}
name="stickyheader"
>
<Switch />
</Form.Item>
</Col>
</Row>
</Form>

View File

@@ -1,26 +1,27 @@
import { SyncOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import Board, { moveCard } from "@asseinfo/react-kanban";
//import "@asseinfo/react-kanban/dist/styles.css";
import "./production-board-kanban.styles.scss";
import { SyncOutlined } from "@ant-design/icons";
import { Grid, notification, Button, PageHeader, Space, Statistic } from "antd";
import { Button, Grid, notification, PageHeader, Space, Statistic } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Sticky, StickyContainer } from "react-sticky";
import { createStructuredSelector } from "reselect";
import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ProductionBoardCard from "../production-board-kanban-card/production-board-kanban-card.component";
import { createBoardData } from "./production-board-kanban.utils.js";
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
import styled from "styled-components";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
import ProductionBoardCard from "../production-board-kanban-card/production-board-kanban-card.component";
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
//import "@asseinfo/react-kanban/dist/styles.css";
import "./production-board-kanban.styles.scss";
import { createBoardData } from "./production-board-kanban.utils.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician,
@@ -183,9 +184,52 @@ export function ProductionBoardKanbanComponent({
: standardSizes[selectedBreakpoint[0]]
: "250";
const stickyHeader = {
renderColumnHeader: ({ title }) => (
<Sticky>
{({
style,
// the following are also available but unused in this example
isSticky,
wasSticky,
distanceFromTop,
distanceFromBottom,
calculatedHeight,
}) => (
<div
className="react-kanban-column-header"
style={{ ...style, zIndex: "99", backgroundColor: "#ddd" }}
>
{title}
</div>
)}
</Sticky>
),
};
const cardSettings =
associationSettings &&
associationSettings.kanban_settings &&
Object.keys(associationSettings.kanban_settings).length > 0
? associationSettings.kanban_settings
: {
ats: true,
clm_no: true,
compact: false,
ownr_nm: true,
sublets: true,
ins_co_nm: true,
production_note: true,
employeeassignments: true,
scheduled_completion: true,
stickyheader: false,
};
return (
<Container width={width}>
<IndefiniteLoading loading={isMoving} />
<PageHeader
title={
<Space>
@@ -215,34 +259,19 @@ export function ProductionBoardKanbanComponent({
</Space>
}
/>
<Board
children={boardLanes}
disableCardDrag={isMoving}
renderCard={(card) =>
ProductionBoardCard(
technician,
card,
bodyshop,
associationSettings &&
associationSettings.kanban_settings &&
Object.keys(associationSettings.kanban_settings).length > 0
? associationSettings.kanban_settings
: {
ats: true,
clm_no: true,
compact: false,
ownr_nm: true,
sublets: true,
ins_co_nm: true,
production_note: true,
employeeassignments: true,
scheduled_completion: true,
}
)
}
onCardDragEnd={handleDragEnd}
/>
<ProductionListDetailComponent jobs={data} />
<StickyContainer>
<Board
style={{ height: "100%" }}
children={boardLanes}
disableCardDrag={isMoving}
{...(cardSettings.stickyheader && stickyHeader)}
renderCard={(card) =>
ProductionBoardCard(technician, card, bodyshop, cardSettings)
}
onCardDragEnd={handleDragEnd}
/>
</StickyContainer>
</Container>
);
}

View File

@@ -21,6 +21,7 @@ import ProductionListColumnStatus from "./production-list-columns.status.compone
import ProductionListColumnCategory from "./production-list-columns.status.category";
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
import ProductionListColumnComment from "./production-list-columns.comment.component";
import ProductionListColumnPartsReceived from "./production-list-columns.partsreceived.component";
const r = ({ technician, state, activeStatuses, bodyshop }) => {
return [
@@ -96,7 +97,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
sortOrder:
state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="actual_in" time/>
<ProductionListDate record={record} field="actual_in" time />
),
},
{
@@ -477,6 +478,14 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
/>
),
},
{
title: i18n.t("jobs.labels.parts_received"),
dataIndex: "parts_received",
key: "parts_received",
render: (text, record) => (
<ProductionListColumnPartsReceived record={record} />
),
},
];
};
export default r;

View File

@@ -0,0 +1,41 @@
import { useMemo } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListColumnPartsReceived);
export function ProductionListColumnPartsReceived({ bodyshop, record }) {
const amount = useMemo(() => {
const amount = record.joblines_status.reduce(
(acc, val) => {
acc.total += val.count;
acc.received =
val.status === bodyshop.md_order_statuses.default_received
? acc.received + val.count
: acc.received;
return acc;
},
{ total: 0, received: 0 }
);
return {
...amount,
percent:
amount.total !== 0
? ((amount.received / amount.total) * 100).toFixed(0) + "%"
: "N/A",
};
}, [record, bodyshop.md_order_statuses]);
return `${amount.percent} (${amount.received}/${amount.total})`;
}

View File

@@ -88,12 +88,6 @@ export function ProductionListTable({
);
const handleTableChange = (pagination, filters, sorter) => {
console.log(
"🚀 ~ file: production-list-table.component.jsx ~ line 91 ~ pagination, filters, sorter",
pagination,
filters,
sorter
);
setState({
...state,
filteredInfo: filters,

View File

@@ -163,6 +163,27 @@ export default function ShopInfoGeneral({ form }) {
>
<Switch />
</Form.Item>
<Form.Item shouldUpdate noStyle>
{() => (
<Form.Item
label={t("bodyshop.labels.qbo_usa")}
shouldUpdate
valuePropName="checked"
name={["accountingconfig", "qbo_usa"]}
>
<Switch
disabled={!form.getFieldValue(["accountingconfig", "qbo"])}
/>
</Form.Item>
)}
</Form.Item>
<Form.Item
label={t("bodyshop.labels.qbo_departmentid")}
name={["accountingconfig", "qbo_departmentid"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.accountingtiers")}
rules={[

View File

@@ -146,6 +146,11 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
employee_refinish
employee_prep
employee_csr
joblines_status{
part_type
status
count
}
labhrs: joblines_aggregate(
where: {
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
@@ -219,6 +224,11 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
employee_refinish
employee_prep
employee_csr
joblines_status{
part_type
status
count
}
labhrs: joblines_aggregate(
where: {
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
@@ -294,6 +304,11 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
employee_prep
employee_csr
suspended
joblines_status{
part_type
status
count
}
labhrs: joblines_aggregate(
where: {
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]

View File

@@ -7,6 +7,7 @@ import {
confirmPasswordReset,
signInWithEmailAndPassword,
signOut,
sendPasswordResetEmail,
} from "firebase/auth";
import { doc } from "firebase/firestore";
import i18next from "i18next";
@@ -223,16 +224,16 @@ export function* signInSuccessSaga({ payload }) {
export function* onSendPasswordResetStart() {
yield takeLatest(
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
sendPasswordResetEmail
sendPasswordResetEmailSaga
);
yield takeLatest(
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN,
sendPasswordResetEmail
sendPasswordResetEmailSaga
);
}
export function* sendPasswordResetEmail({ payload }) {
export function* sendPasswordResetEmailSaga({ payload }) {
try {
yield sendPasswordResetEmail(payload, {
yield sendPasswordResetEmail(auth, payload, {
url: "https://imex.online/passwordreset",
});
@@ -269,7 +270,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
const userEmail = yield select((state) => state.user.currentUser.email);
try {
console.log("Setting shop timezone.");
// moment.tz.setDefault(payload.timezone);
// moment.tz.setDefault(payload.timezone);
} catch (error) {
console.log(error);
}

View File

@@ -157,6 +157,7 @@
"federal_tax_rate": "Federal Tax Rate",
"invoice_number": "Invoice Number",
"is_credit_memo": "Credit Memo?",
"is_credit_memo_short": "CM",
"local_tax_rate": "Local Tax Rate",
"ro_number": "RO Number",
"state_tax_rate": "Provincial/State Tax Rate",
@@ -1563,6 +1564,7 @@
"override_header": "Override estimate header on import?",
"ownerassociation": "Owner Association",
"parts": "Parts",
"parts_received": "Parts Rec.",
"parts_tax_rates": "Parts Tax rates",
"partsfilter": "Parts Only",
"partssubletstotal": "Parts & Sublets Total",
@@ -1842,6 +1844,11 @@
"updated": "Note updated successfully."
}
},
"owner": {
"labels": {
"noownerinfo": "No owner information."
}
},
"owners": {
"actions": {
"update": "Update Selected Records"
@@ -2078,6 +2085,7 @@
"labels": "Labels",
"position": "Starting Position"
},
"lag_time_ro": "Lag Time",
"mechanical_authorization": "Mechanical Authorization",
"mpi_animal_checklist": "MPI - Animal Checklist",
"mpi_eglass_auth": "MPI - eGlass Auth",
@@ -2170,6 +2178,12 @@
"ats": "Alternative Transportation",
"bodyhours": "B",
"bodypriority": "B/P",
"bodyshop": {
"labels": {
"qbo_departmentid": "QBO Department ID",
"qbo_usa": "QBO USA"
}
},
"cardsettings": "Card Settings",
"clm_no": "Claim Number",
"comment": "Comment",
@@ -2187,6 +2201,7 @@
"refinishhours": "R",
"scheduled_completion": "Scheduled Completion",
"selectview": "Select a View",
"stickyheader": "Sticky Header (BETA)",
"sublets": "Sublets",
"totalhours": "Total Hrs ",
"touchtime": "T/T",

View File

@@ -157,6 +157,7 @@
"federal_tax_rate": "",
"invoice_number": "",
"is_credit_memo": "",
"is_credit_memo_short": "",
"local_tax_rate": "",
"ro_number": "",
"state_tax_rate": "",
@@ -1563,6 +1564,7 @@
"override_header": "¿Anular encabezado estimado al importar?",
"ownerassociation": "",
"parts": "Partes",
"parts_received": "",
"parts_tax_rates": "",
"partsfilter": "",
"partssubletstotal": "",
@@ -1842,6 +1844,11 @@
"updated": "Nota actualizada con éxito."
}
},
"owner": {
"labels": {
"noownerinfo": ""
}
},
"owners": {
"actions": {
"update": ""
@@ -2078,6 +2085,7 @@
"labels": "",
"position": ""
},
"lag_time_ro": "",
"mechanical_authorization": "",
"mpi_animal_checklist": "",
"mpi_eglass_auth": "",
@@ -2170,6 +2178,12 @@
"ats": "",
"bodyhours": "",
"bodypriority": "",
"bodyshop": {
"labels": {
"qbo_departmentid": "",
"qbo_usa": ""
}
},
"cardsettings": "",
"clm_no": "",
"comment": "",
@@ -2187,6 +2201,7 @@
"refinishhours": "",
"scheduled_completion": "",
"selectview": "",
"stickyheader": "",
"sublets": "",
"totalhours": "",
"touchtime": "",

View File

@@ -157,6 +157,7 @@
"federal_tax_rate": "",
"invoice_number": "",
"is_credit_memo": "",
"is_credit_memo_short": "",
"local_tax_rate": "",
"ro_number": "",
"state_tax_rate": "",
@@ -1563,6 +1564,7 @@
"override_header": "Remplacer l'en-tête d'estimation à l'importation?",
"ownerassociation": "",
"parts": "les pièces",
"parts_received": "",
"parts_tax_rates": "",
"partsfilter": "",
"partssubletstotal": "",
@@ -1842,6 +1844,11 @@
"updated": "Remarque mise à jour avec succès."
}
},
"owner": {
"labels": {
"noownerinfo": ""
}
},
"owners": {
"actions": {
"update": ""
@@ -2078,6 +2085,7 @@
"labels": "",
"position": ""
},
"lag_time_ro": "",
"mechanical_authorization": "",
"mpi_animal_checklist": "",
"mpi_eglass_auth": "",
@@ -2170,6 +2178,12 @@
"ats": "",
"bodyhours": "",
"bodypriority": "",
"bodyshop": {
"labels": {
"qbo_departmentid": "",
"qbo_usa": ""
}
},
"cardsettings": "",
"clm_no": "",
"comment": "",
@@ -2187,6 +2201,7 @@
"refinishhours": "",
"scheduled_completion": "",
"selectview": "",
"stickyheader": "",
"sublets": "",
"totalhours": "",
"touchtime": "",

View File

@@ -464,6 +464,14 @@ export const TemplateList = (type, context) => {
disabled: false,
group: "post",
},
lag_time_ro: {
title: i18n.t("printcenter.jobs.lag_time_ro"),
description: "CASL Authorization",
subject: i18n.t("printcenter.jobs.lag_time_ro"),
key: "lag_time_ro",
disabled: false,
group: "ro",
},
}
: {}),
...(!type || type === "job_special"

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -611,6 +611,32 @@ exports.default = function ({
}
}
//QB USA with GST
//This was required for the No. 1 Collision Group.
if (
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
bodyshop.region_config.includes("CA_")
) {
InvoiceLineAdd.push({
DetailType: "SalesItemLineDetail",
Amount: Dinero(jobs_by_pk.job_totals.totals.federal_tax).toFormat(
DineroQbFormat
),
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
ItemRef: {
value:
items[bodyshop.md_responsibility_centers.taxes.federal.accountitem],
},
Qty: 1,
},
});
}
return InvoiceLineAdd;
};
@@ -626,7 +652,7 @@ const findTaxCode = ({ local, state, federal }, taxcode) => {
} else if (t.length > 1) {
return "Multiple Tax Codes Match";
} else {
return "No Tax Code Matches";
return "";
}
};
exports.findTaxCode = findTaxCode;

View File

@@ -174,6 +174,55 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
req
);
const lines = bill.billlines.map((il) =>
generateBillLine(
il,
accounts,
bill.job.class,
bill.job.bodyshop.md_responsibility_centers.sales_tax_codes,
classes,
taxCodes,
bill.job.bodyshop.md_responsibility_centers.costs
)
);
//QB USA with GST
//This was required for the No. 1 Collision Group.
if (
bill.job.bodyshop.accountingconfig &&
bill.job.bodyshop.accountingconfig.qbo &&
bill.job.bodyshop.accountingconfig.qbo_usa &&
bill.job.bodyshop.region_config.includes("CA_")
) {
lines.push({
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
...(bill.job.class
? { ClassRef: { value: classes[bill.job.class] } }
: {}),
AccountRef: {
value:
accounts[
bill.job.bodyshop.md_responsibility_centers.taxes.federal
.accountdesc
],
},
},
Amount: Dinero({
amount: Math.round(
bill.billlines.reduce((acc, val) => {
return acc + val.actual_cost * val.quantity;
}, 0) * 100
),
})
.percentage(bill.federal_tax_rate)
.toFormat(DineroQbFormat),
});
}
const billQbo = {
VendorRef: {
value: vendor.Id,
@@ -192,17 +241,7 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
//...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
PrivateNote: `RO ${bill.job.ro_number || ""}`,
Line: bill.billlines.map((il) =>
generateBillLine(
il,
accounts,
bill.job.class,
bill.job.bodyshop.md_responsibility_centers.sales_tax_codes,
classes,
taxCodes,
bill.job.bodyshop.md_responsibility_centers.costs
)
),
Line: lines,
};
logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, {
billQbo,
@@ -282,7 +321,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
url: urlBuilder(
qbo_realmId,
"query",
`select * From Account where AccountType = 'Cost of Goods Sold'`
`select * From Account where AccountType in ('Cost of Goods Sold', 'Other Current Liability')`
),
method: "POST",
headers: {

View File

@@ -455,6 +455,25 @@ async function InsertInvoice(
CustomerRef: {
value: parentTierRef.Id,
},
...(bodyshop.accountingconfig.qbo_departmentid &&
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid },
}),
...(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
bodyshop.region_config.includes("CA_") && {
TxnTaxDetail: {
TxnTaxCodeRef: {
value:
taxCodes[
bodyshop.md_responsibility_centers.taxes.state.accountitem
],
},
},
}),
...(bodyshop.accountingconfig.printlater
? { PrintStatus: "NeedToPrint" }
: {}),

View File

@@ -39,7 +39,7 @@ exports.default = async (req, res) => {
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
const specificShopIds = req.body.bodyshopIds; // ['uuid]
const start = req.body.start; //YYYY-MM-DD
const { start, end } = req.body; //YYYY-MM-DD
const allxmlsToUpload = [];
const allErrors = [];
try {
@@ -58,6 +58,7 @@ exports.default = async (req, res) => {
start: start
? moment(start).startOf("day")
: moment().subtract(3, "days").startOf("day"),
...(end && { end: moment(end).startOf("day") }),
}
);
@@ -184,6 +185,11 @@ exports.default = async (req, res) => {
const CreateRepairOrderTag = (job, errorCallback) => {
//Level 2
if (!job.job_totals) {
errorCallback({ job, error: { toString: () => "No job totals for RO." } });
return {};
}
const repairCosts = CreateCosts(job);
try {

View File

@@ -111,6 +111,11 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
ro_number
clm_total
clm_no
v_model_yr
v_model_desc
v_make_desc
v_vin
plate_no
ownerid
ownr_ln
ownr_fn
@@ -176,6 +181,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
bodyshops(where: {associations: {active: {_eq: true}}}) {
id
md_responsibility_centers
region_config
accountingconfig
md_ins_cos
timezone
@@ -389,6 +395,8 @@ query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) {
bodyshop{
md_responsibility_centers
timezone
region_config
accountingconfig
}
}
billlines{
@@ -550,7 +558,7 @@ exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employee
}
}`;
exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshopid: uuid!) {
exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
bodyshops_by_pk(id: $bodyshopid){
id
@@ -568,7 +576,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
jc_hourly_rates
timezone
}
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {shopid: {_eq: $bodyshopid}}]}) {
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {updated_at: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}]}) {
id
ro_number
status