Compare commits
6 Commits
647d5f919d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21e7b91871 | ||
|
|
c708d30660 | ||
|
|
215d4d2c7c | ||
|
|
6db9d119c1 | ||
|
|
5c89c963ee | ||
|
|
05c479397b |
@@ -26,8 +26,18 @@ $routes->get('bag/code-details/(:num)', 'Bag::codeDetails/$1');
|
||||
// 옛 주소 호환: 세부 목록만 사이트로 이동
|
||||
$routes->get('admin/code-details/(:num)', 'Admin\CodeDetail::index/$1');
|
||||
$routes->get('bag/purchase-inbound', 'Bag::purchaseInbound');
|
||||
$routes->get('bag/issue', 'Bag::issue');
|
||||
$routes->get('bag/issue', 'Bag::issueLegacy');
|
||||
$routes->get('bag/issue/cancel', 'Bag::issue');
|
||||
$routes->get('bag/inventory', 'Bag::inventory');
|
||||
$routes->get('bag/inventory/export', 'Bag::inventoryExport');
|
||||
$routes->get('bag/inventory/inspection-select', 'Bag::inspectionSelect');
|
||||
$routes->get('bag/inventory/inspection-work', 'Bag::inspectionWork');
|
||||
$routes->post('bag/inventory/inspection-run', 'Bag::inspectionRun');
|
||||
$routes->post('bag/inventory/inspection-select/save', 'Bag::inspectionSelectSave');
|
||||
$routes->post('bag/inventory/inspection-select/confirm', 'Bag::inspectionSelectConfirm');
|
||||
$routes->get('bag/inventory/inspection/(:num)', 'Bag::inspectionDetail/$1');
|
||||
$routes->post('bag/inventory/inspection/(:num)/save', 'Bag::inspectionSave/$1');
|
||||
$routes->post('bag/inventory/inspection/(:num)/apply', 'Bag::inspectionApply/$1');
|
||||
$routes->get('bag/sales', 'Bag::sales');
|
||||
$routes->get('bag/sales-stats', 'Bag::salesStats');
|
||||
$routes->get('bag/flow', 'Bag::flow');
|
||||
@@ -41,11 +51,24 @@ $routes->post('bag/inventory/adjust', 'Bag::inventoryAdjustStore');
|
||||
$routes->get('bag/issue/create', 'Bag::issueCreate');
|
||||
$routes->post('bag/issue/store', 'Bag::issueStore');
|
||||
$routes->post('bag/issue/cancel/(:num)', 'Bag::issueCancel/$1');
|
||||
$routes->post('bag/issue/cancel-save', 'Bag::issueCancelSave');
|
||||
$routes->get('bag/order/create', 'Bag::orderCreate');
|
||||
$routes->get('bag/order/change', 'Bag::orderChange');
|
||||
$routes->get('bag/order/revise/(:num)', 'Bag::orderRevise/$1');
|
||||
$routes->get('bag/order/lot-seed', 'Bag::orderLotSeed');
|
||||
$routes->post('bag/order/lot-seed/generate', 'Bag::orderLotSeedGenerate');
|
||||
$routes->post('bag/order/store', 'Bag::orderStore');
|
||||
$routes->post('bag/order/cancel/(:num)', 'Bag::orderCancel/$1');
|
||||
$routes->post('bag/order/delete', 'Bag::orderDeletePost');
|
||||
$routes->post('bag/order/delete/(:num)', 'Bag::orderDelete/$1');
|
||||
$routes->get('bag/receiving/create', 'Bag::receivingCreate');
|
||||
$routes->post('bag/receiving/store', 'Bag::receivingStore');
|
||||
$routes->get('bag/receiving/scanner', 'Bag::receivingScanner');
|
||||
$routes->post('bag/receiving/scanner/store', 'Bag::receivingScannerStore');
|
||||
$routes->get('bag/receiving/batch', 'Bag::receivingBatch');
|
||||
$routes->post('bag/receiving/batch/store', 'Bag::receivingBatchStore');
|
||||
$routes->get('bag/receiving/status', 'Bag::receivingStatus');
|
||||
$routes->get('bag/receiving/status/export', 'Bag::receivingStatusExport');
|
||||
$routes->get('bag/sale/create', 'Bag::saleCreate');
|
||||
$routes->post('bag/sale/store', 'Bag::saleStore');
|
||||
$routes->get('bag/shop-order/create', 'Bag::shopOrderCreate');
|
||||
@@ -108,6 +131,7 @@ $routes->group('bag', ['filter' => 'adminAuth'], static function ($routes): void
|
||||
$routes->get('bag-orders/export', 'Admin\BagOrder::export');
|
||||
$routes->get('bag-orders', 'Admin\BagOrder::index');
|
||||
$routes->get('bag-orders/create', 'Admin\BagOrder::create');
|
||||
$routes->get('bag-orders/revise/(:num)', 'Admin\BagOrder::revise/$1');
|
||||
$routes->post('bag-orders/store', 'Admin\BagOrder::store');
|
||||
$routes->get('bag-orders/detail/(:num)', 'Admin\BagOrder::detail/$1');
|
||||
$routes->post('bag-orders/cancel/(:num)', 'Admin\BagOrder::cancel/$1');
|
||||
|
||||
@@ -9,8 +9,11 @@ use App\Models\BagPriceModel;
|
||||
use App\Models\PackagingUnitModel;
|
||||
use App\Models\CompanyModel;
|
||||
use App\Models\SalesAgencyModel;
|
||||
use App\Models\BagReceivingModel;
|
||||
use App\Models\CodeKindModel;
|
||||
use App\Models\CodeDetailModel;
|
||||
use App\Models\LocalGovernmentModel;
|
||||
use App\Libraries\Blockchain\SqlLedger;
|
||||
class BagOrder extends BaseController
|
||||
{
|
||||
private BagOrderModel $orderModel;
|
||||
@@ -30,36 +33,76 @@ class BagOrder extends BaseController
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$builder = $this->orderModel->where('bo_lg_idx', $lgIdx);
|
||||
|
||||
// 기간 필터
|
||||
$startDate = $this->request->getGet('start_date');
|
||||
$endDate = $this->request->getGet('end_date');
|
||||
$status = $this->request->getGet('status');
|
||||
if ($startDate) $builder->where('bo_order_date >=', $startDate);
|
||||
if ($endDate) $builder->where('bo_order_date <=', $endDate);
|
||||
if ($status) $builder->where('bo_status', $status);
|
||||
|
||||
$list = $builder->orderBy('bo_order_date', 'DESC')->orderBy('bo_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->orderModel->pager;
|
||||
|
||||
// 발주별 품목 합계
|
||||
$itemSummary = [];
|
||||
foreach ($list as $order) {
|
||||
$items = $this->itemModel->where('boi_bo_idx', $order->bo_idx)->findAll();
|
||||
$totalQty = 0; $totalAmt = 0;
|
||||
foreach ($items as $it) { $totalQty += (int) $it->boi_qty_sheet; $totalAmt += (float) $it->boi_amount; }
|
||||
$itemSummary[$order->bo_idx] = ['qty' => $totalQty, 'amount' => $totalAmt, 'count' => count($items)];
|
||||
$startMonth = (string) ($this->request->getGet('start_month') ?? date('Y-m'));
|
||||
$endMonth = (string) ($this->request->getGet('end_month') ?? date('Y-m'));
|
||||
if (! preg_match('/^\d{4}-\d{2}$/', $startMonth)) {
|
||||
$startMonth = date('Y-m');
|
||||
}
|
||||
if (! preg_match('/^\d{4}-\d{2}$/', $endMonth)) {
|
||||
$endMonth = $startMonth;
|
||||
}
|
||||
if (strtotime($startMonth . '-01') > strtotime($endMonth . '-01')) {
|
||||
[$startMonth, $endMonth] = [$endMonth, $startMonth];
|
||||
}
|
||||
|
||||
// 제작업체/대행소 이름 매핑
|
||||
$companyMap = []; $agencyMap = [];
|
||||
foreach (model(CompanyModel::class)->where('cp_lg_idx', $lgIdx)->findAll() as $c) $companyMap[$c->cp_idx] = $c->cp_name;
|
||||
foreach (model(SalesAgencyModel::class)->where('sa_lg_idx', $lgIdx)->orderForDisplay()->findAll() as $a) {
|
||||
$agencyMap[$a->sa_idx] = '[' . ($a->sa_kind ?? '') . '] ' . ($a->sa_code ?? '') . ' — ' . ($a->sa_name ?? '');
|
||||
$companyIdx = (int) ($this->request->getGet('company_idx') ?? 0);
|
||||
$bagCode = trim((string) ($this->request->getGet('bag_code') ?? ''));
|
||||
$receiveType = (string) ($this->request->getGet('receive_type') ?? 'all');
|
||||
if (! in_array($receiveType, ['all', 'received', 'pending'], true)) {
|
||||
$receiveType = 'all';
|
||||
}
|
||||
|
||||
return $this->renderWorkPage('발주 현황', 'admin/bag_order/index', compact('list', 'itemSummary', 'companyMap', 'agencyMap', 'startDate', 'endDate', 'status', 'pager'));
|
||||
$companies = model(CompanyModel::class)
|
||||
->where('cp_lg_idx', $lgIdx)
|
||||
->where('cp_type', '제작업체')
|
||||
->where('cp_state', 1)
|
||||
->orderBy('cp_name', 'ASC')
|
||||
->findAll();
|
||||
$companyMap = [];
|
||||
foreach ($companies as $company) {
|
||||
$companyMap[(int) $company->cp_idx] = (string) $company->cp_name;
|
||||
}
|
||||
|
||||
$agencyMap = [];
|
||||
foreach (model(SalesAgencyModel::class)->where('sa_lg_idx', $lgIdx)->orderForDisplay()->findAll() as $agency) {
|
||||
$agencyMap[(int) $agency->sa_idx] = (string) ($agency->sa_name ?? '');
|
||||
}
|
||||
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx) : [];
|
||||
$bagNameMap = [];
|
||||
foreach ($bagCodes as $code) {
|
||||
$bagNameMap[(string) $code->cd_code] = (string) $code->cd_name;
|
||||
}
|
||||
|
||||
$reportData = $this->buildOrderStatusRows(
|
||||
$lgIdx,
|
||||
$startMonth,
|
||||
$endMonth,
|
||||
$companyIdx,
|
||||
$bagCode,
|
||||
$receiveType,
|
||||
$companyMap,
|
||||
$agencyMap,
|
||||
$bagNameMap
|
||||
);
|
||||
|
||||
return $this->renderWorkPage(
|
||||
'발주 현황',
|
||||
'admin/bag_order/index',
|
||||
[
|
||||
'startMonth' => $startMonth,
|
||||
'endMonth' => $endMonth,
|
||||
'companyIdx' => $companyIdx,
|
||||
'bagCode' => $bagCode,
|
||||
'receiveType' => $receiveType,
|
||||
'companyOptions' => $companies,
|
||||
'bagCodeOptions' => $bagCodes,
|
||||
'rows' => $reportData['rows'],
|
||||
'groupRows' => $reportData['groupRows'],
|
||||
'grandTotals' => $reportData['grandTotals'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function export()
|
||||
@@ -70,44 +113,240 @@ class BagOrder extends BaseController
|
||||
return redirect()->to(mgmt_url('bag-orders'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$builder = $this->orderModel->where('bo_lg_idx', $lgIdx);
|
||||
$startDate = $this->request->getGet('start_date');
|
||||
$endDate = $this->request->getGet('end_date');
|
||||
$status = $this->request->getGet('status');
|
||||
if ($startDate) $builder->where('bo_order_date >=', $startDate);
|
||||
if ($endDate) $builder->where('bo_order_date <=', $endDate);
|
||||
if ($status) $builder->where('bo_status', $status);
|
||||
$startMonth = (string) ($this->request->getGet('start_month') ?? date('Y-m'));
|
||||
$endMonth = (string) ($this->request->getGet('end_month') ?? date('Y-m'));
|
||||
if (! preg_match('/^\d{4}-\d{2}$/', $startMonth)) {
|
||||
$startMonth = date('Y-m');
|
||||
}
|
||||
if (! preg_match('/^\d{4}-\d{2}$/', $endMonth)) {
|
||||
$endMonth = $startMonth;
|
||||
}
|
||||
if (strtotime($startMonth . '-01') > strtotime($endMonth . '-01')) {
|
||||
[$startMonth, $endMonth] = [$endMonth, $startMonth];
|
||||
}
|
||||
|
||||
$list = $builder->orderBy('bo_order_date', 'DESC')->orderBy('bo_idx', 'DESC')->findAll();
|
||||
$companyIdx = (int) ($this->request->getGet('company_idx') ?? 0);
|
||||
$bagCode = trim((string) ($this->request->getGet('bag_code') ?? ''));
|
||||
$receiveType = (string) ($this->request->getGet('receive_type') ?? 'all');
|
||||
if (! in_array($receiveType, ['all', 'received', 'pending'], true)) {
|
||||
$receiveType = 'all';
|
||||
}
|
||||
|
||||
$companyMap = [];
|
||||
foreach (model(CompanyModel::class)->where('cp_lg_idx', $lgIdx)->where('cp_type', '제작업체')->findAll() as $company) {
|
||||
$companyMap[(int) $company->cp_idx] = (string) $company->cp_name;
|
||||
}
|
||||
$agencyMap = [];
|
||||
foreach (model(SalesAgencyModel::class)->where('sa_lg_idx', $lgIdx)->orderForDisplay()->findAll() as $agency) {
|
||||
$agencyMap[(int) $agency->sa_idx] = (string) ($agency->sa_name ?? '');
|
||||
}
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx) : [];
|
||||
$bagNameMap = [];
|
||||
foreach ($bagCodes as $code) {
|
||||
$bagNameMap[(string) $code->cd_code] = (string) $code->cd_name;
|
||||
}
|
||||
|
||||
$reportData = $this->buildOrderStatusRows(
|
||||
$lgIdx,
|
||||
$startMonth,
|
||||
$endMonth,
|
||||
$companyIdx,
|
||||
$bagCode,
|
||||
$receiveType,
|
||||
$companyMap,
|
||||
$agencyMap,
|
||||
$bagNameMap
|
||||
);
|
||||
|
||||
$rows = [];
|
||||
$statusMap = ['normal' => '정상', 'cancelled' => '취소', 'deleted' => '삭제'];
|
||||
foreach ($list as $row) {
|
||||
$items = $this->itemModel->where('boi_bo_idx', $row->bo_idx)->findAll();
|
||||
$totalQty = 0;
|
||||
$totalAmt = 0;
|
||||
foreach ($items as $it) {
|
||||
$totalQty += (int) $it->boi_qty_sheet;
|
||||
$totalAmt += (float) $it->boi_amount;
|
||||
foreach ($reportData['rows'] as $row) {
|
||||
if (! empty($row['is_subtotal'])) {
|
||||
$rows[] = [
|
||||
'',
|
||||
'',
|
||||
(string) ($row['label'] ?? '소계'),
|
||||
(int) ($row['order_qty'] ?? 0),
|
||||
(int) ($row['received_qty'] ?? 0),
|
||||
(int) ($row['pending_qty'] ?? 0),
|
||||
(float) ($row['amount'] ?? 0),
|
||||
'',
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
$rows[] = [
|
||||
$row->bo_idx,
|
||||
$row->bo_lot_no,
|
||||
$row->bo_order_date,
|
||||
count($items),
|
||||
$totalQty,
|
||||
$totalAmt,
|
||||
$statusMap[$row->bo_status] ?? $row->bo_status,
|
||||
(string) ($row['order_date'] ?? ''),
|
||||
(string) ($row['company_name'] ?? ''),
|
||||
(string) ($row['bag_name'] ?? ''),
|
||||
(int) ($row['order_qty'] ?? 0),
|
||||
(int) ($row['received_qty'] ?? 0),
|
||||
(int) ($row['pending_qty'] ?? 0),
|
||||
(float) ($row['amount'] ?? 0),
|
||||
(string) ($row['agency_name'] ?? ''),
|
||||
'',
|
||||
];
|
||||
}
|
||||
|
||||
export_csv(
|
||||
'발주현황_' . date('Ymd') . '.csv',
|
||||
['번호', 'LOT번호', '발주일', '품목수', '총수량', '총금액', '상태'],
|
||||
$gt = $reportData['grandTotals'] ?? [];
|
||||
$rows[] = [
|
||||
'',
|
||||
'',
|
||||
'총계',
|
||||
(int) ($gt['order_qty'] ?? 0),
|
||||
(int) ($gt['received_qty'] ?? 0),
|
||||
(int) ($gt['pending_qty'] ?? 0),
|
||||
(float) ($gt['amount'] ?? 0),
|
||||
'',
|
||||
'',
|
||||
];
|
||||
|
||||
export_xlsx(
|
||||
'발주현황_' . date('Ymd'),
|
||||
'발주현황',
|
||||
['발주일자', '제작업체', '품명', '발주수량', '입고수량', '미입고수량', '발주금액', '입고처', '비고'],
|
||||
$rows
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 발주 현황(품목 기준) 행 및 소계를 만든다.
|
||||
*/
|
||||
private function buildOrderStatusRows(
|
||||
int $lgIdx,
|
||||
string $startMonth,
|
||||
string $endMonth,
|
||||
int $companyIdx,
|
||||
string $bagCode,
|
||||
string $receiveType,
|
||||
array $companyMap,
|
||||
array $agencyMap,
|
||||
array $bagNameMap
|
||||
): array {
|
||||
$startDate = $startMonth . '-01';
|
||||
$endDate = date('Y-m-t', strtotime($endMonth . '-01 00:00:00'));
|
||||
|
||||
$builder = $this->orderModel
|
||||
->where('bo_lg_idx', $lgIdx)
|
||||
->whereLatestHead($lgIdx)
|
||||
->where('bo_order_date >=', $startDate)
|
||||
->where('bo_order_date <=', $endDate)
|
||||
->whereIn('bo_status', ['normal', 'cancelled'])
|
||||
->orderBy('bo_order_date', 'DESC')
|
||||
->orderBy('bo_idx', 'DESC');
|
||||
if ($companyIdx > 0) {
|
||||
$builder->where('bo_company_idx', $companyIdx);
|
||||
}
|
||||
$orders = $builder->findAll();
|
||||
|
||||
if (empty($orders)) {
|
||||
return ['rows' => [], 'groupRows' => [], 'grandTotals' => ['order_qty' => 0, 'received_qty' => 0, 'pending_qty' => 0, 'amount' => 0]];
|
||||
}
|
||||
|
||||
$orderIds = array_map(static fn($order) => (int) $order->bo_idx, $orders);
|
||||
|
||||
$itemsByOrder = [];
|
||||
if (! empty($orderIds)) {
|
||||
$allItems = $this->itemModel
|
||||
->whereIn('boi_bo_idx', $orderIds)
|
||||
->orderBy('boi_bo_idx', 'DESC')
|
||||
->orderBy('boi_idx', 'ASC')
|
||||
->findAll();
|
||||
foreach ($allItems as $item) {
|
||||
$boIdx = (int) ($item->boi_bo_idx ?? 0);
|
||||
if (! isset($itemsByOrder[$boIdx])) {
|
||||
$itemsByOrder[$boIdx] = [];
|
||||
}
|
||||
$itemsByOrder[$boIdx][] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$receivedMap = [];
|
||||
$receivingRows = model(BagReceivingModel::class)
|
||||
->select('br_bo_idx, br_bag_code, SUM(br_qty_sheet) as recv_qty')
|
||||
->where('br_lg_idx', $lgIdx)
|
||||
->whereIn('br_bo_idx', $orderIds)
|
||||
->groupBy('br_bo_idx, br_bag_code')
|
||||
->findAll();
|
||||
foreach ($receivingRows as $received) {
|
||||
$key = (int) ($received->br_bo_idx ?? 0) . '|' . (string) ($received->br_bag_code ?? '');
|
||||
$receivedMap[$key] = (int) ($received->recv_qty ?? 0);
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
$groupRows = [];
|
||||
$grandTotals = ['order_qty' => 0, 'received_qty' => 0, 'pending_qty' => 0, 'amount' => 0.0];
|
||||
|
||||
foreach ($orders as $order) {
|
||||
$boIdx = (int) ($order->bo_idx ?? 0);
|
||||
$items = $itemsByOrder[$boIdx] ?? [];
|
||||
$groupCount = 0;
|
||||
$groupTotalOrder = 0;
|
||||
$groupTotalReceived = 0;
|
||||
$groupTotalPending = 0;
|
||||
$groupTotalAmount = 0.0;
|
||||
|
||||
foreach ($items as $item) {
|
||||
$itemBagCode = (string) ($item->boi_bag_code ?? '');
|
||||
if ($bagCode !== '' && $itemBagCode !== $bagCode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$orderQty = (int) ($item->boi_qty_sheet ?? 0);
|
||||
$recvQty = (int) ($receivedMap[$boIdx . '|' . $itemBagCode] ?? 0);
|
||||
if ($recvQty > $orderQty) {
|
||||
$recvQty = $orderQty;
|
||||
}
|
||||
$pendingQty = max(0, $orderQty - $recvQty);
|
||||
|
||||
if ($receiveType === 'received' && $recvQty <= 0) {
|
||||
continue;
|
||||
}
|
||||
if ($receiveType === 'pending' && $pendingQty <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$amount = (float) ($item->boi_amount ?? 0);
|
||||
$rows[] = [
|
||||
'bo_idx' => $boIdx,
|
||||
'order_date' => (string) ($order->bo_order_date ?? ''),
|
||||
'company_name' => (string) ($companyMap[(int) ($order->bo_company_idx ?? 0)] ?? ''),
|
||||
'bag_name' => (string) ($item->boi_bag_name ?? ($bagNameMap[$itemBagCode] ?? $itemBagCode)),
|
||||
'order_qty' => $orderQty,
|
||||
'received_qty' => $recvQty,
|
||||
'pending_qty' => $pendingQty,
|
||||
'amount' => $amount,
|
||||
'agency_name' => (string) ($agencyMap[(int) ($order->bo_agency_idx ?? 0)] ?? ''),
|
||||
];
|
||||
|
||||
$groupCount++;
|
||||
$groupTotalOrder += $orderQty;
|
||||
$groupTotalReceived += $recvQty;
|
||||
$groupTotalPending += $pendingQty;
|
||||
$groupTotalAmount += $amount;
|
||||
}
|
||||
|
||||
if ($groupCount > 0) {
|
||||
$groupRows[$boIdx] = $groupCount;
|
||||
$rows[] = [
|
||||
'bo_idx' => $boIdx,
|
||||
'is_subtotal' => true,
|
||||
'label' => '소계',
|
||||
'order_qty' => $groupTotalOrder,
|
||||
'received_qty' => $groupTotalReceived,
|
||||
'pending_qty' => $groupTotalPending,
|
||||
'amount' => $groupTotalAmount,
|
||||
];
|
||||
$grandTotals['order_qty'] += $groupTotalOrder;
|
||||
$grandTotals['received_qty'] += $groupTotalReceived;
|
||||
$grandTotals['pending_qty'] += $groupTotalPending;
|
||||
$grandTotals['amount'] += $groupTotalAmount;
|
||||
}
|
||||
}
|
||||
|
||||
return ['rows' => $rows, 'groupRows' => $groupRows, 'grandTotals' => $grandTotals];
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
helper('admin');
|
||||
@@ -119,18 +358,105 @@ class BagOrder extends BaseController
|
||||
// 봉투 종류 + 단가 + 포장단위
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx) : [];
|
||||
$prices = model(BagPriceModel::class)->where('bp_lg_idx', $lgIdx)->where('bp_state', 1)->findAll();
|
||||
$units = model(PackagingUnitModel::class)->where('pu_lg_idx', $lgIdx)->where('pu_state', 1)->findAll();
|
||||
$companies = model(CompanyModel::class)->where('cp_lg_idx', $lgIdx)->where('cp_type', '제작업체')->where('cp_state', 1)->findAll();
|
||||
$agencies = model(SalesAgencyModel::class)->where('sa_lg_idx', $lgIdx)->orderForDisplay()->findAll();
|
||||
$priceMapRows = model(BagPriceModel::class)->latestActiveMapByBagCode($lgIdx);
|
||||
$units = model(PackagingUnitModel::class)->where('pu_lg_idx', $lgIdx)->where('pu_state', 1)->findAll();
|
||||
|
||||
return $this->renderWorkPage('발주 등록', 'admin/bag_order/create', compact('bagCodes', 'prices', 'units', 'companies', 'agencies'));
|
||||
$companies = model(CompanyModel::class)->where('cp_lg_idx', $lgIdx)->where('cp_type', '제작업체')->where('cp_state', 1)->findAll();
|
||||
$associations = model(CompanyModel::class)->where('cp_lg_idx', $lgIdx)->where('cp_type', '협회')->where('cp_state', 1)->findAll();
|
||||
$agencies = model(SalesAgencyModel::class)->where('sa_lg_idx', $lgIdx)->orderForDisplay()->findAll();
|
||||
|
||||
$companyMap = [];
|
||||
foreach (model(CompanyModel::class)->where('cp_lg_idx', $lgIdx)->findAll() as $company) {
|
||||
$companyMap[(int) $company->cp_idx] = (string) $company->cp_name;
|
||||
}
|
||||
$agencyMap = [];
|
||||
foreach ($agencies as $agency) {
|
||||
$agencyMap[(int) $agency->sa_idx] = '[' . ($agency->sa_kind ?? '') . '] ' . ($agency->sa_code ?? '') . ' — ' . ($agency->sa_name ?? '');
|
||||
}
|
||||
|
||||
$recentOrders = $this->orderModel
|
||||
->where('bo_lg_idx', $lgIdx)
|
||||
->whereLatestHead($lgIdx)
|
||||
->orderBy('bo_order_date', 'DESC')
|
||||
->orderBy('bo_idx', 'DESC')
|
||||
->findAll(12);
|
||||
|
||||
$bagNameMap = [];
|
||||
foreach ($bagCodes as $codeDetail) {
|
||||
$bagNameMap[(string) $codeDetail->cd_code] = (string) $codeDetail->cd_name;
|
||||
}
|
||||
$priceMap = [];
|
||||
foreach ($priceMapRows as $bagCode => $price) {
|
||||
$priceMap[(string) $bagCode] = (float) ($price->bp_order_price ?? 0);
|
||||
}
|
||||
$unitMap = [];
|
||||
foreach ($units as $unit) {
|
||||
$unitMap[(string) $unit->pu_bag_code] = [
|
||||
'boxPerPack' => (int) $unit->pu_box_per_pack,
|
||||
'packPerSheet' => (int) $unit->pu_pack_per_sheet,
|
||||
'totalPerBox' => (int) $unit->pu_total_per_box,
|
||||
];
|
||||
}
|
||||
|
||||
$bagReferenceRows = [];
|
||||
foreach ($bagCodes as $codeDetail) {
|
||||
$bagCode = (string) $codeDetail->cd_code;
|
||||
$unit = $unitMap[$bagCode] ?? ['boxPerPack' => 0, 'packPerSheet' => 0, 'totalPerBox' => 0];
|
||||
$bagReferenceRows[] = [
|
||||
'code' => $bagCode,
|
||||
'name' => (string) ($bagNameMap[$bagCode] ?? ''),
|
||||
'orderPrice' => (float) ($priceMap[$bagCode] ?? 0),
|
||||
'boxPerPack' => (int) $unit['boxPerPack'],
|
||||
'packPerSheet' => (int) $unit['packPerSheet'],
|
||||
'totalPerBox' => (int) $unit['totalPerBox'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->renderWorkPage(
|
||||
'발주 등록',
|
||||
'admin/bag_order/create',
|
||||
compact(
|
||||
'bagCodes',
|
||||
'units',
|
||||
'companies',
|
||||
'associations',
|
||||
'agencies',
|
||||
'recentOrders',
|
||||
'companyMap',
|
||||
'agencyMap',
|
||||
'bagReferenceRows'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function revise(int $id)
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
$order = $this->orderModel->find($id);
|
||||
if (! $order || (int) $order->bo_lg_idx !== $lgIdx) {
|
||||
return redirect()->to(mgmt_url('bag-orders'))->with('error', '발주를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
return redirect()->to(site_url('bag/order/revise/' . $id));
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null || $lgIdx <= 0) {
|
||||
return redirect()->back()->withInput()->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$sourceIdx = (int) ($this->request->getPost('bo_source_idx') ?? 0);
|
||||
$sourceOrder = null;
|
||||
if ($sourceIdx > 0) {
|
||||
$sourceOrder = $this->orderModel->find($sourceIdx);
|
||||
if (! $sourceOrder || (int) $sourceOrder->bo_lg_idx !== $lgIdx) {
|
||||
return redirect()->back()->withInput()->with('error', '수정 대상 발주를 찾을 수 없습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'bo_order_date' => 'required|valid_date[Y-m-d]',
|
||||
@@ -141,65 +467,114 @@ class BagOrder extends BaseController
|
||||
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$bagCodes = $this->request->getPost('item_bag_code') ?? [];
|
||||
$qtySheets = $this->request->getPost('item_qty_sheet') ?? [];
|
||||
$qtyBoxes = $this->request->getPost('item_qty_box') ?? []; // 구 화면 호환
|
||||
$postedUnitPrices = $this->request->getPost('item_unit_price');
|
||||
$changeKind = (string) ($this->request->getPost('bo_change_mode') ?? 'meta');
|
||||
if (! in_array($changeKind, ['price', 'meta', 'delete'], true)) {
|
||||
$changeKind = 'meta';
|
||||
}
|
||||
$itemCount = count($bagCodes);
|
||||
$normalizedItems = [];
|
||||
for ($i = 0; $i < $itemCount; $i++) {
|
||||
$code = trim((string) ($bagCodes[$i] ?? ''));
|
||||
$qtySheet = (int) ($qtySheets[$i] ?? 0);
|
||||
$qtyBox = (int) ($qtyBoxes[$i] ?? 0);
|
||||
if ($code === '' || ($qtySheet <= 0 && $qtyBox <= 0)) {
|
||||
continue;
|
||||
}
|
||||
$normalizedItems[] = ['code' => $code, 'qtySheet' => $qtySheet, 'qtyBox' => $qtyBox];
|
||||
}
|
||||
if (empty($normalizedItems)) {
|
||||
return redirect()->back()->withInput()->with('error', '최소 1개 이상의 봉투 수량을 입력해 주세요.');
|
||||
}
|
||||
|
||||
$priceByCode = [];
|
||||
if ($sourceOrder !== null && $changeKind === 'price' && is_array($postedUnitPrices)) {
|
||||
for ($pi = 0; $pi < count($bagCodes); $pi++) {
|
||||
$c = trim((string) ($bagCodes[$pi] ?? ''));
|
||||
if ($c === '') {
|
||||
continue;
|
||||
}
|
||||
$raw = $postedUnitPrices[$pi] ?? null;
|
||||
if ($raw !== null && $raw !== '' && is_numeric($raw)) {
|
||||
$priceByCode[$c] = round((float) $raw, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$db->transStart();
|
||||
|
||||
// UUID 생성
|
||||
$uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
|
||||
|
||||
// LOT 번호 생성
|
||||
$lotNo = 'LOT-' . date('Ymd') . '-' . strtoupper(substr(md5($uuid), 0, 6));
|
||||
try {
|
||||
if ($sourceOrder) {
|
||||
$uuid = (string) $sourceOrder->bo_uuid;
|
||||
$maxVerRow = $this->orderModel->selectMax('bo_version')->where('bo_uuid', $uuid)->first();
|
||||
$latestVersion = ($maxVerRow !== null && isset($maxVerRow->bo_version)) ? (int) $maxVerRow->bo_version : 0;
|
||||
$version = $latestVersion + 1;
|
||||
$lotNo = (string) $sourceOrder->bo_lot_no;
|
||||
} else {
|
||||
$uuid = $this->generateUuidV4();
|
||||
$version = 1;
|
||||
$lotNo = $this->generateLotNo6();
|
||||
}
|
||||
|
||||
$orderData = [
|
||||
'bo_uuid' => $uuid,
|
||||
'bo_version' => 1,
|
||||
'bo_version' => $version,
|
||||
'bo_lg_idx' => $lgIdx,
|
||||
'bo_gugun_code' => $this->request->getPost('bo_gugun_code') ?? '',
|
||||
'bo_dong_code' => $this->request->getPost('bo_dong_code') ?? '',
|
||||
'bo_gugun_code' => $this->resolveGugunCodeFromLg($lgIdx),
|
||||
'bo_dong_code' => '',
|
||||
'bo_company_idx' => $this->request->getPost('bo_company_idx') ?: null,
|
||||
'bo_agency_idx' => $this->request->getPost('bo_agency_idx') ?: null,
|
||||
'bo_fee_rate' => (float) ($this->request->getPost('bo_fee_rate') ?: 0),
|
||||
'bo_order_date' => $this->request->getPost('bo_order_date'),
|
||||
'bo_bag_types' => '',
|
||||
'bo_unit_prices' => '',
|
||||
'bo_qty_boxes' => '',
|
||||
'bo_lot_no' => $lotNo,
|
||||
'bo_status' => 'normal',
|
||||
'bo_orderer_idx' => session()->get('mb_idx'),
|
||||
'bo_regdate' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
// SHA-256 해시
|
||||
$orderData['bo_hash'] = hash('sha256', json_encode($orderData));
|
||||
// 품목 입력 후 최종 payload 기준으로 해시를 계산하므로 우선 빈값으로 생성
|
||||
$orderData['bo_hash'] = '';
|
||||
|
||||
$this->orderModel->insert($orderData);
|
||||
$boIdx = (int) $this->orderModel->getInsertID();
|
||||
|
||||
// CT-05: 감사 로그
|
||||
helper('audit');
|
||||
audit_log('create', 'bag_order', $boIdx, null, array_merge($orderData, ['bo_idx' => $boIdx]));
|
||||
|
||||
// 품목 저장
|
||||
$bagCodes = $this->request->getPost('item_bag_code') ?? [];
|
||||
$qtyBoxes = $this->request->getPost('item_qty_box') ?? [];
|
||||
foreach ($bagCodes as $i => $code) {
|
||||
if (empty($code) || empty($qtyBoxes[$i])) continue;
|
||||
$qtyBox = (int) $qtyBoxes[$i];
|
||||
|
||||
$hashItems = [];
|
||||
$bagTypesForHeader = [];
|
||||
$unitPricesForHeader = [];
|
||||
$qtyBoxesForHeader = [];
|
||||
foreach ($normalizedItems as $item) {
|
||||
$code = $item['code'];
|
||||
$qtySheetInput = (int) ($item['qtySheet'] ?? 0);
|
||||
$qtyBoxInput = (int) ($item['qtyBox'] ?? 0);
|
||||
// 포장단위에서 낱장 환산
|
||||
$unit = model(PackagingUnitModel::class)->where('pu_lg_idx', $lgIdx)->where('pu_bag_code', $code)->where('pu_state', 1)->first();
|
||||
$totalPerBox = $unit ? (int) $unit->pu_total_per_box : 1;
|
||||
$qtySheet = $qtyBox * $totalPerBox;
|
||||
$totalPerBox = $unit ? max(1, (int) $unit->pu_total_per_box) : 1;
|
||||
$qtySheet = $qtySheetInput > 0 ? $qtySheetInput : ($qtyBoxInput * $totalPerBox);
|
||||
if ($qtySheet <= 0) {
|
||||
continue;
|
||||
}
|
||||
$qtyBox = intdiv($qtySheet, $totalPerBox);
|
||||
|
||||
// 단가
|
||||
$price = model(BagPriceModel::class)->where('bp_lg_idx', $lgIdx)->where('bp_bag_code', $code)->where('bp_state', 1)->first();
|
||||
// 단가 (발주 변경·단가 구분 시 POST 단가 우선)
|
||||
$price = model(BagPriceModel::class)->latestActiveByBagCode($lgIdx, $code);
|
||||
$unitPrice = $price ? (float) $price->bp_order_price : 0;
|
||||
if ($sourceOrder !== null && isset($priceByCode[$code])) {
|
||||
$unitPrice = $priceByCode[$code];
|
||||
}
|
||||
|
||||
// 봉투명
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, (string) $code, $lgIdx) : null;
|
||||
|
||||
$this->itemModel->insert([
|
||||
$itemData = [
|
||||
'boi_bo_idx' => $boIdx,
|
||||
'boi_bag_code' => $code,
|
||||
'boi_bag_name' => $detail ? $detail->cd_name : '',
|
||||
@@ -207,14 +582,204 @@ class BagOrder extends BaseController
|
||||
'boi_qty_box' => $qtyBox,
|
||||
'boi_qty_sheet' => $qtySheet,
|
||||
'boi_amount' => $unitPrice * $qtySheet,
|
||||
]);
|
||||
];
|
||||
$this->itemModel->insert($itemData);
|
||||
$hashItems[] = $itemData;
|
||||
|
||||
$bagTypesForHeader[] = [
|
||||
'code' => $itemData['boi_bag_code'],
|
||||
'name' => $itemData['boi_bag_name'],
|
||||
];
|
||||
$unitPricesForHeader[] = [
|
||||
'code' => $itemData['boi_bag_code'],
|
||||
'unit_price' => $itemData['boi_unit_price'],
|
||||
];
|
||||
$qtyBoxesForHeader[] = [
|
||||
'code' => $itemData['boi_bag_code'],
|
||||
'qty_box' => $itemData['boi_qty_box'],
|
||||
];
|
||||
}
|
||||
|
||||
$db->transComplete();
|
||||
$orderData['bo_bag_types'] = json_encode($bagTypesForHeader, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]';
|
||||
$orderData['bo_unit_prices'] = json_encode($unitPricesForHeader, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]';
|
||||
$orderData['bo_qty_boxes'] = json_encode($qtyBoxesForHeader, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]';
|
||||
|
||||
// 최종 발주 데이터(헤더+품목) 해시
|
||||
$hashPayload = $orderData;
|
||||
$hashPayload['bo_idx'] = $boIdx;
|
||||
$hashPayload['items'] = $hashItems;
|
||||
$hashJson = json_encode($hashPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$orderHash = hash('sha256', $hashJson !== false ? $hashJson : (string) $boIdx);
|
||||
$this->orderModel->update($boIdx, [
|
||||
'bo_bag_types' => $orderData['bo_bag_types'],
|
||||
'bo_unit_prices' => $orderData['bo_unit_prices'],
|
||||
'bo_qty_boxes' => $orderData['bo_qty_boxes'],
|
||||
'bo_hash' => $orderHash,
|
||||
]);
|
||||
|
||||
$beforeHash = $sourceOrder ? (string) ($sourceOrder->bo_hash ?? '') : '';
|
||||
$seedFilePath = $this->generateBarcodeSeedFile($uuid, $version, $lotNo, $orderData, $hashItems, $orderHash);
|
||||
$blockPayload = [
|
||||
'bo_idx' => $boIdx,
|
||||
'bo_uuid' => $uuid,
|
||||
'bo_version' => $version,
|
||||
'bo_lot_no' => $lotNo,
|
||||
'bo_hash' => $orderHash,
|
||||
'seed_file' => $seedFilePath,
|
||||
'hash_chain' => $beforeHash !== '' ? [$beforeHash, $orderHash] : [$orderHash],
|
||||
'order' => $orderData,
|
||||
'items' => $hashItems,
|
||||
];
|
||||
$ledger = new SqlLedger();
|
||||
$ledger->appendBlock(
|
||||
$sourceOrder ? 'ORDER_UPDATE' : 'ORDER_CREATE',
|
||||
$blockPayload,
|
||||
$uuid,
|
||||
$version,
|
||||
session()->get('mb_idx') ? (int) session()->get('mb_idx') : null,
|
||||
$lgIdx
|
||||
);
|
||||
|
||||
// CT-05: 감사 로그
|
||||
helper('audit');
|
||||
if ($sourceOrder) {
|
||||
audit_log(
|
||||
'update',
|
||||
'bag_order',
|
||||
$boIdx,
|
||||
['bo_idx' => (int) $sourceOrder->bo_idx, 'bo_hash' => $beforeHash, 'bo_version' => (int) $sourceOrder->bo_version],
|
||||
array_merge($orderData, ['bo_idx' => $boIdx, 'bo_hash' => $orderHash, 'items' => $hashItems, 'seed_file' => $seedFilePath])
|
||||
);
|
||||
} else {
|
||||
audit_log('create', 'bag_order', $boIdx, null, array_merge($orderData, ['bo_idx' => $boIdx, 'bo_hash' => $orderHash, 'items' => $hashItems, 'seed_file' => $seedFilePath]));
|
||||
}
|
||||
|
||||
if (! $db->transComplete()) {
|
||||
throw new \RuntimeException('Transaction did not complete');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$db->transRollback();
|
||||
log_message('error', 'BagOrder::store: ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
|
||||
|
||||
return redirect()->back()->withInput()->with('error', '발주 저장 중 오류가 발생했습니다. 잠시 후 다시 시도하거나 관리자에게 문의하세요.');
|
||||
}
|
||||
|
||||
return redirect()->to(mgmt_url('bag-orders'))->with('success', '발주가 등록되었습니다. LOT: ' . $lotNo);
|
||||
}
|
||||
|
||||
/** 효과 지자체(`local_government`)의 행정 구·군 코드(lg_code) */
|
||||
private function resolveGugunCodeFromLg(int $lgIdx): string
|
||||
{
|
||||
$lg = model(LocalGovernmentModel::class)->find($lgIdx);
|
||||
|
||||
return $lg ? trim((string) ($lg->lg_code ?? '')) : '';
|
||||
}
|
||||
|
||||
private function generateUuidV4(): string
|
||||
{
|
||||
$bytes = random_bytes(16);
|
||||
$bytes[6] = chr((ord($bytes[6]) & 0x0f) | 0x40);
|
||||
$bytes[8] = chr((ord($bytes[8]) & 0x3f) | 0x80);
|
||||
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($bytes), 4));
|
||||
}
|
||||
|
||||
private function generateLotNo6(): string
|
||||
{
|
||||
// 문서의 "LOT 번호 6 Byte" 요구를 맞추기 위해 영숫자 6자리로 생성한다.
|
||||
// 충돌 가능성을 낮추기 위해 최대 20회 재시도 후 timestamp 기반으로 fallback.
|
||||
$chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
for ($attempt = 0; $attempt < 20; $attempt++) {
|
||||
$lot = '';
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$lot .= $chars[random_int(0, strlen($chars) - 1)];
|
||||
}
|
||||
|
||||
$exists = $this->orderModel->where('bo_lot_no', $lot)->countAllResults() > 0;
|
||||
if (! $exists) {
|
||||
return $lot;
|
||||
}
|
||||
}
|
||||
|
||||
return strtoupper(substr(base_convert((string) time(), 10, 36), -6));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $orderData
|
||||
* @param array<int,array<string,mixed>> $items
|
||||
*/
|
||||
private function generateBarcodeSeedFile(string $uuid, int $version, string $lotNo, array $orderData, array $items, string $orderHash): string
|
||||
{
|
||||
$baseDir = WRITEPATH . 'barcode-seeds';
|
||||
if (! is_dir($baseDir)) {
|
||||
mkdir($baseDir, 0775, true);
|
||||
}
|
||||
$keyDir = WRITEPATH . 'keys';
|
||||
if (! is_dir($keyDir)) {
|
||||
mkdir($keyDir, 0775, true);
|
||||
}
|
||||
|
||||
$privateKeyPath = $keyDir . DIRECTORY_SEPARATOR . 'barcode_seed_private.pem';
|
||||
$publicKeyPath = $keyDir . DIRECTORY_SEPARATOR . 'barcode_seed_public.pem';
|
||||
if (! is_file($privateKeyPath) || ! is_file($publicKeyPath)) {
|
||||
$config = [
|
||||
'private_key_bits' => 2048,
|
||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||
];
|
||||
$resource = openssl_pkey_new($config);
|
||||
if ($resource !== false) {
|
||||
$privatePem = '';
|
||||
openssl_pkey_export($resource, $privatePem);
|
||||
$details = openssl_pkey_get_details($resource);
|
||||
$publicPem = $details['key'] ?? '';
|
||||
if ($privatePem !== '' && $publicPem !== '') {
|
||||
file_put_contents($privateKeyPath, $privatePem);
|
||||
file_put_contents($publicKeyPath, $publicPem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'uuid' => $uuid,
|
||||
'version' => $version,
|
||||
'lot_no' => $lotNo,
|
||||
'order_hash' => $orderHash,
|
||||
'order' => $orderData,
|
||||
'items' => $items,
|
||||
];
|
||||
$payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}';
|
||||
|
||||
$aesKey = random_bytes(32);
|
||||
$iv = random_bytes(16);
|
||||
$cipherRaw = openssl_encrypt($payloadJson, 'AES-256-CBC', $aesKey, OPENSSL_RAW_DATA, $iv);
|
||||
if ($cipherRaw === false) {
|
||||
$cipherRaw = $payloadJson;
|
||||
}
|
||||
$encryptedKey = '';
|
||||
$publicPem = is_file($publicKeyPath) ? file_get_contents($publicKeyPath) : '';
|
||||
if (is_string($publicPem) && $publicPem !== '') {
|
||||
openssl_public_encrypt($aesKey, $encryptedKey, $publicPem, OPENSSL_PKCS1_OAEP_PADDING);
|
||||
}
|
||||
|
||||
$seed = [
|
||||
'algorithm' => ['symmetric' => 'AES-256-CBC', 'asymmetric' => 'RSA-2048'],
|
||||
'lot_no' => $lotNo,
|
||||
'uuid' => $uuid,
|
||||
'version' => $version,
|
||||
'iv_b64' => base64_encode($iv),
|
||||
'key_b64' => $encryptedKey !== '' ? base64_encode($encryptedKey) : '',
|
||||
'cipher_b64' => base64_encode((string) $cipherRaw),
|
||||
'payload_hash' => hash('sha256', $payloadJson),
|
||||
'created_at' => date('c'),
|
||||
];
|
||||
|
||||
$fileName = $lotNo . '_v' . $version . '.seed.json';
|
||||
$fullPath = $baseDir . DIRECTORY_SEPARATOR . $fileName;
|
||||
file_put_contents($fullPath, json_encode($seed, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
return $fullPath;
|
||||
}
|
||||
|
||||
public function detail(int $id)
|
||||
{
|
||||
helper('admin');
|
||||
@@ -250,9 +815,11 @@ class BagOrder extends BaseController
|
||||
}
|
||||
|
||||
$before = (array) $order;
|
||||
$this->orderModel->update($id, ['bo_status' => 'cancelled', 'bo_moddate' => date('Y-m-d H:i:s')]);
|
||||
$beforeHash = (string) ($order->bo_hash ?? '');
|
||||
$this->appendLedgerForStatusChange($order, $id, 'ORDER_CANCEL', 'cancelled', $beforeHash);
|
||||
$after = (array) $this->orderModel->find($id);
|
||||
helper('audit');
|
||||
audit_log('update', 'bag_order', $id, $before, ['bo_status' => 'cancelled']);
|
||||
audit_log('update', 'bag_order', $id, $before, $after);
|
||||
|
||||
return redirect()->to(mgmt_url('bag-orders'))->with('success', '발주가 취소되었습니다.');
|
||||
}
|
||||
@@ -266,10 +833,117 @@ class BagOrder extends BaseController
|
||||
}
|
||||
|
||||
$before = (array) $order;
|
||||
$this->orderModel->update($id, ['bo_status' => 'deleted', 'bo_moddate' => date('Y-m-d H:i:s')]);
|
||||
$beforeHash = (string) ($order->bo_hash ?? '');
|
||||
$this->appendLedgerForStatusChange($order, $id, 'ORDER_DELETE', 'deleted', $beforeHash);
|
||||
$after = (array) $this->orderModel->find($id);
|
||||
helper('audit');
|
||||
audit_log('delete', 'bag_order', $id, $before, ['bo_status' => 'deleted']);
|
||||
audit_log('delete', 'bag_order', $id, $before, $after);
|
||||
|
||||
return redirect()->to(mgmt_url('bag-orders'))->with('success', '발주가 삭제 처리되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 변경 시(취소/삭제) 무결성 검증을 위해 bo_hash 재계산 후
|
||||
* SQL-Ledger(append-only)에 블록을 추가한다.
|
||||
*
|
||||
* @param object $order
|
||||
* @param int $boIdx
|
||||
* @param string $txType ORDER_CANCEL|ORDER_DELETE
|
||||
* @param string $newStatus cancelled|deleted
|
||||
* @param string $previousHash
|
||||
*/
|
||||
private function appendLedgerForStatusChange(object $order, int $boIdx, string $txType, string $newStatus, string $previousHash): void
|
||||
{
|
||||
// 품목은 상태 변경 시 그대로이므로, 동일 payload 형태로 items array를 만든다.
|
||||
$items = $this->itemModel->where('boi_bo_idx', $boIdx)->findAll();
|
||||
$hashItems = [];
|
||||
foreach ($items as $it) {
|
||||
$hashItems[] = [
|
||||
'boi_bo_idx' => (int) $it->boi_bo_idx,
|
||||
'boi_bag_code' => (string) $it->boi_bag_code,
|
||||
'boi_bag_name' => (string) ($it->boi_bag_name ?? ''),
|
||||
'boi_unit_price' => (float) $it->boi_unit_price,
|
||||
'boi_qty_box' => (int) $it->boi_qty_box,
|
||||
'boi_qty_sheet' => (int) $it->boi_qty_sheet,
|
||||
'boi_amount' => (float) $it->boi_amount,
|
||||
];
|
||||
}
|
||||
|
||||
$newOrder = $order;
|
||||
$newOrder->bo_status = $newStatus;
|
||||
|
||||
$newHash = $this->computeOrderHash($boIdx, $newOrder, $hashItems);
|
||||
$actorIdx = session()->get('mb_idx') ? (int) session()->get('mb_idx') : null;
|
||||
$lgIdx = (int) ($order->bo_lg_idx ?? 0);
|
||||
|
||||
$seedFilePath = '';
|
||||
$ledgerPayload = [
|
||||
'bo_idx' => $boIdx,
|
||||
'bo_uuid' => (string) $order->bo_uuid,
|
||||
'bo_version' => (int) $order->bo_version,
|
||||
'bo_lot_no' => (string) $order->bo_lot_no,
|
||||
'bo_hash' => $newHash,
|
||||
'seed_file' => $seedFilePath,
|
||||
'hash_chain' => [$previousHash, $newHash],
|
||||
'order' => [
|
||||
'bo_status' => $newStatus,
|
||||
'bo_hash' => $newHash,
|
||||
],
|
||||
'items' => $hashItems,
|
||||
];
|
||||
|
||||
$ledger = new SqlLedger();
|
||||
$ledger->appendBlock(
|
||||
$txType,
|
||||
$ledgerPayload,
|
||||
(string) $order->bo_uuid,
|
||||
(int) $order->bo_version,
|
||||
$actorIdx,
|
||||
$lgIdx
|
||||
);
|
||||
|
||||
// order row에 hash 반영
|
||||
$this->orderModel->update($boIdx, [
|
||||
'bo_status' => $newStatus,
|
||||
'bo_moddate' => date('Y-m-d H:i:s'),
|
||||
'bo_hash' => $newHash,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* store()에서 생성하는 bo_hash와 동일한 "헤더+items" 규격을 사용해 SHA-256을 계산한다.
|
||||
*
|
||||
* @param int $boIdx
|
||||
* @param object $order
|
||||
* @param array<int,array<string,mixed>> $hashItems
|
||||
*/
|
||||
private function computeOrderHash(int $boIdx, object $order, array $hashItems): string
|
||||
{
|
||||
$orderData = [
|
||||
'bo_uuid' => (string) $order->bo_uuid,
|
||||
'bo_version' => (int) $order->bo_version,
|
||||
'bo_lg_idx' => (int) $order->bo_lg_idx,
|
||||
'bo_gugun_code' => (string) ($order->bo_gugun_code ?? ''),
|
||||
'bo_dong_code' => (string) ($order->bo_dong_code ?? ''),
|
||||
'bo_company_idx' => $order->bo_company_idx !== null ? (int) $order->bo_company_idx : null,
|
||||
'bo_agency_idx' => $order->bo_agency_idx !== null ? (int) $order->bo_agency_idx : null,
|
||||
'bo_fee_rate' => (float) ($order->bo_fee_rate ?? 0),
|
||||
'bo_order_date' => (string) $order->bo_order_date,
|
||||
'bo_bag_types' => (string) ($order->bo_bag_types ?? ''),
|
||||
'bo_unit_prices' => (string) ($order->bo_unit_prices ?? ''),
|
||||
'bo_qty_boxes' => (string) ($order->bo_qty_boxes ?? ''),
|
||||
'bo_lot_no' => (string) $order->bo_lot_no,
|
||||
'bo_hash' => '',
|
||||
'bo_status' => (string) $order->bo_status,
|
||||
'bo_orderer_idx' => $order->bo_orderer_idx !== null ? (int) $order->bo_orderer_idx : null,
|
||||
'bo_regdate' => (string) ($order->bo_regdate ?? ''),
|
||||
];
|
||||
|
||||
$hashPayload = $orderData;
|
||||
$hashPayload['bo_idx'] = $boIdx;
|
||||
$hashPayload['items'] = $hashItems;
|
||||
|
||||
$hashJson = json_encode($hashPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
return hash('sha256', $hashJson !== false ? $hashJson : (string) $boIdx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,143 @@ class BagPrice extends BaseController
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$list = $this->priceModel->where('bp_lg_idx', $lgIdx)
|
||||
$get = $this->request->getGet();
|
||||
$readSrc = static function (array $src, string $key): ?string {
|
||||
if (! array_key_exists($key, $src)) {
|
||||
return null;
|
||||
}
|
||||
$v = $src[$key];
|
||||
if ($v === null || is_array($v)) {
|
||||
return null;
|
||||
}
|
||||
$s = trim((string) $v);
|
||||
|
||||
return $s === '' ? null : $s;
|
||||
};
|
||||
|
||||
$sy = $readSrc($get, 'start_y');
|
||||
$sm = $readSrc($get, 'start_m');
|
||||
$sd = $readSrc($get, 'start_d');
|
||||
$ey = $readSrc($get, 'end_y');
|
||||
$em = $readSrc($get, 'end_m');
|
||||
$ed = $readSrc($get, 'end_d');
|
||||
|
||||
$startDate = null;
|
||||
if ($sy !== null && $sy !== '' && $sm !== null && $sm !== '' && $sd !== null && $sd !== '') {
|
||||
$startDate = parse_ymd_from_triple($sy, $sm, $sd);
|
||||
}
|
||||
if ($startDate === null) {
|
||||
$legacyStart = $readSrc($get, 'start_date');
|
||||
$startDate = ($legacyStart !== null && $legacyStart !== '') ? $legacyStart : null;
|
||||
}
|
||||
|
||||
$endDate = null;
|
||||
if ($ey !== null && $ey !== '' && $em !== null && $em !== '' && $ed !== null && $ed !== '') {
|
||||
$endDate = parse_ymd_from_triple($ey, $em, $ed);
|
||||
}
|
||||
if ($endDate === null) {
|
||||
$legacyEnd = $readSrc($get, 'end_date');
|
||||
$endDate = ($legacyEnd !== null && $legacyEnd !== '') ? $legacyEnd : null;
|
||||
}
|
||||
|
||||
$startParts = ['y' => '', 'm' => '', 'd' => ''];
|
||||
$endParts = ['y' => '', 'm' => '', 'd' => ''];
|
||||
if ($startDate !== null && preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $startDate, $m)) {
|
||||
$startParts = ['y' => $m[1], 'm' => (int) $m[2], 'd' => (int) $m[3]];
|
||||
}
|
||||
if ($endDate !== null && preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $endDate, $m)) {
|
||||
$endParts = ['y' => $m[1], 'm' => (int) $m[2], 'd' => (int) $m[3]];
|
||||
}
|
||||
|
||||
$bagKindE = $readSrc($get, 'bag_kind_e');
|
||||
$bagCode = $readSrc($get, 'bag_code');
|
||||
|
||||
$builder = $this->priceModel->where('bp_lg_idx', $lgIdx);
|
||||
if (($startDate !== null && $startDate !== '') || ($endDate !== null && $endDate !== '')) {
|
||||
$qStart = ($startDate !== null && $startDate !== '') ? $startDate : $endDate;
|
||||
$qEnd = ($endDate !== null && $endDate !== '') ? $endDate : $startDate;
|
||||
if (strcmp((string) $qStart, (string) $qEnd) > 0) {
|
||||
[$qStart, $qEnd] = [$qEnd, $qStart];
|
||||
}
|
||||
$builder->where('bp_start_date <=', $qEnd);
|
||||
$builder->groupStart()
|
||||
->where('bp_end_date IS NULL')
|
||||
->orWhere('bp_end_date >=', $qStart)
|
||||
->groupEnd();
|
||||
}
|
||||
|
||||
if ($bagKindE !== null && $bagKindE !== '') {
|
||||
$kindE = model(CodeKindModel::class)->where('ck_code', 'E')->first();
|
||||
if ($kindE) {
|
||||
$detailE = model(CodeDetailModel::class)
|
||||
->where('cd_ck_idx', (int) $kindE->ck_idx)
|
||||
->where('cd_code', $bagKindE)
|
||||
->where('cd_state', 1)
|
||||
->first();
|
||||
if ($detailE !== null) {
|
||||
$builder->like('bp_bag_code', $bagKindE, 'after');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($bagCode !== null && $bagCode !== '') {
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
if ($kindO) {
|
||||
$detailO = model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, $bagCode, $lgIdx);
|
||||
if ($detailO !== null) {
|
||||
$builder->where('bp_bag_code', $bagCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$list = $builder
|
||||
->orderBy('bp_bag_code', 'ASC')
|
||||
->orderBy('bp_start_date', 'DESC')
|
||||
->paginate(20);
|
||||
|
||||
$queryForPager = [];
|
||||
if ($sy !== null && $sm !== null && $sd !== null && $sy !== '' && $sm !== '' && $sd !== '') {
|
||||
$queryForPager['start_y'] = $sy;
|
||||
$queryForPager['start_m'] = $sm;
|
||||
$queryForPager['start_d'] = $sd;
|
||||
}
|
||||
if ($ey !== null && $em !== null && $ed !== null && $ey !== '' && $em !== '' && $ed !== '') {
|
||||
$queryForPager['end_y'] = $ey;
|
||||
$queryForPager['end_m'] = $em;
|
||||
$queryForPager['end_d'] = $ed;
|
||||
}
|
||||
if ($bagKindE !== null && $bagKindE !== '') {
|
||||
$queryForPager['bag_kind_e'] = $bagKindE;
|
||||
}
|
||||
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);
|
||||
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kindO
|
||||
? model(CodeDetailModel::class)->getByKind((int) $kindO->ck_idx, true, $lgIdx)
|
||||
: [];
|
||||
$kindE = model(CodeKindModel::class)->where('ck_code', 'E')->first();
|
||||
$bagKindOptions = $kindE
|
||||
? model(CodeDetailModel::class)->getByKind((int) $kindE->ck_idx, true, null)
|
||||
: [];
|
||||
|
||||
return $this->renderWorkPage('봉투 단가 관리', 'admin/bag_price/index', [
|
||||
'list' => $list,
|
||||
'list' => $list,
|
||||
'pager' => $this->priceModel->pager,
|
||||
'startParts' => $startParts,
|
||||
'endParts' => $endParts,
|
||||
'dateYearMin' => (int) date('Y') - 12,
|
||||
'dateYearMax' => (int) date('Y') + 2,
|
||||
'bag_kind_e' => $bagKindE,
|
||||
'bag_code' => $bagCode,
|
||||
'bag_codes' => $bagCodes,
|
||||
'bag_kind_options' => $bagKindOptions,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class BagReceiving extends BaseController
|
||||
return redirect()->to(mgmt_url('bag-receivings'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$orders = model(BagOrderModel::class)->where('bo_lg_idx', $lgIdx)->where('bo_status', 'normal')->orderBy('bo_order_date', 'DESC')->findAll();
|
||||
$orders = model(BagOrderModel::class)->where('bo_lg_idx', $lgIdx)->whereLatestHead($lgIdx)->where('bo_status', 'normal')->orderBy('bo_order_date', 'DESC')->findAll();
|
||||
|
||||
return $this->renderWorkPage('입고 처리', 'admin/bag_receiving/create', compact('orders'));
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ class BagSale extends BaseController
|
||||
$shop = model(DesignatedShopModel::class)->find($dsIdx);
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, (string) $bagCode, $lgIdx) : null;
|
||||
$price = model(BagPriceModel::class)->where('bp_lg_idx', $lgIdx)->where('bp_bag_code', $bagCode)->where('bp_state', 1)->first();
|
||||
$price = model(BagPriceModel::class)->latestActiveByBagCode($lgIdx, (string) $bagCode);
|
||||
$unitPrice = $price ? (float) $price->bp_consumer : 0;
|
||||
|
||||
$actualQty = ($type === 'return') ? -$qty : $qty;
|
||||
|
||||
@@ -9,6 +9,11 @@ class Company extends BaseController
|
||||
{
|
||||
private CompanyModel $model;
|
||||
|
||||
private function companyTypeOptions(): array
|
||||
{
|
||||
return ['협회', '제작업체', '회수업체'];
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->model = model(CompanyModel::class);
|
||||
@@ -22,10 +27,33 @@ class Company extends BaseController
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$list = $this->model->where('cp_lg_idx', $lgIdx)->orderBy('cp_idx', 'DESC')->paginate(20);
|
||||
$companyType = trim((string) ($this->request->getGet('cp_type') ?? ''));
|
||||
$typeOptions = $this->companyTypeOptions();
|
||||
|
||||
$builder = $this->model->where('cp_lg_idx', $lgIdx);
|
||||
if ($companyType !== '' && in_array($companyType, $typeOptions, true)) {
|
||||
$builder->where('cp_type', $companyType);
|
||||
}
|
||||
|
||||
$list = $builder->orderBy('cp_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->model->pager;
|
||||
|
||||
return $this->renderWorkPage('업체 관리', 'admin/company/index', ['list' => $list, 'pager' => $pager]);
|
||||
$queryForPager = [];
|
||||
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);
|
||||
|
||||
return $this->renderWorkPage('업체 관리', 'admin/company/index', [
|
||||
'list' => $list,
|
||||
'pager' => $pager,
|
||||
'cpType' => $companyType,
|
||||
'typeOptions' => $typeOptions,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
|
||||
@@ -227,6 +227,9 @@ class DesignatedShop extends BaseController
|
||||
*/
|
||||
private function buildDesignatedShopDetailPayload(array $list, array $lgMap): array
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx() ?? 0;
|
||||
$gugunMap = $lgIdx > 0 ? $this->gugunCodeNameMap($lgIdx) : [];
|
||||
$payload = [];
|
||||
foreach ($list as $row) {
|
||||
$sn = (string) ($row->ds_shop_no ?? '');
|
||||
@@ -263,6 +266,7 @@ class DesignatedShop extends BaseController
|
||||
'ds_rep_phone' => (string) ($row->ds_rep_phone ?? ''),
|
||||
'ds_email' => (string) ($row->ds_email ?? ''),
|
||||
'ds_gugun_code' => (string) ($row->ds_gugun_code ?? ''),
|
||||
'gugun_name' => $gugunMap[(string) ($row->ds_gugun_code ?? '')] ?? (string) ($row->ds_gugun_code ?? ''),
|
||||
'ds_zone_code' => $this->designatedShopScalar($row, 'ds_zone_code'),
|
||||
'ds_branch_no' => $this->designatedShopScalar($row, 'ds_branch_no'),
|
||||
'ds_designated_at' => $daOut,
|
||||
@@ -306,6 +310,7 @@ class DesignatedShop extends BaseController
|
||||
}
|
||||
|
||||
$stateCounts = $this->countDesignatedShopsByState($lgIdx, $dsName, $dsGugunCode, $dsState);
|
||||
$gugunNameMap = $this->gugunCodeNameMap($lgIdx);
|
||||
$detailRows = $this->buildDesignatedShopDetailPayload($list, $lgMap);
|
||||
|
||||
// 구군코드 목록 (검색 필터용)
|
||||
@@ -321,6 +326,7 @@ class DesignatedShop extends BaseController
|
||||
'dsState' => $dsState ?? '',
|
||||
'gugunCodes' => $gugunCodes,
|
||||
'stateCounts' => $stateCounts,
|
||||
'gugunNameMap' => $gugunNameMap,
|
||||
'detailRowsJson' => json_encode($detailRows, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE),
|
||||
'kakaoJavascriptKey' => $this->kakaoJavascriptKey(),
|
||||
];
|
||||
@@ -336,6 +342,7 @@ class DesignatedShop extends BaseController
|
||||
return redirect()->to(work_area_home_url())
|
||||
->with('error', '작업할 지자체가 선택되지 않았습니다. 지자체를 선택해 주세요.');
|
||||
}
|
||||
$data['readOnly'] = false;
|
||||
|
||||
return $this->renderWorkPage('지정판매소 관리', 'admin/designated_shop/index', $data);
|
||||
}
|
||||
@@ -352,7 +359,7 @@ class DesignatedShop extends BaseController
|
||||
}
|
||||
$data['readOnly'] = true;
|
||||
|
||||
return $this->renderWorkPage('지정판매소 조회', 'admin/designated_shop/index', $data);
|
||||
return $this->renderWorkPage('지정판매소 조회', 'admin/designated_shop/manage', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,15 @@ class Manager extends BaseController
|
||||
return $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx) : [];
|
||||
}
|
||||
|
||||
private function managerCategoryOptions(): array
|
||||
{
|
||||
return [
|
||||
'company' => '제작업체',
|
||||
'district' => '구·군',
|
||||
'agency' => '대행소',
|
||||
];
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
helper('admin');
|
||||
@@ -35,16 +44,29 @@ class Manager extends BaseController
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$list = $this->model->where('mg_lg_idx', $lgIdx)->orderBy('mg_idx', 'DESC')->paginate(20);
|
||||
$category = (string) ($this->request->getGet('category') ?? '');
|
||||
$categories = $this->managerCategoryOptions();
|
||||
|
||||
$builder = $this->model->where('mg_lg_idx', $lgIdx);
|
||||
if ($category !== '' && isset($categories[$category])) {
|
||||
$builder->where('mg_dept_code', $category);
|
||||
}
|
||||
|
||||
$list = $builder->orderBy('mg_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->model->pager;
|
||||
|
||||
return $this->renderWorkPage('담당자 관리', 'admin/manager/index', ['list' => $list, 'pager' => $pager]);
|
||||
return $this->renderWorkPage('담당자 관리', 'admin/manager/index', [
|
||||
'list' => $list,
|
||||
'pager' => $pager,
|
||||
'categories' => $categories,
|
||||
'category' => $category,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return $this->renderWorkPage('담당자 등록', 'admin/manager/create', [
|
||||
'deptCodes' => $this->getCodeOptions('S'),
|
||||
'categories' => $this->managerCategoryOptions(),
|
||||
'positionCodes' => $this->getCodeOptions('T'),
|
||||
]);
|
||||
}
|
||||
@@ -54,6 +76,7 @@ class Manager extends BaseController
|
||||
helper(['admin', 'url']);
|
||||
$rules = [
|
||||
'mg_name' => 'required|max_length[50]',
|
||||
'mg_category' => 'required|in_list[company,district,agency]',
|
||||
'mg_tel' => 'permit_empty|max_length[20]',
|
||||
'mg_phone' => 'permit_empty|max_length[20]',
|
||||
'mg_email' => 'permit_empty|valid_email|max_length[100]',
|
||||
@@ -65,7 +88,7 @@ class Manager extends BaseController
|
||||
$this->model->insert([
|
||||
'mg_lg_idx' => admin_effective_lg_idx(),
|
||||
'mg_name' => $this->request->getPost('mg_name'),
|
||||
'mg_dept_code' => $this->request->getPost('mg_dept_code') ?? '',
|
||||
'mg_dept_code' => (string) ($this->request->getPost('mg_category') ?? ''),
|
||||
'mg_position_code' => $this->request->getPost('mg_position_code') ?? '',
|
||||
'mg_tel' => $this->request->getPost('mg_tel') ?? '',
|
||||
'mg_phone' => $this->request->getPost('mg_phone') ?? '',
|
||||
@@ -87,7 +110,7 @@ class Manager extends BaseController
|
||||
|
||||
return $this->renderWorkPage('담당자 수정', 'admin/manager/edit', [
|
||||
'item' => $item,
|
||||
'deptCodes' => $this->getCodeOptions('S'),
|
||||
'categories' => $this->managerCategoryOptions(),
|
||||
'positionCodes' => $this->getCodeOptions('T'),
|
||||
]);
|
||||
}
|
||||
@@ -102,6 +125,7 @@ class Manager extends BaseController
|
||||
|
||||
$rules = [
|
||||
'mg_name' => 'required|max_length[50]',
|
||||
'mg_category' => 'required|in_list[company,district,agency]',
|
||||
'mg_state' => 'required|in_list[0,1]',
|
||||
];
|
||||
if (! $this->validate($rules)) {
|
||||
@@ -110,7 +134,7 @@ class Manager extends BaseController
|
||||
|
||||
$this->model->update($id, [
|
||||
'mg_name' => $this->request->getPost('mg_name'),
|
||||
'mg_dept_code' => $this->request->getPost('mg_dept_code') ?? '',
|
||||
'mg_dept_code' => (string) ($this->request->getPost('mg_category') ?? ''),
|
||||
'mg_position_code' => $this->request->getPost('mg_position_code') ?? '',
|
||||
'mg_tel' => $this->request->getPost('mg_tel') ?? '',
|
||||
'mg_phone' => $this->request->getPost('mg_phone') ?? '',
|
||||
|
||||
@@ -23,6 +23,9 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus()) {
|
||||
return $deny;
|
||||
}
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
@@ -93,6 +96,9 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function list()
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus(true)) {
|
||||
return $deny;
|
||||
}
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return $this->response->setJSON(['status' => 0, 'msg' => '지자체를 선택하세요.']);
|
||||
@@ -112,6 +118,9 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus()) {
|
||||
return $deny;
|
||||
}
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
@@ -144,6 +153,7 @@ class Menu extends BaseController
|
||||
if ($mmPidx > 0) {
|
||||
$this->menuModel->updateCnode($mmPidx, 1);
|
||||
}
|
||||
$this->menuModel->syncTypeToAllLgs($mtIdx, $lgIdx);
|
||||
return redirect()->back()->with('success', '메뉴가 등록되었습니다.');
|
||||
}
|
||||
|
||||
@@ -152,6 +162,9 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function update(int $id)
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus()) {
|
||||
return $deny;
|
||||
}
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
@@ -171,6 +184,7 @@ class Menu extends BaseController
|
||||
'mm_is_view' => $this->request->getPost('mm_is_view') ? 'Y' : 'N',
|
||||
];
|
||||
$this->menuModel->update($id, $data);
|
||||
$this->menuModel->syncTypeToAllLgs((int) $row->mt_idx, $lgIdx);
|
||||
return redirect()->back()->with('success', '메뉴가 수정되었습니다.');
|
||||
}
|
||||
|
||||
@@ -179,6 +193,9 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function delete(int $id)
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus()) {
|
||||
return $deny;
|
||||
}
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
@@ -190,6 +207,7 @@ class Menu extends BaseController
|
||||
}
|
||||
$result = $this->menuModel->deleteSafe($id);
|
||||
if ($result['ok']) {
|
||||
$this->menuModel->syncTypeToAllLgs((int) $row->mt_idx, $lgIdx);
|
||||
return redirect()->back()->with('success', '메뉴가 삭제되었습니다.');
|
||||
}
|
||||
return redirect()->back()->with('error', $result['msg']);
|
||||
@@ -200,6 +218,9 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function move()
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus()) {
|
||||
return $deny;
|
||||
}
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
@@ -209,7 +230,12 @@ class Menu extends BaseController
|
||||
if (! is_array($ids) || empty($ids)) {
|
||||
return redirect()->back()->with('error', '순서를 적용할 메뉴가 없습니다.');
|
||||
}
|
||||
$firstId = (int) ($ids[0] ?? 0);
|
||||
$firstRow = $firstId > 0 ? $this->menuModel->find($firstId) : null;
|
||||
$this->menuModel->setOrder($ids, $lgIdx);
|
||||
if ($firstRow && (int) $firstRow->lg_idx === $lgIdx) {
|
||||
$this->menuModel->syncTypeToAllLgs((int) $firstRow->mt_idx, $lgIdx);
|
||||
}
|
||||
return redirect()->back()->with('success', '순서가 적용되었습니다.');
|
||||
}
|
||||
|
||||
@@ -266,4 +292,27 @@ class Menu extends BaseController
|
||||
|
||||
return (int) $types[0]->mt_idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 관리는 레벨4 이상(슈퍼/본부 관리자)만 허용.
|
||||
*
|
||||
* @return \CodeIgniter\HTTP\RedirectResponse|\CodeIgniter\HTTP\ResponseInterface|null
|
||||
*/
|
||||
private function denyUnlessLevel4Plus(bool $json = false)
|
||||
{
|
||||
$level = (int) session()->get('mb_level');
|
||||
if (Roles::isSuperAdminEquivalent($level)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($json) {
|
||||
return $this->response->setJSON([
|
||||
'status' => 0,
|
||||
'msg' => '메뉴 관리는 레벨4 이상만 접근할 수 있습니다.',
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->to(base_url('admin/dashboard'))
|
||||
->with('error', '메뉴 관리는 레벨4 이상만 접근할 수 있습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,14 +143,31 @@ class PackagingUnit extends BaseController
|
||||
$db = \Config\Database::connect();
|
||||
$db->transStart();
|
||||
|
||||
$trackFields = ['pu_box_per_pack', 'pu_pack_per_sheet'];
|
||||
$trackFields = ['pu_box_per_pack', 'pu_pack_per_sheet', 'pu_start_date', 'pu_end_date', 'pu_state'];
|
||||
$fieldLabels = [
|
||||
'pu_box_per_pack' => '박스당 팩 수',
|
||||
'pu_pack_per_sheet' => '팩당 낱장 수',
|
||||
'pu_start_date' => '적용시작일',
|
||||
'pu_end_date' => '적용종료일',
|
||||
'pu_state' => '상태',
|
||||
];
|
||||
foreach ($trackFields as $field) {
|
||||
$oldVal = (string) $item->$field;
|
||||
$newVal = (string) $this->request->getPost($field);
|
||||
$oldRaw = $item->$field;
|
||||
$newRaw = $this->request->getPost($field);
|
||||
if ($field === 'pu_end_date') {
|
||||
$oldRaw = $oldRaw ?: '';
|
||||
$newRaw = $newRaw ?: '';
|
||||
}
|
||||
if ($field === 'pu_state') {
|
||||
$oldRaw = (int) $oldRaw === 1 ? '사용' : '미사용';
|
||||
$newRaw = (int) $newRaw === 1 ? '사용' : '미사용';
|
||||
}
|
||||
$oldVal = (string) $oldRaw;
|
||||
$newVal = (string) $newRaw;
|
||||
if ($oldVal !== $newVal) {
|
||||
$this->historyModel->insert([
|
||||
'puh_pu_idx' => $id,
|
||||
'puh_field' => $field,
|
||||
'puh_field' => $fieldLabels[$field] ?? $field,
|
||||
'puh_old_value' => $oldVal,
|
||||
'puh_new_value' => $newVal,
|
||||
'puh_changed_at' => date('Y-m-d H:i:s'),
|
||||
|
||||
@@ -105,7 +105,7 @@ class ShopOrder extends BaseController
|
||||
}
|
||||
$qty = (int) $qtys[$i];
|
||||
|
||||
$price = model(BagPriceModel::class)->where('bp_lg_idx', $lgIdx)->where('bp_bag_code', $code)->where('bp_state', 1)->first();
|
||||
$price = model(BagPriceModel::class)->latestActiveByBagCode($lgIdx, (string) $code);
|
||||
$unitPrice = $price ? (float) $price->bp_consumer : 0;
|
||||
$amount = $unitPrice * $qty;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,9 +10,26 @@ class BagOrderModel extends Model
|
||||
protected $primaryKey = 'bo_idx';
|
||||
protected $returnType = 'object';
|
||||
protected $useTimestamps = false;
|
||||
|
||||
/**
|
||||
* 동일 발주 UUID에 대해 bo_version이 최대인 행만 (수정으로 생긴 이전 버전 행은 목록·이력에서 제외).
|
||||
* DB에는 버전별 행이 그대로 남고, 조회 시에만 필터한다.
|
||||
*/
|
||||
public function whereLatestHead(int $lgIdx): self
|
||||
{
|
||||
$lg = (int) $lgIdx;
|
||||
|
||||
return $this->where(
|
||||
"(bo_uuid, bo_version) IN (SELECT bo_uuid, MAX(bo_version) FROM {$this->table} WHERE bo_lg_idx = {$lg} GROUP BY bo_uuid)",
|
||||
null,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
protected $allowedFields = [
|
||||
'bo_uuid', 'bo_version', 'bo_lg_idx', 'bo_gugun_code', 'bo_dong_code',
|
||||
'bo_company_idx', 'bo_agency_idx', 'bo_fee_rate', 'bo_order_date',
|
||||
'bo_bag_types', 'bo_unit_prices', 'bo_qty_boxes',
|
||||
'bo_lot_no', 'bo_hash', 'bo_status', 'bo_orderer_idx',
|
||||
'bo_regdate', 'bo_moddate',
|
||||
];
|
||||
|
||||
@@ -16,4 +16,45 @@ class BagPriceModel extends Model
|
||||
'bp_start_date', 'bp_end_date', 'bp_state',
|
||||
'bp_regdate', 'bp_moddate', 'bp_reg_mb_idx',
|
||||
];
|
||||
|
||||
/**
|
||||
* 같은 봉투코드에 단가 기간이 겹쳐도 "나중 등록 단가"가 우선되도록
|
||||
* 활성 단가를 등록일/PK 역순으로 정렬해 봉투코드별 1건만 남긴다.
|
||||
*
|
||||
* @return array<string, object>
|
||||
*/
|
||||
public function latestActiveMapByBagCode(int $lgIdx): array
|
||||
{
|
||||
$rows = $this->where('bp_lg_idx', $lgIdx)
|
||||
->where('bp_state', 1)
|
||||
->orderBy('bp_regdate', 'DESC')
|
||||
->orderBy('bp_idx', 'DESC')
|
||||
->findAll();
|
||||
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
$code = (string) ($row->bp_bag_code ?? '');
|
||||
if ($code === '' || isset($map[$code])) {
|
||||
continue;
|
||||
}
|
||||
$map[$code] = $row;
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function latestActiveByBagCode(int $lgIdx, string $bagCode): ?object
|
||||
{
|
||||
$bagCode = trim($bagCode);
|
||||
if ($bagCode === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->where('bp_lg_idx', $lgIdx)
|
||||
->where('bp_bag_code', $bagCode)
|
||||
->where('bp_state', 1)
|
||||
->orderBy('bp_regdate', 'DESC')
|
||||
->orderBy('bp_idx', 'DESC')
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,4 +189,67 @@ class MenuModel extends Model
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 메뉴 타입(mt_idx)을 source 지자체 기준으로 모든 지자체에 재배포.
|
||||
* 기존 대상 지자체의 해당 타입 메뉴는 삭제 후 source 구조로 재생성한다.
|
||||
*/
|
||||
public function syncTypeToAllLgs(int $mtIdx, int $sourceLg): void
|
||||
{
|
||||
if ($mtIdx <= 0 || $sourceLg <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$source = $this->where('mt_idx', $mtIdx)
|
||||
->where('lg_idx', $sourceLg)
|
||||
->orderBy('mm_dep', 'ASC')
|
||||
->orderBy('mm_num', 'ASC')
|
||||
->findAll();
|
||||
if (empty($source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lgRows = $this->db->table('local_government')
|
||||
->select('lg_idx')
|
||||
->orderBy('lg_idx', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($lgRows as $lgRow) {
|
||||
$destLg = (int) ($lgRow['lg_idx'] ?? 0);
|
||||
if ($destLg <= 0 || $destLg === $sourceLg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->db->transStart();
|
||||
$this->where('mt_idx', $mtIdx)
|
||||
->where('lg_idx', $destLg)
|
||||
->delete();
|
||||
|
||||
$idMap = [];
|
||||
foreach ($source as $row) {
|
||||
$oldId = (int) ($row->mm_idx ?? 0);
|
||||
$oldP = (int) ($row->mm_pidx ?? 0);
|
||||
$newPidx = 0;
|
||||
if ($oldP > 0 && isset($idMap[$oldP])) {
|
||||
$newPidx = (int) $idMap[$oldP];
|
||||
}
|
||||
|
||||
$this->insert([
|
||||
'mt_idx' => $mtIdx,
|
||||
'lg_idx' => $destLg,
|
||||
'mm_name' => (string) ($row->mm_name ?? ''),
|
||||
'mm_link' => (string) ($row->mm_link ?? ''),
|
||||
'mm_pidx' => $newPidx,
|
||||
'mm_dep' => (int) ($row->mm_dep ?? 0),
|
||||
'mm_num' => (int) ($row->mm_num ?? 0),
|
||||
'mm_cnode' => (int) ($row->mm_cnode ?? 0),
|
||||
'mm_level' => (string) ($row->mm_level ?? ''),
|
||||
'mm_is_view' => (string) ($row->mm_is_view ?? 'Y'),
|
||||
]);
|
||||
$idMap[$oldId] = (int) $this->getInsertID();
|
||||
}
|
||||
$this->db->transComplete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,83 +1,443 @@
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<span class="text-sm font-bold text-gray-700">발주 등록</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-4xl">
|
||||
<form action="<?= mgmt_url('bag-orders/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">발주일 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="bo_order_date" type="date" value="<?= esc(old('bo_order_date', date('Y-m-d'))) ?>" required/>
|
||||
<?php
|
||||
$oldBagCodes = old('item_bag_code');
|
||||
$oldQtyBoxes = old('item_qty_box');
|
||||
$oldQtySheets = old('item_qty_sheet');
|
||||
$oldBagCodes = is_array($oldBagCodes) ? $oldBagCodes : [];
|
||||
$oldQtyBoxes = is_array($oldQtyBoxes) ? $oldQtyBoxes : [];
|
||||
$oldQtySheets = is_array($oldQtySheets) ? $oldQtySheets : [];
|
||||
$defaultOrderDate = old('bo_order_date', date('Y-m-d'));
|
||||
$defaultOrderMonth = old('bo_order_month_ui', substr($defaultOrderDate, 0, 7));
|
||||
|
||||
$bagMeta = [];
|
||||
foreach (($bagReferenceRows ?? []) as $row) {
|
||||
$bagMeta[$row['code']] = [
|
||||
'name' => $row['name'],
|
||||
'orderPrice' => (float) $row['orderPrice'],
|
||||
'boxPerPack' => (int) $row['boxPerPack'],
|
||||
'packPerSheet' => (int) $row['packPerSheet'],
|
||||
'totalPerBox' => max(1, (int) $row['totalPerBox']),
|
||||
];
|
||||
}
|
||||
|
||||
$initialSelectedItems = [];
|
||||
$maxOldCount = max(count($oldBagCodes), count($oldQtySheets), count($oldQtyBoxes));
|
||||
for ($i = 0; $i < $maxOldCount; $i++) {
|
||||
$code = trim((string) ($oldBagCodes[$i] ?? ''));
|
||||
if ($code === '' || ! isset($bagMeta[$code])) {
|
||||
continue;
|
||||
}
|
||||
$fallbackQtyBox = (int) ($oldQtyBoxes[$i] ?? 0);
|
||||
$rawQtySheet = (int) ($oldQtySheets[$i] ?? 0);
|
||||
$fallbackTotalPerBox = (int) ($bagMeta[$code]['totalPerBox'] ?? 1);
|
||||
if ($fallbackQtyBox <= 0 && $rawQtySheet > 0) {
|
||||
$fallbackQtyBox = intdiv($rawQtySheet, max(1, $fallbackTotalPerBox));
|
||||
}
|
||||
$initialSelectedItems[] = [
|
||||
'code' => $code,
|
||||
'qtyBox' => max(0, $fallbackQtyBox),
|
||||
];
|
||||
}
|
||||
|
||||
$statusMap = ['normal' => '정상', 'cancelled' => '취소', 'deleted' => '삭제'];
|
||||
?>
|
||||
|
||||
<form action="<?= mgmt_url('bag-orders/store') ?>" method="POST" class="mt-2 space-y-2">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="border border-gray-300 bg-white p-2">
|
||||
<div class="flex flex-wrap items-center gap-4 text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="bo_order_month_ui" class="font-bold text-gray-700">발주월</label>
|
||||
<input id="bo_order_month_ui" name="bo_order_month_ui" type="month" value="<?= esc($defaultOrderMonth) ?>" class="border border-gray-300 rounded px-2 py-1" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="bo_order_date" class="font-bold text-gray-700">발주일 <span class="text-red-500">*</span></label>
|
||||
<input id="bo_order_date" name="bo_order_date" type="date" value="<?= esc($defaultOrderDate) ?>" required class="border border-gray-300 rounded px-2 py-1" />
|
||||
</div>
|
||||
<p class="text-blue-600 font-bold">※ 발주수량은 박스단위로 입력해 주세요. (발주일은 미래일도 선택 가능)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">수수료율</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32 text-right" name="bo_fee_rate" type="number" step="0.01" value="<?= esc(old('bo_fee_rate', '0')) ?>"/>
|
||||
<span class="text-sm text-gray-500">%</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">제작업체</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="bo_company_idx">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($companies as $cp): ?>
|
||||
<option value="<?= esc($cp->cp_idx) ?>" <?= (int) old('bo_company_idx') === (int) $cp->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc($cp->cp_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">입고처</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="bo_agency_idx">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($agencies as $ag): ?>
|
||||
<option value="<?= esc($ag->sa_idx) ?>" <?= (int) old('bo_agency_idx') === (int) $ag->sa_idx ? 'selected' : '' ?>>
|
||||
[<?= esc($ag->sa_kind ?? '') ?>] <?= esc($ag->sa_code ?? '') ?> — <?= esc($ag->sa_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label class="block text-sm font-bold text-gray-700 mb-2">발주 품목</label>
|
||||
<div class="border border-gray-300 overflow-auto">
|
||||
<table class="w-full data-table">
|
||||
<div class="grid grid-cols-1 xl:grid-cols-12 gap-2">
|
||||
<section class="xl:col-span-5 border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 이력</div>
|
||||
<div class="overflow-auto max-h-[410px]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-16">순번</th>
|
||||
<th>봉투</th>
|
||||
<th class="w-32">박스수</th>
|
||||
<th class="w-28">발주일</th>
|
||||
<th>제작업체</th>
|
||||
<th>입고처</th>
|
||||
<th class="w-16">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php for ($i = 0; $i < 3; $i++): ?>
|
||||
<?php foreach (($recentOrders ?? []) as $history): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= $i + 1 ?></td>
|
||||
<td>
|
||||
<select class="border border-gray-300 rounded px-2 py-1 text-sm w-full" name="item_bag_code[]">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($bagCodes as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>">
|
||||
<?= esc($cd->cd_code) ?> — <?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input class="border border-gray-300 rounded px-2 py-1 text-sm w-full text-right" name="item_qty_box[]" type="number" min="0" value="0"/>
|
||||
</td>
|
||||
<td class="text-center"><?= esc((string) $history->bo_order_date) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($companyMap[(int) $history->bo_company_idx] ?? '-')) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($agencyMap[(int) $history->bo_agency_idx] ?? '-')) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($statusMap[(string) $history->bo_status] ?? $history->bo_status)) ?></td>
|
||||
</tr>
|
||||
<?php endfor; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($recentOrders)): ?>
|
||||
<tr><td colspan="4" class="text-center text-gray-400 py-4">발주 이력이 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= mgmt_url('bag-orders') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<section class="xl:col-span-7 border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 Form</div>
|
||||
<div class="p-2 space-y-2">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_fee_rate" class="w-20 font-bold text-gray-700">수수료</label>
|
||||
<input id="bo_fee_rate" name="bo_fee_rate" type="number" step="0.01" value="<?= esc(old('bo_fee_rate', '0')) ?>" class="border border-gray-300 rounded px-2 py-1 w-24 text-right" />
|
||||
<span>%</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_association_idx" class="w-20 font-bold text-gray-700">협회</label>
|
||||
<select id="bo_association_idx" name="bo_association_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
|
||||
<option value="">선택</option>
|
||||
<?php foreach (($associations ?? []) as $association): ?>
|
||||
<option value="<?= esc((string) $association->cp_idx) ?>" <?= (int) old('bo_association_idx') === (int) $association->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc((string) $association->cp_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_company_idx" class="w-20 font-bold text-gray-700">제작업체</label>
|
||||
<select id="bo_company_idx" name="bo_company_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
|
||||
<option value="">선택</option>
|
||||
<?php foreach (($companies ?? []) as $company): ?>
|
||||
<option value="<?= esc((string) $company->cp_idx) ?>" <?= (int) old('bo_company_idx') === (int) $company->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc((string) $company->cp_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_agency_idx" class="w-20 font-bold text-gray-700">입고처</label>
|
||||
<select id="bo_agency_idx" name="bo_agency_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
|
||||
<option value="">선택</option>
|
||||
<?php foreach (($agencies ?? []) as $agency): ?>
|
||||
<option value="<?= esc((string) $agency->sa_idx) ?>" <?= (int) old('bo_agency_idx') === (int) $agency->sa_idx ? 'selected' : '' ?>>
|
||||
[<?= esc((string) ($agency->sa_kind ?? '')) ?>] <?= esc((string) ($agency->sa_code ?? '')) ?> — <?= esc((string) $agency->sa_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-gray-300 overflow-auto">
|
||||
<table class="w-full data-table text-sm order-input-table" id="order-item-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-12">번호</th>
|
||||
<th class="w-16">선택</th>
|
||||
<th>품명</th>
|
||||
<th class="w-28">수량(BOX)</th>
|
||||
<th class="w-24">단가</th>
|
||||
<th class="w-24">환산수량</th>
|
||||
<th class="w-28">금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="selected-order-items-body"></tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="3" class="text-center">계</th>
|
||||
<th class="text-right pr-2" id="sum-box-qty">0</th>
|
||||
<th></th>
|
||||
<th class="text-right pr-2" id="sum-sheet-qty">0</th>
|
||||
<th class="text-right pr-2" id="sum-amount">0</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 pt-1">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">발주</button>
|
||||
<a href="<?= mgmt_url('bag-orders') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 등록 종류</div>
|
||||
<p class="text-xs text-gray-600 px-2 py-1">아래 목록에서 봉투를 선택하면 발주 품목에 추가됩니다. (개수 제한 없음)</p>
|
||||
<div class="overflow-auto">
|
||||
<table class="w-full data-table text-sm order-reference-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-12">번호</th>
|
||||
<th class="w-20">선택</th>
|
||||
<th>봉투 종류</th>
|
||||
<th class="w-24">발주단가</th>
|
||||
<th class="w-24">Box당 팩</th>
|
||||
<th class="w-24">팩당 낱장</th>
|
||||
<th class="w-28">1박스 총 낱장</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($bagReferenceRows ?? []) as $idx => $row): ?>
|
||||
<tr data-reference-row data-code="<?= esc((string) $row['code']) ?>" class="cursor-pointer">
|
||||
<td class="text-center"><?= $idx + 1 ?></td>
|
||||
<td class="text-center">
|
||||
<button type="button" class="js-toggle-bag border border-gray-300 rounded px-2 py-0.5 text-xs hover:bg-gray-100" data-code="<?= esc((string) $row['code']) ?>">선택</button>
|
||||
</td>
|
||||
<td class="text-left pl-2"><?= esc((string) $row['name']) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((float) $row['orderPrice'], 2) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) $row['boxPerPack']) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) $row['packPerSheet']) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) $row['totalPerBox']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($bagReferenceRows)): ?>
|
||||
<tr><td colspan="7" class="text-center text-gray-400 py-4">표시할 봉투 기준 데이터가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
.order-input-table tbody tr,
|
||||
.order-reference-table tbody tr {
|
||||
height: 34px;
|
||||
}
|
||||
.order-input-table tbody td,
|
||||
.order-reference-table tbody td {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const bagMeta = <?= json_encode($bagMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
const initialSelectedItems = <?= json_encode($initialSelectedItems, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
const selectedBody = document.getElementById('selected-order-items-body');
|
||||
const referenceRows = Array.from(document.querySelectorAll('[data-reference-row]'));
|
||||
const sumBoxQtyEl = document.getElementById('sum-box-qty');
|
||||
const sumSheetQtyEl = document.getElementById('sum-sheet-qty');
|
||||
const sumAmountEl = document.getElementById('sum-amount');
|
||||
const monthInput = document.getElementById('bo_order_month_ui');
|
||||
const orderDateInput = document.getElementById('bo_order_date');
|
||||
const orderForm = document.querySelector('form[action*="bag-orders/store"]');
|
||||
const selectedItems = new Map();
|
||||
let activeCode = null;
|
||||
|
||||
const formatNumber = (value) => new Intl.NumberFormat('ko-KR').format(Number.isFinite(value) ? value : 0);
|
||||
const escapeHtml = (value) => String(value ?? '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
|
||||
const syncMonthFromDate = () => {
|
||||
if (!orderDateInput || !monthInput || !orderDateInput.value) return;
|
||||
monthInput.value = orderDateInput.value.substring(0, 7);
|
||||
};
|
||||
|
||||
const syncDateFromMonth = () => {
|
||||
if (!orderDateInput || !monthInput || !monthInput.value) return;
|
||||
const parts = monthInput.value.split('-');
|
||||
if (parts.length !== 2) return;
|
||||
const year = Number(parts[0]);
|
||||
const month = Number(parts[1]);
|
||||
if (!Number.isFinite(year) || !Number.isFinite(month)) return;
|
||||
|
||||
const currentDay = orderDateInput.value ? Number(orderDateInput.value.split('-')[2]) : 1;
|
||||
const lastDay = new Date(year, month, 0).getDate();
|
||||
const day = String(Math.min(Math.max(currentDay, 1), lastDay)).padStart(2, '0');
|
||||
orderDateInput.value = `${String(year)}-${String(month).padStart(2, '0')}-${day}`;
|
||||
};
|
||||
|
||||
const updateReferenceSelectionUi = () => {
|
||||
referenceRows.forEach((row) => {
|
||||
const code = row.dataset.code || '';
|
||||
const button = row.querySelector('.js-toggle-bag');
|
||||
const isSelected = selectedItems.has(code);
|
||||
row.classList.toggle('bg-blue-50', isSelected);
|
||||
if (button) {
|
||||
button.textContent = isSelected ? '선택됨' : '선택';
|
||||
button.classList.toggle('bg-blue-600', isSelected);
|
||||
button.classList.toggle('text-white', isSelected);
|
||||
button.classList.toggle('border-blue-600', isSelected);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const updateTotals = () => {
|
||||
let sumBoxQty = 0;
|
||||
let sumSheetQty = 0;
|
||||
let sumAmount = 0;
|
||||
|
||||
selectedBody.querySelectorAll('tr[data-item-row]').forEach((row) => {
|
||||
const code = row.dataset.code || '';
|
||||
const qtyInput = row.querySelector('.item-qty-box');
|
||||
const qtyBox = Math.max(0, parseInt(qtyInput?.value || '0', 10));
|
||||
const meta = bagMeta[code] || { orderPrice: 0, totalPerBox: 1 };
|
||||
const unitPrice = Number(meta.orderPrice || 0);
|
||||
const totalPerBox = Math.max(1, Number(meta.totalPerBox || 1));
|
||||
const qtySheet = qtyBox * totalPerBox;
|
||||
const amount = qtySheet * unitPrice;
|
||||
|
||||
const unitPriceEl = row.querySelector('.item-unit-price');
|
||||
const qtySheetEl = row.querySelector('.item-qty-sheet');
|
||||
const sheetHelpEl = row.querySelector('.item-sheet-help');
|
||||
const amountEl = row.querySelector('.item-amount');
|
||||
if (unitPriceEl) unitPriceEl.textContent = formatNumber(unitPrice);
|
||||
if (qtySheetEl) qtySheetEl.textContent = formatNumber(qtySheet);
|
||||
if (sheetHelpEl) sheetHelpEl.textContent = `낱장 ${formatNumber(qtySheet)}장`;
|
||||
if (amountEl) amountEl.textContent = formatNumber(amount);
|
||||
|
||||
selectedItems.set(code, { qtyBox });
|
||||
sumBoxQty += qtyBox;
|
||||
sumSheetQty += qtySheet;
|
||||
sumAmount += amount;
|
||||
});
|
||||
|
||||
if (sumBoxQtyEl) sumBoxQtyEl.textContent = formatNumber(sumBoxQty);
|
||||
if (sumSheetQtyEl) sumSheetQtyEl.textContent = formatNumber(sumSheetQty);
|
||||
if (sumAmountEl) sumAmountEl.textContent = formatNumber(sumAmount);
|
||||
};
|
||||
|
||||
const setActiveRow = (code) => {
|
||||
activeCode = code || null;
|
||||
selectedBody.querySelectorAll('tr[data-item-row]').forEach((row) => {
|
||||
row.classList.toggle('bg-amber-50', row.dataset.code === activeCode);
|
||||
});
|
||||
};
|
||||
|
||||
const renderSelectedRows = () => {
|
||||
const codes = Object.keys(bagMeta).filter((code) => selectedItems.has(code));
|
||||
if (codes.length === 0) {
|
||||
selectedBody.innerHTML = '<tr><td colspan="7" class="text-center text-gray-400 py-4">아래 "발주 등록 종류"에서 봉투를 선택해 주세요.</td></tr>';
|
||||
setActiveRow(null);
|
||||
updateTotals();
|
||||
updateReferenceSelectionUi();
|
||||
return;
|
||||
}
|
||||
|
||||
selectedBody.innerHTML = codes.map((code, idx) => {
|
||||
const meta = bagMeta[code];
|
||||
const qtyBox = Math.max(0, parseInt(String(selectedItems.get(code)?.qtyBox ?? 0), 10));
|
||||
const name = meta?.name || code;
|
||||
|
||||
return `
|
||||
<tr data-item-row data-code="${escapeHtml(code)}" class="cursor-pointer">
|
||||
<td class="text-center">${idx + 1}</td>
|
||||
<td class="text-center">
|
||||
<button type="button" class="js-remove-selected text-xs text-red-600 hover:underline" data-code="${escapeHtml(code)}">해제</button>
|
||||
</td>
|
||||
<td class="text-left pl-2">
|
||||
${escapeHtml(name)}
|
||||
<input type="hidden" name="item_bag_code[]" value="${escapeHtml(code)}" />
|
||||
</td>
|
||||
<td>
|
||||
<input name="item_qty_box[]" type="number" min="0" step="1" value="${qtyBox}" class="item-qty-box border border-gray-300 rounded px-2 py-1 text-sm w-full text-right leading-tight" />
|
||||
<p class="text-[11px] text-gray-500 mt-1 item-sheet-help">낱장 0장</p>
|
||||
</td>
|
||||
<td class="text-right pr-2 item-unit-price">0</td>
|
||||
<td class="text-right pr-2 item-qty-sheet">0</td>
|
||||
<td class="text-right pr-2 item-amount">0</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
if (!activeCode || !selectedItems.has(activeCode)) {
|
||||
activeCode = codes[0];
|
||||
}
|
||||
setActiveRow(activeCode);
|
||||
updateTotals();
|
||||
updateReferenceSelectionUi();
|
||||
};
|
||||
|
||||
const toggleSelection = (code) => {
|
||||
if (!code || !bagMeta[code]) return;
|
||||
if (selectedItems.has(code)) {
|
||||
selectedItems.delete(code);
|
||||
if (activeCode === code) activeCode = null;
|
||||
} else {
|
||||
selectedItems.set(code, { qtyBox: 0 });
|
||||
activeCode = code;
|
||||
}
|
||||
renderSelectedRows();
|
||||
};
|
||||
|
||||
initialSelectedItems.forEach((item) => {
|
||||
if (!item || !item.code || !bagMeta[item.code]) return;
|
||||
selectedItems.set(item.code, { qtyBox: Math.max(0, parseInt(String(item.qtyBox ?? 0), 10)) });
|
||||
activeCode = item.code;
|
||||
});
|
||||
|
||||
selectedBody.addEventListener('click', (event) => {
|
||||
const removeButton = event.target.closest('.js-remove-selected');
|
||||
if (removeButton) {
|
||||
toggleSelection(removeButton.dataset.code || '');
|
||||
return;
|
||||
}
|
||||
|
||||
const row = event.target.closest('tr[data-item-row]');
|
||||
if (!row) return;
|
||||
const code = row.dataset.code || '';
|
||||
setActiveRow(code);
|
||||
const qtyInput = row.querySelector('.item-qty-box');
|
||||
if (qtyInput) qtyInput.focus();
|
||||
});
|
||||
|
||||
selectedBody.addEventListener('input', (event) => {
|
||||
const qtyInput = event.target.closest('.item-qty-box');
|
||||
if (!qtyInput) return;
|
||||
const row = qtyInput.closest('tr[data-item-row]');
|
||||
if (!row) return;
|
||||
const code = row.dataset.code || '';
|
||||
selectedItems.set(code, { qtyBox: Math.max(0, parseInt(qtyInput.value || '0', 10)) });
|
||||
updateTotals();
|
||||
});
|
||||
|
||||
referenceRows.forEach((row) => {
|
||||
row.addEventListener('click', (event) => {
|
||||
const button = event.target.closest('.js-toggle-bag');
|
||||
if (button) {
|
||||
toggleSelection(button.dataset.code || '');
|
||||
return;
|
||||
}
|
||||
if (event.target.closest('td')) {
|
||||
toggleSelection(row.dataset.code || '');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (monthInput) monthInput.addEventListener('change', () => { syncDateFromMonth(); updateTotals(); });
|
||||
if (orderDateInput) orderDateInput.addEventListener('change', syncMonthFromDate);
|
||||
|
||||
if (orderForm) {
|
||||
orderForm.addEventListener('submit', (event) => {
|
||||
const hasValidItem = Array.from(selectedBody.querySelectorAll('tr[data-item-row]')).some((row) => {
|
||||
const qtyInput = row.querySelector('.item-qty-box');
|
||||
return Math.max(0, parseInt(qtyInput?.value || '0', 10)) > 0;
|
||||
});
|
||||
|
||||
if (!hasValidItem) {
|
||||
event.preventDefault();
|
||||
alert('봉투를 선택하고 수량을 1 이상 입력해 주세요.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
syncMonthFromDate();
|
||||
renderSelectedRows();
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -1,81 +1,200 @@
|
||||
<?= view('components/print_header', ['printTitle' => '발주 현황']) ?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<?php
|
||||
// 발주기간: native month 입력은 로케일에 따라 Jan 등 영문 표기될 수 있어 YYYY-MM select + 한글 라벨 사용
|
||||
$bagOrderYmChoices = [];
|
||||
$bagOrderYmCenterY = (int) date('Y');
|
||||
for ($by = $bagOrderYmCenterY - 4; $by <= $bagOrderYmCenterY + 2; $by++) {
|
||||
for ($bm = 1; $bm <= 12; $bm++) {
|
||||
$bagOrderYmChoices[] = sprintf('%04d-%02d', $by, $bm);
|
||||
}
|
||||
}
|
||||
foreach ([(string) ($startMonth ?? ''), (string) ($endMonth ?? '')] as $ymExtra) {
|
||||
if (preg_match('/^\d{4}-\d{2}$/', $ymExtra) && ! in_array($ymExtra, $bagOrderYmChoices, true)) {
|
||||
$bagOrderYmChoices[] = $ymExtra;
|
||||
}
|
||||
}
|
||||
sort($bagOrderYmChoices);
|
||||
$bagOrderYmLabel = static function (string $ym): string {
|
||||
if (preg_match('/^(\d{4})-(\d{2})$/', $ym, $m)) {
|
||||
return $m[1] . '년 ' . (int) $m[2] . '월';
|
||||
}
|
||||
|
||||
return $ym;
|
||||
};
|
||||
?>
|
||||
<?= view('components/print_header', ['printTitle' => '봉투 발주 현황', 'printShowApproval' => false]) ?>
|
||||
<section class="no-print border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700">발주 현황</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= mgmt_url('bag-orders/export') . '?' . http_build_query(array_filter(['start_date' => $startDate ?? '', 'end_date' => $endDate ?? '', 'status' => $status ?? ''])) ?>" class="no-print border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
||||
<a href="<?= mgmt_url('bag-orders/export') . '?' . http_build_query(array_filter(['start_month' => $startMonth ?? '', 'end_month' => $endMonth ?? '', 'company_idx' => $companyIdx ?? 0, 'bag_code' => $bagCode ?? '', 'receive_type' => $receiveType ?? ''])) ?>" class="no-print border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('bag-orders/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">발주 등록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('bag-orders') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">발주일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">상태</label>
|
||||
<select name="status" class="border border-gray-300 rounded px-2 py-1 text-sm">
|
||||
<option value="">전체</option>
|
||||
<option value="normal" <?= ($status ?? '') === 'normal' ? 'selected' : '' ?>>정상</option>
|
||||
<option value="cancelled" <?= ($status ?? '') === 'cancelled' ? 'selected' : '' ?>>취소</option>
|
||||
<option value="deleted" <?= ($status ?? '') === 'deleted' ? 'selected' : '' ?>>삭제</option>
|
||||
</select>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('bag-orders') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
|
||||
<section class="no-print p-2 bg-white border-b border-gray-200">
|
||||
<!-- GBMS 발주현황: 발주기간은 [시작] ~ [끝] 한 줄 고정, 필터 블록은 가로 나열 후 좁으면 블록 단위로만 줄바꿈 -->
|
||||
<form method="GET" action="<?= mgmt_url('bag-orders') ?>" class="flex flex-wrap items-end gap-x-5 gap-y-3 w-full">
|
||||
<div class="flex flex-nowrap items-center gap-2 shrink-0">
|
||||
<label class="text-sm text-gray-600 whitespace-nowrap">발주 기간</label>
|
||||
<select name="start_month" class="border border-gray-300 rounded px-2 py-1 text-sm w-[11.5rem] max-w-[13rem] shrink-0">
|
||||
<?php foreach ($bagOrderYmChoices as $ym): ?>
|
||||
<option value="<?= esc($ym) ?>" <?= ($startMonth ?? date('Y-m')) === $ym ? 'selected' : '' ?>><?= esc($bagOrderYmLabel($ym)) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<span class="text-sm text-gray-500 select-none">~</span>
|
||||
<select name="end_month" class="border border-gray-300 rounded px-2 py-1 text-sm w-[11.5rem] max-w-[13rem] shrink-0">
|
||||
<?php foreach ($bagOrderYmChoices as $ym): ?>
|
||||
<option value="<?= esc($ym) ?>" <?= ($endMonth ?? date('Y-m')) === $ym ? 'selected' : '' ?>><?= esc($bagOrderYmLabel($ym)) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-nowrap items-center gap-2 shrink-0">
|
||||
<label class="text-sm text-gray-600 whitespace-nowrap">제작 업체</label>
|
||||
<select name="company_idx" class="border border-gray-300 rounded px-2 py-1 text-sm w-[11rem] max-w-[14rem]">
|
||||
<option value="0">전 체</option>
|
||||
<?php foreach (($companyOptions ?? []) as $company): ?>
|
||||
<option value="<?= (int) $company->cp_idx ?>" <?= (int) ($companyIdx ?? 0) === (int) $company->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc((string) $company->cp_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-nowrap items-center gap-2 shrink-0">
|
||||
<label class="text-sm text-gray-600 whitespace-nowrap">품 명</label>
|
||||
<select name="bag_code" class="border border-gray-300 rounded px-2 py-1 text-sm w-[11rem] max-w-[16rem]">
|
||||
<option value="">전 체</option>
|
||||
<?php foreach (($bagCodeOptions ?? []) as $bag): ?>
|
||||
<option value="<?= esc((string) $bag->cd_code) ?>" <?= (string) ($bagCode ?? '') === (string) $bag->cd_code ? 'selected' : '' ?>>
|
||||
<?= esc((string) $bag->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-nowrap items-center gap-2 shrink-0">
|
||||
<label class="text-sm text-gray-600 whitespace-nowrap">입고 구분</label>
|
||||
<select name="receive_type" class="border border-gray-300 rounded px-2 py-1 text-sm w-[8.5rem]">
|
||||
<option value="all" <?= ($receiveType ?? 'all') === 'all' ? 'selected' : '' ?>>전 체</option>
|
||||
<option value="received" <?= ($receiveType ?? 'all') === 'received' ? 'selected' : '' ?>>입고완료</option>
|
||||
<option value="pending" <?= ($receiveType ?? 'all') === 'pending' ? 'selected' : '' ?>>미입고</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-nowrap items-center gap-2 shrink-0 sm:ml-auto">
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('bag-orders') ?>" class="text-sm text-gray-500 hover:underline whitespace-nowrap">초기화</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<table class="w-full data-table">
|
||||
|
||||
<div class="bag-order-print-wrap border border-gray-300 overflow-auto mt-2">
|
||||
<table class="bag-order-print-table w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-16">번호</th>
|
||||
<th>LOT번호</th>
|
||||
<th>발주일</th>
|
||||
<th>제작업체</th>
|
||||
<th>입고처</th>
|
||||
<th>품목수</th>
|
||||
<th>총수량</th>
|
||||
<th>총금액</th>
|
||||
<th class="w-20">상태</th>
|
||||
<th class="w-44">작업</th>
|
||||
<th class="w-32">발주일자</th>
|
||||
<th class="min-w-[10rem]">제작 업체</th>
|
||||
<th class="min-w-[12rem]">품 명</th>
|
||||
<th class="w-28">발주 수량</th>
|
||||
<th class="w-28">입고 수량</th>
|
||||
<th class="w-28">미입고수량</th>
|
||||
<th class="w-32">발주 금액</th>
|
||||
<th class="min-w-[9rem]">입고처</th>
|
||||
<th class="min-w-[8rem]">비 고</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach ($list as $row): ?>
|
||||
<?php $printedGroup = []; ?>
|
||||
<?php foreach (($rows ?? []) as $row): ?>
|
||||
<?php if (! empty($row['is_subtotal'])): ?>
|
||||
<tr class="bg-gray-50 font-semibold">
|
||||
<td colspan="3" class="text-center"><?= esc((string) ($row['label'] ?? '소계')) ?></td>
|
||||
<td><?= number_format((int) ($row['order_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($row['received_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($row['pending_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((float) ($row['amount'] ?? 0)) ?></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<?php continue; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$boIdx = (int) ($row['bo_idx'] ?? 0);
|
||||
$showGroup = ! isset($printedGroup[$boIdx]);
|
||||
$rowspan = (int) (($groupRows[$boIdx] ?? 1));
|
||||
if ($showGroup) {
|
||||
$printedGroup[$boIdx] = true;
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->bo_idx) ?></td>
|
||||
<td class="text-center font-mono"><?= esc($row->bo_lot_no) ?></td>
|
||||
<td class="text-center"><?= esc($row->bo_order_date) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($companyMap[$row->bo_company_idx] ?? '') ?></td>
|
||||
<td class="text-left pl-2"><?= esc($agencyMap[$row->bo_agency_idx] ?? '') ?></td>
|
||||
<td><?= number_format((int) ($itemSummary[$row->bo_idx]['count'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($itemSummary[$row->bo_idx]['qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($itemSummary[$row->bo_idx]['amount'] ?? 0)) ?></td>
|
||||
<td class="text-center">
|
||||
<?php
|
||||
$statusMap = ['normal' => '정상', 'cancelled' => '취소', 'deleted' => '삭제'];
|
||||
echo esc($statusMap[$row->bo_status] ?? $row->bo_status);
|
||||
?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="<?= mgmt_url('bag-orders/detail/' . (int) $row->bo_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">상세</a>
|
||||
<form action="<?= mgmt_url('bag-orders/cancel/' . (int) $row->bo_idx) ?>" method="POST" class="inline" onsubmit="return confirm('취소하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-orange-600 hover:underline text-sm mr-1">취소</button>
|
||||
</form>
|
||||
<form action="<?= mgmt_url('bag-orders/delete/' . (int) $row->bo_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
|
||||
</form>
|
||||
</td>
|
||||
<?php if ($showGroup): ?>
|
||||
<td class="text-center align-top" rowspan="<?= $rowspan ?>"><?= esc((string) ($row['order_date'] ?? '')) ?></td>
|
||||
<td class="text-left pl-2 align-top" rowspan="<?= $rowspan ?>"><?= esc((string) ($row['company_name'] ?? '')) ?></td>
|
||||
<?php endif; ?>
|
||||
<td class="text-left pl-2"><?= esc((string) ($row['bag_name'] ?? '')) ?></td>
|
||||
<td><?= number_format((int) ($row['order_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($row['received_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($row['pending_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((float) ($row['amount'] ?? 0)) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($row['agency_name'] ?? '')) ?></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($list)): ?>
|
||||
<tr><td colspan="10" class="text-center text-gray-400 py-4">등록된 발주가 없습니다.</td></tr>
|
||||
|
||||
<?php if (empty($rows ?? [])): ?>
|
||||
<tr><td colspan="9" class="text-center text-gray-400 py-6">조회 조건에 해당하는 발주 내역이 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="bg-gray-100 font-bold">
|
||||
<td colspan="3" class="text-center">총계</td>
|
||||
<td class="text-right"><?= number_format((int) ($grandTotals['order_qty'] ?? 0)) ?></td>
|
||||
<td class="text-right"><?= number_format((int) ($grandTotals['received_qty'] ?? 0)) ?></td>
|
||||
<td class="text-right"><?= number_format((int) ($grandTotals['pending_qty'] ?? 0)) ?></td>
|
||||
<td class="text-right"><?= number_format((float) ($grandTotals['amount'] ?? 0)) ?></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<?php if (isset($pager)): ?><div class="mt-3"><?= $pager->links() ?></div><?php endif; ?>
|
||||
|
||||
<style>
|
||||
@media print {
|
||||
#debug-icon,
|
||||
#debug-bar,
|
||||
#debug-bar-contents,
|
||||
#debug-toolbar,
|
||||
.debug-toolbar,
|
||||
.ci-debug-toolbar,
|
||||
[id^='debug-bar-'],
|
||||
[id^='debug-icon'],
|
||||
[class*='debug-toolbar'] {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.bag-order-print-wrap {
|
||||
overflow: visible !important;
|
||||
border: none !important;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.bag-order-print-table {
|
||||
width: 100% !important;
|
||||
table-layout: auto !important;
|
||||
}
|
||||
|
||||
.bag-order-print-table th,
|
||||
.bag-order-print-table td {
|
||||
white-space: nowrap !important;
|
||||
word-break: keep-all !important;
|
||||
overflow-wrap: normal !important;
|
||||
font-size: 10px !important;
|
||||
padding: 2px 3px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?= view('components/print_header', ['printTitle' => '봉투 단가 관리']) ?>
|
||||
<?= view('components/print_header', ['printTitle' => '봉투 단가 관리', 'printShowApproval' => false]) ?>
|
||||
<style>
|
||||
@media print {
|
||||
.no-print { display: none !important; }
|
||||
@@ -9,12 +9,91 @@
|
||||
<span class="text-sm font-bold text-gray-700">봉투 단가 관리</span>
|
||||
<div class="flex items-center gap-2 no-print">
|
||||
<button onclick="window.print()" class="border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= base_url('bag/prices') ?>" class="text-blue-600 hover:underline text-sm">단가 조회·검색</a>
|
||||
<a href="<?= mgmt_url('bag-prices/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">단가 등록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<p class="text-xs text-gray-500 mt-2 no-print">목록·등록·수정·삭제는 이 화면에서, <strong>기간·봉투별 조회·인쇄</strong>는 <a href="<?= base_url('bag/prices') ?>" class="text-blue-600 hover:underline">봉투 단가(조회)</a>에서 이용하세요.</p>
|
||||
<section class="no-print border border-gray-200 rounded-lg bg-white p-3 mt-2">
|
||||
<form method="get" action="<?= mgmt_url('bag-prices') ?>" class="flex flex-wrap items-end gap-3" autocomplete="off">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">봉투구분</label>
|
||||
<select name="bag_kind_e" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[9rem]">
|
||||
<option value="">전체</option>
|
||||
<?php foreach ($bag_kind_options ?? [] as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= (string) ($cd->cd_code ?? '') === (string) ($bag_kind_e ?? '') ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">봉투코드</label>
|
||||
<select name="bag_code" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[11rem]">
|
||||
<option value="">전체</option>
|
||||
<?php foreach ($bag_codes ?? [] as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= (string) ($cd->cd_code ?? '') === (string) ($bag_code ?? '') ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_code) ?> — <?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">조회 기간 (적용기간 겹침)</label>
|
||||
<?php
|
||||
$sp = $startParts ?? ['y' => '', 'm' => '', 'd' => ''];
|
||||
$ep = $endParts ?? ['y' => '', 'm' => '', 'd' => ''];
|
||||
$ymin = (int) ($dateYearMin ?? ((int) date('Y') - 12));
|
||||
$ymax = (int) ($dateYearMax ?? ((int) date('Y') + 2));
|
||||
?>
|
||||
<div class="flex flex-wrap items-center gap-1">
|
||||
<span class="text-xs text-gray-500 mr-0.5">시작</span>
|
||||
<select name="start_y" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[4.5rem]">
|
||||
<option value="">연도</option>
|
||||
<?php for ($yy = $ymin; $yy <= $ymax; $yy++): ?>
|
||||
<option value="<?= $yy ?>" <?= (string) ($sp['y'] ?? '') === (string) $yy ? 'selected' : '' ?>><?= $yy ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<select name="start_m" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[3.75rem]">
|
||||
<option value="">월</option>
|
||||
<?php for ($mi = 1; $mi <= 12; $mi++): ?>
|
||||
<option value="<?= $mi ?>" <?= isset($sp['m']) && (int) $sp['m'] === $mi ? 'selected' : '' ?>><?= $mi ?>월</option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<select name="start_d" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[3.75rem]">
|
||||
<option value="">일</option>
|
||||
<?php for ($di = 1; $di <= 31; $di++): ?>
|
||||
<option value="<?= $di ?>" <?= isset($sp['d']) && (int) $sp['d'] === $di ? 'selected' : '' ?>><?= $di ?>일</option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<span class="text-sm text-gray-500 mx-0.5">~</span>
|
||||
<span class="text-xs text-gray-500 mr-0.5">종료</span>
|
||||
<select name="end_y" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[4.5rem]">
|
||||
<option value="">연도</option>
|
||||
<?php for ($yy = $ymin; $yy <= $ymax; $yy++): ?>
|
||||
<option value="<?= $yy ?>" <?= (string) ($ep['y'] ?? '') === (string) $yy ? 'selected' : '' ?>><?= $yy ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<select name="end_m" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[3.75rem]">
|
||||
<option value="">월</option>
|
||||
<?php for ($mi = 1; $mi <= 12; $mi++): ?>
|
||||
<option value="<?= $mi ?>" <?= isset($ep['m']) && (int) $ep['m'] === $mi ? 'selected' : '' ?>><?= $mi ?>월</option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<select name="end_d" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[3.75rem]">
|
||||
<option value="">일</option>
|
||||
<?php for ($di = 1; $di <= 31; $di++): ?>
|
||||
<option value="<?= $di ?>" <?= isset($ep['d']) && (int) $ep['d'] === $di ? 'selected' : '' ?>><?= $di ?>일</option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 pb-0.5">
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('bag-prices') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
<button type="button" onclick="window.print()" class="border border-gray-300 text-gray-700 px-3 py-1.5 rounded-sm text-sm hover:bg-gray-50">인쇄</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
@@ -32,9 +111,17 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach ($list as $row): ?>
|
||||
<?php
|
||||
$startNo = 1;
|
||||
if (isset($pager) && method_exists($pager, 'getCurrentPage') && method_exists($pager, 'getPerPage')) {
|
||||
$currentPage = (int) $pager->getCurrentPage();
|
||||
$perPage = (int) $pager->getPerPage();
|
||||
$startNo = (($currentPage > 0 ? $currentPage : 1) - 1) * ($perPage > 0 ? $perPage : 20) + 1;
|
||||
}
|
||||
?>
|
||||
<?php foreach (($list ?? []) as $idx => $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->bp_idx) ?></td>
|
||||
<td class="text-center"><?= (int) $startNo + (int) $idx ?></td>
|
||||
<td class="text-center font-mono"><?= esc($row->bp_bag_code) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->bp_bag_name) ?></td>
|
||||
<td><?= number_format((float) $row->bp_order_price) ?></td>
|
||||
|
||||
@@ -8,6 +8,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200 no-print">
|
||||
<form method="GET" action="<?= mgmt_url('companies') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">업체유형</label>
|
||||
<select name="cp_type" class="border border-gray-300 rounded px-2 py-1 text-sm w-44">
|
||||
<option value="">전 체</option>
|
||||
<?php foreach (($typeOptions ?? []) as $type): ?>
|
||||
<option value="<?= esc($type) ?>" <?= (string) ($cpType ?? '') === (string) $type ? 'selected' : '' ?>><?= esc($type) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('companies') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
@@ -24,9 +37,17 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($list as $row): ?>
|
||||
<?php
|
||||
$startNo = 1;
|
||||
if (isset($pager) && method_exists($pager, 'getCurrentPage') && method_exists($pager, 'getPerPage')) {
|
||||
$currentPage = (int) $pager->getCurrentPage();
|
||||
$perPage = (int) $pager->getPerPage();
|
||||
$startNo = (($currentPage > 0 ? $currentPage : 1) - 1) * ($perPage > 0 ? $perPage : 20) + 1;
|
||||
}
|
||||
?>
|
||||
<?php foreach (($list ?? []) as $idx => $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->cp_idx) ?></td>
|
||||
<td class="text-center"><?= (int) $startNo + (int) $idx ?></td>
|
||||
<td class="text-center"><?= esc($row->cp_type) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->cp_name) ?></td>
|
||||
<td class="text-center"><?= esc($row->cp_biz_no) ?></td>
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
<?php $readOnly = ! empty($readOnly); ?>
|
||||
<?php
|
||||
helper('admin');
|
||||
$currentPath = current_nav_request_path();
|
||||
if ($currentPath === 'bag/designated-shops') {
|
||||
$readOnly = false;
|
||||
} elseif ($currentPath === 'bag/designated-shops/browse') {
|
||||
$readOnly = true;
|
||||
} else {
|
||||
$readOnly = ! empty($readOnly);
|
||||
}
|
||||
?>
|
||||
<?= view('components/print_header', ['printTitle' => $readOnly ? '지정판매소 조회 목록' : '지정판매소 목록']) ?>
|
||||
<style>
|
||||
/* 목록 위 → 지정판매소 정보 아래 (가로 2열 없음) */
|
||||
@@ -181,11 +191,12 @@ $listBasePath = $readOnly ? 'designated-shops/browse' : 'designated-shops';
|
||||
<span class="text-sm font-semibold text-gray-700 mr-1">지정판매소 검색</span>
|
||||
<label class="text-sm text-gray-600">상호명</label>
|
||||
<input type="text" name="ds_name" value="<?= esc($dsName ?? '') ?>" placeholder="상호명" class="border border-gray-300 rounded px-2 py-1 text-sm w-36"/>
|
||||
<label class="text-sm text-gray-600">구군코드</label>
|
||||
<select name="ds_gugun_code" class="border border-gray-300 rounded px-2 py-1 text-sm">
|
||||
<label class="text-sm text-gray-600">구·군 코드</label>
|
||||
<select name="ds_gugun_code" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[14rem]">
|
||||
<option value="">전체</option>
|
||||
<?php foreach (($gugunCodes ?? []) as $gc): ?>
|
||||
<option value="<?= esc($gc->ds_gugun_code) ?>" <?= ($dsGugunCode ?? '') === $gc->ds_gugun_code ? 'selected' : '' ?>><?= esc($gc->ds_gugun_code) ?></option>
|
||||
<?php $gCode = (string) ($gc->ds_gugun_code ?? ''); ?>
|
||||
<option value="<?= esc($gCode) ?>" <?= ($dsGugunCode ?? '') === $gCode ? 'selected' : '' ?>><?= esc((string) (($gugunNameMap[$gCode] ?? '') !== '' ? $gugunNameMap[$gCode] : $gCode)) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<label class="text-sm text-gray-600">상태</label>
|
||||
@@ -223,7 +234,6 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
|
||||
<th class="ds-col-tight">상호명</th>
|
||||
<th class="ds-col-zip">우편번호</th>
|
||||
<th class="text-left">주소</th>
|
||||
<th class="text-left">상세주소</th>
|
||||
<th class="w-28">사업자번호</th>
|
||||
<th class="w-28">전화</th>
|
||||
<th class="w-16">상태</th>
|
||||
@@ -242,7 +252,8 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
|
||||
}
|
||||
$st = (int) ($row->ds_state ?? 1);
|
||||
$stLabel = $st === 1 ? '' : ($st === 2 ? '폐업' : '해지');
|
||||
$ggLabel = (string) ($row->ds_gugun_code ?? '');
|
||||
$ggCode = (string) ($row->ds_gugun_code ?? '');
|
||||
$ggLabel = (string) (($gugunNameMap[$ggCode] ?? '') !== '' ? $gugunNameMap[$ggCode] : $ggCode);
|
||||
$da = $row->ds_designated_at ?? null;
|
||||
$daDisp = ($da !== null && $da !== '' && (string) $da !== '0000-00-00') ? substr((string) $da, 0, 10) : '';
|
||||
$zone = (string) ($row->ds_zone_code ?? '');
|
||||
@@ -251,6 +262,10 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
|
||||
$jibunL = trim((string) ($row->ds_addr_jibun ?? ''));
|
||||
$addrMainList = $roadL !== '' ? $roadL : $jibunL;
|
||||
$addrDetailList = trim((string) ($row->ds_addr_detail ?? ''));
|
||||
$addrCombinedList = trim($addrMainList . ' ' . $addrDetailList);
|
||||
if ($addrCombinedList === '') {
|
||||
$addrCombinedList = $addrMainList;
|
||||
}
|
||||
?>
|
||||
<tr class="ds-list-row cursor-pointer" data-row-index="<?= (int) $i ?>" role="button" tabindex="0">
|
||||
<td class="text-center"><?= esc($shortNo) ?></td>
|
||||
@@ -260,8 +275,7 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
|
||||
<td class="text-left pl-1 text-xs ds-col-tight" title="<?= esc($row->ds_rep_name ?? '') ?>"><?= esc($row->ds_rep_name ?? '') ?></td>
|
||||
<td class="text-left pl-1 text-xs ds-col-tight" title="<?= esc($row->ds_name ?? '') ?>"><?= esc($row->ds_name ?? '') ?></td>
|
||||
<td class="text-center text-xs ds-col-zip" title="<?= esc($zipList) ?>"><?= esc($zipList) ?></td>
|
||||
<td class="text-left pl-1 text-xs ds-col-addr-list" title="<?= esc($addrMainList) ?>"><?= esc($addrMainList) ?></td>
|
||||
<td class="text-left pl-1 text-xs ds-col-detail-list" title="<?= esc($addrDetailList) ?>"><?= esc($addrDetailList) ?></td>
|
||||
<td class="text-left pl-1 text-xs ds-col-addr-list" title="<?= esc($addrCombinedList) ?>"><?= esc($addrCombinedList) ?></td>
|
||||
<td class="text-left pl-1 text-xs"><?= esc($row->ds_biz_no ?? '') ?></td>
|
||||
<td class="text-left pl-1 text-xs"><?= esc($row->ds_tel ?? '') ?></td>
|
||||
<td class="text-center <?= $st === 2 ? 'text-pink-600 font-medium' : ($st === 3 ? 'text-orange-700' : '') ?>"><?= esc($stLabel) ?></td>
|
||||
@@ -296,7 +310,7 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
|
||||
<th>지번주소</th>
|
||||
<th>상세주소</th>
|
||||
<th>개인전화</th>
|
||||
<th>구코드</th>
|
||||
<th>구·군</th>
|
||||
<th>구역</th>
|
||||
<th>가상계좌(은행)</th>
|
||||
<th>계좌번호</th>
|
||||
@@ -325,7 +339,7 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
|
||||
<td class="text-left" data-ro="ds_addr_jibun">—</td>
|
||||
<td class="text-left" data-ro="ds_addr_detail">—</td>
|
||||
<td class="text-left" data-ro="ds_rep_phone">—</td>
|
||||
<td class="text-left" data-ro="ds_gugun_code">—</td>
|
||||
<td class="text-left" data-ro="gugun_name">—</td>
|
||||
<td class="text-left" data-ro="ds_zone_code">—</td>
|
||||
<td class="text-left" data-ro="ds_va_bank">—</td>
|
||||
<td class="text-left" data-ro="ds_va_account">—</td>
|
||||
@@ -492,7 +506,6 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
|
||||
<th>상호명</th>
|
||||
<th>우편번호</th>
|
||||
<th>주소</th>
|
||||
<th>상세주소</th>
|
||||
<th>사업자번호</th>
|
||||
<th>전화</th>
|
||||
<th>판매소번호</th>
|
||||
@@ -521,18 +534,22 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
|
||||
$jibP = trim((string) ($row->ds_addr_jibun ?? ''));
|
||||
$addrP = $roadP !== '' ? $roadP : $jibP;
|
||||
$detP = trim((string) ($row->ds_addr_detail ?? ''));
|
||||
$addrCombinedP = trim($addrP . ' ' . $detP);
|
||||
if ($addrCombinedP === '') {
|
||||
$addrCombinedP = $addrP;
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($shortNoP) ?></td>
|
||||
<td class="text-left"><?= esc($lgMap[$row->ds_lg_idx] ?? '') ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_gugun_code ?? '') ?></td>
|
||||
<?php $gCodeP = (string) ($row->ds_gugun_code ?? ''); ?>
|
||||
<td class="text-left"><?= esc((string) (($gugunNameMap[$gCodeP] ?? '') !== '' ? $gugunNameMap[$gCodeP] : $gCodeP)) ?></td>
|
||||
<td class="text-center"><?= esc($daDispP) ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_zone_code ?? '') ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_rep_name ?? '') ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_name ?? '') ?></td>
|
||||
<td class="text-left"><?= esc($zipP) ?></td>
|
||||
<td class="text-left"><?= esc($addrP) ?></td>
|
||||
<td class="text-left"><?= esc($detP) ?></td>
|
||||
<td class="text-left"><?= esc($addrCombinedP) ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_biz_no ?? '') ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_tel ?? '') ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_shop_no) ?></td>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
<th>대상자명</th>
|
||||
<th>연락처</th>
|
||||
<th>주소</th>
|
||||
<th>동코드</th>
|
||||
<th>비고</th>
|
||||
<th>종료일</th>
|
||||
<th class="w-20">상태</th>
|
||||
@@ -30,7 +29,6 @@
|
||||
<td class="text-left pl-2"><?= esc($row->fr_name) ?></td>
|
||||
<td class="text-center"><?= esc($row->fr_phone) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->fr_addr) ?></td>
|
||||
<td class="text-center"><?= esc($row->fr_dong_code) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->fr_note) ?></td>
|
||||
<td class="text-center"><?= esc($row->fr_end_date) ?></td>
|
||||
<td class="text-center"><?= (int) $row->fr_state === 1 ? '사용' : '미사용' ?></td>
|
||||
@@ -45,7 +43,7 @@
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($list)): ?>
|
||||
<tr>
|
||||
<td colspan="9" class="text-center text-gray-500 py-4 text-sm space-y-1">
|
||||
<td colspan="8" class="text-center text-gray-500 py-4 text-sm space-y-1">
|
||||
<p>등록된 데이터가 없습니다.</p>
|
||||
<p class="text-gray-400">다른 지자체를 선택 중이면 해당 지자체 기준으로만 조회됩니다. Super Admin 은 상단에서 작업 지자체를 바꿔 보세요.</p>
|
||||
</td>
|
||||
|
||||
@@ -11,28 +11,18 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">소속</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_dept_code">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">담당자 구분 <span class="text-red-500">*</span></label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_category" required>
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($deptCodes as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= old('mg_dept_code') === $cd->cd_code ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_name) ?>
|
||||
<?php foreach (($categories ?? []) as $key => $label): ?>
|
||||
<option value="<?= esc($key) ?>" <?= old('mg_category') === $key ? 'selected' : '' ?>>
|
||||
<?= esc($label) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">직위</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_position_code">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($positionCodes as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= old('mg_position_code') === $cd->cd_code ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<input type="hidden" name="mg_position_code" value="<?= esc(old('mg_position_code', '')) ?>"/>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">전화</label>
|
||||
|
||||
@@ -11,28 +11,18 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">소속</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_dept_code">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">담당자 구분 <span class="text-red-500">*</span></label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_category" required>
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($deptCodes as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= old('mg_dept_code', $item->mg_dept_code) === $cd->cd_code ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_name) ?>
|
||||
<?php foreach (($categories ?? []) as $key => $label): ?>
|
||||
<option value="<?= esc($key) ?>" <?= old('mg_category', (string) ($item->mg_dept_code ?? '')) === $key ? 'selected' : '' ?>>
|
||||
<?= esc($label) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">직위</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_position_code">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($positionCodes as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= old('mg_position_code', $item->mg_position_code) === $cd->cd_code ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<input type="hidden" name="mg_position_code" value="<?= esc(old('mg_position_code', $item->mg_position_code)) ?>"/>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">전화</label>
|
||||
|
||||
@@ -8,14 +8,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200 no-print">
|
||||
<form method="GET" action="<?= mgmt_url('managers') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">카테고리</label>
|
||||
<select name="category" class="border border-gray-300 rounded px-2 py-1 text-sm w-44">
|
||||
<option value="">전 체</option>
|
||||
<?php foreach (($categories ?? []) as $key => $label): ?>
|
||||
<option value="<?= esc($key) ?>" <?= ($category ?? '') === $key ? 'selected' : '' ?>><?= esc($label) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('managers') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-16">번호</th>
|
||||
<th>담당자명</th>
|
||||
<th>소속</th>
|
||||
<th>직위</th>
|
||||
<th>카테고리</th>
|
||||
<th>전화</th>
|
||||
<th>휴대전화</th>
|
||||
<th>이메일</th>
|
||||
@@ -28,8 +40,13 @@
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->mg_idx) ?></td>
|
||||
<td class="text-center"><?= esc($row->mg_name) ?></td>
|
||||
<td class="text-center"><?= esc($row->mg_dept_code) ?></td>
|
||||
<td class="text-center"><?= esc($row->mg_position_code) ?></td>
|
||||
<td class="text-center">
|
||||
<?php
|
||||
$cat = (string) ($row->mg_dept_code ?? '');
|
||||
$catLabel = $categories[$cat] ?? $cat;
|
||||
echo esc($catLabel);
|
||||
?>
|
||||
</td>
|
||||
<td class="text-center"><?= esc($row->mg_tel) ?></td>
|
||||
<td class="text-center"><?= esc($row->mg_phone) ?></td>
|
||||
<td class="text-center"><?= esc($row->mg_email) ?></td>
|
||||
@@ -44,7 +61,7 @@
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($list)): ?>
|
||||
<tr><td colspan="9" class="text-center text-gray-400 py-4">등록된 데이터가 없습니다.</td></tr>
|
||||
<tr><td colspan="8" class="text-center text-gray-400 py-4">등록된 데이터가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-32">적용시작일 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="pu_start_date" type="date" value="<?= esc(old('pu_start_date', $item->pu_start_date)) ?>" required/>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="pu_start_date" type="date" value="<?= esc(old('pu_start_date', date('Y-m-d'))) ?>" required/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
|
||||
@@ -6,11 +6,20 @@
|
||||
</div>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<?php
|
||||
$fieldLabelMap = [
|
||||
'pu_box_per_pack' => '박스당 팩 수',
|
||||
'pu_pack_per_sheet' => '팩당 낱장 수',
|
||||
'pu_start_date' => '적용시작일',
|
||||
'pu_end_date' => '적용종료일',
|
||||
'pu_state' => '상태',
|
||||
];
|
||||
?>
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-16">번호</th>
|
||||
<th>변경 필드</th>
|
||||
<th>변경 내용</th>
|
||||
<th>이전 값</th>
|
||||
<th>변경 값</th>
|
||||
<th>변경일시</th>
|
||||
@@ -20,7 +29,7 @@
|
||||
<?php foreach ($list as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->puh_idx) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->puh_field) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($fieldLabelMap[(string) $row->puh_field] ?? $row->puh_field) ?></td>
|
||||
<td><?= esc($row->puh_old_value) ?></td>
|
||||
<td><?= esc($row->puh_new_value) ?></td>
|
||||
<td class="text-center"><?= esc($row->puh_changed_at) ?></td>
|
||||
|
||||
@@ -35,9 +35,17 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach ($list as $row): ?>
|
||||
<?php
|
||||
$startNo = 1;
|
||||
if (isset($pager) && method_exists($pager, 'getCurrentPage') && method_exists($pager, 'getPerPage')) {
|
||||
$currentPage = (int) $pager->getCurrentPage();
|
||||
$perPage = (int) $pager->getPerPage();
|
||||
$startNo = (($currentPage > 0 ? $currentPage : 1) - 1) * ($perPage > 0 ? $perPage : 20) + 1;
|
||||
}
|
||||
?>
|
||||
<?php foreach (($list ?? []) as $idx => $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->pu_idx) ?></td>
|
||||
<td class="text-center"><?= (int) $startNo + (int) $idx ?></td>
|
||||
<td class="text-center font-mono"><?= esc($row->pu_bag_code) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->pu_bag_name) ?></td>
|
||||
<td><?= number_format((int) $row->pu_box_per_pack) ?></td>
|
||||
|
||||
@@ -44,9 +44,17 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($list as $row): ?>
|
||||
<?php
|
||||
$startNo = 1;
|
||||
if (isset($pager) && method_exists($pager, 'getCurrentPage') && method_exists($pager, 'getPerPage')) {
|
||||
$currentPage = (int) $pager->getCurrentPage();
|
||||
$perPage = (int) $pager->getPerPage();
|
||||
$startNo = (($currentPage > 0 ? $currentPage : 1) - 1) * ($perPage > 0 ? $perPage : 20) + 1;
|
||||
}
|
||||
?>
|
||||
<?php foreach (($list ?? []) as $idx => $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->sa_idx) ?></td>
|
||||
<td class="text-center"><?= (int) $startNo + (int) $idx ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->sa_kind ?? '') ?></td>
|
||||
<td class="text-center"><?= esc($row->sa_code ?? '') ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->sa_name) ?></td>
|
||||
|
||||
@@ -1,83 +1,701 @@
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<span class="text-sm font-bold text-gray-700">발주 등록</span>
|
||||
<?php
|
||||
$editDefaults = is_array($editDefaults ?? null) ? $editDefaults : [];
|
||||
$editMode = (bool) ($editMode ?? false);
|
||||
$oldBagCodes = old('item_bag_code', $editDefaults['item_bag_code'] ?? []);
|
||||
$oldQtySheets = old('item_qty_sheet', $editDefaults['item_qty_sheet'] ?? []);
|
||||
$oldQtyBoxes = old('item_qty_box', $editDefaults['item_qty_box'] ?? []);
|
||||
$oldBagCodes = is_array($oldBagCodes) ? $oldBagCodes : [];
|
||||
$oldQtySheets = is_array($oldQtySheets) ? $oldQtySheets : [];
|
||||
$oldQtyBoxes = is_array($oldQtyBoxes) ? $oldQtyBoxes : [];
|
||||
$itemRowCount = max(8, count($oldBagCodes), count($oldQtySheets), count($oldQtyBoxes));
|
||||
$defaultOrderDate = old('bo_order_date', $editDefaults['bo_order_date'] ?? date('Y-m-d'));
|
||||
$defaultOrderMonth = old('bo_order_month_ui', substr($defaultOrderDate, 0, 7));
|
||||
$defaultOrderYear = (int) substr($defaultOrderMonth, 0, 4);
|
||||
$monthOptionValues = [];
|
||||
for ($year = $defaultOrderYear - 2; $year <= $defaultOrderYear + 2; $year++) {
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$monthValue = sprintf('%04d-%02d', $year, $month);
|
||||
$monthLabel = $year . '년 ' . $month . '월';
|
||||
$monthOptionValues[] = ['value' => $monthValue, 'label' => $monthLabel];
|
||||
}
|
||||
}
|
||||
|
||||
$bagMeta = [];
|
||||
foreach (($bagReferenceRows ?? []) as $row) {
|
||||
$bagMeta[$row['code']] = [
|
||||
'name' => $row['name'],
|
||||
'orderPrice' => (float) $row['orderPrice'],
|
||||
'boxPerPack' => (int) $row['boxPerPack'],
|
||||
'packPerSheet' => (int) $row['packPerSheet'],
|
||||
'totalPerBox' => max(1, (int) $row['totalPerBox']),
|
||||
];
|
||||
}
|
||||
|
||||
$initialSelectedItems = [];
|
||||
$maxOldCount = max(count($oldBagCodes), count($oldQtySheets), count($oldQtyBoxes));
|
||||
for ($i = 0; $i < $maxOldCount; $i++) {
|
||||
$code = trim((string) ($oldBagCodes[$i] ?? ''));
|
||||
if ($code === '' || !isset($bagMeta[$code])) {
|
||||
continue;
|
||||
}
|
||||
$fallbackQtyBox = (int) ($oldQtyBoxes[$i] ?? 0);
|
||||
$rawQtySheet = (int) ($oldQtySheets[$i] ?? 0);
|
||||
$fallbackTotalPerBox = (int) ($bagMeta[$code]['totalPerBox'] ?? 1);
|
||||
if ($fallbackQtyBox <= 0 && $rawQtySheet > 0) {
|
||||
$fallbackQtyBox = intdiv($rawQtySheet, max(1, $fallbackTotalPerBox));
|
||||
}
|
||||
$initialSelectedItems[] = [
|
||||
'code' => $code,
|
||||
'qtyBox' => max(0, $fallbackQtyBox),
|
||||
];
|
||||
}
|
||||
|
||||
$statusMap = ['normal' => '정상', 'cancelled' => '취소', 'deleted' => '삭제'];
|
||||
|
||||
$hubReturn = (bool) ($hubReturn ?? false);
|
||||
$changeMode = $editMode ? (string) ($changeMode ?? 'meta') : 'meta';
|
||||
if (! in_array($changeMode, ['price', 'meta', 'delete'], true)) {
|
||||
$changeMode = 'meta';
|
||||
}
|
||||
$orderLotNo = (string) ($orderLotNo ?? '');
|
||||
$orderReturnMonth = (string) ($orderReturnMonth ?? '');
|
||||
$deleteMode = $editMode && $changeMode === 'delete';
|
||||
$defaultDeleteBoIdx = (int) ($editDefaults['bo_source_idx'] ?? 0);
|
||||
$firstNormalBoIdx = null;
|
||||
foreach (($recentOrders ?? []) as $_h) {
|
||||
if ((string) ($_h->bo_status ?? '') === 'normal') {
|
||||
$firstNormalBoIdx = (int) $_h->bo_idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$defaultDeleteOk = false;
|
||||
foreach (($recentOrders ?? []) as $_h) {
|
||||
if ((int) $_h->bo_idx === $defaultDeleteBoIdx && (string) ($_h->bo_status ?? '') === 'normal') {
|
||||
$defaultDeleteOk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$selectedDeleteBoIdx = $defaultDeleteOk ? $defaultDeleteBoIdx : (int) ($firstNormalBoIdx ?? 0);
|
||||
?>
|
||||
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="text-sm font-bold text-gray-700"><?= $editMode ? '발주 변경' : '발주 등록' ?></span>
|
||||
<?php if ($hubReturn && $orderReturnMonth !== ''): ?>
|
||||
<a href="<?= base_url('bag/order/change?month=' . rawurlencode($orderReturnMonth) . '&hub_mode=' . rawurlencode($changeMode)) ?>" class="text-sm text-blue-600 hover:underline">발주 변경 목록</a>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-4xl">
|
||||
<form action="<?= base_url('bag/order/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">발주일 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="bo_order_date" type="date" value="<?= esc(old('bo_order_date', date('Y-m-d'))) ?>" required/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">수수료율</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32 text-right" name="bo_fee_rate" type="number" step="0.01" value="<?= esc(old('bo_fee_rate', '0')) ?>"/>
|
||||
<span class="text-sm text-gray-500">%</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">제작업체</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="bo_company_idx">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($companies as $cp): ?>
|
||||
<option value="<?= esc($cp->cp_idx) ?>" <?= (int) old('bo_company_idx') === (int) $cp->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc($cp->cp_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">입고처</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="bo_agency_idx">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($agencies as $ag): ?>
|
||||
<option value="<?= esc($ag->sa_idx) ?>" <?= (int) old('bo_agency_idx') === (int) $ag->sa_idx ? 'selected' : '' ?>>
|
||||
[<?= esc($ag->sa_kind ?? '') ?>] <?= esc($ag->sa_code ?? '') ?> — <?= esc($ag->sa_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label class="block text-sm font-bold text-gray-700 mb-2">발주 품목</label>
|
||||
<div class="border border-gray-300 overflow-auto">
|
||||
<table class="w-full data-table">
|
||||
<?php if ($deleteMode): ?>
|
||||
<form action="<?= base_url('bag/order/delete') ?>" method="post" class="mt-2 space-y-2">
|
||||
<?= csrf_field() ?>
|
||||
<div class="border border-gray-300 bg-white p-2 text-sm">
|
||||
<span class="font-bold text-gray-700">발주월</span>
|
||||
<span class="ml-2"><?= esc($defaultOrderMonth) ?></span>
|
||||
<span class="text-gray-600 ml-2">왼쪽 목록에서 삭제할 발주를 선택한 뒤 「삭제 실행」을 누르세요.</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 xl:grid-cols-12 gap-2">
|
||||
<section class="xl:col-span-5 border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 이력</div>
|
||||
<div class="overflow-auto max-h-[410px]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-16">순번</th>
|
||||
<th>봉투</th>
|
||||
<th class="w-32">박스수</th>
|
||||
<th class="w-10">선택</th>
|
||||
<th class="w-28">발주일</th>
|
||||
<th>제작업체</th>
|
||||
<th>입고처</th>
|
||||
<th class="w-16">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php for ($i = 0; $i < 3; $i++): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= $i + 1 ?></td>
|
||||
<td>
|
||||
<select class="border border-gray-300 rounded px-2 py-1 text-sm w-full" name="item_bag_code[]">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($bagCodes as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>">
|
||||
<?= esc($cd->cd_code) ?> — <?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input class="border border-gray-300 rounded px-2 py-1 text-sm w-full text-right" name="item_qty_box[]" type="number" min="0" value="0"/>
|
||||
<?php $deleteRadioFirst = true; ?>
|
||||
<?php foreach (($recentOrders ?? []) as $history): ?>
|
||||
<?php $rowNormal = (string) ($history->bo_status ?? '') === 'normal'; ?>
|
||||
<tr class="<?= $rowNormal ? '' : 'opacity-60' ?>">
|
||||
<td class="text-center align-middle">
|
||||
<?php if ($rowNormal): ?>
|
||||
<input type="radio" name="bo_idx" value="<?= (int) $history->bo_idx ?>" class="accent-red-600" <?= (int) $history->bo_idx === $selectedDeleteBoIdx ? 'checked' : '' ?> <?= $deleteRadioFirst ? 'required' : '' ?> />
|
||||
<?php $deleteRadioFirst = false; ?>
|
||||
<?php else: ?>
|
||||
<span class="text-gray-400" title="삭제할 수 없는 상태">—</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-center"><?= esc((string) $history->bo_order_date) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($companyMap[(int) $history->bo_company_idx] ?? '-')) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($agencyMap[(int) $history->bo_agency_idx] ?? '-')) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($statusMap[(string) $history->bo_status] ?? $history->bo_status)) ?></td>
|
||||
</tr>
|
||||
<?php endfor; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($recentOrders)): ?>
|
||||
<tr><td colspan="5" class="text-center text-gray-400 py-4">발주 이력이 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="xl:col-span-7 border border-red-200 bg-red-50 p-4">
|
||||
<p class="text-sm font-bold text-red-800 mb-2">발주 삭제</p>
|
||||
<p class="text-sm text-gray-700 mb-4">목록에서 선택한 발주를 삭제 처리합니다. 계속하시겠습니까?</p>
|
||||
<div class="flex flex-wrap gap-2 items-center">
|
||||
<button type="submit" class="bg-red-600 text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90" <?= $firstNormalBoIdx === null ? 'disabled' : '' ?>>삭제 실행</button>
|
||||
<a href="<?= base_url('bag/order/change?month=' . rawurlencode($orderReturnMonth !== '' ? $orderReturnMonth : substr($defaultOrderDate, 0, 7))) ?>" class="bg-gray-200 text-gray-800 px-6 py-1.5 rounded-sm text-sm">취소</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= base_url('bag/purchase-inbound') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<form action="<?= base_url('bag/order/store') ?>" method="POST" class="mt-2 space-y-2" id="bag-order-store-form">
|
||||
<?= csrf_field() ?>
|
||||
<?php if ($editMode): ?>
|
||||
<input type="hidden" name="bo_source_idx" value="<?= esc((string) ($editDefaults['bo_source_idx'] ?? 0)) ?>" />
|
||||
<fieldset class="border border-gray-200 rounded px-3 py-2 mb-1 text-sm bg-white">
|
||||
<legend class="text-xs font-bold text-gray-600 px-1">변경 구분</legend>
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-1">
|
||||
<label class="inline-flex items-center gap-1 cursor-pointer">
|
||||
<input type="radio" name="bo_change_mode" value="price" <?= $changeMode === 'price' ? 'checked' : '' ?> />
|
||||
<span>발주·도매·판매 단가</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-1 cursor-pointer">
|
||||
<input type="radio" name="bo_change_mode" value="meta" <?= $changeMode === 'meta' ? 'checked' : '' ?> />
|
||||
<span>업체·수수료·협회·발주</span>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
발주 삭제는 <a class="text-blue-600 hover:underline" href="<?= base_url('bag/order/revise/' . (int) ($editDefaults['bo_source_idx'] ?? 0) . '?change_mode=delete') ?>">발주 삭제 화면</a>으로 이동합니다.
|
||||
</p>
|
||||
</fieldset>
|
||||
<?php endif; ?>
|
||||
<?php if ($hubReturn && $orderReturnMonth !== ''): ?>
|
||||
<input type="hidden" name="order_return_hub" value="1" />
|
||||
<input type="hidden" name="order_return_month" value="<?= esc($orderReturnMonth) ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="border border-gray-300 bg-white p-2">
|
||||
<div class="flex flex-wrap items-center gap-4 text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="bo_order_month_ui" class="font-bold text-gray-700">발주월</label>
|
||||
<select id="bo_order_month_ui" name="bo_order_month_ui" class="border border-gray-300 rounded px-2 py-1 w-40">
|
||||
<?php foreach ($monthOptionValues as $monthOption): ?>
|
||||
<option value="<?= esc($monthOption['value']) ?>" <?= $monthOption['value'] === $defaultOrderMonth ? 'selected' : '' ?>>
|
||||
<?= esc($monthOption['label']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<span id="bo_order_month_ko" class="text-sm text-gray-700"></span>
|
||||
</div>
|
||||
<p class="text-blue-600 font-bold">※ 발주수량을 박스단위로 떨어지게 입력해 주세요.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 xl:grid-cols-12 gap-2">
|
||||
<section class="xl:col-span-5 border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 이력</div>
|
||||
<div class="overflow-auto max-h-[410px]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-28">발주일</th>
|
||||
<th>제작업체</th>
|
||||
<th>입고처</th>
|
||||
<th class="w-16">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($recentOrders ?? []) as $history): ?>
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="<?= base_url('bag/order/revise/' . (int) $history->bo_idx) ?>" class="text-blue-600 hover:underline">
|
||||
<?= esc((string) $history->bo_order_date) ?>
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($companyMap[(int) $history->bo_company_idx] ?? '-')) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($agencyMap[(int) $history->bo_agency_idx] ?? '-')) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($statusMap[(string) $history->bo_status] ?? $history->bo_status)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($recentOrders)): ?>
|
||||
<tr><td colspan="4" class="text-center text-gray-400 py-4">발주 이력이 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="xl:col-span-7 border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700"><?= $editMode ? '발주 변경 수정' : '발주 Form' ?></div>
|
||||
<div class="p-2 space-y-2">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_order_date" class="w-20 font-bold text-gray-700">발주일 <span class="text-red-500">*</span></label>
|
||||
<input id="bo_order_date" name="bo_order_date" type="date" value="<?= esc($defaultOrderDate) ?>" required class="border border-gray-300 rounded px-2 py-1 w-full" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_association_idx" class="w-20 font-bold text-gray-700">협회</label>
|
||||
<select id="bo_association_idx" name="bo_association_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
|
||||
<option value="">선택</option>
|
||||
<?php foreach (($associations ?? []) as $association): ?>
|
||||
<option value="<?= esc((string) $association->cp_idx) ?>" <?= (int) old('bo_association_idx', $editDefaults['bo_association_idx'] ?? 0) === (int) $association->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc((string) $association->cp_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_company_idx" class="w-20 font-bold text-gray-700">제작업체</label>
|
||||
<select id="bo_company_idx" name="bo_company_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
|
||||
<option value="">선택</option>
|
||||
<?php foreach (($companies ?? []) as $company): ?>
|
||||
<option value="<?= esc((string) $company->cp_idx) ?>" <?= (int) old('bo_company_idx', $editDefaults['bo_company_idx'] ?? 0) === (int) $company->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc((string) $company->cp_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_agency_idx" class="w-20 font-bold text-gray-700">입고처</label>
|
||||
<select id="bo_agency_idx" name="bo_agency_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
|
||||
<option value="">선택</option>
|
||||
<?php foreach (($agencies ?? []) as $agency): ?>
|
||||
<option value="<?= esc((string) $agency->sa_idx) ?>" <?= (int) old('bo_agency_idx', $editDefaults['bo_agency_idx'] ?? 0) === (int) $agency->sa_idx ? 'selected' : '' ?>>
|
||||
[<?= esc((string) ($agency->sa_kind ?? '')) ?>] <?= esc((string) ($agency->sa_code ?? '')) ?> — <?= esc((string) $agency->sa_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-gray-300 overflow-auto">
|
||||
<table class="w-full data-table text-sm order-input-table" id="order-item-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-12">번호</th>
|
||||
<th class="w-16">선택</th>
|
||||
<th>품명</th>
|
||||
<th class="w-24">수량</th>
|
||||
<th class="w-24">단가</th>
|
||||
<th class="w-24">낱장환산</th>
|
||||
<?php if ($editMode): ?>
|
||||
<th class="w-24">선정수량</th>
|
||||
<th class="w-20">LOT</th>
|
||||
<?php endif; ?>
|
||||
<th class="w-28">금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="selected-order-items-body"></tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="3" class="text-center">계</th>
|
||||
<th class="text-right pr-2" id="sum-box-qty">0</th>
|
||||
<th></th>
|
||||
<th class="text-right pr-2" id="sum-sheet-qty">0</th>
|
||||
<?php if ($editMode): ?>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<?php endif; ?>
|
||||
<th class="text-right pr-2" id="sum-amount">0</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="border border-gray-300 bg-gray-50 p-2 text-sm">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-700">품목 금액 합계</span>
|
||||
<strong id="sum-item-amount">0</strong>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-700">조달수수료</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<input id="bo_fee_rate_summary" name="bo_fee_rate" type="number" step="0.01" value="<?= esc(old('bo_fee_rate', $editDefaults['bo_fee_rate'] ?? '0')) ?>" class="border border-gray-300 rounded px-2 py-0.5 w-20 text-right" />
|
||||
<strong>%</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-700">조달수수료액</span>
|
||||
<strong id="sum-fee-amount">0</strong>
|
||||
</div>
|
||||
<div class="flex items-center justify-between border-t border-red-300 pt-1">
|
||||
<span class="font-bold text-gray-800">품목 + 수수료 합계</span>
|
||||
<strong id="sum-grand-amount" class="text-red-700">0</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 pt-1">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition"><?= $editMode ? '변경 저장' : '발주' ?></button>
|
||||
<a href="<?= base_url('bag/purchase-inbound') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 등록 종류</div>
|
||||
<div class="overflow-auto">
|
||||
<table class="w-full data-table text-sm order-reference-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-12">번호</th>
|
||||
<th class="w-20">선택</th>
|
||||
<th>봉투 종류</th>
|
||||
<th class="w-24">발주단가</th>
|
||||
<th class="w-24">Box당 팩</th>
|
||||
<th class="w-24">팩당 낱장</th>
|
||||
<th class="w-28">1박스 총 낱장</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($bagReferenceRows ?? []) as $idx => $row): ?>
|
||||
<tr data-reference-row data-code="<?= esc((string) $row['code']) ?>" class="cursor-pointer">
|
||||
<td class="text-center"><?= $idx + 1 ?></td>
|
||||
<td class="text-center">
|
||||
<button type="button" class="js-toggle-bag border border-gray-300 rounded px-2 py-0.5 text-xs hover:bg-gray-100" data-code="<?= esc((string) $row['code']) ?>">선택</button>
|
||||
</td>
|
||||
<td class="text-left pl-2"><?= esc((string) $row['name']) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((float) $row['orderPrice'], 2) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) $row['boxPerPack']) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) $row['packPerSheet']) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) $row['totalPerBox']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($bagReferenceRows)): ?>
|
||||
<tr><td colspan="6" class="text-center text-gray-400 py-4">표시할 봉투 기준 데이터가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (! $deleteMode): ?>
|
||||
<style>
|
||||
.order-input-table tbody tr,
|
||||
.order-reference-table tbody tr {
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.order-input-table tbody td,
|
||||
.order-reference-table tbody td {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const bagMeta = <?= json_encode($bagMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
const initialSelectedItems = <?= json_encode($initialSelectedItems, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
const selectedBody = document.getElementById('selected-order-items-body');
|
||||
const referenceRows = Array.from(document.querySelectorAll('[data-reference-row]'));
|
||||
const sumBoxQtyEl = document.getElementById('sum-box-qty');
|
||||
const sumSheetQtyEl = document.getElementById('sum-sheet-qty');
|
||||
const sumAmountEl = document.getElementById('sum-amount');
|
||||
const sumItemAmountEl = document.getElementById('sum-item-amount');
|
||||
const sumFeeAmountEl = document.getElementById('sum-fee-amount');
|
||||
const sumGrandAmountEl = document.getElementById('sum-grand-amount');
|
||||
const monthInput = document.getElementById('bo_order_month_ui');
|
||||
const monthKoLabel = document.getElementById('bo_order_month_ko');
|
||||
const orderDateInput = document.getElementById('bo_order_date');
|
||||
const feeRateSummaryInput = document.getElementById('bo_fee_rate_summary');
|
||||
const orderForm = document.querySelector('form[action*="bag/order/store"]');
|
||||
const selectedItems = new Map();
|
||||
let activeCode = null;
|
||||
const editMode = <?= $editMode ? 'true' : 'false' ?>;
|
||||
const changeMode = <?= json_encode($changeMode, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
const orderLotNo = <?= json_encode($orderLotNo, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
const colEmpty = editMode ? 9 : 7;
|
||||
|
||||
const formatNumber = (value) => new Intl.NumberFormat('ko-KR').format(Number.isFinite(value) ? value : 0);
|
||||
const escapeHtml = (value) => String(value ?? '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
|
||||
const ensureMonthOption = (monthValue) => {
|
||||
if (!monthInput || !monthValue) return;
|
||||
const hasOption = Array.from(monthInput.options || []).some((option) => option.value === monthValue);
|
||||
if (hasOption) return;
|
||||
const [year, month] = monthValue.split('-');
|
||||
const label = `${Number(year)}년 ${Number(month)}월`;
|
||||
const option = new Option(label, monthValue, false, false);
|
||||
monthInput.add(option);
|
||||
};
|
||||
|
||||
const syncMonthFromDate = () => {
|
||||
if (!orderDateInput || !monthInput || !orderDateInput.value) return;
|
||||
const monthValue = orderDateInput.value.substring(0, 7);
|
||||
ensureMonthOption(monthValue);
|
||||
monthInput.value = monthValue;
|
||||
updateMonthKoLabel();
|
||||
};
|
||||
|
||||
const syncDateFromMonth = () => {
|
||||
if (!orderDateInput || !monthInput || !monthInput.value) return;
|
||||
const parts = monthInput.value.split('-');
|
||||
if (parts.length !== 2) return;
|
||||
const year = Number(parts[0]);
|
||||
const month = Number(parts[1]);
|
||||
if (!Number.isFinite(year) || !Number.isFinite(month)) return;
|
||||
|
||||
const currentDay = orderDateInput.value ? Number(orderDateInput.value.split('-')[2]) : 1;
|
||||
const lastDay = new Date(year, month, 0).getDate();
|
||||
const day = String(Math.min(Math.max(currentDay, 1), lastDay)).padStart(2, '0');
|
||||
orderDateInput.value = `${String(year)}-${String(month).padStart(2, '0')}-${day}`;
|
||||
updateMonthKoLabel();
|
||||
};
|
||||
|
||||
const updateMonthKoLabel = () => {
|
||||
if (!monthKoLabel || !monthInput || !monthInput.value) {
|
||||
if (monthKoLabel) monthKoLabel.textContent = '';
|
||||
return;
|
||||
}
|
||||
const [year, month] = monthInput.value.split('-');
|
||||
if (!year || !month) {
|
||||
monthKoLabel.textContent = '';
|
||||
return;
|
||||
}
|
||||
monthKoLabel.textContent = `${Number(year)}년 ${Number(month)}월`;
|
||||
};
|
||||
|
||||
const updateReferenceSelectionUi = () => {
|
||||
referenceRows.forEach((row) => {
|
||||
const code = row.dataset.code || '';
|
||||
const button = row.querySelector('.js-toggle-bag');
|
||||
const isSelected = selectedItems.has(code);
|
||||
row.classList.toggle('bg-blue-50', isSelected);
|
||||
if (button) {
|
||||
button.textContent = isSelected ? '선택됨' : '선택';
|
||||
button.classList.toggle('bg-blue-600', isSelected);
|
||||
button.classList.toggle('text-white', isSelected);
|
||||
button.classList.toggle('border-blue-600', isSelected);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const applyModeLock = () => {
|
||||
if (!editMode) return;
|
||||
const isPrice = changeMode === 'price';
|
||||
selectedBody.querySelectorAll('.item-qty-box').forEach((el) => {
|
||||
el.readOnly = isPrice;
|
||||
});
|
||||
};
|
||||
|
||||
const updateTotals = () => {
|
||||
let sumBoxQty = 0;
|
||||
let sumSheetQty = 0;
|
||||
let sumAmount = 0;
|
||||
|
||||
selectedBody.querySelectorAll('tr[data-item-row]').forEach((row) => {
|
||||
const code = row.dataset.code || '';
|
||||
const qtyInput = row.querySelector('.item-qty-box');
|
||||
const qtyBox = Math.max(0, parseInt(qtyInput?.value || '0', 10));
|
||||
const meta = bagMeta[code] || { orderPrice: 0, totalPerBox: 1 };
|
||||
const priceInput = row.querySelector('.item-unit-price-input');
|
||||
let unitPrice = Number(meta.orderPrice || 0);
|
||||
if (priceInput) {
|
||||
unitPrice = Math.max(0, parseFloat(priceInput.value || '0') || 0);
|
||||
}
|
||||
const totalPerBox = Math.max(1, Number(meta.totalPerBox || 1));
|
||||
const qtySheet = qtyBox * totalPerBox;
|
||||
const amount = qtySheet * unitPrice;
|
||||
|
||||
const unitPriceEl = row.querySelector('.item-unit-price');
|
||||
const qtySheetEl = row.querySelector('.item-qty-sheet');
|
||||
const selQtyEl = row.querySelector('.item-sel-qty');
|
||||
const sheetHelpEl = row.querySelector('.item-sheet-help');
|
||||
const amountEl = row.querySelector('.item-amount');
|
||||
if (unitPriceEl) unitPriceEl.textContent = formatNumber(unitPrice);
|
||||
if (qtySheetEl) qtySheetEl.textContent = formatNumber(qtySheet);
|
||||
if (selQtyEl) selQtyEl.textContent = formatNumber(qtySheet);
|
||||
if (sheetHelpEl) sheetHelpEl.textContent = `낱장 ${formatNumber(qtySheet)}장`;
|
||||
if (amountEl) amountEl.textContent = formatNumber(amount);
|
||||
|
||||
selectedItems.set(code, { qtyBox });
|
||||
sumBoxQty += qtyBox;
|
||||
sumSheetQty += qtySheet;
|
||||
sumAmount += amount;
|
||||
});
|
||||
|
||||
if (sumBoxQtyEl) sumBoxQtyEl.textContent = formatNumber(sumBoxQty);
|
||||
if (sumSheetQtyEl) sumSheetQtyEl.textContent = formatNumber(sumSheetQty);
|
||||
if (sumAmountEl) sumAmountEl.textContent = formatNumber(sumAmount);
|
||||
if (sumItemAmountEl) sumItemAmountEl.textContent = formatNumber(sumAmount);
|
||||
|
||||
const feeRateSource = feeRateSummaryInput?.value ?? '0';
|
||||
const feeRate = Math.max(0, parseFloat(feeRateSource) || 0);
|
||||
const feeAmount = Math.round(sumAmount * (feeRate / 100));
|
||||
const grandAmount = sumAmount + feeAmount;
|
||||
|
||||
if (feeRateSummaryInput && feeRateSummaryInput.value !== feeRate.toString()) {
|
||||
feeRateSummaryInput.value = feeRate.toString();
|
||||
}
|
||||
if (sumFeeAmountEl) sumFeeAmountEl.textContent = formatNumber(feeAmount);
|
||||
if (sumGrandAmountEl) sumGrandAmountEl.textContent = formatNumber(grandAmount);
|
||||
};
|
||||
|
||||
const setActiveRow = (code) => {
|
||||
activeCode = code || null;
|
||||
selectedBody.querySelectorAll('tr[data-item-row]').forEach((row) => {
|
||||
row.classList.toggle('bg-amber-50', row.dataset.code === activeCode);
|
||||
});
|
||||
};
|
||||
|
||||
const renderSelectedRows = () => {
|
||||
const codes = Object.keys(bagMeta).filter((code) => selectedItems.has(code));
|
||||
if (codes.length === 0) {
|
||||
selectedBody.innerHTML = `<tr><td colspan="${colEmpty}" class="text-center text-gray-400 py-4">아래 "발주 등록 종류"에서 봉투를 선택해 주세요.</td></tr>`;
|
||||
setActiveRow(null);
|
||||
updateTotals();
|
||||
updateReferenceSelectionUi();
|
||||
applyModeLock();
|
||||
return;
|
||||
}
|
||||
|
||||
selectedBody.innerHTML = codes.map((code, idx) => {
|
||||
const meta = bagMeta[code];
|
||||
const qtyBox = Math.max(0, parseInt(String(selectedItems.get(code)?.qtyBox ?? 0), 10));
|
||||
const name = meta?.name || code;
|
||||
const up = Number(meta.orderPrice || 0);
|
||||
const priceCell = editMode && changeMode === 'price'
|
||||
? `<td class="text-right pr-1"><input type="number" name="item_unit_price[]" step="0.01" min="0" class="item-unit-price-input border border-gray-300 rounded px-1 py-0.5 w-full text-sm text-right" value="${up}" /></td>`
|
||||
: `<td class="text-right pr-2 item-unit-price">0</td>`;
|
||||
const extraCols = editMode
|
||||
? `<td class="text-right pr-2 item-sel-qty">0</td><td class="text-center text-[11px] item-lot-cell leading-tight text-gray-700">생성<br /><span class="font-mono">${escapeHtml(orderLotNo || '—')}</span></td>`
|
||||
: '';
|
||||
|
||||
return `
|
||||
<tr data-item-row data-code="${escapeHtml(code)}" class="cursor-pointer">
|
||||
<td class="text-center">${idx + 1}</td>
|
||||
<td class="text-center">
|
||||
<button type="button" class="js-remove-selected text-xs text-red-600 hover:underline" data-code="${escapeHtml(code)}">해제</button>
|
||||
</td>
|
||||
<td class="text-left pl-2">
|
||||
${escapeHtml(name)}
|
||||
<input type="hidden" name="item_bag_code[]" value="${escapeHtml(code)}" />
|
||||
</td>
|
||||
<td>
|
||||
<input name="item_qty_box[]" type="number" min="0" step="1" value="${qtyBox}" class="item-qty-box border border-gray-300 rounded px-2 py-1 text-sm w-full text-right leading-tight" />
|
||||
<p class="text-[11px] text-gray-500 mt-1 item-sheet-help">낱장 0장</p>
|
||||
</td>
|
||||
${priceCell}
|
||||
<td class="text-right pr-2 item-qty-sheet">0</td>
|
||||
${extraCols}
|
||||
<td class="text-right pr-2 item-amount">0</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
if (!activeCode || !selectedItems.has(activeCode)) {
|
||||
activeCode = codes[0];
|
||||
}
|
||||
setActiveRow(activeCode);
|
||||
updateTotals();
|
||||
updateReferenceSelectionUi();
|
||||
applyModeLock();
|
||||
};
|
||||
|
||||
const toggleSelection = (code) => {
|
||||
if (!code || !bagMeta[code]) return;
|
||||
if (selectedItems.has(code)) {
|
||||
selectedItems.delete(code);
|
||||
if (activeCode === code) activeCode = null;
|
||||
} else {
|
||||
selectedItems.set(code, { qtyBox: 0 });
|
||||
activeCode = code;
|
||||
}
|
||||
renderSelectedRows();
|
||||
};
|
||||
|
||||
initialSelectedItems.forEach((item) => {
|
||||
if (!item || !item.code || !bagMeta[item.code]) return;
|
||||
selectedItems.set(item.code, { qtyBox: Math.max(0, parseInt(String(item.qtyBox ?? 0), 10)) });
|
||||
activeCode = item.code;
|
||||
});
|
||||
|
||||
selectedBody.addEventListener('click', (event) => {
|
||||
const removeButton = event.target.closest('.js-remove-selected');
|
||||
if (removeButton) {
|
||||
toggleSelection(removeButton.dataset.code || '');
|
||||
return;
|
||||
}
|
||||
|
||||
const row = event.target.closest('tr[data-item-row]');
|
||||
if (!row) return;
|
||||
const code = row.dataset.code || '';
|
||||
setActiveRow(code);
|
||||
const qtyInput = row.querySelector('.item-qty-box');
|
||||
if (qtyInput) qtyInput.focus();
|
||||
});
|
||||
|
||||
selectedBody.addEventListener('input', (event) => {
|
||||
const qtyInput = event.target.closest('.item-qty-box');
|
||||
const priceField = event.target.closest('.item-unit-price-input');
|
||||
if (qtyInput) {
|
||||
const row = qtyInput.closest('tr[data-item-row]');
|
||||
if (!row) return;
|
||||
const code = row.dataset.code || '';
|
||||
selectedItems.set(code, { qtyBox: Math.max(0, parseInt(qtyInput.value || '0', 10)) });
|
||||
updateTotals();
|
||||
return;
|
||||
}
|
||||
if (priceField) {
|
||||
updateTotals();
|
||||
}
|
||||
});
|
||||
|
||||
referenceRows.forEach((row) => {
|
||||
row.addEventListener('click', (event) => {
|
||||
const button = event.target.closest('.js-toggle-bag');
|
||||
if (button) {
|
||||
toggleSelection(button.dataset.code || '');
|
||||
return;
|
||||
}
|
||||
if (event.target.closest('td')) {
|
||||
toggleSelection(row.dataset.code || '');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (monthInput) monthInput.addEventListener('change', () => { syncDateFromMonth(); updateTotals(); });
|
||||
if (orderDateInput) orderDateInput.addEventListener('change', syncMonthFromDate);
|
||||
if (feeRateSummaryInput) {
|
||||
feeRateSummaryInput.addEventListener('input', () => {
|
||||
updateTotals();
|
||||
});
|
||||
}
|
||||
|
||||
if (orderForm) {
|
||||
orderForm.addEventListener('submit', (event) => {
|
||||
const hasValidItem = Array.from(selectedBody.querySelectorAll('tr[data-item-row]')).some((row) => {
|
||||
const qtyInput = row.querySelector('.item-qty-box');
|
||||
return Math.max(0, parseInt(qtyInput?.value || '0', 10)) > 0;
|
||||
});
|
||||
|
||||
if (!hasValidItem) {
|
||||
event.preventDefault();
|
||||
alert('봉투를 선택하고 수량을 1 이상 입력해 주세요.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
syncMonthFromDate();
|
||||
updateMonthKoLabel();
|
||||
renderSelectedRows();
|
||||
|
||||
if (editMode) {
|
||||
document.querySelectorAll('input[name="bo_change_mode"]').forEach((r) => {
|
||||
r.addEventListener('change', () => {
|
||||
const u = new URL(window.location.href);
|
||||
u.searchParams.set('change_mode', r.value);
|
||||
window.location.href = u.toString();
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
396
app/Views/bag/inventory_inspection_select.php
Normal file
396
app/Views/bag/inventory_inspection_select.php
Normal file
@@ -0,0 +1,396 @@
|
||||
<?php
|
||||
$startDate = (string) ($startDate ?? date('Y-m-01'));
|
||||
$endDate = (string) ($endDate ?? date('Y-m-d'));
|
||||
$workDate = (string) ($workDate ?? date('Y-m-d'));
|
||||
$itemCode = (string) ($itemCode ?? '');
|
||||
$viewType = (string) ($viewType ?? 'box');
|
||||
$inspectionRuns = is_array($inspectionRuns ?? null) ? $inspectionRuns : [];
|
||||
$popupItems = is_array($popupItems ?? null) ? $popupItems : [];
|
||||
$items = is_array($items ?? null) ? $items : [];
|
||||
$overviewRows = is_array($overviewRows ?? null) ? $overviewRows : [];
|
||||
$selectedInspectionItemId = (int) ($selectedInspectionItemId ?? 0);
|
||||
$selectedInspectionId = (int) ($selectedInspectionId ?? 0);
|
||||
$boxRows = is_array($boxRows ?? null) ? $boxRows : [];
|
||||
$selectedBoxCode = (string) ($selectedBoxCode ?? '');
|
||||
$selectedPackCode = (string) ($selectedPackCode ?? '');
|
||||
$sheetRows = is_array($sheetRows ?? null) ? $sheetRows : [];
|
||||
$overviewTotalQty = 0;
|
||||
foreach ($overviewRows as $row) {
|
||||
$overviewTotalQty += (int) ($row['bisi_system_qty'] ?? 0);
|
||||
}
|
||||
$overviewTotalActual = 0;
|
||||
foreach ($overviewRows as $row) {
|
||||
$overviewTotalActual += (int) ($row['bisi_actual_qty'] ?? 0);
|
||||
}
|
||||
$overviewTotalDiff = $overviewTotalActual - $overviewTotalQty;
|
||||
$packTotalQty = 0;
|
||||
foreach ($boxRows as $row) {
|
||||
$packTotalQty += (int) ($row['bisp_sheet_qty'] ?? 0);
|
||||
}
|
||||
$packTotalActual = 0;
|
||||
foreach ($boxRows as $row) {
|
||||
$packTotalActual += (int) ($row['bisp_actual_qty'] ?? 0);
|
||||
}
|
||||
$packTotalDiff = $packTotalActual - $packTotalQty;
|
||||
$sheetTotalQty = 0;
|
||||
foreach ($sheetRows as $row) {
|
||||
$sheetTotalQty += (int) ($row['biss_system_qty'] ?? 0);
|
||||
}
|
||||
$sheetTotalActual = 0;
|
||||
foreach ($sheetRows as $row) {
|
||||
$sheetTotalActual += (int) ($row['biss_actual_qty'] ?? 0);
|
||||
}
|
||||
$sheetTotalDiff = $sheetTotalActual - $sheetTotalQty;
|
||||
?>
|
||||
|
||||
<div class="space-y-2">
|
||||
<section class="border border-gray-300 bg-white p-2">
|
||||
<form method="get" class="flex flex-wrap items-end justify-between gap-2 text-sm">
|
||||
<div class="flex flex-wrap items-end gap-2">
|
||||
<label class="font-bold text-gray-700">실사기간</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate) ?>" class="border border-gray-300 rounded px-2 py-1">
|
||||
<span>~</span>
|
||||
<input type="date" name="end_date" value="<?= esc($endDate) ?>" class="border border-gray-300 rounded px-2 py-1">
|
||||
|
||||
<label class="font-bold text-gray-700 ml-2">실사품목</label>
|
||||
<select name="item_code" class="border border-gray-300 rounded px-2 py-1 min-w-[11rem]">
|
||||
<option value="">전체</option>
|
||||
<?php foreach ($items as $it): ?>
|
||||
<?php $code = (string) ($it['bag_code'] ?? ''); ?>
|
||||
<option value="<?= esc($code) ?>" <?= $itemCode === $code ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($it['bag_name'] ?? '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<label class="font-bold text-gray-700 ml-2">조회구분</label>
|
||||
<select name="view_type" class="border border-gray-300 rounded px-2 py-1 min-w-[7rem]">
|
||||
<option value="box" <?= $viewType === 'box' ? 'selected' : '' ?>>박스별</option>
|
||||
<option value="pack" <?= $viewType === 'pack' ? 'selected' : '' ?>>팩별</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-3 py-1 rounded-sm">조회</button>
|
||||
<button type="button" id="open-inspection-popup" class="border border-blue-300 text-blue-700 px-3 py-1 rounded-sm hover:bg-blue-50">실사 선별</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="mt-1 text-xs text-blue-700">※ 해당 박스와 팩을 클릭하면 팩과 낱장이 조회됩니다.</p>
|
||||
</section>
|
||||
|
||||
<section class="grid grid-cols-1 xl:grid-cols-2 gap-2">
|
||||
<div class="border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">실사 선별자</div>
|
||||
<div class="overflow-auto max-h-[500px]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-24">실사일자</th>
|
||||
<th>종류</th>
|
||||
<th class="w-40">박스</th>
|
||||
<th class="w-20">전산재고</th>
|
||||
<th class="w-20">실사재고</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($overviewRows !== []): ?>
|
||||
<?php $prevBagName = null; ?>
|
||||
<?php foreach ($overviewRows as $row): ?>
|
||||
<?php
|
||||
$itemId = (int) ($row['bisi_idx'] ?? 0);
|
||||
$bagName = (string) ($row['bisi_bag_name'] ?? '');
|
||||
$showBagName = $prevBagName !== $bagName;
|
||||
$prevBagName = $bagName;
|
||||
$boxCode = (string) ($row['box_code'] ?? '');
|
||||
$isSelected = $itemId === $selectedInspectionItemId
|
||||
&& ($selectedBoxCode === '' || $selectedBoxCode === $boxCode);
|
||||
$url = base_url('bag/inventory/inspection-work?' . http_build_query([
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'bis_id' => $selectedInspectionId,
|
||||
'item_code' => $itemCode,
|
||||
'view_type' => $viewType,
|
||||
'sel_item_id' => $itemId,
|
||||
'sel_box_code' => $boxCode,
|
||||
'sel_pack_code' => '',
|
||||
]));
|
||||
?>
|
||||
<tr class="<?= $isSelected ? 'bg-blue-100' : 'cursor-pointer hover:bg-blue-50' ?>" onclick="window.location.href='<?= esc($url, 'attr') ?>'">
|
||||
<td class="text-center"><?= esc((string) ($row['bis_work_date'] ?? '')) ?></td>
|
||||
<td class="pl-2"><?= $showBagName ? esc($bagName) : '' ?></td>
|
||||
<td class="text-center"><?= esc((string) ($row['box_code'] ?? '')) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) ($row['bisi_system_qty'] ?? 0)) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) ($row['bisi_actual_qty'] ?? 0)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr><td colspan="5" class="text-center text-gray-400 py-4">조회 결과가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="bg-gray-100 font-semibold">
|
||||
<td class="text-center" colspan="3">합계</td>
|
||||
<td class="text-right pr-2"><?= number_format($overviewTotalQty) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format($overviewTotalActual) ?></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">실사 선별 품목(선택 박스 조회)</div>
|
||||
<form method="post" action="<?= base_url('bag/inventory/inspection-select/save') ?>" id="inspection-save-form">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="bisi_idx" value="<?= esc((string) $selectedInspectionItemId) ?>">
|
||||
<input type="hidden" name="bis_id" value="<?= esc((string) $selectedInspectionId) ?>">
|
||||
<input type="hidden" name="start_date" value="<?= esc($startDate) ?>">
|
||||
<input type="hidden" name="end_date" value="<?= esc($endDate) ?>">
|
||||
<input type="hidden" name="item_code" value="<?= esc($itemCode) ?>">
|
||||
<input type="hidden" name="view_type" value="<?= esc($viewType) ?>">
|
||||
<input type="hidden" name="sel_item_id" value="<?= esc((string) $selectedInspectionItemId) ?>">
|
||||
<input type="hidden" name="sel_box_code" value="<?= esc($selectedBoxCode) ?>">
|
||||
<input type="hidden" name="sel_pack_code" value="<?= esc($selectedPackCode) ?>">
|
||||
<input type="hidden" name="pack_actual_json" value="">
|
||||
<div class="overflow-auto max-h-[260px]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>팩코드</th>
|
||||
<th class="w-16">포장량</th>
|
||||
<th class="w-16">재고</th>
|
||||
<th class="w-20">실사재고</th>
|
||||
<th class="w-16">차이</th>
|
||||
<th>낱장(시작)</th>
|
||||
<th>낱장(끝)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($boxRows !== []): ?>
|
||||
<?php foreach ($boxRows as $row): ?>
|
||||
<?php
|
||||
$code = (string) ($row['bisp_box_code'] ?? '');
|
||||
$packCode = (string) ($row['bisp_pack_code'] ?? '');
|
||||
$systemQty = (int) ($row['bisp_sheet_qty'] ?? 0);
|
||||
$actualQty = isset($row['bisp_actual_qty']) ? (int) $row['bisp_actual_qty'] : $systemQty;
|
||||
$url = base_url('bag/inventory/inspection-work?' . http_build_query([
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'bis_id' => $selectedInspectionId,
|
||||
'item_code' => $itemCode,
|
||||
'view_type' => $viewType,
|
||||
'sel_item_id' => $selectedInspectionItemId,
|
||||
'sel_box_code' => $code,
|
||||
'sel_pack_code' => $packCode,
|
||||
]));
|
||||
$isSelected = $selectedBoxCode === $code && $selectedPackCode === $packCode;
|
||||
?>
|
||||
<tr class="<?= $isSelected ? 'bg-blue-100' : 'cursor-pointer hover:bg-blue-50' ?>" onclick="window.location.href='<?= esc($url, 'attr') ?>'">
|
||||
<td class="pl-2"><?= esc($packCode) ?></td>
|
||||
<td class="text-center js-pack-system"><?= number_format($systemQty) ?></td>
|
||||
<td class="text-right pr-2 js-pack-stock"><?= number_format($systemQty) ?></td>
|
||||
<td class="text-right pr-2">
|
||||
<input type="number" min="0"
|
||||
value="<?= esc((string) $actualQty) ?>"
|
||||
data-pack-idx="<?= esc((string) ($row['bisp_idx'] ?? 0), 'attr') ?>"
|
||||
data-original-value="<?= esc((string) $systemQty, 'attr') ?>"
|
||||
data-system-qty="<?= esc((string) $systemQty, 'attr') ?>"
|
||||
class="border border-gray-300 rounded px-1 py-0.5 w-20 text-right"
|
||||
onclick="event.stopPropagation();">
|
||||
</td>
|
||||
<?php $diff = (int) ($row['bisp_diff_qty'] ?? 0); ?>
|
||||
<td class="text-right pr-2 js-pack-diff <?= $diff === 0 ? '' : ($diff > 0 ? 'text-blue-700' : 'text-red-700') ?>"><?= number_format($diff) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($row['bisp_sheet_start_code'] ?? '')) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($row['bisp_sheet_end_code'] ?? '')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr><td colspan="7" class="text-center text-gray-400 py-4">선택된 품목이 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="bg-gray-100 font-semibold">
|
||||
<td class="text-center" colspan="2">합계</td>
|
||||
<td class="text-right pr-2"><?= number_format($packTotalQty) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format($packTotalActual) ?></td>
|
||||
<td class="text-right pr-2 <?= $packTotalDiff === 0 ? '' : ($packTotalDiff > 0 ? 'text-blue-700' : 'text-red-700') ?>"><?= number_format($packTotalDiff) ?></td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div class="p-2 border-t border-gray-300 flex items-center justify-between">
|
||||
<span class="text-xs text-gray-500">선택 품목의 팩 실사수량을 입력 후 저장하세요.</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-3 py-1 rounded-sm text-sm">실사 저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">실사 선별 내용(선택 낱장 코드 조회)</div>
|
||||
<div class="overflow-auto max-h-[240px]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-12">No</th>
|
||||
<th>낱장</th>
|
||||
<th class="w-16">전산</th>
|
||||
<th class="w-16">실사</th>
|
||||
<th class="w-16">차이</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($sheetRows !== []): ?>
|
||||
<?php foreach ($sheetRows as $row): ?>
|
||||
<?php
|
||||
$sSystem = (int) ($row['biss_system_qty'] ?? 1);
|
||||
$sActual = (int) ($row['biss_actual_qty'] ?? 0);
|
||||
$sDiff = (int) ($row['biss_diff_qty'] ?? 0);
|
||||
?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc((string) ($row['no'] ?? '')) ?></td>
|
||||
<td class="pl-2"><?= esc((string) ($row['biss_sheet_code'] ?? '')) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format($sSystem) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format($sActual) ?></td>
|
||||
<td class="text-right pr-2 <?= $sDiff === 0 ? '' : ($sDiff > 0 ? 'text-blue-700' : 'text-red-700') ?>"><?= number_format($sDiff) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr><td colspan="5" class="text-center text-gray-400 py-4">선택된 팩/박스가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="bg-gray-100 font-semibold">
|
||||
<td class="text-center" colspan="2">합계</td>
|
||||
<td class="text-right pr-2"><?= number_format($sheetTotalQty) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format($sheetTotalActual) ?></td>
|
||||
<td class="text-right pr-2 <?= $sheetTotalDiff === 0 ? '' : ($sheetTotalDiff > 0 ? 'text-blue-700' : 'text-red-700') ?>"><?= number_format($sheetTotalDiff) ?></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border border-gray-300 bg-white p-2 text-sm text-gray-600">
|
||||
실사 저장 시 차이수량이 즉시 장부 재고에 반영됩니다.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="inspection-popup" class="fixed inset-0 bg-black/40 hidden items-center justify-center z-[999]">
|
||||
<div class="bg-white border border-gray-400 w-[min(720px,95vw)] max-h-[90vh] overflow-auto p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-lg font-bold">실사 선별</h3>
|
||||
<button type="button" id="close-inspection-popup" class="text-gray-600 hover:text-gray-900">닫기</button>
|
||||
</div>
|
||||
<form method="post" action="<?= base_url('bag/inventory/inspection-run') ?>" id="inspection-run-form" class="space-y-3">
|
||||
<?= csrf_field() ?>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="font-bold text-gray-700 text-sm">작업 일자</label>
|
||||
<input type="date" name="work_date" value="<?= esc($workDate) ?>" class="border border-gray-300 rounded px-2 py-1 text-sm">
|
||||
</div>
|
||||
<p class="text-red-600 font-semibold">바코드가 없는 봉투는 실사에서 제외 됩니다.</p>
|
||||
<div class="overflow-auto border border-gray-300 max-h-[55vh]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr><th>종류</th><th class="w-20">선택구분</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($popupItems as $row): ?>
|
||||
<?php $hasBarcode = (bool) ($row['has_barcode'] ?? false); ?>
|
||||
<tr class="<?= $hasBarcode ? '' : 'bg-gray-50 text-gray-400' ?>">
|
||||
<td class="pl-2"><?= esc((string) ($row['bag_name'] ?? '')) ?></td>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" name="bag_codes[]" value="<?= esc((string) ($row['bag_code'] ?? ''), 'attr') ?>" <?= $hasBarcode ? '' : 'disabled' ?>>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-5 py-1.5 rounded-sm text-sm">실행</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const popup = document.getElementById('inspection-popup');
|
||||
const openBtn = document.getElementById('open-inspection-popup');
|
||||
const closeBtn = document.getElementById('close-inspection-popup');
|
||||
if (openBtn && popup) {
|
||||
openBtn.addEventListener('click', () => {
|
||||
popup.classList.remove('hidden');
|
||||
popup.classList.add('flex');
|
||||
});
|
||||
}
|
||||
if (closeBtn && popup) {
|
||||
closeBtn.addEventListener('click', () => {
|
||||
popup.classList.add('hidden');
|
||||
popup.classList.remove('flex');
|
||||
});
|
||||
}
|
||||
const form = document.getElementById('inspection-run-form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', (event) => {
|
||||
if (!window.confirm('전산 선별 처리를 실행하시겠습니까?')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const saveForm = document.getElementById('inspection-save-form');
|
||||
if (saveForm) {
|
||||
const formatNumber = (value) => {
|
||||
const n = Number.isFinite(value) ? value : 0;
|
||||
return n.toLocaleString('ko-KR');
|
||||
};
|
||||
const updateDiff = (input) => {
|
||||
const row = input.closest('tr');
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
const system = parseInt(String(input.getAttribute('data-system-qty') ?? '0'), 10) || 0;
|
||||
const actual = Math.max(0, parseInt(String(input.value ?? '0'), 10) || 0);
|
||||
const diff = actual - system;
|
||||
const diffCell = row.querySelector('.js-pack-diff');
|
||||
if (!diffCell) {
|
||||
return;
|
||||
}
|
||||
diffCell.textContent = formatNumber(diff);
|
||||
diffCell.classList.remove('text-blue-700', 'text-red-700');
|
||||
if (diff > 0) {
|
||||
diffCell.classList.add('text-blue-700');
|
||||
} else if (diff < 0) {
|
||||
diffCell.classList.add('text-red-700');
|
||||
}
|
||||
};
|
||||
|
||||
const qtyInputs = saveForm.querySelectorAll('input[data-pack-idx]');
|
||||
qtyInputs.forEach((input) => {
|
||||
input.addEventListener('input', () => updateDiff(input));
|
||||
updateDiff(input);
|
||||
});
|
||||
|
||||
saveForm.addEventListener('submit', () => {
|
||||
const payload = {};
|
||||
qtyInputs.forEach((input) => {
|
||||
const idx = String(input.getAttribute('data-pack-idx') ?? '').trim();
|
||||
if (idx === '') {
|
||||
return;
|
||||
}
|
||||
payload[idx] = Math.max(0, parseInt(String(input.value ?? '0'), 10) || 0);
|
||||
});
|
||||
const jsonInput = saveForm.querySelector('input[name="pack_actual_json"]');
|
||||
if (jsonInput) {
|
||||
jsonInput.value = JSON.stringify(payload);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
273
app/Views/bag/inventory_inspection_select_overview.php
Normal file
273
app/Views/bag/inventory_inspection_select_overview.php
Normal file
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
$startDate = (string) ($startDate ?? date('Y-m-01'));
|
||||
$endDate = (string) ($endDate ?? date('Y-m-d'));
|
||||
$workDate = (string) ($workDate ?? date('Y-m-d'));
|
||||
$itemCode = (string) ($itemCode ?? '');
|
||||
$viewType = (string) ($viewType ?? 'box');
|
||||
$inspectionRuns = is_array($inspectionRuns ?? null) ? $inspectionRuns : [];
|
||||
$items = is_array($items ?? null) ? $items : [];
|
||||
$popupItems = is_array($popupItems ?? null) ? $popupItems : [];
|
||||
$overviewRows = is_array($overviewRows ?? null) ? $overviewRows : [];
|
||||
$boxRows = is_array($boxRows ?? null) ? $boxRows : [];
|
||||
$sheetRows = is_array($sheetRows ?? null) ? $sheetRows : [];
|
||||
$selectedInspectionId = (int) ($selectedInspectionId ?? 0);
|
||||
$selectedInspectionItemId = (int) ($selectedInspectionItemId ?? 0);
|
||||
$selectedBoxCode = (string) ($selectedBoxCode ?? '');
|
||||
$selectedPackCode = (string) ($selectedPackCode ?? '');
|
||||
$overviewTotalQty = 0;
|
||||
foreach ($overviewRows as $row) {
|
||||
$overviewTotalQty += (int) ($row['bisi_system_qty'] ?? 0);
|
||||
}
|
||||
$packTotalQty = 0;
|
||||
foreach ($boxRows as $row) {
|
||||
$packTotalQty += (int) ($row['bisp_sheet_qty'] ?? 0);
|
||||
}
|
||||
$sheetTotalQty = 0;
|
||||
foreach ($sheetRows as $row) {
|
||||
$sheetTotalQty += (int) ($row['biss_system_qty'] ?? 0);
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="space-y-2">
|
||||
<section class="border border-gray-300 bg-white p-2">
|
||||
<form method="get" class="flex flex-wrap items-end justify-between gap-2 text-sm">
|
||||
<div class="flex flex-wrap items-end gap-2">
|
||||
<label class="font-bold text-gray-700">실사기간</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate) ?>" class="border border-gray-300 rounded px-2 py-1">
|
||||
<span>~</span>
|
||||
<input type="date" name="end_date" value="<?= esc($endDate) ?>" class="border border-gray-300 rounded px-2 py-1">
|
||||
|
||||
<label class="font-bold text-gray-700 ml-2">실사품목</label>
|
||||
<select name="item_code" class="border border-gray-300 rounded px-2 py-1 min-w-[11rem]">
|
||||
<option value="">전체</option>
|
||||
<?php foreach ($items as $it): ?>
|
||||
<?php $code = (string) ($it['bag_code'] ?? ''); ?>
|
||||
<option value="<?= esc($code) ?>" <?= $itemCode === $code ? 'selected' : '' ?>><?= esc((string) ($it['bag_name'] ?? '')) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<label class="font-bold text-gray-700 ml-2">조회구분</label>
|
||||
<select name="view_type" class="border border-gray-300 rounded px-2 py-1 min-w-[7rem]">
|
||||
<option value="box" <?= $viewType === 'box' ? 'selected' : '' ?>>박스별</option>
|
||||
<option value="pack" <?= $viewType === 'pack' ? 'selected' : '' ?>>팩별</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-3 py-1 rounded-sm">조회</button>
|
||||
<button type="button" id="open-inspection-popup" class="border border-blue-300 text-blue-700 px-3 py-1 rounded-sm hover:bg-blue-50">실사 선별</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="mt-1 text-xs text-blue-700">※ 실사 선별 처리 결과를 조회하는 화면입니다(읽기 전용).</p>
|
||||
</section>
|
||||
|
||||
<section class="grid grid-cols-1 xl:grid-cols-2 gap-2">
|
||||
<div class="border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">실사 선별자</div>
|
||||
<div class="overflow-auto max-h-[500px]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-24">실사일자</th>
|
||||
<th>종류</th>
|
||||
<th class="w-40">박스</th>
|
||||
<th class="w-20">전산재고</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($overviewRows !== []): ?>
|
||||
<?php $prevBagName = null; ?>
|
||||
<?php foreach ($overviewRows as $row): ?>
|
||||
<?php
|
||||
$itemId = (int) ($row['bisi_idx'] ?? 0);
|
||||
$bagName = (string) ($row['bisi_bag_name'] ?? '');
|
||||
$showBagName = $prevBagName !== $bagName;
|
||||
$prevBagName = $bagName;
|
||||
$isSelected = $itemId === $selectedInspectionItemId;
|
||||
$url = base_url('bag/inventory/inspection-select?' . http_build_query([
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'bis_id' => $selectedInspectionId,
|
||||
'item_code' => $itemCode,
|
||||
'view_type' => $viewType,
|
||||
'sel_item_id' => $itemId,
|
||||
]));
|
||||
?>
|
||||
<tr class="<?= $isSelected ? 'bg-blue-100' : 'cursor-pointer hover:bg-blue-50' ?>" onclick="window.location.href='<?= esc($url, 'attr') ?>'">
|
||||
<td class="text-center"><?= esc((string) ($row['bis_work_date'] ?? '')) ?></td>
|
||||
<td class="pl-2"><?= $showBagName ? esc($bagName) : '' ?></td>
|
||||
<td class="text-center"><?= esc((string) ($row['box_code'] ?? '')) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) ($row['bisi_system_qty'] ?? 0)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr><td colspan="4" class="text-center text-gray-400 py-4">조회 결과가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="bg-gray-100 font-semibold">
|
||||
<td class="text-center" colspan="3">합계</td>
|
||||
<td class="text-right pr-2"><?= number_format($overviewTotalQty) ?></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">실사 선별 품목(읽기 전용)</div>
|
||||
<div class="overflow-auto max-h-[260px]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>팩코드</th>
|
||||
<th class="w-16">포장량</th>
|
||||
<th class="w-16">재고</th>
|
||||
<th>낱장(시작)</th>
|
||||
<th>낱장(끝)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($boxRows !== []): ?>
|
||||
<?php foreach ($boxRows as $row): ?>
|
||||
<?php
|
||||
$code = (string) ($row['bisp_box_code'] ?? '');
|
||||
$packCode = (string) ($row['bisp_pack_code'] ?? '');
|
||||
$systemQty = (int) ($row['bisp_sheet_qty'] ?? 0);
|
||||
$url = base_url('bag/inventory/inspection-select?' . http_build_query([
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'bis_id' => $selectedInspectionId,
|
||||
'item_code' => $itemCode,
|
||||
'view_type' => $viewType,
|
||||
'sel_item_id' => $selectedInspectionItemId,
|
||||
'sel_box_code' => $code,
|
||||
'sel_pack_code' => $packCode,
|
||||
]));
|
||||
$isSelected = $selectedBoxCode === $code && $selectedPackCode === $packCode;
|
||||
?>
|
||||
<tr class="<?= $isSelected ? 'bg-blue-100' : 'cursor-pointer hover:bg-blue-50' ?>" onclick="window.location.href='<?= esc($url, 'attr') ?>'">
|
||||
<td class="pl-2"><?= esc($packCode) ?></td>
|
||||
<td class="text-center"><?= number_format($systemQty) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format($systemQty) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($row['bisp_sheet_start_code'] ?? '')) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($row['bisp_sheet_end_code'] ?? '')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr><td colspan="5" class="text-center text-gray-400 py-4">선택된 품목이 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="bg-gray-100 font-semibold">
|
||||
<td class="text-center" colspan="2">합계</td>
|
||||
<td class="text-right pr-2"><?= number_format($packTotalQty) ?></td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">실사 선별 내용(읽기 전용)</div>
|
||||
<div class="overflow-auto max-h-[240px]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-12">No</th>
|
||||
<th>낱장</th>
|
||||
<th class="w-16">수량</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($sheetRows !== []): ?>
|
||||
<?php foreach ($sheetRows as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc((string) ($row['no'] ?? '')) ?></td>
|
||||
<td class="pl-2"><?= esc((string) ($row['biss_sheet_code'] ?? '')) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) ($row['biss_system_qty'] ?? 0)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr><td colspan="3" class="text-center text-gray-400 py-4">선택된 팩/박스가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="bg-gray-100 font-semibold">
|
||||
<td class="text-center" colspan="2">합계</td>
|
||||
<td class="text-right pr-2"><?= number_format($sheetTotalQty) ?></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="inspection-popup" class="fixed inset-0 bg-black/40 hidden items-center justify-center z-[999]">
|
||||
<div class="bg-white border border-gray-400 w-[min(720px,95vw)] max-h-[90vh] overflow-auto p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-lg font-bold">실사 선별</h3>
|
||||
<button type="button" id="close-inspection-popup" class="text-gray-600 hover:text-gray-900">닫기</button>
|
||||
</div>
|
||||
<form method="post" action="<?= base_url('bag/inventory/inspection-run') ?>" id="inspection-run-form" class="space-y-3">
|
||||
<?= csrf_field() ?>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="font-bold text-gray-700 text-sm">작업 일자</label>
|
||||
<input type="date" name="work_date" value="<?= esc($workDate) ?>" class="border border-gray-300 rounded px-2 py-1 text-sm">
|
||||
</div>
|
||||
<p class="text-red-600 font-semibold">바코드가 없는 봉투는 실사에서 제외 됩니다.</p>
|
||||
<div class="overflow-auto border border-gray-300 max-h-[55vh]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr><th>종류</th><th class="w-20">선택구분</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($popupItems as $row): ?>
|
||||
<?php $hasBarcode = (bool) ($row['has_barcode'] ?? false); ?>
|
||||
<tr class="<?= $hasBarcode ? '' : 'bg-gray-50 text-gray-400' ?>">
|
||||
<td class="pl-2"><?= esc((string) ($row['bag_name'] ?? '')) ?></td>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" name="bag_codes[]" value="<?= esc((string) ($row['bag_code'] ?? ''), 'attr') ?>" <?= $hasBarcode ? '' : 'disabled' ?>>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-5 py-1.5 rounded-sm text-sm">실행</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const popup = document.getElementById('inspection-popup');
|
||||
const openBtn = document.getElementById('open-inspection-popup');
|
||||
const closeBtn = document.getElementById('close-inspection-popup');
|
||||
if (openBtn && popup) {
|
||||
openBtn.addEventListener('click', () => {
|
||||
popup.classList.remove('hidden');
|
||||
popup.classList.add('flex');
|
||||
});
|
||||
}
|
||||
if (closeBtn && popup) {
|
||||
closeBtn.addEventListener('click', () => {
|
||||
popup.classList.add('hidden');
|
||||
popup.classList.remove('flex');
|
||||
});
|
||||
}
|
||||
const form = document.getElementById('inspection-run-form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', (event) => {
|
||||
if (!window.confirm('전산 선별 처리를 실행하시겠습니까?')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
106
app/Views/bag/order_change.php
Normal file
106
app/Views/bag/order_change.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
$month = $month ?? date('Y-m');
|
||||
$hubMode = in_array(($hubMode ?? 'meta'), ['price', 'meta', 'delete'], true) ? (string) ($hubMode ?? 'meta') : 'meta';
|
||||
$monthOptionValues = [];
|
||||
$y = (int) substr($month, 0, 4);
|
||||
for ($year = $y - 2; $year <= $y + 2; $year++) {
|
||||
for ($m = 1; $m <= 12; $m++) {
|
||||
$monthValue = sprintf('%04d-%02d', $year, $m);
|
||||
$monthOptionValues[] = ['value' => $monthValue, 'label' => $year . '년 ' . $m . '월'];
|
||||
}
|
||||
}
|
||||
$statusMap = ['normal' => '정상', 'cancelled' => '취소', 'deleted' => '삭제'];
|
||||
?>
|
||||
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="text-sm font-bold text-gray-700">발주 변경</span>
|
||||
<a href="<?= base_url('bag/purchase-inbound') ?>" class="text-sm text-gray-600 hover:underline">발주 입고 관리로</a>
|
||||
</section>
|
||||
|
||||
<div class="mt-2 space-y-3">
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="border border-green-300 bg-green-50 text-green-800 px-3 py-2 text-sm"><?= esc((string) session()->getFlashdata('success')) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="border border-red-300 bg-red-50 text-red-800 px-3 py-2 text-sm"><?= esc((string) session()->getFlashdata('error')) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="get" action="<?= base_url('bag/order/change') ?>" class="border border-gray-300 bg-white p-3 flex flex-wrap items-end gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="hub_month" class="text-sm font-bold text-gray-700">발주월</label>
|
||||
<select id="hub_month" name="month" class="border border-gray-300 rounded px-2 py-1 w-44 text-sm">
|
||||
<?php foreach ($monthOptionValues as $opt): ?>
|
||||
<option value="<?= esc($opt['value']) ?>" <?= $opt['value'] === $month ? 'selected' : '' ?>><?= esc($opt['label']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<fieldset class="border border-gray-200 rounded px-3 py-2">
|
||||
<legend class="text-xs font-bold text-gray-600 px-1">변경 구분</legend>
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
||||
<label class="inline-flex items-center gap-1 cursor-pointer">
|
||||
<input type="radio" name="hub_mode" value="price" <?= $hubMode === 'price' ? 'checked' : '' ?> />
|
||||
<span>발주·도매·판매 단가</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-1 cursor-pointer">
|
||||
<input type="radio" name="hub_mode" value="meta" <?= $hubMode === 'meta' ? 'checked' : '' ?> />
|
||||
<span>업체·수수료·협회·발주</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-1 cursor-pointer">
|
||||
<input type="radio" name="hub_mode" value="delete" <?= $hubMode === 'delete' ? 'checked' : '' ?> />
|
||||
<span>발주 삭제</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm shadow hover:opacity-90">조회</button>
|
||||
</form>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-2">
|
||||
<section class="lg:col-span-5 border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 목록 (발주일 · 제작업체)</div>
|
||||
<div class="overflow-auto max-h-[min(420px,60vh)]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-28">발주일</th>
|
||||
<th>제작업체</th>
|
||||
<th class="w-20">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($monthOrders ?? []) as $row): ?>
|
||||
<?php
|
||||
$canEdit = ((string) ($row->bo_status ?? '')) === 'normal';
|
||||
$href = base_url('bag/order/revise/' . (int) $row->bo_idx . '?change_mode=' . rawurlencode($hubMode));
|
||||
?>
|
||||
<tr class="<?= $canEdit ? '' : 'opacity-70' ?>">
|
||||
<td class="text-center">
|
||||
<?php if ($canEdit): ?>
|
||||
<a href="<?= esc($href) ?>" class="text-blue-600 hover:underline font-medium"><?= esc((string) ($row->bo_order_date ?? '')) ?></a>
|
||||
<?php else: ?>
|
||||
<span><?= esc((string) ($row->bo_order_date ?? '')) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($companyMap[(int) ($row->bo_company_idx ?? 0)] ?? '-')) ?></td>
|
||||
<td class="text-center text-xs"><?= esc((string) ($statusMap[(string) ($row->bo_status ?? '')] ?? $row->bo_status)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($monthOrders)): ?>
|
||||
<tr><td colspan="3" class="text-center text-gray-400 py-6">해당 월 발주가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="lg:col-span-7 border border-dashed border-gray-300 bg-gray-50 p-4 text-sm text-gray-600">
|
||||
<p class="font-bold text-gray-800 mb-2">안내</p>
|
||||
<ul class="list-disc pl-5 space-y-1">
|
||||
<li><strong class="text-gray-900">발주 삭제</strong>를 선택하고 <strong class="text-gray-900">조회</strong>하면 발주 삭제 화면으로 이동합니다 (해당 월에 정상 발주가 있을 때).</li>
|
||||
<li>그 외 변경 구분에서는 왼쪽 목록에서 <strong class="text-gray-900">발주일</strong>을 클릭하면 선택한 <strong class="text-gray-900">변경 구분</strong>으로 발주 변경 화면이 열립니다.</li>
|
||||
<li><strong class="text-gray-900">발주·도매·판매 단가</strong>: 품목별 단가를 수정해 저장합니다 (발주 변경 시에만).</li>
|
||||
<li><strong class="text-gray-900">업체·수수료·협회·발주</strong>: 발주일·입고처·수량 등을 수정해 저장합니다.</li>
|
||||
<li><strong class="text-gray-900">발주 삭제</strong> 화면에서 목록을 선택해 삭제 처리합니다 (복구 불가에 가깝게 동작하므로 확인 후 진행하세요).</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,7 +58,11 @@
|
||||
<section class="mt-4">
|
||||
<div class="flex items-center justify-between mb-2 border-b pb-1">
|
||||
<h3 class="text-base font-bold text-gray-700">입고 현황</h3>
|
||||
<a href="<?= base_url('bag/receiving/create') ?>" class="bg-btn-search text-white px-3 py-1.5 rounded-sm text-sm">입고 처리</a>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= base_url('bag/receiving/scanner') ?>" class="bg-btn-search text-white px-3 py-1.5 rounded-sm text-sm">입고 처리</a>
|
||||
<a href="<?= base_url('bag/receiving/batch') ?>" class="border border-gray-300 text-gray-700 px-3 py-1.5 rounded-sm text-sm hover:bg-gray-50">일괄 입고</a>
|
||||
<a href="<?= base_url('bag/receiving/status') ?>" class="border border-gray-300 text-gray-700 px-3 py-1.5 rounded-sm text-sm hover:bg-gray-50">입고 현황 리포트</a>
|
||||
</div>
|
||||
</div>
|
||||
<table class="data-table">
|
||||
<thead><tr>
|
||||
|
||||
119
app/Views/bag/receiving_batch.php
Normal file
119
app/Views/bag/receiving_batch.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="text-sm font-bold text-gray-700">일괄 입고</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= base_url('bag/receiving/scanner') ?>" class="border border-gray-300 text-gray-700 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">스캐너 입고</a>
|
||||
<a href="<?= base_url('bag/receiving/status') ?>" class="border border-gray-300 text-gray-700 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">입고 현황</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="mt-2 border border-green-300 bg-green-50 text-green-800 px-3 py-2 text-sm"><?= esc((string) session()->getFlashdata('success')) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="mt-2 border border-red-300 bg-red-50 text-red-800 px-3 py-2 text-sm"><?= esc((string) session()->getFlashdata('error')) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="p-2 bg-white border border-gray-300 mt-2">
|
||||
<form method="get" action="<?= base_url('bag/receiving/batch') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">제작 업체</label>
|
||||
<select name="company_idx" class="border border-gray-300 rounded px-2 py-1 text-sm w-56">
|
||||
<option value="0">전 체</option>
|
||||
<?php foreach (($companies ?? []) as $company): ?>
|
||||
<option value="<?= (int) $company->cp_idx ?>" <?= (int) ($companyIdx ?? 0) === (int) $company->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($company->cp_name ?? '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<form action="<?= base_url('bag/receiving/batch/store') ?>" method="post" class="mt-2 space-y-2">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="company_idx" value="<?= (int) ($companyIdx ?? 0) ?>" />
|
||||
|
||||
<section class="p-2 bg-white border border-gray-300">
|
||||
<div class="grid grid-cols-1 xl:grid-cols-12 gap-2 items-end">
|
||||
<div class="xl:col-span-3 flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 shrink-0 w-28">인수자 (대행소)</label>
|
||||
<select name="br_receiver_ref" class="border border-gray-300 rounded px-2 py-1 text-sm w-full" required>
|
||||
<?php foreach (($receiverOptions ?? []) as $opt): ?>
|
||||
<option value="<?= esc((string) ($opt['ref'] ?? '')) ?>" <?= (string) ($receiverRef ?? '') === (string) ($opt['ref'] ?? '') ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($opt['label'] ?? '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="xl:col-span-3 flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 shrink-0 w-28">인계자 (제작업체)</label>
|
||||
<select name="br_sender_idx" class="border border-gray-300 rounded px-2 py-1 text-sm w-full">
|
||||
<?php foreach (($senders ?? []) as $sender): ?>
|
||||
<option value="<?= (int) ($sender->mg_idx ?? 0) ?>" <?= (int) ($senderIdx ?? 0) === (int) ($sender->mg_idx ?? 0) ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($sender->mg_name ?? '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="xl:col-span-2 flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 w-16">입고일</label>
|
||||
<input type="date" name="br_receive_date" value="<?= esc((string) old('br_receive_date', date('Y-m-d'))) ?>" class="border border-gray-300 rounded px-2 py-1 text-sm w-full" required />
|
||||
</div>
|
||||
<div class="xl:col-span-2">
|
||||
<button type="submit" class="w-full border border-blue-600 text-blue-700 px-2 py-1 rounded-sm text-sm hover:bg-blue-50">입고 처리</button>
|
||||
</div>
|
||||
<div class="xl:col-span-2 text-xs text-gray-500">체크한 LOT-봉투 행의 미입고량을 전부 입고 처리합니다.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="border border-gray-300 overflow-auto bg-white">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-12"><input type="checkbox" id="check-all" /></th>
|
||||
<th>발주일자</th>
|
||||
<th>봉투종류</th>
|
||||
<th>발주량(매)</th>
|
||||
<th>미입고량(매)</th>
|
||||
<th>제작업체</th>
|
||||
<th>LOT NO</th>
|
||||
<th>발주NO</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($rows ?? []) as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" name="selected_rows[]" value="<?= esc((string) ($row['row_key'] ?? '')) ?>" />
|
||||
</td>
|
||||
<td class="text-center"><?= esc((string) ($row['order_date'] ?? '')) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($row['bag_name'] ?? '')) ?></td>
|
||||
<td class="text-right"><?= number_format((int) ($row['order_qty_sheet'] ?? 0)) ?></td>
|
||||
<td class="text-right text-blue-700 font-semibold"><?= number_format((int) ($row['pending_qty_sheet'] ?? 0)) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($row['company_name'] ?? '')) ?></td>
|
||||
<td class="text-center font-mono"><?= esc((string) ($row['lot_no'] ?? '')) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($row['order_no'] ?? '')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($rows ?? [])): ?>
|
||||
<tr><td colspan="8" class="text-center text-gray-400 py-4">일괄 입고 가능한 미입고 행이 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= base_url('bag/purchase-inbound') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const all = document.getElementById('check-all');
|
||||
if (!all) return;
|
||||
all.addEventListener('change', () => {
|
||||
document.querySelectorAll('input[name="selected_rows[]"]').forEach((el) => {
|
||||
el.checked = all.checked;
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
153
app/Views/bag/receiving_scanner.php
Normal file
153
app/Views/bag/receiving_scanner.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="text-sm font-bold text-gray-700">발주 입고(스캐너 대체 수동입력)</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= base_url('bag/receiving/batch') ?>" class="border border-gray-300 text-gray-700 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">일괄 입고</a>
|
||||
<a href="<?= base_url('bag/receiving/status') ?>" class="border border-gray-300 text-gray-700 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">입고 현황</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="mt-2 border border-green-300 bg-green-50 text-green-800 px-3 py-2 text-sm"><?= esc((string) session()->getFlashdata('success')) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="mt-2 border border-red-300 bg-red-50 text-red-800 px-3 py-2 text-sm"><?= esc((string) session()->getFlashdata('error')) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="p-2 bg-white border border-gray-300 mt-2">
|
||||
<form method="get" action="<?= base_url('bag/receiving/scanner') ?>" class="flex flex-wrap items-end gap-2">
|
||||
<div class="flex flex-col min-w-[14rem] max-w-[22rem]">
|
||||
<label class="text-xs text-gray-500 mb-1">제작업체</label>
|
||||
<select name="company_idx" class="border border-gray-300 rounded px-2 py-1.5 text-sm bg-white">
|
||||
<option value="0">제작업체 선택</option>
|
||||
<?php foreach (($companies ?? []) as $company): ?>
|
||||
<option value="<?= (int) ($company->cp_idx ?? 0) ?>" <?= (int) ($companyIdx ?? 0) === (int) ($company->cp_idx ?? 0) ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($company->cp_name ?? '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm shrink-0">조회</button>
|
||||
</form>
|
||||
<?php if ((int) ($companyIdx ?? 0) <= 0): ?>
|
||||
<p class="text-xs text-gray-600 mt-2">제작업체를 선택하면 해당 업체 발주 중 미입고 내역을 조회합니다.</p>
|
||||
<?php elseif (empty($rows ?? [])): ?>
|
||||
<p class="text-xs text-amber-700 mt-2">미입고 잔량이 있는 발주가 없습니다. 발주 등록 후 다시 확인해 주세요.</p>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<form action="<?= base_url('bag/receiving/scanner/store') ?>" method="post" class="mt-2 space-y-2">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="company_idx" value="<?= (int) ($companyIdx ?? 0) ?>" />
|
||||
|
||||
<section class="p-2 bg-white border border-gray-300">
|
||||
<div class="grid grid-cols-1 xl:grid-cols-12 gap-2 items-end">
|
||||
<div class="xl:col-span-3 flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 shrink-0 w-28">인수자 (대행소)</label>
|
||||
<select name="br_receiver_ref" class="border border-gray-300 rounded px-2 py-1 text-sm w-full" required>
|
||||
<?php foreach (($receiverOptions ?? []) as $opt): ?>
|
||||
<option value="<?= esc((string) ($opt['ref'] ?? '')) ?>" <?= (string) ($receiverRef ?? '') === (string) ($opt['ref'] ?? '') ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($opt['label'] ?? '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="xl:col-span-3 flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 shrink-0 w-28">인계자 (제작업체)</label>
|
||||
<select name="br_sender_idx" class="border border-gray-300 rounded px-2 py-1 text-sm w-full">
|
||||
<?php foreach (($senders ?? []) as $sender): ?>
|
||||
<option value="<?= (int) ($sender->mg_idx ?? 0) ?>" <?= (int) ($senderIdx ?? 0) === (int) ($sender->mg_idx ?? 0) ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($sender->mg_name ?? '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="xl:col-span-2 flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 w-16">입고일</label>
|
||||
<input type="date" name="br_receive_date" value="<?= esc((string) old('br_receive_date', date('Y-m-d'))) ?>" class="border border-gray-300 rounded px-2 py-1 text-sm w-full" required />
|
||||
</div>
|
||||
<div class="xl:col-span-2">
|
||||
<button type="submit" class="w-full border border-blue-600 text-blue-700 px-2 py-1 rounded-sm text-sm hover:bg-blue-50">입고 처리</button>
|
||||
</div>
|
||||
<div class="xl:col-span-2 text-xs text-gray-500">상단에서 제작업체를 조회한 뒤, 아래에서 입고량(매)을 입력해 저장합니다.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="border border-gray-300 overflow-auto bg-white">
|
||||
<table class="w-full data-table text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>발주일자</th>
|
||||
<th>봉투종류</th>
|
||||
<th>발주량(매)</th>
|
||||
<th>미입고량(매)</th>
|
||||
<th>입고량(매)</th>
|
||||
<th>제작업체</th>
|
||||
<th>LOT NO</th>
|
||||
<th>발주NO</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($rows ?? []) as $row): ?>
|
||||
<?php $k = (string) ($row['row_key'] ?? ''); ?>
|
||||
<tr data-row-key="<?= esc($k) ?>" data-lot-no="<?= esc((string) ($row['lot_no'] ?? '')) ?>" data-bag-code="<?= esc((string) ($row['bag_code'] ?? '')) ?>" data-total-per-box="<?= (int) ($row['total_per_box'] ?? 1) ?>" data-pack-per-sheet="<?= (int) ($row['pack_per_sheet'] ?? 1) ?>" data-pending-original="<?= (int) ($row['pending_qty_sheet'] ?? 0) ?>">
|
||||
<td class="text-center"><?= esc((string) ($row['order_date'] ?? '')) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($row['bag_name'] ?? '')) ?></td>
|
||||
<td class="text-right"><?= number_format((int) ($row['order_qty_sheet'] ?? 0)) ?></td>
|
||||
<td class="text-right pending-cell"><?= number_format((int) ($row['pending_qty_sheet'] ?? 0)) ?></td>
|
||||
<td class="text-right">
|
||||
<input type="number" min="0" max="<?= (int) ($row['pending_qty_sheet'] ?? 0) ?>" name="receive_qty_sheet[<?= esc($k) ?>]" value="<?= esc((string) old('receive_qty_sheet.' . $k, '0')) ?>" class="w-24 border border-gray-300 rounded px-1 py-0.5 text-sm text-right receive-input" />
|
||||
</td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($row['company_name'] ?? '')) ?></td>
|
||||
<td class="text-center font-mono"><?= esc((string) ($row['lot_no'] ?? '')) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($row['order_no'] ?? '')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($rows ?? [])): ?>
|
||||
<tr><td colspan="8" class="text-center text-gray-400 py-4"><?php
|
||||
if ((int) ($companyIdx ?? 0) <= 0) {
|
||||
echo '제작업체를 선택하고 조회해 주세요.';
|
||||
} else {
|
||||
echo '해당 제작업체의 미입고 발주 내역이 없습니다.';
|
||||
}
|
||||
?></td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= base_url('bag/purchase-inbound') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const parseIntSafe = (v) => {
|
||||
const n = Number(String(v ?? '').replace(/,/g, ''));
|
||||
return Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
|
||||
};
|
||||
|
||||
const refreshPending = (row) => {
|
||||
const pendingCell = row.querySelector('.pending-cell');
|
||||
const input = row.querySelector('.receive-input');
|
||||
if (!pendingCell || !input) return;
|
||||
const original = parseIntSafe(row.getAttribute('data-pending-original'));
|
||||
const current = parseIntSafe(input.value);
|
||||
const remain = Math.max(0, original - current);
|
||||
pendingCell.textContent = Number(remain || 0).toLocaleString('ko-KR');
|
||||
};
|
||||
|
||||
document.querySelectorAll('.receive-input').forEach((input) => {
|
||||
input.addEventListener('input', (e) => {
|
||||
const row = e.target.closest('tr');
|
||||
if (!row) return;
|
||||
const original = parseIntSafe(row.getAttribute('data-pending-original'));
|
||||
const current = Math.min(parseIntSafe(input.value), original);
|
||||
input.value = String(current);
|
||||
refreshPending(row);
|
||||
});
|
||||
const row = input.closest('tr');
|
||||
if (row) refreshPending(row);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
138
app/Views/bag/receiving_status.php
Normal file
138
app/Views/bag/receiving_status.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?= view('components/print_header', ['printTitle' => '봉투 입고 현황', 'printShowApproval' => false]) ?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel no-print">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700">입고 현황</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= base_url('bag/receiving/status/export') . '?' . http_build_query(array_filter(['start_date' => $startDate ?? '', 'end_date' => $endDate ?? '', 'company_idx' => $companyIdx ?? 0, 'bag_code' => $bagCode ?? '', 'receive_type' => $receiveType ?? ''])) ?>" class="border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50">엑셀저장</a>
|
||||
<button onclick="window.print()" class="border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">인쇄</button>
|
||||
<a href="<?= base_url('bag/receiving/scanner') ?>" class="border border-gray-300 text-gray-700 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">입고 처리</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="p-2 bg-white border-b border-gray-200 no-print">
|
||||
<form method="get" action="<?= base_url('bag/receiving/status') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">입고 기간</label>
|
||||
<input type="date" name="start_date" value="<?= esc((string) ($startDate ?? '')) ?>" class="border border-gray-300 rounded px-2 py-1 text-sm" />
|
||||
<span class="text-sm text-gray-500">~</span>
|
||||
<input type="date" name="end_date" value="<?= esc((string) ($endDate ?? '')) ?>" class="border border-gray-300 rounded px-2 py-1 text-sm" />
|
||||
|
||||
<label class="text-sm text-gray-600 ml-2">제작 업체</label>
|
||||
<select name="company_idx" class="border border-gray-300 rounded px-2 py-1 text-sm w-52">
|
||||
<option value="0">전 체</option>
|
||||
<?php foreach (($companies ?? []) as $company): ?>
|
||||
<option value="<?= (int) $company->cp_idx ?>" <?= (int) ($companyIdx ?? 0) === (int) $company->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($company->cp_name ?? '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<label class="text-sm text-gray-600">품명</label>
|
||||
<select name="bag_code" class="border border-gray-300 rounded px-2 py-1 text-sm w-44">
|
||||
<option value="">전 체</option>
|
||||
<?php foreach (($bagCodeOptions ?? []) as $bag): ?>
|
||||
<option value="<?= esc((string) ($bag->cd_code ?? '')) ?>" <?= (string) ($bagCode ?? '') === (string) ($bag->cd_code ?? '') ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($bag->cd_name ?? '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<label class="text-sm text-gray-600">입고 구분</label>
|
||||
<select name="receive_type" class="border border-gray-300 rounded px-2 py-1 text-sm w-36">
|
||||
<option value="all" <?= ($receiveType ?? 'all') === 'all' ? 'selected' : '' ?>>전 체</option>
|
||||
<option value="completed" <?= ($receiveType ?? 'all') === 'completed' ? 'selected' : '' ?>>완료</option>
|
||||
<option value="pending" <?= ($receiveType ?? 'all') === 'pending' ? 'selected' : '' ?>>미완료</option>
|
||||
</select>
|
||||
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= base_url('bag/receiving/status') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<div class="border border-gray-300 overflow-auto mt-2 receiving-status-print-wrap">
|
||||
<table class="w-full data-table text-sm receiving-status-print-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>입고일자</th>
|
||||
<th>품명</th>
|
||||
<th>입고수량</th>
|
||||
<th>발주일자</th>
|
||||
<th>발주수량</th>
|
||||
<th>발주번호</th>
|
||||
<th>제작업체</th>
|
||||
<th>입고여부</th>
|
||||
<th>입고처</th>
|
||||
<th>비고</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$prevDate = null;
|
||||
$runningSum = 0;
|
||||
?>
|
||||
<?php foreach (($rows ?? []) as $i => $row): ?>
|
||||
<?php
|
||||
$d = (string) ($row['display_date'] ?? '');
|
||||
if ($prevDate !== null && $d !== $prevDate):
|
||||
?>
|
||||
<tr class="bg-gray-50 font-semibold">
|
||||
<td colspan="2" class="text-center">소 계</td>
|
||||
<td class="text-right"><?= number_format($runningSum) ?></td>
|
||||
<td colspan="7"></td>
|
||||
</tr>
|
||||
<?php
|
||||
$runningSum = 0;
|
||||
endif;
|
||||
$runningSum += (int) ($row['received_qty_sheet'] ?? 0);
|
||||
$prevDate = $d;
|
||||
?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($d) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($row['bag_name'] ?? '')) ?></td>
|
||||
<td class="text-right"><?= number_format((int) ($row['received_qty_sheet'] ?? 0)) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($row['order_date'] ?? '')) ?></td>
|
||||
<td class="text-right"><?= number_format((int) ($row['order_qty_sheet'] ?? 0)) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($row['order_no'] ?? '')) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($row['company_name'] ?? '')) ?></td>
|
||||
<td class="text-center <?= ($row['receive_status_label'] ?? '') === '완료' ? 'text-red-600 font-bold' : 'text-blue-600 font-bold' ?>"><?= esc((string) ($row['receive_status_label'] ?? '')) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($row['agency_name'] ?? '')) ?></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if (! empty($rows ?? [])): ?>
|
||||
<tr class="bg-gray-50 font-semibold">
|
||||
<td colspan="2" class="text-center">소 계</td>
|
||||
<td class="text-right"><?= number_format($runningSum) ?></td>
|
||||
<td colspan="7"></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (empty($rows ?? [])): ?>
|
||||
<tr><td colspan="10" class="text-center text-gray-400 py-4">조회 결과가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="bg-gray-100 font-bold">
|
||||
<td colspan="2" class="text-center">합 계</td>
|
||||
<td class="text-right"><?= number_format((int) ($grandTotalReceive ?? 0)) ?></td>
|
||||
<td colspan="7"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@media print {
|
||||
.receiving-status-print-wrap {
|
||||
overflow: visible !important;
|
||||
border: none !important;
|
||||
}
|
||||
.receiving-status-print-table th,
|
||||
.receiving-status-print-table td {
|
||||
white-space: nowrap !important;
|
||||
font-size: 10px !important;
|
||||
padding: 2px 3px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user