Add AssetItem.assetTag, status-aware bulk import, nullable purchaseDate
CI / test (push) Failing after 8s
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).
This commit is contained in:
+23
-4
@@ -26,8 +26,9 @@ import {
|
||||
interface AssetItemRecord {
|
||||
id: string;
|
||||
serial_number: string;
|
||||
asset_tag: string | null;
|
||||
model_id: string;
|
||||
purchase_date: string;
|
||||
purchase_date: string | null;
|
||||
warranty_end_date: string | null;
|
||||
status: "in_stock" | "assigned" | "repair" | "retired";
|
||||
purchase_order_id: string | null;
|
||||
@@ -70,6 +71,7 @@ export default function AssetsPage() {
|
||||
const { data: purchaseOrders } = useApiList<PurchaseOrderRecord>("/api/purchase-orders");
|
||||
|
||||
const [serialNumber, setSerialNumber] = useState("");
|
||||
const [assetTag, setAssetTag] = useState("");
|
||||
const [modelId, setModelId] = useState("");
|
||||
const [purchaseDate, setPurchaseDate] = useState("");
|
||||
const [warrantyEndDate, setWarrantyEndDate] = useState("");
|
||||
@@ -126,6 +128,7 @@ export default function AssetsPage() {
|
||||
setSubmitting(true);
|
||||
setSubmitError(null);
|
||||
|
||||
const hasAssetTag = assetTag.trim() !== "";
|
||||
const hasWarrantyEndDate = warrantyEndDate.trim() !== "";
|
||||
const hasPurchaseOrderId = purchaseOrderId !== NO_PURCHASE_ORDER_VALUE;
|
||||
|
||||
@@ -140,6 +143,7 @@ export default function AssetsPage() {
|
||||
// Omit optional keys entirely when left blank, rather than
|
||||
// sending null — matches the create-form pattern used on other
|
||||
// pages (e.g. purchase-orders page's receivedAt/receivedById).
|
||||
...(hasAssetTag ? { assetTag } : {}),
|
||||
...(hasWarrantyEndDate ? { warrantyEndDate } : {}),
|
||||
...(hasPurchaseOrderId ? { purchaseOrderId } : {}),
|
||||
}),
|
||||
@@ -165,6 +169,7 @@ export default function AssetsPage() {
|
||||
}
|
||||
|
||||
setSerialNumber("");
|
||||
setAssetTag("");
|
||||
setModelId("");
|
||||
setPurchaseDate("");
|
||||
setWarrantyEndDate("");
|
||||
@@ -192,6 +197,7 @@ export default function AssetsPage() {
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>Serial Number</TableHead>
|
||||
<TableHead>Asset Tag</TableHead>
|
||||
<TableHead>Model ID</TableHead>
|
||||
<TableHead>Purchase Date</TableHead>
|
||||
<TableHead>Warranty End Date</TableHead>
|
||||
@@ -205,8 +211,9 @@ export default function AssetsPage() {
|
||||
<TableRow key={assetItem.id}>
|
||||
<TableCell>{assetItem.id}</TableCell>
|
||||
<TableCell>{assetItem.serial_number}</TableCell>
|
||||
<TableCell>{assetItem.asset_tag ?? "-"}</TableCell>
|
||||
<TableCell>{assetItem.model_id}</TableCell>
|
||||
<TableCell>{assetItem.purchase_date}</TableCell>
|
||||
<TableCell>{assetItem.purchase_date ?? "-"}</TableCell>
|
||||
<TableCell>{assetItem.warranty_end_date ?? "-"}</TableCell>
|
||||
<TableCell>{assetItem.status}</TableCell>
|
||||
<TableCell>{assetItem.purchase_order_id ?? "-"}</TableCell>
|
||||
@@ -226,8 +233,11 @@ export default function AssetsPage() {
|
||||
<a href="/asset-models" className="underline">
|
||||
/asset-models
|
||||
</a>
|
||||
), <code>purchase date</code> (bắt buộc), <code>warranty end date</code>,{" "}
|
||||
<code>purchase order id</code> (tuỳ chọn -- bỏ trống thì tự tính từ warranty của model).
|
||||
), <code>purchase date</code> (bắt buộc -- cũng nhận <code>order received</code>).
|
||||
Tuỳ chọn: <code>asset tag</code>, <code>warranty end date</code> (bỏ trống thì tự tính
|
||||
từ warranty của model), <code>status</code>/<code>state</code> (nhận{" "}
|
||||
<code>In use</code> → assigned, <code>In stock</code> → in_stock, bỏ trống thì mặc định
|
||||
in_stock), <code>purchase order id</code>.
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<a href="/api/assets/export">
|
||||
@@ -275,6 +285,15 @@ export default function AssetsPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="assetTag">Asset Tag (optional)</Label>
|
||||
<Input
|
||||
id="assetTag"
|
||||
value={assetTag}
|
||||
onChange={(event) => setAssetTag(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="modelId">Model</Label>
|
||||
<Select value={modelId} onValueChange={setModelId} required>
|
||||
|
||||
Reference in New Issue
Block a user