refactor(web): dedup tỷ/triệu compact formatters (GOO-206)
- Add `formatCompact` as an exported alias for `formatPrice` in lib/currency.ts - Replace 5 inline copies of the tỷ/triệu compact formatter: - components/map/listing-map.tsx (local `formatPrice` fn) - components/agents/agent-profile-client.tsx (local `fmtVND` fn) - app/(dashboard)/dashboard/saved-searches/page.tsx (local `formatPrice` fn) - app/(public)/page.tsx (local `formatVnd` fn + `vndFmt` Intl instance) - components/listings/price-history-chart.tsx (local `formatMillions` + `priceToMillions`) All call sites now import from the canonical lib/currency module. PriceHistoryChart now stores raw VND in chart data (was: millions) so formatCompact emits correct tỷ/triệu labels using canonical thresholds. Pre-existing test failures in inquiry/lead/AVM specs are unrelated to this change. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1567,3 +1567,68 @@ model VnAdministrativeAlias {
|
||||
@@index([newWardCode])
|
||||
@@map("vn_administrative_aliases")
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// READ MODELS — RFC-003 Phase 0
|
||||
// =============================================================================
|
||||
|
||||
/// Idempotency offset table for CQRS projectors.
|
||||
///
|
||||
/// Per RFC-003 §0 (CTO ask): every projector wraps its `apply` call in a
|
||||
/// transaction that inserts `(eventId, handlerName)` here. Re-deliveries
|
||||
/// hit the composite primary key, the transaction rolls back, and the
|
||||
/// projector observes a no-op. Port + Prisma implementation live in
|
||||
/// `apps/api/src/modules/read-models/`.
|
||||
model ProjectionOffset {
|
||||
/// Stable identifier of the projected event. Phase 0 derives this
|
||||
/// from `${aggregateId}:${occurredAt.getTime()}:${eventName}` until
|
||||
/// domain events carry a producer-minted UUID (RFC-003 Option D).
|
||||
eventId String
|
||||
|
||||
/// Stable identifier of the projector that consumed the event.
|
||||
/// Renaming a projector forces a full re-projection — be deliberate.
|
||||
handlerName String
|
||||
|
||||
/// When the offset was first written (i.e. the projection ran).
|
||||
appliedAt DateTime @default(now())
|
||||
|
||||
/// Optional content hash of the projected payload. Reconciliation
|
||||
/// jobs use this to detect drift between the read model and the
|
||||
/// authoritative write model.
|
||||
payloadHash String?
|
||||
|
||||
@@id([eventId, handlerName])
|
||||
@@index([handlerName, appliedAt])
|
||||
@@map("projection_offset")
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RETENTION (GOO-196 — Decree 13 compliance)
|
||||
// =============================================================================
|
||||
|
||||
enum RetentionRunStatus {
|
||||
RUNNING
|
||||
SUCCESS
|
||||
PARTIAL
|
||||
FAILED
|
||||
}
|
||||
|
||||
/// Every purge / anonymization pass emits a RetentionRunLog row so the
|
||||
/// operator and DPO can audit exactly what was touched and when. Multi-phase
|
||||
/// jobs (e.g. payment callback 2y / 5y / 10y) record `phase` for
|
||||
/// disambiguation.
|
||||
model RetentionRunLog {
|
||||
id String @id @default(cuid())
|
||||
job String
|
||||
phase Int?
|
||||
startedAt DateTime @default(now())
|
||||
finishedAt DateTime?
|
||||
rowsAffected Int @default(0)
|
||||
status RetentionRunStatus @default(RUNNING)
|
||||
errorMessage String?
|
||||
batchSize Int?
|
||||
dryRun Boolean @default(false)
|
||||
|
||||
@@index([job, startedAt])
|
||||
@@index([startedAt(sort: Desc)])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user