사이트·관리자 봉투 물류 기능(수불·통계·레포트·재고·발주)과 DB·메뉴·E2E를 운영 반영한다.

통계 분석(전년대비·월별·계절별), 수급계획·LOT 수불, 지정판매소·실사·메뉴 링크 등을 포함한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
taekyoungc
2026-06-01 16:15:15 +09:00
parent 21e7b91871
commit 0f1d414f37
129 changed files with 18068 additions and 1585 deletions

View File

@@ -43,8 +43,9 @@ class BagInventory extends BaseController
];
}
export_csv(
'재고현황_' . date('Ymd') . '.csv',
export_xlsx(
'재고현황_' . date('Ymd') . '.xlsx',
'재고현황',
['번호', '봉투코드', '봉투명', '현재재고(낱장)', '최종갱신'],
$rows
);

View File

@@ -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();

View File

@@ -138,11 +138,7 @@ class BagPrice extends BaseController
if ($bagCode !== null && $bagCode !== '') {
$queryForPager['bag_code'] = $bagCode;
}
$pagerPath = mgmt_url('bag-prices');
if ($queryForPager !== []) {
$pagerPath .= '?' . http_build_query($queryForPager);
}
$this->priceModel->pager->setPath($pagerPath);
apply_pager_path($this->priceModel->pager, mgmt_path('bag-prices'), $queryForPager);
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
$bagCodes = $kindO

View File

@@ -42,11 +42,7 @@ class Company extends BaseController
if ($companyType !== '' && in_array($companyType, $typeOptions, true)) {
$queryForPager['cp_type'] = $companyType;
}
$pagerPath = mgmt_url('companies');
if ($queryForPager !== []) {
$pagerPath .= '?' . http_build_query($queryForPager);
}
$pager->setPath($pagerPath);
apply_pager_path($pager, mgmt_path('companies'), $queryForPager);
return $this->renderWorkPage('업체 관리', 'admin/company/index', [
'list' => $list,

View File

@@ -40,7 +40,10 @@ class Dashboard extends BaseController
FROM bag_order_item GROUP BY boi_bo_idx
) sub ON sub.boi_bo_idx = bo.bo_idx
WHERE bo.bo_lg_idx = ? AND bo.bo_status = 'normal'
", [$lgIdx])->getRow();
AND (bo.bo_uuid, bo.bo_version) IN (
SELECT bo_uuid, MAX(bo_version) FROM bag_order WHERE bo_lg_idx = ? GROUP BY bo_uuid
)
", [$lgIdx, $lgIdx])->getRow();
$stats['order_count'] = (int) ($orderStats->cnt ?? 0);
$stats['order_amount'] = (int) ($orderStats->total_amount ?? 0);
@@ -72,9 +75,12 @@ class Dashboard extends BaseController
SELECT bo_idx, bo_lot_no, bo_order_date, bo_status
FROM bag_order
WHERE bo_lg_idx = ?
AND (bo_uuid, bo_version) IN (
SELECT bo_uuid, MAX(bo_version) FROM bag_order WHERE bo_lg_idx = ? GROUP BY bo_uuid
)
ORDER BY bo_order_date DESC, bo_idx DESC
LIMIT 5
", [$lgIdx])->getResult();
", [$lgIdx, $lgIdx])->getResult();
// 최근 판매 5건
$stats['recent_sales'] = $db->query("

View File

@@ -16,6 +16,19 @@ class FreeRecipient extends BaseController
$this->model = model(FreeRecipientModel::class);
}
/**
* 무료용 대상 구분(스크린샷 기준): 사람뿐 아니라 동사무소 자체도 등록 가능.
*
* @return array<string,string>
*/
private function recipientTypeOptions(): array
{
return [
'office' => '읍.면.동 사무소',
'target' => '무료 대상자',
];
}
private function getCodeOptions(string $ckCode): array
{
helper('admin');
@@ -33,16 +46,42 @@ class FreeRecipient extends BaseController
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
}
$list = $this->model->where('fr_lg_idx', $lgIdx)->orderBy('fr_idx', 'DESC')->paginate(20);
$list = $this->model
->where('fr_lg_idx', $lgIdx)
->orderBy('fr_type_code', 'ASC')
->orderBy('fr_name', 'ASC')
->orderBy('fr_idx', 'DESC')
->paginate(20);
$pager = $this->model->pager;
$perPage = 20;
$currentPage = (int) ($pager->getCurrentPage() ?: 1);
$totalCount = (int) $this->model
->where('fr_lg_idx', $lgIdx)
->countAllResults();
$dongNameMap = [];
foreach ($this->getCodeOptions('D') as $dong) {
$code = (string) ($dong->cd_code ?? '');
if ($code === '') {
continue;
}
$dongNameMap[$code] = (string) ($dong->cd_name ?? $code);
}
return $this->renderWorkPage('무료용 대상자 관리', 'admin/free_recipient/index', ['list' => $list, 'pager' => $pager]);
return $this->renderWorkPage('무료용 대상자 관리', 'admin/free_recipient/index', [
'list' => $list,
'pager' => $pager,
'recipientTypeOptions' => $this->recipientTypeOptions(),
'dongNameMap' => $dongNameMap,
'totalCount' => $totalCount,
'currentPage' => $currentPage,
'perPage' => $perPage,
]);
}
public function create()
{
return $this->renderWorkPage('무료용 대상자 등록', 'admin/free_recipient/create', [
'typeCodes' => $this->getCodeOptions('H'),
'recipientTypeOptions' => $this->recipientTypeOptions(),
'dongCodes' => $this->getCodeOptions('D'),
]);
}
@@ -85,7 +124,7 @@ class FreeRecipient extends BaseController
return $this->renderWorkPage('무료용 대상자 수정', 'admin/free_recipient/edit', [
'item' => $item,
'typeCodes' => $this->getCodeOptions('H'),
'recipientTypeOptions' => $this->recipientTypeOptions(),
'dongCodes' => $this->getCodeOptions('D'),
]);
}

View File

@@ -63,6 +63,11 @@ class Menu extends BaseController
}
}
if ($effectiveMtIdx > 0 && $currentTypeCode === 'site') {
$this->menuModel->pruneInventoryManagementMenus($effectiveMtIdx, $lgIdx);
$list = $this->menuModel->getAllByType($effectiveMtIdx, $lgIdx);
}
// 트리 순서대로 상위 메뉴 바로 아래에 하위 메뉴가 오도록 평탄화
if (! empty($list)) {
$tree = build_menu_tree($list);
@@ -109,6 +114,10 @@ class Menu extends BaseController
if ($mtIdx <= 0) {
return $this->response->setJSON(['status' => 0, 'msg' => 'mt_idx required']);
}
$type = $this->typeModel->find($mtIdx);
if ($type && (string) ($type->mt_code ?? '') === 'site') {
$this->menuModel->pruneInventoryManagementMenus($mtIdx, $lgIdx);
}
$list = $this->menuModel->getAllByType($mtIdx, $lgIdx);
return $this->response->setJSON(['status' => 1, 'data' => $list]);
}
@@ -153,6 +162,7 @@ class Menu extends BaseController
if ($mmPidx > 0) {
$this->menuModel->updateCnode($mmPidx, 1);
}
$this->menuModel->pruneInventoryManagementMenus($mtIdx, $lgIdx);
$this->menuModel->syncTypeToAllLgs($mtIdx, $lgIdx);
return redirect()->back()->with('success', '메뉴가 등록되었습니다.');
}
@@ -184,6 +194,7 @@ class Menu extends BaseController
'mm_is_view' => $this->request->getPost('mm_is_view') ? 'Y' : 'N',
];
$this->menuModel->update($id, $data);
$this->menuModel->pruneInventoryManagementMenus((int) $row->mt_idx, $lgIdx);
$this->menuModel->syncTypeToAllLgs((int) $row->mt_idx, $lgIdx);
return redirect()->back()->with('success', '메뉴가 수정되었습니다.');
}
@@ -207,6 +218,7 @@ class Menu extends BaseController
}
$result = $this->menuModel->deleteSafe($id);
if ($result['ok']) {
$this->menuModel->pruneInventoryManagementMenus((int) $row->mt_idx, $lgIdx);
$this->menuModel->syncTypeToAllLgs((int) $row->mt_idx, $lgIdx);
return redirect()->back()->with('success', '메뉴가 삭제되었습니다.');
}
@@ -234,6 +246,7 @@ class Menu extends BaseController
$firstRow = $firstId > 0 ? $this->menuModel->find($firstId) : null;
$this->menuModel->setOrder($ids, $lgIdx);
if ($firstRow && (int) $firstRow->lg_idx === $lgIdx) {
$this->menuModel->pruneInventoryManagementMenus((int) $firstRow->mt_idx, $lgIdx);
$this->menuModel->syncTypeToAllLgs((int) $firstRow->mt_idx, $lgIdx);
}
return redirect()->back()->with('success', '순서가 적용되었습니다.');

View File

@@ -55,11 +55,7 @@ class SalesAgency extends BaseController
'sa_idx' => $saIdx,
];
$queryForPager = array_filter($queryForPager, static fn ($v) => $v !== null && $v !== '');
$pagerPath = mgmt_url('sales-agencies');
if ($queryForPager !== []) {
$pagerPath .= '?' . http_build_query($queryForPager);
}
$pager->setPath($pagerPath);
apply_pager_path($pager, mgmt_path('sales-agencies'), $queryForPager);
return $this->renderWorkPage('판매 대행소 관리', 'admin/sales_agency/index', [
'list' => $list,

File diff suppressed because it is too large Load Diff

View File

@@ -57,8 +57,21 @@ class ShopOrder extends BaseController
$shops = model(DesignatedShopModel::class)->where('ds_lg_idx', $lgIdx)->where('ds_state', 1)->findAll();
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx) : [];
$priceMap = model(BagPriceModel::class)->latestActiveMapByBagCode($lgIdx);
$unitRows = model(PackagingUnitModel::class)
->where('pu_lg_idx', $lgIdx)
->where('pu_state', 1)
->findAll();
$unitMap = [];
foreach ($unitRows as $unit) {
$code = (string) ($unit->pu_bag_code ?? '');
if ($code === '' || isset($unitMap[$code])) {
continue;
}
$unitMap[$code] = $unit;
}
return $this->renderWorkPage('주문 접수', 'admin/shop_order/create', compact('shops', 'bagCodes'));
return $this->renderWorkPage('주문 접수', 'admin/shop_order/create', compact('shops', 'bagCodes', 'priceMap', 'unitMap'));
}
public function store()
@@ -81,7 +94,7 @@ class ShopOrder extends BaseController
$dsIdx = (int) $this->request->getPost('so_ds_idx');
$shop = model(DesignatedShopModel::class)->find($dsIdx);
$this->orderModel->insert([
$orderData = [
'so_lg_idx' => $lgIdx,
'so_ds_idx' => $dsIdx,
'so_ds_name' => $shop ? $shop->ds_name : '',
@@ -91,8 +104,24 @@ class ShopOrder extends BaseController
'so_status' => 'normal',
'so_orderer_idx' => session()->get('mb_idx'),
'so_regdate' => date('Y-m-d H:i:s'),
]);
];
// shop_order.so_channel 이 아직 반영되지 않은 DB와의 호환 처리
if ($db->fieldExists('so_channel', 'shop_order')) {
$orderData['so_channel'] = 'phone';
}
$insertOk = $this->orderModel->insert($orderData);
if ($insertOk === false) {
$db->transRollback();
$errors = $this->orderModel->errors();
$msg = ! empty($errors) ? implode(' / ', array_values($errors)) : '주문 저장에 실패했습니다.';
return redirect()->back()->withInput()->with('error', $msg);
}
$soIdx = (int) $this->orderModel->getInsertID();
if ($soIdx <= 0) {
$db->transRollback();
return redirect()->back()->withInput()->with('error', '주문번호 생성에 실패했습니다. DB 스키마를 확인해 주세요.');
}
$bagCodes = $this->request->getPost('item_bag_code') ?? [];
$qtys = $this->request->getPost('item_qty') ?? [];