Merge branch 'feature/payroll' into feature/america
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<babeledit_project version="1.2" be_version="2.7.1">
|
||||
<babeledit_project be_version="2.7.1" version="1.2">
|
||||
<!--
|
||||
|
||||
BabelEdit project file
|
||||
@@ -6898,6 +6898,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>editcommitted</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>enter</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -6963,6 +6984,53 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>ttapprovals</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>approve</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>view</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>users</name>
|
||||
<children>
|
||||
@@ -32555,6 +32623,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ttapprovals</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>vehicles</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -44733,6 +44822,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>committed</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>cost_center</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -44817,6 +44927,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>employee_team</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>flat_rate</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -46438,6 +46569,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ttapprovals</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>vehicle-details</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -47364,6 +47516,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ttapprovals</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>vehicledetail</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -47408,6 +47581,37 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>tt_approvals</name>
|
||||
<children>
|
||||
<folder_node>
|
||||
<name>actions</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>approveselected</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>user</name>
|
||||
<children>
|
||||
|
||||
@@ -262,6 +262,11 @@ function Header({
|
||||
{t("menus.header.timetickets")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="ttapprovals" icon={<FieldTimeOutlined />}>
|
||||
<Link to="/manage/ttapprovals">
|
||||
{t("menus.header.ttapprovals")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="entertimetickets"
|
||||
icon={<Icon component={GiPlayerTime} />}
|
||||
|
||||
@@ -532,6 +532,42 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "timetickets:editcommitted"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.ttapprovals.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "ttapprovals:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "ttapprovals:approve"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.vendors")}
|
||||
rules={[
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EditFilled } from "@ant-design/icons";
|
||||
import { Button, Card, Space, Table } from "antd";
|
||||
import { Button, Card, Checkbox, Space, Table } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import moment from "moment";
|
||||
import React, { useMemo, useState } from "react";
|
||||
@@ -62,6 +62,14 @@ export function TimeTicketList({
|
||||
}, [timetickets]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("timetickets.fields.committed"),
|
||||
dataIndex: "committed_at",
|
||||
key: "committed_at",
|
||||
render: (text, record) => (
|
||||
<Checkbox disabled checked={record.committed_at} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.date"),
|
||||
dataIndex: "date",
|
||||
@@ -236,6 +244,11 @@ export function TimeTicketList({
|
||||
timeticket: record,
|
||||
}}
|
||||
disabled={
|
||||
HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:editcommitted",
|
||||
}) &&
|
||||
HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
@@ -271,7 +284,6 @@ export function TimeTicketList({
|
||||
}
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTimeTicketTaskContext();
|
||||
setTimeTicketTaskContext({
|
||||
actions: {},
|
||||
context: { jobid: jobId },
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Space,
|
||||
Table,
|
||||
} from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -19,10 +20,9 @@ import EmployeeSearchSelectComponent from "../employee-search-select/employee-se
|
||||
import EmployeeTeamSearchSelectComponent from "../employee-team-search-select/employee-team-search-select.component";
|
||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component";
|
||||
import TimeTicketsTasksPresets from "../time-ticket-tasks-presets/time-ticket-tasks-presets.component";
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
import _ from "lodash";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -39,7 +39,6 @@ export default connect(
|
||||
export function TimeTicketTaskModalComponent({
|
||||
bodyshop,
|
||||
form,
|
||||
employeeAutoCompleteOptions,
|
||||
lineTicketCalled,
|
||||
calculateTimeTickets,
|
||||
lineTicketLoading,
|
||||
@@ -72,18 +71,6 @@ export function TimeTicketTaskModalComponent({
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="employeeid" label={t("timetickets.fields.employee")}>
|
||||
<EmployeeSearchSelectComponent
|
||||
options={employeeAutoCompleteOptions}
|
||||
allowClear
|
||||
onSelect={(value) => {
|
||||
const emps =
|
||||
employeeAutoCompleteOptions &&
|
||||
employeeAutoCompleteOptions.filter((e) => e.id === value)[0];
|
||||
form.setFieldsValue({ flat_rate: emps && emps.flat_rate });
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="employeeteamid"
|
||||
label={t("timetickets.fields.employee_team")}
|
||||
@@ -243,7 +230,13 @@ export function TimeTicketTaskModalComponent({
|
||||
{errors.map((e, idx) => (
|
||||
<Alert key={idx} message={e} />
|
||||
))}
|
||||
<div style={{ display: "none" }}>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
//display: "none"
|
||||
}
|
||||
}
|
||||
>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item
|
||||
key={field.key}
|
||||
@@ -358,7 +351,7 @@ export function TimeTicketTaskModalComponent({
|
||||
!lineTicketLoading)
|
||||
) {
|
||||
queryJobInfo({ variables: { id: jobid } }).then(() =>
|
||||
calculateTimeTickets()
|
||||
calculateTimeTickets("")
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -15,6 +15,7 @@ import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectTimeTicketTasks } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import TimeTicketTaskModalComponent from "./time-ticket-task-modal.component";
|
||||
import { INSERT_NEW_TT_APPROVALS } from "../../graphql/tt-approvals.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
timeTicketTasksModal: selectTimeTicketTasks,
|
||||
@@ -40,8 +41,10 @@ export function TimeTickeTaskModalContainer({
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [insertTimeTickets] = useMutation(INSERT_NEW_TIME_TICKET);
|
||||
const [insertTimeTicketApproval] = useMutation(INSERT_NEW_TT_APPROVALS);
|
||||
const [queryJobInfo, { called, loading, data: lineTicketData }] =
|
||||
useLazyQuery(GET_JOB_INFO_DRAW_CALCULATIONS, {
|
||||
fetchPolicy: "network-only",
|
||||
@@ -49,31 +52,53 @@ export function TimeTickeTaskModalContainer({
|
||||
});
|
||||
|
||||
async function handleFinish(values) {
|
||||
console.log(
|
||||
"🚀 ~ file: time-ticket-task-modal.container.jsx:52 ~ handleFinish ~ values:",
|
||||
values
|
||||
);
|
||||
try {
|
||||
const result = await insertTimeTickets({
|
||||
variables: {
|
||||
timeTicketInput: values.timetickets.map((ticket) =>
|
||||
_.omit(ticket, "pay")
|
||||
),
|
||||
},
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: JSON.stringify(result.errors),
|
||||
}),
|
||||
if (true) {
|
||||
const result = await insertTimeTicketApproval({
|
||||
variables: {
|
||||
timeTicketInput: values.timetickets.map((ticket) => ({
|
||||
..._.omit(ticket, "pay"),
|
||||
bodyshopid: bodyshop.id,
|
||||
})),
|
||||
},
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.created"),
|
||||
});
|
||||
form.resetFields();
|
||||
toggleModalVisible();
|
||||
}
|
||||
} else {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.created"),
|
||||
const result = await insertTimeTickets({
|
||||
variables: {
|
||||
timeTicketInput: values.timetickets.map((ticket) =>
|
||||
_.omit(ticket, "pay")
|
||||
),
|
||||
},
|
||||
});
|
||||
toggleModalVisible();
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.created"),
|
||||
});
|
||||
toggleModalVisible();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
@@ -81,13 +106,13 @@ export function TimeTickeTaskModalContainer({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (context.jobid) {
|
||||
console.log("UE Fired.");
|
||||
if (visible && context.jobid) {
|
||||
queryJobInfo({ variables: { id: context.jobid } });
|
||||
}
|
||||
}, [context.jobid, queryJobInfo]);
|
||||
}, [context.jobid, queryJobInfo, visible]);
|
||||
|
||||
const calculateTimeTickets = (presetMemo) => {
|
||||
console.log("🚀 ~ file: time-ticket-task-modal.container.jsx:115 ~ calculateTimeTickets ~ presetMemo:", presetMemo)
|
||||
const formData = form.getFieldsValue();
|
||||
if (
|
||||
!formData.jobid ||
|
||||
@@ -99,14 +124,13 @@ export function TimeTickeTaskModalContainer({
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = [];
|
||||
let eligibleHours = 0;
|
||||
|
||||
const theTeam = JSON.parse(formData.employeeteamid);
|
||||
|
||||
if (theTeam) {
|
||||
data = [];
|
||||
|
||||
formData.hourstype.forEach((hourstype) => {
|
||||
eligibleHours =
|
||||
lineTicketData.joblines.reduce(
|
||||
@@ -125,6 +149,7 @@ export function TimeTickeTaskModalContainer({
|
||||
actualhrs: 0,
|
||||
memo: presetMemo,
|
||||
flat_rate: true,
|
||||
ciecacode: hourstype,
|
||||
cost_center:
|
||||
bodyshop.md_responsibility_centers.defaults.costs[hourstype],
|
||||
productivehrs:
|
||||
@@ -152,7 +177,10 @@ export function TimeTickeTaskModalContainer({
|
||||
<Modal
|
||||
destroyOnClose
|
||||
open={visible}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
onCancel={() => {
|
||||
toggleModalVisible();
|
||||
form.resetFields();
|
||||
}}
|
||||
width="80%"
|
||||
onOk={() => form.submit()}
|
||||
>
|
||||
@@ -161,7 +189,6 @@ export function TimeTickeTaskModalContainer({
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
// onFieldsChange={handleFieldsChange}
|
||||
initialValues={context}
|
||||
>
|
||||
<TimeTicketTaskModalComponent
|
||||
|
||||
@@ -15,6 +15,7 @@ export default function TimeTicketsTasksPresets({
|
||||
percent: preset.percent,
|
||||
hourstype: preset.hourstype,
|
||||
});
|
||||
|
||||
calculateTimeTickets(preset.memo);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Space, Table, Tag } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import {
|
||||
selectAuthLevel,
|
||||
selectBodyshop,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import TtApproveButtonComponent from "../tt-approve-button/tt-approve-button.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
authLevel: selectAuthLevel,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setTimeTicketTaskContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TtApprovalsListComponent);
|
||||
|
||||
export function TtApprovalsListComponent({
|
||||
bodyshop,
|
||||
setTimeTicketTaskContext,
|
||||
authLevel,
|
||||
disabled,
|
||||
loading,
|
||||
tt_approval_queue,
|
||||
total,
|
||||
refetch,
|
||||
techConsole,
|
||||
jobId,
|
||||
extra,
|
||||
}) {
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" },
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const { page } = search;
|
||||
const [selectedTickets, setSelectedTickets] = useState([]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("timetickets.fields.date"),
|
||||
dataIndex: "date",
|
||||
key: "date",
|
||||
sorter: (a, b) => dateSort(a.date, b.date),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "date" && state.sortedInfo.order,
|
||||
render: (text, record) => <DateFormatter>{record.date}</DateFormatter>,
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.employee"),
|
||||
dataIndex: "employeeid",
|
||||
key: "employeeid",
|
||||
sorter: (a, b) => alphaSort(a.employee.last_name, b.employee.last_name),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
`${record.employee.first_name} ${record.employee.last_name}`,
|
||||
filters:
|
||||
tt_approval_queue
|
||||
.map((l) => l.employeeid)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: (() => {
|
||||
const emp = bodyshop.employees.find((e) => e.id === s);
|
||||
|
||||
return `${emp?.first_name} ${emp?.last_name}`;
|
||||
})(), //
|
||||
value: [s],
|
||||
};
|
||||
}) || [],
|
||||
onFilter: (value, record) => value.includes(record.employeeid),
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.cost_center"),
|
||||
dataIndex: "cost_center",
|
||||
key: "cost_center",
|
||||
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
|
||||
render: (text, record) =>
|
||||
record.cost_center === "timetickets.labels.shift"
|
||||
? t(record.cost_center)
|
||||
: record.cost_center,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
|
||||
filters:
|
||||
tt_approval_queue
|
||||
.map((l) => l.cost_center)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s === "timetickets.labels.shift" ? t(s) : s, //|| "No Status*",
|
||||
value: [s],
|
||||
};
|
||||
}) || [],
|
||||
onFilter: (value, record) => value.includes(record.cost_center),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) =>
|
||||
alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
record.job && (
|
||||
<Link to={"/manage/jobs/" + record.job.id}>
|
||||
<Space wrap>
|
||||
{record.job.ro_number || "N/A"}
|
||||
<Tag>{record.job.status}</Tag>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.productivehrs"),
|
||||
dataIndex: "productivehrs",
|
||||
key: "productivehrs",
|
||||
sorter: (a, b) => a.productivehrs - b.productivehrs,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "productivehrs" &&
|
||||
state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.actualhrs"),
|
||||
dataIndex: "actualhrs",
|
||||
key: "actualhrs",
|
||||
sorter: (a, b) => a.actualhrs - b.actualhrs,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "actualhrs" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.memo"),
|
||||
dataIndex: "memo",
|
||||
key: "memo",
|
||||
sorter: (a, b) => alphaSort(a.memo, b.memo),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
record.clockon || record.clockoff ? t(record.memo) : record.memo,
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.clockon"),
|
||||
dataIndex: "clockon",
|
||||
key: "clockon",
|
||||
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.clockon}</DateTimeFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Pay",
|
||||
dataIndex: "pay",
|
||||
key: "pay",
|
||||
render: (text, record) =>
|
||||
Dinero({ amount: Math.round(record.rate * 100) })
|
||||
.multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||
.toFormat("$0.00"),
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
search.page = pagination.current;
|
||||
if (sorter && sorter.column && sorter.column.sortObject) {
|
||||
search.searchObj = JSON.stringify(sorter.column.sortObject(sorter.order));
|
||||
} else {
|
||||
delete search.searchObj;
|
||||
search.sortcolumn = sorter.order ? sorter.columnKey : null;
|
||||
search.sortorder = sorter.order;
|
||||
}
|
||||
|
||||
search.sort = JSON.stringify({ [sorter.columnKey]: sorter.order });
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("timetickets.labels.timetickets")}
|
||||
extra={
|
||||
<Space wrap>
|
||||
{extra}
|
||||
<TtApproveButtonComponent
|
||||
selectedTickets={selectedTickets}
|
||||
disabled={selectedTickets.length === 0}
|
||||
completedCallback={setSelectedTickets}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
scroll={{
|
||||
x: true,
|
||||
}}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: 25,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
}}
|
||||
dataSource={tt_approval_queue}
|
||||
onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelectAll: (selected, selectedRows) =>
|
||||
setSelectedTickets(selectedRows.map((i) => i.id)),
|
||||
onSelect: (record, selected, selectedRows, nativeEvent) => {
|
||||
setSelectedTickets(selectedRows.map((i) => i.id));
|
||||
},
|
||||
selectedRowKeys: selectedTickets,
|
||||
type: "checkbox",
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_ALL_TT_APPROVALS_PAGINATED } from "../../graphql/tt-approvals.queries";
|
||||
import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import TtApprovalsListComponent from "./tt-approvals-list.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
});
|
||||
|
||||
export function TimeTicketsContainer({
|
||||
bodyshop,
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
}) {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { page, sortcolumn, sortorder, search, searchObj } = searchParams;
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(
|
||||
QUERY_ALL_TT_APPROVALS_PAGINATED,
|
||||
{
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
search: search || "",
|
||||
offset: page ? (page - 1) * 25 : 0,
|
||||
limit: 25,
|
||||
order: [
|
||||
searchObj
|
||||
? JSON.parse(searchObj)
|
||||
: {
|
||||
[sortcolumn || "date"]: sortorder
|
||||
? sortorder === "descend"
|
||||
? "desc"
|
||||
: "asc"
|
||||
: "desc",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<TtApprovalsListComponent
|
||||
loading={loading}
|
||||
tt_approval_queue={data ? data.tt_approval_queue : []}
|
||||
total={data ? data.tt_approval_queue_aggregate.aggregate.count : 0}
|
||||
refetch={refetch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TimeTicketsContainer);
|
||||
@@ -0,0 +1,92 @@
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import _ from "lodash";
|
||||
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_TIME_TICKET_AND_APPROVE } from "../../graphql/timetickets.queries";
|
||||
import { QUERY_TT_APPROVALS_BY_IDS } from "../../graphql/tt-approvals.queries";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
|
||||
export function TtApproveButton({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
selectedTickets,
|
||||
disabled,
|
||||
loadingCallback,
|
||||
completedCallback,
|
||||
refetch,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleQbxml = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await client.query({
|
||||
query: QUERY_TT_APPROVALS_BY_IDS,
|
||||
variables: { ids: selectedTickets },
|
||||
});
|
||||
|
||||
const insertResponse = await client.mutate({
|
||||
mutation: INSERT_TIME_TICKET_AND_APPROVE,
|
||||
variables: {
|
||||
timeTicketInput: data.tt_approval_queue.map((tta) => ({
|
||||
..._.omit(tta, ["id", "__typename"]),
|
||||
ttapprovalqueueid: tta.id,
|
||||
})),
|
||||
approvalIds: selectedTickets,
|
||||
approvalUpdate: {
|
||||
approved_at: moment(),
|
||||
approved_by: currentUser.email,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (insertResponse.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: JSON.stringify(insertResponse.errors),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.createdg"),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: error.message,
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
// if (!!completedCallback) completedCallback([]);
|
||||
// if (!!loadingCallback) loadingCallback(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
{t("tt_approvals.actions.approveselected")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(TtApproveButton);
|
||||
@@ -51,6 +51,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
op_code_desc
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
|
||||
}
|
||||
timetickets(where: { jobid: { _eq: $id } }) {
|
||||
actualhrs
|
||||
@@ -65,6 +66,8 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
clockon
|
||||
clockoff
|
||||
rate
|
||||
committed_at
|
||||
committed_by
|
||||
employee {
|
||||
id
|
||||
first_name
|
||||
|
||||
@@ -158,6 +158,8 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
|
||||
productivehrs
|
||||
memo
|
||||
jobid
|
||||
committed_at
|
||||
committed_by
|
||||
flat_rate
|
||||
job {
|
||||
id
|
||||
@@ -224,6 +226,39 @@ export const INSERT_NEW_TIME_TICKET = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const INSERT_TIME_TICKET_AND_APPROVE = gql`
|
||||
mutation INSERT_TIME_TICKET_AND_APPROVE(
|
||||
$timeTicketInput: [timetickets_insert_input!]!
|
||||
$approvalIds: [uuid!]!
|
||||
$approvalUpdate: tt_approval_queue_set_input
|
||||
) {
|
||||
insert_timetickets(objects: $timeTicketInput) {
|
||||
returning {
|
||||
id
|
||||
clockon
|
||||
clockoff
|
||||
employeeid
|
||||
productivehrs
|
||||
actualhrs
|
||||
ciecacode
|
||||
date
|
||||
memo
|
||||
flat_rate
|
||||
}
|
||||
}
|
||||
update_tt_approval_queue(
|
||||
where: { id: { _in: $approvalIds } }
|
||||
_set: $approvalUpdate
|
||||
) {
|
||||
returning {
|
||||
id
|
||||
approved_at
|
||||
approved_at
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_TIME_TICKET = gql`
|
||||
mutation UPDATE_TIME_TICKET(
|
||||
$timeticketId: uuid!
|
||||
|
||||
98
client/src/graphql/tt-approvals.queries.js
Normal file
98
client/src/graphql/tt-approvals.queries.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const QUERY_ALL_TT_APPROVALS_PAGINATED = gql`
|
||||
query QUERY_ALL_TT_APPROVALS_PAGINATED(
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$order: [tt_approval_queue_order_by!]!
|
||||
) {
|
||||
tt_approval_queue(
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: { approved_at: { _is_null: true } }
|
||||
) {
|
||||
id
|
||||
jobid
|
||||
bodyshopid
|
||||
employeeid
|
||||
employee {
|
||||
first_name
|
||||
last_name
|
||||
id
|
||||
}
|
||||
job {
|
||||
ro_number
|
||||
status
|
||||
id
|
||||
}
|
||||
employeeid
|
||||
actualhrs
|
||||
productivehrs
|
||||
ciecacode
|
||||
cost_center
|
||||
date
|
||||
rate
|
||||
}
|
||||
tt_approval_queue_aggregate {
|
||||
aggregate {
|
||||
count(distinct: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const INSERT_NEW_TT_APPROVALS = gql`
|
||||
mutation INSERT_NEW_TT_APPROVALS(
|
||||
$timeTicketInput: [tt_approval_queue_insert_input!]!
|
||||
) {
|
||||
insert_tt_approval_queue(objects: $timeTicketInput) {
|
||||
returning {
|
||||
id
|
||||
employeeid
|
||||
productivehrs
|
||||
actualhrs
|
||||
ciecacode
|
||||
date
|
||||
memo
|
||||
flat_rate
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_TT_APPROVALS_BY_IDS = gql`
|
||||
query QUERY_TT_APPROVALS_BY_IDS($ids: [uuid!]!) {
|
||||
tt_approval_queue(where: { id: { _in: $ids } }) {
|
||||
id
|
||||
productivehrs
|
||||
actualhrs
|
||||
rate
|
||||
memo
|
||||
jobid
|
||||
flat_rate
|
||||
employeeid
|
||||
date
|
||||
ciecacode
|
||||
bodyshopid
|
||||
cost_center
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_TT_BY_APPROVAL = gql`
|
||||
mutation UPDATE_TT_BY_APPROVAL(
|
||||
$ttApprovalUpdates: [tt_approval_queue_updates!]!
|
||||
) {
|
||||
update_tt_approval_queue_many(updates: $ttApprovalUpdates) {
|
||||
returning {
|
||||
id
|
||||
approved_at
|
||||
approved_by
|
||||
timeticket {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -174,6 +174,9 @@ const Dms = lazy(() => import("../dms/dms.container"));
|
||||
const DmsPayables = lazy(() =>
|
||||
import("../dms-payables/dms-payables.container")
|
||||
);
|
||||
const TtApprovals = lazy(() =>
|
||||
import("../tt-approvals/tt-approvals.page.container")
|
||||
);
|
||||
|
||||
const { Content, Footer } = Layout;
|
||||
|
||||
@@ -376,6 +379,7 @@ export function Manage({ match, conflict, bodyshop }) {
|
||||
path={`${match.path}/accounting/exportlogs`}
|
||||
component={ExportLogs}
|
||||
/>
|
||||
<Route exact path={`${match.path}/ttapprovals`} component={TtApprovals} />
|
||||
<Route exact path={`${match.path}/partsqueue`} component={PartsQueue} />
|
||||
<Route exact path={`${match.path}/phonebook`} component={Phonebook} />
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import TtApprovalsList from "../../components/tt-approvals-list/tt-approvals-list.container";
|
||||
import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
});
|
||||
|
||||
export function TtApprovalsPage({ setBreadcrumbs, setSelectedHeader }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t("titles.ttapprovals");
|
||||
setSelectedHeader("ttapprovals");
|
||||
setBreadcrumbs([
|
||||
{
|
||||
link: "/manage/ttapprovals",
|
||||
label: t("titles.bc.ttapprovals"),
|
||||
},
|
||||
]);
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
return (
|
||||
<RbacWrapper action="ttapprovals:view">
|
||||
<TtApprovalsList />
|
||||
</RbacWrapper>
|
||||
);
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TtApprovalsPage);
|
||||
@@ -435,10 +435,15 @@
|
||||
},
|
||||
"timetickets": {
|
||||
"edit": "Time Tickets -> Edit",
|
||||
"editcommitted": "Time Tickets -> Edit Committed",
|
||||
"enter": "Time Tickets -> Enter",
|
||||
"list": "Time Tickets -> List",
|
||||
"shiftedit": "Time Tickets -> Shift Edit"
|
||||
},
|
||||
"ttapprovals": {
|
||||
"approve": "Time Ticket Approval -> Approve",
|
||||
"view": "Time Ticket Approval -> View"
|
||||
},
|
||||
"users": {
|
||||
"editaccess": "Users -> Edit access"
|
||||
}
|
||||
@@ -1907,6 +1912,7 @@
|
||||
"shop_vendors": "Vendors",
|
||||
"temporarydocs": "Temporary Documents",
|
||||
"timetickets": "Time Tickets",
|
||||
"ttapprovals": "Time Ticket Approvals",
|
||||
"vehicles": "Vehicles"
|
||||
},
|
||||
"jobsactions": {
|
||||
@@ -2649,10 +2655,12 @@
|
||||
"clockhours": "Clock Hours",
|
||||
"clockoff": "Clock Off",
|
||||
"clockon": "Clocked In",
|
||||
"committed": "Committed",
|
||||
"cost_center": "Cost Center",
|
||||
"date": "Ticket Date",
|
||||
"efficiency": "Efficiency",
|
||||
"employee": "Employee",
|
||||
"employee_team": "Employee Team",
|
||||
"flat_rate": "Flat Rate?",
|
||||
"memo": "Memo",
|
||||
"productivehrs": "Productive Hours",
|
||||
@@ -2739,6 +2747,7 @@
|
||||
"shop-vendors": "Vendors",
|
||||
"temporarydocs": "Temporary Documents",
|
||||
"timetickets": "Time Tickets",
|
||||
"ttapprovals": "Time Ticket Approvals",
|
||||
"vehicle-details": "Vehicle: {{vehicle}}",
|
||||
"vehicles": "Vehicles"
|
||||
},
|
||||
@@ -2784,9 +2793,15 @@
|
||||
"shop_vendors": "Vendors | $t(titles.app)",
|
||||
"temporarydocs": "Temporary Documents | $t(titles.app)",
|
||||
"timetickets": "Time Tickets | $t(titles.app)",
|
||||
"ttapprovals": "Time Ticket Approvals | $t(titles.app)",
|
||||
"vehicledetail": "Vehicle Details {{vehicle}} | $t(titles.app)",
|
||||
"vehicles": "All Vehicles | $t(titles.app)"
|
||||
},
|
||||
"tt_approvals": {
|
||||
"actions": {
|
||||
"approveselected": "Approve Selected"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"changepassword": "Change Password",
|
||||
|
||||
@@ -435,10 +435,15 @@
|
||||
},
|
||||
"timetickets": {
|
||||
"edit": "",
|
||||
"editcommitted": "",
|
||||
"enter": "",
|
||||
"list": "",
|
||||
"shiftedit": ""
|
||||
},
|
||||
"ttapprovals": {
|
||||
"approve": "",
|
||||
"view": ""
|
||||
},
|
||||
"users": {
|
||||
"editaccess": ""
|
||||
}
|
||||
@@ -1907,6 +1912,7 @@
|
||||
"shop_vendors": "Vendedores",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicles": "Vehículos"
|
||||
},
|
||||
"jobsactions": {
|
||||
@@ -2649,10 +2655,12 @@
|
||||
"clockhours": "",
|
||||
"clockoff": "",
|
||||
"clockon": "",
|
||||
"committed": "",
|
||||
"cost_center": "",
|
||||
"date": "",
|
||||
"efficiency": "",
|
||||
"employee": "",
|
||||
"employee_team": "",
|
||||
"flat_rate": "",
|
||||
"memo": "",
|
||||
"productivehrs": "",
|
||||
@@ -2739,6 +2747,7 @@
|
||||
"shop-vendors": "",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicle-details": "",
|
||||
"vehicles": ""
|
||||
},
|
||||
@@ -2784,9 +2793,15 @@
|
||||
"shop_vendors": "Vendedores | $t(titles.app)",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicledetail": "Detalles del vehículo {{vehicle}} | $t(titles.app)",
|
||||
"vehicles": "Todos los vehiculos | $t(titles.app)"
|
||||
},
|
||||
"tt_approvals": {
|
||||
"actions": {
|
||||
"approveselected": ""
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"changepassword": "",
|
||||
|
||||
@@ -435,10 +435,15 @@
|
||||
},
|
||||
"timetickets": {
|
||||
"edit": "",
|
||||
"editcommitted": "",
|
||||
"enter": "",
|
||||
"list": "",
|
||||
"shiftedit": ""
|
||||
},
|
||||
"ttapprovals": {
|
||||
"approve": "",
|
||||
"view": ""
|
||||
},
|
||||
"users": {
|
||||
"editaccess": ""
|
||||
}
|
||||
@@ -1907,6 +1912,7 @@
|
||||
"shop_vendors": "Vendeurs",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicles": "Véhicules"
|
||||
},
|
||||
"jobsactions": {
|
||||
@@ -2649,10 +2655,12 @@
|
||||
"clockhours": "",
|
||||
"clockoff": "",
|
||||
"clockon": "",
|
||||
"committed": "",
|
||||
"cost_center": "",
|
||||
"date": "",
|
||||
"efficiency": "",
|
||||
"employee": "",
|
||||
"employee_team": "",
|
||||
"flat_rate": "",
|
||||
"memo": "",
|
||||
"productivehrs": "",
|
||||
@@ -2739,6 +2747,7 @@
|
||||
"shop-vendors": "",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicle-details": "",
|
||||
"vehicles": ""
|
||||
},
|
||||
@@ -2784,9 +2793,15 @@
|
||||
"shop_vendors": "Vendeurs | $t(titles.app)",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
"vehicledetail": "Détails du véhicule {{vehicle} | $t(titles.app)",
|
||||
"vehicles": "Tous les véhicules | $t(titles.app)"
|
||||
},
|
||||
"tt_approvals": {
|
||||
"actions": {
|
||||
"approveselected": ""
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"actions": {
|
||||
"changepassword": "",
|
||||
|
||||
Reference in New Issue
Block a user