결재 및 마스터 모듈을 v4 API 계약에 맞게 조정
This commit is contained in:
@@ -197,10 +197,9 @@ class _StubApprovalRepository implements ApprovalRepository {
|
||||
Future<PaginatedResult<Approval>> list({
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
String? query,
|
||||
String? status,
|
||||
DateTime? from,
|
||||
DateTime? to,
|
||||
int? transactionId,
|
||||
int? approvalStatusId,
|
||||
int? requestedById,
|
||||
bool includeHistories = false,
|
||||
bool includeSteps = false,
|
||||
}) async {
|
||||
|
||||
@@ -22,6 +22,52 @@ void main() {
|
||||
repository = ApprovalRepositoryRemote(apiClient: apiClient);
|
||||
});
|
||||
|
||||
test('list는 신규 필터 파라미터를 전달한다', () async {
|
||||
const path = '/api/v1/approvals';
|
||||
when(
|
||||
() => apiClient.get<Map<String, dynamic>>(
|
||||
path,
|
||||
query: any(named: 'query'),
|
||||
options: any(named: 'options'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => Response<Map<String, dynamic>>(
|
||||
data: {'items': const [], 'page': 1, 'page_size': 20, 'total': 0},
|
||||
statusCode: 200,
|
||||
requestOptions: RequestOptions(path: path),
|
||||
),
|
||||
);
|
||||
|
||||
await repository.list(
|
||||
page: 2,
|
||||
pageSize: 50,
|
||||
transactionId: 10,
|
||||
approvalStatusId: 5,
|
||||
requestedById: 7,
|
||||
includeSteps: true,
|
||||
includeHistories: true,
|
||||
);
|
||||
|
||||
final captured = verify(
|
||||
() => apiClient.get<Map<String, dynamic>>(
|
||||
captureAny(),
|
||||
query: captureAny(named: 'query'),
|
||||
options: any(named: 'options'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).captured;
|
||||
|
||||
expect(captured.first, equals(path));
|
||||
final query = captured[1] as Map<String, dynamic>;
|
||||
expect(query['page'], 2);
|
||||
expect(query['page_size'], 50);
|
||||
expect(query['transaction_id'], 10);
|
||||
expect(query['approval_status_id'], 5);
|
||||
expect(query['requested_by_id'], 7);
|
||||
expect(query['include'], 'steps,histories');
|
||||
});
|
||||
|
||||
Map<String, dynamic> buildStep({
|
||||
required int id,
|
||||
required int order,
|
||||
@@ -147,4 +193,52 @@ void main() {
|
||||
expect(result.histories.length, 2);
|
||||
expect(result.histories.last.id, 91001);
|
||||
});
|
||||
|
||||
test('fetchDetail은 include 파라미터를 steps,histories로 조합한다', () async {
|
||||
const path = '/api/v1/approvals/1';
|
||||
when(
|
||||
() => apiClient.get<Map<String, dynamic>>(
|
||||
path,
|
||||
query: any(named: 'query'),
|
||||
options: any(named: 'options'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => Response<Map<String, dynamic>>(
|
||||
data: {
|
||||
'data': {
|
||||
'id': 1,
|
||||
'approval_no': 'AP-1',
|
||||
'status': {'id': 1, 'status_name': '대기'},
|
||||
'current_step': {'id': 2, 'step_order': 1},
|
||||
'requester': {
|
||||
'id': 3,
|
||||
'employee_no': 'EMP-3',
|
||||
'employee_name': '신청자',
|
||||
},
|
||||
'requested_at': '2024-01-01T00:00:00Z',
|
||||
'steps': const [],
|
||||
'histories': const [],
|
||||
},
|
||||
},
|
||||
statusCode: 200,
|
||||
requestOptions: RequestOptions(path: path),
|
||||
),
|
||||
);
|
||||
|
||||
await repository.fetchDetail(1, includeSteps: true, includeHistories: true);
|
||||
|
||||
final query =
|
||||
verify(
|
||||
() => apiClient.get<Map<String, dynamic>>(
|
||||
any(),
|
||||
query: captureAny(named: 'query'),
|
||||
options: any(named: 'options'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).captured.first
|
||||
as Map<String, dynamic>;
|
||||
|
||||
expect(query['include'], 'steps,histories');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import 'package:superport_v2/features/approvals/domain/entities/approval_proceed
|
||||
import 'package:superport_v2/features/approvals/domain/repositories/approval_repository.dart';
|
||||
import 'package:superport_v2/features/approvals/domain/repositories/approval_template_repository.dart';
|
||||
import 'package:superport_v2/features/approvals/presentation/controllers/approval_controller.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';
|
||||
|
||||
/// ApprovalRepository 모킹 클래스.
|
||||
class _MockApprovalRepository extends Mock implements ApprovalRepository {}
|
||||
@@ -26,6 +28,9 @@ class _MockApprovalTemplateRepository extends Mock
|
||||
class _FakeStepAssignmentInput extends Fake
|
||||
implements ApprovalStepAssignmentInput {}
|
||||
|
||||
class _MockInventoryLookupRepository extends Mock
|
||||
implements InventoryLookupRepository {}
|
||||
|
||||
void main() {
|
||||
late ApprovalController controller;
|
||||
late _MockApprovalRepository repository;
|
||||
@@ -90,10 +95,9 @@ void main() {
|
||||
() => repository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
status: any(named: 'status'),
|
||||
from: any(named: 'from'),
|
||||
to: any(named: 'to'),
|
||||
transactionId: any(named: 'transactionId'),
|
||||
approvalStatusId: any(named: 'approvalStatusId'),
|
||||
requestedById: any(named: 'requestedById'),
|
||||
includeHistories: any(named: 'includeHistories'),
|
||||
includeSteps: any(named: 'includeSteps'),
|
||||
),
|
||||
@@ -110,11 +114,13 @@ void main() {
|
||||
|
||||
// 검색어/상태/기간 필터가 Repository 호출에 반영되는지 확인한다.
|
||||
test('필터 전달을 검증한다', () async {
|
||||
controller.updateQuery('TRX');
|
||||
controller.updateTransactionFilter(55);
|
||||
controller.updateStatusFilter(ApprovalStatusFilter.approved);
|
||||
final from = DateTime(2024, 4, 1);
|
||||
final to = DateTime(2024, 4, 30);
|
||||
controller.updateDateRange(from, to);
|
||||
controller.updateRequestedByFilter(
|
||||
id: 77,
|
||||
name: '상신자',
|
||||
employeeNo: 'EMP077',
|
||||
);
|
||||
|
||||
await controller.fetch(page: 3);
|
||||
|
||||
@@ -122,10 +128,9 @@ void main() {
|
||||
() => repository.list(
|
||||
page: 3,
|
||||
pageSize: 20,
|
||||
query: 'TRX',
|
||||
status: 'approved',
|
||||
from: from,
|
||||
to: to,
|
||||
transactionId: 55,
|
||||
approvalStatusId: null,
|
||||
requestedById: 77,
|
||||
includeHistories: false,
|
||||
includeSteps: false,
|
||||
),
|
||||
@@ -138,10 +143,9 @@ void main() {
|
||||
() => repository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
status: any(named: 'status'),
|
||||
from: any(named: 'from'),
|
||||
to: any(named: 'to'),
|
||||
transactionId: any(named: 'transactionId'),
|
||||
approvalStatusId: any(named: 'approvalStatusId'),
|
||||
requestedById: any(named: 'requestedById'),
|
||||
includeHistories: any(named: 'includeHistories'),
|
||||
includeSteps: any(named: 'includeSteps'),
|
||||
),
|
||||
@@ -151,6 +155,46 @@ void main() {
|
||||
|
||||
expect(controller.errorMessage, isNotNull);
|
||||
});
|
||||
|
||||
test('상태 룩업 로드 후 status ID를 전달한다', () async {
|
||||
final lookupRepository = _MockInventoryLookupRepository();
|
||||
final ctrl = ApprovalController(
|
||||
approvalRepository: repository,
|
||||
templateRepository: templateRepository,
|
||||
lookupRepository: lookupRepository,
|
||||
);
|
||||
when(
|
||||
() => lookupRepository.fetchApprovalStatuses(
|
||||
activeOnly: any(named: 'activeOnly'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => [
|
||||
LookupItem(
|
||||
id: 5,
|
||||
name: '승인완료',
|
||||
code: 'approved',
|
||||
isDefault: false,
|
||||
isActive: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
await ctrl.loadStatusLookups();
|
||||
ctrl.updateStatusFilter(ApprovalStatusFilter.approved);
|
||||
await ctrl.fetch();
|
||||
|
||||
verify(
|
||||
() => repository.list(
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
transactionId: null,
|
||||
approvalStatusId: 5,
|
||||
requestedById: null,
|
||||
includeHistories: false,
|
||||
includeSteps: false,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('selectApproval', () {
|
||||
@@ -304,10 +348,9 @@ void main() {
|
||||
() => repository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
status: any(named: 'status'),
|
||||
from: any(named: 'from'),
|
||||
to: any(named: 'to'),
|
||||
transactionId: any(named: 'transactionId'),
|
||||
approvalStatusId: any(named: 'approvalStatusId'),
|
||||
requestedById: any(named: 'requestedById'),
|
||||
includeHistories: any(named: 'includeHistories'),
|
||||
includeSteps: any(named: 'includeSteps'),
|
||||
),
|
||||
@@ -471,10 +514,9 @@ void main() {
|
||||
() => repository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
status: any(named: 'status'),
|
||||
from: any(named: 'from'),
|
||||
to: any(named: 'to'),
|
||||
transactionId: any(named: 'transactionId'),
|
||||
approvalStatusId: any(named: 'approvalStatusId'),
|
||||
requestedById: any(named: 'requestedById'),
|
||||
includeHistories: any(named: 'includeHistories'),
|
||||
includeSteps: any(named: 'includeSteps'),
|
||||
),
|
||||
@@ -571,15 +613,19 @@ void main() {
|
||||
});
|
||||
|
||||
test('필터 초기화', () {
|
||||
controller.updateQuery('abc');
|
||||
controller.updateTransactionFilter(42);
|
||||
controller.updateStatusFilter(ApprovalStatusFilter.rejected);
|
||||
controller.updateDateRange(DateTime(2024, 1, 1), DateTime(2024, 1, 31));
|
||||
controller.updateRequestedByFilter(
|
||||
id: 11,
|
||||
name: '요청자',
|
||||
employeeNo: 'EMP011',
|
||||
);
|
||||
|
||||
controller.clearFilters();
|
||||
|
||||
expect(controller.query, isEmpty);
|
||||
expect(controller.transactionIdFilter, isNull);
|
||||
expect(controller.statusFilter, ApprovalStatusFilter.all);
|
||||
expect(controller.fromDate, isNull);
|
||||
expect(controller.toDate, isNull);
|
||||
expect(controller.requestedById, isNull);
|
||||
expect(controller.requestedByName, isNull);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,10 +105,9 @@ void main() {
|
||||
() => repository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
status: any(named: 'status'),
|
||||
from: any(named: 'from'),
|
||||
to: any(named: 'to'),
|
||||
transactionId: any(named: 'transactionId'),
|
||||
approvalStatusId: any(named: 'approvalStatusId'),
|
||||
requestedById: any(named: 'requestedById'),
|
||||
includeHistories: any(named: 'includeHistories'),
|
||||
includeSteps: any(named: 'includeSteps'),
|
||||
),
|
||||
|
||||
@@ -61,7 +61,7 @@ void main() {
|
||||
final query = captured[1] as Map<String, dynamic>;
|
||||
expect(query['page'], 1);
|
||||
expect(query['page_size'], 200);
|
||||
expect(query['is_active'], true);
|
||||
expect(query['active'], true);
|
||||
});
|
||||
|
||||
test('fetchApprovalActions는 is_active 파라미터 없이 호출한다', () async {
|
||||
@@ -80,6 +80,6 @@ void main() {
|
||||
),
|
||||
).captured[1]
|
||||
as Map<String, dynamic>;
|
||||
expect(query.containsKey('is_active'), isFalse);
|
||||
expect(query.containsKey('active'), isFalse);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ void main() {
|
||||
expect(query['transaction_status_id'], 7);
|
||||
expect(query['warehouse_id'], 3);
|
||||
expect(query['customer_id'], 99);
|
||||
expect(query['from'], '2024-01-01T00:00:00.000');
|
||||
expect(query['to'], '2024-01-31T00:00:00.000');
|
||||
expect(query['date_from'], '2024-01-01');
|
||||
expect(query['date_to'], '2024-01-31');
|
||||
expect(query['sort'], 'transaction_date');
|
||||
expect(query['order'], 'desc');
|
||||
expect(query['include'], 'lines,approval');
|
||||
|
||||
@@ -72,7 +72,7 @@ void main() {
|
||||
expect(query['q'], 'sup');
|
||||
expect(query['is_partner'], true);
|
||||
expect(query['is_general'], false);
|
||||
expect(query['is_active'], true);
|
||||
expect(query['active'], true);
|
||||
});
|
||||
|
||||
test('fetchDetail은 include=zipcode 파라미터를 전달한다', () async {
|
||||
|
||||
@@ -177,7 +177,7 @@ void main() {
|
||||
await tester.tap(find.text('등록'));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('우편번호 검색으로 주소를 선택하세요.'), findsOneWidget);
|
||||
expect(find.text('검색 버튼을 눌러 주소를 선택하세요.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('신규 등록 성공 시 repository.create 호출', (tester) async {
|
||||
@@ -224,6 +224,7 @@ void main() {
|
||||
id: 2,
|
||||
customerCode: capturedInput!.customerCode,
|
||||
customerName: capturedInput!.customerName,
|
||||
contactName: capturedInput!.contactName,
|
||||
isPartner: capturedInput!.isPartner,
|
||||
isGeneral: capturedInput!.isGeneral,
|
||||
);
|
||||
@@ -245,8 +246,9 @@ void main() {
|
||||
|
||||
await tester.enterText(editableTexts.at(0), 'C-100');
|
||||
await tester.enterText(editableTexts.at(1), '신규 고객');
|
||||
await tester.enterText(editableTexts.at(2), 'new@superport.com');
|
||||
await tester.enterText(editableTexts.at(3), '02-0000-0000');
|
||||
await tester.enterText(editableTexts.at(2), '홍길동');
|
||||
await tester.enterText(editableTexts.at(3), 'new@superport.com');
|
||||
await tester.enterText(editableTexts.at(4), '02-0000-0000');
|
||||
|
||||
// 유형 체크박스: 기본값 partner=false, general=true. partner on 추가
|
||||
await tester.tap(find.text('파트너'));
|
||||
@@ -257,6 +259,7 @@ void main() {
|
||||
|
||||
expect(capturedInput, isNotNull);
|
||||
expect(capturedInput?.customerCode, 'C-100');
|
||||
expect(capturedInput?.contactName, '홍길동');
|
||||
expect(find.byType(Dialog), findsNothing);
|
||||
expect(find.text('C-100'), findsOneWidget);
|
||||
verify(() => repository.create(any())).called(1);
|
||||
|
||||
@@ -78,7 +78,7 @@ void main() {
|
||||
expect(query['include'], 'vendor,uom');
|
||||
expect(query['vendor_id'], 10);
|
||||
expect(query['uom_id'], 5);
|
||||
expect(query['is_active'], false);
|
||||
expect(query['active'], false);
|
||||
expect(query['page'], 3);
|
||||
expect(query['page_size'], 40);
|
||||
expect(query['q'], 'gear');
|
||||
|
||||
@@ -295,7 +295,7 @@ void main() {
|
||||
|
||||
await tester.tap(find.text('제조사를 선택하세요'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('슈퍼벤더'));
|
||||
await tester.tap(find.text('슈퍼벤더 (V-001)'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('단위를 선택하세요'));
|
||||
|
||||
@@ -98,8 +98,8 @@ void main() {
|
||||
|
||||
expect(captured.first, equals(path));
|
||||
final query = captured[1] as Map<String, dynamic>;
|
||||
expect(query['from'], request.from.toIso8601String());
|
||||
expect(query['to'], request.to.toIso8601String());
|
||||
expect(query['date_from'], '2024-01-01');
|
||||
expect(query['date_to'], '2024-01-31');
|
||||
expect(query['format'], 'xlsx');
|
||||
expect(query['transaction_status_id'], 3);
|
||||
expect(query['approval_status_id'], 7);
|
||||
|
||||
Reference in New Issue
Block a user