Commit Graph
26 Commits
Author SHA1 Message Date
toanhoang 69437cf38f Add AssetItem.assetTag, status-aware bulk import, nullable purchaseDate
CI / test (push) Failing after 8s
Prep work for importing a real company ITAM export (613 real assets):

- AssetItem.assetTag (nullable, unique) -- company physical tag, was only
  on InventoryScan (move feature) before, now on the actual asset record.
- AssetItem.purchaseDate is now nullable (was required) -- some legacy
  rows genuinely have no known purchase date. Manual /assets create form
  still requires it in the HTML input; this only relaxes the DB/import
  path for historical data.
- importAssetItems() accepts a status column now (free-text "In use"/
  "In stock"/etc mapped via IMPORT_STATUS_MAP), unlike manual creation
  which always defaults in_stock -- bulk-imported inventory is usually
  already deployed, not freshly purchased, so forcing in_stock would
  misrepresent reality.

Hand-written migrations (asset_tag, purchase_date nullable), no Vitest
coverage -- same deviation as the rest of this branch, see CLAUDE.md.
tsc/lint clean.

Actually imported 613/613 real AssetItem rows from the export with 0
errors after this (not committed here -- thats data, not code; see
CLAUDE.md for the seeding/import process and whats deliberately out of
scope this round: Cost, PO number, employee Assignment).
2026-08-23 21:33:48 -04:00
toanhoang aa7f5515db Add AssetItem CSV import/export
CI / test (push) Failing after 8s
/assets gets Export CSV (GET /api/assets/export) and Import CSV
(POST /api/assets/import) buttons. Export emits human-readable
manufacturer/model columns instead of raw modelId; import resolves those
back to an AssetModel (case-insensitive match, must already exist -- this
does not auto-create models) so export -> edit in Excel -> re-import is a
real round trip.

Per-row import errors (unmatched model, missing required field, bad date)
are collected and returned alongside the created count rather than
aborting the whole file on the first bad row -- a typo in row 200 should
not cost rows 1-199. warrantyEndDate still auto-fills from the models
warrantyMonths when the CSV omits it, same as manual creation.

Extracted src/lib/csv.ts (splitCsvLine/splitCsvLines/normalizeHeader/
csvField) out of expected-assets.ts so both CSV parsers share one
implementation instead of duplicating it.

Manual smoke test only (tsx script: export shape, valid row + unmatched-
model row + missing-date row all handled correctly), no Vitest coverage,
not through the ba->pm->feature-implementer->test-runner->code-reviewer
pipeline -- same deviation as the rest of this branch, see CLAUDE.md.
tsc/lint clean.
2026-08-23 07:41:52 -04:00
toanhoang d9b360c1df Auto-fill AssetItem.warrantyEndDate from AssetModel.warrantyMonths
CI / test (push) Failing after 8s
AssetModel gains an optional warrantyMonths (standard vendor warranty
length). createAssetItem() now defaults warrantyEndDate to purchaseDate +
warrantyMonths (month-end clamped, e.g. Dec 31 + 2mo -> Feb 28, not Mar 3)
whenever the caller omits warrantyEndDate -- still overridable per item
for batches with extended/different warranty terms. /asset-models UI gets
an input for the new field; /assets needs no change since it already
omits warrantyEndDate from the payload when left blank.

Hand-written migration + manual smoke test (tsx script, not Vitest),
same deviation as the other 2026-08-2x work in this branch -- see
CLAUDE.md. tsc/lint clean.
2026-08-21 09:10:55 -04:00
toanhoang 8ac95c974b Add ExpectedAsset CSV import + bot search/pick for pre-known inventory
CI / test (push) Failing after 8s
Lets the operator upload a CSV of already-known assets (asset tag, model,
serial, old area/assignee) before an office-move walk, via a new
session-authed /inventory-scans/import page. The Telegram bot gains a
"tim trong list" search step at the asset-tag prompt: picking a match
pre-fills tag/serial and offers one-tap "giu nguyen" buttons for the
old area/assignee, then marks that row matched once the scan is created.

Follows the same-day decision to turn off local OCR (qwen2.5vl via Ollama)
for this feature -- see CLAUDE.md and the ITAM/Ollama vault notes for the
accuracy/speed numbers behind that call.

Deviates from repo convention same as the InventoryScan work above: no
Vitest coverage (manual smoke test only, via simulated webhook calls),
hand-written migration, not through the ba->pm->feature-implementer->
test-runner->code-reviewer pipeline. tsc/lint are clean.
2026-08-21 08:59:18 -04:00
toanhoangandClaude Sonnet 5 b4e2f6c83e docs: note decision to merge inventory-capture bot into this app
CI / test (push) Failing after 9s
Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-20 09:47:33 -04:00
toanhoangandClaude Sonnet 5 9773dd8a39 docs: document InventoryScan feature + note deviation from agent pipeline
Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-19 22:07:39 -04:00
toanhoang e30489297c docs: note Assignment transfer UI landing in CLAUDE.md 2026-08-04 10:44:05 -04:00
toanhoang 713dfad364 docs: note MaintenanceRecord UI landing in CLAUDE.md 2026-08-04 10:24:27 -04:00
toanhoang 44dd33dd3b docs: note MaintenanceRecord landing + code-review gate PASS in CLAUDE.md
Records the entity build (4 commits), the HIGH race-condition finding
and MEDIUM targetStatus finding from code-reviewer, and their fixes.
2026-08-04 06:36:05 -04:00
toanhoang 7e1351bde2 fix: isolate Prisma Client per test file, re-enable fileParallelism
Root-cause fix for cross-file DB pollution in the vitest suite, which
surfaced as intermittent FK-violation failures once the suite hit 12
test files. tests/setup.ts previously mutated the global
process.env.DATABASE_URL and relied on src/lib/prisma.ts's singleton
picking it up, which is racy across files sharing a worker process
(process.env isn't reset between files even though Vitest resets each
file's module registry). fileParallelism: false only masked this.

Now each test file's own tests/setup.ts creates its own throwaway
Postgres database and vi.mock()s "@/lib/prisma" with a dedicated
PrismaClient constructed via the explicit datasourceUrl option, so no
mutable state is shared between files and fileParallelism can go back
to its default (true).

Verified with 4 consecutive full `npm test` runs (327/327 passing each
time) and no leftover throwaway databases after each run.
2026-08-04 06:04:23 -04:00
toanhoang e1ad9da72d docs: consolidate CLAUDE.md status/backlog, add Assignment review findings
Condensed the "Trạng thái hiện tại"/"Việc còn dở" sections — most of the
detailed per-entity narrative was either resolved, superseded by a later
change, or derivable from git log, so it's trimmed to what isn't:
decisions, gotchas, and still-open risks. Added the UI (worktree-ui-basic
merge), the Assignment transfer fix, and the two dormant findings
(XOR defense-in-depth, Assignment FK onDelete) from the code-reviewer gate.
2026-08-03 20:27:52 -04:00
toanhoangandClaude Sonnet 5 f325abf22c docs: resolve Assignment open questions, unblock Lane C
Stakeholder decided: createAssignment() blocks if AssetItem.status
isn't in_stock, and update routes use PATCH /api/<entity>/[id]
(applies to both Assignment and PurchaseOrder).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-02 19:22:43 -04:00
toanhoangandClaude Sonnet 5 4a0d00afd8 docs: spec Vendor/PurchaseOrder/Assignment + attribute defs, sequence tasks
BA formalizes the Vendor, PurchaseOrder (absorbs Receipt), Assignment,
and AssetCategoryAttributeDef designs decided in stakeholder brainstorm
into docs/asset-license-management.md. PM sequences the resulting work
into dependency-ordered, commit-mapped tasks across 3 independent lanes
in CLAUDE.md, surfacing that AssetCategory/AssetModel/ConsumableItem
were spec'd earlier but never actually built, and flagging two open
questions (Assignment precondition on AssetItem.status; REST shape for
update operations) that need stakeholder input before Lane C starts.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-02 13:12:27 -04:00
toanhoang 3a18414d03 docs: add ba<->pm feedback loop + commit-mapping to workflow
pm now asks ba back when a spec gap could change business behavior,
instead of guessing; task lists must also map cleanly onto the
existing per-commit convention (model+migration/lib/API/tests).
2026-08-02 11:24:35 -04:00
toanhoangandClaude Sonnet 5 296a8c5d35 docs: update CLAUDE.md + docs for License entity
Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-02 08:55:51 -04:00
toanhoangandClaude Sonnet 5 9e08c3f994 feat: add ba + pm agents to multi-agent dev workflow
Stakeholder should only need to state a business goal/priority, not
pick between implementation options or break work into tasks
themselves. `ba` turns a business ask into a concrete spec grounded in
docs/asset-license-management.md, `pm` breaks that spec into an
ordered, dependency-aware sequence of feature-implementer-sized tasks.
Neither writes code or resolves business-rule ambiguity — that stays
with the stakeholder.

Also fixes code-reviewer.md/feature-implementer.md pointing at the old
../processes/asset-license-management.md path — the doc moved into
docs/ inside this repo a while ago.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-02 08:36:21 -04:00
toanhoangandClaude Sonnet 5 87e2cca019 feat: add Dockerfile + app service for production deploy
Adds a production Docker image (node:20-alpine) and an `app` service in
docker-compose.yml so the Next.js app can run as a long-lived container
instead of just build/test in ephemeral containers. Requires `apk add
openssl` before `npm ci`, otherwise Prisma silently picks the wrong query
engine binary on Alpine and only fails at runtime. Also wires
AUTH_TRUST_HOST=true, required by Auth.js when running behind the NPM
reverse proxy.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-02 08:25:25 -04:00
toanhoang 539e511022 docs: update CLAUDE.md + docs for AssetItem entity 2026-07-31 16:10:57 +07:00
toanhoang 3f6497e2df docs: fix stale Employee entry, note fileParallelism scalability tradeoff 2026-07-31 15:25:34 +07:00
toanhoang 41381147f8 docs: update CLAUDE.md for Employee entity + Request FK retrofit
- Trang thai hien tai: Employee entity done (model, lib, API route,
  tests), Request.employee_id now a real FK to Employee - documents
  the intentional behavior change (employee_id must now be a valid
  UUID referencing a real Employee, not free text; non-existent
  references get a 400, not a 500). Also documents the vitest
  fileParallelism fix and the resulting 31/31 test count.
- Viec con do: remove Employee from the remaining-entities list.
2026-07-31 15:16:12 +07:00
toanhoang 3645e907bc docs: update CLAUDE.md with route-gating status
Moves the middleware/route-gating item out of Viec con do now that
it is done, and documents the auth.config.ts split, matcher choice,
the / home page being protected, and the vi.mock test approach in
Trang thai hien tai.
2026-07-31 12:39:06 +07:00
toanhoang c059e2f50f docs: record Auth verification results and follow-up gaps 2026-07-31 12:12:23 +07:00
toanhoang d72b73b2f5 docs: add git commit convention (Conventional Commits, split by layer) 2026-07-31 10:55:46 +07:00
toanhoang f2ca214b55 Move business-context doc from workspace processes/ into repo
- docs/asset-license-management.md (moved from ../processes/, fixed
  relative links to stay accurate from the new location)
- CLAUDE.md now points at docs/asset-license-management.md
- removed stale no-git-repo-yet note, added git init/push status
2026-07-30 18:32:01 +07:00
toanhoang 19b4c96eaa CLAUDE.md: make Môi trường section host-agnostic (Windows no-Node vs Linux VM native Node) 2026-07-30 18:27:09 +07:00
toanhoang afdbdbdcfb Initial commit: Next.js scaffold, Request entity, Prisma+Zod, Tailwind v4+shadcn
- Next.js 14 (TS, App Router) monolith
- Request entity (Draft state) via Prisma + Postgres, validated with Zod
- ESLint configured
- Tailwind v4 + shadcn/ui (new-york-v4 style)
- Vitest test suite (6 tests, runs against throwaway Postgres DB per file)
- Multi-agent dev workflow docs in CLAUDE.md and .claude/agents/
2026-07-30 18:14:36 +07:00