904bfbe67d
照原型 inventory.html 把库存屏从「3 tab」重排为单视图: - 去 3 tab(库存查询/预警/流水)→ 单视图;预警并入 KPI 缺货卡点击筛选;流水移商品详情 - 头部:库存管理 + 共 N 个 SKU·数据实时 + 列设置/导出(盘点/新增入库去掉,走专门菜单) - KPI 4 卡走全店汇总接口:SKU总数/库存货值(Σqty×进价)/在库数量/缺货预警(可点筛选) - 表格 12 列:加 库存(低库存染色)/成本价(进价)/单价(售价);操作列只「查看详情」;状态圆点 - 仓库/备注默认隐藏;工具栏 搜索+状态筛选+重置 - 后端 GET /inventory/summary 全店汇总(守 shop_id);前端 model/repository/provider 配套 - golden_harness:monospace 指向 Noto(消等宽数字黑块,全 golden 受益);库存屏存量 grey 清零 后端编译/库存测试通过;前端 analyze 0 error,flutter test 242 通过;代码端颜色闸 0 违规。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TSKEiHsvauyxYUW2itzUXX
146 lines
4.6 KiB
Dart
146 lines
4.6 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;
|
|
const InventorySummary({
|
|
this.skuCount = 0,
|
|
this.stockValue = 0,
|
|
this.inStockQty = 0,
|
|
this.shortageCount = 0,
|
|
this.warningCount = 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,
|
|
);
|
|
}
|
|
|
|
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?,
|
|
);
|
|
}
|