376 lines
13 KiB
PHP
376 lines
13 KiB
PHP
<?php
|
||
|
||
namespace App\Libraries;
|
||
|
||
use CodeIgniter\Database\BaseConnection;
|
||
|
||
/**
|
||
* Partner (agent) grid: same data as
|
||
* SELECT prr.retention_rate AS partner_RR, prr.vehicle_type_id, pvt.vehicle_type AS partner_VT
|
||
* FROM partner_retention_rate prr
|
||
* LEFT JOIN vehicle_type vt ON vt.id = prr.vehicle_type_id
|
||
* WHERE prr.agent_id = ? AND prr.is_active = 1
|
||
* Matched grid row (vehicle type aligns with that PRR row): partner_VT, partner_RR set from DB;
|
||
* comp/tp/od = partner_RR − original (null/empty base → 0); result ≤ 0 → '-'.
|
||
* No matching PRR row for this grid vehicle type: partner_VT, partner_RR, comp, tp, od → '-'.
|
||
*
|
||
* Manager/Accounts: applyManagerAccountsDisplayToRows() — comp/tp/od null/empty/zero/negative → '-', no math.
|
||
*/
|
||
class PartnerPayoutGridRetention
|
||
{
|
||
/**
|
||
* Active partner_retention_rate rows with joined vehicle_type (one query for maps + agent grid meta).
|
||
*
|
||
* @return array<int, array<string, mixed>>
|
||
*/
|
||
private static function fetchPrrPvtRows(BaseConnection $db, int $agentId): array
|
||
{
|
||
if ($agentId <= 0) {
|
||
return [];
|
||
}
|
||
|
||
return $db->table('partner_retention_rate prr')
|
||
->select('prr.vehicle_type_id, prr.retention_rate, vt.vehicle_type')
|
||
->join('vehicle_type vt', 'vt.id = prr.vehicle_type_id', 'left')
|
||
->where('prr.agent_id', $agentId)
|
||
->where('prr.is_active', 1)
|
||
->get()
|
||
->getResultArray();
|
||
}
|
||
|
||
public static function normalizeVehicleTypeLabel(?string $s): string
|
||
{
|
||
if ($s === null || $s === '') {
|
||
return '';
|
||
}
|
||
|
||
return strtolower(trim(preg_replace('/\s+/u', ' ', $s)));
|
||
}
|
||
|
||
/**
|
||
* @param array<int, array<string, mixed>> $prrRows
|
||
*
|
||
* @return array<string, array{partner_VT: string, partner_RR: float}>
|
||
*/
|
||
private static function partnerMetaMapFromPrrRows(array $prrRows): array
|
||
{
|
||
$meta = [];
|
||
foreach ($prrRows as $r) {
|
||
if (! array_key_exists('retention_rate', $r) || $r['retention_rate'] === null || $r['retention_rate'] === '') {
|
||
continue;
|
||
}
|
||
$partnerRr = (float) $r['retention_rate'];
|
||
$vtRaw = isset($r['vehicle_type']) ? trim((string) $r['vehicle_type']) : '';
|
||
$partnerVt = $vtRaw !== '' ? $vtRaw : 'N/A';
|
||
$entry = [
|
||
'partner_VT' => $partnerVt,
|
||
'partner_RR' => $partnerRr,
|
||
];
|
||
if ($vtRaw !== '') {
|
||
$meta[self::normalizeVehicleTypeLabel($vtRaw)] = $entry;
|
||
}
|
||
$vid = isset($r['vehicle_type_id']) ? (int) $r['vehicle_type_id'] : 0;
|
||
if ($vid > 0) {
|
||
$meta['id:' . $vid] = $entry;
|
||
}
|
||
}
|
||
|
||
return $meta;
|
||
}
|
||
|
||
/**
|
||
* vehicle_type.vehicle_type as partner_VT, partner_retention_rate.retention_rate as partner_RR.
|
||
* Keys: normalized vehicle name, and id:{vehicle_type_id}. Rows without retention_rate are omitted.
|
||
*
|
||
* @return array<string, array{partner_VT: string, partner_RR: float}>
|
||
*/
|
||
public static function buildPartnerMetaMap(BaseConnection $db, int $agentId): array
|
||
{
|
||
return self::partnerMetaMapFromPrrRows(self::fetchPrrPvtRows($db, $agentId));
|
||
}
|
||
|
||
/**
|
||
* Normalized master label → vehicle_type.id (active rows only).
|
||
* Used when grid row text must be resolved to an id for partner_retention_rate lookup.
|
||
*
|
||
* @return array<string, int>
|
||
*/
|
||
public static function buildMasterNormLabelToVehicleTypeIdMap(BaseConnection $db): array
|
||
{
|
||
$rows = $db->table('vehicle_type')
|
||
->select('id, vehicle_type')
|
||
->where('is_active', 1)
|
||
->get()
|
||
->getResultArray();
|
||
|
||
$out = [];
|
||
foreach ($rows as $r) {
|
||
$k = self::normalizeVehicleTypeLabel($r['vehicle_type'] ?? '');
|
||
if ($k === '') {
|
||
continue;
|
||
}
|
||
$out[$k] = (int) $r['id'];
|
||
}
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* @param array<string, array{partner_VT: string, partner_RR: float}> $metaMap
|
||
* @param array<string, int>|null $masterNormLabelToVtId from buildMasterNormLabelToVehicleTypeIdMap()
|
||
*
|
||
* @return array{partner_VT: string, partner_RR: float}|null null = no row in partner_retention_rate for this grid vehicle type (agent)
|
||
*/
|
||
public static function resolvePartnerMetaForGridRow(array $row, array $metaMap, ?array $masterNormLabelToVtId = null): ?array
|
||
{
|
||
$key = self::normalizeVehicleTypeLabel(isset($row['vehicle_type']) ? (string) $row['vehicle_type'] : '');
|
||
if ($key !== '' && isset($metaMap[$key])) {
|
||
return $metaMap[$key];
|
||
}
|
||
$vid = isset($row['vehicle_type_id']) ? (int) $row['vehicle_type_id'] : 0;
|
||
if ($vid > 0 && isset($metaMap['id:' . $vid])) {
|
||
return $metaMap['id:' . $vid];
|
||
}
|
||
// Grid text matches master vehicle_type but meta was only keyed by id (e.g. weak pvt join on prr)
|
||
if ($masterNormLabelToVtId !== null && $key !== '') {
|
||
$mid = $masterNormLabelToVtId[$key] ?? 0;
|
||
if ($mid > 0 && isset($metaMap['id:' . $mid])) {
|
||
return $metaMap['id:' . $mid];
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Manager / Accounts: no math — null, empty, '-', unparseable, zero, or negative → '-';
|
||
* else return formatted positive value.
|
||
*/
|
||
public static function gridPayoutDisplayManagerAccounts($raw): string
|
||
{
|
||
if ($raw === null || $raw === '' || $raw === '-') {
|
||
return '-';
|
||
}
|
||
$v = self::toFloat($raw);
|
||
if ($v === null || $v <= 0) {
|
||
return '-';
|
||
}
|
||
|
||
return self::formatGridNumericDisplay($v);
|
||
}
|
||
|
||
/**
|
||
* Agent: only if original comp/tp/od is a positive number → partner_RR − value.
|
||
* Original null / empty / '-' / zero / negative / unparseable → '-'.
|
||
*/
|
||
public static function agentPayoutColumnFromPartnerRr($raw, float $partnerRr): string
|
||
{
|
||
if ($raw === null || $raw === '' || $raw === '-') {
|
||
return '-';
|
||
}
|
||
$base = self::toFloat($raw);
|
||
if ($base === null || $base <= 0) {
|
||
return '-';
|
||
}
|
||
|
||
$result = $partnerRr - $base;
|
||
return self::formatGridNumericDisplay($result);
|
||
}
|
||
|
||
/**
|
||
* @param array<int, array<string, mixed>> $rows
|
||
*
|
||
* @return array<int, array<string, mixed>>
|
||
*/
|
||
public static function applyManagerAccountsDisplayToRows(array $rows): array
|
||
{
|
||
foreach ($rows as &$row) {
|
||
if (array_key_exists('comp', $row)) {
|
||
$row['comp'] = self::gridPayoutDisplayManagerAccounts($row['comp'] ?? null);
|
||
}
|
||
if (array_key_exists('tp', $row)) {
|
||
$row['tp'] = self::gridPayoutDisplayManagerAccounts($row['tp'] ?? null);
|
||
}
|
||
if (array_key_exists('od', $row)) {
|
||
$row['od'] = self::gridPayoutDisplayManagerAccounts($row['od'] ?? null);
|
||
}
|
||
}
|
||
unset($row);
|
||
|
||
return $rows;
|
||
}
|
||
|
||
private static function formatGridNumericDisplay(float $v): string
|
||
{
|
||
if (abs($v - round($v)) < 0.00001) {
|
||
return (string) (int) round($v);
|
||
}
|
||
|
||
return rtrim(rtrim(number_format($v, 2, '.', ''), '0'), '.');
|
||
}
|
||
|
||
/**
|
||
* @param array<int, array<string, mixed>> $prrRows
|
||
*
|
||
* @return array<string, float> normalized name => rate, and id:{vehicle_type_id} => rate
|
||
*/
|
||
private static function retentionMapFromPrrRows(array $prrRows): array
|
||
{
|
||
$map = [];
|
||
foreach ($prrRows as $r) {
|
||
if (! array_key_exists('retention_rate', $r) || $r['retention_rate'] === null || $r['retention_rate'] === '') {
|
||
continue;
|
||
}
|
||
$rate = (float) $r['retention_rate'];
|
||
$name = isset($r['vehicle_type']) ? trim((string) $r['vehicle_type']) : '';
|
||
if ($name !== '') {
|
||
$map[self::normalizeVehicleTypeLabel($name)] = $rate;
|
||
}
|
||
$vid = isset($r['vehicle_type_id']) ? (int) $r['vehicle_type_id'] : 0;
|
||
if ($vid > 0) {
|
||
$map['id:' . $vid] = $rate;
|
||
}
|
||
}
|
||
|
||
return $map;
|
||
}
|
||
|
||
/**
|
||
* @return array<string, float> normalized name => rate, and id:{vehicle_type_id} => rate
|
||
*/
|
||
public static function buildRetentionMap(BaseConnection $db, int $agentId): array
|
||
{
|
||
return self::retentionMapFromPrrRows(self::fetchPrrPvtRows($db, $agentId));
|
||
}
|
||
|
||
/**
|
||
* @param array<int, array<string, mixed>> $rows
|
||
*
|
||
* @return array<int, array<string, mixed>>
|
||
*/
|
||
public static function applyToRows(array $rows, int $agentId, BaseConnection $db): array
|
||
{
|
||
if ($agentId <= 0 || $rows === []) {
|
||
return $rows;
|
||
}
|
||
|
||
$prrRows = self::fetchPrrPvtRows($db, $agentId);
|
||
$metaMap = self::partnerMetaMapFromPrrRows($prrRows);
|
||
$master = self::buildMasterNormLabelToVehicleTypeIdMap($db);
|
||
|
||
foreach ($rows as &$row) {
|
||
$oa = $row['comp'] ?? null;
|
||
$ob = $row['tp'] ?? null;
|
||
$oc = $row['od'] ?? null;
|
||
$row['oa'] = $oa;
|
||
$row['ob'] = $ob;
|
||
$row['oc'] = $oc;
|
||
// Explicit raw aliases for UI/API consumers.
|
||
$row['raw_comp'] = $oa;
|
||
$row['raw_tp'] = $ob;
|
||
$row['raw_od'] = $oc;
|
||
|
||
$pm = self::resolvePartnerMetaForGridRow($row, $metaMap, $master);
|
||
if ($pm === null) {
|
||
$row['partner_VT'] = '-';
|
||
$row['partner_RR'] = '-';
|
||
$row['partner_comp'] = '-';
|
||
$row['partner_tp'] = '-';
|
||
$row['partner_od'] = '-';
|
||
if (array_key_exists('comp', $row)) {
|
||
$row['comp'] = '-';
|
||
}
|
||
if (array_key_exists('tp', $row)) {
|
||
$row['tp'] = '-';
|
||
}
|
||
if (array_key_exists('od', $row)) {
|
||
$row['od'] = '-';
|
||
}
|
||
|
||
continue;
|
||
}
|
||
|
||
$row['partner_VT'] = $pm['partner_VT'];
|
||
$row['partner_RR'] = $pm['partner_RR'];
|
||
$rr = (float) $pm['partner_RR'];
|
||
|
||
$partnerComp = array_key_exists('comp', $row)
|
||
? self::agentPayoutColumnFromPartnerRr($oa, $rr)
|
||
: '-';
|
||
$partnerTp = array_key_exists('tp', $row)
|
||
? self::agentPayoutColumnFromPartnerRr($ob, $rr)
|
||
: '-';
|
||
$partnerOd = array_key_exists('od', $row)
|
||
? self::agentPayoutColumnFromPartnerRr($oc, $rr)
|
||
: '-';
|
||
|
||
$row['partner_comp'] = $partnerComp;
|
||
$row['partner_tp'] = $partnerTp;
|
||
$row['partner_od'] = $partnerOd;
|
||
|
||
if (array_key_exists('comp', $row)) {
|
||
$row['comp'] = $partnerComp;
|
||
}
|
||
if (array_key_exists('tp', $row)) {
|
||
$row['tp'] = $partnerTp;
|
||
}
|
||
if (array_key_exists('od', $row)) {
|
||
$row['od'] = $partnerOd;
|
||
}
|
||
}
|
||
unset($row);
|
||
|
||
return $rows;
|
||
}
|
||
|
||
public static function retentionForRow(array $row, array $map): ?float
|
||
{
|
||
$key = self::normalizeVehicleTypeLabel(isset($row['vehicle_type']) ? (string) $row['vehicle_type'] : '');
|
||
if ($key !== '' && isset($map[$key])) {
|
||
return $map[$key];
|
||
}
|
||
$vid = isset($row['vehicle_type_id']) ? (int) $row['vehicle_type_id'] : 0;
|
||
if ($vid > 0 && isset($map['id:' . $vid])) {
|
||
return $map['id:' . $vid];
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* @return float|int|string|null '-' when adjusted <= 0, else numeric display
|
||
*/
|
||
public static function adjustPayoutValue($raw, float $rate)
|
||
{
|
||
if ($raw === null || $raw === '' || $raw === '-') {
|
||
return $raw;
|
||
}
|
||
$base = self::toFloat($raw);
|
||
if ($base === null) {
|
||
return $raw;
|
||
}
|
||
$adj = $base - $rate;
|
||
if ($adj <= 0) {
|
||
return '-';
|
||
}
|
||
if (abs($adj - round($adj)) < 0.00001) {
|
||
return (int) round($adj);
|
||
}
|
||
|
||
return rtrim(rtrim(number_format($adj, 2, '.', ''), '0'), '.');
|
||
}
|
||
|
||
private static function toFloat($raw): ?float
|
||
{
|
||
if (is_numeric($raw)) {
|
||
return (float) $raw;
|
||
}
|
||
$clean = preg_replace('/[^0-9.\-]/', '', (string) $raw);
|
||
if ($clean === '' || $clean === '-' || $clean === '.') {
|
||
return null;
|
||
}
|
||
|
||
return (float) $clean;
|
||
}
|
||
}
|