refactor: 인벤토리 테이블 스펙과 도메인 계층 정비

This commit is contained in:
JiWoong Sul
2025-10-14 18:09:26 +09:00
parent 8d3b2c1e20
commit 1325109fba
32 changed files with 5550 additions and 290 deletions

View File

@@ -0,0 +1,178 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:superport_v2/core/network/api_error.dart';
import 'package:superport_v2/core/network/failure.dart';
import 'package:superport_v2/features/inventory/outbound/presentation/controllers/outbound_controller.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/inventory/lookups/domain/repositories/inventory_lookup_repository.dart';
class _MockStockTransactionRepository extends Mock
implements StockTransactionRepository {}
class _MockInventoryLookupRepository extends Mock
implements InventoryLookupRepository {}
class _MockTransactionLineRepository extends Mock
implements TransactionLineRepository {}
class _MockTransactionCustomerRepository extends Mock
implements TransactionCustomerRepository {}
class _FakeStockTransactionCreateInput extends Fake
implements StockTransactionCreateInput {}
class _FakeStockTransactionUpdateInput extends Fake
implements StockTransactionUpdateInput {}
class _FakeStockTransactionListFilter extends Fake
implements StockTransactionListFilter {}
void main() {
group('OutboundController', () {
late StockTransactionRepository transactionRepository;
late InventoryLookupRepository lookupRepository;
late TransactionLineRepository lineRepository;
late TransactionCustomerRepository customerRepository;
late OutboundController controller;
setUpAll(() {
registerFallbackValue(_FakeStockTransactionCreateInput());
registerFallbackValue(_FakeStockTransactionUpdateInput());
registerFallbackValue(_FakeStockTransactionListFilter());
});
setUp(() {
transactionRepository = _MockStockTransactionRepository();
lookupRepository = _MockInventoryLookupRepository();
lineRepository = _MockTransactionLineRepository();
customerRepository = _MockTransactionCustomerRepository();
controller = OutboundController(
transactionRepository: transactionRepository,
lineRepository: lineRepository,
customerRepository: customerRepository,
lookupRepository: lookupRepository,
);
});
test('createTransaction은 레코드를 추가한다', () async {
final transaction = _buildTransaction();
when(
() => transactionRepository.create(any()),
).thenAnswer((_) async => transaction);
final record = await controller.createTransaction(
StockTransactionCreateInput(
transactionTypeId: 1,
transactionStatusId: 2,
warehouseId: 3,
transactionDate: DateTime(2024, 4, 1),
createdById: 7,
),
refreshAfter: false,
);
expect(record.id, equals(transaction.id));
expect(controller.records.first.id, equals(transaction.id));
});
test('completeTransaction은 레코드를 갱신하고 처리 상태를 추적한다', () async {
final original = _buildTransaction();
final completed = _buildTransaction(statusName: '출고완료');
when(
() => transactionRepository.create(any()),
).thenAnswer((_) async => original);
when(
() => transactionRepository.complete(any()),
).thenAnswer((_) async => completed);
await controller.createTransaction(
StockTransactionCreateInput(
transactionTypeId: 1,
transactionStatusId: 2,
warehouseId: 3,
transactionDate: DateTime(2024, 4, 1),
createdById: 7,
),
refreshAfter: false,
);
final future = controller.completeTransaction(
original.id!,
refreshAfter: false,
);
expect(controller.processingTransactionIds.contains(original.id), isTrue);
final record = await future;
expect(record.status, equals('출고완료'));
expect(controller.records.first.status, equals('출고완료'));
expect(
controller.processingTransactionIds.contains(original.id),
isFalse,
);
});
test('fetchTransactions 실패 시 Failure 메시지를 기록한다', () async {
final exception = ApiException(
code: ApiErrorCode.badRequest,
message: '출고 데이터를 가져오지 못했습니다.',
details: {
'errors': {
'warehouse_id': ['창고를 선택하세요.'],
},
},
);
when(
() => transactionRepository.list(filter: any(named: 'filter')),
).thenThrow(exception);
await controller.fetchTransactions(
filter: StockTransactionListFilter(transactionTypeId: 1),
);
final failure = Failure.from(exception);
expect(controller.errorMessage, equals(failure.describe()));
expect(controller.records, isEmpty);
});
});
}
StockTransaction _buildTransaction({int id = 200, String statusName = '출고대기'}) {
return StockTransaction(
id: id,
transactionNo: 'OUT-$id',
transactionDate: DateTime(2024, 4, 1),
type: StockTransactionType(id: 20, name: '출고'),
status: StockTransactionStatus(id: 21, name: statusName),
warehouse: StockTransactionWarehouse(id: 2, code: 'WH-2', name: '부산 센터'),
createdBy: StockTransactionEmployee(
id: 2,
employeeNo: 'EMP-2',
name: '출고 담당',
),
lines: [
StockTransactionLine(
id: 10,
lineNo: 1,
product: StockTransactionProduct(id: 10, code: 'P-10', name: '출고 품목'),
quantity: 3,
unitPrice: 2500.0,
),
],
customers: [
StockTransactionCustomer(
id: 1,
customer: StockTransactionCustomerSummary(
id: 100,
code: 'C-1',
name: '슈퍼포트 고객',
),
),
],
);
}