# ManualEditFlag — Design **Fase:** 0 **Implementação:** tabela satélite `WorkOrderFieldLocks` --- ## 1. Problema Sync/Lambda faz upsert overwrite em campos que dispatchers editam no SHOC. Precisamos saber, por campo, se houve edição manual SHOC para rejeitar ingest conflitante. --- ## 2. Solução escolhida: tabela satélite ```text WorkOrderFieldLocks Id (PK) WorkOrderId (FK) FieldName (string, max 64) LockedAt (UTC) LockedByUserId (nullable — null = system/bootstrap) UNIQUE (WorkOrderId, FieldName) ``` **Alternativa descartada:** bitmask — difícil de auditar e estender. --- ## 3. Comportamento | Evento | Ação | |--------|------| | SHOC edita campo X pela 1ª vez | INSERT lock (WorkOrderId, FieldName) | | Sync tenta atualizar campo X com lock | Skip update; audit `SyncRejected` | | Sync atualiza campo Y sem lock | Apply merge normal | | WO novo (Sync create) | Sem locks; full ingest | | Blazor edita campo legado | **Não** cria lock Fase 0 (gap conhecido; Blazor congelado) | --- ## 4. Campos elegíveis a lock (SHOC writers) Definidos em `SyncFieldMergePolicy.ShocOwnedFields`: - LifecycleStatus, AssignTo, ScheduledDate, ScheduledEnd, TargetWeek, ScheduleWeekOnly - DueDate, Description, WorkerOrderTitle, SiteCode, Building, LocationId - WorkOrderType, DocStatus, Trade, Problem, Priority, InternalWONumber --- ## 5. Audit em rejeição ```json { "eventType": "Sync", "action": "SyncRejected", "fieldName": "DueDate", "oldValue": "2026-06-01", "newValue": "2026-06-15", "actorType": "Sync" } ``` --- ## 6. API interna ```csharp interface IWorkOrderFieldLockService { Task LockFieldAsync(int workOrderId, string fieldName, string? userId); Task IsLockedAsync(int workOrderId, string fieldName); } ``` Implementação: `WorkOrderFieldLockService` em `SeaHaven.Services`. Lock criado automaticamente por `IWorkOrderAuditService` em eventos `Manual` + `FieldChanged` / `StatusChanged`. --- ## 7. Critérios de aceite - [ ] Tabela criada na migration Phase0 - [ ] Sync consulta locks antes de overwrite - [ ] SyncRejected aparece no audit log