import 'package:get_it/get_it.dart'; import 'package:superport_v2/core/network/api_error.dart'; import 'package:superport_v2/core/common/models/paginated_result.dart'; import 'package:superport_v2/features/inventory/lookups/domain/entities/lookup_item.dart'; import 'package:superport_v2/features/inventory/lookups/domain/repositories/inventory_lookup_repository.dart'; import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction.dart'; import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction_input.dart'; import 'package:superport_v2/features/inventory/transactions/domain/repositories/stock_transaction_repository.dart'; import 'package:superport_v2/features/masters/product/domain/entities/product.dart'; import 'package:superport_v2/features/masters/product/domain/repositories/product_repository.dart'; import 'package:superport_v2/features/masters/warehouse/domain/entities/warehouse.dart'; import 'package:superport_v2/features/masters/warehouse/domain/repositories/warehouse_repository.dart'; const int _inboundTypeId = 100; const int _outboundTypeId = 200; const int _rentalRentTypeId = 300; const int _rentalReturnTypeId = 301; const int _statusDraftId = 10; const int _statusPendingId = 11; const int _statusCompleteId = 12; const int _statusOutboundWaitId = 21; const int _statusOutboundDoneId = 22; const int _statusRentalRentingId = 31; const int _statusRentalReturnWaitId = 32; const int _statusRentalFinishedId = 33; StockTransactionListFilter? lastTransactionListFilter; class InventoryTestStubConfig { const InventoryTestStubConfig({ this.submitFailure, this.registerProductRepository = false, }); final ApiException? submitFailure; final bool registerProductRepository; } InventoryTestStubConfig _stubConfig = const InventoryTestStubConfig(); void registerInventoryTestStubs([ InventoryTestStubConfig config = const InventoryTestStubConfig(), ]) { _stubConfig = config; lastTransactionListFilter = null; final lookup = _StubInventoryLookupRepository( transactionTypes: [ LookupItem(id: _inboundTypeId, name: '입고', code: 'INBOUND'), LookupItem(id: _outboundTypeId, name: '출고', code: 'OUTBOUND'), LookupItem(id: _rentalRentTypeId, name: '대여', code: 'RENT'), LookupItem(id: _rentalReturnTypeId, name: '반납', code: 'RETURN'), ], statuses: [ LookupItem(id: _statusDraftId, name: '작성중'), LookupItem(id: _statusPendingId, name: '승인대기'), LookupItem(id: _statusCompleteId, name: '승인완료'), LookupItem(id: _statusOutboundWaitId, name: '출고대기'), LookupItem(id: _statusOutboundDoneId, name: '출고완료'), LookupItem(id: _statusRentalRentingId, name: '대여중'), LookupItem(id: _statusRentalReturnWaitId, name: '반납대기'), LookupItem(id: _statusRentalFinishedId, name: '완료'), ], approvalStatuses: [ LookupItem(id: 401, name: '승인대기', isDefault: true), LookupItem(id: 402, name: '승인완료'), ], ); final transactions = _buildTransactions(); final repository = _StubStockTransactionRepository( transactions: transactions, ); final lineRepository = _StubTransactionLineRepository( transactions: transactions, ); final customerRepository = _StubTransactionCustomerRepository( transactions: transactions, ); final warehouses = [ Warehouse(id: 1, warehouseCode: 'WH-001', warehouseName: '서울 1창고'), Warehouse(id: 2, warehouseCode: 'WH-002', warehouseName: '부산 센터'), Warehouse(id: 3, warehouseCode: 'WH-003', warehouseName: '대전 물류'), ]; final warehouseRepository = _StubWarehouseRepository(warehouses: warehouses); final getIt = GetIt.I; if (getIt.isRegistered()) { getIt.unregister(); } if (getIt.isRegistered()) { getIt.unregister(); } if (getIt.isRegistered()) { getIt.unregister(); } if (getIt.isRegistered()) { getIt.unregister(); } if (getIt.isRegistered()) { getIt.unregister(); } getIt.registerSingleton(lookup); getIt.registerSingleton(repository); getIt.registerSingleton(lineRepository); getIt.registerSingleton(customerRepository); getIt.registerSingleton(warehouseRepository); if (config.registerProductRepository) { final products = [ Product( id: 501, productCode: 'XR-5000', productName: 'XR-5000', vendor: ProductVendor( id: 11, vendorCode: 'VN-11', vendorName: 'X-Ray Co.', ), uom: ProductUom(id: 21, uomName: 'EA'), ), Product( id: 502, productCode: 'Eco-200', productName: 'Eco-200', vendor: ProductVendor( id: 12, vendorCode: 'VN-12', vendorName: 'Eco Supplies', ), uom: ProductUom(id: 22, uomName: 'EA'), ), ]; if (getIt.isRegistered()) { getIt.unregister(); } getIt.registerSingleton( _StubProductRepository(products: products), ); } } class _StubInventoryLookupRepository implements InventoryLookupRepository { _StubInventoryLookupRepository({ required List transactionTypes, required List statuses, required List approvalStatuses, }) : _transactionTypes = transactionTypes, _statuses = statuses, _approvalStatuses = approvalStatuses; final List _transactionTypes; final List _statuses; final List _approvalStatuses; @override Future> fetchTransactionTypes({ bool activeOnly = true, }) async { return _transactionTypes; } @override Future> fetchTransactionStatuses({ bool activeOnly = true, }) async { return _statuses; } @override Future> fetchApprovalStatuses({ bool activeOnly = true, }) async { return _approvalStatuses; } @override Future> fetchApprovalActions({ bool activeOnly = true, }) async { return const []; } } class _StubProductRepository implements ProductRepository { _StubProductRepository({required List products}) : _products = products; final List _products; @override Future> list({ int page = 1, int pageSize = 20, String? query, int? vendorId, int? uomId, bool? isActive, }) async { Iterable filtered = _products; if (query != null && query.trim().isNotEmpty) { final normalized = query.trim().toLowerCase(); filtered = filtered.where( (product) => product.productCode.toLowerCase().contains(normalized) || product.productName.toLowerCase().contains(normalized), ); } final items = filtered.toList(growable: false); return PaginatedResult( items: items, page: page, pageSize: pageSize, total: items.length, ); } @override Future create(ProductInput input) { throw UnimplementedError(); } @override Future delete(int id) { throw UnimplementedError(); } @override Future restore(int id) { throw UnimplementedError(); } @override Future update(int id, ProductInput input) { throw UnimplementedError(); } } class _StubStockTransactionRepository implements StockTransactionRepository { _StubStockTransactionRepository({ required List transactions, }) : _transactions = transactions; final List _transactions; StockTransaction _findTransaction(int id) { final index = _transactions.indexWhere( (transaction) => transaction.id == id, ); if (index == -1) { throw StateError('Transaction $id not found'); } return _transactions[index]; } @override Future> list({ StockTransactionListFilter? filter, }) async { final resolved = filter ?? StockTransactionListFilter(); lastTransactionListFilter = resolved; final filtered = _transactions.where((transaction) { if (resolved.transactionTypeId != null && transaction.type.id != resolved.transactionTypeId) { return false; } if (resolved.transactionStatusId != null && transaction.status.id != resolved.transactionStatusId) { return false; } if (resolved.from != null && transaction.transactionDate.isBefore(resolved.from!)) { return false; } if (resolved.to != null && transaction.transactionDate.isAfter(resolved.to!)) { return false; } if (resolved.query != null) { final query = resolved.query!.toLowerCase(); final matchesQuery = transaction.transactionNo.toLowerCase().contains(query) || transaction.createdBy.name.toLowerCase().contains(query) || transaction.lines.any( (line) => line.product.name.toLowerCase().contains(query), ) || transaction.customers.any( (customer) => customer.customer.name.toLowerCase().contains(query), ); if (!matchesQuery) { return false; } } return true; }).toList(); final page = resolved.page; final pageSize = resolved.pageSize; final startIndex = (page - 1) * pageSize; final paged = startIndex >= filtered.length ? const [] : filtered.skip(startIndex).take(pageSize).toList(); return PaginatedResult( items: paged, page: page, pageSize: pageSize, total: filtered.length, ); } @override Future fetchDetail( int id, { List include = const ['lines', 'customers', 'approval'], }) async { return _findTransaction(id); } @override Future create(StockTransactionCreateInput input) { throw UnimplementedError(); } @override Future update(int id, StockTransactionUpdateInput input) { throw UnimplementedError(); } @override Future delete(int id) { throw UnimplementedError(); } @override Future restore(int id) { throw UnimplementedError(); } @override Future submit(int id, {String? note}) async { final failure = _stubConfig.submitFailure; if (failure != null) { throw failure; } return _mutateTransaction(id, _applySubmitStatus); } @override Future complete(int id, {String? note}) async { return _mutateTransaction(id, _applyCompleteStatus); } @override Future approve(int id, {String? note}) async { return _mutateTransaction(id, (transaction) => transaction); } @override Future reject(int id, {String? note}) async { return _mutateTransaction(id, (transaction) => transaction); } @override Future cancel(int id, {String? note}) async { return _mutateTransaction(id, (transaction) => transaction); } Future _mutateTransaction( int id, StockTransaction Function(StockTransaction transaction) transform, ) async { final current = _findTransaction(id); final updated = transform(current); final index = _transactions.indexOf(current); _transactions[index] = updated; return updated; } StockTransaction _applySubmitStatus(StockTransaction transaction) { final nextStatus = _nextSubmitStatus(transaction); if (nextStatus == null) { return transaction; } return transaction.copyWith(status: nextStatus); } StockTransaction _applyCompleteStatus(StockTransaction transaction) { final nextStatus = _nextCompleteStatus(transaction); if (nextStatus == null) { return transaction; } return transaction.copyWith(status: nextStatus); } StockTransactionStatus? _nextSubmitStatus(StockTransaction transaction) { final status = transaction.status.name; if (status.contains('작성중')) { if (transaction.type.name.contains('입고')) { return StockTransactionStatus(id: _statusPendingId, name: '승인대기'); } if (transaction.type.name.contains('출고')) { return StockTransactionStatus(id: _statusOutboundWaitId, name: '출고대기'); } } if (status.contains('대여중')) { return StockTransactionStatus( id: _statusRentalReturnWaitId, name: '반납대기', ); } return null; } StockTransactionStatus? _nextCompleteStatus(StockTransaction transaction) { final status = transaction.status.name; if (status.contains('승인대기')) { return StockTransactionStatus(id: _statusCompleteId, name: '승인완료'); } if (status.contains('출고대기')) { return StockTransactionStatus(id: _statusOutboundDoneId, name: '출고완료'); } if (status.contains('반납대기')) { return StockTransactionStatus(id: _statusRentalFinishedId, name: '완료'); } return null; } } class _StubTransactionLineRepository implements TransactionLineRepository { _StubTransactionLineRepository({required List transactions}) : _transactions = transactions; final List _transactions; List _linesFor(int transactionId) { final transaction = _transactions.firstWhere( (item) => item.id == transactionId, orElse: () => throw StateError('Transaction $transactionId not found'), ); return transaction.lines; } StockTransactionLine _findLine(int lineId) { for (final transaction in _transactions) { for (final line in transaction.lines) { if (line.id == lineId) { return line; } } } throw StateError('Line $lineId not found'); } @override Future addLines( int transactionId, List lines, ) async { _linesFor(transactionId); } @override Future deleteLine(int lineId) async {} @override Future updateLines( int transactionId, List lines, ) async { _linesFor(transactionId); } @override Future restoreLine(int lineId) async { _findLine(lineId); } } class _StubTransactionCustomerRepository implements TransactionCustomerRepository { _StubTransactionCustomerRepository({ required List transactions, }) : _transactions = transactions; final List _transactions; List _customersFor(int transactionId) { final transaction = _transactions.firstWhere( (item) => item.id == transactionId, orElse: () => throw StateError('Transaction $transactionId not found'), ); return transaction.customers; } @override Future addCustomers( int transactionId, List customers, ) async { _customersFor(transactionId); } @override Future deleteCustomer(int customerLinkId) async {} @override Future updateCustomers( int transactionId, List customers, ) async { _customersFor(transactionId); } } class _StubWarehouseRepository implements WarehouseRepository { _StubWarehouseRepository({required List warehouses}) : _warehouses = warehouses; final List _warehouses; @override Future> list({ int page = 1, int pageSize = 20, String? query, bool? isActive, bool includeZipcode = true, }) async { final filtered = _warehouses .where((warehouse) { if (query != null && query.isNotEmpty) { final normalized = query.toLowerCase(); final matches = warehouse.warehouseName.toLowerCase().contains(normalized) || warehouse.warehouseCode.toLowerCase().contains(normalized); if (!matches) { return false; } } if (isActive != null && warehouse.isActive != isActive) { return false; } return true; }) .toList(growable: false); return PaginatedResult( items: filtered, page: 1, pageSize: filtered.length, total: filtered.length, ); } @override Future create(WarehouseInput input) { throw UnimplementedError(); } @override Future delete(int id) { throw UnimplementedError(); } @override Future restore(int id) { throw UnimplementedError(); } @override Future update(int id, WarehouseInput input) { throw UnimplementedError(); } } List _buildTransactions() { return [ _createInboundTransaction(), _createInboundTransaction2(), _createInboundTransaction3(), _createOutboundTransaction1(), _createOutboundTransaction2(), _createOutboundTransaction3(), _createRentalTransaction1(), _createRentalTransaction2(), _createRentalTransaction3(), ]; } StockTransaction _createInboundTransaction() { return StockTransaction( id: 1, transactionNo: 'TX-20240301-001', transactionDate: DateTime(2024, 3, 1), type: StockTransactionType(id: _inboundTypeId, name: '입고'), status: StockTransactionStatus(id: _statusDraftId, name: '작성중'), warehouse: StockTransactionWarehouse(id: 1, code: 'WH-001', name: '서울 1창고'), createdBy: StockTransactionEmployee( id: 1, employeeNo: 'EMP-001', name: '홍길동', ), note: '-', lines: [ StockTransactionLine( id: 1, lineNo: 1, product: StockTransactionProduct( id: 1, code: 'P-100', name: 'XR-5000', vendor: StockTransactionVendorSummary(id: 1, name: '슈퍼벤더'), uom: StockTransactionUomSummary(id: 1, name: 'EA'), ), quantity: 40, unitPrice: 120000, note: '', ), StockTransactionLine( id: 2, lineNo: 2, product: StockTransactionProduct( id: 2, code: 'P-101', name: 'XR-5001', vendor: StockTransactionVendorSummary(id: 1, name: '슈퍼벤더'), uom: StockTransactionUomSummary(id: 1, name: 'EA'), ), quantity: 60, unitPrice: 98000, note: '', ), ], customers: const [], ); } StockTransaction _createInboundTransaction2() { return StockTransaction( id: 2, transactionNo: 'TX-20240305-010', transactionDate: DateTime(2024, 3, 5), type: StockTransactionType(id: _inboundTypeId, name: '입고'), status: StockTransactionStatus(id: _statusPendingId, name: '승인대기'), warehouse: StockTransactionWarehouse(id: 2, code: 'WH-002', name: '부산 센터'), createdBy: StockTransactionEmployee( id: 2, employeeNo: 'EMP-010', name: '김담당', ), note: '긴급 입고', lines: [ StockTransactionLine( id: 3, lineNo: 1, product: StockTransactionProduct( id: 3, code: 'P-200', name: 'Eco-200', vendor: StockTransactionVendorSummary(id: 3, name: '그린텍'), uom: StockTransactionUomSummary(id: 1, name: 'EA'), ), quantity: 25, unitPrice: 145000, note: 'QC 필요', ), StockTransactionLine( id: 4, lineNo: 2, product: StockTransactionProduct( id: 4, code: 'P-201', name: 'Eco-200B', vendor: StockTransactionVendorSummary(id: 3, name: '그린텍'), uom: StockTransactionUomSummary(id: 1, name: 'EA'), ), quantity: 10, unitPrice: 160000, note: '', ), ], customers: const [], ); } StockTransaction _createInboundTransaction3() { return StockTransaction( id: 3, transactionNo: 'TX-20240310-004', transactionDate: DateTime(2024, 3, 10), type: StockTransactionType(id: _inboundTypeId, name: '입고'), status: StockTransactionStatus(id: _statusCompleteId, name: '승인완료'), warehouse: StockTransactionWarehouse(id: 3, code: 'WH-003', name: '대전 물류'), createdBy: StockTransactionEmployee( id: 3, employeeNo: 'EMP-020', name: '최검수', ), note: '완료', lines: [ StockTransactionLine( id: 5, lineNo: 1, product: StockTransactionProduct( id: 5, code: 'P-300', name: 'Delta-One', vendor: StockTransactionVendorSummary(id: 4, name: '델타'), uom: StockTransactionUomSummary(id: 2, name: 'SET'), ), quantity: 8, unitPrice: 450000, note: '설치 일정 확인', ), ], customers: const [], ); } StockTransaction _createOutboundTransaction1() { return StockTransaction( id: 10, transactionNo: 'TX-20240302-010', transactionDate: DateTime(2024, 3, 2), type: StockTransactionType(id: _outboundTypeId, name: '출고'), status: StockTransactionStatus(id: _statusOutboundWaitId, name: '출고대기'), warehouse: StockTransactionWarehouse(id: 4, code: 'WH-001', name: '서울 1창고'), createdBy: StockTransactionEmployee( id: 4, employeeNo: 'EMP-030', name: '이영희', ), note: '-', lines: [ StockTransactionLine( id: 6, lineNo: 1, product: StockTransactionProduct( id: 1, code: 'P-100', name: 'XR-5000', vendor: StockTransactionVendorSummary(id: 1, name: '슈퍼벤더'), uom: StockTransactionUomSummary(id: 1, name: 'EA'), ), quantity: 30, unitPrice: 130000, note: '긴급 출고', ), StockTransactionLine( id: 7, lineNo: 2, product: StockTransactionProduct( id: 2, code: 'P-101', name: 'XR-5001', vendor: StockTransactionVendorSummary(id: 1, name: '슈퍼벤더'), uom: StockTransactionUomSummary(id: 1, name: 'EA'), ), quantity: 20, unitPrice: 118000, note: '', ), ], customers: [ StockTransactionCustomer( id: 1, customer: StockTransactionCustomerSummary( id: 1, code: 'C-1001', name: '슈퍼포트 파트너', ), note: '', ), StockTransactionCustomer( id: 2, customer: StockTransactionCustomerSummary( id: 2, code: 'C-1002', name: '그린에너지', ), note: '', ), ], ); } StockTransaction _createOutboundTransaction2() { return StockTransaction( id: 11, transactionNo: 'TX-20240304-005', transactionDate: DateTime(2024, 3, 4), type: StockTransactionType(id: _outboundTypeId, name: '출고'), status: StockTransactionStatus(id: _statusOutboundDoneId, name: '출고완료'), warehouse: StockTransactionWarehouse(id: 5, code: 'WH-002', name: '부산 센터'), createdBy: StockTransactionEmployee( id: 5, employeeNo: 'EMP-040', name: '강물류', ), note: '완납', lines: [ StockTransactionLine( id: 8, lineNo: 1, product: StockTransactionProduct( id: 3, code: 'P-200', name: 'Eco-200', vendor: StockTransactionVendorSummary(id: 3, name: '그린텍'), uom: StockTransactionUomSummary(id: 1, name: 'EA'), ), quantity: 15, unitPrice: 150000, note: '', ), ], customers: [ StockTransactionCustomer( id: 3, customer: StockTransactionCustomerSummary( id: 3, code: 'C-1010', name: '테크솔루션', ), note: '', ), ], ); } StockTransaction _createOutboundTransaction3() { return StockTransaction( id: 12, transactionNo: 'TX-20240309-012', transactionDate: DateTime(2024, 3, 9), type: StockTransactionType(id: _outboundTypeId, name: '출고'), status: StockTransactionStatus(id: _statusDraftId, name: '작성중'), warehouse: StockTransactionWarehouse(id: 6, code: 'WH-003', name: '대전 물류'), createdBy: StockTransactionEmployee( id: 6, employeeNo: 'EMP-050', name: '최준비', ), note: '운송배차 예정', lines: [ StockTransactionLine( id: 9, lineNo: 1, product: StockTransactionProduct( id: 5, code: 'P-300', name: 'Delta-One', vendor: StockTransactionVendorSummary(id: 4, name: '델타'), uom: StockTransactionUomSummary(id: 2, name: 'SET'), ), quantity: 6, unitPrice: 460000, note: '시연용', ), ], customers: [ StockTransactionCustomer( id: 4, customer: StockTransactionCustomerSummary( id: 4, code: 'C-1012', name: '에이치솔루션', ), note: '', ), StockTransactionCustomer( id: 5, customer: StockTransactionCustomerSummary( id: 5, code: 'C-1013', name: '블루하이드', ), note: '', ), ], ); } StockTransaction _createRentalTransaction1() { return StockTransaction( id: 20, transactionNo: 'TX-20240305-030', transactionDate: DateTime(2024, 3, 5), type: StockTransactionType(id: _rentalRentTypeId, name: '대여'), status: StockTransactionStatus(id: _statusRentalRentingId, name: '대여중'), warehouse: StockTransactionWarehouse(id: 1, code: 'WH-001', name: '서울 1창고'), createdBy: StockTransactionEmployee( id: 7, employeeNo: 'EMP-060', name: '박대여', ), note: '장기 대여', expectedReturnDate: DateTime(2024, 3, 12), lines: [ StockTransactionLine( id: 10, lineNo: 1, product: StockTransactionProduct( id: 1, code: 'P-100', name: 'XR-5000', vendor: StockTransactionVendorSummary(id: 1, name: '슈퍼벤더'), uom: StockTransactionUomSummary(id: 1, name: 'EA'), ), quantity: 10, unitPrice: 120000, note: '검수 예정', ), StockTransactionLine( id: 11, lineNo: 2, product: StockTransactionProduct( id: 6, code: 'P-102', name: 'XR-5002', vendor: StockTransactionVendorSummary(id: 1, name: '슈퍼벤더'), uom: StockTransactionUomSummary(id: 1, name: 'EA'), ), quantity: 5, unitPrice: 110000, note: '', ), ], customers: [ StockTransactionCustomer( id: 6, customer: StockTransactionCustomerSummary( id: 1, code: 'C-1001', name: '슈퍼포트 파트너', ), note: '', ), ], ); } StockTransaction _createRentalTransaction2() { return StockTransaction( id: 21, transactionNo: 'TX-20240308-014', transactionDate: DateTime(2024, 3, 8), type: StockTransactionType(id: _rentalRentTypeId, name: '대여'), status: StockTransactionStatus(id: _statusRentalReturnWaitId, name: '반납대기'), warehouse: StockTransactionWarehouse(id: 2, code: 'WH-002', name: '부산 센터'), createdBy: StockTransactionEmployee( id: 8, employeeNo: 'EMP-070', name: '이반납', ), note: '-', expectedReturnDate: DateTime(2024, 3, 15), lines: [ StockTransactionLine( id: 12, lineNo: 1, product: StockTransactionProduct( id: 3, code: 'P-200', name: 'Eco-200', vendor: StockTransactionVendorSummary(id: 3, name: '그린텍'), uom: StockTransactionUomSummary(id: 1, name: 'EA'), ), quantity: 8, unitPrice: 145000, note: '', ), ], customers: [ StockTransactionCustomer( id: 7, customer: StockTransactionCustomerSummary( id: 2, code: 'C-1002', name: '그린에너지', ), note: '', ), StockTransactionCustomer( id: 8, customer: StockTransactionCustomerSummary( id: 3, code: 'C-1010', name: '테크솔루션', ), note: '', ), ], ); } StockTransaction _createRentalTransaction3() { return StockTransaction( id: 22, transactionNo: 'TX-20240312-021', transactionDate: DateTime(2024, 3, 12), type: StockTransactionType(id: _rentalReturnTypeId, name: '반납'), status: StockTransactionStatus(id: _statusRentalFinishedId, name: '완료'), warehouse: StockTransactionWarehouse(id: 3, code: 'WH-003', name: '대전 물류'), createdBy: StockTransactionEmployee( id: 9, employeeNo: 'EMP-080', name: '최관리', ), note: '정상 반납', expectedReturnDate: DateTime(2024, 3, 12), lines: [ StockTransactionLine( id: 13, lineNo: 1, product: StockTransactionProduct( id: 5, code: 'P-300', name: 'Delta-One', vendor: StockTransactionVendorSummary(id: 4, name: '델타'), uom: StockTransactionUomSummary(id: 2, name: 'SET'), ), quantity: 4, unitPrice: 480000, note: '', ), ], customers: [ StockTransactionCustomer( id: 9, customer: StockTransactionCustomerSummary( id: 4, code: 'C-1012', name: '에이치솔루션', ), note: '', ), ], ); }