Files
jongryangje/app/Controllers/Admin/DesignatedShop.php
taekyoungc a3f92cd322 feat: TOTP 2차 인증, 관리자 메뉴/대시보드 및 의존성 반영
- robthree/twofactorauth, Auth 설정·TotpService·2FA 뷰·라우트
- member TOTP 컬럼 DDL(login_tables, member_add_totp.sql)
- 관리자 메뉴·레이아웃·필터·대시보드 등 연관 변경
- env 샘플에 auth.requireTotp 주석

Made-with: Cursor
2026-03-26 15:30:32 +09:00

308 lines
11 KiB
PHP

<?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
{
return Roles::isSuperAdminEquivalent((int) session()->get('mb_level'));
}
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', '작업할 지자체가 선택되지 않았습니다. 지자체를 선택해 주세요.');
}
$list = $this->shopModel
->where('ds_lg_idx', $lgIdx)
->orderBy('ds_idx', 'DESC')
->findAll();
// 지자체 이름 매핑용
$lgMap = [];
foreach ($this->lgModel->findAll() as $lg) {
$lgMap[$lg->lg_idx] = $lg->lg_name;
}
return view('admin/layout', [
'title' => '지정판매소 관리',
'content' => view('admin/designated_shop/index', [
'list' => $list,
'lgMap' => $lgMap,
]),
]);
}
/**
* 지정판매소 등록 폼 (효과 지자체 기준)
*/
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', '지정판매소가 삭제되었습니다.');
}
/**
* 지자체별 다음 판매소번호 생성 (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);
}
}