2026-03-25 12:05:33 +09:00
< ? php
namespace App\Controllers\Admin ;
use App\Controllers\BaseController ;
use App\Models\DesignatedShopModel ;
use App\Models\LocalGovernmentModel ;
use Config\Roles ;
class DesignatedShop extends BaseController
{
private DesignatedShopModel $shopModel ;
private LocalGovernmentModel $lgModel ;
private Roles $roles ;
public function __construct ()
{
$this -> shopModel = model ( DesignatedShopModel :: class );
$this -> lgModel = model ( LocalGovernmentModel :: class );
$this -> roles = config ( 'Roles' );
}
private function isSuperAdmin () : bool
{
2026-03-26 15:29:55 +09:00
return Roles :: isSuperAdminEquivalent (( int ) session () -> get ( 'mb_level' ));
2026-03-25 12:05:33 +09:00
}
private function isLocalAdmin () : bool
{
return ( int ) session () -> get ( 'mb_level' ) === Roles :: LEVEL_LOCAL_ADMIN ;
}
/**
* 지정판매소 목록 ( 효과 지자체 기준 : super admin = 선택 지자체 , 지자체관리자 = mb_lg_idx )
*/
public function index ()
{
helper ( 'admin' );
$lgIdx = admin_effective_lg_idx ();
if ( $lgIdx === null || $lgIdx <= 0 ) {
return redirect () -> to ( site_url ( 'admin' ))
-> with ( 'error' , '작업할 지자체가 선택되지 않았습니다. 지자체를 선택해 주세요.' );
}
2026-03-26 16:50:28 +09:00
$builder = $this -> shopModel -> where ( 'ds_lg_idx' , $lgIdx );
// 다조건 검색 (P2-15)
$dsName = $this -> request -> getGet ( 'ds_name' );
$dsGugunCode = $this -> request -> getGet ( 'ds_gugun_code' );
$dsState = $this -> request -> getGet ( 'ds_state' );
if ( $dsName !== null && $dsName !== '' ) {
$builder -> like ( 'ds_name' , $dsName );
}
if ( $dsGugunCode !== null && $dsGugunCode !== '' ) {
$builder -> where ( 'ds_gugun_code' , $dsGugunCode );
}
if ( $dsState !== null && $dsState !== '' ) {
$builder -> where ( 'ds_state' , ( int ) $dsState );
}
$list = $builder -> orderBy ( 'ds_idx' , 'DESC' ) -> paginate ( 20 );
2026-03-26 16:40:49 +09:00
$pager = $this -> shopModel -> pager ;
2026-03-25 12:05:33 +09:00
// 지자체 이름 매핑용
$lgMap = [];
foreach ( $this -> lgModel -> findAll () as $lg ) {
$lgMap [ $lg -> lg_idx ] = $lg -> lg_name ;
}
2026-03-26 16:50:28 +09:00
// 구군코드 목록 (검색 필터용)
$db = \Config\Database :: connect ();
$gugunCodes = $db -> query ( " SELECT DISTINCT ds_gugun_code FROM designated_shop WHERE ds_lg_idx = ? AND ds_gugun_code != '' ORDER BY ds_gugun_code " , [ $lgIdx ]) -> getResult ();
2026-03-25 12:05:33 +09:00
return view ( 'admin/layout' , [
'title' => '지정판매소 관리' ,
'content' => view ( 'admin/designated_shop/index' , [
2026-03-26 16:50:28 +09:00
'list' => $list ,
'lgMap' => $lgMap ,
'pager' => $pager ,
'dsName' => $dsName ? ? '' ,
'dsGugunCode' => $dsGugunCode ? ? '' ,
'dsState' => $dsState ? ? '' ,
'gugunCodes' => $gugunCodes ,
2026-03-25 12:05:33 +09:00
]),
]);
}
2026-03-26 16:40:49 +09:00
public function export ()
{
2026-04-08 15:22:24 +09:00
helper ([ 'admin' , 'export' , 'pii_mask' ]);
2026-03-26 16:40:49 +09:00
$lgIdx = admin_effective_lg_idx ();
if ( ! $lgIdx ) {
return redirect () -> to ( site_url ( 'admin/designated-shops' )) -> with ( 'error' , '지자체를 선택해 주세요.' );
}
$list = $this -> shopModel -> where ( 'ds_lg_idx' , $lgIdx ) -> orderBy ( 'ds_idx' , 'DESC' ) -> findAll ();
$rows = [];
foreach ( $list as $row ) {
$stateMap = [ 1 => '정상' , 2 => '폐업' , 3 => '직권해지' ];
$rows [] = [
$row -> ds_idx ,
$row -> ds_shop_no ,
$row -> ds_name ,
2026-04-08 15:22:24 +09:00
mask_person_name ( $row -> ds_rep_name ? ? null ),
2026-03-26 16:40:49 +09:00
$row -> ds_biz_no ,
$row -> ds_va_number ,
$row -> ds_tel ? ? '' ,
$row -> ds_addr ? ? '' ,
$stateMap [( int ) $row -> ds_state ] ? ? '' ,
$row -> ds_regdate ? ? '' ,
];
}
export_csv (
'지정판매소_' . date ( 'Ymd' ) . '.csv' ,
[ '번호' , '판매소번호' , '상호명' , '대표자' , '사업자번호' , '가상계좌' , '전화번호' , '주소' , '상태' , '등록일' ],
$rows
);
}
2026-04-08 15:22:24 +09:00
/**
* 지정판매소 상세 ( 읽기 전용 , 목록에서 연결 )
*/
public function show ( int $id )
{
helper ( 'admin' );
$lgIdx = admin_effective_lg_idx ();
if ( $lgIdx === null || $lgIdx <= 0 ) {
return redirect () -> to ( work_area_home_url ())
-> with ( 'error' , '작업할 지자체가 선택되지 않았습니다. 지자체를 선택해 주세요.' );
}
$shop = $this -> shopModel -> find ( $id );
if ( $shop === null || ( int ) $shop -> ds_lg_idx !== $lgIdx ) {
return redirect () -> to ( mgmt_url ( 'designated-shops' ))
-> with ( 'error' , '해당 지정판매소를 찾을 수 없습니다.' );
}
$currentLg = $this -> lgModel -> find ( $lgIdx );
$stateLabel = match (( int ) $shop -> ds_state ) {
1 => '정상' ,
2 => '폐업' ,
3 => '직권해지' ,
default => ( string ) $shop -> ds_state ,
};
return $this -> renderWorkPage ( '지정판매소 정보' , 'admin/designated_shop/show' , [
'shop' => $shop ,
'currentLg' => $currentLg ,
'stateLabel' => $stateLabel ,
'can_edit' => $this -> isSuperAdmin () || $this -> isLocalAdmin (),
]);
}
2026-03-25 12:05:33 +09:00
/**
* 지정판매소 등록 폼 ( 효과 지자체 기준 )
*/
public function create ()
{
helper ( 'admin' );
$lgIdx = admin_effective_lg_idx ();
if ( $lgIdx === null || $lgIdx <= 0 ) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '작업할 지자체가 선택되지 않았습니다. 지자체를 선택해 주세요.' );
}
$currentLg = $this -> lgModel -> find ( $lgIdx );
if ( $currentLg === null ) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '선택한 지자체 정보를 찾을 수 없습니다.' );
}
return view ( 'admin/layout' , [
'title' => '지정판매소 등록' ,
'content' => view ( 'admin/designated_shop/create' , [
'localGovs' => [],
'currentLg' => $currentLg ,
]),
]);
}
/**
* 지정판매소 등록 처리
*/
public function store ()
{
if ( ! $this -> isSuperAdmin () && ! $this -> isLocalAdmin ()) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '지정판매소 등록은 관리자만 가능합니다.' );
}
$rules = [
'ds_name' => 'required|max_length[100]' ,
'ds_biz_no' => 'required|max_length[20]' ,
'ds_rep_name' => 'required|max_length[50]' ,
'ds_va_number' => 'permit_empty|max_length[50]' ,
'ds_email' => 'permit_empty|valid_email|max_length[100]' ,
];
if ( ! $this -> validate ( $rules )) {
return redirect () -> back ()
-> withInput ()
-> with ( 'errors' , $this -> validator -> getErrors ());
}
helper ( 'admin' );
$lgIdx = admin_effective_lg_idx ();
if ( $lgIdx === null || $lgIdx <= 0 ) {
return redirect () -> back ()
-> withInput ()
-> with ( 'error' , '소속 지자체가 올바르지 않습니다.' );
}
$lg = $this -> lgModel -> find ( $lgIdx );
if ( $lg === null || ( string ) $lg -> lg_code === '' ) {
return redirect () -> back ()
-> withInput ()
-> with ( 'error' , '지자체 코드 정보를 찾을 수 없습니다.' );
}
$dsShopNo = $this -> generateNextShopNo ( $lgIdx , ( string ) $lg -> lg_code );
$data = [
'ds_lg_idx' => $lgIdx ,
'ds_mb_idx' => null ,
'ds_shop_no' => $dsShopNo ,
'ds_name' => ( string ) $this -> request -> getPost ( 'ds_name' ),
'ds_biz_no' => ( string ) $this -> request -> getPost ( 'ds_biz_no' ),
'ds_rep_name' => ( string ) $this -> request -> getPost ( 'ds_rep_name' ),
'ds_va_number' => ( string ) $this -> request -> getPost ( 'ds_va_number' ),
'ds_zip' => ( string ) $this -> request -> getPost ( 'ds_zip' ),
'ds_addr' => ( string ) $this -> request -> getPost ( 'ds_addr' ),
'ds_addr_jibun' => ( string ) $this -> request -> getPost ( 'ds_addr_jibun' ),
'ds_tel' => ( string ) $this -> request -> getPost ( 'ds_tel' ),
'ds_rep_phone' => ( string ) $this -> request -> getPost ( 'ds_rep_phone' ),
'ds_email' => ( string ) $this -> request -> getPost ( 'ds_email' ),
'ds_gugun_code' => ( string ) $lg -> lg_code ,
'ds_designated_at' => $this -> request -> getPost ( 'ds_designated_at' ) ? : null ,
'ds_state' => 1 ,
'ds_regdate' => date ( 'Y-m-d H:i:s' ),
];
$this -> shopModel -> insert ( $data );
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'success' , '지정판매소가 등록되었습니다.' );
}
/**
* 지정판매소 수정 폼 ( 효과 지자체 소속만 허용 )
* 문서 : docs / 기본 개발계획 / 23 - 지정판매소_수정_삭제_기능 . md
*/
public function edit ( int $id )
{
if ( ! $this -> isSuperAdmin () && ! $this -> isLocalAdmin ()) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '권한이 없습니다.' );
}
helper ( 'admin' );
$lgIdx = admin_effective_lg_idx ();
if ( $lgIdx === null || $lgIdx <= 0 ) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '작업할 지자체가 선택되지 않았습니다.' );
}
$shop = $this -> shopModel -> find ( $id );
if ( $shop === null || ( int ) $shop -> ds_lg_idx !== $lgIdx ) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '해당 지정판매소를 찾을 수 없거나 수정할 수 없습니다.' );
}
$currentLg = $this -> lgModel -> find ( $lgIdx );
return view ( 'admin/layout' , [
'title' => '지정판매소 수정' ,
'content' => view ( 'admin/designated_shop/edit' , [
'shop' => $shop ,
'currentLg' => $currentLg ,
]),
]);
}
/**
* 지정판매소 수정 처리
*/
public function update ( int $id )
{
if ( ! $this -> isSuperAdmin () && ! $this -> isLocalAdmin ()) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '권한이 없습니다.' );
}
helper ( 'admin' );
$lgIdx = admin_effective_lg_idx ();
if ( $lgIdx === null || $lgIdx <= 0 ) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '작업할 지자체가 선택되지 않았습니다.' );
}
$shop = $this -> shopModel -> find ( $id );
if ( $shop === null || ( int ) $shop -> ds_lg_idx !== $lgIdx ) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '해당 지정판매소를 찾을 수 없거나 수정할 수 없습니다.' );
}
$rules = [
'ds_name' => 'required|max_length[100]' ,
'ds_biz_no' => 'required|max_length[20]' ,
'ds_rep_name' => 'required|max_length[50]' ,
'ds_va_number' => 'permit_empty|max_length[50]' ,
'ds_email' => 'permit_empty|valid_email|max_length[100]' ,
'ds_state' => 'permit_empty|in_list[1,2,3]' ,
];
if ( ! $this -> validate ( $rules )) {
return redirect () -> back ()
-> withInput ()
-> with ( 'errors' , $this -> validator -> getErrors ());
}
$data = [
'ds_name' => ( string ) $this -> request -> getPost ( 'ds_name' ),
'ds_biz_no' => ( string ) $this -> request -> getPost ( 'ds_biz_no' ),
'ds_rep_name' => ( string ) $this -> request -> getPost ( 'ds_rep_name' ),
'ds_va_number' => ( string ) $this -> request -> getPost ( 'ds_va_number' ),
'ds_zip' => ( string ) $this -> request -> getPost ( 'ds_zip' ),
'ds_addr' => ( string ) $this -> request -> getPost ( 'ds_addr' ),
'ds_addr_jibun' => ( string ) $this -> request -> getPost ( 'ds_addr_jibun' ),
'ds_tel' => ( string ) $this -> request -> getPost ( 'ds_tel' ),
'ds_rep_phone' => ( string ) $this -> request -> getPost ( 'ds_rep_phone' ),
'ds_email' => ( string ) $this -> request -> getPost ( 'ds_email' ),
'ds_designated_at' => $this -> request -> getPost ( 'ds_designated_at' ) ? : null ,
'ds_state' => ( int ) ( $this -> request -> getPost ( 'ds_state' ) ? : 1 ),
];
$this -> shopModel -> update ( $id , $data );
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'success' , '지정판매소 정보가 수정되었습니다.' );
}
/**
* 지정판매소 삭제 ( 물리 삭제 , 효과 지자체 소속만 허용 )
* 문서 : docs / 기본 개발계획 / 23 - 지정판매소_수정_삭제_기능 . md
*/
public function delete ( int $id )
{
if ( ! $this -> isSuperAdmin () && ! $this -> isLocalAdmin ()) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '권한이 없습니다.' );
}
helper ( 'admin' );
$lgIdx = admin_effective_lg_idx ();
if ( $lgIdx === null || $lgIdx <= 0 ) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '작업할 지자체가 선택되지 않았습니다.' );
}
$shop = $this -> shopModel -> find ( $id );
if ( $shop === null || ( int ) $shop -> ds_lg_idx !== $lgIdx ) {
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'error' , '해당 지정판매소를 찾을 수 없거나 삭제할 수 없습니다.' );
}
$this -> shopModel -> delete ( $id );
return redirect () -> to ( site_url ( 'admin/designated-shops' ))
-> with ( 'success' , '지정판매소가 삭제되었습니다.' );
}
2026-03-26 16:50:28 +09:00
/**
* P2 - 17 : 지정판매소 지도 표시
*/
public function map ()
{
helper ( 'admin' );
$lgIdx = admin_effective_lg_idx ();
if ( $lgIdx === null || $lgIdx <= 0 ) {
return redirect () -> to ( site_url ( 'admin' ))
-> with ( 'error' , '작업할 지자체가 선택되지 않았습니다.' );
}
$shops = $this -> shopModel
-> where ( 'ds_lg_idx' , $lgIdx )
-> where ( 'ds_state' , 1 )
-> findAll ();
return view ( 'admin/layout' , [
'title' => '지정판매소 지도' ,
'content' => view ( 'admin/designated_shop/map' , [
'shops' => $shops ,
]),
]);
}
/**
* P2 - 18 : 지정판매소 현황 ( 연도별 신규 / 취소 )
*/
public function status ()
{
helper ( 'admin' );
$lgIdx = admin_effective_lg_idx ();
if ( $lgIdx === null || $lgIdx <= 0 ) {
return redirect () -> to ( site_url ( 'admin' ))
-> with ( 'error' , '작업할 지자체가 선택되지 않았습니다.' );
}
$db = \Config\Database :: connect ();
// 연도별 신규등록 건수 (ds_designated_at 기준)
$newByYear = $db -> query ( "
SELECT YEAR ( ds_designated_at ) as yr , COUNT ( * ) as cnt
FROM designated_shop
WHERE ds_lg_idx = ? AND ds_designated_at IS NOT NULL
GROUP BY YEAR ( ds_designated_at )
ORDER BY yr DESC
" , [ $lgIdx ])->getResult();
// 연도별 취소/비활성 건수 (ds_state != 1, ds_regdate 기준)
$cancelByYear = $db -> query ( "
SELECT YEAR ( ds_regdate ) as yr , COUNT ( * ) as cnt
FROM designated_shop
WHERE ds_lg_idx = ? AND ds_state != 1
GROUP BY YEAR ( ds_regdate )
ORDER BY yr DESC
" , [ $lgIdx ])->getResult();
// 전체 현황 합계
$totalActive = $this -> shopModel -> where ( 'ds_lg_idx' , $lgIdx ) -> where ( 'ds_state' , 1 ) -> countAllResults ( false );
$totalInactive = $this -> shopModel -> where ( 'ds_lg_idx' , $lgIdx ) -> where ( 'ds_state !=' , 1 ) -> countAllResults ( false );
return view ( 'admin/layout' , [
'title' => '지정판매소 현황' ,
'content' => view ( 'admin/designated_shop/status' , [
'newByYear' => $newByYear ,
'cancelByYear' => $cancelByYear ,
'totalActive' => $totalActive ,
'totalInactive' => $totalInactive ,
]),
]);
}
2026-03-25 12:05:33 +09:00
/**
* 지자체별 다음 판매소번호 생성 ( lg_code + 3 자리 일련번호 )
* 문서 : docs / 기본 개발계획 / 22 - 판매소번호_일련번호_결정 . md §3
*/
private function generateNextShopNo ( int $lgIdx , string $lgCode ) : string
{
$prefixLen = strlen ( $lgCode );
$existing = $this -> shopModel -> where ( 'ds_lg_idx' , $lgIdx ) -> findAll ();
$maxSerial = 0 ;
foreach ( $existing as $row ) {
$no = $row -> ds_shop_no ;
if ( strlen ( $no ) === $prefixLen + 3 && str_starts_with ( $no , $lgCode )) {
$n = ( int ) substr ( $no , - 3 );
if ( $n > $maxSerial ) {
$maxSerial = $n ;
}
}
}
return $lgCode . sprintf ( '%03d' , $maxSerial + 1 );
}
}