All files / src/api/inventory normalizers.ts

98.34% Statements 119/121
80.95% Branches 17/21
100% Functions 1/1
98.34% Lines 119/121

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 1221x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 5x 5x 5x 3x 2x 6x 6x 3x 3x 3x 1x     6x 6x 6x 2x 2x 1x 6x 6x 6x 2x 6x 6x 6x 6x 6x 6x 2x 1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x  
/**
 * @file normalizers.ts
 * @module api/inventory/normalizers
 *
 * @summary
 * Data normalization utilities for inventory API responses.
 * Converts tolerant backend responses into typed frontend models.
 *
 * @enterprise
 * - Tolerant parsing: handles missing/unknown fields gracefully
 * - Type narrowing via guards before unsafe operations
 * - No throwing: returns null for invalid data instead of errors
 * - Defensive field mapping with fallback chains
 */
 
import type { InventoryRow } from './types';
import {
  isRecord,
  pickString,
  pickNumber,
  pickNumberFromList,
  pickStringFromList,
} from './utils';
 
/**
 * Normalize raw backend response into typed InventoryRow.
 * Returns null if required fields (id) are missing.
 * Uses defensive field picking with fallback chains for flexibility.
 *
 * @param raw - Unknown backend response data
 * @returns Normalized InventoryRow or null if validation fails
 *
 * @enterprise
 * - Accepts multiple field name variations from backend
 * - Gracefully handles missing optional fields
 * - Never throws; returns null for invalid data
 */
export function normalizeInventoryRow(raw: unknown): InventoryRow | null {
  if (!isRecord(raw)) return null;
 
  const id =
    pickString(raw, 'id') ??
    pickString(raw, 'itemId') ??
    pickString(raw, 'item_id');
 
  if (!id) return null;
 
  const name =
    pickString(raw, 'name') ??
    pickString(raw, 'itemName') ??
    pickString(raw, 'title') ??
    '—';
 
  const code =
    pickString(raw, 'code') ??
    pickString(raw, 'sku') ??
    pickString(raw, 'itemCode') ??
    null;
 
  const supplierIdRaw =
    pickString(raw, 'supplierId') ??
    pickString(raw, 'supplier_id');
  const supplierIdNum = pickNumber(raw, 'supplierId');
  const supplierId: string | number | null =
    supplierIdRaw ?? (typeof supplierIdNum === 'number' ? supplierIdNum : null);
 
  const supplierName =
    pickString(raw, 'supplierName') ??
    pickString(raw, 'supplier') ??
    null;
 
  const onHand = pickNumberFromList(raw, [
    'quantity',
    'onHand',
    'availableQuantity',
    'stockQuantity',
    'stockQty',
    'qty',
    'currentQuantity',
    'currentQty',
    'quantityOnHand',
    'onHandQuantity',
    'stock',
  ]) ?? 0;
 
  const minQty =
    pickNumberFromList(raw, [
      'minimumQuantity',
      'minQty',
      'min_quantity',
      'minimum',
      'reorderLevel',
    ]) ?? null;
 
  // Backend list payload only has createdAt; surface it as updatedAt for the grid.
  const updatedAt = pickStringFromList(raw, [
    'updatedAt',
    'updated_at',
    'lastUpdate',
    'lastModified',
    'lastModifiedDate',
    'modifiedAt',
    'modified_at',
    'createdAt',
    'created_at',
    'createdDate',
    'created_date',
    'created',
  ]) ?? null;
 
  return {
    id: String(id),
    name,
    code,
    supplierId,
    supplierName,
    onHand,
    minQty,
    updatedAt,
  };
}