65c672b4ad
- 新组件 DsSwitch(原型 atoms .switch 对照,L1 已登记:38×22 pill,on=primary) - 商品抽屉状态行:三态徽章后跟挂售 switch(在售⇄库存 即时生效,已售只读, readonly 禁用),桌面 drawer / 移动 sheet 同组件 - 列表状态列/卡片徽章三态化(在售绿/库存蓝/已售灰,icons BADGE_ICON 补两词); 状态筛选改「全部/在售/库存/已售」走服务端;预警缺货退出状态维度由数量染色承担 - KPI 第四卡「缺货预警」→「已售」(sold_count,点击筛选已售) - 原型同步:atoms/index 登记 switch 与 b-库存/b-已售、inventory 两屏三态数据、 抽屉状态行 switch;12 道 ds 闸 + L1 同源闸全过 - golden 重录 ×9(三态徽章 + 已售卡形态) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
163 lines
5.4 KiB
Dart
163 lines
5.4 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;
|
||
|
||
/// 库存三态(2026-07-07):stock=库存 / on_sale=在售(公开酒单) / sold=已售(卖光留痕)
|
||
final String status;
|
||
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.status = 'stock',
|
||
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(),
|
||
status: json['status'] as String? ?? 'stock',
|
||
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 soldCount; // 已售留痕行数(status=sold)
|
||
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.soldCount = 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,
|
||
soldCount: (j['sold_count'] as num?)?.toInt() ?? 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?,
|
||
);
|
||
}
|