feat: '발주파일 생성'(구 LOT-No 디스켓 불출) 이름변경 + 생성 전 내용 확인

- 메뉴/화면/매뉴얼 명칭을 '발주파일 생성'으로 변경(메뉴 rename 마이그레이션 SQL 포함)
- 발주파일(seed) 생성 전 '내용 확인' 미리보기 추가(암호화 전 발주 메타·품목 표시)
  · GET bag/order/lot-seed/preview 신설, 생성/미리보기 데이터 구성 공용화

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
taekyoungc
2026-07-06 18:26:58 +09:00
parent a5d995588b
commit de15cc9e47
9 changed files with 270 additions and 33 deletions

View File

@@ -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<string,mixed>, items?: array<int,array<string,mixed>>, 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