diff --git a/app/Config/Manual.php b/app/Config/Manual.php index daace1b..8814728 100644 --- a/app/Config/Manual.php +++ b/app/Config/Manual.php @@ -47,7 +47,7 @@ class Manual extends BaseConfig */ public array $screenHelp = [ 'bag/order/phone' => 'sales#전화 주문 접수', - 'bag/order/lot-seed' => 'order#LOT-No 디스켓 불출', + 'bag/order/lot-seed' => 'order#발주파일 생성', 'bag/order' => 'order#발주 등록', 'bag/password-change' => 'account#비밀번호 변경', 'bag/bag-orders' => 'order#발주 현황', diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 936dd0d..91d03b2 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -83,6 +83,7 @@ $routes->get('bag/order/phone/receipt/(:num)', 'Bag::phoneOrderReceipt/$1'); $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->get('bag/order/lot-seed/preview', 'Bag::orderLotSeedPreview'); $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'); diff --git a/app/Controllers/Bag.php b/app/Controllers/Bag.php index 9cdeb1f..4052b4c 100644 --- a/app/Controllers/Bag.php +++ b/app/Controllers/Bag.php @@ -3953,7 +3953,7 @@ SQL); } /** - * LOT-No 디스켓 불출: 발주 건을 선택해 암호화 seed 파일 생성/다운로드. + * 발주파일 생성(구 'LOT-No 디스켓 불출'): 발주 건을 선택해 암호화 seed 파일 생성/다운로드. */ public function orderLotSeed(): string|RedirectResponse { @@ -4031,7 +4031,7 @@ SQL); } } - return $this->render('LOT-No 디스켓 불출', 'bag/order_lot_seed', [ + return $this->render('발주파일 생성', 'bag/order_lot_seed', [ 'orders' => $orders, 'pager' => $pager, 'startMonth' => $startMonth, @@ -4044,17 +4044,16 @@ SQL); ]); } - public function orderLotSeedGenerate(): RedirectResponse|ResponseInterface + /** + * 발주파일(seed) 생성·미리보기가 공용으로 쓰는 데이터 구성. + * 발주 메타(order_data)와 품목(items, 팩/박스 환산 포함), 무결성 해시를 만든다. + * + * @return array{ok: bool, error?: string, order?: object, order_data?: array, items?: array>, lot_no?: string, uuid?: string, version?: int, order_hash?: string} + */ + private function buildLotSeedData(int $lgIdx, int $boIdx): array { - helper('admin'); - $lgIdx = $this->lgIdx(); - if (! $lgIdx) { - return redirect()->to(site_url('bag/purchase-inbound'))->with('error', '지자체를 선택해 주세요.'); - } - - $boIdx = (int) ($this->request->getPost('bo_idx') ?? 0); if ($boIdx <= 0) { - return redirect()->back()->with('error', '발주 건을 선택해 주세요.'); + return ['ok' => false, 'error' => '발주 건을 선택해 주세요.']; } $order = model(BagOrderModel::class) @@ -4062,14 +4061,14 @@ SQL); ->where('bo_idx', $boIdx) ->first(); if (! $order) { - return redirect()->back()->with('error', '발주 정보를 찾을 수 없습니다.'); + return ['ok' => false, 'error' => '발주 정보를 찾을 수 없습니다.']; } $lotNo = trim((string) ($order->bo_lot_no ?? '')); $uuid = trim((string) ($order->bo_uuid ?? '')); $version = max(1, (int) ($order->bo_version ?? 1)); if ($lotNo === '' || $uuid === '') { - return redirect()->back()->with('error', '발주의 LOT/UUID 정보가 없어 seed 파일을 생성할 수 없습니다.'); + return ['ok' => false, 'error' => '발주의 LOT/UUID 정보가 없어 발주파일을 생성할 수 없습니다.']; } $items = model(BagOrderItemModel::class) @@ -4077,7 +4076,7 @@ SQL); ->orderBy('boi_idx', 'ASC') ->findAll(); if ($items === []) { - return redirect()->back()->with('error', '발주 품목이 없어 seed 파일을 생성할 수 없습니다.'); + return ['ok' => false, 'error' => '발주 품목이 없어 발주파일을 생성할 수 없습니다.']; } $unitRows = model(PackagingUnitModel::class) @@ -4142,10 +4141,100 @@ SQL); $orderHash = hash('sha256', $payloadJson); } - $seedPath = $this->generateLotSeedFile($uuid, $version, $lotNo, $orderData, $hashItems, $orderHash); + return [ + 'ok' => true, + 'order' => $order, + 'order_data' => $orderData, + 'items' => $hashItems, + 'lot_no' => $lotNo, + 'uuid' => $uuid, + 'version' => $version, + 'order_hash' => $orderHash, + ]; + } + + /** + * 발주파일 생성 전 '내용 확인'(미리보기): 암호화 전 평문 내용을 JSON 으로 반환. + */ + public function orderLotSeedPreview(): ResponseInterface + { + helper('admin'); + $lgIdx = $this->lgIdx(); + if (! $lgIdx) { + return $this->response->setJSON(['ok' => false, 'error' => '지자체를 선택해 주세요.']); + } + + $boIdx = (int) ($this->request->getGet('bo_idx') ?? 0); + $data = $this->buildLotSeedData($lgIdx, $boIdx); + if (! ($data['ok'] ?? false)) { + return $this->response->setJSON(['ok' => false, 'error' => (string) ($data['error'] ?? '내용을 확인할 수 없습니다.')]); + } + + $order = $data['order']; + $companyIdx = (int) ($order->bo_company_idx ?? 0); + $companyName = ''; + if ($companyIdx > 0) { + $company = model(CompanyModel::class) + ->where('cp_lg_idx', $lgIdx) + ->where('cp_idx', $companyIdx) + ->first(); + $companyName = (string) ($company->cp_name ?? ''); + } + + $items = array_map(static fn (array $it): array => [ + 'bag_code' => (string) $it['boi_bag_code'], + 'bag_name' => (string) $it['boi_bag_name'], + 'unit_price' => (float) $it['boi_unit_price'], + 'qty_box' => (int) $it['boi_qty_box'], + 'qty_pack' => (int) $it['boi_qty_pack'], + 'qty_sheet' => (int) $it['boi_qty_sheet'], + 'amount' => (float) $it['boi_amount'], + ], $data['items']); + + return $this->response->setJSON([ + 'ok' => true, + 'bo_idx' => $boIdx, + 'lot_no' => $data['lot_no'], + 'uuid' => $data['uuid'], + 'version' => $data['version'], + 'order_date' => (string) ($order->bo_order_date ?? ''), + 'company' => $companyName, + 'order_hash' => $data['order_hash'], + 'items' => $items, + 'totals' => [ + 'line_count' => count($items), + 'qty_box' => array_sum(array_map(static fn ($i) => (int) $i['qty_box'], $items)), + 'qty_sheet' => array_sum(array_map(static fn ($i) => (int) $i['qty_sheet'], $items)), + 'amount' => array_sum(array_map(static fn ($i) => (float) $i['amount'], $items)), + ], + ]); + } + + public function orderLotSeedGenerate(): RedirectResponse|ResponseInterface + { + helper('admin'); + $lgIdx = $this->lgIdx(); + if (! $lgIdx) { + return redirect()->to(site_url('bag/purchase-inbound'))->with('error', '지자체를 선택해 주세요.'); + } + + $boIdx = (int) ($this->request->getPost('bo_idx') ?? 0); + $data = $this->buildLotSeedData($lgIdx, $boIdx); + if (! ($data['ok'] ?? false)) { + return redirect()->back()->with('error', (string) ($data['error'] ?? '발주파일을 생성할 수 없습니다.')); + } + + $seedPath = $this->generateLotSeedFile( + $data['uuid'], + $data['version'], + $data['lot_no'], + $data['order_data'], + $data['items'], + $data['order_hash'] + ); $seedBinary = @file_get_contents($seedPath); if (! is_string($seedBinary) || $seedBinary === '') { - return redirect()->back()->with('error', 'seed 파일 생성에는 성공했으나 파일을 읽을 수 없습니다.'); + return redirect()->back()->with('error', '발주파일 생성에는 성공했으나 파일을 읽을 수 없습니다.'); } return $this->response diff --git a/app/Docs/manual/20_order_receiving.md b/app/Docs/manual/20_order_receiving.md index 78da414..f73c7ba 100644 --- a/app/Docs/manual/20_order_receiving.md +++ b/app/Docs/manual/20_order_receiving.md @@ -67,14 +67,17 @@ --- -## LOT-No 디스켓 불출 · *발주 입고 관리 › LOT-No 디스켓 불출* +## 발주파일 생성 · *발주 입고 관리 › 발주파일 생성* -발주별 **LOT 번호 시드(seed) 데이터를 파일로 생성**하는 화면입니다. (바코드·LOT 부여에 쓰는 디스켓/파일 형태 데이터) +선택한 발주 건의 내용을 **봉투 생산기(바코드 인쇄) 전달용 파일**로 만드는 화면입니다. (예전 이름: *LOT-No 디스켓 불출*) 생성되는 파일은 발주 정보와 품목이 담긴 **암호화된 발주파일**(`.seed.json`)이며, 봉투 생산 장비가 이 파일을 읽어 그 발주의 바코드를 인쇄합니다. + +**LOT-No란?** 발주를 등록할 때 그 건에 자동 부여되는 **6자리 배치(묶음) 번호**입니다. 이후 그 발주로 인쇄되는 모든 봉투 바코드의 앞자리(예: `ZLCH2M-000006-…`)가 되어, 어느 발주에서 나온 봉투인지 구분하는 기준이 됩니다. **필터**: 시작월 · 종료월 · LOT-No · 제작업체. **순서** 1. 기간·조건으로 발주 목록을 조회합니다. -2. 해당 발주 행의 **`seed 생성`** 을 눌러 그 발주의 LOT 시드 파일을 만듭니다. +2. 대상 발주 행의 **`내용 확인`** 을 누르면, 파일에 담길 **발주 정보와 품목 목록(봉투코드·수량·금액 등)** 을 팝업으로 미리 볼 수 있습니다. +3. 내용을 확인한 뒤 **`발주파일 생성(다운로드)`** 을 누르면 암호화된 발주파일이 내려받아집니다. -**표 컬럼**: 발주일자 · LOT-No · 품명 · 수량 · 제작업체 · (생성 버튼). -> 정상·취소 발주가 함께 조회되며, 시드 생성은 발주 단위로 동작합니다. +**표 컬럼**: 발주일자 · LOT-No · 품명 · 수량 · 제작업체 · (내용 확인 버튼). +> 정상·취소 발주가 함께 조회되며, 파일 생성은 발주 단위로 동작합니다. 생성 전 **`내용 확인`** 단계에서 반드시 품목·수량을 검토하세요. diff --git a/app/Views/bag/order_lot_seed.php b/app/Views/bag/order_lot_seed.php index 34b51ce..3dda276 100644 --- a/app/Views/bag/order_lot_seed.php +++ b/app/Views/bag/order_lot_seed.php @@ -21,7 +21,7 @@ for ($year = $baseYear - 2; $year <= $baseYear + 2; $year++) { ?>
- LOT-No 디스켓 불출 + 발주파일 생성
@@ -92,11 +92,12 @@ for ($year = $baseYear - 2; $year <= $baseYear + 2; $year++) { ?> -
- - - -
+ bo_order_date ?? '')) ?> bo_lot_no ?? '')) ?> @@ -128,3 +129,133 @@ for ($year = $baseYear - 2; $year <= $baseYear + 2; $year++) {
links() ?>
+ + + + + diff --git a/writable/database/menu_link_lot_seed_issue.sql b/writable/database/menu_link_lot_seed_issue.sql index ad92e86..70aa470 100644 --- a/writable/database/menu_link_lot_seed_issue.sql +++ b/writable/database/menu_link_lot_seed_issue.sql @@ -1,4 +1,4 @@ --- LOT-No 디스켓 불출 소메뉴 링크 보정 +-- 발주파일 생성(구 LOT-No 디스켓 불출) 소메뉴 링크 보정 -- 실행: -- mysql --default-character-set=utf8mb4 -u ... -p DBNAME < writable/database/menu_link_lot_seed_issue.sql @@ -8,4 +8,4 @@ SET CHARACTER_SET_CLIENT = utf8mb4; UPDATE `menu` m INNER JOIN `menu_type` t ON t.mt_idx = m.mt_idx AND t.mt_code = 'site' SET m.mm_link = 'bag/order/lot-seed' -WHERE m.mm_name = 'LOT-No 디스켓 불출'; +WHERE m.mm_name IN ('발주파일 생성', 'LOT-No 디스켓 불출'); diff --git a/writable/database/menu_site_fill_empty_second_level_links.sql b/writable/database/menu_site_fill_empty_second_level_links.sql index cbc475b..2d96c44 100644 --- a/writable/database/menu_site_fill_empty_second_level_links.sql +++ b/writable/database/menu_site_fill_empty_second_level_links.sql @@ -27,7 +27,7 @@ SET m.mm_link = CASE m.mm_name WHEN '지정 판매소 현황' THEN 'bag/designated-shops/district-new-cancel' WHEN '발주 등록' THEN 'bag/order/create' WHEN '발주 변경' THEN 'bag/order/change' - WHEN 'LOT-No 디스켓 불출' THEN 'bag/order/lot-seed' + WHEN '발주파일 생성' THEN 'bag/order/lot-seed' WHEN '발주 현황' THEN 'bag/bag-orders' WHEN '발주 입고[스캐너]' THEN 'bag/receiving/create' WHEN '입고 현황' THEN 'bag/bag-receivings' diff --git a/writable/database/menu_site_seed_from_csv.sql b/writable/database/menu_site_seed_from_csv.sql index 799690b..200ed4e 100644 --- a/writable/database/menu_site_seed_from_csv.sql +++ b/writable/database/menu_site_seed_from_csv.sql @@ -83,7 +83,7 @@ SELECT @mt_site, 1, t.mm_name, CASE t.mm_name WHEN '발주 등록' THEN 'bag/order/create' WHEN '발주 변경' THEN 'bag/order/change' - WHEN 'LOT-No 디스켓 불출' THEN 'bag/order/lot-seed' + WHEN '발주파일 생성' THEN 'bag/order/lot-seed' WHEN '발주 현황' THEN 'bag/bag-orders' WHEN '발주 입고[스캐너]' THEN 'bag/receiving/create' WHEN '입고 현황' THEN 'bag/bag-receivings' @@ -93,7 +93,7 @@ SELECT @mt_site, 1, t.mm_name, FROM ( SELECT 0 AS mm_num, '발주 등록' AS mm_name UNION ALL SELECT 1, '발주 변경' UNION ALL - SELECT 2, 'LOT-No 디스켓 불출' UNION ALL + SELECT 2, '발주파일 생성' UNION ALL SELECT 3, '발주 현황' UNION ALL SELECT 4, '발주 입고[스캐너]' UNION ALL SELECT 5, '일괄입고' UNION ALL diff --git a/writable/database/rename_lot_seed_menu_to_order_file.sql b/writable/database/rename_lot_seed_menu_to_order_file.sql new file mode 100644 index 0000000..946101d --- /dev/null +++ b/writable/database/rename_lot_seed_menu_to_order_file.sql @@ -0,0 +1,13 @@ +-- 메뉴명 변경: 'LOT-No 디스켓 불출' → '발주파일 생성' +-- 링크(mm_link='bag/order/lot-seed') 기준으로, 기본 이름을 쓰는 항목만 안전하게 변경한다. +-- (지자체가 직접 다른 이름으로 바꿔 둔 항목은 건드리지 않음) +-- 실행: +-- mysql --default-character-set=utf8mb4 -u USER -p DBNAME < writable/database/rename_lot_seed_menu_to_order_file.sql + +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; +SET CHARACTER_SET_CLIENT = utf8mb4; + +UPDATE `menu` +SET `mm_name` = '발주파일 생성' +WHERE `mm_link` = 'bag/order/lot-seed' + AND `mm_name` = 'LOT-No 디스켓 불출';