Files
sam-docs/plans/mng-numbering-rule-management-plan.md
권혁성 0e9559fcd8 docs: 개발 계획 문서 5건 추가
- db-trigger-audit-system-plan.md
- intermediate-inspection-report-plan.md
- mng-numbering-rule-management-plan.md
- quote-order-sync-improvement-plan.md
- tenant-numbering-system-plan.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:02:47 +09:00

17 KiB

MNG 채번 규칙 관리 UI 계획

작성일: 2026-02-07 목적: MNG 관리자 패널에서 테넌트별 채번 규칙(견적번호, 수주로트번호 등)을 CRUD 관리하는 UI 구현 기준 문서: docs/plans/tenant-numbering-system-plan.md (API 채번 시스템) 상태: 대기


1. 개요

1.1 배경

  • API에 채번 규칙 시스템(numbering_rules, numbering_sequences 테이블)이 이미 구현됨
  • 현재는 Seeder로만 규칙 등록 가능 → MNG에서 관리 UI가 필요
  • 테넌트별로 견적, 수주, 원자재수입검사 등 문서유형별 채번 패턴을 설정/수정/삭제할 수 있어야 함

1.2 기준 원칙

- MNG 독립 모델 사용 (API 테이블 참조, 마이그레이션 생성 금지)
- MNG 기존 패턴 준수: Controller(Blade) + Api Controller(HTMX/JSON) + Service + FormRequest
- HTMX + Alpine.js로 SPA 유사 UX 제공
- JSON 패턴 편집을 위한 동적 폼 (세그먼트 추가/삭제/정렬)

1.3 변경 승인 정책

분류 예시 승인
즉시 가능 MNG 모델/서비스/컨트롤러/뷰 생성 불필요
컨펌 필요 routes/web.php 수정, 사이드바 메뉴 추가 필수
금지 mng/database/migrations/ 파일 생성, API 테이블 구조 변경 별도 협의

2. 기술 스택 & 패턴

2.1 MNG 프로젝트 스택

항목 기술
Backend Laravel 12, PHP 8.4+
Template Blade (Plain Laravel, React/Vue 없음)
CSS Tailwind CSS
비동기 HTMX 1.9 (페이지 새로고침 없이 테이블/폼 업데이트)
JS 프레임워크 Alpine.js (동적 폼, 탭, 모달)
인증 Session 기반 (middleware: auth, tenant)
Multi-tenant session('selected_tenant_id') 기반

2.2 참고 패턴 (부서관리 CRUD)

mng/app/Http/Controllers/DepartmentController.php        ← Blade 렌더링만
mng/app/Http/Controllers/Api/Admin/DepartmentController.php ← CRUD 로직 (HTMX/JSON)
mng/app/Services/DepartmentService.php                    ← 비즈니스 로직
mng/app/Http/Requests/StoreDepartmentRequest.php          ← 검증
mng/resources/views/departments/index.blade.php           ← 목록 (HTMX 테이블)
mng/resources/views/departments/create.blade.php          ← 생성 폼
mng/resources/views/departments/edit.blade.php            ← 수정 폼
mng/resources/views/departments/partials/table.blade.php  ← HTMX 파셜

3. 대상 범위

3.1 Phase 1: 백엔드 (Model + Service + Controller + FormRequest + Route)

# 작업 항목 상태 비고
1.1 NumberingRule 모델 생성 API 테이블 참조, BelongsToTenant
1.2 NumberingRuleService 생성 CRUD + 미리보기
1.3 NumberingRuleController (페이지) 생성 Blade 렌더링
1.4 Api/Admin/NumberingRuleController 생성 HTMX/JSON CRUD
1.5 FormRequest 생성 JSON 패턴 검증
1.6 routes/web.php 라우트 추가 ⚠️ 컨펌 필요

3.2 Phase 2: 프론트엔드 (Blade Views)

# 작업 항목 상태 비고
2.1 index.blade.php (목록) HTMX 테이블, 필터
2.2 partials/table.blade.php HTMX 파셜
2.3 create.blade.php (생성) Alpine.js 동적 세그먼트 폼
2.4 edit.blade.php (수정) 기존 패턴 로드 + 편집
2.5 partials/segment-form.blade.php 세그먼트 편집 컴포넌트
2.6 partials/preview.blade.php 실시간 미리보기

3.3 Phase 3: 통합 & 검증

# 작업 항목 상태 비고
3.1 사이드바 메뉴 추가 ⚠️ 컨펌 필요
3.2 기능 테스트 CRUD + 미리보기
3.3 기존 시더 데이터 확인 tenant_id=287 규칙 편집 가능 확인

4. 상세 설계

4.1 파일 구조 (생성할 파일 목록)

mng/
├── app/
│   ├── Models/
│   │   └── NumberingRule.php                          ← NEW
│   ├── Services/
│   │   └── NumberingRuleService.php                   ← NEW
│   ├── Http/
│   │   ├── Controllers/
│   │   │   ├── NumberingRuleController.php            ← NEW (Blade)
│   │   │   └── Api/Admin/
│   │   │       └── NumberingRuleController.php        ← NEW (HTMX/JSON)
│   │   └── Requests/
│   │       ├── StoreNumberingRuleRequest.php          ← NEW
│   │       └── UpdateNumberingRuleRequest.php         ← NEW
├── resources/views/
│   └── numbering/
│       ├── index.blade.php                            ← NEW
│       ├── create.blade.php                           ← NEW
│       ├── edit.blade.php                             ← NEW
│       └── partials/
│           ├── table.blade.php                        ← NEW
│           ├── segment-form.blade.php                 ← NEW
│           └── preview.blade.php                      ← NEW
└── routes/
    └── web.php                                        ← MODIFY (라우트 추가)

4.2 DB 스키마 (이미 존재, 참조용)

-- numbering_rules (API에서 생성 완료)
id, tenant_id, document_type(50), rule_name(100),
pattern(JSON), reset_period(20), sequence_padding(INT),
is_active(BOOL), created_by, updated_by, timestamps
UNIQUE(tenant_id, document_type)

-- numbering_sequences (API에서 생성 완료, 조회 전용)
id, tenant_id, document_type(50), scope_key(100),
period_key(20), last_sequence(INT), timestamps
UNIQUE(tenant_id, document_type, scope_key, period_key)

4.3 JSON 패턴 세그먼트 타입

타입 필드 예시 설명
static value {"type":"static","value":"KD"} 고정 문자열
separator value {"type":"separator","value":"-"} 구분자
date format {"type":"date","format":"ymd"} PHP date format
param key, default {"type":"param","key":"pair_code","default":"SS"} 외부 파라미터
mapping key, map, default {"type":"mapping","key":"product_category","map":{"screen":"SC","steel":"ST"},"default":"SC"} 값 매핑
sequence (없음) {"type":"sequence"} 자동 순번

4.4 UI 설계

목록 페이지 (index.blade.php)

┌──────────────────────────────────────────────────────────┐
│ 채번 규칙 관리                               [+ 새 규칙] │
├──────────────────────────────────────────────────────────┤
│ [문서유형 ▼] [상태 ▼] [검색...]              [검색 버튼] │
├──────────────────────────────────────────────────────────┤
│ # │ 규칙명         │ 문서유형 │ 패턴 미리보기    │ 상태 │ 작업  │
│ 1 │ 5130 견적번호  │ quote   │ KD-PR-260207-01 │ 활성 │ 수정/삭제│
│ 2 │ 5130 수주 로트 │ order   │ KD-SS-260207-01 │ 활성 │ 수정/삭제│
└──────────────────────────────────────────────────────────┘

생성/수정 페이지 (create.blade.php / edit.blade.php)

┌──────────────────────────────────────────────────────────┐
│ 채번 규칙 생성                                ← 목록으로 │
├──────────────────────────────────────────────────────────┤
│ ┌─ 기본 정보 ──────────────────────────────────────────┐ │
│ │ 규칙명: [________]    문서유형: [quote ▼]            │ │
│ │ 리셋주기: [daily ▼]   시퀀스 자릿수: [2]             │ │
│ │ 활성: [✓]                                           │ │
│ └──────────────────────────────────────────────────────┘ │
│                                                          │
│ ┌─ 패턴 세그먼트 ─────────────────────────────────────┐ │
│ │ ① [static ▼] value: [KD]                    [✕] [↕] │ │
│ │ ② [separator ▼] value: [-]                  [✕] [↕] │ │
│ │ ③ [static ▼] value: [PR]                    [✕] [↕] │ │
│ │ ④ [separator ▼] value: [-]                  [✕] [↕] │ │
│ │ ⑤ [date ▼] format: [ymd]                    [✕] [↕] │ │
│ │ ⑥ [separator ▼] value: [-]                  [✕] [↕] │ │
│ │ ⑦ [sequence ▼]                               [✕] [↕] │ │
│ │                              [+ 세그먼트 추가]       │ │
│ └──────────────────────────────────────────────────────┘ │
│                                                          │
│ ┌─ 미리보기 ──────────────────────────────────────────┐ │
│ │ 생성 예시: KD-PR-260207-01                          │ │
│ │           KD-PR-260207-02                          │ │
│ └──────────────────────────────────────────────────────┘ │
│                                                          │
│                               [취소]  [저장]            │
└──────────────────────────────────────────────────────────┘

4.5 핵심 구현 코드 (Blueprint)

Model (mng/app/Models/NumberingRule.php)

<?php
namespace App\Models;

use App\Traits\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;

class NumberingRule extends Model
{
    use BelongsToTenant;

    protected $fillable = [
        'tenant_id', 'document_type', 'rule_name', 'pattern',
        'reset_period', 'sequence_padding', 'is_active',
        'created_by', 'updated_by',
    ];

    protected $casts = [
        'pattern' => 'array',
        'is_active' => 'boolean',
        'sequence_padding' => 'integer',
    ];

    // 문서유형 상수
    const DOC_QUOTE = 'quote';
    const DOC_ORDER = 'order';
    const DOC_SALE = 'sale';
    const DOC_WORK_ORDER = 'work_order';
    const DOC_MATERIAL_RECEIPT = 'material_receipt';

    public static function documentTypes(): array
    {
        return [
            self::DOC_QUOTE => '견적',
            self::DOC_ORDER => '수주',
            self::DOC_SALE => '매출',
            self::DOC_WORK_ORDER => '작업지시',
            self::DOC_MATERIAL_RECEIPT => '원자재수입검사',
        ];
    }

    public static function resetPeriods(): array
    {
        return [
            'daily' => '일별',
            'monthly' => '월별',
            'yearly' => '연별',
            'never' => '리셋안함',
        ];
    }

    /**
     * 패턴 미리보기 문자열 생성 (실제 시퀀스 없이)
     */
    public function getPreviewAttribute(): string
    {
        $result = '';
        foreach ($this->pattern as $segment) {
            $result .= match ($segment['type']) {
                'static' => $segment['value'],
                'separator' => $segment['value'],
                'date' => now()->format($segment['format']),
                'param' => $segment['default'] ?? '{' . $segment['key'] . '}',
                'mapping' => $segment['default'] ?? '{' . $segment['key'] . '}',
                'sequence' => str_pad('1', $this->sequence_padding, '0', STR_PAD_LEFT),
                default => '',
            };
        }
        return $result;
    }
}

Service (mng/app/Services/NumberingRuleService.php)

<?php
namespace App\Services;

use App\Models\NumberingRule;
use Illuminate\Pagination\LengthAwarePaginator;

class NumberingRuleService
{
    public function getRules(array $filters = [], int $perPage = 20): LengthAwarePaginator
    {
        $tenantId = session('selected_tenant_id');
        $query = NumberingRule::query();

        if ($tenantId) {
            $query->where('tenant_id', $tenantId);
        }
        if (!empty($filters['document_type'])) {
            $query->where('document_type', $filters['document_type']);
        }
        if (isset($filters['is_active']) && $filters['is_active'] !== '') {
            $query->where('is_active', (bool) $filters['is_active']);
        }
        if (!empty($filters['search'])) {
            $query->where('rule_name', 'like', "%{$filters['search']}%");
        }

        return $query->orderBy('document_type')->paginate($perPage);
    }

    public function getRule(int $id): ?NumberingRule { ... }
    public function createRule(array $data): NumberingRule { ... }
    public function updateRule(int $id, array $data): bool { ... }
    public function deleteRule(int $id): bool { ... }
}

세그먼트 동적 폼 (Alpine.js)

// create.blade.php 내 Alpine.js 컴포넌트
Alpine.data('patternEditor', () => ({
    segments: [],
    segmentTypes: [
        { value: 'static', label: '고정 문자열' },
        { value: 'separator', label: '구분자' },
        { value: 'date', label: '날짜' },
        { value: 'param', label: '외부 파라미터' },
        { value: 'mapping', label: '값 매핑' },
        { value: 'sequence', label: '자동 순번' },
    ],
    dateFormats: [
        { value: 'ymd', label: 'YYMMDD (260207)' },
        { value: 'Ymd', label: 'YYYYMMDD (20260207)' },
        { value: 'Ym', label: 'YYYYMM (202602)' },
        { value: 'Y', label: 'YYYY (2026)' },
    ],

    addSegment() {
        this.segments.push({ type: 'static', value: '' });
    },
    removeSegment(index) {
        this.segments.splice(index, 1);
    },
    moveSegment(from, to) { ... },

    get preview() {
        return this.segments.map(seg => {
            switch(seg.type) {
                case 'static': return seg.value || '?';
                case 'separator': return seg.value || '-';
                case 'date': return formatDate(seg.format || 'ymd');
                case 'param': return seg.default || `{${seg.key || '?'}}`;
                case 'mapping': return seg.default || `{${seg.key || '?'}}`;
                case 'sequence': return '01';
                default: return '';
            }
        }).join('');
    }
}));

5. 구현 순서 & 예상 작업량

Phase 작업 파일 수 예상
1 백엔드 (Model, Service, Controller, FormRequest, Route) 6개 생성 + 1개 수정
2 프론트엔드 (Blade Views 6개) 6개 생성 대 (Alpine.js 동적 폼)
3 통합 & 검증 (메뉴, 테스트) 1개 수정

핵심 난이도: Phase 2의 세그먼트 동적 폼 (Alpine.js로 JSON 배열 편집 + 실시간 미리보기)


6. 검증 결과

6.1 테스트 시나리오

입력 예상 결과 상태
목록 진입 tenant_id=287 규칙 2건 표시
견적 규칙 수정 → 저장 pattern JSON 업데이트, 미리보기 변경
새 규칙 생성 (material_receipt) 규칙 3건으로 증가
세그먼트 추가/삭제/순서변경 Alpine.js 동적 폼 동작
미리보기 버튼 실시간 번호 예시 표시
규칙 삭제 soft delete 또는 hard delete
중복 document_type 생성 시도 유니크 제약 에러 표시

6.2 성공 기준

기준 달성 비고
규칙 CRUD 정상 동작 생성/조회/수정/삭제
세그먼트 동적 편집 추가/삭제/순서변경
실시간 미리보기 패턴 변경 시 즉시 반영
기존 API 채번 로직과 호환 MNG에서 수정한 규칙이 API에서 정상 작동
MNG 기존 패턴 준수 HTMX + Alpine.js + Tailwind

7. 참고 문서

  • 채번 시스템 설계: docs/plans/tenant-numbering-system-plan.md
  • MNG CRUD 패턴: mng/app/Http/Controllers/DepartmentController.php + Api/Admin/DepartmentController.php
  • Alpine.js 동적 폼 참고: mng/resources/views/quote-formulas/edit.blade.php (탭 + 동적 아이템)
  • HTMX 테이블 참고: mng/resources/views/departments/partials/table.blade.php

이 문서는 /sc:plan 스킬로 생성되었습니다.