# KBX Grid Productivity Contract v25

## 1. 목적

KBX에서 Grid는 단순 조회 Table이 아니라 조회·선택·편집·대량입력·검증·일괄처리의 업무 작업면이다. v25는 이 역할을 `KbxDataGrid`의 명시적 계약으로 고정한다.

## 2. Fast Entry Contract

`KbxGridEditingPolicy`가 다음을 선언한다.

```text
allowRowAdd
allowRowDuplicate
fillDown
paste
errorNavigation
```

업무 화면은 AG Grid 내부 API를 직접 제어하지 않고 필요한 기능만 정책으로 선언한다.

### Row Add

Grid는 `rowAddRequested`를 발생시키고 실제 Row identity 생성은 Vertical Slice가 수행한다. 신규 행의 `clientId`와 DB identity를 혼동하지 않는다.

### Row Duplicate

Grid는 현재 선택된 Row를 `rowDuplicateRequested`로 전달한다. 업무 Slice가 복제 가능한 필드와 새 `clientId`를 책임진다.

### Fill Down

현재 Focus Cell 값을 선택된 다른 Row의 동일 editable field에 적용한다. Readonly/Computed Cell은 변경하지 않는다. 변경은 기존 `cellChanged` 흐름으로 통과하므로 계산·Dirty·Validation 계약이 우회되지 않는다.

### Paste

Excel 다중 셀 붙여넣기는 Cell type에 따라 먼저 정규화한다.

```text
"1,200"   -> 1200
"20260808" -> "2026-08-08"
"Y"       -> true
```

변환 불가 값은 임의로 `null` 처리하지 않는다. 원값을 남기고 `KbxGridPasteIssue`를 제공하여 Inline Validation에서 사용자가 원인과 위치를 확인하게 한다.

## 3. Error Navigation

대량 Paste 후 Modal을 반복해서 띄우지 않는다.

```text
7개 셀 오류
[첫/다음 오류]
```

오류는 안정적인 `rowKey + field`로 탐색한다. 배열 index 기반 오류 위치는 행 정렬/삭제에 취약하므로 사용하지 않는다.

## 4. Context Menu와 Drill-down

Context Menu는 보조 진입점이다. 핵심 Row Add/복제/Fill Down/Error Navigation은 Toolbar에서도 접근 가능하므로 우클릭 전용 업무는 만들지 않는다.

`link` 또는 `drilldown` Column은 `drillDownRequested`를 발생시킨다. Drawer/Page 선택은 업무 화면이 결정한다.

## 5. 대량 선택 Contract

명시 선택:

```json
{ "mode": "ids", "ids": ["..."] }
```

검색결과 전체선택:

```json
{
  "mode": "filter",
  "filter": { "...": "last applied search" },
  "excludedIds": ["..."]
}
```

10만 건 조회에서 브라우저가 10만 ID를 저장하지 않는다.

## 6. Applied Search Snapshot

사용자가 결과를 조회한 이후 검색폼을 수정했지만 아직 F3 조회를 하지 않은 경우가 있다.

Bulk Command는 **현재 입력 중인 Search Form**이 아니라 **마지막 실제 조회에 적용한 `appliedSearch`**를 사용한다.

```text
Search Form edit
   │
   ├─ F3 실행 안 함 -> 기존 결과/기존 appliedSearch 유지
   │
   └─ F3 실행       -> appliedSearch 교체 + selection reset
```

이 경계가 없으면 사용자가 화면에서 보고 있는 주문과 서버가 처리하는 주문 집합이 달라질 수 있다.

## 7. Server-side Bulk

OMS 출고지시는 서버에서 두 Selection Mode를 처리한다.

```text
ids
  -> unnest explicit ids

filter
  -> Search Projection에서 target resolve
  -> excludedIds 제외
```

이후 PostgreSQL CTE에서 다음을 set-based로 수행한다.

```text
target
  ↓
accepted (상태전이 가능한 주문만 UPDATE)
  ├─ audit_insert
  └─ outbox_insert
```

Application은 대상 ID 전체를 다시 메모리에 적재하지 않고 `Requested / Accepted / Rejected`만 반환한다.

## 8. Search/Bulk Filter Parity

OMS 주문관리의 `exceptionOnly`는 v24 화면에는 있었지만 Backend Search Request/SQL에는 없었다. v25에서 다음 세 지점의 의미를 일치시켰다.

```text
UI Search Filter
  = SearchOrdersRequest
  = Search Projection WHERE
  = Bulk Filter Target WHERE
```

같은 이름의 필터가 조회와 Bulk에서 다른 의미를 가지지 않게 한다.

## 9. 테스트

- `tests/unit/kbx-grid-productivity.contract.spec.ts`
  - Paste normalization
  - invalid value preservation
  - all-filtered envelope
- `tests/e2e/kbx-grid-fast-entry.spec.ts`
  - Catalog에서 explicit Fast Entry action과 all-filtered action 재현
- canonical scenarios
  - `scenario.oms.order-register.fast-entry`
  - `scenario.oms.order-list.all-filtered-ship`
- static gate
  - `validate-grid-productivity.mjs`
