diff --git a/components/Selects/select-cost-center.jsx b/components/Selects/select-cost-center.jsx
index 81abd44..136c076 100644
--- a/components/Selects/select-cost-center.jsx
+++ b/components/Selects/select-cost-center.jsx
@@ -77,12 +77,13 @@ export default connect(null, null)(CostCenterSelect);
const styles = StyleSheet.create({
container: {
- padding: 16,
+ marginVertical: 4,
+ marginHorizontal: 16,
justifyContent: "center",
alignContent: "center",
},
dropdown: {
- height: 50,
+ height: 48,
borderColor: "gray",
borderWidth: 0.5,
borderRadius: 8,
diff --git a/components/labor-allocations-table/labor-allocations-table.component.jsx b/components/labor-allocations-table/labor-allocations-table.component.jsx
index b7ec0c5..e1096f3 100644
--- a/components/labor-allocations-table/labor-allocations-table.component.jsx
+++ b/components/labor-allocations-table/labor-allocations-table.component.jsx
@@ -1,23 +1,183 @@
-import React from "react";
+import React, { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { FlatList, RefreshControl, StyleSheet, Text, View } from "react-native";
+import _ from "lodash";
import { Card, DataTable } from "react-native-paper";
+import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs.queries";
+import ErrorDisplay from "../error-display/error-display.component";
+import { useQuery } from "@apollo/client";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import { selectCurrentEmployee } from "../../redux/employee/employee.selectors";
+import { CalculateAllocationsTotals } from "../../util/labor-allocations-table.utility";
-export default function LaborAllocationsTable({ job, loading, refetch }) {
- const { t } = useTranslation();
-
- if (!job) {
-
- Job is not defined.
- ;
- }
- const onRefresh = async () => {
- return refetch();
- };
-
- return (
-
-
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop,
+ technician: selectCurrentEmployee,
+});
+
+export function LaborAllocationsTable({ jobId, bodyshop, technician }) {
+ console.log("LaborAllocationsTable, jobId", jobId);
+ //, loading, refetch
+ //const jobid = jobid !== undefined ? jobid : "";
+ const { t } = useTranslation();
+
+ const onRefresh = async () => {
+
+ console.log("LaborAllocationsTable refetch");
+ return refetch();
+ };
+
+ //maybe use this
+ const { loading, error, data, refetch } = useQuery(GET_LINE_TICKET_BY_PK, {
+ variables: { id: jobId },
+ skip: !!!jobId,
+ fetchPolicy: "network-only",
+ nextFetchPolicy: "network-only",
+ });
+
+ // console.log("LaborAllocationsTable, data", data);
+ if (error) return ;
+
+ // let joblines = [];
+ // let timetickets = [];
+ // let adjustments = [];
+
+ const [totals, setTotals] = useState([]);
+ const [state, setState] = useState({
+ sortedInfo: {
+ columnKey: "cost_center",
+ field: "cost_center",
+ order: "ascend",
+ },
+ filteredInfo: {},
+ });
+
+ useEffect(() => {
+ console.log("LaborAllocationsTable useEffect on data change");
+ // joblines = data?.joblines ? data.joblines : [];
+ // timetickets = data?.timetickets ? data.timetickets : [];
+ // adjustments = data?.adjustments ? data.adjustments : [];
+ }, [data]);
+
+ useEffect(() => {
+ console.log(
+ "LaborAllocationsTable useEffect on [joblines, timetickets, bodyshop, adjustments, jobId] change",
+ data?.joblines,
+ data?.adjustments
+ );
+ if (!!data?.joblines && !!data?.timetickets && !!bodyshop)
+ setTotals(
+ CalculateAllocationsTotals(
+ bodyshop,
+ data?.joblines,
+ data?.timetickets,
+ data?.adjustments
+ )
+ );
+ if (!jobId) setTotals([]);
+ }, [data?.joblines, data?.timetickets, bodyshop, data?.adjustments, jobId]);
+
+ // const convertedLines = useMemo(
+ // () => data?.joblines && data?.joblines.filter((j) => j.convertedtolbr),
+ // [data?.joblines]
+ // );
+
+ const summary =
+ totals &&
+ totals.reduce(
+ (acc, val) => {
+ acc.hrs_total += val.total;
+ acc.hrs_claimed += val.claimed;
+ acc.adjustments += val.adjustments;
+ acc.difference += val.difference;
+ return acc;
+ },
+ { hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 }
+ );
+
+ console.log("labor summary is:", summary);
+
+ return (
+
+ {typeof data !== "undefined" ? (
+
+
+
+ Cost Center
+
+ Hours Total
+
+
+ Hours Claimed
+
+
+ Adjustments
+
+
+ Difference
+
+
+
+
+
+ }
+ keyExtractor={(item) => item.cost_center}
+ renderItem={(object) => (
+
+
+
+
+ {object.item.cost_center}
+ {object.item.mod_lbr_ty}
+
+
+
+ {object.item.total && object.item.total.toFixed(1)}
+
+
+ {object.item.claimed && object.item.claimed.toFixed(1)}
+
+
+
+ {object.item.adjustments && object.item.adjustments.toFixed(1)}
+ {/* {!technician && (
+
+
+
+ )} */}
+
+
+
+ = 0 ? "green" : "red",
+ }}>
+ {/* = 0 ? "green" : "red",
+ }}
+ >
+ {_.round(object.difference, 1)}
+ */}
+ {_.round(object.item.difference, 1)}
+
+
+
+ )}
+ />
+
+
+ ) : null}
+ {/*
{t("jobdetail.labels.lines_desc")}
@@ -35,9 +195,9 @@ export default function LaborAllocationsTable({ job, loading, refetch }) {
{t("jobdetail.labels.lines_qty")}
-
-
- */}
+
+ {/*
@@ -64,10 +224,13 @@ export default function LaborAllocationsTable({ job, loading, refetch }) {
)}
- />
-
- );
- }
-
- const localStyles = StyleSheet.create({});
-
\ No newline at end of file
+ /> */}
+
+ {/* use "totals" for the rows in the table */}
+ {/* use "summary" for the totals at the bottom */}
+
+ );
+}
+
+const localStyles = StyleSheet.create({});
+export default connect(mapStateToProps, null)(LaborAllocationsTable);
diff --git a/components/time-ticket/screen-time-ticket-create.component.jsx b/components/time-ticket/screen-time-ticket-create.component.jsx
index b016ab0..3d051ac 100644
--- a/components/time-ticket/screen-time-ticket-create.component.jsx
+++ b/components/time-ticket/screen-time-ticket-create.component.jsx
@@ -14,6 +14,8 @@ import {
} from "../../redux/employee/employee.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useCallback } from "react";
+// import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
+import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
//TODO add props needed for call
const mapStateToProps = createStructuredSelector({
@@ -34,15 +36,15 @@ export function TimeTicketCreate({
const [date2, setDate2] = useState(new Date());
const [currentSCC, setCurrentSCC] = useState(null);
- const [currentSJob, setCurrentSJob] = useState(null);
+ // const [currentSJob, setCurrentSJob] = useState(null);
const [currentSJobId, setCurrentSJobId] = useState(null);
- const wrapperSetCurrentSJobState = useCallback(
- (val) => {
- setCurrentSJob(val);
- },
- [setCurrentSJob]
- );
+ // const wrapperSetCurrentSJobState = useCallback(
+ // (val) => {
+ // setCurrentSJob(val);
+ // },
+ // [setCurrentSJob]
+ // );
const showDatePicker = () => {
setDatePickerVisibility(true);
@@ -56,19 +58,20 @@ export function TimeTicketCreate({
hideDatePicker();
};
const formSubmit = (values) => {
- Dialog.alert({ content:
{JSON.stringify(values, null, 2)} });
+ console.log("values", values);
+ //Dialog.alert({ content: {JSON.stringify(values, null, 2)} });
//TODO update with start call for create time ticket
};
return (
-
+
{/* Below is for list of jobs/tickets */}
-
+
+
+
+
+
+
);
}
@@ -159,12 +167,13 @@ const localStyles = StyleSheet.create({
display: "flex",
flex: 1,
},
- topTimeTicketContainer: {},
- bottomTimeTicketContainer: {},
+ topContainer: {},
+ bottomContainer: {},
input: {},
dateButton: {
- margin: 16,
- height: 40,
+ marginVertical: 4,
+ marginHorizontal: 16,
+ height: 48,
justifyContent: "center",
alignContent: "center",
borderColor: "blue",
@@ -177,7 +186,8 @@ const localStyles = StyleSheet.create({
alignContent: "center",
},
inputStyle: {
- margin: 16,
+ marginVertical: 4,
+ marginHorizontal: 16,
height: 48,
fontSize: 16,
},
diff --git a/graphql/jobs.queries.js b/graphql/jobs.queries.js
index d56fa26..22adc17 100644
--- a/graphql/jobs.queries.js
+++ b/graphql/jobs.queries.js
@@ -923,6 +923,54 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
}
`;
+export const GET_LINE_TICKET_BY_PK = gql`
+ query GET_LINE_TICKET_BY_PK($id: uuid!) {
+ jobs_by_pk(id: $id) {
+ id
+ lbr_adjustments
+ converted
+ }
+ joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) {
+ id
+ line_desc
+ part_type
+ oem_partno
+ db_price
+ act_price
+ part_qty
+ mod_lbr_ty
+ db_hrs
+ mod_lb_hrs
+ lbr_op
+ lbr_amt
+ op_code_desc
+ convertedtolbr
+ convertedtolbr_data
+ }
+ timetickets(where: { jobid: { _eq: $id } }) {
+ actualhrs
+ ciecacode
+ cost_center
+ date
+ id
+ jobid
+ employeeid
+ memo
+ flat_rate
+ clockon
+ clockoff
+ rate
+ employee {
+ id
+ first_name
+ last_name
+ employee_number
+ }
+ productivehrs
+ }
+ }
+`;
+
export const generate_UPDATE_JOB_KANBAN = (
oldChildId,
oldChildNewParent,
diff --git a/translations/en-US/common.json b/translations/en-US/common.json
index 7929c3b..c3af9b5 100644
--- a/translations/en-US/common.json
+++ b/translations/en-US/common.json
@@ -360,6 +360,96 @@
"titles": {
"createtimeticket": "Create a Time Ticket"
}
+ },
+ "joblines": {
+ "actions": {
+ "converttolabor": "Convert amount to Labor.",
+ "new": "New Line"
+ },
+ "errors": {
+ "creating": "Error encountered while creating job line. {{message}}",
+ "updating": "Error encountered updating job line. {{message}}"
+ },
+ "fields": {
+ "act_price": "Retail Price",
+ "ah_detail_line": "Mark as Detail Labor Line (Autohouse Only)",
+ "db_price": "List Price",
+ "lbr_types": {
+ "LA1": "LA1",
+ "LA2": "LA2",
+ "LA3": "LA3",
+ "LA4": "LA4",
+ "LAA": "Aluminum",
+ "LAB": "Body",
+ "LAD": "Diagnostic",
+ "LAE": "Electrical",
+ "LAF": "Frame",
+ "LAG": "Glass",
+ "LAM": "Mechanical",
+ "LAR": "Refinish",
+ "LAS": "Structural",
+ "LAU": "User Defined"
+ },
+ "line_desc": "Line Desc.",
+ "line_ind": "S#",
+ "line_no": "Line #",
+ "location": "Location",
+ "mod_lb_hrs": "Hrs",
+ "mod_lbr_ty": "Labor Type",
+ "notes": "Notes",
+ "oem_partno": "OEM Part #",
+ "op_code_desc": "Op Code Description",
+ "part_qty": "Qty.",
+ "part_type": "Part Type",
+ "part_types": {
+ "CCC": "CC Cleaning",
+ "CCD": "CC Damage Waiver",
+ "CCDR": "CC Daily Rate",
+ "CCF": "CC Refuel",
+ "CCM": "CC Mileage",
+ "PAA": "Aftermarket",
+ "PAC": "Rechromed",
+ "PAE": "Existing",
+ "PAG": "Glass",
+ "PAL": "LKQ",
+ "PAM": "Remanufactured",
+ "PAN": "New/OEM",
+ "PAO": "Other",
+ "PAP": "OEM Partial",
+ "PAR": "Recored",
+ "PAS": "Sublet",
+ "PASL": "Sublet (L)"
+ },
+ "profitcenter_labor": "Profit Center: Labor",
+ "profitcenter_part": "Profit Center: Part",
+ "prt_dsmk_m": "Line Discount/Markup $",
+ "prt_dsmk_p": "Line Discount/Markup %",
+ "status": "Status",
+ "tax_part": "Tax Part",
+ "total": "Total",
+ "unq_seq": "Seq #"
+ },
+ "labels": {
+ "adjustmenttobeadded": "Adjustment to be added: {{adjustment}}",
+ "billref": "Latest Bill",
+ "convertedtolabor": "This line has been converted to labor. Ensure you adjust the profit center for the amount accordingly.",
+ "edit": "Edit Line",
+ "ioucreated": "IOU",
+ "new": "New Line",
+ "nostatus": "No Status",
+ "presets": "Jobline Presets"
+ },
+ "successes": {
+ "created": "Job line created successfully.",
+ "saved": "Job line saved.",
+ "updated": "Job line updated successfully."
+ },
+ "validations": {
+ "ahdetailonlyonuserdefinedtypes": "Detail line indicator can only be set for LA1, LA2, LA3, LA4, and LAU labor types.",
+ "hrsrequirediflbrtyp": "Labor hours are required if a labor type is selected. Clear the labor type if there are no labor hours.",
+ "requiredifparttype": "Required if a part type has been specified.",
+ "zeropriceexistingpart": "This line cannot have any price since it uses an existing part."
+ }
}
}
}
diff --git a/translations/es-MX/common.json b/translations/es-MX/common.json
index 88a4f1a..0d4aac5 100644
--- a/translations/es-MX/common.json
+++ b/translations/es-MX/common.json
@@ -360,6 +360,96 @@
"titles": {
"createtimeticket": ""
}
+ },
+ "joblines": {
+ "actions": {
+ "converttolabor": "",
+ "new": ""
+ },
+ "errors": {
+ "creating": "",
+ "updating": ""
+ },
+ "fields": {
+ "act_price": "Precio actual",
+ "ah_detail_line": "",
+ "db_price": "Precio de base de datos",
+ "lbr_types": {
+ "LA1": "",
+ "LA2": "",
+ "LA3": "",
+ "LA4": "",
+ "LAA": "",
+ "LAB": "",
+ "LAD": "",
+ "LAE": "",
+ "LAF": "",
+ "LAG": "",
+ "LAM": "",
+ "LAR": "",
+ "LAS": "",
+ "LAU": ""
+ },
+ "line_desc": "Descripción de línea",
+ "line_ind": "S#",
+ "line_no": "",
+ "location": "",
+ "mod_lb_hrs": "Horas laborales",
+ "mod_lbr_ty": "Tipo de trabajo",
+ "notes": "",
+ "oem_partno": "OEM parte #",
+ "op_code_desc": "",
+ "part_qty": "",
+ "part_type": "Tipo de parte",
+ "part_types": {
+ "CCC": "",
+ "CCD": "",
+ "CCDR": "",
+ "CCF": "",
+ "CCM": "",
+ "PAA": "",
+ "PAC": "",
+ "PAE": "",
+ "PAG": "",
+ "PAL": "",
+ "PAM": "",
+ "PAN": "",
+ "PAO": "",
+ "PAP": "",
+ "PAR": "",
+ "PAS": "",
+ "PASL": ""
+ },
+ "profitcenter_labor": "",
+ "profitcenter_part": "",
+ "prt_dsmk_m": "",
+ "prt_dsmk_p": "",
+ "status": "Estado",
+ "tax_part": "",
+ "total": "",
+ "unq_seq": "Seq #"
+ },
+ "labels": {
+ "adjustmenttobeadded": "",
+ "billref": "",
+ "convertedtolabor": "",
+ "edit": "Línea de edición",
+ "ioucreated": "",
+ "new": "Nueva línea",
+ "nostatus": "",
+ "presets": ""
+ },
+ "successes": {
+ "created": "",
+ "saved": "",
+ "updated": ""
+ },
+ "validations": {
+ "ahdetailonlyonuserdefinedtypes": "",
+ "hrsrequirediflbrtyp": "",
+ "requiredifparttype": "",
+ "zeropriceexistingpart": ""
+ }
}
}
}
diff --git a/translations/fr-CA/common.json b/translations/fr-CA/common.json
index a1d1cad..efb30bd 100644
--- a/translations/fr-CA/common.json
+++ b/translations/fr-CA/common.json
@@ -360,6 +360,96 @@
"titles": {
"createtimeticket": ""
}
+ },
+ "joblines": {
+ "actions": {
+ "converttolabor": "",
+ "new": ""
+ },
+ "errors": {
+ "creating": "",
+ "updating": ""
+ },
+ "fields": {
+ "act_price": "Prix actuel",
+ "ah_detail_line": "",
+ "db_price": "Prix de la base de données",
+ "lbr_types": {
+ "LA1": "",
+ "LA2": "",
+ "LA3": "",
+ "LA4": "",
+ "LAA": "",
+ "LAB": "",
+ "LAD": "",
+ "LAE": "",
+ "LAF": "",
+ "LAG": "",
+ "LAM": "",
+ "LAR": "",
+ "LAS": "",
+ "LAU": ""
+ },
+ "line_desc": "Description de la ligne",
+ "line_ind": "S#",
+ "line_no": "",
+ "location": "",
+ "mod_lb_hrs": "Heures de travail",
+ "mod_lbr_ty": "Type de travail",
+ "notes": "",
+ "oem_partno": "Pièce OEM #",
+ "op_code_desc": "",
+ "part_qty": "",
+ "part_type": "Type de pièce",
+ "part_types": {
+ "CCC": "",
+ "CCD": "",
+ "CCDR": "",
+ "CCF": "",
+ "CCM": "",
+ "PAA": "",
+ "PAC": "",
+ "PAE": "",
+ "PAG": "",
+ "PAL": "",
+ "PAM": "",
+ "PAN": "",
+ "PAO": "",
+ "PAP": "",
+ "PAR": "",
+ "PAS": "",
+ "PASL": ""
+ },
+ "profitcenter_labor": "",
+ "profitcenter_part": "",
+ "prt_dsmk_m": "",
+ "prt_dsmk_p": "",
+ "status": "Statut",
+ "tax_part": "",
+ "total": "",
+ "unq_seq": "Seq #"
+ },
+ "labels": {
+ "adjustmenttobeadded": "",
+ "billref": "",
+ "convertedtolabor": "",
+ "edit": "Ligne d'édition",
+ "ioucreated": "",
+ "new": "Nouvelle ligne",
+ "nostatus": "",
+ "presets": ""
+ },
+ "successes": {
+ "created": "",
+ "saved": "",
+ "updated": ""
+ },
+ "validations": {
+ "ahdetailonlyonuserdefinedtypes": "",
+ "hrsrequirediflbrtyp": "",
+ "requiredifparttype": "",
+ "zeropriceexistingpart": ""
+ }
}
}
}
diff --git a/util/labor-allocations-table.utility.js b/util/labor-allocations-table.utility.js
new file mode 100644
index 0000000..368e2cd
--- /dev/null
+++ b/util/labor-allocations-table.utility.js
@@ -0,0 +1,54 @@
+import i18next from "i18next";
+import React from "react";
+
+export const CalculateAllocationsTotals = (
+ bodyshop,
+ joblines,
+ timetickets,
+ adjustments = []
+) => {
+// console.log("🚀 ~ file: adjustments", adjustments);
+// console.log("🚀 ~ file: bodyshop", bodyshop);
+ console.log("🚀 ~ file: joblines", joblines);
+// console.log("🚀 ~ file: timetickets", timetickets);
+ const responsibilitycenters = bodyshop.md_responsibility_centers;
+ const jobCodes = joblines.map((item) => item.mod_lbr_ty);
+ console.log("jobCodes :", jobCodes);
+ //.filter((value, index, self) => self.indexOf(value) === index && !!value);
+ const ticketCodes = timetickets.map((item) => item.ciecacode);
+ console.log("ticketCodes :", ticketCodes);
+ //.filter((value, index, self) => self.indexOf(value) === index && !!value);
+ const adjustmentCodes = Object.keys(adjustments);
+ console.log("adjustmentCodes :", adjustmentCodes);
+ const allCodes = [...jobCodes, ...ticketCodes, ...adjustmentCodes].filter(
+ (value, index, self) => self.indexOf(value) === index && !!value
+ );
+ console.log("allCodes :", allCodes);
+
+ const r = allCodes.reduce((acc, value) => {
+ const r = {
+ opcode: value,
+ cost_center:
+ bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
+ ? i18next.t(
+ `joblines.fields.lbr_types.${value && value.toUpperCase()}`
+ )
+ : responsibilitycenters.defaults.costs[value],
+ mod_lbr_ty: value,
+ total: joblines.reduce((acc2, val2) => {
+ return val2.mod_lbr_ty === value ? acc2 + val2.mod_lb_hrs : acc2;
+ }, 0),
+ adjustments: adjustments[value] || 0,
+ claimed: timetickets.reduce((acc3, val3) => {
+ return val3.ciecacode === value ? acc3 + val3.productivehrs : acc3;
+ }, 0),
+ };
+
+ r.difference = r.total + r.adjustments - r.claimed;
+ acc.push(r);
+ return acc;
+ }, []);
+ console.log(" r is :", r);
+
+ return r;
+};