Added parts backorder + receiving for orders BOD-159
This commit is contained in:
24
_reference/SampleMetadata.md
Normal file
24
_reference/SampleMetadata.md
Normal file
File diff suppressed because one or more lines are too long
@@ -14513,6 +14513,53 @@
|
|||||||
<folder_node>
|
<folder_node>
|
||||||
<name>parts_orders</name>
|
<name>parts_orders</name>
|
||||||
<children>
|
<children>
|
||||||
|
<folder_node>
|
||||||
|
<name>actions</name>
|
||||||
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>backordered</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>receive</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>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
<name>errors</name>
|
<name>errors</name>
|
||||||
<children>
|
<children>
|
||||||
@@ -14689,6 +14736,69 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>oem_partno</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>order_date</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>order_number</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>quantity</name>
|
<name>quantity</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -14710,6 +14820,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>status</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>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { setModalContext } from "../../redux/modals/modals.actions";
|
|||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
import queryString from "query-string";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPartsOrderContext: (context) =>
|
setPartsOrderContext: (context) =>
|
||||||
@@ -22,7 +24,6 @@ export function InvoicesListTableComponent({
|
|||||||
job,
|
job,
|
||||||
loading,
|
loading,
|
||||||
invoicesQuery,
|
invoicesQuery,
|
||||||
selectedInvoice,
|
|
||||||
handleOnRowClick,
|
handleOnRowClick,
|
||||||
setPartsOrderContext,
|
setPartsOrderContext,
|
||||||
setInvoiceEnterContext,
|
setInvoiceEnterContext,
|
||||||
@@ -32,6 +33,8 @@ export function InvoicesListTableComponent({
|
|||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
});
|
});
|
||||||
|
const search = queryString.parse(useLocation().search);
|
||||||
|
const selectedInvoice = search.invoiceid;
|
||||||
|
|
||||||
const invoices = invoicesQuery.data ? invoicesQuery.data.invoices : [];
|
const invoices = invoicesQuery.data ? invoicesQuery.data.invoices : [];
|
||||||
const { refetch } = invoicesQuery;
|
const { refetch } = invoicesQuery;
|
||||||
@@ -91,7 +94,8 @@ export function InvoicesListTableComponent({
|
|||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
to={`/manage/invoices?invoiceid=${record.id}&vendorid=${record.vendorid}`}>
|
to={`/manage/invoices?invoiceid=${record.id}&vendorid=${record.vendorid}`}
|
||||||
|
>
|
||||||
<Button>{t("invoices.actions.edit")}</Button>
|
<Button>{t("invoices.actions.edit")}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Button
|
<Button
|
||||||
@@ -116,7 +120,8 @@ export function InvoicesListTableComponent({
|
|||||||
isReturn: true,
|
isReturn: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
{t("invoices.actions.return")}
|
{t("invoices.actions.return")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -232,11 +237,11 @@ export function InvoicesListTableComponent({
|
|||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
<Table
|
<Table
|
||||||
size='small'
|
size="small"
|
||||||
scroll={{ x: "50%", y: "40rem" }}
|
scroll={{ x: "50%", y: "40rem" }}
|
||||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey='id'
|
rowKey="id"
|
||||||
dataSource={record.invoicelines}
|
dataSource={record.invoicelines}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -246,9 +251,9 @@ export function InvoicesListTableComponent({
|
|||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size='small'
|
size="small"
|
||||||
title={() => (
|
title={() => (
|
||||||
<div className='imex-table-header'>
|
<div className="imex-table-header">
|
||||||
<Button onClick={() => refetch()}>
|
<Button onClick={() => refetch()}>
|
||||||
<SyncOutlined />
|
<SyncOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -260,7 +265,8 @@ export function InvoicesListTableComponent({
|
|||||||
job,
|
job,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{t("jobs.actions.postInvoices")}
|
{t("jobs.actions.postInvoices")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -273,10 +279,11 @@ export function InvoicesListTableComponent({
|
|||||||
(invoicesQuery.data && invoicesQuery.data.invoices) || [],
|
(invoicesQuery.data && invoicesQuery.data.invoices) || [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{t("jobs.actions.reconcile")}
|
{t("jobs.actions.reconcile")}
|
||||||
</Button>{" "}
|
</Button>{" "}
|
||||||
<div className='imex-table-header__search'>
|
<div className="imex-table-header__search">
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -290,7 +297,7 @@ export function InvoicesListTableComponent({
|
|||||||
expandedRowRender={rowExpander}
|
expandedRowRender={rowExpander}
|
||||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey='id'
|
rowKey="id"
|
||||||
dataSource={invoices}
|
dataSource={invoices}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
expandable={{
|
expandable={{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Dropdown, Input, Menu, Table } from "antd";
|
import { Button, Dropdown, Input, Menu, Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -11,6 +12,8 @@ import AllocationsAssignmentContainer from "../allocations-assignment/allocation
|
|||||||
import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
|
import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
|
||||||
import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
|
import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
|
||||||
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
||||||
|
import queryString from "query-string";
|
||||||
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setJobLineEditContext: (context) =>
|
setJobLineEditContext: (context) =>
|
||||||
@@ -35,6 +38,9 @@ export function JobLinesComponent({
|
|||||||
});
|
});
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const search = queryString.parse(useLocation().search);
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "#",
|
title: "#",
|
||||||
@@ -213,7 +219,8 @@ export function JobLinesComponent({
|
|||||||
actions: { refetch: refetch },
|
actions: { refetch: refetch },
|
||||||
context: record,
|
context: record,
|
||||||
});
|
});
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{t("general.actions.edit")}
|
{t("general.actions.edit")}
|
||||||
</Button>
|
</Button>
|
||||||
<AllocationsAssignmentContainer
|
<AllocationsAssignmentContainer
|
||||||
@@ -237,9 +244,9 @@ export function JobLinesComponent({
|
|||||||
|
|
||||||
const markMenu = (
|
const markMenu = (
|
||||||
<Menu onClick={handleMark}>
|
<Menu onClick={handleMark}>
|
||||||
<Menu.Item key='PAA'>PAA</Menu.Item>
|
<Menu.Item key="PAA">PAA</Menu.Item>
|
||||||
<Menu.Item key='PAN'>PAN</Menu.Item>
|
<Menu.Item key="PAN">PAN</Menu.Item>
|
||||||
<Menu.Item key='PAL'>PAL</Menu.Item>
|
<Menu.Item key="PAL">PAL</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -248,16 +255,19 @@ export function JobLinesComponent({
|
|||||||
<PartsOrderModalContainer />
|
<PartsOrderModalContainer />
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey='id'
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size='small'
|
size="small"
|
||||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||||
dataSource={jobLines}
|
dataSource={jobLines}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
scroll={{ x: true, y: "40rem" }}
|
scroll={{ x: true, y: "40rem" }}
|
||||||
title={() => {
|
title={() => {
|
||||||
return (
|
return (
|
||||||
<div className='imex-table-header'>
|
<div className="imex-table-header">
|
||||||
|
<Button onClick={() => refetch()}>
|
||||||
|
<SyncOutlined />
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={selectedLines.length > 0 ? false : true}
|
disabled={selectedLines.length > 0 ? false : true}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -268,7 +278,8 @@ export function JobLinesComponent({
|
|||||||
linesToOrder: selectedLines,
|
linesToOrder: selectedLines,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{t("parts.actions.order")}
|
{t("parts.actions.order")}
|
||||||
</Button>
|
</Button>
|
||||||
<Dropdown overlay={markMenu} trigger={["click"]}>
|
<Dropdown overlay={markMenu} trigger={["click"]}>
|
||||||
@@ -284,10 +295,11 @@ export function JobLinesComponent({
|
|||||||
actions: { refetch: refetch },
|
actions: { refetch: refetch },
|
||||||
context: { jobid: jobId },
|
context: { jobid: jobId },
|
||||||
});
|
});
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{t("joblines.actions.new")}
|
{t("joblines.actions.new")}
|
||||||
</Button>
|
</Button>
|
||||||
<div className='imex-table-header__search'>
|
<div className="imex-table-header__search">
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -300,11 +312,16 @@ export function JobLinesComponent({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
expandedRowRender={(record) => (
|
expandedRowRender={(record) => (
|
||||||
<div style={{ margin: 0 }}>
|
<div>
|
||||||
<strong>{t("parts_orders.labels.orderhistory")}</strong>
|
<strong>{t("parts_orders.labels.orderhistory")}</strong>
|
||||||
{record.parts_order_lines.map((item) => (
|
{record.parts_order_lines.map((item) => (
|
||||||
<div key={item.id}>
|
<div key={item.id}>
|
||||||
{`${item.parts_order.order_number || ""} from `}
|
<Link
|
||||||
|
to={`/manage/jobs/${jobId}?tab=partssublet&partsorderid=${item.parts_order.id}`}
|
||||||
|
>
|
||||||
|
{item.parts_order.order_number || ""}
|
||||||
|
</Link>
|
||||||
|
-
|
||||||
<Link to={`/manage/shop/vendors/${item.parts_order.vendor.id}`}>
|
<Link to={`/manage/shop/vendors/${item.parts_order.vendor.id}`}>
|
||||||
{item.parts_order.vendor.name || ""}
|
{item.parts_order.vendor.name || ""}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export function JobsCloseExportButton({ bodyshop, jobId, disabled }) {
|
|||||||
let PartnerResponse;
|
let PartnerResponse;
|
||||||
try {
|
try {
|
||||||
PartnerResponse = await axios.post(
|
PartnerResponse = await axios.post(
|
||||||
"http://localhost:1337/qb/",
|
// "http://localhost:1337/qb/",
|
||||||
|
"http://b47e67f9cbe3.ngrok.io/qb/",
|
||||||
QbXmlResponse.data,
|
QbXmlResponse.data,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(setModalContext({ context: context, modal: "schedule" })),
|
dispatch(setModalContext({ context: context, modal: "schedule" })),
|
||||||
setInvoiceEnterContext: (context) =>
|
setInvoiceEnterContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
|
dispatch(setModalContext({ context: context, modal: "invoiceEnter" })),
|
||||||
|
setPaymentContext: (context) =>
|
||||||
|
dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsDetailHeaderActions({
|
export function JobsDetailHeaderActions({
|
||||||
@@ -29,12 +31,13 @@ export function JobsDetailHeaderActions({
|
|||||||
refetch,
|
refetch,
|
||||||
setScheduleContext,
|
setScheduleContext,
|
||||||
setInvoiceEnterContext,
|
setInvoiceEnterContext,
|
||||||
|
setPaymentContext,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const statusmenu = (
|
const statusmenu = (
|
||||||
<Menu key='popovermenu'>
|
<Menu key="popovermenu">
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setScheduleContext({
|
setScheduleContext({
|
||||||
@@ -44,30 +47,43 @@ export function JobsDetailHeaderActions({
|
|||||||
job: job,
|
job: job,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{t("jobs.actions.schedule")}
|
{t("jobs.actions.schedule")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
<Menu.Item key='cccontract'>
|
key="enterpayments"
|
||||||
|
onClick={() => {
|
||||||
|
setPaymentContext({
|
||||||
|
actions: {},
|
||||||
|
context: { jobId: job.id },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("menus.header.enterpayment")}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="cccontract">
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: "/manage/courtesycars/contracts/new",
|
pathname: "/manage/courtesycars/contracts/new",
|
||||||
state: { jobId: job.id },
|
state: { jobId: job.id },
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{t("menus.jobsactions.newcccontract")}
|
{t("menus.jobsactions.newcccontract")}
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key='addtoproduction'
|
key="addtoproduction"
|
||||||
disabled={!!!job.converted || !!job.inproduction}
|
disabled={!!!job.converted || !!job.inproduction}
|
||||||
onClick={() => AddToProduction(client, job.id, refetch)}>
|
onClick={() => AddToProduction(client, job.id, refetch)}
|
||||||
|
>
|
||||||
{t("jobs.actions.addtoproduction")}
|
{t("jobs.actions.addtoproduction")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key='duplicatejob'>
|
<Menu.Item key="duplicatejob">
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={t("jobs.labels.duplicateconfirm")}
|
title={t("jobs.labels.duplicateconfirm")}
|
||||||
okText='Yes'
|
okText="Yes"
|
||||||
cancelText='No'
|
cancelText="No"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onConfirm={() =>
|
onConfirm={() =>
|
||||||
DuplicateJob(
|
DuplicateJob(
|
||||||
@@ -79,12 +95,13 @@ export function JobsDetailHeaderActions({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
getPopupContainer={(trigger) => trigger.parentNode}>
|
getPopupContainer={(trigger) => trigger.parentNode}
|
||||||
|
>
|
||||||
{t("menus.jobsactions.duplicate")}
|
{t("menus.jobsactions.duplicate")}
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key='postinvoices'
|
key="postinvoices"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setInvoiceEnterContext({
|
setInvoiceEnterContext({
|
||||||
actions: { refetch: refetch },
|
actions: { refetch: refetch },
|
||||||
@@ -92,14 +109,16 @@ export function JobsDetailHeaderActions({
|
|||||||
job: job,
|
job: job,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{t("jobs.actions.postInvoices")}
|
{t("jobs.actions.postInvoices")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key='closejob'>
|
<Menu.Item key="closejob">
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: `/manage/jobs/${job.id}/close`,
|
pathname: `/manage/jobs/${job.id}/close`,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{t("menus.jobsactions.closejob")}
|
{t("menus.jobsactions.closejob")}
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
@@ -108,10 +127,11 @@ export function JobsDetailHeaderActions({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
className='imex-flex-row__margin'
|
className="imex-flex-row__margin"
|
||||||
overlay={statusmenu}
|
overlay={statusmenu}
|
||||||
trigger={["click"]}
|
trigger={["click"]}
|
||||||
key='changestatus'>
|
key="changestatus"
|
||||||
|
>
|
||||||
<Button>
|
<Button>
|
||||||
{t("general.labels.actions")} <DownCircleFilled />
|
{t("general.labels.actions")} <DownCircleFilled />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import AlertComponent from "../alert/alert.component";
|
|||||||
import InvoicesListTableComponent from "../invoices-list-table/invoices-list-table.component";
|
import InvoicesListTableComponent from "../invoices-list-table/invoices-list-table.component";
|
||||||
import JobInvoicesTotalsComponent from "../job-invoices-total/job-invoices-total.component";
|
import JobInvoicesTotalsComponent from "../job-invoices-total/job-invoices-total.component";
|
||||||
import PartsOrderModal from "../parts-order-modal/parts-order-modal.container";
|
import PartsOrderModal from "../parts-order-modal/parts-order-modal.container";
|
||||||
|
import { PartsOrderListTableComponent } from "../parts-order-list-table/parts-order-list-table.component";
|
||||||
const tableCol = {
|
const tableCol = {
|
||||||
xs: {
|
xs: {
|
||||||
span: 24,
|
span: 24,
|
||||||
@@ -25,22 +26,29 @@ const totalsCol = {
|
|||||||
export default function JobsDetailPliComponent({
|
export default function JobsDetailPliComponent({
|
||||||
job,
|
job,
|
||||||
invoicesQuery,
|
invoicesQuery,
|
||||||
handleOnRowClick,
|
handleInvoiceOnRowClick,
|
||||||
|
handlePartsOrderOnRowClick,
|
||||||
selectedInvoice,
|
selectedInvoice,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PartsOrderModal />
|
<PartsOrderModal />
|
||||||
{invoicesQuery.error ? (
|
{invoicesQuery.error ? (
|
||||||
<AlertComponent message={invoicesQuery.error.message} type='error' />
|
<AlertComponent message={invoicesQuery.error.message} type="error" />
|
||||||
) : null}
|
) : null}
|
||||||
<Row>
|
<Row>
|
||||||
<Col {...tableCol}>
|
<Col {...tableCol}>
|
||||||
|
<PartsOrderListTableComponent
|
||||||
|
job={job}
|
||||||
|
loading={invoicesQuery.loading}
|
||||||
|
handleOnRowClick={handlePartsOrderOnRowClick}
|
||||||
|
selectedInvoice={selectedInvoice}
|
||||||
|
invoicesQuery={invoicesQuery}
|
||||||
|
/>
|
||||||
<InvoicesListTableComponent
|
<InvoicesListTableComponent
|
||||||
job={job}
|
job={job}
|
||||||
loading={invoicesQuery.loading}
|
loading={invoicesQuery.loading}
|
||||||
handleOnRowClick={handleOnRowClick}
|
handleOnRowClick={handleInvoiceOnRowClick}
|
||||||
selectedInvoice={selectedInvoice}
|
|
||||||
invoicesQuery={invoicesQuery}
|
invoicesQuery={invoicesQuery}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default function JobsDetailPliContainer({ job }) {
|
|||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const handleOnRowClick = (record) => {
|
const handleInvoiceOnRowClick = (record) => {
|
||||||
if (record) {
|
if (record) {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
search.invoiceid = record.id;
|
search.invoiceid = record.id;
|
||||||
@@ -25,12 +25,24 @@ export default function JobsDetailPliContainer({ job }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePartsOrderOnRowClick = (record) => {
|
||||||
|
if (record) {
|
||||||
|
if (record.id) {
|
||||||
|
search.partsorderid = record.id;
|
||||||
|
history.push({ search: queryString.stringify(search) });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete search.partsorderid;
|
||||||
|
history.push({ search: queryString.stringify(search) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<JobsDetailPliComponent
|
<JobsDetailPliComponent
|
||||||
job={job}
|
job={job}
|
||||||
invoicesQuery={invoicesQuery}
|
invoicesQuery={invoicesQuery}
|
||||||
handleOnRowClick={handleOnRowClick}
|
handleInvoiceOnRowClick={handleInvoiceOnRowClick}
|
||||||
selectedInvoice={search.invoiceid}
|
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Button } from "antd";
|
||||||
|
import { useMutation } from "@apollo/react-hooks";
|
||||||
|
import { MUTATION_BACKORDER_PART_LINE } from "../../graphql/parts-orders.queries";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function PartsOrderLineBackorderButton({
|
||||||
|
partsOrderStatus,
|
||||||
|
partsLineId,
|
||||||
|
jobLineId,
|
||||||
|
bodyshop,
|
||||||
|
}) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [backorderLine] = useMutation(MUTATION_BACKORDER_PART_LINE);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const isAlreadyBackordered =
|
||||||
|
bodyshop.md_order_statuses.default_bo === partsOrderStatus;
|
||||||
|
|
||||||
|
const handleOnClick = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const result = await backorderLine({
|
||||||
|
variables: {
|
||||||
|
jobLineId,
|
||||||
|
partsLineId,
|
||||||
|
status: isAlreadyBackordered
|
||||||
|
? bodyshop.md_order_statuses.default_received || "Received*"
|
||||||
|
: bodyshop.md_order_statuses.default_bo || "Backordered*",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button loading={loading} onClick={handleOnClick}>
|
||||||
|
{isAlreadyBackordered
|
||||||
|
? t("parts_orders.actions.receive")
|
||||||
|
: t("parts_orders.actions.backordered")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, null)(PartsOrderLineBackorderButton);
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
|
import { Button, Input, Table } from "antd";
|
||||||
|
import queryString from "query-string";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
|
|
||||||
|
export function PartsOrderListTableComponent({
|
||||||
|
job,
|
||||||
|
loading,
|
||||||
|
invoicesQuery,
|
||||||
|
|
||||||
|
handleOnRowClick,
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [state, setState] = useState({
|
||||||
|
sortedInfo: {},
|
||||||
|
});
|
||||||
|
const search = queryString.parse(useLocation().search);
|
||||||
|
const selectedpartsorder = search.partsorderid;
|
||||||
|
|
||||||
|
const parts_orders = invoicesQuery.data
|
||||||
|
? invoicesQuery.data.parts_orders
|
||||||
|
: [];
|
||||||
|
const { refetch } = invoicesQuery;
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("vendors.fields.name"),
|
||||||
|
dataIndex: "vendorname",
|
||||||
|
key: "vendorname",
|
||||||
|
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => <span>{record.vendor.name}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("parts_orders.fields.order_number"),
|
||||||
|
dataIndex: "order_number",
|
||||||
|
key: "order_number",
|
||||||
|
sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "invoice_number" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("parts_orders.fields.order_date"),
|
||||||
|
dataIndex: "order_date",
|
||||||
|
key: "order_date",
|
||||||
|
sorter: (a, b) => a.order_date - b.order_date,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "order_date" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<DateFormatter>{record.order_date}</DateFormatter>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("parts_orders.fields.deliver_by"),
|
||||||
|
dataIndex: "deliver_by",
|
||||||
|
key: "deliver_by",
|
||||||
|
sorter: (a, b) => a.deliver_by - b.deliver_by,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "deliver_by" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<DateFormatter>{record.deliver_by}</DateFormatter>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
|
};
|
||||||
|
|
||||||
|
const rowExpander = (record) => {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("parts_orders.fields.line_desc"),
|
||||||
|
dataIndex: "line_desc",
|
||||||
|
key: "line_desc",
|
||||||
|
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("parts_orders.fields.db_price"),
|
||||||
|
dataIndex: "db_price",
|
||||||
|
key: "db_price",
|
||||||
|
sorter: (a, b) => a.db_price - b.db_price,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "db_price" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<CurrencyFormatter>{record.db_price}</CurrencyFormatter>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("parts_orders.fields.act_price"),
|
||||||
|
dataIndex: "act_price",
|
||||||
|
key: "act_price",
|
||||||
|
sorter: (a, b) => a.act_price - b.act_price,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("parts_orders.fields.oem_partno"),
|
||||||
|
dataIndex: "oem_partno",
|
||||||
|
key: "oem_partno",
|
||||||
|
sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("parts_orders.fields.line_remarks"),
|
||||||
|
dataIndex: "line_remarks",
|
||||||
|
key: "line_remarks",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("parts_orders.fields.status"),
|
||||||
|
dataIndex: "status",
|
||||||
|
key: "status",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("general.labels.actions"),
|
||||||
|
dataIndex: "actions",
|
||||||
|
key: "actions",
|
||||||
|
render: (text, record) => (
|
||||||
|
<div>
|
||||||
|
<PartsOrderLineBackorderButton
|
||||||
|
partsOrderStatus={record.status}
|
||||||
|
partsLineId={record.id}
|
||||||
|
jobLineId={record.job_line_id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
size="small"
|
||||||
|
scroll={{ x: "50%", y: "40rem" }}
|
||||||
|
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={record.parts_order_lines}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
loading={loading}
|
||||||
|
size="small"
|
||||||
|
title={() => (
|
||||||
|
<div className="imex-table-header">
|
||||||
|
<Button onClick={() => refetch()}>
|
||||||
|
<SyncOutlined />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="imex-table-header__search">
|
||||||
|
<Input.Search
|
||||||
|
placeholder={t("general.labels.search")}
|
||||||
|
onChange={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
scroll={{ x: "50%", y: "40rem" }}
|
||||||
|
expandedRowRender={rowExpander}
|
||||||
|
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={parts_orders}
|
||||||
|
onChange={handleTableChange}
|
||||||
|
expandable={{
|
||||||
|
expandedRowKeys: [selectedpartsorder],
|
||||||
|
onExpand: (expanded, record) => {
|
||||||
|
handleOnRowClick(expanded ? record : null);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
rowSelection={{
|
||||||
|
onSelect: (record) => {
|
||||||
|
handleOnRowClick(record);
|
||||||
|
},
|
||||||
|
selectedRowKeys: [selectedpartsorder],
|
||||||
|
type: "radio",
|
||||||
|
}}
|
||||||
|
onRow={(record, rowIndex) => {
|
||||||
|
return {
|
||||||
|
onClick: (event) => {
|
||||||
|
handleOnRowClick(record);
|
||||||
|
}, // click row
|
||||||
|
onDoubleClick: (event) => {}, // double click row
|
||||||
|
onContextMenu: (event) => {}, // right button click row
|
||||||
|
onMouseEnter: (event) => {}, // mouse enter row
|
||||||
|
onMouseLeave: (event) => {}, // mouse leave row
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default connect(null, mapDispatchToProps)(PartsOrderListTableComponent);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useElements, useStripe, CardElement } from "@stripe/react-stripe-js";
|
import { useElements, useStripe, CardElement } from "@stripe/react-stripe-js";
|
||||||
import { Form, Modal, notification } from "antd";
|
import { Form, Modal, notification } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -37,6 +37,7 @@ function InvoiceEnterModalContainer({
|
|||||||
const elements = useElements();
|
const elements = useElements();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { context, actions, visible } = paymentModal;
|
const { context, actions, visible } = paymentModal;
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const stripeStateArr = useState({
|
const stripeStateArr = useState({
|
||||||
error: null,
|
error: null,
|
||||||
@@ -118,6 +119,10 @@ function InvoiceEnterModalContainer({
|
|||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) form.resetFields();
|
||||||
|
}, [visible, form]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t("payments.labels.new")}
|
title={t("payments.labels.new")}
|
||||||
|
|||||||
@@ -36,11 +36,6 @@ export function ProductionBoardKanbanComponent({ data, bodyshop }) {
|
|||||||
bodyshop.md_ro_statuses.production_statuses,
|
bodyshop.md_ro_statuses.production_statuses,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const findById = (id) => {
|
|
||||||
return id;
|
|
||||||
//return (data && data.find((x) => x.id === id).ro_number) || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
|
|
||||||
const handleDragEnd = async (card, source, destination) => {
|
const handleDragEnd = async (card, source, destination) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { Button, Form, Input, InputNumber, Select, Row, Col } from "antd";
|
import { Button, Col, Form, Input, InputNumber, Row } from "antd";
|
||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
//TODO Fix up styles.
|
//TODO Fix up styles.
|
||||||
export default function ShopInfoSchedulingComponent({ form }) {
|
export default function ShopInfoSchedulingComponent({ form }) {
|
||||||
|
|||||||
@@ -38,7 +38,32 @@ export const QUERY_ALL_INVOICES_PAGINATED = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const QUERY_INVOICES_BY_JOBID = gql`
|
export const QUERY_INVOICES_BY_JOBID = gql`
|
||||||
query QUERY_INVOICES_BY_JOBID($jobid: uuid!) {
|
query QUERY_PARTS_INVOICES_BY_JOBID($jobid: uuid!) {
|
||||||
|
parts_orders(
|
||||||
|
where: { jobid: { _eq: $jobid } }
|
||||||
|
order_by: { order_date: desc }
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
vendor {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
order_date
|
||||||
|
deliver_by
|
||||||
|
parts_order_lines {
|
||||||
|
id
|
||||||
|
act_price
|
||||||
|
db_price
|
||||||
|
line_desc
|
||||||
|
oem_partno
|
||||||
|
status
|
||||||
|
line_remarks
|
||||||
|
quantity
|
||||||
|
job_line_id
|
||||||
|
}
|
||||||
|
order_number
|
||||||
|
user_email
|
||||||
|
}
|
||||||
invoices(where: { jobid: { _eq: $jobid } }, order_by: { date: desc }) {
|
invoices(where: { jobid: { _eq: $jobid } }, order_by: { date: desc }) {
|
||||||
id
|
id
|
||||||
vendorid
|
vendorid
|
||||||
|
|||||||
@@ -9,3 +9,30 @@ export const INSERT_NEW_PARTS_ORDERS = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const MUTATION_BACKORDER_PART_LINE = gql`
|
||||||
|
mutation MUTATION_BACKORDER_PART_LINE(
|
||||||
|
$jobLineId: uuid!
|
||||||
|
$partsLineId: uuid!
|
||||||
|
$status: String!
|
||||||
|
) {
|
||||||
|
update_parts_order_lines(
|
||||||
|
where: { id: { _eq: $partsLineId } }
|
||||||
|
_set: { status: $status }
|
||||||
|
) {
|
||||||
|
returning {
|
||||||
|
status
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_joblines(
|
||||||
|
where: { id: { _eq: $jobLineId } }
|
||||||
|
_set: { status: $status }
|
||||||
|
) {
|
||||||
|
returning {
|
||||||
|
status
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -899,6 +899,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parts_orders": {
|
"parts_orders": {
|
||||||
|
"actions": {
|
||||||
|
"backordered": "Backordered",
|
||||||
|
"receive": "Receive"
|
||||||
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"creating": "Error encountered when creating parts order. "
|
"creating": "Error encountered when creating parts order. "
|
||||||
},
|
},
|
||||||
@@ -910,7 +914,11 @@
|
|||||||
"line_desc": "Line Description",
|
"line_desc": "Line Description",
|
||||||
"line_remarks": "Remarks",
|
"line_remarks": "Remarks",
|
||||||
"lineremarks": "Line Remarks",
|
"lineremarks": "Line Remarks",
|
||||||
"quantity": "Qty."
|
"oem_partno": "Part #",
|
||||||
|
"order_date": "Order Date",
|
||||||
|
"order_number": "Order Number",
|
||||||
|
"quantity": "Qty.",
|
||||||
|
"status": "Status"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"email": "Send by Email",
|
"email": "Send by Email",
|
||||||
|
|||||||
@@ -899,6 +899,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parts_orders": {
|
"parts_orders": {
|
||||||
|
"actions": {
|
||||||
|
"backordered": "",
|
||||||
|
"receive": ""
|
||||||
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"creating": "Se encontró un error al crear el pedido de piezas."
|
"creating": "Se encontró un error al crear el pedido de piezas."
|
||||||
},
|
},
|
||||||
@@ -910,7 +914,11 @@
|
|||||||
"line_desc": "",
|
"line_desc": "",
|
||||||
"line_remarks": "",
|
"line_remarks": "",
|
||||||
"lineremarks": "Comentarios de línea",
|
"lineremarks": "Comentarios de línea",
|
||||||
"quantity": ""
|
"oem_partno": "",
|
||||||
|
"order_date": "",
|
||||||
|
"order_number": "",
|
||||||
|
"quantity": "",
|
||||||
|
"status": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"email": "Enviar por correo electrónico",
|
"email": "Enviar por correo electrónico",
|
||||||
|
|||||||
@@ -899,6 +899,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parts_orders": {
|
"parts_orders": {
|
||||||
|
"actions": {
|
||||||
|
"backordered": "",
|
||||||
|
"receive": ""
|
||||||
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"creating": "Erreur rencontrée lors de la création de la commande de pièces."
|
"creating": "Erreur rencontrée lors de la création de la commande de pièces."
|
||||||
},
|
},
|
||||||
@@ -910,7 +914,11 @@
|
|||||||
"line_desc": "",
|
"line_desc": "",
|
||||||
"line_remarks": "",
|
"line_remarks": "",
|
||||||
"lineremarks": "Remarques sur la ligne",
|
"lineremarks": "Remarques sur la ligne",
|
||||||
"quantity": ""
|
"oem_partno": "",
|
||||||
|
"order_date": "",
|
||||||
|
"order_number": "",
|
||||||
|
"quantity": "",
|
||||||
|
"status": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"email": "Envoyé par email",
|
"email": "Envoyé par email",
|
||||||
|
|||||||
@@ -36,22 +36,20 @@
|
|||||||
data = data.replace(/\s/g, " ");
|
data = data.replace(/\s/g, " ");
|
||||||
var track = data.match(/(.*?\?)(.*?\?)(.*?\?)/);
|
var track = data.match(/(.*?\?)(.*?\?)(.*?\?)/);
|
||||||
var res1 = track[1].match(
|
var res1 = track[1].match(
|
||||||
/(\%)([A-Z]{2})([^\^]{0,13})\^?([^\^]{0,35})\^?([^\^]{0,60})\^?\s*?\?/
|
/(%)([A-Z]{2})([^^]{0,13})\^?([^^]{0,35})\^?([^^]{0,60})\^?\s*?\?/
|
||||||
);
|
);
|
||||||
var res2 = track[2].match(
|
var res2 = track[2].match(
|
||||||
/(;)(\d{6})(\d{0,13})(\=)(\d{4})(\d{8})(\d{0,5})\=?\?/
|
/(;)(\d{6})(\d{0,13})(=)(\d{4})(\d{8})(\d{0,5})=?\?/
|
||||||
);
|
);
|
||||||
var res3 = track[3].match(
|
var res3 = track[3].match(
|
||||||
/(\#|\%|\+)(\d|\!|\")(\d|\s|.)([0-9A-Z ]{11})([0-9A-Z ]{2})([0-9A-Z ]{10})([0-9A-Z ]{4})([12MF ]{1})([0-9A-Z ]{3})([0-9A-Z ]{3})([0-9A-Z ]{3})([0-9A-Z ]{3})(.*?)\?/
|
/(#|%|\+)(\d|!|")(\d|\s|.)([0-9A-Z ]{11})([0-9A-Z ]{2})([0-9A-Z ]{10})([0-9A-Z ]{4})([12MF ]{1})([0-9A-Z ]{3})([0-9A-Z ]{3})([0-9A-Z ]{3})([0-9A-Z ]{3})(.*?)\?/
|
||||||
);
|
);
|
||||||
var state = res1[2];
|
var state = res1[2];
|
||||||
return {
|
return {
|
||||||
state: state,
|
state: state,
|
||||||
city: res1[3],
|
city: res1[3],
|
||||||
name: (function () {
|
name: (function () {
|
||||||
var res = res1[4].match(
|
var res = res1[4].match(/([^$]{0,35})\$?([^$]{0,35})?\$?([^$]{0,35})?/);
|
||||||
/([^\$]{0,35})\$?([^\$]{0,35})?\$?([^\$]{0,35})?/
|
|
||||||
);
|
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
return {
|
return {
|
||||||
last: res[1],
|
last: res[1],
|
||||||
@@ -90,13 +88,12 @@
|
|||||||
switch (Number(res3[8])) {
|
switch (Number(res3[8])) {
|
||||||
case 1:
|
case 1:
|
||||||
return "MALE";
|
return "MALE";
|
||||||
break;
|
|
||||||
case 2:
|
case 2:
|
||||||
return "FEMALE";
|
return "FEMALE";
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return "MISSING/INVALID";
|
return "MISSING/INVALID";
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
height: res3[9],
|
height: res3[9],
|
||||||
@@ -452,7 +449,7 @@
|
|||||||
var parsedData = {};
|
var parsedData = {};
|
||||||
var res = data.match(parseRegex);
|
var res = data.match(parseRegex);
|
||||||
|
|
||||||
for (var i = 1; i < res.length; i++) {
|
for (i = 1; i < res.length; i++) {
|
||||||
if (res[i] !== undefined) {
|
if (res[i] !== undefined) {
|
||||||
parsedData[String(res[i]).substring(0, 3)] = res[i].substring(3).trim();
|
parsedData[String(res[i]).substring(0, 3)] = res[i].substring(3).trim();
|
||||||
}
|
}
|
||||||
@@ -525,10 +522,10 @@
|
|||||||
switch (Number(parsedData.DBC)) {
|
switch (Number(parsedData.DBC)) {
|
||||||
case 1:
|
case 1:
|
||||||
return "MALE";
|
return "MALE";
|
||||||
break;
|
|
||||||
case 2:
|
case 2:
|
||||||
return "FEMALE";
|
return "FEMALE";
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
if (parsedData.DBC[0] === "M") {
|
if (parsedData.DBC[0] === "M") {
|
||||||
return "MALE";
|
return "MALE";
|
||||||
@@ -536,7 +533,6 @@
|
|||||||
return "FEMALE";
|
return "FEMALE";
|
||||||
}
|
}
|
||||||
return "MISSING/INVALID";
|
return "MISSING/INVALID";
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
height: undefined,
|
height: undefined,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
exports.DineroQbFormat = "0,0.00";
|
exports.DineroQbFormat = "0.00";
|
||||||
|
|||||||
@@ -72,13 +72,224 @@ exports.default = async (req, res) => {
|
|||||||
qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop),
|
qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop),
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200).json(QbXmlToExecute);
|
res.status(200).json([{ id: jobId, okStatusCodes: ["0"], qbxml: t }]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("error", error);
|
console.log("error", error);
|
||||||
res.status(400).send(JSON.stringify(error));
|
res.status(400).send(JSON.stringify(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const t = `<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<?qbxml version="13.0"?>
|
||||||
|
<QBXML>
|
||||||
|
<QBXMLMsgsRq onError="continueOnError">
|
||||||
|
<CustomerAddRq>
|
||||||
|
<CustomerAdd>
|
||||||
|
<Name>Insurance Corporation of British Co</Name>
|
||||||
|
<BillAddress>
|
||||||
|
<Addr1>21291 122B AVE</Addr1>
|
||||||
|
<City>MAPLE RIDGE</City>
|
||||||
|
<State>BC</State>
|
||||||
|
</BillAddress>
|
||||||
|
</CustomerAdd>
|
||||||
|
</CustomerAddRq>
|
||||||
|
|
||||||
|
<CustomerAddRq>
|
||||||
|
<CustomerAdd>
|
||||||
|
<Name>CLOVER LANDON #4</Name>
|
||||||
|
<ParentRef>
|
||||||
|
<FullName>Insurance Corporation of British Co</FullName>
|
||||||
|
</ParentRef>
|
||||||
|
</CustomerAdd>
|
||||||
|
</CustomerAddRq>
|
||||||
|
|
||||||
|
<CustomerAddRq>
|
||||||
|
<CustomerAdd>
|
||||||
|
<Name>RO29</Name>
|
||||||
|
<ParentRef>
|
||||||
|
<FullName>Insurance Corporation of British Co:CLOVER LANDON #4</FullName>
|
||||||
|
</ParentRef>
|
||||||
|
</CustomerAdd>
|
||||||
|
</CustomerAddRq>
|
||||||
|
|
||||||
|
<InvoiceAddRq>
|
||||||
|
<InvoiceAdd>
|
||||||
|
<CustomerRef>
|
||||||
|
<FullName>Insurance Corporation of British Co:CLOVER LANDON #4:RO29</FullName>
|
||||||
|
</CustomerRef>
|
||||||
|
<TxnDate/>
|
||||||
|
<RefNumber>RO29</RefNumber>
|
||||||
|
<BillAddress>
|
||||||
|
<Addr1>21291 122B AVE</Addr1>
|
||||||
|
<City>MAPLE RIDGE</City>
|
||||||
|
<State>BC</State>
|
||||||
|
</BillAddress>
|
||||||
|
<PONumber>BM27914-1-A</PONumber>
|
||||||
|
<InvoiceLineAdd>
|
||||||
|
<ItemRef>
|
||||||
|
<FullName>BODY SHOP_PAA</FullName>
|
||||||
|
</ItemRef>
|
||||||
|
<Desc>Aftermarketd</Desc>
|
||||||
|
<Quantity>1</Quantity>
|
||||||
|
<Amount>1351.03</Amount>
|
||||||
|
<SalesTaxCodeRef>
|
||||||
|
<FullName>E</FullName>
|
||||||
|
</SalesTaxCodeRef>
|
||||||
|
</InvoiceLineAdd>
|
||||||
|
<InvoiceLineAdd>
|
||||||
|
<ItemRef>
|
||||||
|
<FullName>BODY SHOP_PAN</FullName>
|
||||||
|
</ItemRef>
|
||||||
|
<Desc>BODY SHOP SALES:PARTS:OEM</Desc>
|
||||||
|
<Quantity>1</Quantity>
|
||||||
|
<Amount>292.45</Amount>
|
||||||
|
<SalesTaxCodeRef>
|
||||||
|
<FullName>E</FullName>
|
||||||
|
</SalesTaxCodeRef>
|
||||||
|
</InvoiceLineAdd>
|
||||||
|
<InvoiceLineAdd>
|
||||||
|
<ItemRef>
|
||||||
|
<FullName>BODY SHOP_ATP</FullName>
|
||||||
|
</ItemRef>
|
||||||
|
<Desc>ATPd</Desc>
|
||||||
|
<Quantity>1</Quantity>
|
||||||
|
<Amount>144.09</Amount>
|
||||||
|
<SalesTaxCodeRef>
|
||||||
|
<FullName>E</FullName>
|
||||||
|
</SalesTaxCodeRef>
|
||||||
|
</InvoiceLineAdd>
|
||||||
|
<InvoiceLineAdd>
|
||||||
|
<ItemRef>
|
||||||
|
<FullName>BODY SHOP_LAB</FullName>
|
||||||
|
</ItemRef>
|
||||||
|
<Desc>BODY SHOP SALESLABOR:BODY</Desc>
|
||||||
|
<Quantity>1</Quantity>
|
||||||
|
<Amount>653.35</Amount>
|
||||||
|
<SalesTaxCodeRef>
|
||||||
|
<FullName>E</FullName>
|
||||||
|
</SalesTaxCodeRef>
|
||||||
|
</InvoiceLineAdd>
|
||||||
|
<InvoiceLineAdd>
|
||||||
|
<ItemRef>
|
||||||
|
<FullName>BODY SHOP_LAR</FullName>
|
||||||
|
</ItemRef>
|
||||||
|
<Desc>BODY SHOP SALES:LABOR:REFINISH</Desc>
|
||||||
|
<Quantity>1</Quantity>
|
||||||
|
<Amount>565.26</Amount>
|
||||||
|
<SalesTaxCodeRef>
|
||||||
|
<FullName>E</FullName>
|
||||||
|
</SalesTaxCodeRef>
|
||||||
|
</InvoiceLineAdd>
|
||||||
|
<InvoiceLineAdd>
|
||||||
|
<ItemRef>
|
||||||
|
<FullName>BODY SHOP_MAPA</FullName>
|
||||||
|
</ItemRef>
|
||||||
|
<Desc>paintd</Desc>
|
||||||
|
<Quantity>1</Quantity>
|
||||||
|
<Amount>347.66</Amount>
|
||||||
|
<SalesTaxCodeRef>
|
||||||
|
<FullName>E</FullName>
|
||||||
|
</SalesTaxCodeRef>
|
||||||
|
</InvoiceLineAdd>
|
||||||
|
<InvoiceLineAdd>
|
||||||
|
<ItemRef>
|
||||||
|
<FullName>BODY SHOP_MASH</FullName>
|
||||||
|
</ItemRef>
|
||||||
|
<Desc>shopd</Desc>
|
||||||
|
<Quantity>1</Quantity>
|
||||||
|
<Amount>54.38</Amount>
|
||||||
|
<SalesTaxCodeRef>
|
||||||
|
<FullName>E</FullName>
|
||||||
|
</SalesTaxCodeRef>
|
||||||
|
</InvoiceLineAdd>
|
||||||
|
<InvoiceLineAdd>
|
||||||
|
<ItemRef>
|
||||||
|
<FullName>GST On Sales</FullName>
|
||||||
|
</ItemRef>
|
||||||
|
<Desc>Receiver General - GST</Desc>
|
||||||
|
<Amount>170.41</Amount>
|
||||||
|
</InvoiceLineAdd>
|
||||||
|
<InvoiceLineAdd>
|
||||||
|
<ItemRef>
|
||||||
|
<FullName>PST On Sales</FullName>
|
||||||
|
</ItemRef>
|
||||||
|
<Desc>Ministry of Finance (BC)</Desc>
|
||||||
|
<Amount>238.58</Amount>
|
||||||
|
</InvoiceLineAdd>
|
||||||
|
</InvoiceAdd>
|
||||||
|
</InvoiceAddRq>
|
||||||
|
</QBXMLMsgsRq>
|
||||||
|
|
||||||
|
</QBXML>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// exports.default = async (req, res) => {
|
||||||
|
// const BearerToken = req.headers.authorization;
|
||||||
|
// const { jobId } = req.body;
|
||||||
|
|
||||||
|
// const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||||
|
// headers: {
|
||||||
|
// Authorization: BearerToken,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const result = await client
|
||||||
|
// .setHeaders({ Authorization: BearerToken })
|
||||||
|
// .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { id: jobId });
|
||||||
|
// const { jobs_by_pk } = result;
|
||||||
|
// const { bodyshop } = jobs_by_pk;
|
||||||
|
// const QbXmlToExecute = [];
|
||||||
|
|
||||||
|
// //Is this a two tier, or 3 tier setup?
|
||||||
|
// const isThreeTier = bodyshop.accountingconfig.tiers === 3;
|
||||||
|
// const twoTierPref = bodyshop.accountingconfig.twotierpref;
|
||||||
|
|
||||||
|
// if (isThreeTier) {
|
||||||
|
// QbXmlToExecute.push({
|
||||||
|
// id: jobId,
|
||||||
|
// okStatusCodes: ["0", "3100"],
|
||||||
|
// qbxml: generateSourceCustomerQbxml(jobs_by_pk, bodyshop), // Create the source customer.
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// QbXmlToExecute.push({
|
||||||
|
// id: jobId,
|
||||||
|
// okStatusCodes: ["0", "3100"],
|
||||||
|
// qbxml: generateJobQbxml(
|
||||||
|
// jobs_by_pk,
|
||||||
|
// bodyshop,
|
||||||
|
// isThreeTier,
|
||||||
|
// 2,
|
||||||
|
// twoTierPref
|
||||||
|
// ),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// QbXmlToExecute.push({
|
||||||
|
// id: jobId,
|
||||||
|
// okStatusCodes: ["0", "3100"],
|
||||||
|
// qbxml: generateJobQbxml(
|
||||||
|
// jobs_by_pk,
|
||||||
|
// bodyshop,
|
||||||
|
// isThreeTier,
|
||||||
|
// 3,
|
||||||
|
// twoTierPref
|
||||||
|
// ),
|
||||||
|
// });
|
||||||
|
// //Generate the actual invoice.
|
||||||
|
// QbXmlToExecute.push({
|
||||||
|
// id: jobId,
|
||||||
|
// okStatusCodes: ["0"],
|
||||||
|
// qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// res.status(200).json(QbXmlToExecute);
|
||||||
|
// } catch (error) {
|
||||||
|
// console.log("error", error);
|
||||||
|
// res.status(400).send(JSON.stringify(error));
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
const generateSourceCustomerQbxml = (jobs_by_pk, bodyshop) => {
|
const generateSourceCustomerQbxml = (jobs_by_pk, bodyshop) => {
|
||||||
const customerQbxmlObj = {
|
const customerQbxmlObj = {
|
||||||
QBXML: {
|
QBXML: {
|
||||||
@@ -129,7 +340,13 @@ const generateOwnerTier = (jobs_by_pk) => {
|
|||||||
}`;
|
}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateJobQbxml = (jobs_by_pk, bodyshop, isThreeTier, tierLevel, twoTierPref) => {
|
const generateJobQbxml = (
|
||||||
|
jobs_by_pk,
|
||||||
|
bodyshop,
|
||||||
|
isThreeTier,
|
||||||
|
tierLevel,
|
||||||
|
twoTierPref
|
||||||
|
) => {
|
||||||
let Name;
|
let Name;
|
||||||
let ParentRefName;
|
let ParentRefName;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user