2026-03-26 14:30:45 +09:00
< ? php
declare ( strict_types = 1 );
namespace App\Controllers ;
2026-03-30 15:07:09 +09:00
use CodeIgniter\Database\Exceptions\DatabaseException ;
2026-04-08 00:20:09 +09:00
use CodeIgniter\HTTP\RedirectResponse ;
2026-04-29 14:59:49 +09:00
use CodeIgniter\HTTP\ResponseInterface ;
2026-03-26 14:30:45 +09:00
use App\Models\BagInventoryModel ;
use App\Models\BagIssueModel ;
2026-04-29 14:59:49 +09:00
use App\Models\BagIssueItemCodeModel ;
2026-03-26 14:30:45 +09:00
use App\Models\BagOrderModel ;
use App\Models\BagOrderItemModel ;
use App\Models\BagPriceModel ;
use App\Models\BagReceivingModel ;
use App\Models\BagSaleModel ;
use App\Models\CodeKindModel ;
use App\Models\CodeDetailModel ;
use App\Models\CompanyModel ;
use App\Models\PackagingUnitModel ;
use App\Models\SalesAgencyModel ;
use App\Models\ShopOrderModel ;
2026-03-26 16:13:07 +09:00
use App\Models\DesignatedShopModel ;
2026-04-08 00:20:09 +09:00
use App\Models\LocalGovernmentModel ;
2026-04-22 15:35:36 +09:00
use App\Models\ManagerModel ;
2026-03-30 15:07:09 +09:00
use Config\Roles ;
2026-03-26 14:30:45 +09:00
class Bag extends BaseController
{
/**
* 로그인 사용자의 지자체 PK 반환 ( 미로그인 / 미지정 시 null )
*/
private function lgIdx () : ? int
{
helper ( 'admin' );
return admin_effective_lg_idx ();
}
2026-04-22 15:35:36 +09:00
/**
* 입고 화면용 인계자 : 제작업체 ( company ) 담당자 .
*
* @ return array { senders : list < object > , defaultSenderIdx : int }
*/
private function receivingManagerPickers ( int $lgIdx ) : array
{
$senders = model ( ManagerModel :: class , false )
-> where ( 'mg_lg_idx' , $lgIdx )
-> where ( 'mg_state' , 1 )
-> where ( 'mg_dept_code' , 'company' )
-> orderBy ( 'mg_name' , 'ASC' )
-> findAll ();
$sessionName = trim (( string ) ( session () -> get ( 'mb_name' ) ? ? '' ));
$defaultSenderIdx = 0 ;
foreach ( $senders as $s ) {
if (( string ) ( $s -> mg_name ? ? '' ) === $sessionName ) {
$defaultSenderIdx = ( int ) ( $s -> mg_idx ? ? 0 );
break ;
}
}
if ( $defaultSenderIdx <= 0 && $senders !== []) {
$defaultSenderIdx = ( int ) ( $senders [ 0 ] -> mg_idx ? ? 0 );
}
return [
'senders' => $senders ,
'defaultSenderIdx' => $defaultSenderIdx ,
];
}
/**
* 인수자 드롭다운 : 맨 위에 현재 로그인 회원 , 이어서 대행소 ( agency ) 담당자 .
* value 는 br_receiver_ref 로 전달 : m_ { mb_idx } | g_ { mg_idx }
*
* @ return array { receiverOptions : list < array { ref : string , label : string } > , defaultReceiverRef : string }
*/
private function receivingReceiverSelect ( int $lgIdx ) : array
{
$sessionMbIdx = ( int ) ( session () -> get ( 'mb_idx' ) ? ? 0 );
$sessionName = trim (( string ) ( session () -> get ( 'mb_name' ) ? ? '' ));
$normalizeName = static fn ( string $name ) : string => preg_replace ( '/\s+/u' , '' , trim ( $name )) ? ? '' ;
$normSession = $normalizeName ( $sessionName );
$options = [];
if ( $sessionMbIdx > 0 ) {
$label = $sessionName !== '' ? $sessionName : '로그인 사용자' ;
$options [] = [ 'ref' => 'm_' . $sessionMbIdx , 'label' => $label ];
}
$agencyManagers = model ( ManagerModel :: class , false )
-> where ( 'mg_lg_idx' , $lgIdx )
-> where ( 'mg_state' , 1 )
-> where ( 'mg_dept_code' , 'agency' )
-> orderBy ( 'mg_name' , 'ASC' )
-> findAll ();
foreach ( $agencyManagers as $rcv ) {
$mgIdx = ( int ) ( $rcv -> mg_idx ? ? 0 );
$receiverName = trim (( string ) ( $rcv -> mg_name ? ? '' ));
if ( $mgIdx <= 0 ) {
continue ;
}
if ( $normSession !== '' && $normalizeName ( $receiverName ) === $normSession ) {
continue ;
}
$options [] = [ 'ref' => 'g_' . $mgIdx , 'label' => $receiverName ];
}
$defaultRef = $options !== [] ? ( string ) ( $options [ 0 ][ 'ref' ] ? ? '' ) : '' ;
return [
'receiverOptions' => $options ,
'defaultReceiverRef' => $defaultRef ,
];
}
/**
* @ param list < array { ref : string , label : string } > $options
*/
private function sanitizeReceiverRef ( array $options , string $ref ) : string
{
foreach ( $options as $opt ) {
if (( $opt [ 'ref' ] ? ? '' ) === $ref ) {
return $ref ;
}
}
return '' ;
}
private function parseReceiverRefToStoredIdx ( int $lgIdx , string $ref ) : int
{
$ref = trim ( $ref );
if ( preg_match ( '/^m_(\d+)$/' , $ref , $mm )) {
$mbIdx = ( int ) $mm [ 1 ];
if ( $mbIdx <= 0 || $mbIdx !== ( int ) ( session () -> get ( 'mb_idx' ) ? ? 0 )) {
return 0 ;
}
return $mbIdx ;
}
if ( preg_match ( '/^g_(\d+)$/' , $ref , $mg )) {
return $this -> assertAgencyReceiverIdx ( $lgIdx , ( int ) $mg [ 1 ]);
}
return 0 ;
}
private function assertAgencyReceiverIdx ( int $lgIdx , int $mgIdx ) : int
{
if ( $mgIdx <= 0 ) {
return 0 ;
}
$row = model ( ManagerModel :: class , false ) -> where ([
'mg_idx' => $mgIdx ,
'mg_lg_idx' => $lgIdx ,
'mg_state' => 1 ,
'mg_dept_code' => 'agency' ,
]) -> first ();
return $row ? $mgIdx : 0 ;
}
private function resolveCompanySenderName ( int $lgIdx , int $mgIdx ) : string
{
if ( $mgIdx <= 0 ) {
return '' ;
}
$row = model ( ManagerModel :: class , false ) -> where ([
'mg_idx' => $mgIdx ,
'mg_lg_idx' => $lgIdx ,
'mg_state' => 1 ,
'mg_dept_code' => 'company' ,
]) -> first ();
return $row ? trim (( string ) ( $row -> mg_name ? ? '' )) : '' ;
}
2026-03-26 14:30:45 +09:00
private function render ( string $title , string $viewFile , array $data = []) : string
{
return view ( 'bag/layout/main' , [
'title' => $title ,
'content' => view ( $viewFile , $data ),
]);
}
// ──────────────────────────────────────────────
2026-04-08 00:20:09 +09:00
// 기본정보관리 (단가·포장 단위 진입 허브)
2026-03-26 14:30:45 +09:00
// ──────────────────────────────────────────────
public function basicInfo () : string
{
2026-04-08 00:20:09 +09:00
return $this -> render ( '기본정보관리' , 'bag/basic_info' , []);
}
/** 봉투 단가 조회 (사이트) — 기간·봉투구분·봉투코드 필터, 적용기간 겹침, 페이징·인쇄 */
public function prices () : string | RedirectResponse
{
helper ( 'admin' );
if ( $this -> request -> is ( 'post' )) {
$post = $this -> request -> getPost ();
$pick = 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 ;
};
session () -> setFlashdata ( 'bag_prices_filter' , [
'start_y' => $pick ( $post , 'start_y' ),
'start_m' => $pick ( $post , 'start_m' ),
'start_d' => $pick ( $post , 'start_d' ),
'end_y' => $pick ( $post , 'end_y' ),
'end_m' => $pick ( $post , 'end_m' ),
'end_d' => $pick ( $post , 'end_d' ),
'bag_kind_e' => $pick ( $post , 'bag_kind_e' ),
'bag_code' => $pick ( $post , 'bag_code' ),
]);
return redirect () -> to ( site_url ( 'bag/prices' ));
}
$lgIdx = $this -> lgIdx ();
$bagPrices = [];
$get = $this -> request -> getGet ();
$flash = session () -> getFlashdata ( 'bag_prices_filter' );
$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 ;
};
$filterKeys = [
'start_y' , 'start_m' , 'start_d' ,
'end_y' , 'end_m' , 'end_d' ,
'bag_kind_e' , 'bag_code' ,
'start_date' , 'end_date' ,
2026-03-30 15:07:09 +09:00
];
2026-04-08 00:20:09 +09:00
$hasExplicitGetFilter = false ;
foreach ( $filterKeys as $fk ) {
$v = $get [ $fk ] ? ? null ;
if ( $v !== null && ! is_array ( $v ) && trim (( string ) $v ) !== '' ) {
$hasExplicitGetFilter = true ;
break ;
}
}
2026-03-26 14:30:45 +09:00
2026-04-08 00:20:09 +09:00
$src = [];
if ( $hasExplicitGetFilter ) {
$src = $get ;
} elseif ( is_array ( $flash )) {
$src = $flash ;
}
$sy = $readSrc ( $src , 'start_y' );
$sm = $readSrc ( $src , 'start_m' );
$sd = $readSrc ( $src , 'start_d' );
$ey = $readSrc ( $src , 'end_y' );
$em = $readSrc ( $src , 'end_m' );
$ed = $readSrc ( $src , '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 ) {
$g = $readSrc ( $src , 'start_date' );
$startDate = ( $g !== null && $g !== '' ) ? $g : null ;
}
$endDate = null ;
if ( $ey !== null && $ey !== '' && $em !== null && $em !== '' && $ed !== null && $ed !== '' ) {
$endDate = parse_ymd_from_triple ( $ey , $em , $ed );
}
if ( $endDate === null ) {
$g = $readSrc ( $src , 'end_date' );
$endDate = ( $g !== null && $g !== '' ) ? $g : 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 ]];
} elseif ( $sy !== null && $sm !== null && $sd !== null && $sy !== '' && $sm !== '' && $sd !== '' ) {
$iy = ( int ) $sy ;
$im = ( int ) $sm ;
$id = ( int ) $sd ;
if ( $iy >= 1000 && $iy <= 9999 && $im >= 1 && $im <= 12 && $id >= 1 && $id <= 31 ) {
$startParts = [ 'y' => ( string ) $iy , 'm' => $im , 'd' => $id ];
}
}
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 ]];
} elseif ( $ey !== null && $em !== null && $ed !== null && $ey !== '' && $em !== '' && $ed !== '' ) {
$iy = ( int ) $ey ;
$im = ( int ) $em ;
$id = ( int ) $ed ;
if ( $iy >= 1000 && $iy <= 9999 && $im >= 1 && $im <= 12 && $id >= 1 && $id <= 31 ) {
$endParts = [ 'y' => ( string ) $iy , 'm' => $im , 'd' => $id ];
}
}
$dateYearMin = ( int ) date ( 'Y' ) - 12 ;
$dateYearMax = ( int ) date ( 'Y' ) + 2 ;
$bagKindE = $readSrc ( $src , 'bag_kind_e' );
$bagCode = $readSrc ( $src , 'bag_code' );
$pager = null ;
$bagCodes = [];
$bagKindOpts = [];
$printLines = [];
$printLgName = '' ;
if ( $lgIdx !== null ) {
2026-03-30 15:07:09 +09:00
try {
2026-04-08 00:20:09 +09:00
$priceModel = model ( BagPriceModel :: class );
$builder = $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 !== '' ) {
$ek = model ( CodeKindModel :: class ) -> where ( 'ck_code' , 'E' ) -> first ();
if ( $ek ) {
$eDetail = model ( CodeDetailModel :: class )
-> where ( 'cd_ck_idx' , ( int ) $ek -> ck_idx )
-> where ( 'cd_code' , $bagKindE )
-> where ( 'cd_state' , 1 )
-> first ();
if ( $eDetail !== null ) {
$builder -> like ( 'bp_bag_code' , ( string ) $bagKindE , 'after' );
}
}
}
if ( $bagCode !== null && $bagCode !== '' ) {
$ok = model ( CodeKindModel :: class ) -> where ( 'ck_code' , 'O' ) -> first ();
if ( $ok ) {
$oDetail = model ( CodeDetailModel :: class ) -> findResolvedByKindAndCode (( int ) $ok -> ck_idx , ( string ) $bagCode , $lgIdx );
if ( $oDetail !== null ) {
$builder -> where ( 'bp_bag_code' , $bagCode );
}
}
}
$bagPrices = $builder -> orderBy ( 'bp_bag_code' , 'ASC' ) -> orderBy ( 'bp_start_date' , 'DESC' ) -> paginate ( 20 );
$queryForPager = [];
$tripleS = $sy !== null && $sy !== '' && $sm !== null && $sm !== '' && $sd !== null && $sd !== '' ;
$tripleE = $ey !== null && $ey !== '' && $em !== null && $em !== '' && $ed !== null && $ed !== '' ;
if ( $tripleS ) {
$queryForPager [ 'start_y' ] = $sy ;
$queryForPager [ 'start_m' ] = $sm ;
$queryForPager [ 'start_d' ] = $sd ;
} else {
$legacyS = $readSrc ( $src , 'start_date' );
if ( $legacyS !== null ) {
$queryForPager [ 'start_date' ] = $legacyS ;
}
}
if ( $tripleE ) {
$queryForPager [ 'end_y' ] = $ey ;
$queryForPager [ 'end_m' ] = $em ;
$queryForPager [ 'end_d' ] = $ed ;
} else {
$legacyE = $readSrc ( $src , 'end_date' );
if ( $legacyE !== null ) {
$queryForPager [ 'end_date' ] = $legacyE ;
}
}
if ( $bagKindE !== null && $bagKindE !== '' ) {
$queryForPager [ 'bag_kind_e' ] = $bagKindE ;
}
if ( $bagCode !== null && $bagCode !== '' ) {
$queryForPager [ 'bag_code' ] = $bagCode ;
}
$queryForPager = array_filter (
$queryForPager ,
static fn ( $v ) => $v !== null && $v !== ''
);
$pagerPath = site_url ( 'bag/prices' );
if ( $queryForPager !== []) {
$pagerPath .= '?' . http_build_query ( $queryForPager );
}
$priceModel -> pager -> setPath ( $pagerPath );
$pager = $priceModel -> pager ;
$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 ();
$bagKindOpts = $kindE
? model ( CodeDetailModel :: class ) -> getByKind (( int ) $kindE -> ck_idx , true , null )
: [];
$lgRow = model ( LocalGovernmentModel :: class ) -> find ( $lgIdx );
$printLgName = $lgRow !== null ? $lgRow -> lg_name : '' ;
2026-03-30 15:07:09 +09:00
} catch ( DatabaseException $e ) {
2026-04-08 00:20:09 +09:00
log_message ( 'error' , '[prices] bag_price 조회 실패: ' . $e -> getMessage ());
2026-03-30 15:07:09 +09:00
}
2026-04-08 00:20:09 +09:00
}
if (( $startDate !== null && $startDate !== '' ) || ( $endDate !== null && $endDate !== '' )) {
$qs = ( $startDate !== null && $startDate !== '' ) ? $startDate : $endDate ;
$qe = ( $endDate !== null && $endDate !== '' ) ? $endDate : $startDate ;
if ( strcmp (( string ) $qs , ( string ) $qe ) > 0 ) {
[ $qs , $qe ] = [ $qe , $qs ];
}
$printLines [] = '조회기간(적용기간 겹침): ' . format_ymd_korean ( $qs ) . ' ~ ' . format_ymd_korean ( $qe );
}
if ( $bagKindE !== null && $bagKindE !== '' ) {
foreach ( $bagKindOpts as $cd ) {
if (( string ) $cd -> cd_code === ( string ) $bagKindE ) {
$printLines [] = '봉투구분: ' . $cd -> cd_name . ' (' . $bagKindE . ')' ;
break ;
}
}
}
if ( $bagCode !== null && $bagCode !== '' ) {
$printLines [] = '봉투코드: ' . $bagCode ;
}
$viewData = [
'lgIdx' => $lgIdx ,
'bagPrices' => $bagPrices ,
'pager' => $pager ,
'startDate' => $startDate ,
'endDate' => $endDate ,
'startParts' => $startParts ,
'endParts' => $endParts ,
'dateYearMin' => $dateYearMin ,
'dateYearMax' => $dateYearMax ,
'bag_kind_e' => $bagKindE ,
'bag_code' => $bagCode ,
'bag_codes' => $bagCodes ,
'bag_kind_options' => $bagKindOpts ,
'printExtraLines' => $printLines ,
];
if ( $printLgName !== '' ) {
$viewData [ 'printLgName' ] = $printLgName ;
}
return $this -> render ( '봉투 단가' , 'bag/prices' , $viewData );
}
/** 포장 단위 조회 (사이트) */
public function packagingUnits () : string
{
$lgIdx = $this -> lgIdx ();
$packagingUnits = [];
if ( $lgIdx ) {
2026-03-30 15:07:09 +09:00
try {
2026-04-08 00:20:09 +09:00
$packagingUnits = model ( PackagingUnitModel :: class ) -> where ( 'pu_lg_idx' , $lgIdx ) -> orderBy ( 'pu_bag_code' , 'ASC' ) -> findAll ();
2026-03-30 15:07:09 +09:00
} catch ( DatabaseException $e ) {
2026-04-08 00:20:09 +09:00
log_message ( 'error' , '[packagingUnits] packaging_unit 조회 실패: ' . $e -> getMessage ());
2026-03-30 15:07:09 +09:00
}
2026-03-26 14:30:45 +09:00
}
2026-04-09 13:01:31 +09:00
return $this -> render ( '포장 단위' , 'bag/packaging_units' , [ 'packagingUnits' => $packagingUnits ]);
2026-03-26 14:30:45 +09:00
}
2026-03-30 15:07:09 +09:00
/**
* 기본코드 종류·세부코드 조회 전용 ( 사이트 메뉴 기본코드관리 )
*/
public function codeKinds () : string
{
$kindModel = model ( CodeKindModel :: class );
$detailModel = model ( CodeDetailModel :: class );
2026-04-09 12:20:35 +09:00
$kinds = [];
2026-03-30 15:07:09 +09:00
$countMap = [];
2026-04-14 14:49:15 +09:00
$selectedKind = null ;
$detailList = [];
$rowCanEdit = [];
2026-04-09 12:20:35 +09:00
$lgIdx = $this -> lgIdx ();
try {
$kinds = $kindModel -> orderBy ( 'ck_code' , 'ASC' ) -> findAll ();
foreach ( $kinds as $row ) {
$countMap [ $row -> ck_idx ] = ( int ) $detailModel -> where ( 'cd_ck_idx' , $row -> ck_idx )
-> filterByTenantScope ( $lgIdx )
-> countAllResults ();
}
} catch ( \Throwable $e ) {
log_message ( 'error' , '[codeKinds] 실패: {type} {message} @ {file}:{line} / lg={lg}, user={user}, level={level}' , [
'type' => $e :: class ,
'message' => $e -> getMessage (),
'file' => $e -> getFile (),
'line' => $e -> getLine (),
'lg' => $lgIdx !== null ? ( string ) $lgIdx : 'null' ,
'user' => ( string ) ( session () -> get ( 'mb_id' ) ? ? '' ),
'level' => ( string ) ( session () -> get ( 'mb_level' ) ? ? '' ),
]);
session () -> setFlashdata ( 'error' , '기본코드 조회 중 오류가 발생했습니다. 관리자에게 로그 확인을 요청해 주세요.' );
2026-03-30 15:07:09 +09:00
}
2026-04-08 00:20:09 +09:00
$level = ( int ) session () -> get ( 'mb_level' );
2026-04-14 14:49:15 +09:00
$canManageDetails = Roles :: canManageCodeMaster ( $level );
if ( $kinds !== []) {
$selectedCkIdx = ( int ) ( $this -> request -> getGet ( 'ck_idx' ) ? ? 0 );
foreach ( $kinds as $row ) {
if (( int ) $row -> ck_idx === $selectedCkIdx ) {
$selectedKind = $row ;
break ;
}
}
if ( $selectedKind === null ) {
$selectedKind = $kinds [ 0 ];
}
}
if ( $selectedKind !== null ) {
$detailList = $detailModel -> where ( 'cd_ck_idx' , ( int ) $selectedKind -> ck_idx )
-> filterByTenantScope ( $lgIdx )
-> orderBy ( 'cd_sort' , 'ASC' )
2026-04-22 15:35:36 +09:00
-> orderBy ( 'cd_code' , 'ASC' )
2026-04-14 14:49:15 +09:00
-> orderBy ( 'cd_idx' , 'ASC' )
-> findAll ();
helper ( 'admin' );
$adminLg = admin_effective_lg_idx ();
foreach ( $detailList as $row ) {
$rowCanEdit [ $row -> cd_idx ] = Roles :: canEditCodeDetailRow ( $level , $row , $adminLg );
}
}
2026-04-08 00:20:09 +09:00
2026-03-30 15:07:09 +09:00
return $this -> render ( '기본코드관리' , 'bag/code_kinds' , [
2026-04-08 00:20:09 +09:00
'codeKinds' => $kinds ,
'countMap' => $countMap ,
'canManageKinds' => Roles :: canManageCodeKindMaster ( $level ),
2026-04-14 14:49:15 +09:00
'canManageDetails' => $canManageDetails ,
'selectedKind' => $selectedKind ,
'detailList' => $detailList ,
'rowCanEdit' => $rowCanEdit ,
2026-03-30 15:07:09 +09:00
]);
}
/**
* 기본코드 세부 목록 ( 사이트 레이아웃 ) . 등록·수정·삭제 폼은 / admin / code - details /* 유지 .
*/
public function codeDetails ( int $ckIdx )
{
$kindModel = model ( CodeKindModel :: class );
$detailModel = model ( CodeDetailModel :: class );
2026-04-09 12:20:35 +09:00
$kind = null ;
try {
$kind = $kindModel -> find ( $ckIdx );
} catch ( \Throwable $e ) {
log_message ( 'error' , '[codeDetails] kind 조회 실패: {type} {message} @ {file}:{line} / ck={ck}, user={user}, level={level}' , [
'type' => $e :: class ,
'message' => $e -> getMessage (),
'file' => $e -> getFile (),
'line' => $e -> getLine (),
'ck' => ( string ) $ckIdx ,
'user' => ( string ) ( session () -> get ( 'mb_id' ) ? ? '' ),
'level' => ( string ) ( session () -> get ( 'mb_level' ) ? ? '' ),
]);
return redirect () -> to ( site_url ( 'bag/code-kinds' )) -> with ( 'error' , '세부코드 조회 중 오류가 발생했습니다. 관리자에게 로그 확인을 요청해 주세요.' );
}
2026-03-30 15:07:09 +09:00
if ( $kind === null ) {
return redirect () -> to ( site_url ( 'bag/code-kinds' )) -> with ( 'error' , '코드 종류를 찾을 수 없습니다.' );
}
2026-04-08 00:20:09 +09:00
$lgIdx = $this -> lgIdx ();
2026-04-09 12:20:35 +09:00
try {
$list = $detailModel -> where ( 'cd_ck_idx' , $ckIdx )
-> filterByTenantScope ( $lgIdx )
-> orderBy ( 'cd_sort' , 'ASC' )
2026-04-22 15:35:36 +09:00
-> orderBy ( 'cd_code' , 'ASC' )
2026-04-09 12:20:35 +09:00
-> orderBy ( 'cd_idx' , 'ASC' )
-> paginate ( 20 );
$pager = $detailModel -> pager ;
} catch ( \Throwable $e ) {
log_message ( 'error' , '[codeDetails] list 조회 실패: {type} {message} @ {file}:{line} / ck={ck}, lg={lg}, user={user}, level={level}' , [
'type' => $e :: class ,
'message' => $e -> getMessage (),
'file' => $e -> getFile (),
'line' => $e -> getLine (),
'ck' => ( string ) $ckIdx ,
'lg' => $lgIdx !== null ? ( string ) $lgIdx : 'null' ,
'user' => ( string ) ( session () -> get ( 'mb_id' ) ? ? '' ),
'level' => ( string ) ( session () -> get ( 'mb_level' ) ? ? '' ),
]);
return redirect () -> to ( site_url ( 'bag/code-kinds' )) -> with ( 'error' , '세부코드 조회 중 오류가 발생했습니다. 관리자에게 로그 확인을 요청해 주세요.' );
}
2026-03-30 15:07:09 +09:00
2026-04-08 00:20:09 +09:00
helper ( 'admin' );
$level = ( int ) session () -> get ( 'mb_level' );
$adminLg = admin_effective_lg_idx ();
$canManage = Roles :: canManageCodeMaster ( $level );
$rowCanEdit = [];
foreach ( $list as $row ) {
$rowCanEdit [ $row -> cd_idx ] = Roles :: canEditCodeDetailRow ( $level , $row , $adminLg );
}
$title = ( $canManage ? '세부코드 관리' : '세부코드 조회' ) . ' — ' . $kind -> ck_name . ' (' . $kind -> ck_code . ')' ;
2026-03-30 15:07:09 +09:00
return $this -> render ( $title , 'bag/code_details' , [
2026-04-08 00:20:09 +09:00
'kind' => $kind ,
'list' => $list ,
'pager' => $pager ,
'canManage' => $canManage ,
'rowCanEdit' => $rowCanEdit ,
2026-03-30 15:07:09 +09:00
]);
}
2026-03-26 14:30:45 +09:00
// ──────────────────────────────────────────────
// 발주 입고 관리
// ──────────────────────────────────────────────
public function purchaseInbound () : string
{
$lgIdx = $this -> lgIdx ();
$data = [ 'orders' => [], 'receivings' => [], 'startDate' => null , 'endDate' => null ];
if ( $lgIdx ) {
$startDate = $this -> request -> getGet ( 'start_date' );
$endDate = $this -> request -> getGet ( 'end_date' );
$data [ 'startDate' ] = $startDate ;
$data [ 'endDate' ] = $endDate ;
// 발주 목록
2026-04-22 15:35:36 +09:00
$orderBuilder = model ( BagOrderModel :: class ) -> where ( 'bo_lg_idx' , $lgIdx ) -> whereLatestHead ( $lgIdx );
2026-03-26 14:30:45 +09:00
if ( $startDate ) $orderBuilder -> where ( 'bo_order_date >=' , $startDate );
if ( $endDate ) $orderBuilder -> where ( 'bo_order_date <=' , $endDate );
2026-03-26 16:40:49 +09:00
$data [ 'orders' ] = $orderBuilder -> orderBy ( 'bo_order_date' , 'DESC' ) -> paginate ( 20 , 'orders' );
$data [ 'orderPager' ] = model ( BagOrderModel :: class ) -> pager ;
2026-03-26 14:30:45 +09:00
// 발주별 품목 합계
$itemSummary = [];
foreach ( $data [ 'orders' ] as $order ) {
$items = model ( BagOrderItemModel :: class ) -> 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 )];
}
$data [ 'itemSummary' ] = $itemSummary ;
// 입고 목록
$recvBuilder = model ( BagReceivingModel :: class ) -> where ( 'br_lg_idx' , $lgIdx );
if ( $startDate ) $recvBuilder -> where ( 'br_receive_date >=' , $startDate );
if ( $endDate ) $recvBuilder -> where ( 'br_receive_date <=' , $endDate );
2026-03-26 16:40:49 +09:00
$data [ 'receivings' ] = $recvBuilder -> orderBy ( 'br_receive_date' , 'DESC' ) -> paginate ( 20 , 'receivings' );
$data [ 'recvPager' ] = model ( BagReceivingModel :: class ) -> pager ;
2026-03-26 14:30:45 +09:00
}
return $this -> render ( '발주 입고 관리' , 'bag/purchase_inbound' , $data );
}
// ──────────────────────────────────────────────
// 불출 관리
// ──────────────────────────────────────────────
2026-04-29 14:59:49 +09:00
public function issueLegacy () : RedirectResponse
{
return redirect () -> to ( site_url ( 'bag/issue/cancel' ));
}
2026-03-26 14:30:45 +09:00
public function issue () : string
{
2026-04-29 14:59:49 +09:00
$lgIdx = $this -> lgIdx ();
$data = [
'filters' => [
'issue_month' => ( string ) ( $this -> request -> getGet ( 'issue_month' ) ? ? '' ),
'dest_name' => ( string ) ( $this -> request -> getGet ( 'dest_name' ) ? ? '' ),
'issue_type' => ( string ) ( $this -> request -> getGet ( 'issue_type' ) ? ? '' ),
'bag_code' => ( string ) ( $this -> request -> getGet ( 'bag_code' ) ? ? '' ),
],
'monthOptions' => [],
'destOptions' => [],
'typeOptions' => [ '무료용' , '공공용' ],
'bagOptions' => [],
'issueGroups' => [],
'detailRows' => [],
'detailSourceRows' => [],
'codeRows' => [],
'selectedGroupDate' => '' ,
'selectedGroupDest' => '' ,
'selectedIssueId' => 0 ,
'selectedBagCode' => '' ,
];
if ( ! $lgIdx ) {
return $this -> render ( '불출 관리' , 'bag/issue' , $data );
}
$db = \Config\Database :: connect ();
$issueTable = $db -> table ( 'bag_issue' );
$hasItemCodeTable = $db -> tableExists ( 'bag_issue_item_code' );
$filterMonth = trim (( string ) $data [ 'filters' ][ 'issue_month' ]);
$filterDest = trim (( string ) $data [ 'filters' ][ 'dest_name' ]);
$filterType = trim (( string ) $data [ 'filters' ][ 'issue_type' ]);
$filterBag = trim (( string ) $data [ 'filters' ][ 'bag_code' ]);
$applyCommonFilters = static function ( $builder ) use ( $lgIdx , $filterMonth , $filterDest , $filterType , $filterBag ) : void {
$builder -> where ( 'bi2_lg_idx' , $lgIdx );
if ( preg_match ( '/^\d{4}-\d{2}$/' , $filterMonth ) === 1 ) {
$start = $filterMonth . '-01' ;
$end = date ( 'Y-m-t' , strtotime ( $start ));
$builder -> where ( 'bi2_issue_date >=' , $start );
$builder -> where ( 'bi2_issue_date <=' , $end );
}
if ( $filterDest !== '' ) {
$builder -> where ( 'bi2_dest_name' , $filterDest );
}
if ( $filterType !== '' ) {
$builder -> where ( 'bi2_issue_type' , $filterType );
}
if ( $filterBag !== '' ) {
$builder -> where ( 'bi2_bag_code' , $filterBag );
}
};
$monthRows = $db -> table ( 'bag_issue' )
-> select ( " DATE_FORMAT(bi2_issue_date, '%Y-%m') AS issue_month " , false )
-> where ( 'bi2_lg_idx' , $lgIdx )
-> groupBy ( " DATE_FORMAT(bi2_issue_date, '%Y-%m') " , false )
-> orderBy ( 'issue_month' , 'DESC' )
-> get ()
-> getResultArray ();
foreach ( $monthRows as $row ) {
$month = ( string ) ( $row [ 'issue_month' ] ? ? '' );
if ( $month !== '' ) {
$data [ 'monthOptions' ][] = $month ;
}
}
$destRows = $db -> table ( 'bag_issue' )
-> select ( 'bi2_dest_name' )
-> where ( 'bi2_lg_idx' , $lgIdx )
-> groupBy ( 'bi2_dest_name' )
-> orderBy ( 'bi2_dest_name' , 'ASC' )
-> get ()
-> getResultArray ();
$data [ 'destOptions' ] = array_values ( array_filter ( array_map ( static fn ( $r ) : string => ( string ) ( $r [ 'bi2_dest_name' ] ? ? '' ), $destRows )));
$bagRows = $db -> table ( 'bag_issue' )
-> select ( 'bi2_bag_code, MAX(bi2_bag_name) AS bi2_bag_name' , false )
-> where ( 'bi2_lg_idx' , $lgIdx )
-> groupBy ( 'bi2_bag_code' )
-> orderBy ( 'bi2_bag_code' , 'ASC' )
-> get ()
-> getResultArray ();
foreach ( $bagRows as $row ) {
$code = ( string ) ( $row [ 'bi2_bag_code' ] ? ? '' );
if ( $code === '' ) {
continue ;
}
$data [ 'bagOptions' ][] = [
'code' => $code ,
'name' => ( string ) ( $row [ 'bi2_bag_name' ] ? ? $code ),
];
}
$groupBuilder = $db -> table ( 'bag_issue' )
-> select ( 'bi2_issue_date, bi2_dest_name, COUNT(*) AS row_count, SUM(bi2_qty) AS total_qty' , false )
-> groupBy ( 'bi2_issue_date, bi2_dest_name' );
$applyCommonFilters ( $groupBuilder );
$data [ 'issueGroups' ] = $groupBuilder
-> orderBy ( 'bi2_issue_date' , 'DESC' )
-> orderBy ( 'bi2_dest_name' , 'ASC' )
-> get ()
-> getResultArray ();
$selectedDate = ( string ) ( $this -> request -> getGet ( 'sel_date' ) ? ? '' );
$selectedDest = ( string ) ( $this -> request -> getGet ( 'sel_dest' ) ? ? '' );
if (( $selectedDate === '' || $selectedDest === '' ) && $data [ 'issueGroups' ] !== []) {
$selectedDate = ( string ) ( $data [ 'issueGroups' ][ 0 ][ 'bi2_issue_date' ] ? ? '' );
$selectedDest = ( string ) ( $data [ 'issueGroups' ][ 0 ][ 'bi2_dest_name' ] ? ? '' );
}
$data [ 'selectedGroupDate' ] = $selectedDate ;
$data [ 'selectedGroupDest' ] = $selectedDest ;
if ( $selectedDate !== '' && $selectedDest !== '' ) {
$detailBuilder = $db -> table ( 'bag_issue' )
-> select ( 'bi2_idx, bi2_issue_date, bi2_issue_type, bi2_bag_code, bi2_bag_name, bi2_qty, bi2_status' )
-> where ( 'bi2_lg_idx' , $lgIdx )
-> where ( 'bi2_issue_date' , $selectedDate )
-> where ( 'bi2_dest_name' , $selectedDest );
if ( $filterType !== '' ) {
$detailBuilder -> where ( 'bi2_issue_type' , $filterType );
}
if ( $filterBag !== '' ) {
$detailBuilder -> where ( 'bi2_bag_code' , $filterBag );
}
$data [ 'detailRows' ] = $detailBuilder
-> orderBy ( 'bi2_idx' , 'ASC' )
-> get ()
-> getResultArray ();
$data [ 'detailSourceRows' ] = $data [ 'detailRows' ];
}
$detailIssueIds = array_map ( static fn ( $row ) : int => ( int ) ( $row [ 'bi2_idx' ] ? ? 0 ), $data [ 'detailRows' ]);
$cancelMap = [];
$codeQtyMap = [];
if ( $hasItemCodeTable && $detailIssueIds !== []) {
$aggRows = $db -> table ( 'bag_issue_item_code' )
-> select ( 'bic_bi2_idx, SUM(bic_qty) AS sum_qty, SUM(bic_cancel_qty) AS sum_cancel' , false )
-> whereIn ( 'bic_bi2_idx' , $detailIssueIds )
-> groupBy ( 'bic_bi2_idx' )
-> get ()
-> getResultArray ();
foreach ( $aggRows as $agg ) {
$idx = ( int ) ( $agg [ 'bic_bi2_idx' ] ? ? 0 );
if ( $idx <= 0 ) {
continue ;
}
$cancelMap [ $idx ] = ( int ) ( $agg [ 'sum_cancel' ] ? ? 0 );
$codeQtyMap [ $idx ] = ( int ) ( $agg [ 'sum_qty' ] ? ? 0 );
}
}
foreach ( $data [ 'detailRows' ] as & $row ) {
$idx = ( int ) ( $row [ 'bi2_idx' ] ? ? 0 );
$cancelQty = ( int ) ( $cancelMap [ $idx ] ? ? 0 );
$baseQty = isset ( $codeQtyMap [ $idx ]) ? ( int ) $codeQtyMap [ $idx ] : (( int ) ( $row [ 'bi2_qty' ] ? ? 0 ) + $cancelQty );
$row [ 'base_qty' ] = max ( 0 , $baseQty );
$row [ 'cancel_qty' ] = max ( 0 , min ( $row [ 'base_qty' ], $cancelQty ));
$row [ 'remain_qty' ] = max ( 0 , $row [ 'base_qty' ] - $row [ 'cancel_qty' ]);
}
unset ( $row );
$data [ 'detailSourceRows' ] = $data [ 'detailRows' ];
$aggByBag = [];
foreach ( $data [ 'detailRows' ] as $row ) {
$bagCodeKey = ( string ) ( $row [ 'bi2_bag_code' ] ? ? '' );
if ( $bagCodeKey === '' ) {
continue ;
}
if ( ! isset ( $aggByBag [ $bagCodeKey ])) {
$aggByBag [ $bagCodeKey ] = [
'bi2_issue_date' => ( string ) ( $row [ 'bi2_issue_date' ] ? ? '' ),
'bi2_issue_type' => ( string ) ( $row [ 'bi2_issue_type' ] ? ? '' ),
'bi2_bag_code' => $bagCodeKey ,
'bi2_bag_name' => ( string ) ( $row [ 'bi2_bag_name' ] ? ? $bagCodeKey ),
'base_qty' => 0 ,
'cancel_qty' => 0 ,
'issue_ids' => [],
];
}
$aggByBag [ $bagCodeKey ][ 'base_qty' ] += ( int ) ( $row [ 'base_qty' ] ? ? 0 );
$aggByBag [ $bagCodeKey ][ 'cancel_qty' ] += ( int ) ( $row [ 'cancel_qty' ] ? ? 0 );
$aggByBag [ $bagCodeKey ][ 'issue_ids' ][] = ( int ) ( $row [ 'bi2_idx' ] ? ? 0 );
}
$data [ 'detailRows' ] = array_values ( $aggByBag );
$selectedIssueId = ( int ) ( $this -> request -> getGet ( 'sel_issue_id' ) ? ? 0 );
if ( $selectedIssueId <= 0 && $data [ 'detailRows' ] !== []) {
$selectedIssueId = ( int ) (( $data [ 'detailRows' ][ 0 ][ 'issue_ids' ][ 0 ] ? ? 0 ));
}
$data [ 'selectedIssueId' ] = $selectedIssueId ;
$selectedBagCode = trim (( string ) ( $this -> request -> getGet ( 'sel_bag_code' ) ? ? '' ));
if ( $selectedBagCode === '' && $data [ 'detailRows' ] !== []) {
$selectedBagCode = ( string ) ( $data [ 'detailRows' ][ 0 ][ 'bi2_bag_code' ] ? ? '' );
}
$data [ 'selectedBagCode' ] = $selectedBagCode ;
if ( $selectedBagCode !== '' ) {
$selectedIssueIds = [];
foreach (( $data [ 'detailRows' ] ? ? []) as $detailRow ) {
if (( string ) ( $detailRow [ 'bi2_bag_code' ] ? ? '' ) !== $selectedBagCode ) {
continue ;
}
$selectedIssueIds = array_values ( array_filter ( array_map ( 'intval' , ( array ) ( $detailRow [ 'issue_ids' ] ? ? []))));
break ;
}
$sourceByIssue = [];
foreach (( $data [ 'detailSourceRows' ] ? ? []) as $sourceRow ) {
if (( string ) ( $sourceRow [ 'bi2_bag_code' ] ? ? '' ) !== $selectedBagCode ) {
continue ;
}
$sourceIssueId = ( int ) ( $sourceRow [ 'bi2_idx' ] ? ? 0 );
if ( $sourceIssueId <= 0 ) {
continue ;
}
$sourceByIssue [ $sourceIssueId ] = $sourceRow ;
}
if ( $hasItemCodeTable ) {
if ( $selectedIssueIds !== []) {
$existingRows = $db -> table ( 'bag_issue_item_code' )
-> select ( 'bic_bi2_idx' )
-> where ( 'bic_lg_idx' , $lgIdx )
-> where ( 'bic_bag_code' , $selectedBagCode )
-> whereIn ( 'bic_bi2_idx' , $selectedIssueIds )
-> groupBy ( 'bic_bi2_idx' )
-> get ()
-> getResultArray ();
$existingIssueSet = [];
foreach ( $existingRows as $existingRow ) {
$issueId = ( int ) ( $existingRow [ 'bic_bi2_idx' ] ? ? 0 );
if ( $issueId > 0 ) {
$existingIssueSet [ $issueId ] = true ;
}
}
foreach ( $selectedIssueIds as $issueId ) {
$issueId = ( int ) $issueId ;
if ( $issueId <= 0 || isset ( $existingIssueSet [ $issueId ])) {
continue ;
}
$source = $sourceByIssue [ $issueId ] ? ? null ;
if ( ! is_array ( $source )) {
continue ;
}
$sourceQty = max ( 0 , ( int ) ( $source [ 'base_qty' ] ? ? 0 ));
if ( $sourceQty <= 0 ) {
continue ;
}
$sourceCancel = max ( 0 , min ( $sourceQty , ( int ) ( $source [ 'cancel_qty' ] ? ? 0 )));
$db -> table ( 'bag_issue_item_code' ) -> insert ([
'bic_lg_idx' => $lgIdx ,
'bic_bi2_idx' => $issueId ,
'bic_bag_code' => $selectedBagCode ,
'bic_issue_code' => sprintf ( '%02d-%06d-001' , ( int ) date ( 'y' ), $issueId ),
'bic_qty' => $sourceQty ,
'bic_cancel_qty' => $sourceCancel ,
'bic_state' => ( $sourceCancel >= $sourceQty ) ? 'cancelled' : 'normal' ,
'bic_regdate' => date ( 'Y-m-d H:i:s' ),
]);
}
$data [ 'codeRows' ] = $db -> table ( 'bag_issue_item_code' )
-> select ( 'bic_idx, bic_bi2_idx, bic_issue_code, bic_qty, bic_cancel_qty' )
-> where ( 'bic_lg_idx' , $lgIdx )
-> where ( 'bic_bag_code' , $selectedBagCode )
-> whereIn ( 'bic_bi2_idx' , $selectedIssueIds )
-> orderBy ( 'bic_bi2_idx' , 'ASC' )
-> orderBy ( 'bic_idx' , 'ASC' )
-> get ()
-> getResultArray ();
}
}
$existingIssueIds = [];
foreach (( $data [ 'codeRows' ] ? ? []) as $codeRow ) {
$existingIssueId = ( int ) ( $codeRow [ 'bic_bi2_idx' ] ? ? 0 );
if ( $existingIssueId > 0 ) {
$existingIssueIds [ $existingIssueId ] = true ;
}
}
foreach ( $sourceByIssue as $sourceIssueId => $sourceRow ) {
if ( isset ( $existingIssueIds [ $sourceIssueId ])) {
continue ;
}
$data [ 'codeRows' ][] = [
'bic_idx' => 0 ,
'bic_bi2_idx' => $sourceIssueId ,
'bic_issue_code' => sprintf ( '%02d-%06d-001' , ( int ) date ( 'y' ), $sourceIssueId ),
'bic_qty' => ( int ) ( $sourceRow [ 'base_qty' ] ? ? 0 ),
'bic_cancel_qty' => ( int ) ( $sourceRow [ 'cancel_qty' ] ? ? 0 ),
];
}
if (( $data [ 'codeRows' ] ? ? []) !== []) {
usort ( $data [ 'codeRows' ], static function ( array $a , array $b ) : int {
$issueCmp = (( int ) ( $a [ 'bic_bi2_idx' ] ? ? 0 )) <=> (( int ) ( $b [ 'bic_bi2_idx' ] ? ? 0 ));
if ( $issueCmp !== 0 ) {
return $issueCmp ;
}
return (( int ) ( $a [ 'bic_idx' ] ? ? 0 )) <=> (( int ) ( $b [ 'bic_idx' ] ? ? 0 ));
});
}
}
return $this -> render ( '불출 관리' , 'bag/issue' , $data );
}
public function issueCancelSave () : RedirectResponse
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/issue/cancel' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$db = \Config\Database :: connect ();
$issueModel = model ( BagIssueModel :: class );
$inventoryModel = model ( BagInventoryModel :: class );
$hasItemCodeTable = $db -> tableExists ( 'bag_issue_item_code' );
$codeCancelQtyInput = $this -> request -> getPost ( 'code_cancel_qty' );
$codeCancelQtyInput = is_array ( $codeCancelQtyInput ) ? $codeCancelQtyInput : [];
$codeCheckedInput = $this -> request -> getPost ( 'code_cancel_check' );
$codeCheckedInput = is_array ( $codeCheckedInput ) ? $codeCheckedInput : [];
$issueCancelQtyInput = $this -> request -> getPost ( 'issue_cancel_qty' );
$issueCancelQtyInput = is_array ( $issueCancelQtyInput ) ? $issueCancelQtyInput : [];
$issueCheckedInput = $this -> request -> getPost ( 'issue_cancel_check' );
$issueCheckedInput = is_array ( $issueCheckedInput ) ? $issueCheckedInput : [];
$issueDeltaMap = [];
$touchedIssueIds = [];
$db -> transStart ();
if ( $hasItemCodeTable && $codeCancelQtyInput !== []) {
$codeIds = array_values ( array_unique ( array_map ( 'intval' , array_keys ( $codeCancelQtyInput ))));
$codeIds = array_values ( array_filter ( $codeIds , static fn ( $v ) : bool => $v > 0 ));
if ( $codeIds !== []) {
$rows = $db -> table ( 'bag_issue_item_code' )
-> select ( 'bic_idx, bic_bi2_idx, bic_qty, bic_cancel_qty' )
-> where ( 'bic_lg_idx' , $lgIdx )
-> whereIn ( 'bic_idx' , $codeIds )
-> get ()
-> getResultArray ();
foreach ( $rows as $row ) {
$bicIdx = ( int ) ( $row [ 'bic_idx' ] ? ? 0 );
$bi2Idx = ( int ) ( $row [ 'bic_bi2_idx' ] ? ? 0 );
$qty = ( int ) ( $row [ 'bic_qty' ] ? ? 0 );
$oldCancel = ( int ) ( $row [ 'bic_cancel_qty' ] ? ? 0 );
$isChecked = isset ( $codeCheckedInput [( string ) $bicIdx ]);
$inputCancel = ( int ) ( $codeCancelQtyInput [( string ) $bicIdx ] ? ? 0 );
$newCancel = $isChecked ? $qty : max ( 0 , min ( $qty , $inputCancel ));
if ( $newCancel === $oldCancel ) {
continue ;
}
$db -> table ( 'bag_issue_item_code' )
-> where ( 'bic_idx' , $bicIdx )
-> update ([
'bic_cancel_qty' => $newCancel ,
'bic_state' => ( $newCancel >= $qty ) ? 'cancelled' : 'normal' ,
]);
if ( ! isset ( $issueDeltaMap [ $bi2Idx ])) {
$issueDeltaMap [ $bi2Idx ] = 0 ;
}
$issueDeltaMap [ $bi2Idx ] += ( $newCancel - $oldCancel );
$touchedIssueIds [ $bi2Idx ] = true ;
}
}
}
$fallbackIssueIds = array_values ( array_unique ( array_map ( 'intval' , array_keys ( $issueCancelQtyInput ))));
$fallbackIssueIds = array_values ( array_filter ( $fallbackIssueIds , static fn ( $v ) : bool => $v > 0 ));
if ( $fallbackIssueIds !== []) {
$issueRows = $issueModel
-> where ( 'bi2_lg_idx' , $lgIdx )
-> whereIn ( 'bi2_idx' , $fallbackIssueIds )
-> findAll ();
foreach ( $issueRows as $issueRow ) {
$bi2Idx = ( int ) ( $issueRow -> bi2_idx ? ? 0 );
if ( $bi2Idx <= 0 || isset ( $touchedIssueIds [ $bi2Idx ])) {
continue ;
}
$baseQty = ( int ) ( $issueRow -> bi2_qty ? ? 0 );
$isChecked = isset ( $issueCheckedInput [( string ) $bi2Idx ]);
$inputCancel = ( int ) ( $issueCancelQtyInput [( string ) $bi2Idx ] ? ? 0 );
$newCancel = $isChecked ? $baseQty : max ( 0 , min ( $baseQty , $inputCancel ));
if ( $newCancel <= 0 ) {
continue ;
}
$issueModel -> update ( $bi2Idx , [
'bi2_qty' => $baseQty - $newCancel ,
'bi2_status' => ( $newCancel >= $baseQty ) ? 'cancelled' : 'normal' ,
]);
$inventoryModel -> adjustQty (
$lgIdx ,
( string ) ( $issueRow -> bi2_bag_code ? ? '' ),
( string ) ( $issueRow -> bi2_bag_name ? ? '' ),
$newCancel
);
}
}
if ( $touchedIssueIds !== []) {
$issueIds = array_keys ( $touchedIssueIds );
$aggRows = $db -> table ( 'bag_issue_item_code' )
-> select ( 'bic_bi2_idx, SUM(bic_qty) AS sum_qty, SUM(bic_cancel_qty) AS sum_cancel' , false )
-> whereIn ( 'bic_bi2_idx' , $issueIds )
-> groupBy ( 'bic_bi2_idx' )
-> get ()
-> getResultArray ();
$aggMap = [];
foreach ( $aggRows as $row ) {
$idx = ( int ) ( $row [ 'bic_bi2_idx' ] ? ? 0 );
if ( $idx <= 0 ) {
continue ;
}
$aggMap [ $idx ] = [
'sum_qty' => ( int ) ( $row [ 'sum_qty' ] ? ? 0 ),
'sum_cancel' => ( int ) ( $row [ 'sum_cancel' ] ? ? 0 ),
];
}
$issues = $issueModel -> where ( 'bi2_lg_idx' , $lgIdx ) -> whereIn ( 'bi2_idx' , $issueIds ) -> findAll ();
foreach ( $issues as $issue ) {
$bi2Idx = ( int ) ( $issue -> bi2_idx ? ? 0 );
$sumQty = ( int ) ( $aggMap [ $bi2Idx ][ 'sum_qty' ] ? ? ( int ) ( $issue -> bi2_qty ? ? 0 ));
$sumCancel = ( int ) ( $aggMap [ $bi2Idx ][ 'sum_cancel' ] ? ? 0 );
$remain = max ( 0 , $sumQty - $sumCancel );
$issueModel -> update ( $bi2Idx , [
'bi2_qty' => $remain ,
'bi2_status' => ( $remain <= 0 ? 'cancelled' : 'normal' ),
]);
$delta = ( int ) ( $issueDeltaMap [ $bi2Idx ] ? ? 0 );
if ( $delta !== 0 ) {
$inventoryModel -> adjustQty (
$lgIdx ,
( string ) ( $issue -> bi2_bag_code ? ? '' ),
( string ) ( $issue -> bi2_bag_name ? ? '' ),
$delta
);
}
}
}
$db -> transComplete ();
if ( ! $db -> transStatus ()) {
return redirect () -> back () -> withInput () -> with ( 'error' , '불출 취소 저장 중 오류가 발생했습니다.' );
}
return redirect () -> back () -> with ( 'success' , '불출 취소 수량이 저장되었습니다.' );
}
// ──────────────────────────────────────────────
// 재고 관리
// ──────────────────────────────────────────────
public function inventory () : string
{
$lgIdx = $this -> lgIdx ();
$baseDate = trim (( string ) ( $this -> request -> getGet ( 'base_date' ) ? ? date ( 'Y-m-d' )));
if ( ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $baseDate )) {
$baseDate = date ( 'Y-m-d' );
}
$agencyIdx = ( int ) ( $this -> request -> getGet ( 'agency_idx' ) ? ? 0 );
$data = [
'baseDate' => $baseDate ,
'agencyIdx' => $agencyIdx ,
'agencyOptions' => [],
'rows' => [],
'subtotals' => [],
'grandTotals' => [ 'total' => 0 , 'gugun' => 0 , 'agency' => 0 ],
];
if ( $lgIdx ) {
$agencyModel = model ( SalesAgencyModel :: class );
$data [ 'agencyOptions' ] = $agencyModel
-> where ( 'sa_lg_idx' , $lgIdx )
-> orderForDisplay ()
-> findAll ();
$report = $this -> buildInventoryStatusData ( $lgIdx , $baseDate , $agencyIdx );
$data = array_merge ( $data , $report );
}
return $this -> render ( '재고 현황' , 'bag/inventory' , $data );
}
public function inventoryExport () : ResponseInterface | RedirectResponse
{
helper ([ 'admin' , 'export' ]);
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/inventory' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$baseDate = trim (( string ) ( $this -> request -> getGet ( 'base_date' ) ? ? date ( 'Y-m-d' )));
if ( ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $baseDate )) {
$baseDate = date ( 'Y-m-d' );
}
$agencyIdx = ( int ) ( $this -> request -> getGet ( 'agency_idx' ) ? ? 0 );
$report = $this -> buildInventoryStatusData ( $lgIdx , $baseDate , $agencyIdx );
$rows = [];
foreach (( $report [ 'rows' ] ? ? []) as $row ) {
$rows [] = [
( string ) ( $row [ 'group' ] ? ? '' ),
( string ) ( $row [ 'name' ] ? ? '' ),
( int ) ( $row [ 'total_qty' ] ? ? 0 ),
( int ) ( $row [ 'gugun_qty' ] ? ? 0 ),
( int ) ( $row [ 'agency_qty' ] ? ? 0 ),
];
}
foreach (( $report [ 'subtotals' ] ? ? []) as $subtotal ) {
$rows [] = [
( string ) ( $subtotal [ 'group' ] ? ? '' ),
'소계' ,
( int ) ( $subtotal [ 'total_qty' ] ? ? 0 ),
( int ) ( $subtotal [ 'gugun_qty' ] ? ? 0 ),
( int ) ( $subtotal [ 'agency_qty' ] ? ? 0 ),
];
}
$rows [] = [
'' ,
'합계' ,
( int ) ( $report [ 'grandTotals' ][ 'total' ] ? ? 0 ),
( int ) ( $report [ 'grandTotals' ][ 'gugun' ] ? ? 0 ),
( int ) ( $report [ 'grandTotals' ][ 'agency' ] ? ? 0 ),
];
export_xlsx (
'재고현황_' . str_replace ( '-' , '' , $baseDate ) . '.xlsx' ,
'재고현황' ,
[ '품목구분' , '봉투/스티커 종류' , '계' , '시군구 재고' , '대행소 재고' ],
$rows
);
}
/**
* @ return array {
* rows : list < array { group : string , name : string , total_qty : int , gugun_qty : int , agency_qty : int } > ,
* subtotals : list < array { group : string , total_qty : int , gugun_qty : int , agency_qty : int } > ,
* grandTotals : array { total : int , gugun : int , agency : int }
* }
*/
private function buildInventoryStatusData ( int $lgIdx , string $baseDate , int $agencyIdx ) : array
{
$builder = model ( BagInventoryModel :: class )
-> where ( 'bi_lg_idx' , $lgIdx )
-> where ( 'bi_updated_at <=' , $baseDate . ' 23:59:59' )
-> orderBy ( 'bi_bag_code' , 'ASC' );
// 대행소 재고 연계 테이블이 아직 없어 agency 필터는 조회조건 표시용으로만 유지한다.
if ( $agencyIdx > 0 ) {
// no-op
}
$list = $builder -> findAll ();
$rows = [];
$subtotalMap = [];
$groupOrder = [];
$grand = [ 'total' => 0 , 'gugun' => 0 , 'agency' => 0 ];
foreach ( $list as $row ) {
$bagName = trim (( string ) ( $row -> bi_bag_name ? ? '' ));
$bagCode = trim (( string ) ( $row -> bi_bag_code ? ? '' ));
$group = $this -> inventoryGroupLabel ( $bagName , $bagCode );
if ( ! isset ( $groupOrder [ $group ])) {
$groupOrder [ $group ] = count ( $groupOrder );
}
$gugunQty = max ( 0 , ( int ) ( $row -> bi_qty ? ? 0 ));
$agencyQty = 0 ;
$totalQty = $gugunQty + $agencyQty ;
$rows [] = [
'group' => $group ,
'name' => $bagName !== '' ? $bagName : $bagCode ,
'total_qty' => $totalQty ,
'gugun_qty' => $gugunQty ,
'agency_qty' => $agencyQty ,
'_sort' => $groupOrder [ $group ],
];
if ( ! isset ( $subtotalMap [ $group ])) {
$subtotalMap [ $group ] = [ 'group' => $group , 'total_qty' => 0 , 'gugun_qty' => 0 , 'agency_qty' => 0 ];
}
$subtotalMap [ $group ][ 'total_qty' ] += $totalQty ;
$subtotalMap [ $group ][ 'gugun_qty' ] += $gugunQty ;
$subtotalMap [ $group ][ 'agency_qty' ] += $agencyQty ;
$grand [ 'total' ] += $totalQty ;
$grand [ 'gugun' ] += $gugunQty ;
$grand [ 'agency' ] += $agencyQty ;
}
usort ( $rows , static function ( array $a , array $b ) : int {
$g = (( int ) ( $a [ '_sort' ] ? ? 0 )) <=> (( int ) ( $b [ '_sort' ] ? ? 0 ));
if ( $g !== 0 ) {
return $g ;
}
return strnatcmp (( string ) ( $a [ 'name' ] ? ? '' ), ( string ) ( $b [ 'name' ] ? ? '' ));
});
foreach ( $rows as & $row ) {
unset ( $row [ '_sort' ]);
}
unset ( $row );
$subtotals = array_values ( $subtotalMap );
usort ( $subtotals , static function ( array $a , array $b ) use ( $groupOrder ) : int {
return (( int ) ( $groupOrder [ $a [ 'group' ]] ? ? 0 )) <=> (( int ) ( $groupOrder [ $b [ 'group' ]] ? ? 0 ));
});
return [
'rows' => $rows ,
'subtotals' => $subtotals ,
'grandTotals' => $grand ,
];
}
private function inventoryGroupLabel ( string $bagName , string $bagCode ) : string
{
$name = trim ( $bagName );
$code = trim ( $bagCode );
$source = $name !== '' ? $name : $code ;
if ( mb_strpos ( $source , '스티커' ) !== false ) {
if ( mb_strpos ( $source , '음식물' ) !== false ) {
return '음식물 스티커' ;
}
if ( mb_strpos ( $source , '폐기물' ) !== false ) {
return '대형폐기물 스티커' ;
}
return '기타 스티커' ;
}
if ( mb_strpos ( $source , '재사용' ) !== false ) {
return '재사용' ;
}
if ( mb_strpos ( $source , '공공' ) !== false || mb_strpos ( $source , '공동주택' ) !== false ) {
return '공공용' ;
}
if ( mb_strpos ( $source , '음식물' ) !== false ) {
return '음식물 봉투' ;
}
return '일반용' ;
}
public function inspectionSelect () : string | RedirectResponse
{
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/inventory' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$this -> ensureInventoryInspectionTables ();
$this -> ensureInspectionPackSnapshotTable ();
$today = date ( 'Y-m-d' );
$startDate = trim (( string ) ( $this -> request -> getGet ( 'start_date' ) ? ? date ( 'Y-m-01' )));
$endDate = trim (( string ) ( $this -> request -> getGet ( 'end_date' ) ? ? $today ));
$workDate = trim (( string ) ( $this -> request -> getGet ( 'work_date' ) ? ? $today ));
$itemCode = trim (( string ) ( $this -> request -> getGet ( 'item_code' ) ? ? '' ));
$selectedInspectionId = ( int ) ( $this -> request -> getGet ( 'bis_id' ) ? ? 0 );
$viewType = trim (( string ) ( $this -> request -> getGet ( 'view_type' ) ? ? 'box' ));
if ( ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $startDate )) {
$startDate = date ( 'Y-m-01' );
}
if ( ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $endDate )) {
$endDate = $today ;
}
if ( ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $workDate )) {
$workDate = $today ;
}
if ( ! in_array ( $viewType , [ 'box' , 'pack' ], true )) {
$viewType = 'box' ;
}
$inventoryRows = model ( BagInventoryModel :: class )
-> where ( 'bi_lg_idx' , $lgIdx )
-> orderBy ( 'bi_bag_code' , 'ASC' )
-> findAll ();
$db = \Config\Database :: connect ();
$barcodeRows = $db -> table ( 'bag_receiving_pack_code' )
-> select ( 'brpc_bag_code' )
-> distinct ()
-> where ( 'brpc_lg_idx' , $lgIdx )
-> where ( 'brpc_bag_code !=' , '' )
-> get ()
-> getResultArray ();
$barcodeSet = [];
foreach ( $barcodeRows as $row ) {
$code = trim (( string ) ( $row [ 'brpc_bag_code' ] ? ? '' ));
if ( $code !== '' ) {
$barcodeSet [ $code ] = true ;
}
}
$popupItems = [];
foreach ( $inventoryRows as $inv ) {
$code = trim (( string ) ( $inv -> bi_bag_code ? ? '' ));
if ( $code === '' ) {
continue ;
}
$name = trim (( string ) ( $inv -> bi_bag_name ? ? $code ));
$qty = ( int ) ( $inv -> bi_qty ? ? 0 );
$hasBarcode = isset ( $barcodeSet [ $code ]);
$popupItems [] = [
'bag_code' => $code ,
'bag_name' => $name ,
'qty' => $qty ,
'has_barcode' => $hasBarcode ,
];
}
if ( $selectedInspectionId <= 0 ) {
$latestInspection = $db -> table ( 'bag_inventory_inspection' )
-> select ( " bis_idx, (CASE bis_status WHEN 'confirmed' THEN 3 WHEN 'counting' THEN 2 WHEN 'selected' THEN 1 ELSE 0 END) AS status_rank " , false )
-> where ( 'bis_lg_idx' , $lgIdx )
-> where ( 'bis_work_date >=' , $startDate )
-> where ( 'bis_work_date <=' , $endDate )
-> orderBy ( 'status_rank' , 'DESC' )
-> orderBy ( 'bis_work_date' , 'DESC' )
-> orderBy ( 'bis_idx' , 'DESC' )
-> get ()
-> getRowArray ();
$selectedInspectionId = ( int ) ( $latestInspection [ 'bis_idx' ] ? ? 0 );
}
$inspectionRuns = $db -> table ( 'bag_inventory_inspection' )
-> select ( 'bis_idx, bis_work_date, bis_status' )
-> where ( 'bis_lg_idx' , $lgIdx )
-> where ( 'bis_work_date >=' , $startDate )
-> where ( 'bis_work_date <=' , $endDate )
-> orderBy ( 'bis_work_date' , 'DESC' )
-> orderBy ( 'bis_idx' , 'DESC' )
-> get ()
-> getResultArray ();
$overviewBuilder = $db -> table ( 'bag_inventory_inspection_item i' )
-> select ( 'i.bisi_idx, i.bisi_bis_idx, i.bisi_bag_code, i.bisi_bag_name, i.bisi_system_qty, h.bis_work_date, h.bis_status' )
-> join ( 'bag_inventory_inspection h' , 'h.bis_idx = i.bisi_bis_idx' , 'inner' )
-> where ( 'h.bis_lg_idx' , $lgIdx )
-> where ( 'h.bis_work_date >=' , $startDate )
-> where ( 'h.bis_work_date <=' , $endDate );
if ( $selectedInspectionId > 0 ) {
$overviewBuilder -> where ( 'i.bisi_bis_idx' , $selectedInspectionId );
}
if ( $itemCode !== '' ) {
$overviewBuilder -> where ( 'i.bisi_bag_code' , $itemCode );
}
$overviewRows = $overviewBuilder
-> orderBy ( 'h.bis_work_date' , 'ASC' )
-> orderBy ( 'i.bisi_bag_code' , 'ASC' )
-> orderBy ( 'i.bisi_idx' , 'ASC' )
-> get ()
-> getResultArray ();
$overviewRows = $this -> expandInspectionRowsByBox ( $db , $lgIdx , $overviewRows , true );
$selectedInspectionItemId = ( int ) ( $this -> request -> getGet ( 'sel_item_id' ) ? ? 0 );
if ( $selectedInspectionItemId <= 0 && $overviewRows !== []) {
$selectedInspectionItemId = ( int ) ( $overviewRows [ 0 ][ 'bisi_idx' ] ? ? 0 );
}
$selectedInspectionItem = null ;
foreach ( $overviewRows as $row ) {
if (( int ) ( $row [ 'bisi_idx' ] ? ? 0 ) === $selectedInspectionItemId ) {
$selectedInspectionItem = $row ;
$selectedInspectionId = ( int ) ( $row [ 'bisi_bis_idx' ] ? ? 0 );
break ;
}
}
$items = [];
foreach ( $overviewRows as $row ) {
$code = trim (( string ) ( $row [ 'bisi_bag_code' ] ? ? '' ));
if ( $code === '' || isset ( $items [ $code ])) {
continue ;
}
$items [ $code ] = [
'bag_code' => $code ,
'bag_name' => trim (( string ) ( $row [ 'bisi_bag_name' ] ? ? $code )),
];
}
$items = array_values ( $items );
$boxRows = [];
$sheetRows = [];
$selectedBoxCode = trim (( string ) ( $this -> request -> getGet ( 'sel_box_code' ) ? ? '' ));
$selectedPackCode = trim (( string ) ( $this -> request -> getGet ( 'sel_pack_code' ) ? ? '' ));
if ( is_array ( $selectedInspectionItem )) {
$this -> ensureInspectionPackSnapshotForItem ( $lgIdx , $selectedInspectionItemId );
$bagCode = trim (( string ) ( $selectedInspectionItem [ 'bisi_bag_code' ] ? ? '' ));
if ( $bagCode !== '' ) {
$boxRows = $db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> select ( 'bisp_idx, bisp_box_code, bisp_pack_code, bisp_sheet_start_code, bisp_sheet_end_code, bisp_sheet_qty' )
-> where ( 'bisp_lg_idx' , $lgIdx )
-> where ( 'bisp_bisi_idx' , $selectedInspectionItemId )
-> where ( 'bisp_bag_code' , $bagCode )
-> orderBy ( 'bisp_sheet_qty' , 'DESC' )
-> orderBy ( 'bisp_idx' , 'ASC' )
-> get ()
-> getResultArray ();
}
if ( $selectedBoxCode === '' && $boxRows !== []) {
$selectedBoxCode = ( string ) ( $boxRows [ 0 ][ 'bisp_box_code' ] ? ? '' );
}
if ( $selectedPackCode === '' && $boxRows !== []) {
$selectedPackCode = ( string ) ( $boxRows [ 0 ][ 'bisp_pack_code' ] ? ? '' );
}
foreach ( $boxRows as $boxRow ) {
$boxCode = ( string ) ( $boxRow [ 'bisp_box_code' ] ? ? '' );
$packCode = ( string ) ( $boxRow [ 'bisp_pack_code' ] ? ? '' );
if ( $boxCode === '' || $packCode === '' ) {
continue ;
}
if ( $boxCode !== $selectedBoxCode || $packCode !== $selectedPackCode ) {
continue ;
}
$startCode = ( string ) ( $boxRow [ 'bisp_sheet_start_code' ] ? ? '' );
$endCode = ( string ) ( $boxRow [ 'bisp_sheet_end_code' ] ? ? '' );
$sheetRows = [[
'no' => 1 ,
'biss_sheet_code' => $startCode . ' ~ ' . $endCode ,
'biss_system_qty' => max ( 0 , ( int ) ( $boxRow [ 'bisp_sheet_qty' ] ? ? 0 )),
]];
break ;
}
}
return $this -> render ( '실사 선별 조회' , 'bag/inventory_inspection_select_overview' , [
'startDate' => $startDate ,
'endDate' => $endDate ,
'workDate' => $workDate ,
'itemCode' => $itemCode ,
'viewType' => $viewType ,
'inspectionRuns' => $inspectionRuns ,
'items' => $items ,
'selectedInspectionId' => $selectedInspectionId ,
'selectedInspectionItemId' => $selectedInspectionItemId ,
'overviewRows' => $overviewRows ,
'boxRows' => $boxRows ,
'sheetRows' => $sheetRows ,
'selectedBoxCode' => $selectedBoxCode ,
'selectedPackCode' => $selectedPackCode ,
'popupItems' => $popupItems ,
]);
}
public function inspectionWork () : string | RedirectResponse
{
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/inventory' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$this -> ensureInventoryInspectionTables ();
$this -> ensureInspectionPackSnapshotTable ();
$this -> ensureInspectionSheetSnapshotTable ();
$today = date ( 'Y-m-d' );
$startDate = trim (( string ) ( $this -> request -> getGet ( 'start_date' ) ? ? date ( 'Y-m-01' )));
$endDate = trim (( string ) ( $this -> request -> getGet ( 'end_date' ) ? ? $today ));
$workDate = trim (( string ) ( $this -> request -> getGet ( 'work_date' ) ? ? $today ));
$itemCode = trim (( string ) ( $this -> request -> getGet ( 'item_code' ) ? ? '' ));
$selectedInspectionId = ( int ) ( $this -> request -> getGet ( 'bis_id' ) ? ? 0 );
$viewType = trim (( string ) ( $this -> request -> getGet ( 'view_type' ) ? ? 'box' ));
if ( ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $startDate )) {
$startDate = date ( 'Y-m-01' );
}
if ( ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $endDate )) {
$endDate = $today ;
}
if ( ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $workDate )) {
$workDate = $today ;
}
if ( ! in_array ( $viewType , [ 'box' , 'pack' ], true )) {
$viewType = 'box' ;
}
$inventoryRows = model ( BagInventoryModel :: class )
-> where ( 'bi_lg_idx' , $lgIdx )
-> orderBy ( 'bi_bag_code' , 'ASC' )
-> findAll ();
$db = \Config\Database :: connect ();
$barcodeRows = $db -> table ( 'bag_receiving_pack_code' )
-> select ( 'brpc_bag_code' )
-> distinct ()
-> where ( 'brpc_lg_idx' , $lgIdx )
-> where ( 'brpc_bag_code !=' , '' )
-> get ()
-> getResultArray ();
$barcodeSet = [];
foreach ( $barcodeRows as $row ) {
$code = trim (( string ) ( $row [ 'brpc_bag_code' ] ? ? '' ));
if ( $code !== '' ) {
$barcodeSet [ $code ] = true ;
}
}
$popupItems = [];
foreach ( $inventoryRows as $inv ) {
$code = trim (( string ) ( $inv -> bi_bag_code ? ? '' ));
if ( $code === '' ) {
continue ;
}
$popupItems [] = [
'bag_code' => $code ,
'bag_name' => trim (( string ) ( $inv -> bi_bag_name ? ? $code )),
'qty' => ( int ) ( $inv -> bi_qty ? ? 0 ),
'has_barcode' => isset ( $barcodeSet [ $code ]),
];
}
if ( $selectedInspectionId <= 0 ) {
$latestInspection = $db -> table ( 'bag_inventory_inspection' )
-> select ( " bis_idx, (CASE bis_status WHEN 'confirmed' THEN 3 WHEN 'counting' THEN 2 WHEN 'selected' THEN 1 ELSE 0 END) AS status_rank " , false )
-> where ( 'bis_lg_idx' , $lgIdx )
-> where ( 'bis_work_date >=' , $startDate )
-> where ( 'bis_work_date <=' , $endDate )
-> orderBy ( 'status_rank' , 'DESC' )
-> orderBy ( 'bis_work_date' , 'DESC' )
-> orderBy ( 'bis_idx' , 'DESC' )
-> get ()
-> getRowArray ();
$selectedInspectionId = ( int ) ( $latestInspection [ 'bis_idx' ] ? ? 0 );
}
$inspectionRuns = $db -> table ( 'bag_inventory_inspection' )
-> select ( 'bis_idx, bis_work_date, bis_status' )
-> where ( 'bis_lg_idx' , $lgIdx )
-> where ( 'bis_work_date >=' , $startDate )
-> where ( 'bis_work_date <=' , $endDate )
-> orderBy ( 'bis_work_date' , 'DESC' )
-> orderBy ( 'bis_idx' , 'DESC' )
-> get ()
-> getResultArray ();
$requestedInspectionItemId = ( int ) ( $this -> request -> getGet ( 'sel_item_id' ) ? ? 0 );
if ( $requestedInspectionItemId > 0 ) {
$this -> ensureInspectionPackSnapshotForItem ( $lgIdx , $requestedInspectionItemId );
}
$overviewBuilder = $db -> table ( 'bag_inventory_inspection_item i' )
-> select ( 'i.bisi_idx, i.bisi_bis_idx, i.bisi_bag_code, i.bisi_bag_name, i.bisi_system_qty, i.bisi_actual_qty, i.bisi_diff_qty, i.bisi_apply_yn, h.bis_work_date, h.bis_status' )
-> join ( 'bag_inventory_inspection h' , 'h.bis_idx = i.bisi_bis_idx' , 'inner' )
-> where ( 'h.bis_lg_idx' , $lgIdx )
-> where ( 'h.bis_work_date >=' , $startDate )
-> where ( 'h.bis_work_date <=' , $endDate );
if ( $selectedInspectionId > 0 ) {
$overviewBuilder -> where ( 'i.bisi_bis_idx' , $selectedInspectionId );
}
if ( $itemCode !== '' ) {
$overviewBuilder -> where ( 'i.bisi_bag_code' , $itemCode );
}
$overviewRows = $overviewBuilder
-> orderBy ( 'h.bis_work_date' , 'ASC' )
-> orderBy ( 'i.bisi_bag_code' , 'ASC' )
-> orderBy ( 'i.bisi_idx' , 'ASC' )
-> get ()
-> getResultArray ();
$overviewRows = $this -> expandInspectionRowsByBox ( $db , $lgIdx , $overviewRows , true );
$selectedInspectionItemId = ( int ) ( $this -> request -> getGet ( 'sel_item_id' ) ? ? 0 );
if ( $selectedInspectionItemId <= 0 && $overviewRows !== []) {
$selectedInspectionItemId = ( int ) ( $overviewRows [ 0 ][ 'bisi_idx' ] ? ? 0 );
}
$selectedInspectionItem = null ;
foreach ( $overviewRows as $row ) {
if (( int ) ( $row [ 'bisi_idx' ] ? ? 0 ) === $selectedInspectionItemId ) {
$selectedInspectionItem = $row ;
$selectedInspectionId = ( int ) ( $row [ 'bisi_bis_idx' ] ? ? 0 );
break ;
}
}
$items = [];
foreach ( $overviewRows as $row ) {
$code = trim (( string ) ( $row [ 'bisi_bag_code' ] ? ? '' ));
if ( $code === '' || isset ( $items [ $code ])) {
continue ;
}
$items [ $code ] = [
'bag_code' => $code ,
'bag_name' => trim (( string ) ( $row [ 'bisi_bag_name' ] ? ? $code )),
'qty' => ( int ) ( $row [ 'bisi_system_qty' ] ? ? 0 ),
'has_barcode' => true ,
];
}
$items = array_values ( $items );
$boxRows = [];
$sheetRows = [];
$selectedBoxCode = trim (( string ) ( $this -> request -> getGet ( 'sel_box_code' ) ? ? '' ));
$selectedPackCode = trim (( string ) ( $this -> request -> getGet ( 'sel_pack_code' ) ? ? '' ));
if ( is_array ( $selectedInspectionItem )) {
$this -> ensureInspectionPackSnapshotForItem ( $lgIdx , $selectedInspectionItemId );
$bagCode = trim (( string ) ( $selectedInspectionItem [ 'bisi_bag_code' ] ? ? '' ));
if ( $bagCode !== '' ) {
$boxRowsAll = $db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> select ( 'bisp_idx, bisp_box_code, bisp_pack_code, bisp_sheet_start_code, bisp_sheet_end_code, bisp_sheet_qty, bisp_actual_qty, bisp_diff_qty' )
-> where ( 'bisp_lg_idx' , $lgIdx )
-> where ( 'bisp_bisi_idx' , $selectedInspectionItemId )
-> where ( 'bisp_bag_code' , $bagCode )
-> orderBy ( 'bisp_sheet_qty' , 'DESC' )
-> orderBy ( 'bisp_idx' , 'ASC' )
-> get ()
-> getResultArray ();
foreach ( $boxRowsAll as & $boxRow ) {
$systemQty = max ( 0 , ( int ) ( $boxRow [ 'bisp_sheet_qty' ] ? ? 0 ));
$actualRaw = $boxRow [ 'bisp_actual_qty' ] ? ? null ;
$actualQty = $actualRaw === null ? $systemQty : max ( 0 , ( int ) $actualRaw );
// 화면 초기 표시값은 포장량/재고/실사재고를 동일하게 맞춘다.
$displayQty = $actualQty ;
$boxRow [ 'bisp_sheet_qty' ] = $displayQty ;
$boxRow [ 'bisp_actual_qty' ] = $displayQty ;
$boxRow [ 'bisp_diff_qty' ] = 0 ;
}
unset ( $boxRow );
if ( $selectedBoxCode !== '' ) {
$boxRows = array_values ( array_filter (
$boxRowsAll ,
static fn ( array $row ) : bool => trim (( string ) ( $row [ 'bisp_box_code' ] ? ? '' )) === $selectedBoxCode
));
} else {
$boxRows = $boxRowsAll ;
}
}
if ( $selectedBoxCode === '' && $boxRows !== []) {
$selectedBoxCode = ( string ) ( $boxRows [ 0 ][ 'bisp_box_code' ] ? ? '' );
$boxRows = array_values ( array_filter (
$boxRows ,
static fn ( array $row ) : bool => trim (( string ) ( $row [ 'bisp_box_code' ] ? ? '' )) === $selectedBoxCode
));
}
if ( $selectedPackCode === '' && $boxRows !== []) {
$selectedPackCode = ( string ) ( $boxRows [ 0 ][ 'bisp_pack_code' ] ? ? '' );
}
foreach ( $boxRows as $boxRow ) {
$boxCode = ( string ) ( $boxRow [ 'bisp_box_code' ] ? ? '' );
$packCode = ( string ) ( $boxRow [ 'bisp_pack_code' ] ? ? '' );
if ( $boxCode === '' || $packCode === '' ) {
continue ;
}
if ( $boxCode !== $selectedBoxCode || $packCode !== $selectedPackCode ) {
continue ;
}
$this -> ensureInspectionSheetSnapshotForPack (
$lgIdx ,
$selectedInspectionItemId ,
$packCode ,
( string ) ( $boxRow [ 'bisp_sheet_start_code' ] ? ? '' ),
( string ) ( $boxRow [ 'bisp_sheet_end_code' ] ? ? '' )
);
$sheetRows = $db -> table ( 'bag_inventory_inspection_sheet_snapshot' )
-> select ( 'biss_idx, biss_sheet_code, biss_system_qty, biss_actual_qty, biss_diff_qty, biss_checked_yn' )
-> where ( 'biss_lg_idx' , $lgIdx )
-> where ( 'biss_bisi_idx' , $selectedInspectionItemId )
-> where ( 'biss_pack_code' , $packCode )
-> orderBy ( 'biss_sheet_code' , 'ASC' )
-> get ()
-> getResultArray ();
$n = 1 ;
foreach ( $sheetRows as & $sr ) {
$sr [ 'no' ] = $n ++ ;
}
unset ( $sr );
break ;
}
}
return $this -> render ( '실사 선별 관리' , 'bag/inventory_inspection_select' , [
'startDate' => $startDate ,
'endDate' => $endDate ,
'workDate' => $workDate ,
'itemCode' => $itemCode ,
'viewType' => $viewType ,
'inspectionRuns' => $inspectionRuns ,
'items' => $items ,
'popupItems' => $popupItems ,
'overviewRows' => $overviewRows ,
'selectedInspectionItemId' => $selectedInspectionItemId ,
'selectedInspectionId' => $selectedInspectionId ,
'boxRows' => $boxRows ,
'selectedBoxCode' => $selectedBoxCode ,
'selectedPackCode' => $selectedPackCode ,
'sheetRows' => $sheetRows ,
]);
}
/**
* @ return list < array { no : int , sheet_code : string , qty : int } >
*/
private function expandInspectionRowsByBox ( \CodeIgniter\Database\BaseConnection $db , int $lgIdx , array $overviewRows , bool $includeActual ) : array
{
if ( $overviewRows === []) {
return [];
}
$itemIds = array_values ( array_filter ( array_map (
static fn ( array $r ) : int => ( int ) ( $r [ 'bisi_idx' ] ? ? 0 ),
$overviewRows
)));
if ( $itemIds === []) {
return $overviewRows ;
}
$boxAggRows = $db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> select ( 'bisp_bisi_idx, bisp_box_code, SUM(bisp_sheet_qty) AS sum_system, SUM(COALESCE(bisp_actual_qty,0)) AS sum_actual, SUM(COALESCE(bisp_diff_qty,0)) AS sum_diff' , false )
-> where ( 'bisp_lg_idx' , $lgIdx )
-> whereIn ( 'bisp_bisi_idx' , $itemIds )
-> groupBy ( 'bisp_bisi_idx, bisp_box_code' )
-> orderBy ( 'bisp_bisi_idx' , 'ASC' )
-> orderBy ( 'bisp_box_code' , 'ASC' )
-> get ()
-> getResultArray ();
$boxAggMap = [];
foreach ( $boxAggRows as $bRow ) {
$id = ( int ) ( $bRow [ 'bisp_bisi_idx' ] ? ? 0 );
if ( $id <= 0 ) {
continue ;
}
$boxAggMap [ $id ][] = $bRow ;
}
$expandedRows = [];
foreach ( $overviewRows as $row ) {
$itemId = ( int ) ( $row [ 'bisi_idx' ] ? ? 0 );
$group = $boxAggMap [ $itemId ] ? ? [];
if ( $group === []) {
$row [ 'box_code' ] = '' ;
$expandedRows [] = $row ;
continue ;
}
foreach ( $group as $g ) {
$expanded = $row ;
$expanded [ 'box_code' ] = trim (( string ) ( $g [ 'bisp_box_code' ] ? ? '' ));
$expanded [ 'bisi_total_system_qty' ] = ( int ) ( $row [ 'bisi_system_qty' ] ? ? 0 );
$expanded [ 'bisi_system_qty' ] = ( int ) ( $g [ 'sum_system' ] ? ? 0 );
if ( $includeActual ) {
$expanded [ 'bisi_actual_qty' ] = ( int ) ( $g [ 'sum_actual' ] ? ? 0 );
$expanded [ 'bisi_diff_qty' ] = ( int ) ( $g [ 'sum_diff' ] ? ? 0 );
}
$expandedRows [] = $expanded ;
}
}
return $expandedRows ;
}
private function expandInspectionRowsByPack ( \CodeIgniter\Database\BaseConnection $db , int $lgIdx , array $overviewRows , bool $includeActual ) : array
{
if ( $overviewRows === []) {
return [];
}
$itemIds = array_values ( array_filter ( array_map (
static fn ( array $r ) : int => ( int ) ( $r [ 'bisi_idx' ] ? ? 0 ),
$overviewRows
)));
if ( $itemIds === []) {
return $overviewRows ;
}
$packRows = $db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> select ( 'bisp_bisi_idx, bisp_idx, bisp_box_code, bisp_sheet_qty, COALESCE(bisp_actual_qty,0) AS bisp_actual_qty, COALESCE(bisp_diff_qty,0) AS bisp_diff_qty' , false )
-> where ( 'bisp_lg_idx' , $lgIdx )
-> whereIn ( 'bisp_bisi_idx' , $itemIds )
-> orderBy ( 'bisp_bisi_idx' , 'ASC' )
-> orderBy ( 'bisp_box_code' , 'ASC' )
-> orderBy ( 'bisp_idx' , 'ASC' )
-> get ()
-> getResultArray ();
$packMap = [];
foreach ( $packRows as $pRow ) {
$id = ( int ) ( $pRow [ 'bisp_bisi_idx' ] ? ? 0 );
if ( $id <= 0 ) {
continue ;
}
$packMap [ $id ][] = $pRow ;
}
$expandedRows = [];
foreach ( $overviewRows as $row ) {
$itemId = ( int ) ( $row [ 'bisi_idx' ] ? ? 0 );
$group = $packMap [ $itemId ] ? ? [];
if ( $group === []) {
$row [ 'box_code' ] = '' ;
$expandedRows [] = $row ;
continue ;
}
foreach ( $group as $g ) {
$expanded = $row ;
$expanded [ 'box_code' ] = trim (( string ) ( $g [ 'bisp_box_code' ] ? ? '' ));
$expanded [ 'bisi_total_system_qty' ] = ( int ) ( $row [ 'bisi_system_qty' ] ? ? 0 );
$expanded [ 'bisi_system_qty' ] = ( int ) ( $g [ 'bisp_sheet_qty' ] ? ? 0 );
if ( $includeActual ) {
$expanded [ 'bisi_actual_qty' ] = ( int ) ( $g [ 'bisp_actual_qty' ] ? ? 0 );
$expanded [ 'bisi_diff_qty' ] = ( int ) ( $g [ 'bisp_diff_qty' ] ? ? 0 );
}
$expandedRows [] = $expanded ;
}
}
return $expandedRows ;
}
/**
* @ return list < array { no : int , sheet_code : string , qty : int } >
*/
private function expandSheetCodes ( string $startCode , string $endCode ) : array
{
$startCode = trim ( $startCode );
$endCode = trim ( $endCode );
if ( $startCode === '' || $endCode === '' ) {
return [];
}
if ( preg_match ( '/^(.*?)(\d+)$/' , $startCode , $sm ) !== 1 || preg_match ( '/^(.*?)(\d+)$/' , $endCode , $em ) !== 1 ) {
return [[ 'no' => 1 , 'sheet_code' => $startCode , 'qty' => 1 ]];
}
$startPrefix = ( string ) ( $sm [ 1 ] ? ? '' );
$endPrefix = ( string ) ( $em [ 1 ] ? ? '' );
$startNumRaw = ( string ) ( $sm [ 2 ] ? ? '' );
$endNumRaw = ( string ) ( $em [ 2 ] ? ? '' );
if ( $startPrefix !== $endPrefix ) {
return [[ 'no' => 1 , 'sheet_code' => $startCode , 'qty' => 1 ]];
}
$startNum = ( int ) $startNumRaw ;
$endNum = ( int ) $endNumRaw ;
if ( $startNum <= 0 || $endNum < $startNum ) {
return [[ 'no' => 1 , 'sheet_code' => $startCode , 'qty' => 1 ]];
}
$width = max ( strlen ( $startNumRaw ), strlen ( $endNumRaw ));
$rows = [];
$no = 1 ;
for ( $n = $startNum ; $n <= $endNum ; $n ++ ) {
$rows [] = [
'no' => $no ++ ,
'sheet_code' => $startPrefix . str_pad (( string ) $n , $width , '0' , STR_PAD_LEFT ),
'qty' => 1 ,
];
if ( $no > 10000 ) {
break ;
}
}
return $rows ;
}
public function inspectionRun () : RedirectResponse
{
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/inventory' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$this -> ensureInventoryInspectionTables ();
$this -> ensureInspectionPackSnapshotTable ();
$this -> ensureInspectionSheetSnapshotTable ();
$workDate = trim (( string ) ( $this -> request -> getPost ( 'work_date' ) ? ? '' ));
if ( ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $workDate )) {
return redirect () -> back () -> withInput () -> with ( 'error' , '작업일자를 확인해 주세요.' );
}
$selectedCodes = $this -> request -> getPost ( 'bag_codes' );
$selectedCodes = is_array ( $selectedCodes ) ? array_values ( array_unique ( array_map ( static fn ( $v ) : string => trim (( string ) $v ), $selectedCodes ))) : [];
$selectedCodes = array_values ( array_filter ( $selectedCodes , static fn ( $v ) : bool => $v !== '' ));
if ( $selectedCodes === []) {
return redirect () -> back () -> withInput () -> with ( 'error' , '실사 대상 품목을 선택해 주세요.' );
}
$db = \Config\Database :: connect ();
$barcodeRows = $db -> table ( 'bag_receiving_pack_code' )
-> select ( 'brpc_bag_code' )
-> distinct ()
-> where ( 'brpc_lg_idx' , $lgIdx )
-> whereIn ( 'brpc_bag_code' , $selectedCodes )
-> get ()
-> getResultArray ();
$barcodeSet = [];
foreach ( $barcodeRows as $row ) {
$code = trim (( string ) ( $row [ 'brpc_bag_code' ] ? ? '' ));
if ( $code !== '' ) {
$barcodeSet [ $code ] = true ;
}
}
$effectiveCodes = array_values ( array_filter ( $selectedCodes , static fn ( $code ) : bool => isset ( $barcodeSet [ $code ])));
if ( $effectiveCodes === []) {
return redirect () -> back () -> withInput () -> with ( 'error' , '바코드가 있는 품목만 실사 대상으로 선택할 수 있습니다.' );
}
foreach ( $effectiveCodes as $code ) {
$this -> ensureReceivingPackCodesForBag ( $lgIdx , $code );
}
$inventoryRows = $db -> table ( 'bag_inventory' )
-> select ( 'bi_bag_code, bi_bag_name, bi_qty' )
-> where ( 'bi_lg_idx' , $lgIdx )
-> whereIn ( 'bi_bag_code' , $effectiveCodes )
-> orderBy ( 'bi_bag_code' , 'ASC' )
-> get ()
-> getResultArray ();
if ( $inventoryRows === []) {
return redirect () -> back () -> withInput () -> with ( 'error' , '선택한 품목의 재고 데이터가 없습니다.' );
}
$db -> transStart ();
$firstInspectionItemId = 0 ;
$db -> table ( 'bag_inventory_inspection' ) -> insert ([
'bis_lg_idx' => $lgIdx ,
'bis_work_date' => $workDate ,
'bis_status' => 'selected' ,
'bis_reg_mb_idx' => ( int ) ( session () -> get ( 'mb_idx' ) ? ? 0 ),
'bis_regdate' => date ( 'Y-m-d H:i:s' ),
'bis_moddate' => null ,
]);
$inspectionId = ( int ) $db -> insertID ();
foreach ( $inventoryRows as $row ) {
$code = trim (( string ) ( $row [ 'bi_bag_code' ] ? ? '' ));
if ( $code === '' || ! isset ( $barcodeSet [ $code ])) {
continue ;
}
$systemQty = ( int ) ( $row [ 'bi_qty' ] ? ? 0 );
$db -> table ( 'bag_inventory_inspection_item' ) -> insert ([
'bisi_bis_idx' => $inspectionId ,
'bisi_bag_code' => $code ,
'bisi_bag_name' => trim (( string ) ( $row [ 'bi_bag_name' ] ? ? $code )),
'bisi_system_qty' => $systemQty ,
'bisi_actual_qty' => null ,
'bisi_diff_qty' => 0 ,
'bisi_has_barcode' => 'Y' ,
'bisi_apply_yn' => 'N' ,
]);
$inspectionItemId = ( int ) $db -> insertID ();
if ( $firstInspectionItemId <= 0 && $inspectionItemId > 0 ) {
$firstInspectionItemId = $inspectionItemId ;
}
}
$db -> transComplete ();
if ( ! $db -> transStatus () || $inspectionId <= 0 ) {
return redirect () -> back () -> withInput () -> with ( 'error' , '전산 선별 처리 중 오류가 발생했습니다.' );
}
$query = http_build_query ([
'start_date' => $workDate ,
'end_date' => $workDate ,
'bis_id' => $inspectionId ,
'sel_item_id' => $firstInspectionItemId ,
]);
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $query ))
-> with ( 'success' , '전산 선별 처리가 완료되었습니다.' );
}
public function inspectionSelectSave () : RedirectResponse
{
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/inventory' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$this -> ensureInventoryInspectionTables ();
$this -> ensureInspectionPackSnapshotTable ();
$this -> ensureInspectionSheetSnapshotTable ();
$inspectionItemId = ( int ) ( $this -> request -> getPost ( 'bisi_idx' ) ? ? 0 );
if ( $inspectionItemId <= 0 ) {
return redirect () -> back () -> with ( 'error' , '실사 대상 품목이 올바르지 않습니다.' );
}
$returnQuery = $this -> inspectionReturnQueryFromPost ( $inspectionItemId );
$db = \Config\Database :: connect ();
$item = $db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_idx' , $inspectionItemId )
-> get ()
-> getRowArray ();
if ( ! is_array ( $item ) || ( int ) ( $item [ 'bisi_idx' ] ? ? 0 ) <= 0 ) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '실사 대상 품목을 찾을 수 없습니다.' );
}
$requestInspectionId = ( int ) ( $this -> request -> getPost ( 'bis_id' ) ? ? 0 );
$itemInspectionId = ( int ) ( $item [ 'bisi_bis_idx' ] ? ? 0 );
if ( $requestInspectionId > 0 && $requestInspectionId !== $itemInspectionId ) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '선택한 실사 작업과 품목 정보가 일치하지 않습니다.' );
}
$header = $db -> table ( 'bag_inventory_inspection' )
-> select ( 'bis_idx' )
-> where ( 'bis_idx' , $itemInspectionId )
-> where ( 'bis_lg_idx' , $lgIdx )
-> get ()
-> getRowArray ();
if ( ! is_array ( $header ) || ( int ) ( $header [ 'bis_idx' ] ? ? 0 ) <= 0 ) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '실사 작업 정보가 올바르지 않습니다.' );
}
$actualInput = $this -> request -> getPost ( 'pack_actual_qty' );
$actualInput = is_array ( $actualInput ) ? $actualInput : [];
$actualJson = trim (( string ) ( $this -> request -> getPost ( 'pack_actual_json' ) ? ? '' ));
$actualFromJson = false ;
if ( $actualJson !== '' ) {
$decoded = json_decode ( $actualJson , true );
if ( is_array ( $decoded )) {
$actualInput = [];
foreach ( $decoded as $k => $v ) {
$key = trim (( string ) $k );
if ( $key === '' || ! ctype_digit ( $key )) {
continue ;
}
$actualInput [ $key ] = max ( 0 , ( int ) $v );
}
$actualFromJson = true ;
}
}
if ( $actualFromJson && $actualInput === []) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '저장할 실사 수량(JSON)이 비어 있습니다. 다시 시도해 주세요.' );
}
if ( ! $actualFromJson && $actualInput === []) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '저장할 실사 수량이 없습니다. 수량을 변경한 뒤 다시 저장해 주세요.' );
}
$snapshotRows = $db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> select ( 'bisp_idx, bisp_bag_code, bisp_pack_code, bisp_sheet_start_code, bisp_sheet_end_code, bisp_sheet_qty, bisp_actual_qty' )
-> where ( 'bisp_lg_idx' , $lgIdx )
-> where ( 'bisp_bisi_idx' , $inspectionItemId )
-> orderBy ( 'bisp_idx' , 'ASC' )
-> get ()
-> getResultArray ();
if ( $snapshotRows === []) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '실사 팩 스냅샷이 없습니다.' );
}
$db -> transStart ();
$sumActual = 0 ;
$packUpdates = [];
$changedPackQtyMap = [];
$bagCodeForSync = trim (( string ) ( $item [ 'bisi_bag_code' ] ? ? '' ));
$capacityMap = [];
if ( $bagCodeForSync !== '' ) {
$capacityRows = $db -> table ( 'bag_receiving_pack_code' )
-> select ( 'brpc_pack_code, brpc_sheet_qty' )
-> where ( 'brpc_lg_idx' , $lgIdx )
-> where ( 'brpc_bag_code' , $bagCodeForSync )
-> where ( 'brpc_pack_code !=' , '' )
-> get ()
-> getResultArray ();
foreach ( $capacityRows as $cRow ) {
$packCode = trim (( string ) ( $cRow [ 'brpc_pack_code' ] ? ? '' ));
if ( $packCode === '' ) {
continue ;
}
$capacityMap [ $packCode ] = max ( 0 , ( int ) ( $cRow [ 'brpc_sheet_qty' ] ? ? 0 ));
}
}
foreach ( $snapshotRows as $row ) {
$idx = ( int ) ( $row [ 'bisp_idx' ] ? ? 0 );
if ( $idx <= 0 ) {
continue ;
}
$systemQty = max ( 0 , ( int ) ( $row [ 'bisp_sheet_qty' ] ? ? 0 ));
$existingActualRaw = $row [ 'bisp_actual_qty' ] ? ? null ;
$existingActual = $existingActualRaw === null ? $systemQty : max ( 0 , ( int ) $existingActualRaw );
$key = ( string ) $idx ;
$actualQty = array_key_exists ( $key , $actualInput )
? max ( 0 , ( int ) $actualInput [ $key ])
: $existingActual ;
$packCode = trim (( string ) ( $row [ 'bisp_pack_code' ] ? ? '' ));
$startCode = trim (( string ) ( $row [ 'bisp_sheet_start_code' ] ? ? '' ));
$currentEndCode = trim (( string ) ( $row [ 'bisp_sheet_end_code' ] ? ? '' ));
if ( $packCode !== '' && isset ( $capacityMap [ $packCode ])) {
$maxQty = ( int ) ( $capacityMap [ $packCode ] ? ? 0 );
if ( $maxQty > 0 && $actualQty > $maxQty ) {
$db -> transRollback ();
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '팩 ' . $packCode . '의 허용 수량(' . number_format ( $maxQty ) . '장)을 초과했습니다.' );
}
}
$nextEndCode = $this -> resolveSheetEndCodeByQty ( $startCode , $currentEndCode , $actualQty );
$sumActual += $actualQty ;
if ( ! array_key_exists ( $key , $actualInput )) {
continue ;
}
if ( $packCode !== '' && $actualQty !== $existingActual ) {
$changedPackQtyMap [ $packCode ] = [
'qty' => $actualQty ,
'end_code' => $nextEndCode ,
];
}
$packUpdates [] = [
'bisp_idx' => $idx ,
'bisp_sheet_qty' => $actualQty ,
'bisp_actual_qty' => $actualQty ,
'bisp_diff_qty' => 0 ,
'bisp_sheet_end_code' => $nextEndCode ,
'bisp_checked_yn' => 'Y' ,
];
}
if ( $packUpdates !== []) {
$chunk = 500 ;
$count = count ( $packUpdates );
for ( $i = 0 ; $i < $count ; $i += $chunk ) {
$slice = array_slice ( $packUpdates , $i , $chunk );
$db -> table ( 'bag_inventory_inspection_pack_snapshot' ) -> updateBatch ( $slice , 'bisp_idx' );
}
}
// 같은 봉투코드/팩코드는 다른 실사작업에서도 동일 실사값으로 보이도록 동기화
// (요구사항: 48에서 12로 저장하면 47에서도 12로 조회)
if ( $bagCodeForSync !== '' && $changedPackQtyMap !== []) {
foreach ( $changedPackQtyMap as $packCode => $meta ) {
$qty = max ( 0 , ( int ) ( $meta [ 'qty' ] ? ? 0 ));
$endCode = ( string ) ( $meta [ 'end_code' ] ? ? '' );
$db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> where ( 'bisp_lg_idx' , $lgIdx )
-> where ( 'bisp_bag_code' , $bagCodeForSync )
-> where ( 'bisp_pack_code' , $packCode )
-> update ([
'bisp_sheet_qty' => $qty ,
'bisp_actual_qty' => $qty ,
'bisp_diff_qty' => 0 ,
'bisp_sheet_end_code' => $endCode ,
'bisp_checked_yn' => 'Y' ,
]);
}
}
$systemQty = max ( 0 , ( int ) ( $item [ 'bisi_system_qty' ] ? ? 0 ));
$newDiff = $sumActual - $systemQty ;
$prevDiff = ( int ) ( $item [ 'bisi_diff_qty' ] ? ? 0 );
$alreadyApplied = ( string ) ( $item [ 'bisi_apply_yn' ] ? ? 'N' ) === 'Y' ;
$applyDelta = $alreadyApplied ? ( $newDiff - $prevDiff ) : $newDiff ;
$invModel = model ( BagInventoryModel :: class );
if ( $applyDelta !== 0 ) {
$invModel -> adjustQty (
$lgIdx ,
( string ) ( $item [ 'bisi_bag_code' ] ? ? '' ),
( string ) ( $item [ 'bisi_bag_name' ] ? ? '' ),
$applyDelta
);
}
$db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_idx' , $inspectionItemId )
-> update ([
'bisi_system_qty' => $sumActual ,
'bisi_actual_qty' => $sumActual ,
'bisi_diff_qty' => 0 ,
'bisi_apply_yn' => 'Y' ,
]);
$inspectionId = ( int ) ( $item [ 'bisi_bis_idx' ] ? ? 0 );
$remain = $db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_bis_idx' , $inspectionId )
-> where ( 'bisi_apply_yn' , 'N' )
-> countAllResults ();
$db -> table ( 'bag_inventory_inspection' )
-> where ( 'bis_idx' , $inspectionId )
-> update ([
'bis_status' => ( $remain === 0 ) ? 'confirmed' : 'counting' ,
'bis_moddate' => date ( 'Y-m-d H:i:s' ),
]);
$db -> transComplete ();
if ( ! $db -> transStatus ()) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '실사 저장 중 오류가 발생했습니다.' );
}
$savedItem = $db -> table ( 'bag_inventory_inspection_item' )
-> select ( 'bisi_system_qty, bisi_actual_qty, bisi_apply_yn' )
-> where ( 'bisi_idx' , $inspectionItemId )
-> get ()
-> getRowArray ();
if (
! is_array ( $savedItem )
|| ( string ) ( $savedItem [ 'bisi_apply_yn' ] ? ? 'N' ) !== 'Y'
|| ( int ) ( $savedItem [ 'bisi_actual_qty' ] ? ? - 1 ) !== $sumActual
) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '실사 저장 검증에 실패했습니다. 다시 저장해 주세요.' );
}
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'success' , '실사 저장 완료 (합계: ' . number_format ( $sumActual ) . '장)' );
}
public function inspectionSelectConfirm () : RedirectResponse
{
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/inventory' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$inspectionItemId = ( int ) ( $this -> request -> getPost ( 'bisi_idx' ) ? ? 0 );
if ( $inspectionItemId <= 0 ) {
return redirect () -> back () -> with ( 'error' , '실사 대상 품목이 올바르지 않습니다.' );
}
$returnQuery = $this -> inspectionReturnQueryFromPost ( $inspectionItemId );
$db = \Config\Database :: connect ();
$item = $db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_idx' , $inspectionItemId )
-> get ()
-> getRowArray ();
if ( ! is_array ( $item )) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '실사 대상 품목을 찾을 수 없습니다.' );
}
$requestInspectionId = ( int ) ( $this -> request -> getPost ( 'bis_id' ) ? ? 0 );
$itemInspectionId = ( int ) ( $item [ 'bisi_bis_idx' ] ? ? 0 );
if ( $requestInspectionId > 0 && $requestInspectionId !== $itemInspectionId ) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '선택한 실사 작업과 품목 정보가 일치하지 않습니다.' );
}
$header = $db -> table ( 'bag_inventory_inspection' )
-> select ( 'bis_idx' )
-> where ( 'bis_idx' , $itemInspectionId )
-> where ( 'bis_lg_idx' , $lgIdx )
-> get ()
-> getRowArray ();
if ( ! is_array ( $header ) || ( int ) ( $header [ 'bis_idx' ] ? ? 0 ) <= 0 ) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '실사 작업 정보가 올바르지 않습니다.' );
}
if (( string ) ( $item [ 'bisi_apply_yn' ] ? ? 'N' ) === 'Y' ) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '이미 확정된 실사 품목입니다.' );
}
$actualQty = $item [ 'bisi_actual_qty' ];
if ( $actualQty === null ) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '먼저 실사 수량을 저장해 주세요.' );
}
$diff = ( int ) ( $item [ 'bisi_diff_qty' ] ? ? 0 );
$invModel = model ( BagInventoryModel :: class );
$db -> transStart ();
if ( $diff !== 0 ) {
$invModel -> adjustQty (
$lgIdx ,
( string ) ( $item [ 'bisi_bag_code' ] ? ? '' ),
( string ) ( $item [ 'bisi_bag_name' ] ? ? '' ),
$diff
);
}
$db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_idx' , $inspectionItemId )
-> update ([ 'bisi_apply_yn' => 'Y' ]);
$inspectionId = ( int ) ( $item [ 'bisi_bis_idx' ] ? ? 0 );
$remain = $db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_bis_idx' , $inspectionId )
-> where ( 'bisi_apply_yn' , 'N' )
-> countAllResults ();
$db -> table ( 'bag_inventory_inspection' )
-> where ( 'bis_idx' , $inspectionId )
-> update ([
'bis_status' => ( $remain === 0 ) ? 'confirmed' : 'counting' ,
'bis_moddate' => date ( 'Y-m-d H:i:s' ),
]);
$db -> transComplete ();
if ( ! $db -> transStatus ()) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'error' , '실사 확정 중 오류가 발생했습니다.' );
}
return redirect () -> to ( site_url ( 'bag/inventory/inspection-work?' . $returnQuery ))
-> with ( 'success' , '실사 결과가 재고에 반영되었습니다.' );
}
private function inspectionReturnQueryFromPost ( int $fallbackItemId ) : string
{
$startDate = trim (( string ) ( $this -> request -> getPost ( 'start_date' ) ? ? '' ));
$endDate = trim (( string ) ( $this -> request -> getPost ( 'end_date' ) ? ? '' ));
$bisId = ( int ) ( $this -> request -> getPost ( 'bis_id' ) ? ? 0 );
$itemCode = trim (( string ) ( $this -> request -> getPost ( 'item_code' ) ? ? '' ));
$viewType = trim (( string ) ( $this -> request -> getPost ( 'view_type' ) ? ? 'box' ));
$selItemId = ( int ) ( $this -> request -> getPost ( 'sel_item_id' ) ? ? $fallbackItemId );
$selBoxCode = trim (( string ) ( $this -> request -> getPost ( 'sel_box_code' ) ? ? '' ));
$selPackCode = trim (( string ) ( $this -> request -> getPost ( 'sel_pack_code' ) ? ? '' ));
return http_build_query ([
'start_date' => $startDate ,
'end_date' => $endDate ,
'bis_id' => $bisId ,
'item_code' => $itemCode ,
'view_type' => $viewType ,
'sel_item_id' => $selItemId > 0 ? $selItemId : $fallbackItemId ,
'sel_box_code' => $selBoxCode ,
'sel_pack_code' => $selPackCode ,
]);
}
public function inspectionDetail ( int $id ) : string | RedirectResponse
{
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/inventory' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$this -> ensureInventoryInspectionTables ();
$db = \Config\Database :: connect ();
$inspection = $db -> table ( 'bag_inventory_inspection' )
-> where ( 'bis_idx' , $id )
-> where ( 'bis_lg_idx' , $lgIdx )
-> get ()
-> getRowArray ();
if ( ! is_array ( $inspection )) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-select' )) -> with ( 'error' , '실사 작업을 찾을 수 없습니다.' );
}
$items = $db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_bis_idx' , $id )
-> orderBy ( 'bisi_bag_code' , 'ASC' )
-> get ()
-> getResultArray ();
return $this -> render ( '실사 조회' , 'bag/inventory_inspection_detail' , [
'inspection' => $inspection ,
'items' => $items ,
]);
}
public function inspectionSave ( int $id ) : RedirectResponse
{
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/inventory' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$this -> ensureInventoryInspectionTables ();
$db = \Config\Database :: connect ();
$inspection = $db -> table ( 'bag_inventory_inspection' )
-> where ( 'bis_idx' , $id )
-> where ( 'bis_lg_idx' , $lgIdx )
-> get ()
-> getRowArray ();
if ( ! is_array ( $inspection )) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-select' )) -> with ( 'error' , '실사 작업을 찾을 수 없습니다.' );
}
$actualQtyInput = $this -> request -> getPost ( 'actual_qty' );
$actualQtyInput = is_array ( $actualQtyInput ) ? $actualQtyInput : [];
if ( $actualQtyInput === []) {
return redirect () -> back () -> with ( 'error' , '실사 수량을 입력해 주세요.' );
}
$itemIds = array_values ( array_unique ( array_map ( 'intval' , array_keys ( $actualQtyInput ))));
$itemIds = array_values ( array_filter ( $itemIds , static fn ( $v ) : bool => $v > 0 ));
if ( $itemIds === []) {
return redirect () -> back () -> with ( 'error' , '실사 수량 입력 대상이 없습니다.' );
}
$rows = $db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_bis_idx' , $id )
-> whereIn ( 'bisi_idx' , $itemIds )
-> get ()
-> getResultArray ();
$rowMap = [];
foreach ( $rows as $r ) {
$rowMap [( int ) ( $r [ 'bisi_idx' ] ? ? 0 )] = $r ;
}
$db -> transStart ();
foreach ( $itemIds as $itemId ) {
if ( ! isset ( $rowMap [ $itemId ])) {
continue ;
}
$systemQty = ( int ) ( $rowMap [ $itemId ][ 'bisi_system_qty' ] ? ? 0 );
$actualQty = max ( 0 , ( int ) ( $actualQtyInput [( string ) $itemId ] ? ? 0 ));
$diffQty = $actualQty - $systemQty ;
$db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_idx' , $itemId )
-> update ([
'bisi_actual_qty' => $actualQty ,
'bisi_diff_qty' => $diffQty ,
]);
}
$db -> table ( 'bag_inventory_inspection' )
-> where ( 'bis_idx' , $id )
-> update ([
'bis_status' => 'counted' ,
'bis_moddate' => date ( 'Y-m-d H:i:s' ),
]);
$db -> transComplete ();
if ( ! $db -> transStatus ()) {
return redirect () -> back () -> with ( 'error' , '실사 저장 중 오류가 발생했습니다.' );
}
return redirect () -> back () -> with ( 'success' , '실사 수량이 저장되었습니다.' );
}
public function inspectionApply ( int $id ) : RedirectResponse
{
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/inventory' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$this -> ensureInventoryInspectionTables ();
$db = \Config\Database :: connect ();
$inspection = $db -> table ( 'bag_inventory_inspection' )
-> where ( 'bis_idx' , $id )
-> where ( 'bis_lg_idx' , $lgIdx )
-> get ()
-> getRowArray ();
if ( ! is_array ( $inspection )) {
return redirect () -> to ( site_url ( 'bag/inventory/inspection-select' )) -> with ( 'error' , '실사 작업을 찾을 수 없습니다.' );
}
$items = $db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_bis_idx' , $id )
-> where ( 'bisi_actual_qty IS NOT NULL' , null , false )
-> where ( 'bisi_apply_yn' , 'N' )
-> get ()
-> getResultArray ();
if ( $items === []) {
return redirect () -> back () -> with ( 'error' , '재고 반영할 실사 데이터가 없습니다.' );
}
$invModel = model ( BagInventoryModel :: class );
$db -> transStart ();
foreach ( $items as $item ) {
$diff = ( int ) ( $item [ 'bisi_diff_qty' ] ? ? 0 );
if ( $diff !== 0 ) {
$invModel -> adjustQty (
$lgIdx ,
( string ) ( $item [ 'bisi_bag_code' ] ? ? '' ),
( string ) ( $item [ 'bisi_bag_name' ] ? ? '' ),
$diff
);
}
$db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_idx' , ( int ) ( $item [ 'bisi_idx' ] ? ? 0 ))
-> update ([ 'bisi_apply_yn' => 'Y' ]);
}
$db -> table ( 'bag_inventory_inspection' )
-> where ( 'bis_idx' , $id )
-> update ([
'bis_status' => 'applied' ,
'bis_moddate' => date ( 'Y-m-d H:i:s' ),
]);
$db -> transComplete ();
if ( ! $db -> transStatus ()) {
return redirect () -> back () -> with ( 'error' , '재고 반영 중 오류가 발생했습니다.' );
}
return redirect () -> back () -> with ( 'success' , '실사 결과가 재고에 반영되었습니다.' );
}
private function ensureReceivingPackCodeTableAndBackfill ( int $lgIdx ) : void
{
$db = \Config\Database :: connect ();
if ( ! $db -> tableExists ( 'bag_receiving_pack_code' )) {
$db -> query ( <<< 'SQL'
CREATE TABLE IF NOT EXISTS `bag_receiving_pack_code` (
`brpc_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`brpc_br_idx` INT UNSIGNED NOT NULL ,
`brpc_lg_idx` INT UNSIGNED NOT NULL ,
`brpc_bag_code` VARCHAR ( 50 ) NOT NULL ,
`brpc_bag_name` VARCHAR ( 100 ) NOT NULL DEFAULT '' ,
`brpc_lot_no` VARCHAR ( 50 ) NOT NULL DEFAULT '' ,
`brpc_box_code` VARCHAR ( 80 ) NOT NULL DEFAULT '' ,
`brpc_pack_code` VARCHAR ( 80 ) NOT NULL ,
`brpc_sheet_start_code` VARCHAR ( 120 ) NOT NULL ,
`brpc_sheet_end_code` VARCHAR ( 120 ) NOT NULL ,
`brpc_sheet_qty` INT UNSIGNED NOT NULL DEFAULT 0 ,
`brpc_state` VARCHAR ( 20 ) NOT NULL DEFAULT 'in_stock' ,
`brpc_regdate` DATETIME NOT NULL ,
PRIMARY KEY ( `brpc_idx` ),
UNIQUE KEY `uk_brpc_pack_code` ( `brpc_pack_code` ),
KEY `idx_brpc_br_idx` ( `brpc_br_idx` ),
KEY `idx_brpc_lg_bag` ( `brpc_lg_idx` , `brpc_bag_code` )
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
SQL );
}
$unitRows = model ( PackagingUnitModel :: class )
-> where ( 'pu_lg_idx' , $lgIdx )
-> where ( 'pu_state' , 1 )
-> findAll ();
$unitMap = [];
foreach ( $unitRows as $unit ) {
$unitMap [( string ) ( $unit -> pu_bag_code ? ? '' )] = [
'pack_per_sheet' => max ( 1 , ( int ) ( $unit -> pu_pack_per_sheet ? ? 1 )),
'total_per_box' => max ( 1 , ( int ) ( $unit -> pu_total_per_box ? ? 1 )),
];
}
while ( true ) {
$missingRows = $db -> table ( 'bag_receiving r' )
-> select ( 'r.br_idx, r.br_bo_idx, r.br_bag_code, r.br_bag_name, r.br_qty_sheet, o.bo_lot_no' )
-> join ( 'bag_order o' , 'o.bo_idx = r.br_bo_idx' , 'left' )
-> join ( 'bag_receiving_pack_code c' , 'c.brpc_br_idx = r.br_idx' , 'left' )
-> where ( 'r.br_lg_idx' , $lgIdx )
-> where ( 'c.brpc_idx IS NULL' , null , false )
-> orderBy ( 'r.br_idx' , 'ASC' )
-> limit ( 500 )
-> get ()
-> getResultArray ();
if ( $missingRows === []) {
break ;
}
foreach ( $missingRows as $row ) {
$bagCode = ( string ) ( $row [ 'br_bag_code' ] ? ? '' );
$unit = $unitMap [ $bagCode ] ? ? [ 'pack_per_sheet' => 1 , 'total_per_box' => 1 ];
$this -> createReceivingPackCodes (
$lgIdx ,
( int ) ( $row [ 'br_idx' ] ? ? 0 ),
( int ) ( $row [ 'br_bo_idx' ] ? ? 0 ),
$bagCode ,
( string ) ( $row [ 'br_bag_name' ] ? ? '' ),
( int ) ( $row [ 'br_qty_sheet' ] ? ? 0 ),
( int ) ( $unit [ 'pack_per_sheet' ] ? ? 1 ),
( int ) ( $unit [ 'total_per_box' ] ? ? 1 ),
( string ) ( $row [ 'bo_lot_no' ] ? ? '' )
);
}
}
}
private function ensureReceivingPackCodesForBag ( int $lgIdx , string $bagCode ) : void
{
$bagCode = trim ( $bagCode );
if ( $lgIdx <= 0 || $bagCode === '' ) {
return ;
}
$db = \Config\Database :: connect ();
if ( ! $db -> tableExists ( 'bag_receiving_pack_code' )) {
return ;
}
$unit = model ( PackagingUnitModel :: class )
-> where ( 'pu_lg_idx' , $lgIdx )
-> where ( 'pu_state' , 1 )
-> where ( 'pu_bag_code' , $bagCode )
-> first ();
$packPerSheet = max ( 1 , ( int ) ( $unit -> pu_pack_per_sheet ? ? 1 ));
$totalPerBox = max ( 1 , ( int ) ( $unit -> pu_total_per_box ? ? 1 ));
while ( true ) {
$missingRows = $db -> table ( 'bag_receiving r' )
-> select ( 'r.br_idx, r.br_bo_idx, r.br_bag_code, r.br_bag_name, r.br_qty_sheet, o.bo_lot_no' )
-> join ( 'bag_order o' , 'o.bo_idx = r.br_bo_idx' , 'left' )
-> join ( 'bag_receiving_pack_code c' , 'c.brpc_br_idx = r.br_idx' , 'left' )
-> where ( 'r.br_lg_idx' , $lgIdx )
-> where ( 'r.br_bag_code' , $bagCode )
-> where ( 'c.brpc_idx IS NULL' , null , false )
-> orderBy ( 'r.br_idx' , 'ASC' )
-> limit ( 200 )
-> get ()
-> getResultArray ();
if ( $missingRows === []) {
break ;
}
foreach ( $missingRows as $row ) {
$this -> createReceivingPackCodes (
$lgIdx ,
( int ) ( $row [ 'br_idx' ] ? ? 0 ),
( int ) ( $row [ 'br_bo_idx' ] ? ? 0 ),
( string ) ( $row [ 'br_bag_code' ] ? ? '' ),
( string ) ( $row [ 'br_bag_name' ] ? ? '' ),
( int ) ( $row [ 'br_qty_sheet' ] ? ? 0 ),
$packPerSheet ,
$totalPerBox ,
( string ) ( $row [ 'bo_lot_no' ] ? ? '' )
);
}
}
}
private function createReceivingPackCodes (
int $lgIdx ,
int $brIdx ,
int $boIdx ,
string $bagCode ,
string $bagName ,
int $qtySheet ,
int $packPerSheet ,
int $totalPerBox ,
string $lotNo = ''
) : void {
if ( $brIdx <= 0 || $qtySheet <= 0 || $bagCode === '' ) {
return ;
}
$db = \Config\Database :: connect ();
if ( ! $db -> tableExists ( 'bag_receiving_pack_code' )) {
return ;
}
$exists = $db -> table ( 'bag_receiving_pack_code' )
-> where ( 'brpc_br_idx' , $brIdx )
-> countAllResults ();
if ( $exists > 0 ) {
return ;
}
$lotNo = trim ( $lotNo );
if ( $lotNo === '' && $boIdx > 0 ) {
$order = model ( BagOrderModel :: class ) -> find ( $boIdx );
$lotNo = trim (( string ) ( $order -> bo_lot_no ? ? '' ));
}
if ( $lotNo === '' ) {
$lotNo = $bagCode ;
}
$packPerSheet = max ( 1 , $packPerSheet );
$totalPerBox = max ( 1 , $totalPerBox );
$packsPerBox = max ( 1 , intdiv ( $totalPerBox , $packPerSheet ));
$packCount = ( int ) ceil ( $qtySheet / $packPerSheet );
$sheetCursor = 1 ;
$regdate = date ( 'Y-m-d H:i:s' );
$rows = [];
for ( $packSeq = 1 ; $packSeq <= $packCount ; $packSeq ++ ) {
$boxSeq = ( int ) ceil ( $packSeq / $packsPerBox );
$sheetQty = min ( $packPerSheet , max ( 0 , $qtySheet - (( $packSeq - 1 ) * $packPerSheet )));
if ( $sheetQty <= 0 ) {
break ;
}
$sheetStartNo = $sheetCursor ;
$sheetEndNo = $sheetCursor + $sheetQty - 1 ;
$sheetCursor = $sheetEndNo + 1 ;
$boxCode = sprintf ( '%s-%06d-B%03d' , $lotNo , $brIdx , $boxSeq );
$packCode = sprintf ( '%s-%06d-P%03d' , $lotNo , $brIdx , $packSeq );
$startCode = sprintf ( '%s-S%05d' , $packCode , $sheetStartNo );
$endCode = sprintf ( '%s-S%05d' , $packCode , $sheetEndNo );
$rows [] = [
'brpc_br_idx' => $brIdx ,
'brpc_lg_idx' => $lgIdx ,
'brpc_bag_code' => $bagCode ,
'brpc_bag_name' => $bagName !== '' ? $bagName : $bagCode ,
'brpc_lot_no' => $lotNo ,
'brpc_box_code' => $boxCode ,
'brpc_pack_code' => $packCode ,
'brpc_sheet_start_code' => $startCode ,
'brpc_sheet_end_code' => $endCode ,
'brpc_sheet_qty' => $sheetQty ,
'brpc_state' => 'in_stock' ,
'brpc_regdate' => $regdate ,
];
}
if ( $rows !== []) {
$db -> table ( 'bag_receiving_pack_code' ) -> insertBatch ( $rows );
}
}
private function ensureInspectionPackSnapshotTable () : void
{
$db = \Config\Database :: connect ();
if ( $db -> tableExists ( 'bag_inventory_inspection_pack_snapshot' )) {
return ;
}
$db -> query ( <<< 'SQL'
CREATE TABLE IF NOT EXISTS `bag_inventory_inspection_pack_snapshot` (
`bisp_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`bisp_bisi_idx` INT UNSIGNED NOT NULL ,
`bisp_lg_idx` INT UNSIGNED NOT NULL ,
`bisp_bag_code` VARCHAR ( 50 ) NOT NULL ,
`bisp_box_code` VARCHAR ( 80 ) NOT NULL DEFAULT '' ,
`bisp_pack_code` VARCHAR ( 80 ) NOT NULL ,
`bisp_sheet_start_code` VARCHAR ( 120 ) NOT NULL ,
`bisp_sheet_end_code` VARCHAR ( 120 ) NOT NULL ,
`bisp_sheet_qty` INT UNSIGNED NOT NULL DEFAULT 0 ,
`bisp_actual_qty` INT UNSIGNED NULL DEFAULT NULL ,
`bisp_diff_qty` INT NOT NULL DEFAULT 0 ,
`bisp_checked_yn` CHAR ( 1 ) NOT NULL DEFAULT 'N' ,
`bisp_regdate` DATETIME NOT NULL ,
PRIMARY KEY ( `bisp_idx` ),
UNIQUE KEY `uk_bisp_item_pack` ( `bisp_bisi_idx` , `bisp_pack_code` ),
KEY `idx_bisp_item` ( `bisp_bisi_idx` )
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
SQL );
}
private function ensureInspectionSheetSnapshotTable () : void
{
$db = \Config\Database :: connect ();
if ( $db -> tableExists ( 'bag_inventory_inspection_sheet_snapshot' )) {
return ;
}
$db -> query ( <<< 'SQL'
CREATE TABLE IF NOT EXISTS `bag_inventory_inspection_sheet_snapshot` (
`biss_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`biss_bisi_idx` INT UNSIGNED NOT NULL ,
`biss_lg_idx` INT UNSIGNED NOT NULL ,
`biss_pack_code` VARCHAR ( 80 ) NOT NULL ,
`biss_sheet_code` VARCHAR ( 120 ) NOT NULL ,
`biss_system_qty` INT UNSIGNED NOT NULL DEFAULT 1 ,
`biss_actual_qty` INT UNSIGNED NULL DEFAULT NULL ,
`biss_diff_qty` INT NOT NULL DEFAULT 0 ,
`biss_checked_yn` CHAR ( 1 ) NOT NULL DEFAULT 'N' ,
`biss_regdate` DATETIME NOT NULL ,
PRIMARY KEY ( `biss_idx` ),
UNIQUE KEY `uk_biss_item_sheet` ( `biss_bisi_idx` , `biss_sheet_code` ),
KEY `idx_biss_item_pack` ( `biss_bisi_idx` , `biss_pack_code` )
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
SQL );
}
private function ensureInspectionPackSnapshotForItem ( int $lgIdx , int $inspectionItemId , bool $forceRebuild = false ) : void
{
if ( $inspectionItemId <= 0 ) {
return ;
}
$db = \Config\Database :: connect ();
if ( ! $db -> tableExists ( 'bag_inventory_inspection_pack_snapshot' )) {
return ;
}
$item = $db -> table ( 'bag_inventory_inspection_item' )
-> where ( 'bisi_idx' , $inspectionItemId )
-> where ( 'bisi_has_barcode' , 'Y' )
-> get ()
-> getRowArray ();
if ( ! is_array ( $item )) {
return ;
}
$bagCode = trim (( string ) ( $item [ 'bisi_bag_code' ] ? ? '' ));
if ( $bagCode === '' ) {
return ;
}
$this -> ensureReceivingPackCodesForBag ( $lgIdx , $bagCode );
$sourceRows = $db -> table ( 'bag_receiving_pack_code' )
-> select ( 'brpc_box_code, brpc_pack_code, brpc_sheet_start_code, brpc_sheet_end_code, brpc_sheet_qty' )
-> where ( 'brpc_lg_idx' , $lgIdx )
-> where ( 'brpc_bag_code' , $bagCode )
-> where ( 'brpc_state' , 'in_stock' )
-> orderBy ( 'brpc_box_code' , 'ASC' )
-> orderBy ( 'brpc_pack_code' , 'ASC' )
-> get ()
-> getResultArray ();
if ( $sourceRows === []) {
return ;
}
$existingSnapshot = $db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> select ( 'COUNT(*) AS row_cnt' , false )
-> where ( 'bisp_bisi_idx' , $inspectionItemId )
-> get ()
-> getRowArray ();
$existingCount = ( int ) ( $existingSnapshot [ 'row_cnt' ] ? ? 0 );
// 실사 저장 이후에는 사용자가 수정한 수량을 유지해야 하므로
// 강제 재생성이 아니면 기존 스냅샷이 존재할 때 재생성하지 않는다.
if ( ! $forceRebuild && $existingCount > 0 ) {
$this -> applyLatestPackAdjustmentsToSnapshot ( $db , $lgIdx , $inspectionItemId , $bagCode );
return ;
}
// 스냅샷은 선택 품목의 현재 in_stock 전체 팩/박스를 기준으로 재생성한다.
$db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> where ( 'bisp_bisi_idx' , $inspectionItemId )
-> delete ();
$insertRows = [];
$now = date ( 'Y-m-d H:i:s' );
foreach ( $sourceRows as $src ) {
$rowQty = max ( 0 , ( int ) ( $src [ 'brpc_sheet_qty' ] ? ? 0 ));
if ( $rowQty <= 0 ) {
continue ;
}
$startCode = ( string ) ( $src [ 'brpc_sheet_start_code' ] ? ? '' );
$endCode = ( string ) ( $src [ 'brpc_sheet_end_code' ] ? ? '' );
$insertRows [] = [
'bisp_bisi_idx' => $inspectionItemId ,
'bisp_lg_idx' => $lgIdx ,
'bisp_bag_code' => $bagCode ,
'bisp_box_code' => ( string ) ( $src [ 'brpc_box_code' ] ? ? '' ),
'bisp_pack_code' => ( string ) ( $src [ 'brpc_pack_code' ] ? ? '' ),
'bisp_sheet_start_code' => $startCode ,
'bisp_sheet_end_code' => $endCode ,
'bisp_sheet_qty' => $rowQty ,
'bisp_actual_qty' => $rowQty ,
'bisp_diff_qty' => 0 ,
'bisp_checked_yn' => 'N' ,
'bisp_regdate' => $now ,
];
}
if ( $insertRows !== []) {
$db -> table ( 'bag_inventory_inspection_pack_snapshot' ) -> insertBatch ( $insertRows );
}
$this -> applyLatestPackAdjustmentsToSnapshot ( $db , $lgIdx , $inspectionItemId , $bagCode );
}
private function applyLatestPackAdjustmentsToSnapshot (
\CodeIgniter\Database\BaseConnection $db ,
int $lgIdx ,
int $inspectionItemId ,
string $bagCode
) : void {
$bagCode = trim ( $bagCode );
if ( $inspectionItemId <= 0 || $bagCode === '' ) {
return ;
}
$latestRows = $db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> select ( 'bisp_pack_code, MAX(bisp_idx) AS latest_idx' , false )
-> where ( 'bisp_lg_idx' , $lgIdx )
-> where ( 'bisp_bag_code' , $bagCode )
-> where ( 'bisp_checked_yn' , 'Y' )
-> where ( 'bisp_pack_code !=' , '' )
-> groupBy ( 'bisp_pack_code' )
-> get ()
-> getResultArray ();
if ( $latestRows === []) {
return ;
}
$latestIdxList = [];
foreach ( $latestRows as $row ) {
$latestIdx = ( int ) ( $row [ 'latest_idx' ] ? ? 0 );
if ( $latestIdx > 0 ) {
$latestIdxList [] = $latestIdx ;
}
}
if ( $latestIdxList === []) {
return ;
}
$latestValues = $db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> select ( 'bisp_pack_code, bisp_sheet_qty' )
-> whereIn ( 'bisp_idx' , $latestIdxList )
-> get ()
-> getResultArray ();
if ( $latestValues === []) {
return ;
}
$latestMap = [];
foreach ( $latestValues as $row ) {
$packCode = trim (( string ) ( $row [ 'bisp_pack_code' ] ? ? '' ));
if ( $packCode === '' ) {
continue ;
}
$latestMap [ $packCode ] = [
'qty' => max ( 0 , ( int ) ( $row [ 'bisp_sheet_qty' ] ? ? 0 )),
];
}
if ( $latestMap === []) {
return ;
}
$currentRows = $db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> select ( 'bisp_idx, bisp_pack_code, bisp_sheet_start_code, bisp_sheet_end_code, bisp_sheet_qty' )
-> where ( 'bisp_lg_idx' , $lgIdx )
-> where ( 'bisp_bisi_idx' , $inspectionItemId )
-> where ( 'bisp_bag_code' , $bagCode )
-> get ()
-> getResultArray ();
if ( $currentRows === []) {
return ;
}
$updates = [];
foreach ( $currentRows as $row ) {
$packCode = trim (( string ) ( $row [ 'bisp_pack_code' ] ? ? '' ));
if ( $packCode === '' || ! isset ( $latestMap [ $packCode ])) {
continue ;
}
$targetQty = ( int ) ( $latestMap [ $packCode ][ 'qty' ] ? ? 0 );
$currentQty = max ( 0 , ( int ) ( $row [ 'bisp_sheet_qty' ] ? ? 0 ));
$startCode = trim (( string ) ( $row [ 'bisp_sheet_start_code' ] ? ? '' ));
$currentEndCode = trim (( string ) ( $row [ 'bisp_sheet_end_code' ] ? ? '' ));
$targetEndCode = $this -> resolveSheetEndCodeByQty ( $startCode , $currentEndCode , $targetQty );
if ( $targetQty === $currentQty && $targetEndCode === $currentEndCode ) {
continue ;
}
$updates [] = [
'bisp_idx' => ( int ) ( $row [ 'bisp_idx' ] ? ? 0 ),
'bisp_sheet_qty' => $targetQty ,
'bisp_actual_qty' => $targetQty ,
'bisp_diff_qty' => 0 ,
'bisp_sheet_end_code' => $targetEndCode ,
'bisp_checked_yn' => 'Y' ,
];
}
if ( $updates !== []) {
$db -> table ( 'bag_inventory_inspection_pack_snapshot' ) -> updateBatch ( $updates , 'bisp_idx' );
}
}
private function ensureInspectionSheetSnapshotForItem ( int $lgIdx , int $inspectionItemId ) : void
{
if ( $inspectionItemId <= 0 ) {
return ;
}
$db = \Config\Database :: connect ();
if ( ! $db -> tableExists ( 'bag_inventory_inspection_sheet_snapshot' )) {
return ;
}
$exists = $db -> table ( 'bag_inventory_inspection_sheet_snapshot' )
-> where ( 'biss_bisi_idx' , $inspectionItemId )
-> countAllResults ();
if ( $exists > 0 ) {
return ;
}
2026-03-26 14:30:45 +09:00
2026-04-29 14:59:49 +09:00
$packRows = $db -> table ( 'bag_inventory_inspection_pack_snapshot' )
-> select ( 'bisp_pack_code, bisp_sheet_start_code, bisp_sheet_end_code' )
-> where ( 'bisp_lg_idx' , $lgIdx )
-> where ( 'bisp_bisi_idx' , $inspectionItemId )
-> orderBy ( 'bisp_idx' , 'ASC' )
-> get ()
-> getResultArray ();
if ( $packRows === []) {
return ;
}
$insertRows = [];
$now = date ( 'Y-m-d H:i:s' );
foreach ( $packRows as $packRow ) {
$packCode = ( string ) ( $packRow [ 'bisp_pack_code' ] ? ? '' );
if ( $packCode === '' ) {
continue ;
}
$codes = $this -> expandSheetCodes (
( string ) ( $packRow [ 'bisp_sheet_start_code' ] ? ? '' ),
( string ) ( $packRow [ 'bisp_sheet_end_code' ] ? ? '' )
);
foreach ( $codes as $codeRow ) {
$sheetCode = ( string ) ( $codeRow [ 'sheet_code' ] ? ? '' );
if ( $sheetCode === '' ) {
continue ;
}
$insertRows [] = [
'biss_bisi_idx' => $inspectionItemId ,
'biss_lg_idx' => $lgIdx ,
'biss_pack_code' => $packCode ,
'biss_sheet_code' => $sheetCode ,
'biss_system_qty' => 1 ,
'biss_actual_qty' => null ,
'biss_diff_qty' => 0 ,
'biss_checked_yn' => 'N' ,
'biss_regdate' => $now ,
];
if ( count ( $insertRows ) >= 1000 ) {
$db -> table ( 'bag_inventory_inspection_sheet_snapshot' ) -> insertBatch ( $insertRows );
$insertRows = [];
}
}
}
if ( $insertRows !== []) {
$db -> table ( 'bag_inventory_inspection_sheet_snapshot' ) -> insertBatch ( $insertRows );
}
}
2026-03-26 14:30:45 +09:00
2026-04-29 14:59:49 +09:00
private function ensureInspectionSheetSnapshotForPack (
int $lgIdx ,
int $inspectionItemId ,
string $packCode ,
string $startCode ,
string $endCode
) : void {
if ( $inspectionItemId <= 0 || trim ( $packCode ) === '' ) {
return ;
}
$db = \Config\Database :: connect ();
if ( ! $db -> tableExists ( 'bag_inventory_inspection_sheet_snapshot' )) {
return ;
}
$exists = $db -> table ( 'bag_inventory_inspection_sheet_snapshot' )
-> where ( 'biss_lg_idx' , $lgIdx )
-> where ( 'biss_bisi_idx' , $inspectionItemId )
-> where ( 'biss_pack_code' , $packCode )
-> countAllResults ();
if ( $exists > 0 ) {
return ;
2026-03-26 14:30:45 +09:00
}
2026-04-29 14:59:49 +09:00
$codes = $this -> expandSheetCodes ( $startCode , $endCode );
if ( $codes === []) {
return ;
}
$now = date ( 'Y-m-d H:i:s' );
$rows = [];
foreach ( $codes as $codeRow ) {
$sheetCode = trim (( string ) ( $codeRow [ 'sheet_code' ] ? ? '' ));
if ( $sheetCode === '' ) {
continue ;
}
$rows [] = [
'biss_bisi_idx' => $inspectionItemId ,
'biss_lg_idx' => $lgIdx ,
'biss_pack_code' => $packCode ,
'biss_sheet_code' => $sheetCode ,
'biss_system_qty' => 1 ,
'biss_actual_qty' => null ,
'biss_diff_qty' => 0 ,
'biss_checked_yn' => 'N' ,
'biss_regdate' => $now ,
];
}
if ( $rows !== []) {
$db -> table ( 'bag_inventory_inspection_sheet_snapshot' ) -> insertBatch ( $rows );
}
2026-03-26 14:30:45 +09:00
}
2026-04-29 14:59:49 +09:00
private function resolveSheetEndCodeByQty ( string $startCode , string $fallbackEndCode , int $qty ) : string
2026-03-26 14:30:45 +09:00
{
2026-04-29 14:59:49 +09:00
if ( $qty <= 0 ) {
return $fallbackEndCode ;
}
if ( preg_match ( '/^(.*?)(\d+)$/' , $startCode , $m ) !== 1 ) {
return $fallbackEndCode ;
}
$prefix = ( string ) ( $m [ 1 ] ? ? '' );
$startNumRaw = ( string ) ( $m [ 2 ] ? ? '' );
$startNum = ( int ) $startNumRaw ;
if ( $startNum <= 0 ) {
return $fallbackEndCode ;
}
$endNum = $startNum + $qty - 1 ;
$width = strlen ( $startNumRaw );
2026-03-26 14:30:45 +09:00
2026-04-29 14:59:49 +09:00
return $prefix . str_pad (( string ) $endNum , $width , '0' , STR_PAD_LEFT );
}
/**
* @ param list < array < string , mixed >> $rows
* @ return list < array < string , mixed >>
*/
private function trimPackRowsToTargetQty ( array $rows , int $targetQty ) : array
{
$targetQty = max ( 0 , $targetQty );
if ( $targetQty <= 0 || $rows === []) {
return [];
}
$trimmed = [];
$remain = $targetQty ;
foreach ( $rows as $row ) {
if ( $remain <= 0 ) {
break ;
}
$rowQty = max ( 0 , ( int ) ( $row [ 'bisp_sheet_qty' ] ? ? 0 ));
if ( $rowQty <= 0 ) {
continue ;
}
if ( $rowQty <= $remain ) {
$trimmed [] = $row ;
$remain -= $rowQty ;
continue ;
}
$startCode = ( string ) ( $row [ 'bisp_sheet_start_code' ] ? ? '' );
$endCode = ( string ) ( $row [ 'bisp_sheet_end_code' ] ? ? '' );
$row [ 'bisp_sheet_qty' ] = $remain ;
$row [ 'bisp_sheet_end_code' ] = $this -> resolveSheetEndCodeByQty ( $startCode , $endCode , $remain );
$trimmed [] = $row ;
$remain = 0 ;
2026-03-26 14:30:45 +09:00
}
2026-04-29 14:59:49 +09:00
return $trimmed ;
}
private function ensureInventoryInspectionTables () : void
{
$db = \Config\Database :: connect ();
if ( ! $db -> tableExists ( 'bag_inventory_inspection' )) {
$db -> query ( <<< 'SQL'
CREATE TABLE IF NOT EXISTS `bag_inventory_inspection` (
`bis_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`bis_lg_idx` INT UNSIGNED NOT NULL ,
`bis_work_date` DATE NOT NULL ,
`bis_status` VARCHAR ( 20 ) NOT NULL DEFAULT 'selected' ,
`bis_reg_mb_idx` INT UNSIGNED NOT NULL DEFAULT 0 ,
`bis_regdate` DATETIME NOT NULL ,
`bis_moddate` DATETIME NULL DEFAULT NULL ,
PRIMARY KEY ( `bis_idx` ),
KEY `idx_bis_lg_work` ( `bis_lg_idx` , `bis_work_date` )
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
SQL );
}
if ( ! $db -> tableExists ( 'bag_inventory_inspection_item' )) {
$db -> query ( <<< 'SQL'
CREATE TABLE IF NOT EXISTS `bag_inventory_inspection_item` (
`bisi_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`bisi_bis_idx` INT UNSIGNED NOT NULL ,
`bisi_bag_code` VARCHAR ( 50 ) NOT NULL ,
`bisi_bag_name` VARCHAR ( 100 ) NOT NULL DEFAULT '' ,
`bisi_system_qty` INT NOT NULL DEFAULT 0 ,
`bisi_actual_qty` INT NULL DEFAULT NULL ,
`bisi_diff_qty` INT NOT NULL DEFAULT 0 ,
`bisi_has_barcode` CHAR ( 1 ) NOT NULL DEFAULT 'Y' ,
`bisi_apply_yn` CHAR ( 1 ) NOT NULL DEFAULT 'N' ,
PRIMARY KEY ( `bisi_idx` ),
KEY `idx_bisi_bis` ( `bisi_bis_idx` )
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
SQL );
} else {
$fields = $db -> getFieldNames ( 'bag_inventory_inspection_pack_snapshot' );
if ( ! in_array ( 'bisp_actual_qty' , $fields , true )) {
$db -> query ( " ALTER TABLE `bag_inventory_inspection_pack_snapshot` ADD COLUMN `bisp_actual_qty` INT UNSIGNED NULL DEFAULT NULL AFTER `bisp_sheet_qty` " );
}
if ( ! in_array ( 'bisp_diff_qty' , $fields , true )) {
$db -> query ( " ALTER TABLE `bag_inventory_inspection_pack_snapshot` ADD COLUMN `bisp_diff_qty` INT NOT NULL DEFAULT 0 AFTER `bisp_actual_qty` " );
}
if ( ! in_array ( 'bisp_checked_yn' , $fields , true )) {
$db -> query ( " ALTER TABLE `bag_inventory_inspection_pack_snapshot` ADD COLUMN `bisp_checked_yn` CHAR(1) NOT NULL DEFAULT 'N' AFTER `bisp_diff_qty` " );
}
}
2026-03-26 14:30:45 +09:00
}
// ──────────────────────────────────────────────
// 판매 관리
// ──────────────────────────────────────────────
public function sales () : string
{
$lgIdx = $this -> lgIdx ();
$data = [ 'salesList' => [], 'orderList' => [], 'startDate' => null , 'endDate' => null ];
if ( $lgIdx ) {
$startDate = $this -> request -> getGet ( 'start_date' );
$endDate = $this -> request -> getGet ( 'end_date' );
$data [ 'startDate' ] = $startDate ;
$data [ 'endDate' ] = $endDate ;
// 판매/반품
$saleBuilder = model ( BagSaleModel :: class ) -> where ( 'bs_lg_idx' , $lgIdx );
if ( $startDate ) $saleBuilder -> where ( 'bs_sale_date >=' , $startDate );
if ( $endDate ) $saleBuilder -> where ( 'bs_sale_date <=' , $endDate );
2026-03-26 16:40:49 +09:00
$data [ 'salesList' ] = $saleBuilder -> orderBy ( 'bs_sale_date' , 'DESC' ) -> paginate ( 20 , 'sales' );
$data [ 'salesPager' ] = model ( BagSaleModel :: class ) -> pager ;
2026-03-26 14:30:45 +09:00
// 주문 접수
$orderBuilder = model ( ShopOrderModel :: class ) -> where ( 'so_lg_idx' , $lgIdx );
if ( $startDate ) $orderBuilder -> where ( 'so_delivery_date >=' , $startDate );
if ( $endDate ) $orderBuilder -> where ( 'so_delivery_date <=' , $endDate );
2026-03-26 16:40:49 +09:00
$data [ 'orderList' ] = $orderBuilder -> orderBy ( 'so_idx' , 'DESC' ) -> paginate ( 20 , 'shoporders' );
$data [ 'orderPager' ] = model ( ShopOrderModel :: class ) -> pager ;
2026-03-26 14:30:45 +09:00
}
return $this -> render ( '판매 관리' , 'bag/sales' , $data );
}
// ──────────────────────────────────────────────
// 판매 현황
// ──────────────────────────────────────────────
public function salesStats () : string
{
$lgIdx = $this -> lgIdx ();
$data = [ 'result' => [], 'startDate' => null , 'endDate' => null ];
if ( $lgIdx ) {
$startDate = $this -> request -> getGet ( 'start_date' );
$endDate = $this -> request -> getGet ( 'end_date' );
$data [ 'startDate' ] = $startDate ;
$data [ 'endDate' ] = $endDate ;
$builder = model ( BagSaleModel :: class ) -> where ( 'bs_lg_idx' , $lgIdx ) -> where ( 'bs_type' , 'sale' );
if ( $startDate ) $builder -> where ( 'bs_sale_date >=' , $startDate );
if ( $endDate ) $builder -> where ( 'bs_sale_date <=' , $endDate );
2026-03-26 16:40:49 +09:00
$data [ 'result' ] = $builder -> orderBy ( 'bs_sale_date' , 'DESC' ) -> paginate ( 20 );
$data [ 'pager' ] = model ( BagSaleModel :: class ) -> pager ;
2026-03-26 14:30:45 +09:00
}
return $this -> render ( '판매 현황' , 'bag/sales_stats' , $data );
}
// ──────────────────────────────────────────────
// 봉투 수불 관리
// ──────────────────────────────────────────────
public function flow () : string
{
$lgIdx = $this -> lgIdx ();
$data = [ 'receiving' => [], 'sales' => [], 'issues' => [], 'inventory' => [], 'startDate' => null , 'endDate' => null ];
if ( $lgIdx ) {
$startDate = $this -> request -> getGet ( 'start_date' );
$endDate = $this -> request -> getGet ( 'end_date' );
$data [ 'startDate' ] = $startDate ;
$data [ 'endDate' ] = $endDate ;
$data [ 'inventory' ] = model ( BagInventoryModel :: class ) -> where ( 'bi_lg_idx' , $lgIdx ) -> findAll ();
$recvBuilder = model ( BagReceivingModel :: class ) -> where ( 'br_lg_idx' , $lgIdx );
if ( $startDate ) $recvBuilder -> where ( 'br_receive_date >=' , $startDate );
if ( $endDate ) $recvBuilder -> where ( 'br_receive_date <=' , $endDate );
$data [ 'receiving' ] = $recvBuilder -> findAll ();
$saleBuilder = model ( BagSaleModel :: class ) -> where ( 'bs_lg_idx' , $lgIdx );
if ( $startDate ) $saleBuilder -> where ( 'bs_sale_date >=' , $startDate );
if ( $endDate ) $saleBuilder -> where ( 'bs_sale_date <=' , $endDate );
$data [ 'sales' ] = $saleBuilder -> findAll ();
$issueBuilder = model ( BagIssueModel :: class ) -> where ( 'bi2_lg_idx' , $lgIdx );
if ( $startDate ) $issueBuilder -> where ( 'bi2_issue_date >=' , $startDate );
if ( $endDate ) $issueBuilder -> where ( 'bi2_issue_date <=' , $endDate );
$data [ 'issues' ] = $issueBuilder -> findAll ();
}
return $this -> render ( '봉투 수불 관리' , 'bag/flow' , $data );
}
// ──────────────────────────────────────────────
// 통계 분석 관리
// ──────────────────────────────────────────────
public function analytics () : string
{
return $this -> render ( '통계 분석 관리' , 'bag/analytics' , []);
}
// ──────────────────────────────────────────────
// 창 (프로그램 창 관리 - 추후)
// ──────────────────────────────────────────────
public function window () : string
{
return $this -> render ( '창' , 'bag/window' , []);
}
// ──────────────────────────────────────────────
// 도움말
// ──────────────────────────────────────────────
public function help () : string
{
return $this -> render ( '도움말' , 'bag/help' , []);
}
2026-03-26 16:13:07 +09:00
2026-03-26 16:20:35 +09:00
// ──────────────────────────────────────────────
// 재고 조정 (실사)
// ──────────────────────────────────────────────
public function inventoryAdjust () : string
{
$lgIdx = $this -> lgIdx ();
$inventory = $lgIdx ? model ( BagInventoryModel :: class ) -> where ( 'bi_lg_idx' , $lgIdx ) -> orderBy ( 'bi_bag_code' ) -> findAll () : [];
return $this -> render ( '재고 조정' , 'bag/inventory_adjust' , compact ( 'inventory' ));
}
public function inventoryAdjustStore ()
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/inventory' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$rules = [
'bag_code' => 'required|max_length[50]' ,
'adjust_type' => 'required|in_list[set,add,sub]' ,
'qty' => 'required|is_natural' ,
];
if ( ! $this -> validate ( $rules )) {
return redirect () -> back () -> withInput () -> with ( 'errors' , $this -> validator -> getErrors ());
}
$bagCode = $this -> request -> getPost ( 'bag_code' );
$type = $this -> request -> getPost ( 'adjust_type' );
$qty = ( int ) $this -> request -> getPost ( 'qty' );
$invModel = model ( BagInventoryModel :: class );
$existing = $invModel -> where ( 'bi_lg_idx' , $lgIdx ) -> where ( 'bi_bag_code' , $bagCode ) -> first ();
if ( $type === 'set' ) {
if ( $existing ) {
$invModel -> update ( $existing -> bi_idx , [ 'bi_qty' => $qty , 'bi_updated_at' => date ( 'Y-m-d H:i:s' )]);
}
} elseif ( $type === 'add' ) {
$bagName = $existing ? $existing -> bi_bag_name : '' ;
$invModel -> adjustQty ( $lgIdx , $bagCode , $bagName , $qty );
} elseif ( $type === 'sub' ) {
$bagName = $existing ? $existing -> bi_bag_name : '' ;
$invModel -> adjustQty ( $lgIdx , $bagCode , $bagName , - $qty );
}
return redirect () -> to ( site_url ( 'bag/inventory' )) -> with ( 'success' , '재고가 조정되었습니다.' );
}
2026-03-26 16:13:07 +09:00
// ══════════════════════════════════════════════
// CRUD — 사이트 레이아웃으로 등록/처리 폼 제공
// ══════════════════════════════════════════════
// --- 불출 등록 ---
public function issueCreate () : string
{
2026-04-29 14:59:49 +09:00
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
$kindO = model ( CodeKindModel :: class ) -> where ( 'ck_code' , 'O' ) -> first ();
$bagCodes = $kindO ? model ( CodeDetailModel :: class ) -> getByKind (( int ) $kindO -> ck_idx , true , $lgIdx ) : [];
$bagNameMap = [];
foreach ( $bagCodes as $cd ) {
$bagNameMap [( string ) ( $cd -> cd_code ? ? '' )] = ( string ) ( $cd -> cd_name ? ? '' );
}
$inventoryRows = $lgIdx
? model ( BagInventoryModel :: class )
-> where ( 'bi_lg_idx' , $lgIdx )
-> where ( 'bi_qty >' , 0 )
-> orderBy ( 'bi_bag_code' , 'ASC' )
-> findAll ()
: [];
$inventoryMap = [];
foreach ( $inventoryRows as $inv ) {
$code = ( string ) ( $inv -> bi_bag_code ? ? '' );
if ( $code === '' ) {
continue ;
}
$inventoryMap [ $code ] = ( int ) ( $inv -> bi_qty ? ? 0 );
}
$unitRows = $lgIdx
? model ( PackagingUnitModel :: class )
-> where ( 'pu_lg_idx' , $lgIdx )
-> where ( 'pu_state' , 1 )
-> findAll ()
: [];
$packagingMap = [];
foreach ( $unitRows as $unit ) {
$code = ( string ) ( $unit -> pu_bag_code ? ? '' );
if ( $code === '' ) {
continue ;
}
$packagingMap [ $code ] = [
'packPerSheet' => max ( 1 , ( int ) ( $unit -> pu_pack_per_sheet ? ? 1 )),
'totalPerBox' => max ( 1 , ( int ) ( $unit -> pu_total_per_box ? ? 1 )),
];
}
$bagMeta = [];
foreach ( $inventoryMap as $code => $qty ) {
$bagMeta [ $code ] = [
'name' => ( string ) ( $bagNameMap [ $code ] ? ? '' ),
'inventoryQty' => ( int ) $qty ,
'packPerSheet' => ( int ) (( $packagingMap [ $code ][ 'packPerSheet' ] ? ? 1 )),
'totalPerBox' => ( int ) (( $packagingMap [ $code ][ 'totalPerBox' ] ? ? 1 )),
];
}
$availableBagRows = [];
foreach ( $inventoryMap as $code => $qty ) {
$availableBagRows [] = [
'bag_code' => ( string ) $code ,
'bag_name' => ( string ) ( $bagNameMap [ $code ] ? ? $code ),
'inventory_qty' => ( int ) $qty ,
'pack_per_sheet' => ( int ) (( $packagingMap [ $code ][ 'packPerSheet' ] ? ? 1 )),
'total_per_box' => ( int ) (( $packagingMap [ $code ][ 'totalPerBox' ] ? ? 1 )),
];
}
$recentIssueRows = $lgIdx
? model ( BagIssueModel :: class )
-> where ( 'bi2_lg_idx' , $lgIdx )
-> orderBy ( 'bi2_issue_date' , 'DESC' )
-> orderBy ( 'bi2_idx' , 'DESC' )
-> findAll ( 20 )
: [];
$kindD = model ( CodeKindModel :: class ) -> where ( 'ck_code' , 'D' ) -> first ();
$dongCodes = $kindD ? model ( CodeDetailModel :: class ) -> getByKind (( int ) $kindD -> ck_idx , true , $lgIdx ) : [];
$today = date ( 'Y-m-d' );
$freeDongRows = [];
if ( $lgIdx ) {
$freeDongRows = model ( \App\Models\FreeRecipientModel :: class )
-> builder ()
-> select ( 'fr_dong_code' )
-> distinct ()
-> where ( 'fr_lg_idx' , $lgIdx )
-> where ( 'fr_state' , 1 )
-> groupStart ()
-> where ( 'fr_end_date IS NULL' )
-> orWhere ( 'fr_end_date >=' , $today )
-> groupEnd ()
-> where ( 'fr_dong_code !=' , '' )
-> get ()
-> getResult ();
}
$freeDongSet = [];
foreach ( $freeDongRows as $row ) {
$code = trim (( string ) ( $row -> fr_dong_code ? ? '' ));
if ( $code !== '' ) {
$freeDongSet [ $code ] = true ;
}
}
$destTypeOptions = [ '구청' , '기타' ];
if ( $lgIdx ) {
$typeRows = model ( \App\Models\FreeRecipientModel :: class )
-> builder ()
-> select ( 'fr_type_code, fr_name' )
-> distinct ()
-> where ( 'fr_lg_idx' , $lgIdx )
-> where ( 'fr_state' , 1 )
-> groupStart ()
-> where ( 'fr_end_date IS NULL' )
-> orWhere ( 'fr_end_date >=' , $today )
-> groupEnd ()
-> whereIn ( 'fr_type_code' , [ 'office' , 'target' ])
-> orderBy ( 'fr_name' , 'ASC' )
-> get ()
-> getResult ();
foreach ( $typeRows as $row ) {
$typeCode = trim (( string ) ( $row -> fr_type_code ? ? '' ));
$name = trim (( string ) ( $row -> fr_name ? ? '' ));
if ( $typeCode === 'office' ) {
$destTypeOptions [] = '동사무소' ;
continue ;
}
if ( $typeCode === 'target' && $name !== '' ) {
$destTypeOptions [] = $name ;
}
}
$destTypeOptions = array_values ( array_unique ( $destTypeOptions ));
}
return $this -> render ( '불출 처리' , 'bag/create_bag_issue' , compact (
'bagCodes' ,
'bagMeta' ,
'inventoryMap' ,
'packagingMap' ,
'availableBagRows' ,
'recentIssueRows' ,
'dongCodes' ,
'freeDongSet' ,
'destTypeOptions'
));
2026-03-26 16:13:07 +09:00
}
public function issueStore ()
{
$admin = new \App\Controllers\Admin\BagIssue ();
$admin -> initController ( $this -> request , $this -> response , service ( 'logger' ));
$result = $admin -> store ();
if ( $result instanceof \CodeIgniter\HTTP\RedirectResponse ) {
$to = ( string ) $result -> getHeaderLine ( 'Location' );
$to = str_replace ( '/admin/bag-issues' , '/bag/issue' , $to );
return redirect () -> to ( $to ) -> with ( 'success' , session () -> getFlashdata ( 'success' )) -> with ( 'errors' , session () -> getFlashdata ( 'errors' ));
}
2026-04-29 14:59:49 +09:00
return redirect () -> to ( site_url ( 'bag/issue/cancel' )) -> with ( 'success' , '불출 처리되었습니다.' );
2026-03-26 16:13:07 +09:00
}
public function issueCancel ( int $id )
{
$admin = new \App\Controllers\Admin\BagIssue ();
$admin -> initController ( $this -> request , $this -> response , service ( 'logger' ));
$admin -> cancel ( $id );
2026-04-29 14:59:49 +09:00
return redirect () -> to ( site_url ( 'bag/issue/cancel' )) -> with ( 'success' , session () -> getFlashdata ( 'success' ) ? ? '취소되었습니다.' );
2026-03-26 16:13:07 +09:00
}
// --- 발주 등록 ---
public function orderCreate () : string
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
2026-04-08 00:20:09 +09:00
$companies = $lgIdx
2026-04-22 15:35:36 +09:00
? model ( CompanyModel :: class ) -> where ( 'cp_lg_idx' , $lgIdx ) -> where ( 'cp_type' , '제작업체' ) -> where ( 'cp_state' , 1 ) -> findAll ()
: [];
$associations = $lgIdx
? model ( CompanyModel :: class ) -> where ( 'cp_lg_idx' , $lgIdx ) -> where ( 'cp_type' , '협회' ) -> where ( 'cp_state' , 1 ) -> findAll ()
: [];
$agencies = $lgIdx ? model ( SalesAgencyModel :: class ) -> where ( 'sa_lg_idx' , $lgIdx ) -> orderForDisplay () -> findAll () : [];
$kind = model ( CodeKindModel :: class ) -> where ( 'ck_code' , 'O' ) -> first ();
$bagCodes = $kind ? model ( CodeDetailModel :: class ) -> getByKind (( int ) $kind -> ck_idx , true , $lgIdx ) : [];
$priceMapRows = $lgIdx ? model ( BagPriceModel :: class ) -> latestActiveMapByBagCode ( $lgIdx ) : [];
$units = $lgIdx ? model ( PackagingUnitModel :: class ) -> where ( 'pu_lg_idx' , $lgIdx ) -> where ( 'pu_state' , 1 ) -> findAll () : [];
$recentOrders = $lgIdx
? model ( BagOrderModel :: class ) -> where ( 'bo_lg_idx' , $lgIdx ) -> whereLatestHead ( $lgIdx ) -> orderBy ( 'bo_order_date' , 'DESC' ) -> orderBy ( 'bo_idx' , 'DESC' ) -> findAll ( 12 )
: [];
$companyMap = [];
foreach ( $companies 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 ? ? '' );
}
$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 -> render (
'발주 등록' ,
'bag/create_bag_order' ,
array_merge (
compact (
'companies' ,
'associations' ,
'agencies' ,
'bagCodes' ,
'recentOrders' ,
'companyMap' ,
'agencyMap' ,
'bagReferenceRows'
),
[ 'editMode' => false , 'editDefaults' => null ]
)
);
}
2026-04-29 14:59:49 +09:00
/**
* LOT - No 디스켓 불출 : 발주 건을 선택해 암호화 seed 파일 생성 / 다운로드 .
*/
public function orderLotSeed () : string | RedirectResponse
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/purchase-inbound' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$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 ];
}
$lotNo = trim (( string ) ( $this -> request -> getGet ( 'lot_no' ) ? ? '' ));
$companyIdx = ( int ) ( $this -> request -> getGet ( 'company_idx' ) ? ? 0 );
$startDate = $startMonth . '-01' ;
$endDate = date ( 'Y-m-t' , strtotime ( $endMonth . '-01 00:00:00' ));
$orderModel = model ( BagOrderModel :: class );
$builder = $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 ( $lotNo !== '' ) {
$builder -> where ( 'bo_lot_no' , $lotNo );
}
if ( $companyIdx > 0 ) {
$builder -> where ( 'bo_company_idx' , $companyIdx );
}
$orders = $builder -> paginate ( 20 );
$pager = $orderModel -> 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 ? ? 0 )] = ( string ) ( $company -> cp_name ? ? '' );
}
$orderIds = array_values ( array_map ( static fn ( $o ) : int => ( int ) ( $o -> bo_idx ? ? 0 ), $orders ));
$itemSummary = [];
if ( $orderIds !== []) {
$items = model ( BagOrderItemModel :: class )
-> whereIn ( 'boi_bo_idx' , $orderIds )
-> orderBy ( 'boi_bo_idx' , 'ASC' )
-> findAll ();
foreach ( $items as $item ) {
$boIdx = ( int ) ( $item -> boi_bo_idx ? ? 0 );
if ( ! isset ( $itemSummary [ $boIdx ])) {
$itemSummary [ $boIdx ] = [
'line_count' => 0 ,
'qty_box' => 0 ,
'qty_sheet' => 0 ,
];
}
$itemSummary [ $boIdx ][ 'line_count' ] ++ ;
$itemSummary [ $boIdx ][ 'qty_box' ] += ( int ) ( $item -> boi_qty_box ? ? 0 );
$itemSummary [ $boIdx ][ 'qty_sheet' ] += ( int ) ( $item -> boi_qty_sheet ? ? 0 );
}
}
return $this -> render ( 'LOT-No 디스켓 불출' , 'bag/order_lot_seed' , [
'orders' => $orders ,
'pager' => $pager ,
'startMonth' => $startMonth ,
'endMonth' => $endMonth ,
'lotNo' => $lotNo ,
'companyIdx' => $companyIdx ,
'companies' => $companies ,
'companyMap' => $companyMap ,
'itemSummary' => $itemSummary ,
]);
}
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 );
if ( $boIdx <= 0 ) {
return redirect () -> back () -> with ( 'error' , '발주 건을 선택해 주세요.' );
}
$order = model ( BagOrderModel :: class )
-> where ( 'bo_lg_idx' , $lgIdx )
-> where ( 'bo_idx' , $boIdx )
-> first ();
if ( ! $order ) {
return redirect () -> back () -> with ( '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 파일을 생성할 수 없습니다.' );
}
$items = model ( BagOrderItemModel :: class )
-> where ( 'boi_bo_idx' , $boIdx )
-> orderBy ( 'boi_idx' , 'ASC' )
-> findAll ();
if ( $items === []) {
return redirect () -> back () -> with ( 'error' , '발주 품목이 없어 seed 파일을 생성할 수 없습니다.' );
}
$unitRows = model ( PackagingUnitModel :: class )
-> where ( 'pu_lg_idx' , $lgIdx )
-> where ( 'pu_state' , 1 )
-> findAll ();
$packMap = [];
foreach ( $unitRows as $unit ) {
$code = trim (( string ) ( $unit -> pu_bag_code ? ? '' ));
if ( $code === '' ) {
continue ;
}
$packMap [ $code ] = [
'pack_per_sheet' => max ( 1 , ( int ) ( $unit -> pu_pack_per_sheet ? ? 1 )),
'total_per_box' => max ( 1 , ( int ) ( $unit -> pu_total_per_box ? ? 1 )),
];
}
$orderData = [
'bo_idx' => $boIdx ,
'bo_uuid' => ( string ) $uuid ,
'bo_version' => $version ,
'bo_lg_idx' => ( int ) ( $order -> bo_lg_idx ? ? 0 ),
'bo_gugun_code' => ( string ) ( $order -> bo_gugun_code ? ? '' ),
'bo_dong_code' => ( string ) ( $order -> bo_dong_code ? ? '' ),
'bo_company_idx' => ( int ) ( $order -> bo_company_idx ? ? 0 ),
'bo_agency_idx' => ( int ) ( $order -> bo_agency_idx ? ? 0 ),
'bo_fee_rate' => ( float ) ( $order -> bo_fee_rate ? ? 0 ),
'bo_order_date' => ( string ) ( $order -> bo_order_date ? ? '' ),
'bo_lot_no' => ( string ) $lotNo ,
'bo_status' => ( string ) ( $order -> bo_status ? ? 'normal' ),
];
$hashItems = [];
foreach ( $items as $item ) {
$code = ( string ) ( $item -> boi_bag_code ? ? '' );
$pack = $packMap [ $code ] ? ? [ 'pack_per_sheet' => 1 , 'total_per_box' => 1 ];
$qtySheet = max ( 0 , ( int ) ( $item -> boi_qty_sheet ? ? 0 ));
$qtyPack = intdiv ( $qtySheet , max ( 1 , ( int ) $pack [ 'pack_per_sheet' ]));
$hashItems [] = [
'boi_idx' => ( int ) ( $item -> boi_idx ? ? 0 ),
'boi_bag_code' => $code ,
'boi_bag_name' => ( string ) ( $item -> boi_bag_name ? ? '' ),
'boi_unit_price' => ( float ) ( $item -> boi_unit_price ? ? 0 ),
'boi_qty_box' => ( int ) ( $item -> boi_qty_box ? ? 0 ),
'boi_qty_pack' => $qtyPack ,
'boi_qty_sheet' => $qtySheet ,
'pack_per_sheet' => ( int ) $pack [ 'pack_per_sheet' ],
'total_per_box' => ( int ) $pack [ 'total_per_box' ],
'boi_amount' => ( float ) ( $item -> boi_amount ? ? 0 ),
];
}
$orderHash = trim (( string ) ( $order -> bo_hash ? ? '' ));
if ( $orderHash === '' ) {
$payload = [
'bo_idx' => $boIdx ,
'order' => $orderData ,
'items' => $hashItems ,
];
$payloadJson = json_encode ( $payload , JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) ? : ( string ) $boIdx ;
$orderHash = hash ( 'sha256' , $payloadJson );
}
$seedPath = $this -> generateLotSeedFile ( $uuid , $version , $lotNo , $orderData , $hashItems , $orderHash );
$seedBinary = @ file_get_contents ( $seedPath );
if ( ! is_string ( $seedBinary ) || $seedBinary === '' ) {
return redirect () -> back () -> with ( 'error' , 'seed 파일 생성에는 성공했으나 파일을 읽을 수 없습니다.' );
}
return $this -> response
-> download ( $seedPath , $seedBinary )
-> setFileName ( basename ( $seedPath ));
}
/**
* @ param array < string , mixed > $orderData
* @ param array < int , array < string , mixed >> $items
*/
private function generateLotSeedFile ( 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 = sprintf ( '%s_v%d_diskette_%s.seed.json' , $lotNo , $version , date ( 'Ymd_His' ));
$fullPath = $baseDir . DIRECTORY_SEPARATOR . $fileName ;
file_put_contents ( $fullPath , json_encode ( $seed , JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ));
return $fullPath ;
}
2026-04-22 15:35:36 +09:00
/**
* 발주 변경 허브 : 발주월·변경 구분 선택 후 목록에서 발주를 선택 ( GBMS 발주 변경 화면 흐름 ) .
*/
public function orderChange () : string | RedirectResponse
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
$month = $this -> request -> getGet ( 'month' );
if ( $month === null || $month === '' || ! is_string ( $month ) || ! preg_match ( '/^\d{4}-\d{2}$/' , $month )) {
$month = date ( 'Y-m' );
}
$hubMode = $this -> request -> getGet ( 'hub_mode' );
$hubMode = in_array ( $hubMode , [ 'price' , 'meta' , 'delete' ], true ) ? $hubMode : 'meta' ;
$companyMap = [];
if ( $lgIdx ) {
$companies = model ( CompanyModel :: class ) -> where ( 'cp_lg_idx' , $lgIdx ) -> where ( 'cp_type' , '제작업체' ) -> where ( 'cp_state' , 1 ) -> findAll ();
foreach ( $companies as $company ) {
$companyMap [( int ) $company -> cp_idx ] = ( string ) $company -> cp_name ;
}
}
$monthOrders = [];
if ( $lgIdx ) {
$start = $month . '-01' ;
$end = date ( 'Y-m-t' , strtotime ( $start . ' 00:00:00' ));
$monthOrders = model ( BagOrderModel :: class )
-> where ( 'bo_lg_idx' , $lgIdx )
-> whereLatestHead ( $lgIdx )
-> where ( 'bo_order_date >=' , $start )
-> where ( 'bo_order_date <=' , $end )
-> whereIn ( 'bo_status' , [ 'normal' , 'cancelled' ])
-> orderBy ( 'bo_order_date' , 'DESC' )
-> orderBy ( 'bo_idx' , 'DESC' )
-> findAll ();
}
if ( $hubMode === 'delete' ) {
foreach ( $monthOrders as $row ) {
if (( string ) ( $row -> bo_status ? ? '' ) === 'normal' ) {
return redirect () -> to (
site_url ( 'bag/order/revise/' . ( int ) $row -> bo_idx . '?change_mode=delete' )
);
}
}
if ( $lgIdx ) {
session () -> setFlashdata ( 'error' , '해당 월에 삭제할 수 있는 발주(정상)가 없습니다.' );
}
}
return $this -> render (
'발주 변경' ,
'bag/order_change' ,
compact ( 'month' , 'hubMode' , 'monthOrders' , 'companyMap' )
);
}
public function orderRevise ( int $id ) : string | RedirectResponse
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
$orderModel = model ( BagOrderModel :: class );
$itemModel = model ( \App\Models\BagOrderItemModel :: class );
$target = $orderModel -> find ( $id );
if ( ! $target || ( int ) $target -> bo_lg_idx !== $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/order/change' )) -> with ( 'error' , '수정할 발주를 찾을 수 없습니다.' );
}
if (( string ) ( $target -> bo_status ? ? '' ) !== 'normal' ) {
return redirect () -> to ( site_url ( 'bag/order/change' )) -> with ( 'error' , '변경할 수 없는 발주입니다.' );
}
$changeMode = $this -> request -> getGet ( 'change_mode' );
$changeMode = in_array ( $changeMode , [ 'price' , 'meta' , 'delete' ], true ) ? $changeMode : 'meta' ;
$companies = $lgIdx
? model ( CompanyModel :: class ) -> where ( 'cp_lg_idx' , $lgIdx ) -> where ( 'cp_type' , '제작업체' ) -> where ( 'cp_state' , 1 ) -> findAll ()
: [];
$associations = $lgIdx
? model ( CompanyModel :: class ) -> where ( 'cp_lg_idx' , $lgIdx ) -> where ( 'cp_type' , '협회' ) -> where ( 'cp_state' , 1 ) -> findAll ()
2026-04-08 00:20:09 +09:00
: [];
$agencies = $lgIdx ? model ( SalesAgencyModel :: class ) -> where ( 'sa_lg_idx' , $lgIdx ) -> orderForDisplay () -> findAll () : [];
2026-03-26 16:13:07 +09:00
$kind = model ( CodeKindModel :: class ) -> where ( 'ck_code' , 'O' ) -> first ();
2026-04-22 15:35:36 +09:00
$bagCodes = $kind ? model ( CodeDetailModel :: class ) -> getByKind (( int ) $kind -> ck_idx , true , $lgIdx ) : [];
$priceMapRows = $lgIdx ? model ( BagPriceModel :: class ) -> latestActiveMapByBagCode ( $lgIdx ) : [];
$units = $lgIdx ? model ( PackagingUnitModel :: class ) -> where ( 'pu_lg_idx' , $lgIdx ) -> where ( 'pu_state' , 1 ) -> findAll () : [];
$companyMap = [];
foreach ( $companies 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 ? ? '' );
}
$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' ],
];
}
$items = $itemModel -> where ( 'boi_bo_idx' , ( int ) $target -> bo_idx ) -> orderBy ( 'boi_idx' , 'ASC' ) -> findAll ();
$orderReturnMonth = substr (( string ) ( $target -> bo_order_date ? ? date ( 'Y-m-d' )), 0 , 7 );
$monthStart = $orderReturnMonth . '-01' ;
$monthEnd = date ( 'Y-m-t' , strtotime ( $monthStart . ' 00:00:00' ));
$recentOrders = $lgIdx
? $orderModel -> where ( 'bo_lg_idx' , $lgIdx )
-> whereLatestHead ( $lgIdx )
-> where ( 'bo_order_date >=' , $monthStart )
-> where ( 'bo_order_date <=' , $monthEnd )
-> whereIn ( 'bo_status' , [ 'normal' , 'cancelled' ])
-> orderBy ( 'bo_order_date' , 'DESC' )
-> orderBy ( 'bo_idx' , 'DESC' )
-> findAll ()
: [];
$itemCodes = [];
$itemQtyBoxes = [];
$itemQtySheets = [];
foreach ( $items as $item ) {
$itemCodes [] = ( string ) ( $item -> boi_bag_code ? ? '' );
$itemQtyBoxes [] = ( int ) ( $item -> boi_qty_box ? ? 0 );
$itemQtySheets [] = ( int ) ( $item -> boi_qty_sheet ? ? 0 );
}
$savedLinePrices = [];
foreach ( $items as $item ) {
$savedLinePrices [( string ) ( $item -> boi_bag_code ? ? '' )] = ( float ) ( $item -> boi_unit_price ? ? 0 );
}
foreach ( $bagReferenceRows as & $brow ) {
$c = ( string ) ( $brow [ 'code' ] ? ? '' );
if ( $c !== '' && isset ( $savedLinePrices [ $c ])) {
$brow [ 'orderPrice' ] = $savedLinePrices [ $c ];
}
}
unset ( $brow );
$orderLotNo = ( string ) ( $target -> bo_lot_no ? ? '' );
$editDefaults = [
'bo_source_idx' => ( int ) $target -> bo_idx ,
'bo_order_date' => ( string ) ( $target -> bo_order_date ? ? date ( 'Y-m-d' )),
'bo_order_month_ui' => substr (( string ) ( $target -> bo_order_date ? ? date ( 'Y-m-d' )), 0 , 7 ),
'bo_fee_rate' => ( string ) ( $target -> bo_fee_rate ? ? '0' ),
'bo_association_idx' => ( string ) ( $target -> bo_association_idx ? ? '' ),
'bo_company_idx' => ( string ) ( $target -> bo_company_idx ? ? '' ),
'bo_agency_idx' => ( string ) ( $target -> bo_agency_idx ? ? '' ),
'item_bag_code' => $itemCodes ,
'item_qty_box' => $itemQtyBoxes ,
'item_qty_sheet' => $itemQtySheets ,
];
return $this -> render (
'발주 변경' ,
'bag/create_bag_order' ,
compact (
'companies' ,
'associations' ,
'agencies' ,
'bagCodes' ,
'recentOrders' ,
'companyMap' ,
'agencyMap' ,
'bagReferenceRows' ,
'editDefaults' ,
'changeMode' ,
'orderReturnMonth' ,
'orderLotNo'
)
+ [ 'editMode' => true , 'hubReturn' => true ]
);
2026-03-26 16:13:07 +09:00
}
public function orderStore ()
{
$admin = new \App\Controllers\Admin\BagOrder ();
$admin -> initController ( $this -> request , $this -> response , service ( 'logger' ));
$result = $admin -> store ();
2026-04-22 15:35:36 +09:00
if ( $result instanceof RedirectResponse ) {
$success = session () -> getFlashdata ( 'success' );
$error = session () -> getFlashdata ( 'error' );
$errors = session () -> getFlashdata ( 'errors' );
if ( ! empty ( $error ) || ! empty ( $errors )) {
$sourceIdx = ( int ) ( $this -> request -> getPost ( 'bo_source_idx' ) ? ? 0 );
$reviseMode = ( string ) ( $this -> request -> getPost ( 'bo_change_mode' ) ? ? 'meta' );
$redirectUrl = $sourceIdx > 0
? site_url ( 'bag/order/revise/' . $sourceIdx . '?change_mode=' . rawurlencode ( $reviseMode ))
: site_url ( 'bag/order/create' );
return redirect () -> to ( $redirectUrl )
-> withInput ()
-> with ( 'error' , $error )
-> with ( 'errors' , $errors );
}
$returnHub = ( int ) ( $this -> request -> getPost ( 'order_return_hub' ) ? ? 0 ) === 1 ;
$returnMonth = ( string ) ( $this -> request -> getPost ( 'order_return_month' ) ? ? '' );
$sourceIdxPost = ( int ) ( $this -> request -> getPost ( 'bo_source_idx' ) ? ? 0 );
if ( $returnHub && $sourceIdxPost > 0 && preg_match ( '/^\d{4}-\d{2}$/' , $returnMonth )) {
return redirect () -> to ( site_url ( 'bag/order/change?month=' . $returnMonth ))
-> with ( 'success' , $success ? ? '발주가 저장되었습니다.' );
}
return redirect () -> to ( site_url ( 'bag/order/create' ))
-> with ( 'success' , $success );
}
$returnHub = ( int ) ( $this -> request -> getPost ( 'order_return_hub' ) ? ? 0 ) === 1 ;
$returnMonth = ( string ) ( $this -> request -> getPost ( 'order_return_month' ) ? ? '' );
if ( $returnHub && ( int ) ( $this -> request -> getPost ( 'bo_source_idx' ) ? ? 0 ) > 0 && preg_match ( '/^\d{4}-\d{2}$/' , $returnMonth )) {
return redirect () -> to ( site_url ( 'bag/order/change?month=' . $returnMonth )) -> with ( 'success' , '발주가 저장되었습니다.' );
2026-03-26 16:13:07 +09:00
}
2026-04-22 15:35:36 +09:00
return redirect () -> to ( site_url ( 'bag/order/create' )) -> with ( 'success' , '발주 등록되었습니다.' );
}
public function orderDeletePost ()
{
$id = ( int ) ( $this -> request -> getPost ( 'bo_idx' ) ? ? 0 );
if ( $id <= 0 ) {
return redirect () -> to ( site_url ( 'bag/order/change' )) -> with ( 'error' , '삭제할 발주를 선택해 주세요.' );
}
return $this -> orderDelete ( $id );
}
public function orderDelete ( int $id )
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
if ( $lgIdx === null || $lgIdx <= 0 ) {
return redirect () -> to ( site_url ( 'bag/order/change' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$orderModel = model ( BagOrderModel :: class );
$order = $orderModel -> find ( $id );
if ( ! $order || ( int ) $order -> bo_lg_idx !== $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/order/change' )) -> with ( 'error' , '발주를 찾을 수 없습니다.' );
}
if (( string ) ( $order -> bo_status ? ? '' ) !== 'normal' ) {
return redirect () -> to ( site_url ( 'bag/order/change' )) -> with ( 'error' , '삭제할 수 없는 발주입니다.' );
}
$month = substr (( string ) ( $order -> bo_order_date ? ? date ( 'Y-m-d' )), 0 , 7 );
$admin = new \App\Controllers\Admin\BagOrder ();
$admin -> initController ( $this -> request , $this -> response , service ( 'logger' ));
$response = $admin -> delete ( $id );
if ( $response instanceof RedirectResponse ) {
$msg = session () -> getFlashdata ( 'success' ) ? ? '발주가 삭제 처리되었습니다.' ;
return redirect () -> to ( site_url ( 'bag/order/change?month=' . $month )) -> with ( 'success' , $msg );
}
return redirect () -> to ( site_url ( 'bag/order/change?month=' . $month )) -> with ( 'success' , '처리되었습니다.' );
2026-03-26 16:13:07 +09:00
}
2026-04-08 00:20:09 +09:00
public function orderCancel ( int $id )
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/purchase-inbound' )) -> with ( 'error' , '지자체를 확인할 수 없습니다.' );
}
$orderModel = model ( BagOrderModel :: class );
$order = $orderModel -> find ( $id );
if ( ! $order || ( int ) $order -> bo_lg_idx !== $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/purchase-inbound' )) -> with ( 'error' , '발주를 찾을 수 없습니다.' );
}
$before = ( array ) $order ;
$orderModel -> update ( $id , [ 'bo_status' => 'cancelled' , 'bo_moddate' => date ( 'Y-m-d H:i:s' )]);
helper ( 'audit' );
audit_log ( 'update' , 'bag_order' , $id , $before , [ 'bo_status' => 'cancelled' ]);
return redirect () -> to ( site_url ( 'bag/purchase-inbound' )) -> with ( 'success' , '발주가 취소되었습니다.' );
}
2026-03-26 16:13:07 +09:00
// --- 입고 처리 ---
public function receivingCreate () : string
2026-04-22 15:35:36 +09:00
{
return $this -> receivingScanner ();
}
public function receivingStore ()
{
return $this -> receivingScannerStore ();
}
/**
* 발주 입고 ( 스캐너 대체 수동입력 )
* - 미입고가 남은 발주의 LOT·봉투 ( 이름 ) 로 조회 범위를 좁힌 뒤 입고 처리
* - 인수자 : 대행소 ( agency ) 담당자 , 기본값 동명이면 로그인 사용자명과 일치하는 담당자
* - 인계자 : 제작업체 ( company ) 담당자
*/
public function receivingScanner () : string | RedirectResponse
2026-03-26 16:13:07 +09:00
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
2026-04-22 15:35:36 +09:00
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/purchase-inbound' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$companyIdx = ( int ) old ( 'company_idx' , ( int ) ( $this -> request -> getGet ( 'company_idx' ) ? ? 0 ));
$lotNo = '' ;
$bagCode = '' ;
$companies = model ( CompanyModel :: class )
-> where ( 'cp_lg_idx' , $lgIdx )
-> where ( 'cp_type' , '제작업체' )
-> where ( 'cp_state' , 1 )
-> orderBy ( 'cp_name' , 'ASC' )
-> findAll ();
$defaultCompanyIdx = ! empty ( $companies )
? ( int ) ( $companies [ 0 ] -> cp_idx ? ? 0 )
: 0 ;
if ( $companyIdx > 0 ) {
$validCompany = false ;
foreach ( $companies as $company ) {
if (( int ) ( $company -> cp_idx ? ? 0 ) === $companyIdx ) {
$validCompany = true ;
break ;
}
}
if ( ! $validCompany ) {
$companyIdx = $defaultCompanyIdx ;
}
} elseif ( $defaultCompanyIdx > 0 ) {
// 초기 진입 시 드롭다운 최상단 제작업체를 기본 선택한다.
$companyIdx = $defaultCompanyIdx ;
}
$lotChoices = [];
$bagFilterOptions = $this -> receivingBagFilterOptions ( $lgIdx , $companyIdx , '' );
$pick = $this -> receivingManagerPickers ( $lgIdx );
$recvSel = $this -> receivingReceiverSelect ( $lgIdx );
$receiverRef = ( string ) old ( 'br_receiver_ref' , $recvSel [ 'defaultReceiverRef' ]);
$receiverRef = $this -> sanitizeReceiverRef ( $recvSel [ 'receiverOptions' ], $receiverRef );
if ( $receiverRef === '' ) {
$receiverRef = $recvSel [ 'defaultReceiverRef' ];
}
$senderIdx = ( int ) old ( 'br_sender_idx' , $pick [ 'defaultSenderIdx' ]);
$rows = $companyIdx > 0
? $this -> buildReceivingCandidateRows ( $lgIdx , $companyIdx , '' , true , '' )
: [];
$rowsByKey = [];
foreach ( $rows as $row ) {
$rowsByKey [( string ) $row [ 'row_key' ]] = $row ;
}
return $this -> render (
'발주 입고(스캐너)' ,
'bag/receiving_scanner' ,
[
'companyIdx' => $companyIdx ,
'companies' => $companies ,
'lotNo' => '' ,
'bagCode' => '' ,
'bagFilterOptions' => $bagFilterOptions ,
'lotChoices' => $lotChoices ,
'receiverOptions' => $recvSel [ 'receiverOptions' ],
'receiverRef' => $receiverRef ,
'senders' => $pick [ 'senders' ],
'senderIdx' => $senderIdx ,
'rows' => $rows ,
'rowsByKey' => $rowsByKey ,
]
);
2026-03-26 16:13:07 +09:00
}
2026-04-22 15:35:36 +09:00
public function receivingScannerStore () : RedirectResponse
2026-03-26 16:13:07 +09:00
{
2026-04-22 15:35:36 +09:00
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/purchase-inbound' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$receiveDate = ( string ) ( $this -> request -> getPost ( 'br_receive_date' ) ? ? date ( 'Y-m-d' ));
$companyIdx = ( int ) ( $this -> request -> getPost ( 'company_idx' ) ? ? 0 );
$lotNo = '' ;
$filterBagCode = '' ;
$recvSel = $this -> receivingReceiverSelect ( $lgIdx );
$receiverRef = ( string ) ( $this -> request -> getPost ( 'br_receiver_ref' ) ? ? '' );
$receiverRef = $this -> sanitizeReceiverRef ( $recvSel [ 'receiverOptions' ], $receiverRef );
if ( $receiverRef === '' ) {
$receiverRef = $recvSel [ 'defaultReceiverRef' ];
}
$receiverIdx = $this -> parseReceiverRefToStoredIdx ( $lgIdx , $receiverRef );
$senderIdx = ( int ) ( $this -> request -> getPost ( 'br_sender_idx' ) ? ? 0 );
$inputQty = $this -> request -> getPost ( 'receive_qty_sheet' );
$inputQty = is_array ( $inputQty ) ? $inputQty : [];
if ( ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $receiveDate )) {
return redirect () -> back () -> withInput () -> with ( 'error' , '입고일 형식을 확인해 주세요.' );
}
if ( $companyIdx <= 0 ) {
return redirect () -> back () -> withInput () -> with ( 'error' , '제작업체를 선택해 주세요.' );
}
if ( $receiverIdx <= 0 ) {
return redirect () -> back () -> withInput () -> with ( 'error' , '인수자를 선택해 주세요.' );
}
$senderResolved = $this -> resolveCompanySenderName ( $lgIdx , $senderIdx );
$rows = $this -> buildReceivingCandidateRows ( $lgIdx , $companyIdx , '' , true , '' );
$rowMap = [];
foreach ( $rows as $row ) {
$rowMap [( string ) $row [ 'row_key' ]] = $row ;
}
$insertRows = [];
foreach ( $inputQty as $rowKey => $qtyRaw ) {
$rowKey = ( string ) $rowKey ;
$qty = ( int ) $qtyRaw ;
if ( $qty <= 0 || ! isset ( $rowMap [ $rowKey ])) {
continue ;
}
$base = $rowMap [ $rowKey ];
$pending = ( int ) ( $base [ 'pending_qty_sheet' ] ? ? 0 );
if ( $pending <= 0 ) {
continue ;
}
if ( $qty > $pending ) {
$qty = $pending ;
}
$totalPerBox = max ( 1 , ( int ) ( $base [ 'total_per_box' ] ? ? 1 ));
$qtyBox = intdiv ( $qty , $totalPerBox );
$sender = $senderResolved !== '' ? $senderResolved : ( string ) ( $base [ 'company_rep_name' ] ? ? '' );
$insertRows [] = [
'br_bo_idx' => ( int ) $base [ 'bo_idx' ],
'br_lg_idx' => $lgIdx ,
'br_bag_code' => ( string ) $base [ 'bag_code' ],
'br_bag_name' => ( string ) $base [ 'bag_name' ],
'br_qty_box' => $qtyBox ,
'br_qty_sheet' => $qty ,
'br_receive_date' => $receiveDate ,
'br_receiver_idx' => $receiverIdx ,
'br_sender_name' => $sender ,
'br_type' => 'scanner' ,
'br_regdate' => date ( 'Y-m-d H:i:s' ),
];
}
if ( empty ( $insertRows )) {
return redirect () -> back () -> withInput () -> with ( 'error' , '입고 처리할 수량을 입력해 주세요.' );
}
$recvModel = model ( BagReceivingModel :: class );
$invModel = model ( BagInventoryModel :: class );
$db = \Config\Database :: connect ();
$db -> transStart ();
foreach ( $insertRows as $row ) {
$recvModel -> insert ( $row );
2026-04-29 14:59:49 +09:00
$brIdx = ( int ) $recvModel -> getInsertID ();
2026-04-22 15:35:36 +09:00
$invModel -> adjustQty (
$lgIdx ,
( string ) $row [ 'br_bag_code' ],
( string ) $row [ 'br_bag_name' ],
( int ) $row [ 'br_qty_sheet' ]
);
2026-04-29 14:59:49 +09:00
$this -> createReceivingPackCodes (
$lgIdx ,
$brIdx ,
( int ) $row [ 'br_bo_idx' ],
( string ) $row [ 'br_bag_code' ],
( string ) $row [ 'br_bag_name' ],
( int ) $row [ 'br_qty_sheet' ],
max ( 1 , ( int ) ( $rowMap [( string ) (( int ) $row [ 'br_bo_idx' ] . '|' . ( string ) $row [ 'br_bag_code' ])][ 'pack_per_sheet' ] ? ? 1 )),
max ( 1 , ( int ) ( $rowMap [( string ) (( int ) $row [ 'br_bo_idx' ] . '|' . ( string ) $row [ 'br_bag_code' ])][ 'total_per_box' ] ? ? 1 ))
);
2026-04-22 15:35:36 +09:00
}
$db -> transComplete ();
if ( ! $db -> transStatus ()) {
return redirect () -> back () -> withInput () -> with ( 'error' , '입고 처리 중 오류가 발생했습니다.' );
}
$query = [ 'company_idx' => $companyIdx ];
return redirect () -> to ( site_url ( 'bag/receiving/scanner' ) . '?' . http_build_query ( $query ))
-> with ( 'success' , count ( $insertRows ) . '건 입고 처리되었습니다.' );
}
/**
* 일괄 입고 : LOT - 봉투 행 기준 미입고량 전체 입고 .
*/
public function receivingBatch () : string | RedirectResponse
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/purchase-inbound' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$companyIdx = ( int ) ( $this -> request -> getGet ( 'company_idx' ) ? ? 0 );
$bagCode = trim (( string ) ( $this -> request -> getGet ( 'bag_code' ) ? ? '' ));
$companies = model ( CompanyModel :: class )
-> where ( 'cp_lg_idx' , $lgIdx )
-> where ( 'cp_type' , '제작업체' )
-> where ( 'cp_state' , 1 )
-> orderBy ( 'cp_name' , 'ASC' )
-> findAll ();
$kind = model ( CodeKindModel :: class ) -> where ( 'ck_code' , 'O' ) -> first ();
$bagCodeOptions = $kind ? model ( CodeDetailModel :: class ) -> getByKind (( int ) $kind -> ck_idx , true , $lgIdx ) : [];
$pick = $this -> receivingManagerPickers ( $lgIdx );
$recvSel = $this -> receivingReceiverSelect ( $lgIdx );
$receiverRef = ( string ) old ( 'br_receiver_ref' , $recvSel [ 'defaultReceiverRef' ]);
$receiverRef = $this -> sanitizeReceiverRef ( $recvSel [ 'receiverOptions' ], $receiverRef );
if ( $receiverRef === '' ) {
$receiverRef = $recvSel [ 'defaultReceiverRef' ];
}
// 조회 화면에서는 입고완료 행도 함께 보여 미입고량 0을 확인할 수 있게 한다.
$rows = $this -> buildReceivingCandidateRows ( $lgIdx , $companyIdx , $bagCode , false , '' );
return $this -> render (
'일괄 입고' ,
'bag/receiving_batch' ,
[
'companyIdx' => $companyIdx ,
'bagCode' => $bagCode ,
'companies' => $companies ,
'bagCodeOptions' => $bagCodeOptions ,
'receiverOptions' => $recvSel [ 'receiverOptions' ],
'receiverRef' => $receiverRef ,
'senders' => $pick [ 'senders' ],
'senderIdx' => ( int ) old ( 'br_sender_idx' , $pick [ 'defaultSenderIdx' ]),
'rows' => $rows ,
]
);
}
public function receivingBatchStore () : RedirectResponse
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/purchase-inbound' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$companyIdx = ( int ) ( $this -> request -> getPost ( 'company_idx' ) ? ? 0 );
$bagCode = trim (( string ) ( $this -> request -> getPost ( 'bag_code' ) ? ? '' ));
$selected = $this -> request -> getPost ( 'selected_rows' );
$selected = is_array ( $selected ) ? array_map ( 'strval' , $selected ) : [];
$receiveDate = ( string ) ( $this -> request -> getPost ( 'br_receive_date' ) ? ? date ( 'Y-m-d' ));
$recvSel = $this -> receivingReceiverSelect ( $lgIdx );
$receiverRef = ( string ) ( $this -> request -> getPost ( 'br_receiver_ref' ) ? ? '' );
$receiverRef = $this -> sanitizeReceiverRef ( $recvSel [ 'receiverOptions' ], $receiverRef );
if ( $receiverRef === '' ) {
$receiverRef = $recvSel [ 'defaultReceiverRef' ];
}
$receiverIdx = $this -> parseReceiverRefToStoredIdx ( $lgIdx , $receiverRef );
$senderIdx = ( int ) ( $this -> request -> getPost ( 'br_sender_idx' ) ? ? 0 );
if ( empty ( $selected )) {
return redirect () -> back () -> withInput () -> with ( 'error' , '일괄 입고할 행을 선택해 주세요.' );
}
if ( ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $receiveDate )) {
return redirect () -> back () -> withInput () -> with ( 'error' , '입고일 형식을 확인해 주세요.' );
}
if ( $receiverIdx <= 0 ) {
return redirect () -> back () -> withInput () -> with ( 'error' , '인수자를 선택해 주세요.' );
}
$senderResolved = $this -> resolveCompanySenderName ( $lgIdx , $senderIdx );
$rows = $this -> buildReceivingCandidateRows ( $lgIdx , 0 , '' , true , '' );
$rowMap = [];
foreach ( $rows as $row ) {
$rowMap [( string ) $row [ 'row_key' ]] = $row ;
}
$insertRows = [];
foreach ( $selected as $rowKey ) {
if ( ! isset ( $rowMap [ $rowKey ])) {
continue ;
}
$base = $rowMap [ $rowKey ];
$qty = ( int ) ( $base [ 'pending_qty_sheet' ] ? ? 0 );
if ( $qty <= 0 ) {
continue ;
}
$totalPerBox = max ( 1 , ( int ) ( $base [ 'total_per_box' ] ? ? 1 ));
$qtyBox = intdiv ( $qty , $totalPerBox );
$sender = $senderResolved !== '' ? $senderResolved : ( string ) ( $base [ 'company_rep_name' ] ? ? '' );
$insertRows [] = [
'br_bo_idx' => ( int ) $base [ 'bo_idx' ],
'br_lg_idx' => $lgIdx ,
'br_bag_code' => ( string ) $base [ 'bag_code' ],
'br_bag_name' => ( string ) $base [ 'bag_name' ],
'br_qty_box' => $qtyBox ,
'br_qty_sheet' => $qty ,
'br_receive_date' => $receiveDate ,
'br_receiver_idx' => $receiverIdx ,
'br_sender_name' => $sender ,
'br_type' => 'batch' ,
'br_regdate' => date ( 'Y-m-d H:i:s' ),
];
}
if ( empty ( $insertRows )) {
return redirect () -> back () -> withInput () -> with ( 'error' , '선택한 행에 입고할 수량이 없습니다.' );
}
$recvModel = model ( BagReceivingModel :: class );
$invModel = model ( BagInventoryModel :: class );
$db = \Config\Database :: connect ();
$db -> transStart ();
foreach ( $insertRows as $row ) {
$recvModel -> insert ( $row );
2026-04-29 14:59:49 +09:00
$brIdx = ( int ) $recvModel -> getInsertID ();
2026-04-22 15:35:36 +09:00
$invModel -> adjustQty (
$lgIdx ,
( string ) $row [ 'br_bag_code' ],
( string ) $row [ 'br_bag_name' ],
( int ) $row [ 'br_qty_sheet' ]
);
2026-04-29 14:59:49 +09:00
$this -> createReceivingPackCodes (
$lgIdx ,
$brIdx ,
( int ) $row [ 'br_bo_idx' ],
( string ) $row [ 'br_bag_code' ],
( string ) $row [ 'br_bag_name' ],
( int ) $row [ 'br_qty_sheet' ],
max ( 1 , ( int ) ( $rowMap [( string ) (( int ) $row [ 'br_bo_idx' ] . '|' . ( string ) $row [ 'br_bag_code' ])][ 'pack_per_sheet' ] ? ? 1 )),
max ( 1 , ( int ) ( $rowMap [( string ) (( int ) $row [ 'br_bo_idx' ] . '|' . ( string ) $row [ 'br_bag_code' ])][ 'total_per_box' ] ? ? 1 ))
);
2026-04-22 15:35:36 +09:00
}
$db -> transComplete ();
if ( ! $db -> transStatus ()) {
return redirect () -> back () -> withInput () -> with ( 'error' , '일괄 입고 처리 중 오류가 발생했습니다.' );
}
return redirect () -> to ( site_url ( 'bag/receiving/batch?company_idx=' . $companyIdx . '&bag_code=' . rawurlencode ( $bagCode )))
-> with ( 'success' , count ( $insertRows ) . '건 일괄 입고 처리되었습니다.' );
}
public function receivingStatus () : string | RedirectResponse
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/purchase-inbound' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$startDate = ( string ) ( $this -> request -> getGet ( 'start_date' ) ? ? date ( 'Y-m-01' ));
$endDate = ( string ) ( $this -> request -> getGet ( 'end_date' ) ? ? date ( 'Y-m-d' ));
$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' , 'completed' , 'pending' ], true )) {
$receiveType = 'all' ;
}
$companies = model ( CompanyModel :: class )
-> where ( 'cp_lg_idx' , $lgIdx )
-> where ( 'cp_type' , '제작업체' )
-> where ( 'cp_state' , 1 )
-> orderBy ( 'cp_name' , 'ASC' )
-> findAll ();
$kind = model ( CodeKindModel :: class ) -> where ( 'ck_code' , 'O' ) -> first ();
$bagCodeOptions = $kind ? model ( CodeDetailModel :: class ) -> getByKind (( int ) $kind -> ck_idx , true , $lgIdx ) : [];
$rows = $this -> buildReceivingStatusRows ( $lgIdx , $startDate , $endDate , $companyIdx , $bagCode , $receiveType );
$groupTotals = [];
$grandTotalReceive = 0 ;
foreach ( $rows as $row ) {
$key = ( string ) ( $row [ 'display_date' ] ? ? '' );
if ( ! isset ( $groupTotals [ $key ])) {
$groupTotals [ $key ] = 0 ;
}
$groupTotals [ $key ] += ( int ) ( $row [ 'received_qty_sheet' ] ? ? 0 );
$grandTotalReceive += ( int ) ( $row [ 'received_qty_sheet' ] ? ? 0 );
}
return $this -> render (
'입고 현황' ,
'bag/receiving_status' ,
compact (
'startDate' ,
'endDate' ,
'companyIdx' ,
'bagCode' ,
'receiveType' ,
'companies' ,
'bagCodeOptions' ,
'rows' ,
'groupTotals' ,
'grandTotalReceive'
)
);
}
public function receivingStatusExport () : RedirectResponse
{
helper ([ 'admin' , 'export' ]);
$lgIdx = $this -> lgIdx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'bag/purchase-inbound' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$startDate = ( string ) ( $this -> request -> getGet ( 'start_date' ) ? ? date ( 'Y-m-01' ));
$endDate = ( string ) ( $this -> request -> getGet ( 'end_date' ) ? ? date ( 'Y-m-d' ));
$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' , 'completed' , 'pending' ], true )) {
$receiveType = 'all' ;
}
$rows = $this -> buildReceivingStatusRows ( $lgIdx , $startDate , $endDate , $companyIdx , $bagCode , $receiveType );
$exportRows = [];
foreach ( $rows as $row ) {
$exportRows [] = [
( string ) ( $row [ 'display_date' ] ? ? '' ),
( string ) ( $row [ 'bag_name' ] ? ? '' ),
( int ) ( $row [ 'received_qty_sheet' ] ? ? 0 ),
( string ) ( $row [ 'order_date' ] ? ? '' ),
( int ) ( $row [ 'order_qty_sheet' ] ? ? 0 ),
( string ) ( $row [ 'order_no' ] ? ? '' ),
( string ) ( $row [ 'company_name' ] ? ? '' ),
( string ) ( $row [ 'receive_status_label' ] ? ? '' ),
( string ) ( $row [ 'agency_name' ] ? ? '' ),
'' ,
];
}
export_xlsx (
'입고현황_' . date ( 'Ymd' ),
'입고현황' ,
[ '입고일자' , '품명' , '입고수량' , '발주일자' , '발주수량' , '발주번호' , '제작업체' , '입고여부' , '입고처' , '비고' ],
$exportRows
);
}
/**
* 미입고 잔량이 있는 발주 LOT 목록 ( 스캐너 입고용 드롭다운 ) .
*
* @ return list < array { lot_no : string , bo_idx : int , order_date : string , company_name : string , pending_lines : int } >
*/
private function buildReceivingPendingLotChoices ( int $lgIdx , int $companyIdx = 0 ) : array
{
$rows = $this -> buildReceivingCandidateRows ( $lgIdx , $companyIdx , '' , true , '' );
$byLot = [];
foreach ( $rows as $r ) {
$lot = ( string ) ( $r [ 'lot_no' ] ? ? '' );
if ( $lot === '' ) {
continue ;
}
if ( ! isset ( $byLot [ $lot ])) {
$byLot [ $lot ] = [
'lot_no' => $lot ,
'bo_idx' => ( int ) ( $r [ 'bo_idx' ] ? ? 0 ),
'order_date' => ( string ) ( $r [ 'order_date' ] ? ? '' ),
'company_name' => ( string ) ( $r [ 'company_name' ] ? ? '' ),
'pending_lines' => 0 ,
];
}
$byLot [ $lot ][ 'pending_lines' ] ++ ;
2026-03-26 16:13:07 +09:00
}
2026-04-22 15:35:36 +09:00
$list = array_values ( $byLot );
usort ( $list , static function ( array $a , array $b ) : int {
$da = ( string ) ( $a [ 'order_date' ] ? ? '' );
$db = ( string ) ( $b [ 'order_date' ] ? ? '' );
if ( $da === $db ) {
return strcmp (( string ) ( $b [ 'lot_no' ] ? ? '' ), ( string ) ( $a [ 'lot_no' ] ? ? '' ));
}
return strcmp ( $db , $da );
});
return $list ;
}
private function sanitizeLotNoForReceiving ( int $lgIdx , int $companyIdx , string $lotNo ) : string
{
$lotNo = trim ( $lotNo );
if ( $lotNo === '' ) {
return '' ;
}
foreach ( $this -> buildReceivingPendingLotChoices ( $lgIdx , $companyIdx ) as $choice ) {
if (( string ) ( $choice [ 'lot_no' ] ? ? '' ) === $lotNo ) {
return $lotNo ;
}
}
return '' ;
}
/**
* 선택 조건 ( 제작업체 + LOT ) 에 해당하는 미입고 품목 ( 봉투 ) 목록 — 조회 조건 드롭다운용 .
*
* @ return list < array { bag_code : string , bag_name : string } >
*/
private function receivingBagFilterOptions ( int $lgIdx , int $companyIdx , string $lotNo = '' ) : array
{
if ( $companyIdx <= 0 ) {
return [];
}
$allForFilter = $this -> buildReceivingCandidateRows ( $lgIdx , $companyIdx , '' , true , $lotNo );
$byCode = [];
foreach ( $allForFilter as $r ) {
$c = ( string ) ( $r [ 'bag_code' ] ? ? '' );
if ( $c === '' ) {
continue ;
}
if ( ! isset ( $byCode [ $c ])) {
$byCode [ $c ] = ( string ) ( $r [ 'bag_name' ] ? ? '' );
}
}
$list = [];
foreach ( $byCode as $code => $name ) {
$list [] = [ 'bag_code' => $code , 'bag_name' => $name ];
}
usort ( $list , static fn ( array $a , array $b ) : int => strcmp ( $a [ 'bag_name' ], $b [ 'bag_name' ]));
return $list ;
}
private function sanitizeBagCodeForReceiving ( int $lgIdx , int $companyIdx , string $lotNo , string $bagCode ) : string
{
$bagCode = trim ( $bagCode );
if ( $bagCode === '' || $companyIdx <= 0 ) {
return '' ;
}
foreach ( $this -> receivingBagFilterOptions ( $lgIdx , $companyIdx , $lotNo ) as $opt ) {
if ( $opt [ 'bag_code' ] === $bagCode ) {
return $bagCode ;
}
}
return '' ;
}
/**
* 입고 대상 후보 ( LOT - 봉투행 ) 생성 .
*
* @ param string $lotNo 빈 문자열이면 LOT 제한 없음 . 지정 시 해당 LOT ( 최신 헤드 ) 발주만 .
*/
private function buildReceivingCandidateRows ( int $lgIdx , int $companyIdx = 0 , string $bagCode = '' , bool $onlyPending = true , string $lotNo = '' ) : array
{
$orderBuilder = model ( BagOrderModel :: class )
-> where ( 'bo_lg_idx' , $lgIdx )
-> whereLatestHead ( $lgIdx )
-> where ( 'bo_status' , 'normal' )
-> orderBy ( 'bo_order_date' , 'DESC' )
-> orderBy ( 'bo_idx' , 'DESC' );
if ( $lotNo !== '' ) {
$orderBuilder -> where ( 'bo_lot_no' , $lotNo );
}
if ( $companyIdx > 0 ) {
$orderBuilder -> where ( 'bo_company_idx' , $companyIdx );
}
$orders = $orderBuilder -> findAll ();
if ( empty ( $orders )) {
return [];
}
$orderIds = array_map ( static fn ( $o ) => ( int ) ( $o -> bo_idx ? ? 0 ), $orders );
$companyMap = [];
foreach ( model ( CompanyModel :: class ) -> where ( 'cp_lg_idx' , $lgIdx ) -> where ( 'cp_type' , '제작업체' ) -> findAll () as $company ) {
$companyMap [( int ) ( $company -> cp_idx ? ? 0 )] = [
'name' => ( string ) ( $company -> cp_name ? ? '' ),
'rep' => ( string ) ( $company -> cp_rep_name ? ? '' ),
];
}
$agencyMap = [];
foreach ( model ( SalesAgencyModel :: class ) -> where ( 'sa_lg_idx' , $lgIdx ) -> orderForDisplay () -> findAll () as $agency ) {
$agencyMap [( int ) ( $agency -> sa_idx ? ? 0 )] = ( string ) ( $agency -> sa_name ? ? '' );
}
$unitMap = [];
foreach ( model ( PackagingUnitModel :: class ) -> where ( 'pu_lg_idx' , $lgIdx ) -> where ( 'pu_state' , 1 ) -> findAll () as $unit ) {
$unitMap [( string ) ( $unit -> pu_bag_code ? ? '' )] = [
'pack_per_sheet' => ( int ) ( $unit -> pu_pack_per_sheet ? ? 1 ),
'total_per_box' => ( int ) ( $unit -> pu_total_per_box ? ? 1 ),
];
}
$itemBuilder = model ( BagOrderItemModel :: class ) -> whereIn ( 'boi_bo_idx' , $orderIds );
if ( $bagCode !== '' ) {
$itemBuilder -> where ( 'boi_bag_code' , $bagCode );
}
$items = $itemBuilder -> orderBy ( 'boi_bo_idx' , 'DESC' ) -> orderBy ( 'boi_idx' , 'ASC' ) -> findAll ();
$receivedRows = model ( BagReceivingModel :: class )
-> select ( 'br_bo_idx, br_bag_code, SUM(br_qty_sheet) as recv_qty_sheet, MAX(br_receive_date) as last_receive_date' )
-> where ( 'br_lg_idx' , $lgIdx )
-> whereIn ( 'br_bo_idx' , $orderIds )
-> groupBy ( 'br_bo_idx, br_bag_code' )
-> findAll ();
$receivedMap = [];
foreach ( $receivedRows as $recv ) {
$receivedMap [( int ) ( $recv -> br_bo_idx ? ? 0 ) . '|' . ( string ) ( $recv -> br_bag_code ? ? '' )] = [
'recv_qty_sheet' => ( int ) ( $recv -> recv_qty_sheet ? ? 0 ),
'last_receive_date' => ( string ) ( $recv -> last_receive_date ? ? '' ),
];
}
$orderMap = [];
foreach ( $orders as $order ) {
$orderMap [( int ) ( $order -> bo_idx ? ? 0 )] = $order ;
}
$rows = [];
foreach ( $items as $item ) {
$boIdx = ( int ) ( $item -> boi_bo_idx ? ? 0 );
if ( ! isset ( $orderMap [ $boIdx ])) {
continue ;
}
$order = $orderMap [ $boIdx ];
$itemBagCode = ( string ) ( $item -> boi_bag_code ? ? '' );
$recv = $receivedMap [ $boIdx . '|' . $itemBagCode ] ? ? [ 'recv_qty_sheet' => 0 , 'last_receive_date' => '' ];
$orderQtySheet = ( int ) ( $item -> boi_qty_sheet ? ? 0 );
$receivedQtySheet = min ( $orderQtySheet , ( int ) ( $recv [ 'recv_qty_sheet' ] ? ? 0 ));
$pendingQtySheet = max ( 0 , $orderQtySheet - $receivedQtySheet );
if ( $onlyPending && $pendingQtySheet <= 0 ) {
continue ;
}
$unit = $unitMap [ $itemBagCode ] ? ? [ 'pack_per_sheet' => 1 , 'total_per_box' => 1 ];
$companyInfo = $companyMap [( int ) ( $order -> bo_company_idx ? ? 0 )] ? ? [ 'name' => '' , 'rep' => '' ];
$rows [] = [
'row_key' => $boIdx . '|' . $itemBagCode ,
'bo_idx' => $boIdx ,
'order_no' => sprintf ( '%06d' , $boIdx ),
'lot_no' => ( string ) ( $order -> bo_lot_no ? ? '' ),
'order_date' => ( string ) ( $order -> bo_order_date ? ? '' ),
'company_name' => ( string ) ( $companyInfo [ 'name' ] ? ? '' ),
'company_rep_name' => ( string ) ( $companyInfo [ 'rep' ] ? ? '' ),
'agency_name' => ( string ) ( $agencyMap [( int ) ( $order -> bo_agency_idx ? ? 0 )] ? ? '' ),
'bag_code' => $itemBagCode ,
'bag_name' => ( string ) ( $item -> boi_bag_name ? ? '' ),
'order_qty_sheet' => $orderQtySheet ,
'received_qty_sheet' => $receivedQtySheet ,
'pending_qty_sheet' => $pendingQtySheet ,
'pack_per_sheet' => max ( 1 , ( int ) ( $unit [ 'pack_per_sheet' ] ? ? 1 )),
'total_per_box' => max ( 1 , ( int ) ( $unit [ 'total_per_box' ] ? ? 1 )),
'last_receive_date' => ( string ) ( $recv [ 'last_receive_date' ] ? ? '' ),
];
}
return $rows ;
}
private function buildReceivingStatusRows (
int $lgIdx ,
string $startDate ,
string $endDate ,
int $companyIdx ,
string $bagCode ,
string $receiveType
) : array {
$rows = $this -> buildReceivingCandidateRows ( $lgIdx , $companyIdx , $bagCode , false , '' );
$filtered = [];
foreach ( $rows as $row ) {
$pendingQty = ( int ) ( $row [ 'pending_qty_sheet' ] ? ? 0 );
$isCompleted = $pendingQty <= 0 ;
if ( $receiveType === 'completed' && ! $isCompleted ) {
continue ;
}
if ( $receiveType === 'pending' && $isCompleted ) {
continue ;
}
$displayDate = ( string ) ( $row [ 'last_receive_date' ] ? ? '' );
if ( $displayDate === '' ) {
$displayDate = ( string ) ( $row [ 'order_date' ] ? ? '' );
}
if ( $startDate !== '' && preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $startDate ) && $displayDate < $startDate ) {
continue ;
}
if ( $endDate !== '' && preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $endDate ) && $displayDate > $endDate ) {
continue ;
}
$row [ 'display_date' ] = $displayDate ;
$row [ 'receive_status_label' ] = $isCompleted ? '완료' : '미완료' ;
$filtered [] = $row ;
}
usort ( $filtered , static function ( array $a , array $b ) : int {
$da = ( string ) ( $a [ 'display_date' ] ? ? '' );
$db = ( string ) ( $b [ 'display_date' ] ? ? '' );
if ( $da === $db ) {
return strcmp (( string ) ( $a [ 'bag_name' ] ? ? '' ), ( string ) ( $b [ 'bag_name' ] ? ? '' ));
}
return strcmp ( $da , $db );
});
return $filtered ;
2026-03-26 16:13:07 +09:00
}
// --- 판매 등록 ---
public function saleCreate () : string
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
$shops = $lgIdx ? model ( DesignatedShopModel :: class ) -> where ( 'ds_lg_idx' , $lgIdx ) -> where ( 'ds_state' , 1 ) -> findAll () : [];
2026-04-08 00:20:09 +09:00
$kind = model ( CodeKindModel :: class ) -> where ( 'ck_code' , 'O' ) -> first ();
$bagCodes = $kind ? model ( CodeDetailModel :: class ) -> getByKind (( int ) $kind -> ck_idx , true , $lgIdx ) : [];
2026-03-26 16:13:07 +09:00
return $this -> render ( '판매 등록' , 'bag/create_bag_sale' , compact ( 'shops' , 'bagCodes' ));
}
public function saleStore ()
{
$admin = new \App\Controllers\Admin\BagSale ();
$admin -> initController ( $this -> request , $this -> response , service ( 'logger' ));
$result = $admin -> store ();
if ( $result instanceof \CodeIgniter\HTTP\RedirectResponse ) {
return redirect () -> to ( site_url ( 'bag/sales' )) -> with ( 'success' , session () -> getFlashdata ( 'success' )) -> with ( 'errors' , session () -> getFlashdata ( 'errors' ));
}
return redirect () -> to ( site_url ( 'bag/sales' )) -> with ( 'success' , '판매 등록되었습니다.' );
}
// --- 주문 접수 ---
public function shopOrderCreate () : string
{
helper ( 'admin' );
$lgIdx = $this -> lgIdx ();
$shops = $lgIdx ? model ( DesignatedShopModel :: class ) -> where ( 'ds_lg_idx' , $lgIdx ) -> where ( 'ds_state' , 1 ) -> findAll () : [];
2026-04-08 00:20:09 +09:00
$kind = model ( CodeKindModel :: class ) -> where ( 'ck_code' , 'O' ) -> first ();
$bagCodes = $kind ? model ( CodeDetailModel :: class ) -> getByKind (( int ) $kind -> ck_idx , true , $lgIdx ) : [];
2026-03-26 16:13:07 +09:00
return $this -> render ( '주문 접수' , 'bag/create_shop_order' , compact ( 'shops' , 'bagCodes' ));
}
public function shopOrderStore ()
{
$admin = new \App\Controllers\Admin\ShopOrder ();
$admin -> initController ( $this -> request , $this -> response , service ( 'logger' ));
$result = $admin -> store ();
if ( $result instanceof \CodeIgniter\HTTP\RedirectResponse ) {
return redirect () -> to ( site_url ( 'bag/sales' )) -> with ( 'success' , session () -> getFlashdata ( 'success' )) -> with ( 'errors' , session () -> getFlashdata ( 'errors' ));
}
return redirect () -> to ( site_url ( 'bag/sales' )) -> with ( 'success' , '주문 접수되었습니다.' );
}
2026-03-26 14:30:45 +09:00
}