Compare commits

..

5 Commits

Author SHA1 Message Date
Allan Carr
2336617077 IO-3020 Fix PrintCenter Upsell restrictions
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-12-16 12:42:21 -08:00
Patrick Fic
98f7147378 IO-3020 Resolve smart scheduling upsell displays when they shouldn't. 2024-12-16 10:26:03 -08:00
Patrick Fic
54b8f564e4 Merged in hotfix/IO-3001-null-cieca-scrubbing (pull request #2025)
IO-3001 Add CIECA data check for scrubbing.
2024-12-16 16:07:15 +00:00
Patrick Fic
591284c972 IO-3001 Add CIECA data check for scrubbing. 2024-12-16 08:06:39 -08:00
Dave Richer
9e0dae2adf Merged in release/2024-12-13 (pull request #2023)
[DO NOT MERGE] Release/2024-12-13 into master-AIO - IO-2968, IO-3020, IO-3036, IO-3056, IO-3096
2024-12-14 04:35:30 +00:00
6 changed files with 104 additions and 79 deletions

View File

@@ -9,6 +9,7 @@ import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import { HasFeatureAccess } from './../feature-wrapper/feature-wrapper.component';
const mapStateToProps = createStructuredSelector({
printCenterModal: selectPrintCenter,
@@ -43,10 +44,12 @@ export function PrintCenterItemComponent({
setLoading(false);
};
if (disabled || item.featureNameRestricted)
if (disabled || (item.featureNameRestricted && !HasFeatureAccess({"featureName": item.featureNameRestricted, bodyshop})))
return (
<li className="print-center-item">
<LockWrapperComponent featureName={item.featureNameRestricted}>{item.title}</LockWrapperComponent>
<LockWrapperComponent featureName={item.featureNameRestricted} bodyshop={bodyshop}>
{item.title}
</LockWrapperComponent>
</li>
);
return (

View File

@@ -8,6 +8,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
import { UpsellMaskWrapper, upsellEnum } from "../upsell/upsell.component";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -35,6 +36,28 @@ export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) {
[]
);
}, [loadData, ssbuckets]);
const hasSmartSchedulingAccess = HasFeatureAccess({ featureName: "smartscheduling", bodyshop });
const chartContents = (
<>
<RadarChart
// cx={300}
// cy={250}
// outerRadius={150}
width={800}
height={600}
data={data}
>
<PolarGrid />
<PolarAngleAxis dataKey="bucket" />
<PolarRadiusAxis angle={90} />
<Radar name="Ideal Load" dataKey="target" stroke="darkgreen" fill="white" fillOpacity={0} />
<Radar name="EOD Load" dataKey="current" stroke="dodgerblue" fill="dodgerblue" fillOpacity={0.6} />
<Tooltip />
<Legend />
</RadarChart>
</>
);
const popContent = (
<div>
@@ -48,26 +71,13 @@ export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) {
<strong>{loadData?.expectedJobCount}</strong>
</BlurWrapperComponent>
</Space>
<UpsellMaskWrapper upsell={upsellEnum().smartscheduling.general}>
<BlurWrapperComponent featureName="smartscheduling">
<RadarChart
// cx={300}
// cy={250}
// outerRadius={150}
width={800}
height={600}
data={data}
>
<PolarGrid />
<PolarAngleAxis dataKey="bucket" />
<PolarRadiusAxis angle={90} />
<Radar name="Ideal Load" dataKey="target" stroke="darkgreen" fill="white" fillOpacity={0} />
<Radar name="EOD Load" dataKey="current" stroke="dodgerblue" fill="dodgerblue" fillOpacity={0.6} />
<Tooltip />
<Legend />
</RadarChart>
</BlurWrapperComponent>
</UpsellMaskWrapper>
<BlurWrapperComponent featureName="smartscheduling">
{hasSmartSchedulingAccess ? (
chartContents
) : (
<UpsellMaskWrapper upsell={upsellEnum().smartscheduling.general}>{chartContents}</UpsellMaskWrapper>
)}
</BlurWrapperComponent>
</div>
);

View File

@@ -18,6 +18,7 @@ import OwnerNameDisplay from "../owner-name-display/owner-name-display.component
import ScheduleBlockDay from "../schedule-block-day/schedule-block-day.component";
import ScheduleCalendarHeaderGraph from "./schedule-calendar-header-graph.component";
import UpsellComponent, { upsellEnum, UpsellMaskWrapper } from "../upsell/upsell.component";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -52,6 +53,7 @@ export function ScheduleCalendarHeaderComponent({
const { t } = useTranslation();
const loadData = load[date.toISOString().substr(0, 10)];
const hasSmartSchedulingAccess = HasFeatureAccess({ featureName: "smartscheduling", bodyshop });
const jobsOutPopup = () => (
<div onClick={(e) => e.stopPropagation()}>
@@ -89,9 +91,11 @@ export function ScheduleCalendarHeaderComponent({
)}
</tbody>
</table>
<Card style={{ maxWidth: "30rem" }}>
<UpsellComponent size="small" upsell={upsellEnum().smartscheduling.hrsdelta} />
</Card>
{!hasSmartSchedulingAccess && (
<Card style={{ maxWidth: "30rem" }}>
<UpsellComponent size="small" upsell={upsellEnum().smartscheduling.hrsdelta} />
</Card>
)}
</div>
);
@@ -129,9 +133,11 @@ export function ScheduleCalendarHeaderComponent({
</BlurWrapperComponent>
</tr>
)}
<Card style={{ maxWidth: "30rem" }}>
<UpsellComponent size="small" upsell={upsellEnum().smartscheduling.hrsdelta} />
</Card>
{!hasSmartSchedulingAccess && (
<Card style={{ maxWidth: "30rem" }}>
<UpsellComponent size="small" upsell={upsellEnum().smartscheduling.hrsdelta} />
</Card>
)}
</tbody>
</table>
</div>

View File

@@ -16,7 +16,7 @@ import ScheduleDayViewContainer from "../schedule-day-view/schedule-day-view.con
import ScheduleExistingAppointmentsList from "../schedule-existing-appointments-list/schedule-existing-appointments-list.component";
import "./schedule-job-modal.scss";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import { BlurWrapper } from "../feature-wrapper/blur-wrapper.component";
import BlurWrapper from "../feature-wrapper/blur-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({
@@ -138,7 +138,7 @@ export function ScheduleJobModalComponent({
</BlurWrapper>
</Button>
))}
{smartOptions.length > 1 && hasSmartSchedulingAccess && (
{!smartOptions.length > 1 && hasSmartSchedulingAccess && (
<UpsellComponent upsell={upsellEnum().smartscheduling.general} />
)}
</Space>

View File

@@ -19,7 +19,6 @@ export default function UpsellComponent({ featureName, subFeatureName, upsell, d
const { t } = useTranslation();
const resultProps = upsell || upsellEnum[featureName][subFeatureName];
console.trace("***LOG ~ file: upsell.component.jsx:22 ~ UpsellComponent ~ resultProps:", resultProps);
const componentRef = useRef(null);
useEffect(() => {

View File

@@ -49,7 +49,8 @@ exports.totalsSsu = async function (req, res) {
} catch (error) {
logger.log("job-totals-ssu-USA-error", "ERROR", req?.user?.email, id, {
jobid: id,
error: error.message
error: error.message,
stack: error.stack
});
res.status(503).send();
}
@@ -68,59 +69,63 @@ async function TotalsServerSide(req, res) {
ret.additional = CalculateAdditional(job);
ret.totals = CalculateTaxesTotals(job, ret);
// Sub total scrubbbing.
const emsTotal =
job.cieca_ttl.data.n_ttl_amt === job.cieca_ttl.data.g_ttl_amt //It looks like sometimes, gross and net are the same, but they shouldn't be.
? job.cieca_ttl.data.n_ttl_amt - job.cieca_ttl.data.g_tax
: job.cieca_ttl.data.g_ttl_amt - job.cieca_ttl.data.g_tax; //If they are, adjust the gross total down by the tax amount.
const ttlDifference =
emsTotal -
ret.totals.subtotal
.add(
Dinero({
amount: Math.round((job.adjustment_bottom_line || 0) * 100)
}).multiply(-1) //Add back in the adjustment to the subtotal. We don't want to scrub it twice.
)
.getAmount() /
//If we don't have the CIECA_TTL, we can't scrub. E.g. a private estimate.
if (job.cieca_ttl) {
// Sub total scrubbbing.
const emsTotal =
job.cieca_ttl.data.n_ttl_amt === job.cieca_ttl.data.g_ttl_amt //It looks like sometimes, gross and net are the same, but they shouldn't be.
? job.cieca_ttl.data.n_ttl_amt - job.cieca_ttl.data.g_tax
: job.cieca_ttl.data.g_ttl_amt - job.cieca_ttl.data.g_tax; //If they are, adjust the gross total down by the tax amount.
const ttlDifference =
emsTotal -
ret.totals.subtotal
.add(
Dinero({
amount: Math.round((job.adjustment_bottom_line || 0) * 100)
}).multiply(-1) //Add back in the adjustment to the subtotal. We don't want to scrub it twice.
)
.getAmount() /
100;
if (Math.abs(ttlDifference) > 0.0) {
//If difference is greater than a pennny, we need to adjust it.
ret.totals.ttl_adjustment = Dinero({ amount: Math.round(ttlDifference * 100) });
ret.totals.subtotal = ret.totals.subtotal.add(ret.totals.ttl_adjustment);
ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_adjustment);
ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_adjustment);
logger.log("job-totals-USA-ttl-adj", "DEBUG", null, job.id, {
adjAmount: ttlDifference
});
}
//Taxes Scrubbing
const emsTaxTotal = job.cieca_ttl.data.g_tax;
const totalUsTaxes =
(ret.totals.us_sales_tax_breakdown.ty1Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty2Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty3Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty4Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty5Tax.getAmount()) /
100;
const ttlTaxDifference = emsTaxTotal - totalUsTaxes;
if (Math.abs(ttlDifference) > 0.0) {
//If difference is greater than a pennny, we need to adjust it.
ret.totals.ttl_adjustment = Dinero({ amount: Math.round(ttlDifference * 100) });
ret.totals.subtotal = ret.totals.subtotal.add(ret.totals.ttl_adjustment);
ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_adjustment);
ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_adjustment);
logger.log("job-totals-USA-ttl-adj", "DEBUG", null, job.id, {
adjAmount: ttlDifference
});
}
//Taxes Scrubbing
const emsTaxTotal = job.cieca_ttl.data.g_tax;
const totalUsTaxes =
(ret.totals.us_sales_tax_breakdown.ty1Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty2Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty3Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty4Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty5Tax.getAmount()) /
100;
const ttlTaxDifference = emsTaxTotal - totalUsTaxes;
if (Math.abs(ttlTaxDifference) > 0.0) {
//If difference is greater than a pennny, we need to adjust it.
ret.totals.ttl_tax_adjustment = Dinero({ amount: Math.round(ttlTaxDifference * 100) });
ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_tax_adjustment);
ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_tax_adjustment);
logger.log("job-totals-USA-ttl-tax-adj", "DEBUG", null, job.id, {
adjAmount: ttlTaxDifference
});
if (Math.abs(ttlTaxDifference) > 0.0) {
//If difference is greater than a pennny, we need to adjust it.
ret.totals.ttl_tax_adjustment = Dinero({ amount: Math.round(ttlTaxDifference * 100) });
ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_tax_adjustment);
ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_tax_adjustment);
logger.log("job-totals-USA-ttl-tax-adj", "DEBUG", null, job.id, {
adjAmount: ttlTaxDifference
});
}
}
return ret;
} catch (error) {
logger.log("job-totals-ssu-USA-error", "ERROR", req.user?.email, job.id, {
jobid: job.id,
error
error: error.message,
stack: error.stack
});
res.status(400).send(JSON.stringify(error));
}
@@ -150,7 +155,8 @@ async function Totals(req, res) {
} catch (error) {
logger.log("job-totals-USA-error", "ERROR", req.user.email, job.id, {
jobid: job.id,
error
error: error.message,
stack: error.stack
});
res.status(400).send(JSON.stringify(error));
}
@@ -1075,7 +1081,8 @@ function CalculateTaxesTotals(job, otherTotals) {
}
} catch (error) {
logger.log("job-totals-USA - PFP Calculation Error", "error", null, job.id, {
error: error.message
error: error.message,
stack: error.stack
});
}
});