Final changes for RO closer.

This commit is contained in:
Patrick Fic
2024-04-03 14:18:45 -07:00
parent 815ada0516
commit 5e63ce7271
18 changed files with 1266 additions and 494 deletions

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Alert, Card } from 'antd';
import { useTranslation } from 'react-i18next';
@@ -20,7 +20,7 @@ const mapDispatchToProps = (dispatch) => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRoGuardProfit);
export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form }) {
export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallback }) {
const { t } = useTranslation();
const total = useMemo(() => {
@@ -39,6 +39,12 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form }) {
return Dinero({ amount: 0 }).subtract(total);
}, [job, total]);
useEffect(() => {
if (balance.getAmount !== 0) {
warningCallback({ key: 'ar', warning: t('jobs.labels.outstanding_ar') });
}
}, [balance, t, warningCallback]);
return (
<Card title={t('jobs.labels.accountsreceivable')} style={{ height: '100%' }}>
<DataLabel label={t('payments.labels.totalpayments')}>{total.toFormat()}</DataLabel>
@@ -49,7 +55,11 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form }) {
{balance.toFormat()}
</DataLabel>
{balance.getAmount !== 0 && (
<Alert style={{margin: "8px 0px"}} type="warning" message={t('jobs.labels.outstanding_ar')} />
<Alert
style={{ margin: '8px 0px' }}
type="warning"
message={t('jobs.labels.outstanding_ar')}
/>
)}
</Card>
);

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useState } from 'react';
import { Badge, Card, Col, Collapse, Row, Space } from 'antd';
import { LockOutlined } from '@ant-design/icons';
import { Badge, Card, Col, Collapse, Form, Input, Row, Space, Tooltip } from 'antd';
import { useTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
@@ -25,16 +25,20 @@ const mapDispatchToProps = (dispatch) => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRoGuardContainer);
const colSpans = {
md: 24,
lg: 12,
xl: 8,
};
export function JobCloseRoGuardContainer({ job, jobRO, bodyshop, form }) {
const { t } = useTranslation();
const [warnings, setWarnings] = useState([]);
const warningCallback = useCallback((warning) => setWarnings((state) => [...state, warning]), []);
const warningCallback = useCallback(
({ key, warning }) =>
setWarnings((state) => {
if (state.find((s) => s.key === key)) return state;
return [...state, { key, warning }];
}),
[]
);
if (!bodyshop?.md_ro_guard?.enabled) return null;
return (
<>
@@ -48,8 +52,15 @@ export function JobCloseRoGuardContainer({ job, jobRO, bodyshop, form }) {
}
>
<ul>
{warnings.map((warning, index) => (
<li key={index}>{warning}</li>
{warnings.map((w, index) => (
<li key={index}>
{bodyshop.md_ro_guard[`enforce_${w.key}`] && (
<Tooltip title={t('jobs.labels.ro_guard.enforced')}>
<LockOutlined style={{ color: 'tomato', marginRight: '8px' }} />
</Tooltip>
)}
{w.warning}
</li>
))}
</ul>
</Card>
@@ -72,16 +83,135 @@ export function JobCloseRoGuardContainer({ job, jobRO, bodyshop, form }) {
</Col>
</Row>
</Col>
<Col className="ro-guard-col" md={24} lg={8}>
<Col className="ro-guard-col-50" md={24} lg={8}>
<JobCloseRoGuardSublet job={job} warningCallback={warningCallback} />
<JobCloseRoGuardPpd job={job} warningCallback={warningCallback} />
</Col>
<Col className="ro-guard-col" md={24} lg={10}>
<JobCloseRoGuardLabor job={job} form={form} warningCallback={warningCallback} />
</Col>
<Col className="ro-guard-col" {...colSpans}>
<JobCloseRoGuardPpd job={job} warningCallback={warningCallback} />
</Col>
</Row>
<Form.Item
name="masterbypass"
label={t('jobs.labels.masterbypass')}
rules={[
({ getFieldValue }) => ({
validator(_, value) {
if (
!PasswordCheck({ bodyshop, value }) &&
bodyshop.md_ro_guard.enforce_bills &&
warnings.find((w) => w.key === 'bills')
) {
return Promise.reject(t('jobs.labels.ro_guard.enforce_bills'));
}
return Promise.resolve();
},
}),
({ getFieldValue }) => ({
validator(_, value) {
if (
!PasswordCheck({ bodyshop, value }) &&
bodyshop.md_ro_guard.enforce_cm &&
warnings.find((w) => w.key === 'cm')
) {
return Promise.reject(
t('translation.jobs.labels.ro_guard.enforce_validation', {
message: t('jobs.labels.ro_guard.enforce_cm'),
})
);
}
return Promise.resolve();
},
}),
({ getFieldValue }) => ({
validator(_, value) {
if (
!PasswordCheck({ bodyshop, value }) &&
bodyshop.md_ro_guard.enforce_profit &&
warnings.find((w) => w.key === 'profit')
) {
return Promise.reject(
t('translation.jobs.labels.ro_guard.enforce_validation', {
message: t('jobs.labels.ro_guard.enforce_profit'),
})
);
}
return Promise.resolve();
},
}),
({ getFieldValue }) => ({
validator(_, value) {
if (
!PasswordCheck({ bodyshop, value }) &&
bodyshop.md_ro_guard.enforce_ar &&
warnings.find((w) => w.key === 'ar')
) {
return Promise.reject(
t('translation.jobs.labels.ro_guard.enforce_validation', {
message: t('jobs.labels.ro_guard.enforce_ar'),
})
);
}
return Promise.resolve();
},
}),
({ getFieldValue }) => ({
validator(_, value) {
if (
!PasswordCheck({ bodyshop, value }) &&
bodyshop.md_ro_guard.enforce_sublet &&
warnings.find((w) => w.key === 'sublet')
) {
return Promise.reject(
t('translation.jobs.labels.ro_guard.enforce_validation', {
message: t('jobs.labels.ro_guard.enforce_sublet'),
})
);
}
return Promise.resolve();
},
}),
({ getFieldValue }) => ({
validator(_, value) {
if (
!PasswordCheck({ bodyshop, value }) &&
bodyshop.md_ro_guard.enforce_ppd &&
warnings.find((w) => w.key === 'ppd')
) {
return Promise.reject(
t('translation.jobs.labels.ro_guard.enforce_validation', {
message: t('jobs.labels.ro_guard.enforce_ppd'),
})
);
}
return Promise.resolve();
},
}),
({ getFieldValue }) => ({
validator(_, value) {
if (
!PasswordCheck({ bodyshop, value }) &&
bodyshop.md_ro_guard.enforce_labor &&
warnings.find((w) => w.key === 'labor')
) {
return Promise.reject(
t('translation.jobs.labels.ro_guard.enforce_validation', {
message: t('jobs.labels.ro_guard.enforce_labor'),
})
);
}
return Promise.resolve();
},
}),
]}
>
<Input
prefix={<LockOutlined />}
type="password"
placeholder="Password"
disabled={jobRO}
/>
</Form.Item>
</Collapse.Panel>
<Collapse.Panel header={t('jobs.labels.performance')}>
@@ -95,3 +225,7 @@ export function JobCloseRoGuardContainer({ job, jobRO, bodyshop, form }) {
</>
);
}
function PasswordCheck({ bodyshop, value }) {
return value === bodyshop?.md_ro_guard?.masterbypass;
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Card, Table } from 'antd';
import { Alert, Card, Table } from 'antd';
import { t } from 'i18next';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
@@ -18,11 +18,17 @@ const mapDispatchToProps = (dispatch) => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRGuardPpd);
export function JobCloseRGuardPpd({ job, jobRO, bodyshop, form }) {
const subletsNotDone = job?.joblines.filter(
export function JobCloseRGuardPpd({ job, jobRO, bodyshop, form, warningCallback }) {
const linesWithPPD = job?.joblines.filter(
(j) => j.act_price_before_ppc !== 0 && j.act_price_before_ppc !== null
);
useEffect(() => {
if (linesWithPPD.length > 0) {
warningCallback({ key: 'ppd', warning: t('jobs.labels.outstanding_sublets') });
}
}, [linesWithPPD.length, warningCallback]);
const columns = [
{
title: t('joblines.fields.line_desc'),
@@ -71,15 +77,22 @@ export function JobCloseRGuardPpd({ job, jobRO, bodyshop, form }) {
];
return (
<Card title={t('jobs.labels.subletsnotcompleted')}>
<Card title={t('jobs.labels.ppdnotexported')}>
<Table
dataSource={subletsNotDone}
dataSource={linesWithPPD}
columns={columns}
pagination={false}
rowKey="id"
bordered
size="small"
/>
{linesWithPPD.length > 0 && (
<Alert
style={{ margin: '8px 0px' }}
type="warning"
message={t('jobs.labels.outstanding_ppd')}
/>
)}
</Card>
);
}

View File

@@ -42,11 +42,11 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallb
}, [job.id]);
const enforceProfitPassword =
parseFloat(costingData?.summaryData.gppercent) < bodyshop?.md_ro_guard?.totalgppercent_minimum; //TODO Add bodyshop related values.
parseFloat(costingData?.summaryData.gppercent) < bodyshop?.md_ro_guard?.totalgppercent_minimum;
useEffect(() => {
if (enforceProfitPassword && typeof warningCallback === 'function') {
warningCallback(t('jobs.labels.profitbypassrequired'));
warningCallback({ key: 'profit', warning: t('jobs.labels.profitbypassrequired') });
}
}, [enforceProfitPassword, t, warningCallback]);
@@ -55,32 +55,6 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallb
return (
<Card title={t('jobs.labels.profits')} style={{ height: '100%' }}>
<JobCostingStatistics summaryData={costingData?.summaryData} onlyGP />
{enforceProfitPassword && (
<Form.Item
name="profitbypasspassword"
label={t('jobs.labels.profitbypasspassword')}
rules={[
{
required: enforceProfitPassword,
},
({ getFieldValue }) => ({
validator(_, value) {
if (
parseFloat(costingData?.summaryData.gppercent) <
bodyshop?.md_ro_guard?.totalgppercent_minimum &&
value !== bodyshop.md_ro_guard.profitbypasspassword
) {
return Promise.reject(t('jobs.labels.profitbypassrequired'));
}
return Promise.resolve();
},
}),
]}
>
<Input prefix={<LockOutlined />} type="password" placeholder="Password" />
</Form.Item>
)}
</Card>
);
}

View File

@@ -3,3 +3,8 @@
height: 100%;
}
}
.ro-guard-col-50 {
.ant-card {
height: 50%;
}
}

View File

@@ -63,7 +63,7 @@ export function JobCloseRGuardSublet({ job, jobRO, bodyshop, form, warningCallba
useEffect(() => {
if (subletsNotDone.length > 0) {
warningCallback(t('jobs.labels.outstanding_sublets'));
warningCallback({ key: 'sublet', warning: t('jobs.labels.outstanding_sublets') });
}
}, [subletsNotDone.length, warningCallback]);

View File

@@ -18,7 +18,6 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRoGuardTTLif
export function JobCloseRoGuardTTLifeCycle({ job, jobRO, bodyshop, form }) {
return (
<div>
//TODO Add Touch Time
<JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses} />
</div>
);