Added multiple labor rates to employees

IO-548
This commit is contained in:
Patrick Fic
2021-01-21 10:45:59 -08:00
parent 4b75f2eccd
commit 6d1dbe5f63
21 changed files with 429 additions and 70 deletions

View File

@@ -9239,6 +9239,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>newrate</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> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -9522,6 +9543,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>rate</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>termination_date</name> <name>termination_date</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -35,11 +35,9 @@ const EmployeeSearchSelect = (
key={o.id} key={o.id}
value={o.id} value={o.id}
search={`${o.employee_number} ${o.first_name} ${o.last_name}`} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}
discount={o.discount}
> >
<Space> <Space>
{`${o.employee_number} ${o.first_name} ${o.last_name}`} {`${o.employee_number} ${o.first_name} ${o.last_name}`}
<Tag color="blue">{o.cost_center}</Tag>
<Tag color="green"> <Tag color="green">
{o.flat_rate {o.flat_rate

View File

@@ -87,7 +87,7 @@ export function JobCostingModalComponent({ bodyshop, job }) {
].add( ].add(
Dinero({ Dinero({
amount: Math.round((ticket_val.rate || 0) * 100), amount: Math.round((ticket_val.rate || 0) * 100),
}).multiply(ticket_val.actualhrs || 0) }).multiply(ticket_val.actualhrs || ticket_val.productivehrs || 0)
); );
return ticket_acc; return ticket_acc;

View File

@@ -1,4 +1,5 @@
import { Button, Form, Input, Select, Switch } from "antd"; import { DeleteFilled } from "@ant-design/icons";
import { Button, Form, Input, InputNumber, Select, Switch } from "antd";
import moment from "moment"; import moment from "moment";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useApolloClient } from "react-apollo"; import { useApolloClient } from "react-apollo";
@@ -8,7 +9,8 @@ import { createStructuredSelector } from "reselect";
import { QUERY_USERS_BY_EMAIL } from "../../graphql/employees.queries"; import { QUERY_USERS_BY_EMAIL } from "../../graphql/employees.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -131,36 +133,74 @@ export function ShopEmployeesFormComponent({
<FormDatePicker /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.List name={["rates"]}>
label={t("employees.fields.cost_center")} {(fields, { add, remove, move }) => {
name="cost_center" return (
rules={[ <div>
{ {fields.map((field, index) => (
required: true, <Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
message: t("general.validation.required"), <LayoutFormRow grow>
}, <Form.Item
]} label={t("employees.fields.cost_center")}
> key={`${index}`}
<Select> name={[field.name, "cost_center"]}
{bodyshop.md_responsibility_centers.costs.map((c) => ( rules={[
<Select.Option key={c.name} value={c.name}> {
{c.name} required: true,
</Select.Option> message: t("general.validation.required"),
))} },
</Select> ]}
</Form.Item> >
<Form.Item <Select>
label={t("employees.fields.base_rate")} {bodyshop.md_responsibility_centers.costs.map((c) => (
name="base_rate" <Select.Option key={c.name} value={c.name}>
rules={[ {c.name}
{ </Select.Option>
required: true, ))}
message: t("general.validation.required"), </Select>
}, </Form.Item>
]} <Form.Item
> label={t("employees.fields.rate")}
<CurrencyInput /> key={`${index}`}
</Form.Item> name={[field.name, "rate"]}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<InputNumber min={0} precision={2} />
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</LayoutFormRow>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("employees.actions.newrate")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item <Form.Item
label={t("employees.fields.user_email")} label={t("employees.fields.user_email")}
name="user_email" name="user_email"

View File

@@ -13,7 +13,6 @@ export default function TimeTicketModalComponent({
employeeAutoCompleteOptions, employeeAutoCompleteOptions,
loadLineTicketData, loadLineTicketData,
lineTicketData, lineTicketData,
responsibilityCenters,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -92,21 +91,38 @@ export default function TimeTicketModalComponent({
> >
<InputNumber min={0} precision={1} /> <InputNumber min={0} precision={1} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="cost_center" shouldUpdate={(prev, cur) => prev.employeeid !== cur.employeeid}
label={t("timetickets.fields.cost_center")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
> >
<Select> {() => {
{responsibilityCenters.costs.map((item) => ( const employeeId = form.getFieldValue("employeeid");
<Select.Option key={item.name}>{item.name}</Select.Option> const emps =
))} employeeAutoCompleteOptions &&
</Select> employeeAutoCompleteOptions.filter((e) => e.id === employeeId)[0];
return (
<Form.Item
name="cost_center"
label={t("timetickets.fields.cost_center")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<Select>
{emps &&
emps.rates.map((item) => (
<Select.Option key={item.cost_center}>
{item.cost_center}
</Select.Option>
))}
</Select>
</Form.Item>
);
}}
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="ciecacode" name="ciecacode"

View File

@@ -44,24 +44,42 @@ export function TimeTicketModalContainer({
); );
const handleFinish = (values) => { const handleFinish = (values) => {
const emps = EmployeeAutoCompleteData.employees.filter(
(e) => e.id === values.employeeid
);
if (timeTicketModal.context.id) { if (timeTicketModal.context.id) {
updateTicket({ updateTicket({
variables: { variables: {
timeticketId: timeTicketModal.context.id, timeticketId: timeTicketModal.context.id,
timeticket: values, timeticket: {
...values,
rate:
emps.length === 1
? emps[0].rates.filter(
(r) => r.cost_center === values.cost_center
)[0].rate
: null,
},
}, },
}) })
.then(handleMutationSuccess) .then(handleMutationSuccess)
.catch(handleMutationError); .catch(handleMutationError);
} else { } else {
//Get selected employee rate. //Get selected employee rate.
const rate = EmployeeAutoCompleteData.employees.filter(
(i) => i.id === values.employeeid
)[0].base_rate;
insertTicket({ insertTicket({
variables: { variables: {
timeTicketInput: [{ ...values, rate, bodyshopid: bodyshop.id }], timeTicketInput: [
{
...values,
rate:
emps.length === 1
? emps[0].rates.filter(
(r) => r.cost_center === values.cost_center
)[0].rate
: null,
bodyshopid: bodyshop.id,
},
],
}, },
}) })
.then(handleMutationSuccess) .then(handleMutationSuccess)
@@ -196,7 +214,6 @@ export function TimeTicketModalContainer({
employeeAutoCompleteOptions={ employeeAutoCompleteOptions={
EmployeeAutoCompleteData && EmployeeAutoCompleteData.employees EmployeeAutoCompleteData && EmployeeAutoCompleteData.employees
} }
responsibilityCenters={bodyshop.md_responsibility_centers || null}
loadLineTicketData={loadLineTicketData} loadLineTicketData={loadLineTicketData}
lineTicketData={ lineTicketData={
lineTicketData ? lineTicketData : { joblines: [], timetickets: [] } lineTicketData ? lineTicketData : { joblines: [], timetickets: [] }

View File

@@ -81,7 +81,7 @@ export const QUERY_BODYSHOP = gql`
first_name first_name
last_name last_name
employee_number employee_number
cost_center rates
} }
} }
} }

View File

@@ -11,8 +11,7 @@ export const QUERY_EMPLOYEES = gql`
termination_date termination_date
hire_date hire_date
flat_rate flat_rate
cost_center rates
base_rate
pin pin
user_email user_email
} }
@@ -41,8 +40,7 @@ export const UPDATE_EMPLOYEE = gql`
termination_date termination_date
hire_date hire_date
flat_rate flat_rate
cost_center rates
base_rate
pin pin
user_email user_email
} }

View File

@@ -302,6 +302,7 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
rate rate
cost_center cost_center
actualhrs actualhrs
productivehrs
} }
} }
} }

View File

@@ -616,7 +616,8 @@
}, },
"employees": { "employees": {
"actions": { "actions": {
"new": "New Employee" "new": "New Employee",
"newrate": "New Rate"
}, },
"errors": { "errors": {
"delete": "Error encountered while deleting employee. {{message}}", "delete": "Error encountered while deleting employee. {{message}}",
@@ -634,6 +635,7 @@
"hire_date": "Hire Date", "hire_date": "Hire Date",
"last_name": "Last Name", "last_name": "Last Name",
"pin": "Tech Console PIN", "pin": "Tech Console PIN",
"rate": "Rate",
"termination_date": "Termination Date", "termination_date": "Termination Date",
"user_email": "User Email" "user_email": "User Email"
}, },

View File

@@ -616,7 +616,8 @@
}, },
"employees": { "employees": {
"actions": { "actions": {
"new": "Nuevo empleado" "new": "Nuevo empleado",
"newrate": ""
}, },
"errors": { "errors": {
"delete": "Se encontró un error al eliminar al empleado. {{message}}", "delete": "Se encontró un error al eliminar al empleado. {{message}}",
@@ -634,6 +635,7 @@
"hire_date": "Fecha de contratación", "hire_date": "Fecha de contratación",
"last_name": "Apellido", "last_name": "Apellido",
"pin": "", "pin": "",
"rate": "",
"termination_date": "Fecha de conclusión", "termination_date": "Fecha de conclusión",
"user_email": "" "user_email": ""
}, },

View File

@@ -616,7 +616,8 @@
}, },
"employees": { "employees": {
"actions": { "actions": {
"new": "Nouvel employé" "new": "Nouvel employé",
"newrate": ""
}, },
"errors": { "errors": {
"delete": "Erreur rencontrée lors de la suppression de l'employé. {{message}}", "delete": "Erreur rencontrée lors de la suppression de l'employé. {{message}}",
@@ -634,6 +635,7 @@
"hire_date": "Date d'embauche", "hire_date": "Date d'embauche",
"last_name": "Nom de famille", "last_name": "Nom de famille",
"pin": "", "pin": "",
"rate": "",
"termination_date": "Date de résiliation", "termination_date": "Date de résiliation",
"user_email": "" "user_email": ""
}, },

View File

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

View File

@@ -0,0 +1,6 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."employees" ADD COLUMN "rates" jsonb NOT NULL DEFAULT
jsonb_build_array();
type: run_sql

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,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
- created_at
- employee_number
- first_name
- flat_rate
- hire_date
- id
- last_name
- pin
- rates
- shopid
- termination_date
- updated_at
- user_email
set: {}
role: user
table:
name: employees
schema: public
type: create_insert_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,39 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- active
- created_at
- employee_number
- first_name
- flat_rate
- hire_date
- id
- last_name
- pin
- rates
- 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,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: employees
schema: public
type: drop_update_permission
- args:
permission:
columns:
- active
- created_at
- employee_number
- first_name
- flat_rate
- hire_date
- id
- last_name
- pin
- rates
- 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

@@ -1550,8 +1550,6 @@ tables:
_eq: true _eq: true
columns: columns:
- active - active
- base_rate
- cost_center
- created_at - created_at
- employee_number - employee_number
- first_name - first_name
@@ -1560,6 +1558,7 @@ tables:
- id - id
- last_name - last_name
- pin - pin
- rates
- shopid - shopid
- termination_date - termination_date
- updated_at - updated_at
@@ -1569,8 +1568,6 @@ tables:
permission: permission:
columns: columns:
- active - active
- base_rate
- cost_center
- created_at - created_at
- employee_number - employee_number
- first_name - first_name
@@ -1579,6 +1576,7 @@ tables:
- id - id
- last_name - last_name
- pin - pin
- rates
- shopid - shopid
- termination_date - termination_date
- updated_at - updated_at
@@ -1597,8 +1595,6 @@ tables:
permission: permission:
columns: columns:
- active - active
- base_rate
- cost_center
- created_at - created_at
- employee_number - employee_number
- first_name - first_name
@@ -1607,6 +1603,7 @@ tables:
- id - id
- last_name - last_name
- pin - pin
- rates
- shopid - shopid
- termination_date - termination_date
- updated_at - updated_at