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:
@@ -0,0 +1,25 @@
|
||||
-- GOO-196: Data retention policy & purge jobs (Decree 13 compliance)
|
||||
-- Adds the RetentionRunLog table so every purge / anonymization pass is auditable.
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RetentionRunStatus" AS ENUM ('RUNNING', 'SUCCESS', 'PARTIAL', 'FAILED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RetentionRunLog" (
|
||||
"id" TEXT NOT NULL,
|
||||
"job" TEXT NOT NULL,
|
||||
"phase" INTEGER,
|
||||
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"finishedAt" TIMESTAMP(3),
|
||||
"rowsAffected" INTEGER NOT NULL DEFAULT 0,
|
||||
"status" "RetentionRunStatus" NOT NULL DEFAULT 'RUNNING',
|
||||
"errorMessage" TEXT,
|
||||
"batchSize" INTEGER,
|
||||
"dryRun" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "RetentionRunLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RetentionRunLog_job_startedAt_idx" ON "RetentionRunLog"("job", "startedAt");
|
||||
CREATE INDEX "RetentionRunLog_startedAt_idx" ON "RetentionRunLog"("startedAt" DESC);
|
||||
@@ -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