Compare commits

...

31 Commits

Author SHA1 Message Date
Patrick Fic
5832a5f529 Add improved logging to export procedures. 2022-05-20 10:31:31 -07:00
Patrick Fic
f3ee20e89d Bug fixes for this weeks stories. 2022-05-20 09:19:54 -07:00
Patrick Fic
bcd062dffd Remove IO Event Tracking. 2022-05-20 09:01:37 -07:00
Patrick Fic
bd8b961bda IO-1894 Add scheduled return to all cc list. 2022-05-20 08:53:37 -07:00
Patrick Fic
43b6bdb024 IO-1896 Add remove from partst queue on parts order. 2022-05-19 11:08:12 -07:00
Patrick Fic
3dfc6eede2 Change scoreboard to query instead of subscription. 2022-05-19 11:07:51 -07:00
Patrick Fic
e70c11d4e6 IO-1890 Clear page when searching phonebook. 2022-05-19 10:46:36 -07:00
Patrick Fic
abc7262584 IO-1886 Handle large numbers of of menu items for notes and file handler. 2022-05-19 10:09:45 -07:00
Patrick Fic
e10ca9897c Merged in hotfix/2022-05-18 (pull request #485)
hotfix/2022-05-18

Approved-by: Patrick Fic
2022-05-18 19:00:28 +00:00
Patrick Fic
6f561e4caa Resolve job notes insert for ROs with no vehicle. 2022-05-18 11:59:46 -07:00
Patrick Fic
97beb14209 Merged in release/2022-05-13 (pull request #483)
release/2022-05-13

Approved-by: Patrick Fic
2022-05-17 18:24:42 +00:00
Patrick Fic
5568a434b9 Merged in release/2022-05-13 (pull request #482)
release/2022-05-13

Approved-by: Patrick Fic
2022-05-17 18:24:12 +00:00
Patrick Fic
e92827aeb2 Resolve note saving issue. 2022-05-17 11:23:50 -07:00
Patrick Fic
ac890bd92b IO-1898 Add vehicle notes. 2022-05-17 08:24:04 -07:00
Patrick Fic
0eaf23841a IO-1893 Add scheduled in to parts queue. 2022-05-16 16:23:25 -07:00
Patrick Fic
82c13eae9e update NPM manager. 2022-05-13 17:14:03 -07:00
Patrick Fic
708eb3c73f Merged in release/2022-05-13 (pull request #481)
Updated CI config.

Approved-by: Patrick Fic
2022-05-13 23:41:10 +00:00
Patrick Fic
cdb8e48f0d Updated CI config. 2022-05-13 16:39:47 -07:00
Patrick Fic
90600cdff4 Local media server bugfixes. 2022-05-13 09:34:19 -07:00
Patrick Fic
cdcfea988f Merged in hotfix/2022-05-10 (pull request #477)
Hotfix/2022 05 10
2022-05-12 23:58:38 +00:00
Patrick Fic
26f58961a0 IO-1875 Add CNR by Vendor 2022-05-12 16:58:02 -07:00
Patrick Fic
8daa0ac154 Update URL to create explorer link. 2022-05-12 16:04:10 -07:00
Patrick Fic
76fee429ea Merge branch 'hotfix/2022-05-10' into release/2022-05-13 2022-05-12 12:40:26 -07:00
Patrick Fic
d1a65530a3 IO-1881 Related RO notes. 2022-05-12 12:35:38 -07:00
Patrick Fic
4613a93d09 IO-1877 Multi line notes presets. 2022-05-12 11:36:11 -07:00
Patrick Fic
faf1d638fb IO-1874 Custom fields for receivables. 2022-05-12 11:34:15 -07:00
Patrick Fic
55144bd621 Added IMS token changes. 2022-05-11 16:31:09 -07:00
Patrick Fic
bbf908e5e1 Merge branch 'hotfix/2022-05-10' into release/2022-05-13 2022-05-10 14:29:22 -07:00
Patrick Fic
9da126879e Merged in hotfix/2022-05-10 (pull request #470)
Update QBO export query for payments.

Approved-by: Patrick Fic
2022-05-10 21:25:52 +00:00
Patrick Fic
18fa00785c Update QBO export query for payments. 2022-05-10 14:25:28 -07:00
Patrick Fic
3192e918a4 Merged in feature/local-images (pull request #464)
feature/local-images

Approved-by: Patrick Fic
2022-05-10 20:18:39 +00:00
57 changed files with 26049 additions and 2705 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1">
<babeledit_project be_version="2.7.1" version="1.2">
<!--
BabelEdit project file
@@ -3586,6 +3586,27 @@
<folder_node>
<name>fields</name>
<children>
<concept_node>
<name>ReceivableCustomField</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>address1</name>
<definition_loaded>false</definition_loaded>
@@ -4577,6 +4598,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>localmediatoken</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>logo_img_footer_margin</name>
<definition_loaded>false</definition_loaded>
@@ -31726,6 +31768,27 @@
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>addtorelatedro</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>newnoteplaceholder</name>
<definition_loaded>false</definition_loaded>
@@ -33470,6 +33533,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>removefrompartsqueue</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>returnpartsorder</name>
<definition_loaded>false</definition_loaded>
@@ -38625,6 +38709,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>credits_not_received_date_vendorid</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>csi</name>
<definition_loaded>false</definition_loaded>
@@ -44165,6 +44270,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>notes</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>plate_no</name>
<definition_loaded>false</definition_loaded>

View File

@@ -4,54 +4,54 @@
"private": true,
"proxy": "http://localhost:4000",
"dependencies": {
"@apollo/client": "^3.5.10",
"@apollo/client": "^3.6.2",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.3",
"@fingerprintjs/fingerprintjs": "^3.3.3",
"@sentry/react": "^6.19.6",
"@sentry/tracing": "^6.19.6",
"@splitsoftware/splitio-react": "^1.4.0",
"@stripe/react-stripe-js": "^1.7.1",
"@stripe/stripe-js": "^1.27.0",
"@tanem/react-nprogress": "^4.0.12",
"antd": "^4.19.5",
"@sentry/react": "^6.19.7",
"@sentry/tracing": "^6.19.7",
"@splitsoftware/splitio-react": "^1.4.1",
"@stripe/react-stripe-js": "^1.8.0",
"@stripe/stripe-js": "^1.29.0",
"@tanem/react-nprogress": "^5.0.0",
"antd": "^4.20.5",
"apollo-link-logger": "^2.0.0",
"axios": "^0.26.1",
"axios": "^0.27.2",
"craco-less": "^1.20.0",
"dinero.js": "^1.9.1",
"dotenv": "^16.0.0",
"dotenv": "^16.0.1",
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^9.6.11",
"graphql": "^16.3.0",
"i18next": "^21.6.16",
"firebase": "^9.8.1",
"graphql": "^16.5.0",
"i18next": "^21.8.2",
"i18next-browser-languagedetector": "^6.1.4",
"jsoneditor": "^9.7.4",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.51",
"logrocket": "^2.2.1",
"markerjs2": "^2.21.1",
"libphonenumber-js": "^1.9.53",
"logrocket": "^3.0.0",
"markerjs2": "^2.21.4",
"moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34",
"normalize-url": "^7.0.3",
"phone": "^3.1.15",
"phone": "^3.1.17",
"preval.macro": "^5.0.0",
"prop-types": "^15.8.1",
"query-string": "^7.1.1",
"rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6",
"react": "^17.0.2",
"react-big-calendar": "^0.38.2",
"react-big-calendar": "^0.40.1",
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-drag-listview": "^0.1.9",
"react-drag-listview": "^0.2.0",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.4",
"react-i18next": "^11.16.6",
"react-i18next": "^11.16.9",
"react-icons": "^4.3.1",
"react-number-format": "^4.9.1",
"react-number-format": "^4.9.3",
"react-redux": "^7.2.8",
"react-resizable": "^3.0.4",
"react-router-dom": "^5.3.0",
@@ -60,28 +60,28 @@
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3",
"recharts": "^2.1.9",
"redux": "^4.1.2",
"redux": "^4.2.0",
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2",
"reselect": "^4.1.5",
"sass": "^1.50.0",
"socket.io-client": "^4.4.1",
"sass": "^1.51.0",
"socket.io-client": "^4.5.0",
"styled-components": "^5.3.5",
"subscriptions-transport-ws": "^0.11.0",
"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",
"workbox-background-sync": "^6.5.3",
"workbox-broadcast-update": "^6.5.3",
"workbox-cacheable-response": "^6.5.3",
"workbox-core": "^6.5.3",
"workbox-expiration": "^6.5.3",
"workbox-google-analytics": "^6.5.3",
"workbox-navigation-preload": "^6.5.3",
"workbox-precaching": "^6.5.3",
"workbox-range-requests": "^6.5.3",
"workbox-routing": "^6.5.3",
"workbox-strategies": "^6.5.3",
"workbox-streams": "^6.5.3",
"yauzl": "^2.10.0"
},
"scripts": {
@@ -118,11 +118,11 @@
"react-error-overlay": "6.0.9"
},
"devDependencies": {
"@sentry/webpack-plugin": "^1.18.8",
"@sentry/webpack-plugin": "^1.18.9",
"@testing-library/cypress": "^8.0.2",
"cypress": "^9.5.3",
"cypress": "^9.6.1",
"eslint-plugin-cypress": "^2.12.1",
"react-error-overlay": "6.0.10",
"react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.2"
}

View File

@@ -3,6 +3,7 @@ import { Button, Card, Input, Space, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
@@ -105,6 +106,17 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
</Link>
) : null,
},
{
title: t("contracts.fields.scheduledreturn"),
dataIndex: "scheduledreturn",
key: "scheduledreturn",
render: (text, record) =>
record.cccontracts.length === 1 && (
<DateTimeFormatter>
{record.cccontracts[0].scheduledreturn}
</DateTimeFormatter>
),
},
];
const handleTableChange = (pagination, filters, sorter) => {

View File

@@ -7,8 +7,12 @@ export const handleUpload = async ({ ev, context }) => {
const { onError, onSuccess, onProgress, file } = ev;
const { jobid, invoice_number, vendorid, callbackAfterUpload } = context;
const bodyshop = store.getState().user.bodyshop;
var options = {
headers: { "X-Requested-With": "XMLHttpRequest" },
headers: {
"X-Requested-With": "XMLHttpRequest",
ims_token: bodyshop.localmediatoken,
},
onUploadProgress: (e) => {
if (!!onProgress) onProgress({ percent: (e.loaded / e.total) * 100 });
},
@@ -22,7 +26,6 @@ export const handleUpload = async ({ ev, context }) => {
formData.append("vendorid", vendorid);
}
formData.append("file", file);
const bodyshop = store.getState().user.bodyshop;
const imexMediaServerResponse = await cleanAxios.post(
normalizeUrl(

View File

@@ -24,7 +24,7 @@ export function JoblinePresetButton({ bodyshop, form }) {
const menu = (
<Menu>
{bodyshop.md_jobline_presets.map((i, idx) => (
<Menu.Item onClick={() => handleSelect(i)} key={idx}>
<Menu.Item onClick={() => handleSelect(i)} key={idx}>
{i.label}
</Menu.Item>
))}

View File

@@ -16,15 +16,18 @@ export function JobsDetailChangeFilehandler({ disabled, form, bodyshop }) {
};
const menu = (
<div>
<Menu onClick={handleClick}>
{bodyshop.md_filehandlers.map((est, idx) => (
<Menu.Item value={est} key={idx}>
{`${est.ins_ct_fn} ${est.ins_ct_ln}`}
</Menu.Item>
))}
</Menu>
</div>
<Menu
onClick={handleClick}
style={{
columnCount: Math.floor(bodyshop.md_filehandlers.length / 10) + 1,
}}
>
{bodyshop.md_filehandlers.map((est, idx) => (
<Menu.Item value={est} key={idx} style={{ breakInside: "avoid" }}>
{`${est.ins_ct_fn} ${est.ins_ct_ln}`}
</Menu.Item>
))}
</Menu>
);
return (

View File

@@ -216,6 +216,22 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<DataLabel label={t("jobs.labels.relatedros")}>
<JobsRelatedRos jobid={job.id} job={job} />
</DataLabel>
{job.vehicle && job.vehicle.notes && (
<DataLabel label={t("vehicles.fields.notes")}>
<span style={{ whiteSpace: "pre" }}>{job.vehicle.notes}</span>
</DataLabel>
)}
{
// job.vehicle && job.vehicle.v_paint_codes && (
// <DataLabel label={t("vehicles.fields.v_paint_codes")}>
// <span style={{ whiteSpace: "pre" }}>
// {Object.keys(job.vehicle.v_paint_codes).map((key, idx) => (
// <Tag key={idx}>{job.vehicle.v_paint_codes[key]}</Tag>
// ))}
// </span>
// </DataLabel>
// )
}
</div>
</Card>
</Col>

View File

@@ -15,6 +15,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
import DocumentsLocalUploadComponent from "../documents-local-upload/documents-local-upload.component";
import JobsDocumentsLocalGalleryReassign from "./jobs-documents-local-gallery.reassign.component";
import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-gallery.selectall.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -76,6 +77,7 @@ export function JobsDocumentsLocalGallery({
<Button>{t("documents.labels.openinexplorer")}</Button>
</a>
<JobsDocumentsLocalGalleryReassign jobid={job.id} />
<JobsDocumentsLocalGallerySelectAllComponent jobid={job.id} />
</Space>
<Card>
<DocumentsLocalUploadComponent

View File

@@ -39,11 +39,15 @@ export function JobsDocumentsLocalGalleryReassign({
setLoading(true);
const selectedDocuments = allMedia[jobid].filter((m) => m.isSelected);
await cleanAxios.post(`${bodyshop.localmediaserverhttp}/jobs/move`, {
from_jobid: jobid,
jobid: newJobid,
files: selectedDocuments.map((f) => f.filename),
});
await cleanAxios.post(
`${bodyshop.localmediaserverhttp}/jobs/move`,
{
from_jobid: jobid,
jobid: newJobid,
files: selectedDocuments.map((f) => f.filename),
},
{ headers: { ims_token: bodyshop.localmediatoken } }
);
getJobMedia(jobid);
setVisible(false);

View File

@@ -0,0 +1,53 @@
import { Button, Space } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectAllmediaForJob,
deselectAllMediaForJob,
} from "../../redux/media/media.actions";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
selectAllmediaForJob: (jobid) => dispatch(selectAllmediaForJob(jobid)),
deselectAllmediaForJob: (jobid) => dispatch(deselectAllMediaForJob(jobid)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsDocumentsLocalGallerySelectAllComponent);
export function JobsDocumentsLocalGallerySelectAllComponent({
jobid,
selectAllmediaForJob,
deselectAllmediaForJob,
}) {
const { t } = useTranslation();
// onSelectImage={(index, image) => {
// toggleMediaSelected({ jobid: job.id, filename: image.filename });
// }}
const handleSelectAll = () => {
selectAllmediaForJob({ jobid });
};
const handleDeselectAll = () => {
deselectAllmediaForJob({ jobid });
};
return (
<Space wrap>
<Button onClick={handleSelectAll}>
{t("general.actions.selectall")}
</Button>
<Button onClick={handleDeselectAll}>
{t("general.actions.deselectall")}
</Button>
</Space>
);
}

View File

@@ -61,6 +61,9 @@ export function JobNotesContainer({ jobId, insertAuditTrail }) {
jobId={jobId}
loading={loading}
data={data ? data.jobs_by_pk.notes : null}
relatedRos={
data ? data.jobs_by_pk.vehicle && data.jobs_by_pk.vehicle.jobs : null
}
refetch={refetch}
deleteLoading={deleteLoading}
handleNoteDelete={handleNoteDelete}

View File

@@ -37,6 +37,7 @@ export function JobNotesComponent({
setNoteUpsertContext,
deleteLoading,
ro_number,
relatedRos,
}) {
const { t } = useTranslation();
const Templates = TemplateList("job_special", {
@@ -149,6 +150,7 @@ export function JobNotesComponent({
actions: { refetch: refetch },
context: {
jobId: jobId,
relatedRos: relatedRos,
},
});
}}

View File

@@ -1,51 +1,92 @@
import { Col, Form, Input, Row, Switch } from "antd";
import { Checkbox, Col, Form, Input, Row, Space, Switch, Tag } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectNoteUpsert } from "../../redux/modals/modals.selectors";
import NotesPresetButton from "../notes-preset-button/notes-preset-button.component";
export default function NoteUpsertModalComponent({ form }) {
const mapStateToProps = createStructuredSelector({
noteUpsertModal: selectNoteUpsert,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(NoteUpsertModalComponent);
export function NoteUpsertModalComponent({ form, noteUpsertModal }) {
const { t } = useTranslation();
const { jobId, existingNote, relatedRos } = noteUpsertModal.context;
const filteredRelatedRos = relatedRos
? relatedRos.filter((j) => j.id !== jobId)
: [];
return (
<Row gutter={[16, 16]}>
<Col span={8}>
<Form.Item
label={t("notes.fields.critical")}
name="critical"
valuePropName="checked"
>
<Switch />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("notes.fields.private")}
name="private"
valuePropName="checked"
>
<Switch />
</Form.Item>
</Col>
<Col span={8}>
<NotesPresetButton form={form} />
</Col>
<Col span={24}>
<Form.Item
label={t("notes.fields.text")}
name="text"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input.TextArea
rows={8}
placeholder={t("notes.labels.newnoteplaceholder")}
/>
</Form.Item>
</Col>
</Row>
<>
<Row gutter={[16, 16]}>
<Col span={8}>
<Form.Item
label={t("notes.fields.critical")}
name="critical"
valuePropName="checked"
>
<Switch />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("notes.fields.private")}
name="private"
valuePropName="checked"
>
<Switch />
</Form.Item>
</Col>
<Col span={8}>
<NotesPresetButton form={form} />
</Col>
<Col span={24}>
<Form.Item
label={t("notes.fields.text")}
name="text"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input.TextArea
rows={8}
placeholder={t("notes.labels.newnoteplaceholder")}
/>
</Form.Item>
</Col>
</Row>
<div>
<div>{!existingNote && t("notes.labels.addtorelatedro")}</div>
{!existingNote &&
filteredRelatedRos.map((j, idx) => (
<Space key={j.id} align="center">
<Form.Item
noStyle
name={["relatedros", j.id]}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
<Tag>
{`${j.ro_number || "N/A"}${j.clm_no ? ` | ${j.clm_no}` : ""}${
j.status ? ` | ${j.status}` : ""
}`}
</Tag>
</Space>
))}
</div>
</>
);
}

View File

@@ -4,14 +4,14 @@ import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_NEW_NOTE, UPDATE_NOTE } from "../../graphql/notes.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectNoteUpsert } from "../../redux/modals/modals.selectors";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import NoteUpsertModalComponent from "./note-upsert-modal.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import NoteUpsertModalComponent from "./note-upsert-modal.component";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -48,7 +48,9 @@ export function NoteUpsertModalContainer({
}
}, [existingNote, form, visible]);
const handleFinish = (values) => {
const handleFinish = async (formValues) => {
const { relatedros, ...values } = formValues;
if (existingNote) {
logImEXEvent("job_note_update");
@@ -70,24 +72,44 @@ export function NoteUpsertModalContainer({
toggleModalVisible();
} else {
logImEXEvent("job_note_insert");
const AdditionalNoteInserts = relatedros
? Object.keys(relatedros).filter((key) => relatedros[key])
: [];
insertNote({
await insertNote({
variables: {
noteInput: [
{ ...values, jobid: jobId, created_by: currentUser.email },
],
},
}).then((r) => {
if (refetch) refetch();
form.resetFields();
toggleModalVisible();
notification["success"]({
message: t("notes.successes.create"),
});
insertAuditTrail({
jobid: context.jobId,
operation: AuditTrailMapping.jobnoteadded(),
});
if (AdditionalNoteInserts.length > 0) {
//Insert the others.
AdditionalNoteInserts.forEach(async (newJobId) => {
await insertNote({
variables: {
noteInput: [
{ ...values, jobid: newJobId, created_by: currentUser.email },
],
},
});
insertAuditTrail({
jobid: newJobId,
operation: AuditTrailMapping.jobnoteadded(),
});
});
}
if (refetch) refetch();
form.resetFields();
toggleModalVisible();
notification["success"]({
message: t("notes.successes.create"),
});
insertAuditTrail({
jobid: context.jobId,
operation: AuditTrailMapping.jobnoteadded(),
});
}
};

View File

@@ -22,9 +22,17 @@ export function NotesPresetButton({ bodyshop, form }) {
};
const menu = (
<Menu>
<Menu
style={{
columnCount: Math.floor(bodyshop.md_notes_presets.length / 10) + 1,
}}
>
{bodyshop.md_notes_presets.map((i, idx) => (
<Menu.Item onClick={() => handleSelect(i)} key={idx}>
<Menu.Item
onClick={() => handleSelect(i)}
key={idx}
style={{ breakInside: "avoid" }}
>
{i.label}
</Menu.Item>
))}

View File

@@ -11,6 +11,7 @@ import {
Select,
Menu,
Dropdown,
Checkbox,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -114,6 +115,15 @@ export function PartsOrderModalComponent({
</Space>
</Tag>
)}
{!isReturn && (
<Form.Item
name="removefrompartsqueue"
label={t("parts_orders.labels.removefrompartsqueue")}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
)}
</LayoutFormRow>
<Divider orientation="left">
{t("parts_orders.labels.inthisorder")}
@@ -280,6 +290,7 @@ export function PartsOrderModalComponent({
>
<Input.TextArea rows={3} />
</Form.Item>
<Radio.Group
defaultValue={sendType}
onChange={(e) => setSendType(e.target.value)}

View File

@@ -32,6 +32,7 @@ import PartsOrderModalComponent from "./parts-order-modal.component";
import axios from "axios";
import { useTreatments } from "@splitsoftware/splitio-react";
import _ from "lodash";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -90,8 +91,9 @@ export function PartsOrderModalContainer({
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
const [updateJob] = useMutation(UPDATE_JOB);
const handleFinish = async (values) => {
const handleFinish = async ({ removefrompartsqueue, ...values }) => {
logImEXEvent("parts_order_insert");
setSaving(true);
const insertResult = await insertPartOrder({
@@ -128,6 +130,17 @@ export function PartsOrderModalContainer({
},
});
if (!isReturn && removefrompartsqueue) {
await updateJob({
variables: {
jobId: jobId,
job: {
queued_for_parts: false,
},
},
});
}
insertAuditTrail({
jobid: jobId,
operation: isReturn

View File

@@ -8,8 +8,11 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment";
import { useApolloClient } from "@apollo/client";
import { GET_BLOCKED_DAYS } from "../../graphql/scoreboard.queries";
import { useApolloClient, useQuery } from "@apollo/client";
import {
GET_BLOCKED_DAYS,
QUERY_SCOREBOARD,
} from "../../graphql/scoreboard.queries";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -22,10 +25,15 @@ export default connect(
mapDispatchToProps
)(ScoreboardDisplayComponent);
export function ScoreboardDisplayComponent({
bodyshop,
scoreboardSubscription,
}) {
export function ScoreboardDisplayComponent({ bodyshop }) {
const scoreboardSubscription = useQuery(QUERY_SCOREBOARD, {
variables: {
start: moment().startOf("month"),
end: moment().endOf("month"),
},
pollInterval: 60000,
});
const { data } = scoreboardSubscription;
const client = useApolloClient();
const scoreBoardlist = (data && data.scoreboard) || [];

View File

@@ -352,10 +352,27 @@ export default function ShopInfoGeneral({ form }) {
>
<Switch />
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField1"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 1 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField2"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 2 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField3"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 3 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["md_classes"]}
label={t("bodyshop.fields.md_classes")}
dependencies={["enforce_class"]}
rules={[
({ getFieldValue }) => {
return {
@@ -603,6 +620,12 @@ export default function ShopInfoGeneral({ form }) {
>
<Input />
</Form.Item>
<Form.Item
name={["localmediatoken"]}
label={t("bodyshop.fields.localmediatoken")}
>
<Input />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
<Form.List name={["md_messaging_presets"]}>
@@ -701,7 +724,7 @@ export default function ShopInfoGeneral({ form }) {
},
]}
>
<Input />
<Input.TextArea rows={3} />
</Form.Item>
<Space wrap>
<DeleteFilled
@@ -1469,3 +1492,11 @@ export default function ShopInfoGeneral({ form }) {
</div>
);
}
const ReceivableCustomFieldSelect = (
<Select>
<Select.Option value="v_vin">VIN</Select.Option>
<Select.Option value="clm_no">Claim No.</Select.Option>
<Select.Option value="ded_amt">Deductible Amount</Select.Option>
</Select>
);

View File

@@ -6,15 +6,20 @@ import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import JobLinesContainer from "../job-detail-lines/job-lines.container";
import JobsDetailHeader from "../jobs-detail-header/jobs-detail-header.component";
import JobsDocumentsGalleryContainer from "../jobs-documents-gallery/jobs-documents-gallery.container";
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import JobNotesContainer from "../jobs-notes/jobs-notes.container";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop });
const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "printCenter" })),
@@ -29,7 +34,7 @@ const mapDispatchToProps = (dispatch) => ({
// },
// };
export function JobDetailCards({ setPrintCenterContext }) {
export function TechLookupJobsDrawer({ bodyshop, setPrintCenterContext }) {
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
@@ -110,7 +115,13 @@ export function JobDetailCards({ setPrintCenterContext }) {
/>
</Tabs.TabPane>
<Tabs.TabPane key="documents" tab={t("jobs.labels.documents")}>
<JobsDocumentsGalleryContainer jobId={searchParams.selected} />
{bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery
job={data ? data.jobs_by_pk : null}
/>
) : (
<JobsDocumentsGalleryContainer jobId={searchParams.selected} />
)}
</Tabs.TabPane>
<Tabs.TabPane key="notes" tab={t("jobs.labels.notes")}>
<JobNotesContainer jobId={searchParams.selected} />
@@ -121,4 +132,7 @@ export function JobDetailCards({ setPrintCenterContext }) {
</Drawer>
);
}
export default connect(null, mapDispatchToProps)(JobDetailCards);
export default connect(
mapStateToProps,
mapDispatchToProps
)(TechLookupJobsDrawer);

View File

@@ -139,6 +139,9 @@ export default function VehicleDetailFormComponent({ form, loading }) {
<Input />
</Form.Item>
</LayoutFormRow>
<Form.Item label={t("vehicles.fields.notes")} name="notes">
<Input.TextArea rows={4} />
</Form.Item>
</div>
);
}

View File

@@ -110,6 +110,7 @@ export const QUERY_BODYSHOP = gql`
uselocalmediaserver
localmediaserverhttp
localmediaservernetwork
localmediatoken
employees {
user_email
id
@@ -218,6 +219,7 @@ export const UPDATE_SHOP = gql`
uselocalmediaserver
localmediaserverhttp
localmediaservernetwork
localmediatoken
employees {
id
first_name

View File

@@ -86,6 +86,7 @@ export const QUERY_ALL_CC = gql`
limit: 1
) {
id
scheduledreturn
job {
id
ro_number

View File

@@ -75,13 +75,9 @@ export const QUERY_PARTS_QUEUE = gql`
v_make_desc
v_color
vehicleid
actual_completion
actual_delivery
actual_in
scheduled_in
id
clm_no
clm_total
owner_owing
ro_number
status
updated_at
@@ -534,6 +530,8 @@ export const GET_JOB_BY_PK = gql`
v_model_desc
v_make_desc
v_color
notes
v_paint_codes
jobs {
id
ro_number
@@ -870,6 +868,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
v_model_desc
v_color
plate_no
notes
jobs {
id
clm_no

View File

@@ -15,6 +15,14 @@ export const QUERY_NOTES_BY_JOB_PK = gql`
jobs_by_pk(id: $id) {
id
ro_number
vehicle{
jobs{
id
ro_number
status
clm_no
}
}
notes {
created_at
created_by

View File

@@ -1,7 +1,7 @@
import { gql } from "@apollo/client";
export const SUBSCRIPTION_SCOREBOARD = gql`
subscription SUBSCRIPTION_SCOREBOARD($start: date!, $end: date!) {
export const QUERY_SCOREBOARD = gql`
query QUERY_SCOREBOARD($start: date!, $end: date!) {
scoreboard(
where: { _and: { date: { _gte: $start, _lte: $end } } }
order_by: { date: asc }

View File

@@ -27,6 +27,7 @@ export const QUERY_VEHICLE_BY_ID = gql`
v_bstyle
updated_at
trim_color
notes
jobs {
id
ro_number

View File

@@ -15,7 +15,7 @@ import OwnerNameDisplay from "../../components/owner-name-display/owner-name-dis
import { QUERY_PARTS_QUEUE } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import { DateTimeFormatter, TimeAgoFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
const mapStateToProps = createStructuredSelector({
@@ -158,6 +158,17 @@ export function PartsQueuePageComponent({ bodyshop }) {
},
},
{
title: t("jobs.fields.scheduled_in"),
dataIndex: "scheduled_in",
key: "scheduled_in",
ellipsis: true,
sorter: (a, b) => dateSort(a.scheduled_in, b.scheduled_in),
sortOrder: sortcolumn === "scheduled_in" && sortorder,
render: (text, record) => (
<DateTimeFormatter>{record.scheduled_in}</DateTimeFormatter>
),
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
@@ -194,13 +205,6 @@ export function PartsQueuePageComponent({ bodyshop }) {
ellipsis: true,
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortcolumn === "clm_no" && sortorder,
render: (text, record) => {
return record.clm_no ? (
<span>{record.clm_no}</span>
) : (
t("general.labels.unknown")
);
},
},
// {
// title: t("jobs.fields.clm_total"),

View File

@@ -160,6 +160,7 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) {
<Button
onClick={() => {
delete searchParams.search;
searchParams.page = 1;
history.push({ search: queryString.stringify(searchParams) });
}}
>
@@ -177,6 +178,7 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) {
placeholder={searchParams.search || t("general.labels.search")}
onSearch={(value) => {
searchParams.search = value;
searchParams.page = 1;
history.push({ search: queryString.stringify(searchParams) });
}}
/>

View File

@@ -2,17 +2,14 @@ import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import ScoreboardDisplay from "../../components/scoreboard-display/scoreboard-display.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ScoreboardDisplay from "../../components/scoreboard-display/scoreboard-display.component";
import { useSubscription } from "@apollo/client";
import { SUBSCRIPTION_SCOREBOARD } from "../../graphql/scoreboard.queries";
import moment from "moment";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -26,13 +23,6 @@ const mapDispatchToProps = (dispatch) => ({
export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const scoreboardSubscription = useSubscription(SUBSCRIPTION_SCOREBOARD, {
variables: {
start: moment().startOf("month"),
end: moment().endOf("month"),
},
});
useEffect(() => {
document.title = t("titles.scoreboard");
setSelectedHeader("scoreboard");
@@ -47,7 +37,7 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
return (
<FeatureWrapper featureName="scoreboard">
<RbacWrapper action="scoreboard:view">
<ScoreboardDisplay scoreboardSubscription={scoreboardSubscription} />
<ScoreboardDisplay />
</RbacWrapper>
</FeatureWrapper>
);

View File

@@ -32,3 +32,13 @@ export const toggleMediaSelected = ({ jobid, filename }) => ({
type: MediaActionTypes.TOGGLE_MEDIA_SELECTED,
payload: { jobid, filename },
});
export const deselectAllMediaForJob = ({ jobid }) => ({
type: MediaActionTypes.DESELECT_ALL_MEDIA_FOR_JOB,
payload: { jobid },
});
export const selectAllmediaForJob = ({ jobid }) => ({
type: MediaActionTypes.SELECT_ALL_MEDIA_FOR_JOB,
payload: { jobid },
});

View File

@@ -26,6 +26,23 @@ const mediaReducer = (state = INITIAL_STATE, action) => {
return p;
}),
};
case MediaActionTypes.SELECT_ALL_MEDIA_FOR_JOB:
return {
...state,
[action.payload.jobid]: state[action.payload.jobid].map((p) => {
p.isSelected = true;
return p;
}),
};
case MediaActionTypes.DESELECT_ALL_MEDIA_FOR_JOB:
return {
...state,
[action.payload.jobid]: state[action.payload.jobid].map((p) => {
p.isSelected = false;
return p;
}),
};
default:
return state;
}

View File

@@ -9,22 +9,23 @@ export function* onSetJobMedia() {
}
export function* getJobMedia({ payload: jobid }) {
try {
const localmediaserverhttp = (yield select(
(state) => state.user.bodyshop.localmediaserverhttp
)).trim();
const bodyshop = yield select((state) => state.user.bodyshop);
const localmediaserverhttp = bodyshop.localmediaserverhttp.trim();
if (localmediaserverhttp && localmediaserverhttp !== "") {
const imagesFetch = yield cleanAxios.post(
`${localmediaserverhttp}/jobs/list`,
{
jobid,
}
},
{ headers: { ims_token: bodyshop.localmediatoken } }
);
const documentsFetch = yield cleanAxios.post(
`${localmediaserverhttp}/bills/list`,
{
jobid,
}
},
{ headers: { ims_token: bodyshop.localmediatoken } }
);
yield put(
@@ -66,9 +67,8 @@ export function* onSetBillMedia() {
}
export function* getBillMedia({ payload: { jobid, invoice_number } }) {
try {
const localmediaserverhttp = (yield select(
(state) => state.user.bodyshop.localmediaserverhttp
)).trim();
const bodyshop = yield select((state) => state.user.bodyshop);
const localmediaserverhttp = bodyshop.localmediaserverhttp.trim();
if (localmediaserverhttp && localmediaserverhttp !== "") {
const documentsFetch = yield cleanAxios.post(
@@ -76,7 +76,8 @@ export function* getBillMedia({ payload: { jobid, invoice_number } }) {
{
jobid,
invoice_number,
}
},
{ headers: { ims_token: bodyshop.localmediatoken } }
);
yield put(

View File

@@ -4,9 +4,8 @@ const MediaActionTypes = {
GET_MEDIA_FOR_JOB_ERROR: "GET_MEDIA_FOR_JOB_ERROR",
ADD_MEDIA_FOR_JOB: "ADD_MEDIA_FOR_JOB",
TOGGLE_MEDIA_SELECTED: "TOGGLE_MEDIA_SELECTED",
POST_MEDIA_FOR_JOB: "POST_MEDIA_FOR_JOB",
POST_MEDIA_FOR_JOB_SUCCESS: "POST_MEDIA_FOR_JOB_SUCCESS",
POST_MEDIA_FOR_JOB_ERROR: "POST_MEDIA_FOR_JOB_ERROR",
GET_MEDIA_FOR_BILL: "GET_MEDIA_FOR_BILL",
SELECT_ALL_MEDIA_FOR_JOB: "SELECT_ALL_MEDIA_FOR_JOB",
DESELECT_ALL_MEDIA_FOR_JOB: "DESELECT_ALL_MEDIA_FOR_JOB",
};
export default MediaActionTypes;

View File

@@ -228,6 +228,7 @@
"saving": "Error encountered while saving. {{message}}"
},
"fields": {
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
"address1": "Address 1",
"address2": "Address 2",
"appt_alt_transport": "Appointment Alternative Transportation Options",
@@ -284,6 +285,7 @@
"lastnumberworkingdays": "Scoreboard - Last Number of Working Days",
"localmediaserverhttp": "Local Media Server - HTTP Path",
"localmediaservernetwork": "Local Media Server - Network Path",
"localmediatoken": "Local Media Server - Token",
"logo_img_footer_margin": "Footer Margin (px)",
"logo_img_header_margin": "Header Margin (px)",
"logo_img_path": "Shop Logo",
@@ -1864,6 +1866,7 @@
"updatedat": "Updated At"
},
"labels": {
"addtorelatedro": "Add to Related ROs",
"newnoteplaceholder": "Add a note...",
"notetoadd": "Note to Add"
},
@@ -1977,6 +1980,7 @@
"parts_orders": "Parts Orders",
"print": "Show Printed Form",
"receive": "Receive Parts Order",
"removefrompartsqueue": "Remove from Parts Queue?",
"returnpartsorder": "Return Parts Order"
},
"successes": {
@@ -2300,6 +2304,7 @@
"attendance_employee": "Employee Attendance",
"attendance_summary": "Attendance Summary (All Employees)",
"credits_not_received_date": "Credits not Received by Date",
"credits_not_received_date_vendorid": "Credits not Received by Vendor",
"csi": "CSI Responses",
"estimates_written_converted": "Estimates Written/Converted",
"estimator_detail": "Jobs by Estimator (Detail)",
@@ -2629,6 +2634,7 @@
},
"fields": {
"description": "Vehicle Description",
"notes": "Vehicle Notes",
"plate_no": "License Plate",
"plate_st": "Plate Jurisdiction",
"trim_color": "Trim Color",

View File

@@ -228,6 +228,7 @@
"saving": ""
},
"fields": {
"ReceivableCustomField": "",
"address1": "",
"address2": "",
"appt_alt_transport": "",
@@ -284,6 +285,7 @@
"lastnumberworkingdays": "",
"localmediaserverhttp": "",
"localmediaservernetwork": "",
"localmediatoken": "",
"logo_img_footer_margin": "",
"logo_img_header_margin": "",
"logo_img_path": "",
@@ -1864,6 +1866,7 @@
"updatedat": "Actualizado en"
},
"labels": {
"addtorelatedro": "",
"newnoteplaceholder": "Agrega una nota...",
"notetoadd": ""
},
@@ -1977,6 +1980,7 @@
"parts_orders": "",
"print": "Mostrar formulario impreso",
"receive": "",
"removefrompartsqueue": "",
"returnpartsorder": ""
},
"successes": {
@@ -2300,6 +2304,7 @@
"attendance_employee": "",
"attendance_summary": "",
"credits_not_received_date": "",
"credits_not_received_date_vendorid": "",
"csi": "",
"estimates_written_converted": "",
"estimator_detail": "",
@@ -2629,6 +2634,7 @@
},
"fields": {
"description": "Descripcion del vehiculo",
"notes": "",
"plate_no": "Placa",
"plate_st": "Jurisdicción de placas",
"trim_color": "Recortar color",

View File

@@ -228,6 +228,7 @@
"saving": ""
},
"fields": {
"ReceivableCustomField": "",
"address1": "",
"address2": "",
"appt_alt_transport": "",
@@ -284,6 +285,7 @@
"lastnumberworkingdays": "",
"localmediaserverhttp": "",
"localmediaservernetwork": "",
"localmediatoken": "",
"logo_img_footer_margin": "",
"logo_img_header_margin": "",
"logo_img_path": "",
@@ -1864,6 +1866,7 @@
"updatedat": "Mis à jour à"
},
"labels": {
"addtorelatedro": "",
"newnoteplaceholder": "Ajouter une note...",
"notetoadd": ""
},
@@ -1977,6 +1980,7 @@
"parts_orders": "",
"print": "Afficher le formulaire imprimé",
"receive": "",
"removefrompartsqueue": "",
"returnpartsorder": ""
},
"successes": {
@@ -2300,6 +2304,7 @@
"attendance_employee": "",
"attendance_summary": "",
"credits_not_received_date": "",
"credits_not_received_date_vendorid": "",
"csi": "",
"estimates_written_converted": "",
"estimator_detail": "",
@@ -2629,6 +2634,7 @@
},
"fields": {
"description": "Description du véhicule",
"notes": "",
"plate_no": "Plaque d'immatriculation",
"plate_st": "Juridiction de la plaque",
"trim_color": "Couleur de garniture",

View File

@@ -6,10 +6,10 @@ import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";
//import { split } from "apollo-link";
import apolloLogger from "apollo-link-logger";
import axios from "axios";
//import axios from "axios";
import { auth } from "../firebase/firebase.utils";
import errorLink from "../graphql/apollo-error-handling";
import { store } from "../redux/store";
//import { store } from "../redux/store";
const httpLink = new HttpLink({
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT,
});
@@ -48,25 +48,25 @@ const roundTripLink = new ApolloLink((operation, forward) => {
});
const TrackExecutionTime = async (operationName, time) => {
if (process.env.NODE_ENV === "development") return;
const rdxStore = store.getState();
try {
axios.post("/ioevent", {
operationName,
time,
dbevent: true,
user:
rdxStore.user &&
rdxStore.user.currentUser &&
rdxStore.user.currentUser.email,
imexshopid:
rdxStore.user &&
rdxStore.user.bodyshop &&
rdxStore.user.bodyshop.imexshopid,
});
} catch (error) {
console.log("IOEvent Error", error);
}
// if (process.env.NODE_ENV === "development") return;
// const rdxStore = store.getState();
// try {
// axios.post("/ioevent", {
// operationName,
// time,
// dbevent: true,
// user:
// rdxStore.user &&
// rdxStore.user.currentUser &&
// rdxStore.user.currentUser.email,
// imexshopid:
// rdxStore.user &&
// rdxStore.user.bodyshop &&
// rdxStore.user.bodyshop.imexshopid,
// });
// } catch (error) {
// console.log("IOEvent Error", error);
// }
};
const subscriptionMiddleware = {

View File

@@ -1510,6 +1510,22 @@ export const TemplateList = (type, context) => {
},
group: "sales",
},
credits_not_received_date_vendorid: {
title: i18n.t(
"reportcenter.templates.credits_not_received_date_vendorid"
),
subject: i18n.t(
"reportcenter.templates.credits_not_received_date_vendorid"
),
key: "credits_not_received_date_vendorid",
idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
group: "purchases",
},
}
: {}),
...(!type || type === "courtesycarcontract"

View File

@@ -2,5 +2,5 @@ import { store } from "../redux/store";
export function CreateExplorerLinkForJob({ jobid }) {
const bodyshop = store.getState().user.bodyshop;
return `imexmedia://${bodyshop.localmediaservernetwork}/Jobs/${jobid}`;
return `imexmedia://${bodyshop.localmediaservernetwork}\\Jobs\\${jobid}`;
}

File diff suppressed because it is too large Load Diff

View File

@@ -837,6 +837,7 @@
- last_name_first
- localmediaserverhttp
- localmediaservernetwork
- localmediatoken
- logo_img_path
- md_categories
- md_ccc_rates
@@ -927,6 +928,7 @@
- last_name_first
- localmediaserverhttp
- localmediaservernetwork
- localmediatoken
- logo_img_path
- md_categories
- md_ccc_rates
@@ -4923,60 +4925,62 @@
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- v_vin
- v_make_desc
- v_model_desc
- v_model_yr
- v_color
- v_paint_codes
- v_bstyle
- v_engine
- shopid
- db_v_code
- id
- notes
- plate_no
- plate_st
- v_cond
- v_prod_dt
- v_type
- v_trimcode
- shopid
- trim_color
- v_mldgcode
- v_options
- v_tone
- v_stage
- updated_at
- v_bstyle
- v_color
- v_cond
- v_engine
- v_make_desc
- v_makecode
- v_mldgcode
- v_model_desc
- v_model_yr
- v_options
- v_paint_codes
- v_prod_dt
- v_stage
- v_tone
- v_trimcode
- v_type
- v_vin
select_permissions:
- role: user
permission:
columns:
- v_paint_codes
- created_at
- db_v_code
- id
- notes
- plate_no
- plate_st
- shopid
- trim_color
- updated_at
- v_bstyle
- v_color
- v_cond
- v_engine
- v_makecode
- v_make_desc
- v_makecode
- v_mldgcode
- v_model_desc
- v_model_yr
- v_options
- v_paint_codes
- v_prod_dt
- v_stage
- v_tone
- v_trimcode
- v_type
- v_vin
- created_at
- updated_at
- id
- shopid
filter:
bodyshop:
associations:
@@ -4991,31 +4995,32 @@
- role: user
permission:
columns:
- v_paint_codes
- created_at
- db_v_code
- id
- notes
- plate_no
- plate_st
- shopid
- trim_color
- updated_at
- v_bstyle
- v_color
- v_cond
- v_engine
- v_makecode
- v_make_desc
- v_makecode
- v_mldgcode
- v_model_desc
- v_model_yr
- v_options
- v_paint_codes
- v_prod_dt
- v_stage
- v_tone
- v_trimcode
- v_type
- v_vin
- created_at
- updated_at
- id
- shopid
filter:
bodyshop:
associations:

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 "localmediatoken" text
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "localmediatoken" 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"."vehicles" add column "notes" text
-- null;

View File

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

File diff suppressed because one or more lines are too long

9612
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@
"version": "0.0.1",
"license": "UNLICENSED",
"engines": {
"node": "16.15.0",
"npm": "7.17.0"
"node": ">=16.0.0",
"npm": ">=8.0.0"
},
"scripts": {
"setup": "yarn && cd client && yarn",
@@ -17,21 +17,21 @@
"start": "node server.js"
},
"dependencies": {
"aws-sdk": "^2.1116.0",
"axios": "^0.24.0",
"aws-sdk": "^2.1136.0",
"axios": "^0.27.2",
"bluebird": "^3.7.2",
"body-parser": "^1.20.0",
"cloudinary": "^1.29.1",
"cloudinary": "^1.30.0",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"cors": "2.8.5",
"csrf": "^3.1.0",
"dinero.js": "^1.9.1",
"dotenv": "10.0.0",
"express": "^4.17.3",
"firebase-admin": "^10.0.2",
"graphql": "^16.3.0",
"graphql-request": "^3.7.0",
"dotenv": "16.0.1",
"express": "^4.18.1",
"firebase-admin": "^10.2.0",
"graphql": "^16.5.0",
"graphql-request": "^4.2.0",
"graylog2": "^0.2.1",
"inline-css": "^3.0.0",
"intuit-oauth": "^4.0.0",
@@ -40,16 +40,16 @@
"moment": "^2.29.3",
"moment-timezone": "^0.5.34",
"multer": "^1.4.4",
"node-mailjet": "^3.3.10",
"node-mailjet": "^3.4.1",
"node-quickbooks": "^2.0.39",
"nodemailer": "^6.7.3",
"phone": "^3.1.15",
"nodemailer": "^6.7.5",
"phone": "^3.1.17",
"query-string": "^7.1.1",
"soap": "^0.43.0",
"socket.io": "^4.4.1",
"socket.io": "^4.5.0",
"ssh2-sftp-client": "^8.0.0",
"stripe": "^8.217.0",
"twilio": "^3.76.1",
"stripe": "^9.1.0",
"twilio": "^3.77.0",
"uuid": "^8.3.2",
"xml2js": "^0.4.23",
"xmlbuilder2": "^3.0.2"

View File

@@ -147,7 +147,7 @@ exports.default = async (req, res) => {
res.status(200).json(ret);
} catch (error) {
console.log(error);
logger.log("qbo-payable-create-error", "ERROR", req.user.email, { error });
logger.log("qbo-payable-create-error", "ERROR", req.user.email, { error: error.message, stack: error.stack });
res.status(400).json(error);
}
};

View File

@@ -179,6 +179,7 @@ exports.default = async (req, res) => {
ret.push({ paymentid: payment.id, success: true });
} catch (error) {
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
error:
(error && error.authResponse && error.authResponse.body) ||
(error && error.message),
@@ -216,7 +217,7 @@ exports.default = async (req, res) => {
res.status(200).json(ret);
} catch (error) {
console.log(error);
logger.log("qbo-payment-create-error", "ERROR", req.user.email, { error });
logger.log("qbo-payment-create-error", "ERROR", req.user.email, { error: error.message, stack: error.stack });
res.status(400).json(error);
}
};

View File

@@ -197,7 +197,8 @@ exports.default = async (req, res) => {
} catch (error) {
console.log(error);
logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
error,
error: error.message,
stack: error.stack,
});
res.status(400).json(error);
}
@@ -503,7 +504,38 @@ async function InsertInvoice(
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid },
}),
CustomField: [
...(bodyshop.accountingconfig.ReceivableCustomField1
? [
{
DefinitionId: "1",
StringValue:
job[bodyshop.accountingconfig.ReceivableCustomField1],
Type: "StringType",
},
]
: []),
...(bodyshop.accountingconfig.ReceivableCustomField2
? [
{
DefinitionId: "2",
StringValue:
job[bodyshop.accountingconfig.ReceivableCustomField2],
Type: "StringType",
},
]
: []),
...(bodyshop.accountingconfig.ReceivableCustomField3
? [
{
DefinitionId: "3",
StringValue:
job[bodyshop.accountingconfig.ReceivableCustomField3],
Type: "StringType",
},
]
: []),
],
...(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&

View File

@@ -57,7 +57,7 @@ exports.default = async (req, res) => {
"ERROR",
req.user.email,
req.body.billsToQuery,
{ error }
{ error: error.message, stack: error.stack }
);
res.status(400).send(JSON.stringify(error));
}

View File

@@ -95,7 +95,7 @@ exports.default = async (req, res) => {
"error",
req.user.email,
req.body.paymentsToQuery,
error
{ error: error.message, stack: error.stack }
);
res.status(400).send(JSON.stringify(error));
}

View File

@@ -109,7 +109,7 @@ exports.default = async (req, res) => {
"error",
req.user.email,
req.body.jobIds,
{ error: error }
{ error: error.message, stack: error.stack }
);
res.status(400).send(JSON.stringify(error));
}

View File

@@ -139,6 +139,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
jobs(where: {id: {_in: $ids}}) {
id
job_totals
ded_amt
date_invoiced
ro_number
clm_total
@@ -459,6 +460,7 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
md_responsibility_centers
accountingconfig
timezone
md_ins_cos
}
payments(where: {id: {_in: $payments}}) {
id
@@ -486,6 +488,7 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
bodyshop{
accountingconfig
md_responsibility_centers
md_ins_cos
}
}
transactionid
@@ -914,7 +917,10 @@ exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) {
est_co_nm
est_ct_fn
est_ct_ln
vehicle{
id
notes
}
est_ph1
est_ea
selling_dealer

1844
yarn.lock

File diff suppressed because it is too large Load Diff