Compare commits
2 Commits
main
...
e4bd3f9ffc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4bd3f9ffc | ||
|
|
71edc1eb20 |
@@ -1,10 +0,0 @@
|
||||
---
|
||||
description: 패키지 설치 전 승인·안정성 확인 및 공급망 보안 습관
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# 의존성·패키지 보안
|
||||
|
||||
- **새 패키지(npm, Composer 등)를 설치·추가하기 전에 반드시 사용자에게 먼저 물어본다.** 자동으로 `npm install`, `composer require` 등을 실행하지 않는다(사용자가 명시적으로 요청한 경우만).
|
||||
- 새 버전을 제안할 때는 **공식 레지스트리(npmjs.org, packagist.org) 출처**인지 확인하고, **출시된 지 최소 며칠(가이드: 7일) 이상 지난 안정(stable) 버전**을 우선 제안한다. 방금 출시된 버전은 typosquat·피싱 패키지 위험이 있어 사용자에게 그 점을 짚어 준다.
|
||||
- 락 파일(`package-lock.json`, `composer.lock`)을 대량 수정하거나 생소한 패키지를 넣지 않는다. 이상하면 사용자에게 중단하고 확인을 요청한다.
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -42,6 +42,7 @@ $RECYCLE.BIN/
|
||||
# These should never be under version control,
|
||||
# as it poses a security risk.
|
||||
.env
|
||||
env
|
||||
.env.local
|
||||
.env.*.local
|
||||
.env.production
|
||||
@@ -56,10 +57,12 @@ Vagrantfile
|
||||
# Local docs & MCP (저장소에 올리지 않음)
|
||||
#-------------------------
|
||||
docs/local/
|
||||
docs/
|
||||
mcp-servers/
|
||||
|
||||
# Cursor MCP — API 키·로컬 경로 등 포함 가능
|
||||
.cursor/mcp.json
|
||||
.cursor/
|
||||
|
||||
#-------------------------
|
||||
# Secrets & credentials (보안)
|
||||
@@ -174,4 +177,3 @@ blob-report/
|
||||
|
||||
/results/
|
||||
/phpunit*.xml
|
||||
docs/
|
||||
|
||||
84
README.md
84
README.md
@@ -39,7 +39,7 @@ app/
|
||||
├── Config/ # Routes, Database, Roles, Filters, Session 등 (45개)
|
||||
├── Controllers/ # 28개 컨트롤러
|
||||
│ ├── Auth.php # 로그인/로그아웃/회원가입
|
||||
│ ├── Bag.php # 사이트 메뉴 페이지 (기본정보·기본코드 목록 등)
|
||||
│ ├── Bag.php # 사이트 메뉴 페이지 (10개 메뉴)
|
||||
│ ├── Home.php # 홈/대시보드
|
||||
│ └── Admin/ # 관리자 컨트롤러 24개
|
||||
│ ├── BagOrder.php # 발주 관리
|
||||
@@ -69,7 +69,7 @@ app/
|
||||
│ ├── bag/ # 사이트 메뉴 뷰 (17개 + 레이아웃)
|
||||
│ ├── auth/ # 로그인/회원가입 (2개)
|
||||
│ └── home/ # 대시보드 (1개)
|
||||
├── Filters/ # AdminAuthFilter, LoginAuthFilter (`/bag/code-kinds` 등)
|
||||
├── Filters/ # AdminAuthFilter (관리자 접근 제어)
|
||||
├── Helpers/ # admin_helper, pii_encryption_helper
|
||||
└── Database/ # Migrations, Seeds
|
||||
public/ # 웹 루트
|
||||
@@ -160,7 +160,6 @@ assets/ # 기획 문서 (엑셀)
|
||||
|
||||
- 역할 상수: `Config\Roles` -- `LEVEL_SUPER_ADMIN(4)`, `LEVEL_LOCAL_ADMIN(3)`, `LEVEL_SHOP(2)`, `LEVEL_CITIZEN(1)`
|
||||
- `AdminAuthFilter`가 로그인 + 레벨 3/4 + 지자체 선택 여부 검증
|
||||
- **기본코드 마스터 CRUD**는 `Roles::canManageCodeMaster()`(지자체관리자·Super Admin 등)로 제한. **종류·세부 목록 조회**는 로그인 사용자 전원 (`/bag/code-kinds`, `/bag/code-details/{ck_idx}`, `loginAuth` 필터)
|
||||
|
||||
## 멀티테넌시
|
||||
|
||||
@@ -186,9 +185,7 @@ assets/ # 기획 문서 (엑셀)
|
||||
|
||||
| 경로 | 설명 | 기능 |
|
||||
|------|------|------|
|
||||
| `/bag/basic-info` | 기본정보관리 | 단가·포장단위 등 링크 허브 (`/bag/code-kinds`로 기본코드 조회) |
|
||||
| `/bag/code-kinds` | 기본코드 종류 | 종류 목록·세부코드 링크 (조회; CRUD는 관리자만) |
|
||||
| `/bag/code-details/{ck_idx}` | 기본코드 세부 | 해당 종류의 세부코드 목록 (조회; CRUD는 관리자만) |
|
||||
| `/bag/basic-info` | 기본정보관리 | 코드/단가/포장단위 조회 + 관리 링크 |
|
||||
| `/bag/purchase-inbound` | 발주 입고 관리 | 발주/입고 목록 + 등록 버튼 |
|
||||
| `/bag/issue` | 불출 관리 | 불출 목록 + 처리/취소 |
|
||||
| `/bag/inventory` | 재고 관리 | 봉투별 현재 재고 조회 |
|
||||
@@ -213,39 +210,46 @@ assets/ # 기획 문서 (엑셀)
|
||||
| `/admin/menus/*` | 메뉴 관리 (트리 CRUD) |
|
||||
| `/admin/local-governments/*` | 지자체 관리 (CRUD) |
|
||||
| `/admin/select-local-government` | 작업 지자체 선택 (Super Admin) |
|
||||
| `/admin/password-change` | 비밀번호 변경 |
|
||||
| `/admin/designated-shops/*` | 지정판매소 관리 (CRUD) |
|
||||
|
||||
**기본코드 CRUD만 관리자 경로 (목록·조회는 `/bag/*`)**
|
||||
**기본정보관리 (Phase 2)**
|
||||
|
||||
| 경로 | 기능 |
|
||||
|------|------|
|
||||
| `/admin/code-kinds/*` | 기본코드 종류 **CRUD만** (create/edit/store/update/delete; **목록 없음** — 조회는 `/bag/code-kinds`) |
|
||||
| `/admin/code-details/*` | 세부코드 **CRUD만** (**목록 없음** — 조회는 `/bag/code-details/{ck_idx}`) |
|
||||
| (호환) `GET /admin/code-details/{ck_idx}` | `/bag/code-details/{ck_idx}` 로 리다이렉트 |
|
||||
| `/admin/code-kinds/*` | 기본코드 종류 (CRUD) |
|
||||
| `/admin/code-details/*` | 세부코드 (CRUD) |
|
||||
| `/admin/bag-prices/*` | 봉투 단가 (CRUD + 이력) |
|
||||
| `/admin/packaging-units/*` | 포장 단위 (CRUD + 이력) |
|
||||
| `/admin/sales-agencies/*` | 판매 대행소 (CRUD) |
|
||||
| `/admin/managers/*` | 담당자 (CRUD) |
|
||||
| `/admin/companies/*` | 업체 (CRUD) |
|
||||
| `/admin/free-recipients/*` | 무료용 대상자 (CRUD) |
|
||||
|
||||
### 업무 화면 (`/bag/*`, adminAuth 필터)
|
||||
|
||||
동일 `Admin\*` 컨트롤러·뷰를 쓰며 메인 사이트 레이아웃으로 렌더된다. `GET /admin/managers` 등 옛 업무 URL은 `301`·`POST` 는 `307`로 `/bag/...`에 리다이렉트된다.
|
||||
**발주/입고/재고 (Phase 3)**
|
||||
|
||||
| 경로 | 기능 |
|
||||
|------|------|
|
||||
| `/bag/password-change` | 비밀번호 변경 |
|
||||
| `/bag/designated-shops/*` | 지정판매소 관리 (CRUD) |
|
||||
| `/bag/bag-prices/*` | 봉투 단가 (CRUD + 이력) |
|
||||
| `/bag/packaging-units/manage/*` | 포장 단위 (CRUD + 이력; 조회 전용은 `/bag/packaging-units`) |
|
||||
| `/bag/sales-agencies/*` | 판매 대행소 (CRUD) |
|
||||
| `/bag/managers/*` | 담당자 (CRUD) |
|
||||
| `/bag/companies/*` | 업체 (CRUD) |
|
||||
| `/bag/free-recipients/*` | 무료용 대상자 (CRUD) |
|
||||
| `/bag/bag-orders/*` | 발주 관리 (등록/상세/취소/삭제) |
|
||||
| `/bag/bag-receivings/*` | 입고 관리 (등록, 재고 자동 반영) |
|
||||
| `/bag/bag-inventory` | 재고 현황 조회 |
|
||||
| `/bag/shop-orders/*` | 주문 접수 (등록/취소) |
|
||||
| `/bag/bag-sales/*` | 판매/반품 (등록) |
|
||||
| `/bag/bag-issues/*` | 무료용 불출 (등록/취소, 재고 연동) |
|
||||
| `/bag/reports/sales-ledger` | 판매 대장 (일자별/기간별) |
|
||||
| `/bag/reports/daily-summary` | 일계표 (일계 + 월간 누계) |
|
||||
| `/bag/reports/period-sales` | 기간별 판매현황 |
|
||||
| `/bag/reports/supply-demand` | 봉투 수불 현황 |
|
||||
| `/admin/bag-orders/*` | 발주 관리 (등록/상세/취소/삭제) |
|
||||
| `/admin/bag-receivings/*` | 입고 관리 (등록, 재고 자동 반영) |
|
||||
| `/admin/bag-inventory` | 재고 현황 조회 |
|
||||
|
||||
**판매/주문/불출 (Phase 4)**
|
||||
|
||||
| 경로 | 기능 |
|
||||
|------|------|
|
||||
| `/admin/shop-orders/*` | 주문 접수 (등록/취소) |
|
||||
| `/admin/bag-sales/*` | 판매/반품 (등록) |
|
||||
| `/admin/bag-issues/*` | 무료용 불출 (등록/취소, 재고 연동) |
|
||||
|
||||
**리포트 (Phase 5)**
|
||||
|
||||
| 경로 | 기능 |
|
||||
|------|------|
|
||||
| `/admin/reports/sales-ledger` | 판매 대장 (일자별/기간별) |
|
||||
| `/admin/reports/daily-summary` | 일계표 (일계 + 월간 누계) |
|
||||
| `/admin/reports/period-sales` | 기간별 판매현황 |
|
||||
| `/admin/reports/supply-demand` | 봉투 수불 현황 |
|
||||
|
||||
---
|
||||
|
||||
@@ -285,7 +289,7 @@ assets/ # 기획 문서 (엑셀)
|
||||
|
||||
| 항목 | 구현 |
|
||||
|------|------|
|
||||
| 인증 | 세션 기반 로그인, AdminAuthFilter로 관리자 접근 제어, 기본코드 목록 경로는 LoginAuthFilter(`loginAuth`) |
|
||||
| 인증 | 세션 기반 로그인, AdminAuthFilter로 관리자 접근 제어 |
|
||||
| RBAC | 4단계 역할 (Config\Roles), 메뉴별 역할 노출 |
|
||||
| PII 암호화 | `pii_encryption_helper` (AES, `ENC:` prefix) - 이메일/전화번호 |
|
||||
| 비밀번호 | `password_hash()` + `password_verify()` (bcrypt) |
|
||||
@@ -345,8 +349,6 @@ SQL 스크립트 실행 순서:
|
||||
| 17 | `seed_test_accounts.sql` | 테스터 계정 4개 |
|
||||
| 18 | `seed_realistic_data.sql` | 실제형 시범 데이터 |
|
||||
|
||||
**기본코드 전용 보강 (선택·기존 DB):** `menu_fix_basic_code_link.sql`, `menu_site_add_basic_code_child.sql`, 개발목록 CSV 반영 시 `code_master_sync_from_csv.sql` 또는 `writable/tools/sync_basic_codes_from_csv.py`.
|
||||
|
||||
### 4) 개발 서버 실행
|
||||
|
||||
```bash
|
||||
@@ -382,7 +384,6 @@ npx playwright test -g "로그인 페이지"
|
||||
|
||||
| ID | 역할 | Level |
|
||||
|----|------|-------|
|
||||
| `tester_badmin` | 본부 관리자 | 5 |
|
||||
| `tester_admin` | Super Admin | 4 |
|
||||
| `tester_local` | 지자체관리자 (중구청) | 3 |
|
||||
| `tester_shop` | 지정판매소 | 2 |
|
||||
@@ -396,7 +397,7 @@ npx playwright test -g "로그인 페이지"
|
||||
| admin.spec.js | 10 | 관리자 패널 접근 |
|
||||
| public.spec.js | 4 | 공개 페이지 |
|
||||
| bag-site.spec.js | 11 | 사이트 메뉴 /bag/* |
|
||||
| code-management.spec.js | 7 | 기본코드 CRUD (`/bag/*` 목록 + `/admin/*` 폼) |
|
||||
| code-management.spec.js | 7 | 기본코드 CRUD |
|
||||
| bag-price.spec.js | 6 | 봉투 단가 |
|
||||
| packaging-unit.spec.js | 3 | 포장 단위 |
|
||||
| phase2-entities.spec.js | 8 | 대행소/담당자/업체/무료대상자 |
|
||||
@@ -409,14 +410,6 @@ npx playwright test -g "로그인 페이지"
|
||||
|
||||
## 기본코드 체계
|
||||
|
||||
### 코드 관리 URL·동작
|
||||
|
||||
| 구분 | 경로 | 설명 |
|
||||
|------|------|------|
|
||||
| 목록·조회 | `/bag/code-kinds`, `/bag/code-details/{ck_idx}` | 사이트(`bag`) 레이아웃. 시민·판매소는 열람만; 코드 마스터 관리 권한이 있으면 CRUD용 링크(관리자 화면) 노출 |
|
||||
| 등록·수정·삭제 | `/admin/code-kinds/*`, `/admin/code-details/*` | `adminAuth` + `canManageCodeMaster`. 처리 후 flash 메시지와 함께 위 `bag` 목록으로 되돌아감 |
|
||||
| 데이터 보강 (선택) | `writable/tools/sync_basic_codes_from_csv.py` → 생성 SQL 또는 `code_master_sync_from_csv.sql` | 개발목록 CSV와 DB 종류·세부코드 맞출 때 참고 |
|
||||
|
||||
A~T 총 20종의 코드 체계 (`code_kind` + `code_detail`):
|
||||
|
||||
| 코드 | 코드명 | 코드 | 코드명 |
|
||||
@@ -480,9 +473,6 @@ A~T 총 20종의 코드 체계 (`code_kind` + `code_detail`):
|
||||
| `menu_site_seed_from_csv.sql` | 사이트 네비게이션 시드 |
|
||||
| `local_government_init_daegu.sql` | 대구 8개 구군 지자체 |
|
||||
| `code_master_init_daegu.sql` | 기본코드 20종 + 세부코드 |
|
||||
| `menu_fix_basic_code_link.sql` | 사이트 메뉴에서 기본코드 링크 보정 (기존 DB용, 선택) |
|
||||
| `menu_site_add_basic_code_child.sql` | 사이트 메뉴에 기본코드 하위 항목 (선택) |
|
||||
| `code_master_sync_from_csv.sql` | CSV 기준 기본코드 보강 (선택) |
|
||||
| `bag_price_tables.sql` | bag_price, bag_price_history |
|
||||
| `packaging_unit_tables.sql` | packaging_unit, packaging_unit_history |
|
||||
| `sales_agency_tables.sql` | sales_agency |
|
||||
|
||||
@@ -40,7 +40,7 @@ class App extends BaseConfig
|
||||
* something else. If you have configured your web server to remove this file
|
||||
* from your site URIs, set this variable to an empty string.
|
||||
*/
|
||||
public string $indexPage = '';
|
||||
public string $indexPage = 'index.php';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
|
||||
@@ -7,19 +7,17 @@ use CodeIgniter\Config\BaseConfig;
|
||||
/**
|
||||
* 로그인·2차 인증(TOTP) 관련 설정
|
||||
*
|
||||
* .env 의 auth.requireTotp 가 Config 기본값보다 우선합니다. 끄려면 반드시 false 로 두세요.
|
||||
* 예:
|
||||
* auth.requireTotp = false
|
||||
* auth.requireTotp = true # 운영에서 2FA 켤 때
|
||||
* auth.totpIssuer = "종량제 시스템"
|
||||
* .env 예:
|
||||
* auth.requireTotp = true
|
||||
* auth.totpIssuer = "쓰레기봉투 물류시스템"
|
||||
*/
|
||||
class Auth extends BaseConfig
|
||||
{
|
||||
/** false 이면 로그인 시 TOTP·등록 유도 없음. 운영에서 켤 때 .env 에 auth.requireTotp = true */
|
||||
public bool $requireTotp = false;
|
||||
/** 운영·스테이징 true 권장. 로컬 개발 시 false 로 1단계만 로그인 가능 */
|
||||
public bool $requireTotp = true;
|
||||
|
||||
/** 인증 앱에 표시되는 발급자(issuer) */
|
||||
public string $totpIssuer = '종량제 시스템';
|
||||
public string $totpIssuer = '쓰레기봉투 물류시스템';
|
||||
|
||||
/** TOTP 연속 실패 시 세션 종료 전 허용 횟수 */
|
||||
public int $totpMaxAttempts = 5;
|
||||
|
||||
@@ -27,40 +27,8 @@ class Encryption extends BaseConfig
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$hex = trim((string) env('encryption.key', ''));
|
||||
if (
|
||||
(str_starts_with($hex, "'") && str_ends_with($hex, "'"))
|
||||
|| (str_starts_with($hex, '"') && str_ends_with($hex, '"'))
|
||||
) {
|
||||
$hex = substr($hex, 1, -1);
|
||||
}
|
||||
$hex = (string) env('encryption.key', '');
|
||||
$this->key = (strlen($hex) === 64 && ctype_xdigit($hex)) ? hex2bin($hex) : '';
|
||||
|
||||
$prev = trim((string) env('encryption.previousKeys', ''));
|
||||
if ($prev !== '') {
|
||||
$parsed = [];
|
||||
$parts = array_map('trim', explode(',', $prev));
|
||||
foreach ($parts as $part) {
|
||||
if ($part === '') {
|
||||
continue;
|
||||
}
|
||||
if (str_starts_with($part, 'hex2bin:')) {
|
||||
$part = substr($part, 8);
|
||||
}
|
||||
if (
|
||||
(str_starts_with($part, "'") && str_ends_with($part, "'"))
|
||||
|| (str_starts_with($part, '"') && str_ends_with($part, '"'))
|
||||
) {
|
||||
$part = substr($part, 1, -1);
|
||||
}
|
||||
if (strlen($part) === 64 && ctype_xdigit($part)) {
|
||||
$parsed[] = 'hex2bin:' . $part;
|
||||
}
|
||||
}
|
||||
if (! empty($parsed)) {
|
||||
$this->previousKeys = $parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,6 @@ class Filters extends BaseFilters
|
||||
*/
|
||||
public array $aliases = [
|
||||
'adminAuth' => \App\Filters\AdminAuthFilter::class,
|
||||
'loginAuth' => \App\Filters\LoginAuthFilter::class,
|
||||
'csrf' => CSRF::class,
|
||||
'toolbar' => DebugToolbar::class,
|
||||
'honeypot' => Honeypot::class,
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
|
||||
/**
|
||||
* 카카오 Developers — 내 애플리케이션 — 앱 키 — JavaScript 키
|
||||
* .env 예: kakao.javascriptKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
*/
|
||||
class Kakao extends BaseConfig
|
||||
{
|
||||
public string $javascriptKey = '';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$v = env('kakao.javascriptKey');
|
||||
if (is_string($v) && $v !== '') {
|
||||
$this->javascriptKey = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,52 +42,6 @@ class Roles extends BaseConfig
|
||||
return $level === self::LEVEL_SUPER_ADMIN || $level === self::LEVEL_HEADQUARTERS_ADMIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본코드(종류·세부) 등록·수정·삭제 가능 (super admin(4) · 본부 관리자(5)만)
|
||||
*/
|
||||
public static function canManageCodeMaster(int $level): bool
|
||||
{
|
||||
return self::isSuperAdminEquivalent($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본코드 종류(code_kind) CRUD — super·본부만
|
||||
*/
|
||||
public static function canManageCodeKindMaster(int $level): bool
|
||||
{
|
||||
return self::isSuperAdminEquivalent($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* 플랫폼 공통 세부코드(CSV·시드) 수정·삭제 — super·본부만
|
||||
*/
|
||||
public static function canEditPlatformCodeDetail(int $level): bool
|
||||
{
|
||||
return self::isSuperAdminEquivalent($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* 세부코드 행 단위 수정/삭제 가능 여부
|
||||
*
|
||||
* @param object $row code_detail (cd_source, cd_lg_idx)
|
||||
*/
|
||||
public static function canEditCodeDetailRow(int $level, object $row, ?int $adminEffectiveLgIdx): bool
|
||||
{
|
||||
if (! self::canManageCodeMaster($level)) {
|
||||
return false;
|
||||
}
|
||||
$src = $row->cd_source ?? 'platform';
|
||||
$lg = (int) ($row->cd_lg_idx ?? 0);
|
||||
if ($src === 'platform' && $lg === 0) {
|
||||
return self::canEditPlatformCodeDetail($level);
|
||||
}
|
||||
if (self::isSuperAdminEquivalent($level)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $adminEffectiveLgIdx !== null && $adminEffectiveLgIdx > 0 && $lg === $adminEffectiveLgIdx;
|
||||
}
|
||||
|
||||
/**
|
||||
* TOTP 2차 인증 적용 대상 (지자체·super·본부 관리자)
|
||||
*/
|
||||
|
||||
@@ -26,18 +26,8 @@ $routes->get('bag/code-details/(:num)', 'Bag::codeDetails/$1');
|
||||
// 옛 주소 호환: 세부 목록만 사이트로 이동
|
||||
$routes->get('admin/code-details/(:num)', 'Admin\CodeDetail::index/$1');
|
||||
$routes->get('bag/purchase-inbound', 'Bag::purchaseInbound');
|
||||
$routes->get('bag/issue', 'Bag::issueLegacy');
|
||||
$routes->get('bag/issue/cancel', 'Bag::issue');
|
||||
$routes->get('bag/issue', 'Bag::issue');
|
||||
$routes->get('bag/inventory', 'Bag::inventory');
|
||||
$routes->get('bag/inventory/export', 'Bag::inventoryExport');
|
||||
$routes->get('bag/inventory/inspection-select', 'Bag::inspectionSelect');
|
||||
$routes->get('bag/inventory/inspection-work', 'Bag::inspectionWork');
|
||||
$routes->post('bag/inventory/inspection-run', 'Bag::inspectionRun');
|
||||
$routes->post('bag/inventory/inspection-select/save', 'Bag::inspectionSelectSave');
|
||||
$routes->post('bag/inventory/inspection-select/confirm', 'Bag::inspectionSelectConfirm');
|
||||
$routes->get('bag/inventory/inspection/(:num)', 'Bag::inspectionDetail/$1');
|
||||
$routes->post('bag/inventory/inspection/(:num)/save', 'Bag::inspectionSave/$1');
|
||||
$routes->post('bag/inventory/inspection/(:num)/apply', 'Bag::inspectionApply/$1');
|
||||
$routes->get('bag/sales', 'Bag::sales');
|
||||
$routes->get('bag/sales-stats', 'Bag::salesStats');
|
||||
$routes->get('bag/flow', 'Bag::flow');
|
||||
@@ -51,24 +41,11 @@ $routes->post('bag/inventory/adjust', 'Bag::inventoryAdjustStore');
|
||||
$routes->get('bag/issue/create', 'Bag::issueCreate');
|
||||
$routes->post('bag/issue/store', 'Bag::issueStore');
|
||||
$routes->post('bag/issue/cancel/(:num)', 'Bag::issueCancel/$1');
|
||||
$routes->post('bag/issue/cancel-save', 'Bag::issueCancelSave');
|
||||
$routes->get('bag/order/create', 'Bag::orderCreate');
|
||||
$routes->get('bag/order/change', 'Bag::orderChange');
|
||||
$routes->get('bag/order/revise/(:num)', 'Bag::orderRevise/$1');
|
||||
$routes->get('bag/order/lot-seed', 'Bag::orderLotSeed');
|
||||
$routes->post('bag/order/lot-seed/generate', 'Bag::orderLotSeedGenerate');
|
||||
$routes->post('bag/order/store', 'Bag::orderStore');
|
||||
$routes->post('bag/order/cancel/(:num)', 'Bag::orderCancel/$1');
|
||||
$routes->post('bag/order/delete', 'Bag::orderDeletePost');
|
||||
$routes->post('bag/order/delete/(:num)', 'Bag::orderDelete/$1');
|
||||
$routes->get('bag/receiving/create', 'Bag::receivingCreate');
|
||||
$routes->post('bag/receiving/store', 'Bag::receivingStore');
|
||||
$routes->get('bag/receiving/scanner', 'Bag::receivingScanner');
|
||||
$routes->post('bag/receiving/scanner/store', 'Bag::receivingScannerStore');
|
||||
$routes->get('bag/receiving/batch', 'Bag::receivingBatch');
|
||||
$routes->post('bag/receiving/batch/store', 'Bag::receivingBatchStore');
|
||||
$routes->get('bag/receiving/status', 'Bag::receivingStatus');
|
||||
$routes->get('bag/receiving/status/export', 'Bag::receivingStatusExport');
|
||||
$routes->get('bag/sale/create', 'Bag::saleCreate');
|
||||
$routes->post('bag/sale/store', 'Bag::saleStore');
|
||||
$routes->get('bag/shop-order/create', 'Bag::shopOrderCreate');
|
||||
@@ -77,6 +54,7 @@ $routes->post('bag/shop-order/store', 'Bag::shopOrderStore');
|
||||
// 메인 사이트 메뉴용 업무 URL (관리자 권한). 동일 컨트롤러가 URI 가 bag 이면 메인 사이트 레이아웃으로 렌더.
|
||||
$routes->group('bag', ['filter' => 'adminAuth'], static function ($routes): void {
|
||||
$routes->get('managers', 'Admin\Manager::index');
|
||||
$routes->post('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');
|
||||
@@ -91,6 +69,7 @@ $routes->group('bag', ['filter' => 'adminAuth'], static function ($routes): void
|
||||
$routes->post('sales-agencies/delete/(:num)', 'Admin\SalesAgency::delete/$1');
|
||||
|
||||
$routes->get('companies', 'Admin\Company::index');
|
||||
$routes->post('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');
|
||||
@@ -98,6 +77,7 @@ $routes->group('bag', ['filter' => 'adminAuth'], static function ($routes): void
|
||||
$routes->post('companies/delete/(:num)', 'Admin\Company::delete/$1');
|
||||
|
||||
$routes->get('free-recipients', 'Admin\FreeRecipient::index');
|
||||
$routes->post('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');
|
||||
@@ -106,13 +86,8 @@ $routes->group('bag', ['filter' => 'adminAuth'], static function ($routes): void
|
||||
|
||||
$routes->get('designated-shops/export', 'Admin\DesignatedShop::export');
|
||||
$routes->get('designated-shops/map', 'Admin\DesignatedShop::map');
|
||||
$routes->get('designated-shops/status/export', 'Admin\DesignatedShop::statusExport');
|
||||
$routes->get('designated-shops/status', 'Admin\DesignatedShop::status');
|
||||
$routes->get('designated-shops/barcode', 'Admin\DesignatedShop::barcode');
|
||||
$routes->post('designated-shops/barcode/print', 'Admin\DesignatedShop::barcodePrint');
|
||||
$routes->get('designated-shops/district-new-cancel/export', 'Admin\DesignatedShop::districtNewCancelExport');
|
||||
$routes->get('designated-shops/district-new-cancel', 'Admin\DesignatedShop::districtNewCancel');
|
||||
$routes->get('designated-shops/browse', 'Admin\DesignatedShop::browse');
|
||||
$routes->get('designated-shops/show/(:num)', 'Admin\DesignatedShop::show/$1');
|
||||
$routes->get('designated-shops', 'Admin\DesignatedShop::index');
|
||||
$routes->get('designated-shops/create', 'Admin\DesignatedShop::create');
|
||||
$routes->post('designated-shops/store', 'Admin\DesignatedShop::store');
|
||||
@@ -131,7 +106,6 @@ $routes->group('bag', ['filter' => 'adminAuth'], static function ($routes): void
|
||||
$routes->get('bag-orders/export', 'Admin\BagOrder::export');
|
||||
$routes->get('bag-orders', 'Admin\BagOrder::index');
|
||||
$routes->get('bag-orders/create', 'Admin\BagOrder::create');
|
||||
$routes->get('bag-orders/revise/(:num)', 'Admin\BagOrder::revise/$1');
|
||||
$routes->post('bag-orders/store', 'Admin\BagOrder::store');
|
||||
$routes->get('bag-orders/detail/(:num)', 'Admin\BagOrder::detail/$1');
|
||||
$routes->post('bag-orders/cancel/(:num)', 'Admin\BagOrder::cancel/$1');
|
||||
|
||||
@@ -11,23 +11,24 @@ class BagInventory extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$invModel = model(BagInventoryModel::class);
|
||||
$list = $invModel->where('bi_lg_idx', $lgIdx)->orderBy('bi_bag_code', 'ASC')->paginate(20);
|
||||
$pager = $invModel->pager;
|
||||
$list = $invModel->where('bi_lg_idx', $lgIdx)->orderBy('bi_bag_code', 'ASC')->paginate(20);
|
||||
$pager = $invModel->pager;
|
||||
|
||||
return $this->renderWorkPage('재고 현황', 'admin/bag_inventory/index', ['list' => $list, 'pager' => $pager]);
|
||||
return view('admin/layout', [
|
||||
'title' => '재고 현황',
|
||||
'content' => view('admin/bag_inventory/index', ['list' => $list, 'pager' => $pager]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function export()
|
||||
{
|
||||
helper(['admin', 'export']);
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(mgmt_url('bag-inventory'))->with('error', '지자체를 선택해 주세요.');
|
||||
if (!$lgIdx) {
|
||||
return redirect()->to(site_url('admin/bag-inventory'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$list = model(BagInventoryModel::class)->where('bi_lg_idx', $lgIdx)->orderBy('bi_bag_code', 'ASC')->findAll();
|
||||
|
||||
@@ -21,34 +21,33 @@ class BagIssue extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$builder = $this->issueModel->where('bi2_lg_idx', $lgIdx);
|
||||
$startDate = $this->request->getGet('start_date');
|
||||
$endDate = $this->request->getGet('end_date');
|
||||
if ($startDate) {
|
||||
$builder->where('bi2_issue_date >=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$builder->where('bi2_issue_date <=', $endDate);
|
||||
}
|
||||
if ($startDate) $builder->where('bi2_issue_date >=', $startDate);
|
||||
if ($endDate) $builder->where('bi2_issue_date <=', $endDate);
|
||||
|
||||
$list = $builder->orderBy('bi2_issue_date', 'DESC')->orderBy('bi2_idx', 'DESC')->paginate(20);
|
||||
$list = $builder->orderBy('bi2_issue_date', 'DESC')->orderBy('bi2_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->issueModel->pager;
|
||||
|
||||
return $this->renderWorkPage('무료용 불출 관리', 'admin/bag_issue/index', compact('list', 'startDate', 'endDate', 'pager'));
|
||||
return view('admin/layout', [
|
||||
'title' => '무료용 불출 관리',
|
||||
'content' => view('admin/bag_issue/index', compact('list', 'startDate', 'endDate', 'pager')),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx) : [];
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true) : [];
|
||||
|
||||
return $this->renderWorkPage('무료용 불출 처리', 'admin/bag_issue/create', compact('bagCodes'));
|
||||
return view('admin/layout', [
|
||||
'title' => '무료용 불출 처리',
|
||||
'content' => view('admin/bag_issue/create', compact('bagCodes')),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store()
|
||||
@@ -72,8 +71,8 @@ class BagIssue extends BaseController
|
||||
$bagCode = $this->request->getPost('bi2_bag_code');
|
||||
$qty = (int) $this->request->getPost('bi2_qty');
|
||||
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, (string) $bagCode, $lgIdx) : null;
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(CodeDetailModel::class)->where('cd_ck_idx', $kindO->ck_idx)->where('cd_code', $bagCode)->first() : null;
|
||||
$bagName = $detail ? $detail->cd_name : '';
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
@@ -96,22 +95,24 @@ class BagIssue extends BaseController
|
||||
$this->issueModel->insert($issueData);
|
||||
$bi2Idx = (int) $this->issueModel->getInsertID();
|
||||
|
||||
// CT-05: 감사 로그
|
||||
helper('audit');
|
||||
audit_log('create', 'bag_issue', $bi2Idx, null, array_merge($issueData, ['bi2_idx' => $bi2Idx]));
|
||||
|
||||
// 재고 감산
|
||||
model(BagInventoryModel::class)->adjustQty($lgIdx, $bagCode, $bagName, -$qty);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->to(mgmt_url('bag-issues'))->with('success', '불출 처리되었습니다.');
|
||||
return redirect()->to(site_url('admin/bag-issues'))->with('success', '불출 처리되었습니다.');
|
||||
}
|
||||
|
||||
public function cancel(int $id)
|
||||
{
|
||||
helper('admin');
|
||||
$item = $this->issueModel->find($id);
|
||||
if (! $item || (int) $item->bi2_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(mgmt_url('bag-issues'))->with('error', '불출 내역을 찾을 수 없습니다.');
|
||||
if (!$item || (int) $item->bi2_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(site_url('admin/bag-issues'))->with('error', '불출 내역을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
@@ -119,12 +120,14 @@ class BagIssue extends BaseController
|
||||
|
||||
$before = (array) $item;
|
||||
$this->issueModel->update($id, ['bi2_status' => 'cancelled']);
|
||||
// CT-05: 감사 로그
|
||||
helper('audit');
|
||||
audit_log('update', 'bag_issue', $id, $before, ['bi2_status' => 'cancelled']);
|
||||
// 재고 복원
|
||||
model(BagInventoryModel::class)->adjustQty((int) $item->bi2_lg_idx, $item->bi2_bag_code, $item->bi2_bag_name, (int) $item->bi2_qty);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->to(mgmt_url('bag-issues'))->with('success', '불출이 취소되었습니다.');
|
||||
return redirect()->to(site_url('admin/bag-issues'))->with('success', '불출이 취소되었습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\BagPriceHistoryModel;
|
||||
use App\Models\BagPriceModel;
|
||||
use App\Models\CodeDetailModel;
|
||||
use App\Models\BagPriceHistoryModel;
|
||||
use App\Models\CodeKindModel;
|
||||
use App\Models\CodeDetailModel;
|
||||
|
||||
class BagPrice extends BaseController
|
||||
{
|
||||
@@ -23,147 +23,36 @@ class BagPrice extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
if (!$lgIdx) {
|
||||
return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$get = $this->request->getGet();
|
||||
$readSrc = static function (array $src, string $key): ?string {
|
||||
if (! array_key_exists($key, $src)) {
|
||||
return null;
|
||||
}
|
||||
$v = $src[$key];
|
||||
if ($v === null || is_array($v)) {
|
||||
return null;
|
||||
}
|
||||
$s = trim((string) $v);
|
||||
|
||||
return $s === '' ? null : $s;
|
||||
};
|
||||
|
||||
$sy = $readSrc($get, 'start_y');
|
||||
$sm = $readSrc($get, 'start_m');
|
||||
$sd = $readSrc($get, 'start_d');
|
||||
$ey = $readSrc($get, 'end_y');
|
||||
$em = $readSrc($get, 'end_m');
|
||||
$ed = $readSrc($get, 'end_d');
|
||||
|
||||
$startDate = null;
|
||||
if ($sy !== null && $sy !== '' && $sm !== null && $sm !== '' && $sd !== null && $sd !== '') {
|
||||
$startDate = parse_ymd_from_triple($sy, $sm, $sd);
|
||||
}
|
||||
if ($startDate === null) {
|
||||
$legacyStart = $readSrc($get, 'start_date');
|
||||
$startDate = ($legacyStart !== null && $legacyStart !== '') ? $legacyStart : null;
|
||||
}
|
||||
|
||||
$endDate = null;
|
||||
if ($ey !== null && $ey !== '' && $em !== null && $em !== '' && $ed !== null && $ed !== '') {
|
||||
$endDate = parse_ymd_from_triple($ey, $em, $ed);
|
||||
}
|
||||
if ($endDate === null) {
|
||||
$legacyEnd = $readSrc($get, 'end_date');
|
||||
$endDate = ($legacyEnd !== null && $legacyEnd !== '') ? $legacyEnd : null;
|
||||
}
|
||||
|
||||
$startParts = ['y' => '', 'm' => '', 'd' => ''];
|
||||
$endParts = ['y' => '', 'm' => '', 'd' => ''];
|
||||
if ($startDate !== null && preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $startDate, $m)) {
|
||||
$startParts = ['y' => $m[1], 'm' => (int) $m[2], 'd' => (int) $m[3]];
|
||||
}
|
||||
if ($endDate !== null && preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $endDate, $m)) {
|
||||
$endParts = ['y' => $m[1], 'm' => (int) $m[2], 'd' => (int) $m[3]];
|
||||
}
|
||||
|
||||
$bagKindE = $readSrc($get, 'bag_kind_e');
|
||||
$bagCode = $readSrc($get, 'bag_code');
|
||||
|
||||
$builder = $this->priceModel->where('bp_lg_idx', $lgIdx);
|
||||
if (($startDate !== null && $startDate !== '') || ($endDate !== null && $endDate !== '')) {
|
||||
$qStart = ($startDate !== null && $startDate !== '') ? $startDate : $endDate;
|
||||
$qEnd = ($endDate !== null && $endDate !== '') ? $endDate : $startDate;
|
||||
if (strcmp((string) $qStart, (string) $qEnd) > 0) {
|
||||
[$qStart, $qEnd] = [$qEnd, $qStart];
|
||||
}
|
||||
$builder->where('bp_start_date <=', $qEnd);
|
||||
|
||||
// 기간 필터 (P2-04)
|
||||
$startDate = $this->request->getGet('start_date');
|
||||
$endDate = $this->request->getGet('end_date');
|
||||
if ($startDate) {
|
||||
$builder->where('bp_start_date >=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$builder->groupStart()
|
||||
->where('bp_end_date IS NULL')
|
||||
->orWhere('bp_end_date >=', $qStart)
|
||||
->orWhere('bp_end_date <=', $endDate)
|
||||
->groupEnd();
|
||||
}
|
||||
|
||||
if ($bagKindE !== null && $bagKindE !== '') {
|
||||
$kindE = model(CodeKindModel::class)->where('ck_code', 'E')->first();
|
||||
if ($kindE) {
|
||||
$detailE = model(CodeDetailModel::class)
|
||||
->where('cd_ck_idx', (int) $kindE->ck_idx)
|
||||
->where('cd_code', $bagKindE)
|
||||
->where('cd_state', 1)
|
||||
->first();
|
||||
if ($detailE !== null) {
|
||||
$builder->like('bp_bag_code', $bagKindE, 'after');
|
||||
}
|
||||
}
|
||||
}
|
||||
$list = $builder->orderBy('bp_bag_code', 'ASC')->orderBy('bp_start_date', 'DESC')->paginate(20);
|
||||
$pager = $this->priceModel->pager;
|
||||
|
||||
if ($bagCode !== null && $bagCode !== '') {
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
if ($kindO) {
|
||||
$detailO = model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, $bagCode, $lgIdx);
|
||||
if ($detailO !== null) {
|
||||
$builder->where('bp_bag_code', $bagCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$list = $builder
|
||||
->orderBy('bp_bag_code', 'ASC')
|
||||
->orderBy('bp_start_date', 'DESC')
|
||||
->paginate(20);
|
||||
|
||||
$queryForPager = [];
|
||||
if ($sy !== null && $sm !== null && $sd !== null && $sy !== '' && $sm !== '' && $sd !== '') {
|
||||
$queryForPager['start_y'] = $sy;
|
||||
$queryForPager['start_m'] = $sm;
|
||||
$queryForPager['start_d'] = $sd;
|
||||
}
|
||||
if ($ey !== null && $em !== null && $ed !== null && $ey !== '' && $em !== '' && $ed !== '') {
|
||||
$queryForPager['end_y'] = $ey;
|
||||
$queryForPager['end_m'] = $em;
|
||||
$queryForPager['end_d'] = $ed;
|
||||
}
|
||||
if ($bagKindE !== null && $bagKindE !== '') {
|
||||
$queryForPager['bag_kind_e'] = $bagKindE;
|
||||
}
|
||||
if ($bagCode !== null && $bagCode !== '') {
|
||||
$queryForPager['bag_code'] = $bagCode;
|
||||
}
|
||||
$pagerPath = mgmt_url('bag-prices');
|
||||
if ($queryForPager !== []) {
|
||||
$pagerPath .= '?' . http_build_query($queryForPager);
|
||||
}
|
||||
$this->priceModel->pager->setPath($pagerPath);
|
||||
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kindO
|
||||
? model(CodeDetailModel::class)->getByKind((int) $kindO->ck_idx, true, $lgIdx)
|
||||
: [];
|
||||
$kindE = model(CodeKindModel::class)->where('ck_code', 'E')->first();
|
||||
$bagKindOptions = $kindE
|
||||
? model(CodeDetailModel::class)->getByKind((int) $kindE->ck_idx, true, null)
|
||||
: [];
|
||||
|
||||
return $this->renderWorkPage('봉투 단가 관리', 'admin/bag_price/index', [
|
||||
'list' => $list,
|
||||
'pager' => $this->priceModel->pager,
|
||||
'startParts' => $startParts,
|
||||
'endParts' => $endParts,
|
||||
'dateYearMin' => (int) date('Y') - 12,
|
||||
'dateYearMax' => (int) date('Y') + 2,
|
||||
'bag_kind_e' => $bagKindE,
|
||||
'bag_code' => $bagCode,
|
||||
'bag_codes' => $bagCodes,
|
||||
'bag_kind_options' => $bagKindOptions,
|
||||
return view('admin/layout', [
|
||||
'title' => '봉투 단가 관리',
|
||||
'content' => view('admin/bag_price/index', [
|
||||
'list' => $list,
|
||||
'startDate' => $startDate,
|
||||
'endDate' => $endDate,
|
||||
'pager' => $pager,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -171,18 +60,22 @@ class BagPrice extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(mgmt_url('bag-prices'))->with('error', '지자체를 선택해 주세요.');
|
||||
if (!$lgIdx) {
|
||||
return redirect()->to(site_url('admin/bag-prices'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
// 봉투명 코드(O) 목록
|
||||
$kindModel = model(CodeKindModel::class);
|
||||
$kind = $kindModel->where('ck_code', 'O')->first();
|
||||
$bagCodes = [];
|
||||
$kind = $kindModel->where('ck_code', 'O')->first();
|
||||
$bagCodes = [];
|
||||
if ($kind) {
|
||||
$bagCodes = model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx);
|
||||
$bagCodes = model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true);
|
||||
}
|
||||
|
||||
return $this->renderWorkPage('봉투 단가 등록', 'admin/bag_price/create', ['bagCodes' => $bagCodes]);
|
||||
return view('admin/layout', [
|
||||
'title' => '봉투 단가 등록',
|
||||
'content' => view('admin/bag_price/create', ['bagCodes' => $bagCodes]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store()
|
||||
@@ -203,12 +96,13 @@ class BagPrice extends BaseController
|
||||
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$bagCode = $this->request->getPost('bp_bag_code');
|
||||
// 봉투명 스냅샷
|
||||
$bagCode = $this->request->getPost('bp_bag_code');
|
||||
$kindModel = model(CodeKindModel::class);
|
||||
$kind = $kindModel->where('ck_code', 'O')->first();
|
||||
$bagName = '';
|
||||
$kind = $kindModel->where('ck_code', 'O')->first();
|
||||
$bagName = '';
|
||||
if ($kind) {
|
||||
$detail = model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kind->ck_idx, (string) $bagCode, $lgIdx);
|
||||
$detail = model(CodeDetailModel::class)->where('cd_ck_idx', $kind->ck_idx)->where('cd_code', $bagCode)->first();
|
||||
$bagName = $detail ? $detail->cd_name : '';
|
||||
}
|
||||
|
||||
@@ -226,34 +120,36 @@ class BagPrice extends BaseController
|
||||
'bp_reg_mb_idx' => session()->get('mb_idx'),
|
||||
]);
|
||||
|
||||
return redirect()->to(mgmt_url('bag-prices'))->with('success', '봉투 단가가 등록되었습니다.');
|
||||
return redirect()->to(site_url('admin/bag-prices'))->with('success', '봉투 단가가 등록되었습니다.');
|
||||
}
|
||||
|
||||
public function edit(int $id)
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
$item = $this->priceModel->find($id);
|
||||
if (! $item || (int) $item->bp_lg_idx !== $lgIdx) {
|
||||
return redirect()->to(mgmt_url('bag-prices'))->with('error', '단가 정보를 찾을 수 없습니다.');
|
||||
$item = $this->priceModel->find($id);
|
||||
if (!$item || (int) $item->bp_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(site_url('admin/bag-prices'))->with('error', '단가 정보를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$kindModel = model(CodeKindModel::class);
|
||||
$kind = $kindModel->where('ck_code', 'O')->first();
|
||||
$bagCodes = [];
|
||||
$kind = $kindModel->where('ck_code', 'O')->first();
|
||||
$bagCodes = [];
|
||||
if ($kind) {
|
||||
$bagCodes = model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx);
|
||||
$bagCodes = model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true);
|
||||
}
|
||||
|
||||
return $this->renderWorkPage('봉투 단가 수정', 'admin/bag_price/edit', ['item' => $item, 'bagCodes' => $bagCodes]);
|
||||
return view('admin/layout', [
|
||||
'title' => '봉투 단가 수정',
|
||||
'content' => view('admin/bag_price/edit', ['item' => $item, 'bagCodes' => $bagCodes]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(int $id)
|
||||
{
|
||||
helper('admin');
|
||||
$item = $this->priceModel->find($id);
|
||||
if (! $item || (int) $item->bp_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(mgmt_url('bag-prices'))->with('error', '단가 정보를 찾을 수 없습니다.');
|
||||
if (!$item || (int) $item->bp_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(site_url('admin/bag-prices'))->with('error', '단가 정보를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$rules = [
|
||||
@@ -269,6 +165,7 @@ class BagPrice extends BaseController
|
||||
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
// 이력 기록
|
||||
$db = \Config\Database::connect();
|
||||
$db->transStart();
|
||||
|
||||
@@ -278,12 +175,12 @@ class BagPrice extends BaseController
|
||||
$newVal = (string) $this->request->getPost($field);
|
||||
if ($oldVal !== $newVal) {
|
||||
$this->historyModel->insert([
|
||||
'bph_bp_idx' => $id,
|
||||
'bph_field' => $field,
|
||||
'bph_old_value' => $oldVal,
|
||||
'bph_new_value' => $newVal,
|
||||
'bph_changed_at' => date('Y-m-d H:i:s'),
|
||||
'bph_changed_by' => session()->get('mb_idx'),
|
||||
'bph_bp_idx' => $id,
|
||||
'bph_field' => $field,
|
||||
'bph_old_value' => $oldVal,
|
||||
'bph_new_value' => $newVal,
|
||||
'bph_changed_at'=> date('Y-m-d H:i:s'),
|
||||
'bph_changed_by'=> session()->get('mb_idx'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -300,32 +197,34 @@ class BagPrice extends BaseController
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->to(mgmt_url('bag-prices'))->with('success', '봉투 단가가 수정되었습니다.');
|
||||
return redirect()->to(site_url('admin/bag-prices'))->with('success', '봉투 단가가 수정되었습니다.');
|
||||
}
|
||||
|
||||
public function delete(int $id)
|
||||
{
|
||||
helper('admin');
|
||||
$item = $this->priceModel->find($id);
|
||||
if (! $item || (int) $item->bp_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(mgmt_url('bag-prices'))->with('error', '단가 정보를 찾을 수 없습니다.');
|
||||
if (!$item || (int) $item->bp_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(site_url('admin/bag-prices'))->with('error', '단가 정보를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$this->priceModel->delete($id);
|
||||
|
||||
return redirect()->to(mgmt_url('bag-prices'))->with('success', '봉투 단가가 삭제되었습니다.');
|
||||
return redirect()->to(site_url('admin/bag-prices'))->with('success', '봉투 단가가 삭제되었습니다.');
|
||||
}
|
||||
|
||||
public function history(int $bpIdx)
|
||||
{
|
||||
helper('admin');
|
||||
$item = $this->priceModel->find($bpIdx);
|
||||
if (! $item || (int) $item->bp_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(mgmt_url('bag-prices'))->with('error', '단가 정보를 찾을 수 없습니다.');
|
||||
if (!$item || (int) $item->bp_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(site_url('admin/bag-prices'))->with('error', '단가 정보를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$list = $this->historyModel->where('bph_bp_idx', $bpIdx)->orderBy('bph_changed_at', 'DESC')->findAll();
|
||||
|
||||
return $this->renderWorkPage('단가 변경 이력 — ' . $item->bp_bag_name, 'admin/bag_price/history', ['item' => $item, 'list' => $list]);
|
||||
return view('admin/layout', [
|
||||
'title' => '단가 변경 이력 — ' . $item->bp_bag_name,
|
||||
'content' => view('admin/bag_price/history', ['item' => $item, 'list' => $list]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,37 +22,36 @@ class BagReceiving extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$builder = $this->recvModel->where('br_lg_idx', $lgIdx);
|
||||
$startDate = $this->request->getGet('start_date');
|
||||
$endDate = $this->request->getGet('end_date');
|
||||
if ($startDate) {
|
||||
$builder->where('br_receive_date >=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$builder->where('br_receive_date <=', $endDate);
|
||||
}
|
||||
if ($startDate) $builder->where('br_receive_date >=', $startDate);
|
||||
if ($endDate) $builder->where('br_receive_date <=', $endDate);
|
||||
|
||||
$list = $builder->orderBy('br_receive_date', 'DESC')->orderBy('br_idx', 'DESC')->paginate(20);
|
||||
$list = $builder->orderBy('br_receive_date', 'DESC')->orderBy('br_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->recvModel->pager;
|
||||
|
||||
return $this->renderWorkPage('입고 현황', 'admin/bag_receiving/index', compact('list', 'startDate', 'endDate', 'pager'));
|
||||
return view('admin/layout', [
|
||||
'title' => '입고 현황',
|
||||
'content' => view('admin/bag_receiving/index', compact('list', 'startDate', 'endDate', 'pager')),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(mgmt_url('bag-receivings'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin/bag-receivings'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$orders = model(BagOrderModel::class)->where('bo_lg_idx', $lgIdx)->whereLatestHead($lgIdx)->where('bo_status', 'normal')->orderBy('bo_order_date', 'DESC')->findAll();
|
||||
// 미입고 발주 목록
|
||||
$orders = model(BagOrderModel::class)->where('bo_lg_idx', $lgIdx)->where('bo_status', 'normal')->orderBy('bo_order_date', 'DESC')->findAll();
|
||||
|
||||
return $this->renderWorkPage('입고 처리', 'admin/bag_receiving/create', compact('orders'));
|
||||
return view('admin/layout', [
|
||||
'title' => '입고 처리',
|
||||
'content' => view('admin/bag_receiving/create', compact('orders')),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store()
|
||||
@@ -74,12 +73,14 @@ class BagReceiving extends BaseController
|
||||
$bagCode = $this->request->getPost('br_bag_code');
|
||||
$qtyBox = (int) $this->request->getPost('br_qty_box');
|
||||
|
||||
// 포장단위로 낱장 환산
|
||||
$unit = model(\App\Models\PackagingUnitModel::class)->where('pu_lg_idx', $lgIdx)->where('pu_bag_code', $bagCode)->where('pu_state', 1)->first();
|
||||
$totalPerBox = $unit ? (int) $unit->pu_total_per_box : 1;
|
||||
$qtySheet = $qtyBox * $totalPerBox;
|
||||
$qtySheet = $qtyBox * $totalPerBox;
|
||||
|
||||
$kindO = model(\App\Models\CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(\App\Models\CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, (string) $bagCode, $lgIdx) : null;
|
||||
// 봉투명
|
||||
$kindO = model(\App\Models\CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(\App\Models\CodeDetailModel::class)->where('cd_ck_idx', $kindO->ck_idx)->where('cd_code', $bagCode)->first() : null;
|
||||
$bagName = $detail ? $detail->cd_name : '';
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
@@ -99,10 +100,11 @@ class BagReceiving extends BaseController
|
||||
'br_regdate' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
// 재고 가산
|
||||
model(BagInventoryModel::class)->adjustQty($lgIdx, $bagCode, $bagName, $qtySheet);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->to(mgmt_url('bag-receivings'))->with('success', '입고 처리되었습니다. (' . $bagName . ' ' . $qtyBox . '박스)');
|
||||
return redirect()->to(site_url('admin/bag-receivings'))->with('success', '입고 처리되었습니다. (' . $bagName . ' ' . $qtyBox . '박스)');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,56 +23,45 @@ class BagSale extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$builder = $this->saleModel->where('bs_lg_idx', $lgIdx);
|
||||
$startDate = $this->request->getGet('start_date');
|
||||
$endDate = $this->request->getGet('end_date');
|
||||
$type = $this->request->getGet('type');
|
||||
if ($startDate) {
|
||||
$builder->where('bs_sale_date >=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$builder->where('bs_sale_date <=', $endDate);
|
||||
}
|
||||
if ($type) {
|
||||
$builder->where('bs_type', $type);
|
||||
}
|
||||
if ($startDate) $builder->where('bs_sale_date >=', $startDate);
|
||||
if ($endDate) $builder->where('bs_sale_date <=', $endDate);
|
||||
if ($type) $builder->where('bs_type', $type);
|
||||
|
||||
$list = $builder->orderBy('bs_sale_date', 'DESC')->orderBy('bs_idx', 'DESC')->paginate(20);
|
||||
$list = $builder->orderBy('bs_sale_date', 'DESC')->orderBy('bs_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->saleModel->pager;
|
||||
|
||||
return $this->renderWorkPage('판매/반품 관리', 'admin/bag_sale/index', compact('list', 'startDate', 'endDate', 'type', 'pager'));
|
||||
return view('admin/layout', [
|
||||
'title' => '판매/반품 관리',
|
||||
'content' => view('admin/bag_sale/index', compact('list', 'startDate', 'endDate', 'type', 'pager')),
|
||||
]);
|
||||
}
|
||||
|
||||
public function export()
|
||||
{
|
||||
helper(['admin', 'export']);
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(mgmt_url('bag-sales'))->with('error', '지자체를 선택해 주세요.');
|
||||
if (!$lgIdx) {
|
||||
return redirect()->to(site_url('admin/bag-sales'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$builder = $this->saleModel->where('bs_lg_idx', $lgIdx);
|
||||
$startDate = $this->request->getGet('start_date');
|
||||
$endDate = $this->request->getGet('end_date');
|
||||
$type = $this->request->getGet('type');
|
||||
if ($startDate) {
|
||||
$builder->where('bs_sale_date >=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$builder->where('bs_sale_date <=', $endDate);
|
||||
}
|
||||
if ($type) {
|
||||
$builder->where('bs_type', $type);
|
||||
}
|
||||
if ($startDate) $builder->where('bs_sale_date >=', $startDate);
|
||||
if ($endDate) $builder->where('bs_sale_date <=', $endDate);
|
||||
if ($type) $builder->where('bs_type', $type);
|
||||
|
||||
$list = $builder->orderBy('bs_sale_date', 'DESC')->orderBy('bs_idx', 'DESC')->findAll();
|
||||
|
||||
$typeMap = ['sale' => '판매', 'return' => '반품', 'cancel' => '취소'];
|
||||
$rows = [];
|
||||
$rows = [];
|
||||
foreach ($list as $row) {
|
||||
$rows[] = [
|
||||
$row->bs_idx,
|
||||
@@ -98,15 +87,16 @@ class BagSale extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(mgmt_url('bag-sales'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin/bag-sales'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$shops = model(DesignatedShopModel::class)->where('ds_lg_idx', $lgIdx)->where('ds_state', 1)->findAll();
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx) : [];
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true) : [];
|
||||
|
||||
return $this->renderWorkPage('판매 등록', 'admin/bag_sale/create', compact('shops', 'bagCodes'));
|
||||
return view('admin/layout', [
|
||||
'title' => '판매 등록',
|
||||
'content' => view('admin/bag_sale/create', compact('shops', 'bagCodes')),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store()
|
||||
@@ -130,10 +120,10 @@ class BagSale extends BaseController
|
||||
$qty = (int) $this->request->getPost('bs_qty');
|
||||
$type = $this->request->getPost('bs_type');
|
||||
|
||||
$shop = model(DesignatedShopModel::class)->find($dsIdx);
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, (string) $bagCode, $lgIdx) : null;
|
||||
$price = model(BagPriceModel::class)->latestActiveByBagCode($lgIdx, (string) $bagCode);
|
||||
$shop = model(DesignatedShopModel::class)->find($dsIdx);
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(CodeDetailModel::class)->where('cd_ck_idx', $kindO->ck_idx)->where('cd_code', $bagCode)->first() : null;
|
||||
$price = model(BagPriceModel::class)->where('bp_lg_idx', $lgIdx)->where('bp_bag_code', $bagCode)->where('bp_state', 1)->first();
|
||||
$unitPrice = $price ? (float) $price->bp_consumer : 0;
|
||||
|
||||
$actualQty = ($type === 'return') ? -$qty : $qty;
|
||||
@@ -142,30 +132,31 @@ class BagSale extends BaseController
|
||||
$db->transStart();
|
||||
|
||||
$saleData = [
|
||||
'bs_lg_idx' => $lgIdx,
|
||||
'bs_ds_idx' => $dsIdx,
|
||||
'bs_ds_name' => $shop ? $shop->ds_name : '',
|
||||
'bs_sale_date' => $this->request->getPost('bs_sale_date'),
|
||||
'bs_bag_code' => $bagCode,
|
||||
'bs_bag_name' => $detail ? $detail->cd_name : '',
|
||||
'bs_qty' => $actualQty,
|
||||
'bs_unit_price' => $unitPrice,
|
||||
'bs_amount' => $unitPrice * abs($actualQty),
|
||||
'bs_type' => $type,
|
||||
'bs_regdate' => date('Y-m-d H:i:s'),
|
||||
'bs_lg_idx' => $lgIdx,
|
||||
'bs_ds_idx' => $dsIdx,
|
||||
'bs_ds_name' => $shop ? $shop->ds_name : '',
|
||||
'bs_sale_date' => $this->request->getPost('bs_sale_date'),
|
||||
'bs_bag_code' => $bagCode,
|
||||
'bs_bag_name' => $detail ? $detail->cd_name : '',
|
||||
'bs_qty' => $actualQty,
|
||||
'bs_unit_price'=> $unitPrice,
|
||||
'bs_amount' => $unitPrice * abs($actualQty),
|
||||
'bs_type' => $type,
|
||||
'bs_regdate' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
$this->saleModel->insert($saleData);
|
||||
$bsIdx = (int) $this->saleModel->getInsertID();
|
||||
|
||||
// CT-05: 감사 로그
|
||||
helper('audit');
|
||||
audit_log('create', 'bag_sale', $bsIdx, null, array_merge($saleData, ['bs_idx' => $bsIdx]));
|
||||
|
||||
// 재고 감산(판매) / 가산(반품)
|
||||
model(BagInventoryModel::class)->adjustQty($lgIdx, $bagCode, $detail ? $detail->cd_name : '', -$actualQty);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
$msg = ($type === 'sale') ? '판매 처리되었습니다.' : '반품 처리되었습니다.';
|
||||
|
||||
return redirect()->to(mgmt_url('bag-sales'))->with('success', $msg);
|
||||
return redirect()->to(site_url('admin/bag-sales'))->with('success', $msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\CodeKindModel;
|
||||
use App\Models\CodeDetailModel;
|
||||
use App\Models\LocalGovernmentModel;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use Config\Roles;
|
||||
|
||||
class CodeDetail extends BaseController
|
||||
{
|
||||
@@ -22,57 +17,41 @@ class CodeDetail extends BaseController
|
||||
$this->detailModel = model(CodeDetailModel::class);
|
||||
}
|
||||
|
||||
private function redirectIfCannotManageCodeMaster(): ?RedirectResponse
|
||||
public function index(int $ckIdx)
|
||||
{
|
||||
if (! Roles::canManageCodeMaster((int) session()->get('mb_level'))) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 관리 권한이 없습니다.');
|
||||
$kind = $this->kindModel->find($ckIdx);
|
||||
if ($kind === null) {
|
||||
return redirect()->to(site_url('admin/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
$list = $this->detailModel->where('cd_ck_idx', $ckIdx)->orderBy('cd_sort', 'ASC')->paginate(20);
|
||||
$pager = $this->detailModel->pager;
|
||||
|
||||
/** @deprecated 사이트 URL 유지용 — 세부 목록은 /bag/code-details/{ck_idx} */
|
||||
public function index(int $ckIdx): RedirectResponse
|
||||
{
|
||||
return redirect()->to(site_url('bag/code-details/' . $ckIdx));
|
||||
return view('admin/layout', [
|
||||
'title' => '세부코드 관리 — ' . $kind->ck_name . ' (' . $kind->ck_code . ')',
|
||||
'content' => view('admin/code_detail/index', [
|
||||
'kind' => $kind,
|
||||
'list' => $list,
|
||||
'pager' => $pager,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(int $ckIdx)
|
||||
{
|
||||
if ($r = $this->redirectIfCannotManageCodeMaster()) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
$kind = $this->kindModel->find($ckIdx);
|
||||
if ($kind === null) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
|
||||
return redirect()->to(site_url('admin/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$level = (int) session()->get('mb_level');
|
||||
$canPlatformScope = Roles::isSuperAdminEquivalent($level);
|
||||
$govs = $canPlatformScope
|
||||
? model(LocalGovernmentModel::class)->where('lg_state', 1)->orderBy('lg_name', 'ASC')->findAll()
|
||||
: [];
|
||||
|
||||
helper('admin');
|
||||
|
||||
return view('admin/layout', [
|
||||
'title' => '세부코드 등록 — ' . $kind->ck_name,
|
||||
'content' => view('admin/code_detail/create', [
|
||||
'kind' => $kind,
|
||||
'canPlatformScope' => $canPlatformScope,
|
||||
'localGovernments' => $govs,
|
||||
'effectiveLgIdx' => admin_effective_lg_idx(),
|
||||
]),
|
||||
'content' => view('admin/code_detail/create', ['kind' => $kind]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
if ($r = $this->redirectIfCannotManageCodeMaster()) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'cd_ck_idx' => 'required|is_natural_no_zero',
|
||||
'cd_code' => 'required|max_length[50]',
|
||||
@@ -85,73 +64,24 @@ class CodeDetail extends BaseController
|
||||
}
|
||||
|
||||
$ckIdx = (int) $this->request->getPost('cd_ck_idx');
|
||||
$kind = $this->kindModel->find($ckIdx);
|
||||
if ($kind === null) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
helper('admin');
|
||||
$level = (int) session()->get('mb_level');
|
||||
|
||||
if (Roles::isSuperAdminEquivalent($level)) {
|
||||
$scope = $this->request->getPost('cd_scope') === 'local' ? 'local' : 'platform';
|
||||
if ($scope === 'platform') {
|
||||
$cdSource = 'platform';
|
||||
$cdLgIdx = 0;
|
||||
} else {
|
||||
$cdLgIdx = (int) $this->request->getPost('cd_lg_idx');
|
||||
if ($cdLgIdx < 1) {
|
||||
return redirect()->back()->withInput()->with('error', '지자체 전용인 경우 소속 지자체를 선택해 주세요.');
|
||||
}
|
||||
$gov = model(LocalGovernmentModel::class)->find($cdLgIdx);
|
||||
if ($gov === null) {
|
||||
return redirect()->back()->withInput()->with('error', '유효하지 않은 지자체입니다.');
|
||||
}
|
||||
$cdSource = 'local';
|
||||
}
|
||||
} else {
|
||||
$lg = admin_effective_lg_idx();
|
||||
if ($lg === null || (int) $lg < 1) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('error', '지자체를 선택한 뒤 등록해 주세요.');
|
||||
}
|
||||
$cdSource = 'local';
|
||||
$cdLgIdx = (int) $lg;
|
||||
}
|
||||
|
||||
$cdCode = (string) $this->request->getPost('cd_code');
|
||||
$dup = $this->detailModel->where('cd_ck_idx', $ckIdx)->where('cd_code', $cdCode)->where('cd_lg_idx', $cdLgIdx)->first();
|
||||
if ($dup !== null) {
|
||||
return redirect()->back()->withInput()->with('error', '같은 종류·코드값·소속 범위에 이미 등록된 행이 있습니다.');
|
||||
}
|
||||
|
||||
$this->detailModel->insert([
|
||||
'cd_ck_idx' => $ckIdx,
|
||||
'cd_source' => $cdSource,
|
||||
'cd_lg_idx' => $cdLgIdx,
|
||||
'cd_code' => $cdCode,
|
||||
'cd_code' => $this->request->getPost('cd_code'),
|
||||
'cd_name' => $this->request->getPost('cd_name'),
|
||||
'cd_sort' => (int) ($this->request->getPost('cd_sort') ?: 0),
|
||||
'cd_state' => 1,
|
||||
'cd_regdate' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
return redirect()->to(site_url('bag/code-details/' . $ckIdx))->with('success', '세부코드가 등록되었습니다.');
|
||||
return redirect()->to(site_url('admin/code-details/' . $ckIdx))->with('success', '세부코드가 등록되었습니다.');
|
||||
}
|
||||
|
||||
public function edit(int $id)
|
||||
{
|
||||
if ($r = $this->redirectIfCannotManageCodeMaster()) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
$item = $this->detailModel->find($id);
|
||||
if ($item === null) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
helper('admin');
|
||||
if (! Roles::canEditCodeDetailRow((int) session()->get('mb_level'), $item, admin_effective_lg_idx())) {
|
||||
return redirect()->to(site_url('bag/code-details/' . $item->cd_ck_idx))->with('error', '이 세부코드를 수정할 권한이 없습니다.');
|
||||
return redirect()->to(site_url('admin/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$kind = $this->kindModel->find($item->cd_ck_idx);
|
||||
@@ -167,18 +97,9 @@ class CodeDetail extends BaseController
|
||||
|
||||
public function update(int $id)
|
||||
{
|
||||
if ($r = $this->redirectIfCannotManageCodeMaster()) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
$item = $this->detailModel->find($id);
|
||||
if ($item === null) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
helper('admin');
|
||||
if (! Roles::canEditCodeDetailRow((int) session()->get('mb_level'), $item, admin_effective_lg_idx())) {
|
||||
return redirect()->to(site_url('bag/code-details/' . $item->cd_ck_idx))->with('error', '이 세부코드를 수정할 권한이 없습니다.');
|
||||
return redirect()->to(site_url('admin/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$rules = [
|
||||
@@ -197,28 +118,19 @@ class CodeDetail extends BaseController
|
||||
'cd_state' => (int) $this->request->getPost('cd_state'),
|
||||
]);
|
||||
|
||||
return redirect()->to(site_url('bag/code-details/' . $item->cd_ck_idx))->with('success', '세부코드가 수정되었습니다.');
|
||||
return redirect()->to(site_url('admin/code-details/' . $item->cd_ck_idx))->with('success', '세부코드가 수정되었습니다.');
|
||||
}
|
||||
|
||||
public function delete(int $id)
|
||||
{
|
||||
if ($r = $this->redirectIfCannotManageCodeMaster()) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
$item = $this->detailModel->find($id);
|
||||
if ($item === null) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
helper('admin');
|
||||
if (! Roles::canEditCodeDetailRow((int) session()->get('mb_level'), $item, admin_effective_lg_idx())) {
|
||||
return redirect()->to(site_url('bag/code-details/' . $item->cd_ck_idx))->with('error', '이 세부코드를 삭제할 권한이 없습니다.');
|
||||
return redirect()->to(site_url('admin/code-kinds'))->with('error', '세부코드를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$ckIdx = $item->cd_ck_idx;
|
||||
$this->detailModel->delete($id);
|
||||
|
||||
return redirect()->to(site_url('bag/code-details/' . $ckIdx))->with('success', '세부코드가 삭제되었습니다.');
|
||||
return redirect()->to(site_url('admin/code-details/' . $ckIdx))->with('success', '세부코드가 삭제되었습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\CodeKindModel;
|
||||
use App\Models\CodeDetailModel;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use Config\Roles;
|
||||
|
||||
class CodeKind extends BaseController
|
||||
@@ -19,21 +16,30 @@ class CodeKind extends BaseController
|
||||
$this->kindModel = model(CodeKindModel::class);
|
||||
}
|
||||
|
||||
private function redirectIfCannotManageCodeKindMaster(): ?RedirectResponse
|
||||
public function index()
|
||||
{
|
||||
if (! Roles::canManageCodeKindMaster((int) session()->get('mb_level'))) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 종류는 super admin·본부 관리자만 관리할 수 있습니다.');
|
||||
$list = $this->kindModel->orderBy('ck_code', 'ASC')->paginate(20);
|
||||
$pager = $this->kindModel->pager;
|
||||
|
||||
// 세부코드 수 매핑
|
||||
$detailModel = model(CodeDetailModel::class);
|
||||
$countMap = [];
|
||||
foreach ($list as $row) {
|
||||
$countMap[$row->ck_idx] = $detailModel->where('cd_ck_idx', $row->ck_idx)->countAllResults(false);
|
||||
}
|
||||
|
||||
return null;
|
||||
return view('admin/layout', [
|
||||
'title' => '기본코드 종류 관리',
|
||||
'content' => view('admin/code_kind/index', [
|
||||
'list' => $list,
|
||||
'countMap' => $countMap,
|
||||
'pager' => $pager,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
if ($r = $this->redirectIfCannotManageCodeKindMaster()) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
return view('admin/layout', [
|
||||
'title' => '기본코드 종류 등록',
|
||||
'content' => view('admin/code_kind/create'),
|
||||
@@ -42,10 +48,6 @@ class CodeKind extends BaseController
|
||||
|
||||
public function store()
|
||||
{
|
||||
if ($r = $this->redirectIfCannotManageCodeKindMaster()) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'ck_code' => 'required|max_length[20]|is_unique[code_kind.ck_code]',
|
||||
'ck_name' => 'required|max_length[100]',
|
||||
@@ -62,18 +64,14 @@ class CodeKind extends BaseController
|
||||
'ck_regdate' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('success', '코드 종류가 등록되었습니다.');
|
||||
return redirect()->to(site_url('admin/code-kinds'))->with('success', '코드 종류가 등록되었습니다.');
|
||||
}
|
||||
|
||||
public function edit(int $id)
|
||||
{
|
||||
if ($r = $this->redirectIfCannotManageCodeKindMaster()) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
$item = $this->kindModel->find($id);
|
||||
if ($item === null) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
|
||||
return redirect()->to(site_url('admin/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
return view('admin/layout', [
|
||||
@@ -84,13 +82,9 @@ class CodeKind extends BaseController
|
||||
|
||||
public function update(int $id)
|
||||
{
|
||||
if ($r = $this->redirectIfCannotManageCodeKindMaster()) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
$item = $this->kindModel->find($id);
|
||||
if ($item === null) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
|
||||
return redirect()->to(site_url('admin/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$rules = [
|
||||
@@ -107,28 +101,24 @@ class CodeKind extends BaseController
|
||||
'ck_state' => (int) $this->request->getPost('ck_state'),
|
||||
]);
|
||||
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('success', '코드 종류가 수정되었습니다.');
|
||||
return redirect()->to(site_url('admin/code-kinds'))->with('success', '코드 종류가 수정되었습니다.');
|
||||
}
|
||||
|
||||
public function delete(int $id)
|
||||
{
|
||||
if ($r = $this->redirectIfCannotManageCodeKindMaster()) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
$item = $this->kindModel->find($id);
|
||||
if ($item === null) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
|
||||
return redirect()->to(site_url('admin/code-kinds'))->with('error', '코드 종류를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 세부코드가 있으면 삭제 불가
|
||||
$detailCount = model(CodeDetailModel::class)->where('cd_ck_idx', $id)->countAllResults();
|
||||
if ($detailCount > 0) {
|
||||
return redirect()->to(site_url('bag/code-kinds'))
|
||||
return redirect()->to(site_url('admin/code-kinds'))
|
||||
->with('error', '세부코드가 ' . $detailCount . '개 존재하여 삭제할 수 없습니다. 먼저 세부코드를 삭제해 주세요.');
|
||||
}
|
||||
|
||||
$this->kindModel->delete($id);
|
||||
|
||||
return redirect()->to(site_url('bag/code-kinds'))->with('success', '코드 종류가 삭제되었습니다.');
|
||||
return redirect()->to(site_url('admin/code-kinds'))->with('success', '코드 종류가 삭제되었습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,6 @@ class Company extends BaseController
|
||||
{
|
||||
private CompanyModel $model;
|
||||
|
||||
private function companyTypeOptions(): array
|
||||
{
|
||||
return ['협회', '제작업체', '회수업체'];
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->model = model(CompanyModel::class);
|
||||
@@ -27,32 +22,58 @@ class Company extends BaseController
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$companyType = trim((string) ($this->request->getGet('cp_type') ?? ''));
|
||||
$typeOptions = $this->companyTypeOptions();
|
||||
if ($this->request->is('post')) {
|
||||
$searchField = trim((string) ($this->request->getPost('search_field') ?? ''));
|
||||
$searchQuery = trim((string) ($this->request->getPost('search_query') ?? ''));
|
||||
session()->setFlashdata('company_search', [
|
||||
'search_field' => $searchField,
|
||||
'search_query' => $searchQuery,
|
||||
]);
|
||||
|
||||
return redirect()->to(mgmt_url('companies'));
|
||||
}
|
||||
|
||||
$fromGetField = trim((string) ($this->request->getGet('search_field') ?? ''));
|
||||
$fromGetQuery = trim((string) ($this->request->getGet('search_query') ?? ''));
|
||||
$flash = session()->getFlashdata('company_search');
|
||||
if ($fromGetField !== '' || $fromGetQuery !== '') {
|
||||
$searchField = $fromGetField;
|
||||
$searchQuery = $fromGetQuery;
|
||||
} elseif (is_array($flash)) {
|
||||
$searchField = trim((string) ($flash['search_field'] ?? ''));
|
||||
$searchQuery = trim((string) ($flash['search_query'] ?? ''));
|
||||
} else {
|
||||
$searchField = '';
|
||||
$searchQuery = '';
|
||||
}
|
||||
|
||||
$allowedFields = ['cp_idx', 'cp_type', 'cp_name', 'cp_biz_no', 'cp_rep_name', 'cp_tel', 'cp_addr'];
|
||||
if (! in_array($searchField, $allowedFields, true)) {
|
||||
$searchField = 'cp_name';
|
||||
}
|
||||
|
||||
$builder = $this->model->where('cp_lg_idx', $lgIdx);
|
||||
if ($companyType !== '' && in_array($companyType, $typeOptions, true)) {
|
||||
$builder->where('cp_type', $companyType);
|
||||
if ($searchQuery !== '') {
|
||||
if ($searchField === 'cp_idx') {
|
||||
if (ctype_digit($searchQuery)) {
|
||||
$builder->where('cp_idx', (int) $searchQuery);
|
||||
} else {
|
||||
$builder->where('cp_idx', 0);
|
||||
}
|
||||
} else {
|
||||
$builder->like($searchField, $searchQuery);
|
||||
}
|
||||
}
|
||||
|
||||
$list = $builder->orderBy('cp_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->model->pager;
|
||||
|
||||
$queryForPager = [];
|
||||
if ($companyType !== '' && in_array($companyType, $typeOptions, true)) {
|
||||
$queryForPager['cp_type'] = $companyType;
|
||||
}
|
||||
$pagerPath = mgmt_url('companies');
|
||||
if ($queryForPager !== []) {
|
||||
$pagerPath .= '?' . http_build_query($queryForPager);
|
||||
}
|
||||
$pager->setPath($pagerPath);
|
||||
$pager->setPath('bag/companies');
|
||||
|
||||
return $this->renderWorkPage('업체 관리', 'admin/company/index', [
|
||||
'list' => $list,
|
||||
'pager' => $pager,
|
||||
'cpType' => $companyType,
|
||||
'typeOptions' => $typeOptions,
|
||||
'list' => $list,
|
||||
'pager' => $pager,
|
||||
'search_field' => $searchField,
|
||||
'search_query' => $searchQuery,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
|
||||
class Dashboard extends BaseController
|
||||
{
|
||||
@@ -23,71 +22,65 @@ class Dashboard extends BaseController
|
||||
'issue_count_month'=> 0,
|
||||
'recent_orders' => [],
|
||||
'recent_sales' => [],
|
||||
'stats_unavailable'=> false,
|
||||
];
|
||||
|
||||
if ($lgIdx) {
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
try {
|
||||
// 총 발주 건수/금액
|
||||
$orderStats = $db->query("
|
||||
SELECT COUNT(*) as cnt,
|
||||
COALESCE(SUM(sub.total_amt), 0) as total_amount
|
||||
FROM bag_order bo
|
||||
LEFT JOIN (
|
||||
SELECT boi_bo_idx, SUM(boi_amount) as total_amt
|
||||
FROM bag_order_item GROUP BY boi_bo_idx
|
||||
) sub ON sub.boi_bo_idx = bo.bo_idx
|
||||
WHERE bo.bo_lg_idx = ? AND bo.bo_status = 'normal'
|
||||
", [$lgIdx])->getRow();
|
||||
$stats['order_count'] = (int) ($orderStats->cnt ?? 0);
|
||||
$stats['order_amount'] = (int) ($orderStats->total_amount ?? 0);
|
||||
// 총 발주 건수/금액
|
||||
$orderStats = $db->query("
|
||||
SELECT COUNT(*) as cnt,
|
||||
COALESCE(SUM(sub.total_amt), 0) as total_amount
|
||||
FROM bag_order bo
|
||||
LEFT JOIN (
|
||||
SELECT boi_bo_idx, SUM(boi_amount) as total_amt
|
||||
FROM bag_order_item GROUP BY boi_bo_idx
|
||||
) sub ON sub.boi_bo_idx = bo.bo_idx
|
||||
WHERE bo.bo_lg_idx = ? AND bo.bo_status = 'normal'
|
||||
", [$lgIdx])->getRow();
|
||||
$stats['order_count'] = (int) ($orderStats->cnt ?? 0);
|
||||
$stats['order_amount'] = (int) ($orderStats->total_amount ?? 0);
|
||||
|
||||
// 총 판매 건수/금액
|
||||
$saleStats = $db->query("
|
||||
SELECT COUNT(*) as cnt, COALESCE(SUM(bs_amount), 0) as total_amount
|
||||
FROM bag_sale
|
||||
WHERE bs_lg_idx = ? AND bs_type = 'sale'
|
||||
", [$lgIdx])->getRow();
|
||||
$stats['sale_count'] = (int) ($saleStats->cnt ?? 0);
|
||||
$stats['sale_amount'] = (int) ($saleStats->total_amount ?? 0);
|
||||
// 총 판매 건수/금액
|
||||
$saleStats = $db->query("
|
||||
SELECT COUNT(*) as cnt, COALESCE(SUM(bs_amount), 0) as total_amount
|
||||
FROM bag_sale
|
||||
WHERE bs_lg_idx = ? AND bs_type = 'sale'
|
||||
", [$lgIdx])->getRow();
|
||||
$stats['sale_count'] = (int) ($saleStats->cnt ?? 0);
|
||||
$stats['sale_amount'] = (int) ($saleStats->total_amount ?? 0);
|
||||
|
||||
// 현재 재고 품목 수
|
||||
$invCount = $db->query("
|
||||
SELECT COUNT(*) as cnt FROM bag_inventory WHERE bi_lg_idx = ? AND bi_qty > 0
|
||||
", [$lgIdx])->getRow();
|
||||
$stats['inventory_count'] = (int) ($invCount->cnt ?? 0);
|
||||
// 현재 재고 품목 수
|
||||
$invCount = $db->query("
|
||||
SELECT COUNT(*) as cnt FROM bag_inventory WHERE bi_lg_idx = ? AND bi_qty > 0
|
||||
", [$lgIdx])->getRow();
|
||||
$stats['inventory_count'] = (int) ($invCount->cnt ?? 0);
|
||||
|
||||
// 이번 달 불출 건수
|
||||
$monthStart = date('Y-m-01');
|
||||
$issueCount = $db->query("
|
||||
SELECT COUNT(*) as cnt FROM bag_issue
|
||||
WHERE bi2_lg_idx = ? AND bi2_status = 'normal' AND bi2_issue_date >= ?
|
||||
", [$lgIdx, $monthStart])->getRow();
|
||||
$stats['issue_count_month'] = (int) ($issueCount->cnt ?? 0);
|
||||
// 이번 달 불출 건수
|
||||
$monthStart = date('Y-m-01');
|
||||
$issueCount = $db->query("
|
||||
SELECT COUNT(*) as cnt FROM bag_issue
|
||||
WHERE bi2_lg_idx = ? AND bi2_status = 'normal' AND bi2_issue_date >= ?
|
||||
", [$lgIdx, $monthStart])->getRow();
|
||||
$stats['issue_count_month'] = (int) ($issueCount->cnt ?? 0);
|
||||
|
||||
// 최근 발주 5건
|
||||
$stats['recent_orders'] = $db->query("
|
||||
SELECT bo_idx, bo_lot_no, bo_order_date, bo_status
|
||||
FROM bag_order
|
||||
WHERE bo_lg_idx = ?
|
||||
ORDER BY bo_order_date DESC, bo_idx DESC
|
||||
LIMIT 5
|
||||
", [$lgIdx])->getResult();
|
||||
// 최근 발주 5건
|
||||
$stats['recent_orders'] = $db->query("
|
||||
SELECT bo_idx, bo_lot_no, bo_order_date, bo_status
|
||||
FROM bag_order
|
||||
WHERE bo_lg_idx = ?
|
||||
ORDER BY bo_order_date DESC, bo_idx DESC
|
||||
LIMIT 5
|
||||
", [$lgIdx])->getResult();
|
||||
|
||||
// 최근 판매 5건
|
||||
$stats['recent_sales'] = $db->query("
|
||||
SELECT bs_idx, bs_ds_name, bs_bag_name, bs_qty, bs_amount, bs_sale_date, bs_type
|
||||
FROM bag_sale
|
||||
WHERE bs_lg_idx = ?
|
||||
ORDER BY bs_sale_date DESC, bs_idx DESC
|
||||
LIMIT 5
|
||||
", [$lgIdx])->getResult();
|
||||
} catch (DatabaseException $e) {
|
||||
$stats['stats_unavailable'] = true;
|
||||
log_message('error', '[Dashboard] 통계 조회 실패(테이블 미생성 등): ' . $e->getMessage());
|
||||
}
|
||||
// 최근 판매 5건
|
||||
$stats['recent_sales'] = $db->query("
|
||||
SELECT bs_idx, bs_ds_name, bs_bag_name, bs_qty, bs_amount, bs_sale_date, bs_type
|
||||
FROM bag_sale
|
||||
WHERE bs_lg_idx = ?
|
||||
ORDER BY bs_sale_date DESC, bs_idx DESC
|
||||
LIMIT 5
|
||||
", [$lgIdx])->getResult();
|
||||
}
|
||||
|
||||
return view('admin/layout', [
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,10 +33,101 @@ class FreeRecipient extends BaseController
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$list = $this->model->where('fr_lg_idx', $lgIdx)->orderBy('fr_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->model->pager;
|
||||
if ($this->request->is('post')) {
|
||||
$searchField = trim((string) ($this->request->getPost('search_field') ?? ''));
|
||||
$searchQuery = trim((string) ($this->request->getPost('search_query') ?? ''));
|
||||
session()->setFlashdata('free_recipient_search', [
|
||||
'search_field' => $searchField,
|
||||
'search_query' => $searchQuery,
|
||||
]);
|
||||
|
||||
return $this->renderWorkPage('무료용 대상자 관리', 'admin/free_recipient/index', ['list' => $list, 'pager' => $pager]);
|
||||
return redirect()->to(mgmt_url('free-recipients'));
|
||||
}
|
||||
|
||||
$fromGetField = trim((string) ($this->request->getGet('search_field') ?? ''));
|
||||
$fromGetQuery = trim((string) ($this->request->getGet('search_query') ?? ''));
|
||||
$flash = session()->getFlashdata('free_recipient_search');
|
||||
if ($fromGetField !== '' || $fromGetQuery !== '') {
|
||||
$searchField = $fromGetField;
|
||||
$searchQuery = $fromGetQuery;
|
||||
} elseif (is_array($flash)) {
|
||||
$searchField = trim((string) ($flash['search_field'] ?? ''));
|
||||
$searchQuery = trim((string) ($flash['search_query'] ?? ''));
|
||||
} else {
|
||||
$searchField = '';
|
||||
$searchQuery = '';
|
||||
}
|
||||
|
||||
$allowedFields = ['fr_idx', 'fr_type_code', 'fr_name', 'fr_phone', 'fr_addr', 'fr_dong_code', 'fr_note'];
|
||||
if (! in_array($searchField, $allowedFields, true)) {
|
||||
$searchField = 'fr_name';
|
||||
}
|
||||
|
||||
$typeCodes = $this->getCodeOptions('H');
|
||||
$typeCodeMap = [];
|
||||
foreach ($typeCodes as $cd) {
|
||||
$typeCodeMap[(string) $cd->cd_code] = (string) $cd->cd_name;
|
||||
}
|
||||
|
||||
$dongCodes = $this->getCodeOptions('D');
|
||||
$dongCodeMap = [];
|
||||
foreach ($dongCodes as $cd) {
|
||||
$dongCodeMap[(string) $cd->cd_code] = (string) $cd->cd_name;
|
||||
}
|
||||
|
||||
$builder = $this->model->where('fr_lg_idx', $lgIdx);
|
||||
if ($searchQuery !== '') {
|
||||
if ($searchField === 'fr_idx') {
|
||||
if (ctype_digit($searchQuery)) {
|
||||
$builder->where('fr_idx', (int) $searchQuery);
|
||||
} else {
|
||||
$builder->where('fr_idx', 0);
|
||||
}
|
||||
} elseif ($searchField === 'fr_type_code') {
|
||||
$codes = [];
|
||||
foreach ($typeCodes as $cd) {
|
||||
$code = (string) ($cd->cd_code ?? '');
|
||||
$name = (string) ($cd->cd_name ?? '');
|
||||
if ($code !== '' && (stripos($code, $searchQuery) !== false || stripos($name, $searchQuery) !== false)) {
|
||||
$codes[] = $code;
|
||||
}
|
||||
}
|
||||
if ($codes === []) {
|
||||
$builder->where('fr_idx', 0);
|
||||
} else {
|
||||
$builder->whereIn('fr_type_code', array_values(array_unique($codes)));
|
||||
}
|
||||
} elseif ($searchField === 'fr_dong_code') {
|
||||
$codes = [];
|
||||
foreach ($dongCodes as $cd) {
|
||||
$code = (string) ($cd->cd_code ?? '');
|
||||
$name = (string) ($cd->cd_name ?? '');
|
||||
if ($code !== '' && (stripos($code, $searchQuery) !== false || stripos($name, $searchQuery) !== false)) {
|
||||
$codes[] = $code;
|
||||
}
|
||||
}
|
||||
if ($codes === []) {
|
||||
$builder->where('fr_idx', 0);
|
||||
} else {
|
||||
$builder->whereIn('fr_dong_code', array_values(array_unique($codes)));
|
||||
}
|
||||
} else {
|
||||
$builder->like($searchField, $searchQuery);
|
||||
}
|
||||
}
|
||||
|
||||
$list = $builder->orderBy('fr_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->model->pager;
|
||||
$pager->setPath('bag/free-recipients');
|
||||
|
||||
return $this->renderWorkPage('무료용 대상자 관리', 'admin/free_recipient/index', [
|
||||
'list' => $list,
|
||||
'pager' => $pager,
|
||||
'search_field' => $searchField,
|
||||
'search_query' => $searchQuery,
|
||||
'type_code_map' => $typeCodeMap,
|
||||
'dong_code_map' => $dongCodeMap,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
|
||||
@@ -25,15 +25,6 @@ class Manager extends BaseController
|
||||
return $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx) : [];
|
||||
}
|
||||
|
||||
private function managerCategoryOptions(): array
|
||||
{
|
||||
return [
|
||||
'company' => '제작업체',
|
||||
'district' => '구·군',
|
||||
'agency' => '대행소',
|
||||
];
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
helper('admin');
|
||||
@@ -44,29 +35,93 @@ class Manager extends BaseController
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$category = (string) ($this->request->getGet('category') ?? '');
|
||||
$categories = $this->managerCategoryOptions();
|
||||
if ($this->request->is('post')) {
|
||||
$searchField = trim((string) ($this->request->getPost('search_field') ?? ''));
|
||||
$searchQuery = trim((string) ($this->request->getPost('search_query') ?? ''));
|
||||
session()->setFlashdata('manager_search', [
|
||||
'search_field' => $searchField,
|
||||
'search_query' => $searchQuery,
|
||||
]);
|
||||
|
||||
return redirect()->to(mgmt_url('managers'));
|
||||
}
|
||||
|
||||
$fromGetField = trim((string) ($this->request->getGet('search_field') ?? ''));
|
||||
$fromGetQuery = trim((string) ($this->request->getGet('search_query') ?? ''));
|
||||
$flash = session()->getFlashdata('manager_search');
|
||||
if ($fromGetField !== '' || $fromGetQuery !== '') {
|
||||
$searchField = $fromGetField;
|
||||
$searchQuery = $fromGetQuery;
|
||||
} elseif (is_array($flash)) {
|
||||
$searchField = trim((string) ($flash['search_field'] ?? ''));
|
||||
$searchQuery = trim((string) ($flash['search_query'] ?? ''));
|
||||
} else {
|
||||
$searchField = '';
|
||||
$searchQuery = '';
|
||||
}
|
||||
|
||||
$allowedFields = ['mg_idx', 'mg_name', 'mg_dept_code', 'mg_position_code', 'mg_tel', 'mg_phone', 'mg_email'];
|
||||
if (! in_array($searchField, $allowedFields, true)) {
|
||||
$searchField = 'mg_name';
|
||||
}
|
||||
|
||||
$deptCodes = $this->getCodeOptions('S');
|
||||
$posCodes = $this->getCodeOptions('T');
|
||||
$deptCodeMap = [];
|
||||
foreach ($deptCodes as $cd) {
|
||||
$deptCodeMap[(string) $cd->cd_code] = (string) $cd->cd_name;
|
||||
}
|
||||
$posCodeMap = [];
|
||||
foreach ($posCodes as $cd) {
|
||||
$posCodeMap[(string) $cd->cd_code] = (string) $cd->cd_name;
|
||||
}
|
||||
|
||||
$builder = $this->model->where('mg_lg_idx', $lgIdx);
|
||||
if ($category !== '' && isset($categories[$category])) {
|
||||
$builder->where('mg_dept_code', $category);
|
||||
if ($searchQuery !== '') {
|
||||
if ($searchField === 'mg_idx') {
|
||||
if (ctype_digit($searchQuery)) {
|
||||
$builder->where('mg_idx', (int) $searchQuery);
|
||||
} else {
|
||||
$builder->where('mg_idx', 0);
|
||||
}
|
||||
} elseif ($searchField === 'mg_dept_code' || $searchField === 'mg_position_code') {
|
||||
$sourceList = $searchField === 'mg_dept_code' ? $deptCodes : $posCodes;
|
||||
$codes = [];
|
||||
foreach ($sourceList as $cd) {
|
||||
$code = (string) ($cd->cd_code ?? '');
|
||||
$name = (string) ($cd->cd_name ?? '');
|
||||
if ($code !== '' && (stripos($code, $searchQuery) !== false || stripos($name, $searchQuery) !== false)) {
|
||||
$codes[] = $code;
|
||||
}
|
||||
}
|
||||
if ($codes === []) {
|
||||
$builder->where('mg_idx', 0);
|
||||
} else {
|
||||
$builder->whereIn($searchField, array_values(array_unique($codes)));
|
||||
}
|
||||
} else {
|
||||
$builder->like($searchField, $searchQuery);
|
||||
}
|
||||
}
|
||||
|
||||
$list = $builder->orderBy('mg_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->model->pager;
|
||||
$pager->setPath('bag/managers');
|
||||
|
||||
return $this->renderWorkPage('담당자 관리', 'admin/manager/index', [
|
||||
'list' => $list,
|
||||
'pager' => $pager,
|
||||
'categories' => $categories,
|
||||
'category' => $category,
|
||||
'list' => $list,
|
||||
'pager' => $pager,
|
||||
'search_field' => $searchField,
|
||||
'search_query' => $searchQuery,
|
||||
'dept_code_map' => $deptCodeMap,
|
||||
'pos_code_map' => $posCodeMap,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return $this->renderWorkPage('담당자 등록', 'admin/manager/create', [
|
||||
'categories' => $this->managerCategoryOptions(),
|
||||
'deptCodes' => $this->getCodeOptions('S'),
|
||||
'positionCodes' => $this->getCodeOptions('T'),
|
||||
]);
|
||||
}
|
||||
@@ -76,7 +131,6 @@ class Manager extends BaseController
|
||||
helper(['admin', 'url']);
|
||||
$rules = [
|
||||
'mg_name' => 'required|max_length[50]',
|
||||
'mg_category' => 'required|in_list[company,district,agency]',
|
||||
'mg_tel' => 'permit_empty|max_length[20]',
|
||||
'mg_phone' => 'permit_empty|max_length[20]',
|
||||
'mg_email' => 'permit_empty|valid_email|max_length[100]',
|
||||
@@ -88,7 +142,7 @@ class Manager extends BaseController
|
||||
$this->model->insert([
|
||||
'mg_lg_idx' => admin_effective_lg_idx(),
|
||||
'mg_name' => $this->request->getPost('mg_name'),
|
||||
'mg_dept_code' => (string) ($this->request->getPost('mg_category') ?? ''),
|
||||
'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') ?? '',
|
||||
@@ -110,7 +164,7 @@ class Manager extends BaseController
|
||||
|
||||
return $this->renderWorkPage('담당자 수정', 'admin/manager/edit', [
|
||||
'item' => $item,
|
||||
'categories' => $this->managerCategoryOptions(),
|
||||
'deptCodes' => $this->getCodeOptions('S'),
|
||||
'positionCodes' => $this->getCodeOptions('T'),
|
||||
]);
|
||||
}
|
||||
@@ -125,7 +179,6 @@ class Manager extends BaseController
|
||||
|
||||
$rules = [
|
||||
'mg_name' => 'required|max_length[50]',
|
||||
'mg_category' => 'required|in_list[company,district,agency]',
|
||||
'mg_state' => 'required|in_list[0,1]',
|
||||
];
|
||||
if (! $this->validate($rules)) {
|
||||
@@ -134,7 +187,7 @@ class Manager extends BaseController
|
||||
|
||||
$this->model->update($id, [
|
||||
'mg_name' => $this->request->getPost('mg_name'),
|
||||
'mg_dept_code' => (string) ($this->request->getPost('mg_category') ?? ''),
|
||||
'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') ?? '',
|
||||
|
||||
@@ -23,9 +23,6 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus()) {
|
||||
return $deny;
|
||||
}
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
@@ -33,34 +30,18 @@ class Menu extends BaseController
|
||||
->with('error', '메뉴를 관리하려면 먼저 지자체를 선택하세요.');
|
||||
}
|
||||
$types = $this->typeModel->orderBy('mt_sort', 'ASC')->findAll();
|
||||
$requestedMtIdx = (int) ($this->request->getGet('mt_idx') ?? 0);
|
||||
$mtIdx = $this->resolveMtIdx($requestedMtIdx, $types);
|
||||
$effectiveMtIdx = $mtIdx;
|
||||
$debugMode = $this->request->getGet('debug') === '1';
|
||||
$fallbackApplied = false;
|
||||
$list = $effectiveMtIdx > 0 ? $this->menuModel->getAllByType($effectiveMtIdx, $lgIdx) : [];
|
||||
$currentType = $mtIdx > 0 ? $this->typeModel->find($mtIdx) : null;
|
||||
$currentTypeCode = (string) ($currentType->mt_code ?? '');
|
||||
$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 ($effectiveMtIdx > 0 && empty($list)) {
|
||||
$this->menuModel->copyDefaultsFromLg($effectiveMtIdx, 1, $lgIdx);
|
||||
$list = $this->menuModel->getAllByType($effectiveMtIdx, $lgIdx);
|
||||
}
|
||||
|
||||
// 운영 DB 불일치 대응: site 타입인데 mt_idx 매핑이 어긋난 경우(예: menu_type=2, menu는 4 사용)
|
||||
if (empty($list) && $currentTypeCode === 'site' && $effectiveMtIdx !== 4) {
|
||||
$fallbackMtIdx = 4;
|
||||
$fallbackList = $this->menuModel->getAllByType($fallbackMtIdx, $lgIdx);
|
||||
if (empty($fallbackList)) {
|
||||
$this->menuModel->copyDefaultsFromLg($fallbackMtIdx, 1, $lgIdx);
|
||||
$fallbackList = $this->menuModel->getAllByType($fallbackMtIdx, $lgIdx);
|
||||
}
|
||||
if (! empty($fallbackList)) {
|
||||
$effectiveMtIdx = $fallbackMtIdx;
|
||||
$list = $fallbackList;
|
||||
$fallbackApplied = true;
|
||||
}
|
||||
if ($mtIdx > 0 && empty($list)) {
|
||||
$this->menuModel->copyDefaultsFromLg($mtIdx, 1, $lgIdx);
|
||||
$list = $this->menuModel->getAllByType($mtIdx, $lgIdx);
|
||||
}
|
||||
|
||||
// 트리 순서대로 상위 메뉴 바로 아래에 하위 메뉴가 오도록 평탄화
|
||||
@@ -69,24 +50,16 @@ class Menu extends BaseController
|
||||
$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' => $currentTypeCode,
|
||||
'mtCode' => $currentType->mt_code ?? '',
|
||||
'list' => $list,
|
||||
'levelNames' => config('Roles')->levelNames,
|
||||
'debug_mode' => $debugMode,
|
||||
'debug_info' => [
|
||||
'lg_idx' => $lgIdx,
|
||||
'requested_mt_idx' => $requestedMtIdx,
|
||||
'resolved_mt_idx' => $mtIdx,
|
||||
'effective_mt_idx' => $effectiveMtIdx,
|
||||
'resolved_mt_code' => $currentTypeCode,
|
||||
'list_count' => count($list),
|
||||
'fallback_applied' => $fallbackApplied ? 'Y' : 'N',
|
||||
],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -96,16 +69,11 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function list()
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus(true)) {
|
||||
return $deny;
|
||||
}
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return $this->response->setJSON(['status' => 0, 'msg' => '지자체를 선택하세요.']);
|
||||
}
|
||||
$types = $this->typeModel->orderBy('mt_sort', 'ASC')->findAll();
|
||||
$requestedMtIdx = (int) $this->request->getGet('mt_idx');
|
||||
$mtIdx = $this->resolveMtIdx($requestedMtIdx, $types);
|
||||
$mtIdx = (int) $this->request->getGet('mt_idx');
|
||||
if ($mtIdx <= 0) {
|
||||
return $this->response->setJSON(['status' => 0, 'msg' => 'mt_idx required']);
|
||||
}
|
||||
@@ -118,9 +86,6 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus()) {
|
||||
return $deny;
|
||||
}
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
@@ -153,7 +118,6 @@ class Menu extends BaseController
|
||||
if ($mmPidx > 0) {
|
||||
$this->menuModel->updateCnode($mmPidx, 1);
|
||||
}
|
||||
$this->menuModel->syncTypeToAllLgs($mtIdx, $lgIdx);
|
||||
return redirect()->back()->with('success', '메뉴가 등록되었습니다.');
|
||||
}
|
||||
|
||||
@@ -162,9 +126,6 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function update(int $id)
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus()) {
|
||||
return $deny;
|
||||
}
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
@@ -184,7 +145,6 @@ class Menu extends BaseController
|
||||
'mm_is_view' => $this->request->getPost('mm_is_view') ? 'Y' : 'N',
|
||||
];
|
||||
$this->menuModel->update($id, $data);
|
||||
$this->menuModel->syncTypeToAllLgs((int) $row->mt_idx, $lgIdx);
|
||||
return redirect()->back()->with('success', '메뉴가 수정되었습니다.');
|
||||
}
|
||||
|
||||
@@ -193,9 +153,6 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function delete(int $id)
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus()) {
|
||||
return $deny;
|
||||
}
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
@@ -207,7 +164,6 @@ class Menu extends BaseController
|
||||
}
|
||||
$result = $this->menuModel->deleteSafe($id);
|
||||
if ($result['ok']) {
|
||||
$this->menuModel->syncTypeToAllLgs((int) $row->mt_idx, $lgIdx);
|
||||
return redirect()->back()->with('success', '메뉴가 삭제되었습니다.');
|
||||
}
|
||||
return redirect()->back()->with('error', $result['msg']);
|
||||
@@ -218,9 +174,6 @@ class Menu extends BaseController
|
||||
*/
|
||||
public function move()
|
||||
{
|
||||
if ($deny = $this->denyUnlessLevel4Plus()) {
|
||||
return $deny;
|
||||
}
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx === null) {
|
||||
return redirect()->to(base_url('admin/select-local-government'))
|
||||
@@ -230,12 +183,7 @@ class Menu extends BaseController
|
||||
if (! is_array($ids) || empty($ids)) {
|
||||
return redirect()->back()->with('error', '순서를 적용할 메뉴가 없습니다.');
|
||||
}
|
||||
$firstId = (int) ($ids[0] ?? 0);
|
||||
$firstRow = $firstId > 0 ? $this->menuModel->find($firstId) : null;
|
||||
$this->menuModel->setOrder($ids, $lgIdx);
|
||||
if ($firstRow && (int) $firstRow->lg_idx === $lgIdx) {
|
||||
$this->menuModel->syncTypeToAllLgs((int) $firstRow->mt_idx, $lgIdx);
|
||||
}
|
||||
return redirect()->back()->with('success', '순서가 적용되었습니다.');
|
||||
}
|
||||
|
||||
@@ -262,57 +210,4 @@ class Menu extends BaseController
|
||||
|
||||
return implode(',', array_values($levels));
|
||||
}
|
||||
|
||||
/**
|
||||
* 요청된 mt_idx를 현재 DB 상태에 맞게 보정.
|
||||
* - 유효한 mt_idx면 그대로 사용
|
||||
* - 레거시 site 값(2) 요청 시 site 타입의 실제 mt_idx로 치환
|
||||
* - 그 외 미지정/잘못된 값은 site 우선, 없으면 첫 타입으로 보정
|
||||
*
|
||||
* @param array<int,object> $types
|
||||
*/
|
||||
private function resolveMtIdx(int $requestedMtIdx, array $types): int
|
||||
{
|
||||
if (empty($types)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$validTypeIds = array_map(static fn ($t): int => (int) ($t->mt_idx ?? 0), $types);
|
||||
if ($requestedMtIdx > 0 && in_array($requestedMtIdx, $validTypeIds, true)) {
|
||||
return $requestedMtIdx;
|
||||
}
|
||||
|
||||
$siteType = $this->typeModel->where('mt_code', 'site')->first();
|
||||
if ($siteType !== null) {
|
||||
// 과거 링크(/admin/menus?mt_idx=2) 호환
|
||||
if ($requestedMtIdx === 2 || $requestedMtIdx <= 0 || ! in_array($requestedMtIdx, $validTypeIds, true)) {
|
||||
return (int) $siteType->mt_idx;
|
||||
}
|
||||
}
|
||||
|
||||
return (int) $types[0]->mt_idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 관리는 레벨4 이상(슈퍼/본부 관리자)만 허용.
|
||||
*
|
||||
* @return \CodeIgniter\HTTP\RedirectResponse|\CodeIgniter\HTTP\ResponseInterface|null
|
||||
*/
|
||||
private function denyUnlessLevel4Plus(bool $json = false)
|
||||
{
|
||||
$level = (int) session()->get('mb_level');
|
||||
if (Roles::isSuperAdminEquivalent($level)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($json) {
|
||||
return $this->response->setJSON([
|
||||
'status' => 0,
|
||||
'msg' => '메뉴 관리는 레벨4 이상만 접근할 수 있습니다.',
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->to(base_url('admin/dashboard'))
|
||||
->with('error', '메뉴 관리는 레벨4 이상만 접근할 수 있습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ class PackagingUnit extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
if (!$lgIdx) {
|
||||
return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$builder = $this->unitModel->where('pu_lg_idx', $lgIdx);
|
||||
@@ -38,26 +38,31 @@ class PackagingUnit extends BaseController
|
||||
$builder->groupStart()->where('pu_end_date IS NULL')->orWhere('pu_end_date <=', $endDate)->groupEnd();
|
||||
}
|
||||
|
||||
$list = $builder->orderBy('pu_bag_code', 'ASC')->orderBy('pu_start_date', 'DESC')->paginate(20);
|
||||
$list = $builder->orderBy('pu_bag_code', 'ASC')->orderBy('pu_start_date', 'DESC')->paginate(20);
|
||||
$pager = $this->unitModel->pager;
|
||||
|
||||
return $this->renderWorkPage('포장 단위 관리', 'admin/packaging_unit/index', [
|
||||
'list' => $list, 'startDate' => $startDate, 'endDate' => $endDate, 'pager' => $pager,
|
||||
return view('admin/layout', [
|
||||
'title' => '포장 단위 관리',
|
||||
'content' => view('admin/packaging_unit/index', [
|
||||
'list' => $list, 'startDate' => $startDate, 'endDate' => $endDate, 'pager' => $pager,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
helper('admin');
|
||||
if (! admin_effective_lg_idx()) {
|
||||
return redirect()->to(mgmt_url('packaging-units'))->with('error', '지자체를 선택해 주세요.');
|
||||
if (!admin_effective_lg_idx()) {
|
||||
return redirect()->to(site_url('admin/packaging-units'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx) : [];
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true) : [];
|
||||
|
||||
return $this->renderWorkPage('포장 단위 등록', 'admin/packaging_unit/create', ['bagCodes' => $bagCodes]);
|
||||
return view('admin/layout', [
|
||||
'title' => '포장 단위 등록',
|
||||
'content' => view('admin/packaging_unit/create', ['bagCodes' => $bagCodes]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store()
|
||||
@@ -78,10 +83,10 @@ class PackagingUnit extends BaseController
|
||||
}
|
||||
|
||||
$bagCode = $this->request->getPost('pu_bag_code');
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagName = '';
|
||||
if ($kind) {
|
||||
$detail = model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kind->ck_idx, (string) $bagCode, $lgIdx);
|
||||
$detail = model(CodeDetailModel::class)->where('cd_ck_idx', $kind->ck_idx)->where('cd_code', $bagCode)->first();
|
||||
$bagName = $detail ? $detail->cd_name : '';
|
||||
}
|
||||
|
||||
@@ -102,30 +107,32 @@ class PackagingUnit extends BaseController
|
||||
'pu_reg_mb_idx' => session()->get('mb_idx'),
|
||||
]);
|
||||
|
||||
return redirect()->to(mgmt_url('packaging-units'))->with('success', '포장 단위가 등록되었습니다.');
|
||||
return redirect()->to(site_url('admin/packaging-units'))->with('success', '포장 단위가 등록되었습니다.');
|
||||
}
|
||||
|
||||
public function edit(int $id)
|
||||
{
|
||||
helper('admin');
|
||||
$item = $this->unitModel->find($id);
|
||||
if (! $item || (int) $item->pu_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(mgmt_url('packaging-units'))->with('error', '포장 단위를 찾을 수 없습니다.');
|
||||
if (!$item || (int) $item->pu_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(site_url('admin/packaging-units'))->with('error', '포장 단위를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx) : [];
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true) : [];
|
||||
|
||||
return $this->renderWorkPage('포장 단위 수정', 'admin/packaging_unit/edit', ['item' => $item, 'bagCodes' => $bagCodes]);
|
||||
return view('admin/layout', [
|
||||
'title' => '포장 단위 수정',
|
||||
'content' => view('admin/packaging_unit/edit', ['item' => $item, 'bagCodes' => $bagCodes]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(int $id)
|
||||
{
|
||||
helper('admin');
|
||||
$item = $this->unitModel->find($id);
|
||||
if (! $item || (int) $item->pu_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(mgmt_url('packaging-units'))->with('error', '포장 단위를 찾을 수 없습니다.');
|
||||
if (!$item || (int) $item->pu_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(site_url('admin/packaging-units'))->with('error', '포장 단위를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$rules = [
|
||||
@@ -143,35 +150,18 @@ class PackagingUnit extends BaseController
|
||||
$db = \Config\Database::connect();
|
||||
$db->transStart();
|
||||
|
||||
$trackFields = ['pu_box_per_pack', 'pu_pack_per_sheet', 'pu_start_date', 'pu_end_date', 'pu_state'];
|
||||
$fieldLabels = [
|
||||
'pu_box_per_pack' => '박스당 팩 수',
|
||||
'pu_pack_per_sheet' => '팩당 낱장 수',
|
||||
'pu_start_date' => '적용시작일',
|
||||
'pu_end_date' => '적용종료일',
|
||||
'pu_state' => '상태',
|
||||
];
|
||||
$trackFields = ['pu_box_per_pack', 'pu_pack_per_sheet'];
|
||||
foreach ($trackFields as $field) {
|
||||
$oldRaw = $item->$field;
|
||||
$newRaw = $this->request->getPost($field);
|
||||
if ($field === 'pu_end_date') {
|
||||
$oldRaw = $oldRaw ?: '';
|
||||
$newRaw = $newRaw ?: '';
|
||||
}
|
||||
if ($field === 'pu_state') {
|
||||
$oldRaw = (int) $oldRaw === 1 ? '사용' : '미사용';
|
||||
$newRaw = (int) $newRaw === 1 ? '사용' : '미사용';
|
||||
}
|
||||
$oldVal = (string) $oldRaw;
|
||||
$newVal = (string) $newRaw;
|
||||
$oldVal = (string) $item->$field;
|
||||
$newVal = (string) $this->request->getPost($field);
|
||||
if ($oldVal !== $newVal) {
|
||||
$this->historyModel->insert([
|
||||
'puh_pu_idx' => $id,
|
||||
'puh_field' => $fieldLabels[$field] ?? $field,
|
||||
'puh_old_value' => $oldVal,
|
||||
'puh_new_value' => $newVal,
|
||||
'puh_changed_at' => date('Y-m-d H:i:s'),
|
||||
'puh_changed_by' => session()->get('mb_idx'),
|
||||
'puh_pu_idx' => $id,
|
||||
'puh_field' => $field,
|
||||
'puh_old_value' => $oldVal,
|
||||
'puh_new_value' => $newVal,
|
||||
'puh_changed_at'=> date('Y-m-d H:i:s'),
|
||||
'puh_changed_by'=> session()->get('mb_idx'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -190,33 +180,34 @@ class PackagingUnit extends BaseController
|
||||
]);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->to(mgmt_url('packaging-units'))->with('success', '포장 단위가 수정되었습니다.');
|
||||
return redirect()->to(site_url('admin/packaging-units'))->with('success', '포장 단위가 수정되었습니다.');
|
||||
}
|
||||
|
||||
public function delete(int $id)
|
||||
{
|
||||
helper('admin');
|
||||
$item = $this->unitModel->find($id);
|
||||
if (! $item || (int) $item->pu_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(mgmt_url('packaging-units'))->with('error', '포장 단위를 찾을 수 없습니다.');
|
||||
if (!$item || (int) $item->pu_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(site_url('admin/packaging-units'))->with('error', '포장 단위를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$this->unitModel->delete($id);
|
||||
|
||||
return redirect()->to(mgmt_url('packaging-units'))->with('success', '포장 단위가 삭제되었습니다.');
|
||||
return redirect()->to(site_url('admin/packaging-units'))->with('success', '포장 단위가 삭제되었습니다.');
|
||||
}
|
||||
|
||||
public function history(int $puIdx)
|
||||
{
|
||||
helper('admin');
|
||||
$item = $this->unitModel->find($puIdx);
|
||||
if (! $item || (int) $item->pu_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(mgmt_url('packaging-units'))->with('error', '포장 단위를 찾을 수 없습니다.');
|
||||
if (!$item || (int) $item->pu_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(site_url('admin/packaging-units'))->with('error', '포장 단위를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$list = $this->historyModel->where('puh_pu_idx', $puIdx)->orderBy('puh_changed_at', 'DESC')->findAll();
|
||||
|
||||
return $this->renderWorkPage('포장 단위 변경 이력 — ' . $item->pu_bag_name, 'admin/packaging_unit/history', ['item' => $item, 'list' => $list]);
|
||||
return view('admin/layout', [
|
||||
'title' => '포장 단위 변경 이력 — ' . $item->pu_bag_name,
|
||||
'content' => view('admin/packaging_unit/history', ['item' => $item, 'list' => $list]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@ class PasswordChange extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
helper('admin');
|
||||
|
||||
return $this->renderWorkPage('비밀번호 변경', 'admin/password_change/index');
|
||||
return view('admin/layout', [
|
||||
'title' => '비밀번호 변경',
|
||||
'content' => view('admin/password_change/index'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
helper('admin');
|
||||
$rules = [
|
||||
'current_password' => 'required',
|
||||
'new_password' => 'required|min_length[4]|max_length[255]',
|
||||
@@ -50,6 +50,6 @@ class PasswordChange extends BaseController
|
||||
'mb_passwd' => password_hash($this->request->getPost('new_password'), PASSWORD_DEFAULT),
|
||||
]);
|
||||
|
||||
return redirect()->to(mgmt_url('password-change'))->with('success', '비밀번호가 변경되었습니다.');
|
||||
return redirect()->to(site_url('admin/password-change'))->with('success', '비밀번호가 변경되었습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,48 +26,37 @@ class SalesAgency extends BaseController
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$saKind = trim((string) ($this->request->getGet('sa_kind') ?? ''));
|
||||
$saCode = trim((string) ($this->request->getGet('sa_code') ?? ''));
|
||||
$saName = trim((string) ($this->request->getGet('sa_name') ?? ''));
|
||||
$saIdx = trim((string) ($this->request->getGet('sa_idx') ?? ''));
|
||||
$searchField = trim((string) ($this->request->getGet('search_field') ?? ''));
|
||||
$searchQuery = trim((string) ($this->request->getGet('search_query') ?? ''));
|
||||
$allowedFields = ['sa_idx', 'sa_kind', 'sa_code', 'sa_name'];
|
||||
if (! in_array($searchField, $allowedFields, true)) {
|
||||
$searchField = 'sa_name';
|
||||
}
|
||||
|
||||
$builder = $this->model->where('sa_lg_idx', $lgIdx);
|
||||
if ($saKind !== '') {
|
||||
$builder->like('sa_kind', $saKind);
|
||||
}
|
||||
if ($saCode !== '') {
|
||||
$builder->like('sa_code', $saCode);
|
||||
}
|
||||
if ($saName !== '') {
|
||||
$builder->like('sa_name', $saName);
|
||||
}
|
||||
if ($saIdx !== '' && ctype_digit($saIdx)) {
|
||||
$builder->where('sa_idx', (int) $saIdx);
|
||||
if ($searchQuery !== '') {
|
||||
if ($searchField === 'sa_idx') {
|
||||
if (ctype_digit($searchQuery)) {
|
||||
$builder->where('sa_idx', (int) $searchQuery);
|
||||
} else {
|
||||
// 번호 검색은 숫자만 허용한다.
|
||||
$builder->where('sa_idx', 0);
|
||||
}
|
||||
} else {
|
||||
$builder->like($searchField, $searchQuery);
|
||||
}
|
||||
}
|
||||
|
||||
$list = $builder->orderForDisplay()->paginate(20);
|
||||
$list = $builder->orderBy('sa_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->model->pager;
|
||||
|
||||
$queryForPager = [
|
||||
'sa_kind' => $saKind,
|
||||
'sa_code' => $saCode,
|
||||
'sa_name' => $saName,
|
||||
'sa_idx' => $saIdx,
|
||||
];
|
||||
$queryForPager = array_filter($queryForPager, static fn ($v) => $v !== null && $v !== '');
|
||||
$pagerPath = mgmt_url('sales-agencies');
|
||||
if ($queryForPager !== []) {
|
||||
$pagerPath .= '?' . http_build_query($queryForPager);
|
||||
}
|
||||
$pager->setPath($pagerPath);
|
||||
// 전체 URL·쿼리를 setPath에 넣으면 Pager URI 경로가 깨져 404가 난다. 경로만 지정한다(필터는 현재 GET이 병합됨).
|
||||
$pager->setPath('bag/sales-agencies');
|
||||
|
||||
return $this->renderWorkPage('판매 대행소 관리', 'admin/sales_agency/index', [
|
||||
'list' => $list,
|
||||
'pager' => $pager,
|
||||
'sa_kind' => $saKind,
|
||||
'sa_code' => $saCode,
|
||||
'sa_name' => $saName,
|
||||
'sa_idx' => $saIdx,
|
||||
'list' => $list,
|
||||
'pager' => $pager,
|
||||
'search_field' => $searchField,
|
||||
'search_query' => $searchQuery,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,7 @@ class SalesReport extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$startDate = $this->request->getGet('start_date') ?? date('Y-m-01');
|
||||
$endDate = $this->request->getGet('end_date') ?? date('Y-m-d');
|
||||
@@ -52,7 +50,10 @@ class SalesReport extends BaseController
|
||||
", [$lgIdx, $startDate, $endDate])->getResult();
|
||||
}
|
||||
|
||||
return $this->renderWorkPage('판매 대장', 'admin/sales_report/sales_ledger', compact('result', 'startDate', 'endDate', 'mode'));
|
||||
return view('admin/layout', [
|
||||
'title' => '판매 대장',
|
||||
'content' => view('admin/sales_report/sales_ledger', compact('result', 'startDate', 'endDate', 'mode')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,9 +63,7 @@ class SalesReport extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$date = $this->request->getGet('date') ?? date('Y-m-d');
|
||||
$db = \Config\Database::connect();
|
||||
@@ -92,7 +91,10 @@ class SalesReport extends BaseController
|
||||
ORDER BY bs_bag_code
|
||||
", [$lgIdx, $monthStart, $date])->getResult();
|
||||
|
||||
return $this->renderWorkPage('일계표', 'admin/sales_report/daily_summary', compact('daily', 'monthly', 'date'));
|
||||
return view('admin/layout', [
|
||||
'title' => '일계표',
|
||||
'content' => view('admin/sales_report/daily_summary', compact('daily', 'monthly', 'date')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,9 +104,7 @@ class SalesReport extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$startDate = $this->request->getGet('start_date') ?? date('Y-m-01');
|
||||
$endDate = $this->request->getGet('end_date') ?? date('Y-m-d');
|
||||
@@ -122,7 +122,10 @@ class SalesReport extends BaseController
|
||||
ORDER BY bs_bag_code
|
||||
", [$lgIdx, $startDate, $endDate])->getResult();
|
||||
|
||||
return $this->renderWorkPage('기간별 판매현황', 'admin/sales_report/period_sales', compact('result', 'startDate', 'endDate'));
|
||||
return view('admin/layout', [
|
||||
'title' => '기간별 판매현황',
|
||||
'content' => view('admin/sales_report/period_sales', compact('result', 'startDate', 'endDate')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,9 +135,7 @@ class SalesReport extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$year = $this->request->getGet('year') ?? date('Y');
|
||||
$db = \Config\Database::connect();
|
||||
@@ -160,7 +161,10 @@ class SalesReport extends BaseController
|
||||
ORDER BY bs_bag_code
|
||||
", [$lgIdx, $year])->getResult();
|
||||
|
||||
return $this->renderWorkPage('년 판매 현황', 'admin/sales_report/yearly_sales', compact('result', 'year'));
|
||||
return view('admin/layout', [
|
||||
'title' => '년 판매 현황',
|
||||
'content' => view('admin/sales_report/yearly_sales', compact('result', 'year')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,9 +174,7 @@ class SalesReport extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$startDate = $this->request->getGet('start_date') ?? date('Y-m-01');
|
||||
$endDate = $this->request->getGet('end_date') ?? date('Y-m-d');
|
||||
@@ -190,7 +192,10 @@ class SalesReport extends BaseController
|
||||
ORDER BY bs_ds_name
|
||||
", [$lgIdx, $startDate, $endDate])->getResult();
|
||||
|
||||
return $this->renderWorkPage('지정판매소별 판매현황', 'admin/sales_report/shop_sales', compact('result', 'startDate', 'endDate'));
|
||||
return view('admin/layout', [
|
||||
'title' => '지정판매소별 판매현황',
|
||||
'content' => view('admin/sales_report/shop_sales', compact('result', 'startDate', 'endDate')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,9 +205,7 @@ class SalesReport extends BaseController
|
||||
{
|
||||
helper(['admin', 'export']);
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$startDate = $this->request->getGet('start_date') ?? date('Y-m-01');
|
||||
$endDate = $this->request->getGet('end_date') ?? date('Y-m-d');
|
||||
@@ -254,9 +257,7 @@ class SalesReport extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$startDate = $this->request->getGet('start_date') ?? date('Y-m-01');
|
||||
$endDate = $this->request->getGet('end_date') ?? date('Y-m-d');
|
||||
@@ -270,7 +271,10 @@ class SalesReport extends BaseController
|
||||
ORDER BY bs_sale_date DESC, bs_ds_name
|
||||
", [$lgIdx, $startDate, $endDate])->getResult();
|
||||
|
||||
return $this->renderWorkPage('반품/파기 현황', 'admin/sales_report/returns', compact('result', 'startDate', 'endDate'));
|
||||
return view('admin/layout', [
|
||||
'title' => '반품/파기 현황',
|
||||
'content' => view('admin/sales_report/returns', compact('result', 'startDate', 'endDate')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,9 +284,7 @@ class SalesReport extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$lotNo = $this->request->getGet('lot_no') ?? '';
|
||||
$order = null;
|
||||
@@ -298,7 +300,10 @@ class SalesReport extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
return $this->renderWorkPage('LOT 수불 조회', 'admin/sales_report/lot_flow', compact('lotNo', 'order', 'items', 'receivings'));
|
||||
return view('admin/layout', [
|
||||
'title' => 'LOT 수불 조회',
|
||||
'content' => view('admin/sales_report/lot_flow', compact('lotNo', 'order', 'items', 'receivings')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,9 +313,7 @@ class SalesReport extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$startDate = $this->request->getGet('start_date') ?? date('Y-m-01');
|
||||
$endDate = $this->request->getGet('end_date') ?? date('Y-m-d');
|
||||
@@ -329,9 +332,12 @@ class SalesReport extends BaseController
|
||||
|
||||
// 봉투 코드 목록
|
||||
$kindO = model(\App\Models\CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kindO ? model(\App\Models\CodeDetailModel::class)->getByKind((int) $kindO->ck_idx, true, $lgIdx) : [];
|
||||
$bagCodes = $kindO ? model(\App\Models\CodeDetailModel::class)->getByKind((int) $kindO->ck_idx, true) : [];
|
||||
|
||||
return $this->renderWorkPage('기타 입출고', 'admin/sales_report/misc_flow', compact('result', 'startDate', 'endDate', 'bagCodes', 'tableExists'));
|
||||
return view('admin/layout', [
|
||||
'title' => '기타 입출고',
|
||||
'content' => view('admin/sales_report/misc_flow', compact('result', 'startDate', 'endDate', 'bagCodes', 'tableExists')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,7 +347,7 @@ class SalesReport extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (!$lgIdx) return redirect()->to(mgmt_url('reports/misc-flow'))->with('error', '지자체를 선택해 주세요.');
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin/reports/misc-flow'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$rules = [
|
||||
'bmf_type' => 'required|in_list[in,out]',
|
||||
@@ -360,7 +366,7 @@ class SalesReport extends BaseController
|
||||
|
||||
// 봉투명 조회
|
||||
$kindO = model(\App\Models\CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(\App\Models\CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, (string) $bagCode, $lgIdx) : null;
|
||||
$detail = $kindO ? model(\App\Models\CodeDetailModel::class)->where('cd_ck_idx', $kindO->ck_idx)->where('cd_code', $bagCode)->first() : null;
|
||||
$bagName = $detail ? $detail->cd_name : '';
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
@@ -377,7 +383,7 @@ class SalesReport extends BaseController
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->to(mgmt_url('reports/misc-flow'))->with('success', '기타 입출고가 등록되었습니다.');
|
||||
return redirect()->to(site_url('admin/reports/misc-flow'))->with('success', '기타 입출고가 등록되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,9 +393,7 @@ class SalesReport extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$startDate = $this->request->getGet('start_date') ?? date('Y-m-01');
|
||||
$endDate = $this->request->getGet('end_date') ?? date('Y-m-d');
|
||||
@@ -426,6 +430,9 @@ class SalesReport extends BaseController
|
||||
// 현재 재고
|
||||
$inventory = model(BagInventoryModel::class)->where('bi_lg_idx', $lgIdx)->findAll();
|
||||
|
||||
return $this->renderWorkPage('봉투 수불 현황', 'admin/sales_report/supply_demand', compact('receiving', 'sales', 'issues', 'inventory', 'startDate', 'endDate'));
|
||||
return view('admin/layout', [
|
||||
'title' => '봉투 수불 현황',
|
||||
'content' => view('admin/sales_report/supply_demand', compact('receiving', 'sales', 'issues', 'inventory', 'startDate', 'endDate')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,39 +26,37 @@ class ShopOrder extends BaseController
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(work_area_home_url())->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$builder = $this->orderModel->where('so_lg_idx', $lgIdx);
|
||||
$startDate = $this->request->getGet('start_date');
|
||||
$endDate = $this->request->getGet('end_date');
|
||||
if ($startDate) {
|
||||
$builder->where('so_delivery_date >=', $startDate);
|
||||
}
|
||||
if ($endDate) {
|
||||
$builder->where('so_delivery_date <=', $endDate);
|
||||
}
|
||||
if ($startDate) $builder->where('so_delivery_date >=', $startDate);
|
||||
if ($endDate) $builder->where('so_delivery_date <=', $endDate);
|
||||
|
||||
$list = $builder->orderBy('so_idx', 'DESC')->paginate(20);
|
||||
$list = $builder->orderBy('so_idx', 'DESC')->paginate(20);
|
||||
$pager = $this->orderModel->pager;
|
||||
|
||||
return $this->renderWorkPage('주문 접수 관리', 'admin/shop_order/index', compact('list', 'startDate', 'endDate', 'pager'));
|
||||
return view('admin/layout', [
|
||||
'title' => '주문 접수 관리',
|
||||
'content' => view('admin/shop_order/index', compact('list', 'startDate', 'endDate', 'pager')),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if (! $lgIdx) {
|
||||
return redirect()->to(mgmt_url('shop-orders'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
if (!$lgIdx) return redirect()->to(site_url('admin/shop-orders'))->with('error', '지자체를 선택해 주세요.');
|
||||
|
||||
$shops = model(DesignatedShopModel::class)->where('ds_lg_idx', $lgIdx)->where('ds_state', 1)->findAll();
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true, $lgIdx) : [];
|
||||
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true) : [];
|
||||
|
||||
return $this->renderWorkPage('주문 접수', 'admin/shop_order/create', compact('shops', 'bagCodes'));
|
||||
return view('admin/layout', [
|
||||
'title' => '주문 접수',
|
||||
'content' => view('admin/shop_order/create', compact('shops', 'bagCodes')),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store()
|
||||
@@ -67,9 +65,9 @@ class ShopOrder extends BaseController
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
|
||||
$rules = [
|
||||
'so_ds_idx' => 'required|is_natural_no_zero',
|
||||
'so_delivery_date' => 'required|valid_date[Y-m-d]',
|
||||
'so_payment_type' => 'required|in_list[이체,가상계좌]',
|
||||
'so_ds_idx' => 'required|is_natural_no_zero',
|
||||
'so_delivery_date'=> 'required|valid_date[Y-m-d]',
|
||||
'so_payment_type' => 'required|in_list[이체,가상계좌]',
|
||||
];
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||||
@@ -79,62 +77,57 @@ class ShopOrder extends BaseController
|
||||
$db->transStart();
|
||||
|
||||
$dsIdx = (int) $this->request->getPost('so_ds_idx');
|
||||
$shop = model(DesignatedShopModel::class)->find($dsIdx);
|
||||
$shop = model(DesignatedShopModel::class)->find($dsIdx);
|
||||
|
||||
$this->orderModel->insert([
|
||||
'so_lg_idx' => $lgIdx,
|
||||
'so_ds_idx' => $dsIdx,
|
||||
'so_ds_name' => $shop ? $shop->ds_name : '',
|
||||
'so_order_date' => date('Y-m-d'),
|
||||
'so_delivery_date' => $this->request->getPost('so_delivery_date'),
|
||||
'so_payment_type' => $this->request->getPost('so_payment_type'),
|
||||
'so_status' => 'normal',
|
||||
'so_orderer_idx' => session()->get('mb_idx'),
|
||||
'so_regdate' => date('Y-m-d H:i:s'),
|
||||
'so_lg_idx' => $lgIdx,
|
||||
'so_ds_idx' => $dsIdx,
|
||||
'so_ds_name' => $shop ? $shop->ds_name : '',
|
||||
'so_order_date' => date('Y-m-d'),
|
||||
'so_delivery_date'=> $this->request->getPost('so_delivery_date'),
|
||||
'so_payment_type' => $this->request->getPost('so_payment_type'),
|
||||
'so_status' => 'normal',
|
||||
'so_orderer_idx' => session()->get('mb_idx'),
|
||||
'so_regdate' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
$soIdx = (int) $this->orderModel->getInsertID();
|
||||
|
||||
$bagCodes = $this->request->getPost('item_bag_code') ?? [];
|
||||
$qtys = $this->request->getPost('item_qty') ?? [];
|
||||
$totalQty = 0;
|
||||
$totalAmt = 0;
|
||||
$totalQty = 0; $totalAmt = 0;
|
||||
|
||||
foreach ($bagCodes as $i => $code) {
|
||||
if (empty($code) || empty($qtys[$i])) {
|
||||
continue;
|
||||
}
|
||||
if (empty($code) || empty($qtys[$i])) continue;
|
||||
$qty = (int) $qtys[$i];
|
||||
|
||||
$price = model(BagPriceModel::class)->latestActiveByBagCode($lgIdx, (string) $code);
|
||||
$price = model(BagPriceModel::class)->where('bp_lg_idx', $lgIdx)->where('bp_bag_code', $code)->where('bp_state', 1)->first();
|
||||
$unitPrice = $price ? (float) $price->bp_consumer : 0;
|
||||
$amount = $unitPrice * $qty;
|
||||
$amount = $unitPrice * $qty;
|
||||
|
||||
$unit = model(PackagingUnitModel::class)->where('pu_lg_idx', $lgIdx)->where('pu_bag_code', $code)->where('pu_state', 1)->first();
|
||||
$boxCount = 0;
|
||||
$packCount = 0;
|
||||
$sheetCount = $qty;
|
||||
$boxCount = 0; $packCount = 0; $sheetCount = $qty;
|
||||
if ($unit && (int) $unit->pu_total_per_box > 0) {
|
||||
$boxCount = intdiv($qty, (int) $unit->pu_total_per_box);
|
||||
$remainder = $qty % (int) $unit->pu_total_per_box;
|
||||
$boxCount = intdiv($qty, (int) $unit->pu_total_per_box);
|
||||
$remainder = $qty % (int) $unit->pu_total_per_box;
|
||||
if ((int) $unit->pu_pack_per_sheet > 0) {
|
||||
$packCount = intdiv($remainder, (int) $unit->pu_pack_per_sheet);
|
||||
$packCount = intdiv($remainder, (int) $unit->pu_pack_per_sheet);
|
||||
$sheetCount = $remainder % (int) $unit->pu_pack_per_sheet;
|
||||
}
|
||||
}
|
||||
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(CodeDetailModel::class)->findResolvedByKindAndCode((int) $kindO->ck_idx, (string) $code, $lgIdx) : null;
|
||||
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||
$detail = $kindO ? model(CodeDetailModel::class)->where('cd_ck_idx', $kindO->ck_idx)->where('cd_code', $code)->first() : null;
|
||||
|
||||
$this->itemModel->insert([
|
||||
'soi_so_idx' => $soIdx,
|
||||
'soi_bag_code' => $code,
|
||||
'soi_bag_name' => $detail ? $detail->cd_name : '',
|
||||
'soi_unit_price' => $unitPrice,
|
||||
'soi_qty' => $qty,
|
||||
'soi_amount' => $amount,
|
||||
'soi_box_count' => $boxCount,
|
||||
'soi_pack_count' => $packCount,
|
||||
'soi_sheet_count' => $sheetCount,
|
||||
'soi_so_idx' => $soIdx,
|
||||
'soi_bag_code' => $code,
|
||||
'soi_bag_name' => $detail ? $detail->cd_name : '',
|
||||
'soi_unit_price' => $unitPrice,
|
||||
'soi_qty' => $qty,
|
||||
'soi_amount' => $amount,
|
||||
'soi_box_count' => $boxCount,
|
||||
'soi_pack_count' => $packCount,
|
||||
'soi_sheet_count'=> $sheetCount,
|
||||
]);
|
||||
|
||||
$totalQty += $qty;
|
||||
@@ -144,19 +137,18 @@ class ShopOrder extends BaseController
|
||||
$this->orderModel->update($soIdx, ['so_total_qty' => $totalQty, 'so_total_amount' => $totalAmt]);
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->to(mgmt_url('shop-orders'))->with('success', '주문이 접수되었습니다.');
|
||||
return redirect()->to(site_url('admin/shop-orders'))->with('success', '주문이 접수되었습니다.');
|
||||
}
|
||||
|
||||
public function cancel(int $id)
|
||||
{
|
||||
helper('admin');
|
||||
$order = $this->orderModel->find($id);
|
||||
if (! $order || (int) $order->so_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(mgmt_url('shop-orders'))->with('error', '주문을 찾을 수 없습니다.');
|
||||
if (!$order || (int) $order->so_lg_idx !== admin_effective_lg_idx()) {
|
||||
return redirect()->to(site_url('admin/shop-orders'))->with('error', '주문을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$this->orderModel->update($id, ['so_status' => 'cancelled']);
|
||||
|
||||
return redirect()->to(mgmt_url('shop-orders'))->with('success', '주문이 취소되었습니다.');
|
||||
return redirect()->to(site_url('admin/shop-orders'))->with('success', '주문이 취소되었습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,10 +121,8 @@ class User extends BaseController
|
||||
if (! $member) {
|
||||
return redirect()->to(site_url('admin/users'))->with('error', '회원을 찾을 수 없습니다.');
|
||||
}
|
||||
$email = pii_decrypt($member->mb_email ?? '');
|
||||
$phone = pii_decrypt($member->mb_phone ?? '');
|
||||
$member->mb_email = $email;
|
||||
$member->mb_phone = $phone;
|
||||
$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', [
|
||||
@@ -179,23 +177,6 @@ class User extends BaseController
|
||||
return redirect()->to(site_url('admin/users'))->with('success', '회원 정보가 수정되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 실패 누적 잠금(mb_locked_until) 해제 — 비밀번호는 그대로 두고 재시도만 가능하게 함
|
||||
*/
|
||||
public function unlockLogin(int $id)
|
||||
{
|
||||
$member = $this->memberModel->find($id);
|
||||
if (! $member) {
|
||||
return redirect()->to(site_url('admin/users'))->with('error', '회원을 찾을 수 없습니다.');
|
||||
}
|
||||
$this->memberModel->update($id, [
|
||||
'mb_login_fail_count' => 0,
|
||||
'mb_locked_until' => null,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', '로그인 잠금이 해제되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 로그인한 관리자가 부여 가능한 역할 목록.
|
||||
* super/본부만 4·5 부여 가능, 지자체 관리자는 1~3만.
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
/**
|
||||
* 구 관리자 업무 URL(admin/…) → 메인 사이트 업무 URL(bag/…) 영구 이전.
|
||||
* POST 폼은 307로 메서드·본문 유지를 시도하고, GET 은 301.
|
||||
*/
|
||||
class WorkMovedToBag extends BaseController
|
||||
{
|
||||
public function toBag(string $prefix, string $rest = ''): \CodeIgniter\HTTP\RedirectResponse
|
||||
{
|
||||
$rest = trim($rest, '/');
|
||||
|
||||
if ($prefix === 'packaging-units') {
|
||||
$path = 'packaging-units/manage';
|
||||
if ($rest !== '') {
|
||||
$path .= '/' . $rest;
|
||||
}
|
||||
} else {
|
||||
$path = $prefix;
|
||||
if ($rest !== '') {
|
||||
$path .= '/' . $rest;
|
||||
}
|
||||
}
|
||||
|
||||
$target = site_url('bag/' . $path);
|
||||
$query = $this->request->getUri()->getQuery();
|
||||
if ($query !== '') {
|
||||
$target .= '?' . $query;
|
||||
}
|
||||
|
||||
$code = $this->request->getMethod() === 'post' ? 307 : 301;
|
||||
|
||||
return redirect()->to($target)->setStatusCode($code);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,34 +42,4 @@ abstract class BaseController extends Controller
|
||||
// Preload any models, libraries, etc, here.
|
||||
// $this->session = service('session');
|
||||
}
|
||||
|
||||
/**
|
||||
* /admin/* 또는 /bag/* 업무 화면 공통: 요청이 bag 이면 메인 사이트 레이아웃, 아니면 관리자 레이아웃.
|
||||
*
|
||||
* @param array<string, mixed> $contentData
|
||||
*/
|
||||
protected function renderWorkPage(string $title, string $contentView, array $contentData = []): string
|
||||
{
|
||||
$content = view($contentView, $contentData);
|
||||
helper('admin');
|
||||
$path = function_exists('current_nav_request_path') ? current_nav_request_path() : '';
|
||||
if ($path === '') {
|
||||
$uri = service('request')->getUri();
|
||||
$path = trim((string) $uri->getPath(), '/');
|
||||
}
|
||||
while (str_starts_with($path, 'index.php/')) {
|
||||
$path = substr($path, strlen('index.php/'));
|
||||
}
|
||||
if ($path === 'bag' || str_starts_with($path, 'bag/')) {
|
||||
return view('bag/layout/main', [
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
]);
|
||||
}
|
||||
|
||||
return view('admin/layout', [
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,11 @@ class Home extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 후 메인 — site 메뉴 레이아웃 + 종합·그래프(blend) 본문
|
||||
* 로그인 후 원래 메인 화면 (admin 유사 레이아웃 + site 메뉴 호버 드롭다운)
|
||||
*/
|
||||
public function dashboard()
|
||||
{
|
||||
return view('bag/layout/main', [
|
||||
'title' => '업무 현황 · 종합·그래프',
|
||||
'content' => view('bag/dashboard_blend_inner', [
|
||||
'lgLabel' => $this->resolveLgLabel(),
|
||||
]),
|
||||
]);
|
||||
return view('bag/daily_inventory');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,11 +62,13 @@ class Home extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* /dashboard 와 동일 본문(호환 URL)
|
||||
* dense(표·KPI) + charts(Chart.js) 혼합. URL: /dashboard/blend
|
||||
*/
|
||||
public function dashboardBlend()
|
||||
{
|
||||
return $this->dashboard();
|
||||
return view('bag/lg_dashboard_blend', [
|
||||
'lgLabel' => $this->resolveLgLabel(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,5 +114,4 @@ class Home extends BaseController
|
||||
|
||||
return '북구 (데모)';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use CodeIgniter\Filters\FilterInterface;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* 로그인만 필요 (mb_level 무관). 기본코드 조회 등 시민·판매소도 접근 가능한 /admin/* 하위용.
|
||||
*/
|
||||
class LoginAuthFilter implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null)
|
||||
{
|
||||
if (! session()->get('logged_in')) {
|
||||
return redirect()->to(site_url('login'))->with('error', '로그인이 필요합니다.');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,8 @@ use Config\Roles;
|
||||
|
||||
if (! function_exists('admin_effective_lg_idx')) {
|
||||
/**
|
||||
* 관리자 화면·사이트 메뉴·Bag 등에서 쓰는 작업 지자체 PK.
|
||||
* Super/본부 → admin_selected_lg_idx(미선택 시 null).
|
||||
* 지자체관리자·지정판매소·일반 사용자 → mb_lg_idx(없으면 null).
|
||||
* 현재 로그인한 관리자가 작업 대상으로 사용하는 지자체 PK.
|
||||
* Super/본부 관리자 → admin_selected_lg_idx, 지자체 관리자 → mb_lg_idx, 그 외 null.
|
||||
*/
|
||||
function admin_effective_lg_idx(): ?int
|
||||
{
|
||||
@@ -17,9 +16,7 @@ if (! function_exists('admin_effective_lg_idx')) {
|
||||
$idx = session()->get('admin_selected_lg_idx');
|
||||
return $idx !== null && $idx !== '' ? (int) $idx : null;
|
||||
}
|
||||
if ($level === Roles::LEVEL_LOCAL_ADMIN
|
||||
|| $level === Roles::LEVEL_SHOP
|
||||
|| $level === Roles::LEVEL_CITIZEN) {
|
||||
if ($level === Roles::LEVEL_LOCAL_ADMIN) {
|
||||
$idx = session()->get('mb_lg_idx');
|
||||
return $idx !== null && $idx !== '' ? (int) $idx : null;
|
||||
}
|
||||
@@ -27,23 +24,6 @@ if (! function_exists('admin_effective_lg_idx')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('resolve_site_menu_lg_idx')) {
|
||||
/**
|
||||
* site 상단 메뉴(menu 테이블) 조회용 지자체 PK.
|
||||
* admin_effective_lg_idx() 우선(메뉴 관리·Bag과 동일), 없으면 mb_lg_idx, 그다음 기본 1.
|
||||
*/
|
||||
function resolve_site_menu_lg_idx(): int
|
||||
{
|
||||
$lgIdx = admin_effective_lg_idx();
|
||||
if ($lgIdx !== null) {
|
||||
return $lgIdx;
|
||||
}
|
||||
$raw = session()->get('mb_lg_idx');
|
||||
|
||||
return ($raw !== null && $raw !== '') ? (int) $raw : 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_admin_nav_items')) {
|
||||
/**
|
||||
* 관리자 상단 메뉴 항목 (DB menu 테이블, admin 타입, 현재 지자체·mb_level 기준, 평면 배열).
|
||||
@@ -150,31 +130,22 @@ if (! function_exists('get_site_nav_tree')) {
|
||||
function get_site_nav_tree(): array
|
||||
{
|
||||
try {
|
||||
$lgIdx = resolve_site_menu_lg_idx();
|
||||
$lgIdx = session()->get('mb_lg_idx');
|
||||
// 시민 등 지자체 정보가 세션에 없으면 기본 지자체(1) 기준으로 메뉴를 보여 준다.
|
||||
if ($lgIdx === null || $lgIdx === '') {
|
||||
$lgIdx = 1;
|
||||
}
|
||||
$typeRow = model(\App\Models\MenuTypeModel::class)->getByCode('site');
|
||||
if (! $typeRow) {
|
||||
return [];
|
||||
}
|
||||
$mbLevel = (int) session()->get('mb_level');
|
||||
$menuModel = model(\App\Models\MenuModel::class);
|
||||
$typeRow = model(\App\Models\MenuTypeModel::class)->getByCode('site');
|
||||
$siteMtIdx = $typeRow ? (int) $typeRow->mt_idx : 0;
|
||||
if ($siteMtIdx <= 0) {
|
||||
// 운영 DB 불일치 대비: menu_type 누락 시 legacy site mt_idx(4)로 시도
|
||||
$siteMtIdx = 4;
|
||||
}
|
||||
|
||||
$flat = $menuModel->getVisibleByLevel($siteMtIdx, $mbLevel, (int) $lgIdx);
|
||||
$flat = $menuModel->getVisibleByLevel((int) $typeRow->mt_idx, $mbLevel, (int) $lgIdx);
|
||||
// 현재 지자체에 site 메뉴가 없으면, 기본 지자체(1)의 메뉴를 한 번 복사한 뒤 다시 시도
|
||||
if (empty($flat)) {
|
||||
$menuModel->copyDefaultsFromLg($siteMtIdx, 1, (int) $lgIdx);
|
||||
$flat = $menuModel->getVisibleByLevel($siteMtIdx, $mbLevel, (int) $lgIdx);
|
||||
}
|
||||
|
||||
// site 타입 매핑 불일치(예: menu_type=2, menu 데이터=4) 보정
|
||||
if (empty($flat) && $siteMtIdx !== 4) {
|
||||
$legacyMtIdx = 4;
|
||||
$flat = $menuModel->getVisibleByLevel($legacyMtIdx, $mbLevel, (int) $lgIdx);
|
||||
if (empty($flat)) {
|
||||
$menuModel->copyDefaultsFromLg($legacyMtIdx, 1, (int) $lgIdx);
|
||||
$flat = $menuModel->getVisibleByLevel($legacyMtIdx, $mbLevel, (int) $lgIdx);
|
||||
}
|
||||
$menuModel->copyDefaultsFromLg((int) $typeRow->mt_idx, 1, (int) $lgIdx);
|
||||
$flat = $menuModel->getVisibleByLevel((int) $typeRow->mt_idx, $mbLevel, (int) $lgIdx);
|
||||
}
|
||||
if (empty($flat)) {
|
||||
return [];
|
||||
@@ -185,381 +156,3 @@ if (! function_exists('get_site_nav_tree')) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('current_nav_request_path')) {
|
||||
/**
|
||||
* 메뉴 활성·mm_link 비교용 현재 경로 (라우트 기준, base_url 뒤 세그먼트).
|
||||
* request->getPath() · uri_string() · SiteURI::getRoutePath() 중 비어 있지 않은 값을 사용.
|
||||
*/
|
||||
function current_nav_request_path(): string
|
||||
{
|
||||
helper('url');
|
||||
|
||||
$request = service('request');
|
||||
// 프레임워크 권장: uri_string() = baseURL 기준 경로 (우선)
|
||||
$candidates = [trim(uri_string(), '/')];
|
||||
if ($request instanceof \CodeIgniter\HTTP\IncomingRequest) {
|
||||
$candidates[] = trim((string) $request->getPath(), '/');
|
||||
}
|
||||
$uri = $request->getUri();
|
||||
if ($uri instanceof \CodeIgniter\HTTP\SiteURI) {
|
||||
$candidates[] = trim($uri->getRoutePath(), '/');
|
||||
}
|
||||
|
||||
$path = '';
|
||||
foreach ($candidates as $c) {
|
||||
if ($c !== '') {
|
||||
$path = $c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (str_starts_with($path, 'index.php/')) {
|
||||
$path = substr($path, strlen('index.php/'));
|
||||
}
|
||||
|
||||
// baseURL 에 경로가 있으면(서브폴더 설치) URI 앞에 붙은 동일 접두 제거
|
||||
$basePath = parse_url(config(\Config\App::class)->baseURL, PHP_URL_PATH);
|
||||
$basePath = is_string($basePath) ? trim($basePath, '/') : '';
|
||||
if ($basePath !== '' && $path !== '' && ($path === $basePath || str_starts_with($path, $basePath . '/'))) {
|
||||
$path = $path === $basePath ? '' : substr($path, strlen($basePath) + 1);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('normalize_menu_link_for_url')) {
|
||||
/**
|
||||
* menu.mm_link 를 base_url() 인자로 쓸 수 있는 상대 경로로 정규화합니다.
|
||||
* http(s)://... 전체 URL이면 path 만 사용하고, 앞뒤 공백·슬래시를 정리합니다.
|
||||
*/
|
||||
function normalize_menu_link_for_url(?string $mmLink): string
|
||||
{
|
||||
$s = trim((string) $mmLink);
|
||||
if ($s === '') {
|
||||
return '';
|
||||
}
|
||||
if (str_contains($s, '://')) {
|
||||
$path = parse_url($s, PHP_URL_PATH);
|
||||
$s = is_string($path) ? trim($path, '/') : '';
|
||||
} else {
|
||||
$s = trim($s, '/');
|
||||
}
|
||||
|
||||
while (str_starts_with($s, 'index.php/')) {
|
||||
$s = substr($s, strlen('index.php/'));
|
||||
}
|
||||
if (str_starts_with($s, 'public/')) {
|
||||
$s = substr($s, strlen('public/'));
|
||||
}
|
||||
|
||||
$basePath = parse_url(config(\Config\App::class)->baseURL, PHP_URL_PATH);
|
||||
$basePath = is_string($basePath) ? trim($basePath, '/') : '';
|
||||
if ($basePath !== '' && $s !== '' && ($s === $basePath || str_starts_with($s, $basePath . '/'))) {
|
||||
$s = $s === $basePath ? '' : substr($s, strlen($basePath) + 1);
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('mgmt_url')) {
|
||||
/**
|
||||
* 업무 화면 링크: 정식 URL 은 /bag/* (adminAuth). 포장 단위 CRUD 는 packaging-units/manage 로 치환.
|
||||
*/
|
||||
function mgmt_url(string $path): string
|
||||
{
|
||||
helper('url');
|
||||
$path = trim($path, '/');
|
||||
// bag/packaging-units 는 조회 전용(Bag) — CRUD 는 /bag/packaging-units/manage/* 로 분리
|
||||
if ($path === 'packaging-units') {
|
||||
$path = 'packaging-units/manage';
|
||||
} elseif (str_starts_with($path, 'packaging-units/')) {
|
||||
$path = 'packaging-units/manage/' . substr($path, strlen('packaging-units/'));
|
||||
}
|
||||
|
||||
return site_url('bag/' . $path);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('work_area_home_url')) {
|
||||
/**
|
||||
* 지자체 미선택 등으로 돌아갈 때: bag 업무 중이면 대시보드, 관리자면 admin 홈.
|
||||
*/
|
||||
function work_area_home_url(): string
|
||||
{
|
||||
helper('url');
|
||||
$seg1 = service('request')->getUri()->getSegment(1);
|
||||
|
||||
return ($seg1 === 'bag') ? site_url('dashboard') : site_url('admin');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('format_ymd_korean')) {
|
||||
/**
|
||||
* Y-m-d 날짜를 '2026년 1월 5일' 형식으로 (월·일은 숫자, 월명은 한글 '월').
|
||||
*/
|
||||
function format_ymd_korean(?string $ymd): string
|
||||
{
|
||||
if ($ymd === null || trim($ymd) === '') {
|
||||
return '—';
|
||||
}
|
||||
$t = \DateTimeImmutable::createFromFormat('Y-m-d', trim($ymd));
|
||||
if ($t === false) {
|
||||
return $ymd;
|
||||
}
|
||||
|
||||
return $t->format('Y') . '년 ' . (int) $t->format('n') . '월 ' . (int) $t->format('j') . '일';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('parse_ymd_from_triple')) {
|
||||
/**
|
||||
* 연·월·일 GET 값으로 Y-m-d 생성 (유효하지 않은 날짜는 null).
|
||||
*/
|
||||
function parse_ymd_from_triple(?string $y, ?string $m, ?string $d): ?string
|
||||
{
|
||||
if ($y === null || $y === '' || $m === null || $m === '' || $d === null || $d === '') {
|
||||
return null;
|
||||
}
|
||||
$yi = (int) $y;
|
||||
$mi = (int) $m;
|
||||
$di = (int) $d;
|
||||
if ($yi < 1000 || $yi > 9999 || ! checkdate($mi, $di, $yi)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sprintf('%04d-%02d-%02d', $yi, $mi, $di);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('site_nav_resolved_link_path')) {
|
||||
/**
|
||||
* 사이트 상단 메뉴 URL 세그먼트. mm_link(DB)만 사용 (비어 있으면 빈 문자열).
|
||||
*
|
||||
* @param string|null $mmName 호환용(미사용).
|
||||
*
|
||||
* @return string base_url() 인자 세그먼트(앞뒤 슬래시 없음)
|
||||
*/
|
||||
function site_nav_resolved_link_path(?string $mmLink, ?string $mmName = null): string
|
||||
{
|
||||
return normalize_menu_link_for_url($mmLink);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('menu_link_candidate_paths')) {
|
||||
/**
|
||||
* 활성 비교용 경로 후보. DB에 "menus" 처럼 짧게 넣은 경우 실제 URI가 admin/menus·bag/… 일 수 있어,
|
||||
* 현재 요청 경로에 맞게 admin/·bag/ 접두를 붙인 후보도 만든다. (슬래시 포함·admin 단독은 그대로 1개만)
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
function menu_link_candidate_paths(?string $mmLink, string $currentPath): array
|
||||
{
|
||||
$p = normalize_menu_link_for_url($mmLink);
|
||||
if ($p === '') {
|
||||
return [];
|
||||
}
|
||||
if (str_contains($p, '/') || $p === 'admin') {
|
||||
$cands = [$p];
|
||||
if (preg_match('#^bag/packaging-units/manage(/.*)?$#', $p, $m)) {
|
||||
$cands[] = 'admin/packaging-units' . ($m[1] ?? '');
|
||||
} elseif (preg_match('#^admin/packaging-units(/.*)?$#', $p, $m)) {
|
||||
$cands[] = 'bag/packaging-units/manage' . ($m[1] ?? '');
|
||||
} elseif (str_starts_with($p, 'admin/')) {
|
||||
$cands[] = 'bag/' . substr($p, strlen('admin/'));
|
||||
} elseif (str_starts_with($p, 'bag/')) {
|
||||
$cands[] = 'admin/' . substr($p, strlen('bag/'));
|
||||
}
|
||||
|
||||
return array_values(array_unique($cands));
|
||||
}
|
||||
$out = [$p];
|
||||
if (str_starts_with($currentPath, 'admin/') || $currentPath === 'admin') {
|
||||
$out[] = 'admin/' . $p;
|
||||
}
|
||||
if (str_starts_with($currentPath, 'bag/') || $currentPath === 'bag') {
|
||||
$out[] = 'bag/' . $p;
|
||||
}
|
||||
|
||||
return array_values(array_unique($out));
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('menu_link_preferred_href_path')) {
|
||||
/**
|
||||
* base_url() 용 경로: 짧게 저장된 mm_link 는 현재 요청 기준 admin/·bag/ 후보 중 가장 알맞은 것 사용.
|
||||
*/
|
||||
function menu_link_preferred_href_path(?string $mmLink, string $currentPath): string
|
||||
{
|
||||
$cands = menu_link_candidate_paths($mmLink, $currentPath);
|
||||
if ($cands === []) {
|
||||
return '';
|
||||
}
|
||||
foreach ($cands as $c) {
|
||||
$cl = strtolower($currentPath);
|
||||
$cc = strtolower($c);
|
||||
if ($cl === $cc || str_starts_with($cl, $cc . '/')) {
|
||||
return $c;
|
||||
}
|
||||
}
|
||||
foreach ($cands as $c) {
|
||||
if (str_contains($c, '/')) {
|
||||
return $c;
|
||||
}
|
||||
}
|
||||
|
||||
return $cands[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('menu_single_path_matches_request')) {
|
||||
/**
|
||||
* 단일 정규 경로가 현재 요청 path 와 일치하는지.
|
||||
*
|
||||
* @param list<string> $dashboardPathAliases
|
||||
*/
|
||||
function menu_single_path_matches_request(string $path, string $currentPath, array $dashboardPathAliases = []): bool
|
||||
{
|
||||
if ($path === '') {
|
||||
return false;
|
||||
}
|
||||
$pathLower = strtolower($path);
|
||||
$currentLower = strtolower($currentPath);
|
||||
$aliasesLower = array_map(strtolower(...), $dashboardPathAliases);
|
||||
|
||||
if ($dashboardPathAliases !== []
|
||||
&& in_array($pathLower, $aliasesLower, true)
|
||||
&& in_array($currentLower, $aliasesLower, true)) {
|
||||
return true;
|
||||
}
|
||||
if ($currentLower === $pathLower) {
|
||||
return true;
|
||||
}
|
||||
if ($pathLower === 'admin') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return str_starts_with($currentLower, $pathLower . '/');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('menu_link_matches_request')) {
|
||||
/**
|
||||
* 메뉴 mm_link(DB)가 현재 요청과 같은 메뉴인지. 비어 있으면 false.
|
||||
*
|
||||
* @param list<string> $dashboardPathAliases
|
||||
*/
|
||||
function menu_link_matches_request(?string $mmLink, string $currentPath, array $dashboardPathAliases = []): bool
|
||||
{
|
||||
foreach (menu_link_candidate_paths($mmLink, $currentPath) as $cand) {
|
||||
if (menu_single_path_matches_request($cand, $currentPath, $dashboardPathAliases)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('site_nav_link_matches_current')) {
|
||||
/**
|
||||
* 사이트 상단 메뉴 활성 여부 (경로 후보·대시보드 별칭은 menu_link_matches_request 와 동일).
|
||||
*
|
||||
* @param list<string> $dashboardPathAliases
|
||||
*/
|
||||
function site_nav_link_matches_current(?string $mmLink, string $currentPath, array $dashboardPathAliases = []): bool
|
||||
{
|
||||
return menu_link_matches_request($mmLink, $currentPath, $dashboardPathAliases);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('menu_active_child_for_parent')) {
|
||||
/**
|
||||
* 같은 부모 아래 형제 소메뉴 중, 현재 요청에 해당하는 항목을 하나만 고른다.
|
||||
*
|
||||
* 짧은 mm_link(예: bag/designated-shops)가 긴 경로(bag/designated-shops/browse)와
|
||||
* 동시에 prefix 규칙으로 매칭될 때, 가장 구체적인 경로(일치한 후보 문자열 길이 최대)만 활성으로 본다.
|
||||
* 길이가 같으면 mm_num이 작은 항목을 선택(동일 URL이 여러 메뉴에 매핑된 경우 등).
|
||||
*
|
||||
* @param object{children?: array<int, object>} $parentNavItem
|
||||
* @param list<string> $dashboardPathAliases
|
||||
*
|
||||
* @return object|null 활성으로 표시할 자식 노드(mm_idx 등 포함), 없으면 null
|
||||
*/
|
||||
function menu_active_child_for_parent(object $parentNavItem, string $currentPath, array $dashboardPathAliases = []): ?object
|
||||
{
|
||||
$children = $parentNavItem->children ?? [];
|
||||
if ($children === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$best = null;
|
||||
$bestLen = -1;
|
||||
$bestMmNum = PHP_INT_MAX;
|
||||
|
||||
foreach ($children as $child) {
|
||||
$mmLink = $child->mm_link ?? null;
|
||||
$maxLen = -1;
|
||||
foreach (menu_link_candidate_paths($mmLink, $currentPath) as $cand) {
|
||||
if (menu_single_path_matches_request($cand, $currentPath, $dashboardPathAliases)) {
|
||||
$maxLen = max($maxLen, strlen($cand));
|
||||
}
|
||||
}
|
||||
if ($maxLen < 0) {
|
||||
continue;
|
||||
}
|
||||
$mmNum = (int) ($child->mm_num ?? 0);
|
||||
if ($maxLen > $bestLen || ($maxLen === $bestLen && $mmNum < $bestMmNum)) {
|
||||
$bestLen = $maxLen;
|
||||
$bestMmNum = $mmNum;
|
||||
$best = $child;
|
||||
}
|
||||
}
|
||||
|
||||
return $best;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('site_nav_active_child_for_parent')) {
|
||||
/**
|
||||
* 사이트 상단 메뉴 전용 호환 래퍼.
|
||||
*
|
||||
* @param object{children?: array<int, object>} $parentNavItem
|
||||
* @param list<string> $dashboardPathAliases
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
function site_nav_active_child_for_parent(object $parentNavItem, string $currentPath, array $dashboardPathAliases = []): ?object
|
||||
{
|
||||
return menu_active_child_for_parent($parentNavItem, $currentPath, $dashboardPathAliases);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('session_user_nav_display')) {
|
||||
/**
|
||||
* 상단 메뉴바용: 로그인 사용자 이름·역할 표시
|
||||
*
|
||||
* @return array{name: string, role_label: string}|null
|
||||
*/
|
||||
function session_user_nav_display(): ?array
|
||||
{
|
||||
if (! session()->get('logged_in')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = trim((string) session()->get('mb_name'));
|
||||
if ($name === '') {
|
||||
$name = (string) session()->get('mb_id');
|
||||
}
|
||||
|
||||
$level = (int) session()->get('mb_level');
|
||||
$roleLabel = config('Roles')->getLevelName($level);
|
||||
|
||||
return [
|
||||
'name' => $name,
|
||||
'role_label' => $roleLabel,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ declare(strict_types=1);
|
||||
*
|
||||
* 저장 형식: 암호화된 값은 "ENC:" + base64(암호문) 으로 저장. "ENC:" 없으면 평문(기존)으로 간주.
|
||||
*/
|
||||
|
||||
if (! function_exists('pii_encrypt')) {
|
||||
function pii_encrypt(?string $value): string
|
||||
{
|
||||
@@ -22,8 +21,9 @@ if (! function_exists('pii_encrypt')) {
|
||||
}
|
||||
$encrypter = service('encrypter');
|
||||
$encrypted = $encrypter->encrypt($value);
|
||||
|
||||
return 'ENC:' . base64_encode($encrypted);
|
||||
} catch (Throwable $e) {
|
||||
} catch (Throwable) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -44,23 +44,13 @@ if (! function_exists('pii_decrypt')) {
|
||||
return $value;
|
||||
}
|
||||
$encrypter = service('encrypter');
|
||||
$payload = substr($value, 4);
|
||||
|
||||
// 현재 포맷: ENC: + base64(raw ciphertext)
|
||||
$raw = base64_decode($payload, true);
|
||||
if ($raw !== false) {
|
||||
try {
|
||||
return $encrypter->decrypt($raw);
|
||||
} catch (Throwable $e) {
|
||||
// legacy 포맷 재시도
|
||||
}
|
||||
$raw = base64_decode(substr($value, 4), true);
|
||||
if ($raw === false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// 레거시 포맷 호환:
|
||||
// - ENC: + encrypter 반환값(rawData=false 환경 등) 또는
|
||||
// - ENC: + 기타 문자열 포맷
|
||||
return $encrypter->decrypt($payload);
|
||||
} catch (Throwable $e) {
|
||||
return $encrypter->decrypt($raw);
|
||||
} catch (Throwable) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
84
app/Helpers/pii_mask_helper.php
Normal file
84
app/Helpers/pii_mask_helper.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 화면 표시용 개인정보 비식별화 (저장 값은 변경하지 않음).
|
||||
* ENC: 접두사 값은 pii_decrypt 후 마스킹한다.
|
||||
*/
|
||||
|
||||
helper('pii_encryption');
|
||||
|
||||
if (! function_exists('mask_person_name')) {
|
||||
/**
|
||||
* 인명 마스킹 (예: 홍길동 → 홍*동, 김철 → 김*, 남궁민수 → 남**수).
|
||||
*/
|
||||
function mask_person_name(?string $name): string
|
||||
{
|
||||
if ($name === null) {
|
||||
return '';
|
||||
}
|
||||
$plain = pii_decrypt(trim($name));
|
||||
if ($plain === '') {
|
||||
return '';
|
||||
}
|
||||
$len = mb_strlen($plain, 'UTF-8');
|
||||
if ($len <= 1) {
|
||||
return '*';
|
||||
}
|
||||
if ($len === 2) {
|
||||
return mb_substr($plain, 0, 1, 'UTF-8') . '*';
|
||||
}
|
||||
$first = mb_substr($plain, 0, 1, 'UTF-8');
|
||||
$last = mb_substr($plain, -1, 1, 'UTF-8');
|
||||
if ($len === 3) {
|
||||
return $first . '*' . $last;
|
||||
}
|
||||
|
||||
return $first . str_repeat('*', $len - 2) . $last;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('mask_mobile_phone')) {
|
||||
/**
|
||||
* 휴대·개인 전화 표시용 (예: 010-1234-5678 → 010-****-5678).
|
||||
* 10자리(02 등)·11자리 휴대번호·+82 형태를 단순 정규화 후 처리한다.
|
||||
*/
|
||||
function mask_mobile_phone(?string $phone): string
|
||||
{
|
||||
if ($phone === null) {
|
||||
return '';
|
||||
}
|
||||
$plain = pii_decrypt(trim($phone));
|
||||
if ($plain === '') {
|
||||
return '';
|
||||
}
|
||||
$digits = preg_replace('/\D+/', '', $plain) ?? '';
|
||||
if ($digits === '') {
|
||||
return '';
|
||||
}
|
||||
if (str_starts_with($digits, '82') && strlen($digits) >= 12) {
|
||||
$digits = '0' . substr($digits, 2);
|
||||
}
|
||||
$len = strlen($digits);
|
||||
if ($len < 7) {
|
||||
return str_repeat('*', min(11, max(4, $len)));
|
||||
}
|
||||
// 7~9자리도 실제 앞자리(통상 010 등)를 노출하고 가운데만 **** (***-****-xxxx 방지)
|
||||
if ($len < 10) {
|
||||
return substr($digits, 0, 3) . '-****-' . substr($digits, -4);
|
||||
}
|
||||
if ($len === 10) {
|
||||
if (str_starts_with($digits, '02')) {
|
||||
return '02-****-' . substr($digits, -4);
|
||||
}
|
||||
|
||||
return substr($digits, 0, 3) . '-****-' . substr($digits, -4);
|
||||
}
|
||||
if ($len === 11) {
|
||||
return substr($digits, 0, 3) . '-****-' . substr($digits, -4);
|
||||
}
|
||||
|
||||
return substr($digits, 0, 3) . '-****-' . substr($digits, -4);
|
||||
}
|
||||
}
|
||||
@@ -10,26 +10,9 @@ class BagOrderModel extends Model
|
||||
protected $primaryKey = 'bo_idx';
|
||||
protected $returnType = 'object';
|
||||
protected $useTimestamps = false;
|
||||
|
||||
/**
|
||||
* 동일 발주 UUID에 대해 bo_version이 최대인 행만 (수정으로 생긴 이전 버전 행은 목록·이력에서 제외).
|
||||
* DB에는 버전별 행이 그대로 남고, 조회 시에만 필터한다.
|
||||
*/
|
||||
public function whereLatestHead(int $lgIdx): self
|
||||
{
|
||||
$lg = (int) $lgIdx;
|
||||
|
||||
return $this->where(
|
||||
"(bo_uuid, bo_version) IN (SELECT bo_uuid, MAX(bo_version) FROM {$this->table} WHERE bo_lg_idx = {$lg} GROUP BY bo_uuid)",
|
||||
null,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
protected $allowedFields = [
|
||||
'bo_uuid', 'bo_version', 'bo_lg_idx', 'bo_gugun_code', 'bo_dong_code',
|
||||
'bo_company_idx', 'bo_agency_idx', 'bo_fee_rate', 'bo_order_date',
|
||||
'bo_bag_types', 'bo_unit_prices', 'bo_qty_boxes',
|
||||
'bo_lot_no', 'bo_hash', 'bo_status', 'bo_orderer_idx',
|
||||
'bo_regdate', 'bo_moddate',
|
||||
];
|
||||
|
||||
@@ -16,45 +16,4 @@ class BagPriceModel extends Model
|
||||
'bp_start_date', 'bp_end_date', 'bp_state',
|
||||
'bp_regdate', 'bp_moddate', 'bp_reg_mb_idx',
|
||||
];
|
||||
|
||||
/**
|
||||
* 같은 봉투코드에 단가 기간이 겹쳐도 "나중 등록 단가"가 우선되도록
|
||||
* 활성 단가를 등록일/PK 역순으로 정렬해 봉투코드별 1건만 남긴다.
|
||||
*
|
||||
* @return array<string, object>
|
||||
*/
|
||||
public function latestActiveMapByBagCode(int $lgIdx): array
|
||||
{
|
||||
$rows = $this->where('bp_lg_idx', $lgIdx)
|
||||
->where('bp_state', 1)
|
||||
->orderBy('bp_regdate', 'DESC')
|
||||
->orderBy('bp_idx', 'DESC')
|
||||
->findAll();
|
||||
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
$code = (string) ($row->bp_bag_code ?? '');
|
||||
if ($code === '' || isset($map[$code])) {
|
||||
continue;
|
||||
}
|
||||
$map[$code] = $row;
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function latestActiveByBagCode(int $lgIdx, string $bagCode): ?object
|
||||
{
|
||||
$bagCode = trim($bagCode);
|
||||
if ($bagCode === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->where('bp_lg_idx', $lgIdx)
|
||||
->where('bp_bag_code', $bagCode)
|
||||
->where('bp_state', 1)
|
||||
->orderBy('bp_regdate', 'DESC')
|
||||
->orderBy('bp_idx', 'DESC')
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
@@ -14,8 +12,6 @@ class CodeDetailModel extends Model
|
||||
protected $useTimestamps = false;
|
||||
protected $allowedFields = [
|
||||
'cd_ck_idx',
|
||||
'cd_source',
|
||||
'cd_lg_idx',
|
||||
'cd_code',
|
||||
'cd_name',
|
||||
'cd_sort',
|
||||
@@ -23,51 +19,15 @@ class CodeDetailModel extends Model
|
||||
'cd_regdate',
|
||||
];
|
||||
|
||||
/**
|
||||
* 목록 조회: 플랫폼(0) + (선택) 해당 지자체 행
|
||||
*
|
||||
* @param int|null $effectiveLgIdx null 또는 1 미만이면 플랫폼 공통만
|
||||
*/
|
||||
public function filterByTenantScope(?int $effectiveLgIdx): self
|
||||
{
|
||||
if ($effectiveLgIdx === null || $effectiveLgIdx < 1) {
|
||||
return $this->where('cd_lg_idx', 0);
|
||||
}
|
||||
|
||||
return $this->groupStart()
|
||||
->where('cd_lg_idx', 0)
|
||||
->orWhere('cd_lg_idx', $effectiveLgIdx)
|
||||
->groupEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 코드 종류의 세부코드 목록
|
||||
*
|
||||
* @param int|null $effectiveLgIdx 테넌트 범위 (null=플랫폼만)
|
||||
*/
|
||||
public function getByKind(int $ckIdx, bool $activeOnly = false, ?int $effectiveLgIdx = null): array
|
||||
public function getByKind(int $ckIdx, bool $activeOnly = false): array
|
||||
{
|
||||
$this->where('cd_ck_idx', $ckIdx);
|
||||
$this->filterByTenantScope($effectiveLgIdx);
|
||||
$builder = $this->where('cd_ck_idx', $ckIdx);
|
||||
if ($activeOnly) {
|
||||
$this->where('cd_state', 1);
|
||||
$builder->where('cd_state', 1);
|
||||
}
|
||||
|
||||
return $this->orderBy('cd_sort', 'ASC')->orderBy('cd_idx', 'ASC')->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 동일 세부코드값: 지자체 전용이 있으면 우선, 없으면 플랫폼
|
||||
*/
|
||||
public function findResolvedByKindAndCode(int $ckIdx, string $code, ?int $effectiveLgIdx): ?object
|
||||
{
|
||||
if ($effectiveLgIdx !== null && $effectiveLgIdx > 0) {
|
||||
$local = $this->where('cd_ck_idx', $ckIdx)->where('cd_code', $code)->where('cd_lg_idx', $effectiveLgIdx)->first();
|
||||
if ($local !== null) {
|
||||
return $local;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->where('cd_ck_idx', $ckIdx)->where('cd_code', $code)->where('cd_lg_idx', 0)->first();
|
||||
return $builder->orderBy('cd_sort', 'ASC')->findAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,25 +17,16 @@ class DesignatedShopModel extends Model
|
||||
'ds_name',
|
||||
'ds_biz_no',
|
||||
'ds_rep_name',
|
||||
'ds_biz_type',
|
||||
'ds_biz_kind',
|
||||
'ds_va_number',
|
||||
'ds_va_bank',
|
||||
'ds_va_account',
|
||||
'ds_zip',
|
||||
'ds_addr',
|
||||
'ds_addr_jibun',
|
||||
'ds_addr_detail',
|
||||
'ds_tel',
|
||||
'ds_rep_phone',
|
||||
'ds_email',
|
||||
'ds_gugun_code',
|
||||
'ds_zone_code',
|
||||
'ds_branch_no',
|
||||
'ds_designated_at',
|
||||
'ds_state',
|
||||
'ds_state_changed_at',
|
||||
'ds_change_reason',
|
||||
'ds_regdate',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -189,67 +189,4 @@ class MenuModel extends Model
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 메뉴 타입(mt_idx)을 source 지자체 기준으로 모든 지자체에 재배포.
|
||||
* 기존 대상 지자체의 해당 타입 메뉴는 삭제 후 source 구조로 재생성한다.
|
||||
*/
|
||||
public function syncTypeToAllLgs(int $mtIdx, int $sourceLg): void
|
||||
{
|
||||
if ($mtIdx <= 0 || $sourceLg <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$source = $this->where('mt_idx', $mtIdx)
|
||||
->where('lg_idx', $sourceLg)
|
||||
->orderBy('mm_dep', 'ASC')
|
||||
->orderBy('mm_num', 'ASC')
|
||||
->findAll();
|
||||
if (empty($source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lgRows = $this->db->table('local_government')
|
||||
->select('lg_idx')
|
||||
->orderBy('lg_idx', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($lgRows as $lgRow) {
|
||||
$destLg = (int) ($lgRow['lg_idx'] ?? 0);
|
||||
if ($destLg <= 0 || $destLg === $sourceLg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->db->transStart();
|
||||
$this->where('mt_idx', $mtIdx)
|
||||
->where('lg_idx', $destLg)
|
||||
->delete();
|
||||
|
||||
$idMap = [];
|
||||
foreach ($source as $row) {
|
||||
$oldId = (int) ($row->mm_idx ?? 0);
|
||||
$oldP = (int) ($row->mm_pidx ?? 0);
|
||||
$newPidx = 0;
|
||||
if ($oldP > 0 && isset($idMap[$oldP])) {
|
||||
$newPidx = (int) $idMap[$oldP];
|
||||
}
|
||||
|
||||
$this->insert([
|
||||
'mt_idx' => $mtIdx,
|
||||
'lg_idx' => $destLg,
|
||||
'mm_name' => (string) ($row->mm_name ?? ''),
|
||||
'mm_link' => (string) ($row->mm_link ?? ''),
|
||||
'mm_pidx' => $newPidx,
|
||||
'mm_dep' => (int) ($row->mm_dep ?? 0),
|
||||
'mm_num' => (int) ($row->mm_num ?? 0),
|
||||
'mm_cnode' => (int) ($row->mm_cnode ?? 0),
|
||||
'mm_level' => (string) ($row->mm_level ?? ''),
|
||||
'mm_is_view' => (string) ($row->mm_is_view ?? 'Y'),
|
||||
]);
|
||||
$idMap[$oldId] = (int) $this->getInsertID();
|
||||
}
|
||||
$this->db->transComplete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
@@ -13,34 +11,7 @@ class SalesAgencyModel extends Model
|
||||
protected $returnType = 'object';
|
||||
protected $useTimestamps = false;
|
||||
protected $allowedFields = [
|
||||
'sa_lg_idx',
|
||||
'sa_kind',
|
||||
'sa_code',
|
||||
'sa_name',
|
||||
'sa_regdate',
|
||||
'sa_lg_idx', 'sa_name', 'sa_biz_no', 'sa_rep_name',
|
||||
'sa_tel', 'sa_addr', 'sa_state', 'sa_regdate',
|
||||
];
|
||||
|
||||
/** sales_agency 테이블에 sa_kind, sa_code 컬럼이 있는지(마이그레이션 적용 여부). */
|
||||
public function hasKindCodeColumns(): bool
|
||||
{
|
||||
static $cache = null;
|
||||
if ($cache === null) {
|
||||
$cols = db_connect()->getFieldNames($this->table);
|
||||
$cache = in_array('sa_kind', $cols, true) && in_array('sa_code', $cols, true);
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 신규 스키마면 구분·코드 순, 아니면 명·PK 순(옛 DB 호환).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function orderForDisplay()
|
||||
{
|
||||
return $this->hasKindCodeColumns()
|
||||
? $this->orderBy('sa_kind', 'ASC')->orderBy('sa_code', 'ASC')
|
||||
: $this->orderBy('sa_name', 'ASC')->orderBy('sa_idx', 'ASC');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700">재고 현황</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= mgmt_url('bag-inventory/export') ?>" class="no-print border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
||||
<a href="<?= base_url('admin/bag-inventory/export') ?>" class="no-print border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">무료용 불출 처리</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('bag-issues/store') ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/bag-issues/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= mgmt_url('bag-issues') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/bag-issues') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
<span class="text-sm font-bold text-gray-700">무료용 불출 관리</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('bag-issues/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">불출 처리</a>
|
||||
<a href="<?= base_url('admin/bag-issues/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">불출 처리</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('bag-issues') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="GET" action="<?= base_url('admin/bag-issues') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">불출일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('bag-issues') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
<a href="<?= base_url('admin/bag-issues') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
@@ -49,7 +49,7 @@
|
||||
<td><?= number_format((int) $row->bi2_qty) ?></td>
|
||||
<td class="text-center"><?= esc($row->bi2_status) ?></td>
|
||||
<td class="text-center">
|
||||
<form action="<?= mgmt_url('bag-issues/cancel/' . (int) $row->bi2_idx) ?>" method="POST" class="inline" onsubmit="return confirm('취소하시겠습니까?');">
|
||||
<form action="<?= base_url('admin/bag-issues/cancel/' . (int) $row->bi2_idx) ?>" method="POST" class="inline" onsubmit="return confirm('취소하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-orange-600 hover:underline text-sm">취소</button>
|
||||
</form>
|
||||
|
||||
@@ -1,443 +1,83 @@
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<span class="text-sm font-bold text-gray-700">발주 등록</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-4xl">
|
||||
<form action="<?= base_url('admin/bag-orders/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<?php
|
||||
$oldBagCodes = old('item_bag_code');
|
||||
$oldQtyBoxes = old('item_qty_box');
|
||||
$oldQtySheets = old('item_qty_sheet');
|
||||
$oldBagCodes = is_array($oldBagCodes) ? $oldBagCodes : [];
|
||||
$oldQtyBoxes = is_array($oldQtyBoxes) ? $oldQtyBoxes : [];
|
||||
$oldQtySheets = is_array($oldQtySheets) ? $oldQtySheets : [];
|
||||
$defaultOrderDate = old('bo_order_date', date('Y-m-d'));
|
||||
$defaultOrderMonth = old('bo_order_month_ui', substr($defaultOrderDate, 0, 7));
|
||||
|
||||
$bagMeta = [];
|
||||
foreach (($bagReferenceRows ?? []) as $row) {
|
||||
$bagMeta[$row['code']] = [
|
||||
'name' => $row['name'],
|
||||
'orderPrice' => (float) $row['orderPrice'],
|
||||
'boxPerPack' => (int) $row['boxPerPack'],
|
||||
'packPerSheet' => (int) $row['packPerSheet'],
|
||||
'totalPerBox' => max(1, (int) $row['totalPerBox']),
|
||||
];
|
||||
}
|
||||
|
||||
$initialSelectedItems = [];
|
||||
$maxOldCount = max(count($oldBagCodes), count($oldQtySheets), count($oldQtyBoxes));
|
||||
for ($i = 0; $i < $maxOldCount; $i++) {
|
||||
$code = trim((string) ($oldBagCodes[$i] ?? ''));
|
||||
if ($code === '' || ! isset($bagMeta[$code])) {
|
||||
continue;
|
||||
}
|
||||
$fallbackQtyBox = (int) ($oldQtyBoxes[$i] ?? 0);
|
||||
$rawQtySheet = (int) ($oldQtySheets[$i] ?? 0);
|
||||
$fallbackTotalPerBox = (int) ($bagMeta[$code]['totalPerBox'] ?? 1);
|
||||
if ($fallbackQtyBox <= 0 && $rawQtySheet > 0) {
|
||||
$fallbackQtyBox = intdiv($rawQtySheet, max(1, $fallbackTotalPerBox));
|
||||
}
|
||||
$initialSelectedItems[] = [
|
||||
'code' => $code,
|
||||
'qtyBox' => max(0, $fallbackQtyBox),
|
||||
];
|
||||
}
|
||||
|
||||
$statusMap = ['normal' => '정상', 'cancelled' => '취소', 'deleted' => '삭제'];
|
||||
?>
|
||||
|
||||
<form action="<?= mgmt_url('bag-orders/store') ?>" method="POST" class="mt-2 space-y-2">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="border border-gray-300 bg-white p-2">
|
||||
<div class="flex flex-wrap items-center gap-4 text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="bo_order_month_ui" class="font-bold text-gray-700">발주월</label>
|
||||
<input id="bo_order_month_ui" name="bo_order_month_ui" type="month" value="<?= esc($defaultOrderMonth) ?>" class="border border-gray-300 rounded px-2 py-1" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="bo_order_date" class="font-bold text-gray-700">발주일 <span class="text-red-500">*</span></label>
|
||||
<input id="bo_order_date" name="bo_order_date" type="date" value="<?= esc($defaultOrderDate) ?>" required class="border border-gray-300 rounded px-2 py-1" />
|
||||
</div>
|
||||
<p class="text-blue-600 font-bold">※ 발주수량은 박스단위로 입력해 주세요. (발주일은 미래일도 선택 가능)</p>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">발주일 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="bo_order_date" type="date" value="<?= esc(old('bo_order_date', date('Y-m-d'))) ?>" required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 xl:grid-cols-12 gap-2">
|
||||
<section class="xl:col-span-5 border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 이력</div>
|
||||
<div class="overflow-auto max-h-[410px]">
|
||||
<table class="w-full data-table text-sm">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">수수료율</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32 text-right" name="bo_fee_rate" type="number" step="0.01" value="<?= esc(old('bo_fee_rate', '0')) ?>"/>
|
||||
<span class="text-sm text-gray-500">%</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">제작업체</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="bo_company_idx">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($companies as $cp): ?>
|
||||
<option value="<?= esc($cp->cp_idx) ?>" <?= (int) old('bo_company_idx') === (int) $cp->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc($cp->cp_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">입고처</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="bo_agency_idx">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($agencies as $ag): ?>
|
||||
<option value="<?= esc($ag->sa_idx) ?>" <?= (int) old('bo_agency_idx') === (int) $ag->sa_idx ? 'selected' : '' ?>>
|
||||
<?= esc($ag->sa_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label class="block text-sm font-bold text-gray-700 mb-2">발주 품목</label>
|
||||
<div class="border border-gray-300 overflow-auto">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-28">발주일</th>
|
||||
<th>제작업체</th>
|
||||
<th>입고처</th>
|
||||
<th class="w-16">상태</th>
|
||||
<th class="w-16">순번</th>
|
||||
<th>봉투</th>
|
||||
<th class="w-32">박스수</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($recentOrders ?? []) as $history): ?>
|
||||
<?php for ($i = 0; $i < 3; $i++): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc((string) $history->bo_order_date) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($companyMap[(int) $history->bo_company_idx] ?? '-')) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($agencyMap[(int) $history->bo_agency_idx] ?? '-')) ?></td>
|
||||
<td class="text-center"><?= esc((string) ($statusMap[(string) $history->bo_status] ?? $history->bo_status)) ?></td>
|
||||
<td class="text-center"><?= $i + 1 ?></td>
|
||||
<td>
|
||||
<select class="border border-gray-300 rounded px-2 py-1 text-sm w-full" name="item_bag_code[]">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($bagCodes as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>">
|
||||
<?= esc($cd->cd_code) ?> — <?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input class="border border-gray-300 rounded px-2 py-1 text-sm w-full text-right" name="item_qty_box[]" type="number" min="0" value="0"/>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($recentOrders)): ?>
|
||||
<tr><td colspan="4" class="text-center text-gray-400 py-4">발주 이력이 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php endfor; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="xl:col-span-7 border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 Form</div>
|
||||
<div class="p-2 space-y-2">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_fee_rate" class="w-20 font-bold text-gray-700">수수료</label>
|
||||
<input id="bo_fee_rate" name="bo_fee_rate" type="number" step="0.01" value="<?= esc(old('bo_fee_rate', '0')) ?>" class="border border-gray-300 rounded px-2 py-1 w-24 text-right" />
|
||||
<span>%</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_association_idx" class="w-20 font-bold text-gray-700">협회</label>
|
||||
<select id="bo_association_idx" name="bo_association_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
|
||||
<option value="">선택</option>
|
||||
<?php foreach (($associations ?? []) as $association): ?>
|
||||
<option value="<?= esc((string) $association->cp_idx) ?>" <?= (int) old('bo_association_idx') === (int) $association->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc((string) $association->cp_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_company_idx" class="w-20 font-bold text-gray-700">제작업체</label>
|
||||
<select id="bo_company_idx" name="bo_company_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
|
||||
<option value="">선택</option>
|
||||
<?php foreach (($companies ?? []) as $company): ?>
|
||||
<option value="<?= esc((string) $company->cp_idx) ?>" <?= (int) old('bo_company_idx') === (int) $company->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc((string) $company->cp_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<label for="bo_agency_idx" class="w-20 font-bold text-gray-700">입고처</label>
|
||||
<select id="bo_agency_idx" name="bo_agency_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
|
||||
<option value="">선택</option>
|
||||
<?php foreach (($agencies ?? []) as $agency): ?>
|
||||
<option value="<?= esc((string) $agency->sa_idx) ?>" <?= (int) old('bo_agency_idx') === (int) $agency->sa_idx ? 'selected' : '' ?>>
|
||||
[<?= esc((string) ($agency->sa_kind ?? '')) ?>] <?= esc((string) ($agency->sa_code ?? '')) ?> — <?= esc((string) $agency->sa_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-gray-300 overflow-auto">
|
||||
<table class="w-full data-table text-sm order-input-table" id="order-item-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-12">번호</th>
|
||||
<th class="w-16">선택</th>
|
||||
<th>품명</th>
|
||||
<th class="w-28">수량(BOX)</th>
|
||||
<th class="w-24">단가</th>
|
||||
<th class="w-24">환산수량</th>
|
||||
<th class="w-28">금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="selected-order-items-body"></tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="3" class="text-center">계</th>
|
||||
<th class="text-right pr-2" id="sum-box-qty">0</th>
|
||||
<th></th>
|
||||
<th class="text-right pr-2" id="sum-sheet-qty">0</th>
|
||||
<th class="text-right pr-2" id="sum-amount">0</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 pt-1">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">발주</button>
|
||||
<a href="<?= mgmt_url('bag-orders') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="border border-gray-300 bg-white">
|
||||
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 등록 종류</div>
|
||||
<p class="text-xs text-gray-600 px-2 py-1">아래 목록에서 봉투를 선택하면 발주 품목에 추가됩니다. (개수 제한 없음)</p>
|
||||
<div class="overflow-auto">
|
||||
<table class="w-full data-table text-sm order-reference-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-12">번호</th>
|
||||
<th class="w-20">선택</th>
|
||||
<th>봉투 종류</th>
|
||||
<th class="w-24">발주단가</th>
|
||||
<th class="w-24">Box당 팩</th>
|
||||
<th class="w-24">팩당 낱장</th>
|
||||
<th class="w-28">1박스 총 낱장</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($bagReferenceRows ?? []) as $idx => $row): ?>
|
||||
<tr data-reference-row data-code="<?= esc((string) $row['code']) ?>" class="cursor-pointer">
|
||||
<td class="text-center"><?= $idx + 1 ?></td>
|
||||
<td class="text-center">
|
||||
<button type="button" class="js-toggle-bag border border-gray-300 rounded px-2 py-0.5 text-xs hover:bg-gray-100" data-code="<?= esc((string) $row['code']) ?>">선택</button>
|
||||
</td>
|
||||
<td class="text-left pl-2"><?= esc((string) $row['name']) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((float) $row['orderPrice'], 2) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) $row['boxPerPack']) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) $row['packPerSheet']) ?></td>
|
||||
<td class="text-right pr-2"><?= number_format((int) $row['totalPerBox']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($bagReferenceRows)): ?>
|
||||
<tr><td colspan="7" class="text-center text-gray-400 py-4">표시할 봉투 기준 데이터가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
.order-input-table tbody tr,
|
||||
.order-reference-table tbody tr {
|
||||
height: 34px;
|
||||
}
|
||||
.order-input-table tbody td,
|
||||
.order-reference-table tbody td {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const bagMeta = <?= json_encode($bagMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
const initialSelectedItems = <?= json_encode($initialSelectedItems, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
const selectedBody = document.getElementById('selected-order-items-body');
|
||||
const referenceRows = Array.from(document.querySelectorAll('[data-reference-row]'));
|
||||
const sumBoxQtyEl = document.getElementById('sum-box-qty');
|
||||
const sumSheetQtyEl = document.getElementById('sum-sheet-qty');
|
||||
const sumAmountEl = document.getElementById('sum-amount');
|
||||
const monthInput = document.getElementById('bo_order_month_ui');
|
||||
const orderDateInput = document.getElementById('bo_order_date');
|
||||
const orderForm = document.querySelector('form[action*="bag-orders/store"]');
|
||||
const selectedItems = new Map();
|
||||
let activeCode = null;
|
||||
|
||||
const formatNumber = (value) => new Intl.NumberFormat('ko-KR').format(Number.isFinite(value) ? value : 0);
|
||||
const escapeHtml = (value) => String(value ?? '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
|
||||
const syncMonthFromDate = () => {
|
||||
if (!orderDateInput || !monthInput || !orderDateInput.value) return;
|
||||
monthInput.value = orderDateInput.value.substring(0, 7);
|
||||
};
|
||||
|
||||
const syncDateFromMonth = () => {
|
||||
if (!orderDateInput || !monthInput || !monthInput.value) return;
|
||||
const parts = monthInput.value.split('-');
|
||||
if (parts.length !== 2) return;
|
||||
const year = Number(parts[0]);
|
||||
const month = Number(parts[1]);
|
||||
if (!Number.isFinite(year) || !Number.isFinite(month)) return;
|
||||
|
||||
const currentDay = orderDateInput.value ? Number(orderDateInput.value.split('-')[2]) : 1;
|
||||
const lastDay = new Date(year, month, 0).getDate();
|
||||
const day = String(Math.min(Math.max(currentDay, 1), lastDay)).padStart(2, '0');
|
||||
orderDateInput.value = `${String(year)}-${String(month).padStart(2, '0')}-${day}`;
|
||||
};
|
||||
|
||||
const updateReferenceSelectionUi = () => {
|
||||
referenceRows.forEach((row) => {
|
||||
const code = row.dataset.code || '';
|
||||
const button = row.querySelector('.js-toggle-bag');
|
||||
const isSelected = selectedItems.has(code);
|
||||
row.classList.toggle('bg-blue-50', isSelected);
|
||||
if (button) {
|
||||
button.textContent = isSelected ? '선택됨' : '선택';
|
||||
button.classList.toggle('bg-blue-600', isSelected);
|
||||
button.classList.toggle('text-white', isSelected);
|
||||
button.classList.toggle('border-blue-600', isSelected);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const updateTotals = () => {
|
||||
let sumBoxQty = 0;
|
||||
let sumSheetQty = 0;
|
||||
let sumAmount = 0;
|
||||
|
||||
selectedBody.querySelectorAll('tr[data-item-row]').forEach((row) => {
|
||||
const code = row.dataset.code || '';
|
||||
const qtyInput = row.querySelector('.item-qty-box');
|
||||
const qtyBox = Math.max(0, parseInt(qtyInput?.value || '0', 10));
|
||||
const meta = bagMeta[code] || { orderPrice: 0, totalPerBox: 1 };
|
||||
const unitPrice = Number(meta.orderPrice || 0);
|
||||
const totalPerBox = Math.max(1, Number(meta.totalPerBox || 1));
|
||||
const qtySheet = qtyBox * totalPerBox;
|
||||
const amount = qtySheet * unitPrice;
|
||||
|
||||
const unitPriceEl = row.querySelector('.item-unit-price');
|
||||
const qtySheetEl = row.querySelector('.item-qty-sheet');
|
||||
const sheetHelpEl = row.querySelector('.item-sheet-help');
|
||||
const amountEl = row.querySelector('.item-amount');
|
||||
if (unitPriceEl) unitPriceEl.textContent = formatNumber(unitPrice);
|
||||
if (qtySheetEl) qtySheetEl.textContent = formatNumber(qtySheet);
|
||||
if (sheetHelpEl) sheetHelpEl.textContent = `낱장 ${formatNumber(qtySheet)}장`;
|
||||
if (amountEl) amountEl.textContent = formatNumber(amount);
|
||||
|
||||
selectedItems.set(code, { qtyBox });
|
||||
sumBoxQty += qtyBox;
|
||||
sumSheetQty += qtySheet;
|
||||
sumAmount += amount;
|
||||
});
|
||||
|
||||
if (sumBoxQtyEl) sumBoxQtyEl.textContent = formatNumber(sumBoxQty);
|
||||
if (sumSheetQtyEl) sumSheetQtyEl.textContent = formatNumber(sumSheetQty);
|
||||
if (sumAmountEl) sumAmountEl.textContent = formatNumber(sumAmount);
|
||||
};
|
||||
|
||||
const setActiveRow = (code) => {
|
||||
activeCode = code || null;
|
||||
selectedBody.querySelectorAll('tr[data-item-row]').forEach((row) => {
|
||||
row.classList.toggle('bg-amber-50', row.dataset.code === activeCode);
|
||||
});
|
||||
};
|
||||
|
||||
const renderSelectedRows = () => {
|
||||
const codes = Object.keys(bagMeta).filter((code) => selectedItems.has(code));
|
||||
if (codes.length === 0) {
|
||||
selectedBody.innerHTML = '<tr><td colspan="7" class="text-center text-gray-400 py-4">아래 "발주 등록 종류"에서 봉투를 선택해 주세요.</td></tr>';
|
||||
setActiveRow(null);
|
||||
updateTotals();
|
||||
updateReferenceSelectionUi();
|
||||
return;
|
||||
}
|
||||
|
||||
selectedBody.innerHTML = codes.map((code, idx) => {
|
||||
const meta = bagMeta[code];
|
||||
const qtyBox = Math.max(0, parseInt(String(selectedItems.get(code)?.qtyBox ?? 0), 10));
|
||||
const name = meta?.name || code;
|
||||
|
||||
return `
|
||||
<tr data-item-row data-code="${escapeHtml(code)}" class="cursor-pointer">
|
||||
<td class="text-center">${idx + 1}</td>
|
||||
<td class="text-center">
|
||||
<button type="button" class="js-remove-selected text-xs text-red-600 hover:underline" data-code="${escapeHtml(code)}">해제</button>
|
||||
</td>
|
||||
<td class="text-left pl-2">
|
||||
${escapeHtml(name)}
|
||||
<input type="hidden" name="item_bag_code[]" value="${escapeHtml(code)}" />
|
||||
</td>
|
||||
<td>
|
||||
<input name="item_qty_box[]" type="number" min="0" step="1" value="${qtyBox}" class="item-qty-box border border-gray-300 rounded px-2 py-1 text-sm w-full text-right leading-tight" />
|
||||
<p class="text-[11px] text-gray-500 mt-1 item-sheet-help">낱장 0장</p>
|
||||
</td>
|
||||
<td class="text-right pr-2 item-unit-price">0</td>
|
||||
<td class="text-right pr-2 item-qty-sheet">0</td>
|
||||
<td class="text-right pr-2 item-amount">0</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
if (!activeCode || !selectedItems.has(activeCode)) {
|
||||
activeCode = codes[0];
|
||||
}
|
||||
setActiveRow(activeCode);
|
||||
updateTotals();
|
||||
updateReferenceSelectionUi();
|
||||
};
|
||||
|
||||
const toggleSelection = (code) => {
|
||||
if (!code || !bagMeta[code]) return;
|
||||
if (selectedItems.has(code)) {
|
||||
selectedItems.delete(code);
|
||||
if (activeCode === code) activeCode = null;
|
||||
} else {
|
||||
selectedItems.set(code, { qtyBox: 0 });
|
||||
activeCode = code;
|
||||
}
|
||||
renderSelectedRows();
|
||||
};
|
||||
|
||||
initialSelectedItems.forEach((item) => {
|
||||
if (!item || !item.code || !bagMeta[item.code]) return;
|
||||
selectedItems.set(item.code, { qtyBox: Math.max(0, parseInt(String(item.qtyBox ?? 0), 10)) });
|
||||
activeCode = item.code;
|
||||
});
|
||||
|
||||
selectedBody.addEventListener('click', (event) => {
|
||||
const removeButton = event.target.closest('.js-remove-selected');
|
||||
if (removeButton) {
|
||||
toggleSelection(removeButton.dataset.code || '');
|
||||
return;
|
||||
}
|
||||
|
||||
const row = event.target.closest('tr[data-item-row]');
|
||||
if (!row) return;
|
||||
const code = row.dataset.code || '';
|
||||
setActiveRow(code);
|
||||
const qtyInput = row.querySelector('.item-qty-box');
|
||||
if (qtyInput) qtyInput.focus();
|
||||
});
|
||||
|
||||
selectedBody.addEventListener('input', (event) => {
|
||||
const qtyInput = event.target.closest('.item-qty-box');
|
||||
if (!qtyInput) return;
|
||||
const row = qtyInput.closest('tr[data-item-row]');
|
||||
if (!row) return;
|
||||
const code = row.dataset.code || '';
|
||||
selectedItems.set(code, { qtyBox: Math.max(0, parseInt(qtyInput.value || '0', 10)) });
|
||||
updateTotals();
|
||||
});
|
||||
|
||||
referenceRows.forEach((row) => {
|
||||
row.addEventListener('click', (event) => {
|
||||
const button = event.target.closest('.js-toggle-bag');
|
||||
if (button) {
|
||||
toggleSelection(button.dataset.code || '');
|
||||
return;
|
||||
}
|
||||
if (event.target.closest('td')) {
|
||||
toggleSelection(row.dataset.code || '');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (monthInput) monthInput.addEventListener('change', () => { syncDateFromMonth(); updateTotals(); });
|
||||
if (orderDateInput) orderDateInput.addEventListener('change', syncMonthFromDate);
|
||||
|
||||
if (orderForm) {
|
||||
orderForm.addEventListener('submit', (event) => {
|
||||
const hasValidItem = Array.from(selectedBody.querySelectorAll('tr[data-item-row]')).some((row) => {
|
||||
const qtyInput = row.querySelector('.item-qty-box');
|
||||
return Math.max(0, parseInt(qtyInput?.value || '0', 10)) > 0;
|
||||
});
|
||||
|
||||
if (!hasValidItem) {
|
||||
event.preventDefault();
|
||||
alert('봉투를 선택하고 수량을 1 이상 입력해 주세요.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
syncMonthFromDate();
|
||||
renderSelectedRows();
|
||||
})();
|
||||
</script>
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= base_url('admin/bag-orders') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= mgmt_url('bag-orders') ?>" class="text-blue-600 hover:underline text-sm">← 발주 목록</a>
|
||||
<a href="<?= base_url('admin/bag-orders') ?>" class="text-blue-600 hover:underline text-sm">← 발주 목록</a>
|
||||
<span class="text-gray-400">|</span>
|
||||
<span class="text-sm font-bold text-gray-700">발주 상세 — <?= esc($order->bo_lot_no) ?></span>
|
||||
</div>
|
||||
|
||||
@@ -1,200 +1,81 @@
|
||||
<?php
|
||||
// 발주기간: native month 입력은 로케일에 따라 Jan 등 영문 표기될 수 있어 YYYY-MM select + 한글 라벨 사용
|
||||
$bagOrderYmChoices = [];
|
||||
$bagOrderYmCenterY = (int) date('Y');
|
||||
for ($by = $bagOrderYmCenterY - 4; $by <= $bagOrderYmCenterY + 2; $by++) {
|
||||
for ($bm = 1; $bm <= 12; $bm++) {
|
||||
$bagOrderYmChoices[] = sprintf('%04d-%02d', $by, $bm);
|
||||
}
|
||||
}
|
||||
foreach ([(string) ($startMonth ?? ''), (string) ($endMonth ?? '')] as $ymExtra) {
|
||||
if (preg_match('/^\d{4}-\d{2}$/', $ymExtra) && ! in_array($ymExtra, $bagOrderYmChoices, true)) {
|
||||
$bagOrderYmChoices[] = $ymExtra;
|
||||
}
|
||||
}
|
||||
sort($bagOrderYmChoices);
|
||||
$bagOrderYmLabel = static function (string $ym): string {
|
||||
if (preg_match('/^(\d{4})-(\d{2})$/', $ym, $m)) {
|
||||
return $m[1] . '년 ' . (int) $m[2] . '월';
|
||||
}
|
||||
|
||||
return $ym;
|
||||
};
|
||||
?>
|
||||
<?= view('components/print_header', ['printTitle' => '봉투 발주 현황', 'printShowApproval' => false]) ?>
|
||||
<section class="no-print border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<?= view('components/print_header', ['printTitle' => '발주 현황']) ?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700">발주 현황</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= mgmt_url('bag-orders/export') . '?' . http_build_query(array_filter(['start_month' => $startMonth ?? '', 'end_month' => $endMonth ?? '', 'company_idx' => $companyIdx ?? 0, 'bag_code' => $bagCode ?? '', 'receive_type' => $receiveType ?? ''])) ?>" class="no-print border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
||||
<a href="<?= base_url('admin/bag-orders/export') . '?' . http_build_query(array_filter(['start_date' => $startDate ?? '', 'end_date' => $endDate ?? '', 'status' => $status ?? ''])) ?>" class="no-print border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('bag-orders/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">발주 등록</a>
|
||||
<a href="<?= base_url('admin/bag-orders/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">발주 등록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="no-print p-2 bg-white border-b border-gray-200">
|
||||
<!-- GBMS 발주현황: 발주기간은 [시작] ~ [끝] 한 줄 고정, 필터 블록은 가로 나열 후 좁으면 블록 단위로만 줄바꿈 -->
|
||||
<form method="GET" action="<?= mgmt_url('bag-orders') ?>" class="flex flex-wrap items-end gap-x-5 gap-y-3 w-full">
|
||||
<div class="flex flex-nowrap items-center gap-2 shrink-0">
|
||||
<label class="text-sm text-gray-600 whitespace-nowrap">발주 기간</label>
|
||||
<select name="start_month" class="border border-gray-300 rounded px-2 py-1 text-sm w-[11.5rem] max-w-[13rem] shrink-0">
|
||||
<?php foreach ($bagOrderYmChoices as $ym): ?>
|
||||
<option value="<?= esc($ym) ?>" <?= ($startMonth ?? date('Y-m')) === $ym ? 'selected' : '' ?>><?= esc($bagOrderYmLabel($ym)) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<span class="text-sm text-gray-500 select-none">~</span>
|
||||
<select name="end_month" class="border border-gray-300 rounded px-2 py-1 text-sm w-[11.5rem] max-w-[13rem] shrink-0">
|
||||
<?php foreach ($bagOrderYmChoices as $ym): ?>
|
||||
<option value="<?= esc($ym) ?>" <?= ($endMonth ?? date('Y-m')) === $ym ? 'selected' : '' ?>><?= esc($bagOrderYmLabel($ym)) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-nowrap items-center gap-2 shrink-0">
|
||||
<label class="text-sm text-gray-600 whitespace-nowrap">제작 업체</label>
|
||||
<select name="company_idx" class="border border-gray-300 rounded px-2 py-1 text-sm w-[11rem] max-w-[14rem]">
|
||||
<option value="0">전 체</option>
|
||||
<?php foreach (($companyOptions ?? []) as $company): ?>
|
||||
<option value="<?= (int) $company->cp_idx ?>" <?= (int) ($companyIdx ?? 0) === (int) $company->cp_idx ? 'selected' : '' ?>>
|
||||
<?= esc((string) $company->cp_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-nowrap items-center gap-2 shrink-0">
|
||||
<label class="text-sm text-gray-600 whitespace-nowrap">품 명</label>
|
||||
<select name="bag_code" class="border border-gray-300 rounded px-2 py-1 text-sm w-[11rem] max-w-[16rem]">
|
||||
<option value="">전 체</option>
|
||||
<?php foreach (($bagCodeOptions ?? []) as $bag): ?>
|
||||
<option value="<?= esc((string) $bag->cd_code) ?>" <?= (string) ($bagCode ?? '') === (string) $bag->cd_code ? 'selected' : '' ?>>
|
||||
<?= esc((string) $bag->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-nowrap items-center gap-2 shrink-0">
|
||||
<label class="text-sm text-gray-600 whitespace-nowrap">입고 구분</label>
|
||||
<select name="receive_type" class="border border-gray-300 rounded px-2 py-1 text-sm w-[8.5rem]">
|
||||
<option value="all" <?= ($receiveType ?? 'all') === 'all' ? 'selected' : '' ?>>전 체</option>
|
||||
<option value="received" <?= ($receiveType ?? 'all') === 'received' ? 'selected' : '' ?>>입고완료</option>
|
||||
<option value="pending" <?= ($receiveType ?? 'all') === 'pending' ? 'selected' : '' ?>>미입고</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-nowrap items-center gap-2 shrink-0 sm:ml-auto">
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('bag-orders') ?>" class="text-sm text-gray-500 hover:underline whitespace-nowrap">초기화</a>
|
||||
</div>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= base_url('admin/bag-orders') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">발주일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">상태</label>
|
||||
<select name="status" class="border border-gray-300 rounded px-2 py-1 text-sm">
|
||||
<option value="">전체</option>
|
||||
<option value="normal" <?= ($status ?? '') === 'normal' ? 'selected' : '' ?>>정상</option>
|
||||
<option value="cancelled" <?= ($status ?? '') === 'cancelled' ? 'selected' : '' ?>>취소</option>
|
||||
<option value="deleted" <?= ($status ?? '') === 'deleted' ? 'selected' : '' ?>>삭제</option>
|
||||
</select>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= base_url('admin/bag-orders') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<div class="bag-order-print-wrap border border-gray-300 overflow-auto mt-2">
|
||||
<table class="bag-order-print-table w-full data-table">
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-32">발주일자</th>
|
||||
<th class="min-w-[10rem]">제작 업체</th>
|
||||
<th class="min-w-[12rem]">품 명</th>
|
||||
<th class="w-28">발주 수량</th>
|
||||
<th class="w-28">입고 수량</th>
|
||||
<th class="w-28">미입고수량</th>
|
||||
<th class="w-32">발주 금액</th>
|
||||
<th class="min-w-[9rem]">입고처</th>
|
||||
<th class="min-w-[8rem]">비 고</th>
|
||||
<th class="w-16">번호</th>
|
||||
<th>LOT번호</th>
|
||||
<th>발주일</th>
|
||||
<th>제작업체</th>
|
||||
<th>입고처</th>
|
||||
<th>품목수</th>
|
||||
<th>총수량</th>
|
||||
<th>총금액</th>
|
||||
<th class="w-20">상태</th>
|
||||
<th class="w-44">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php $printedGroup = []; ?>
|
||||
<?php foreach (($rows ?? []) as $row): ?>
|
||||
<?php if (! empty($row['is_subtotal'])): ?>
|
||||
<tr class="bg-gray-50 font-semibold">
|
||||
<td colspan="3" class="text-center"><?= esc((string) ($row['label'] ?? '소계')) ?></td>
|
||||
<td><?= number_format((int) ($row['order_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($row['received_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($row['pending_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((float) ($row['amount'] ?? 0)) ?></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<?php continue; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$boIdx = (int) ($row['bo_idx'] ?? 0);
|
||||
$showGroup = ! isset($printedGroup[$boIdx]);
|
||||
$rowspan = (int) (($groupRows[$boIdx] ?? 1));
|
||||
if ($showGroup) {
|
||||
$printedGroup[$boIdx] = true;
|
||||
}
|
||||
?>
|
||||
<?php foreach ($list as $row): ?>
|
||||
<tr>
|
||||
<?php if ($showGroup): ?>
|
||||
<td class="text-center align-top" rowspan="<?= $rowspan ?>"><?= esc((string) ($row['order_date'] ?? '')) ?></td>
|
||||
<td class="text-left pl-2 align-top" rowspan="<?= $rowspan ?>"><?= esc((string) ($row['company_name'] ?? '')) ?></td>
|
||||
<?php endif; ?>
|
||||
<td class="text-left pl-2"><?= esc((string) ($row['bag_name'] ?? '')) ?></td>
|
||||
<td><?= number_format((int) ($row['order_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($row['received_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($row['pending_qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((float) ($row['amount'] ?? 0)) ?></td>
|
||||
<td class="text-left pl-2"><?= esc((string) ($row['agency_name'] ?? '')) ?></td>
|
||||
<td></td>
|
||||
<td class="text-center"><?= esc($row->bo_idx) ?></td>
|
||||
<td class="text-center font-mono"><?= esc($row->bo_lot_no) ?></td>
|
||||
<td class="text-center"><?= esc($row->bo_order_date) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($companyMap[$row->bo_company_idx] ?? '') ?></td>
|
||||
<td class="text-left pl-2"><?= esc($agencyMap[$row->bo_agency_idx] ?? '') ?></td>
|
||||
<td><?= number_format((int) ($itemSummary[$row->bo_idx]['count'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($itemSummary[$row->bo_idx]['qty'] ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($itemSummary[$row->bo_idx]['amount'] ?? 0)) ?></td>
|
||||
<td class="text-center">
|
||||
<?php
|
||||
$statusMap = ['normal' => '정상', 'cancelled' => '취소', 'deleted' => '삭제'];
|
||||
echo esc($statusMap[$row->bo_status] ?? $row->bo_status);
|
||||
?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="<?= base_url('admin/bag-orders/detail/' . (int) $row->bo_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">상세</a>
|
||||
<form action="<?= base_url('admin/bag-orders/cancel/' . (int) $row->bo_idx) ?>" method="POST" class="inline" onsubmit="return confirm('취소하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-orange-600 hover:underline text-sm mr-1">취소</button>
|
||||
</form>
|
||||
<form action="<?= base_url('admin/bag-orders/delete/' . (int) $row->bo_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if (empty($rows ?? [])): ?>
|
||||
<tr><td colspan="9" class="text-center text-gray-400 py-6">조회 조건에 해당하는 발주 내역이 없습니다.</td></tr>
|
||||
<?php if (empty($list)): ?>
|
||||
<tr><td colspan="10" class="text-center text-gray-400 py-4">등록된 발주가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="bg-gray-100 font-bold">
|
||||
<td colspan="3" class="text-center">총계</td>
|
||||
<td class="text-right"><?= number_format((int) ($grandTotals['order_qty'] ?? 0)) ?></td>
|
||||
<td class="text-right"><?= number_format((int) ($grandTotals['received_qty'] ?? 0)) ?></td>
|
||||
<td class="text-right"><?= number_format((int) ($grandTotals['pending_qty'] ?? 0)) ?></td>
|
||||
<td class="text-right"><?= number_format((float) ($grandTotals['amount'] ?? 0)) ?></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@media print {
|
||||
#debug-icon,
|
||||
#debug-bar,
|
||||
#debug-bar-contents,
|
||||
#debug-toolbar,
|
||||
.debug-toolbar,
|
||||
.ci-debug-toolbar,
|
||||
[id^='debug-bar-'],
|
||||
[id^='debug-icon'],
|
||||
[class*='debug-toolbar'] {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.bag-order-print-wrap {
|
||||
overflow: visible !important;
|
||||
border: none !important;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.bag-order-print-table {
|
||||
width: 100% !important;
|
||||
table-layout: auto !important;
|
||||
}
|
||||
|
||||
.bag-order-print-table th,
|
||||
.bag-order-print-table td {
|
||||
white-space: nowrap !important;
|
||||
word-break: keep-all !important;
|
||||
overflow-wrap: normal !important;
|
||||
font-size: 10px !important;
|
||||
padding: 2px 3px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<?php if (isset($pager)): ?><div class="mt-3"><?= $pager->links() ?></div><?php endif; ?>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">봉투 단가 등록</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('bag-prices/store') ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/bag-prices/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= mgmt_url('bag-prices') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/bag-prices') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">봉투 단가 수정</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('bag-prices/update/' . (int) $item->bp_idx) ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/bag-prices/update/' . (int) $item->bp_idx) ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button>
|
||||
<a href="<?= mgmt_url('bag-prices') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/bag-prices') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= mgmt_url('bag-prices') ?>" class="text-blue-600 hover:underline text-sm">← 단가 목록</a>
|
||||
<a href="<?= base_url('admin/bag-prices') ?>" class="text-blue-600 hover:underline text-sm">← 단가 목록</a>
|
||||
<span class="text-gray-400">|</span>
|
||||
<span class="text-sm font-bold text-gray-700">단가 변경 이력 — <?= esc($item->bp_bag_name) ?> (<?= esc($item->bp_bag_code) ?>)</span>
|
||||
</div>
|
||||
|
||||
@@ -1,97 +1,21 @@
|
||||
<?= view('components/print_header', ['printTitle' => '봉투 단가 관리', 'printShowApproval' => false]) ?>
|
||||
<style>
|
||||
@media print {
|
||||
.no-print { display: none !important; }
|
||||
}
|
||||
</style>
|
||||
<?= view('components/print_header', ['printTitle' => '봉투 단가 관리']) ?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700">봉투 단가 관리</span>
|
||||
<div class="flex items-center gap-2 no-print">
|
||||
<button onclick="window.print()" class="border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('bag-prices/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">단가 등록</a>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= base_url('admin/bag-prices/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">단가 등록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="no-print border border-gray-200 rounded-lg bg-white p-3 mt-2">
|
||||
<form method="get" action="<?= mgmt_url('bag-prices') ?>" class="flex flex-wrap items-end gap-3" autocomplete="off">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">봉투구분</label>
|
||||
<select name="bag_kind_e" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[9rem]">
|
||||
<option value="">전체</option>
|
||||
<?php foreach ($bag_kind_options ?? [] as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= (string) ($cd->cd_code ?? '') === (string) ($bag_kind_e ?? '') ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">봉투코드</label>
|
||||
<select name="bag_code" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[11rem]">
|
||||
<option value="">전체</option>
|
||||
<?php foreach ($bag_codes ?? [] as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= (string) ($cd->cd_code ?? '') === (string) ($bag_code ?? '') ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_code) ?> — <?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">조회 기간 (적용기간 겹침)</label>
|
||||
<?php
|
||||
$sp = $startParts ?? ['y' => '', 'm' => '', 'd' => ''];
|
||||
$ep = $endParts ?? ['y' => '', 'm' => '', 'd' => ''];
|
||||
$ymin = (int) ($dateYearMin ?? ((int) date('Y') - 12));
|
||||
$ymax = (int) ($dateYearMax ?? ((int) date('Y') + 2));
|
||||
?>
|
||||
<div class="flex flex-wrap items-center gap-1">
|
||||
<span class="text-xs text-gray-500 mr-0.5">시작</span>
|
||||
<select name="start_y" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[4.5rem]">
|
||||
<option value="">연도</option>
|
||||
<?php for ($yy = $ymin; $yy <= $ymax; $yy++): ?>
|
||||
<option value="<?= $yy ?>" <?= (string) ($sp['y'] ?? '') === (string) $yy ? 'selected' : '' ?>><?= $yy ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<select name="start_m" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[3.75rem]">
|
||||
<option value="">월</option>
|
||||
<?php for ($mi = 1; $mi <= 12; $mi++): ?>
|
||||
<option value="<?= $mi ?>" <?= isset($sp['m']) && (int) $sp['m'] === $mi ? 'selected' : '' ?>><?= $mi ?>월</option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<select name="start_d" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[3.75rem]">
|
||||
<option value="">일</option>
|
||||
<?php for ($di = 1; $di <= 31; $di++): ?>
|
||||
<option value="<?= $di ?>" <?= isset($sp['d']) && (int) $sp['d'] === $di ? 'selected' : '' ?>><?= $di ?>일</option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<span class="text-sm text-gray-500 mx-0.5">~</span>
|
||||
<span class="text-xs text-gray-500 mr-0.5">종료</span>
|
||||
<select name="end_y" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[4.5rem]">
|
||||
<option value="">연도</option>
|
||||
<?php for ($yy = $ymin; $yy <= $ymax; $yy++): ?>
|
||||
<option value="<?= $yy ?>" <?= (string) ($ep['y'] ?? '') === (string) $yy ? 'selected' : '' ?>><?= $yy ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<select name="end_m" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[3.75rem]">
|
||||
<option value="">월</option>
|
||||
<?php for ($mi = 1; $mi <= 12; $mi++): ?>
|
||||
<option value="<?= $mi ?>" <?= isset($ep['m']) && (int) $ep['m'] === $mi ? 'selected' : '' ?>><?= $mi ?>월</option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<select name="end_d" class="border border-gray-300 rounded px-1.5 py-1.5 text-sm min-w-[3.75rem]">
|
||||
<option value="">일</option>
|
||||
<?php for ($di = 1; $di <= 31; $di++): ?>
|
||||
<option value="<?= $di ?>" <?= isset($ep['d']) && (int) $ep['d'] === $di ? 'selected' : '' ?>><?= $di ?>일</option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 pb-0.5">
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('bag-prices') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
<button type="button" onclick="window.print()" class="border border-gray-300 text-gray-700 px-3 py-1.5 rounded-sm text-sm hover:bg-gray-50">인쇄</button>
|
||||
</div>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= base_url('admin/bag-prices') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">적용시작일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= base_url('admin/bag-prices') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
@@ -107,21 +31,13 @@
|
||||
<th>적용시작</th>
|
||||
<th>적용종료</th>
|
||||
<th class="w-20">상태</th>
|
||||
<th class="w-36 no-print">작업</th>
|
||||
<th class="w-36">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php
|
||||
$startNo = 1;
|
||||
if (isset($pager) && method_exists($pager, 'getCurrentPage') && method_exists($pager, 'getPerPage')) {
|
||||
$currentPage = (int) $pager->getCurrentPage();
|
||||
$perPage = (int) $pager->getPerPage();
|
||||
$startNo = (($currentPage > 0 ? $currentPage : 1) - 1) * ($perPage > 0 ? $perPage : 20) + 1;
|
||||
}
|
||||
?>
|
||||
<?php foreach (($list ?? []) as $idx => $row): ?>
|
||||
<?php foreach ($list as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= (int) $startNo + (int) $idx ?></td>
|
||||
<td class="text-center"><?= esc($row->bp_idx) ?></td>
|
||||
<td class="text-center font-mono"><?= esc($row->bp_bag_code) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->bp_bag_name) ?></td>
|
||||
<td><?= number_format((float) $row->bp_order_price) ?></td>
|
||||
@@ -130,10 +46,10 @@
|
||||
<td class="text-center"><?= esc($row->bp_start_date) ?></td>
|
||||
<td class="text-center"><?= esc($row->bp_end_date ?? '현재') ?></td>
|
||||
<td class="text-center"><?= (int) $row->bp_state === 1 ? '사용' : '미사용' ?></td>
|
||||
<td class="text-center no-print">
|
||||
<a href="<?= mgmt_url('bag-prices/history/' . (int) $row->bp_idx) ?>" class="text-green-600 hover:underline text-sm mr-1">이력</a>
|
||||
<a href="<?= mgmt_url('bag-prices/edit/' . (int) $row->bp_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
|
||||
<form action="<?= mgmt_url('bag-prices/delete/' . (int) $row->bp_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<td class="text-center">
|
||||
<a href="<?= base_url('admin/bag-prices/history/' . (int) $row->bp_idx) ?>" class="text-green-600 hover:underline text-sm mr-1">이력</a>
|
||||
<a href="<?= base_url('admin/bag-prices/edit/' . (int) $row->bp_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
|
||||
<form action="<?= base_url('admin/bag-prices/delete/' . (int) $row->bp_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
|
||||
</form>
|
||||
@@ -146,4 +62,4 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if (isset($pager)): ?><div class="mt-3 no-print"><?= $pager->links() ?></div><?php endif; ?>
|
||||
<?php if (isset($pager)): ?><div class="mt-3"><?= $pager->links() ?></div><?php endif; ?>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">입고 처리</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('bag-receivings/store') ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/bag-receivings/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= mgmt_url('bag-receivings') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/bag-receivings') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
<span class="text-sm font-bold text-gray-700">입고 현황</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('bag-receivings/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">입고 처리</a>
|
||||
<a href="<?= base_url('admin/bag-receivings/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">입고 처리</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('bag-receivings') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="GET" action="<?= base_url('admin/bag-receivings') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">입고일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('bag-receivings') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
<a href="<?= base_url('admin/bag-receivings') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">판매 등록</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('bag-sales/store') ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/bag-sales/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= mgmt_url('bag-sales') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/bag-sales') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700">판매/반품 관리</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= mgmt_url('bag-sales/export') . '?' . http_build_query(array_filter(['start_date' => $startDate ?? '', 'end_date' => $endDate ?? '', 'type' => $type ?? ''])) ?>" class="no-print border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
||||
<a href="<?= base_url('admin/bag-sales/export') . '?' . http_build_query(array_filter(['start_date' => $startDate ?? '', 'end_date' => $endDate ?? '', 'type' => $type ?? ''])) ?>" class="no-print border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('bag-sales/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">판매 등록</a>
|
||||
<a href="<?= base_url('admin/bag-sales/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">판매 등록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('bag-sales') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="GET" action="<?= base_url('admin/bag-sales') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">판매일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
@@ -23,7 +23,7 @@
|
||||
<option value="cancel" <?= ($type ?? '') === 'cancel' ? 'selected' : '' ?>>취소</option>
|
||||
</select>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('bag-sales') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
<a href="<?= base_url('admin/bag-sales') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= base_url('bag/code-details/' . (int) $kind->ck_idx) ?>" class="text-blue-600 hover:underline text-sm">← <?= esc($kind->ck_name) ?></a>
|
||||
<a href="<?= base_url('admin/code-details/' . (int) $kind->ck_idx) ?>" class="text-blue-600 hover:underline text-sm">← <?= esc($kind->ck_name) ?></a>
|
||||
<span class="text-gray-400">|</span>
|
||||
<span class="text-sm font-bold text-gray-700">세부코드 등록</span>
|
||||
</div>
|
||||
@@ -30,42 +30,9 @@
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-24" name="cd_sort" type="number" value="<?= esc(old('cd_sort', '0')) ?>" min="0"/>
|
||||
</div>
|
||||
|
||||
<?php if (! empty($canPlatformScope)): ?>
|
||||
<div class="space-y-2 border-t border-gray-200 pt-3">
|
||||
<p class="text-sm font-bold text-gray-700">등록 범위</p>
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input type="radio" name="cd_scope" value="platform" <?= old('cd_scope', 'platform') === 'platform' ? 'checked' : '' ?> class="cd-scope-radio"/>
|
||||
플랫폼 공통 (CSV·시드와 동일 — 전 지자체, super/본부만 이후 수정)
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input type="radio" name="cd_scope" value="local" <?= old('cd_scope') === 'local' ? 'checked' : '' ?> class="cd-scope-radio"/>
|
||||
지자체 전용 (해당 지자체 관리자가 수정·삭제)
|
||||
</label>
|
||||
<div id="cd-lg-wrap" class="<?= old('cd_scope') === 'local' ? '' : 'hidden' ?> flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">소속 지자체</label>
|
||||
<select name="cd_lg_idx" class="border border-gray-300 rounded px-3 py-1.5 text-sm min-w-[14rem]">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($localGovernments as $gov): ?>
|
||||
<option value="<?= (int) $gov->lg_idx ?>" <?= (string) old('cd_lg_idx') === (string) $gov->lg_idx ? 'selected' : '' ?>><?= esc($gov->lg_name) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
document.querySelectorAll('.cd-scope-radio').forEach(function (r) {
|
||||
r.addEventListener('change', function () {
|
||||
var w = document.getElementById('cd-lg-wrap');
|
||||
if (w) w.classList.toggle('hidden', document.querySelector('input[name="cd_scope"][value="local"]:checked') === null);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= base_url('bag/code-details/' . (int) $kind->ck_idx) ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/code-details/' . (int) $kind->ck_idx) ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= base_url('bag/code-details/' . (int) $kind->ck_idx) ?>" class="text-blue-600 hover:underline text-sm">← <?= esc($kind->ck_name) ?></a>
|
||||
<a href="<?= base_url('admin/code-details/' . (int) $kind->ck_idx) ?>" class="text-blue-600 hover:underline text-sm">← <?= esc($kind->ck_name) ?></a>
|
||||
<span class="text-gray-400">|</span>
|
||||
<span class="text-sm font-bold text-gray-700">세부코드 수정</span>
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button>
|
||||
<a href="<?= base_url('bag/code-details/' . (int) $kind->ck_idx) ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/code-details/' . (int) $kind->ck_idx) ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
49
app/Views/admin/code_detail/index.php
Normal file
49
app/Views/admin/code_detail/index.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?= view('components/print_header', ['printTitle' => '세부코드 관리 - ' . esc($kind->ck_name)]) ?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= base_url('admin/code-kinds') ?>" class="text-blue-600 hover:underline text-sm">← 코드 종류</a>
|
||||
<span class="text-gray-400">|</span>
|
||||
<span class="text-sm font-bold text-gray-700">세부코드 — <?= esc($kind->ck_name) ?> (<?= esc($kind->ck_code) ?>)</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= base_url('admin/code-details/' . (int) $kind->ck_idx . '/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">세부코드 등록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-16">번호</th>
|
||||
<th class="w-24">코드</th>
|
||||
<th>코드명</th>
|
||||
<th class="w-20">정렬순서</th>
|
||||
<th class="w-20">상태</th>
|
||||
<th>등록일</th>
|
||||
<th class="w-28">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach ($list as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->cd_idx) ?></td>
|
||||
<td class="text-center font-mono"><?= esc($row->cd_code) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->cd_name) ?></td>
|
||||
<td class="text-center"><?= (int) $row->cd_sort ?></td>
|
||||
<td class="text-center"><?= (int) $row->cd_state === 1 ? '사용' : '미사용' ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->cd_regdate ?? '') ?></td>
|
||||
<td class="text-center">
|
||||
<a href="<?= base_url('admin/code-details/edit/' . (int) $row->cd_idx) ?>" class="text-blue-600 hover:underline text-sm">수정</a>
|
||||
<form action="<?= base_url('admin/code-details/delete/' . (int) $row->cd_idx) ?>" method="POST" class="inline ml-1" onsubmit="return confirm('이 세부코드를 삭제하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if (isset($pager)): ?><div class="mt-3"><?= $pager->links() ?></div><?php endif; ?>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= base_url('bag/code-kinds') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/code-kinds') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button>
|
||||
<a href="<?= base_url('bag/code-kinds') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/code-kinds') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
48
app/Views/admin/code_kind/index.php
Normal file
48
app/Views/admin/code_kind/index.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?= view('components/print_header', ['printTitle' => '기본코드 종류 관리']) ?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700">기본코드 종류 관리</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= base_url('admin/code-kinds/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">코드 종류 등록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-16">번호</th>
|
||||
<th class="w-20">코드</th>
|
||||
<th>코드명</th>
|
||||
<th class="w-24">세부코드 수</th>
|
||||
<th class="w-20">상태</th>
|
||||
<th>등록일</th>
|
||||
<th class="w-40">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach ($list as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->ck_idx) ?></td>
|
||||
<td class="text-center font-mono font-bold"><?= esc($row->ck_code) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->ck_name) ?></td>
|
||||
<td class="text-center">
|
||||
<a href="<?= base_url('admin/code-details/' . (int) $row->ck_idx) ?>" class="text-blue-600 hover:underline"><?= (int) ($countMap[$row->ck_idx] ?? 0) ?>개</a>
|
||||
</td>
|
||||
<td class="text-center"><?= (int) $row->ck_state === 1 ? '사용' : '미사용' ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->ck_regdate ?? '') ?></td>
|
||||
<td class="text-center">
|
||||
<a href="<?= base_url('admin/code-details/' . (int) $row->ck_idx) ?>" class="text-green-600 hover:underline text-sm mr-1">세부코드</a>
|
||||
<a href="<?= base_url('admin/code-kinds/edit/' . (int) $row->ck_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
|
||||
<form action="<?= base_url('admin/code-kinds/delete/' . (int) $row->ck_idx) ?>" method="POST" class="inline" onsubmit="return confirm('이 코드 종류를 삭제하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if (isset($pager)): ?><div class="mt-3"><?= $pager->links() ?></div><?php endif; ?>
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">업체 등록</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('companies/store') ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/companies/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= mgmt_url('companies') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/companies') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">업체 수정</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('companies/update/' . (int) $item->cp_idx) ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/companies/update/' . (int) $item->cp_idx) ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button>
|
||||
<a href="<?= mgmt_url('companies') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/companies') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -4,21 +4,33 @@
|
||||
<span class="text-sm font-bold text-gray-700">업체 관리</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('companies/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">업체 등록</a>
|
||||
<a href="<?= base_url('admin/companies/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">업체 등록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200 no-print">
|
||||
<form method="GET" action="<?= mgmt_url('companies') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">업체유형</label>
|
||||
<select name="cp_type" class="border border-gray-300 rounded px-2 py-1 text-sm w-44">
|
||||
<option value="">전 체</option>
|
||||
<?php foreach (($typeOptions ?? []) as $type): ?>
|
||||
<option value="<?= esc($type) ?>" <?= (string) ($cpType ?? '') === (string) $type ? 'selected' : '' ?>><?= esc($type) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('companies') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
<section class="no-print border border-gray-300 bg-white p-2 mt-2">
|
||||
<form method="POST" action="<?= mgmt_url('companies') ?>" class="flex flex-wrap items-end gap-2" autocomplete="off">
|
||||
<?= csrf_field() ?>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">조회 기준</label>
|
||||
<select name="search_field" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[10rem]">
|
||||
<option value="cp_idx" <?= ($search_field ?? 'cp_name') === 'cp_idx' ? 'selected' : '' ?>>번호</option>
|
||||
<option value="cp_type" <?= ($search_field ?? 'cp_name') === 'cp_type' ? 'selected' : '' ?>>업체유형</option>
|
||||
<option value="cp_name" <?= ($search_field ?? 'cp_name') === 'cp_name' ? 'selected' : '' ?>>업체명</option>
|
||||
<option value="cp_biz_no" <?= ($search_field ?? 'cp_name') === 'cp_biz_no' ? 'selected' : '' ?>>사업자번호</option>
|
||||
<option value="cp_rep_name" <?= ($search_field ?? 'cp_name') === 'cp_rep_name' ? 'selected' : '' ?>>대표자</option>
|
||||
<option value="cp_tel" <?= ($search_field ?? 'cp_name') === 'cp_tel' ? 'selected' : '' ?>>전화</option>
|
||||
<option value="cp_addr" <?= ($search_field ?? 'cp_name') === 'cp_addr' ? 'selected' : '' ?>>주소</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">조회어</label>
|
||||
<input type="text" name="search_query" value="<?= esc($search_query ?? '') ?>" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[18rem]" placeholder="조회어 입력">
|
||||
</div>
|
||||
<div class="flex items-center gap-2 pb-0.5">
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('companies') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
@@ -37,17 +49,9 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$startNo = 1;
|
||||
if (isset($pager) && method_exists($pager, 'getCurrentPage') && method_exists($pager, 'getPerPage')) {
|
||||
$currentPage = (int) $pager->getCurrentPage();
|
||||
$perPage = (int) $pager->getPerPage();
|
||||
$startNo = (($currentPage > 0 ? $currentPage : 1) - 1) * ($perPage > 0 ? $perPage : 20) + 1;
|
||||
}
|
||||
?>
|
||||
<?php foreach (($list ?? []) as $idx => $row): ?>
|
||||
<?php foreach ($list as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= (int) $startNo + (int) $idx ?></td>
|
||||
<td class="text-center"><?= esc($row->cp_idx) ?></td>
|
||||
<td class="text-center"><?= esc($row->cp_type) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->cp_name) ?></td>
|
||||
<td class="text-center"><?= esc($row->cp_biz_no) ?></td>
|
||||
@@ -56,8 +60,8 @@
|
||||
<td class="text-left pl-2"><?= esc($row->cp_addr) ?></td>
|
||||
<td class="text-center"><?= (int) $row->cp_state === 1 ? '사용' : '미사용' ?></td>
|
||||
<td class="text-center">
|
||||
<a href="<?= mgmt_url('companies/edit/' . (int) $row->cp_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
|
||||
<form action="<?= mgmt_url('companies/delete/' . (int) $row->cp_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<a href="<?= base_url('admin/companies/edit/' . (int) $row->cp_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
|
||||
<form action="<?= base_url('admin/companies/delete/' . (int) $row->cp_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
|
||||
</form>
|
||||
|
||||
@@ -6,15 +6,6 @@
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<?php if (! empty($s['stats_unavailable'])): ?>
|
||||
<div class="border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900 mb-4">
|
||||
발주·판매·재고·불출 통계 테이블이 아직 없거나 조회에 실패했습니다. MySQL에서
|
||||
<code class="text-xs bg-white px-1 rounded">writable/database/order_tables.sql</code>과
|
||||
<code class="text-xs bg-white px-1 rounded">writable/database/sales_tables.sql</code>을
|
||||
<code class="text-xs bg-white px-1 rounded">jongryangje_dev</code> DB에 실행해 주세요.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 통계 카드 -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
|
||||
<div class="border border-gray-300 p-4 bg-white">
|
||||
@@ -45,7 +36,7 @@
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<h3 class="text-sm font-bold text-gray-700">최근 발주 5건</h3>
|
||||
<a href="<?= base_url('bag/bag-orders') ?>" class="text-xs text-blue-600 hover:underline">전체보기</a>
|
||||
<a href="<?= base_url('admin/bag-orders') ?>" class="text-xs text-blue-600 hover:underline">전체보기</a>
|
||||
</div>
|
||||
<div class="border border-gray-300 overflow-auto">
|
||||
<table class="w-full data-table">
|
||||
@@ -63,7 +54,7 @@
|
||||
?>
|
||||
<tr>
|
||||
<td class="font-mono text-sm">
|
||||
<a href="<?= base_url('bag/bag-orders/detail/' . (int) $order->bo_idx) ?>" class="text-blue-600 hover:underline"><?= esc($order->bo_lot_no) ?></a>
|
||||
<a href="<?= base_url('admin/bag-orders/detail/' . (int) $order->bo_idx) ?>" class="text-blue-600 hover:underline"><?= esc($order->bo_lot_no) ?></a>
|
||||
</td>
|
||||
<td><?= esc($order->bo_order_date) ?></td>
|
||||
<td>
|
||||
@@ -90,7 +81,7 @@
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<h3 class="text-sm font-bold text-gray-700">최근 판매 5건</h3>
|
||||
<a href="<?= base_url('bag/bag-sales') ?>" class="text-xs text-blue-600 hover:underline">전체보기</a>
|
||||
<a href="<?= base_url('admin/bag-sales') ?>" class="text-xs text-blue-600 hover:underline">전체보기</a>
|
||||
</div>
|
||||
<div class="border border-gray-300 overflow-auto">
|
||||
<table class="w-full data-table">
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
<?= view('components/print_header', ['printTitle' => '지정판매소 바코드 출력']) ?>
|
||||
<style>
|
||||
.ds-bc-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||||
.ds-bc-table th, .ds-bc-table td { border: 1px solid #ccc; padding: 4px 6px; }
|
||||
.ds-bc-table th { background: #e9ecef; color: #2d3748; }
|
||||
.ds-bc-table td { background: #fff; }
|
||||
.ds-bc-table td.name-cell { max-width: 14rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.ds-bc-table td.addr-cell { max-width: 24rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.ds-bc-check { width: 14px; height: 14px; }
|
||||
</style>
|
||||
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700">지정판매소 바코드 출력</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button type="button" id="ds-bc-print-btn" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">인쇄</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="p-2 bg-white border-b border-gray-200 no-print">
|
||||
<form id="ds-bc-filter-form" method="get" action="<?= mgmt_url('designated-shops/barcode') ?>" class="flex flex-wrap items-end gap-3">
|
||||
<div class="min-w-[12rem]">
|
||||
<label class="block text-xs text-gray-600 mb-0.5">군·구</label>
|
||||
<div class="border border-gray-300 rounded px-3 py-1 text-sm bg-gray-50 text-gray-800 font-medium pointer-events-none select-none">
|
||||
<?= esc($fixedGugunLabel ?? '현재 지자체') ?>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600 mb-0.5">읍·면·동</label>
|
||||
<select name="ds_zone_code" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[10rem]">
|
||||
<option value="">전체</option>
|
||||
<?php foreach (($zones ?? []) as $z): ?>
|
||||
<?php $zc = trim((string) ($z->zone_code ?? '')); ?>
|
||||
<option value="<?= esc($zc) ?>" <?= ($zoneFilter ?? '') === $zc ? 'selected' : '' ?>><?= esc($zc) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600 mb-0.5">조회순서</label>
|
||||
<select name="order_by" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[8rem]">
|
||||
<option value="shop_no" <?= ($orderBy ?? 'shop_no') === 'shop_no' ? 'selected' : '' ?>>판매소 코드</option>
|
||||
<option value="name" <?= ($orderBy ?? '') === 'name' ? 'selected' : '' ?>>판매소명</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="mx-2 mt-2 mb-2">
|
||||
<form id="ds-bc-print-form" method="post" action="<?= mgmt_url('designated-shops/barcode/print') ?>" target="ds-bc-print-frame">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="zone_label" value="<?= esc(($zoneFilter ?? '') !== '' ? (string) $zoneFilter : '전체') ?>">
|
||||
<div class="mb-1 text-xs text-gray-600">
|
||||
<label class="inline-flex items-center gap-1 cursor-pointer"><input type="checkbox" id="ds-bc-check-all" class="ds-bc-check"> 전체선택</label>
|
||||
<span class="ml-3">선택 건수: <strong id="ds-bc-selected-count">0</strong></span>
|
||||
</div>
|
||||
<div class="overflow-auto border border-gray-300 bg-white">
|
||||
<table class="ds-bc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-14">출력</th>
|
||||
<th class="w-36">판매소 코드</th>
|
||||
<th>판매소명</th>
|
||||
<th class="w-24">대표자명</th>
|
||||
<th class="w-32">사업자번호</th>
|
||||
<th>사업장 주소</th>
|
||||
<th class="w-16">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($list ?? []) as $row): ?>
|
||||
<?php
|
||||
$st = (int) ($row->ds_state ?? 1);
|
||||
$stLabel = $st === 1 ? '사용' : '정지';
|
||||
?>
|
||||
<tr>
|
||||
<td class="text-center"><input class="ds-bc-row-check ds-bc-check" type="checkbox" name="ds_idx[]" value="<?= (int) $row->ds_idx ?>"></td>
|
||||
<td class="text-center text-blue-700"><?= esc((string) ($row->ds_shop_no ?? '')) ?></td>
|
||||
<td class="name-cell text-blue-700" title="<?= esc((string) ($row->ds_name ?? '')) ?>"><?= esc((string) ($row->ds_name ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row->ds_rep_name ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row->ds_biz_no ?? '')) ?></td>
|
||||
<td class="addr-cell" title="<?= esc((string) ($row->ds_addr ?? '')) ?>"><?= esc((string) ($row->ds_addr ?? '')) ?></td>
|
||||
<td class="<?= $st === 1 ? 'text-blue-700' : 'text-red-600' ?>"><?= esc($stLabel) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($list)): ?>
|
||||
<tr><td colspan="7" class="text-center text-gray-400 py-8">조회된 지정판매소가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
<iframe name="ds-bc-print-frame" class="hidden" style="display:none;width:0;height:0;border:0;" aria-hidden="true"></iframe>
|
||||
</section>
|
||||
|
||||
<?php if (isset($pager)): ?>
|
||||
<div class="mt-2 mb-2 mx-2 no-print"><?= $pager->links() ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var all = document.getElementById('ds-bc-check-all');
|
||||
var countEl = document.getElementById('ds-bc-selected-count');
|
||||
var printBtn = document.getElementById('ds-bc-print-btn');
|
||||
var printForm = document.getElementById('ds-bc-print-form');
|
||||
var rows = Array.prototype.slice.call(document.querySelectorAll('.ds-bc-row-check'));
|
||||
if (!all || !countEl || !rows.length) return;
|
||||
|
||||
function refreshCount() {
|
||||
var n = rows.filter(function (el) { return el.checked; }).length;
|
||||
countEl.textContent = String(n);
|
||||
all.checked = n > 0 && n === rows.length;
|
||||
all.indeterminate = n > 0 && n < rows.length;
|
||||
}
|
||||
|
||||
all.addEventListener('change', function () {
|
||||
rows.forEach(function (el) { el.checked = all.checked; });
|
||||
refreshCount();
|
||||
});
|
||||
rows.forEach(function (el) { el.addEventListener('change', refreshCount); });
|
||||
|
||||
if (printBtn && printForm) {
|
||||
printBtn.addEventListener('click', function () {
|
||||
var selected = rows.filter(function (el) { return el.checked; }).length;
|
||||
if (selected < 1) {
|
||||
window.alert('출력할 지정판매소를 1개 이상 선택해 주세요.');
|
||||
return;
|
||||
}
|
||||
printForm.action = "<?= esc(mgmt_url('designated-shops/barcode/print')) ?>?autoprint=1";
|
||||
printForm.submit();
|
||||
});
|
||||
}
|
||||
|
||||
refreshCount();
|
||||
})();
|
||||
</script>
|
||||
@@ -1,94 +0,0 @@
|
||||
<?php
|
||||
$rows = $rows ?? [];
|
||||
$zoneLabel = trim((string) ($zoneLabel ?? '전체'));
|
||||
$printedAt = trim((string) ($printedAt ?? date('Y.m.d')));
|
||||
$chunks = array_chunk($rows, 12);
|
||||
$totalPages = count($chunks);
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>지정판매소 바코드</title>
|
||||
<style>
|
||||
body { margin: 0; font-family: Arial, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif; color: #222; background: #fff; }
|
||||
.page { width: 210mm; min-height: 297mm; margin: 0 auto; padding: 14mm 12mm 12mm; box-sizing: border-box; }
|
||||
.title { text-align: center; font-size: 42px; letter-spacing: 1px; font-weight: 500; margin: 0 0 14px; }
|
||||
.meta { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #555; padding-bottom: 4px; font-size: 13px; margin-bottom: 8px; }
|
||||
.meta .center { font-weight: 700; }
|
||||
.cards { display: flex; flex-wrap: wrap; align-content: flex-start; }
|
||||
.card { width: 33.3333%; padding: 0 8px 12px; box-sizing: border-box; }
|
||||
.barcode-wrap { min-height: 40px; }
|
||||
.barcode-svg { width: 100%; max-width: 270px; height: 22px; }
|
||||
.code-text { text-align: center; margin-top: 1px; font-size: 16px; letter-spacing: 0.35px; }
|
||||
.name-text { text-align: center; margin-top: 5px; font-size: 14px; line-height: 1.2; word-break: keep-all; }
|
||||
@media print {
|
||||
@page { size: A4; margin: 0; }
|
||||
.page { page-break-after: always; }
|
||||
.page:last-child { page-break-after: auto; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<?php if ($rows === []): ?>
|
||||
<div class="page">
|
||||
<h1 class="title">지정판매소 바코드</h1>
|
||||
<p style="text-align:center; margin-top:30px; color:#666;">출력할 지정판매소가 없습니다.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($chunks as $pageIndex => $pageRows): ?>
|
||||
<section class="page">
|
||||
<h1 class="title">지정판매소 바코드</h1>
|
||||
<div class="meta">
|
||||
<span>출 력 일 자: <?= esc($printedAt) ?></span>
|
||||
<span class="center"><?= esc($zoneLabel) ?></span>
|
||||
<span>페 이 지: <?= (int) ($pageIndex + 1) ?> / <?= (int) $totalPages ?></span>
|
||||
</div>
|
||||
<div class="cards">
|
||||
<?php foreach ($pageRows as $row): ?>
|
||||
<?php
|
||||
$code = trim((string) ($row->ds_shop_no ?? ''));
|
||||
$nm = trim((string) ($row->ds_name ?? ''));
|
||||
$rep = trim((string) ($row->ds_rep_name ?? ''));
|
||||
$label = trim($nm . ($rep !== '' ? ('-' . $rep) : ''));
|
||||
?>
|
||||
<div class="card">
|
||||
<div class="barcode-wrap">
|
||||
<svg class="barcode-svg" data-barcode="<?= esc($code, 'attr') ?>"></svg>
|
||||
</div>
|
||||
<div class="code-text"><?= esc($code) ?></div>
|
||||
<div class="name-text"><?= esc($label) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.6/dist/JsBarcode.all.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var svgs = document.querySelectorAll('svg[data-barcode]');
|
||||
svgs.forEach(function (svg) {
|
||||
var code = (svg.getAttribute('data-barcode') || '').trim();
|
||||
if (!code) return;
|
||||
try {
|
||||
JsBarcode(svg, code, {
|
||||
format: 'CODE128',
|
||||
displayValue: false,
|
||||
margin: 0,
|
||||
height: 16,
|
||||
width: 1.28
|
||||
});
|
||||
} catch (e) {
|
||||
svg.outerHTML = '<div style="font-size:12px;color:#b91c1c;">바코드 생성 실패: ' + code + '</div>';
|
||||
}
|
||||
});
|
||||
if (window.location.search.indexOf('autoprint=1') >= 0) {
|
||||
setTimeout(function () { window.print(); }, 200);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">지정판매소 등록</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form id="designated-shop-create-form" action="<?= mgmt_url('designated-shops/store') ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/designated-shops/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<?php if (! empty($localGovs)): ?>
|
||||
@@ -23,18 +23,14 @@
|
||||
<div class="text-sm">
|
||||
<span class="font-semibold"><?= esc($currentLg->lg_name) ?></span>
|
||||
<span class="text-gray-500 ml-2">(<?= esc($currentLg->lg_code) ?>)</span>
|
||||
<span class="text-gray-500 ml-1"><?= esc(trim((string) ($currentLg->lg_sido ?? '') . ' ' . (string) ($currentLg->lg_gugun ?? ''))) ?></span>
|
||||
</div>
|
||||
<input type="hidden" name="ds_lg_idx" value="<?= esc($currentLg->lg_idx) ?>"/>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<input type="hidden" name="addr_search_sido" id="addr_search_sido" value="<?= esc((string) old('addr_search_sido', '')) ?>"/>
|
||||
<input type="hidden" name="addr_search_sigungu" id="addr_search_sigungu" value="<?= esc((string) old('addr_search_sigungu', '')) ?>"/>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">판매소번호</label>
|
||||
<div class="text-sm text-gray-600">등록 시 자동 부여 (주소 기준 기본코드 B·C·D 조합 + 일련번호 3자리)</div>
|
||||
<div class="text-sm text-gray-600">등록 시 자동 부여 (지자체코드 + 일련번호 3자리)</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -53,50 +49,23 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">업태</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-48" name="ds_biz_type" type="text" value="<?= esc(old('ds_biz_type')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">업종</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-48" name="ds_biz_kind" type="text" value="<?= esc(old('ds_biz_kind')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">가상계좌(은행)</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-48" name="ds_va_bank" type="text" value="<?= esc(old('ds_va_bank')) ?>" placeholder="예: 농협"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">계좌번호</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="ds_va_account" type="text" value="<?= esc(old('ds_va_account')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-start gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28 pt-1.5">주소</label>
|
||||
<p class="text-xs text-gray-600 max-w-lg leading-relaxed">우편번호 옆 <strong>주소 검색</strong>으로만 지정합니다(직접 입력 불가). <strong>지도</strong>는 카카오맵에서 현재 입력된 주소를 검색해 엽니다. 관할이 아니면 검색 직후 안내 후 반영되지 않습니다. 동·호 등은 아래 <strong>상세주소</strong>에 입력하세요.</p>
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">가상계좌</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="ds_va_number" type="text" value="<?= esc(old('ds_va_number')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">우편번호</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32 bg-gray-100 text-gray-800 cursor-not-allowed" name="ds_zip" id="ds_zip" type="text" value="<?= esc(old('ds_zip')) ?>" maxlength="10" autocomplete="postal-code" readonly tabindex="-1"/>
|
||||
<button type="button" id="btn-ds-kakao-postcode" class="no-print border border-btn-print-border text-gray-700 px-3 py-1.5 rounded-sm text-sm hover:bg-gray-50 transition shrink-0">주소 검색</button>
|
||||
<?= view('components/kakao_map_link_button', ['buttonId' => 'btn-ds-kakao-map-create']) ?>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32" name="ds_zip" type="text" value="<?= esc(old('ds_zip')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">도로명주소</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96 max-w-full bg-gray-100 text-gray-800 cursor-not-allowed" name="ds_addr" id="ds_addr" type="text" value="<?= esc(old('ds_addr')) ?>" readonly tabindex="-1"/>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96" name="ds_addr" type="text" value="<?= esc(old('ds_addr')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">지번주소</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96 max-w-full bg-gray-100 text-gray-800 cursor-not-allowed" name="ds_addr_jibun" id="ds_addr_jibun" type="text" value="<?= esc(old('ds_addr_jibun')) ?>" readonly tabindex="-1"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">상세주소</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96 max-w-full" name="ds_addr_detail" id="ds_addr_detail" type="text" value="<?= esc(old('ds_addr_detail')) ?>" maxlength="200" placeholder="동·호수·층 등"/>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96" name="ds_addr_jibun" type="text" value="<?= esc(old('ds_addr_jibun')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -119,49 +88,15 @@
|
||||
<div class="text-sm text-gray-600">해당 지자체(구·군) 코드로 등록 시 자동 설정</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">구역</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-72" name="ds_zone_code" type="text" value="<?= esc(old('ds_zone_code')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">종사업장번호</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-48" name="ds_branch_no" type="text" value="<?= esc(old('ds_branch_no')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">지정일자</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-40" name="ds_designated_at" type="date" value="<?= esc(old('ds_designated_at')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">변경일자</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-40" name="ds_state_changed_at" type="date" value="<?= esc(old('ds_state_changed_at')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-start gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28 pt-1.5">변경사유</label>
|
||||
<textarea class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96 min-h-[4rem]" name="ds_change_reason" rows="3"><?= esc(old('ds_change_reason')) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= mgmt_url('designated-shops') ?>" class="bg-white text-black border border-btn-print-border px-4 py-1.5 rounded-sm text-sm shadow hover:bg-gray-50 transition">목록</a>
|
||||
<a href="<?= base_url('admin/designated-shops') ?>" class="bg-white text-black border border-btn-print-border px-4 py-1.5 rounded-sm text-sm shadow hover:bg-gray-50 transition">목록</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?= view('components/kakao_map_modal', ['kakaoJavascriptKey' => $kakaoJavascriptKey ?? '']) ?>
|
||||
|
||||
<?= view('components/kakao_address_search', [
|
||||
'buttonId' => 'btn-ds-kakao-postcode',
|
||||
'zipName' => 'ds_zip',
|
||||
'roadName' => 'ds_addr',
|
||||
'jibunName' => 'ds_addr_jibun',
|
||||
'sidoFieldName' => 'addr_search_sido',
|
||||
'sigunguFieldName' => 'addr_search_sigungu',
|
||||
'tenantScope' => $addrTenantScope ?? ['lg_sido' => '', 'lg_gugun' => ''],
|
||||
'roadBaseOnly' => true,
|
||||
'detailFieldName' => 'ds_addr_detail',
|
||||
]) ?>
|
||||
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
<?php
|
||||
$ry = (int) ($reportYear ?? (int) date('Y'));
|
||||
$lg = $currentLg ?? null;
|
||||
$lgSido = $lg !== null ? trim((string) ($lg->lg_sido ?? '')) : '';
|
||||
$lgGugun = $lg !== null ? trim((string) ($lg->lg_gugun ?? '')) : '';
|
||||
$lgName = $lg !== null ? trim((string) ($lg->lg_name ?? '')) : '';
|
||||
$scopeLabel = $lgSido !== '' && $lgGugun !== ''
|
||||
? $lgSido . ' ' . $lgGugun
|
||||
: ($lgName !== '' ? $lgName : '—');
|
||||
$exportUrl = mgmt_url('designated-shops/district-new-cancel/export') . '?' . http_build_query(['year' => $ry]);
|
||||
?>
|
||||
<?= view('components/print_header', ['printTitle' => '지정 판매소 신규/취소 현황 (' . $ry . '년)']) ?>
|
||||
<style>
|
||||
.gbms-dnc-wrap { max-width: 100%; }
|
||||
.gbms-dnc-table { border-collapse: collapse; width: 100%; font-size: 13px; }
|
||||
.gbms-dnc-table th,
|
||||
.gbms-dnc-table td {
|
||||
border: 1px solid #7a8aa0;
|
||||
padding: 6px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.gbms-dnc-table thead th {
|
||||
background: linear-gradient(180deg, #e8eef6 0%, #d4dee9 100%);
|
||||
font-weight: 700;
|
||||
color: #1a2a3a;
|
||||
}
|
||||
.gbms-dnc-table thead th.gbms-sub {
|
||||
background: #dce6f0;
|
||||
font-weight: 600;
|
||||
}
|
||||
.gbms-dnc-table tbody td.text-left { text-align: left; }
|
||||
.gbms-dnc-table tbody tr.gbms-total td {
|
||||
font-weight: 700;
|
||||
border: 2px solid #c62828;
|
||||
background: #fff8f8;
|
||||
}
|
||||
.gbms-dnc-caption {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
margin: 8px 0 6px;
|
||||
color: #1a2a3a;
|
||||
}
|
||||
.gbms-unit-pill {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #0d47a1;
|
||||
background: #e3f2fd;
|
||||
border: 1px solid #90caf9;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.gbms-tip {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.gbms-help {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 1rem;
|
||||
height: 1rem;
|
||||
padding: 0 2px;
|
||||
border: 1px solid #5c6f85;
|
||||
border-radius: 999px;
|
||||
background: #fef9c3;
|
||||
color: #111827;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
font-family: Arial, sans-serif;
|
||||
user-select: none;
|
||||
cursor: help;
|
||||
}
|
||||
.gbms-help::after {
|
||||
content: attr(data-tip);
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: calc(100% + 6px);
|
||||
transform: translateX(-50%);
|
||||
display: none;
|
||||
min-width: 12rem;
|
||||
max-width: 14rem;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #8fa0b3;
|
||||
border-radius: 4px;
|
||||
background: #1f2937;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1.35;
|
||||
text-align: left;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
||||
z-index: 30;
|
||||
}
|
||||
.gbms-help:hover::after,
|
||||
.gbms-help:focus::after {
|
||||
display: block;
|
||||
}
|
||||
@media print {
|
||||
.gbms-dnc-table { font-size: 11px; }
|
||||
.gbms-help { display: none !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel no-print">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-800">[지정 판매소 신규/취소 현황]</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= esc($exportUrl) ?>" class="border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
||||
<button type="button" onclick="window.print()" class="border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('designated-shops') ?>" class="border border-gray-300 text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">목록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="p-3 bg-white border-b border-gray-200 no-print">
|
||||
<form method="get" action="<?= mgmt_url('designated-shops/district-new-cancel') ?>" class="flex flex-wrap items-end gap-4">
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600 mb-0.5">조회년도</label>
|
||||
<select name="year" class="border border-gray-400 rounded px-2 py-1.5 text-sm min-w-[7rem] bg-white">
|
||||
<?php foreach (($yearChoices ?? []) as $y): ?>
|
||||
<option value="<?= (int) $y ?>" <?= $ry === (int) $y ? 'selected' : '' ?>><?= (int) $y ?>년</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-1 min-w-[12rem]">
|
||||
<span class="block text-xs text-gray-600 mb-0.5">군·구 (소속 지자체)</span>
|
||||
<div class="border border-gray-300 rounded px-3 py-1.5 text-sm bg-gray-50 text-gray-800 font-medium">
|
||||
<?= esc($scopeLabel) ?>
|
||||
</div>
|
||||
</div>
|
||||
<span class="gbms-unit-pill self-end mb-0.5">단위: 판매소</span>
|
||||
<button type="submit" class="bg-btn-search text-white px-5 py-1.5 rounded-sm text-sm font-medium shadow-sm hover:opacity-90">조회</button>
|
||||
</form>
|
||||
<p class="text-xs text-gray-500 mt-2">
|
||||
종전·현행은 각각 <?= $ry - 1 ?>-12-31·<?= $ry ?>-12-31 기준 정상(지정일 이전·비정상 전환일 이후) 건수이며, 지정·취소는 <?= $ry ?>년 달력연도 기준입니다. 구·군 행은 효과 지자체의 기본코드(구 코드) 순서로 표시됩니다.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div class="mx-2 mt-3 mb-4 gbms-dnc-wrap">
|
||||
<div class="gbms-dnc-caption">지정 판매소 신규/취소 현황 조회 내역</div>
|
||||
<div class="overflow-x-auto border border-gray-400 bg-white">
|
||||
<table class="gbms-dnc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2" class="min-w-[6rem]">군·구</th>
|
||||
<th rowspan="2">
|
||||
<span class="gbms-tip">
|
||||
종전
|
||||
<span class="gbms-help" tabindex="0" aria-label="종전 설명" data-tip="전년도 12월 31일 기준 정상 상태 판매소 수">?</span>
|
||||
</span>
|
||||
<br><span class="text-xs font-normal">(전년도말)</span>
|
||||
</th>
|
||||
<th colspan="2">사용</th>
|
||||
<th rowspan="2">
|
||||
<span class="gbms-tip">
|
||||
현행
|
||||
<span class="gbms-help" tabindex="0" aria-label="현행 설명" data-tip="조회년도 12월 31일 기준 정상 상태 판매소 수">?</span>
|
||||
</span>
|
||||
<br><span class="text-xs font-normal">(금년도말)</span>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="gbms-sub">
|
||||
<span class="gbms-tip">
|
||||
지정
|
||||
<span class="gbms-help" tabindex="0" aria-label="지정 설명" data-tip="조회년도 내 지정일이 속한 신규 지정 건수">?</span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="gbms-sub">
|
||||
<span class="gbms-tip">
|
||||
취소
|
||||
<span class="gbms-help" tabindex="0" aria-label="취소 설명" data-tip="조회년도 내 폐업/해지 전환일이 속한 건수">?</span>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($districtRows ?? []) as $row): ?>
|
||||
<tr>
|
||||
<td class="text-left font-medium"><?= esc($row->region_label) ?></td>
|
||||
<td><?= number_format((int) $row->prev_end) ?></td>
|
||||
<td><?= number_format((int) $row->designated_y) ?></td>
|
||||
<td><?= number_format((int) $row->cancelled_y) ?></td>
|
||||
<td><?= number_format((int) $row->curr_end) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($districtRows)): ?>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-gray-500 py-8">표시할 구·군 또는 지정판매소 데이터가 없습니다.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php if (! empty($districtRows) && isset($districtTotal)): ?>
|
||||
<tr class="gbms-total">
|
||||
<td class="text-left"><?= esc($districtTotal->region_label) ?></td>
|
||||
<td><?= number_format((int) $districtTotal->prev_end) ?></td>
|
||||
<td><?= number_format((int) $districtTotal->designated_y) ?></td>
|
||||
<td><?= number_format((int) $districtTotal->cancelled_y) ?></td>
|
||||
<td><?= number_format((int) $districtTotal->curr_end) ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5,35 +5,20 @@ if ($shop === null) {
|
||||
return;
|
||||
}
|
||||
$v = fn ($key, $default = '') => old($key) !== null && old($key) !== '' ? old($key) : ($shop->{$key} ?? $default);
|
||||
$vaAccountDefault = (isset($shop->ds_va_account) && (string) $shop->ds_va_account !== '')
|
||||
? (string) $shop->ds_va_account
|
||||
: (string) ($shop->ds_va_number ?? '');
|
||||
$dateField = static function (string $key) use ($shop, $v): string {
|
||||
$s = (string) $v($key);
|
||||
if ($s === '' || str_starts_with($s, '0000')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $s;
|
||||
};
|
||||
?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<span class="text-sm font-bold text-gray-700">지정판매소 수정</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form id="designated-shop-edit-form" action="<?= mgmt_url('designated-shops/update/' . (int) $shop->ds_idx) ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/designated-shops/update/' . (int) $shop->ds_idx) ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<input type="hidden" name="addr_search_sido" id="addr_search_sido" value="<?= esc((string) old('addr_search_sido', $currentLg !== null ? (string) ($currentLg->lg_sido ?? '') : '')) ?>"/>
|
||||
<input type="hidden" name="addr_search_sigungu" id="addr_search_sigungu" value="<?= esc((string) old('addr_search_sigungu', $currentLg !== null ? (string) ($currentLg->lg_gugun ?? '') : '')) ?>"/>
|
||||
|
||||
<?php if ($currentLg !== null): ?>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">지자체</label>
|
||||
<div class="text-sm">
|
||||
<span class="font-semibold"><?= esc($currentLg->lg_name) ?></span>
|
||||
<span class="text-gray-500 ml-2">(<?= esc($currentLg->lg_code) ?>)</span>
|
||||
<span class="text-gray-500 ml-1"><?= esc(trim((string) ($currentLg->lg_sido ?? '') . ' ' . (string) ($currentLg->lg_gugun ?? ''))) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@@ -64,50 +49,23 @@ $dateField = static function (string $key) use ($shop, $v): string {
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">업태</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-48" name="ds_biz_type" type="text" value="<?= esc($v('ds_biz_type')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">업종</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-48" name="ds_biz_kind" type="text" value="<?= esc($v('ds_biz_kind')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">가상계좌(은행)</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-48" name="ds_va_bank" type="text" value="<?= esc($v('ds_va_bank')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">계좌번호</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="ds_va_account" type="text" value="<?= esc((old('ds_va_account') !== null && old('ds_va_account') !== '') ? old('ds_va_account') : $vaAccountDefault) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-start gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28 pt-1.5">주소</label>
|
||||
<p class="text-xs text-gray-600 max-w-lg leading-relaxed">우편·도로명·지번은 <strong>주소 검색</strong>으로만 바꿀 수 있습니다(직접 입력 불가). <strong>지도</strong>는 카카오맵에서 현재 입력된 주소를 검색해 엽니다. 관할이 아니면 검색 직후 안내 후 반영되지 않습니다. 동·호 등은 <strong>상세주소</strong>에 입력하세요.</p>
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">가상계좌</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="ds_va_number" type="text" value="<?= esc($v('ds_va_number')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">우편번호</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32 bg-gray-100 text-gray-800 cursor-not-allowed" name="ds_zip" id="ds_zip" type="text" value="<?= esc($v('ds_zip')) ?>" maxlength="10" readonly tabindex="-1"/>
|
||||
<button type="button" id="btn-ds-kakao-postcode-edit" class="no-print border border-btn-print-border text-gray-700 px-3 py-1.5 rounded-sm text-sm hover:bg-gray-50 transition shrink-0">주소 검색</button>
|
||||
<?= view('components/kakao_map_link_button', ['buttonId' => 'btn-ds-kakao-map-edit']) ?>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32" name="ds_zip" type="text" value="<?= esc($v('ds_zip')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">도로명주소</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96 max-w-full bg-gray-100 text-gray-800 cursor-not-allowed" name="ds_addr" id="ds_addr" type="text" value="<?= esc($v('ds_addr')) ?>" readonly tabindex="-1"/>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96" name="ds_addr" type="text" value="<?= esc($v('ds_addr')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">지번주소</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96 max-w-full bg-gray-100 text-gray-800 cursor-not-allowed" name="ds_addr_jibun" id="ds_addr_jibun" type="text" value="<?= esc($v('ds_addr_jibun')) ?>" readonly tabindex="-1"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">상세주소</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96 max-w-full" name="ds_addr_detail" id="ds_addr_detail" type="text" value="<?= esc($v('ds_addr_detail')) ?>" maxlength="200" placeholder="동·호수·층 등"/>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96" name="ds_addr_jibun" type="text" value="<?= esc($v('ds_addr_jibun')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -125,16 +83,6 @@ $dateField = static function (string $key) use ($shop, $v): string {
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="ds_email" type="email" value="<?= esc($v('ds_email')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">구역</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-72" name="ds_zone_code" type="text" value="<?= esc($v('ds_zone_code')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">종사업장번호</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-48" name="ds_branch_no" type="text" value="<?= esc($v('ds_branch_no')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">지정일자</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-40" name="ds_designated_at" type="date" value="<?= esc($v('ds_designated_at')) ?>"/>
|
||||
@@ -149,33 +97,9 @@ $dateField = static function (string $key) use ($shop, $v): string {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">변경일자</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-40" name="ds_state_changed_at" type="date" value="<?= esc($dateField('ds_state_changed_at')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-start gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28 pt-1.5">변경사유</label>
|
||||
<textarea class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96 min-h-[4rem]" name="ds_change_reason" rows="3"><?= esc($v('ds_change_reason')) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button>
|
||||
<a href="<?= mgmt_url('designated-shops') ?>" class="bg-white text-black border border-btn-print-border px-4 py-1.5 rounded-sm text-sm shadow hover:bg-gray-50 transition">목록</a>
|
||||
<a href="<?= base_url('admin/designated-shops') ?>" class="bg-white text-black border border-btn-print-border px-4 py-1.5 rounded-sm text-sm shadow hover:bg-gray-50 transition">목록</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?= view('components/kakao_map_modal', ['kakaoJavascriptKey' => $kakaoJavascriptKey ?? '']) ?>
|
||||
|
||||
<?= view('components/kakao_address_search', [
|
||||
'buttonId' => 'btn-ds-kakao-postcode-edit',
|
||||
'zipName' => 'ds_zip',
|
||||
'roadName' => 'ds_addr',
|
||||
'jibunName' => 'ds_addr_jibun',
|
||||
'sidoFieldName' => 'addr_search_sido',
|
||||
'sigunguFieldName' => 'addr_search_sigungu',
|
||||
'tenantScope' => $addrTenantScope ?? ['lg_sido' => '', 'lg_gugun' => ''],
|
||||
'roadBaseOnly' => true,
|
||||
'detailFieldName' => 'ds_addr_detail',
|
||||
]) ?>
|
||||
|
||||
@@ -1,202 +1,25 @@
|
||||
<?php
|
||||
helper('admin');
|
||||
$currentPath = current_nav_request_path();
|
||||
if ($currentPath === 'bag/designated-shops') {
|
||||
$readOnly = false;
|
||||
} elseif ($currentPath === 'bag/designated-shops/browse') {
|
||||
$readOnly = true;
|
||||
} else {
|
||||
$readOnly = ! empty($readOnly);
|
||||
}
|
||||
?>
|
||||
<?= view('components/print_header', ['printTitle' => $readOnly ? '지정판매소 조회 목록' : '지정판매소 목록']) ?>
|
||||
<style>
|
||||
/* 목록 위 → 지정판매소 정보 아래 (가로 2열 없음) */
|
||||
.ds-split {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.ds-list-panel {
|
||||
flex: 0 1 auto;
|
||||
width: 100%;
|
||||
max-height: 42vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
border: 1px solid #ccc;
|
||||
background: #fff;
|
||||
}
|
||||
.ds-detail-panel {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 12rem;
|
||||
border: 1px solid #ccc;
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.ds-panel-title {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding: 6px 10px;
|
||||
background: linear-gradient(180deg, #fafafa 0%, #e9ecef 100%);
|
||||
border-bottom: 1px solid #ccc;
|
||||
color: #333;
|
||||
}
|
||||
.ds-summary-bar {
|
||||
font-size: 12px;
|
||||
padding: 6px 10px;
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
color: #333;
|
||||
}
|
||||
.ds-row-selected td { background-color: #cce5ff !important; }
|
||||
.ds-detail-inner { padding: 10px; overflow: auto; flex: 1; }
|
||||
/* 원본 지정판매소 정보: 라벨 고정폭 + 2열 값(우측 값이 더 넓음), 주소·개인전화는 전폭 */
|
||||
.ds-detail-form {
|
||||
font-size: 12px;
|
||||
border: 1px solid #bbb;
|
||||
background: #fff;
|
||||
}
|
||||
.ds-row {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
.ds-detail-form > .ds-row:last-child { border-bottom: none; }
|
||||
/* 그 외 2+2 동일 비율 (상호명 | 우편번호 등) */
|
||||
.ds-row-4-even {
|
||||
grid-template-columns: 5.5rem minmax(0, 1fr) 5.5rem minmax(0, 1fr);
|
||||
}
|
||||
/* 판매소번호 전폭 행 — 값을 우편·주소 필드처럼 넓게 */
|
||||
.ds-value-shop-wide {
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 13px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
/* 라벨 | 값(나머지 전체) — 도로명·지번·개인전화·이메일 */
|
||||
.ds-row-wide {
|
||||
grid-template-columns: 5.5rem minmax(0, 1fr);
|
||||
}
|
||||
.ds-row-wide-tall .ds-field-value {
|
||||
min-height: 3.25rem;
|
||||
align-content: start;
|
||||
}
|
||||
/* 도로명주소 + 카카오맵 버튼 */
|
||||
.ds-field-value-with-map {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
.ds-field-value-with-map .ds-addr-text {
|
||||
flex: 1 1 12rem;
|
||||
min-width: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
.ds-field-label {
|
||||
background: #eef2f5;
|
||||
border-right: 1px solid #ccc;
|
||||
padding: 5px 8px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.ds-field-value {
|
||||
padding: 5px 8px;
|
||||
background: #fff;
|
||||
word-break: break-word;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
.ds-row-4-even > *:nth-child(4n) { border-right: none; }
|
||||
.ds-row-wide > .ds-field-value { border-right: none; }
|
||||
.ds-field-hint {
|
||||
font-size: 11px;
|
||||
color: #b91c1c;
|
||||
margin-top: 4px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.ds-row-4-even { grid-template-columns: 5rem 1fr; }
|
||||
}
|
||||
.ds-detail-actions { padding: 10px; border-top: 1px solid #ccc; background: #eee; }
|
||||
.ds-detail-info-wrap { overflow-x: auto; }
|
||||
.ds-detail-info-wrap .data-table th { white-space: nowrap; }
|
||||
.ds-detail-info-wrap th.ds-col-tight-head,
|
||||
.ds-detail-info-wrap td.ds-col-tight-cell {
|
||||
max-width: 6.5rem;
|
||||
width: 6.5rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ds-list-panel .ds-col-tight {
|
||||
max-width: 6rem;
|
||||
width: 6rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ds-list-panel .ds-col-zip {
|
||||
width: 4.5rem;
|
||||
max-width: 5rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ds-list-panel .ds-col-addr-list {
|
||||
max-width: 11rem;
|
||||
min-width: 6rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ds-list-panel .ds-col-detail-list {
|
||||
max-width: 8rem;
|
||||
min-width: 4rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ds-ro-road-btn { margin-left: 6px; vertical-align: middle; }
|
||||
</style>
|
||||
|
||||
<?php
|
||||
$listBasePath = $readOnly ? 'designated-shops/browse' : 'designated-shops';
|
||||
?>
|
||||
<?php helper('pii_mask'); ?>
|
||||
<?= view('components/print_header', ['printTitle' => '지정판매소 목록']) ?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700"><?= $readOnly ? '지정판매소 조회' : '지정판매소 관리' ?></span>
|
||||
<span class="text-sm font-bold text-gray-700">지정판매소 목록</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<?php if ($readOnly): ?>
|
||||
<a href="<?= mgmt_url('designated-shops/export') ?>" class="no-print border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
||||
<button type="button" onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<?php endif; ?>
|
||||
<?php if (! $readOnly): ?>
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('designated-shops/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">지정판매소 등록</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- P2-15: 다조건 검색 -->
|
||||
<section class="p-2 bg-white border-b border-gray-200 no-print">
|
||||
<form method="GET" action="<?= mgmt_url($listBasePath) ?>" class="flex flex-wrap items-center gap-2">
|
||||
<span class="text-sm font-semibold text-gray-700 mr-1">지정판매소 검색</span>
|
||||
<form method="GET" action="<?= mgmt_url('designated-shops') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">상호명</label>
|
||||
<input type="text" name="ds_name" value="<?= esc($dsName ?? '') ?>" placeholder="상호명" class="border border-gray-300 rounded px-2 py-1 text-sm w-36"/>
|
||||
<label class="text-sm text-gray-600">구·군 코드</label>
|
||||
<select name="ds_gugun_code" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[14rem]">
|
||||
<input type="text" name="ds_name" value="<?= esc($dsName ?? '') ?>" placeholder="상호명 검색" class="border border-gray-300 rounded px-2 py-1 text-sm w-40"/>
|
||||
<label class="text-sm text-gray-600">구군코드</label>
|
||||
<select name="ds_gugun_code" class="border border-gray-300 rounded px-2 py-1 text-sm">
|
||||
<option value="">전체</option>
|
||||
<?php foreach (($gugunCodes ?? []) as $gc): ?>
|
||||
<?php $gCode = (string) ($gc->ds_gugun_code ?? ''); ?>
|
||||
<option value="<?= esc($gCode) ?>" <?= ($dsGugunCode ?? '') === $gCode ? 'selected' : '' ?>><?= esc((string) (($gugunNameMap[$gCode] ?? '') !== '' ? $gugunNameMap[$gCode] : $gCode)) ?></option>
|
||||
<option value="<?= esc($gc->ds_gugun_code) ?>" <?= ($dsGugunCode ?? '') === $gc->ds_gugun_code ? 'selected' : '' ?>><?= esc($gc->ds_gugun_code) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<label class="text-sm text-gray-600">상태</label>
|
||||
@@ -207,357 +30,55 @@ $listBasePath = $readOnly ? 'designated-shops/browse' : 'designated-shops';
|
||||
<option value="3" <?= ($dsState ?? '') === '3' ? 'selected' : '' ?>>직권해지</option>
|
||||
</select>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url($listBasePath) ?>" class="border border-gray-300 text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">초기화</a>
|
||||
<a href="<?= mgmt_url('designated-shops') ?>" class="border border-gray-300 text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">초기화</a>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<?php
|
||||
$sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
|
||||
?>
|
||||
<div class="ds-summary-bar no-print mx-2 mt-2 rounded-sm">
|
||||
건수 : <?= (int) ($sc['total'] ?? 0) ?>
|
||||
(정상 : <?= (int) ($sc[1] ?? 0) ?> / 폐업 : <?= (int) ($sc[2] ?? 0) ?> / 해지 : <?= (int) ($sc[3] ?? 0) ?>)
|
||||
</div>
|
||||
|
||||
<div class="ds-split no-print mx-2 mb-2 mt-2 flex-1 min-h-0">
|
||||
<div class="ds-list-panel">
|
||||
<div class="ds-panel-title shrink-0">지정판매소 리스트</div>
|
||||
<div class="overflow-auto flex-1 min-h-0">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-14">번호</th>
|
||||
<th class="w-24">구·군</th>
|
||||
<th class="w-24">지정일</th>
|
||||
<th class="w-24">구역</th>
|
||||
<th class="ds-col-tight">대표자명</th>
|
||||
<th class="ds-col-tight">상호명</th>
|
||||
<th class="ds-col-zip">우편번호</th>
|
||||
<th class="text-left">주소</th>
|
||||
<th class="w-28">사업자번호</th>
|
||||
<th class="w-28">전화</th>
|
||||
<th class="w-16">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ds-list-body" class="text-right">
|
||||
<?php foreach ($list as $i => $row): ?>
|
||||
<?php
|
||||
$sn = (string) ($row->ds_shop_no ?? '');
|
||||
if (preg_match('/(\d{3})$/', $sn, $m)) {
|
||||
$shortNo = $m[1];
|
||||
} elseif ($sn !== '' && strlen($sn) >= 3) {
|
||||
$shortNo = substr($sn, -3);
|
||||
} else {
|
||||
$shortNo = $sn;
|
||||
}
|
||||
$st = (int) ($row->ds_state ?? 1);
|
||||
$stLabel = $st === 1 ? '' : ($st === 2 ? '폐업' : '해지');
|
||||
$ggCode = (string) ($row->ds_gugun_code ?? '');
|
||||
$ggLabel = (string) (($gugunNameMap[$ggCode] ?? '') !== '' ? $gugunNameMap[$ggCode] : $ggCode);
|
||||
$da = $row->ds_designated_at ?? null;
|
||||
$daDisp = ($da !== null && $da !== '' && (string) $da !== '0000-00-00') ? substr((string) $da, 0, 10) : '';
|
||||
$zone = (string) ($row->ds_zone_code ?? '');
|
||||
$zipList = trim((string) ($row->ds_zip ?? ''));
|
||||
$roadL = trim((string) ($row->ds_addr ?? ''));
|
||||
$jibunL = trim((string) ($row->ds_addr_jibun ?? ''));
|
||||
$addrMainList = $roadL !== '' ? $roadL : $jibunL;
|
||||
$addrDetailList = trim((string) ($row->ds_addr_detail ?? ''));
|
||||
$addrCombinedList = trim($addrMainList . ' ' . $addrDetailList);
|
||||
if ($addrCombinedList === '') {
|
||||
$addrCombinedList = $addrMainList;
|
||||
}
|
||||
?>
|
||||
<tr class="ds-list-row cursor-pointer" data-row-index="<?= (int) $i ?>" role="button" tabindex="0">
|
||||
<td class="text-center"><?= esc($shortNo) ?></td>
|
||||
<td class="text-left pl-1 text-xs"><?= esc($ggLabel) ?></td>
|
||||
<td class="text-center text-xs"><?= esc($daDisp) ?></td>
|
||||
<td class="text-left pl-1 text-xs"><?= esc($zone) ?></td>
|
||||
<td class="text-left pl-1 text-xs ds-col-tight" title="<?= esc($row->ds_rep_name ?? '') ?>"><?= esc($row->ds_rep_name ?? '') ?></td>
|
||||
<td class="text-left pl-1 text-xs ds-col-tight" title="<?= esc($row->ds_name ?? '') ?>"><?= esc($row->ds_name ?? '') ?></td>
|
||||
<td class="text-center text-xs ds-col-zip" title="<?= esc($zipList) ?>"><?= esc($zipList) ?></td>
|
||||
<td class="text-left pl-1 text-xs ds-col-addr-list" title="<?= esc($addrCombinedList) ?>"><?= esc($addrCombinedList) ?></td>
|
||||
<td class="text-left pl-1 text-xs"><?= esc($row->ds_biz_no ?? '') ?></td>
|
||||
<td class="text-left pl-1 text-xs"><?= esc($row->ds_tel ?? '') ?></td>
|
||||
<td class="text-center <?= $st === 2 ? 'text-pink-600 font-medium' : ($st === 3 ? 'text-orange-700' : '') ?>"><?= esc($stLabel) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ds-detail-panel">
|
||||
<div class="ds-panel-title shrink-0">지정판매소 정보</div>
|
||||
<div class="ds-detail-inner" id="ds-detail-box">
|
||||
<p id="ds-detail-placeholder" class="text-sm text-gray-500 py-6 text-center">위 목록에서 행을 선택하세요.</p>
|
||||
<div id="ds-detail-fields" class="hidden">
|
||||
<div class="ds-detail-info-wrap">
|
||||
<table class="w-full data-table text-sm" id="ds-detail-info-table" aria-label="지정판매소 상세">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>판매소번호</th>
|
||||
<th class="ds-col-tight-head">상호명</th>
|
||||
<th>우편번호</th>
|
||||
<th>사업자번호</th>
|
||||
<th>일반전화</th>
|
||||
<th class="ds-col-tight-head">대표자명</th>
|
||||
<th>이메일</th>
|
||||
<th>업태</th>
|
||||
<th>업종</th>
|
||||
<th>지정일자</th>
|
||||
<th>지자체</th>
|
||||
<th>도로명주소</th>
|
||||
<th>지번주소</th>
|
||||
<th>상세주소</th>
|
||||
<th>개인전화</th>
|
||||
<th>구·군</th>
|
||||
<th>구역</th>
|
||||
<th>가상계좌(은행)</th>
|
||||
<th>계좌번호</th>
|
||||
<th>종사업장번호</th>
|
||||
<th>변경일자</th>
|
||||
<th>영업상태</th>
|
||||
<th>등록일시</th>
|
||||
<th>변경사유</th>
|
||||
<th class="no-print w-14">지도</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-left" data-ro="ds_shop_no">—</td>
|
||||
<td class="text-left ds-col-tight-cell" data-ro="ds_name">—</td>
|
||||
<td class="text-left" data-ro="ds_zip">—</td>
|
||||
<td class="text-left" data-ro="ds_biz_no">—</td>
|
||||
<td class="text-left" data-ro="ds_tel">—</td>
|
||||
<td class="text-left ds-col-tight-cell" data-ro="ds_rep_name">—</td>
|
||||
<td class="text-left" data-ro="ds_email">—</td>
|
||||
<td class="text-left" data-ro="ds_biz_type">—</td>
|
||||
<td class="text-left" data-ro="ds_biz_kind">—</td>
|
||||
<td class="text-left" data-ro="ds_designated_at">—</td>
|
||||
<td class="text-left" data-ro="lg_name">—</td>
|
||||
<td class="text-left min-w-[10rem]"><span data-ro="ds_addr">—</span></td>
|
||||
<td class="text-left" data-ro="ds_addr_jibun">—</td>
|
||||
<td class="text-left" data-ro="ds_addr_detail">—</td>
|
||||
<td class="text-left" data-ro="ds_rep_phone">—</td>
|
||||
<td class="text-left" data-ro="gugun_name">—</td>
|
||||
<td class="text-left" data-ro="ds_zone_code">—</td>
|
||||
<td class="text-left" data-ro="ds_va_bank">—</td>
|
||||
<td class="text-left" data-ro="ds_va_account">—</td>
|
||||
<td class="text-left" data-ro="ds_branch_no">—</td>
|
||||
<td class="text-left" data-ro="ds_state_changed_at">—</td>
|
||||
<td class="text-left" data-ro="state_label">—</td>
|
||||
<td class="text-left" data-ro="ds_regdate">—</td>
|
||||
<td class="text-left min-w-[8rem]" data-ro="ds_change_reason">—</td>
|
||||
<td class="text-center no-print">
|
||||
<button type="button" class="border border-btn-print-border text-gray-700 px-2 py-0.5 rounded-sm text-xs hover:bg-gray-50" id="ds-ro-map-btn">지도</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (! $readOnly): ?>
|
||||
<div class="ds-detail-actions no-print flex flex-wrap items-center gap-3 shrink-0">
|
||||
<a id="ds-edit-link" href="#" class="text-blue-700 hover:underline text-sm font-medium pointer-events-none opacity-40">수정</a>
|
||||
<form id="ds-delete-form" method="POST" action="" class="inline" onsubmit="return confirm('이 지정판매소를 삭제하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" id="ds-delete-btn" class="text-red-600 hover:underline text-sm pointer-events-none opacity-40" disabled>삭제</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (isset($pager)): ?>
|
||||
<div class="mt-2 mb-2 mx-2 no-print"><?= $pager->links() ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= view('components/kakao_map_modal', ['kakaoJavascriptKey' => $kakaoJavascriptKey ?? '']) ?>
|
||||
|
||||
<script type="application/json" id="ds-detail-json"><?= $detailRowsJson ?? '[]' ?></script>
|
||||
<script>
|
||||
(function () {
|
||||
var raw = document.getElementById('ds-detail-json');
|
||||
var rows = [];
|
||||
try {
|
||||
rows = JSON.parse(raw.textContent || '[]');
|
||||
} catch (e) {
|
||||
rows = [];
|
||||
}
|
||||
var readOnly = <?= json_encode($readOnly) ?>;
|
||||
var body = document.getElementById('ds-list-body');
|
||||
var placeholder = document.getElementById('ds-detail-placeholder');
|
||||
var fieldsWrap = document.getElementById('ds-detail-fields');
|
||||
var infoTable = document.getElementById('ds-detail-info-table');
|
||||
var editLink = readOnly ? null : document.getElementById('ds-edit-link');
|
||||
var delForm = readOnly ? null : document.getElementById('ds-delete-form');
|
||||
var delBtn = readOnly ? null : document.getElementById('ds-delete-btn');
|
||||
// mgmt_url() 이 path 를 trim 하므로 'edit/33' 이 아니라 'edit33' 로 붙지 않게 슬래시를 넣음
|
||||
var editBase = <?= json_encode(mgmt_url('designated-shops/edit')) ?>;
|
||||
var delBase = <?= json_encode(mgmt_url('designated-shops/delete')) ?>;
|
||||
|
||||
function textVal(v) {
|
||||
return (v === '' || v == null) ? '—' : String(v);
|
||||
}
|
||||
function buildKakaoMapSearchQuery(d) {
|
||||
var road = String(d.ds_addr || '').trim();
|
||||
var jibun = String(d.ds_addr_jibun || '').trim();
|
||||
var detail = String(d.ds_addr_detail || '').trim();
|
||||
var q = road || jibun;
|
||||
if (detail) {
|
||||
q = q ? (q + ' ' + detail) : detail;
|
||||
}
|
||||
return q;
|
||||
}
|
||||
function fillDetailInfoTable(d) {
|
||||
if (!infoTable) return;
|
||||
infoTable.querySelectorAll('[data-ro]').forEach(function (el) {
|
||||
var k = el.getAttribute('data-ro');
|
||||
var v = d[k];
|
||||
if (k === 'ds_va_account') {
|
||||
v = d.ds_va_account || d.ds_va_number || '';
|
||||
}
|
||||
el.textContent = textVal(v);
|
||||
});
|
||||
window.__dsDetailForMap = d;
|
||||
}
|
||||
|
||||
function selectIndex(idx) {
|
||||
if (!rows.length || idx < 0 || idx >= rows.length) return;
|
||||
var d = rows[idx];
|
||||
Array.prototype.forEach.call(body.querySelectorAll('tr.ds-list-row'), function (tr) {
|
||||
tr.classList.remove('ds-row-selected');
|
||||
});
|
||||
var tr = body.querySelector('tr[data-row-index="' + idx + '"]');
|
||||
if (tr) tr.classList.add('ds-row-selected');
|
||||
|
||||
placeholder.classList.add('hidden');
|
||||
fieldsWrap.classList.remove('hidden');
|
||||
fillDetailInfoTable(d);
|
||||
|
||||
if (!readOnly && editLink && delForm && delBtn) {
|
||||
var id = d.ds_idx;
|
||||
editLink.href = editBase + '/' + id;
|
||||
editLink.classList.remove('pointer-events-none', 'opacity-40');
|
||||
delForm.action = delBase + '/' + id;
|
||||
delBtn.disabled = false;
|
||||
delBtn.classList.remove('pointer-events-none', 'opacity-40');
|
||||
}
|
||||
}
|
||||
|
||||
if (body) {
|
||||
body.addEventListener('click', function (e) {
|
||||
var tr = e.target.closest('tr.ds-list-row');
|
||||
if (!tr) return;
|
||||
var idx = parseInt(tr.getAttribute('data-row-index'), 10);
|
||||
if (!isNaN(idx)) selectIndex(idx);
|
||||
});
|
||||
body.addEventListener('keydown', function (e) {
|
||||
if (e.key !== 'Enter' && e.key !== ' ') return;
|
||||
var tr = e.target.closest('tr.ds-list-row');
|
||||
if (!tr) return;
|
||||
e.preventDefault();
|
||||
var idx = parseInt(tr.getAttribute('data-row-index'), 10);
|
||||
if (!isNaN(idx)) selectIndex(idx);
|
||||
});
|
||||
}
|
||||
|
||||
var mapBtnRo = document.getElementById('ds-ro-map-btn');
|
||||
if (mapBtnRo) {
|
||||
mapBtnRo.addEventListener('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
var d = window.__dsDetailForMap;
|
||||
if (!d) return;
|
||||
var q = buildKakaoMapSearchQuery(d);
|
||||
if (!q) {
|
||||
window.alert('표시할 주소 정보가 없습니다.');
|
||||
return;
|
||||
}
|
||||
if (typeof window.openDesignatedShopKakaoMap === 'function') {
|
||||
window.openDesignatedShopKakaoMap(q);
|
||||
} else {
|
||||
window.open('https://map.kakao.com/link/search/' + encodeURIComponent(q), '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (rows.length > 0) {
|
||||
selectIndex(0);
|
||||
} else if (!readOnly && editLink && delBtn) {
|
||||
editLink.classList.add('pointer-events-none', 'opacity-40');
|
||||
delBtn.disabled = true;
|
||||
delBtn.classList.add('pointer-events-none', 'opacity-40');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- 인쇄용: 전체 테이블 -->
|
||||
<div class="hidden print:block print:p-4">
|
||||
<table class="w-full data-table text-xs">
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>번호</th>
|
||||
<th class="w-16">번호</th>
|
||||
<th>지자체</th>
|
||||
<th>구·군</th>
|
||||
<th>지정일</th>
|
||||
<th>구역</th>
|
||||
<th>대표자명</th>
|
||||
<th>상호명</th>
|
||||
<th>우편번호</th>
|
||||
<th>주소</th>
|
||||
<th>사업자번호</th>
|
||||
<th>전화</th>
|
||||
<th>판매소번호</th>
|
||||
<th>상호명</th>
|
||||
<th>대표자</th>
|
||||
<th>사업자번호</th>
|
||||
<th>가상계좌</th>
|
||||
<th>상태</th>
|
||||
<th>등록일</th>
|
||||
<th class="w-28">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="text-right">
|
||||
<?php foreach ($list as $row): ?>
|
||||
<?php
|
||||
$snP = (string) ($row->ds_shop_no ?? '');
|
||||
if (preg_match('/(\d{3})$/', $snP, $mP)) {
|
||||
$shortNoP = $mP[1];
|
||||
} elseif ($snP !== '' && strlen($snP) >= 3) {
|
||||
$shortNoP = substr($snP, -3);
|
||||
} else {
|
||||
$shortNoP = $snP;
|
||||
}
|
||||
$daP = $row->ds_designated_at ?? null;
|
||||
$daDispP = ($daP !== null && $daP !== '' && (string) $daP !== '0000-00-00') ? substr((string) $daP, 0, 10) : '';
|
||||
$stP = (int) ($row->ds_state ?? 1);
|
||||
$stLabP = $stP === 1 ? '정상' : ($stP === 2 ? '폐업' : '직권해지');
|
||||
$zipP = trim((string) ($row->ds_zip ?? ''));
|
||||
$roadP = trim((string) ($row->ds_addr ?? ''));
|
||||
$jibP = trim((string) ($row->ds_addr_jibun ?? ''));
|
||||
$addrP = $roadP !== '' ? $roadP : $jibP;
|
||||
$detP = trim((string) ($row->ds_addr_detail ?? ''));
|
||||
$addrCombinedP = trim($addrP . ' ' . $detP);
|
||||
if ($addrCombinedP === '') {
|
||||
$addrCombinedP = $addrP;
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($shortNoP) ?></td>
|
||||
<td class="text-left"><?= esc($lgMap[$row->ds_lg_idx] ?? '') ?></td>
|
||||
<?php $gCodeP = (string) ($row->ds_gugun_code ?? ''); ?>
|
||||
<td class="text-left"><?= esc((string) (($gugunNameMap[$gCodeP] ?? '') !== '' ? $gugunNameMap[$gCodeP] : $gCodeP)) ?></td>
|
||||
<td class="text-center"><?= esc($daDispP) ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_zone_code ?? '') ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_rep_name ?? '') ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_name ?? '') ?></td>
|
||||
<td class="text-left"><?= esc($zipP) ?></td>
|
||||
<td class="text-left"><?= esc($addrCombinedP) ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_biz_no ?? '') ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_tel ?? '') ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_shop_no) ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_va_number) ?></td>
|
||||
<td class="text-center"><?= esc($stLabP) ?></td>
|
||||
<td class="text-left"><?= esc($row->ds_regdate ?? '') ?></td>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="text-center">
|
||||
<a href="<?= mgmt_url('designated-shops/show/' . (int) $row->ds_idx) ?>" class="text-blue-600 hover:underline"><?= esc($row->ds_idx) ?></a>
|
||||
</td>
|
||||
<td class="text-left pl-2"><?= esc($lgMap[$row->ds_lg_idx] ?? '') ?></td>
|
||||
<td class="text-left pl-2">
|
||||
<a href="<?= mgmt_url('designated-shops/show/' . (int) $row->ds_idx) ?>" class="text-blue-600 hover:underline"><?= esc($row->ds_shop_no) ?></a>
|
||||
</td>
|
||||
<td class="text-left pl-2">
|
||||
<a href="<?= mgmt_url('designated-shops/show/' . (int) $row->ds_idx) ?>" class="text-blue-600 font-medium hover:underline"><?= esc($row->ds_name) ?></a>
|
||||
</td>
|
||||
<td class="text-left pl-2"><?= esc(mask_person_name($row->ds_rep_name ?? null)) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->ds_biz_no) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->ds_va_number) ?></td>
|
||||
<td class="text-center"><?= (int) $row->ds_state === 1 ? '정상' : ((int) $row->ds_state === 2 ? '폐업' : '직권해지') ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->ds_regdate ?? '') ?></td>
|
||||
<td class="text-center">
|
||||
<a href="<?= mgmt_url('designated-shops/show/' . (int) $row->ds_idx) ?>" class="text-gray-700 hover:underline text-sm mr-1">상세</a>
|
||||
<a href="<?= mgmt_url('designated-shops/edit/' . (int) $row->ds_idx) ?>" class="text-blue-600 hover:underline text-sm">수정</a>
|
||||
<form action="<?= mgmt_url('designated-shops/delete/' . (int) $row->ds_idx) ?>" method="POST" class="inline ml-1" onsubmit="return confirm('이 지정판매소를 삭제하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php if (isset($pager)): ?><div class="mt-3"><?= $pager->links() ?></div><?php endif; ?>
|
||||
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
<?php
|
||||
helper('pii_mask');
|
||||
?>
|
||||
<?= view('components/print_header', ['printTitle' => '지정판매소 지도']) ?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700">지정판매소 지도</span>
|
||||
<a href="<?= mgmt_url('designated-shops') ?>" class="border border-gray-300 text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">목록으로</a>
|
||||
<a href="<?= base_url('admin/designated-shops') ?>" class="border border-gray-300 text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">목록으로</a>
|
||||
</div>
|
||||
</section>
|
||||
<div id="kakao-map" class="w-full border border-gray-300 mt-2" style="height:600px;"></div>
|
||||
<div class="mt-2 text-sm text-gray-500">총 <?= count($shops) ?>개 판매소 표시</div>
|
||||
|
||||
<script src="//dapi.kakao.com/v2/maps/sdk.js?appkey=<?= esc($kakaoJavascriptKey ?? '', 'attr') ?>&libraries=services"></script>
|
||||
<script src="//dapi.kakao.com/v2/maps/sdk.js?appkey=KAKAO_APP_KEY&libraries=services"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var mapContainer = document.getElementById('kakao-map');
|
||||
if (typeof kakao === 'undefined' || typeof kakao.maps === 'undefined') {
|
||||
mapContainer.innerHTML = '<div class="flex items-center justify-center h-full text-gray-500 text-sm px-4 text-center">카카오맵을 불러올 수 없습니다. Kakao Developers → 제품 설정에서 「Kakao Map」을 켜고, 플랫폼(Web)에 이 사이트 URL을 등록했는지 확인하세요.</div>';
|
||||
mapContainer.innerHTML = '<div class="flex items-center justify-center h-full text-gray-400">카카오맵 API 키를 설정해 주세요.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -25,7 +28,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
var geocoder = new kakao.maps.services.Geocoder();
|
||||
var shops = <?= json_encode(array_map(function($s) {
|
||||
return ['name' => $s->ds_name, 'addr' => $s->ds_addr ?? '', 'rep' => $s->ds_rep_name ?? '', 'tel' => $s->ds_tel ?? ''];
|
||||
return ['name' => $s->ds_name, 'addr' => $s->ds_addr ?? '', 'rep' => mask_person_name($s->ds_rep_name ?? null), 'tel' => $s->ds_tel ?? ''];
|
||||
}, $shops), JSON_UNESCAPED_UNICODE) ?>;
|
||||
|
||||
var bounds = new kakao.maps.LatLngBounds();
|
||||
|
||||
94
app/Views/admin/designated_shop/show.php
Normal file
94
app/Views/admin/designated_shop/show.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
$shop = $shop ?? null;
|
||||
$currentLg = $currentLg ?? null;
|
||||
$stateLabel = $stateLabel ?? '';
|
||||
$can_edit = $can_edit ?? false;
|
||||
if ($shop === null) {
|
||||
return;
|
||||
}
|
||||
helper('pii_mask');
|
||||
$val = static fn (?string $s): string => $s !== null && $s !== '' ? $s : '—';
|
||||
$dispMask = static function (string $masked): string {
|
||||
return $masked !== '' ? $masked : '—';
|
||||
};
|
||||
?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="text-sm font-bold text-gray-700">지정판매소 정보</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= mgmt_url('designated-shops') ?>" class="border border-gray-300 text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">목록</a>
|
||||
<?php if ($can_edit): ?>
|
||||
<a href="<?= mgmt_url('designated-shops/edit/' . (int) $shop->ds_idx) ?>" class="bg-btn-search text-white px-3 py-1 rounded-sm text-sm hover:opacity-90">수정</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl space-y-3 text-sm">
|
||||
<?php if ($currentLg !== null): ?>
|
||||
<div class="flex flex-wrap gap-2 border-b border-gray-100 pb-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">지자체</span>
|
||||
<span><?= esc($currentLg->lg_name) ?> <span class="text-gray-500">(<?= esc($currentLg->lg_code) ?>)</span></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">판매소번호</span>
|
||||
<span class="font-mono"><?= esc($shop->ds_shop_no) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">구코드</span>
|
||||
<span class="font-mono"><?= esc($val($shop->ds_gugun_code ?? null)) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">상호명</span>
|
||||
<span class="font-semibold"><?= esc($shop->ds_name) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">사업자번호</span>
|
||||
<span><?= esc($val($shop->ds_biz_no ?? null)) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">대표자명</span>
|
||||
<span><?= esc($dispMask(mask_person_name($shop->ds_rep_name ?? null))) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">가상계좌</span>
|
||||
<span><?= esc($val($shop->ds_va_number ?? null)) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">우편번호</span>
|
||||
<span><?= esc($val($shop->ds_zip ?? null)) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">도로명주소</span>
|
||||
<span><?= esc($val($shop->ds_addr ?? null)) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">지번주소</span>
|
||||
<span><?= esc($val($shop->ds_addr_jibun ?? null)) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">일반전화</span>
|
||||
<span><?= esc($val($shop->ds_tel ?? null)) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">대표 휴대전화</span>
|
||||
<span><?= esc($dispMask(mask_mobile_phone($shop->ds_rep_phone ?? null))) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">이메일</span>
|
||||
<span><?= esc($val($shop->ds_email ?? null)) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">지정일자</span>
|
||||
<span><?= esc($val($shop->ds_designated_at ?? null)) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">영업상태</span>
|
||||
<span><?= esc($stateLabel) ?></span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="font-bold text-gray-700 w-28 shrink-0">등록일시</span>
|
||||
<span><?= esc($val($shop->ds_regdate ?? null)) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,387 +1,80 @@
|
||||
<?php
|
||||
$ry = (int) ($reportYear ?? (int) date('Y'));
|
||||
$exportUrl = mgmt_url('designated-shops/status/export') . '?' . http_build_query([
|
||||
'year' => $ry,
|
||||
]);
|
||||
$fixedGugunLabel = trim((string) ($fixedGugunLabel ?? ''));
|
||||
$regionColLabel = '군·구';
|
||||
$sumCurrForPct = (int) ($districtTotal->curr_end ?? 0);
|
||||
?>
|
||||
<style>
|
||||
.ds-status-x-scroll {
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-behavior: smooth;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
max-width: 100%;
|
||||
}
|
||||
@media print {
|
||||
.ds-status-x-scroll { overflow: visible !important; border: none; }
|
||||
}
|
||||
.ds-status-x-scroll .ds-status-table {
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.ds-status-x-scroll .ds-status-table th,
|
||||
.ds-status-x-scroll .ds-status-table td {
|
||||
white-space: nowrap;
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.ds-status-x-scroll .ds-status-table thead th {
|
||||
background: #e9ecef;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.ds-status-x-scroll .ds-status-table tbody td {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.ds-status-x-scroll th.sticky-num,
|
||||
.ds-status-x-scroll td.sticky-num {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
min-width: 3rem;
|
||||
max-width: 3rem;
|
||||
width: 3rem;
|
||||
box-sizing: border-box;
|
||||
background: #e9ecef;
|
||||
border-right: 1px solid #bbb;
|
||||
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.ds-status-x-scroll td.sticky-num {
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
.ds-status-x-scroll tr.sum-row td.sticky-num {
|
||||
background: #f3f4f6;
|
||||
}
|
||||
.ds-status-x-scroll th.sticky-region,
|
||||
.ds-status-x-scroll td.sticky-region {
|
||||
position: sticky;
|
||||
left: 3rem;
|
||||
z-index: 2;
|
||||
background: #e9ecef;
|
||||
border-right: 1px solid #bbb;
|
||||
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.06);
|
||||
max-width: 16rem;
|
||||
text-align: left;
|
||||
}
|
||||
.ds-status-x-scroll td.sticky-region {
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.ds-status-x-scroll tr.sum-row td.sticky-region {
|
||||
background: #f3f4f6;
|
||||
}
|
||||
.ds-help {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.ds-help-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 1rem;
|
||||
height: 1rem;
|
||||
padding: 0 2px;
|
||||
border: 1px solid #64748b;
|
||||
border-radius: 999px;
|
||||
background: #fef9c3;
|
||||
color: #111827;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
font-family: Arial, sans-serif;
|
||||
cursor: help;
|
||||
user-select: none;
|
||||
}
|
||||
.ds-floating-tip {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: none;
|
||||
max-width: min(22rem, calc(100vw - 16px));
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #8fa0b3;
|
||||
border-radius: 4px;
|
||||
background: #1f2937;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1.35;
|
||||
text-align: left;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
<?= view('components/print_header', ['printTitle' => '지정판매소 신규·취소 현황 (' . $ry . '년)']) ?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel no-print">
|
||||
<?= view('components/print_header', ['printTitle' => '지정판매소 현황']) ?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700">지정판매소 현황 (신규/취소)</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= esc($exportUrl) ?>" class="border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
||||
<button type="button" onclick="window.print()" class="border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('designated-shops') ?>" class="border border-gray-300 text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">목록으로</a>
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= base_url('admin/designated-shops') ?>" class="border border-gray-300 text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">목록으로</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="p-2 bg-white border-b border-gray-200 no-print">
|
||||
<form method="get" action="<?= mgmt_url('designated-shops/status') ?>" class="flex flex-wrap items-end gap-3">
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600 mb-0.5">연도</label>
|
||||
<select name="year" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[6rem]">
|
||||
<?php foreach (($yearChoices ?? []) as $y): ?>
|
||||
<option value="<?= (int) $y ?>" <?= $ry === (int) $y ? 'selected' : '' ?>><?= (int) $y ?>년</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="min-w-[12rem]">
|
||||
<label class="block text-xs text-gray-600 mb-0.5">군·구</label>
|
||||
<div class="border border-gray-300 rounded px-3 py-1 text-sm bg-gray-50 text-gray-800 font-medium pointer-events-none select-none">
|
||||
<?= esc($fixedGugunLabel !== '' ? $fixedGugunLabel : '현재 지자체 기준') ?>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
</form>
|
||||
<p class="text-xs text-gray-500 mt-2">
|
||||
종전·현행은 각각 <?= $ry - 1 ?>-12-31·<?= $ry ?>-12-31 기준 정상(지정일 이전·비정상 전환일 이후) 건수이며, 지정·취소는 <?= $ry ?>년 달력연도 기준입니다. 군·구는 현재 로그인 사용자의 지자체 기준으로 고정 표시됩니다.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 인쇄 시에도 보이는 본표 -->
|
||||
<div class="mx-2 mt-2 mb-2 ds-status-x-scroll">
|
||||
<table class="ds-status-table data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sticky-num text-center w-12">순번</th>
|
||||
<th class="sticky-region"><?= esc($regionColLabel) ?></th>
|
||||
<th class="text-left">
|
||||
<span class="ds-help">구코드 <span class="ds-help-badge" tabindex="0" data-tip="지정판매소에 저장된 구·군 코드 값">?</span></span>
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<span class="ds-help">종전 <span class="ds-help-badge" tabindex="0" data-tip="전년도 12월 31일 기준 정상 상태 판매소 수">?</span></span>(전년도말)
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<span class="ds-help">지정 <span class="ds-help-badge" tabindex="0" data-tip="<?= esc($ry) ?>년 내 지정일이 속한 신규 지정 건수">?</span></span>(<?= $ry ?>년)
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<span class="ds-help">취소 <span class="ds-help-badge" tabindex="0" data-tip="<?= esc($ry) ?>년 내 폐업/해지 전환일이 속한 건수">?</span></span>(<?= $ry ?>년)
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<span class="ds-help">현행 <span class="ds-help-badge" tabindex="0" data-tip="조회년도 12월 31일 기준 정상 상태 판매소 수">?</span></span>(금년도말)
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<span class="ds-help">증감 <span class="ds-help-badge" tabindex="0" data-tip="현행에서 종전을 뺀 값 (현행−종전)">?</span></span>
|
||||
<br/><span class="font-normal text-xs">(현행−종전)</span>
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<span class="ds-help">지정−취소 <span class="ds-help-badge" tabindex="0" data-tip="<?= esc($ry) ?>년 지정 건수에서 취소 건수를 뺀 값">?</span></span>
|
||||
<br/><span class="font-normal text-xs">(<?= $ry ?>년)</span>
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<span class="ds-help">현행비중 <span class="ds-help-badge" tabindex="0" data-tip="전체 현행 합계 대비 해당 행 현행 건수의 비율(%)">?</span></span>
|
||||
<br/><span class="font-normal text-xs">(%)</span>
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<span class="ds-help">전년대비 <span class="ds-help-badge ds-help-right" tabindex="0" data-tip="((현행−종전) / 종전) × 100, 종전이 0이면 표시 안함">?</span></span>
|
||||
<br/><span class="font-normal text-xs">증감률(%)</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php $rowNo = 0; ?>
|
||||
<?php foreach (($districtRows ?? []) as $row): ?>
|
||||
<?php
|
||||
$rowNo++;
|
||||
$curr = (int) $row->curr_end;
|
||||
$prev = (int) $row->prev_end;
|
||||
$pctShare = $sumCurrForPct > 0 ? round(($curr / $sumCurrForPct) * 100, 1) : 0.0;
|
||||
$yoyPct = $prev > 0 ? round((($curr - $prev) / $prev) * 100, 1) : null;
|
||||
?>
|
||||
<tr>
|
||||
<td class="sticky-num"><?= $rowNo ?></td>
|
||||
<td class="sticky-region" title="<?= esc($row->region_label) ?>"><?= esc($row->region_label) ?></td>
|
||||
<td class="text-left text-xs"><?= esc((string) ($row->gugun_code ?? '')) ?></td>
|
||||
<td><?= number_format($prev) ?></td>
|
||||
<td><?= number_format((int) $row->designated_y) ?></td>
|
||||
<td><?= number_format((int) $row->cancelled_y) ?></td>
|
||||
<td><?= number_format($curr) ?></td>
|
||||
<td><?= number_format((int) ($row->delta_curr_prev ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($row->delta_des_cancel ?? 0)) ?></td>
|
||||
<td><?= $pctShare ?></td>
|
||||
<td><?= $yoyPct !== null ? $yoyPct : '—' ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($districtRows)): ?>
|
||||
<tr><td colspan="11" class="text-center text-gray-400 py-6">조건에 맞는 데이터가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php if (! empty($districtRows) && isset($districtTotal)): ?>
|
||||
<tr class="font-bold bg-gray-50 sum-row">
|
||||
<td class="sticky-num">—</td>
|
||||
<td class="sticky-region"><?= esc($districtTotal->region_label) ?></td>
|
||||
<td class="text-left">—</td>
|
||||
<td><?= number_format((int) $districtTotal->prev_end) ?></td>
|
||||
<td><?= number_format((int) $districtTotal->designated_y) ?></td>
|
||||
<td><?= number_format((int) $districtTotal->cancelled_y) ?></td>
|
||||
<td><?= number_format((int) $districtTotal->curr_end) ?></td>
|
||||
<td><?= number_format((int) ($districtTotal->delta_curr_prev ?? 0)) ?></td>
|
||||
<td><?= number_format((int) ($districtTotal->delta_des_cancel ?? 0)) ?></td>
|
||||
<td>100</td>
|
||||
<td>
|
||||
<?php
|
||||
$tPrev = (int) $districtTotal->prev_end;
|
||||
$tCurr = (int) $districtTotal->curr_end;
|
||||
echo $tPrev > 0 ? round((($tCurr - $tPrev) / $tPrev) * 100, 1) : '—';
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- 전체 현황 요약 -->
|
||||
<div class="flex gap-4 mt-2 mb-2">
|
||||
<div class="border border-gray-300 p-3 flex-1 text-center">
|
||||
<div class="text-sm text-gray-500">활성 판매소</div>
|
||||
<div class="text-2xl font-bold text-green-600"><?= number_format($totalActive) ?></div>
|
||||
</div>
|
||||
<div class="border border-gray-300 p-3 flex-1 text-center">
|
||||
<div class="text-sm text-gray-500">비활성/취소 판매소</div>
|
||||
<div class="text-2xl font-bold text-red-600"><?= number_format($totalInactive) ?></div>
|
||||
</div>
|
||||
<div class="border border-gray-300 p-3 flex-1 text-center">
|
||||
<div class="text-sm text-gray-500">전체</div>
|
||||
<div class="text-2xl font-bold text-gray-700"><?= number_format($totalActive + $totalInactive) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php $zoneRows = $zoneSummaryRows ?? []; ?>
|
||||
<section class="mx-2 mb-3 no-print">
|
||||
<div class="text-xs font-semibold text-gray-700 mb-1">동별 현행 요약 (<?= esc($fixedGugunLabel ?? '군·구') ?>)</div>
|
||||
<?php if (! empty($zoneRows)): ?>
|
||||
<div class="flex flex-wrap gap-1 mb-2">
|
||||
<?php foreach ($zoneRows as $z): ?>
|
||||
<span class="inline-flex items-center px-2 py-0.5 text-xs rounded border border-gray-300 bg-gray-50 text-gray-700">
|
||||
<?= esc((string) $z->zone_label) ?> <?= number_format((int) $z->curr_end) ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="border border-gray-300 bg-white overflow-auto max-h-56">
|
||||
<table class="w-full data-table text-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">동</th>
|
||||
<th class="text-right">종전</th>
|
||||
<th class="text-right">지정</th>
|
||||
<th class="text-right">취소</th>
|
||||
<th class="text-right">현행</th>
|
||||
<th class="text-right">증감</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach ($zoneRows as $z): ?>
|
||||
<tr>
|
||||
<td class="text-left"><?= esc((string) $z->zone_label) ?></td>
|
||||
<td><?= number_format((int) $z->prev_end) ?></td>
|
||||
<td><?= number_format((int) $z->designated_y) ?></td>
|
||||
<td><?= number_format((int) $z->cancelled_y) ?></td>
|
||||
<td><?= number_format((int) $z->curr_end) ?></td>
|
||||
<td><?= number_format((int) $z->delta_curr_prev) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p class="text-xs text-gray-500">동별 집계 데이터가 없습니다.</p>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<div id="ds-floating-tip" class="ds-floating-tip no-print" aria-hidden="true"></div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var tipEl = document.getElementById('ds-floating-tip');
|
||||
if (!tipEl) return;
|
||||
var badges = Array.prototype.slice.call(document.querySelectorAll('.ds-help-badge'));
|
||||
if (!badges.length) return;
|
||||
|
||||
function placeTip(target) {
|
||||
var text = String(target.getAttribute('data-tip') || '').trim();
|
||||
if (!text) return;
|
||||
tipEl.textContent = text;
|
||||
tipEl.style.display = 'block';
|
||||
tipEl.setAttribute('aria-hidden', 'false');
|
||||
|
||||
var rect = target.getBoundingClientRect();
|
||||
var tipRect = tipEl.getBoundingClientRect();
|
||||
var vw = window.innerWidth || document.documentElement.clientWidth;
|
||||
var vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
var gap = 8;
|
||||
|
||||
var left = rect.left + (rect.width / 2) - (tipRect.width / 2);
|
||||
var top = rect.bottom + gap;
|
||||
|
||||
if (left < gap) left = gap;
|
||||
if (left + tipRect.width > vw - gap) left = vw - gap - tipRect.width;
|
||||
if (top + tipRect.height > vh - gap) top = rect.top - gap - tipRect.height;
|
||||
if (top < gap) top = gap;
|
||||
|
||||
tipEl.style.left = Math.round(left) + 'px';
|
||||
tipEl.style.top = Math.round(top) + 'px';
|
||||
}
|
||||
|
||||
function hideTip() {
|
||||
tipEl.style.display = 'none';
|
||||
tipEl.setAttribute('aria-hidden', 'true');
|
||||
tipEl.textContent = '';
|
||||
}
|
||||
|
||||
badges.forEach(function (badge) {
|
||||
badge.addEventListener('mouseenter', function () { placeTip(badge); });
|
||||
badge.addEventListener('focus', function () { placeTip(badge); });
|
||||
badge.addEventListener('mouseleave', hideTip);
|
||||
badge.addEventListener('blur', hideTip);
|
||||
});
|
||||
window.addEventListener('scroll', hideTip, true);
|
||||
window.addEventListener('resize', hideTip);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<details class="mx-2 mb-4 no-print text-sm">
|
||||
<summary class="cursor-pointer text-gray-600 hover:text-gray-800">연도별 요약 (참고)</summary>
|
||||
<div class="flex gap-4 mt-2">
|
||||
<div class="border border-gray-300 p-2 flex-1">
|
||||
<div class="text-xs font-bold text-gray-700 mb-1">활성 / 비활성 / 전체</div>
|
||||
<div class="text-sm">활성 <?= number_format((int) ($totalActive ?? 0)) ?> · 비활성 <?= number_format((int) ($totalInactive ?? 0)) ?> · 합 <?= number_format((int) ($totalActive ?? 0) + (int) ($totalInactive ?? 0)) ?></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-2">
|
||||
<!-- 연도별 신규등록 -->
|
||||
<div>
|
||||
<h3 class="text-sm font-bold text-gray-700 mb-1">연도별 신규등록 건수</h3>
|
||||
<div class="border border-gray-300 overflow-auto">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>연도</th>
|
||||
<th>신규등록 건수</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach ($newByYear as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->yr) ?>년</td>
|
||||
<td><?= number_format((int) $row->cnt) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($newByYear)): ?>
|
||||
<tr><td colspan="2" class="text-center text-gray-400 py-4">데이터가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-2">
|
||||
<div>
|
||||
<h3 class="text-xs font-bold text-gray-700 mb-1">연도별 신규등록 (지정일)</h3>
|
||||
<div class="border border-gray-300 overflow-auto max-h-48">
|
||||
<table class="w-full data-table text-xs">
|
||||
<thead><tr><th>연도</th><th>건수</th></tr></thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach (($newByYear ?? []) as $row): ?>
|
||||
<tr><td class="text-center"><?= esc($row->yr) ?>년</td><td><?= number_format((int) $row->cnt) ?></td></tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($newByYear)): ?>
|
||||
<tr><td colspan="2" class="text-center text-gray-400 py-2">없음</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xs font-bold text-gray-700 mb-1">연도별 취소/비활성 (등록일 기준)</h3>
|
||||
<div class="border border-gray-300 overflow-auto max-h-48">
|
||||
<table class="w-full data-table text-xs">
|
||||
<thead><tr><th>연도</th><th>건수</th></tr></thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach (($cancelByYear ?? []) as $row): ?>
|
||||
<tr><td class="text-center"><?= esc($row->yr) ?>년</td><td><?= number_format((int) $row->cnt) ?></td></tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($cancelByYear)): ?>
|
||||
<tr><td colspan="2" class="text-center text-gray-400 py-2">없음</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 연도별 취소/비활성 -->
|
||||
<div>
|
||||
<h3 class="text-sm font-bold text-gray-700 mb-1">연도별 취소/비활성 건수</h3>
|
||||
<div class="border border-gray-300 overflow-auto">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>연도</th>
|
||||
<th>취소/비활성 건수</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach ($cancelByYear as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->yr) ?>년</td>
|
||||
<td><?= number_format((int) $row->cnt) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($cancelByYear)): ?>
|
||||
<tr><td colspan="2" class="text-center text-gray-400 py-4">데이터가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">대상자 등록</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('free-recipients/store') ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/free-recipients/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= mgmt_url('free-recipients') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/free-recipients') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">대상자 수정</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('free-recipients/update/' . (int) $item->fr_idx) ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/free-recipients/update/' . (int) $item->fr_idx) ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button>
|
||||
<a href="<?= mgmt_url('free-recipients') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/free-recipients') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -4,18 +4,45 @@
|
||||
<span class="text-sm font-bold text-gray-700">무료용 대상자 관리</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('free-recipients/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">대상자 등록</a>
|
||||
<a href="<?= base_url('admin/free-recipients/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">대상자 등록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="no-print border border-gray-300 bg-white p-2 mt-2">
|
||||
<form method="POST" action="<?= mgmt_url('free-recipients') ?>" class="flex flex-wrap items-end gap-2" autocomplete="off">
|
||||
<?= csrf_field() ?>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">조회 기준</label>
|
||||
<select name="search_field" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[10rem]">
|
||||
<option value="fr_idx" <?= ($search_field ?? 'fr_name') === 'fr_idx' ? 'selected' : '' ?>>번호</option>
|
||||
<option value="fr_type_code" <?= ($search_field ?? 'fr_name') === 'fr_type_code' ? 'selected' : '' ?>>구분</option>
|
||||
<option value="fr_name" <?= ($search_field ?? 'fr_name') === 'fr_name' ? 'selected' : '' ?>>대상자명</option>
|
||||
<option value="fr_phone" <?= ($search_field ?? 'fr_name') === 'fr_phone' ? 'selected' : '' ?>>연락처</option>
|
||||
<option value="fr_addr" <?= ($search_field ?? 'fr_name') === 'fr_addr' ? 'selected' : '' ?>>주소</option>
|
||||
<option value="fr_dong_code" <?= ($search_field ?? 'fr_name') === 'fr_dong_code' ? 'selected' : '' ?>>동</option>
|
||||
<option value="fr_note" <?= ($search_field ?? 'fr_name') === 'fr_note' ? 'selected' : '' ?>>비고</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">조회어</label>
|
||||
<input type="text" name="search_query" value="<?= esc($search_query ?? '') ?>" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[18rem]" placeholder="조회어 입력">
|
||||
</div>
|
||||
<div class="flex items-center gap-2 pb-0.5">
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('free-recipients') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-16">번호</th>
|
||||
<th>구분</th>
|
||||
<th>대상자명</th>
|
||||
<th>연락처</th>
|
||||
<th>주소</th>
|
||||
<th>동</th>
|
||||
<th>비고</th>
|
||||
<th>종료일</th>
|
||||
<th class="w-20">상태</th>
|
||||
@@ -26,15 +53,17 @@
|
||||
<?php foreach ($list as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->fr_idx) ?></td>
|
||||
<td class="text-center"><?= esc($type_code_map[(string) ($row->fr_type_code ?? '')] ?? ($row->fr_type_code ?? '')) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->fr_name) ?></td>
|
||||
<td class="text-center"><?= esc($row->fr_phone) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->fr_addr) ?></td>
|
||||
<td class="text-center"><?= esc($dong_code_map[(string) ($row->fr_dong_code ?? '')] ?? ($row->fr_dong_code ?? '')) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->fr_note) ?></td>
|
||||
<td class="text-center"><?= esc($row->fr_end_date) ?></td>
|
||||
<td class="text-center"><?= (int) $row->fr_state === 1 ? '사용' : '미사용' ?></td>
|
||||
<td class="text-center">
|
||||
<a href="<?= mgmt_url('free-recipients/edit/' . (int) $row->fr_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
|
||||
<form action="<?= mgmt_url('free-recipients/delete/' . (int) $row->fr_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<a href="<?= base_url('admin/free-recipients/edit/' . (int) $row->fr_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
|
||||
<form action="<?= base_url('admin/free-recipients/delete/' . (int) $row->fr_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
|
||||
</form>
|
||||
@@ -42,12 +71,7 @@
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($list)): ?>
|
||||
<tr>
|
||||
<td colspan="8" class="text-center text-gray-500 py-4 text-sm space-y-1">
|
||||
<p>등록된 데이터가 없습니다.</p>
|
||||
<p class="text-gray-400">다른 지자체를 선택 중이면 해당 지자체 기준으로만 조회됩니다. Super Admin 은 상단에서 작업 지자체를 바꿔 보세요.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td colspan="10" class="text-center text-gray-400 py-4">등록된 데이터가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -12,17 +12,15 @@ if ($effectiveLgIdx) {
|
||||
$lgRow = model(\App\Models\LocalGovernmentModel::class)->find($effectiveLgIdx);
|
||||
$effectiveLgName = $lgRow ? $lgRow->lg_name : null;
|
||||
}
|
||||
$userNav = session_user_nav_display();
|
||||
$currentPath = current_nav_request_path();
|
||||
$currentPath = trim((string) $uriObj->getPath(), '/');
|
||||
if (str_starts_with($currentPath, 'index.php/')) {
|
||||
$currentPath = substr($currentPath, strlen('index.php/'));
|
||||
}
|
||||
$adminNavTree = get_admin_nav_tree();
|
||||
|
||||
/** DB 링크(mm_link)만 사용. 짧게 적은 항목(menus 등)은 실제 URI(admin/menus)와 맞춰 후보 비교 */
|
||||
$adminNavItemIsCurrent = static function (?string $mmLink) use ($currentPath): bool {
|
||||
return menu_link_matches_request($mmLink, $currentPath, []);
|
||||
};
|
||||
|
||||
/** 메뉴가 DB에서 안 쓰일 때만(폴백 상단바) 세그먼트 기반 활성 */
|
||||
$isActive = static function (string $path) use ($uri, $seg3) {
|
||||
$isActive = static function (string $path) use ($uri, $seg3, $currentPath, $adminNavTree) {
|
||||
if (! empty($adminNavTree)) {
|
||||
return $currentPath === trim($path, '/');
|
||||
}
|
||||
if ($path === 'admin' || $path === '') return $uri === '';
|
||||
if ($path === 'users') return $uri === 'users';
|
||||
if ($path === 'login-history') return $uri === 'access' && $seg3 === 'login-history';
|
||||
@@ -40,7 +38,7 @@ $isActive = static function (string $path) use ($uri, $seg3) {
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title><?= esc($title ?? '관리자') ?> - 종량제 시스템</title>
|
||||
<title><?= esc($title ?? '관리자') ?> - 쓰레기봉투 물류시스템</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet"/>
|
||||
<script>
|
||||
@@ -48,7 +46,7 @@ tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: { sans: ['"Malgun Gothic"', '"Noto Sans KR"', 'sans-serif'] },
|
||||
colors: {
|
||||
colors: {
|
||||
'system-header': '#ffffff',
|
||||
'title-bar': '#2c3e50',
|
||||
'control-panel': '#f8f9fa',
|
||||
@@ -83,73 +81,61 @@ body { overflow: hidden; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-100 text-gray-800 flex flex-col h-screen font-sans antialiased select-none">
|
||||
<header class="relative bg-white border-b border-gray-300 h-12 flex items-center justify-between px-4 shrink-0 z-50">
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<?= view('components/header_brand', ['href' => base_url('admin')]) ?>
|
||||
</div>
|
||||
<nav class="hidden md:flex gap-5 text-sm font-medium text-gray-600">
|
||||
<?php if (! empty($adminNavTree)): ?>
|
||||
<?php foreach ($adminNavTree as $navItem): ?>
|
||||
<?php
|
||||
$hasChildren = ! empty($navItem->children);
|
||||
$parentLink = menu_link_preferred_href_path($navItem->mm_link ?? null, $currentPath);
|
||||
$activeChild = $hasChildren ? menu_active_child_for_parent($navItem, $currentPath, []) : null;
|
||||
$parentIsCurrent = $adminNavItemIsCurrent($navItem->mm_link ?? null);
|
||||
if (! $parentIsCurrent && $activeChild !== null) {
|
||||
$parentIsCurrent = true;
|
||||
}
|
||||
?>
|
||||
<div class="relative group">
|
||||
<a class="<?= $parentIsCurrent ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>"
|
||||
href="<?= $parentLink !== '' ? base_url($parentLink) : '#' ?>">
|
||||
<?= esc($navItem->mm_name) ?>
|
||||
</a>
|
||||
<?php if ($hasChildren): ?>
|
||||
<?php /* 사이트 메뉴와 동일: 호버 끊김 방지 pt-1, 키보드 포커스, z-index */ ?>
|
||||
<div class="absolute left-0 top-full z-50 hidden pt-1 min-w-[12rem] group-hover:block group-focus-within:block">
|
||||
<div class="bg-white border border-gray-200 rounded shadow-lg py-1">
|
||||
<header class="bg-white border-b border-gray-300 h-12 flex items-center justify-between px-4 shrink-0 z-20">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-6 h-6 flex items-center justify-center shrink-0">
|
||||
<svg class="h-5 w-5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="16" height="16" fill="#2563eb"/><rect x="2" y="2" width="7" height="7" fill="white"/><rect x="5" y="5" width="9" height="9" fill="white"/>
|
||||
</svg>
|
||||
</div>
|
||||
<a href="<?= base_url('admin') ?>" class="text-base font-semibold text-gray-800 tracking-tight hover:text-blue-600">쓰레기봉투 물류시스템</a>
|
||||
</div>
|
||||
<nav class="hidden md:flex gap-5 text-sm font-medium text-gray-600">
|
||||
<?php if (! empty($adminNavTree)): ?>
|
||||
<?php foreach ($adminNavTree as $navItem): ?>
|
||||
<?php $hasChildren = ! empty($navItem->children); ?>
|
||||
<div class="relative group">
|
||||
<a class="<?= $isActive($navItem->mm_link) ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>"
|
||||
href="<?= base_url($navItem->mm_link) ?>">
|
||||
<?= esc($navItem->mm_name) ?>
|
||||
</a>
|
||||
<?php if ($hasChildren): ?>
|
||||
<div class="absolute left-0 top-full hidden group-hover:block bg-white border border-gray-200 rounded shadow-lg min-w-[10rem] z-30">
|
||||
<?php foreach ($navItem->children as $child): ?>
|
||||
<?php
|
||||
$childLink = menu_link_preferred_href_path($child->mm_link ?? null, $currentPath);
|
||||
$childIsCurrent = $activeChild !== null
|
||||
&& (int) ($child->mm_idx ?? 0) === (int) ($activeChild->mm_idx ?? -1);
|
||||
?>
|
||||
<?php if ($childLink !== ''): ?>
|
||||
<a href="<?= base_url($childLink) ?>"
|
||||
class="block px-3 py-1.5 text-sm hover:bg-blue-50 whitespace-nowrap <?= $childIsCurrent ? 'text-blue-700 font-semibold bg-blue-50' : 'text-gray-700' ?>">
|
||||
<a href="<?= base_url($child->mm_link) ?>"
|
||||
class="block px-3 py-1.5 text-sm text-gray-700 hover:bg-blue-50 whitespace-nowrap">
|
||||
<?= esc($child->mm_name) ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span class="block px-3 py-1.5 text-sm text-gray-400 cursor-default whitespace-nowrap" title="메뉴 관리에서 링크를 설정해 주세요">
|
||||
<?= esc($child->mm_name) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<a class="<?= $isActive('') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin') ?>">대시보드</a>
|
||||
<a class="<?= $isActive('users') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/users') ?>">회원 관리</a>
|
||||
<a class="<?= $isActive('login-history') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/access/login-history') ?>">로그인 이력</a>
|
||||
<a class="<?= $isActive('approvals') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/access/approvals') ?>">승인 대기</a>
|
||||
<a class="<?= $isActive('roles') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/roles') ?>">역할</a>
|
||||
<a class="<?= $isActive('menus') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/menus') ?>">메뉴</a>
|
||||
<?php if ($isSuperAdmin): ?>
|
||||
<a class="<?= $isActive('select-local-government') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/select-local-government') ?>">지자체 전환</a>
|
||||
<a class="<?= $isActive('local-governments') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/local-governments') ?>">지자체</a>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<a class="<?= $isActive('') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin') ?>">대시보드</a>
|
||||
<a class="<?= $isActive('users') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/users') ?>">회원 관리</a>
|
||||
<a class="<?= $isActive('login-history') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/access/login-history') ?>">로그인 이력</a>
|
||||
<a class="<?= $isActive('approvals') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/access/approvals') ?>">승인 대기</a>
|
||||
<a class="<?= $isActive('roles') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/roles') ?>">역할</a>
|
||||
<a class="<?= $isActive('menus') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/menus') ?>">메뉴</a>
|
||||
<?php if ($isSuperAdmin): ?>
|
||||
<a class="<?= $isActive('select-local-government') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/select-local-government') ?>">지자체 전환</a>
|
||||
<a class="<?= $isActive('local-governments') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/local-governments') ?>">지자체</a>
|
||||
<?php endif; ?>
|
||||
<a class="<?= $isActive('designated-shops') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/designated-shops') ?>">지정판매소</a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<?php if ($effectiveLgName !== null): ?>
|
||||
<span class="text-sm text-gray-600" title="현재 작업 지자체"><?= esc($effectiveLgName) ?></span>
|
||||
<?php endif; ?>
|
||||
<a class="<?= $isActive('designated-shops') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('bag/designated-shops') ?>">지정판매소</a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
<?= view('components/header_user_tools', [
|
||||
'userNav' => $userNav,
|
||||
'effectiveLgName' => $effectiveLgName,
|
||||
'showSiteLink' => true,
|
||||
'showAdminLink' => false,
|
||||
]) ?>
|
||||
<a href="<?= base_url('/') ?>" class="text-gray-500 hover:text-blue-600 text-sm">사이트</a>
|
||||
<a href="<?= base_url('logout') ?>" class="text-gray-500 hover:text-red-600 transition-colors inline-block p-1 rounded hover:bg-red-50" title="로그아웃">
|
||||
<svg class="h-5 w-5 inline" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M6 18L18 6M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/></svg> 종료
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="bg-title-bar text-white px-4 py-2 text-sm font-medium shrink-0">
|
||||
<?= esc($title ?? '관리자') ?>
|
||||
@@ -169,7 +155,7 @@ body { overflow: hidden; }
|
||||
<?= $content ?>
|
||||
</main>
|
||||
<footer class="bg-gray-200 border-t border-gray-300 px-4 py-1 text-xs text-gray-600 flex items-center justify-between shrink-0">
|
||||
<span>종량제 시스템 관리자</span>
|
||||
<span>쓰레기봉투 물류시스템 관리자</span>
|
||||
<span><?= date('Y.m.d (D) g:i:sA') ?></span>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">담당자 등록</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('managers/store') ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/managers/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -11,18 +11,28 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">담당자 구분 <span class="text-red-500">*</span></label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_category" required>
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">소속</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_dept_code">
|
||||
<option value="">선택</option>
|
||||
<?php foreach (($categories ?? []) as $key => $label): ?>
|
||||
<option value="<?= esc($key) ?>" <?= old('mg_category') === $key ? 'selected' : '' ?>>
|
||||
<?= esc($label) ?>
|
||||
<?php foreach ($deptCodes as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= old('mg_dept_code') === $cd->cd_code ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="mg_position_code" value="<?= esc(old('mg_position_code', '')) ?>"/>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">직위</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_position_code">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($positionCodes as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= old('mg_position_code') === $cd->cd_code ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">전화</label>
|
||||
@@ -41,7 +51,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= mgmt_url('managers') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/managers') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">담당자 수정</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('managers/update/' . (int) $item->mg_idx) ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/managers/update/' . (int) $item->mg_idx) ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -11,18 +11,28 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">담당자 구분 <span class="text-red-500">*</span></label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_category" required>
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">소속</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_dept_code">
|
||||
<option value="">선택</option>
|
||||
<?php foreach (($categories ?? []) as $key => $label): ?>
|
||||
<option value="<?= esc($key) ?>" <?= old('mg_category', (string) ($item->mg_dept_code ?? '')) === $key ? 'selected' : '' ?>>
|
||||
<?= esc($label) ?>
|
||||
<?php foreach ($deptCodes as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= old('mg_dept_code', $item->mg_dept_code) === $cd->cd_code ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="mg_position_code" value="<?= esc(old('mg_position_code', $item->mg_position_code)) ?>"/>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">직위</label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="mg_position_code">
|
||||
<option value="">선택</option>
|
||||
<?php foreach ($positionCodes as $cd): ?>
|
||||
<option value="<?= esc($cd->cd_code) ?>" <?= old('mg_position_code', $item->mg_position_code) === $cd->cd_code ? 'selected' : '' ?>>
|
||||
<?= esc($cd->cd_name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">전화</label>
|
||||
@@ -49,7 +59,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button>
|
||||
<a href="<?= mgmt_url('managers') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/managers') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -4,21 +4,33 @@
|
||||
<span class="text-sm font-bold text-gray-700">담당자 관리</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('managers/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">담당자 등록</a>
|
||||
<a href="<?= base_url('admin/managers/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">담당자 등록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200 no-print">
|
||||
<form method="GET" action="<?= mgmt_url('managers') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">카테고리</label>
|
||||
<select name="category" class="border border-gray-300 rounded px-2 py-1 text-sm w-44">
|
||||
<option value="">전 체</option>
|
||||
<?php foreach (($categories ?? []) as $key => $label): ?>
|
||||
<option value="<?= esc($key) ?>" <?= ($category ?? '') === $key ? 'selected' : '' ?>><?= esc($label) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('managers') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
<section class="no-print border border-gray-300 bg-white p-2 mt-2">
|
||||
<form method="POST" action="<?= mgmt_url('managers') ?>" class="flex flex-wrap items-end gap-2" autocomplete="off">
|
||||
<?= csrf_field() ?>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">조회 기준</label>
|
||||
<select name="search_field" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[10rem]">
|
||||
<option value="mg_idx" <?= ($search_field ?? 'mg_name') === 'mg_idx' ? 'selected' : '' ?>>번호</option>
|
||||
<option value="mg_name" <?= ($search_field ?? 'mg_name') === 'mg_name' ? 'selected' : '' ?>>담당자명</option>
|
||||
<option value="mg_dept_code" <?= ($search_field ?? 'mg_name') === 'mg_dept_code' ? 'selected' : '' ?>>소속</option>
|
||||
<option value="mg_position_code" <?= ($search_field ?? 'mg_name') === 'mg_position_code' ? 'selected' : '' ?>>직위</option>
|
||||
<option value="mg_tel" <?= ($search_field ?? 'mg_name') === 'mg_tel' ? 'selected' : '' ?>>전화</option>
|
||||
<option value="mg_phone" <?= ($search_field ?? 'mg_name') === 'mg_phone' ? 'selected' : '' ?>>휴대전화</option>
|
||||
<option value="mg_email" <?= ($search_field ?? 'mg_name') === 'mg_email' ? 'selected' : '' ?>>이메일</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">조회어</label>
|
||||
<input type="text" name="search_query" value="<?= esc($search_query ?? '') ?>" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[18rem]" placeholder="조회어 입력">
|
||||
</div>
|
||||
<div class="flex items-center gap-2 pb-0.5">
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('managers') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
@@ -27,7 +39,8 @@
|
||||
<tr>
|
||||
<th class="w-16">번호</th>
|
||||
<th>담당자명</th>
|
||||
<th>카테고리</th>
|
||||
<th>소속</th>
|
||||
<th>직위</th>
|
||||
<th>전화</th>
|
||||
<th>휴대전화</th>
|
||||
<th>이메일</th>
|
||||
@@ -40,20 +53,15 @@
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->mg_idx) ?></td>
|
||||
<td class="text-center"><?= esc($row->mg_name) ?></td>
|
||||
<td class="text-center">
|
||||
<?php
|
||||
$cat = (string) ($row->mg_dept_code ?? '');
|
||||
$catLabel = $categories[$cat] ?? $cat;
|
||||
echo esc($catLabel);
|
||||
?>
|
||||
</td>
|
||||
<td class="text-center"><?= esc($dept_code_map[(string) ($row->mg_dept_code ?? '')] ?? ($row->mg_dept_code ?? '')) ?></td>
|
||||
<td class="text-center"><?= esc($pos_code_map[(string) ($row->mg_position_code ?? '')] ?? ($row->mg_position_code ?? '')) ?></td>
|
||||
<td class="text-center"><?= esc($row->mg_tel) ?></td>
|
||||
<td class="text-center"><?= esc($row->mg_phone) ?></td>
|
||||
<td class="text-center"><?= esc($row->mg_email) ?></td>
|
||||
<td class="text-center"><?= (int) $row->mg_state === 1 ? '사용' : '미사용' ?></td>
|
||||
<td class="text-center">
|
||||
<a href="<?= mgmt_url('managers/edit/' . (int) $row->mg_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
|
||||
<form action="<?= mgmt_url('managers/delete/' . (int) $row->mg_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<a href="<?= base_url('admin/managers/edit/' . (int) $row->mg_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
|
||||
<form action="<?= base_url('admin/managers/delete/' . (int) $row->mg_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
|
||||
</form>
|
||||
@@ -61,7 +69,7 @@
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($list)): ?>
|
||||
<tr><td colspan="8" class="text-center text-gray-400 py-4">등록된 데이터가 없습니다.</td></tr>
|
||||
<tr><td colspan="9" class="text-center text-gray-400 py-4">등록된 데이터가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -4,29 +4,6 @@ $list = $list ?? [];
|
||||
$mtIdx = (int) ($mtIdx ?? 0);
|
||||
$mtCode = (string) ($mtCode ?? '');
|
||||
$levelNames = $levelNames ?? [];
|
||||
$debugMode = (bool) ($debug_mode ?? false);
|
||||
$debugInfo = is_array($debug_info ?? null) ? $debug_info : [];
|
||||
helper('admin');
|
||||
$adminMenusNavPath = current_nav_request_path();
|
||||
|
||||
/**
|
||||
* 메뉴 관리 목록용: 저장된 mm_link → 실제 href (외부 http(s) 또는 base_url).
|
||||
*/
|
||||
$adminMenuListResolveHref = static function (string $rawLink) use ($adminMenusNavPath): string {
|
||||
$rawLink = trim($rawLink);
|
||||
if ($rawLink === '') {
|
||||
return '';
|
||||
}
|
||||
if (preg_match('#^https?://#i', $rawLink)) {
|
||||
return $rawLink;
|
||||
}
|
||||
$pathSeg = menu_link_preferred_href_path($rawLink, $adminMenusNavPath);
|
||||
if ($pathSeg === '') {
|
||||
$pathSeg = normalize_menu_link_for_url($rawLink);
|
||||
}
|
||||
|
||||
return $pathSeg !== '' ? base_url($pathSeg) : '';
|
||||
};
|
||||
?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
@@ -46,19 +23,6 @@ $adminMenuListResolveHref = static function (string $rawLink) use ($adminMenusNa
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if ($debugMode): ?>
|
||||
<section class="mt-2 border border-amber-300 bg-amber-50 text-amber-900 rounded p-2 text-xs">
|
||||
<strong>[DEBUG]</strong>
|
||||
lg_idx=<?= esc((string) ($debugInfo['lg_idx'] ?? '')) ?>,
|
||||
requested_mt_idx=<?= esc((string) ($debugInfo['requested_mt_idx'] ?? '')) ?>,
|
||||
resolved_mt_idx=<?= esc((string) ($debugInfo['resolved_mt_idx'] ?? '')) ?>,
|
||||
effective_mt_idx=<?= esc((string) ($debugInfo['effective_mt_idx'] ?? '')) ?>,
|
||||
resolved_mt_code=<?= esc((string) ($debugInfo['resolved_mt_code'] ?? '')) ?>,
|
||||
list_count=<?= esc((string) ($debugInfo['list_count'] ?? '')) ?>,
|
||||
fallback_applied=<?= esc((string) ($debugInfo['fallback_applied'] ?? 'N')) ?>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="flex gap-4 mt-2 flex-wrap">
|
||||
<div class="border border-gray-300 bg-white rounded p-4 flex-1 min-w-0" style="min-width: 320px;">
|
||||
<h3 class="text-sm font-bold text-gray-700 mb-2">메뉴 목록</h3>
|
||||
@@ -84,10 +48,6 @@ $adminMenuListResolveHref = static function (string $rawLink) use ($adminMenusNa
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($list as $i => $row): ?>
|
||||
<?php
|
||||
$rawLink = trim((string) $row->mm_link);
|
||||
$listItemHref = $rawLink !== '' ? $adminMenuListResolveHref($rawLink) : '';
|
||||
?>
|
||||
<tr class="menu-row" data-mm-idx="<?= (int) $row->mm_idx ?>" data-mm-pidx="<?= (int) $row->mm_pidx ?>" data-mm-dep="<?= (int) $row->mm_dep ?>">
|
||||
<td class="text-center align-middle">
|
||||
<span class="menu-drag-handle cursor-move text-gray-400 select-none" title="드래그해서 순서를 변경하세요">↕</span>
|
||||
@@ -107,21 +67,9 @@ $adminMenuListResolveHref = static function (string $rawLink) use ($adminMenusNa
|
||||
└─
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php if ($listItemHref !== ''): ?>
|
||||
<a href="<?= esc($listItemHref) ?>" class="ml-1 text-gray-900 hover:text-blue-700 hover:underline font-medium" target="_blank" rel="noopener noreferrer"><?= esc($row->mm_name) ?></a>
|
||||
<?php else: ?>
|
||||
<span class="ml-1"><?= esc($row->mm_name) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-left pl-2 text-xs">
|
||||
<?php if ($rawLink === ''): ?>
|
||||
<span class="text-gray-400">—</span>
|
||||
<?php elseif ($listItemHref !== ''): ?>
|
||||
<a href="<?= esc($listItemHref) ?>" class="text-blue-600 hover:underline font-medium break-all" target="_blank" rel="noopener noreferrer"><?= esc($rawLink) ?></a>
|
||||
<?php else: ?>
|
||||
<span class="text-amber-700" title="URL로 해석되지 않는 링크입니다"><?= esc($rawLink) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-left pl-2 text-xs"><?= esc($row->mm_link) ?></td>
|
||||
<td class="text-left pl-2 text-xs">
|
||||
<?php
|
||||
if ((string) $row->mm_level === '') {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">포장 단위 등록</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('packaging-units/store') ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/packaging-units/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= mgmt_url('packaging-units') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/packaging-units') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">포장 단위 수정</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('packaging-units/update/' . (int) $item->pu_idx) ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/packaging-units/update/' . (int) $item->pu_idx) ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-32">적용시작일 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="pu_start_date" type="date" value="<?= esc(old('pu_start_date', date('Y-m-d'))) ?>" required/>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="pu_start_date" type="date" value="<?= esc(old('pu_start_date', $item->pu_start_date)) ?>" required/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button>
|
||||
<a href="<?= mgmt_url('packaging-units') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/packaging-units') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="<?= mgmt_url('packaging-units') ?>" class="text-blue-600 hover:underline text-sm">← 포장 단위 목록</a>
|
||||
<a href="<?= base_url('admin/packaging-units') ?>" class="text-blue-600 hover:underline text-sm">← 포장 단위 목록</a>
|
||||
<span class="text-gray-400">|</span>
|
||||
<span class="text-sm font-bold text-gray-700">변경 이력 — <?= esc($item->pu_bag_name) ?> (<?= esc($item->pu_bag_code) ?>)</span>
|
||||
</div>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<?php
|
||||
$fieldLabelMap = [
|
||||
'pu_box_per_pack' => '박스당 팩 수',
|
||||
'pu_pack_per_sheet' => '팩당 낱장 수',
|
||||
'pu_start_date' => '적용시작일',
|
||||
'pu_end_date' => '적용종료일',
|
||||
'pu_state' => '상태',
|
||||
];
|
||||
?>
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-16">번호</th>
|
||||
<th>변경 내용</th>
|
||||
<th>변경 필드</th>
|
||||
<th>이전 값</th>
|
||||
<th>변경 값</th>
|
||||
<th>변경일시</th>
|
||||
@@ -29,7 +20,7 @@
|
||||
<?php foreach ($list as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->puh_idx) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($fieldLabelMap[(string) $row->puh_field] ?? $row->puh_field) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->puh_field) ?></td>
|
||||
<td><?= esc($row->puh_old_value) ?></td>
|
||||
<td><?= esc($row->puh_new_value) ?></td>
|
||||
<td class="text-center"><?= esc($row->puh_changed_at) ?></td>
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
<span class="text-sm font-bold text-gray-700">포장 단위 관리</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<a href="<?= mgmt_url('packaging-units/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">포장 단위 등록</a>
|
||||
<a href="<?= base_url('admin/packaging-units/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">포장 단위 등록</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('packaging-units') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="GET" action="<?= base_url('admin/packaging-units') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">적용시작일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
<a href="<?= mgmt_url('packaging-units') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
<a href="<?= base_url('admin/packaging-units') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||
</form>
|
||||
</section>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
@@ -35,17 +35,9 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php
|
||||
$startNo = 1;
|
||||
if (isset($pager) && method_exists($pager, 'getCurrentPage') && method_exists($pager, 'getPerPage')) {
|
||||
$currentPage = (int) $pager->getCurrentPage();
|
||||
$perPage = (int) $pager->getPerPage();
|
||||
$startNo = (($currentPage > 0 ? $currentPage : 1) - 1) * ($perPage > 0 ? $perPage : 20) + 1;
|
||||
}
|
||||
?>
|
||||
<?php foreach (($list ?? []) as $idx => $row): ?>
|
||||
<?php foreach ($list as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= (int) $startNo + (int) $idx ?></td>
|
||||
<td class="text-center"><?= esc($row->pu_idx) ?></td>
|
||||
<td class="text-center font-mono"><?= esc($row->pu_bag_code) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->pu_bag_name) ?></td>
|
||||
<td><?= number_format((int) $row->pu_box_per_pack) ?></td>
|
||||
@@ -55,9 +47,9 @@
|
||||
<td class="text-center"><?= esc($row->pu_end_date ?? '현재') ?></td>
|
||||
<td class="text-center"><?= (int) $row->pu_state === 1 ? '사용' : '미사용' ?></td>
|
||||
<td class="text-center">
|
||||
<a href="<?= mgmt_url('packaging-units/history/' . (int) $row->pu_idx) ?>" class="text-green-600 hover:underline text-sm mr-1">이력</a>
|
||||
<a href="<?= mgmt_url('packaging-units/edit/' . (int) $row->pu_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
|
||||
<form action="<?= mgmt_url('packaging-units/delete/' . (int) $row->pu_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<a href="<?= base_url('admin/packaging-units/history/' . (int) $row->pu_idx) ?>" class="text-green-600 hover:underline text-sm mr-1">이력</a>
|
||||
<a href="<?= base_url('admin/packaging-units/edit/' . (int) $row->pu_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
|
||||
<form action="<?= base_url('admin/packaging-units/delete/' . (int) $row->pu_idx) ?>" method="POST" class="inline" onsubmit="return confirm('삭제하시겠습니까?');">
|
||||
<?= csrf_field() ?>
|
||||
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
|
||||
</form>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<span class="text-sm font-bold text-gray-700">비밀번호 변경</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-md">
|
||||
<form action="<?= mgmt_url('password-change') ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/password-change') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
|
||||
@@ -2,27 +2,37 @@
|
||||
<span class="text-sm font-bold text-gray-700">대행소 등록</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('sales-agencies/store') ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/sales-agencies/store') ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">대행소 구분 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_kind" type="text" value="<?= esc(old('sa_kind')) ?>" required maxlength="50"/>
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">대행소명 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_name" type="text" value="<?= esc(old('sa_name')) ?>" required/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">대행소 코드 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_code" type="text" value="<?= esc(old('sa_code')) ?>" required maxlength="50"/>
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">사업자번호</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_biz_no" type="text" value="<?= esc(old('sa_biz_no')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">대행소 명 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_name" type="text" value="<?= esc(old('sa_name')) ?>" required maxlength="100"/>
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">대표자</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_rep_name" type="text" value="<?= esc(old('sa_rep_name')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">전화</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_tel" type="text" value="<?= esc(old('sa_tel')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">주소</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96" name="sa_addr" type="text" value="<?= esc(old('sa_addr')) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||
<a href="<?= mgmt_url('sales-agencies') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/sales-agencies') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,27 +2,45 @@
|
||||
<span class="text-sm font-bold text-gray-700">대행소 수정</span>
|
||||
</section>
|
||||
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||
<form action="<?= mgmt_url('sales-agencies/update/' . (int) $item->sa_idx) ?>" method="POST" class="space-y-4">
|
||||
<form action="<?= base_url('admin/sales-agencies/update/' . (int) $item->sa_idx) ?>" method="POST" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">대행소 구분 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_kind" type="text" value="<?= esc(old('sa_kind', $item->sa_kind ?? '')) ?>" required maxlength="50"/>
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">대행소명 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_name" type="text" value="<?= esc(old('sa_name', $item->sa_name)) ?>" required/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">대행소 코드 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_code" type="text" value="<?= esc(old('sa_code', $item->sa_code ?? '')) ?>" required maxlength="50"/>
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">사업자번호</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_biz_no" type="text" value="<?= esc(old('sa_biz_no', $item->sa_biz_no)) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">대행소 명 <span class="text-red-500">*</span></label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_name" type="text" value="<?= esc(old('sa_name', $item->sa_name)) ?>" required maxlength="100"/>
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">대표자</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_rep_name" type="text" value="<?= esc(old('sa_rep_name', $item->sa_rep_name)) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">전화</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="sa_tel" type="text" value="<?= esc(old('sa_tel', $item->sa_tel)) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">주소</label>
|
||||
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96" name="sa_addr" type="text" value="<?= esc(old('sa_addr', $item->sa_addr)) ?>"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label class="block text-sm font-bold text-gray-700 w-28">상태 <span class="text-red-500">*</span></label>
|
||||
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32" name="sa_state" required>
|
||||
<option value="1" <?= (int) old('sa_state', $item->sa_state) === 1 ? 'selected' : '' ?>>사용</option>
|
||||
<option value="0" <?= (int) old('sa_state', $item->sa_state) === 0 ? 'selected' : '' ?>>미사용</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">수정</button>
|
||||
<a href="<?= mgmt_url('sales-agencies') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
<a href="<?= base_url('admin/sales-agencies') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -11,20 +11,17 @@
|
||||
<section class="no-print border border-gray-300 bg-white p-2 mt-2">
|
||||
<form method="GET" action="<?= mgmt_url('sales-agencies') ?>" class="flex flex-wrap items-end gap-2">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">번호</label>
|
||||
<input type="text" name="sa_idx" value="<?= esc($sa_idx ?? '') ?>" class="border border-gray-300 rounded px-2 py-1.5 text-sm w-24" placeholder="예: 12">
|
||||
<label class="text-xs text-gray-500">조회 기준</label>
|
||||
<select name="search_field" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[9rem]">
|
||||
<option value="sa_idx" <?= ($search_field ?? 'sa_name') === 'sa_idx' ? 'selected' : '' ?>>번호</option>
|
||||
<option value="sa_kind" <?= ($search_field ?? 'sa_name') === 'sa_kind' ? 'selected' : '' ?>>대행소 구분</option>
|
||||
<option value="sa_code" <?= ($search_field ?? 'sa_name') === 'sa_code' ? 'selected' : '' ?>>대행소 코드</option>
|
||||
<option value="sa_name" <?= ($search_field ?? 'sa_name') === 'sa_name' ? 'selected' : '' ?>>대행소 명</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">대행소 구분</label>
|
||||
<input type="text" name="sa_kind" value="<?= esc($sa_kind ?? '') ?>" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[10rem]" placeholder="구분 입력">
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">대행소 코드</label>
|
||||
<input type="text" name="sa_code" value="<?= esc($sa_code ?? '') ?>" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[10rem]" placeholder="코드 입력">
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label class="text-xs text-gray-500">대행소 명</label>
|
||||
<input type="text" name="sa_name" value="<?= esc($sa_name ?? '') ?>" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[14rem]" placeholder="대행소 명 입력">
|
||||
<label class="text-xs text-gray-500">조회어</label>
|
||||
<input type="text" name="search_query" value="<?= esc($search_query ?? '') ?>" class="border border-gray-300 rounded px-2 py-1.5 text-sm min-w-[16rem]" placeholder="조회어 입력">
|
||||
</div>
|
||||
<div class="flex items-center gap-2 pb-0.5">
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm">조회</button>
|
||||
@@ -44,17 +41,9 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$startNo = 1;
|
||||
if (isset($pager) && method_exists($pager, 'getCurrentPage') && method_exists($pager, 'getPerPage')) {
|
||||
$currentPage = (int) $pager->getCurrentPage();
|
||||
$perPage = (int) $pager->getPerPage();
|
||||
$startNo = (($currentPage > 0 ? $currentPage : 1) - 1) * ($perPage > 0 ? $perPage : 20) + 1;
|
||||
}
|
||||
?>
|
||||
<?php foreach (($list ?? []) as $idx => $row): ?>
|
||||
<?php foreach ($list as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= (int) $startNo + (int) $idx ?></td>
|
||||
<td class="text-center"><?= esc($row->sa_idx) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->sa_kind ?? '') ?></td>
|
||||
<td class="text-center"><?= esc($row->sa_code ?? '') ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->sa_name) ?></td>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('reports/daily-summary') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="GET" action="<?= base_url('admin/reports/daily-summary') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">조회일</label>
|
||||
<input type="date" name="date" value="<?= esc($date ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('reports/lot-flow') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="GET" action="<?= base_url('admin/reports/lot-flow') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">LOT 번호</label>
|
||||
<input type="text" name="lot_no" value="<?= esc($lotNo ?? '') ?>" placeholder="LOT-YYYYMMDD-XXXXXX" class="border border-gray-300 rounded px-2 py-1 text-sm w-64"/>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<!-- 등록 폼 -->
|
||||
<section class="p-2 bg-white border-b border-gray-200 no-print">
|
||||
<form method="POST" action="<?= mgmt_url('reports/misc-flow') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="POST" action="<?= base_url('admin/reports/misc-flow') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<?= csrf_field() ?>
|
||||
<label class="text-sm text-gray-600">구분</label>
|
||||
<select name="bmf_type" class="border border-gray-300 rounded px-2 py-1 text-sm" required>
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
<!-- 조회 필터 -->
|
||||
<section class="p-2 bg-white border-b border-gray-200 no-print">
|
||||
<form method="GET" action="<?= mgmt_url('reports/misc-flow') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="GET" action="<?= base_url('admin/reports/misc-flow') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">시작일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('reports/period-sales') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="GET" action="<?= base_url('admin/reports/period-sales') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">시작일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('reports/returns') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="GET" action="<?= base_url('admin/reports/returns') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">시작일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('reports/sales-ledger') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="GET" action="<?= base_url('admin/reports/sales-ledger') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">시작일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('reports/shop-sales') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<form method="GET" action="<?= base_url('admin/reports/shop-sales') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">시작일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user