class StockInItem { final int? id; final int? orderId; final int productId; final double quantity; final double costPrice; // 进价(单瓶) final double costAmount; // 总进价 = quantity × costPrice final double returnedQuantity; // 0=未退;>=quantity 表示整行已退单 final String? batchNo; final String? productionDate; /// 该明细是否已退单(整行已退)。 bool get isReturned => returnedQuantity >= quantity && quantity > 0; // Denormalized for display final String? productName; final String? productCode; final String? productSeries; final String? productSpec; final String? productUnit; final String? productOrigin; final String? productShelfLife; final String? productStorage; const StockInItem({ this.id, this.orderId, required this.productId, required this.quantity, required this.costPrice, required this.costAmount, this.returnedQuantity = 0, this.batchNo, this.productionDate, this.productName, this.productCode, this.productSeries, this.productSpec, this.productUnit, this.productOrigin, this.productShelfLife, this.productStorage, }); factory StockInItem.fromJson(Map json) { final product = json['product'] as Map?; // 历史导入行 product_id=0、无 product 关联,商品信息存在明细行自身列上。 // 优先取明细行快照列,回退到 product 关联(App 新建单据走 product)。 String? lineOrProduct(String lineKey, String productKey) { final line = json[lineKey] as String?; if (line != null && line.isNotEmpty) return line; return product?[productKey] as String?; } return StockInItem( id: json['id'] != null ? (json['id'] as num).toInt() : null, orderId: json['order_id'] != null ? (json['order_id'] as num).toInt() : null, productId: (json['product_id'] as num).toInt(), quantity: (json['quantity'] as num).toDouble(), costPrice: (json['cost_price'] as num?)?.toDouble() ?? 0, costAmount: (json['cost_amount'] as num?)?.toDouble() ?? 0, returnedQuantity: (json['returned_quantity'] as num?)?.toDouble() ?? 0, batchNo: json['batch_no'] as String?, productionDate: json['production_date'] as String?, productName: lineOrProduct('product_name', 'name'), productCode: lineOrProduct('product_code', 'code'), productSeries: lineOrProduct('series', 'series'), productSpec: lineOrProduct('spec', 'spec'), productUnit: (json['product'] as Map?)?['unit'] as String?, productOrigin: ((json['product'] as Map?)?['origin'] as Map?)?['name'] as String?, productShelfLife: ((json['product'] as Map?)?['shelf_life'] as Map?)?['name'] as String?, productStorage: ((json['product'] as Map?)?['storage'] as Map?)?['name'] as String?, ); } Map toJson() => { 'product_id': productId, 'quantity': quantity, 'cost_price': costPrice, 'cost_amount': costAmount, if (batchNo != null) 'batch_no': batchNo, if (productionDate != null) 'production_date': productionDate, }; } class StockInOrder { final int id; final String orderNo; final String? type; final int warehouseId; final String? warehouseName; final int? partnerId; final String? partnerName; final int? operatorId; final String? operatorName; final int? reviewerId; final String? reviewerName; final String status; // draft | pending | approved | rejected final String returnState; // none | partial | full(退单状态) final String? orderDate; final String? reviewedAt; final double? costTotal; // 应付合计 = Σ 总进价 final String? remark; final List items; const StockInOrder({ required this.id, required this.orderNo, this.type, required this.warehouseId, this.warehouseName, this.partnerId, this.partnerName, this.operatorId, this.operatorName, this.reviewerId, this.reviewerName, required this.status, this.returnState = 'none', this.orderDate, this.reviewedAt, this.costTotal, this.remark, this.items = const [], }); factory StockInOrder.fromJson(Map json) => StockInOrder( id: (json['id'] as num).toInt(), orderNo: json['order_no'] as String, type: json['type'] as String?, warehouseId: (json['warehouse_id'] as num).toInt(), warehouseName: (json['warehouse'] as Map?)?['name'] as String?, partnerId: json['partner_id'] != null ? (json['partner_id'] as num).toInt() : null, partnerName: (json['partner'] as Map?)?['name'] as String?, operatorId: json['operator_id'] != null ? (json['operator_id'] as num).toInt() : null, operatorName: (json['operator'] as Map?)?['real_name'] as String?, reviewerId: json['reviewer_id'] != null ? (json['reviewer_id'] as num).toInt() : null, reviewerName: (json['reviewer'] as Map?)?['real_name'] as String?, status: json['status'] as String, returnState: json['return_state'] as String? ?? 'none', orderDate: json['order_date'] as String?, reviewedAt: json['reviewed_at'] as String?, costTotal: json['cost_total'] != null ? (json['cost_total'] as num).toDouble() : null, remark: json['remark'] as String?, items: json['items'] != null ? (json['items'] as List) .map((e) => StockInItem.fromJson(e as Map)) .toList() : [], ); }