Files
sam-docs/rules/numbering-rules.md
김보곤 05cdc6836c docs: [numbering] 재공품 LOT 일련번호 제거
- 담당자 피드백: 같은 날 같은 조합은 동일 LOT 번호 사용
- sequence 세그먼트 제거, 포맷: {prod}{spec}{date}-{length}
- bending-lot-numbering-policy 일련번호 섹션 삭제
2026-03-18 20:00:16 +09:00

360 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 채번규칙 (Numbering Rules)
> **작성일**: 2026-02-27
> **상태**: 운영 중
> **최종 갱신**: 2026-03-17
---
## 1. 개요
### 1.1 목적
문서 번호(견적번호, 수주번호, 원자재 로트번호 등)를 규칙 기반으로 자동 생성하는 내부 서비스. 테넌트별로 채번 규칙을 설정하면, 해당 규칙에 따라 일련번호가 자동 생성된다. 규칙이 없는 경우 레거시 포맷으로 폴백.
### 1.2 핵심 원칙
- 유연한 패턴 기반 번호 생성 (6가지 세그먼트 유형)
- Atomic UPSERT로 동시성 안전 시퀀스 관리
- 기간별 자동 리셋 (일/월/년/무제한)
- 미리보기 기능 (DB 업데이트 없음)
- 규칙 미설정 시 레거시 로직 자동 폴백
---
## 2. 모델
| 모델 | 테이블 | 설명 |
|------|--------|------|
| `NumberingRule` | `numbering_rules` | 채번 규칙 정의 (document_type, pattern, reset_period) |
| — | `numbering_sequences` | 일련번호 카운터 (tenant + type + scope + period) |
**지원 문서 유형:**
| 코드 | 한글 | 사용처 |
|------|------|--------|
| `quote` | 견적 | `QuoteNumberService` |
| `order` | 수주 | `OrderService` |
| `sale` | 매출 | (예정) |
| `work_order` | 작업지시 | (예정) |
| `material_receipt` | 원자재수입검사 | `ReceivingService` |
| `stock_production` | 재고생산 | `BendingCodeService` (향후 연동) |
**리셋 주기:**
| 코드 | 설명 | period_key 예시 |
|------|------|----------------|
| `daily` | 매일 01부터 리셋 | `260317` |
| `monthly` | 매월 01부터 리셋 | `202603` |
| `yearly` | 매년 01부터 리셋 | `2026` |
| `never` | 리셋 없이 계속 증가 | `all` |
---
## 3. 패턴 구조
패턴은 JSON 배열로 세그먼트를 정의한다.
### 3.1 세그먼트 유형
| 세그먼트 | 설명 | 예시 |
|---------|------|------|
| `static` | 고정 문자열 | `{ "type": "static", "value": "KD" }` |
| `separator` | 구분자 | `{ "type": "separator", "value": "-" }` |
| `date` | 날짜 포맷 (PHP date) | `{ "type": "date", "format": "ymd" }``260317` |
| `sequence` | 일련번호 (자릿수는 rule 레벨) | `{ "type": "sequence" }` |
| `param` | 외부 파라미터 | `{ "type": "param", "key": "pair_code", "default": "SS" }` |
| `mapping` | 값 → 코드 매핑 | `{ "type": "mapping", "key": "category", "map": {"SCREEN": "SC"}, "default": "XX" }` |
### 3.2 date 포맷 예시
| format | 결과 | 설명 |
|--------|------|------|
| `ymd` | `260317` | 2자리 연+월+일 |
| `Ymd` | `20260317` | 4자리 연+월+일 |
| `ym` | `2603` | 2자리 연+월 |
| `Y` | `2026` | 4자리 연도 |
---
## 4. MNG 채번규칙 관리
### 4.1 접근 경로
```
MNG > 권한 관리 > 채번 규칙 관리
URL: admin.codebridge-x.com/numbering-rules
```
### 4.2 관리 화면 기능
| 기능 | 설명 |
|------|------|
| 목록 조회 | 문서유형, 상태 필터 + 규칙명 검색 |
| 새 규칙 | 패턴 세그먼트 편집기 + 실시간 미리보기 |
| 수정 | 기존 규칙의 패턴, 리셋주기, 자릿수 변경 |
| 삭제 | 규칙 삭제 (Hard Delete) |
### 4.3 신규 규칙 생성 절차
1. **+ 새 규칙** 버튼 클릭
2. **기본 정보** 입력:
- 규칙명: 관리용 이름 (예: `원자재 로트번호`)
- 문서유형: 드롭다운 선택 (테넌트당 유형별 1개만 가능)
- 리셋 주기: 일별 / 월별 / 연별 / 무제한
- 시퀀스 자릿수: 2 → `01,02` / 3 → `001,002` / 4 → `0001,0002`
3. **패턴 세그먼트** 구성:
- `+ 세그먼트 추가` 버튼으로 세그먼트를 순서대로 추가
- 각 세그먼트의 타입과 값 설정
4. **미리보기** 확인: 세그먼트 변경 시 실시간 미리보기 표시
5. **생성** 버튼으로 저장
> **주의**: 테넌트당 문서유형별 1개 규칙만 가능하다. 이미 등록된 유형은 선택 불가.
---
## 5. 신규 규칙 생성 템플릿
아래는 자주 사용하는 채번 패턴 템플릿이다. MNG에서 새 규칙 생성 시 참고한다.
### 5.1 원자재 로트번호 (YYMMDD-NN)
> 5130 레거시 방식 차용. 일별 시퀀스 리셋.
| 항목 | 값 |
|------|------|
| 규칙명 | `원자재 로트번호` |
| 문서유형 | `material_receipt` (원자재수입검사) |
| 리셋 주기 | `daily` (일별) |
| 시퀀스 자릿수 | `2` |
**패턴:**
```json
[
{ "type": "date", "format": "ymd" },
{ "type": "separator", "value": "-" },
{ "type": "sequence" }
]
```
**결과:** `260317-01`, `260317-02`, ... → 다음날 `260318-01`
### 5.2 견적번호 (KD-PR-YYMMDD-NN)
| 항목 | 값 |
|------|------|
| 규칙명 | `5130 견적번호` |
| 문서유형 | `quote` (견적) |
| 리셋 주기 | `daily` |
| 시퀀스 자릿수 | `2` |
**패턴:**
```json
[
{ "type": "static", "value": "KD" },
{ "type": "separator", "value": "-" },
{ "type": "static", "value": "PR" },
{ "type": "separator", "value": "-" },
{ "type": "date", "format": "ymd" },
{ "type": "separator", "value": "-" },
{ "type": "sequence" }
]
```
**결과:** `KD-PR-260317-01`
### 5.3 수주 로트번호 (KD-{pairCode}-YYMMDD-NN)
| 항목 | 값 |
|------|------|
| 규칙명 | `5130 수주 로트번호` |
| 문서유형 | `order` (수주) |
| 리셋 주기 | `daily` |
| 시퀀스 자릿수 | `2` |
**패턴:**
```json
[
{ "type": "static", "value": "KD" },
{ "type": "separator", "value": "-" },
{ "type": "param", "key": "pair_code", "default": "SS" },
{ "type": "separator", "value": "-" },
{ "type": "date", "format": "ymd" },
{ "type": "separator", "value": "-" },
{ "type": "sequence" }
]
```
**결과:** `KD-SS-260317-01`, `KD-TE-260317-01` (pairCode별 독립 시퀀스)
### 5.4 작업지시번호 (WO-YYYYMMDD-NNNN)
| 항목 | 값 |
|------|------|
| 규칙명 | `작업지시번호` |
| 문서유형 | `work_order` (작업지시) |
| 리셋 주기 | `daily` |
| 시퀀스 자릿수 | `4` |
**패턴:**
```json
[
{ "type": "static", "value": "WO" },
{ "type": "separator", "value": "-" },
{ "type": "date", "format": "Ymd" },
{ "type": "separator", "value": "-" },
{ "type": "sequence" }
]
```
**결과:** `WO-20260317-0001`
### 5.5 재공품 로트번호 ({prod}{spec}{date}-{length})
> 절곡품 LOT 번호. 제품+종류+날짜+길이 코드를 조합한 고정 번호.
> 날짜코드는 `BendingCodeService::generateDateCode()`가 생성한 4자리 압축 포맷.
> 같은 날 같은 조합은 동일 LOT 번호를 사용한다 (일련번호 없음).
| 항목 | 값 |
|------|------|
| 규칙명 | `5130 재공품 로트번호` |
| 문서유형 | `stock_production` (재고생산) |
| 리셋 주기 | — (시퀀스 미사용) |
**패턴:**
```json
[
{ "type": "param", "key": "prod_code", "default": "R" },
{ "type": "param", "key": "spec_code", "default": "M" },
{ "type": "param", "key": "date_code", "default": "6318" },
{ "type": "separator", "value": "-" },
{ "type": "param", "key": "length_code", "default": "30" }
]
```
**결과:** `GI6318-53` (연기차단재 화이바원단 W50×3000, 2026-03-18)
**호출 예시:**
```php
$numberingService->generate('stock_production', [
'prod_code' => 'G', // 연기차단재
'spec_code' => 'I', // 화이바원단(W50)
'date_code' => '6318', // BendingCodeService::generateDateCode()
'length_code' => '53', // W50×3000
]);
```
### 5.6 월별 리셋 예시 (INV-YYYYMM-NNN)
| 항목 | 값 |
|------|------|
| 규칙명 | `매출번호` |
| 문서유형 | `sale` (매출) |
| 리셋 주기 | `monthly` |
| 시퀀스 자릿수 | `3` |
**패턴:**
```json
[
{ "type": "static", "value": "INV" },
{ "type": "separator", "value": "-" },
{ "type": "date", "format": "Ym" },
{ "type": "separator", "value": "-" },
{ "type": "sequence" }
]
```
**결과:** `INV-202603-001` → 4월 리셋 → `INV-202604-001`
---
## 6. 서비스 연동
### 6.1 호출 구조
```
호출 서비스 → NumberingService.generate(documentType, params)
├─ 규칙 있음 → 패턴 기반 생성 (Atomic UPSERT)
└─ 규칙 없음 → null 반환 → 호출 서비스가 레거시 로직 사용
```
### 6.2 현재 연동 서비스
| 서비스 | 문서유형 | 레거시 폴백 |
|--------|---------|------------|
| `QuoteNumberService` | `quote` | `QT{YYYYMMDD}{NNNN}` |
| `OrderService` | `order` | `ORD{YYYYMMDD}{NNNN}` |
| `ReceivingService` | `material_receipt` | `YYMMDD-NN` (DB 시퀀스) |
### 6.3 연동 코드 패턴
새로운 서비스에서 채번규칙을 사용하려면 아래 패턴을 따른다:
```php
// 1. NumberingService 호출
$numberingService = app(NumberingService::class);
$numberingService->setContext($this->tenantId(), $this->apiUserId());
// 2. 규칙 기반 생성 시도
$number = $numberingService->generate('document_type', [
'param_key' => 'value', // param/mapping 세그먼트용
]);
// 3. 규칙 없으면 레거시 폴백
if ($number !== null) {
return $number;
}
return $this->generateLegacy();
```
---
## 7. 동시성 처리
```sql
INSERT INTO numbering_sequences
(tenant_id, document_type, scope_key, period_key, last_sequence, created_at, updated_at)
VALUES (?, ?, ?, ?, 1, NOW(), NOW())
ON DUPLICATE KEY UPDATE
last_sequence = last_sequence + 1, updated_at = NOW()
```
MySQL의 `ON DUPLICATE KEY UPDATE`로 Atomic 연산을 보장한다. 동시 요청에서도 시퀀스 충돌 없이 안전하게 번호를 생성한다.
**scope_key 분리**: `param`, `mapping` 세그먼트 값이 **누적 결합**되어 scope_key로 저장된다. 복수 param이 있으면 조합별 독립 시퀀스를 유지한다.
예: 수주 `KD-SS-260317-01``KD-TE-260317-01`은 scope_key가 `SS`, `TE`로 각각 독립 시퀀스.
---
## 8. 현재 등록 현황
### 프론트_테스트회사 (tenant_id: 287)
| # | 규칙명 | 문서유형 | 패턴 미리보기 | 리셋 | 자릿수 |
|---|--------|---------|-------------|------|:------:|
| 1 | 5130 견적번호 | quote | `KD-PR-260317-01` | 일별 | 2 |
| 2 | 5130 수주 로트번호 | order | `KD-SS-260317-01` | 일별 | 2 |
| 3 | 원자재 로트번호 | material_receipt | `260317-01` | 일별 | 2 |
| 4 | 5130 재공품 로트번호 | stock_production | `RM6318-30` | — | — |
> 신규 테넌트에 규칙이 없으면 각 서비스의 레거시 로직이 자동 적용된다.
---
## 관련 문서
- [DB 스키마 — 공통](../system/database/commons.md)
- [품목 정책](item-policy.md) — 품목 코드 체계
---
**최종 업데이트**: 2026-03-18