| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 | <?phpnamespace app\common\service;use Carbon\Carbon;use think\Validate;use think\facade\Db;use app\common\model\Io;use app\common\model\Good;use app\common\model\Repo;use app\common\model\Admin;use app\common\model\Stock;use app\common\model\IoDetail;use app\common\util\WhereBuilder;use app\common\util\PhpSpreadsheetExportV2;class IoService extends Service{    /**     * 处理分页请求     *      * @param array $params 请求参数数组     * @return mixed 分页结果     */    public function page($params = [])    {        // 自动处理请求参数        $this->autoParams($params);        // 从请求参数中获取相关值        $type = $this->pg('type');        $change_type = $this->pg('change_type');        $keyword = $this->pg('keyword');        $repo_id = $this->pg('repo_id');        $begin_date = $this->pg('begin_date');        $end_date = $this->pg('end_date');        // 如果存在结束日期,则向后延长一天        if ($end_date) {            $end_date = Carbon::parse($end_date)->addDay()->toDateString();        }        // 获取是否包含运输状态参数        $contain_transit = $this->pgd(false, 'contain_transit');        // 构建查询条件        $where = WhereBuilder::builder()            ->like('io.sn|r.name|io.remark', $keyword)            ->in('io.type', $type)            ->in('io.change_type', $change_type)            ->in('io.repo_id', $repo_id)            ->between('io.date', $begin_date, $end_date)            ->where('d.transit_status', 'in', [IoDetail::TRANSIT_STATUS_TRANSIT, IoDetail::TRANSIT_STATUS_TRANSIT], $contain_transit)            ->build();        // 构建查询对象        $query = (new Io)            ->alias('io')            ->field('io.*, r.name as repo_name')            ->join('repo r', 'r.id = io.repo_id');        // 如果包含运输状态,则进行关联查询        if ($contain_transit) {            $query = $query->join('io_detail d', 'd.io_id = io.id');        }        // 执行分页查询        $page = $query->where($where)            ->order('io.create_time desc')            ->paginate($this->tp6Page());        // 添加附加字段到分页结果集        $page->getCollection()->append(['change_type_text']);        // 返回分页结果        return $page;    }    /**     * 获取Io信息     *      * @param array $params 请求参数数组     * @return mixed Io对象     */    public function info($params = [])    {        // 自动处理请求参数        $this->autoParams($params);        // 查询单个Io对象        $io = $this->one(Io::class);        // 添加附加字段到Io对象        $io->append(['change_type_text', 'details', 'repo', 'details.good']);        // 返回Io对象        return $io;    }    /**     * 创建运输记录事务     *      * @param Admin $admin 管理员对象     * @param array $params 请求参数数组     * @return mixed 运输记录创建结果     */    public function createTrans(Admin $admin, $params = [])    {        // 使用数据库事务执行创建操作        return Db::transaction(fn() => $this->create($admin, $params));    }    /**     * 创建运输记录     *      * @param Admin $admin 管理员对象     * @param array $params 请求参数数组     * @return mixed 创建的Io对象     * @throws \Exception 创建失败时抛出异常     */    public function create(Admin $admin, $params = [])    {        // 自动处理请求参数        $params = $this->autoParams($params);        // 如果存在管理员对象,则将管理员ID赋值给参数数组        if ($admin) {            $params['admin_id'] = $admin->id;        }        // 查询Repo对象        $repo = $this->one(Repo::class, 'repo_id');        // 获取出入库明细        $details = $this->pg('details');        // 过滤掉数量为0的明细        $details = array_filter($details, fn($detail) => $detail['num']);        // 创建Io对象        $io = Io::create($params);        // 赋值并检查明细        foreach ($details as &$detail) {            $detail['io_id'] = $io->id;            $detail['date'] = isset($detail['date']) && $detail['date'] ? $detail['date'] : $io->date;            $detail['type'] = isset($detail['type']) && $detail['type'] ? $detail['type'] : $io->type;            $detail['repo_id'] = isset($detail['repo_id']) && $detail['repo_id'] ? $detail['repo_id'] : $io->repo_id;            // 检查出入库数量            if ($detail['num'] == 0) {                $good = Good::find($detail['good_id']);                throw $this->exception("物品--{$good?->name}的出入库数量不能为0");            }            if ($detail['num'] > 0 && $detail['type'] == Io::TYPE_OUT) {                $good = Good::find($detail['good_id']);                throw $this->exception("物品--{$good?->name}的出入库数量大于0但是类型为出库");            }            if ($detail['num'] < 0 && $detail['type'] == Io::TYPE_IN) {                $good = Good::find($detail['good_id']);                throw $this->exception("物品--{$good?->name}的出入库数量小于0但是类型为入库");            }        }        // 保存出入库明细        $details = (new IoDetail)->saveAll($details);        // 在oninsert事件中进行库存变更        $io->details = $details;        // 返回创建的Io对象        return $io;    }    /**     * 导出运输记录     *      * @param array $params 请求参数数组     * @return mixed 导出结果     */    public function export($params = [])    {        // 自动处理请求参数        $this->autoParams($params);        $type = $this->pg('type');        // 获取起始日期和结束日期        $begin_date = $this->pg('begin_date');        $end_date = $this->pg('end_date');        // 如果存在结束日期,则向后延长一天        if ($end_date) {            $end_date = Carbon::parse($end_date)->addDay()->toDateString();        }        // 构建查询条件        $where = WhereBuilder::builder()            ->in('io.type', $type)            ->between('i.date', $begin_date, $end_date)            ->build();        // 导出表格的表头        $header = [            [                '编号',                '仓库名',                '出入库类型',                '变更原因',                '备注',                '来源',                '创建时间',                '日期',                '物品名',                '数量',                '明细备注',                '在途状态',                '归还数量',                '遗失数量'            ]        ];        // 构建查询字段中的change_type_export        $changeTypeField = <<<SQL            CASE i.change_type                 WHEN 1 THEN "出入库"                 WHEN 2 THEN "调拨"                 WHEN 3 THEN "盘点"                 ELSE "出入库"             END as change_type_export        SQL;        // 构建查询字段中的transit_status_export        $transitStatusField = <<<SQL            CASE d.transit_status                WHEN "TRANSIT" THEN "在途"                WHEN "COMPLETED" THEN "完成"                ELSE ""            END as transit_status_export        SQL;        // 执行查询        $details = (new IoDetail)->alias('d')            ->field('i.sn, r.name as repo_name, if(i.type = 1, "入库", "出库")')            ->field($changeTypeField)            ->field('i.remark, i.source, i.create_time, i.date')            ->field('g.name as good_name, d.num, d.remark as detail_remark')            ->field($transitStatusField)            ->field('d.transit_received, d.transit_lost')            ->join('io i', 'i.id = d.io_id', 'LEFT')            ->join('repo r', 'r.id = i.repo_id', 'LEFT')            ->join('good g', 'g.id = d.good_id', 'LEFT')            ->where($where)            ->select()            ->toArray();        // 导出表格文件        $res = PhpSpreadsheetExportV2::outputFile(            $details,            $header,            'io' . date('Ymdis'),            []        );        // 返回导出结果        return $res;    }    public function completeTrans(Admin $admin, $params = [])    {        return Db::transaction(fn() => $this->complete($admin, $params));    }    public function complete(Admin $admin, $params = [])    {        $params = $this->autoParams($params);        $this->validate($params, new CompleteValidate);        $transits = $this->pg('transits');        foreach ($transits as &$transit) {            /**             * @var IoDetail             */            $detail = IoDetail::find($transit['id']);            $stock = (new Stock)                ->where('repo_id', '=', $detail->repo_id)                ->where('good_id', '=', $detail->good_id)                ->find();            // 检查数量            $num = abs($detail->num);            if ($transit['transit_received'] + $transit['transit_lost'] == $num) {                $transit['transit_status'] = IoDetail::TRANSIT_STATUS_COMPLETED;            } elseif ($transit['transit_received'] + $transit['transit_lost'] > $num) {                throw $this->exception("到达/归还数量{$transit['transit_received']} 加 在途遗失数{$transit['transit_lost']} 大于该出入库明细的数量:{$num}");            } elseif ($transit['transit_received'] < $detail->transit_received) {                throw $this->exception("到达/归还数量{$transit['transit_received']} 比原来还少了!(原数量:{$detail->transit_received}) 如发现归还数量错误,请使用盘点功能");            } elseif ($transit['transit_lost'] < $detail->transit_lost) {                throw $this->exception("在途遗失数量{$transit['transit_lost']} 比原来还少了!(原数量:{$detail->transit_lost}) 如发现遗失后又归还,请使用盘点功能");            } else {                $transit['transit_status'] = isset($transit['transit_status']) ? $transit['transit_status'] : IoDetail::TRANSIT_STATUS_TRANSIT;            }            $stock->num += self::getTypeMul($detail->type) * ($transit['transit_received'] - $detail->transit_received);            $stock->save();        }        (new IoDetail)->allowField(['transit_status', 'transit_received', 'transit_lost'])->saveAll($transits);        return true;    }    public function revertTrans(Admin $admin, $params = []) {        return Db::transaction(fn() => $this->revert($admin, $params));    }    protected function revert(Admin $admin, $params = [])    {        $this->autoParams($params);        $io = $this->one(Io::class);        if ($io->revert_id) {            return true;        }        $revertData = [            'repo_id' => $io->repo_id,            'date' => date('Ymd'),            'type' => self::inverseIoType($io['type']),            'sn' => 'REVERT_T' . hrtime(true) . '_SN' . rand(100000, 999999),            'remark' => "回滚自$io->sn",            'admin_id' => $admin->id,            'change_type' => $io->change_type,            'source' => "$io->sn"        ];        $revert = Io::create($revertData);        $revertDetails = [];        foreach ($io->details as $detail) {            $revertDetail = [                'repo_id' => $detail->repo_id,                'good_id' => $detail->good_id,                'num' => -$detail['num'],                'date' => date('Ymd'),                'type' => self::inverseIoType($detail['type']),                'remark' => '回滚',                'io_id' => $revert->id,            ];            $revertDetails[] = $revertDetail;        }        $details = (new IoDetail)->saveAll($revertDetails);        $io->revert_id = $revert->id;        $io->save();        return $revert;    }    /**     * 翻转出入库类型     *     * @param int $type 出入库类型     * @return int 翻转后的出入库类型     */    protected static function inverseIoType(int $type)    {        return $type == Io::TYPE_IN ? Io::TYPE_OUT : Io::TYPE_IN;    }    /**     * 获取出入库类型的乘数     *     * @param int $type 出入库类型     * @return int 出入库类型的乘数     */    protected static function getTypeMul(int $type)    {        return $type == Io::TYPE_IN ? 1 : -1;    }}class CreateIoValidate extends Validate{    protected $rule = [        'repo_id|仓库' => 'require',        'valid|状态' => 'require',        'type|出入库类型' => 'require',        'change_type|变更类型' => 'require',        'details|出入库内容' => 'checkDetails',    ];    protected function checkDetails($details)    {        if (!is_array($details)) {            return 'details必须为数组';        }        if (!$details) {            return '出入库内容不能为空';        }        $setAndNotEmpty = fn($item, $index) => isset($item[$index]) && $item[$index];        $requires = ['good_id', 'num', 'type', 'remark'];        foreach ($details as $index => $detail) {            foreach ($requires as $require) {                if (!$setAndNotEmpty($detail, $require)) {                    return "details[$index].$require 不能为空";                }            }            $good_id = $detail['good_id'];            $good = Good::find($good_id);            if (!$good) {                return "未找到物品对象, id={$good_id}";            }        }        return true;    }}class CompleteValidate extends Validate{    protected $rule = [        'transits' => 'array'    ];}
 |