| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586 | <?phpnamespace app\common\Service;use app\common\Enum\GoodsEnum;use app\common\model\Order;use app\common\model\OrderGoods;use app\common\model\OrderAction;use app\common\model\Address;use app\common\model\UserCoupon;use app\common\model\Goods;use app\common\model\Sku;use app\common\model\Freight;use app\common\model\Coupon;use app\common\model\Carts;use think\Db;use think\Exception;use app\common\model\OrderAddress;/** * 订单服务类 * 封装订单创建相关逻辑 */class OrderService{        /**     * 根据商品列表计算订单明细     * @param array $orderInfo 订单基础信息     * @param array $goods_list 商品列表     * @param int $user_id 用户ID     * @param int $area_id 地区ID     * @param int $user_coupon_id 优惠券ID     * @return array     * @throws Exception     */    protected static function computeGoods(&$orderInfo, $goodsList, $userId, $areaId, $userCouponId = 0)    {        $config = get_addon_config('shop');        $orderInfo['amount'] = 0;        $orderInfo['goods_price'] = 0;        $orderInfo['goods_num'] = 0;        $orderInfo['express_fee'] = 0;        $orderInfo['discount_fee'] = 0;        $orderInfo['coupon_discount_fee'] = 0;        $orderInfo['order_amount'] = 0;        $orderItem = [];        $shippingTemp = [];        $userCoupon = null;        $processedGoodsList = []; // 处理后的商品列表,避免与参数$goodsList冲突        // 校验优惠券        if ($userCouponId) {            $userCouponModel = new UserCoupon();            $userCoupon = $userCouponModel->checkUserOrUse($userCouponId, $userId);            $orderInfo['user_coupon_id'] = $userCouponId;        }        // 提取所有商品ID和SKU ID,进行批量查询        $goodsIds = array_column($goodsList, 'goods_id');        $skuIds = [];        foreach ($goodsList as $index => $item) {            if (isset($item['goods_sku_id']) && $item['goods_sku_id'] > 0) {                $skuIds[] = $item['goods_sku_id'];            }        }        // 批量查询商品信息        $goodsData = [];        if (!empty($goodsIds)) {            $goodsCollection = Goods::with(['brand'])                ->where('id', 'in', $goodsIds)                ->where('status', GoodsEnum::STATUS_ON_SALE)                ->select();            foreach ($goodsCollection as $goods) {                $goodsData[$goods->id] = $goods;            }        }        // 批量查询SKU信息        $skuData = [];        $multiSpecSkuIds = []; // 用于存储多规格商品的SKU ID        if (!empty($skuIds)) {            $skuCollection = Sku::where('id', 'in', $skuIds)->select();            foreach ($skuCollection as $sku) {                               $skuData[$sku->id] = $sku;                // 过滤出有规格值的SKU ID(spec_value_ids不为空)                if (!empty($sku->spec_value_ids)) {                    $multiSpecSkuIds[] = $sku->id;                }            }        }        // 批量查询规格属性字符串(只查询多规格商品的SKU)        $skuAttrData = [];        if (!empty($multiSpecSkuIds)) {            $skuAttrData = \app\common\Service\SkuSpec::getSkuAttrs($multiSpecSkuIds);        }        // 验证并构建商品数据        foreach ($goodsList as $item) {            $goods_id = $item['goods_id'];            $goods_sku_id = $item['goods_sku_id']; // 现在所有商品都应该有SKU ID            $nums = $item['nums'];            if ($nums <= 0) {                throw new Exception("商品数量必须大于0");            }            // 检查商品是否存在            if (!isset($goodsData[$goods_id])) {                throw new Exception("商品已下架");            }            $goods = $goodsData[$goods_id];            // 所有商品都必须有SKU(包括单规格商品的默认SKU)            if (empty($skuData) || !isset($skuData[$goods_sku_id])) {                throw new Exception("商品规格不存在");            }            $sku = $skuData[$goods_sku_id];                        // 验证SKU是否属于该商品            if ($sku->goods_id != $goods_id) {                throw new Exception("商品规格不匹配");            }                        // 获取规格属性字符串(单规格商品的sku_attr为空)            $sku_attr = $skuAttrData[$goods_sku_id] ?? '';            // 构建商品对象,模拟购物车数据结构            $goodsItem = (object)[                'goods_id' => $goods_id,                'goods_sku_id' => $goods_sku_id,                'nums' => $nums,                'goods' => $goods,                'sku' => $sku,                'sku_attr' => $sku_attr            ];            $processedGoodsList[] = $goodsItem;        }            // 计算商品价格和运费(统一使用SKU进行计算)        foreach ($processedGoodsList as $item) {            $goodsItemData = [];                        if (empty($item->goods) || empty($item->sku)) {                throw new Exception("商品已下架");            }                        // 库存验证(统一使用SKU库存)            if ($item->sku->stocks < $item->nums) {                throw new Exception("商品库存不足,请重新修改数量");            }                        // 统一使用SKU数据进行计算            $goodsItemData['image'] = !empty($item->sku->image) ? $item->sku->image : $item->goods->image;            $goodsItemData['price'] = $item->sku->price;            // $goodsItemData['lineation_price'] = $item->sku->lineation_price;            $goodsItemData['sku_sn'] = $item->sku->sku_sn;            $amount = bcmul($item->sku->price, $item->nums, 2);                        $goodsItemData['amount'] = $amount;            // 订单应付金额            $orderInfo['amount'] = bcadd($orderInfo['amount'], $amount, 2);            // 商品总价            $orderInfo['goods_price'] = bcadd($orderInfo['goods_price'], $amount, 2);            // 商品数量累计            $orderInfo['goods_num'] += $item->nums;            $freight_id = $item->goods->express_template_id;            // 计算邮费【合并运费模板】            if (!isset($shippingTemp[$freight_id])) {                $shippingTemp[$freight_id] = [                    'nums'   => $item->nums,                    'weight' => $item->sku->weight,                    'amount' => $amount                ];            } else {                $shippingTemp[$freight_id] = [                    'nums'   => bcadd($shippingTemp[$freight_id]['nums'], $item->nums, 2),                    'weight' => bcadd($shippingTemp[$freight_id]['weight'], $item->sku->weight, 2),                    'amount' => bcadd($shippingTemp[$freight_id]['amount'], $amount, 2)                ];            }                        // 创建订单商品数据 (基于正确的表结构)            $orderItemData = [                'user_id'              => $userId,                'order_sn'             => '', // 将在订单创建后补充                'goods_sn'             => $item->goods->goods_sn ?: '', // 商品货号                'sku_sn'               => $item->sku->sku_sn ?: '', // SKU编号                'goods_type'           => $item->goods->type ?: 0, // 商品类型                'goods_id'             => $item->goods->id,                'goods_sku_attr'       => $item->sku->sku_attr,                'goods_spec_value_ids' => $item->sku->spec_value_ids,                'goods_sku_id'         => $item->sku->id,                'goods_title'          => $item->goods->title,                'goods_market_price'   => $item->sku->market_price ?: 0, // 市场价                'goods_original_price' => $item->sku->price, // 商城售价                'goods_price'          => $amount, // 实付金额                'goods_image'          => $goodsItemData['image'],                'goods_weight'         => $item->sku->weight ?: 0,                'nums'                 => $item->nums,                'sale_status'          => 0, // 销售状态:0=待申请                'comment_status'       => 0, // 评论状态:0=未评论                'status'               => 1, // 状态                // 添加分类和品牌信息用于优惠券计算 (临时字段,不会保存到数据库)                // 'category_id'          => $item->goods->category_ids,                'brand_id'             => $item->goods->brand_id,            ];                        $orderItem[] = $orderItemData;        }            // 按运费模板计算        foreach ($shippingTemp as $key => $item) {            $shippingfee = Freight::calculate($key, $areaId, $item['nums'], $item['weight'], $item['amount']);            $orderInfo['express_fee'] = bcadd($orderInfo['express_fee'], $shippingfee, 2);        }        // 订单金额(商品价格+运费)        $orderInfo['order_amount'] = bcadd($orderInfo['goods_price'], $orderInfo['express_fee'], 2);        // 订单应付金额(暂时等于订单金额,后续会减去优惠)        $orderInfo['amount'] = $orderInfo['order_amount'];        // if (!empty($userCoupon)) {        //     // 校验优惠券        //     $goods_ids = array_column($orderItem, 'goods_id');        //     $category_ids = array_column($orderItem, 'category_id');        //     $brand_ids = array_column($orderItem, 'brand_id');        //     $couponModel = new Coupon();        //     $coupon = $couponModel->getCoupon($userCoupon['coupon_id'])        //         ->checkCoupon()        //         ->checkOpen()        //         ->checkUseTime($userCoupon['createtime'])        //         ->checkConditionGoods($goods_ids, $userId, $category_ids, $brand_ids);        //     // 计算折扣金额,判断是使用不含运费,还是含运费的金额        //     $amount = !isset($config['shippingfeecoupon']) || $config['shippingfeecoupon'] == 0 ? $orderInfo['goods_price'] : $orderInfo['order_amount'];        //     list($new_money, $coupon_money) = $coupon->doBuy($amount);        //     // 判断优惠金额是否超出总价,超出则直接设定优惠金额为总价        //     $orderInfo['coupon_discount_fee'] = $coupon_money > $amount ? $amount : $coupon_money;        //     $orderInfo['discount_fee'] = $orderInfo['coupon_discount_fee'];        // }        // 计算最终应付金额【订单金额减去折扣】        $orderInfo['amount'] = max(0, bcsub($orderInfo['order_amount'], $orderInfo['discount_fee'], 2));        $orderInfo['pay_amount'] = $orderInfo['amount']; // 实际付款金额等于应付金额        $orderInfo['discount_fee'] = bcadd($orderInfo['discount_fee'], 0, 2);        return [            $orderItem,            $processedGoodsList,            $userCoupon        ];    }        /**     * 统一的创建订单方法     * @param int $address_id 地址ID     * @param int $user_id 用户ID     * @param array $goods_list 标准化的商品列表     * @param int $user_coupon_id 优惠券ID     * @param string $memo 备注     * @param array $cart_ids 购物车ID数组(如果是购物车模式需要清空)     * @return Order     * @throws Exception     */    public static function createOrder($addressId, $userId, $goodsList, $userCouponId = 0, $remark = '')    {        $address = Address::get($addressId);        if (!$address || $address['user_id'] != $userId) {            throw new Exception("地址未找到");        }        if (empty($goodsList)) {            throw new Exception("商品列表不能为空");        }        $config = get_addon_config('shop');        $orderSn = date("Ymdhis") . sprintf("%08d", $userId) . mt_rand(1000, 9999);        // 订单主表信息 (基于新表结构)        $orderInfo = [            'type'                 => 1, // 1:普通订单            'source'               => 'H5', // 订单来源 (暂定H5,可根据实际情况调整)            'order_sn'             => $orderSn,            'user_id'              => $userId,            'amount'               => 0, // 订单应付金额            'goods_price'          => 0, // 商品总费用            'goods_num'            => 0, // 商品数量            'discount_fee'         => 0, // 优惠金额            'coupon_discount_fee'  => 0, // 优惠券金额            'promo_discount_fee'   => 0, // 营销金额            'order_amount'         => 0, // 订单金额            'pay_amount'           => 0, // 实际付款金额            'pay_type'             => '', // 支付方式            'pay_time'             => null, // 支付时间            'delivery_type'        => 1, // 发货方式            'express_name'         => '', // 快递名称            'express_no'           => '', // 快递单号            'express_fee'          => 0, // 配送费用            'expire_time'          => time() + $config['order_timeout'], // 过期时间            'order_status'         => \app\common\Enum\OrderEnum::STATUS_CREATE, // 待付款            'invoice_status'       => 0, // 发票开具状态            'remark'               => $remark, // 用户备注            'user_coupon_id'       => $userCouponId ?: null,            'ip'                   => request()->ip(), // IP地址            'status'               => 'normal'        ];              // 通过商品列表计算订单明细        list($orderItem, $calculatedGoodsList, $userCoupon) = self::computeGoods($orderInfo, $goodsList, $userId, $address->area_id, $userCouponId);        // 创建订单        $order = self::createOrderWithTransaction($orderInfo, $orderItem, $calculatedGoodsList, $userCoupon, $address);                return $order;    }    /**     * 在事务中创建订单     * @param array $orderInfo 订单信息     * @param array $orderItem 订单商品列表     * @param array $goodsList 商品列表     * @param object $userCoupon 优惠券     * @param object $address 地址信息     * @return Order     * @throws Exception     */    protected static function createOrderWithTransaction($orderInfo, $orderItem, $goodsList, $userCoupon, $address)    {        $order = null;        Db::startTrans();        try {            // 创建订单            $order = Order::create($orderInfo, true);                        // 为每个订单商品添加订单ID和订单号            foreach ($orderItem as &$item) {                $item['order_id'] = $order->id;                $item['order_sn'] = $order->order_sn;                // 移除临时字段                unset($item['category_id'], $item['brand_id']);            }            unset($item);            // 创建订单地址信息            $orderAddressData = [                'order_id'      => $order->id,                'user_id'       => $orderInfo['user_id'],                'consignee'     => $address->receiver,                'mobile'        => $address->mobile,                'province_name' => $address->province->name ?? '',                'city_name'     => $address->city->name ?? '',                'district_name' => $address->area->name ?? '',                'address'       => $address->address,                'province_id'   => $address->province_id,                'city_id'       => $address->city_id,                'district_id'   => $address->area_id,            ];            OrderAddress::create($orderAddressData);                        // 减库存            foreach ($goodsList as $index => $item) {                if ($item->sku) {                    $item->sku->setDec('stocks', $item->nums);                }                $item->goods->setDec("stocks", $item->nums);            }                        // 计算单个商品折扣后的价格 (基于新字段名)            $saleamount = bcsub($order['amount'], $order['express_fee'], 2);            $saleratio = $order['goods_price'] > 0 ? bcdiv($saleamount, $order['goods_price'], 10) : 1;            $saleremains = $saleamount;                        foreach ($orderItem as $index => &$item) {                if (!isset($orderItem[$index + 1])) {                    $saleprice = $saleremains;                } else {                    $saleprice = $order['discount_fee'] == 0 ? bcmul($item['goods_original_price'], $item['nums'], 2) : bcmul(bcmul($item['goods_original_price'], $item['nums'], 2), $saleratio, 2);                }                $saleremains = bcsub($saleremains, $saleprice, 2);                $item['goods_price'] = $saleprice;            }            unset($item);                        // 批量创建订单商品数据            if (!empty($orderItem)) {                (new OrderGoods())->saveAll($orderItem);            }                        // 修改地址使用次数            if ($address) {                $address->setInc('used_nums');            }                        // 优惠券已使用            if (!empty($userCoupon)) {                $userCoupon->save(['is_used' => 2]);            }                        // 提交事务            Db::commit();        } catch (Exception $e) {            Db::rollback();            throw new Exception($e->getMessage());        }                // 记录操作        OrderAction::push($orderInfo['order_sn'], '系统', '订单创建成功');                // 订单应付金额为0时直接结算        if ($order['amount'] == 0) {            Order::settle($order->order_sn, 0);            $order = Order::get($order->id);        }                return $order;    }    /**     * 验证商品规格参数     * @param array $goods_list 商品列表     * @throws Exception     */    public static function validateGoodsList($goods_list)    {        if (empty($goods_list) || !is_array($goods_list)) {            throw new Exception("商品列表不能为空");        }        foreach ($goods_list as $item) {            if (!isset($item['goods_id']) || !is_numeric($item['goods_id']) || $item['goods_id'] <= 0) {                throw new Exception("商品ID无效");            }                        if (!isset($item['nums']) || !is_numeric($item['nums']) || $item['nums'] <= 0) {                throw new Exception("商品数量必须大于0");            }                        if (isset($item['goods_sku_id']) && !is_numeric($item['goods_sku_id'])) {                throw new Exception("商品规格ID无效");            }        }    }    /**     * 统一的订单计算方法(用于预览订单)     * @param array $goods_list 标准化的商品列表     * @param int $user_id 用户ID     * @param int $area_id 地区ID     * @param int $user_coupon_id 优惠券ID     * @return array     * @throws Exception     */    public static function calculateOrder($goodsList, $userId, $areaId = 0, $userCouponId = 0)    {        if (empty($goodsList)) {            throw new Exception("商品列表不能为空");        }              // 验证商品列表格式        self::validateGoodsList($goodsList);        // 订单基础信息(用于计算,不包含订单号)        $orderInfo = [            'amount'               => 0, // 应付金额            'goods_price'          => 0, // 商品金额 (不含运费)            'goods_num'            => 0, // 商品数量            'discount_fee'         => 0, // 总优惠金额            'coupon_discount_fee'  => 0, // 优惠券金额            'promo_discount_fee'   => 0, // 营销金额            'order_amount'         => 0, // 总金额 (含运费)            'express_fee'          => 0, // 运费        ];               // 计算商品明细        list($orderItem, $calculatedGoodsList, $userCoupon) = self::computeGoods($orderInfo, $goodsList, $userId, $areaId, $userCouponId);        return [            'orderItem' => $orderItem,            'goodsList' => $calculatedGoodsList,            'orderInfo' => $orderInfo,            'userCoupon' => $userCoupon        ];    }        /**     * 订单列表     *     * @param $param     * @return \think\Paginator     */    public static function getOrderList($userId = 0, $param =[],$status = [])    {        $pageSize = 15;        if (!empty($param['pageSize'])) {            $pageSize = $param['pageSize'];        }        return Order::with(['orderGoods'])            ->where(function ($query) use ($param) {                if (!empty($userId)) {                    $query->where('user_id', $userId);                }                               if (!empty($status)) {                    $query->whereIn('order_status', $status );                }                              if (isset($param['keywords']) && $param['keywords'] != '') {                    $query->where('order_sn', 'in', function ($query) use ($param) {                        return $query->name('shop_order_goods')->where('order_sn|goods_title', 'like', '%' . $param['q'] . '%')->field('order_sn');                    });                }            })            ->order('createtime desc')            ->paginate($pageSize, false, ['query' => request()->get()]);    }     /**     *      * @ 订单信息     * @param $orderId     * @param $userId     * @return array|false|\PDOStatement|string|Model     */    public static function getDetail($orderId, $userId)    {        return Order::with(['orderGoods'])        ->where('id', $orderId)        ->where('user_id', $userId)        ->find();    }    // 查询地址信息    public static function getAddressInfo($orderId)    {        return OrderAddress::where('order_id', $orderId)->find();    }          /**     * 判断订单是否失效     * @param $order_sn     * @return bool     */    public static function isExpired($orderSn)    {        $orderInfo = self::getByOrderSn($orderSn);        //订单过期        if (!$orderInfo['orderstate'] && !$orderInfo['paystate'] && time() > $orderInfo['expiretime']) {            // 启动事务            Db::startTrans();            try {                $orderInfo->save(['orderstate' => 2]);                //库存恢复                OrderGoods::setGoodsStocksInc($orderInfo->order_sn);                //恢复优惠券                UserCoupon::resetUserCoupon($orderInfo->user_coupon_id, $orderInfo->order_sn);                // 提交事务                Db::commit();            } catch (\Exception $e) {                // 回滚事务                Db::rollback();            }            return true;        }        return false;    }    public static function getByOrderSn($orderSn)    {        return Order::where('order_sn', $orderSn)->find();    }    public static function getByOrderId($orderId)    {        return Order::where('id', $orderId)->find();    }} 
 |