feat: 재고 상태 전이 플래그 적용 및 실패 메시지 정비

This commit is contained in:
JiWoong Sul
2025-10-14 18:06:40 +09:00
parent 9f61b305d4
commit c072eb1328
9 changed files with 5069 additions and 1287 deletions

View File

@@ -0,0 +1,246 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/network/api_error.dart';
import 'package:superport_v2/core/network/failure.dart';
import 'package:superport_v2/features/inventory/inbound/presentation/controllers/inbound_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 _FakeStockTransactionCreateInput extends Fake
implements StockTransactionCreateInput {}
class _FakeStockTransactionUpdateInput extends Fake
implements StockTransactionUpdateInput {}
class _FakeStockTransactionListFilter extends Fake
implements StockTransactionListFilter {}
void main() {
group('InboundController', () {
late StockTransactionRepository transactionRepository;
late InventoryLookupRepository lookupRepository;
late TransactionLineRepository lineRepository;
late InboundController controller;
setUpAll(() {
registerFallbackValue(_FakeStockTransactionCreateInput());
registerFallbackValue(_FakeStockTransactionUpdateInput());
registerFallbackValue(_FakeStockTransactionListFilter());
});
setUp(() {
transactionRepository = _MockStockTransactionRepository();
lookupRepository = _MockInventoryLookupRepository();
lineRepository = _MockTransactionLineRepository();
controller = InboundController(
transactionRepository: transactionRepository,
lineRepository: lineRepository,
lookupRepository: lookupRepository,
);
});
test('createTransaction은 레코드를 추가하고 결과를 반환한다', () async {
final transaction = _buildTransaction();
when(
() => transactionRepository.create(any()),
).thenAnswer((_) async => transaction);
final input = StockTransactionCreateInput(
transactionTypeId: 1,
transactionStatusId: 2,
warehouseId: 3,
transactionDate: DateTime(2024, 3, 1),
createdById: 9,
);
final record = await controller.createTransaction(
input,
refreshAfter: false,
);
expect(record.id, equals(transaction.id));
expect(controller.records.length, equals(1));
expect(controller.records.first.id, equals(transaction.id));
verify(() => transactionRepository.create(any())).called(1);
});
test('updateTransaction은 로컬 레코드를 갱신한다', () async {
final original = _buildTransaction();
final updated = _buildTransaction(statusName: '승인완료');
when(
() => transactionRepository.create(any()),
).thenAnswer((_) async => original);
when(
() => transactionRepository.update(any(), any()),
).thenAnswer((_) async => updated);
await controller.createTransaction(
StockTransactionCreateInput(
transactionTypeId: 1,
transactionStatusId: 2,
warehouseId: 3,
transactionDate: DateTime(2024, 3, 1),
createdById: 9,
),
refreshAfter: false,
);
final future = controller.updateTransaction(
original.id!,
StockTransactionUpdateInput(transactionStatusId: 3),
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('deleteTransaction은 레코드를 제거한다', () async {
final transaction = _buildTransaction();
when(
() => transactionRepository.create(any()),
).thenAnswer((_) async => transaction);
when(
() => transactionRepository.delete(any()),
).thenAnswer((_) async => Future<void>.value());
await controller.createTransaction(
StockTransactionCreateInput(
transactionTypeId: 1,
transactionStatusId: 2,
warehouseId: 3,
transactionDate: DateTime(2024, 3, 1),
createdById: 9,
),
refreshAfter: false,
);
final future = controller.deleteTransaction(
transaction.id!,
refreshAfter: false,
);
expect(
controller.processingTransactionIds.contains(transaction.id),
isTrue,
);
await future;
expect(controller.records, isEmpty);
expect(
controller.processingTransactionIds.contains(transaction.id),
isFalse,
);
verify(() => transactionRepository.delete(transaction.id!)).called(1);
});
test('submitTransaction은 refreshAfter가 true일 때 목록을 다시 불러온다', () async {
final filter = StockTransactionListFilter(transactionTypeId: 1);
final initial = _buildTransaction();
when(
() => transactionRepository.list(filter: any(named: 'filter')),
).thenAnswer(
(_) async => PaginatedResult<StockTransaction>(
items: [initial],
page: 1,
pageSize: 20,
total: 1,
),
);
await controller.fetchTransactions(filter: filter);
final updated = _buildTransaction(statusName: '승인대기');
when(
() => transactionRepository.submit(any()),
).thenAnswer((_) async => updated);
when(
() => transactionRepository.list(filter: any(named: 'filter')),
).thenAnswer(
(_) async => PaginatedResult<StockTransaction>(
items: [updated],
page: 1,
pageSize: 20,
total: 1,
),
);
final result = await controller.submitTransaction(initial.id!);
expect(result.status, equals('승인대기'));
expect(controller.records.first.status, equals('승인대기'));
verify(() => transactionRepository.submit(initial.id!)).called(1);
});
test('fetchTransactions 실패 시 Failure 메시지를 노출한다', () async {
final exception = ApiException(
code: ApiErrorCode.unprocessableEntity,
message: '입고 목록을 불러오지 못했습니다.',
details: {
'errors': {
'transaction_date': ['처리일자를 선택해 주세요.'],
},
},
);
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 = 100, String statusName = '작성중'}) {
return StockTransaction(
id: id,
transactionNo: 'TX-$id',
transactionDate: DateTime(2024, 3, 1),
type: StockTransactionType(id: 10, name: '입고'),
status: StockTransactionStatus(id: 11, name: statusName),
warehouse: StockTransactionWarehouse(id: 1, code: 'WH', name: '서울 물류'),
createdBy: StockTransactionEmployee(
id: 1,
employeeNo: 'EMP-1',
name: '관리자',
),
lines: [
StockTransactionLine(
id: 1,
lineNo: 1,
product: StockTransactionProduct(id: 1, code: 'P-1', name: '테스트 상품'),
quantity: 5,
unitPrice: 1000.0,
),
],
customers: const [],
);
}

View File

@@ -3,11 +3,19 @@ import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/config/environment.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/network/api_error.dart';
import 'package:superport_v2/features/masters/warehouse/domain/entities/warehouse.dart';
import 'package:superport_v2/features/masters/warehouse/domain/repositories/warehouse_repository.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/reporting/domain/entities/report_download_result.dart';
import 'package:superport_v2/features/reporting/domain/entities/report_export_format.dart';
import 'package:superport_v2/features/reporting/domain/entities/report_export_request.dart';
import 'package:superport_v2/features/reporting/domain/repositories/reporting_repository.dart';
import 'package:superport_v2/features/reporting/presentation/pages/reporting_page.dart';
import 'package:superport_v2/widgets/components/empty_state.dart';
import '../../helpers/test_app.dart';
@@ -25,6 +33,10 @@ void main() {
testWidgets('보고서 화면은 창고 목록 재시도 흐름을 제공한다', (tester) async {
final repo = _FlakyWarehouseRepository();
GetIt.I.registerSingleton<WarehouseRepository>(repo);
GetIt.I.registerSingleton<InventoryLookupRepository>(
_StubLookupRepository(),
);
GetIt.I.registerSingleton<ReportingRepository>(_FakeReportingRepository());
final view = tester.view;
view.physicalSize = const Size(1280, 800);
@@ -38,13 +50,16 @@ void main() {
await tester.pumpAndSettle();
expect(repo.attempts, 1);
expect(find.text('창고 목록을 불러오지 못했습니다. 잠시 후 다시 시도하세요.'), findsOneWidget);
expect(
find.text('창고 목록을 불러오지 못했습니다. 잠시 후 다시 시도하세요.'),
findsWidgets,
);
await tester.tap(find.widgetWithText(ShadButton, '재시도'));
await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 4));
expect(repo.attempts, 2);
expect(find.text('창고 목록을 불러오지 못했습니다. 잠시 후 다시 시도하세요.'), findsNothing);
});
}
@@ -57,10 +72,14 @@ class _FlakyWarehouseRepository implements WarehouseRepository {
int pageSize = 20,
String? query,
bool? isActive,
bool includeZipcode = true,
}) async {
attempts += 1;
if (attempts == 1) {
throw Exception('network down');
throw const ApiException(
code: ApiErrorCode.network,
message: '창고 목록을 불러오지 못했습니다. 잠시 후 다시 시도하세요.',
);
}
return PaginatedResult<Warehouse>(
items: [
@@ -98,3 +117,77 @@ class _FlakyWarehouseRepository implements WarehouseRepository {
throw UnimplementedError();
}
}
class _StubLookupRepository implements InventoryLookupRepository {
@override
Future<List<LookupItem>> fetchTransactionTypes({
bool activeOnly = true,
}) async {
return [
LookupItem(id: 1, name: '입고'),
LookupItem(id: 2, name: '출고'),
LookupItem(id: 3, name: '대여'),
];
}
@override
Future<List<LookupItem>> fetchTransactionStatuses({
bool activeOnly = true,
}) async {
return [
LookupItem(id: 11, name: '작성중'),
LookupItem(id: 12, name: '완료'),
LookupItem(id: 13, name: '취소'),
];
}
@override
Future<List<LookupItem>> fetchApprovalStatuses({
bool activeOnly = true,
}) async {
return [
LookupItem(id: 21, name: '진행중'),
LookupItem(id: 22, name: '완료'),
LookupItem(id: 23, name: '취소'),
];
}
@override
Future<List<LookupItem>> fetchApprovalActions({
bool activeOnly = true,
}) async {
return const [];
}
}
class _FakeReportingRepository implements ReportingRepository {
ReportExportRequest? lastRequest;
ReportExportFormat? lastFormat;
@override
Future<ReportDownloadResult> exportApprovals(
ReportExportRequest request,
) async {
lastRequest = request;
lastFormat = request.format;
return ReportDownloadResult(
downloadUrl: Uri.parse('https://example.com/approvals.pdf'),
filename: 'approvals.pdf',
mimeType: 'application/pdf',
);
}
@override
Future<ReportDownloadResult> exportTransactions(
ReportExportRequest request,
) async {
lastRequest = request;
lastFormat = request.format;
return ReportDownloadResult(
downloadUrl: Uri.parse('https://example.com/transactions.xlsx'),
filename: 'transactions.xlsx',
mimeType:
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
);
}
}

View File

@@ -0,0 +1,937 @@
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/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});
final ApiException? submitFailure;
}
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: '완료'),
],
);
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<InventoryLookupRepository>()) {
getIt.unregister<InventoryLookupRepository>();
}
if (getIt.isRegistered<StockTransactionRepository>()) {
getIt.unregister<StockTransactionRepository>();
}
if (getIt.isRegistered<TransactionLineRepository>()) {
getIt.unregister<TransactionLineRepository>();
}
if (getIt.isRegistered<TransactionCustomerRepository>()) {
getIt.unregister<TransactionCustomerRepository>();
}
if (getIt.isRegistered<WarehouseRepository>()) {
getIt.unregister<WarehouseRepository>();
}
getIt.registerSingleton<InventoryLookupRepository>(lookup);
getIt.registerSingleton<StockTransactionRepository>(repository);
getIt.registerSingleton<TransactionLineRepository>(lineRepository);
getIt.registerSingleton<TransactionCustomerRepository>(customerRepository);
getIt.registerSingleton<WarehouseRepository>(warehouseRepository);
}
class _StubInventoryLookupRepository implements InventoryLookupRepository {
_StubInventoryLookupRepository({
required List<LookupItem> transactionTypes,
required List<LookupItem> statuses,
}) : _transactionTypes = transactionTypes,
_statuses = statuses;
final List<LookupItem> _transactionTypes;
final List<LookupItem> _statuses;
@override
Future<List<LookupItem>> fetchTransactionTypes({
bool activeOnly = true,
}) async {
return _transactionTypes;
}
@override
Future<List<LookupItem>> fetchTransactionStatuses({
bool activeOnly = true,
}) async {
return _statuses;
}
@override
Future<List<LookupItem>> fetchApprovalStatuses({
bool activeOnly = true,
}) async {
return const [];
}
@override
Future<List<LookupItem>> fetchApprovalActions({
bool activeOnly = true,
}) async {
return const [];
}
}
class _StubStockTransactionRepository implements StockTransactionRepository {
_StubStockTransactionRepository({
required List<StockTransaction> transactions,
}) : _transactions = transactions;
final List<StockTransaction> _transactions;
@override
Future<PaginatedResult<StockTransaction>> 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 <StockTransaction>[]
: filtered.skip(startIndex).take(pageSize).toList();
return PaginatedResult<StockTransaction>(
items: paged,
page: page,
pageSize: pageSize,
total: filtered.length,
);
}
@override
Future<StockTransaction> fetchDetail(
int id, {
List<String> include = const ['lines', 'customers', 'approval'],
}) {
throw UnimplementedError();
}
@override
Future<StockTransaction> create(StockTransactionCreateInput input) {
throw UnimplementedError();
}
@override
Future<StockTransaction> update(int id, StockTransactionUpdateInput input) {
throw UnimplementedError();
}
@override
Future<void> delete(int id) {
throw UnimplementedError();
}
@override
Future<StockTransaction> restore(int id) {
throw UnimplementedError();
}
@override
Future<StockTransaction> submit(int id) async {
final failure = _stubConfig.submitFailure;
if (failure != null) {
throw failure;
}
return _mutateTransaction(id, _applySubmitStatus);
}
@override
Future<StockTransaction> complete(int id) async {
return _mutateTransaction(id, _applyCompleteStatus);
}
@override
Future<StockTransaction> approve(int id) async {
return _mutateTransaction(id, (transaction) => transaction);
}
@override
Future<StockTransaction> reject(int id) async {
return _mutateTransaction(id, (transaction) => transaction);
}
@override
Future<StockTransaction> cancel(int id) async {
return _mutateTransaction(id, (transaction) => transaction);
}
Future<StockTransaction> _mutateTransaction(
int id,
StockTransaction Function(StockTransaction transaction) transform,
) async {
final index = _transactions.indexWhere(
(transaction) => transaction.id == id,
);
if (index == -1) {
throw StateError('Transaction $id not found');
}
final current = _transactions[index];
final updated = transform(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<StockTransaction> transactions})
: _transactions = transactions;
final List<StockTransaction> _transactions;
List<StockTransactionLine> _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<List<StockTransactionLine>> addLines(
int transactionId,
List<TransactionLineCreateInput> lines,
) async {
return _linesFor(transactionId);
}
@override
Future<void> deleteLine(int lineId) async {}
@override
Future<List<StockTransactionLine>> updateLines(
int transactionId,
List<TransactionLineUpdateInput> lines,
) async {
return _linesFor(transactionId);
}
@override
Future<StockTransactionLine> restoreLine(int lineId) async {
return _findLine(lineId);
}
}
class _StubTransactionCustomerRepository
implements TransactionCustomerRepository {
_StubTransactionCustomerRepository({
required List<StockTransaction> transactions,
}) : _transactions = transactions;
final List<StockTransaction> _transactions;
List<StockTransactionCustomer> _customersFor(int transactionId) {
final transaction = _transactions.firstWhere(
(item) => item.id == transactionId,
orElse: () => throw StateError('Transaction $transactionId not found'),
);
return transaction.customers;
}
@override
Future<List<StockTransactionCustomer>> addCustomers(
int transactionId,
List<TransactionCustomerCreateInput> customers,
) async {
return _customersFor(transactionId);
}
@override
Future<void> deleteCustomer(int customerLinkId) async {}
@override
Future<List<StockTransactionCustomer>> updateCustomers(
int transactionId,
List<TransactionCustomerUpdateInput> customers,
) async {
return _customersFor(transactionId);
}
}
class _StubWarehouseRepository implements WarehouseRepository {
_StubWarehouseRepository({required List<Warehouse> warehouses})
: _warehouses = warehouses;
final List<Warehouse> _warehouses;
@override
Future<PaginatedResult<Warehouse>> 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<Warehouse>(
items: filtered,
page: 1,
pageSize: filtered.length,
total: filtered.length,
);
}
@override
Future<Warehouse> create(WarehouseInput input) {
throw UnimplementedError();
}
@override
Future<void> delete(int id) {
throw UnimplementedError();
}
@override
Future<Warehouse> restore(int id) {
throw UnimplementedError();
}
@override
Future<Warehouse> update(int id, WarehouseInput input) {
throw UnimplementedError();
}
}
List<StockTransaction> _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: '',
),
],
);
}

View File

@@ -11,7 +11,7 @@ Widget buildTestApp(Widget child, {PermissionManager? permissionManager}) {
debugShowCheckedModeBanner: false,
theme: SuperportShadTheme.light(),
darkTheme: SuperportShadTheme.dark(),
home: Scaffold(body: child),
home: ScaffoldMessenger(child: Scaffold(body: child)),
),
);
}