Compare commits

...

8 Commits

Author SHA1 Message Date
Allan Carr
4e69fe819e IO-2325 Add in Created by from Action menu 2023-08-18 09:03:13 -07:00
Allan Carr
bf7aa17f65 IO-2325 Shift Clock Created By 2023-08-11 08:35:57 -07:00
Allan Carr
ca129fa4a0 IO-2325 Time Ticket Creation Date 2023-08-10 12:06:47 -07:00
Allan Carr
e69e844568 Merged in release/2023-08-04 (pull request #920)
Release/2023 08 04
2023-08-04 18:06:05 +00:00
Allan Carr
2b5268fb77 Merged in feature/IO-2371-Closing-Period (pull request #918)
IO-2371 Closing Period
2023-08-04 17:35:45 +00:00
Allan Carr
eebe7edba8 IO-2371 Closing period timezone adjustments 2023-08-04 10:36:01 -07:00
Allan Carr
1a5c74dc79 IO-2371 Closing Period 2023-08-04 09:05:03 -07:00
Allan Carr
be62ab5ff9 Merged in feature/IO-2370-Work-In-Progress-Jobs (pull request #916)
IO-2370 Work In Progress Jobs
2023-08-02 23:37:51 +00:00
18 changed files with 248 additions and 37 deletions

View File

@@ -1,6 +1,6 @@
import Icon, { UploadOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import { MdOpenInNew } from "react-icons/md";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Alert,
Divider,
@@ -12,14 +12,17 @@ import {
Switch,
Upload,
} from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { MdOpenInNew } from "react-icons/md";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
@@ -28,8 +31,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import BillFormLines from "./bill-form.lines.component";
import { CalculateBillTotal } from "./bill-form.totals.utility";
import { useTreatments } from "@splitsoftware/splitio-react";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -58,6 +59,11 @@ export function BillFormComponent({
{},
bodyshop.imexshopid
);
const { ClosingPeriod } = useTreatments(
["ClosingPeriod"],
{},
bodyshop.imexshopid
);
const handleVendorSelect = (props, opt) => {
setDiscount(opt.discount);
@@ -259,6 +265,37 @@ export function BillFormComponent({
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
validator(rule, value) {
if (
ClosingPeriod.treatment === "on" &&
bodyshop.accountingconfig.ClosingPeriod
) {
if (
moment(value)
.startOf("day")
.isSameOrAfter(
moment(
bodyshop.accountingconfig.ClosingPeriod[0]
).startOf("day")
) &&
moment(value)
.startOf("day")
.isSameOrBefore(
moment(
bodyshop.accountingconfig.ClosingPeriod[1]
).endOf("day")
)
) {
return Promise.resolve();
} else {
return Promise.reject(t("bills.validation.closingperiod"));
}
} else {
return Promise.resolve();
}
},
}),
]}
>
<FormDatePicker disabled={disabled} />

View File

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

View File

@@ -239,7 +239,12 @@ export function JobsDetailHeaderActions({
setTimeTicketContext({
actions: {},
context: { jobId: job.id },
context: {
jobId: job.id,
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
},
});
}}
>

View File

@@ -1,14 +1,14 @@
import React, { useEffect, useState } from "react";
import ShopInfoComponent from "./shop-info.component";
import { useMutation, useQuery } from "@apollo/client";
import { Form, notification } from "antd";
import { useQuery, useMutation } from "@apollo/client";
import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries";
import AlertComponent from "../alert/alert.component";
import FormsFieldChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import moment from "moment";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import ShopInfoComponent from "./shop-info.component";
export default function ShopInfoContainer() {
const [form] = Form.useForm();
const { t } = useTranslation();
@@ -52,13 +52,28 @@ export default function ShopInfoContainer() {
onFinish={handleFinish}
initialValues={
data
? {
...data.bodyshops[0],
schedule_start_time: moment(
data.bodyshops[0].schedule_start_time
),
schedule_end_time: moment(data.bodyshops[0].schedule_end_time),
}
? data.bodyshops[0].accountingconfig.ClosingPeriod
? {
...data.bodyshops[0],
accountingconfig: {
...data.bodyshops[0].accountingconfig,
ClosingPeriod: [
moment(data.bodyshops[0].accountingconfig.ClosingPeriod[0]),
moment(data.bodyshops[0].accountingconfig.ClosingPeriod[1]),
],
},
schedule_start_time: moment(
data.bodyshops[0].schedule_start_time
),
schedule_end_time: moment(data.bodyshops[0].schedule_end_time),
}
: {
...data.bodyshops[0],
schedule_start_time: moment(
data.bodyshops[0].schedule_start_time
),
schedule_end_time: moment(data.bodyshops[0].schedule_end_time),
}
: null
}
>

View File

@@ -1,6 +1,8 @@
import { DeleteFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Button,
DatePicker,
Form,
Input,
InputNumber,
@@ -9,8 +11,13 @@ import {
Space,
Switch,
} from "antd";
import momentTZ from "moment-timezone";
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";
import DatePickerRanges from "../../utils/DatePickerRanges";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import PhoneFormItem, {
@@ -18,12 +25,22 @@ import PhoneFormItem, {
} from "../form-items-formatted/phone-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import momentTZ from "moment-timezone";
const timeZonesList = momentTZ.tz.names();
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral);
export default function ShopInfoGeneral({ form }) {
export function ShopInfoGeneral({ form, bodyshop }) {
const { t } = useTranslation();
const { ClosingPeriod } = useTreatments(
["ClosingPeriod"],
{},
bodyshop && bodyshop.imexshopid
);
return (
<div>
@@ -392,6 +409,20 @@ export default function ShopInfoGeneral({ form }) {
>
<Select mode="tags" />
</Form.Item>
{ClosingPeriod.treatment === "on" && (
<>
<Form.Item
allowClear
name={["accountingconfig", "ClosingPeriod"]}
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
>
<DatePicker.RangePicker
format="MM/DD/YYYY"
ranges={DatePickerRanges}
/>
</Form.Item>
</>
)}
</LayoutFormRow>
<LayoutFormRow
header={t("bodyshop.labels.scoreboardsetup")}
@@ -604,7 +635,9 @@ export default function ShopInfoGeneral({ form }) {
</Form.Item>
<Form.Item
name={["md_email_cc", "parts_return_slip"]}
label={t("bodyshop.fields.md_email_cc", { template: "parts_return_slip" })}
label={t("bodyshop.fields.md_email_cc", {
template: "parts_return_slip",
})}
rules={[
{
//message: t("general.validation.required"),

View File

@@ -1,21 +1,25 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Form, notification, Space } from "antd";
import axios from "axios";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
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 { 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({
technician: selectTechnician,
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
setTimeTicketContext: (context) =>
@@ -25,6 +29,7 @@ export function TechClockInContainer({
setTimeTicketContext,
technician,
bodyshop,
currentUser,
}) {
console.log(
"🚀 ~ file: tech-job-clock-in-form.container.jsx:29 ~ technician:",
@@ -66,6 +71,12 @@ export function TechClockInContainer({
values.cost_center
);
}),
created_by: currentUser.email.concat(
" | ",
technician.employee_number
.concat(" ", technician.first_name, " ", technician.last_name)
.trim()
),
},
],
},
@@ -100,6 +111,17 @@ export function TechClockInContainer({
employeeid: technician.id,
flat_rate: emps.flat_rate,
},
created_by: currentUser.email.concat(
" | ",
technician.employee_number
.concat(
" ",
technician.first_name,
" ",
technician.last_name
)
.trim()
),
},
});
}}

View File

@@ -9,9 +9,10 @@ import { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort, dateSort } from "../../utils/sorters";
import RbacWrapper, {
HasRbacAccess,
@@ -20,6 +21,7 @@ import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -29,6 +31,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketList);
export function TimeTicketList({
bodyshop,
authLevel,
currentUser,
disabled,
loading,
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"),
dataIndex: "actions",
@@ -254,7 +265,12 @@ export function TimeTicketList({
(techConsole ? null : (
<TimeTicketEnterButton
actions={{ refetch }}
context={{ jobId: jobId }}
context={{
jobId: jobId,
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
}}
disabled={disabled}
>
{t("timetickets.actions.enter")}

View File

@@ -1,5 +1,5 @@
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 React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -77,6 +77,7 @@ export function TimeTicketModalContainer({
)[0].rate
: null,
bodyshopid: bodyshop.id,
created_by: timeTicketModal.context.created_by,
},
],
},

View File

@@ -65,6 +65,21 @@ export function TimeTicektShiftContainer({
clockon: theTime,
date: theTime,
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,
},
],
},

View File

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

View File

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

View File

@@ -8,7 +8,6 @@ import {
Form,
Input,
InputNumber,
notification,
PageHeader,
Popconfirm,
Row,
@@ -17,12 +16,14 @@ import {
Statistic,
Switch,
Typography,
notification,
} from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
//import { useHistory } from "react-router-dom";
import { useTreatments } from "@splitsoftware/splitio-react";
import Dinero from "dinero.js";
import moment from "moment";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
@@ -37,7 +38,6 @@ import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.qu
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import Dinero from "dinero.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
@@ -55,6 +55,11 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
{},
bodyshop && bodyshop.imexshopid
);
const { ClosingPeriod } = useTreatments(
["ClosingPeriod"],
{},
bodyshop && bodyshop.imexshopid
);
const handleFinish = async ({ removefromproduction, ...values }) => {
setLoading(true);
@@ -254,12 +259,40 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
if (!value || moment(value).isSameOrAfter(moment(), "day")) {
return Promise.resolve();
}
return Promise.reject(
new Error(t("jobs.labels.dms.invoicedatefuture"))
);
},
}),
({ getFieldValue }) => ({
validator(_, value) {
if (
ClosingPeriod.treatment === "on" &&
bodyshop.accountingconfig.ClosingPeriod
) {
if (
moment(value).isSameOrAfter(
moment(
bodyshop.accountingconfig.ClosingPeriod[0]
).startOf("day")
) &&
moment(value).isSameOrBefore(
moment(
bodyshop.accountingconfig.ClosingPeriod[1]
).endOf("day")
)
) {
return Promise.resolve();
} else {
return Promise.reject(
new Error(t("jobs.labels.closingperiod"))
);
}
} else {
return Promise.resolve();
}
},
}),
]}
>
<DateTimePicker

View File

@@ -223,6 +223,7 @@
"reexport": "Bill marked for re-export."
},
"validation": {
"closingperiod": "This Bill Date is outside of the Closing Period.",
"inventoryquantity": "Quantity must be greater than or equal to what has been added to inventory ({{number}}).",
"manualinhouse": "Manual posting to the in house vendor is restricted. ",
"unique_invoice_number": "This invoice number has already been entered for this vendor."
@@ -262,6 +263,7 @@
"bill_local_tax_rate": "Bill - Provincial/State Tax Rate %",
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %",
"city": "City",
"closingperiod": "Closing Period",
"country": "Country",
"dailybodytarget": "Scoreboard - Daily Body Target",
"dailypainttarget": "Scoreboard - Daily Paint Target",
@@ -1688,6 +1690,7 @@
"checklists": "Checklists",
"closeconfirm": "Are you sure you want to close this job? This cannot be easily undone.",
"closejob": "Close Job {{ro_number}}",
"closingperiod": "This Invoice Date is outside of the Closing Period.",
"contracts": "CC Contracts",
"convertedtolabor": "Lines Converted to Labor",
"cost": "Cost",
@@ -2727,6 +2730,7 @@
"clockon": "Clocked In",
"committed": "",
"cost_center": "Cost Center",
"created_by": "Created By",
"date": "Ticket Date",
"efficiency": "Efficiency",
"employee": "Employee",

View File

@@ -223,6 +223,7 @@
"reexport": ""
},
"validation": {
"closingperiod": "",
"inventoryquantity": "",
"manualinhouse": "",
"unique_invoice_number": ""
@@ -262,6 +263,7 @@
"bill_local_tax_rate": "",
"bill_state_tax_rate": "",
"city": "",
"closingperiod": "",
"country": "",
"dailybodytarget": "",
"dailypainttarget": "",
@@ -1688,6 +1690,7 @@
"checklists": "",
"closeconfirm": "",
"closejob": "",
"closingperiod": "",
"contracts": "",
"convertedtolabor": "",
"cost": "",
@@ -2727,6 +2730,7 @@
"clockon": "",
"committed": "",
"cost_center": "",
"created_by": "",
"date": "",
"efficiency": "",
"employee": "",

View File

@@ -223,6 +223,7 @@
"reexport": ""
},
"validation": {
"closingperiod": "",
"inventoryquantity": "",
"manualinhouse": "",
"unique_invoice_number": ""
@@ -262,6 +263,7 @@
"bill_local_tax_rate": "",
"bill_state_tax_rate": "",
"city": "",
"closingperiod": "",
"country": "",
"dailybodytarget": "",
"dailypainttarget": "",
@@ -1688,6 +1690,7 @@
"checklists": "",
"closeconfirm": "",
"closejob": "",
"closingperiod": "",
"contracts": "",
"convertedtolabor": "",
"cost": "",
@@ -2727,6 +2730,7 @@
"clockon": "",
"committed": "",
"cost_center": "",
"created_by": "",
"date": "",
"efficiency": "",
"employee": "",

View File

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

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;