Compare commits

...

38 Commits

Author SHA1 Message Date
Allan Carr
1a622f1b2c IO-2391 Remote IP comment out till proxy for X-Forwarded-For can be figured out from AWS Load balancer 2023-09-08 13:18:20 -07:00
Allan Carr
bc7d0ef171 IO-2391 Add IP address to Server API Check 2023-09-06 09:06:05 -07:00
Allan Carr
763b199646 Merged in release/2023-08-25 (pull request #954)
Release/2023 08 25
2023-08-25 19:49:35 +00:00
Allan Carr
17905fa844 Merged in feature/IO-2389-Add-Metadata-of-Transaction-Wip (pull request #953)
IO-2389 Metadata to ExportLog
2023-08-25 18:05:01 +00:00
Allan Carr
74a0b78a71 IO-2389 Metadata to ExportLog 2023-08-25 11:05:16 -07:00
Allan Carr
797a423702 Merged in feature/IO-2368-QBO-Successful-Export (pull request #945)
IO-2368 Successful Export Notification for QBO
2023-08-25 17:24:38 +00:00
Allan Carr
2fce8c9644 Merged in feature/IO-2385-Tech-Console-Attendance-Report (pull request #946)
IO-2385 Enabled Emailing of TimeTicket & Attendance from Tech Console
2023-08-25 17:24:23 +00:00
Patrick Fic
9cd39c1c3e Resolve manual appointment old data after submission. 2023-08-25 10:23:22 -07:00
Patrick Fic
6264a2f45c Merge branch 'feature/intellipay' into release/2023-08-25 2023-08-23 13:54:39 -07:00
Allan Carr
9319f492dd IO-2385 Enabled Emailing of TimeTicket & Attendance from Tech Console
Modify print center so that it will/wont display email option if technician is set and production board detail so that it wont display the remove from production / add to scoreboard if technician is set
2023-08-22 13:29:28 -07:00
Allan Carr
8f04c5a12c IO-2368 Successful Export Notification for QBO 2023-08-22 09:15:10 -07:00
Allan Carr
436a41405d Merged in release/2023-08-18 (pull request #944)
Release/2023 08 18
2023-08-18 21:41:04 +00:00
Allan Carr
a2150009db IO-2385 Tech Console to print Attendance Report from Shift Clock 2023-08-18 13:59:42 -07:00
Allan Carr
e1c785322f Merged in feature/IO-2385-Tech-Console-Attendance-Report (pull request #943)
IO-2385 Tech Console to print Attendance Report from Shift Clock
2023-08-18 20:59:23 +00:00
John Allen Delos Reyes
c1d71720ab Merged in feature/IO-2380-parts-order-receive-modal (pull request #934)
Feature/IO-2380 parts order receive modal

Approved-by: Patrick Fic
2023-08-18 18:16:04 +00:00
Allan Carr
89ff7740e2 Merged in feature/IO-2325-Ticket-Create-By (pull request #942)
IO-2325 Add in Created by from Action menu
2023-08-18 16:02:56 +00:00
Allan Carr
4e69fe819e IO-2325 Add in Created by from Action menu 2023-08-18 09:03:13 -07:00
Allan Carr
a8cc3fa190 Merged in feature/IO-2384-Owner-Related-Job-Default-Sort-Order (pull request #939)
IO-2384 Default Sort Order for Related Jobs
2023-08-17 23:19:03 +00:00
Allan Carr
10fceb7ddf IO-2384 Extend to Vehicle Related ROs 2023-08-17 16:18:56 -07:00
Allan Carr
6c1a0cff8d IO-2384 Default Sort Order for Related Jobs 2023-08-17 16:05:33 -07:00
Allan Carr
d92d2cca9a Merged in feature/IO-2247-Dashboard-Components (pull request #938)
IO-2247 Don't push manual appointments into component if they don't have a job associated.
2023-08-17 16:32:37 +00:00
Allan Carr
ea54820bc0 IO-2247 Don't push manual appointments into component if they don't have a job associated. 2023-08-17 09:32:32 -07:00
Allan Carr
62a800a2c0 Merged in feature/IO-2381-Disabled-Cancel-Appt-Button (pull request #937)
IO-2381 Disabled Cancel Appt Button
2023-08-17 01:37:20 +00:00
Allan Carr
9c408d8bf5 IO-2381 Disabled Cancel Appt Button 2023-08-16 18:37:19 -07:00
Allan Carr
45ad09c100 Merged in feature/IO-2383-Timeticket-Employee-Rangefilter (pull request #935)
IO-2383 TimeTicket Employee Range Filter
2023-08-17 00:25:11 +00:00
Allan Carr
dd5cafcd42 IO-2383 TimeTicket Employee Range Filter 2023-08-16 17:25:32 -07:00
swtmply
ddd816e7ca IO-2380 added translation to fields 2023-08-17 06:15:46 +08:00
swtmply
d646e5f285 IO-2380 added part number and price 2023-08-16 11:43:21 +08:00
Allan Carr
ba683a2e8a Merged in release/2023-08-11 (pull request #931)
Release/2023 08 11
2023-08-11 20:36:11 +00:00
Allan Carr
5209c12b89 Merged in feature/IO-2325-Ticket-Create-By (pull request #928)
IO-2325 Shift Clock Created By
2023-08-11 15:36:06 +00:00
Allan Carr
bf7aa17f65 IO-2325 Shift Clock Created By 2023-08-11 08:35:57 -07:00
Allan Carr
cd6e0dcde3 Merged in feature/IO-2325-Ticket-Create-By (pull request #926)
IO-2325 Time Ticket Creation Date
2023-08-10 19:06:58 +00:00
Allan Carr
a2822f5592 Merged in feature/IO-2376-Inactive-Employee-Login (pull request #925)
IO-2376 Prevent Inactive Employee from logging in
2023-08-10 19:06:49 +00:00
Allan Carr
ca129fa4a0 IO-2325 Time Ticket Creation Date 2023-08-10 12:06:47 -07:00
Allan Carr
eee135f4ef IO-2376 Prevent Inactive Employee from logging in 2023-08-09 18:01:28 -07:00
Allan Carr
de92b2d47e Merged in feature/IO-2375-Job-Costing-by-CSR-Filter-Label (pull request #924)
IO-2375 Filter label for Job Costing by CSR correction
2023-08-09 22:26:09 +00:00
Allan Carr
5d7384aa8b IO-2375 Filter label for Job Costing by CSR correction 2023-08-09 15:25:58 -07:00
Patrick Fic
bf18e687da Schema updates for parts dispatch. 2023-08-04 12:36:08 -07:00
47 changed files with 549 additions and 243 deletions

View File

@@ -23,36 +23,40 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
const appt = []; // Flatten Data const appt = []; // Flatten Data
data.scheduled_in_today.forEach((item) => { data.scheduled_in_today.forEach((item) => {
var i = { if (item.job) {
canceled: item.canceled, var i = {
id: item.id, canceled: item.canceled,
alt_transport: item.job.alt_transport, id: item.id,
clm_no: item.job.clm_no, alt_transport: item.job.alt_transport,
jobid: item.job.jobid, clm_no: item.job.clm_no,
ins_co_nm: item.job.ins_co_nm, jobid: item.job.jobid,
iouparent: item.job.iouparent, ins_co_nm: item.job.ins_co_nm,
ownerid: item.job.ownerid, iouparent: item.job.iouparent,
ownr_co_nm: item.job.ownr_co_nm, ownerid: item.job.ownerid,
ownr_ea: item.job.ownr_ea, ownr_co_nm: item.job.ownr_co_nm,
ownr_fn: item.job.ownr_fn, ownr_ea: item.job.ownr_ea,
ownr_ln: item.job.ownr_ln, ownr_fn: item.job.ownr_fn,
ownr_ph1: item.job.ownr_ph1, ownr_ln: item.job.ownr_ln,
ownr_ph2: item.job.ownr_ph2, ownr_ph1: item.job.ownr_ph1,
production_vars: item.job.production_vars, ownr_ph2: item.job.ownr_ph2,
ro_number: item.job.ro_number, production_vars: item.job.production_vars,
suspended: item.job.suspended, ro_number: item.job.ro_number,
v_make_desc: item.job.v_make_desc, suspended: item.job.suspended,
v_model_desc: item.job.v_model_desc, v_make_desc: item.job.v_make_desc,
v_model_yr: item.job.v_model_yr, v_model_desc: item.job.v_model_desc,
v_vin: item.job.v_vin, v_model_yr: item.job.v_model_yr,
vehicleid: item.job.vehicleid, v_vin: item.job.v_vin,
note: item.note, vehicleid: item.job.vehicleid,
start: moment(item.start).format("hh:mm a"), note: item.note,
title: item.title, start: moment(item.start).format("hh:mm a"),
}; title: item.title,
appt.push(i); };
appt.push(i);
}
});
appt.sort(function (a, b) {
return new moment(a.start) - new moment(b.start);
}); });
appt.sort ( function (a, b) { return new Date(a.start) - new Date(b.start); });
const columns = [ const columns = [
{ {
@@ -182,7 +186,12 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
}; };
return ( return (
<Card title={t("dashboard.titles.scheduledintoday", {date: moment().startOf("day").format("MM/DD/YYYY")})} {...cardProps}> <Card
title={t("dashboard.titles.scheduledintoday", {
date: moment().startOf("day").format("MM/DD/YYYY"),
})}
{...cardProps}
>
<div style={{ height: "100%" }}> <div style={{ height: "100%" }}>
<Table <Table
onChange={handleTableChange} onChange={handleTableChange}

View File

@@ -1,10 +1,9 @@
import { useTreatments } from "@splitsoftware/splitio-react";
import Icon, { import Icon, {
BankFilled, BankFilled,
BarChartOutlined, BarChartOutlined,
CarFilled, CarFilled,
ClockCircleFilled,
CheckCircleOutlined, CheckCircleOutlined,
ClockCircleFilled,
DashboardFilled, DashboardFilled,
DollarCircleFilled, DollarCircleFilled,
ExportOutlined, ExportOutlined,
@@ -26,6 +25,7 @@ import Icon, {
UnorderedListOutlined, UnorderedListOutlined,
UserOutlined, UserOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Layout, Menu } from "antd"; import { Layout, Menu } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -274,7 +274,11 @@ function Header({
onClick={() => { onClick={() => {
setTimeTicketContext({ setTimeTicketContext({
actions: {}, actions: {},
context: {}, context: {
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
},
}); });
}} }}
> >

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR } from "../../graphql/vendors.queries"; import { SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR } from "../../graphql/vendors.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
@@ -13,13 +14,14 @@ import VendorSearchSelect from "../vendor-search-select/vendor-search-select.com
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal); export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal);
export function Jobd3RdPartyModal({ bodyshop, jobId, job }) { export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
const [isModalVisible, setIsModalVisible] = useState(false); const [isModalVisible, setIsModalVisible] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -212,7 +214,9 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
]} ]}
> >
<Radio.Group> <Radio.Group>
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio> {!technician ? (
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
) : null}
<Radio value={"p"}>{t("parts_orders.labels.print")}</Radio> <Radio value={"p"}>{t("parts_orders.labels.print")}</Radio>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>

View File

@@ -29,11 +29,11 @@ import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component"; import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleAtChange from "./job-at-change.component"; import ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.component"; import ScheduleEventColor from "./schedule-event.color.component";
import ScheduleEventNote from "./schedule-event.note.component"; import ScheduleEventNote from "./schedule-event.note.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -208,46 +208,56 @@ export function ScheduleEventComponent({
<Button>{t("appointments.actions.sendreminder")}</Button> <Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown> </Dropdown>
) : null} ) : null}
<Popover {event.arrived ? (
trigger="click"
disabled={event.arrived}
content={
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button htmlType="submit">
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
<Button <Button
// onClick={() => handleCancel(event.id)} // onClick={() => handleCancel(event.id)}
disabled={event.arrived} disabled={event.arrived}
> >
{t("appointments.actions.cancel")} {t("appointments.actions.cancel")}
</Button> </Button>
</Popover> ) : (
<Popover
trigger="click"
disabled={event.arrived}
content={
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button htmlType="submit">
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
<Button
// onClick={() => handleCancel(event.id)}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
</Button>
</Popover>
)}
{event.isintake ? ( {event.isintake ? (
<Button <Button
disabled={event.arrived} disabled={event.arrived}

View File

@@ -4,16 +4,15 @@ import axios from "axios";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { useHistory } from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -173,6 +172,13 @@ export function JobsCloseExportButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
notification.open({
type: "success",
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
}
if (setSelectedJobs) { if (setSelectedJobs) {
setSelectedJobs((selectedJobs) => { setSelectedJobs((selectedJobs) => {
return selectedJobs.filter((i) => i !== jobId); return selectedJobs.filter((i) => i !== jobId);

View File

@@ -140,63 +140,67 @@ export function JobsDetailHeaderActions({
<Menu.Item <Menu.Item
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled} disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
> >
<Popover {job.status !== bodyshop.md_ro_statuses.default_scheduled ? (
trigger="click" t("menus.jobsactions.cancelallappointments")
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled} ) : (
content={ <Popover
<Form trigger="click"
layout="vertical" disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
onFinish={async ({ lost_sale_reason }) => { content={
const jobUpdate = await cancelAllAppointments({ <Form
variables: { layout="vertical"
jobid: job.id, onFinish={async ({ lost_sale_reason }) => {
job: { const jobUpdate = await cancelAllAppointments({
date_scheduled: null, variables: {
scheduled_in: null, jobid: job.id,
scheduled_completion: null, job: {
lost_sale_reason, date_scheduled: null,
status: bodyshop.md_ro_statuses.default_imported, scheduled_in: null,
scheduled_completion: null,
lost_sale_reason,
status: bodyshop.md_ro_statuses.default_imported,
},
}, },
},
});
if (!jobUpdate.errors) {
notification["success"]({
message: t("appointments.successes.canceled"),
}); });
return; if (!jobUpdate.errors) {
} notification["success"]({
}} message: t("appointments.successes.canceled"),
> });
<Form.Item return;
name="lost_sale_reason" }
label={t("jobs.fields.lost_sale_reason")} }}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
> >
<Select <Form.Item
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({ name="lost_sale_reason"
label: lsr, label={t("jobs.fields.lost_sale_reason")}
value: lsr, rules={[
}))} {
/> required: true,
</Form.Item> //message: t("general.validation.required"),
<Button },
htmlType="submit" ]}
disabled={ >
job.status !== bodyshop.md_ro_statuses.default_scheduled <Select
} options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
> label: lsr,
{t("appointments.actions.cancel")} value: lsr,
</Button> }))}
</Form> />
} </Form.Item>
> <Button
{t("menus.jobsactions.cancelallappointments")} htmlType="submit"
</Popover> disabled={
job.status !== bodyshop.md_ro_statuses.default_scheduled
}
>
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
{t("menus.jobsactions.cancelallappointments")}
</Popover>
)}
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
disabled={ disabled={
@@ -242,7 +246,12 @@ export function JobsDetailHeaderActions({
setTimeTicketContext({ setTimeTicketContext({
actions: {}, actions: {},
context: { jobId: job.id }, context: {
jobId: job.id,
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
},
}); });
}} }}
> >

View File

@@ -169,14 +169,20 @@ export function JobsExportAllButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
notification.open({
type: "success",
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
}
} }
}) })
); );
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
if (!!completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
setLoading(false); setLoading(false);
}; };

View File

@@ -6,6 +6,7 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters";
import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component"; import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -15,6 +16,15 @@ const mapStateToProps = createStructuredSelector({
function OwnerDetailJobsComponent({ bodyshop, owner }) { function OwnerDetailJobsComponent({ bodyshop, owner }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedJobs, setSelectedJobs] = useState([]); const [selectedJobs, setSelectedJobs] = useState([]);
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const columns = [ const columns = [
{ {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
@@ -26,6 +36,9 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
{record.ro_number || t("general.labels.na")} {record.ro_number || t("general.labels.na")}
</Link> </Link>
), ),
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.vehicle"), title: t("jobs.fields.vehicle"),
@@ -46,11 +59,17 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
title: t("jobs.fields.clm_no"), title: t("jobs.fields.clm_no"),
dataIndex: "clm_no", dataIndex: "clm_no",
key: "clm_no", key: "clm_no",
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
}, },
{ {
@@ -60,6 +79,9 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
render: (text, record) => ( render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter> <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
), ),
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
}, },
]; ];
@@ -80,6 +102,7 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
scroll={{ x: true }} scroll={{ x: true }}
rowKey="id" rowKey="id"
dataSource={owner.jobs} dataSource={owner.jobs}
onChange={handleTableChange}
rowSelection={{ rowSelection={{
onSelect: (record, selected, selectedRows) => { onSelect: (record, selected, selectedRows) => {
setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []); setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []);

View File

@@ -113,6 +113,8 @@ export function PartsOrderListTableComponent({
id: pol.id, id: pol.id,
line_desc: pol.line_desc, line_desc: pol.line_desc,
quantity: pol.quantity, quantity: pol.quantity,
act_price: pol.act_price,
oem_partno: pol.oem_partno,
}; };
}), }),
}, },

View File

@@ -79,6 +79,20 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item
label={t("joblines.fields.oem_partno")}
key={`${index}oem_partno`}
name={[field.name, "oem_partno"]}
>
<Input disabled />
</Form.Item>
<Form.Item
label={t("joblines.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<Input disabled />
</Form.Item>
<Form.Item <Form.Item
label={t("joblines.fields.location")} label={t("joblines.fields.location")}
key={`${index}location`} key={`${index}location`}

View File

@@ -1,20 +1,19 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, notification } from "antd"; import { Button, notification } from "antd";
import axios from "axios"; import axios from "axios";
import _ from "lodash";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_BILLS } from "../../graphql/bills.queries"; import { UPDATE_BILLS } from "../../graphql/bills.queries";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
import _ from "lodash";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -164,6 +163,13 @@ export function PayableExportAll({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
notification.open({
type: "success",
key: "billsuccessexport",
message: t("bills.successes.exported"),
});
}
} }
})() })()
); );
@@ -173,7 +179,6 @@ export function PayableExportAll({
if (!!completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch(); if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
setLoading(false); setLoading(false);
}; };

View File

@@ -4,16 +4,15 @@ import axios from "axios";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_BILLS } from "../../graphql/bills.queries"; import { UPDATE_BILLS } from "../../graphql/bills.queries";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -167,7 +166,13 @@ export function PayableExportButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch(); if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
notification.open({
type: "success",
key: "billsuccessexport",
message: t("bills.successes.exported"),
});
}
if (setSelectedBills) { if (setSelectedBills) {
setSelectedBills((selectedBills) => { setSelectedBills((selectedBills) => {
@@ -177,6 +182,7 @@ export function PayableExportButton({
} }
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
setLoading(false); setLoading(false);
}; };

View File

@@ -172,8 +172,15 @@ export function PaymentExportButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch(); if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
notification.open({
type: "success",
key: "paymentsuccessexport",
message: t("payments.successes.exported"),
});
}
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
setLoading(false); setLoading(false);
}; };

View File

@@ -25,7 +25,7 @@ export function PaymentsExportAllButton({
disabled, disabled,
loadingCallback, loadingCallback,
completedCallback, completedCallback,
refetch refetch,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updatePayments] = useMutation(UPDATE_PAYMENTS); const [updatePayments] = useMutation(UPDATE_PAYMENTS);
@@ -150,6 +150,13 @@ export function PaymentsExportAllButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
notification.open({
type: "success",
key: "paymentsuccessexport",
message: t("payments.successes.exported"),
});
}
} }
})() })()
); );

View File

@@ -5,11 +5,13 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setEmailOptions } from "../../redux/email/email.actions"; import { setEmailOptions } from "../../redux/email/email.actions";
import { selectPrintCenter } from "../../redux/modals/modals.selectors"; import { selectPrintCenter } from "../../redux/modals/modals.selectors";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
printCenterModal: selectPrintCenter, printCenterModal: selectPrintCenter,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)), setEmailOptions: (e) => dispatch(setEmailOptions(e)),
@@ -22,6 +24,7 @@ export function PrintCenterItemComponent({
id, id,
bodyshop, bodyshop,
disabled, disabled,
technician,
}) { }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { context } = printCenterModal; const { context } = printCenterModal;
@@ -44,19 +47,24 @@ export function PrintCenterItemComponent({
<Space wrap> <Space wrap>
{item.title} {item.title}
<PrinterOutlined onClick={renderToNewWindow} /> <PrinterOutlined onClick={renderToNewWindow} />
<MailOutlined {!technician ? (
onClick={() => { <MailOutlined
GenerateDocument( onClick={() => {
{ GenerateDocument(
name: item.key, {
variables: { id: id }, name: item.key,
}, variables: { id: id },
{ to: context.job && context.job.ownr_ea, subject: item.subject }, },
"e", {
id to: context.job && context.job.ownr_ea,
); subject: item.subject,
}} },
/> "e",
id
);
}}
/>
) : null}
{loading && <Spin />} {loading && <Spin />}
</Space> </Space>
</li> </li>

View File

@@ -1,31 +1,33 @@
import { PrinterFilled } from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Descriptions, Drawer, Space, PageHeader, Button } from "antd"; import { Button, Descriptions, Drawer, PageHeader, Space } from "antd";
import queryString from "query-string"; import queryString from "query-string";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom"; import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries"; import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import StartChatButton from "../chat-open-button/chat-open-button.component"; import StartChatButton from "../chat-open-button/chat-open-button.component";
import JobAtChange from "../job-at-change/job-at-change.component";
import JobDetailCardsDocumentsComponent from "../job-detail-cards/job-detail-cards.documents.component"; import JobDetailCardsDocumentsComponent from "../job-detail-cards/job-detail-cards.documents.component";
import JobDetailCardsNotesComponent from "../job-detail-cards/job-detail-cards.notes.component"; import JobDetailCardsNotesComponent from "../job-detail-cards/job-detail-cards.notes.component";
import JobDetailCardsPartsComponent from "../job-detail-cards/job-detail-cards.parts.component"; import JobDetailCardsPartsComponent from "../job-detail-cards/job-detail-cards.parts.component";
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container"; import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
import JobAtChange from "../job-at-change/job-at-change.component";
import { PrinterFilled } from "@ant-design/icons";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component"; import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component";
import { selectBodyshop } from "../../redux/user/user.selectors"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) => setPrintCenterContext: (context) =>
@@ -40,6 +42,7 @@ export function ProductionListDetail({
bodyshop, bodyshop,
jobs, jobs,
setPrintCenterContext, setPrintCenterContext,
technician,
}) { }) {
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const history = useHistory(); const history = useHistory();
@@ -66,7 +69,9 @@ export function ProductionListDetail({
title={theJob.ro_number} title={theJob.ro_number}
extra={ extra={
<Space wrap> <Space wrap>
<ProductionRemoveButton jobId={theJob.id} />{" "} {!technician ? (
<ProductionRemoveButton jobId={theJob.id} />
) : null}
<Button <Button
onClick={() => { onClick={() => {
setPrintCenterContext({ setPrintCenterContext({
@@ -82,7 +87,9 @@ export function ProductionListDetail({
<PrinterFilled /> <PrinterFilled />
{t("jobs.actions.printCenter")} {t("jobs.actions.printCenter")}
</Button> </Button>
<ScoreboardAddButton job={data ? data.jobs_by_pk : {}} /> {!technician ? (
<ScoreboardAddButton job={data ? data.jobs_by_pk : {}} />
) : null}
</Space> </Space>
} }
/> />

View File

@@ -59,11 +59,12 @@ export function ScheduleManualEvent({ bodyshop, event }) {
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"], refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"],
}); });
} }
form.resetFields();
setVisibility(false);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
setLoading(false); setLoading(false);
setVisibility(false);
} }
}; };

View File

@@ -1,21 +1,25 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Card, Form, notification, Space } from "antd"; import { Button, Card, Form, notification, Space } from "antd";
import axios from "axios"; import axios from "axios";
import moment from "moment";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries"; import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries";
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";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
import TechClockInComponent from "./tech-job-clock-in-form.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
technician: selectTechnician, technician: selectTechnician,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setTimeTicketContext: (context) => setTimeTicketContext: (context) =>
@@ -25,9 +29,10 @@ export function TechClockInContainer({
setTimeTicketContext, setTimeTicketContext,
technician, technician,
bodyshop, bodyshop,
currentUser,
}) { }) {
console.log( console.log(
"🚀 ~ file: tech-job-clock-in-form.container.jsx:29 ~ technician:", "🚀 ~ file: tech-job-clock-in-form.container.jsx:30 ~ technician:",
technician technician
); );
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -66,6 +71,12 @@ export function TechClockInContainer({
values.cost_center values.cost_center
); );
}), }),
created_by: currentUser.email.concat(
" | ",
technician.employee_number
.concat(" ", technician.first_name, " ", technician.last_name)
.trim()
),
}, },
], ],
}, },
@@ -100,13 +111,24 @@ export function TechClockInContainer({
employeeid: technician.id, employeeid: technician.id,
flat_rate: emps.flat_rate, flat_rate: emps.flat_rate,
}, },
created_by: currentUser.email.concat(
" | ",
technician.employee_number
.concat(
" ",
technician.first_name,
" ",
technician.last_name
)
.trim()
),
}, },
}); });
}} }}
> >
{t("timetickets.actions.enter")} {t("timetickets.actions.enter")}
</Button> </Button>
<TechJobPrintTickets /> <TechJobPrintTickets attendacePrint={false} />
<Button <Button
type="primary" type="primary"
onClick={() => form.submit()} onClick={() => form.submit()}

View File

@@ -1,4 +1,4 @@
import { Button, Card, DatePicker, Form, Popover, Space } from "antd"; import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
import moment from "moment"; import moment from "moment";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -21,12 +21,13 @@ export default connect(
mapDispatchToProps mapDispatchToProps
)(TechJobPrintTickets); )(TechJobPrintTickets);
export function TechJobPrintTickets({ technician, event }) { export function TechJobPrintTickets({ technician, event, attendacePrint }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [form] = Form.useForm(); const [form] = Form.useForm();
const [visibility, setVisibility] = useState(false); const [visibility, setVisibility] = useState(false);
const Templates = TemplateList("report_center");
useEffect(() => { useEffect(() => {
if (visibility && event) { if (visibility && event) {
@@ -44,7 +45,10 @@ export function TechJobPrintTickets({ technician, event }) {
try { try {
await GenerateDocument( await GenerateDocument(
{ {
name: TemplateList().timetickets_employee.key, name:
attendacePrint === true
? Templates.attendance_employee.key
: Templates.timetickets_employee.key,
variables: { variables: {
...(start ...(start
? { start: moment(start).startOf("day").format("YYYY-MM-DD") } ? { start: moment(start).startOf("day").format("YYYY-MM-DD") }
@@ -60,9 +64,12 @@ export function TechJobPrintTickets({ technician, event }) {
}, },
{ {
to: technician.email, to: technician.email,
subject: TemplateList().timetickets_employee.subject, subject:
attendacePrint === true
? Templates.attendance_employee.subject
: Templates.timetickets_employee.subject,
}, },
"p" values.sendby // === "email" ? "e" : "p"
); );
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -92,10 +99,25 @@ export function TechJobPrintTickets({ technician, event }) {
format={"MM/DD/YYYY"} format={"MM/DD/YYYY"}
/> />
</Form.Item> </Form.Item>
<Form.Item dependencies={["dates"]}>
{() => {
return (
<Form.Item
label={t("general.labels.sendby")}
name="sendby"
initialValue="p"
>
<Radio.Group>
<Radio value="e">{t("general.labels.email")}</Radio>
<Radio value="p">{t("general.labels.print")}</Radio>
</Radio.Group>
</Form.Item>
);
}}
</Form.Item>
<Space wrap> <Space wrap>
<Button type="primary" onClick={() => form.submit()}> <Button type="primary" onClick={() => form.submit()}>
{t("general.actions.print")} {t("reportcenter.actions.generate")}
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
@@ -118,7 +140,7 @@ export function TechJobPrintTickets({ technician, event }) {
return ( return (
<Popover content={overlay} visible={visibility}> <Popover content={overlay} visible={visibility}>
<Button loading={loading} onClick={handleClick}> <Button loading={loading} onClick={handleClick}>
{t("general.actions.print")} {t("general.labels.reports")}
</Button> </Button>
</Popover> </Popover>
); );

View File

@@ -9,9 +9,10 @@ import { createStructuredSelector } from "reselect";
import { import {
selectAuthLevel, selectAuthLevel,
selectBodyshop, selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort, dateSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import RbacWrapper, { import RbacWrapper, {
HasRbacAccess, HasRbacAccess,
@@ -20,6 +21,7 @@ import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
authLevel: selectAuthLevel, authLevel: selectAuthLevel,
currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -29,6 +31,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketList);
export function TimeTicketList({ export function TimeTicketList({
bodyshop, bodyshop,
authLevel, authLevel,
currentUser,
disabled, disabled,
loading, loading,
timetickets, timetickets,
@@ -193,7 +196,15 @@ export function TimeTicketList({
} }
}, },
}, },
{
title: t("timetickets.fields.created_by"),
dataIndex: "created_by",
key: "created_by",
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
sortOrder:
state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
render: (text, record) => record.created_by,
},
{ {
title: t("general.labels.actions"), title: t("general.labels.actions"),
dataIndex: "actions", dataIndex: "actions",
@@ -254,7 +265,12 @@ export function TimeTicketList({
(techConsole ? null : ( (techConsole ? null : (
<TimeTicketEnterButton <TimeTicketEnterButton
actions={{ refetch }} actions={{ refetch }}
context={{ jobId: jobId }} context={{
jobId: jobId,
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
}}
disabled={disabled} disabled={disabled}
> >
{t("timetickets.actions.enter")} {t("timetickets.actions.enter")}

View File

@@ -1,5 +1,5 @@
import { useMutation, useQuery } from "@apollo/client"; import { useMutation, useQuery } from "@apollo/client";
import { Button, Form, Modal, notification, PageHeader, Space } from "antd"; import { Button, Form, Modal, PageHeader, Space, notification } from "antd";
import moment from "moment"; import moment from "moment";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -77,6 +77,7 @@ export function TimeTicketModalContainer({
)[0].rate )[0].rate
: null, : null,
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
created_by: timeTicketModal.context.created_by,
}, },
], ],
}, },

View File

@@ -4,48 +4,67 @@ import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component"; import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
export default function TimeTicketShiftActive({ timetickets, refetch }) { export default function TimeTicketShiftActive({
timetickets,
refetch,
isTechConsole,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div> <div>
{timetickets.length > 0 ? ( {timetickets.length > 0 ? (
<div> <div
<Typography.Title level={2}> style={{
{t("timetickets.labels.shiftalreadyclockedon")} display: "flex",
</Typography.Title> justifyContent: "space-between",
<List flexDirection: "column",
grid={{ height: "100%",
gutter: 32, }}
xs: 1, >
sm: 2, <div style={{ display: "flex", justifyContent: "space-between" }}>
md: 3, <Typography.Title level={2}>
lg: 4, {t("timetickets.labels.shiftalreadyclockedon")}
xl: 5, </Typography.Title>
xxl: 6, {isTechConsole ? (
}} <TechJobPrintTickets attendacePrint={true} />
dataSource={timetickets || []} ) : null}
renderItem={(ticket) => ( </div>
<List.Item> <div style={{ flexGrow: 1 }}>
<Card <List
title={t(ticket.memo)} grid={{
actions={[ gutter: 32,
<TechClockOffButton xs: 1,
jobId={ticket.jobid} sm: 2,
timeTicketId={ticket.id} md: 3,
completedCallback={refetch} lg: 4,
isShiftTicket xl: 5,
/>, xxl: 6,
]} }}
> dataSource={timetickets || []}
<DataLabel label={t("timetickets.fields.clockon")}> renderItem={(ticket) => (
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter> <List.Item>
</DataLabel> <Card
</Card> title={t(ticket.memo)}
</List.Item> actions={[
)} <TechClockOffButton
></List> jobId={ticket.jobid}
timeTicketId={ticket.id}
completedCallback={refetch}
isShiftTicket
/>,
]}
>
<DataLabel label={t("timetickets.fields.clockon")}>
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
</DataLabel>
</Card>
</List.Item>
)}
></List>
</div>
</div> </div>
) : null} ) : null}
</div> </div>

View File

@@ -1,5 +1,5 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Form, notification } from "antd"; import { Button, Form, Space, notification } from "antd";
import axios from "axios"; import axios from "axios";
import moment from "moment"; import moment from "moment";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
@@ -12,6 +12,7 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
import TimeTicektShiftComponent from "./time-ticket-shift-form.component"; import TimeTicektShiftComponent from "./time-ticket-shift-form.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -29,6 +30,10 @@ export function TimeTicektShiftContainer({
isTechConsole, isTechConsole,
checkIfAlreadyClocked, checkIfAlreadyClocked,
}) { }) {
console.log(
"🚀 ~ file: time-ticket-shift-form.container.jsx:28 ~ technician:",
technician
);
const [form] = Form.useForm(); const [form] = Form.useForm();
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET); const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -65,6 +70,21 @@ export function TimeTicektShiftContainer({
clockon: theTime, clockon: theTime,
date: theTime, date: theTime,
memo: values.memo, memo: values.memo,
created_by: isTechConsole
? currentUser.email.concat(
" | ",
technician.employee_number
.concat(
" ",
technician.first_name,
" ",
technician.last_name
)
.trim()
)
: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
}, },
], ],
}, },
@@ -98,9 +118,14 @@ export function TimeTicektShiftContainer({
initialValues={{ cost_center: t("timetickets.labels.shift") }} initialValues={{ cost_center: t("timetickets.labels.shift") }}
> >
<TimeTicektShiftComponent form={form} /> <TimeTicektShiftComponent form={form} />
<Button htmlType="submit" loading={loading}> <Space wrap>
{t("timetickets.actions.clockin")} <Button htmlType="submit" loading={loading} type="primary">
</Button> {t("timetickets.actions.clockin")}
</Button>
{isTechConsole === true ? (
<TechJobPrintTickets attendacePrint={true} />
) : null}
</Space>
</Form> </Form>
</div> </div>
); );

View File

@@ -76,6 +76,7 @@ export function TimeTicketShiftContainer({
<TimeTicketShiftActive <TimeTicketShiftActive
timetickets={data ? data.timetickets : []} timetickets={data ? data.timetickets : []}
refetch={refetch} refetch={refetch}
isTechConsole={isTechConsole}
/> />
) : ( ) : (
<TimeTicketShiftFormContainer <TimeTicketShiftFormContainer

View File

@@ -6,8 +6,9 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component"; import { alphaSort, statusSort } from "../../utils/sorters";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -16,6 +17,14 @@ const mapStateToProps = createStructuredSelector({
export function VehicleDetailJobsComponent({ vehicle, bodyshop }) { export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedJobs, setSelectedJobs] = useState([]); const [selectedJobs, setSelectedJobs] = useState([]);
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const columns = [ const columns = [
{ {
@@ -28,6 +37,9 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
{record.ro_number || t("general.labels.na")} {record.ro_number || t("general.labels.na")}
</Link> </Link>
), ),
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.owner"), title: t("jobs.fields.owner"),
@@ -43,11 +55,17 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
title: t("jobs.fields.clm_no"), title: t("jobs.fields.clm_no"),
dataIndex: "clm_no", dataIndex: "clm_no",
key: "clm_no", key: "clm_no",
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
}, },
{ {
@@ -57,6 +75,9 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
render: (text, record) => ( render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter> <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
), ),
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
}, },
]; ];
@@ -76,6 +97,7 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
rowKey="id" rowKey="id"
scroll={{ x: true }} scroll={{ x: true }}
dataSource={vehicle.jobs} dataSource={vehicle.jobs}
onChange={handleTableChange}
rowSelection={{ rowSelection={{
onSelect: (record, selected, selectedRows) => { onSelect: (record, selected, selectedRows) => {
setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []); setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []);

View File

@@ -57,6 +57,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
actualhrs actualhrs
ciecacode ciecacode
cost_center cost_center
created_by
date date
id id
jobid jobid

View File

@@ -69,7 +69,7 @@ export const QUERY_OWNER_BY_ID = gql`
preferred_contact preferred_contact
note note
tax_number tax_number
jobs { jobs(order_by: { date_open: desc }) {
id id
ro_number ro_number
clm_no clm_no

View File

@@ -37,6 +37,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql`
clockon clockon
cost_center cost_center
created_at created_at
created_by
date date
id id
rate rate
@@ -80,6 +81,7 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
clockon clockon
cost_center cost_center
created_at created_at
created_by
date date
id id
rate rate
@@ -112,6 +114,7 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
clockon clockon
cost_center cost_center
created_at created_at
created_by
date date
id id
rate rate
@@ -151,6 +154,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
clockon clockon
cost_center cost_center
created_at created_at
created_by
date date
id id
rate rate
@@ -181,6 +185,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
clockon clockon
cost_center cost_center
created_at created_at
created_by
date date
id id
rate rate
@@ -210,6 +215,7 @@ export const INSERT_NEW_TIME_TICKET = gql`
insert_timetickets(objects: $timeTicketInput) { insert_timetickets(objects: $timeTicketInput) {
returning { returning {
id id
created_by
clockon clockon
clockoff clockoff
employeeid employeeid

View File

@@ -28,11 +28,10 @@ export const QUERY_VEHICLE_BY_ID = gql`
updated_at updated_at
trim_color trim_color
notes notes
jobs { jobs(order_by: { date_open: desc }) {
id id
ro_number ro_number
ownr_fn ownr_fn
ownr_ln ownr_ln
owner { owner {
id id

View File

@@ -1,21 +1,24 @@
import { BackTop, Layout } from "antd"; import { BackTop, Layout } from "antd";
import React, { lazy, Suspense, useEffect } from "react"; import React, { Suspense, lazy, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Redirect, Route, Switch } from "react-router-dom"; import { Redirect, Route, Switch } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import TechHeader from "../../components/tech-header/tech-header.component"; import TechHeader from "../../components/tech-header/tech-header.component";
import TechSider from "../../components/tech-sider/tech-sider.component"; import TechSider from "../../components/tech-sider/tech-sider.component";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
import "./tech.page.styles.scss";
import UpdateAlert from "../../components/update-alert/update-alert.component"; import UpdateAlert from "../../components/update-alert/update-alert.component";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import "./tech.page.styles.scss";
const TimeTicketModalContainer = lazy(() => const TimeTicketModalContainer = lazy(() =>
import("../../components/time-ticket-modal/time-ticket-modal.container") import("../../components/time-ticket-modal/time-ticket-modal.container")
); );
const EmailOverlayContainer = lazy(() =>
import("../../components/email-overlay/email-overlay.container.jsx")
);
const PrintCenterModalContainer = lazy(() => const PrintCenterModalContainer = lazy(() =>
import("../../components/print-center-modal/print-center-modal.container") import("../../components/print-center-modal/print-center-modal.container")
); );
@@ -69,6 +72,7 @@ export function TechPage({ technician, match }) {
> >
<FeatureWrapper featureName="tech-console"> <FeatureWrapper featureName="tech-console">
<TimeTicketModalContainer /> <TimeTicketModalContainer />
<EmailOverlayContainer />
<PrintCenterModalContainer /> <PrintCenterModalContainer />
<Switch> <Switch>
<Route <Route

View File

@@ -1093,6 +1093,7 @@
"passwordsdonotmatch": "The passwords you have entered do not match.", "passwordsdonotmatch": "The passwords you have entered do not match.",
"print": "Print", "print": "Print",
"refresh": "Refresh", "refresh": "Refresh",
"reports": "Reports",
"required": "Required", "required": "Required",
"saturday": "Saturday", "saturday": "Saturday",
"search": "Search...", "search": "Search...",
@@ -2735,6 +2736,7 @@
"clockon": "Clocked In", "clockon": "Clocked In",
"committed": "", "committed": "",
"cost_center": "Cost Center", "cost_center": "Cost Center",
"created_by": "Created By",
"date": "Ticket Date", "date": "Ticket Date",
"efficiency": "Efficiency", "efficiency": "Efficiency",
"employee": "Employee", "employee": "Employee",

View File

@@ -1093,6 +1093,7 @@
"passwordsdonotmatch": "", "passwordsdonotmatch": "",
"print": "", "print": "",
"refresh": "", "refresh": "",
"reports": "",
"required": "", "required": "",
"saturday": "", "saturday": "",
"search": "Buscar...", "search": "Buscar...",
@@ -2735,6 +2736,7 @@
"clockon": "", "clockon": "",
"committed": "", "committed": "",
"cost_center": "", "cost_center": "",
"created_by": "",
"date": "", "date": "",
"efficiency": "", "efficiency": "",
"employee": "", "employee": "",

View File

@@ -1093,6 +1093,7 @@
"passwordsdonotmatch": "", "passwordsdonotmatch": "",
"print": "", "print": "",
"refresh": "", "refresh": "",
"reports": "",
"required": "", "required": "",
"saturday": "", "saturday": "",
"search": "Chercher...", "search": "Chercher...",
@@ -2735,6 +2736,7 @@
"clockon": "", "clockon": "",
"committed": "", "committed": "",
"cost_center": "", "cost_center": "",
"created_by": "",
"date": "", "date": "",
"efficiency": "", "efficiency": "",
"employee": "", "employee": "",

View File

@@ -1040,7 +1040,7 @@ export const TemplateList = (type, context) => {
disabled: false, disabled: false,
rangeFilter: { rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"), object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"), field: i18n.t("jobs.fields.date_invoiced"),
}, },
group: "jobs", group: "jobs",
}, },
@@ -1135,6 +1135,10 @@ export const TemplateList = (type, context) => {
key: "timetickets_employee", key: "timetickets_employee",
idtype: "employee", idtype: "employee",
disabled: false, disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.timetickets"),
field: i18n.t("timetickets.fields.date"),
},
group: "payroll", group: "payroll",
}, },
attendance_detail: { attendance_detail: {

View File

@@ -4649,6 +4649,7 @@
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
- active: - active:
_eq: true _eq: true
allow_aggregations: true
update_permissions: update_permissions:
- role: user - role: user
permission: permission:
@@ -4726,6 +4727,7 @@
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
- active: - active:
_eq: true _eq: true
allow_aggregations: true
update_permissions: update_permissions:
- role: user - role: user
permission: permission:
@@ -5554,6 +5556,7 @@
- committed_at - committed_at
- cost_center - cost_center
- created_at - created_at
- created_by
- date - date
- employeeid - employeeid
- flat_rate - flat_rate
@@ -5578,6 +5581,7 @@
- committed_at - committed_at
- cost_center - cost_center
- created_at - created_at
- created_by
- date - date
- employeeid - employeeid
- flat_rate - flat_rate
@@ -5611,6 +5615,7 @@
- committed_at - committed_at
- cost_center - cost_center
- created_at - created_at
- created_by
- date - date
- employeeid - employeeid
- flat_rate - flat_rate

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."parts_dispatch_employeeid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "parts_dispatch_employeeid" on
"public"."parts_dispatch" using btree ("employeeid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."parts_dispatch_dispatchid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "parts_dispatch_dispatchid" on
"public"."parts_dispatch_lines" using btree ("partsdispatchid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "public"."parts_dispatch_line_accepted_at";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "parts_dispatch_line_accepted_at" on
"public"."parts_dispatch_lines" using btree ("accepted_at");

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."timetickets" add column "created_by" text
-- null;

View File

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

View File

@@ -42,13 +42,13 @@ const io = new Server(server, {
}); });
exports.io = io; exports.io = io;
require("./server/web-sockets/web-socket"); require("./server/web-sockets/web-socket");
// app.set('trust proxy', true)
//app.use(fb.validateFirebaseIdToken); // app.use(fb.validateFirebaseIdToken);
app.use(compression()); app.use(compression());
app.use(cookieParser()); app.use(cookieParser());
app.use(bodyParser.json({ limit: "50mb" })); app.use(bodyParser.json({ limit: "50mb" }));
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
//app.use(enforce.HTTPS({ trustProtoHeader: true })); // app.use(enforce.HTTPS({ trustProtoHeader: true }));
app.use( app.use(
cors({ credentials: true, exposedHeaders: ["set-cookie"] }) cors({ credentials: true, exposedHeaders: ["set-cookie"] })
// cors({ // cors({
@@ -71,10 +71,17 @@ app.get("/test", async function (req, res) {
const commit = require("child_process").execSync( const commit = require("child_process").execSync(
"git rev-parse --short HEAD" "git rev-parse --short HEAD"
); );
// console.log(app.get('trust proxy'));
// console.log("remoteAddress", req.socket.remoteAddress);
// console.log("X-Forwarded-For", req.header('x-forwarded-for'));
logger.log("test-api-status", "DEBUG", "api", { commit }); logger.log("test-api-status", "DEBUG", "api", { commit });
// sendEmail.sendServerEmail({
// subject: `API Check - ${process.env.NODE_ENV}`,
// text: `Server API check has come in. Remote IP: ${req.socket.remoteAddress}, X-Forwarded-For: ${req.header('x-forwarded-for')}`,
// });
sendEmail.sendServerEmail({ sendEmail.sendServerEmail({
subject: `API Check - ${process.env.NODE_ENV}`, subject: `API Check - ${process.env.NODE_ENV}`,
text: `Server API check has come in. `, text: `Server API check has come in.`,
}); });
res.status(200).send(`OK - ${commit}`); res.status(200).send(`OK - ${commit}`);
}); });

View File

@@ -643,6 +643,7 @@ async function InsertAccountPostingData(socket) {
wips.push(item); wips.push(item);
}); });
socket.transWips = wips;
const { data: AccountPostingChange } = await axios.post( const { data: AccountPostingChange } = await axios.post(
PBS_ENDPOINTS.AccountingPostingChange, PBS_ENDPOINTS.AccountingPostingChange,
@@ -697,6 +698,7 @@ async function MarkJobExported(socket, jobid) {
jobid: jobid, jobid: jobid,
successful: true, successful: true,
useremail: socket.user.email, useremail: socket.user.email,
metadata: socket.transWips,
}, },
bill: { bill: {
exported: true, exported: true,

View File

@@ -653,6 +653,7 @@ exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employee
employee_number employee_number
id id
pin pin
active
} }
}`; }`;

View File

@@ -23,7 +23,7 @@ exports.techLogin = async (req, res) => {
let technician; let technician;
if (result.employees && result.employees[0]) { if (result.employees && result.employees[0]) {
const dbRecord = result.employees[0]; const dbRecord = result.employees[0];
if (dbRecord.pin === pin) { if (dbRecord.pin === pin && dbRecord.active === true) {
valid = true; valid = true;
delete dbRecord.pin; delete dbRecord.pin;
technician = dbRecord; technician = dbRecord;