PromotionRewards.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. <?php
  2. declare(strict_types=1);
  3. namespace plugin\saiadmin\app\service\promotion;
  4. use app\miniapi\model\MiniCheckout;
  5. use plugin\saiadmin\app\model\water\WaterAccount;
  6. use plugin\saiadmin\app\model\water\WaterDelivery;
  7. use plugin\saiadmin\app\model\water\WaterEnterprise;
  8. use plugin\saiadmin\app\model\water\WaterLedger;
  9. use plugin\saiadmin\app\model\water\WaterModel;
  10. use plugin\saiadmin\app\model\water\WaterPromotionReferral;
  11. use plugin\saiadmin\app\model\water\WaterSku;
  12. use plugin\saiadmin\app\service\WaterBusiness;
  13. use plugin\saiadmin\exception\ApiException;
  14. final class PromotionRewards
  15. {
  16. public static function delivery(WaterPromotionReferral $referral): ?WaterDelivery
  17. {
  18. $order = $referral->order;
  19. if (!$order) {
  20. return null;
  21. }
  22. return WaterDelivery::query()->where('enterprise_id', $referral->invited_id)->where('sku_id', $order->sku_id)
  23. ->where('id', '>', (int) $referral->delivery_floor_id)
  24. ->where('status', 'completed')->where('delivered_qty', '>', 0)
  25. ->where('created_at', '>=', $order->created_at)->where('received_at', '>=', $order->created_at)
  26. ->orderBy('received_at')->orderBy('id')->first();
  27. }
  28. public function review(array $input, int $actor): array
  29. {
  30. $id = WaterBusiness::positive($input['id'] ?? null, '奖励记录');
  31. $revision = WaterBusiness::positive($input['revision'] ?? null, '记录版本');
  32. $decision = $input['decision'] ?? '';
  33. $note = $input['note'] ?? '';
  34. if (!in_array($decision, ['issued', 'rejected'], true) || !is_string($note)
  35. || trim($note) === '' || mb_strlen($note) > 500) {
  36. throw new ApiException('请选择审核结果并填写1至500字的核实说明');
  37. }
  38. if ($decision === 'issued' && ($input['receipt_refund_checked'] ?? null) !== true) {
  39. throw new ApiException('请先核实真实收款、无退款及重复客户情况');
  40. }
  41. $note = trim($note);
  42. return WaterModel::transaction(function () use ($id, $revision, $decision, $note, $actor) {
  43. $snapshot = WaterPromotionReferral::query()->find($id);
  44. if (!$snapshot) {
  45. throw new ApiException('奖励记录不存在');
  46. }
  47. // Match receipt/inventory lock order: SKU -> enterprise(s) -> referral -> account.
  48. $sku = WaterSku::query()->lockForUpdate()->find($snapshot->sku_id);
  49. $ids = [(int) $snapshot->inviter_id, (int) $snapshot->invited_id];
  50. sort($ids, SORT_NUMERIC);
  51. $companies = WaterEnterprise::query()->whereIn('id', $ids)->orderBy('id')->lockForUpdate()->get();
  52. $row = WaterPromotionReferral::query()->lockForUpdate()->find($id);
  53. if (!$row) {
  54. throw new ApiException('奖励记录不存在');
  55. }
  56. if (in_array($row->status, ['issued', 'rejected'], true)) {
  57. if ($row->status === $decision && $row->review_note === $note) {
  58. return ['id' => $id, 'status' => $row->status, 'replayed' => true];
  59. }
  60. throw new ApiException('奖励已经处理,请刷新查看');
  61. }
  62. if ($row->status !== 'pending' || (int) $row->revision !== $revision) {
  63. throw new ApiException('尚未首购或记录已更新,请刷新查看');
  64. }
  65. $delivery = null;
  66. if ($decision === 'issued') {
  67. if (!$sku || (int) $sku->status !== 1 || $companies->count() !== 2
  68. || $companies->contains(fn ($company) => (int) $company->status !== 1)) {
  69. throw new ApiException('企业或赠水规格已停用或不存在');
  70. }
  71. $order = $row->order;
  72. $checkout = $row->checkout_id ? MiniCheckout::query()->find($row->checkout_id) : null;
  73. $amount = (int) $row->purchase_amount_cents;
  74. if (!$order || (int) $order->enterprise_id !== (int) $row->invited_id
  75. || $amount < max(1, (int) $row->min_amount_cents)
  76. || str_starts_with($order->reference, 'TEST-') || str_contains($order->package_name, '测试')) {
  77. throw new ApiException('首购收款记录不符合奖励条件');
  78. }
  79. if ($row->checkout_id) {
  80. if (!$checkout || $checkout->status !== 'paid' || (int) $checkout->enterprise_id !== (int) $row->invited_id
  81. || (int) $checkout->amount_cents !== $amount || !str_starts_with($order->reference, $checkout->code . '-')) {
  82. throw new ApiException('首购结算记录不符合奖励条件');
  83. }
  84. } elseif ((int) $order->amount_cents !== $amount) {
  85. throw new ApiException('首购金额已发生变化,不能发放奖励');
  86. }
  87. $delivery = self::delivery($row);
  88. if (!$delivery) {
  89. throw new ApiException('首购后尚无对应规格的有效完整签收');
  90. }
  91. $account = WaterAccount::query()->where('enterprise_id', $row->inviter_id)
  92. ->where('sku_id', $row->sku_id)->lockForUpdate()->first();
  93. if (!$account) {
  94. $account = WaterAccount::query()->create([
  95. 'enterprise_id' => $row->inviter_id, 'sku_id' => $row->sku_id,
  96. 'updated_at' => date('Y-m-d H:i:s'),
  97. ]);
  98. $account->refresh();
  99. }
  100. $account->entitlement = (int) $account->entitlement + (int) $row->quantity;
  101. $account->updated_at = date('Y-m-d H:i:s');
  102. $account->save();
  103. }
  104. $row->fill([
  105. 'status' => $decision, 'reviewed_by' => $actor, 'reviewed_at' => date('Y-m-d H:i:s'),
  106. 'review_note' => $note, 'delivery_id' => $delivery?->id, 'revision' => $revision + 1,
  107. ])->save();
  108. WaterLedger::query()->create([
  109. 'kind' => $decision === 'issued' ? 'promotion_reward' : 'promotion_reject',
  110. 'created_by' => $actor, 'sku_id' => $row->sku_id, 'enterprise_id' => $row->inviter_id,
  111. 'quantity' => $decision === 'issued' ? (int) $row->quantity : 0,
  112. 'reference' => 'promotion:' . $id,
  113. 'note' => '邀请企业#' . $row->invited_id . ',首购#' . $row->order_id . ',' . $note,
  114. 'created_at' => date('Y-m-d H:i:s'),
  115. ]);
  116. return ['id' => $id, 'status' => $decision];
  117. });
  118. }
  119. }