WaterCommissionPolicy.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. <?php
  2. declare(strict_types=1);
  3. namespace plugin\saiadmin\app\service\agent;
  4. use InvalidArgumentException;
  5. use plugin\saiadmin\app\model\water\WaterCommissionSetting;
  6. use plugin\saiadmin\app\model\water\WaterLedger;
  7. use plugin\saiadmin\app\model\water\WaterModel;
  8. use plugin\saiadmin\exception\ApiException;
  9. /** Editable platform rates; each paid order keeps the rate used at accrual time. */
  10. final class WaterCommissionPolicy
  11. {
  12. private const RATE_FIELDS = [
  13. 1 => 'level1_basis_points',
  14. 2 => 'level2_basis_points',
  15. 3 => 'level3_basis_points',
  16. ];
  17. public static function current(bool $lock = false): array
  18. {
  19. $query = WaterCommissionSetting::query();
  20. $row = ($lock ? $query->sharedLock() : $query)->find(1);
  21. if (!$row) {
  22. throw new ApiException('请先初始化供水佣金规则');
  23. }
  24. return [
  25. 'level1_basis_points' => (int) $row->level1_basis_points,
  26. 'level2_basis_points' => (int) $row->level2_basis_points,
  27. 'level3_basis_points' => (int) $row->level3_basis_points,
  28. 'revision' => (int) $row->revision,
  29. 'updated_at' => $row->updated_at,
  30. ];
  31. }
  32. public static function rate(int $level, bool $lock = false): int
  33. {
  34. $field = self::RATE_FIELDS[$level] ?? throw new InvalidArgumentException('未知代理等级');
  35. return self::current($lock)[$field];
  36. }
  37. public static function message(): string
  38. {
  39. $rates = self::current();
  40. $percent = static fn (int $basisPoints): string => rtrim(rtrim(number_format($basisPoints / 100, 2, '.', ''), '0'), '.');
  41. return '新成交订单仅给归属代理计提:一级' . $percent($rates['level1_basis_points'])
  42. . '%、二级' . $percent($rates['level2_basis_points']) . '%、三级'
  43. . $percent($rates['level3_basis_points']) . '%;当前为待结算,提现尚未开放。';
  44. }
  45. public static function save(array $input, int $actor): array
  46. {
  47. $allowed = array_merge(array_values(self::RATE_FIELDS), ['revision']);
  48. if (array_diff(array_keys($input), $allowed)) {
  49. throw new ApiException('佣金规则包含不支持的字段');
  50. }
  51. $values = [];
  52. foreach (self::RATE_FIELDS as $field) {
  53. $values[$field] = self::integer($input[$field] ?? null, $field, 0, 10000);
  54. }
  55. $revision = self::integer($input['revision'] ?? null, 'revision', 1, 2147483646);
  56. return WaterModel::transaction(function () use ($values, $revision, $actor): array {
  57. $row = WaterCommissionSetting::query()->lockForUpdate()->find(1);
  58. if (!$row) {
  59. throw new ApiException('请先初始化供水佣金规则');
  60. }
  61. if ((int) $row->revision !== $revision) {
  62. throw new ApiException('佣金规则已被其他操作员修改,请重新打开后编辑');
  63. }
  64. $before = [(int) $row->level1_basis_points, (int) $row->level2_basis_points, (int) $row->level3_basis_points];
  65. $row->fill($values);
  66. $row->revision = $revision + 1;
  67. $row->updated_by = $actor;
  68. $row->updated_at = date('Y-m-d H:i:s');
  69. $row->save();
  70. WaterLedger::query()->create([
  71. 'kind' => 'commission_policy',
  72. 'created_by' => $actor,
  73. 'sku_id' => 0,
  74. 'enterprise_id' => 0,
  75. 'quantity' => 0,
  76. 'reference' => 'platform:commission:revision:' . $row->revision,
  77. 'note' => '代理佣金比例调整(基点):' . implode('/', $before) . '→' . implode('/', array_values($values)),
  78. 'created_at' => $row->updated_at,
  79. ]);
  80. return self::current();
  81. });
  82. }
  83. public static function amount(int $baseAmountCents, int $level): int
  84. {
  85. return self::amountAtRate($baseAmountCents, self::rate($level));
  86. }
  87. public static function amountAtRate(int $baseAmountCents, int $rate): int
  88. {
  89. if ($baseAmountCents < 0) {
  90. throw new InvalidArgumentException('佣金基数不能为负数');
  91. }
  92. if ($rate < 0 || $rate > 10000) {
  93. throw new InvalidArgumentException('佣金比例必须在0到100%之间');
  94. }
  95. $whole = intdiv($baseAmountCents, 10000) * $rate;
  96. $fraction = intdiv(($baseAmountCents % 10000) * $rate + 5000, 10000);
  97. return $whole + $fraction;
  98. }
  99. private static function integer(mixed $value, string $field, int $min, int $max): int
  100. {
  101. if ((!is_int($value) && !is_string($value)) || filter_var($value, FILTER_VALIDATE_INT) === false
  102. || (int) $value < $min || (int) $value > $max) {
  103. throw new ApiException($field . ' 必须是 ' . $min . ' 到 ' . $max . ' 之间的整数');
  104. }
  105. return (int) $value;
  106. }
  107. }