<?php
// +----------------------------------------------------------------------
// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2017~2023 https://www.yiovo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
// +----------------------------------------------------------------------
// | Author: 萤火科技 <admin@yiovo.com>
// +----------------------------------------------------------------------
declare (strict_types=1);

namespace app\store\service\order;

use app\common\enum\dealer\withdraw\PayType;
use think\facade\Db;
use app\store\model\Order as OrderModel;
use app\store\model\Express as ExpressModel;
use app\store\model\OrderGoods as OrderGoodsModel;
use app\store\model\order\Delivery as DeliveryModel;
use app\store\model\order\DeliveryGoods as DeliveryGoodsModel;
use app\common\service\BaseService;
use app\common\service\Message as MessageService;
use app\common\service\order\Shipping as ShippingService;
use app\common\service\order\source\Factory as OrderSourceFactory;
use cores\exception\BaseException;
use app\common\enum\order\{OrderType as OrderTypeEnum, DeliveryStatus as DeliveryStatusEnum};
use app\common\library\helper;
use app\common\library\FileLocal;
use app\common\library\phpoffice\ReadExecl;

/**
 * 服务层:订单发货事件
 * Class Delivery
 * @package app\store\service\order
 */
class Delivery extends BaseService
{
    // 发货方式: 手动录入
    const DELIVERY_METHOD_MANUAL = 10;

    // 发货方式: 无需物流
    const DELIVERY_METHOD_NONE = 20;

    /**
     * 手动发货
     * @param int $orderId
     * @param array $param
     * @return bool
     */
    public function delivery(int $orderId, array $param): bool
    {
        if(empty($param['expressId'])) {
            $this->error = '物流公司不能为空';
            return false;
        }
        if (empty($param['expressNo'])) {
            $this->error = '物流单号不能为空';
            return false;
        }
        // 设置默认的参数
        $param = $this->buildParam($param);
        // 获取订单详情
        $detail = OrderModel::detail($orderId);
        // 验证订单是否满足发货条件
        if (!$this->verifyDelivery([$detail])) {
            return false;
        }
        Db::transaction(function () use ($detail, $param, $orderId) {
            // 订单发货事件
            $this->deliveryEvent($detail, $param);
            // 获取已发货的订单
            $completed = OrderModel::detail($orderId, ['goods', 'trade']);
            // 发货信息同步微信平台
            (new ShippingService)->syncMpWeixinShipping($completed, [
                // 同步至微信小程序《发货信息录入》
                'syncMpWeixinShipping' => $param['syncMpWeixinShipping'],
                // 物流模式:1物流配送 3虚拟商品 4用户自提
                'logisticsType' => [
                    self::DELIVERY_METHOD_MANUAL => ShippingService::DELIVERY_EXPRESS,
                    self::DELIVERY_METHOD_NONE => ShippingService::DELIVERY_VIRTUAL
                ][$param['deliveryMethod']],
                // 物流公司ID
                'expressId' => $param['expressId'],
                // 物流单号
                'expressNo' => $param['expressNo'],
            ]);
            // 发送消息通知 [未实现]
            // $this->sendDeliveryMessage([$completed]);
        });
        return true;
    }

    /**
     * 设置默认的参数
     * @param array $param
     * @return array
     */
    private function buildParam(array $param): array
    {
        return helper::setQueryDefaultValue($param, [
            // 发货方式 (10手动录入 20无需物流)
            'deliveryMethod' => self::DELIVERY_METHOD_MANUAL,
            // 物流公司ID
            'expressId' => 0,
            // 物流单号
            'expressNo' => '',
            // 整单发货
            'isAllPack' => false,
            // 发货的商品
            'packGoodsData' => [],
            // 同步至微信小程序《发货信息录入》
            'syncMpWeixinShipping' => 1,
        ]);
    }

    /**
     * 批量发货
     * @return bool
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws BaseException
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function batch(): bool
    {
        // 批量发货的数据
        $data = $this->batchData();
        // 订单列表ID集
        $orderIds = helper::getArrayColumn($data, 'orderId');
        // 获取订单列表数据
        $model = new OrderModel;
        $orderList = $model->getListByIds($orderIds, ['user', 'address', 'goods', 'express']);
        // 验证订单是否满足发货条件
        if (!$this->verifyDelivery($orderList)) {
            return false;
        }
        // 订单发货事件
        foreach ($orderList as $item) {
//            Db::transaction(function () use ($data, $item) {
//                $param = [
//                    // 发货方式
//                    'deliveryMethod' => $data[$item['order_id']]['deliveryMethod'],
//                    // 物流公司ID
//                    'expressId' => $data[$item['order_id']]['expressId'],
//                    // 物流单号
//                    'expressNo' => $data[$item['order_id']]['expressNo'],
//                    // 整单发货
//                    'isAllPack' => true,
//                    // 同步至微信小程序《发货信息录入》
//                    'syncMpWeixinShipping' => 0,
//                ];
//                // 执行订单发货
//                $this->deliveryEvent($item, $param);
//                // 获取已发货的订单
//                $completed = OrderModel::detail($item['order_id'], ['goods', 'trade']);
//                // 发货信息同步微信平台
//                $this->syncMpWeixinShipping($completed, $param);
//                // 发送消息通知 [未实现]
//                // $this->sendDeliveryMessage([$completed]);
//            });

            $this->deliveryEvent($item, [
                // 发货方式
                'deliveryMethod' => $data[$item['order_id']]['deliveryMethod'],
                // 物流公司ID
                'expressId' => $data[$item['order_id']]['expressId'],
                // 物流单号
                'expressNo' => $data[$item['order_id']]['expressNo'],
                // 整单发货
                'isAllPack' => true
            ]);
        }
        return true;
    }

    /**
     * 整理批量发货的数据
     * @return array
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws BaseException
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    private function batchData(): array
    {
        // 接收用户上传的模板文件
        $file = request()->file('file');
        empty($file) && throwError('很抱歉,您没有上传模板文件');
        // 写入到本地临时目录
        $path = FileLocal::writeFile($file, 'batch-delivery', $this->getStoreId());
        // 读取excel数据 (仅读取200条)
        $data = ReadExecl::load($path, 0, 3, 200 + 2);
        // 去除标题数据
        unset($data[1], $data[2]);
        // 过滤重复的单号
        $data = helper::arrayUnique($data, 'A');
        // 获取物流公司ID
        $expressIds = ExpressModel::getExpressIds(helper::getArrayColumn($data, 'B'));
        // 获取订单ID集
        $orderIds = OrderModel::getOrderIds(helper::getArrayColumn($data, 'A'));
        // 整理订单发货数据
        $orderData = [];
        foreach ($data as $item) {
            if (empty($item['A']) || empty($item['B'])) {
                throwError("很抱歉,订单号或物流公司不能为空");
            }
            if (!isset($orderIds[$item['A']])) {
                throwError("很抱歉,订单号[{$item['A']}]不存在");
            }
            if ($item['B'] !== '无需物流' && !isset($expressIds[$item['B']])) {
                throwError("很抱歉,物流公司[{$item['B']}]不存在");
            }
            $orderData[$orderIds[$item['A']]] = [
                // 发货方式
                'deliveryMethod' => $item['B'] !== '无需物流' ? self::DELIVERY_METHOD_MANUAL : self::DELIVERY_METHOD_NONE,
                // 订单ID
                'orderId' => $orderIds[$item['A']],
                // 物流公司ID
                'expressId' => $item['B'] !== '无需物流' ? $expressIds[$item['B']] : 0,
                // 物流单号
                'expressNo' => $item['C']
            ];
        }
        return $orderData;
    }

    /**
     * 订单发货事件
     * @param $order
     * @param array $param 发货参数
     * @return bool
     */
    public function deliveryEvent($order, array $param): bool
    {
        // 默认参数
        $param = helper::setQueryDefaultValue($param, [
            // 发货方式 (10手动录入 20无需物流 30电子面单)
            'deliveryMethod' => self::DELIVERY_METHOD_MANUAL,
            // 物流公司ID
            'expressId' => 0,
            // 物流单号
            'expressNo' => '',
            // 为整单发货
            'isAllPack' => false,
            // 发货的商品 (整单发货时无需传入)
            'packGoodsData' => [],
            // 电子面单内容
            'eorderHtml' => ''
        ]);
        // 整单发货时获取所有未发货的商品
        $param['isAllPack'] && $param['packGoodsData'] = $this->getAllPackGoods($order);
        // 实物订单
        if ($order['order_type'] == OrderTypeEnum::PHYSICAL) {
            // 写入发货单
            $this->recordDeliveryOrder($order, $param);
            // 判断订单是否已全部发货
            $deliveredAll = $this->checkDeliveredAll($order['goods'], $param);
            // 更新订单的发货状态
            $deliveryStatus = $deliveredAll ? DeliveryStatusEnum::DELIVERED : DeliveryStatusEnum::PART_DELIVERED;
            $this->updateDeliveryStatus([$order['order_id']], $deliveryStatus);
        }
        // 虚拟订单
        if ($order['order_type'] == OrderTypeEnum::VIRTUAL) {
            $this->updateDeliveryStatus([$order['order_id']], DeliveryStatusEnum::DELIVERED);
        }
        return true;
    }

    /**
     * 整单发货时获取订单所有未发货的商品
     * @param $order
     * @return array
     */
    private function getAllPackGoods($order): array
    {
        $data = [];
        foreach ($order['goods'] as $goods) {
            if ($goods['delivery_status'] != DeliveryStatusEnum::DELIVERED) {
                $data[] = [
                    'orderGoodsId' => $goods['order_goods_id'],
                    'deliveryNum' => $goods['total_num'] - $goods['delivery_num'],
                ];
            }
        }
        return $data;
    }

    /**
     * 写入发货单
     * @param $order
     * @param array $param
     */
    private function recordDeliveryOrder($order, array $param)
    {
        // 新增发货单记录
        $DeliveryModel = new DeliveryModel;
        $DeliveryModel->save([
            'order_id' => $order['order_id'],
            'delivery_method' => $param['deliveryMethod'],
            'express_id' => $param['expressId'],
            'express_no' => $param['deliveryMethod'] == self::DELIVERY_METHOD_NONE ? '' : $param['expressNo'],
            'eorder_html' => $param['eorderHtml'] ?? '',
            'store_id' => $this->getStoreId(),
        ]);
        // 新增发货单商品记录
        $this->recordRelivery($order, $param['packGoodsData'], (int)$DeliveryModel['delivery_id']);
        // 更新订单商品记录
        $this->updateOrderGoods($order, $param['packGoodsData']);
    }

    /**
     * 新增发货单商品记录
     * @param $order
     * @param array $packGoodsData
     * @param int $deliveryId
     */
    private function recordRelivery($order, array $packGoodsData, int $deliveryId)
    {
        // 整理发货单记录
        $dataset = [];
        foreach ($packGoodsData as $item) {
            $goods = helper::arraySearch($order['goods'], 'order_goods_id', $item['orderGoodsId']);
            !empty($goods) && $dataset[] = [
                'delivery_id' => $deliveryId,
                'order_id' => $order['order_id'],
                'order_goods_id' => $item['orderGoodsId'],
                'goods_id' => $goods['goods_id'],
                'delivery_num' => $item['deliveryNum'],
                'store_id' => $this->getStoreId(),
            ];
        }
        (new DeliveryGoodsModel)->addAll($dataset);
    }

    /**
     * 更新订单商品记录
     * @param $order
     * @param array $packGoodsData
     */
    private function updateOrderGoods($order, array $packGoodsData)
    {
        // 更新订单商品记录
        $dataset = [];
        foreach ($order['goods'] as $goods) {
            // 发货的订单商品数据
            $item = helper::arraySearch($packGoodsData, 'orderGoodsId', $goods['order_goods_id']);
            if (!$item) {
                continue;
            }
            // 判断订单商品的发货数量是否已完成
            $newDeliveryNum = $item['deliveryNum'] + $goods['delivery_num'];
            // 记录更新内容
            $dataset[] = [
                'where' => ['order_goods_id' => $goods['order_goods_id']],
                'data' => [
                    'delivery_num' => $newDeliveryNum,
                    'delivery_status' => $newDeliveryNum >= $goods['total_num'] ? DeliveryStatusEnum::DELIVERED
                        : DeliveryStatusEnum::PART_DELIVERED
                ]
            ];
        }
        (new OrderGoodsModel)->updateAll($dataset);
    }

    /**
     * 判断订单是否已全部发货
     * @param $orderGoodsList
     * @param array $param
     * @return bool
     */
    private function checkDeliveredAll($orderGoodsList, array $param): bool
    {
        foreach ($orderGoodsList as $goods) {
            // 查询商品是否在表单中
            $packGoods = helper::arraySearch($param['packGoodsData'], 'orderGoodsId', $goods['order_goods_id']);
            if (!empty($packGoods)) {
                // 判断订单商品的发货数量是否满足
                if (($packGoods['deliveryNum'] + $goods['delivery_num']) < $goods['total_num']) {
                    return false;
                }
            } else {
                // 判断订单商品的发货状态
                if ($goods['delivery_status'] != DeliveryStatusEnum::DELIVERED) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 确认发货后发送消息通知
     * @param $orderList
     * @return void
     */
    private function sendDeliveryMessage($orderList): void
    {
        // 发送消息通知
        foreach ($orderList as $item) {
            MessageService::send('order.delivery', ['order' => $item], $this->getStoreId());
        }
    }

    /**
     * 更新订单发货状态(批量)
     * @param array $orderIds
     * @param int $deliveryStatus 发货状态
     */
    private function updateDeliveryStatus(array $orderIds, int $deliveryStatus): void
    {
        // 整理更新的数据
        $data = [];
        foreach ($orderIds as $orderId) {
            $data[] = [
                'data' => [
                    'delivery_status' => $deliveryStatus,
                    'delivery_time' => time(),
                ],
                'where' => ['order_id' => $orderId]
            ];
        }
        // 批量更新
        (new OrderModel)->updateAll($data);
    }

    /**
     * 验证订单是否满足发货条件
     * @param $orderList
     * @return bool
     */
    public function verifyDelivery($orderList): bool
    {
        foreach ($orderList as $order) {
            $orderSource = OrderSourceFactory::getFactory($order['order_source']);
            if (!$orderSource->checkOrderByDelivery($order)) {
                $this->error = $orderSource->getError();
                return false;
            }
        }
        return true;
    }
}