all()); $itemM = new OutboundItem(); foreach ($list as &$o) { $o['_items'] = $itemM->whereAll('ob_id', $o['id']); } return $this->renderSubsys('psi', 'psi/outbounds', ['list' => $list], \psi_nav(), 'outbounds'); } public function create() { $soNo = $this->get('so'); $so = $soNo ? (new SalesOrder())->where('order_no', $soNo) : null; $soItems = $so ? (new SalesOrderItem())->whereAll('so_id', $so['id']) : []; return $this->renderSubsys('psi', 'psi/outbound_form', [ 'o' => null, 'items' => [], 'so' => $so, 'soItems' => $soItems, 'products' => (new Product())->all(), ], \psi_nav(), 'outbounds'); } public function store() { if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !csrf_check()) return $this->redirect('PSI/outbounds/create'); $items = $this->parseItems(); if (empty($items)) { $this->flash('请至少添加一条出库明细', 'err'); return $this->redirect('PSI/outbounds/create'); } $soNo = trim($this->post('so_no')); $so = $soNo ? (new SalesOrder())->where('order_no', $soNo) : null; $orderNo = \psi_gen_no('OUT'); $id = (new Outbound())->insert([ 'order_no' => $orderNo, 'so_no' => $soNo, 'customer' => trim($this->post('customer')) ?: ($so['customer'] ?? ''), 'salesman' => trim($this->post('salesman')) ?: ($so['salesman'] ?? ($_SESSION['admin_name'] ?? '')), 'warehouse' => trim($this->post('warehouse')), 'status' => 'delivered', 'delivery_date' => $this->post('delivery_date') ?: null, 'remark' => trim($this->post('remark')), 'created_at' => date('Y-m-d H:i:s'), ]); $this->applyItems($id, $items, $soNo, $orderNo); if ($so) { \psi_recompute_so($so['id']); $st = (new SalesOrder())->find($so['id'])['status']; (new Outbound())->update($id, ['status' => $st === 'delivered' ? 'delivered' : 'partial']); } $this->flash('出库单已保存', 'ok'); if ($this->post('auto_print', '1') !== '0') { return $this->redirect('PSI/outbounds/print/' . $id); } return $this->redirect('PSI/outbounds/show/' . $id); } public function show($id) { $o = (new Outbound())->find($id); if (!$o) { \Core\App::notFound(); return; } $items = (new OutboundItem())->whereAll('ob_id', $id); return $this->renderSubsys('psi', 'psi/outbound_show', ['o' => $o, 'items' => $items], \psi_nav(), 'outbounds'); } public function edit($id) { $o = (new Outbound())->find($id); if (!$o) { \Core\App::notFound(); return; } $items = (new OutboundItem())->whereAll('ob_id', $id); $so = $o['so_no'] ? (new SalesOrder())->where('order_no', $o['so_no']) : null; $soItems = $so ? (new SalesOrderItem())->whereAll('so_id', $so['id']) : []; return $this->renderSubsys('psi', 'psi/outbound_form', [ 'o' => $o, 'items' => $items, 'so' => $so, 'soItems' => $soItems, 'products' => (new Product())->all(), ], \psi_nav(), 'outbounds'); } public function update($id) { if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !csrf_check()) return $this->redirect('PSI/outbounds/edit/' . $id); $o = (new Outbound())->find($id); if (!$o) { \Core\App::notFound(); return; } $items = $this->parseItems(); if (empty($items)) { $this->flash('请至少添加一条出库明细', 'err'); return $this->redirect('PSI/outbounds/edit/' . $id); } $this->reverseItems($id); // 回冲库存与已交付 (new Outbound())->update($id, [ 'customer' => trim($this->post('customer')), 'salesman' => trim($this->post('salesman')) ?: ($_SESSION['admin_name'] ?? ''), 'warehouse' => trim($this->post('warehouse')), 'delivery_date' => $this->post('delivery_date') ?: null, 'remark' => trim($this->post('remark')), ]); $this->applyItems($id, $items, $o['so_no'], $o['order_no']); if ($o['so_no']) { $so = (new SalesOrder())->where('order_no', $o['so_no']); if ($so) \psi_recompute_so((int)$so['id']); } $this->flash('出库单已更新', 'ok'); return $this->redirect('PSI/outbounds/show/' . $id); } public function destroy($id) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && csrf_check()) { $this->reverseItems($id); (new OutboundItem())->deleteRaw('ob_id', $id); (new Outbound())->delete($id); $this->flash('出库单已删除', 'ok'); } return $this->redirect('PSI/outbounds'); } public function printDoc($id) { $o = (new Outbound())->find($id); if (!$o) { \Core\App::notFound(); return; } $items = (new OutboundItem())->whereAll('ob_id', $id); $body = \App\Core\View::render('psi/outbound_print', ['o' => $o, 'items' => $items]); echo \psi_print_shell('出库单 ' . $o['order_no'], $body); } /** 应用出库明细:写明细 + 扣库存 + 回写销售订单已交付量 */ private function applyItems(int $obId, array $items, string $soNo, string $orderNo): void { $itemM = new OutboundItem(); $so = $soNo ? (new SalesOrder())->where('order_no', $soNo) : null; foreach ($items as $it) { $it['ob_id'] = $obId; $itemM->insert($it); if ((int)($it['product_id'] ?? 0) > 0) { StockHelper::adjustStock((int)$it['product_id'], (int)$it['qty'], 'out', '销售出库', $orderNo); } if ((int)($it['so_item_id'] ?? 0) > 0) { $si = (new SalesOrderItem())->find($it['so_item_id']); if ($si) { (new SalesOrderItem())->update($si['id'], ['delivered_qty' => (int)$si['delivered_qty'] + (int)$it['qty']]); } } } } /** 回冲:把出库明细的库存与销售订单已交付量还原 */ private function reverseItems(int $obId): void { $old = (new OutboundItem())->whereAll('ob_id', $obId); foreach ($old as $oi) { if ((int)($oi['product_id'] ?? 0) > 0) { StockHelper::adjustStock((int)$oi['product_id'], (int)$oi['qty'], 'in', '出库冲正', $oi['ob_id'] ?? ''); } if ((int)($oi['so_item_id'] ?? 0) > 0) { $si = (new SalesOrderItem())->find($oi['so_item_id']); if ($si) { $back = max(0, (int)$si['delivered_qty'] - (int)$oi['qty']); (new SalesOrderItem())->update($si['id'], ['delivered_qty' => $back]); } } } (new OutboundItem())->deleteRaw('ob_id', $obId); } private function parseItems(): array { $raw = $_POST['items'] ?? []; $out = []; foreach ($raw as $row) { $name = trim((string)($row['name'] ?? '')); $qty = (int)($row['qty'] ?? 0); $price = (float)($row['price'] ?? 0); if ($name === '' || $qty <= 0) continue; $out[] = [ 'so_item_id' => (int)($row['so_item_id'] ?? 0), 'product_id' => (int)($row['product_id'] ?? 0), 'name' => $name, 'spec' => trim((string)($row['spec'] ?? '')), 'unit' => trim((string)($row['unit'] ?? '')), 'qty' => $qty, 'price' => $price, 'amount' => round($qty * $price, 2), ]; } return $out; } }