Files
jiu/client/lib/models/inventory.dart
T
wangjia 6238b86dcb feat(client): 登录/注册页照原型重建,ds 真相源组件族统一全部屏
- 登录/注册(login.html/register.html 1:1):两栏卡片+品牌渐变面板+主题小衣服
  pill(onSurface 变体)+记住我(记录并预填最近账号)+原型式 toast 校验;
  登录 fidelity 1.4–2.1% 三主题全绿;注册暂不入闸(少 门店编号/兑换券 字段,
  已记 CONTRACT,screens.mjs 留存根)
- ds 原子补齐:DsToast(.toast 单例)/DsCheck(.check/.agree)/DsButton lg 档/
  DsSelect 替换全部旧 DropdownButton/DsField label 在上/DsInput 后缀与密码形态
- 全屏统一:对话框按钮全 DsButton、盒式输入主题钉死(visualDensity.standard、
  h38、InputDecorationTheme 渗漏修复)、图标全 lucide、JetBrains Mono 三端同源、
  BrandMark 真相源 logo、只读模式写操作全量守卫(WriteGuard+DsToast)
- 出入库列表:版式对齐原型(卡片对齐+搜索框居中)、KPI 近30天滚动、结清后
  失效财务应收应付表;商品编辑抽屉介绍库改搜索下拉、图片双击全屏预览
- 删除旧 UI 死代码:DataTableCard/FormDialog/PageScaffold/SearchChip/
  SelectProductDialog/tabStateProvider
- golden/fidelity:stock-in/out 补注册(9%)、login 入册(8%)、goldens 全量重打;
  修复 pubspec flutter_web_plugins 非法声明(CI pub get 阻断)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-03 09:58:14 +08:00

155 lines
5.0 KiB
Dart

String? _parseDate(String? s) {
if (s == null || s.isEmpty) return null;
return s.length >= 10 ? s.substring(0, 10) : s;
}
class Inventory {
final int id;
final int? warehouseId;
final int? productId;
final int? stockInItemId;
final double quantity;
final String productCode;
final String productName;
final String series;
final String spec;
final String unit;
final String warehouseName;
final double? unitPrice; // 成本价(入库进价)
final double? salePrice; // 单价(product 售价)
final String? productionDate;
final String batchNo;
final String supplierName;
final String remark;
final int? minStock;
final String brand;
final String? createdAt;
const Inventory({
required this.id,
this.warehouseId,
this.productId,
this.stockInItemId,
required this.quantity,
this.productCode = '',
this.productName = '',
this.series = '',
this.spec = '',
this.unit = '',
this.warehouseName = '',
this.unitPrice,
this.salePrice,
this.productionDate,
this.batchNo = '',
this.supplierName = '',
this.remark = '',
this.minStock,
this.brand = '',
this.createdAt,
});
factory Inventory.fromJson(Map<String, dynamic> json) => Inventory(
id: (json['id'] as num).toInt(),
warehouseId: (json['warehouse_id'] as num?)?.toInt(),
productId: (json['product_id'] as num?)?.toInt(),
stockInItemId: (json['stock_in_item_id'] as num?)?.toInt(),
quantity: (json['quantity'] as num).toDouble(),
productCode: json['product_code'] as String? ?? '',
productName: json['product_name'] as String? ?? '',
series: json['series'] as String? ?? '',
spec: json['spec'] as String? ?? '',
unit: json['unit'] as String? ?? '',
warehouseName: json['warehouse_name'] as String? ?? '',
unitPrice: (json['unit_price'] as num?)?.toDouble(),
salePrice: (json['sale_price'] as num?)?.toDouble(),
productionDate: _parseDate(json['production_date'] as String?),
batchNo: json['batch_no'] as String? ?? '',
supplierName: json['supplier_name'] as String? ?? '',
remark: json['remark'] as String? ?? '',
minStock: (json['min_stock'] as num?)?.toInt(),
brand: json['brand'] as String? ?? '',
createdAt: json['created_at'] as String?,
);
}
/// 全店库存 KPI 汇总(GET /inventory/summary,不随分页)。
class InventorySummary {
final int skuCount;
final double stockValue;
final double inStockQty;
final int shortageCount;
final int warningCount;
// 月初快照(KPI 环比):由 inventory_logs 反推
final int lastMonthSku;
final double lastMonthQty;
final double lastMonthValue;
const InventorySummary({
this.skuCount = 0,
this.stockValue = 0,
this.inStockQty = 0,
this.shortageCount = 0,
this.warningCount = 0,
this.lastMonthSku = 0,
this.lastMonthQty = 0,
this.lastMonthValue = 0,
});
factory InventorySummary.fromJson(Map<String, dynamic> j) => InventorySummary(
skuCount: (j['sku_count'] as num?)?.toInt() ?? 0,
stockValue: (j['stock_value'] as num?)?.toDouble() ?? 0,
inStockQty: (j['in_stock_qty'] as num?)?.toDouble() ?? 0,
shortageCount: (j['shortage_count'] as num?)?.toInt() ?? 0,
warningCount: (j['warning_count'] as num?)?.toInt() ?? 0,
lastMonthSku: (j['last_month_sku'] as num?)?.toInt() ?? 0,
lastMonthQty: (j['last_month_qty'] as num?)?.toDouble() ?? 0,
lastMonthValue: (j['last_month_value'] as num?)?.toDouble() ?? 0,
);
}
class InventoryLog {
final int warehouseId;
final String? warehouseName;
final int productId;
final String? productName;
final String direction; // in | out
final double quantity;
final double? qtyBefore;
final double? qtyAfter;
final String? refType;
final int? refId;
final String? createdAt;
const InventoryLog({
required this.warehouseId,
this.warehouseName,
required this.productId,
this.productName,
required this.direction,
required this.quantity,
this.qtyBefore,
this.qtyAfter,
this.refType,
this.refId,
this.createdAt,
});
factory InventoryLog.fromJson(Map<String, dynamic> json) => InventoryLog(
warehouseId: (json['warehouse_id'] as num).toInt(),
warehouseName:
(json['warehouse'] as Map<String, dynamic>?)?['name'] as String?,
productId: (json['product_id'] as num).toInt(),
productName:
(json['product'] as Map<String, dynamic>?)?['name'] as String?,
direction: json['direction'] as String,
quantity: (json['quantity'] as num).toDouble(),
qtyBefore: json['qty_before'] != null
? (json['qty_before'] as num).toDouble()
: null,
qtyAfter: json['qty_after'] != null
? (json['qty_after'] as num).toDouble()
: null,
refType: json['ref_type'] as String?,
refId: json['ref_id'] != null ? (json['ref_id'] as num).toInt() : null,
createdAt: json['created_at'] as String?,
);
}