diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index 16440e9..4d61127 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -83,6 +83,38 @@ $routes->group('admin', ['filter' => 'adminAuth'], static function ($routes): vo
$routes->post('packaging-units/delete/(:num)', 'Admin\PackagingUnit::delete/$1');
$routes->get('packaging-units/history/(:num)', 'Admin\PackagingUnit::history/$1');
+ // 판매 대행소 관리 (P2-07/08)
+ $routes->get('sales-agencies', 'Admin\SalesAgency::index');
+ $routes->get('sales-agencies/create', 'Admin\SalesAgency::create');
+ $routes->post('sales-agencies/store', 'Admin\SalesAgency::store');
+ $routes->get('sales-agencies/edit/(:num)', 'Admin\SalesAgency::edit/$1');
+ $routes->post('sales-agencies/update/(:num)', 'Admin\SalesAgency::update/$1');
+ $routes->post('sales-agencies/delete/(:num)', 'Admin\SalesAgency::delete/$1');
+
+ // 담당자 관리 (P2-09/10)
+ $routes->get('managers', 'Admin\Manager::index');
+ $routes->get('managers/create', 'Admin\Manager::create');
+ $routes->post('managers/store', 'Admin\Manager::store');
+ $routes->get('managers/edit/(:num)', 'Admin\Manager::edit/$1');
+ $routes->post('managers/update/(:num)', 'Admin\Manager::update/$1');
+ $routes->post('managers/delete/(:num)', 'Admin\Manager::delete/$1');
+
+ // 업체 관리 (P2-11/12)
+ $routes->get('companies', 'Admin\Company::index');
+ $routes->get('companies/create', 'Admin\Company::create');
+ $routes->post('companies/store', 'Admin\Company::store');
+ $routes->get('companies/edit/(:num)', 'Admin\Company::edit/$1');
+ $routes->post('companies/update/(:num)', 'Admin\Company::update/$1');
+ $routes->post('companies/delete/(:num)', 'Admin\Company::delete/$1');
+
+ // 무료용 대상자 관리 (P2-13/14)
+ $routes->get('free-recipients', 'Admin\FreeRecipient::index');
+ $routes->get('free-recipients/create', 'Admin\FreeRecipient::create');
+ $routes->post('free-recipients/store', 'Admin\FreeRecipient::store');
+ $routes->get('free-recipients/edit/(:num)', 'Admin\FreeRecipient::edit/$1');
+ $routes->post('free-recipients/update/(:num)', 'Admin\FreeRecipient::update/$1');
+ $routes->post('free-recipients/delete/(:num)', 'Admin\FreeRecipient::delete/$1');
+
$routes->get('designated-shops', 'Admin\DesignatedShop::index');
$routes->get('designated-shops/create', 'Admin\DesignatedShop::create');
$routes->post('designated-shops/store', 'Admin\DesignatedShop::store');
diff --git a/app/Controllers/Admin/Company.php b/app/Controllers/Admin/Company.php
new file mode 100644
index 0000000..3321fb2
--- /dev/null
+++ b/app/Controllers/Admin/Company.php
@@ -0,0 +1,126 @@
+model = model(CompanyModel::class);
+ }
+
+ public function index()
+ {
+ helper('admin');
+ $lgIdx = admin_effective_lg_idx();
+ if (!$lgIdx) {
+ return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
+ }
+
+ $list = $this->model->where('cp_lg_idx', $lgIdx)->orderBy('cp_idx', 'DESC')->findAll();
+
+ return view('admin/layout', [
+ 'title' => '업체 관리',
+ 'content' => view('admin/company/index', ['list' => $list]),
+ ]);
+ }
+
+ public function create()
+ {
+ return view('admin/layout', [
+ 'title' => '업체 등록',
+ 'content' => view('admin/company/create'),
+ ]);
+ }
+
+ public function store()
+ {
+ helper('admin');
+ $rules = [
+ 'cp_type' => 'required|in_list[협회,제작업체,회수업체]',
+ 'cp_name' => 'required|max_length[100]',
+ 'cp_biz_no' => 'permit_empty|max_length[20]',
+ 'cp_rep_name' => 'permit_empty|max_length[50]',
+ 'cp_tel' => 'permit_empty|max_length[20]',
+ 'cp_addr' => 'permit_empty|max_length[255]',
+ ];
+ if (! $this->validate($rules)) {
+ return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
+ }
+
+ $this->model->insert([
+ 'cp_lg_idx' => admin_effective_lg_idx(),
+ 'cp_type' => $this->request->getPost('cp_type'),
+ 'cp_name' => $this->request->getPost('cp_name'),
+ 'cp_biz_no' => $this->request->getPost('cp_biz_no') ?? '',
+ 'cp_rep_name' => $this->request->getPost('cp_rep_name') ?? '',
+ 'cp_tel' => $this->request->getPost('cp_tel') ?? '',
+ 'cp_addr' => $this->request->getPost('cp_addr') ?? '',
+ 'cp_state' => 1,
+ 'cp_regdate' => date('Y-m-d H:i:s'),
+ ]);
+
+ return redirect()->to(site_url('admin/companies'))->with('success', '업체가 등록되었습니다.');
+ }
+
+ public function edit(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->cp_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/companies'))->with('error', '업체를 찾을 수 없습니다.');
+ }
+
+ return view('admin/layout', [
+ 'title' => '업체 수정',
+ 'content' => view('admin/company/edit', ['item' => $item]),
+ ]);
+ }
+
+ public function update(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->cp_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/companies'))->with('error', '업체를 찾을 수 없습니다.');
+ }
+
+ $rules = [
+ 'cp_type' => 'required|in_list[협회,제작업체,회수업체]',
+ 'cp_name' => 'required|max_length[100]',
+ 'cp_state' => 'required|in_list[0,1]',
+ ];
+ if (! $this->validate($rules)) {
+ return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
+ }
+
+ $this->model->update($id, [
+ 'cp_type' => $this->request->getPost('cp_type'),
+ 'cp_name' => $this->request->getPost('cp_name'),
+ 'cp_biz_no' => $this->request->getPost('cp_biz_no') ?? '',
+ 'cp_rep_name' => $this->request->getPost('cp_rep_name') ?? '',
+ 'cp_tel' => $this->request->getPost('cp_tel') ?? '',
+ 'cp_addr' => $this->request->getPost('cp_addr') ?? '',
+ 'cp_state' => (int) $this->request->getPost('cp_state'),
+ ]);
+
+ return redirect()->to(site_url('admin/companies'))->with('success', '업체가 수정되었습니다.');
+ }
+
+ public function delete(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->cp_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/companies'))->with('error', '업체를 찾을 수 없습니다.');
+ }
+
+ $this->model->delete($id);
+ return redirect()->to(site_url('admin/companies'))->with('success', '업체가 삭제되었습니다.');
+ }
+}
diff --git a/app/Controllers/Admin/FreeRecipient.php b/app/Controllers/Admin/FreeRecipient.php
new file mode 100644
index 0000000..3154485
--- /dev/null
+++ b/app/Controllers/Admin/FreeRecipient.php
@@ -0,0 +1,139 @@
+model = model(FreeRecipientModel::class);
+ }
+
+ private function getCodeOptions(string $ckCode): array
+ {
+ $kind = model(CodeKindModel::class)->where('ck_code', $ckCode)->first();
+ return $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true) : [];
+ }
+
+ public function index()
+ {
+ helper('admin');
+ $lgIdx = admin_effective_lg_idx();
+ if (!$lgIdx) {
+ return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
+ }
+
+ $list = $this->model->where('fr_lg_idx', $lgIdx)->orderBy('fr_idx', 'DESC')->findAll();
+
+ return view('admin/layout', [
+ 'title' => '무료용 대상자 관리',
+ 'content' => view('admin/free_recipient/index', ['list' => $list]),
+ ]);
+ }
+
+ public function create()
+ {
+ return view('admin/layout', [
+ 'title' => '무료용 대상자 등록',
+ 'content' => view('admin/free_recipient/create', [
+ 'typeCodes' => $this->getCodeOptions('H'),
+ 'dongCodes' => $this->getCodeOptions('D'),
+ ]),
+ ]);
+ }
+
+ public function store()
+ {
+ helper('admin');
+ $rules = [
+ 'fr_type_code' => 'required|max_length[20]',
+ 'fr_name' => 'required|max_length[100]',
+ 'fr_end_date' => 'permit_empty|valid_date[Y-m-d]',
+ ];
+ if (! $this->validate($rules)) {
+ return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
+ }
+
+ $this->model->insert([
+ 'fr_lg_idx' => admin_effective_lg_idx(),
+ 'fr_type_code' => $this->request->getPost('fr_type_code'),
+ 'fr_name' => $this->request->getPost('fr_name'),
+ 'fr_phone' => $this->request->getPost('fr_phone') ?? '',
+ 'fr_addr' => $this->request->getPost('fr_addr') ?? '',
+ 'fr_dong_code' => $this->request->getPost('fr_dong_code') ?? '',
+ 'fr_note' => $this->request->getPost('fr_note') ?? '',
+ 'fr_end_date' => $this->request->getPost('fr_end_date') ?: null,
+ 'fr_state' => 1,
+ 'fr_regdate' => date('Y-m-d H:i:s'),
+ ]);
+
+ return redirect()->to(site_url('admin/free-recipients'))->with('success', '무료용 대상자가 등록되었습니다.');
+ }
+
+ public function edit(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->fr_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/free-recipients'))->with('error', '대상자를 찾을 수 없습니다.');
+ }
+
+ return view('admin/layout', [
+ 'title' => '무료용 대상자 수정',
+ 'content' => view('admin/free_recipient/edit', [
+ 'item' => $item,
+ 'typeCodes' => $this->getCodeOptions('H'),
+ 'dongCodes' => $this->getCodeOptions('D'),
+ ]),
+ ]);
+ }
+
+ public function update(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->fr_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/free-recipients'))->with('error', '대상자를 찾을 수 없습니다.');
+ }
+
+ $rules = [
+ 'fr_name' => 'required|max_length[100]',
+ 'fr_state' => 'required|in_list[0,1]',
+ ];
+ if (! $this->validate($rules)) {
+ return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
+ }
+
+ $this->model->update($id, [
+ 'fr_type_code' => $this->request->getPost('fr_type_code') ?? $item->fr_type_code,
+ 'fr_name' => $this->request->getPost('fr_name'),
+ 'fr_phone' => $this->request->getPost('fr_phone') ?? '',
+ 'fr_addr' => $this->request->getPost('fr_addr') ?? '',
+ 'fr_dong_code' => $this->request->getPost('fr_dong_code') ?? '',
+ 'fr_note' => $this->request->getPost('fr_note') ?? '',
+ 'fr_end_date' => $this->request->getPost('fr_end_date') ?: null,
+ 'fr_state' => (int) $this->request->getPost('fr_state'),
+ ]);
+
+ return redirect()->to(site_url('admin/free-recipients'))->with('success', '무료용 대상자가 수정되었습니다.');
+ }
+
+ public function delete(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->fr_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/free-recipients'))->with('error', '대상자를 찾을 수 없습니다.');
+ }
+
+ $this->model->delete($id);
+ return redirect()->to(site_url('admin/free-recipients'))->with('success', '무료용 대상자가 삭제되었습니다.');
+ }
+}
diff --git a/app/Controllers/Admin/Manager.php b/app/Controllers/Admin/Manager.php
new file mode 100644
index 0000000..c14e5a6
--- /dev/null
+++ b/app/Controllers/Admin/Manager.php
@@ -0,0 +1,138 @@
+model = model(ManagerModel::class);
+ }
+
+ private function getCodeOptions(string $ckCode): array
+ {
+ $kind = model(CodeKindModel::class)->where('ck_code', $ckCode)->first();
+ return $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true) : [];
+ }
+
+ public function index()
+ {
+ helper('admin');
+ $lgIdx = admin_effective_lg_idx();
+ if (!$lgIdx) {
+ return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
+ }
+
+ $list = $this->model->where('mg_lg_idx', $lgIdx)->orderBy('mg_idx', 'DESC')->findAll();
+
+ return view('admin/layout', [
+ 'title' => '담당자 관리',
+ 'content' => view('admin/manager/index', ['list' => $list]),
+ ]);
+ }
+
+ public function create()
+ {
+ return view('admin/layout', [
+ 'title' => '담당자 등록',
+ 'content' => view('admin/manager/create', [
+ 'deptCodes' => $this->getCodeOptions('S'),
+ 'positionCodes' => $this->getCodeOptions('T'),
+ ]),
+ ]);
+ }
+
+ public function store()
+ {
+ helper('admin');
+ $rules = [
+ 'mg_name' => 'required|max_length[50]',
+ 'mg_tel' => 'permit_empty|max_length[20]',
+ 'mg_phone' => 'permit_empty|max_length[20]',
+ 'mg_email' => 'permit_empty|valid_email|max_length[100]',
+ ];
+ if (! $this->validate($rules)) {
+ return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
+ }
+
+ $this->model->insert([
+ 'mg_lg_idx' => admin_effective_lg_idx(),
+ 'mg_name' => $this->request->getPost('mg_name'),
+ 'mg_dept_code' => $this->request->getPost('mg_dept_code') ?? '',
+ 'mg_position_code' => $this->request->getPost('mg_position_code') ?? '',
+ 'mg_tel' => $this->request->getPost('mg_tel') ?? '',
+ 'mg_phone' => $this->request->getPost('mg_phone') ?? '',
+ 'mg_email' => $this->request->getPost('mg_email') ?? '',
+ 'mg_state' => 1,
+ 'mg_regdate' => date('Y-m-d H:i:s'),
+ ]);
+
+ return redirect()->to(site_url('admin/managers'))->with('success', '담당자가 등록되었습니다.');
+ }
+
+ public function edit(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->mg_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/managers'))->with('error', '담당자를 찾을 수 없습니다.');
+ }
+
+ return view('admin/layout', [
+ 'title' => '담당자 수정',
+ 'content' => view('admin/manager/edit', [
+ 'item' => $item,
+ 'deptCodes' => $this->getCodeOptions('S'),
+ 'positionCodes' => $this->getCodeOptions('T'),
+ ]),
+ ]);
+ }
+
+ public function update(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->mg_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/managers'))->with('error', '담당자를 찾을 수 없습니다.');
+ }
+
+ $rules = [
+ 'mg_name' => 'required|max_length[50]',
+ 'mg_state' => 'required|in_list[0,1]',
+ ];
+ if (! $this->validate($rules)) {
+ return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
+ }
+
+ $this->model->update($id, [
+ 'mg_name' => $this->request->getPost('mg_name'),
+ 'mg_dept_code' => $this->request->getPost('mg_dept_code') ?? '',
+ 'mg_position_code' => $this->request->getPost('mg_position_code') ?? '',
+ 'mg_tel' => $this->request->getPost('mg_tel') ?? '',
+ 'mg_phone' => $this->request->getPost('mg_phone') ?? '',
+ 'mg_email' => $this->request->getPost('mg_email') ?? '',
+ 'mg_state' => (int) $this->request->getPost('mg_state'),
+ ]);
+
+ return redirect()->to(site_url('admin/managers'))->with('success', '담당자가 수정되었습니다.');
+ }
+
+ public function delete(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->mg_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/managers'))->with('error', '담당자를 찾을 수 없습니다.');
+ }
+
+ $this->model->delete($id);
+ return redirect()->to(site_url('admin/managers'))->with('success', '담당자가 삭제되었습니다.');
+ }
+}
diff --git a/app/Controllers/Admin/SalesAgency.php b/app/Controllers/Admin/SalesAgency.php
new file mode 100644
index 0000000..6a336af
--- /dev/null
+++ b/app/Controllers/Admin/SalesAgency.php
@@ -0,0 +1,122 @@
+model = model(SalesAgencyModel::class);
+ }
+
+ public function index()
+ {
+ helper('admin');
+ $lgIdx = admin_effective_lg_idx();
+ if (!$lgIdx) {
+ return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
+ }
+
+ $list = $this->model->where('sa_lg_idx', $lgIdx)->orderBy('sa_idx', 'DESC')->findAll();
+
+ return view('admin/layout', [
+ 'title' => '판매 대행소 관리',
+ 'content' => view('admin/sales_agency/index', ['list' => $list]),
+ ]);
+ }
+
+ public function create()
+ {
+ return view('admin/layout', [
+ 'title' => '판매 대행소 등록',
+ 'content' => view('admin/sales_agency/create'),
+ ]);
+ }
+
+ public function store()
+ {
+ helper('admin');
+ $rules = [
+ 'sa_name' => 'required|max_length[100]',
+ 'sa_biz_no' => 'permit_empty|max_length[20]',
+ 'sa_rep_name' => 'permit_empty|max_length[50]',
+ 'sa_tel' => 'permit_empty|max_length[20]',
+ 'sa_addr' => 'permit_empty|max_length[255]',
+ ];
+ if (! $this->validate($rules)) {
+ return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
+ }
+
+ $this->model->insert([
+ 'sa_lg_idx' => admin_effective_lg_idx(),
+ 'sa_name' => $this->request->getPost('sa_name'),
+ 'sa_biz_no' => $this->request->getPost('sa_biz_no') ?? '',
+ 'sa_rep_name' => $this->request->getPost('sa_rep_name') ?? '',
+ 'sa_tel' => $this->request->getPost('sa_tel') ?? '',
+ 'sa_addr' => $this->request->getPost('sa_addr') ?? '',
+ 'sa_state' => 1,
+ 'sa_regdate' => date('Y-m-d H:i:s'),
+ ]);
+
+ return redirect()->to(site_url('admin/sales-agencies'))->with('success', '판매 대행소가 등록되었습니다.');
+ }
+
+ public function edit(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->sa_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/sales-agencies'))->with('error', '대행소를 찾을 수 없습니다.');
+ }
+
+ return view('admin/layout', [
+ 'title' => '판매 대행소 수정',
+ 'content' => view('admin/sales_agency/edit', ['item' => $item]),
+ ]);
+ }
+
+ public function update(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->sa_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/sales-agencies'))->with('error', '대행소를 찾을 수 없습니다.');
+ }
+
+ $rules = [
+ 'sa_name' => 'required|max_length[100]',
+ 'sa_state' => 'required|in_list[0,1]',
+ ];
+ if (! $this->validate($rules)) {
+ return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
+ }
+
+ $this->model->update($id, [
+ 'sa_name' => $this->request->getPost('sa_name'),
+ 'sa_biz_no' => $this->request->getPost('sa_biz_no') ?? '',
+ 'sa_rep_name' => $this->request->getPost('sa_rep_name') ?? '',
+ 'sa_tel' => $this->request->getPost('sa_tel') ?? '',
+ 'sa_addr' => $this->request->getPost('sa_addr') ?? '',
+ 'sa_state' => (int) $this->request->getPost('sa_state'),
+ ]);
+
+ return redirect()->to(site_url('admin/sales-agencies'))->with('success', '판매 대행소가 수정되었습니다.');
+ }
+
+ public function delete(int $id)
+ {
+ helper('admin');
+ $item = $this->model->find($id);
+ if (!$item || (int) $item->sa_lg_idx !== admin_effective_lg_idx()) {
+ return redirect()->to(site_url('admin/sales-agencies'))->with('error', '대행소를 찾을 수 없습니다.');
+ }
+
+ $this->model->delete($id);
+ return redirect()->to(site_url('admin/sales-agencies'))->with('success', '판매 대행소가 삭제되었습니다.');
+ }
+}
diff --git a/app/Models/CompanyModel.php b/app/Models/CompanyModel.php
new file mode 100644
index 0000000..fcaec19
--- /dev/null
+++ b/app/Models/CompanyModel.php
@@ -0,0 +1,17 @@
+
+ 업체 등록
+
+
diff --git a/app/Views/admin/company/edit.php b/app/Views/admin/company/edit.php
new file mode 100644
index 0000000..94f2e7e
--- /dev/null
+++ b/app/Views/admin/company/edit.php
@@ -0,0 +1,56 @@
+
+
diff --git a/app/Views/admin/company/index.php b/app/Views/admin/company/index.php
new file mode 100644
index 0000000..acde4e6
--- /dev/null
+++ b/app/Views/admin/company/index.php
@@ -0,0 +1,47 @@
+
+
+
+
+
+ | 번호 |
+ 업체유형 |
+ 업체명 |
+ 사업자번호 |
+ 대표자 |
+ 전화 |
+ 주소 |
+ 상태 |
+ 작업 |
+
+
+
+
+
+ | = esc($row->cp_idx) ?> |
+ = esc($row->cp_type) ?> |
+ = esc($row->cp_name) ?> |
+ = esc($row->cp_biz_no) ?> |
+ = esc($row->cp_rep_name) ?> |
+ = esc($row->cp_tel) ?> |
+ = esc($row->cp_addr) ?> |
+ = (int) $row->cp_state === 1 ? '사용' : '미사용' ?> |
+
+ 수정
+
+ |
+
+
+
+ | 등록된 데이터가 없습니다. |
+
+
+
+
diff --git a/app/Views/admin/free_recipient/create.php b/app/Views/admin/free_recipient/create.php
new file mode 100644
index 0000000..227e2af
--- /dev/null
+++ b/app/Views/admin/free_recipient/create.php
@@ -0,0 +1,62 @@
+
+
diff --git a/app/Views/admin/free_recipient/edit.php b/app/Views/admin/free_recipient/edit.php
new file mode 100644
index 0000000..4154ba0
--- /dev/null
+++ b/app/Views/admin/free_recipient/edit.php
@@ -0,0 +1,70 @@
+
+
diff --git a/app/Views/admin/free_recipient/index.php b/app/Views/admin/free_recipient/index.php
new file mode 100644
index 0000000..ce5a864
--- /dev/null
+++ b/app/Views/admin/free_recipient/index.php
@@ -0,0 +1,49 @@
+
+
+
+
+
+ | 번호 |
+ 구분 |
+ 대상자명 |
+ 연락처 |
+ 주소 |
+ 동코드 |
+ 비고 |
+ 종료일 |
+ 상태 |
+ 작업 |
+
+
+
+
+
+ | = esc($row->fr_idx) ?> |
+ = esc($row->fr_type_code) ?> |
+ = esc($row->fr_name) ?> |
+ = esc($row->fr_phone) ?> |
+ = esc($row->fr_addr) ?> |
+ = esc($row->fr_dong_code) ?> |
+ = esc($row->fr_note) ?> |
+ = esc($row->fr_end_date) ?> |
+ = (int) $row->fr_state === 1 ? '사용' : '미사용' ?> |
+
+ 수정
+
+ |
+
+
+
+ | 등록된 데이터가 없습니다. |
+
+
+
+
diff --git a/app/Views/admin/manager/create.php b/app/Views/admin/manager/create.php
new file mode 100644
index 0000000..ab6ef6e
--- /dev/null
+++ b/app/Views/admin/manager/create.php
@@ -0,0 +1,57 @@
+
+
diff --git a/app/Views/admin/manager/edit.php b/app/Views/admin/manager/edit.php
new file mode 100644
index 0000000..f0f3e0f
--- /dev/null
+++ b/app/Views/admin/manager/edit.php
@@ -0,0 +1,65 @@
+
+
diff --git a/app/Views/admin/manager/index.php b/app/Views/admin/manager/index.php
new file mode 100644
index 0000000..a0d10eb
--- /dev/null
+++ b/app/Views/admin/manager/index.php
@@ -0,0 +1,47 @@
+
+
+
+
+
+ | 번호 |
+ 담당자명 |
+ 소속 |
+ 직위 |
+ 전화 |
+ 휴대전화 |
+ 이메일 |
+ 상태 |
+ 작업 |
+
+
+
+
+
+ | = esc($row->mg_idx) ?> |
+ = esc($row->mg_name) ?> |
+ = esc($row->mg_dept_code) ?> |
+ = esc($row->mg_position_code) ?> |
+ = esc($row->mg_tel) ?> |
+ = esc($row->mg_phone) ?> |
+ = esc($row->mg_email) ?> |
+ = (int) $row->mg_state === 1 ? '사용' : '미사용' ?> |
+
+ 수정
+
+ |
+
+
+
+ | 등록된 데이터가 없습니다. |
+
+
+
+
diff --git a/app/Views/admin/sales_agency/create.php b/app/Views/admin/sales_agency/create.php
new file mode 100644
index 0000000..b0b7640
--- /dev/null
+++ b/app/Views/admin/sales_agency/create.php
@@ -0,0 +1,38 @@
+
+
diff --git a/app/Views/admin/sales_agency/edit.php b/app/Views/admin/sales_agency/edit.php
new file mode 100644
index 0000000..2c15a80
--- /dev/null
+++ b/app/Views/admin/sales_agency/edit.php
@@ -0,0 +1,46 @@
+
+
diff --git a/app/Views/admin/sales_agency/index.php b/app/Views/admin/sales_agency/index.php
new file mode 100644
index 0000000..6ac004e
--- /dev/null
+++ b/app/Views/admin/sales_agency/index.php
@@ -0,0 +1,45 @@
+
+
+
+
+
+ | 번호 |
+ 대행소명 |
+ 사업자번호 |
+ 대표자 |
+ 전화 |
+ 주소 |
+ 상태 |
+ 작업 |
+
+
+
+
+
+ | = esc($row->sa_idx) ?> |
+ = esc($row->sa_name) ?> |
+ = esc($row->sa_biz_no) ?> |
+ = esc($row->sa_rep_name) ?> |
+ = esc($row->sa_tel) ?> |
+ = esc($row->sa_addr) ?> |
+ = (int) $row->sa_state === 1 ? '정상' : '미사용' ?> |
+
+ 수정
+
+ |
+
+
+
+ | 등록된 데이터가 없습니다. |
+
+
+
+
diff --git a/e2e/phase2-entities.spec.js b/e2e/phase2-entities.spec.js
new file mode 100644
index 0000000..67b4632
--- /dev/null
+++ b/e2e/phase2-entities.spec.js
@@ -0,0 +1,73 @@
+// @ts-check
+const { test, expect } = require('@playwright/test');
+const { login } = require('./helpers/auth');
+
+async function loginAsAdmin(page) {
+ await login(page, 'admin');
+ await page.locator('input[name="lg_idx"]').first().check();
+ await page.click('button[type="submit"]');
+ await page.waitForURL(url => !url.pathname.includes('select-local-government'), { timeout: 15000 });
+}
+
+async function loginAsLocal(page) {
+ await login(page, 'local');
+}
+
+test.describe('P2-07/08: 판매 대행소 관리', () => {
+ test('목록 접근 (Super Admin)', async ({ page }) => {
+ await loginAsAdmin(page);
+ await page.goto('/admin/sales-agencies');
+ await expect(page).toHaveURL(/\/admin\/sales-agencies/);
+ });
+ test('등록 폼 표시', async ({ page }) => {
+ await loginAsAdmin(page);
+ await page.goto('/admin/sales-agencies/create');
+ await expect(page.locator('input[name="sa_name"]')).toBeVisible();
+ });
+ test('지자체관리자 접근', async ({ page }) => {
+ await loginAsLocal(page);
+ await page.goto('/admin/sales-agencies');
+ await expect(page).toHaveURL(/\/admin\/sales-agencies/);
+ });
+});
+
+test.describe('P2-09/10: 담당자 관리', () => {
+ test('목록 접근', async ({ page }) => {
+ await loginAsAdmin(page);
+ await page.goto('/admin/managers');
+ await expect(page).toHaveURL(/\/admin\/managers/);
+ });
+ test('등록 폼 표시', async ({ page }) => {
+ await loginAsAdmin(page);
+ await page.goto('/admin/managers/create');
+ await expect(page.locator('input[name="mg_name"]')).toBeVisible();
+ });
+});
+
+test.describe('P2-11/12: 업체 관리', () => {
+ test('목록 접근', async ({ page }) => {
+ await loginAsAdmin(page);
+ await page.goto('/admin/companies');
+ await expect(page).toHaveURL(/\/admin\/companies/);
+ });
+ test('등록 폼 표시', async ({ page }) => {
+ await loginAsAdmin(page);
+ await page.goto('/admin/companies/create');
+ await expect(page.locator('select[name="cp_type"]')).toBeVisible();
+ await expect(page.locator('input[name="cp_name"]')).toBeVisible();
+ });
+});
+
+test.describe('P2-13/14: 무료용 대상자 관리', () => {
+ test('목록 접근', async ({ page }) => {
+ await loginAsAdmin(page);
+ await page.goto('/admin/free-recipients');
+ await expect(page).toHaveURL(/\/admin\/free-recipients/);
+ });
+ test('등록 폼 표시', async ({ page }) => {
+ await loginAsAdmin(page);
+ await page.goto('/admin/free-recipients/create');
+ await expect(page.locator('select[name="fr_type_code"]')).toBeVisible();
+ await expect(page.locator('input[name="fr_name"]')).toBeVisible();
+ });
+});
diff --git a/writable/database/company_tables.sql b/writable/database/company_tables.sql
new file mode 100644
index 0000000..3eda20e
--- /dev/null
+++ b/writable/database/company_tables.sql
@@ -0,0 +1,18 @@
+-- ============================================
+-- 업체 테이블 (P2-11, P2-12)
+-- ============================================
+
+CREATE TABLE IF NOT EXISTS `company` (
+ `cp_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `cp_lg_idx` INT UNSIGNED NOT NULL COMMENT '지자체 FK',
+ `cp_type` VARCHAR(20) NOT NULL COMMENT '협회/제작업체/회수업체',
+ `cp_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '업체명',
+ `cp_biz_no` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '사업자번호',
+ `cp_rep_name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '대표자명',
+ `cp_tel` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '전화번호',
+ `cp_addr` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '주소',
+ `cp_state` TINYINT UNSIGNED NOT NULL DEFAULT 1,
+ `cp_regdate` DATETIME NOT NULL,
+ PRIMARY KEY (`cp_idx`),
+ KEY `idx_cp_lg_idx` (`cp_lg_idx`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='업체(협회/제작/회수)';
diff --git a/writable/database/free_recipient_tables.sql b/writable/database/free_recipient_tables.sql
new file mode 100644
index 0000000..094bce4
--- /dev/null
+++ b/writable/database/free_recipient_tables.sql
@@ -0,0 +1,19 @@
+-- ============================================
+-- 무료용 대상자 테이블 (P2-13, P2-14)
+-- ============================================
+
+CREATE TABLE IF NOT EXISTS `free_recipient` (
+ `fr_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `fr_lg_idx` INT UNSIGNED NOT NULL COMMENT '지자체 FK',
+ `fr_type_code` VARCHAR(20) NOT NULL COMMENT '무상지급구분 code_detail(H)',
+ `fr_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '대상자/기관명',
+ `fr_phone` VARCHAR(20) NOT NULL DEFAULT '',
+ `fr_addr` VARCHAR(255) NOT NULL DEFAULT '',
+ `fr_dong_code` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '동코드 code_detail(D)',
+ `fr_note` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '비고',
+ `fr_end_date` DATE NULL DEFAULT NULL COMMENT '종료일자',
+ `fr_state` TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '1=정상, 0=삭제',
+ `fr_regdate` DATETIME NOT NULL,
+ PRIMARY KEY (`fr_idx`),
+ KEY `idx_fr_lg_idx` (`fr_lg_idx`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='무료용 대상자';
diff --git a/writable/database/manager_tables.sql b/writable/database/manager_tables.sql
new file mode 100644
index 0000000..d1d7636
--- /dev/null
+++ b/writable/database/manager_tables.sql
@@ -0,0 +1,18 @@
+-- ============================================
+-- 담당자 테이블 (P2-09, P2-10)
+-- ============================================
+
+CREATE TABLE IF NOT EXISTS `manager` (
+ `mg_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `mg_lg_idx` INT UNSIGNED NOT NULL COMMENT '지자체 FK',
+ `mg_name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '담당자명',
+ `mg_dept_code` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '소속 code_detail(S)',
+ `mg_position_code` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '직위 code_detail(T)',
+ `mg_tel` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '전화번호',
+ `mg_phone` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '휴대전화',
+ `mg_email` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '이메일',
+ `mg_state` TINYINT UNSIGNED NOT NULL DEFAULT 1,
+ `mg_regdate` DATETIME NOT NULL,
+ PRIMARY KEY (`mg_idx`),
+ KEY `idx_mg_lg_idx` (`mg_lg_idx`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='담당자';
diff --git a/writable/database/sales_agency_tables.sql b/writable/database/sales_agency_tables.sql
new file mode 100644
index 0000000..9b7e7e5
--- /dev/null
+++ b/writable/database/sales_agency_tables.sql
@@ -0,0 +1,17 @@
+-- ============================================
+-- 판매 대행소 테이블 (P2-07, P2-08)
+-- ============================================
+
+CREATE TABLE IF NOT EXISTS `sales_agency` (
+ `sa_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `sa_lg_idx` INT UNSIGNED NOT NULL COMMENT '지자체 FK',
+ `sa_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '대행소명',
+ `sa_biz_no` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '사업자번호',
+ `sa_rep_name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '대표자명',
+ `sa_tel` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '전화번호',
+ `sa_addr` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '주소',
+ `sa_state` TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '1=정상, 0=미사용',
+ `sa_regdate` DATETIME NOT NULL,
+ PRIMARY KEY (`sa_idx`),
+ KEY `idx_sa_lg_idx` (`sa_lg_idx`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='판매 대행소';