6238b86dcb
- 登录/注册(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
155 lines
5.0 KiB
Dart
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?,
|
|
);
|
|
}
|