사이트·관리자 봉투 물류 기능(수불·통계·레포트·재고·발주)과 DB·메뉴·E2E를 운영 반영한다.
통계 분석(전년대비·월별·계절별), 수급계획·LOT 수불, 지정판매소·실사·메뉴 링크 등을 포함한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,17 +4,56 @@ namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\BagIssueModel;
|
||||
use App\Models\BagIssueItemCodeModel;
|
||||
use App\Models\BagInventoryModel;
|
||||
use App\Models\CodeKindModel;
|
||||
use App\Models\CodeDetailModel;
|
||||
use App\Models\FreeRecipientModel;
|
||||
use App\Models\PackagingUnitModel;
|
||||
|
||||
class BagIssue extends BaseController
|
||||
{
|
||||
private BagIssueModel $issueModel;
|
||||
private BagIssueItemCodeModel $issueItemCodeModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->issueModel = model(BagIssueModel::class);
|
||||
$this->issueItemCodeModel = model(BagIssueItemCodeModel::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 낱장 수량을 품목코드 단위로 분해한다.
|
||||
*
|
||||
* @return array<int,array{issueCode:string,qty:int}>
|
||||
*/
|
||||
private function buildIssueCodeRows(int $bi2Idx, int $sheetQty, array $packUnit): array
|
||||
{
|
||||
$sheetQty = max(0, $sheetQty);
|
||||
if ($sheetQty <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$chunkSize = max(
|
||||
1,
|
||||
(int) ($packUnit['totalPerBox'] ?? 0),
|
||||
(int) ($packUnit['packPerSheet'] ?? 0)
|
||||
);
|
||||
|
||||
$rows = [];
|
||||
$remaining = $sheetQty;
|
||||
$seq = 1;
|
||||
while ($remaining > 0) {
|
||||
$qty = min($chunkSize, $remaining);
|
||||
$rows[] = [
|
||||
'issueCode' => sprintf('%d-%06d-%03d', (int) date('y'), $bi2Idx, $seq),
|
||||
'qty' => $qty,
|
||||
];
|
||||
$remaining -= $qty;
|
||||
$seq++;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public function index()
|
||||
@@ -62,48 +101,219 @@ class BagIssue extends BaseController
|
||||
'bi2_issue_type' => 'required|max_length[20]',
|
||||
'bi2_issue_date' => 'required|valid_date[Y-m-d]',
|
||||
'bi2_dest_name' => 'required|max_length[100]',
|
||||
'bi2_bag_code' => 'required|max_length[50]',
|
||||
'bi2_qty' => 'required|is_natural_no_zero',
|
||||
// 사이트 다건 입력(item_bag_code/item_qty)과 기존 관리자 단건 입력을 함께 허용
|
||||
'bi2_bag_code' => 'permit_empty|max_length[50]',
|
||||
'bi2_qty' => 'permit_empty|is_natural_no_zero',
|
||||
];
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$bagCode = $this->request->getPost('bi2_bag_code');
|
||||
$qty = (int) $this->request->getPost('bi2_qty');
|
||||
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, (string) $bagCode, $lgIdx) : null;
|
||||
$bagName = $detail ? $detail->cd_name : '';
|
||||
$issueType = trim((string) $this->request->getPost('bi2_issue_type'));
|
||||
$destType = trim((string) ($this->request->getPost('bi2_dest_type') ?? ''));
|
||||
$destName = trim((string) ($this->request->getPost('bi2_dest_name') ?? ''));
|
||||
$destDongCode = trim((string) ($this->request->getPost('bi2_dest_dong_code') ?? ''));
|
||||
|
||||
if ($destType === '') {
|
||||
$destType = '동사무소';
|
||||
}
|
||||
if ($issueType === '공공용' && $destType === '동사무소') {
|
||||
$destType = '구청';
|
||||
}
|
||||
|
||||
if ($issueType === '무료용' && $destDongCode !== '') {
|
||||
$existsFreeDong = model(FreeRecipientModel::class)
|
||||
->where('fr_lg_idx', $lgIdx)
|
||||
->where('fr_state', 1)
|
||||
->where('fr_dong_code', $destDongCode)
|
||||
->first();
|
||||
if (! $existsFreeDong) {
|
||||
return redirect()->back()->withInput()->with('error', '선택한 불출처는 무료용 대상이 아닙니다.');
|
||||
}
|
||||
}
|
||||
|
||||
$invRows = model(BagInventoryModel::class)
|
||||
->where('bi_lg_idx', $lgIdx)
|
||||
->where('bi_qty >', 0)
|
||||
->findAll();
|
||||
$inventoryMap = [];
|
||||
foreach ($invRows as $inv) {
|
||||
$code = (string) ($inv->bi_bag_code ?? '');
|
||||
if ($code === '') {
|
||||
continue;
|
||||
}
|
||||
$inventoryMap[$code] = [
|
||||
'qty' => (int) ($inv->bi_qty ?? 0),
|
||||
'name' => (string) ($inv->bi_bag_name ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$unitRows = model(PackagingUnitModel::class)
|
||||
->where('pu_lg_idx', $lgIdx)
|
||||
->where('pu_state', 1)
|
||||
->findAll();
|
||||
$packMap = [];
|
||||
foreach ($unitRows as $unit) {
|
||||
$code = (string) ($unit->pu_bag_code ?? '');
|
||||
if ($code === '') {
|
||||
continue;
|
||||
}
|
||||
$packMap[$code] = [
|
||||
'packPerSheet' => max(1, (int) ($unit->pu_pack_per_sheet ?? 1)),
|
||||
'totalPerBox' => max(1, (int) ($unit->pu_total_per_box ?? 1)),
|
||||
];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$itemCodes = $this->request->getPost('item_bag_code');
|
||||
$itemQtys = $this->request->getPost('item_qty');
|
||||
$itemPacks = $this->request->getPost('item_pack');
|
||||
$itemCodes = is_array($itemCodes) ? $itemCodes : [];
|
||||
$itemQtys = is_array($itemQtys) ? $itemQtys : [];
|
||||
$itemPacks = is_array($itemPacks) ? $itemPacks : [];
|
||||
|
||||
$count = max(count($itemCodes), count($itemQtys), count($itemPacks));
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$bagCode = trim((string) ($itemCodes[$i] ?? ''));
|
||||
$qtyRaw = (int) ($itemQtys[$i] ?? 0);
|
||||
$pack = trim((string) ($itemPacks[$i] ?? 'sheet'));
|
||||
if ($bagCode === '' || $qtyRaw <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (! in_array($pack, ['box', 'pack', 'sheet'], true)) {
|
||||
$pack = 'sheet';
|
||||
}
|
||||
$packUnit = $packMap[$bagCode] ?? ['packPerSheet' => 1, 'totalPerBox' => 1];
|
||||
$sheetQty = $qtyRaw;
|
||||
if ($pack === 'box') {
|
||||
$sheetQty = $qtyRaw * (int) $packUnit['totalPerBox'];
|
||||
} elseif ($pack === 'pack') {
|
||||
$sheetQty = $qtyRaw * (int) $packUnit['packPerSheet'];
|
||||
}
|
||||
$sheetQty = max(1, (int) $sheetQty);
|
||||
|
||||
$detail = $kindO
|
||||
? model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, $bagCode, $lgIdx)
|
||||
: null;
|
||||
$bagName = $detail ? (string) ($detail->cd_name ?? '') : (string) ($inventoryMap[$bagCode]['name'] ?? '');
|
||||
if ($bagName === '') {
|
||||
$bagName = (string) $bagCode;
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'bagCode' => $bagCode,
|
||||
'bagName' => $bagName,
|
||||
'pack' => $pack,
|
||||
'rawQty' => $qtyRaw,
|
||||
'sheetQty' => $sheetQty,
|
||||
];
|
||||
}
|
||||
|
||||
// 기존 관리자 단건 폼과의 호환
|
||||
if ($items === []) {
|
||||
$singleBagCode = trim((string) $this->request->getPost('bi2_bag_code'));
|
||||
$singleQty = (int) $this->request->getPost('bi2_qty');
|
||||
if ($singleBagCode !== '' && $singleQty > 0) {
|
||||
$detail = $kindO
|
||||
? model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, $singleBagCode, $lgIdx)
|
||||
: null;
|
||||
$bagName = $detail ? (string) ($detail->cd_name ?? '') : (string) ($inventoryMap[$singleBagCode]['name'] ?? '');
|
||||
if ($bagName === '') {
|
||||
$bagName = (string) $singleBagCode;
|
||||
}
|
||||
$items[] = [
|
||||
'bagCode' => $singleBagCode,
|
||||
'bagName' => $bagName,
|
||||
'pack' => 'sheet',
|
||||
'rawQty' => $singleQty,
|
||||
'sheetQty' => $singleQty,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($items === []) {
|
||||
return redirect()->back()->withInput()->with('error', '불출 품목을 1건 이상 입력해 주세요.');
|
||||
}
|
||||
|
||||
$requiredByBag = [];
|
||||
foreach ($items as $item) {
|
||||
$code = (string) $item['bagCode'];
|
||||
if (! isset($requiredByBag[$code])) {
|
||||
$requiredByBag[$code] = 0;
|
||||
}
|
||||
$requiredByBag[$code] += (int) $item['sheetQty'];
|
||||
}
|
||||
foreach ($requiredByBag as $code => $requiredQty) {
|
||||
$available = (int) ($inventoryMap[$code]['qty'] ?? 0);
|
||||
if ($available <= 0) {
|
||||
return redirect()->back()->withInput()->with('error', '입고 재고가 없는 봉투코드는 불출할 수 없습니다: ' . $code);
|
||||
}
|
||||
if ($available < $requiredQty) {
|
||||
return redirect()->back()->withInput()->with('error', '재고가 부족합니다: ' . $code . ' (재고 ' . number_format($available) . ', 요청 ' . number_format($requiredQty) . ')');
|
||||
}
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$db->transStart();
|
||||
$hasIssueCodeTable = $db->tableExists('bag_issue_item_code');
|
||||
|
||||
$issueData = [
|
||||
'bi2_lg_idx' => $lgIdx,
|
||||
'bi2_year' => (int) $this->request->getPost('bi2_year'),
|
||||
'bi2_quarter' => (int) $this->request->getPost('bi2_quarter'),
|
||||
'bi2_issue_type' => $this->request->getPost('bi2_issue_type'),
|
||||
'bi2_issue_date' => $this->request->getPost('bi2_issue_date'),
|
||||
'bi2_dest_type' => $this->request->getPost('bi2_dest_type') ?? '',
|
||||
'bi2_dest_name' => $this->request->getPost('bi2_dest_name'),
|
||||
'bi2_bag_code' => $bagCode,
|
||||
'bi2_bag_name' => $bagName,
|
||||
'bi2_qty' => $qty,
|
||||
'bi2_status' => 'normal',
|
||||
'bi2_regdate' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
$this->issueModel->insert($issueData);
|
||||
$bi2Idx = (int) $this->issueModel->getInsertID();
|
||||
|
||||
$issueYear = (int) $this->request->getPost('bi2_year');
|
||||
$issueQuarter = (int) $this->request->getPost('bi2_quarter');
|
||||
$issueDate = (string) $this->request->getPost('bi2_issue_date');
|
||||
$createdCount = 0;
|
||||
helper('audit');
|
||||
audit_log('create', 'bag_issue', $bi2Idx, null, array_merge($issueData, ['bi2_idx' => $bi2Idx]));
|
||||
foreach ($items as $item) {
|
||||
$issueData = [
|
||||
'bi2_lg_idx' => $lgIdx,
|
||||
'bi2_year' => $issueYear,
|
||||
'bi2_quarter' => $issueQuarter,
|
||||
'bi2_issue_type' => $issueType,
|
||||
'bi2_issue_date' => $issueDate,
|
||||
'bi2_dest_type' => $destType,
|
||||
'bi2_dest_name' => $destName,
|
||||
'bi2_bag_code' => (string) $item['bagCode'],
|
||||
'bi2_bag_name' => (string) $item['bagName'],
|
||||
'bi2_qty' => (int) $item['sheetQty'],
|
||||
'bi2_status' => 'normal',
|
||||
'bi2_regdate' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
$this->issueModel->insert($issueData);
|
||||
$bi2Idx = (int) $this->issueModel->getInsertID();
|
||||
audit_log('create', 'bag_issue', $bi2Idx, null, array_merge($issueData, ['bi2_idx' => $bi2Idx]));
|
||||
|
||||
model(BagInventoryModel::class)->adjustQty($lgIdx, $bagCode, $bagName, -$qty);
|
||||
if ($hasIssueCodeTable) {
|
||||
$codeRows = $this->buildIssueCodeRows($bi2Idx, (int) $item['sheetQty'], $packMap[(string) $item['bagCode']] ?? []);
|
||||
foreach ($codeRows as $codeRow) {
|
||||
$this->issueItemCodeModel->insert([
|
||||
'bic_lg_idx' => $lgIdx,
|
||||
'bic_bi2_idx' => $bi2Idx,
|
||||
'bic_bag_code' => (string) $item['bagCode'],
|
||||
'bic_issue_code' => (string) $codeRow['issueCode'],
|
||||
'bic_qty' => (int) $codeRow['qty'],
|
||||
'bic_cancel_qty' => 0,
|
||||
'bic_state' => 'normal',
|
||||
'bic_regdate' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
model(BagInventoryModel::class)->adjustQty(
|
||||
$lgIdx,
|
||||
(string) $item['bagCode'],
|
||||
(string) $item['bagName'],
|
||||
-((int) $item['sheetQty'])
|
||||
);
|
||||
$createdCount++;
|
||||
}
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->to(mgmt_url('bag-issues'))->with('success', '불출 처리되었습니다.');
|
||||
if (! $db->transStatus()) {
|
||||
return redirect()->back()->withInput()->with('error', '불출 처리 중 오류가 발생했습니다.');
|
||||
}
|
||||
|
||||
return redirect()->to(mgmt_url('bag-issues'))->with('success', $createdCount . '건 불출 처리되었습니다.');
|
||||
}
|
||||
|
||||
public function cancel(int $id)
|
||||
@@ -116,12 +326,38 @@ class BagIssue extends BaseController
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$db->transStart();
|
||||
$hasIssueCodeTable = $db->tableExists('bag_issue_item_code');
|
||||
|
||||
$before = (array) $item;
|
||||
$this->issueModel->update($id, ['bi2_status' => 'cancelled']);
|
||||
helper('audit');
|
||||
audit_log('update', 'bag_issue', $id, $before, ['bi2_status' => 'cancelled']);
|
||||
model(BagInventoryModel::class)->adjustQty((int) $item->bi2_lg_idx, $item->bi2_bag_code, $item->bi2_bag_name, (int) $item->bi2_qty);
|
||||
|
||||
$restoreQty = (int) $item->bi2_qty;
|
||||
if ($hasIssueCodeTable) {
|
||||
$codeRows = $db->table('bag_issue_item_code')
|
||||
->select('bic_idx, bic_qty, bic_cancel_qty')
|
||||
->where('bic_lg_idx', (int) $item->bi2_lg_idx)
|
||||
->where('bic_bi2_idx', $id)
|
||||
->get()
|
||||
->getResultArray();
|
||||
$restoreQty = 0;
|
||||
foreach ($codeRows as $codeRow) {
|
||||
$bicIdx = (int) ($codeRow['bic_idx'] ?? 0);
|
||||
$qty = (int) ($codeRow['bic_qty'] ?? 0);
|
||||
$oldCancel = (int) ($codeRow['bic_cancel_qty'] ?? 0);
|
||||
$restoreQty += max(0, $qty - $oldCancel);
|
||||
$db->table('bag_issue_item_code')
|
||||
->where('bic_idx', $bicIdx)
|
||||
->update([
|
||||
'bic_cancel_qty' => $qty,
|
||||
'bic_state' => 'cancelled',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
model(BagInventoryModel::class)->adjustQty((int) $item->bi2_lg_idx, $item->bi2_bag_code, $item->bi2_bag_name, $restoreQty);
|
||||
$this->issueModel->update($id, ['bi2_qty' => 0, 'bi2_status' => 'cancelled']);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user