join('store_order o', 'o.id=ci.oid') ->join('store_product p', 'ci.product_id=p.id') ->join('user u', 'u.uid = o.uid') ->where('o.paid', 1) ->where('o.status', '>=', 0) ->where('o.refund_status', 0) ->where('o.is_del', 0) ->where('o.is_system_del', 0) ->where('p.cate_id', $cate_id) ->where('ci.activity', '') ->field('o.id, o.pay_price, ci.product_id, ci.cart_info, u.uid, u.now_money, p.store_name')->select(); $products = $products ? $products->toArray() : []; return $products; } /** * 用统一的架构来计算输赢 */ public function calc() { // 活动分类ID $cate_id = $this->getId(); $products = self::getOrders($cate_id); if (count($products) <= 1) { Log::warning('not enough order, stop activity:' . $this->getNameCN()); return; } // 按商品处理订单 $counter = 0; $left = []; $right = []; $left_spent = 0; $right_spent = 0; $left_cost = 0; $right_cost = 0; // 打乱商品顺序 shuffle($products); shuffle($products); // 标记有多件商品的订单, 再次统计到属于某个订单的商品时, 订单会重复 $orders = []; foreach($products as &$p) { $counter += 1; $ci = json_decode($p['cart_info'], true); $num = $ci['cart_num']; // 购买个数 $productInfo = isset($ci['productInfo']) ? $ci['productInfo'] : []; $attrInfo = isset($productInfo['attrInfo']) ? $productInfo['attrInfo'] : []; // 价格, 成本 $price = isset($attrInfo['price']) ? $attrInfo['price'] : 0.0; $cost = isset($attrInfo['cost']) ? $attrInfo['cost'] : 0.0; // 付款总额, 总成本 $ppaid = bcmul($num, $price, 2);// product paid $pcost = bcmul($num, $cost, 2); // orderId $oid = $p['id']; // 标记付款和成本 $p['paid'] = $ppaid; $p['cost'] = $pcost; // 处理一个订单多件商品的情况 if (!isset($orders[$oid])) { $orders[$oid] = 1; } else { $orders[$oid] += 1; } if ($ppaid <= 1.0) { Log::warning("impossible price, order: ". $p['id']); continue; } if ($pcost <= 0.1) { Log::warning("impossible cost, order: ". $p['id']); continue; } // 按一个规则, 把商品分为两份, 其中一份中奖 if ($this->leaningJudge($counter, $p, $attrInfo)) { $left[] = $p; $left_spent = bcadd($left_spent, $ppaid, 2); $left_cost = bcadd($left_cost, $pcost, 2); } else { $right[] = $p; $right_spent = bcadd($right_spent, $ppaid, 2); $right_cost = bcadd($right_cost, $pcost, 2); } } // foreach // 利润 $left_profit = bcsub($left_spent, $left_cost, 2); $right_profit = bcsub($right_spent, $right_cost, 2); // 定输赢 $diff_money = bcsub($left_spent, $right_spent, 2); if ($diff_money < 0.0) { $diff_money = -$diff_money; } $profit = $left_profit; $winners = $left; $losers = $right; if ($left_profit > $right_profit) { $winners = $right; $losers = $left; } $result = $this->getResult($winners == $left); // 保存开奖结果 SystemAwardHistory::create([ 'activity' => $this->getName(), 'result' => $result, 'ts' => time(), 'order_num' => count($left) + count($right), 'winner_num' => count($winners), 'total_paid' => $left_spent + $right_spent, 'diff_paid' => $diff_money, 'profit' => $profit, 'rate' => $this->repRate(), ]); // 结果处置 foreach($winners as $p) { $single = $orders[$p['id']] == 1; $this->execute($p, $single, true); } foreach($losers as $p) { $single = $orders[$p['id']] == 1; $this->execute($p, $single, false); } Log::warning('activity ' . $this->getNameCN() . ' calc finished. result:'); } protected function execute($product, $single=true, $win=true) { BaseModel::beginTrans(); $result = 0; $reparation = 0.0; if ($win) { $result = 1; $reparation = bcmul(bcsub($product['paid'], $product['cost'], 2), $this->repRate(), 2); } // 标记商品为已参与活动 $r1 = StoreOrderCartInfo::where('oid', $product['id']) ->where('product_id', $product['product_id']) ->update([ 'activity' => $this->getName(), 'result' => $result, 'reparation' => $reparation, ]); if (!$win) { BaseModel::checkTrans($r1); return; } $refund_price = bcadd($product['paid'], $reparation, 2); // 订单全部商品都中奖的退单 $r2 = true; if ($single) { $r2 = StoreOrder::where('id', $product['id'])->update([ 'refund_price' => $refund_price, 'refund_status' => 2, ]); } // 中奖的 退款并赔款返回到佣金 $r3 = UserBill::income('活动' . $this->getNameCN(), $product['uid'], 'now_money', 'brokerage', $refund_price, $product['id'], bcadd($product['now_money'], $refund_price, 2), '订单退款' . $product['paid'] . '+' . $reparation . '元'); $r4 = User::bcInc($product['uid'], 'brokerage_price', $refund_price, 'uid'); $ok = $r1 && $r2 && $r3 && $r4; BaseModel::checkTrans($ok); if ($ok) { // 中奖用户发送中奖消息. UserNotice::sendNoticeTo($product['uid'], '您的订单已退款', '您好,由于活动商品库存有限,您的订单 ' . $product['store_name'] . ' 未能成功分配。 该商品付款' . $product['paid'] . '元已如数退还,并赔付您' . $reparation . '元作为补偿,对您造成的不便我们深感抱歉。同时感谢您对美天旺的信任和喜爱,祝您生活愉快。'); } } // 活动所属的 $categoryId abstract protected function getId(); // 活动英文名, 长度 <8 abstract protected function getName(); // 活动中文名 abstract protected function getNameCN(); // 赔款所占商品利润的百分比 abstract protected function repRate(); // 获得开奖结果 int abstract protected function getResult($leftwin=true); /** * 根据此函数返回值来划分 getOrders() 返回的订单为两份, 其中一份中奖, 当本函数返回 true, $product 加入"左边" * * @param $index: $product 所在序号, 从 1 开始 * @param $product: getOrders() 返回列表中的单个元素 * @param $attr: 商品属性 * @return boolean true 加入左边 */ abstract protected function leaningJudge($index, $product, $attr); }