Initial project import for team collaboration.
Exclude local docs, MCP, and secrets via gitignore. Made-with: Cursor
This commit is contained in:
140
app/Controllers/Admin/Access.php
Normal file
140
app/Controllers/Admin/Access.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\LocalGovernmentModel;
|
||||
use App\Models\MemberApprovalRequestModel;
|
||||
use App\Models\MemberModel;
|
||||
use Config\Roles;
|
||||
use App\Models\MemberLogModel;
|
||||
|
||||
class Access extends BaseController
|
||||
{
|
||||
private MemberLogModel $memberLogModel;
|
||||
private MemberApprovalRequestModel $approvalModel;
|
||||
private MemberModel $memberModel;
|
||||
private Roles $roles;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->memberLogModel = model(MemberLogModel::class);
|
||||
$this->approvalModel = model(MemberApprovalRequestModel::class);
|
||||
$this->memberModel = model(MemberModel::class);
|
||||
$this->roles = config('Roles');
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 이력 (기간 조회)
|
||||
*/
|
||||
public function loginHistory(): string
|
||||
{
|
||||
$start = $this->request->getGet('start');
|
||||
$end = $this->request->getGet('end');
|
||||
$builder = $this->memberLogModel->builder();
|
||||
$builder->select('member_log.*');
|
||||
$builder->orderBy('mll_regdate', 'DESC');
|
||||
if ($start !== null && $start !== '') {
|
||||
$builder->where('mll_regdate >=', $start . ' 00:00:00');
|
||||
}
|
||||
if ($end !== null && $end !== '') {
|
||||
$builder->where('mll_regdate <=', $end . ' 23:59:59');
|
||||
}
|
||||
$list = $builder->get()->getResult();
|
||||
return view('admin/layout', [
|
||||
'title' => '로그인 이력',
|
||||
'content' => view('admin/access/login_history', ['list' => $list, 'start' => $start, 'end' => $end]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function approvals(): string
|
||||
{
|
||||
$status = (string) ($this->request->getGet('status') ?? MemberApprovalRequestModel::STATUS_PENDING);
|
||||
$allowedStatus = [
|
||||
MemberApprovalRequestModel::STATUS_PENDING,
|
||||
MemberApprovalRequestModel::STATUS_APPROVED,
|
||||
MemberApprovalRequestModel::STATUS_REJECTED,
|
||||
];
|
||||
if (! in_array($status, $allowedStatus, true)) {
|
||||
$status = MemberApprovalRequestModel::STATUS_PENDING;
|
||||
}
|
||||
|
||||
$builder = $this->approvalModel->builder();
|
||||
$builder->select(
|
||||
'member_approval_request.*, member.mb_id, member.mb_name, member.mb_lg_idx, local_government.lg_name'
|
||||
);
|
||||
$builder->join('member', 'member.mb_idx = member_approval_request.mb_idx', 'left');
|
||||
$builder->join('local_government', 'local_government.lg_idx = member.mb_lg_idx', 'left');
|
||||
$builder->where('member_approval_request.mar_status', $status);
|
||||
$builder->orderBy('member_approval_request.mar_requested_at', 'DESC');
|
||||
$list = $builder->get()->getResult();
|
||||
|
||||
return view('admin/layout', [
|
||||
'title' => '승인 대기',
|
||||
'content' => view('admin/access/approvals', [
|
||||
'list' => $list,
|
||||
'status' => $status,
|
||||
'roles' => $this->roles,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function approve(int $id)
|
||||
{
|
||||
$requestRow = $this->approvalModel->find($id);
|
||||
if (! $requestRow) {
|
||||
return redirect()->to(site_url('admin/access/approvals'))->with('error', '승인 요청을 찾을 수 없습니다.');
|
||||
}
|
||||
if ($requestRow->mar_status !== MemberApprovalRequestModel::STATUS_PENDING) {
|
||||
return redirect()->to(site_url('admin/access/approvals'))->with('error', '이미 처리된 요청입니다.');
|
||||
}
|
||||
|
||||
$requestedLevel = (int) $requestRow->mar_requested_level;
|
||||
if ($requestedLevel === Roles::LEVEL_SUPER_ADMIN) {
|
||||
return redirect()->to(site_url('admin/access/approvals'))->with('error', 'super admin 역할 요청은 승인할 수 없습니다.');
|
||||
}
|
||||
|
||||
$db = db_connect();
|
||||
$db->transStart();
|
||||
$this->memberModel->update((int) $requestRow->mb_idx, [
|
||||
'mb_level' => $requestedLevel,
|
||||
]);
|
||||
$this->approvalModel->update($id, [
|
||||
'mar_status' => MemberApprovalRequestModel::STATUS_APPROVED,
|
||||
'mar_processed_at' => date('Y-m-d H:i:s'),
|
||||
'mar_processed_by' => (int) (session()->get('mb_idx') ?? 0),
|
||||
'mar_reject_reason' => null,
|
||||
]);
|
||||
$db->transComplete();
|
||||
|
||||
if (! $db->transStatus()) {
|
||||
return redirect()->to(site_url('admin/access/approvals'))->with('error', '승인 처리 중 오류가 발생했습니다.');
|
||||
}
|
||||
|
||||
return redirect()->to(site_url('admin/access/approvals'))->with('success', '승인 처리되었습니다.');
|
||||
}
|
||||
|
||||
public function reject(int $id)
|
||||
{
|
||||
$requestRow = $this->approvalModel->find($id);
|
||||
if (! $requestRow) {
|
||||
return redirect()->to(site_url('admin/access/approvals'))->with('error', '승인 요청을 찾을 수 없습니다.');
|
||||
}
|
||||
if ($requestRow->mar_status !== MemberApprovalRequestModel::STATUS_PENDING) {
|
||||
return redirect()->to(site_url('admin/access/approvals'))->with('error', '이미 처리된 요청입니다.');
|
||||
}
|
||||
|
||||
$reason = trim((string) $this->request->getPost('reject_reason'));
|
||||
if ($reason === '') {
|
||||
$reason = '관리자 반려';
|
||||
}
|
||||
$this->approvalModel->update($id, [
|
||||
'mar_status' => MemberApprovalRequestModel::STATUS_REJECTED,
|
||||
'mar_reject_reason' => mb_substr($reason, 0, 255),
|
||||
'mar_processed_at' => date('Y-m-d H:i:s'),
|
||||
'mar_processed_by' => (int) (session()->get('mb_idx') ?? 0),
|
||||
]);
|
||||
|
||||
return redirect()->to(site_url('admin/access/approvals'))->with('success', '반려 처리되었습니다.');
|
||||
}
|
||||
}
|
||||
16
app/Controllers/Admin/Dashboard.php
Normal file
16
app/Controllers/Admin/Dashboard.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
class Dashboard extends BaseController
|
||||
{
|
||||
public function index(): string
|
||||
{
|
||||
return view('admin/layout', [
|
||||
'title' => '대시보드',
|
||||
'content' => view('admin/dashboard/index'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
307
app/Controllers/Admin/DesignatedShop.php
Normal file
307
app/Controllers/Admin/DesignatedShop.php
Normal file
@@ -0,0 +1,307 @@
|
||||
<?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 (int) session()->get('mb_level') === Roles::LEVEL_SUPER_ADMIN;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
99
app/Controllers/Admin/LocalGovernment.php
Normal file
99
app/Controllers/Admin/LocalGovernment.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\LocalGovernmentModel;
|
||||
use Config\Roles;
|
||||
|
||||
class LocalGovernment extends BaseController
|
||||
{
|
||||
private LocalGovernmentModel $lgModel;
|
||||
private Roles $roles;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->lgModel = model(LocalGovernmentModel::class);
|
||||
$this->roles = config('Roles');
|
||||
}
|
||||
|
||||
private function isSuperAdmin(): bool
|
||||
{
|
||||
return (int) session()->get('mb_level') === Roles::LEVEL_SUPER_ADMIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 지자체 목록
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if (! $this->isSuperAdmin()) {
|
||||
return redirect()->to(site_url('admin'))
|
||||
->with('error', '지자체 관리는 super admin만 접근할 수 있습니다.');
|
||||
}
|
||||
|
||||
$list = $this->lgModel->orderBy('lg_idx', 'DESC')->findAll();
|
||||
|
||||
return view('admin/layout', [
|
||||
'title' => '지자체 관리',
|
||||
'content' => view('admin/local_government/index', ['list' => $list]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 지자체 등록 폼
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
if (! $this->isSuperAdmin()) {
|
||||
return redirect()->to(site_url('admin/local-governments'))
|
||||
->with('error', '지자체 등록은 super admin만 가능합니다.');
|
||||
}
|
||||
|
||||
return view('admin/layout', [
|
||||
'title' => '지자체 등록',
|
||||
'content' => view('admin/local_government/create'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 지자체 등록 처리
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
if (! $this->isSuperAdmin()) {
|
||||
return redirect()->to(site_url('admin/local-governments'))
|
||||
->with('error', '지자체 등록은 super admin만 가능합니다.');
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'lg_name' => 'required|max_length[100]',
|
||||
'lg_code' => 'required|max_length[20]|is_unique[local_government.lg_code]',
|
||||
'lg_sido' => 'required|max_length[50]',
|
||||
'lg_gugun' => 'required|max_length[50]',
|
||||
'lg_addr' => 'permit_empty|max_length[255]',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$data = [
|
||||
'lg_name' => (string) $this->request->getPost('lg_name'),
|
||||
'lg_code' => (string) $this->request->getPost('lg_code'),
|
||||
'lg_sido' => (string) $this->request->getPost('lg_sido'),
|
||||
'lg_gugun' => (string) $this->request->getPost('lg_gugun'),
|
||||
'lg_addr' => (string) $this->request->getPost('lg_addr'),
|
||||
'lg_state' => 1,
|
||||
'lg_regdate' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
$this->lgModel->insert($data);
|
||||
|
||||
return redirect()->to(site_url('admin/local-governments'))
|
||||
->with('success', '지자체가 등록되었습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
213
app/Controllers/Admin/Menu.php
Normal file
213
app/Controllers/Admin/Menu.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\MenuModel;
|
||||
use App\Models\MenuTypeModel;
|
||||
use Config\Roles;
|
||||
|
||||
class Menu extends BaseController
|
||||
{
|
||||
private MenuModel $menuModel;
|
||||
private MenuTypeModel $typeModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->menuModel = model(MenuModel::class);
|
||||
$this->typeModel = model(MenuTypeModel::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 관리 화면 (목록 + 등록/수정 폼). 지자체별 메뉴만 조회·관리.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
->with('error', '메뉴를 관리하려면 먼저 지자체를 선택하세요.');
|
||||
}
|
||||
$types = $this->typeModel->orderBy('mt_sort', 'ASC')->findAll();
|
||||
$mtIdx = (int) ($this->request->getGet('mt_idx') ?? 0);
|
||||
if ($mtIdx <= 0 && ! empty($types)) {
|
||||
// 기본 선택: 사이트 메뉴(mt_code=site), 없으면 첫 번째 타입
|
||||
$siteType = $this->typeModel->where('mt_code', 'site')->first();
|
||||
$mtIdx = $siteType ? (int) $siteType->mt_idx : (int) $types[0]->mt_idx;
|
||||
}
|
||||
$list = $mtIdx > 0 ? $this->menuModel->getAllByType($mtIdx, $lgIdx) : [];
|
||||
|
||||
// 현재 지자체에 메뉴가 없으면, mt_idx별로 기본 지자체(lg_idx=1)의 메뉴를 한 번 복사한다.
|
||||
if ($mtIdx > 0 && empty($list)) {
|
||||
$this->menuModel->copyDefaultsFromLg($mtIdx, 1, $lgIdx);
|
||||
$list = $this->menuModel->getAllByType($mtIdx, $lgIdx);
|
||||
}
|
||||
|
||||
// 트리 순서대로 상위 메뉴 바로 아래에 하위 메뉴가 오도록 평탄화
|
||||
if (! empty($list)) {
|
||||
$tree = build_menu_tree($list);
|
||||
$list = flatten_menu_tree($tree);
|
||||
}
|
||||
|
||||
$currentType = $mtIdx > 0 ? $this->typeModel->find($mtIdx) : null;
|
||||
|
||||
return view('admin/layout', [
|
||||
'title' => '메뉴 관리',
|
||||
'content' => view('admin/menu/index', [
|
||||
'types' => $types,
|
||||
'mtIdx' => $mtIdx,
|
||||
'mtCode' => $currentType->mt_code ?? '',
|
||||
'list' => $list,
|
||||
'levelNames' => config('Roles')->levelNames,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 목록 JSON (트리 정렬된 평면 배열). 현재 지자체만.
|
||||
*/
|
||||
public function list()
|
||||
{
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return $this->response->setJSON(['status' => 0, 'msg' => '지자체를 선택하세요.']);
|
||||
}
|
||||
$mtIdx = (int) $this->request->getGet('mt_idx');
|
||||
if ($mtIdx <= 0) {
|
||||
return $this->response->setJSON(['status' => 0, 'msg' => 'mt_idx required']);
|
||||
}
|
||||
$list = $this->menuModel->getAllByType($mtIdx, $lgIdx);
|
||||
return $this->response->setJSON(['status' => 1, 'data' => $list]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 등록 (현재 지자체 소속으로 저장)
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
->with('error', '메뉴를 등록하려면 먼저 지자체를 선택하세요.');
|
||||
}
|
||||
$mtIdx = (int) $this->request->getPost('mt_idx');
|
||||
$mmPidx = (int) $this->request->getPost('mm_pidx');
|
||||
$mmDep = (int) $this->request->getPost('mm_dep');
|
||||
$mmName = trim((string) $this->request->getPost('mm_name'));
|
||||
if ($mtIdx <= 0) {
|
||||
return redirect()->back()->with('error', '메뉴 종류를 선택하세요.');
|
||||
}
|
||||
if ($mmName === '') {
|
||||
return redirect()->back()->with('error', '메뉴명을 입력하세요.');
|
||||
}
|
||||
$mmNum = $this->menuModel->getNextNum($mtIdx, $lgIdx, $mmPidx, $mmDep);
|
||||
$data = [
|
||||
'mt_idx' => $mtIdx,
|
||||
'lg_idx' => $lgIdx,
|
||||
'mm_name' => $mmName,
|
||||
'mm_link' => (string) $this->request->getPost('mm_link'),
|
||||
'mm_pidx' => $mmPidx,
|
||||
'mm_dep' => $mmDep,
|
||||
'mm_num' => $mmNum,
|
||||
'mm_cnode' => 0,
|
||||
'mm_level' => $this->normalizeMmLevel($mtIdx),
|
||||
'mm_is_view' => $this->request->getPost('mm_is_view') ? 'Y' : 'N',
|
||||
];
|
||||
$this->menuModel->insert($data);
|
||||
if ($mmPidx > 0) {
|
||||
$this->menuModel->updateCnode($mmPidx, 1);
|
||||
}
|
||||
return redirect()->back()->with('success', '메뉴가 등록되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 수정 (현재 지자체 소속 메뉴만 허용)
|
||||
*/
|
||||
public function update(int $id)
|
||||
{
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
->with('error', '지자체를 선택하세요.');
|
||||
}
|
||||
$row = $this->menuModel->find($id);
|
||||
if (! $row) {
|
||||
return redirect()->back()->with('error', '메뉴를 찾을 수 없습니다.');
|
||||
}
|
||||
if ((int) $row->lg_idx !== $lgIdx) {
|
||||
return redirect()->back()->with('error', '해당 지자체의 메뉴만 수정할 수 있습니다.');
|
||||
}
|
||||
$data = [
|
||||
'mm_name' => (string) $this->request->getPost('mm_name'),
|
||||
'mm_link' => (string) $this->request->getPost('mm_link'),
|
||||
'mm_level' => $this->normalizeMmLevel((int) $row->mt_idx),
|
||||
'mm_is_view' => $this->request->getPost('mm_is_view') ? 'Y' : 'N',
|
||||
];
|
||||
$this->menuModel->update($id, $data);
|
||||
return redirect()->back()->with('success', '메뉴가 수정되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 삭제 (현재 지자체 소속만 허용, 하위 있으면 불가)
|
||||
*/
|
||||
public function delete(int $id)
|
||||
{
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
->with('error', '지자체를 선택하세요.');
|
||||
}
|
||||
$row = $this->menuModel->find($id);
|
||||
if (! $row || (int) $row->lg_idx !== $lgIdx) {
|
||||
return redirect()->back()->with('error', '해당 지자체의 메뉴만 삭제할 수 있습니다.');
|
||||
}
|
||||
$result = $this->menuModel->deleteSafe($id);
|
||||
if ($result['ok']) {
|
||||
return redirect()->back()->with('success', '메뉴가 삭제되었습니다.');
|
||||
}
|
||||
return redirect()->back()->with('error', $result['msg']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 순서 변경 (mm_idx[] 순서대로 mm_num 부여). 현재 지자체 메뉴만.
|
||||
*/
|
||||
public function move()
|
||||
{
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
->with('error', '지자체를 선택하세요.');
|
||||
}
|
||||
$ids = $this->request->getPost('mm_idx');
|
||||
if (! is_array($ids) || empty($ids)) {
|
||||
return redirect()->back()->with('error', '순서를 적용할 메뉴가 없습니다.');
|
||||
}
|
||||
$this->menuModel->setOrder($ids, $lgIdx);
|
||||
return redirect()->back()->with('success', '순서가 적용되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 노출 대상: 전체(mm_level_all)이면 빈 문자열, 아니면 선택한 레벨을 쉼표 구분 문자열로
|
||||
*/
|
||||
private function normalizeMmLevel(int $mtIdx): string
|
||||
{
|
||||
// 관리자 메뉴(admin)는 시민/판매소 노출을 허용하지 않음 → 지자체관리자(3)로 고정
|
||||
$type = $this->typeModel->find($mtIdx);
|
||||
if ($type && (string) $type->mt_code === 'admin') {
|
||||
return (string) Roles::LEVEL_LOCAL_ADMIN;
|
||||
}
|
||||
if ($this->request->getPost('mm_level_all')) {
|
||||
return '';
|
||||
}
|
||||
$levels = $this->request->getPost('mm_level');
|
||||
if (! is_array($levels) || empty($levels)) {
|
||||
return '';
|
||||
}
|
||||
$levels = array_map('intval', $levels);
|
||||
// super admin(4)은 DB 저장 대상 아님. 1,2,3은 그대로 저장
|
||||
$levels = array_filter($levels, static fn ($v) => $v > 0 && $v !== \Config\Roles::LEVEL_SUPER_ADMIN);
|
||||
|
||||
return implode(',', array_values($levels));
|
||||
}
|
||||
}
|
||||
22
app/Controllers/Admin/Role.php
Normal file
22
app/Controllers/Admin/Role.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use Config\Roles;
|
||||
|
||||
/**
|
||||
* 역할(mb_level) 관리.
|
||||
* 현재는 Config\Roles 기반 목록만 제공. 추후 role 테이블 연동 시 CRUD 확장.
|
||||
*/
|
||||
class Role extends BaseController
|
||||
{
|
||||
public function index(): string
|
||||
{
|
||||
$roles = config('Roles');
|
||||
return view('admin/layout', [
|
||||
'title' => '역할',
|
||||
'content' => view('admin/role/index', ['roles' => $roles]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
58
app/Controllers/Admin/SelectLocalGovernment.php
Normal file
58
app/Controllers/Admin/SelectLocalGovernment.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\LocalGovernmentModel;
|
||||
use Config\Roles;
|
||||
|
||||
class SelectLocalGovernment extends BaseController
|
||||
{
|
||||
/**
|
||||
* 지자체 선택 화면 (super admin 전용)
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ((int) session()->get('mb_level') !== Roles::LEVEL_SUPER_ADMIN) {
|
||||
return redirect()->to(site_url('admin'))->with('error', '지자체 선택은 super admin만 사용할 수 있습니다.');
|
||||
}
|
||||
|
||||
$list = model(LocalGovernmentModel::class)
|
||||
->where('lg_state', 1)
|
||||
->orderBy('lg_name', 'ASC')
|
||||
->findAll();
|
||||
|
||||
return view('admin/layout', [
|
||||
'title' => '지자체 선택',
|
||||
'content' => view('admin/select_local_government/index', [
|
||||
'list' => $list,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택 처리: admin_selected_lg_idx 저장 후 관리자 대시보드로 이동
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
if ((int) session()->get('mb_level') !== Roles::LEVEL_SUPER_ADMIN) {
|
||||
return redirect()->to(site_url('admin'))->with('error', '지자체 선택은 super admin만 사용할 수 있습니다.');
|
||||
}
|
||||
|
||||
$lgIdx = (int) $this->request->getPost('lg_idx');
|
||||
if ($lgIdx <= 0) {
|
||||
return redirect()->back()
|
||||
->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$exists = model(LocalGovernmentModel::class)->find($lgIdx);
|
||||
if ($exists === null) {
|
||||
return redirect()->back()
|
||||
->with('error', '선택한 지자체를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
session()->set('admin_selected_lg_idx', $lgIdx);
|
||||
|
||||
return redirect()->to(site_url('admin'))->with('success', $exists->lg_name . ' 지자체로 전환되었습니다.');
|
||||
}
|
||||
}
|
||||
210
app/Controllers/Admin/User.php
Normal file
210
app/Controllers/Admin/User.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\MemberApprovalRequestModel;
|
||||
use App\Models\MemberModel;
|
||||
use Config\Roles;
|
||||
|
||||
class User extends BaseController
|
||||
{
|
||||
private MemberModel $memberModel;
|
||||
private MemberApprovalRequestModel $approvalModel;
|
||||
private Roles $roles;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->memberModel = model(MemberModel::class);
|
||||
$this->approvalModel = model(MemberApprovalRequestModel::class);
|
||||
$this->roles = config('Roles');
|
||||
helper('pii_encryption');
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 목록
|
||||
*/
|
||||
public function index(): string
|
||||
{
|
||||
$list = $this->memberModel->orderBy('mb_idx', 'DESC')->findAll();
|
||||
$approvalMap = [];
|
||||
try {
|
||||
$memberIds = array_map(static fn ($row) => (int) $row->mb_idx, $list);
|
||||
if (! empty($memberIds)) {
|
||||
$approvalRows = $this->approvalModel
|
||||
->whereIn('mb_idx', $memberIds)
|
||||
->orderBy('mar_idx', 'DESC')
|
||||
->findAll();
|
||||
foreach ($approvalRows as $approvalRow) {
|
||||
$mbIdx = (int) $approvalRow->mb_idx;
|
||||
if (! isset($approvalMap[$mbIdx])) {
|
||||
$approvalMap[$mbIdx] = (string) $approvalRow->mar_status;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 승인요청 테이블 미생성 등 예외 시 기존 상태 표시로 폴백
|
||||
}
|
||||
|
||||
foreach ($list as $row) {
|
||||
$row->mb_email = pii_decrypt($row->mb_email ?? '');
|
||||
$row->mb_phone = pii_decrypt($row->mb_phone ?? '');
|
||||
}
|
||||
return view('admin/layout', [
|
||||
'title' => '회원 관리',
|
||||
'content' => view('admin/user/index', [
|
||||
'list' => $list,
|
||||
'roles' => $this->roles,
|
||||
'approvalMap' => $approvalMap,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 등록 폼
|
||||
*/
|
||||
public function create(): string
|
||||
{
|
||||
return view('admin/layout', [
|
||||
'title' => '회원 등록',
|
||||
'content' => view('admin/user/create', [
|
||||
'roles' => $this->roles,
|
||||
'assignableLevels' => $this->getAssignableLevels(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 등록 처리
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
$rules = [
|
||||
'mb_id' => 'required|min_length[2]|max_length[50]|is_unique[member.mb_id]',
|
||||
'mb_passwd' => 'required|min_length[4]|max_length[255]',
|
||||
'mb_name' => 'required|max_length[50]',
|
||||
'mb_email' => 'permit_empty|valid_email|max_length[100]',
|
||||
'mb_phone' => 'permit_empty|max_length[20]',
|
||||
'mb_level' => 'required',
|
||||
];
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
$allowedLevels = array_keys($this->getAssignableLevels());
|
||||
$requestedLevel = (int) $this->request->getPost('mb_level');
|
||||
if (! in_array($requestedLevel, $allowedLevels, true)) {
|
||||
return redirect()->back()->withInput()->with('error', '현재 권한으로는 해당 역할을 등록할 수 없습니다.');
|
||||
}
|
||||
$data = [
|
||||
'mb_id' => $this->request->getPost('mb_id'),
|
||||
'mb_passwd' => password_hash((string) $this->request->getPost('mb_passwd'), PASSWORD_DEFAULT),
|
||||
'mb_name' => $this->request->getPost('mb_name'),
|
||||
'mb_email' => pii_encrypt($this->request->getPost('mb_email')),
|
||||
'mb_phone' => pii_encrypt($this->request->getPost('mb_phone')),
|
||||
'mb_level' => $requestedLevel,
|
||||
'mb_state' => 1,
|
||||
'mb_regdate' => date('Y-m-d H:i:s'),
|
||||
'mb_latestdate' => null,
|
||||
];
|
||||
$this->memberModel->insert($data);
|
||||
return redirect()->to(site_url('admin/users'))->with('success', '회원이 등록되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 수정 폼
|
||||
*/
|
||||
public function edit(int $id): string
|
||||
{
|
||||
$member = $this->memberModel->find($id);
|
||||
if (! $member) {
|
||||
return redirect()->to(site_url('admin/users'))->with('error', '회원을 찾을 수 없습니다.');
|
||||
}
|
||||
$member->mb_email = pii_decrypt($member->mb_email ?? '');
|
||||
$member->mb_phone = pii_decrypt($member->mb_phone ?? '');
|
||||
return view('admin/layout', [
|
||||
'title' => '회원 수정',
|
||||
'content' => view('admin/user/edit', [
|
||||
'member' => $member,
|
||||
'roles' => $this->roles,
|
||||
'assignableLevels' => $this->getAssignableLevels(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 수정 처리
|
||||
*/
|
||||
public function update(int $id)
|
||||
{
|
||||
$member = $this->memberModel->find($id);
|
||||
if (! $member) {
|
||||
return redirect()->to(site_url('admin/users'))->with('error', '회원을 찾을 수 없습니다.');
|
||||
}
|
||||
$rules = [
|
||||
'mb_id' => "required|min_length[2]|max_length[50]|is_unique[member.mb_id,mb_idx,{$id}]",
|
||||
'mb_name' => 'required|max_length[50]',
|
||||
'mb_email' => 'permit_empty|valid_email|max_length[100]',
|
||||
'mb_phone' => 'permit_empty|max_length[20]',
|
||||
'mb_level' => 'required',
|
||||
'mb_state' => 'required|in_list[0,1,2]',
|
||||
];
|
||||
$passwd = $this->request->getPost('mb_passwd');
|
||||
if ($passwd !== '') {
|
||||
$rules['mb_passwd'] = 'min_length[4]|max_length[255]';
|
||||
}
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
$allowedLevels = array_keys($this->getAssignableLevels());
|
||||
$requestedLevel = (int) $this->request->getPost('mb_level');
|
||||
if (! in_array($requestedLevel, $allowedLevels, true)) {
|
||||
return redirect()->back()->withInput()->with('error', '현재 권한으로는 해당 역할로 수정할 수 없습니다.');
|
||||
}
|
||||
$data = [
|
||||
'mb_id' => $this->request->getPost('mb_id'),
|
||||
'mb_name' => $this->request->getPost('mb_name'),
|
||||
'mb_email' => pii_encrypt($this->request->getPost('mb_email')),
|
||||
'mb_phone' => pii_encrypt($this->request->getPost('mb_phone')),
|
||||
'mb_level' => $requestedLevel,
|
||||
'mb_state' => (int) $this->request->getPost('mb_state'),
|
||||
];
|
||||
if ($passwd !== '') {
|
||||
$data['mb_passwd'] = password_hash($passwd, PASSWORD_DEFAULT);
|
||||
}
|
||||
$this->memberModel->update($id, $data);
|
||||
return redirect()->to(site_url('admin/users'))->with('success', '회원 정보가 수정되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 로그인한 관리자가 부여 가능한 역할 목록.
|
||||
* super admin만 super admin(4) 부여 가능, 그 외는 1~3만 허용.
|
||||
*
|
||||
* @return array<int,string>
|
||||
*/
|
||||
private function getAssignableLevels(): array
|
||||
{
|
||||
$levelNames = $this->roles->levelNames;
|
||||
$myLevel = (int) session()->get('mb_level');
|
||||
if ($myLevel === Roles::LEVEL_SUPER_ADMIN) {
|
||||
return $levelNames;
|
||||
}
|
||||
unset($levelNames[Roles::LEVEL_SUPER_ADMIN]);
|
||||
return $levelNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 삭제(탈퇴 처리: mb_state=0, mb_leavedate 기록)
|
||||
*/
|
||||
public function delete(int $id)
|
||||
{
|
||||
$member = $this->memberModel->find($id);
|
||||
if (! $member) {
|
||||
return redirect()->to(site_url('admin/users'))->with('error', '회원을 찾을 수 없습니다.');
|
||||
}
|
||||
$this->memberModel->update($id, [
|
||||
'mb_state' => 0,
|
||||
'mb_leavedate' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return redirect()->to(site_url('admin/users'))->with('success', '회원이 탈퇴 처리되었습니다.');
|
||||
}
|
||||
}
|
||||
300
app/Controllers/Auth.php
Normal file
300
app/Controllers/Auth.php
Normal file
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\LocalGovernmentModel;
|
||||
use App\Models\MemberApprovalRequestModel;
|
||||
use App\Models\MemberLogModel;
|
||||
use App\Models\MemberModel;
|
||||
|
||||
class Auth extends BaseController
|
||||
{
|
||||
/** mb_state: 1=정상, 2=정지, 0=탈퇴 */
|
||||
private const MB_STATE_NORMAL = 1;
|
||||
private const MB_STATE_BANNED = 2;
|
||||
private const MB_STATE_LEAVE = 0;
|
||||
|
||||
public function showLoginForm()
|
||||
{
|
||||
if (session()->get('logged_in')) {
|
||||
return redirect()->to('/');
|
||||
}
|
||||
|
||||
return view('auth/login');
|
||||
}
|
||||
|
||||
public function login()
|
||||
{
|
||||
$rules = [
|
||||
'login_id' => 'required|max_length[50]',
|
||||
'password' => 'required|max_length[255]',
|
||||
];
|
||||
|
||||
$messages = [
|
||||
'login_id' => [
|
||||
'required' => '아이디를 입력해 주세요.',
|
||||
'max_length' => '아이디는 50자 이하여야 합니다.',
|
||||
],
|
||||
'password' => [
|
||||
'required' => '비밀번호를 입력해 주세요.',
|
||||
'max_length' => '비밀번호는 255자 이하여야 합니다.',
|
||||
],
|
||||
];
|
||||
|
||||
if (! $this->validate($rules, $messages)) {
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$loginId = trim($this->request->getPost('login_id'));
|
||||
$password = $this->request->getPost('password');
|
||||
|
||||
$memberModel = model(MemberModel::class);
|
||||
$member = $memberModel->findByLoginId($loginId);
|
||||
$approvalModel = model(MemberApprovalRequestModel::class);
|
||||
|
||||
$logData = $this->buildLogData($loginId, $member?->mb_idx);
|
||||
|
||||
if ($member === null) {
|
||||
$this->insertMemberLog($logData, false, '회원 정보를 찾을 수 없습니다.');
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', '아이디 또는 비밀번호가 올바르지 않습니다.');
|
||||
}
|
||||
|
||||
if ($member->mb_state === self::MB_STATE_LEAVE) {
|
||||
$this->insertMemberLog($logData, false, '탈퇴한 회원입니다.');
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', '탈퇴한 회원입니다.');
|
||||
}
|
||||
|
||||
if ($member->mb_state === self::MB_STATE_BANNED) {
|
||||
$this->insertMemberLog($logData, false, '정지된 회원입니다.');
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', '정지된 회원입니다.');
|
||||
}
|
||||
|
||||
if (! password_verify($password, $member->mb_passwd)) {
|
||||
$this->insertMemberLog($logData, false, '비밀번호 불일치');
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', '아이디 또는 비밀번호가 올바르지 않습니다.');
|
||||
}
|
||||
|
||||
// 승인 요청 상태 확인(공개 회원가입 사용자)
|
||||
$latestApproval = $approvalModel->getLatestByMember((int) $member->mb_idx);
|
||||
if ($latestApproval !== null) {
|
||||
if ($latestApproval->mar_status === MemberApprovalRequestModel::STATUS_PENDING) {
|
||||
$this->insertMemberLog($logData, false, '승인 대기 상태');
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', '관리자 승인 후 로그인 가능합니다.');
|
||||
}
|
||||
if ($latestApproval->mar_status === MemberApprovalRequestModel::STATUS_REJECTED) {
|
||||
$this->insertMemberLog($logData, false, '승인 반려 상태');
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', '승인이 반려되었습니다. 관리자에게 문의해 주세요.');
|
||||
}
|
||||
}
|
||||
|
||||
// 로그인 성공
|
||||
$sessionData = [
|
||||
'mb_idx' => $member->mb_idx,
|
||||
'mb_id' => $member->mb_id,
|
||||
'mb_name' => $member->mb_name,
|
||||
'mb_level' => $member->mb_level,
|
||||
'mb_lg_idx' => $member->mb_lg_idx ?? null,
|
||||
'logged_in' => true,
|
||||
];
|
||||
session()->set($sessionData);
|
||||
|
||||
$memberModel->update($member->mb_idx, [
|
||||
'mb_latestdate' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
$this->insertMemberLog($logData, true, '로그인 성공', $member->mb_idx);
|
||||
|
||||
// 지자체 관리자 → 관리자 대시보드로 이동
|
||||
if ((int) $member->mb_level === \Config\Roles::LEVEL_LOCAL_ADMIN) {
|
||||
return redirect()->to(site_url('admin'))->with('success', '로그인되었습니다.');
|
||||
}
|
||||
// super admin → 지자체 선택 페이지로 이동 (선택 후 관리자 페이지 사용)
|
||||
if ((int) $member->mb_level === \Config\Roles::LEVEL_SUPER_ADMIN) {
|
||||
return redirect()->to(site_url('admin/select-local-government'))->with('success', '로그인되었습니다.');
|
||||
}
|
||||
|
||||
return redirect()->to(site_url('/'))->with('success', '로그인되었습니다.');
|
||||
}
|
||||
|
||||
public function logout()
|
||||
{
|
||||
if (session()->get('logged_in')) {
|
||||
$mbIdx = session()->get('mb_idx');
|
||||
$mbId = session()->get('mb_id');
|
||||
$log = model(MemberLogModel::class)
|
||||
->where('mb_idx', $mbIdx)
|
||||
->where('mll_success', 1)
|
||||
->orderBy('mll_idx', 'DESC')
|
||||
->first();
|
||||
if ($log !== null) {
|
||||
model(MemberLogModel::class)->update($log->mll_idx, [
|
||||
'mll_logout_date' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
} else {
|
||||
model(MemberLogModel::class)->insert([
|
||||
'mll_success' => 1,
|
||||
'mb_idx' => $mbIdx,
|
||||
'mb_id' => $mbId ?? '',
|
||||
'mll_regdate' => date('Y-m-d H:i:s'),
|
||||
'mll_ip' => $this->request->getIPAddress(),
|
||||
'mll_msg' => '로그아웃',
|
||||
'mll_useragent' => substr((string) $this->request->getUserAgent(), 0, 500),
|
||||
'mll_logout_date' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
session()->destroy();
|
||||
|
||||
return redirect()->to('login')->with('success', '로그아웃되었습니다.');
|
||||
}
|
||||
|
||||
public function showRegisterForm()
|
||||
{
|
||||
$localGovernments = model(LocalGovernmentModel::class)
|
||||
->where('lg_state', 1)
|
||||
->orderBy('lg_name', 'ASC')
|
||||
->findAll();
|
||||
|
||||
return view('auth/register', [
|
||||
'localGovernments' => $localGovernments,
|
||||
]);
|
||||
}
|
||||
|
||||
public function register()
|
||||
{
|
||||
$rules = [
|
||||
'mb_id' => 'required|min_length[4]|max_length[50]|is_unique[member.mb_id]',
|
||||
'mb_passwd' => 'required|min_length[4]|max_length[255]',
|
||||
'mb_passwd_confirm' => 'required|matches[mb_passwd]',
|
||||
'mb_name' => 'required|max_length[100]',
|
||||
'mb_email' => 'permit_empty|valid_email|max_length[100]',
|
||||
'mb_phone' => 'permit_empty|max_length[20]',
|
||||
'mb_lg_idx' => 'permit_empty|is_natural_no_zero',
|
||||
'mb_level' => 'required|in_list[1,2,3]',
|
||||
];
|
||||
|
||||
$messages = [
|
||||
'mb_id' => [
|
||||
'required' => '아이디를 입력해 주세요.',
|
||||
'min_length' => '아이디는 4자 이상이어야 합니다.',
|
||||
'max_length' => '아이디는 50자 이하여야 합니다.',
|
||||
'is_unique' => '이미 사용 중인 아이디입니다.',
|
||||
],
|
||||
'mb_passwd' => [
|
||||
'required' => '비밀번호를 입력해 주세요.',
|
||||
'min_length' => '비밀번호는 4자 이상이어야 합니다.',
|
||||
'max_length' => '비밀번호는 255자 이하여야 합니다.',
|
||||
],
|
||||
'mb_passwd_confirm' => [
|
||||
'required' => '비밀번호 확인을 입력해 주세요.',
|
||||
'matches' => '비밀번호가 일치하지 않습니다.',
|
||||
],
|
||||
'mb_name' => [
|
||||
'required' => '이름을 입력해 주세요.',
|
||||
'max_length' => '이름은 100자 이하여야 합니다.',
|
||||
],
|
||||
'mb_email' => [
|
||||
'valid_email' => '올바른 이메일 형식이 아닙니다.',
|
||||
'max_length' => '이메일은 100자 이하여야 합니다.',
|
||||
],
|
||||
'mb_phone' => [
|
||||
'max_length' => '연락처는 20자 이하여야 합니다.',
|
||||
],
|
||||
'mb_level' => [
|
||||
'required' => '사용자 역할을 선택해 주세요.',
|
||||
'in_list' => '유효하지 않은 역할입니다.',
|
||||
],
|
||||
];
|
||||
|
||||
if (! $this->validate($rules, $messages)) {
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$mbLevel = (int) $this->request->getPost('mb_level');
|
||||
if (! config('Roles')->isValidLevel($mbLevel)) {
|
||||
$mbLevel = config('Roles')->defaultLevelForSelfRegister;
|
||||
}
|
||||
|
||||
$lgIdx = $this->request->getPost('mb_lg_idx');
|
||||
$mbLgIdx = ($lgIdx !== null && $lgIdx !== '' && (int) $lgIdx > 0) ? (int) $lgIdx : null;
|
||||
|
||||
helper('pii_encryption');
|
||||
$data = [
|
||||
'mb_id' => $this->request->getPost('mb_id'),
|
||||
'mb_passwd' => password_hash($this->request->getPost('mb_passwd'), PASSWORD_DEFAULT),
|
||||
'mb_name' => $this->request->getPost('mb_name'),
|
||||
'mb_email' => pii_encrypt($this->request->getPost('mb_email') ?? ''),
|
||||
'mb_phone' => pii_encrypt($this->request->getPost('mb_phone') ?? ''),
|
||||
'mb_lang' => 'ko',
|
||||
// 공개 회원가입 시점에는 역할을 활성화하지 않고 기본 레벨로 생성(승인 후 requested_level 반영)
|
||||
'mb_level' => \Config\Roles::LEVEL_CITIZEN,
|
||||
'mb_group' => '',
|
||||
'mb_lg_idx' => $mbLgIdx,
|
||||
'mb_state' => 1,
|
||||
'mb_regdate' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
$memberModel = model(MemberModel::class);
|
||||
if (! $memberModel->insert($data)) {
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('error', '회원가입 처리 중 오류가 발생했습니다. 다시 시도해 주세요.');
|
||||
}
|
||||
$newMemberIdx = (int) $memberModel->getInsertID();
|
||||
$approvalModel = model(MemberApprovalRequestModel::class);
|
||||
$approvalModel->insert([
|
||||
'mb_idx' => $newMemberIdx,
|
||||
'mar_requested_level' => $mbLevel,
|
||||
'mar_status' => MemberApprovalRequestModel::STATUS_PENDING,
|
||||
'mar_request_note' => '',
|
||||
'mar_reject_reason' => null,
|
||||
'mar_requested_at' => date('Y-m-d H:i:s'),
|
||||
'mar_requested_by' => $newMemberIdx,
|
||||
'mar_processed_at' => null,
|
||||
'mar_processed_by' => null,
|
||||
]);
|
||||
|
||||
return redirect()->to('login')->with('success', '회원가입이 완료되었습니다. 관리자 승인 후 로그인 가능합니다.');
|
||||
}
|
||||
|
||||
private function buildLogData(string $mbId, ?int $mbIdx): array
|
||||
{
|
||||
return [
|
||||
'mb_idx' => $mbIdx,
|
||||
'mb_id' => $mbId,
|
||||
'mll_regdate' => date('Y-m-d H:i:s'),
|
||||
'mll_ip' => $this->request->getIPAddress(),
|
||||
'mll_useragent' => substr((string) $this->request->getUserAgent(), 0, 500),
|
||||
'mll_url' => current_url(),
|
||||
'mll_referer' => $this->request->getServer('HTTP_REFERER'),
|
||||
];
|
||||
}
|
||||
|
||||
private function insertMemberLog(array $data, bool $success, string $msg, ?int $mbIdx = null): void
|
||||
{
|
||||
$data['mll_success'] = $success ? 1 : 0;
|
||||
$data['mll_msg'] = $msg;
|
||||
if ($mbIdx !== null) {
|
||||
$data['mb_idx'] = $mbIdx;
|
||||
}
|
||||
|
||||
model(MemberLogModel::class)->insert($data);
|
||||
}
|
||||
}
|
||||
45
app/Controllers/BaseController.php
Normal file
45
app/Controllers/BaseController.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* BaseController provides a convenient place for loading components
|
||||
* and performing functions that are needed by all your controllers.
|
||||
*
|
||||
* Extend this class in any new controllers:
|
||||
* ```
|
||||
* class Home extends BaseController
|
||||
* ```
|
||||
*
|
||||
* For security, be sure to declare any new methods as protected or private.
|
||||
*/
|
||||
abstract class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* Be sure to declare properties for any property fetch you initialized.
|
||||
* The creation of dynamic property is deprecated in PHP 8.2.
|
||||
*/
|
||||
|
||||
// protected $session;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
// Load here all helpers you want to be available in your controllers that extend BaseController.
|
||||
// Caution: Do not put the this below the parent::initController() call below.
|
||||
// $this->helpers = ['form', 'url'];
|
||||
|
||||
// Caution: Do not edit this line.
|
||||
parent::initController($request, $response, $logger);
|
||||
|
||||
// Preload any models, libraries, etc, here.
|
||||
// $this->session = service('session');
|
||||
}
|
||||
}
|
||||
107
app/Controllers/Home.php
Normal file
107
app/Controllers/Home.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\LocalGovernmentModel;
|
||||
|
||||
class Home extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
if (session()->get('logged_in')) {
|
||||
return $this->dashboard();
|
||||
}
|
||||
|
||||
return view('welcome_message');
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 후 원래 메인 화면 (admin 유사 레이아웃 + site 메뉴 호버 드롭다운)
|
||||
*/
|
||||
public function dashboard()
|
||||
{
|
||||
return view('bag/daily_inventory');
|
||||
}
|
||||
|
||||
/**
|
||||
* 디자인 시안(기존 /dashboard 연결 화면)
|
||||
*/
|
||||
public function dashboardClassicMock()
|
||||
{
|
||||
return $this->renderDashboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 후 메인 — 모던형(세로 사이드바) 레이아웃. URL: /dashboard/modern
|
||||
*/
|
||||
public function dashboardModern()
|
||||
{
|
||||
return view('bag/lg_dashboard_modern', [
|
||||
'lgLabel' => $this->resolveLgLabel(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 후 메인 — 정보 집약형 종합 현황. URL: /dashboard/dense
|
||||
*/
|
||||
public function dashboardDense()
|
||||
{
|
||||
return view('bag/lg_dashboard_dense', [
|
||||
'lgLabel' => $this->resolveLgLabel(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 후 메인 — 그래프 중심(Chart.js). URL: /dashboard/charts
|
||||
*/
|
||||
public function dashboardCharts()
|
||||
{
|
||||
return view('bag/lg_dashboard_charts', [
|
||||
'lgLabel' => $this->resolveLgLabel(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 재고 조회(수불) 화면 (목업)
|
||||
*/
|
||||
public function inventoryInquiry()
|
||||
{
|
||||
return view('bag/inventory_inquiry');
|
||||
}
|
||||
|
||||
/**
|
||||
* 종량제 수불 그리드 (엔터프라이즈형 목업, 상단 가로 메뉴 + 병합 헤더 표)
|
||||
*/
|
||||
public function wasteSuibalEnterprise()
|
||||
{
|
||||
return view('bag/waste_suibal_enterprise');
|
||||
}
|
||||
|
||||
protected function renderDashboard()
|
||||
{
|
||||
return view('bag/lg_dashboard', [
|
||||
'lgLabel' => $this->resolveLgLabel(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 세션 mb_lg_idx 기준 지자체명 (DB 없거나 실패 시 데모용 문구)
|
||||
*/
|
||||
protected function resolveLgLabel(): string
|
||||
{
|
||||
try {
|
||||
$idx = session()->get('mb_lg_idx');
|
||||
if ($idx === null || $idx === '') {
|
||||
return '로그인 지자체 (미지정)';
|
||||
}
|
||||
$row = model(LocalGovernmentModel::class)->find((int) $idx);
|
||||
if ($row && isset($row->lg_name) && $row->lg_name !== '') {
|
||||
return (string) $row->lg_name;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 테이블 미생성 등
|
||||
}
|
||||
|
||||
return '북구 (데모)';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user