Compare commits
24 Commits
release/20
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
674c06665c | ||
|
|
3feb1a3887 | ||
|
|
1258843f3d | ||
|
|
485b5d0866 | ||
|
|
62b1da0b64 | ||
|
|
a855853230 | ||
|
|
d28d4d6283 | ||
|
|
912756e0f9 | ||
|
|
908c17aa68 | ||
|
|
96995fdd1b | ||
|
|
5341d93e29 | ||
|
|
b8d2dbc2e1 | ||
|
|
595ec72edd | ||
|
|
885a861f1e | ||
|
|
b213e5d54f | ||
|
|
653692b2a5 | ||
|
|
e1e5dda710 | ||
|
|
7f3b1413d7 | ||
|
|
528c68695f | ||
|
|
65a18acdc1 | ||
|
|
930b2791f2 | ||
|
|
1cf5a1fba8 | ||
|
|
83137b2d96 | ||
|
|
7e969e32b2 |
@@ -17213,6 +17213,241 @@
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>inventory</name>
|
||||
<children>
|
||||
<folder_node>
|
||||
<name>actions</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>addtoinventory</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>consumefrominventory</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>
|
||||
<name>errors</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>inserting</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>
|
||||
<name>labels</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>consumedbyjob</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>frombillinvoicenumber</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>fromvendor</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>inventory</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>showall</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>showavailable</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>
|
||||
<name>successes</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>inserted</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>joblines</name>
|
||||
<children>
|
||||
@@ -29934,6 +30169,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>inventory</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>jobs</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -32076,6 +32332,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>note</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>ownr_addr1</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -36237,6 +36514,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>rental_reservation</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>ro_totals</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -40529,6 +40827,69 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>jobs</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>lastmonth</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>lastweek</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>monthlytarget</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -40550,6 +40911,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>productivestatistics</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>productivetimeticketsoverdate</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>targets</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -40571,6 +40974,69 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>thismonth</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>thisweek</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>timetickets</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>todateactual</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -40592,6 +41058,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>totaloverperiod</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>weeklyactual</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -42341,6 +42828,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>inventory</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>jobs</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -43162,6 +43670,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>inventory</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>jobs</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -28,7 +29,12 @@ export default connect(
|
||||
mapDispatchToProps
|
||||
)(AccountingPayablesTableComponent);
|
||||
|
||||
export function AccountingPayablesTableComponent({ bodyshop, loading, bills, refetch }) {
|
||||
export function AccountingPayablesTableComponent({
|
||||
bodyshop,
|
||||
loading,
|
||||
bills,
|
||||
refetch,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedBills, setSelectedBills] = useState([]);
|
||||
const [transInProgress, setTransInProgress] = useState(false);
|
||||
@@ -143,15 +149,13 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||
|
||||
render: (text, record) => (
|
||||
<div>
|
||||
<PayableExportButton
|
||||
billId={record.id}
|
||||
disabled={transInProgress || !!record.exported}
|
||||
loadingCallback={setTransInProgress}
|
||||
setSelectedBills={setSelectedBills}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</div>
|
||||
<PayableExportButton
|
||||
billId={record.id}
|
||||
disabled={transInProgress || !!record.exported}
|
||||
loadingCallback={setTransInProgress}
|
||||
setSelectedBills={setSelectedBills}
|
||||
refetch={refetch}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
@@ -177,6 +181,13 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
||||
<Card
|
||||
extra={
|
||||
<Space wrap>
|
||||
<BillMarkSelectedExported
|
||||
billids={selectedBills}
|
||||
disabled={transInProgress || selectedBills.length === 0}
|
||||
loadingCallback={setTransInProgress}
|
||||
completedCallback={setSelectedBills}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<PayableExportAll
|
||||
billids={selectedBills}
|
||||
disabled={transInProgress || selectedBills.length === 0}
|
||||
|
||||
@@ -73,8 +73,8 @@ export function BillDetailEditcontainer({
|
||||
sm: "100%",
|
||||
md: "100%",
|
||||
lg: "100%",
|
||||
xl: "80%",
|
||||
xxl: "80%",
|
||||
xl: "90%",
|
||||
xxl: "90%",
|
||||
};
|
||||
const drawerPercentage = selectedBreakpoint
|
||||
? bpoints[selectedBreakpoint[0]]
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
UPDATE_JOB,
|
||||
} from "../../graphql/jobs.queries";
|
||||
import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries";
|
||||
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
||||
@@ -50,6 +51,7 @@ function BillEnterModalContainer({
|
||||
const [insertBill] = useMutation(INSERT_NEW_BILL);
|
||||
const [updateJobLines] = useMutation(UPDATE_JOB_LINE);
|
||||
const [updatePartsOrderLines] = useMutation(MUTATION_MARK_RETURN_RECEIVED);
|
||||
const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const client = useApolloClient();
|
||||
|
||||
@@ -79,8 +81,13 @@ function BillEnterModalContainer({
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
const { upload, location, outstanding_returns, ...remainingValues } =
|
||||
values;
|
||||
const {
|
||||
upload,
|
||||
location,
|
||||
outstanding_returns,
|
||||
inventory,
|
||||
...remainingValues
|
||||
} = values;
|
||||
|
||||
let adjustmentsToInsert = {};
|
||||
|
||||
@@ -190,6 +197,26 @@ function BillEnterModalContainer({
|
||||
}
|
||||
|
||||
const billId = r1.data.insert_bills.returning[0].id;
|
||||
const markInventoryConsumed =
|
||||
inventory && inventory.filter((i) => i.consumefrominventory);
|
||||
|
||||
if (markInventoryConsumed && markInventoryConsumed.length > 0) {
|
||||
const r2 = await updateInventoryLines({
|
||||
variables: {
|
||||
InventoryIds: markInventoryConsumed.map((p) => p.id),
|
||||
consumedbybillid: billId,
|
||||
},
|
||||
});
|
||||
if (!!r2.errors) {
|
||||
setLoading(false);
|
||||
setEnterAgain(false);
|
||||
notification["error"]({
|
||||
message: t("inventory.errors.updating", {
|
||||
message: JSON.stringify(r2.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
remainingValues.billlines
|
||||
|
||||
@@ -48,6 +48,7 @@ export function BillFormComponent({
|
||||
disableInvNumber,
|
||||
job,
|
||||
loadOutstandingReturns,
|
||||
loadInventory,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
@@ -61,6 +62,7 @@ export function BillFormComponent({
|
||||
setDiscount(opt.discount);
|
||||
|
||||
opt &&
|
||||
!billEdit &&
|
||||
loadOutstandingReturns({
|
||||
variables: {
|
||||
jobId: form.getFieldValue("jobid"),
|
||||
@@ -86,7 +88,7 @@ export function BillFormComponent({
|
||||
const jobId = form.getFieldValue("jobid");
|
||||
if (jobId) {
|
||||
loadLines({ variables: { id: jobId } });
|
||||
if (form.getFieldValue("is_credit_memo") && vendorId) {
|
||||
if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
|
||||
loadOutstandingReturns({
|
||||
variables: {
|
||||
jobId: jobId,
|
||||
@@ -95,12 +97,19 @@ export function BillFormComponent({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (vendorId === bodyshop.inhousevendorid && !billEdit) {
|
||||
loadInventory();
|
||||
}
|
||||
}, [
|
||||
form,
|
||||
billEdit,
|
||||
loadOutstandingReturns,
|
||||
loadInventory,
|
||||
setDiscount,
|
||||
vendorAutoCompleteOptions,
|
||||
loadLines,
|
||||
bodyshop.inhousevendorid,
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -425,6 +434,7 @@ export function BillFormComponent({
|
||||
form={form}
|
||||
responsibilityCenters={responsibilityCenters}
|
||||
disabled={disabled}
|
||||
billEdit={billEdit}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import BillFormComponent from "./bill-form.component";
|
||||
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
|
||||
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
|
||||
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
|
||||
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -20,6 +23,12 @@ export function BillFormContainer({
|
||||
disabled,
|
||||
disableInvNumber,
|
||||
}) {
|
||||
const { Simple_Inventory } = useTreatments(
|
||||
["Simple_Inventory"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const { data: VendorAutoCompleteData } = useQuery(
|
||||
SEARCH_VENDOR_AUTOCOMPLETE,
|
||||
{ fetchPolicy: "network-only", nextFetchPolicy: "network-only" }
|
||||
@@ -31,6 +40,8 @@ export function BillFormContainer({
|
||||
|
||||
const [loadOutstandingReturns, { loading: returnLoading, data: returnData }] =
|
||||
useLazyQuery(QUERY_UNRECEIVED_LINES);
|
||||
const [loadInventory, { loading: inventoryLoading, data: inventoryData }] =
|
||||
useLazyQuery(QUERY_OUTSTANDING_INVENTORY);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -47,6 +58,7 @@ export function BillFormContainer({
|
||||
responsibilityCenters={bodyshop.md_responsibility_centers || null}
|
||||
disableInvNumber={disableInvNumber}
|
||||
loadOutstandingReturns={loadOutstandingReturns}
|
||||
loadInventory={loadInventory}
|
||||
/>
|
||||
{!billEdit && (
|
||||
<BillCmdReturnsTableComponent
|
||||
@@ -56,6 +68,14 @@ export function BillFormContainer({
|
||||
returnData={returnData}
|
||||
/>
|
||||
)}
|
||||
{Simple_Inventory.treatment === "on" && (
|
||||
<BillInventoryTable
|
||||
form={form}
|
||||
inventoryLoading={inventoryLoading}
|
||||
inventoryData={billEdit ? [] : inventoryData}
|
||||
billEdit={billEdit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -18,6 +18,8 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CiecaSelect from "../../utils/Ciecaselect";
|
||||
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -34,10 +36,16 @@ export function BillEnterModalLinesComponent({
|
||||
discount,
|
||||
form,
|
||||
responsibilityCenters,
|
||||
billEdit,
|
||||
billid,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
||||
|
||||
const { Simple_Inventory } = useTreatments(
|
||||
["Simple_Inventory"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
const columns = (remove) => {
|
||||
return [
|
||||
{
|
||||
@@ -477,9 +485,22 @@ export function BillEnterModalLinesComponent({
|
||||
|
||||
dataIndex: "actions",
|
||||
render: (text, record) => (
|
||||
<Button disabled={disabled} onClick={() => remove(record.name)}>
|
||||
<DeleteFilled />
|
||||
</Button>
|
||||
<Space wrap>
|
||||
<Button disabled={disabled} onClick={() => remove(record.name)}>
|
||||
<DeleteFilled />
|
||||
</Button>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() =>
|
||||
Simple_Inventory.treatment === "on" && (
|
||||
<BilllineAddInventory
|
||||
disabled={!billEdit || form.isFieldsTouched()}
|
||||
billline={getFieldValue("billlines")[record.fieldKey]}
|
||||
jobid={getFieldValue("jobid")}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Form.Item>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import { Checkbox, Form, Skeleton, Typography } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
||||
import "./bill-inventory-table.styles.scss";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BillInventoryTable);
|
||||
|
||||
export function BillInventoryTable({
|
||||
bodyshop,
|
||||
form,
|
||||
billEdit,
|
||||
inventoryLoading,
|
||||
inventoryData,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (inventoryData) {
|
||||
form.setFieldsValue({
|
||||
inventory: inventoryData.inventory,
|
||||
});
|
||||
}
|
||||
}, [inventoryData, form]);
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
shouldUpdate={(prev, cur) => prev.vendorid !== cur.vendorid}
|
||||
noStyle
|
||||
>
|
||||
{() => {
|
||||
const is_inhouse =
|
||||
form.getFieldValue("vendorid") === bodyshop.inhousevendorid;
|
||||
|
||||
if (!is_inhouse || billEdit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inventoryLoading) return <Skeleton />;
|
||||
|
||||
return (
|
||||
<Form.List name="inventory">
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
<>
|
||||
<Typography.Title level={4}>
|
||||
{t("inventory.labels.inventory")}
|
||||
</Typography.Title>
|
||||
<table className="bill-inventory-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("billlines.fields.line_desc")}</th>
|
||||
<th>{t("vendors.fields.name")}</th>
|
||||
<th>{t("billlines.fields.quantity")}</th>
|
||||
<th>{t("billlines.fields.actual_price")}</th>
|
||||
<th>{t("billlines.fields.actual_cost")}</th>
|
||||
<th>{t("inventory.actions.consumefrominventory")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{fields.map((field, index) => (
|
||||
<tr key={field.key}>
|
||||
<td>
|
||||
<Form.Item
|
||||
// label={t("joblines.fields.line_desc")}
|
||||
key={`${index}line_desc`}
|
||||
name={[field.name, "line_desc"]}
|
||||
>
|
||||
<ReadOnlyFormItemComponent />
|
||||
</Form.Item>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<Form.Item
|
||||
span={2}
|
||||
//label={t("joblines.fields.mod_lb_hrs")}
|
||||
key={`${index}part_type`}
|
||||
name={[
|
||||
field.name,
|
||||
"billline",
|
||||
"bill",
|
||||
"vendor",
|
||||
"name",
|
||||
]}
|
||||
>
|
||||
<ReadOnlyFormItemComponent />
|
||||
</Form.Item>
|
||||
</td>
|
||||
<td>
|
||||
<Form.Item
|
||||
span={2}
|
||||
//label={t("joblines.fields.mod_lb_hrs")}
|
||||
key={`${index}quantity`}
|
||||
name={[field.name, "quantity"]}
|
||||
>
|
||||
<ReadOnlyFormItemComponent />
|
||||
</Form.Item>
|
||||
</td>
|
||||
<td>
|
||||
<Form.Item
|
||||
span={2}
|
||||
//label={t("joblines.fields.mod_lb_hrs")}
|
||||
key={`${index}act_price`}
|
||||
name={[field.name, "actual_price"]}
|
||||
>
|
||||
<ReadOnlyFormItemComponent type="currency" />
|
||||
</Form.Item>
|
||||
</td>
|
||||
<td>
|
||||
<Form.Item
|
||||
span={2}
|
||||
//label={t("joblines.fields.mod_lb_hrs")}
|
||||
key={`${index}cost`}
|
||||
name={[field.name, "actual_cost"]}
|
||||
>
|
||||
<ReadOnlyFormItemComponent type="currency" />
|
||||
</Form.Item>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<Form.Item
|
||||
span={2}
|
||||
//label={t("joblines.fields.mod_lb_hrs")}
|
||||
key={`${index}consumefrominventory`}
|
||||
name={[field.name, "consumefrominventory"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.bill-inventory-table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { FileAddFilled } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification, Tooltip } from "antd";
|
||||
import { t } from "i18next";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_INVENTORY_AND_CREDIT } from "../../graphql/inventory.queries";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||
import queryString from "query-string";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(BilllineAddInventory);
|
||||
|
||||
export function BilllineAddInventory({
|
||||
currentUser,
|
||||
bodyshop,
|
||||
billline,
|
||||
disabled,
|
||||
jobid,
|
||||
}) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { billid } = queryString.parse(useLocation().search);
|
||||
|
||||
const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT);
|
||||
|
||||
const addToInventory = async () => {
|
||||
setLoading(true);
|
||||
|
||||
//Check to make sure there are no existing items already in the inventory.
|
||||
|
||||
const cm = {
|
||||
vendorid: bodyshop.inhousevendorid,
|
||||
invoice_number: "ih",
|
||||
jobid: jobid,
|
||||
isinhouse: true,
|
||||
is_credit_memo: true,
|
||||
date: moment().format("YYYY-MM-DD"),
|
||||
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate,
|
||||
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate,
|
||||
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate,
|
||||
total: 0,
|
||||
billlines: [
|
||||
{
|
||||
actual_price: billline.actual_price,
|
||||
actual_cost: billline.actual_cost,
|
||||
quantity: billline.quantity,
|
||||
line_desc: billline.line_desc,
|
||||
cost_center: billline.cost_center,
|
||||
deductedfromlbr: billline.deductedfromlbr,
|
||||
applicable_taxes: {
|
||||
local: false, //billline.applicable_taxes.local,
|
||||
state: false, //billline.applicable_taxes.state,
|
||||
federal: false, // billline.applicable_taxes.federal,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
cm.total = CalculateBillTotal(cm).enteredTotal.getAmount() / 100;
|
||||
|
||||
const insertResult = await insertInventoryLine({
|
||||
variables: {
|
||||
joblineId: billline.joblineid,
|
||||
joblineStatus: bodyshop.md_order_statuses.default_returned,
|
||||
inv: {
|
||||
shopid: bodyshop.id,
|
||||
billlineid: billline.id,
|
||||
actual_price: billline.actual_price,
|
||||
actual_cost: billline.actual_cost,
|
||||
quantity: billline.quantity,
|
||||
line_desc: billline.line_desc,
|
||||
},
|
||||
cm: { ...cm, billlines: { data: cm.billlines } }, //Fix structure for apollo insert.
|
||||
pol: {
|
||||
returnfrombill: billid,
|
||||
vendorid: bodyshop.inhousevendorid,
|
||||
deliver_by: moment().format("YYYY-MM-DD"),
|
||||
parts_order_lines: {
|
||||
data: [
|
||||
{
|
||||
line_desc: billline.line_desc,
|
||||
|
||||
act_price: billline.actual_price,
|
||||
cost: billline.actual_cost,
|
||||
quantity: billline.quantity,
|
||||
job_line_id: billline.joblineid,
|
||||
part_type: billline.jobline.part_type,
|
||||
cm_received: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
order_date: "2022-06-01",
|
||||
orderedby: currentUser.email,
|
||||
jobid: jobid,
|
||||
user_email: currentUser.email,
|
||||
return: true,
|
||||
status: "Ordered",
|
||||
},
|
||||
},
|
||||
refetchQueries: ["QUERY_BILL_BY_PK"],
|
||||
});
|
||||
|
||||
if (!insertResult.errors) {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("inventory.successes.inserted"),
|
||||
});
|
||||
} else {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("inventory.errors.inserting", {
|
||||
error: JSON.stringify(insertResult.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip title={t("inventory.actions.addtoinventory")}>
|
||||
<Button
|
||||
loading={loading}
|
||||
disabled={disabled || billline?.inventories?.length > 0}
|
||||
onClick={addToInventory}
|
||||
>
|
||||
<FileAddFilled />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
@@ -14,7 +15,7 @@ import BillDeleteButton from "../bill-delete-button/bill-delete-button.component
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
@@ -29,6 +30,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export function BillsListTableComponent({
|
||||
bodyshop,
|
||||
jobRO,
|
||||
job,
|
||||
billsQuery,
|
||||
handleOnRowClick,
|
||||
@@ -58,7 +60,9 @@ export function BillsListTableComponent({
|
||||
<BillDeleteButton bill={record} />
|
||||
<Button
|
||||
disabled={
|
||||
record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid
|
||||
record.is_credit_memo ||
|
||||
record.vendorid === bodyshop.inhousevendorid ||
|
||||
jobRO
|
||||
}
|
||||
onClick={() => {
|
||||
setPartsOrderContext({
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
logImEXEvent("job_ca_bc_pvrt_calculate");
|
||||
form.setFieldsValue({ ca_bc_pvrt: (values.rate * values.days).toFixed(2) });
|
||||
form.setFieldsValue({ ca_bc_pvrt: ((values.rate||0) * (values.days||0)).toFixed(2) });
|
||||
setVisibility(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import Icon, {
|
||||
BankFilled,
|
||||
BarChartOutlined,
|
||||
@@ -83,6 +84,12 @@ function Header({
|
||||
setReportCenterContext,
|
||||
recentItems,
|
||||
}) {
|
||||
const { Simple_Inventory } = useTreatments(
|
||||
["Simple_Inventory"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -199,7 +206,20 @@ function Header({
|
||||
>
|
||||
{t("menus.header.enterbills")}
|
||||
</Menu.Item>
|
||||
<Menu.Divider key="div4" />
|
||||
{Simple_Inventory.treatment === "on" && (
|
||||
<>
|
||||
<Menu.Divider key="div4" />
|
||||
<Menu.Item
|
||||
key="inventory"
|
||||
icon={<Icon component={FaFileInvoiceDollar} />}
|
||||
>
|
||||
<Link to="/manage/inventory">
|
||||
{t("menus.header.inventory")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
<Menu.Divider key="div7" />
|
||||
<Menu.Item key="allpayments" icon={<BankFilled />}>
|
||||
<Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
|
||||
</Menu.Item>
|
||||
@@ -216,7 +236,6 @@ function Header({
|
||||
{t("menus.header.enterpayment")}
|
||||
</Menu.Item>
|
||||
<Menu.Divider key="div5" />
|
||||
|
||||
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
|
||||
<Link to="/manage/timetickets">
|
||||
{t("menus.header.timetickets")}
|
||||
@@ -235,7 +254,6 @@ function Header({
|
||||
{t("menus.header.entertimeticket")}
|
||||
</Menu.Item>
|
||||
<Menu.Divider key="div6" />
|
||||
|
||||
<Menu.SubMenu
|
||||
key="accountingexport"
|
||||
title={t("menus.header.export")}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Input, Space, Table, Typography } from "antd";
|
||||
import queryString from "query-string";
|
||||
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 { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const { page, sortcolumn, sortorder } = search;
|
||||
const history = useHistory();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const columns = [
|
||||
{
|
||||
title: t("billlines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
key: "line_desc",
|
||||
|
||||
sorter: true, //(a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||
sortOrder: sortcolumn === "line_desc" && sortorder,
|
||||
},
|
||||
{
|
||||
title: t("inventory.labels.frombillinvoicenumber"),
|
||||
dataIndex: "vendorname",
|
||||
key: "vendorname",
|
||||
ellipsis: true,
|
||||
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
|
||||
//sortOrder: sortcolumn === "ownr_ln" && sortorder,
|
||||
render: (text, record) => record.billline?.bill?.invoice_number,
|
||||
},
|
||||
{
|
||||
title: t("inventory.labels.fromvendor"),
|
||||
dataIndex: "vendorname",
|
||||
key: "vendorname",
|
||||
ellipsis: true,
|
||||
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
|
||||
//sortOrder: sortcolumn === "ownr_ln" && sortorder,
|
||||
render: (text, record) => record.billline?.bill?.vendor?.name,
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.actual_price"),
|
||||
dataIndex: "actual_price",
|
||||
key: "actual_price",
|
||||
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.actual_price}</CurrencyFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.actual_cost"),
|
||||
dataIndex: "actual_cost",
|
||||
key: "actual_cost",
|
||||
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.actual_cost}</CurrencyFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("inventory.labels.consumedbyjob"),
|
||||
dataIndex: "consumedbyjob",
|
||||
key: "consumedbyjob",
|
||||
|
||||
ellipsis: true,
|
||||
render: (text, record) => record.bill?.job?.ro_number,
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
search.page = pagination.current;
|
||||
search.sortcolumn = sorter.column && sorter.column.key;
|
||||
search.sortorder = sorter.order;
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
extra={
|
||||
<Space wrap>
|
||||
{search.search && (
|
||||
<>
|
||||
<Typography.Title level={4}>
|
||||
{t("general.labels.searchresults", { search: search.search })}
|
||||
</Typography.Title>
|
||||
<Button
|
||||
onClick={() => {
|
||||
delete search.search;
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}}
|
||||
>
|
||||
{t("general.actions.clear")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (search.showall) delete search.showall;
|
||||
else {
|
||||
search.showall = true;
|
||||
}
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}}
|
||||
>
|
||||
{search.showall
|
||||
? t("inventory.labels.showavailable")
|
||||
: t("inventory.labels.showall")}
|
||||
</Button>
|
||||
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Input.Search
|
||||
placeholder={search.search || t("general.labels.search")}
|
||||
onSearch={(value) => {
|
||||
search.search = value;
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}}
|
||||
enterButton
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
loading={loading}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: 25,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
}}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={jobs}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsList);
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_INVENTORY_PAGINATED } from "../../graphql/inventory.queries";
|
||||
import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import InventoryListPaginated from "./inventory-list.component";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
});
|
||||
|
||||
export function InventoryList({ setBreadcrumbs, setSelectedHeader }) {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { page, sortcolumn, sortorder, search, showall } = searchParams;
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(
|
||||
QUERY_INVENTORY_PAGINATED,
|
||||
{
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
search: search || "",
|
||||
offset: page ? (page - 1) * 25 : 0,
|
||||
limit: 25,
|
||||
consumedIsNull: showall === "true" ? null : true,
|
||||
order: [
|
||||
{
|
||||
[sortcolumn || "created_at"]:
|
||||
sortorder && sortorder !== "false"
|
||||
? sortorder === "descend"
|
||||
? "desc"
|
||||
: "asc"
|
||||
: "desc",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
return (
|
||||
<RbacWrapper action="jobs:list-all">
|
||||
<InventoryListPaginated
|
||||
refetch={refetch}
|
||||
loading={loading}
|
||||
searchParams={searchParams}
|
||||
total={data ? data.search_inventory_aggregate.aggregate.count : 0}
|
||||
jobs={data ? data.search_inventory : []}
|
||||
/>
|
||||
</RbacWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(InventoryList);
|
||||
@@ -137,9 +137,9 @@ export function JobsList({ bodyshop }) {
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.owner ? (
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.owner.id}
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
|
||||
@@ -14,7 +14,6 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
||||
return (
|
||||
<div>
|
||||
<FormFieldsChanged form={form} />
|
||||
|
||||
<LayoutFormRow header={t("owners.forms.name")}>
|
||||
<Form.Item label={t("owners.fields.ownr_title")} name="ownr_title">
|
||||
<Input />
|
||||
@@ -29,7 +28,6 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow header={t("owners.forms.address")}>
|
||||
<Form.Item label={t("owners.fields.ownr_addr1")} name="ownr_addr1">
|
||||
<Input />
|
||||
@@ -50,7 +48,6 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow header={t("owners.forms.contact")}>
|
||||
<Form.Item
|
||||
label={t("owners.fields.allow_text_message")}
|
||||
@@ -98,6 +95,9 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Form.Item label={t("owners.fields.note")} name="note">
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,14 @@ export default function OwnerFindModalComponent({
|
||||
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.note"),
|
||||
dataIndex: "note",
|
||||
key: "note",
|
||||
render: (text, record) => (
|
||||
<span style={{ whiteSpace: "pre" }}>{record.note}</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const handleOnRowClick = (record) => {
|
||||
|
||||
@@ -101,6 +101,7 @@ export function PartsOrderModalContainer({
|
||||
po: [
|
||||
{
|
||||
...values,
|
||||
order_date: moment().format("YYYY-MM-DD"),
|
||||
orderedby: currentUser.email,
|
||||
jobid: jobId,
|
||||
user_email: currentUser.email,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Form, Modal, notification } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -31,7 +31,7 @@ export function PartsReceiveModalContainer({
|
||||
bodyshop,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { visible, context, actions } = partsReceiveModal;
|
||||
const { partsorderlines } = context;
|
||||
|
||||
@@ -42,7 +42,7 @@ export function PartsReceiveModalContainer({
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
logImEXEvent("parts_order_receive");
|
||||
|
||||
setLoading(true);
|
||||
const result = await Promise.all(
|
||||
values.partsorderlines.map((li) => {
|
||||
return receivePartsLine({
|
||||
@@ -75,7 +75,7 @@ export function PartsReceiveModalContainer({
|
||||
notification["success"]({
|
||||
message: t("parts_orders.successes.received"),
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
if (refetch) refetch();
|
||||
toggleModalVisible();
|
||||
};
|
||||
@@ -96,6 +96,7 @@ export function PartsReceiveModalContainer({
|
||||
title={t("parts_orders.labels.receive")}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
onOk={() => form.submit()}
|
||||
okButtonProps={{ loading: loading }}
|
||||
destroyOnClose
|
||||
forceRender
|
||||
width="50%"
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
selectAuthLevel,
|
||||
selectBodyshop,
|
||||
} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
authLevel: selectAuthLevel,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(BillMarkSelectedExported);
|
||||
|
||||
export function BillMarkSelectedExported({
|
||||
billids,
|
||||
disabled,
|
||||
loadingCallback,
|
||||
completedCallback,
|
||||
refetch,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [updateBill] = useMutation(gql`
|
||||
mutation UPDATE_BILL($billIds: [uuid!]!) {
|
||||
update_bills(where: { id: { _in: $billIds } }, _set: { exported: true }) {
|
||||
returning {
|
||||
id
|
||||
exported
|
||||
exported_at
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const handleUpdate = async () => {
|
||||
setLoading(true);
|
||||
loadingCallback(true);
|
||||
const result = await updateBill({
|
||||
variables: { billIds: billids },
|
||||
update(cache){
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.errors) {
|
||||
notification["success"]({
|
||||
message: t("bills.successes.markexported"),
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("bills.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
loadingCallback(false);
|
||||
completedCallback && completedCallback([]);
|
||||
setLoading(false);
|
||||
refetch && refetch();
|
||||
};
|
||||
|
||||
return (
|
||||
<Button loading={loading} disabled={disabled} onClick={handleUpdate}>
|
||||
{t("bills.labels.markexported")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -42,17 +42,24 @@ export function ProductionColumnsComponent({
|
||||
};
|
||||
|
||||
const columnKeys = columns.map((i) => i.key);
|
||||
|
||||
const cols = dataSource({
|
||||
technician,
|
||||
state: tableState,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
});
|
||||
const menu = (
|
||||
<Menu onClick={handleAdd}>
|
||||
{dataSource({
|
||||
technician,
|
||||
state: tableState,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
})
|
||||
<Menu
|
||||
onClick={handleAdd}
|
||||
style={{
|
||||
columnCount: Math.max(Math.floor(cols.length / 10), 1),
|
||||
}}
|
||||
>
|
||||
{cols
|
||||
.filter((i) => !columnKeys.includes(i.key))
|
||||
.map((item) => (
|
||||
<Menu.Item key={item.key}>{item.title}</Menu.Item>
|
||||
<Menu.Item key={item.key} style={{ breakInside: "avoid" }}>
|
||||
{item.title}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ const ret = {
|
||||
"jobs:detail": 1,
|
||||
"jobs:partsqueue": 4,
|
||||
"jobs:checklist-view": 2,
|
||||
|
||||
"jobs:list-ready": 1,
|
||||
"bills:enter": 2,
|
||||
"bills:view": 2,
|
||||
"bills:list": 2,
|
||||
@@ -66,5 +66,7 @@ const ret = {
|
||||
"timetickets:shiftedit": 5,
|
||||
|
||||
"users:editaccess": 4,
|
||||
|
||||
"inventory:list": 1,
|
||||
};
|
||||
export default ret;
|
||||
|
||||
@@ -177,6 +177,27 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
||||
<Statistic value={values.toDatePaint.toFixed(1)} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col {...statSpans}></Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={(values.todayPaint + values.todayBody).toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}></Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={(values.weeklyPaint + values.weeklyBody).toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}></Col>
|
||||
<Col {...statSpans}></Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={(values.toDatePaint + values.toDateBody).toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
@@ -47,3 +47,15 @@ export const ListOfDaysInCurrentMonth = () => {
|
||||
days.push(dateEnd.format("YYYY-MM-DD"));
|
||||
return days;
|
||||
};
|
||||
|
||||
export const ListDaysBetween = ({ start, end }) => {
|
||||
const days = [];
|
||||
const dateStart = moment(start);
|
||||
const dateEnd = moment(end);
|
||||
while (dateEnd.diff(dateStart, "days") > 0) {
|
||||
days.push(dateStart.format("YYYY-MM-DD"));
|
||||
dateStart.add(1, "days");
|
||||
}
|
||||
days.push(dateEnd.format("YYYY-MM-DD"));
|
||||
return days;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Card } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Bar,
|
||||
CartesianGrid,
|
||||
ComposedChart,
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import TimeTicketsDatesSelector from "../ticket-tickets-dates-selector/time-tickets-dates-selector.component";
|
||||
const graphProps = {
|
||||
strokeWidth: 3,
|
||||
};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScoreboardTicketsBar);
|
||||
|
||||
export function ScoreboardTicketsBar({ data, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Card
|
||||
title={t("scoreboard.labels.productivetimeticketsoverdate")}
|
||||
extra={<TimeTicketsDatesSelector />}
|
||||
>
|
||||
<ResponsiveContainer width="100%" height={475}>
|
||||
<ComposedChart
|
||||
data={data.chartData}
|
||||
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
|
||||
>
|
||||
<CartesianGrid stroke="#f5f5f5" />
|
||||
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
|
||||
<YAxis strokeWidth={graphProps.strokeWidth} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
{/* <Area
|
||||
type="monotone"
|
||||
name="Accumulated Hours"
|
||||
dataKey="accHrs"
|
||||
fill="lightgreen"
|
||||
stroke="green"
|
||||
/> */}
|
||||
{data &&
|
||||
data.employees.map((e, idx) => (
|
||||
<Bar
|
||||
key={`${e}productive`}
|
||||
name={e}
|
||||
dataKey={`employees.${e}.productive`}
|
||||
stackId="productive"
|
||||
// barSize={20}
|
||||
fill={data.colors[idx]}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* <Line
|
||||
name="Target Hours"
|
||||
type="monotone"
|
||||
dataKey="accTargetHrs"
|
||||
stroke="#ff7300"
|
||||
strokeWidth={graphProps.strokeWidth}
|
||||
/> */}
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Row } from "antd";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
import queryString from "query-string";
|
||||
import React, { useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
import ScoreboardTicketsBar from "./scoreboard-timetickets.bar.component";
|
||||
import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component";
|
||||
|
||||
export default function ScoreboardTimeTickets() {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { start, end } = searchParams;
|
||||
const startDate = start
|
||||
? moment(start)
|
||||
: moment().startOf("week").subtract(7, "days");
|
||||
const endDate = end ? moment(end) : moment().endOf("week");
|
||||
|
||||
const fixedPeriods = useMemo(() => {
|
||||
const endOfThisMonth = moment().endOf("month");
|
||||
const startofthisMonth = moment().startOf("month");
|
||||
|
||||
const endOfLastmonth = moment().subtract(1, "month").endOf("month");
|
||||
const startOfLastmonth = moment().subtract(1, "month").startOf("month");
|
||||
|
||||
const endOfThisWeek = moment().endOf("week");
|
||||
const startOfThisWeek = moment().startOf("week");
|
||||
|
||||
const endOfLastWeek = moment().subtract(1, "week").endOf("week");
|
||||
const startOfLastWeek = moment().subtract(1, "week").startOf("week");
|
||||
|
||||
const allDates = [
|
||||
endOfThisMonth,
|
||||
startofthisMonth,
|
||||
endOfLastmonth,
|
||||
startOfLastmonth,
|
||||
endOfThisWeek,
|
||||
startOfThisWeek,
|
||||
endOfLastWeek,
|
||||
startOfLastWeek,
|
||||
];
|
||||
const start = moment.min(allDates);
|
||||
const end = moment.max(allDates);
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
endOfThisMonth,
|
||||
startofthisMonth,
|
||||
endOfLastmonth,
|
||||
startOfLastmonth,
|
||||
endOfThisWeek,
|
||||
startOfThisWeek,
|
||||
endOfLastWeek,
|
||||
startOfLastWeek,
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
|
||||
variables: {
|
||||
start: startDate.format("YYYY-MM-DD"),
|
||||
end: endDate.format("YYYY-MM-DD"),
|
||||
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
|
||||
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
pollInterval: 60000,
|
||||
skip: !fixedPeriods,
|
||||
});
|
||||
|
||||
const calculatedData = useMemo(() => {
|
||||
if (!data) return [];
|
||||
const ret = {
|
||||
totalThisWeek: 0,
|
||||
totalLastWeek: 0,
|
||||
totalThisMonth: 0,
|
||||
totalLastMonth: 0,
|
||||
totalOverPeriod: 0,
|
||||
employees: {},
|
||||
};
|
||||
data.fixedperiod.forEach((ticket) => {
|
||||
const ticketDate = moment(ticket.date);
|
||||
|
||||
if (!ret.employees[ticket.employee.employee_number]) {
|
||||
ret.employees[ticket.employee.employee_number] = {
|
||||
totalThisWeek: 0,
|
||||
totalLastWeek: 0,
|
||||
totalThisMonth: 0,
|
||||
totalLastMonth: 0,
|
||||
totalOverPeriod: 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
ticketDate.isBetween(
|
||||
fixedPeriods.startOfThisWeek,
|
||||
fixedPeriods.endOfThisWeek,
|
||||
undefined,
|
||||
"[]"
|
||||
)
|
||||
) {
|
||||
ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs;
|
||||
ret.employees[ticket.employee.employee_number].totalThisWeek =
|
||||
ret.employees[ticket.employee.employee_number].totalThisWeek +
|
||||
ticket.productivehrs;
|
||||
} else if (
|
||||
ticketDate.isBetween(
|
||||
fixedPeriods.startOfLastWeek,
|
||||
fixedPeriods.endOfLastWeek,
|
||||
undefined,
|
||||
"[]"
|
||||
)
|
||||
) {
|
||||
ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs;
|
||||
ret.employees[ticket.employee.employee_number].totalLastWeek =
|
||||
ret.employees[ticket.employee.employee_number].totalLastWeek +
|
||||
ticket.productivehrs;
|
||||
}
|
||||
if (
|
||||
ticketDate.isBetween(
|
||||
fixedPeriods.startofthisMonth,
|
||||
fixedPeriods.endOfThisMonth,
|
||||
undefined,
|
||||
"[]"
|
||||
)
|
||||
) {
|
||||
ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs;
|
||||
ret.employees[ticket.employee.employee_number].totalThisMonth =
|
||||
ret.employees[ticket.employee.employee_number].totalThisMonth +
|
||||
ticket.productivehrs;
|
||||
} else if (
|
||||
ticketDate.isBetween(
|
||||
fixedPeriods.startOfLastmonth,
|
||||
fixedPeriods.endOfLastmonth,
|
||||
undefined,
|
||||
"[]"
|
||||
)
|
||||
) {
|
||||
ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs;
|
||||
ret.employees[ticket.employee.employee_number].totalLastMonth =
|
||||
ret.employees[ticket.employee.employee_number].totalLastMonth +
|
||||
ticket.productivehrs;
|
||||
}
|
||||
});
|
||||
|
||||
const ticketsGroupedByDate = _.groupBy(data.timetickets, "date");
|
||||
const listOfDays = Utils.ListDaysBetween({
|
||||
start: startDate,
|
||||
end: endDate,
|
||||
});
|
||||
|
||||
const employees = [];
|
||||
const ret2 = [];
|
||||
let totals = {
|
||||
totalproductive: 0,
|
||||
totalactual: 0,
|
||||
employees: {},
|
||||
};
|
||||
|
||||
listOfDays.forEach((day) => {
|
||||
const r = {
|
||||
date: moment(day).format("MM/DD"),
|
||||
actualtotal: 0,
|
||||
productivetotal: 0,
|
||||
employees: {},
|
||||
};
|
||||
|
||||
if (ticketsGroupedByDate[day]) {
|
||||
ticketsGroupedByDate[day].forEach((ticket) => {
|
||||
r.actualtotal = r.actualtotal + ticket.actualhrs;
|
||||
r.productivetotal = r.productivetotal + ticket.productivehrs;
|
||||
totals.totalactual = totals.totalactual + ticket.actualhrs;
|
||||
totals.totalproductive =
|
||||
totals.totalproductive + ticket.productivehrs;
|
||||
|
||||
employees.push(ticket.employee.employee_number);
|
||||
//Add to table data.
|
||||
ret.employees[ticket.employee.employee_number].totalOverPeriod =
|
||||
ret.employees[ticket.employee.employee_number].totalOverPeriod +
|
||||
ticket.productivehrs;
|
||||
|
||||
if (!totals.employees[ticket.employee.employee_number])
|
||||
totals.employees[ticket.employee.employee_number] = {
|
||||
totalactual: 0,
|
||||
totalproductive: 0,
|
||||
};
|
||||
|
||||
if (!r.employees[ticket.employee.employee_number])
|
||||
r.employees[ticket.employee.employee_number] = {
|
||||
actual: 0,
|
||||
productive: 0,
|
||||
};
|
||||
|
||||
//Add to totals.
|
||||
totals.employees[ticket.employee.employee_number].totalproductive =
|
||||
totals.employees[ticket.employee.employee_number].totalproductive +
|
||||
ticket.productivehrs;
|
||||
|
||||
totals.employees[ticket.employee.employee_number].totalactual =
|
||||
totals.employees[ticket.employee.employee_number].totalactual +
|
||||
ticket.actualhrs;
|
||||
//Add to dailys.
|
||||
r.employees[ticket.employee.employee_number].productive =
|
||||
r.employees[ticket.employee.employee_number].productive +
|
||||
ticket.productivehrs;
|
||||
|
||||
r.employees[ticket.employee.employee_number].actual =
|
||||
r.employees[ticket.employee.employee_number].actual +
|
||||
ticket.actualhrs;
|
||||
});
|
||||
}
|
||||
|
||||
ret2.push(r);
|
||||
});
|
||||
|
||||
return {
|
||||
fixed: ret,
|
||||
timeperiod: {
|
||||
totals,
|
||||
chartData: ret2,
|
||||
employees: _.uniq(employees),
|
||||
colors: getColorArray(employees.length),
|
||||
},
|
||||
};
|
||||
}, [fixedPeriods, data, startDate, endDate]);
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
if (loading) return <LoadingSpinner />;
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<ScoreboardTicketsStats data={calculatedData.fixed} />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ScoreboardTicketsBar
|
||||
start={startDate}
|
||||
end={endDate}
|
||||
data={calculatedData.timeperiod}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
//Include a filter by employee.
|
||||
|
||||
//Hours produced today.
|
||||
//Hours produced in last 7 days
|
||||
//Hours produced for time period by day
|
||||
//Hours produced by employee by day for time period.
|
||||
|
||||
function getColorArray(num) {
|
||||
return [
|
||||
"#3366cc",
|
||||
"#dc3912",
|
||||
"#ff9900",
|
||||
"#109618",
|
||||
"#990099",
|
||||
"#0099c6",
|
||||
"#dd4477",
|
||||
"#66aa00",
|
||||
"#b82e2e",
|
||||
"#316395",
|
||||
"#3366cc",
|
||||
"#994499",
|
||||
"#22aa99",
|
||||
"#aaaa11",
|
||||
"#6633cc",
|
||||
"#e67300",
|
||||
"#8b0707",
|
||||
"#651067",
|
||||
"#329262",
|
||||
"#5574a6",
|
||||
"#3b3eac",
|
||||
"#b77322",
|
||||
"#16d620",
|
||||
"#b91383",
|
||||
"#f4359e",
|
||||
"#9c5935",
|
||||
"#a9c413",
|
||||
"#2a778d",
|
||||
"#668d1c",
|
||||
"#bea413",
|
||||
"#0c5922",
|
||||
"#743411",
|
||||
];
|
||||
// var result = [];
|
||||
// for (var i = 0; i < num; i += 1) {
|
||||
// var letters = "0123456789ABCDEF".split("");
|
||||
// var color = "#";
|
||||
// for (var j = 0; j < 6; j += 1) {
|
||||
// color += letters[Math.floor(Math.random() * 16)];
|
||||
// }
|
||||
// result.push(color);
|
||||
// }
|
||||
// return result;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Card, Col, Row, Space, Statistic, Table } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScoreboardTicketsStats);
|
||||
|
||||
export function ScoreboardTicketsStats({ data, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("employees.fields.employee_number"),
|
||||
dataIndex: "employee_number",
|
||||
key: "employee_number",
|
||||
sorter: (a, b) => a.employee_number - b.employee_number,
|
||||
},
|
||||
{
|
||||
title: t("scoreboard.labels.thisweek"),
|
||||
dataIndex: "totalThisWeek",
|
||||
key: "totalThisWeek",
|
||||
sorter: (a, b) => a.totalThisWeek - b.totalThisWeek,
|
||||
},
|
||||
{
|
||||
title: t("scoreboard.labels.lastweek"),
|
||||
dataIndex: "totalLastWeek",
|
||||
key: "totalLastWeek",
|
||||
sorter: (a, b) => a.totalLastWeek - b.totalLastWeek,
|
||||
},
|
||||
{
|
||||
title: t("scoreboard.labels.thismonth"),
|
||||
dataIndex: "totalThisMonth",
|
||||
key: "totalThisMonth",
|
||||
sorter: (a, b) => a.totalThisMonth - b.totalThisMonth,
|
||||
},
|
||||
{
|
||||
title: t("scoreboard.labels.lastmonth"),
|
||||
dataIndex: "totalLastMonth",
|
||||
key: "totalLastMonth",
|
||||
sorter: (a, b) => a.totalLastMonth - b.totalLastMonth,
|
||||
},
|
||||
{
|
||||
title: t("scoreboard.labels.totaloverperiod"),
|
||||
dataIndex: "totalOverPeriod",
|
||||
key: "totalOverPeriod",
|
||||
sorter: (a, b) => a.totalOverPeriod - b.totalOverPeriod,
|
||||
},
|
||||
];
|
||||
|
||||
const tableData = data
|
||||
? Object.keys(data.employees).map((key) => {
|
||||
return { employee_number: key, ...data.employees[key] };
|
||||
})
|
||||
: [];
|
||||
|
||||
return (
|
||||
<Card title={t("scoreboard.labels.productivestatistics")}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col md={24} lg={6}>
|
||||
<Space wrap>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.lastweek")}
|
||||
value={data.totalLastWeek.toFixed(1)}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.thisweek")}
|
||||
value={data.totalThisWeek.toFixed(1)}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.lastmonth")}
|
||||
value={data.totalLastMonth.toFixed(1)}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.thismonth")}
|
||||
value={data.totalThisMonth.toFixed(1)}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col md={24} lg={18}>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={tableData}
|
||||
id="employee_number"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -633,6 +633,18 @@ export default function ShopInfoRbacComponent({ form }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.inventory.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "inventory:list"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</RbacWrapper>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import TechClockInComponent from "./tech-job-clock-in-form.component";
|
||||
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
|
||||
import moment from "moment";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
technician: selectTechnician,
|
||||
@@ -27,14 +28,15 @@ export function TechClockInContainer({ technician, bodyshop }) {
|
||||
const handleFinish = async (values) => {
|
||||
setLoading(true);
|
||||
const theTime = (await axios.post("/utils/time")).data;
|
||||
|
||||
const result = await insertTimeTicket({
|
||||
variables: {
|
||||
timeTicketInput: [
|
||||
{
|
||||
bodyshopid: bodyshop.id,
|
||||
employeeid: technician.id,
|
||||
date: theTime,
|
||||
clockon: theTime,
|
||||
date: moment(theTime).format("YYYY-MM-DD"),
|
||||
clockon: moment(theTime),
|
||||
jobid: values.jobid,
|
||||
cost_center: values.cost_center,
|
||||
ciecacode:
|
||||
|
||||
@@ -55,9 +55,10 @@ export function TechClockOffButton({
|
||||
timeticket: {
|
||||
clockoff: (await axios.post("/utils/time")).data,
|
||||
...values,
|
||||
rate: emps && emps.rates.filter(
|
||||
(r) => r.cost_center === values.cost_center
|
||||
)[0]?.rate,
|
||||
rate:
|
||||
emps &&
|
||||
emps.rates.filter((r) => r.cost_center === values.cost_center)[0]
|
||||
?.rate,
|
||||
ciecacode:
|
||||
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||
? values.cost_center
|
||||
|
||||
@@ -17,6 +17,7 @@ export default function TimeTicketsDatesSelector() {
|
||||
if (!!start && !!end) {
|
||||
history.push({
|
||||
search: queryString.stringify({
|
||||
...searchParams,
|
||||
start: start.format("YYYY-MM-DD"),
|
||||
end: end.format("YYYY-MM-DD"),
|
||||
}),
|
||||
@@ -25,6 +26,7 @@ export default function TimeTicketsDatesSelector() {
|
||||
} else {
|
||||
history.push({
|
||||
search: queryString.stringify({
|
||||
...searchParams,
|
||||
start: null,
|
||||
end: null,
|
||||
}),
|
||||
|
||||
@@ -152,6 +152,10 @@ export const QUERY_BILL_BY_PK = gql`
|
||||
state_tax_rate
|
||||
federal_tax_rate
|
||||
isinhouse
|
||||
inventories {
|
||||
id
|
||||
line_desc
|
||||
}
|
||||
vendor {
|
||||
id
|
||||
name
|
||||
@@ -165,6 +169,9 @@ export const QUERY_BILL_BY_PK = gql`
|
||||
cost_center
|
||||
quantity
|
||||
joblineid
|
||||
inventories {
|
||||
id
|
||||
}
|
||||
jobline {
|
||||
oem_partno
|
||||
part_type
|
||||
|
||||
112
client/src/graphql/inventory.queries.js
Normal file
112
client/src/graphql/inventory.queries.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const INSERT_INVENTORY_AND_CREDIT = gql`
|
||||
mutation INSERT_INVENTORY_AND_CREDIT(
|
||||
$inv: inventory_insert_input!
|
||||
$cm: bills_insert_input!
|
||||
$pol: parts_orders_insert_input!
|
||||
$joblineId: uuid!
|
||||
$joblineStatus: String
|
||||
) {
|
||||
insert_inventory_one(object: $inv) {
|
||||
id
|
||||
}
|
||||
insert_bills_one(object: $cm) {
|
||||
id
|
||||
}
|
||||
insert_parts_orders_one(object: $pol) {
|
||||
id
|
||||
}
|
||||
update_joblines_by_pk(
|
||||
pk_columns: { id: $joblineId }
|
||||
_set: { status: $joblineStatus }
|
||||
) {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const UPDATE_INVENTORY_LINES = gql`
|
||||
mutation UPDATE_INVENTORY_LINES(
|
||||
$InventoryIds: [uuid!]!
|
||||
$consumedbybillid: uuid!
|
||||
) {
|
||||
update_inventory(
|
||||
where: { id: { _in: $InventoryIds } }
|
||||
_set: { consumedbybillid: $consumedbybillid }
|
||||
) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_OUTSTANDING_INVENTORY = gql`
|
||||
query QUERY_OUTSTANDING_INVENTORY {
|
||||
inventory(where: { consumedbybillid: { _is_null: true } }) {
|
||||
id
|
||||
actual_cost
|
||||
actual_price
|
||||
quantity
|
||||
billlineid
|
||||
line_desc
|
||||
billline {
|
||||
bill {
|
||||
invoice_number
|
||||
vendor {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_INVENTORY_PAGINATED = gql`
|
||||
query QUERY_INVENTORY_PAGINATED(
|
||||
$search: String
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$order: [inventory_order_by!]
|
||||
$consumedIsNull: Boolean
|
||||
) {
|
||||
search_inventory(
|
||||
args: { search: $search }
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: { consumedbybillid: { _is_null: $consumedIsNull } }
|
||||
) {
|
||||
id
|
||||
line_desc
|
||||
actual_price
|
||||
actual_cost
|
||||
bill {
|
||||
id
|
||||
invoice_number
|
||||
job {
|
||||
ro_number
|
||||
id
|
||||
}
|
||||
}
|
||||
billline {
|
||||
id
|
||||
bill {
|
||||
id
|
||||
invoice_number
|
||||
vendor {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
search_inventory_aggregate(
|
||||
args: { search: $search }
|
||||
where: { consumedbybillid: { _is_null: $consumedIsNull } }
|
||||
) {
|
||||
aggregate {
|
||||
count(distinct: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -61,6 +61,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
flat_rate
|
||||
clockon
|
||||
clockoff
|
||||
rate
|
||||
employee {
|
||||
id
|
||||
first_name
|
||||
|
||||
@@ -12,6 +12,7 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ea
|
||||
ownerid
|
||||
comment
|
||||
plate_no
|
||||
plate_st
|
||||
|
||||
@@ -15,6 +15,7 @@ export const QUERY_SEARCH_OWNER_BY_IDX = gql`
|
||||
ownr_st
|
||||
ownr_zip
|
||||
id
|
||||
note
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -65,6 +66,7 @@ export const QUERY_OWNER_BY_ID = gql`
|
||||
ownr_title
|
||||
ownr_zip
|
||||
preferred_contact
|
||||
note
|
||||
jobs {
|
||||
id
|
||||
ro_number
|
||||
|
||||
@@ -26,7 +26,12 @@ export const QUERY_TICKETS_BY_JOBID = gql`
|
||||
`;
|
||||
|
||||
export const QUERY_TIME_TICKETS_IN_RANGE = gql`
|
||||
query QUERY_TIME_TICKETS_IN_RANGE($start: date!, $end: date!) {
|
||||
query QUERY_TIME_TICKETS_IN_RANGE(
|
||||
$start: date!
|
||||
$end: date!
|
||||
$fixedStart: date!
|
||||
$fixedEnd: date!
|
||||
) {
|
||||
timetickets(
|
||||
where: { date: { _gte: $start, _lte: $end } }
|
||||
order_by: { date: desc_nulls_first }
|
||||
@@ -56,6 +61,35 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql`
|
||||
last_name
|
||||
}
|
||||
}
|
||||
fixedperiod: timetickets(
|
||||
where: { date: { _gte: $fixedStart, _lte: $fixedEnd } }
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
clockoff
|
||||
clockon
|
||||
cost_center
|
||||
created_at
|
||||
date
|
||||
id
|
||||
rate
|
||||
productivehrs
|
||||
memo
|
||||
jobid
|
||||
flat_rate
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
}
|
||||
employeeid
|
||||
employee {
|
||||
id
|
||||
employee_number
|
||||
first_name
|
||||
last_name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
32
client/src/pages/inventory/inventory.page.jsx
Normal file
32
client/src/pages/inventory/inventory.page.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import InventoryList from "../../components/inventory-list/inventory-list.container";
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
});
|
||||
|
||||
export function InventoryPage({ setBreadcrumbs, setSelectedHeader }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t("titles.inventory");
|
||||
setSelectedHeader("inventory");
|
||||
setBreadcrumbs([{ link: "/manage/jobs", label: t("titles.bc.inventory") }]);
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
return (
|
||||
<RbacWrapper action="inventory:list">
|
||||
<InventoryList />
|
||||
</RbacWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(InventoryPage);
|
||||
@@ -34,6 +34,7 @@ const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
||||
const JobsDetailPage = lazy(() =>
|
||||
import("../jobs-detail/jobs-detail.page.container")
|
||||
);
|
||||
const InventoryListPage = lazy(() => import("../inventory/inventory.page"));
|
||||
const ProfilePage = lazy(() => import("../profile/profile.container.page"));
|
||||
const JobsAvailablePage = lazy(() =>
|
||||
import("../jobs-available/jobs-available.page.container")
|
||||
@@ -250,6 +251,11 @@ export function Manage({ match, conflict, bodyshop }) {
|
||||
<Route path={`${match.path}/jobs/:jobId`} component={JobsDetailPage} />
|
||||
</Switch>
|
||||
<Route exact path={`${match.path}/temporarydocs/`} component={TempDocs} />
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/inventory/`}
|
||||
component={InventoryListPage}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/courtesycars/`}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import Icon, { BarsOutlined } from "@ant-design/icons";
|
||||
import { Tabs } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaShieldAlt } from "react-icons/fa";
|
||||
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 ScoreboardTimeTickets from "../../components/scoreboard-timetickets/scoreboard-timetickets.component";
|
||||
import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import queryString from "query-string";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -22,7 +28,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { tab } = searchParams;
|
||||
const history = useHistory();
|
||||
useEffect(() => {
|
||||
document.title = t("titles.scoreboard");
|
||||
setSelectedHeader("scoreboard");
|
||||
@@ -37,7 +45,41 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
||||
return (
|
||||
<FeatureWrapper featureName="scoreboard">
|
||||
<RbacWrapper action="scoreboard:view">
|
||||
<ScoreboardDisplay />
|
||||
<Tabs
|
||||
activeKey={tab || "sb"}
|
||||
destroyInactiveTabPane
|
||||
onChange={(key) => {
|
||||
searchParams.tab = key;
|
||||
history.push({
|
||||
search: queryString.stringify(searchParams),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaShieldAlt} />
|
||||
{t("scoreboard.labels.jobs")}
|
||||
</span>
|
||||
}
|
||||
destroyInactiveTabPane
|
||||
key="sb"
|
||||
>
|
||||
<ScoreboardDisplay />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<BarsOutlined />
|
||||
{t("scoreboard.labels.timetickets")}
|
||||
</span>
|
||||
}
|
||||
destroyInactiveTabPane
|
||||
key="tickets"
|
||||
>
|
||||
<ScoreboardTimeTickets />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</RbacWrapper>
|
||||
</FeatureWrapper>
|
||||
);
|
||||
|
||||
@@ -1069,6 +1069,26 @@
|
||||
"printpack": "Intake Print Pack"
|
||||
}
|
||||
},
|
||||
"inventory": {
|
||||
"actions": {
|
||||
"addtoinventory": "Add to Inventory",
|
||||
"consumefrominventory": "Consume from Inventory?"
|
||||
},
|
||||
"errors": {
|
||||
"inserting": "Error inserting inventory item. {{error}}"
|
||||
},
|
||||
"labels": {
|
||||
"consumedbyjob": "Consumed by Job",
|
||||
"frombillinvoicenumber": "Original Bill Invoice Number",
|
||||
"fromvendor": "Original Bill Vendor",
|
||||
"inventory": "Inventory",
|
||||
"showall": "Show All Inventory",
|
||||
"showavailable": "Show Only Available Inventory"
|
||||
},
|
||||
"successes": {
|
||||
"inserted": "Added line to inventory."
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"actions": {
|
||||
"new": "New Line"
|
||||
@@ -1754,6 +1774,7 @@
|
||||
"export-logs": "Export Logs",
|
||||
"help": "Help",
|
||||
"home": "Home",
|
||||
"inventory": "Inventory",
|
||||
"jobs": "Jobs",
|
||||
"newjob": "Create New Job",
|
||||
"owners": "Owners",
|
||||
@@ -1893,6 +1914,7 @@
|
||||
"address": "Address",
|
||||
"allow_text_message": "Permission to Text?",
|
||||
"name": "Name",
|
||||
"note": "Owner Note",
|
||||
"ownr_addr1": "Address",
|
||||
"ownr_addr2": "Address 2",
|
||||
"ownr_city": "City",
|
||||
@@ -2144,6 +2166,7 @@
|
||||
"purchases_by_ro_detail": "Purchases - Detail",
|
||||
"purchases_by_ro_summary": "Purchases - Summary",
|
||||
"qc_sheet": "Quality Control Sheet",
|
||||
"rental_reservation": "Rental Reservation",
|
||||
"ro_totals": "RO Totals",
|
||||
"ro_with_description": "RO Summary with Descriptions",
|
||||
"sgi_certificate_of_repairs": "SGI - Certificate of Repairs",
|
||||
@@ -2403,9 +2426,18 @@
|
||||
"asoftodaytarget": "As of Today",
|
||||
"dailyactual": "Actual (D)",
|
||||
"dailytarget": "Daily",
|
||||
"jobs": "Jobs",
|
||||
"lastmonth": "Last Month",
|
||||
"lastweek": "Last Week",
|
||||
"monthlytarget": "Monthly",
|
||||
"productivestatistics": "Productive Hours Statistics",
|
||||
"productivetimeticketsoverdate": "Productive Hours over Selected Dates",
|
||||
"targets": "Targets",
|
||||
"thismonth": "This Month",
|
||||
"thisweek": "This Week",
|
||||
"timetickets": "Timetickets",
|
||||
"todateactual": "Actual (MTD)",
|
||||
"totaloverperiod": "Total Period",
|
||||
"weeklyactual": "Actual (W)",
|
||||
"weeklytarget": "Weekly",
|
||||
"workingdays": "Working Days / Month"
|
||||
@@ -2521,6 +2553,7 @@
|
||||
"dashboard": "Dashboard",
|
||||
"dms": "DMS Export",
|
||||
"export-logs": "Export Logs",
|
||||
"inventory": "Inventory",
|
||||
"jobs": "Jobs",
|
||||
"jobs-active": "Active Jobs",
|
||||
"jobs-admin": "Admin",
|
||||
@@ -2561,6 +2594,7 @@
|
||||
"dashboard": "Dashboard | $t(titles.app)",
|
||||
"dms": "DMS Export | $t(titles.app)",
|
||||
"export-logs": "Export Logs | $t(titles.app)",
|
||||
"inventory": "Inventory | $t(titles.app)",
|
||||
"jobs": "Active Jobs | $t(titles.app)",
|
||||
"jobs-admin": "Job {{ro_number}} - Admin | $t(titles.app)",
|
||||
"jobs-all": "All Jobs | $t(titles.app)",
|
||||
|
||||
@@ -1069,6 +1069,26 @@
|
||||
"printpack": ""
|
||||
}
|
||||
},
|
||||
"inventory": {
|
||||
"actions": {
|
||||
"addtoinventory": "",
|
||||
"consumefrominventory": ""
|
||||
},
|
||||
"errors": {
|
||||
"inserting": ""
|
||||
},
|
||||
"labels": {
|
||||
"consumedbyjob": "",
|
||||
"frombillinvoicenumber": "",
|
||||
"fromvendor": "",
|
||||
"inventory": "",
|
||||
"showall": "",
|
||||
"showavailable": ""
|
||||
},
|
||||
"successes": {
|
||||
"inserted": ""
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"actions": {
|
||||
"new": ""
|
||||
@@ -1754,6 +1774,7 @@
|
||||
"export-logs": "",
|
||||
"help": "",
|
||||
"home": "Casa",
|
||||
"inventory": "",
|
||||
"jobs": "Trabajos",
|
||||
"newjob": "",
|
||||
"owners": "propietarios",
|
||||
@@ -1893,6 +1914,7 @@
|
||||
"address": "Dirección",
|
||||
"allow_text_message": "Permiso de texto?",
|
||||
"name": "Nombre",
|
||||
"note": "",
|
||||
"ownr_addr1": "Dirección",
|
||||
"ownr_addr2": "Dirección 2",
|
||||
"ownr_city": "ciudad",
|
||||
@@ -2144,6 +2166,7 @@
|
||||
"purchases_by_ro_detail": "",
|
||||
"purchases_by_ro_summary": "",
|
||||
"qc_sheet": "",
|
||||
"rental_reservation": "",
|
||||
"ro_totals": "",
|
||||
"ro_with_description": "",
|
||||
"sgi_certificate_of_repairs": "",
|
||||
@@ -2403,9 +2426,18 @@
|
||||
"asoftodaytarget": "",
|
||||
"dailyactual": "",
|
||||
"dailytarget": "",
|
||||
"jobs": "",
|
||||
"lastmonth": "",
|
||||
"lastweek": "",
|
||||
"monthlytarget": "",
|
||||
"productivestatistics": "",
|
||||
"productivetimeticketsoverdate": "",
|
||||
"targets": "",
|
||||
"thismonth": "",
|
||||
"thisweek": "",
|
||||
"timetickets": "",
|
||||
"todateactual": "",
|
||||
"totaloverperiod": "",
|
||||
"weeklyactual": "",
|
||||
"weeklytarget": "",
|
||||
"workingdays": ""
|
||||
@@ -2521,6 +2553,7 @@
|
||||
"dashboard": "",
|
||||
"dms": "",
|
||||
"export-logs": "",
|
||||
"inventory": "",
|
||||
"jobs": "",
|
||||
"jobs-active": "",
|
||||
"jobs-admin": "",
|
||||
@@ -2561,6 +2594,7 @@
|
||||
"dashboard": "",
|
||||
"dms": "",
|
||||
"export-logs": "",
|
||||
"inventory": "",
|
||||
"jobs": "Todos los trabajos | $t(titles.app)",
|
||||
"jobs-admin": "",
|
||||
"jobs-all": "",
|
||||
|
||||
@@ -1069,6 +1069,26 @@
|
||||
"printpack": ""
|
||||
}
|
||||
},
|
||||
"inventory": {
|
||||
"actions": {
|
||||
"addtoinventory": "",
|
||||
"consumefrominventory": ""
|
||||
},
|
||||
"errors": {
|
||||
"inserting": ""
|
||||
},
|
||||
"labels": {
|
||||
"consumedbyjob": "",
|
||||
"frombillinvoicenumber": "",
|
||||
"fromvendor": "",
|
||||
"inventory": "",
|
||||
"showall": "",
|
||||
"showavailable": ""
|
||||
},
|
||||
"successes": {
|
||||
"inserted": ""
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
"actions": {
|
||||
"new": ""
|
||||
@@ -1754,6 +1774,7 @@
|
||||
"export-logs": "",
|
||||
"help": "",
|
||||
"home": "Accueil",
|
||||
"inventory": "",
|
||||
"jobs": "Emplois",
|
||||
"newjob": "",
|
||||
"owners": "Propriétaires",
|
||||
@@ -1893,6 +1914,7 @@
|
||||
"address": "Adresse",
|
||||
"allow_text_message": "Autorisation de texte?",
|
||||
"name": "Prénom",
|
||||
"note": "",
|
||||
"ownr_addr1": "Adresse",
|
||||
"ownr_addr2": "Adresse 2 ",
|
||||
"ownr_city": "Ville",
|
||||
@@ -2144,6 +2166,7 @@
|
||||
"purchases_by_ro_detail": "",
|
||||
"purchases_by_ro_summary": "",
|
||||
"qc_sheet": "",
|
||||
"rental_reservation": "",
|
||||
"ro_totals": "",
|
||||
"ro_with_description": "",
|
||||
"sgi_certificate_of_repairs": "",
|
||||
@@ -2403,9 +2426,18 @@
|
||||
"asoftodaytarget": "",
|
||||
"dailyactual": "",
|
||||
"dailytarget": "",
|
||||
"jobs": "",
|
||||
"lastmonth": "",
|
||||
"lastweek": "",
|
||||
"monthlytarget": "",
|
||||
"productivestatistics": "",
|
||||
"productivetimeticketsoverdate": "",
|
||||
"targets": "",
|
||||
"thismonth": "",
|
||||
"thisweek": "",
|
||||
"timetickets": "",
|
||||
"todateactual": "",
|
||||
"totaloverperiod": "",
|
||||
"weeklyactual": "",
|
||||
"weeklytarget": "",
|
||||
"workingdays": ""
|
||||
@@ -2521,6 +2553,7 @@
|
||||
"dashboard": "",
|
||||
"dms": "",
|
||||
"export-logs": "",
|
||||
"inventory": "",
|
||||
"jobs": "",
|
||||
"jobs-active": "",
|
||||
"jobs-admin": "",
|
||||
@@ -2561,6 +2594,7 @@
|
||||
"dashboard": "",
|
||||
"dms": "",
|
||||
"export-logs": "",
|
||||
"inventory": "",
|
||||
"jobs": "Tous les emplois | $t(titles.app)",
|
||||
"jobs-admin": "",
|
||||
"jobs-all": "",
|
||||
|
||||
@@ -480,6 +480,14 @@ export const TemplateList = (type, context) => {
|
||||
disabled: false,
|
||||
group: "ro",
|
||||
},
|
||||
rental_reservation: {
|
||||
title: i18n.t("printcenter.jobs.rental_reservation"),
|
||||
description: "CASL Authorization",
|
||||
subject: i18n.t("printcenter.jobs.rental_reservation"),
|
||||
key: "rental_reservation",
|
||||
disabled: false,
|
||||
group: "pre",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(!type || type === "job_special"
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
- function:
|
||||
schema: public
|
||||
name: search_exportlog
|
||||
- function:
|
||||
schema: public
|
||||
name: search_inventory
|
||||
- function:
|
||||
schema: public
|
||||
name: search_jobs
|
||||
|
||||
@@ -402,6 +402,14 @@
|
||||
- name: jobline
|
||||
using:
|
||||
foreign_key_constraint_on: joblineid
|
||||
array_relationships:
|
||||
- name: inventories
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: billlineid
|
||||
table:
|
||||
schema: public
|
||||
name: inventory
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
@@ -541,6 +549,13 @@
|
||||
table:
|
||||
schema: public
|
||||
name: exportlog
|
||||
- name: inventories
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: consumedbybillid
|
||||
table:
|
||||
schema: public
|
||||
name: inventory
|
||||
- name: parts_orders
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
@@ -751,6 +766,13 @@
|
||||
table:
|
||||
schema: public
|
||||
name: exportlog
|
||||
- name: inventories
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: shopid
|
||||
table:
|
||||
schema: public
|
||||
name: inventory
|
||||
- name: jobs
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
@@ -2112,6 +2134,97 @@
|
||||
- active:
|
||||
_eq: true
|
||||
allow_aggregations: true
|
||||
- table:
|
||||
schema: public
|
||||
name: inventory
|
||||
object_relationships:
|
||||
- name: bill
|
||||
using:
|
||||
foreign_key_constraint_on: consumedbybillid
|
||||
- name: billline
|
||||
using:
|
||||
foreign_key_constraint_on: billlineid
|
||||
- name: bodyshop
|
||||
using:
|
||||
foreign_key_constraint_on: shopid
|
||||
- name: jobline
|
||||
using:
|
||||
foreign_key_constraint_on: joblineid
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
check:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- actual_cost
|
||||
- actual_price
|
||||
- billlineid
|
||||
- consumedbybillid
|
||||
- created_at
|
||||
- id
|
||||
- joblineid
|
||||
- line_desc
|
||||
- quantity
|
||||
- shopid
|
||||
- updated_at
|
||||
backend_only: false
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- actual_cost
|
||||
- actual_price
|
||||
- billlineid
|
||||
- consumedbybillid
|
||||
- created_at
|
||||
- id
|
||||
- joblineid
|
||||
- line_desc
|
||||
- quantity
|
||||
- shopid
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
allow_aggregations: true
|
||||
update_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- actual_cost
|
||||
- actual_price
|
||||
- billlineid
|
||||
- consumedbybillid
|
||||
- created_at
|
||||
- id
|
||||
- joblineid
|
||||
- line_desc
|
||||
- quantity
|
||||
- shopid
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
check: null
|
||||
- table:
|
||||
schema: public
|
||||
name: ioevents
|
||||
@@ -2211,6 +2324,13 @@
|
||||
table:
|
||||
schema: public
|
||||
name: billlines
|
||||
- name: inventories
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: joblineid
|
||||
table:
|
||||
schema: public
|
||||
name: inventory
|
||||
- name: parts_order_lines
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
@@ -3823,32 +3943,36 @@
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- id
|
||||
- accountingid
|
||||
- allow_text_message
|
||||
- created_at
|
||||
- updated_at
|
||||
- ownr_fn
|
||||
- ownr_ln
|
||||
- id
|
||||
- note
|
||||
- ownr_addr1
|
||||
- ownr_addr2
|
||||
- ownr_city
|
||||
- ownr_st
|
||||
- ownr_zip
|
||||
- ownr_co_nm
|
||||
- ownr_ctry
|
||||
- ownr_ea
|
||||
- ownr_fn
|
||||
- ownr_ln
|
||||
- ownr_ph1
|
||||
- preferred_contact
|
||||
- allow_text_message
|
||||
- shopid
|
||||
- ownr_ph2
|
||||
- ownr_co_nm
|
||||
- ownr_st
|
||||
- ownr_title
|
||||
- accountingid
|
||||
- ownr_zip
|
||||
- preferred_contact
|
||||
- shopid
|
||||
- updated_at
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- allow_text_message
|
||||
- accountingid
|
||||
- allow_text_message
|
||||
- created_at
|
||||
- id
|
||||
- note
|
||||
- ownr_addr1
|
||||
- ownr_addr2
|
||||
- ownr_city
|
||||
@@ -3863,10 +3987,8 @@
|
||||
- ownr_title
|
||||
- ownr_zip
|
||||
- preferred_contact
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- shopid
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -3881,8 +4003,11 @@
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- allow_text_message
|
||||
- accountingid
|
||||
- allow_text_message
|
||||
- created_at
|
||||
- id
|
||||
- note
|
||||
- ownr_addr1
|
||||
- ownr_addr2
|
||||
- ownr_city
|
||||
@@ -3897,10 +4022,8 @@
|
||||
- ownr_title
|
||||
- ownr_zip
|
||||
- preferred_contact
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- shopid
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."owners" add column "note" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."owners" add column "note" text
|
||||
null;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "public"."inventory";
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE "public"."inventory" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "shopid" uuid NOT NULL, "billid" uuid, "joblineid" uuid, "line_desc" text NOT NULL, "actual_price" numeric NOT NULL, "actual_cost" numeric NOT NULL, "quantity" numeric NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("joblineid") REFERENCES "public"."joblines"("id") ON UPDATE restrict ON DELETE restrict);
|
||||
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
_new record;
|
||||
BEGIN
|
||||
_new := NEW;
|
||||
_new."updated_at" = NOW();
|
||||
RETURN _new;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER "set_public_inventory_updated_at"
|
||||
BEFORE UPDATE ON "public"."inventory"
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||
COMMENT ON TRIGGER "set_public_inventory_updated_at" ON "public"."inventory"
|
||||
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."inventory" drop constraint "inventory_billid_fkey";
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."inventory"
|
||||
add constraint "inventory_billid_fkey"
|
||||
foreign key ("billid")
|
||||
references "public"."billlines"
|
||||
("id") on update restrict on delete restrict;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."inventory" drop constraint "inventory_shopid_fkey";
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."inventory"
|
||||
add constraint "inventory_shopid_fkey"
|
||||
foreign key ("shopid")
|
||||
references "public"."bodyshops"
|
||||
("id") on update restrict on delete restrict;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."inventory" rename column "billlineid" to "billid";
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."inventory" rename column "billid" to "billlineid";
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."inventory" add column "consumedbybillid" uuid
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."inventory" add column "consumedbybillid" uuid
|
||||
null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."inventory" drop constraint "inventory_consumedbybillid_fkey";
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."inventory"
|
||||
add constraint "inventory_consumedbybillid_fkey"
|
||||
foreign key ("consumedbybillid")
|
||||
references "public"."bills"
|
||||
("id") on update restrict on delete restrict;
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."inventory" drop constraint "inventory_consumedbybillid_fkey",
|
||||
add constraint "inventory_consumedbybillid_fkey"
|
||||
foreign key ("shopid")
|
||||
references "public"."bodyshops"
|
||||
("id") on update restrict on delete restrict;
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."inventory" drop constraint "inventory_consumedbybillid_fkey",
|
||||
add constraint "inventory_consumedbybillid_fkey"
|
||||
foreign key ("consumedbybillid")
|
||||
references "public"."bills"
|
||||
("id") on update restrict on delete set null;
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."inventory" drop constraint "inventory_consumedbybillid_fkey",
|
||||
add constraint "inventory_consumedbybillid_fkey"
|
||||
foreign key ("shopid")
|
||||
references "public"."bodyshops"
|
||||
("id") on update restrict on delete restrict;
|
||||
@@ -0,0 +1,5 @@
|
||||
alter table "public"."inventory" drop constraint "inventory_consumedbybillid_fkey",
|
||||
add constraint "inventory_consumedbybillid_fkey"
|
||||
foreign key ("consumedbybillid")
|
||||
references "public"."bills"
|
||||
("id") on update cascade on delete set null;
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."inventory_consumedbybillid";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "inventory_consumedbybillid" on
|
||||
"public"."inventory" using btree ("consumedbybillid");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."inventory_shopididx";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "inventory_shopididx" on
|
||||
"public"."inventory" using btree ("shopid");
|
||||
40
hasura/migrations/1654022042838_run_sql_migration/down.sql
Normal file
40
hasura/migrations/1654022042838_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,40 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE OR REPLACE FUNCTION public.search_inventory (search text)
|
||||
-- RETURNS SETOF inventory
|
||||
-- LANGUAGE plpgsql
|
||||
-- STABLE
|
||||
-- AS $function$
|
||||
-- BEGIN
|
||||
-- IF search = '' THEN
|
||||
-- RETURN query
|
||||
-- SELECT
|
||||
-- *
|
||||
-- FROM
|
||||
-- inventory;
|
||||
-- ELSE
|
||||
-- RETURN query
|
||||
-- SELECT
|
||||
-- *
|
||||
-- FROM
|
||||
-- inventory i,
|
||||
-- billlines bl,
|
||||
-- bills b,
|
||||
-- vendors v
|
||||
-- WHERE
|
||||
-- i.billlineid = bl.id
|
||||
-- AND bl.billid = b.id
|
||||
-- AND b.vendorid = v.id
|
||||
-- AND i.line_desc ILIKE '%' || search || '%'
|
||||
-- OR b.invoice_number ILIKE '%' || search || '%'
|
||||
-- OR v.name ILIKE '%' || search || '%'
|
||||
-- ORDER BY
|
||||
-- i.line_desc ILIKE '%' || search || '%'
|
||||
-- OR NULL,
|
||||
-- b.invoice_number ILIKE '%' || search || '%'
|
||||
-- OR NULL,
|
||||
-- v.name ILIKE '%' || search || '%'
|
||||
-- OR NULL;
|
||||
-- END IF;
|
||||
-- END
|
||||
-- $function$;
|
||||
38
hasura/migrations/1654022042838_run_sql_migration/up.sql
Normal file
38
hasura/migrations/1654022042838_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
CREATE OR REPLACE FUNCTION public.search_inventory (search text)
|
||||
RETURNS SETOF inventory
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $function$
|
||||
BEGIN
|
||||
IF search = '' THEN
|
||||
RETURN query
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
inventory;
|
||||
ELSE
|
||||
RETURN query
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
inventory i,
|
||||
billlines bl,
|
||||
bills b,
|
||||
vendors v
|
||||
WHERE
|
||||
i.billlineid = bl.id
|
||||
AND bl.billid = b.id
|
||||
AND b.vendorid = v.id
|
||||
AND i.line_desc ILIKE '%' || search || '%'
|
||||
OR b.invoice_number ILIKE '%' || search || '%'
|
||||
OR v.name ILIKE '%' || search || '%'
|
||||
ORDER BY
|
||||
i.line_desc ILIKE '%' || search || '%'
|
||||
OR NULL,
|
||||
b.invoice_number ILIKE '%' || search || '%'
|
||||
OR NULL,
|
||||
v.name ILIKE '%' || search || '%'
|
||||
OR NULL;
|
||||
END IF;
|
||||
END
|
||||
$function$;
|
||||
37
hasura/migrations/1654023497348_run_sql_migration/down.sql
Normal file
37
hasura/migrations/1654023497348_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE OR REPLACE FUNCTION public.search_inventory (search text)
|
||||
-- RETURNS SETOF inventory
|
||||
-- LANGUAGE plpgsql
|
||||
-- STABLE
|
||||
-- AS $function$
|
||||
-- BEGIN
|
||||
-- IF search = '' THEN
|
||||
-- RETURN query
|
||||
-- SELECT
|
||||
-- *
|
||||
-- FROM
|
||||
-- inventory;
|
||||
-- ELSE
|
||||
-- RETURN query
|
||||
-- SELECT
|
||||
-- *
|
||||
-- FROM
|
||||
-- inventory inner JOIN billlines ON inventory.billlineid = billlines.id
|
||||
-- inner JOIN bills ON billlines.billid = bills.id
|
||||
-- inner JOIN vendors ON bills.vendorid = vendors.id
|
||||
--
|
||||
-- WHERE
|
||||
-- inventory.line_desc ILIKE '%' || search || '%'
|
||||
-- OR bills.invoice_number ILIKE '%' || search || '%'
|
||||
-- OR vendors.name ILIKE '%' || search || '%'
|
||||
-- ORDER BY
|
||||
-- inventory.line_desc ILIKE '%' || search || '%'
|
||||
-- OR NULL,
|
||||
-- bills.invoice_number ILIKE '%' || search || '%'
|
||||
-- OR NULL,
|
||||
-- vendors.name ILIKE '%' || search || '%'
|
||||
-- OR NULL;
|
||||
-- END IF;
|
||||
-- END
|
||||
-- $function$;
|
||||
35
hasura/migrations/1654023497348_run_sql_migration/up.sql
Normal file
35
hasura/migrations/1654023497348_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
CREATE OR REPLACE FUNCTION public.search_inventory (search text)
|
||||
RETURNS SETOF inventory
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $function$
|
||||
BEGIN
|
||||
IF search = '' THEN
|
||||
RETURN query
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
inventory;
|
||||
ELSE
|
||||
RETURN query
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
inventory inner JOIN billlines ON inventory.billlineid = billlines.id
|
||||
inner JOIN bills ON billlines.billid = bills.id
|
||||
inner JOIN vendors ON bills.vendorid = vendors.id
|
||||
|
||||
WHERE
|
||||
inventory.line_desc ILIKE '%' || search || '%'
|
||||
OR bills.invoice_number ILIKE '%' || search || '%'
|
||||
OR vendors.name ILIKE '%' || search || '%'
|
||||
ORDER BY
|
||||
inventory.line_desc ILIKE '%' || search || '%'
|
||||
OR NULL,
|
||||
bills.invoice_number ILIKE '%' || search || '%'
|
||||
OR NULL,
|
||||
vendors.name ILIKE '%' || search || '%'
|
||||
OR NULL;
|
||||
END IF;
|
||||
END
|
||||
$function$;
|
||||
37
hasura/migrations/1654023506265_run_sql_migration/down.sql
Normal file
37
hasura/migrations/1654023506265_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE OR REPLACE FUNCTION public.search_inventory (search text)
|
||||
-- RETURNS SETOF inventory
|
||||
-- LANGUAGE plpgsql
|
||||
-- STABLE
|
||||
-- AS $function$
|
||||
-- BEGIN
|
||||
-- IF search = '' THEN
|
||||
-- RETURN query
|
||||
-- SELECT
|
||||
-- *
|
||||
-- FROM
|
||||
-- inventory;
|
||||
-- ELSE
|
||||
-- RETURN query
|
||||
-- SELECT
|
||||
-- *
|
||||
-- FROM
|
||||
-- inventory inner JOIN billlines ON inventory.billlineid = billlines.id
|
||||
-- inner JOIN bills ON billlines.billid = bills.id
|
||||
-- inner JOIN vendors ON bills.vendorid = vendors.id
|
||||
--
|
||||
-- WHERE
|
||||
-- inventory.line_desc ILIKE '%' || search || '%'
|
||||
-- OR bills.invoice_number ILIKE '%' || search || '%'
|
||||
-- OR vendors.name ILIKE '%' || search || '%'
|
||||
-- ORDER BY
|
||||
-- inventory.line_desc ILIKE '%' || search || '%'
|
||||
-- OR NULL,
|
||||
-- bills.invoice_number ILIKE '%' || search || '%'
|
||||
-- OR NULL,
|
||||
-- vendors.name ILIKE '%' || search || '%'
|
||||
-- OR NULL;
|
||||
-- END IF;
|
||||
-- END
|
||||
-- $function$;
|
||||
35
hasura/migrations/1654023506265_run_sql_migration/up.sql
Normal file
35
hasura/migrations/1654023506265_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
CREATE OR REPLACE FUNCTION public.search_inventory (search text)
|
||||
RETURNS SETOF inventory
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $function$
|
||||
BEGIN
|
||||
IF search = '' THEN
|
||||
RETURN query
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
inventory;
|
||||
ELSE
|
||||
RETURN query
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
inventory inner JOIN billlines ON inventory.billlineid = billlines.id
|
||||
inner JOIN bills ON billlines.billid = bills.id
|
||||
inner JOIN vendors ON bills.vendorid = vendors.id
|
||||
|
||||
WHERE
|
||||
inventory.line_desc ILIKE '%' || search || '%'
|
||||
OR bills.invoice_number ILIKE '%' || search || '%'
|
||||
OR vendors.name ILIKE '%' || search || '%'
|
||||
ORDER BY
|
||||
inventory.line_desc ILIKE '%' || search || '%'
|
||||
OR NULL,
|
||||
bills.invoice_number ILIKE '%' || search || '%'
|
||||
OR NULL,
|
||||
vendors.name ILIKE '%' || search || '%'
|
||||
OR NULL;
|
||||
END IF;
|
||||
END
|
||||
$function$;
|
||||
37
hasura/migrations/1654023641223_run_sql_migration/down.sql
Normal file
37
hasura/migrations/1654023641223_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE OR REPLACE FUNCTION public.search_inventory (search text)
|
||||
-- RETURNS SETOF inventory
|
||||
-- LANGUAGE plpgsql
|
||||
-- STABLE
|
||||
-- AS $function$
|
||||
-- BEGIN
|
||||
-- IF search = '' THEN
|
||||
-- RETURN query
|
||||
-- SELECT
|
||||
-- *
|
||||
-- FROM
|
||||
-- inventory;
|
||||
-- ELSE
|
||||
-- RETURN query
|
||||
-- SELECT
|
||||
-- inventory.*
|
||||
-- FROM
|
||||
-- inventory inner JOIN billlines ON inventory.billlineid = billlines.id
|
||||
-- inner JOIN bills ON billlines.billid = bills.id
|
||||
-- inner JOIN vendors ON bills.vendorid = vendors.id
|
||||
--
|
||||
-- WHERE
|
||||
-- inventory.line_desc ILIKE '%' || search || '%'
|
||||
-- OR bills.invoice_number ILIKE '%' || search || '%'
|
||||
-- OR vendors.name ILIKE '%' || search || '%'
|
||||
-- ORDER BY
|
||||
-- inventory.line_desc ILIKE '%' || search || '%'
|
||||
-- OR NULL,
|
||||
-- bills.invoice_number ILIKE '%' || search || '%'
|
||||
-- OR NULL,
|
||||
-- vendors.name ILIKE '%' || search || '%'
|
||||
-- OR NULL;
|
||||
-- END IF;
|
||||
-- END
|
||||
-- $function$;
|
||||
35
hasura/migrations/1654023641223_run_sql_migration/up.sql
Normal file
35
hasura/migrations/1654023641223_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
CREATE OR REPLACE FUNCTION public.search_inventory (search text)
|
||||
RETURNS SETOF inventory
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $function$
|
||||
BEGIN
|
||||
IF search = '' THEN
|
||||
RETURN query
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
inventory;
|
||||
ELSE
|
||||
RETURN query
|
||||
SELECT
|
||||
inventory.*
|
||||
FROM
|
||||
inventory inner JOIN billlines ON inventory.billlineid = billlines.id
|
||||
inner JOIN bills ON billlines.billid = bills.id
|
||||
inner JOIN vendors ON bills.vendorid = vendors.id
|
||||
|
||||
WHERE
|
||||
inventory.line_desc ILIKE '%' || search || '%'
|
||||
OR bills.invoice_number ILIKE '%' || search || '%'
|
||||
OR vendors.name ILIKE '%' || search || '%'
|
||||
ORDER BY
|
||||
inventory.line_desc ILIKE '%' || search || '%'
|
||||
OR NULL,
|
||||
bills.invoice_number ILIKE '%' || search || '%'
|
||||
OR NULL,
|
||||
vendors.name ILIKE '%' || search || '%'
|
||||
OR NULL;
|
||||
END IF;
|
||||
END
|
||||
$function$;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- create index inventory_linedescidx on inventory(line_desc);
|
||||
1
hasura/migrations/1654023762220_run_sql_migration/up.sql
Normal file
1
hasura/migrations/1654023762220_run_sql_migration/up.sql
Normal file
@@ -0,0 +1 @@
|
||||
create index inventory_linedescidx on inventory(line_desc);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
79340
logs/oAuthClient-log.log
79340
logs/oAuthClient-log.log
File diff suppressed because one or more lines are too long
@@ -249,7 +249,9 @@ const generateInvoiceQbxml = (
|
||||
)}:${generateJobTier(jobs_by_pk)}`
|
||||
).trim(),
|
||||
},
|
||||
|
||||
ARAccountRef: {
|
||||
FullName: bodyshop.md_responsibility_centers.ar.accountname,
|
||||
},
|
||||
...(jobs_by_pk.class
|
||||
? { ClassRef: { FullName: jobs_by_pk.class } }
|
||||
: {}),
|
||||
|
||||
@@ -800,7 +800,7 @@ const CreateCosts = (job) => {
|
||||
};
|
||||
|
||||
const StatusMapping = (status, md_ro_statuses) => {
|
||||
//EST, SCH, ARR, IPR, RDY, DEL, CLO, CAN, UNDEFINED.
|
||||
//Possible return statuses EST, SCH, ARR, IPR, RDY, DEL, CLO, CAN, UNDEFINED.
|
||||
const {
|
||||
default_imported,
|
||||
default_open,
|
||||
@@ -823,8 +823,6 @@ const StatusMapping = (status, md_ro_statuses) => {
|
||||
else if (status === default_void) return "VOID";
|
||||
else if (md_ro_statuses.production_statuses.includes(status)) return "IPR";
|
||||
else return "UNDEFINED";
|
||||
|
||||
// default: return "UNDEFINED"
|
||||
};
|
||||
|
||||
const GenerateDetailLines = (job, line, statuses) => {
|
||||
|
||||
@@ -37,7 +37,7 @@ exports.totalsSsu = async function (req, res) {
|
||||
clm_total: newTotals.totals.total_repairs.toFormat("0.00"),
|
||||
owner_owing: newTotals.totals.custPayable.total.toFormat("0.00"),
|
||||
job_totals: newTotals,
|
||||
queued_for_parts: true,
|
||||
//queued_for_parts: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user