Added shift clock framework + login on tech console BOD-97 BOD-188

This commit is contained in:
Patrick Fic
2020-07-16 10:17:23 -07:00
parent bf74d9a042
commit 6a8b59c7e6
67 changed files with 1791 additions and 217 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.6.1"> <babeledit_project be_version="2.6.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -17556,6 +17556,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>memo</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>productivehrs</name> <name>productivehrs</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -17624,6 +17645,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>ambreak</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>amshift</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>clockintojob</name> <name>clockintojob</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -17708,6 +17771,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>lunch</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>new</name> <name>new</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -17729,6 +17813,90 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>pmbreak</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>pmshift</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>shift</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>shiftalreadyclockedon</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>straight_time</name> <name>straight_time</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -55,7 +55,8 @@
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
width: .25rem; width: 0.25rem;
max-height: 0.25rem;
background-color: #f5f5f5; background-color: #f5f5f5;
} }

View File

@@ -155,6 +155,9 @@ export function ShopEmployeesFormComponent({
]}> ]}>
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item label={t("employees.fields.user_email")} name='user_email'>
<Input />
</Form.Item>
</Form> </Form>
); );
} }

View File

@@ -8,14 +8,14 @@ import {
DELETE_EMPLOYEE, DELETE_EMPLOYEE,
INSERT_EMPLOYEES, INSERT_EMPLOYEES,
QUERY_EMPLOYEES, QUERY_EMPLOYEES,
UPDATE_EMPLOYEE UPDATE_EMPLOYEE,
} from "../../graphql/employees.queries"; } from "../../graphql/employees.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ShopEmployeeComponent from "./shop-employees.component"; import ShopEmployeeComponent from "./shop-employees.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop,
}); });
function ShopEmployeesContainer({ bodyshop }) { function ShopEmployeesContainer({ bodyshop }) {
@@ -23,69 +23,75 @@ function ShopEmployeesContainer({ bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const employeeState = useState(null); const employeeState = useState(null);
const { loading, error, data, refetch } = useQuery(QUERY_EMPLOYEES, { const { loading, error, data, refetch } = useQuery(QUERY_EMPLOYEES, {
fetchPolicy: "network-only" fetchPolicy: "network-only",
}); });
const [updateEmployee] = useMutation(UPDATE_EMPLOYEE); const [updateEmployee] = useMutation(UPDATE_EMPLOYEE);
const [insertEmployees] = useMutation(INSERT_EMPLOYEES); const [insertEmployees] = useMutation(INSERT_EMPLOYEES);
const [deleteEmployee] = useMutation(DELETE_EMPLOYEE); const [deleteEmployee] = useMutation(DELETE_EMPLOYEE);
const handleDelete = id => { const handleDelete = (id) => {
deleteEmployee({ variables: { id: id } }) deleteEmployee({ variables: { id: id } })
.then(r => { .then((r) => {
notification["success"]({ notification["success"]({
message: t("employees.successes.delete") message: t("employees.successes.delete"),
}); });
//TODO Better way to reset the field decorators? //TODO Better way to reset the field decorators?
employeeState[1](null); employeeState[1](null);
refetch().then(r => form.resetFields()); refetch().then((r) => form.resetFields());
}) })
.catch(error => { .catch((error) => {
notification["error"]({ notification["error"]({
message: t("employees.errors.delete") message: t("employees.errors.delete", {
message: JSON.stringify(error),
}),
}); });
}); });
}; };
const handleFinish = values => { const handleFinish = (values) => {
console.log("values", values); console.log("values", values);
if (employeeState[0].id) { if (employeeState[0].id) {
//Update a record. //Update a record.
updateEmployee({ updateEmployee({
variables: { id: employeeState[0].id, employee: values } variables: { id: employeeState[0].id, employee: values },
}) })
.then(r => { .then((r) => {
notification["success"]({ notification["success"]({
message: t("employees.successes.save") message: t("employees.successes.save"),
}); });
//TODO Better way to reset the field decorators? //TODO Better way to reset the field decorators?
employeeState[1](null); employeeState[1](null);
refetch().then(r => form.resetFields()); refetch().then((r) => form.resetFields());
}) })
.catch(error => { .catch((error) => {
notification["error"]({ notification["error"]({
message: t("employees.errors.save") message: t("employees.errors.save", {
message: JSON.stringify(error),
}),
}); });
}); });
} else { } else {
//New record, insert it. //New record, insert it.
insertEmployees({ insertEmployees({
variables: { employees: [{ ...values, shopid: bodyshop.id }] } variables: { employees: [{ ...values, shopid: bodyshop.id }] },
}).then(r => { }).then((r) => {
notification["success"]({ notification["success"]({
message: t("employees.successes.save") message: t("employees.successes.save"),
}); });
//TODO Better way to reset the field decorators? //TODO Better way to reset the field decorators?
employeeState[1](null); employeeState[1](null);
refetch().catch(error => { refetch().catch((error) => {
notification["error"]({ notification["error"]({
message: t("employees.errors.save") message: t("employees.errors.save", {
message: JSON.stringify(error),
}),
}); });
}); });
}); });
} }
}; };
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type='error' />;
return ( return (
<ShopEmployeeComponent <ShopEmployeeComponent

View File

@@ -1,5 +1,6 @@
import { Button, Form, notification, Card } from "antd"; import { Button, Card, Form, notification } from "antd";
import React from "react"; import axios from "axios";
import React, { useState } from "react";
import { useMutation } from "react-apollo"; import { useMutation } from "react-apollo";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -16,29 +17,35 @@ const mapStateToProps = createStructuredSelector({
export function TechClockInContainer({ technician, bodyshop }) { export function TechClockInContainer({ technician, bodyshop }) {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET, { const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET, {
refetchQueries: ["QUERY_ACTIVE_TIME_TICKETS"], refetchQueries: ["QUERY_ACTIVE_TIME_TICKETS"],
}); });
const { t } = useTranslation(); const { t } = useTranslation();
const handleFinish = async (values) => { const handleFinish = async (values) => {
setLoading(true);
const theTime = (await axios.post("/utils/time")).data;
const result = await insertTimeTicket({ const result = await insertTimeTicket({
variables: { variables: {
timeTicketInput: { timeTicketInput: [
employeeid: technician.id, {
date: new Date(), bodyshopid: bodyshop.id,
clockon: new Date(), employeeid: technician.id,
jobid: values.jobid, date: theTime,
cost_center: technician.cost_center, clockon: theTime,
ciecacode: Object.keys( jobid: values.jobid,
bodyshop.md_responsibility_centers.defaults.costs cost_center: technician.cost_center,
).find((key) => { ciecacode: Object.keys(
return ( bodyshop.md_responsibility_centers.defaults.costs
bodyshop.md_responsibility_centers.defaults.costs[key] === ).find((key) => {
values.cost_center return (
); bodyshop.md_responsibility_centers.defaults.costs[key] ===
}), values.cost_center
}, );
}),
},
],
}, },
}); });
@@ -53,13 +60,14 @@ export function TechClockInContainer({ technician, bodyshop }) {
message: t("timetickets.successes.clockedin"), message: t("timetickets.successes.clockedin"),
}); });
} }
setLoading(false);
}; };
return ( return (
<div> <div>
<Card title={t("timetickets.labels.clockintojob")}> <Card title={t("timetickets.labels.clockintojob")}>
<Form form={form} layout="vertical" onFinish={handleFinish}> <Form form={form} layout='vertical' onFinish={handleFinish}>
<Button type="primary" htmlType="submit"> <Button type='primary' htmlType='submit' loading={loading}>
{t("timetickets.actions.clockin")} {t("timetickets.actions.clockin")}
</Button> </Button>
<TechClockInComponent form={form} /> <TechClockInComponent form={form} />

View File

@@ -11,7 +11,7 @@ import {
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries"; import { UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries";
import axios from "axios";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
@@ -27,6 +27,7 @@ export function TechClockOffButton({
technician, technician,
timeTicketId, timeTicketId,
completedCallback, completedCallback,
isShiftTicket,
otherBtnProps, otherBtnProps,
}) { }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -40,7 +41,10 @@ export function TechClockOffButton({
const result = await updateTimeticket({ const result = await updateTimeticket({
variables: { variables: {
timeticketId: timeTicketId, timeticketId: timeTicketId,
timeticket: { clockoff: new Date(), ...values }, timeticket: {
clockoff: (await axios.post("/utils/time")).data,
...values,
},
}, },
}); });
@@ -64,65 +68,76 @@ export function TechClockOffButton({
<div> <div>
<Form <Form
form={form} form={form}
layout="vertical" layout='vertical'
onFinish={handleFinish} onFinish={handleFinish}
initialValues={{ initialValues={{
cost_center: technician ? technician.cost_center : null, cost_center: isShiftTicket
}} ? "timetickets.labels.shift"
> : technician
? technician.cost_center
: null,
}}>
{!isShiftTicket ? (
<div>
<Form.Item
label={t("timetickets.fields.actualhrs")}
name='actualhrs'
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}>
<InputNumber precision={1} />
</Form.Item>
<Form.Item
label={t("timetickets.fields.productivehrs")}
name='productivehrs'
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}>
<InputNumber precision={1} />
</Form.Item>
</div>
) : null}
<Form.Item <Form.Item
label={t("timetickets.fields.actualhrs")} name='cost_center'
name="actualhrs"
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<InputNumber precision={1} />
</Form.Item>
<Form.Item
label={t("timetickets.fields.productivehrs")}
name="productivehrs"
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<InputNumber precision={1} />
</Form.Item>
<Form.Item
name="cost_center"
label={t("timetickets.fields.cost_center")} label={t("timetickets.fields.cost_center")}
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]} ]}>
> <Select disabled={isShiftTicket}>
<Select> {isShiftTicket ? (
{bodyshop.md_responsibility_centers.costs.map((i, idx) => ( <Select.Option value='timetickets.labels.shift'>
<Select.Option key={idx} value={i.name}> {t("timetickets.labels.shift")}
{i.name}
</Select.Option> </Select.Option>
))} ) : (
bodyshop.md_responsibility_centers.costs.map((i, idx) => (
<Select.Option key={idx} value={i.name}>
{i.name}
</Select.Option>
))
)}
</Select> </Select>
</Form.Item> </Form.Item>
<Button type="primary" htmlType="submit"> <Button type='primary' htmlType='submit' loading={loading}>
{t("general.actions.save")} {t("general.actions.save")}
</Button> </Button>
<TechJobClockoutDelete timeTicketId={timeTicketId} /> <TechJobClockoutDelete completedCallback={completedCallback} timeTicketId={timeTicketId} />
</Form> </Form>
</div> </div>
</Card> </Card>
); );
return ( return (
<Popover content={overlay} trigger="click"> <Popover content={overlay} trigger='click'>
<Button loading={loading} {...otherBtnProps}> <Button loading={loading} {...otherBtnProps}>
{t("timetickets.actions.clockout")} {t("timetickets.actions.clockout")}
</Button> </Button>

View File

@@ -5,7 +5,10 @@ import { DELETE_TIME_TICKET } from "../../graphql/timetickets.queries";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useMutation } from "@apollo/react-hooks"; import { useMutation } from "@apollo/react-hooks";
export default function TechJobClockoutDelete({ timeTicketId }) { export default function TechJobClockoutDelete({
timeTicketId,
completedCallback,
}) {
const [deleteTimeTicket] = useMutation(DELETE_TIME_TICKET); const [deleteTimeTicket] = useMutation(DELETE_TIME_TICKET);
const { t } = useTranslation(); const { t } = useTranslation();
const handleDelete = async () => { const handleDelete = async () => {
@@ -25,6 +28,7 @@ export default function TechJobClockoutDelete({ timeTicketId }) {
message: t("timetickets.successes.deleted"), message: t("timetickets.successes.deleted"),
}); });
} }
if (completedCallback) completedCallback();
}; };
return ( return (
@@ -32,8 +36,7 @@ export default function TechJobClockoutDelete({ timeTicketId }) {
title={t("timetickets.labels.deleteconfirm")} title={t("timetickets.labels.deleteconfirm")}
okButtonProps={{ type: "danger" }} okButtonProps={{ type: "danger" }}
okText={t("general.actions.delete")} okText={t("general.actions.delete")}
onConfirm={handleDelete} onConfirm={handleDelete}>
>
<DeleteFilled style={{ margin: "1rem", color: "red" }} /> <DeleteFilled style={{ margin: "1rem", color: "red" }} />
</Popconfirm> </Popconfirm>
); );

View File

@@ -2,17 +2,17 @@ import { Card, List, Typography } from "antd";
import React from "react"; import React from "react";
import { useQuery } from "react-apollo"; import { useQuery } from "react-apollo";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { QUERY_ACTIVE_TIME_TICKETS } from "../../graphql/timetickets.queries"; import { QUERY_ACTIVE_TIME_TICKETS } from "../../graphql/timetickets.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.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 { selectTechnician } from "../../redux/tech/tech.selectors";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
technician: selectTechnician, technician: selectTechnician,
}); });

View File

@@ -2,6 +2,7 @@ import Icon, { SearchOutlined } from "@ant-design/icons";
import { Layout, Menu } from "antd"; import { Layout, Menu } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FaBusinessTime } from "react-icons/fa";
import { FiLogIn, FiLogOut } from "react-icons/fi"; import { FiLogIn, FiLogOut } from "react-icons/fi";
import { MdTimer } from "react-icons/md"; import { MdTimer } from "react-icons/md";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -9,6 +10,7 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { techLogout } from "../../redux/tech/tech.actions"; import { techLogout } from "../../redux/tech/tech.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectTechnician } from "../../redux/tech/tech.selectors";
const { Sider } = Layout; const { Sider } = Layout;
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -27,36 +29,39 @@ export function TechSider({ technician, techLogout }) {
return ( return (
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse}> <Sider collapsible collapsed={collapsed} onCollapse={onCollapse}>
<Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline"> <Menu theme='dark' defaultSelectedKeys={["1"]} mode='inline'>
<Menu.Item <Menu.Item
key="1" key='1'
disabled={!!technician} disabled={!!technician}
icon={<Icon component={FiLogIn} />} icon={<Icon component={FiLogIn} />}>
>
<Link to={`/tech/login`}>{t("menus.tech.login")}</Link> <Link to={`/tech/login`}>{t("menus.tech.login")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="2" disabled={!!!technician} icon={<SearchOutlined />}> <Menu.Item key='2' disabled={!!!technician} icon={<SearchOutlined />}>
<Link to={`/tech/joblookup`}>{t("menus.tech.joblookup")}</Link> <Link to={`/tech/joblookup`}>{t("menus.tech.joblookup")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
key="3" key='3'
disabled={!!!technician} disabled={!!!technician}
icon={<Icon component={MdTimer} />} icon={<Icon component={FaBusinessTime} />}>
> <Link to={`/tech/jobclock`}>{t("menus.tech.jobclockin")}</Link>
<Link to={`/tech/jobclockin`}>{t("menus.tech.jobclockin")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="5" disabled={!!!technician} icon={<SearchOutlined />}> <Menu.Item
key='4'
disabled={!!!technician}
icon={<Icon component={MdTimer} />}>
<Link to={`/tech/shiftclock`}>{t("menus.tech.shiftclockin")}</Link>
</Menu.Item>
<Menu.Item key='5' disabled={!!!technician} icon={<SearchOutlined />}>
<Link to={`/tech/list`}>{t("menus.tech.productionlist")}</Link> <Link to={`/tech/list`}>{t("menus.tech.productionlist")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="6" disabled={!!!technician} icon={<SearchOutlined />}> <Menu.Item key='6' disabled={!!!technician} icon={<SearchOutlined />}>
<Link to={`/tech/board`}> {t("menus.tech.productionboard")}</Link> <Link to={`/tech/board`}> {t("menus.tech.productionboard")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
key="7" key='7'
disabled={!!!technician} disabled={!!!technician}
onClick={() => techLogout()} onClick={() => techLogout()}
icon={<Icon component={FiLogOut} />} icon={<Icon component={FiLogOut} />}>
>
{t("menus.tech.logout")} {t("menus.tech.logout")}
</Menu.Item> </Menu.Item>
</Menu> </Menu>

View File

@@ -19,15 +19,14 @@ export default function TimeTicketModalComponent({
<div> <div>
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<Form.Item <Form.Item
name="jobid" name='jobid'
label={t("timetickets.fields.ro_number")} label={t("timetickets.fields.ro_number")}
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]} ]}>
>
<JobSearchSelect <JobSearchSelect
options={roAutoCompleteOptions} options={roAutoCompleteOptions}
onBlur={() => { onBlur={() => {
@@ -40,15 +39,14 @@ export default function TimeTicketModalComponent({
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="employeeid" name='employeeid'
label={t("timetickets.fields.employee")} label={t("timetickets.fields.employee")}
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]} ]}>
>
<EmployeeSearchSelect options={employeeAutoCompleteOptions} /> <EmployeeSearchSelect options={employeeAutoCompleteOptions} />
</Form.Item> </Form.Item>
</div> </div>
@@ -56,71 +54,65 @@ export default function TimeTicketModalComponent({
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<Form.Item <Form.Item
label={t("timetickets.fields.date")} label={t("timetickets.fields.date")}
name="date" name='date'
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]} ]}>
>
<DatePicker /> <DatePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("timetickets.fields.productivehrs")} label={t("timetickets.fields.productivehrs")}
name="productivehrs" name='productivehrs'
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]} ]}>
> <InputNumber min={0} precision={1} />
<InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("timetickets.fields.actualhrs")} label={t("timetickets.fields.actualhrs")}
name="actualhrs" name='actualhrs'
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]} ]}>
> <InputNumber min={0} precision={1} />
<InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="cost_center" name='cost_center'
label={t("timetickets.fields.cost_center")} label={t("timetickets.fields.cost_center")}
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]} ]}>
>
<Select <Select
style={{ width: "150px" }} style={{ width: "150px" }}
onChange={() => { onChange={() => {
console.log("Changed."); console.log("Changed.");
}} }}>
>
{responsibilityCenters.costs.map((item) => ( {responsibilityCenters.costs.map((item) => (
<Select.Option key={item.name}>{item.name}</Select.Option> <Select.Option key={item.name}>{item.name}</Select.Option>
))} ))}
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="ciecacode" name='ciecacode'
label={t("timetickets.fields.ciecacode")} label={t("timetickets.fields.ciecacode")}
rules={[ rules={[
{ {
required: true, required: true,
message: t("general.validation.required"), message: t("general.validation.required"),
}, },
]} ]}>
>
<Input disabled /> <Input disabled />
</Form.Item> </Form.Item>
</div> </div>

View File

@@ -59,7 +59,7 @@ export function TimeTicketModalContainer({
} else { } else {
insertTicket({ insertTicket({
variables: { variables: {
timeTicketInput: [values], timeTicketInput: [{ ...values, bodyshop: bodyshop.id }],
}, },
}) })
.then(handleMutationSuccess) .then(handleMutationSuccess)
@@ -108,22 +108,21 @@ export function TimeTicketModalContainer({
); );
form.setFieldsValue({ form.setFieldsValue({
cost_center: emps.length > 0 ? emps[0].cost_center : "", cost_center: emps.length > 0 ? emps[0].cost_center : "",
ciecacode: Object.keys( ciecacode:
bodyshop.md_responsibility_centers.defaults Object.keys(bodyshop.md_responsibility_centers.defaults.costs).find(
).find( (key) =>
(key) => bodyshop.md_responsibility_centers.defaults.costs[key] ===
bodyshop.md_responsibility_centers.defaults[key] === emps[0].cost_center
emps[0].cost_center ) || "LAB",
),
}); });
} }
if (!!changedFields.cost_center && !!EmployeeAutoCompleteData) { if (!!changedFields.cost_center && !!EmployeeAutoCompleteData) {
form.setFieldsValue({ form.setFieldsValue({
ciecacode: Object.keys( ciecacode: Object.keys(
bodyshop.md_responsibility_centers.defaults bodyshop.md_responsibility_centers.defaults.costs
).find( ).find(
(key) => (key) =>
bodyshop.md_responsibility_centers.defaults[key] === bodyshop.md_responsibility_centers.defaults.costs[key] ===
changedFields.cost_center changedFields.cost_center
), ),
}); });
@@ -152,18 +151,16 @@ export function TimeTicketModalContainer({
</Button> </Button>
{timeTicketModal.context && timeTicketModal.context.id ? null : ( {timeTicketModal.context && timeTicketModal.context.id ? null : (
<Button <Button
type="primary" type='primary'
onClick={() => { onClick={() => {
setEnterAgain(true); setEnterAgain(true);
}} }}>
>
{t("general.actions.saveandnew")} {t("general.actions.saveandnew")}
</Button> </Button>
)} )}
</span> </span>
} }
destroyOnClose destroyOnClose>
>
<Form <Form
onFinish={handleFinish} onFinish={handleFinish}
autoComplete={"off"} autoComplete={"off"}
@@ -178,8 +175,7 @@ export function TimeTicketModalContainer({
} }
: { jobid: timeTicketModal.context.jobId || null } : { jobid: timeTicketModal.context.jobId || null }
} }
onValuesChange={handleFieldsChange} onValuesChange={handleFieldsChange}>
>
<TimeTicketModalComponent <TimeTicketModalComponent
form={form} form={form}
roAutoCompleteOptions={RoAutoCompleteData && RoAutoCompleteData.jobs} roAutoCompleteOptions={RoAutoCompleteData && RoAutoCompleteData.jobs}

View File

@@ -0,0 +1,50 @@
import { Card, List, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import DataLabel from "../data-label/data-label.component";
import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component";
export default function TimeTicketShiftActive({ timetickets, refetch }) {
const { t } = useTranslation();
return (
<div>
{timetickets.length > 0 ? (
<div>
<Typography.Title level={2}>
{t("timetickets.labels.shiftalreadyclockedon")}
</Typography.Title>
<List
grid={{
gutter: 32,
xs: 1,
sm: 2,
md: 3,
lg: 4,
xl: 5,
xxl: 6,
}}
dataSource={timetickets || []}
renderItem={(ticket) => (
<List.Item>
<Card
title={t(ticket.memo)}
actions={[
<TechClockOffButton
timeTicketId={ticket.id}
completedCallback={refetch}
isShiftTicket
/>,
]}>
<DataLabel label={t("timetickets.fields.clockon")}>
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
</DataLabel>
</Card>
</List.Item>
)}></List>
</div>
) : null}
</div>
);
}

View File

@@ -0,0 +1,53 @@
import { Form, InputNumber, Select } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function TimeTicketShiftFormComponent({ bodyshop, form }) {
const { t } = useTranslation();
return (
<div>
<Form.Item
name='memo'
label={t("timetickets.fields.memo")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}>
<Select>
<Select.Option value='timetickets.labels.amshift'>
{t("timetickets.labels.amshift")}
</Select.Option>
<Select.Option value='timetickets.labels.ambreak'>
{t("timetickets.labels.ambreak")}
</Select.Option>
<Select.Option value='timetickets.labels.lunch'>
{t("timetickets.labels.lunch")}
</Select.Option>
<Select.Option value='timetickets.labels.pmshift'>
{t("timetickets.labels.pmshift")}
</Select.Option>
<Select.Option value='timetickets.labels.pmbreak'>
{t("timetickets.labels.pmbreak")}
</Select.Option>
</Select>
</Form.Item>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TimeTicketShiftFormComponent);

View File

@@ -0,0 +1,90 @@
import { useMutation } from "@apollo/react-hooks";
import { Button, Form, notification } from "antd";
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 TimeTicektShiftComponent from "./time-ticket-shift-form.component";
import axios from "axios";
import moment from "moment";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function TimeTicektShiftContainer({
bodyshop,
technician,
isTechConsole,
}) {
const [form] = Form.useForm();
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET);
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const handleFinish = async (values) => {
setLoading(true);
console.log(values);
const theTime = moment((await axios.post("/utils/time")).data);
const result = await insertTimeTicket({
variables: {
timeTicketInput: [
{
bodyshopid: bodyshop.id,
employeeid: isTechConsole
? technician.id
: bodyshop.associations[0].user.employee.id,
cost_center: "timetickets.labels.shift",
clockon: theTime,
date: theTime,
memo: values.memo,
},
],
},
awaitRefetchQueries: true,
refetchQueries: ["QUERY_ACTIVE_SHIFT_TIME_TICKETS"],
});
if (!!result.errors) {
notification["error"]({
message: t("timetickets.errors.clockingin", {
message: JSON.stringify(result.errors),
}),
});
} else {
notification["success"]({
message: t("timetickets.successes.clockedin"),
});
}
setLoading(false);
};
return (
<div>
The form TimeTicektShiftContainer
<Form
form={form}
layout='vertical'
autoComplete='no'
onFinish={handleFinish}
initialValues={{ cost_center: t("timetickets.labels.shift") }}>
<TimeTicektShiftComponent form={form} />
<Button htmlType='submit' loading={loading}>
{t("timetickets.actions.clockin")}
</Button>
</Form>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TimeTicektShiftContainer);

View File

@@ -0,0 +1,57 @@
import React from "react";
import { useQuery } from "react-apollo";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_ACTIVE_SHIFT_TIME_TICKETS } from "../../graphql/timetickets.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import TimeTicketShiftActive from "../time-ticket-shift-active/time-ticket-shift-active.component";
import TimeTicketShiftFormContainer from "../time-ticket-shift-form/time-ticket-shift-form.container";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function TimeTicketShiftContainer({
bodyshop,
technician,
isTechConsole,
}) {
const { loading, error, data, refetch } = useQuery(
QUERY_ACTIVE_SHIFT_TIME_TICKETS,
{
variables: {
employeeId: isTechConsole
? technician.id
: bodyshop.associations[0].user.employee.id,
},
}
);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type='error' />;
return (
<div>
{data.timetickets.length > 0 ? (
<TimeTicketShiftActive
timetickets={data ? data.timetickets : []}
refetch={refetch}
/>
) : (
<TimeTicketShiftFormContainer isTechConsole={isTechConsole} />
)}
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TimeTicketShiftContainer);

View File

@@ -8,6 +8,9 @@ export const QUERY_BODYSHOP = gql`
authid authid
email email
dashboardlayout dashboardlayout
employee {
id
}
} }
} }
address1 address1

View File

@@ -14,6 +14,7 @@ export const QUERY_EMPLOYEES = gql`
cost_center cost_center
base_rate base_rate
pin pin
user_email
} }
} }
`; `;
@@ -43,6 +44,7 @@ export const UPDATE_EMPLOYEE = gql`
cost_center cost_center
base_rate base_rate
pin pin
user_email
} }
} }
} }

View File

@@ -72,11 +72,13 @@ export const QUERY_ACTIVE_TIME_TICKETS = gql`
clockoff: { _is_null: true } clockoff: { _is_null: true }
employeeid: { _eq: $employeeId } employeeid: { _eq: $employeeId }
clockon: { _is_null: false } clockon: { _is_null: false }
jobid: {_is_null: false}
} }
} }
) { ) {
id id
clockon clockon
memo
job { job {
id id
est_number est_number
@@ -91,6 +93,37 @@ export const QUERY_ACTIVE_TIME_TICKETS = gql`
} }
} }
`; `;
export const QUERY_ACTIVE_SHIFT_TIME_TICKETS = gql`
query QUERY_ACTIVE_SHIFT_TIME_TICKETS($employeeId: uuid) {
timetickets(
where: {
_and: {
clockoff: { _is_null: true }
employeeid: { _eq: $employeeId }
clockon: { _is_null: false }
jobid: {_is_null: true}
}
}
) {
id
clockon
memo
job {
id
est_number
ownr_fn
ownr_ln
ownr_co_nm
v_model_desc
v_make_desc
v_model_yr
ro_number
}
}
}
`;
export const DELETE_TIME_TICKET = gql` export const DELETE_TIME_TICKET = gql`
mutation DELETE_TIME_TICKET($id: uuid!) { mutation DELETE_TIME_TICKET($id: uuid!) {
delete_timetickets_by_pk(id: $id) { delete_timetickets_by_pk(id: $id) {

View File

@@ -0,0 +1,10 @@
import React from "react";
import TimeTicketShift from "../../components/time-ticket-shift/time-ticket-shift.container";
export default function TechShiftClock() {
return (
<div>
<TimeTicketShift isTechConsole />
</div>
);
}

View File

@@ -27,9 +27,12 @@ const ProductionListPage = lazy(() =>
const ProductionBoardPage = lazy(() => const ProductionBoardPage = lazy(() =>
import("../production-board/production-board.container") import("../production-board/production-board.container")
); );
const TechJobClockIn = lazy(() => const TechJobClock = lazy(() =>
import("../tech-job-clock/tech-job-clock.component") import("../tech-job-clock/tech-job-clock.component")
); );
const TechShiftClock = lazy(() =>
import("../tech-shift-clock/tech-shift-clock.component")
);
const { Content } = Layout; const { Content } = Layout;
@@ -48,19 +51,18 @@ export function TechPage({ technician, match }) {
}, [t]); }, [t]);
return ( return (
<Layout className="tech-layout-container"> <Layout className='tech-layout-container'>
<TechSider /> <TechSider />
<Layout> <Layout>
{technician ? null : <Redirect to={`${match.path}/login`} />} {technician ? null : <Redirect to={`${match.path}/login`} />}
<TechHeader /> <TechHeader />
<Content className="tech-content-container"> <Content className='tech-content-container'>
<FcmNotification /> <FcmNotification />
<ErrorBoundary> <ErrorBoundary>
<Suspense <Suspense
fallback={ fallback={
<LoadingSpinner message={t("general.labels.loadingapp")} /> <LoadingSpinner message={t("general.labels.loadingapp")} />
} }>
>
<TimeTicketModalContainer /> <TimeTicketModalContainer />
<PrintCenterModalContainer /> <PrintCenterModalContainer />
<Switch> <Switch>
@@ -78,11 +80,16 @@ export function TechPage({ technician, match }) {
exact exact
path={`${match.path}/list`} path={`${match.path}/list`}
component={ProductionListPage} component={ProductionListPage}
/>{" "} />
<Route <Route
exact exact
path={`${match.path}/jobclockin`} path={`${match.path}/jobclock`}
component={TechJobClockIn} component={TechJobClock}
/>
<Route
exact
path={`${match.path}/shiftclock`}
component={TechShiftClock}
/> />
<Route <Route
exact exact

View File

@@ -27,6 +27,7 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
...state, ...state,
technician: action.payload, technician: action.payload,
loginLoading: false, loginLoading: false,
loginError: false,
}; };
case TechActionTypes.TECH_LOGIN_FAILURE: case TechActionTypes.TECH_LOGIN_FAILURE:
return { return {

View File

@@ -351,8 +351,8 @@
"new": "New Employee" "new": "New Employee"
}, },
"errors": { "errors": {
"delete": "Error encountered while deleting employee.", "delete": "Error encountered while deleting employee. {{message}}",
"save": "Error encountered saving employee.", "save": "Error encountered saving employee. {{message}}",
"validation": "Please check all fields.", "validation": "Please check all fields.",
"validationtitle": "Unable to save employee." "validationtitle": "Unable to save employee."
}, },
@@ -1117,16 +1117,24 @@
"cost_center": "Cost Center", "cost_center": "Cost Center",
"date": "Ticket Date", "date": "Ticket Date",
"employee": "Employee", "employee": "Employee",
"memo": "Memo",
"productivehrs": "Productive Hours", "productivehrs": "Productive Hours",
"ro_number": "Job to Post Against" "ro_number": "Job to Post Against"
}, },
"labels": { "labels": {
"alreadyclockedon": "You are already clocked in to the following job(s):", "alreadyclockedon": "You are already clocked in to the following job(s):",
"ambreak": "AM Break",
"amshift": "AM Shift",
"clockintojob": "Clock In to Job", "clockintojob": "Clock In to Job",
"deleteconfirm": "Are you sure you want to delete this time ticket? This cannot be undone.", "deleteconfirm": "Are you sure you want to delete this time ticket? This cannot be undone.",
"edit": "Edit Time Ticket", "edit": "Edit Time Ticket",
"flat_rate": "Flat Rate", "flat_rate": "Flat Rate",
"lunch": "Lunch",
"new": "New Time Ticket", "new": "New Time Ticket",
"pmbreak": "PM Break",
"pmshift": "PM Shift",
"shift": "Shift",
"shiftalreadyclockedon": "Active Shift Time Tickets",
"straight_time": "Straight Time" "straight_time": "Straight Time"
}, },
"successes": { "successes": {

View File

@@ -351,8 +351,8 @@
"new": "Nuevo empleado" "new": "Nuevo empleado"
}, },
"errors": { "errors": {
"delete": "Se encontró un error al eliminar al empleado.", "delete": "Se encontró un error al eliminar al empleado. {{message}}",
"save": "Se encontró un error al salvar al empleado.", "save": "Se encontró un error al salvar al empleado. {{message}}",
"validation": "Por favor verifique todos los campos.", "validation": "Por favor verifique todos los campos.",
"validationtitle": "No se puede salvar al empleado." "validationtitle": "No se puede salvar al empleado."
}, },
@@ -1117,16 +1117,24 @@
"cost_center": "", "cost_center": "",
"date": "", "date": "",
"employee": "", "employee": "",
"memo": "",
"productivehrs": "", "productivehrs": "",
"ro_number": "" "ro_number": ""
}, },
"labels": { "labels": {
"alreadyclockedon": "", "alreadyclockedon": "",
"ambreak": "",
"amshift": "",
"clockintojob": "", "clockintojob": "",
"deleteconfirm": "", "deleteconfirm": "",
"edit": "", "edit": "",
"flat_rate": "", "flat_rate": "",
"lunch": "",
"new": "", "new": "",
"pmbreak": "",
"pmshift": "",
"shift": "",
"shiftalreadyclockedon": "",
"straight_time": "" "straight_time": ""
}, },
"successes": { "successes": {

View File

@@ -351,8 +351,8 @@
"new": "Nouvel employé" "new": "Nouvel employé"
}, },
"errors": { "errors": {
"delete": "Erreur rencontrée lors de la suppression de l'employé.", "delete": "Erreur rencontrée lors de la suppression de l'employé. {{message}}",
"save": "Une erreur s'est produite lors de l'enregistrement de l'employé.", "save": "Une erreur s'est produite lors de l'enregistrement de l'employé. {{message}}",
"validation": "Veuillez cocher tous les champs.", "validation": "Veuillez cocher tous les champs.",
"validationtitle": "Impossible d'enregistrer l'employé." "validationtitle": "Impossible d'enregistrer l'employé."
}, },
@@ -1117,16 +1117,24 @@
"cost_center": "", "cost_center": "",
"date": "", "date": "",
"employee": "", "employee": "",
"memo": "",
"productivehrs": "", "productivehrs": "",
"ro_number": "" "ro_number": ""
}, },
"labels": { "labels": {
"alreadyclockedon": "", "alreadyclockedon": "",
"ambreak": "",
"amshift": "",
"clockintojob": "", "clockintojob": "",
"deleteconfirm": "", "deleteconfirm": "",
"edit": "", "edit": "",
"flat_rate": "", "flat_rate": "",
"lunch": "",
"new": "", "new": "",
"pmbreak": "",
"pmshift": "",
"shift": "",
"shiftalreadyclockedon": "",
"straight_time": "" "straight_time": ""
}, },
"successes": { "successes": {

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."employees" DROP COLUMN "user_email";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."employees" ADD COLUMN "user_email" text NULL UNIQUE;
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."employees" drop constraint "employees_user_email_fkey";
type: run_sql

View File

@@ -0,0 +1,10 @@
- args:
cascade: false
read_only: false
sql: |-
alter table "public"."employees"
add constraint "employees_user_email_fkey"
foreign key ("user_email")
references "public"."users"
("email") on update restrict on delete restrict;
type: run_sql

View File

@@ -0,0 +1,12 @@
- args:
cascade: false
read_only: false
sql: |-
alter table "public"."employees" drop constraint "employees_user_email_fkey",
add constraint "employees_user_email_fkey"
foreign key ("shopid")
references "public"."bodyshops"
("id")
on update cascade
on delete cascade;
type: run_sql

View File

@@ -0,0 +1,10 @@
- args:
cascade: false
read_only: false
sql: |-
alter table "public"."employees" drop constraint "employees_user_email_fkey",
add constraint "employees_user_email_fkey"
foreign key ("user_email")
references "public"."users"
("email") on update set null on delete set null;
type: run_sql

View File

@@ -0,0 +1,6 @@
- args:
relationship: employee
table:
name: users
schema: public
type: drop_relationship

View File

@@ -0,0 +1,13 @@
- args:
name: employee
table:
name: users
schema: public
using:
manual_configuration:
column_mapping:
email: user_email
remote_table:
name: employees
schema: public
type: create_object_relationship

View File

@@ -0,0 +1,6 @@
- args:
relationship: user
table:
name: employees
schema: public
type: drop_relationship

View File

@@ -0,0 +1,8 @@
- args:
name: user
table:
name: employees
schema: public
using:
foreign_key_constraint_on: user_email
type: create_object_relationship

View File

@@ -0,0 +1,38 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- active
- base_rate
- cost_center
- created_at
- employee_number
- first_name
- flat_rate
- hire_date
- id
- last_name
- pin
- shopid
- termination_date
- updated_at
set: {}
role: user
table:
name: employees
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,39 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- active
- base_rate
- cost_center
- created_at
- employee_number
- first_name
- flat_rate
- hire_date
- id
- last_name
- pin
- shopid
- termination_date
- updated_at
- user_email
set: {}
role: user
table:
name: employees
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,39 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- active
- base_rate
- cost_center
- created_at
- employee_number
- first_name
- flat_rate
- hire_date
- id
- last_name
- pin
- shopid
- termination_date
- updated_at
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: employees
schema: public
type: create_select_permission

View File

@@ -0,0 +1,40 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- active
- base_rate
- cost_center
- created_at
- employee_number
- first_name
- flat_rate
- hire_date
- id
- last_name
- pin
- shopid
- termination_date
- updated_at
- user_email
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: employees
schema: public
type: create_select_permission

View File

@@ -0,0 +1,38 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_update_permission
- args:
permission:
columns:
- active
- base_rate
- cost_center
- created_at
- employee_number
- first_name
- flat_rate
- hire_date
- id
- last_name
- pin
- shopid
- termination_date
- updated_at
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: employees
schema: public
type: create_update_permission

View File

@@ -0,0 +1,39 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_update_permission
- args:
permission:
columns:
- active
- base_rate
- cost_center
- created_at
- employee_number
- first_name
- flat_rate
- hire_date
- id
- last_name
- pin
- shopid
- termination_date
- updated_at
- user_email
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: employees
schema: public
type: create_update_permission

View File

@@ -0,0 +1,38 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_insert_permission
- args:
permission:
check:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- date
- cost_center
- employeeid
- jobid
- rate
- productivehrs
- actualhrs
- clockon
- clockoff
- ciecacode
set: {}
role: user
table:
name: timetickets
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,29 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_insert_permission
- args:
permission:
check: {}
columns:
- id
- created_at
- updated_at
- date
- cost_center
- employeeid
- jobid
- rate
- productivehrs
- actualhrs
- clockon
- clockoff
- ciecacode
set: {}
role: user
table:
name: timetickets
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."timetickets" DROP COLUMN "bodyshopid";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."timetickets" ADD COLUMN "bodyshopid" uuid NOT NULL;
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."timetickets" drop constraint "timetickets_bodyshopid_fkey";
type: run_sql

View File

@@ -0,0 +1,10 @@
- args:
cascade: false
read_only: false
sql: |-
alter table "public"."timetickets"
add constraint "timetickets_bodyshopid_fkey"
foreign key ("bodyshopid")
references "public"."bodyshops"
("id") on update restrict on delete restrict;
type: run_sql

View File

@@ -0,0 +1,12 @@
- args:
relationship: timetickets
table:
name: bodyshops
schema: public
type: drop_relationship
- args:
relationship: bodyshop
table:
name: timetickets
schema: public
type: drop_relationship

View File

@@ -0,0 +1,20 @@
- args:
name: timetickets
table:
name: bodyshops
schema: public
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: timetickets
schema: public
type: create_array_relationship
- args:
name: bodyshop
table:
name: timetickets
schema: public
using:
foreign_key_constraint_on: bodyshopid
type: create_object_relationship

View File

@@ -0,0 +1,29 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_insert_permission
- args:
permission:
check: {}
columns:
- id
- created_at
- updated_at
- date
- cost_center
- employeeid
- jobid
- rate
- productivehrs
- actualhrs
- clockon
- clockoff
- ciecacode
set: {}
role: user
table:
name: timetickets
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,37 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- date
- cost_center
- employeeid
- jobid
- rate
- productivehrs
- actualhrs
- clockon
- clockoff
- ciecacode
set: {}
role: user
table:
name: timetickets
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,39 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- date
- actualhrs
- productivehrs
- rate
- ciecacode
- cost_center
- clockoff
- clockon
- created_at
- updated_at
- employeeid
- id
- jobid
computed_fields: []
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: timetickets
schema: public
type: create_select_permission

View File

@@ -0,0 +1,39 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- productivehrs
- rate
- updated_at
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: timetickets
schema: public
type: create_select_permission

View File

@@ -0,0 +1,37 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- date
- cost_center
- employeeid
- jobid
- rate
- productivehrs
- actualhrs
- clockon
- clockoff
- ciecacode
set: {}
role: user
table:
name: timetickets
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,38 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- productivehrs
- rate
- updated_at
set: {}
role: user
table:
name: timetickets
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,38 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_update_permission
- args:
permission:
columns:
- date
- actualhrs
- productivehrs
- rate
- ciecacode
- cost_center
- clockoff
- clockon
- created_at
- updated_at
- employeeid
- id
- jobid
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: timetickets
schema: public
type: create_update_permission

View File

@@ -0,0 +1,38 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_update_permission
- args:
permission:
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- productivehrs
- rate
- updated_at
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: timetickets
schema: public
type: create_update_permission

View File

@@ -0,0 +1,23 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_delete_permission
- args:
permission:
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: timetickets
schema: public
type: create_delete_permission

View File

@@ -0,0 +1,22 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_delete_permission
- args:
permission:
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: timetickets
schema: public
type: create_delete_permission

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."timetickets" DROP COLUMN "memo";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."timetickets" ADD COLUMN "memo" text NULL;
type: run_sql

View File

@@ -0,0 +1,38 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- productivehrs
- rate
- updated_at
set: {}
role: user
table:
name: timetickets
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,39 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- memo
- productivehrs
- rate
- updated_at
set: {}
role: user
table:
name: timetickets
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,39 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- productivehrs
- rate
- updated_at
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: timetickets
schema: public
type: create_select_permission

View File

@@ -0,0 +1,40 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- memo
- productivehrs
- rate
- updated_at
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: timetickets
schema: public
type: create_select_permission

View File

@@ -0,0 +1,38 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_update_permission
- args:
permission:
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- productivehrs
- rate
- updated_at
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: timetickets
schema: public
type: create_update_permission

View File

@@ -0,0 +1,39 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_update_permission
- args:
permission:
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- memo
- productivehrs
- rate
- updated_at
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: timetickets
schema: public
type: create_update_permission

View File

@@ -433,6 +433,13 @@ tables:
table: table:
schema: public schema: public
name: templates name: templates
- name: timetickets
using:
foreign_key_constraint_on:
column: bodyshopid
table:
schema: public
name: timetickets
- name: vehicles - name: vehicles
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -1175,6 +1182,9 @@ tables:
- name: bodyshop - name: bodyshop
using: using:
foreign_key_constraint_on: shopid foreign_key_constraint_on: shopid
- name: user
using:
foreign_key_constraint_on: user_email
array_relationships: array_relationships:
- name: allocations - name: allocations
using: using:
@@ -1217,6 +1227,7 @@ tables:
- shopid - shopid
- termination_date - termination_date
- updated_at - updated_at
- user_email
select_permissions: select_permissions:
- role: user - role: user
permission: permission:
@@ -1235,6 +1246,7 @@ tables:
- shopid - shopid
- termination_date - termination_date
- updated_at - updated_at
- user_email
filter: filter:
bodyshop: bodyshop:
associations: associations:
@@ -1262,6 +1274,7 @@ tables:
- shopid - shopid
- termination_date - termination_date
- updated_at - updated_at
- user_email
filter: filter:
bodyshop: bodyshop:
associations: associations:
@@ -3538,6 +3551,9 @@ tables:
schema: public schema: public
name: timetickets name: timetickets
object_relationships: object_relationships:
- name: bodyshop
using:
foreign_key_constraint_on: bodyshopid
- name: employee - name: employee
using: using:
foreign_key_constraint_on: employeeid foreign_key_constraint_on: employeeid
@@ -3548,100 +3564,111 @@ tables:
- role: user - role: user
permission: permission:
check: check:
job: bodyshop:
bodyshop: associations:
associations: _and:
_and: - user:
- user: authid:
authid: _eq: X-Hasura-User-Id
_eq: X-Hasura-User-Id - active:
- active: _eq: true
_eq: true
columns: columns:
- id
- created_at
- updated_at
- date
- cost_center
- employeeid
- jobid
- rate
- productivehrs
- actualhrs - actualhrs
- clockon - bodyshopid
- clockoff
- ciecacode - ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- memo
- productivehrs
- rate
- updated_at
select_permissions: select_permissions:
- role: user - role: user
permission: permission:
columns: columns:
- date
- actualhrs - actualhrs
- productivehrs - bodyshopid
- rate
- ciecacode - ciecacode
- cost_center
- clockoff - clockoff
- clockon - clockon
- cost_center
- created_at - created_at
- updated_at - date
- employeeid - employeeid
- id - id
- jobid - jobid
- memo
- productivehrs
- rate
- updated_at
filter: filter:
job: bodyshop:
bodyshop: associations:
associations: _and:
_and: - user:
- user: authid:
authid: _eq: X-Hasura-User-Id
_eq: X-Hasura-User-Id - active:
- active: _eq: true
_eq: true
update_permissions: update_permissions:
- role: user - role: user
permission: permission:
columns: columns:
- date
- actualhrs - actualhrs
- productivehrs - bodyshopid
- rate
- ciecacode - ciecacode
- cost_center
- clockoff - clockoff
- clockon - clockon
- cost_center
- created_at - created_at
- updated_at - date
- employeeid - employeeid
- id - id
- jobid - jobid
- memo
- productivehrs
- rate
- updated_at
filter: filter:
job: bodyshop:
bodyshop: associations:
associations: _and:
_and: - user:
- user: authid:
authid: _eq: X-Hasura-User-Id
_eq: X-Hasura-User-Id - active:
- active: _eq: true
_eq: true
check: null check: null
delete_permissions: delete_permissions:
- role: user - role: user
permission: permission:
filter: filter:
job: bodyshop:
bodyshop: associations:
associations: _and:
_and: - user:
- user: authid:
authid: _eq: X-Hasura-User-Id
_eq: X-Hasura-User-Id - active:
- active: _eq: true
_eq: true
- table: - table:
schema: public schema: public
name: users name: users
object_relationships:
- name: employee
using:
manual_configuration:
remote_table:
schema: public
name: employees
column_mapping:
email: user_email
array_relationships: array_relationships:
- name: associations - name: associations
using: using: