DeviceOperationMetrics.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. <?php
  2. namespace plugin\saiadmin\app\service;
  3. use DateTimeImmutable;
  4. /** Statistics::business_device_data 的经营口径;未知成本/有效分成不按零处理。 */
  5. class DeviceOperationMetrics
  6. {
  7. public static function calculate(array $row, array $daily, DateTimeImmutable $now): array
  8. {
  9. $amount = (float) $row['total_amount'];
  10. $today = (float) $row['today_amount'];
  11. $yesterday = (float) $row['yesterday_amount'];
  12. $rate = isset($row['merchant_rate']) ? (float) $row['merchant_rate'] : null;
  13. $managerRate = isset($row['manager_rate']) ? (float) $row['manager_rate'] : null;
  14. $cost = isset($row['cost_price']) ? (float) $row['cost_price'] : null;
  15. $started = empty($row['bind_time']) ? null : new DateTimeImmutable($row['bind_time']);
  16. $days = $started && $started <= $now
  17. ? (int) $started->setTime(0, 0)->diff($now->setTime(0, 0))->days + 1 : 0;
  18. $average = $days > 0 ? $amount / $days : null;
  19. $performance = self::performance($yesterday, $average ?? 0.0);
  20. $recovery = self::recovery($cost, $managerRate, $started, $days, $daily);
  21. return array_merge($row, [
  22. 'id' => (int) $row['id'],
  23. 'goods_type' => (int) $row['goods_type'],
  24. 'manage_agent_id' => (int) $row['manage_agent_id'],
  25. 'merchant_id' => (int) ($row['merchant_id'] ?? 0),
  26. 'total_amount' => round($amount, 2),
  27. 'today_amount' => round($today, 2),
  28. 'yesterday_amount' => round($yesterday, 2),
  29. 'total_orders' => (int) $row['total_orders'],
  30. 'today_orders' => (int) $row['today_orders'],
  31. 'yesterday_orders' => (int) $row['yesterday_orders'],
  32. 'paid_orders' => (int) $row['paid_orders'],
  33. 'unit_price' => (int) $row['paid_orders'] > 0 ? round($amount / (int) $row['paid_orders'], 4) : 0,
  34. 'merchant_rate' => $rate,
  35. 'manager_rate' => $managerRate,
  36. 'cost_price' => $cost,
  37. 'total_profit' => self::profit($amount, $rate),
  38. 'today_profit' => self::profit($today, $rate),
  39. 'yesterday_profit' => self::profit($yesterday, $rate),
  40. // 旧页面“盈利率”实际为净交易额 / 成本,并非利润率。
  41. 'profit_rate_value' => $cost !== null && $cost > 0 ? round($amount / $cost * 100, 4) : null,
  42. 'daily_growth' => $yesterday > 0 ? round(($today - $yesterday) / $yesterday * 100, 2) : null,
  43. 'operated_days' => $days,
  44. 'daily_average_amount' => $average === null ? null : round($average, 4),
  45. ], $recovery, $performance);
  46. }
  47. private static function profit(float $amount, ?float $rate): ?float
  48. {
  49. // 无交易额即可确定收益为 0;有交易额但缺分成时保持未知。
  50. if ($amount === 0.0) {
  51. return 0.0;
  52. }
  53. return $rate === null ? null : round($amount * $rate / 100, 4);
  54. }
  55. private static function performance(float $yesterday, float $average): array
  56. {
  57. $score = $average > 0 ? $yesterday / $average : 0;
  58. $labels = ['最差', '差', '一般', '中等', '好', '良', '优良', '优秀'];
  59. $grade = 7;
  60. foreach ([0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2] as $index => $threshold) {
  61. if ($score <= $threshold) {
  62. $grade = $index;
  63. break;
  64. }
  65. }
  66. return ['performance_grade' => $grade, 'today_performance' => $labels[$grade]];
  67. }
  68. /** 按日期正序找首次回本日,收入查询已排除本次铺货之前的订单。 */
  69. private static function recovery(?float $cost, ?float $rate, ?DateTimeImmutable $started, int $days, array $daily): array
  70. {
  71. $result = ['is_cost_recovery' => null, 'cost_recovery_date' => null, 'cost_recovery_days' => null];
  72. if ($cost === null || $rate === null || $started === null || $days <= 0) {
  73. return $result;
  74. }
  75. $result['is_cost_recovery'] = false;
  76. $recoveryDate = $cost === 0.0 ? $started->format('Y-m-d') : null;
  77. $cumulative = 0.0;
  78. foreach ($daily as $day) {
  79. if ($recoveryDate !== null) {
  80. break;
  81. }
  82. if ($day['stat_date'] < $started->format('Y-m-d')) {
  83. continue;
  84. }
  85. $cumulative += (float) $day['amount'] * $rate / 100;
  86. if (round($cumulative, 4) >= $cost) {
  87. $recoveryDate = $day['stat_date'];
  88. }
  89. }
  90. if ($recoveryDate !== null) {
  91. $result['is_cost_recovery'] = true;
  92. $result['cost_recovery_date'] = $recoveryDate;
  93. $result['cost_recovery_days'] = (int) $started->setTime(0, 0)->diff(new DateTimeImmutable($recoveryDate))->days + 1;
  94. }
  95. return $result;
  96. }
  97. }