updates on the profitability for the testing
This commit is contained in:
@@ -1604,36 +1604,43 @@ const Dispatch = ({
|
|||||||
|
|
||||||
const totalDailyProfit = useMemo(() => {
|
const totalDailyProfit = useMemo(() => {
|
||||||
let profit = 0;
|
let profit = 0;
|
||||||
const slotRiders = {};
|
const riderDailyData = {};
|
||||||
|
|
||||||
liveRows.forEach(r => {
|
liveRows.forEach(r => {
|
||||||
|
// Filter by selectedDate to align with daily slot aggregations
|
||||||
|
const dateStr = r.assigntime
|
||||||
|
? dayjs(r.assigntime).format('YYYY-MM-DD')
|
||||||
|
: r.deliverydate
|
||||||
|
? dayjs(r.deliverydate).format('YYYY-MM-DD')
|
||||||
|
: 'unknown';
|
||||||
|
if (selectedDate && dateStr !== selectedDate) return;
|
||||||
|
|
||||||
const batch = getRowBatch(r, selectedTimeField, BATCHES);
|
const batch = getRowBatch(r, selectedTimeField, BATCHES);
|
||||||
if (!batch || batch === 'all') return;
|
if (!batch || batch === 'all') return;
|
||||||
|
|
||||||
const riderKey = String(r.userid || r.rider_id || 'unassigned');
|
const riderKey = String(r.userid || r.rider_id || 'unassigned');
|
||||||
if (riderKey === 'unassigned' || riderKey === '0') return;
|
if (riderKey === 'unassigned' || riderKey === '0') return;
|
||||||
|
|
||||||
if (!slotRiders[batch]) slotRiders[batch] = {};
|
if (!riderDailyData[riderKey]) {
|
||||||
if (!slotRiders[batch][riderKey]) {
|
riderDailyData[riderKey] = { revenue: 0, kms: 0, batches: new Set() };
|
||||||
slotRiders[batch][riderKey] = { revenue: 0, kms: 0 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const kms = parseFloat(r.kms || r.actualkms || 0);
|
const kms = parseFloat(r.kms || r.actualkms || 0);
|
||||||
slotRiders[batch][riderKey].kms += kms;
|
riderDailyData[riderKey].kms += kms;
|
||||||
slotRiders[batch][riderKey].revenue += (kms <= 8 ? 30 : 30 + (kms - 8) * 6);
|
riderDailyData[riderKey].revenue += (kms <= 8 ? 30 : 30 + (kms - 8) * 6);
|
||||||
|
riderDailyData[riderKey].batches.add(batch);
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.values(slotRiders).forEach(riderMap => {
|
Object.values(riderDailyData).forEach(stats => {
|
||||||
Object.values(riderMap).forEach(stats => {
|
const variableCost = stats.kms * 2.5;
|
||||||
const variableCost = stats.kms * 2.5;
|
const slotCount = Math.min(stats.batches.size, 3);
|
||||||
const fixedCost = 166.67;
|
const fixedCost = slotCount * (500 / 3);
|
||||||
const totalCost = variableCost + fixedCost;
|
const totalCost = variableCost + fixedCost;
|
||||||
profit += (stats.revenue - totalCost);
|
profit += (stats.revenue - totalCost);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return profit;
|
return profit;
|
||||||
}, [liveRows, selectedTimeField, BATCHES]);
|
}, [liveRows, selectedTimeField, BATCHES, selectedDate]);
|
||||||
|
|
||||||
// Reshape flat delivery rows into the zones/riders/orders structure Dispatch consumes.
|
// Reshape flat delivery rows into the zones/riders/orders structure Dispatch consumes.
|
||||||
const liveData = useMemo(() => {
|
const liveData = useMemo(() => {
|
||||||
@@ -3830,7 +3837,14 @@ const Dispatch = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{viewMode === 'profitability' ? (
|
{viewMode === 'profitability' ? (
|
||||||
<ProfitabilitySection riders={riders} handleRiderFocus={handleRiderFocus} focusedRider={focusedRider} totalDailyProfit={totalDailyProfit} />
|
<ProfitabilitySection
|
||||||
|
riders={riders}
|
||||||
|
handleRiderFocus={handleRiderFocus}
|
||||||
|
focusedRider={focusedRider}
|
||||||
|
totalDailyProfit={totalDailyProfit}
|
||||||
|
selectedDate={selectedDate}
|
||||||
|
batches={BATCHES}
|
||||||
|
/>
|
||||||
) : viewMode === 'rider-info' ? (
|
) : viewMode === 'rider-info' ? (
|
||||||
<div className="rider-info-mode">
|
<div className="rider-info-mode">
|
||||||
<div className="ri-sidebar">
|
<div className="ri-sidebar">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useMemo, useCallback } from 'react';
|
import React, { useState, useMemo, useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import {
|
import {
|
||||||
MdTrendingUp,
|
MdTrendingUp,
|
||||||
MdTrendingDown,
|
MdTrendingDown,
|
||||||
@@ -23,7 +24,7 @@ const BASE_KM_LIMIT = 8;
|
|||||||
const EXTRA_RATE_KM = 6;
|
const EXTRA_RATE_KM = 6;
|
||||||
|
|
||||||
/** Fixed salary cost sliced per slot (₹5000 / 30 days / 1 slot). */
|
/** Fixed salary cost sliced per slot (₹5000 / 30 days / 1 slot). */
|
||||||
const FIXED_COST_PER_SLOT = 166.67;
|
const FIXED_COST_PER_SLOT = 500 / 3;
|
||||||
|
|
||||||
/** Variable fuel / wear cost per km. */
|
/** Variable fuel / wear cost per km. */
|
||||||
const VARIABLE_RATE_KM = 2.5;
|
const VARIABLE_RATE_KM = 2.5;
|
||||||
@@ -53,23 +54,75 @@ function orderRevenue(order) {
|
|||||||
return km <= BASE_KM_LIMIT ? BASE_REVENUE : BASE_REVENUE + (km - BASE_KM_LIMIT) * EXTRA_RATE_KM;
|
return km <= BASE_KM_LIMIT ? BASE_REVENUE : BASE_REVENUE + (km - BASE_KM_LIMIT) * EXTRA_RATE_KM;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcRiderMetrics(rider) {
|
const BATCHES_DEFAULT = [
|
||||||
const orders = rider.orders ?? [];
|
{ id: 'morning', name: 'Morning Batch', startHour: 0, endHour: 8 },
|
||||||
|
{ id: 'afternoon', name: 'Afternoon Batch', startHour: 9, endHour: 12.5 },
|
||||||
|
{ id: 'evening', name: 'Evening Batch', startHour: 16, endHour: 19 }
|
||||||
|
];
|
||||||
|
|
||||||
|
const getBatchForHour = (h, batches = BATCHES_DEFAULT) => {
|
||||||
|
for (const b of batches) {
|
||||||
|
if (h >= b.startHour && h < b.endHour) return b.id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRowBatch = (r, batches = BATCHES_DEFAULT) => {
|
||||||
|
const t = r?.assigntime || r?.deliverydate;
|
||||||
|
if (!t) return null;
|
||||||
|
const str = String(t).trim();
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}$/.test(str)) return null;
|
||||||
|
const d = dayjs(t);
|
||||||
|
if (!d.isValid()) return null;
|
||||||
|
return getBatchForHour(d.hour() + d.minute() / 60, batches);
|
||||||
|
};
|
||||||
|
|
||||||
|
function calcRiderMetrics(rider, selectedDate, batches) {
|
||||||
|
const orders = (rider.orders ?? []).filter((o) => {
|
||||||
|
if (!selectedDate) return true;
|
||||||
|
const dateStr = o.assigntime
|
||||||
|
? dayjs(o.assigntime).format('YYYY-MM-DD')
|
||||||
|
: o.deliverydate
|
||||||
|
? dayjs(o.deliverydate).format('YYYY-MM-DD')
|
||||||
|
: 'unknown';
|
||||||
|
return dateStr === selectedDate;
|
||||||
|
});
|
||||||
let revenue = 0;
|
let revenue = 0;
|
||||||
let kms = 0;
|
let kms = 0;
|
||||||
|
const slotsByDate = {};
|
||||||
|
|
||||||
for (const o of orders) {
|
for (const o of orders) {
|
||||||
revenue += orderRevenue(o);
|
revenue += orderRevenue(o);
|
||||||
kms += parseFloat(o.kms || o.actualkms || 0);
|
kms += parseFloat(o.kms || o.actualkms || 0);
|
||||||
|
|
||||||
|
const slot = getRowBatch(o, batches);
|
||||||
|
if (!slot) continue;
|
||||||
|
|
||||||
|
const dateStr = o.assigntime
|
||||||
|
? dayjs(o.assigntime).format('YYYY-MM-DD')
|
||||||
|
: o.deliverydate
|
||||||
|
? dayjs(o.deliverydate).format('YYYY-MM-DD')
|
||||||
|
: null;
|
||||||
|
if (!dateStr) continue;
|
||||||
|
|
||||||
|
if (!slotsByDate[dateStr]) {
|
||||||
|
slotsByDate[dateStr] = new Set();
|
||||||
|
}
|
||||||
|
slotsByDate[dateStr].add(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let slotCount = 0;
|
||||||
|
Object.values(slotsByDate).forEach((set) => {
|
||||||
|
slotCount += Math.min(set.size, 3);
|
||||||
|
});
|
||||||
|
|
||||||
const varCost = kms * VARIABLE_RATE_KM;
|
const varCost = kms * VARIABLE_RATE_KM;
|
||||||
const fixedCost = orders.length > 0 ? FIXED_COST_PER_SLOT : 0;
|
const fixedCost = slotCount * FIXED_COST_PER_SLOT;
|
||||||
const totalCost = varCost + fixedCost;
|
const totalCost = varCost + fixedCost;
|
||||||
const net = revenue - totalCost;
|
const net = revenue - totalCost;
|
||||||
const margin = revenue > 0 ? (net / revenue) * 100 : 0;
|
const margin = revenue > 0 ? (net / revenue) * 100 : 0;
|
||||||
|
|
||||||
return { revenue, kms, varCost, fixedCost, totalCost, net, margin };
|
return { revenue, kms, varCost, fixedCost, totalCost, net, margin, orders };
|
||||||
}
|
}
|
||||||
|
|
||||||
function rupees(v, decimals = 0) {
|
function rupees(v, decimals = 0) {
|
||||||
@@ -184,7 +237,7 @@ function OrdersBreakdownTable({ orders, getRevenue }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Expanded cost breakdown + orders for one rider. */
|
/** Expanded cost breakdown + orders for one rider. */
|
||||||
function RiderDetailPanel({ rider, metrics }) {
|
function RiderDetailPanel({ metrics }) {
|
||||||
const { varCost, fixedCost, kms, net, margin } = metrics;
|
const { varCost, fixedCost, kms, net, margin } = metrics;
|
||||||
const isProfit = net >= 0;
|
const isProfit = net >= 0;
|
||||||
|
|
||||||
@@ -242,10 +295,10 @@ function RiderDetailPanel({ rider, metrics }) {
|
|||||||
Revenue Breakdown
|
Revenue Breakdown
|
||||||
</div>
|
</div>
|
||||||
<span className="revenue-breakdown-count">
|
<span className="revenue-breakdown-count">
|
||||||
{rider.orders?.length ?? 0} order{(rider.orders?.length ?? 0) !== 1 ? 's' : ''}
|
{metrics.orders?.length ?? 0} order{(metrics.orders?.length ?? 0) !== 1 ? 's' : ''}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<OrdersBreakdownTable orders={rider.orders} getRevenue={orderRevenue} />
|
<OrdersBreakdownTable orders={metrics.orders} getRevenue={orderRevenue} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -254,10 +307,10 @@ function RiderDetailPanel({ rider, metrics }) {
|
|||||||
|
|
||||||
/** Single expandable rider profitability card. */
|
/** Single expandable rider profitability card. */
|
||||||
function RiderProfitabilityCard({ rider, metrics, isExpanded, isFocused, onToggle, onFocus }) {
|
function RiderProfitabilityCard({ rider, metrics, isExpanded, isFocused, onToggle, onFocus }) {
|
||||||
const { revenue, kms, totalCost, net, margin } = metrics;
|
const { revenue, kms, totalCost, net, margin, orders } = metrics;
|
||||||
const isProfit = net >= 0;
|
const isProfit = net >= 0;
|
||||||
const marginBarWidth = `${clamp(Math.abs(margin), 0, 100)}%`;
|
const marginBarWidth = `${clamp(Math.abs(margin), 0, 100)}%`;
|
||||||
const orderCount = rider.orders?.length ?? 0;
|
const orderCount = orders?.length ?? 0;
|
||||||
const name = rider.riderName ?? rider.username ?? `Rider #${rider.id}`;
|
const name = rider.riderName ?? rider.username ?? `Rider #${rider.id}`;
|
||||||
|
|
||||||
const cardClasses = [
|
const cardClasses = [
|
||||||
@@ -357,29 +410,45 @@ function RiderProfitabilityCard({ rider, metrics, isExpanded, isFocused, onToggl
|
|||||||
* focusedRider — Rider object (or null) synced with map
|
* focusedRider — Rider object (or null) synced with map
|
||||||
* handleRiderFocus — (rider) => void called when a card is clicked
|
* handleRiderFocus — (rider) => void called when a card is clicked
|
||||||
*/
|
*/
|
||||||
export default function ProfitabilitySection({ riders = [], totalDailyProfit = 0, focusedRider = null, handleRiderFocus }) {
|
export default function ProfitabilitySection({
|
||||||
|
riders = [],
|
||||||
|
totalDailyProfit = 0,
|
||||||
|
focusedRider = null,
|
||||||
|
handleRiderFocus,
|
||||||
|
selectedDate,
|
||||||
|
batches
|
||||||
|
}) {
|
||||||
const [expanded, setExpanded] = useState({});
|
const [expanded, setExpanded] = useState({});
|
||||||
const [sortMode, setSortMode] = useState('profit-asc'); // 'profit-asc', 'profit-desc', 'name-asc', 'orders-desc'
|
const sortMode = 'profit-asc'; // 'profit-asc', 'profit-desc', 'name-asc', 'orders-desc'
|
||||||
|
|
||||||
const toggleRider = useCallback((id) => {
|
const toggleRider = useCallback((id) => {
|
||||||
setExpanded((prev) => ({ ...prev, [id]: !prev[id] }));
|
setExpanded((prev) => ({ ...prev, [id]: !prev[id] }));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Enrich riders with computed metrics
|
// Enrich riders with computed metrics
|
||||||
const enriched = useMemo(() => riders.map((r) => ({ ...r, _m: calcRiderMetrics(r) })), [riders]);
|
const enriched = useMemo(
|
||||||
|
() => riders.map((r) => ({ ...r, _m: calcRiderMetrics(r, selectedDate, batches) })),
|
||||||
|
[riders, selectedDate, batches]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Filter out inactive riders (those with 0 orders in the selected date/slot) to ensure accurate KPI aggregations
|
||||||
|
const activeEnriched = useMemo(
|
||||||
|
() => enriched.filter((r) => r._m.orders.length > 0),
|
||||||
|
[enriched]
|
||||||
|
);
|
||||||
|
|
||||||
// Aggregate overall slot totals (pre-filtered for BI consistency)
|
// Aggregate overall slot totals (pre-filtered for BI consistency)
|
||||||
const slotRevenue = enriched.reduce((s, r) => s + r._m.revenue, 0);
|
const slotRevenue = activeEnriched.reduce((s, r) => s + r._m.revenue, 0);
|
||||||
const slotCost = enriched.reduce((s, r) => s + r._m.totalCost, 0);
|
const slotCost = activeEnriched.reduce((s, r) => s + r._m.totalCost, 0);
|
||||||
const slotNet = enriched.reduce((s, r) => s + r._m.net, 0);
|
const slotNet = activeEnriched.reduce((s, r) => s + r._m.net, 0);
|
||||||
const profitCount = enriched.filter((r) => r._m.net >= 0).length;
|
const profitCount = activeEnriched.filter((r) => r._m.net >= 0).length;
|
||||||
const lossCount = enriched.length - profitCount;
|
const lossCount = activeEnriched.length - profitCount;
|
||||||
|
|
||||||
const dailyIsProfit = totalDailyProfit >= 0;
|
const dailyIsProfit = totalDailyProfit >= 0;
|
||||||
const slotIsProfit = slotNet >= 0;
|
const slotIsProfit = slotNet >= 0;
|
||||||
|
|
||||||
// Filter riders (filtering by search and status tabs removed)
|
// Filter riders (filtering by search and status tabs removed)
|
||||||
const filtered = enriched;
|
const filtered = activeEnriched;
|
||||||
|
|
||||||
// Sort riders based on selected sortMode
|
// Sort riders based on selected sortMode
|
||||||
const sortedAndFiltered = useMemo(() => {
|
const sortedAndFiltered = useMemo(() => {
|
||||||
@@ -395,7 +464,7 @@ export default function ProfitabilitySection({ riders = [], totalDailyProfit = 0
|
|||||||
return nameA.localeCompare(nameB);
|
return nameA.localeCompare(nameB);
|
||||||
});
|
});
|
||||||
} else if (sortMode === 'orders-desc') {
|
} else if (sortMode === 'orders-desc') {
|
||||||
list.sort((a, b) => (b.orders?.length ?? 0) - (a.orders?.length ?? 0));
|
list.sort((a, b) => (b._m.orders?.length ?? 0) - (a._m.orders?.length ?? 0));
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}, [filtered, sortMode]);
|
}, [filtered, sortMode]);
|
||||||
@@ -412,7 +481,7 @@ export default function ProfitabilitySection({ riders = [], totalDailyProfit = 0
|
|||||||
<h2 className="profitability-header-title">Profitability Overview</h2>
|
<h2 className="profitability-header-title">Profitability Overview</h2>
|
||||||
<p className="profitability-header-subtitle">
|
<p className="profitability-header-subtitle">
|
||||||
<span>
|
<span>
|
||||||
{enriched.length} rider{enriched.length !== 1 ? 's' : ''}
|
{activeEnriched.length} rider{activeEnriched.length !== 1 ? 's' : ''}
|
||||||
</span>
|
</span>
|
||||||
<span className="profitability-header-dot" />
|
<span className="profitability-header-dot" />
|
||||||
<span style={{ color: 'var(--profit-green)' }}>{profitCount} profitable</span>
|
<span style={{ color: 'var(--profit-green)' }}>{profitCount} profitable</span>
|
||||||
@@ -451,7 +520,7 @@ export default function ProfitabilitySection({ riders = [], totalDailyProfit = 0
|
|||||||
<div className="profitability-summary-row" role="group" aria-label="Slot summary">
|
<div className="profitability-summary-row" role="group" aria-label="Slot summary">
|
||||||
<div className="profitability-summary-card profitability-summary-card--primary">
|
<div className="profitability-summary-card profitability-summary-card--primary">
|
||||||
<span className="profitability-summary-label">Riders Active</span>
|
<span className="profitability-summary-label">Riders Active</span>
|
||||||
<span className="profitability-summary-value">{enriched.length}</span>
|
<span className="profitability-summary-value">{activeEnriched.length}</span>
|
||||||
<span className="profitability-summary-detail">
|
<span className="profitability-summary-detail">
|
||||||
{profitCount} in profit · {lossCount} at loss
|
{profitCount} in profit · {lossCount} at loss
|
||||||
</span>
|
</span>
|
||||||
@@ -459,7 +528,7 @@ export default function ProfitabilitySection({ riders = [], totalDailyProfit = 0
|
|||||||
<div className="profitability-summary-card profitability-summary-card--primary">
|
<div className="profitability-summary-card profitability-summary-card--primary">
|
||||||
<span className="profitability-summary-label">Slot Revenue</span>
|
<span className="profitability-summary-label">Slot Revenue</span>
|
||||||
<span className="profitability-summary-value">{rupees(slotRevenue)}</span>
|
<span className="profitability-summary-value">{rupees(slotRevenue)}</span>
|
||||||
<span className="profitability-summary-detail">From {enriched.reduce((s, r) => s + (r.orders?.length ?? 0), 0)} orders</span>
|
<span className="profitability-summary-detail">From {enriched.reduce((s, r) => s + (r._m.orders?.length ?? 0), 0)} orders</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="profitability-summary-card profitability-summary-card--loss">
|
<div className="profitability-summary-card profitability-summary-card--loss">
|
||||||
<span className="profitability-summary-label">Slot Cost</span>
|
<span className="profitability-summary-label">Slot Cost</span>
|
||||||
@@ -558,5 +627,7 @@ ProfitabilitySection.propTypes = {
|
|||||||
riders: PropTypes.array,
|
riders: PropTypes.array,
|
||||||
totalDailyProfit: PropTypes.number,
|
totalDailyProfit: PropTypes.number,
|
||||||
focusedRider: PropTypes.object,
|
focusedRider: PropTypes.object,
|
||||||
handleRiderFocus: PropTypes.func
|
handleRiderFocus: PropTypes.func,
|
||||||
|
selectedDate: PropTypes.string,
|
||||||
|
batches: PropTypes.array
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export default function ProfitabilityReport() {
|
|||||||
.map((r) => {
|
.map((r) => {
|
||||||
let rRevenue = 0;
|
let rRevenue = 0;
|
||||||
let rKms = 0;
|
let rKms = 0;
|
||||||
const activeSlots = new Set();
|
const slotsByDate = {};
|
||||||
let ordersInSlots = 0;
|
let ordersInSlots = 0;
|
||||||
|
|
||||||
r.orders.forEach((o) => {
|
r.orders.forEach((o) => {
|
||||||
@@ -301,8 +301,18 @@ export default function ProfitabilityReport() {
|
|||||||
const oKms = parseFloat(o.kms || o.actualkms || 0);
|
const oKms = parseFloat(o.kms || o.actualkms || 0);
|
||||||
rKms += oKms;
|
rKms += oKms;
|
||||||
rRevenue += oKms <= 8 ? 30 : 30 + (oKms - 8) * 6;
|
rRevenue += oKms <= 8 ? 30 : 30 + (oKms - 8) * 6;
|
||||||
const dateStr = o.assigntime ? dayjs(o.assigntime).format('YYYY-MM-DD') : 'unknown';
|
|
||||||
activeSlots.add(`${dateStr}_${slot}`);
|
const dateStr = o.assigntime
|
||||||
|
? dayjs(o.assigntime).format('YYYY-MM-DD')
|
||||||
|
: o.deliverydate
|
||||||
|
? dayjs(o.deliverydate).format('YYYY-MM-DD')
|
||||||
|
: null;
|
||||||
|
if (!dateStr) return;
|
||||||
|
|
||||||
|
if (!slotsByDate[dateStr]) {
|
||||||
|
slotsByDate[dateStr] = new Set();
|
||||||
|
}
|
||||||
|
slotsByDate[dateStr].add(slot);
|
||||||
ordersInSlots++;
|
ordersInSlots++;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -310,9 +320,14 @@ export default function ProfitabilityReport() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sum unique slots per day, capping at 3 slots max per day
|
||||||
|
let slotCount = 0;
|
||||||
|
Object.values(slotsByDate).forEach((set) => {
|
||||||
|
slotCount += Math.min(set.size, 3);
|
||||||
|
});
|
||||||
|
|
||||||
const rVarCost = rKms * 2.5;
|
const rVarCost = rKms * 2.5;
|
||||||
const slotCount = activeSlots.size;
|
const rFixedCost = slotCount * (500 / 3);
|
||||||
const rFixedCost = slotCount * 166.67;
|
|
||||||
const rTotalCost = rVarCost + rFixedCost;
|
const rTotalCost = rVarCost + rFixedCost;
|
||||||
const rNet = rRevenue - rTotalCost;
|
const rNet = rRevenue - rTotalCost;
|
||||||
const rMargin = rRevenue > 0 ? (rNet / rRevenue) * 100 : 0;
|
const rMargin = rRevenue > 0 ? (rNet / rRevenue) * 100 : 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user