// 功能描述:A1 自动测试与截图共用的明确 Fixture,禁止作为运行入口使用。 // 版本:1.0.0 import 'package:user_app/data/repositories/client_repository.dart'; import 'package:user_app/data/services/api_client.dart'; import 'package:user_app/domain/models/client_models.dart'; import 'package:user_app/domain/models/shipping_address.dart'; import 'package:user_app/domain/models/product_detail.dart'; import 'package:user_app/domain/models/cart_item.dart'; import 'package:user_app/domain/models/product_favorite.dart'; import 'package:user_app/domain/models/primary_models.dart'; import 'package:user_app/domain/models/product_recommendations.dart'; import 'package:user_app/domain/models/shop_order_detail.dart'; import 'package:user_app/domain/models/gas_order_detail.dart'; import 'package:user_app/domain/models/gas_order_checkout.dart'; import 'package:user_app/domain/models/wallet_bill.dart'; import 'package:user_app/domain/models/wallet_account.dart'; import 'package:user_app/domain/models/deposit.dart'; import 'package:user_app/domain/models/recharge.dart'; import 'package:user_app/domain/models/delivery_detail.dart'; import 'package:user_app/domain/models/delivery_track.dart'; import 'package:user_app/domain/models/usage_statistics.dart'; import 'package:user_app/domain/models/message_center.dart'; import 'package:user_app/domain/models/family_sharing.dart'; /// 提供可控数据及失败状态,测试不依赖本地数据库。 class A1FixtureRepository extends ClientRepository { A1FixtureRepository() : super(ApiClient(() => '')); bool fail = false; int withdrawalCalls = 0; int depositReturnCalls = 0; int gasOrderCreateCalls = 0; final markedMessageKeys = []; @override Future familyDashboard() async => FamilyDashboardData( householdName: '薛海路之家', ownerName: '张先生', memberCount: 3, deviceCount: 3, members: [ const FamilyMember( identity: 'owner', name: '张先生', phoneMasked: '138****5678', relationship: '房主', inviteStatus: 20, isOwner: true, permissions: [], ), const FamilyMember( identity: 'member-1', name: '张女士', phoneMasked: '139****6078', relationship: '家人', inviteStatus: 20, isOwner: false, permissions: [ FamilyDevicePermission( deviceIdentity: 'device-1', deviceName: '厨房角阀', deviceKind: 'valve', canView: true, canAlert: true, canControl: true, ), ], ), const FamilyMember( identity: 'member-2', name: '李先生', phoneMasked: '138****0028', relationship: '房东', inviteStatus: 20, isOwner: false, permissions: [ FamilyDevicePermission( deviceIdentity: 'device-2', deviceName: '厨房报警器', deviceKind: 'alarm', canView: true, canAlert: true, canControl: false, ), ], ), FamilyMember( identity: 'member-3', name: '王女士', phoneMasked: '137****9056', relationship: '家人', inviteStatus: 10, isOwner: false, permissions: const [], expiresAt: DateTime(2026, 9, 20), ), ], devices: const [ FamilyDevice( identity: 'device-1', name: '厨房角阀', kind: 'valve', shareCount: 3, mappingConfigured: true, ), FamilyDevice( identity: 'device-2', name: '厨房报警器', kind: 'alarm', shareCount: 3, mappingConfigured: true, ), FamilyDevice( identity: 'device-3', name: '客厅报警器', kind: 'alarm', shareCount: 2, mappingConfigured: true, ), ], ); @override Future messages() async => MessageCenterData( counts: const {'all': 5, 'unread': 2, 'safety': 1, 'order': 2, 'service': 1, 'notice': 1}, items: [ UserMessage( key: 'safety-1', category: 'safety', title: '厨房燃气浓度异常', summary: '厨房报警器1:检测到燃气浓度异常', statusText: '已解除', target: '', targetIdentity: '', occurredAt: DateTime(2026, 9, 12, 9, 18), read: false, ), UserMessage( key: 'order-2', category: 'order', title: '厨房角阀已远程关闭', summary: '厨房角阀已为您远程关闭,保障用气安全', statusText: '已完成', target: '', targetIdentity: '', occurredAt: DateTime(2026, 9, 12, 8, 52), read: true, ), UserMessage( key: 'service-1', category: 'service', title: '报修工单已受理', summary: '您的报修工单已受理,工程师将尽快与您联系', statusText: '进行中', target: 'ticket', targetIdentity: 'ticket-1', occurredAt: DateTime(2026, 9, 11, 16, 30), read: true, ), UserMessage( key: 'order-1', category: 'order', title: '配送员已出发', summary: '您的燃气商品订单配送状态已更新', statusText: '配送中', target: 'gas_order', targetIdentity: 'gas-1', occurredAt: DateTime(2026, 9, 3, 9, 42), read: false, ), UserMessage( key: 'notice-1', category: 'notice', title: '秋季安全用气提醒', summary: '天气转凉,请注意用气通风,定期检查燃气设备。', statusText: '平台公告', target: 'content', targetIdentity: 'notice-1', occurredAt: DateTime(2026, 9, 1, 10), read: true, ), ], ); @override Future markMessagesRead(List keys) async => markedMessageKeys.addAll(keys); @override Future> devices() async => const [ ClientRecord( identity: 'device-1', title: '厨房15kg气瓶', subtitle: 'GZ-15-0286', raw: {'kind': 'valve'}, ), ]; @override Future usageStatistics({ required String deviceIdentity, required String period, DateTime? anchor, }) async { final start = DateTime(2026, 9, 1); final values = [ 0.12, 0.20, 0.13, 0.15, 0.08, 0.16, 0.30, 0.40, 0.28, 0.31, 0.42, 0.30, 0.29, 0.20, 0.28, 0.35, 0.20, 0.27, 0.39, 0.33, 0.19, 0.32, 0.49, 0.62, 0.35, 0.29, 0.37, 0.31, 0.17, 0.23, ]; return UsageStatistics( available: true, deviceIdentity: deviceIdentity, deviceName: '厨房15kg气瓶', deviceCode: 'GZ-15-0286', period: period, periodStart: start, periodEnd: DateTime(2026, 9, 30), unit: 'kg', points: [ for (var index = 0; index < values.length; index++) UsagePoint( date: start.add(Duration(days: index)), usage: values[index], ), ], totalUsage: values.fold(0.0, (sum, value) => sum + value), averageUsage: values.fold(0.0, (sum, value) => sum + value) / values.length, composition: const UsageComposition(breakfast: 2.2, lunch: 2.5, dinner: 3.9), source: 'device', calcVersion: 'v1', updatedAt: DateTime(2026, 9, 30, 23, 50), unavailableReason: '', ); } @override Future gasOrderDelivery(String identity) async => DeliveryDetail.fromJson({ 'identity': identity, 'order_no': '178509050920001', 'status_code': 33, 'status_name': '配送中', 'status_message': '配送员正在前往您指定地点,请留意电话或消息通知', 'appointment_at': '2026-09-05T10:30:00+08:00', 'track_updated_at': '2026-09-05T09:56:00+08:00', 'address': '北京市朝阳区望京中环南路2号星源国际B座1201室', 'contact_name': '张女士', 'contact_phone_masked': '138****5678', 'station_name': '薛海气站', 'delivery_name': '薛海气站配送点', 'delivery_address': '北京市朝阳区薛海路8号', 'staff_name': '张师傅', 'staff_avatar': 'asset:assets/staff/delivery-worker.png', 'staff_assigned': true, 'credential_verified': true, 'credential_type': 'hazmat', 'track_available': true, 'controlled_call_available': false, 'message_available': false, 'vehicle_configured': false, 'products': [ {'name': '15kg液化气', 'quantity': 1}, ], }); @override Future gasOrderDeliveryTrack(String identity) async => DeliveryTrack.fromJson({ 'identity': identity, 'status_code': 33, 'status_name': '配送中', 'status_message': '配送员正在为您配送,请耐心等待', 'appointment_at': '2026-09-05T10:30:00+08:00', 'updated_at': '2026-09-05T09:56:00+08:00', 'station_name': '薛海气站', 'staff_name': '张师傅', 'staff_avatar': 'asset:assets/staff/delivery-worker.png', 'staff_phone_masked': '138****5678', 'controlled_call_available': false, 'route_available': true, 'destination_available': true, 'destination_longitude': 116.472, 'destination_latitude': 39.997, 'points': [ {'longitude': 116.451, 'latitude': 40.002, 'occurred_at': '2026-09-05T09:42:00+08:00'}, {'longitude': 116.456, 'latitude': 40.001, 'occurred_at': '2026-09-05T09:47:00+08:00'}, {'longitude': 116.461, 'latitude': 39.999, 'occurred_at': '2026-09-05T09:51:00+08:00'}, {'longitude': 116.466, 'latitude': 39.998, 'occurred_at': '2026-09-05T09:56:00+08:00'}, ], 'timeline': [ { 'status_code': 18, 'title': '气站已接单', 'detail': '薛海气站已接收您的订单', 'occurred_at': '2026-09-05T09:21:00+08:00', }, { 'status_code': 20, 'title': '商品装车完成', 'detail': '商品已装车,准备出发', 'occurred_at': '2026-09-05T09:35:00+08:00', }, { 'status_code': 33, 'title': '配送员已出发', 'detail': '配送员已从薛海气站出发', 'occurred_at': '2026-09-05T09:42:00+08:00', }, ], }); @override Future rechargeOptions() async => const RechargeOptions( min: 1, max: 500000, channels: [ RechargeChannel('wechat', app: true, wap: true), RechargeChannel('alipay', app: true, wap: true), ], ); @override Future gasOrderOptions() async => GasOrderCheckoutData( stationName: '薛海气站', stationStatus: '营业中', deliveryScope: '薛海镇及周边区域', deliveryFee: 0, products: const [ GasOrderProductOption( name: '5kg 便携瓶', description: '小巧轻便,适合单人或短期使用', unitPrice: 5800, depositAmount: 10000, itemIdentities: ['g5'], ), GasOrderProductOption( name: '10kg 家用瓶', description: '家庭日常使用,经济实惠', unitPrice: 9600, depositAmount: 10000, itemIdentities: ['g10'], ), GasOrderProductOption( name: '15kg 家用瓶', description: '大容量更耐用,适合多人家庭', unitPrice: 12800, depositAmount: 10000, itemIdentities: ['g15'], ), ], addresses: const [ ShippingAddress( identity: 'a1', address: '薛海镇泰山路188号 薛海花园3栋602室', contactName: '张先生', contactPhone: '13812345678', isDefault: true, ), ], slots: [ GasOrderAppointmentSlot(startAt: DateTime(2026, 9, 12, 9), label: '明日 09:00-11:00'), ], ); @override Future createGasOrder({ required String requestNo, required String addressIdentity, required List itemIdentities, required DateTime appointmentAt, required int expectedAmount, }) async { gasOrderCreateCalls++; return GasOrderCreateResult(identity: 'gas-created', payableAmount: expectedAmount); } @override Future deposits() async => DepositSummary( refundableAmount: 20000, usingCount: 2, ruleText: '气瓶完好、回收检测合格后,押金原路退回至余额账户。', items: [ DepositRecord( identity: 'd1', depositNo: 'YJ20260905001', status: 10, statusName: '使用中', amount: 10000, productName: '15kg 液化气钢瓶', productCode: 'GZ-15-0286', paidAt: DateTime(2026, 8, 18), refundedAt: null, allowedActions: const ['return_bottle'], ), DepositRecord( identity: 'd2', depositNo: 'YJ20260904002', status: 20, statusName: '退款中', amount: 10000, productName: '15kg 液化气钢瓶', productCode: 'GZ-15-0193', paidAt: DateTime(2026, 8, 12), refundedAt: null, allowedActions: const [], ), DepositRecord( identity: 'd3', depositNo: 'YJ20260828003', status: 23, statusName: '已退回', amount: 10000, productName: '15kg 液化气钢瓶', productCode: 'GZ-15-0127', paidAt: DateTime(2026, 7, 28), refundedAt: DateTime(2026, 7, 30), allowedActions: const [], ), ], ); @override Future createDepositReturn({ required String depositIdentity, required ShippingAddress address, required DateTime appointmentStart, required DateTime appointmentEnd, required String requestNo, }) async { depositReturnCalls++; return const DepositReturnRequest( identity: 'return-1', status: 10, statusName: '待上门', estimatedAmount: 10000, refundAmount: 0, ); } @override Future walletBills({String direction = '', String cursor = ''}) async { if (fail) throw const ApiException(500, '测试账单错误'); final items = [ for (final (id, type, amount, flow) in [ ('1', 'gas_order', 22800, 'expense'), ('2', 'refund', 5900, 'income'), ('3', 'recharge', 30000, 'income'), ]) WalletBill( identity: id, number: 'TEST-$id', direction: flow, tradeType: type, amount: amount, fee: 0, balanceAfter: 56800, createdAt: DateTime(2026, 9, 6 - int.parse(id)), channel: 'wallet', ), ]; return WalletBillPage( items.where((i) => direction.isEmpty || i.direction == direction).toList(), '', ); } @override Future gasOrderDetail(String identity) async { final payment = identity == 'payment-1'; return GasOrderDetail.fromJson({ 'identity': identity, 'order_no': payment ? '202609051628001' : 'G202609050920001', 'status_name': payment ? '待付款' : '待签收', 'allowed_actions': payment ? ['pay', 'cancel'] : ['confirm_receipt'], 'station_name': '薛海气站', 'delivery_staff_name': '李师傅', 'contract_identity': 'contract-1', 'contract_no': 'HT20260905001', 'product_amount': 12800, 'deposit_amount': payment ? 10000 : 0, 'delivery_fee': 0, 'discount_amount': 0, 'payable_amount': payment ? 22800 : 12800, 'contact_name': '张女士', 'contact_phone': '13800005678', 'address': '北京市朝阳区望京街道望京中环南路2号星源国际B座1201室', 'created_at': '2026-09-05T09:20:00+08:00', 'items': [ {'identity': 'gas-item', 'name': '15kg家用液化气', 'quantity': 1, 'sale_amount': 12800}, ], 'timeline': payment ? [] : [ {'status_name': '已分配', 'occurred_at': '2026-09-05T09:21:00+08:00'}, {'status_name': '配送中', 'occurred_at': '2026-09-05T09:30:00+08:00'}, {'status_name': '待签收', 'occurred_at': '2026-09-05T10:30:00+08:00'}, ], 'payments': payment ? [] : [ {'amount': 12800, 'channel': 'wechat', 'paid_at': '2026-09-05T09:22:00+08:00'}, ], }, (s) => s); } @override Future> gasContracts() async => [ GasContractSummary.fromJson({ 'identity': 'contract-1', 'contract_no': 'HT20260905001', 'title': '居民瓶装燃气供用气合同', 'terms': '测试合同正文', 'contract_status': 11, 'station_name': '薛海气站', 'signed_at': '2026-08-18T00:00:00+08:00', 'effective_at': '2026-08-18T00:00:00+08:00', 'expired_at': '2027-08-17T00:00:00+08:00', 'has_attachment': false, }), ]; @override Future gasContractDetail(String identity) async => GasContractDetail.fromJson({ 'identity': identity, 'contract_no': 'HT20260905001', 'title': '居民瓶装燃气供用气合同', 'terms': '测试合同正文,仅用于自动验证,不作为真实合同。', 'contract_status': 11, 'signed_at': '2026-08-18T00:00:00+08:00', 'effective_at': '2026-08-18T00:00:00+08:00', 'expired_at': '2027-08-17T00:00:00+08:00', 'has_attachment': false, }); @override Future shopOrderDetail(String identity) async { final payment = identity == 'payment-1'; return ShopOrderDetail.fromJson({ 'identity': identity, 'order_no': payment ? 'EC202609051628001' : 'EC202609050920001', 'status_name': payment ? '待付款' : '待收货', 'allowed_actions': payment ? ['pay', 'cancel'] : ['refund', 'confirm_receipt'], 'contact_name': '张女士', 'contact_phone': '13800005678', 'address': '北京市朝阳区望京街道望京中环南路2号星源国际B座1201室', 'product_amount': payment ? 23800 : 12800, 'discount_amount': payment ? 1000 : 0, 'payable_amount': payment ? 22800 : 12800, 'created_at': '2026-09-05T09:20:00+08:00', 'paid_at': payment ? null : '2026-09-05T09:21:00+08:00', 'shipped_at': payment ? null : '2026-09-05T09:30:00+08:00', 'received_at': null, 'remark': '送达前电话联系', 'items': [ { 'identity': 'item-1', 'product_identity': 'p1', 'name': '15kg家用液化气', 'quantity': 1, 'sale_amount': payment ? 23800 : 12800, 'image_url': '', }, ], }, (s) => s); } @override Future recommendations({required String source, int page = 1}) async => ProductRecommendations( items: const [ ProductSummary(identity: 'r1', name: '家用燃气减压阀', price: 3900, stock: 10, category: '燃气配件'), ProductSummary(identity: 'r2', name: '不锈钢燃气管', price: 5900, stock: 10, category: '燃气配件'), ], page: page, hasMore: false, ); @override Future favoriteState(String identity) async => ProductFavoriteState(productIdentity: identity, active: false, revision: ''); @override Future> favorites() async => [ for (final p in await products()) ProductFavorite( state: ProductFavoriteState(productIdentity: p.identity, active: true, revision: 'r1'), product: ProductSummary( identity: p.identity, name: p.title, price: p.raw['price_amount'] as int, stock: 20, category: p.raw['category_name'] as String, ), available: true, ), const ProductFavorite( state: ProductFavoriteState(productIdentity: 'p5', active: true, revision: 'r1'), product: ProductSummary( identity: 'p5', name: '家用燃气灶 双灶', price: 69900, stock: 0, category: '灶具', ), available: false, specifications: ['天然气'], ), ]; @override Future> cart() async => const [ CartItem( product: ProductSummary(identity: 'p1', name: '15kg家用液化气', price: 12800, stock: 30), quantity: 1, selected: true, available: true, revision: 'r1', ), CartItem( product: ProductSummary(identity: 'p2', name: '燃气报警器', price: 16900, stock: 20), quantity: 1, selected: true, available: true, revision: 'r2', ), ]; @override Future productDetail(String identity) async => ProductDetail( identity: identity, name: '15kg家用液化气', price: 12800, stock: 30, category: '家用液化气', attributes: const [(name: '净重', value: '15kg'), (name: '适用家庭', value: '3—5人家庭日常烹饪使用')], ); int relationCalls = 0; @override Future> shippingAddresses() async => const [ ShippingAddress( identity: 'a1', address: '广东省广州市白云区薛海路88号\n6栋502', contactName: '张先生', contactPhone: '13800005678', isDefault: true, ), ShippingAddress( identity: 'a2', address: '广东省广州市白云区泰山路188号\n薛海花园3栋602', contactName: '李女士', contactPhone: '13900006078', ), ShippingAddress( identity: 'a3', address: '广东省广州市白云区同和街道同泰路28号\n同泰大厦1栋901', contactName: '王先生', contactPhone: '13700008910', ), ]; @override Future> contents() async { if (fail) throw const ApiException(500, '测试网络失败'); return const [ ClientRecord( identity: 'notice-1', title: '夏季用气安全提醒', subtitle: '', raw: { 'body': '高温天气注意通风,定期检查燃气设备', 'content_type': 'notice', 'version_no': 1, 'created_at': '2026-07-26T10:00:00+08:00', }, ), ClientRecord( identity: 'notice-2', title: '燃气设施维护通知', subtitle: '', raw: { 'body': '本周将对部分区域进行例行维护', 'content_type': 'notice', 'version_no': 1, 'created_at': '2026-07-24T10:00:00+08:00', }, ), ]; } @override Future?> serviceRelation() async { relationCalls++; return {'gas_name': '薛海气站', 'delivery_name': '城南配送点'}; } @override Future> products() async => [ for (final (identity, name, price, category) in [ ('p1', '15kg家用液化气', 12800, '液化气'), ('p2', '燃气报警器', 16900, '安全设备'), ('p3', '不锈钢减压阀', 5900, '燃气配件'), ('p4', '燃气软管2米', 2990, '燃气配件'), ]) ClientRecord( identity: identity, title: name, subtitle: '', raw: {'price_amount': price, 'stock_quantity': 20, 'category_name': category}, ), ]; @override Future profile() async => const UserProfile( identity: 'user-fixture', name: '张先生', phone: '13800005678', avatar: '', realName: '张先生', ); @override Future wallet() async => const WalletSummary(balance: 56800, withdrawalBalance: 56800, paymentPasswordSet: true); @override Future> walletBanks() async => const [ WalletBank( identity: 'bank-1', maskedNumber: '**** **** **** 2868', bankName: '中国建设银行', owner: '张先生', type: 'debit', isDefault: true, ), WalletBank( identity: 'bank-2', maskedNumber: '**** **** **** 6078', bankName: '中国工商银行', owner: '张先生', type: 'debit', isDefault: false, ), ]; @override Future> walletWithdrawals() async => const []; @override Future createWalletWithdrawal({ required String bankIdentity, required int amount, required String requestNo, required String paymentPassword, }) async { withdrawalCalls++; return WalletWithdrawal( identity: 'withdraw-1', number: 'WD20260911001', amount: amount, fee: 0, status: 10, createdAt: DateTime(2026, 9, 11, 12), ); } @override Future> addresses() async => []; @override Future> emergencyContacts() async => const [ ClientRecord(identity: 'contact-1', title: '家人一', subtitle: '', raw: {}), ClientRecord(identity: 'contact-2', title: '家人二', subtitle: '', raw: {}), ]; @override Future> shopOrders() async => const [ ClientRecord( identity: 'order-1', title: '订单号:1785051399865', subtitle: '', status: 16, raw: { 'payable_amount': 22800, 'status_name': '待付款', 'allowed_actions': ['pay', 'cancel'], 'created_at': '2026-07-26T15:36:00+08:00', 'items': [ {'identity': 'item-1', 'quantity': 1, 'product_snapshot': '{"name":"15kg家用液化气"}'}, ], }, ), ]; @override Future> gasOrders() async => (await shopOrders()) .map( (r) => ClientRecord( identity: r.identity, title: r.title, subtitle: r.subtitle, status: r.status, raw: { ...r.raw, 'station_name': '薛海气站', 'items': [ { 'identity': 'item-1', 'quantity': 1, 'product_type_name': '15kg家用液化气', 'product_params': '{"weight":"15kg"}', }, ], 'allowed_actions': ['pay', 'cancel'], }, ), ) .toList(); @override Future> refunds() async => []; @override Future> tickets() async => [ const ClientRecord( identity: 't1', title: 'BX202609040123', subtitle: '厨房角阀关闭后仍有轻微异响', status: 34, raw: { 'ticket_no': 'BX202609040123', 'ticket_status': 34, 'status_name': '待确认', 'allowed_actions': ['cancel', 'confirm'], 'category': 'repair', 'description': '厨房角阀关闭后仍有轻微异响', 'address': '四川省成都市武侯区万科·公园传奇南区3栋2单元1804', 'created_at': '2026-09-04T01:15:00Z', 'started_at': '2026-09-04T08:00:00Z', 'appointment_at': '2026-09-04T08:00:00Z', 'result': '已检查阀门并更换密封件,请确认处理结果。', }, ), ]; }