API v4 계약 반영하고 보고서·입출고 화면 실연동 강화

This commit is contained in:
JiWoong Sul
2025-10-16 14:57:07 +09:00
parent 7e0f7b1c55
commit d5c99627db
34 changed files with 1767 additions and 327 deletions

View File

@@ -1289,8 +1289,12 @@ class _InboundPageState extends State<InboundPage> {
);
final remarkController = TextEditingController(text: initial?.remark ?? '');
final transactionNumberController = TextEditingController(
text: initial?.transactionNumber ?? '저장 시 자동 생성',
text: initial?.transactionNumber ?? '',
);
final approvalNumberController = TextEditingController(
text: initial?.raw?.approval?.approvalNo ?? '',
);
final approvalNoteController = TextEditingController();
final transactionTypeValue =
initial?.transactionType ??
_transactionTypeLookup?.name ??
@@ -1311,6 +1315,8 @@ class _InboundPageState extends State<InboundPage> {
};
String? writerError;
String? transactionNumberError;
String? approvalNumberError;
String? warehouseError;
String? statusError;
String? headerNotice;
@@ -1339,12 +1345,18 @@ class _InboundPageState extends State<InboundPage> {
writerController: writerController,
writerSelection: writerSelection,
requireWriterSelection: initial == null,
transactionNumberController: transactionNumberController,
transactionNumberRequired: initial == null,
approvalNumberController: approvalNumberController,
approvalNumberRequired: initial == null,
warehouseSelection: warehouseSelection,
statusValue: statusValue.value,
drafts: drafts,
lineErrors: lineErrors,
);
writerError = validationResult.writerError;
transactionNumberError = validationResult.transactionNumberError;
approvalNumberError = validationResult.approvalNumberError;
warehouseError = validationResult.warehouseError;
statusError = validationResult.statusError;
headerNotice = validationResult.headerNotice;
@@ -1388,6 +1400,9 @@ class _InboundPageState extends State<InboundPage> {
final remarkText = remarkController.text.trim();
final remarkValue = remarkText.isEmpty ? null : remarkText;
final transactionNoValue = transactionNumberController.text.trim();
final approvalNoValue = approvalNumberController.text.trim();
final approvalNoteValue = approvalNoteController.text.trim();
final transactionId = initial?.id;
final initialRecord = initial;
@@ -1475,6 +1490,7 @@ class _InboundPageState extends State<InboundPage> {
.toList(growable: false);
final created = await controller.createTransaction(
StockTransactionCreateInput(
transactionNo: transactionNoValue,
transactionTypeId: transactionTypeLookup.id,
transactionStatusId: statusItem.id,
warehouseId: warehouseId,
@@ -1482,6 +1498,11 @@ class _InboundPageState extends State<InboundPage> {
createdById: createdById,
note: remarkValue,
lines: createLines,
approval: StockTransactionApprovalInput(
approvalNo: approvalNoValue,
requestedById: createdById,
note: approvalNoteValue.isEmpty ? null : approvalNoteValue,
),
),
);
result = created;
@@ -1635,10 +1656,41 @@ class _InboundPageState extends State<InboundPage> {
width: 240,
child: SuperportFormField(
label: '트랜잭션번호',
required: true,
errorText: transactionNumberError,
child: ShadInput(
controller: transactionNumberController,
readOnly: true,
enabled: false,
readOnly: initial != null,
enabled: initial == null,
placeholder: const Text('예: IN-2024-0001'),
onChanged: (_) {
if (transactionNumberError != null) {
setState(() {
transactionNumberError = null;
});
}
},
),
),
),
SizedBox(
width: 240,
child: SuperportFormField(
label: '결재번호',
required: true,
errorText: approvalNumberError,
child: ShadInput(
controller: approvalNumberController,
readOnly: initial != null,
enabled: initial == null,
placeholder: const Text('예: APP-2024-0001'),
onChanged: (_) {
if (approvalNumberError != null) {
setState(() {
approvalNumberError = null;
});
}
},
),
),
),
@@ -1680,6 +1732,16 @@ class _InboundPageState extends State<InboundPage> {
),
),
),
SizedBox(
width: 500,
child: SuperportFormField(
label: '결재 메모',
child: ShadInput(
controller: approvalNoteController,
maxLines: 2,
),
),
),
SizedBox(
width: 500,
child: SuperportFormField(
@@ -1802,6 +1864,8 @@ class _InboundPageState extends State<InboundPage> {
writerController.dispose();
remarkController.dispose();
transactionNumberController.dispose();
approvalNumberController.dispose();
approvalNoteController.dispose();
transactionTypeController.dispose();
processedAt.dispose();
@@ -2346,6 +2410,10 @@ _InboundFormValidation _validateInboundForm({
required TextEditingController writerController,
required InventoryEmployeeSuggestion? writerSelection,
required bool requireWriterSelection,
required TextEditingController transactionNumberController,
required bool transactionNumberRequired,
required TextEditingController approvalNumberController,
required bool approvalNumberRequired,
required InventoryWarehouseOption? warehouseSelection,
required String statusValue,
required List<_LineItemDraft> drafts,
@@ -2353,6 +2421,8 @@ _InboundFormValidation _validateInboundForm({
}) {
var isValid = true;
String? writerError;
String? transactionNumberError;
String? approvalNumberError;
String? warehouseError;
String? statusError;
String? headerNotice;
@@ -2368,6 +2438,18 @@ _InboundFormValidation _validateInboundForm({
isValid = false;
}
final transactionNumber = transactionNumberController.text.trim();
if (transactionNumberRequired && transactionNumber.isEmpty) {
transactionNumberError = '거래번호를 입력하세요.';
isValid = false;
}
final approvalNumber = approvalNumberController.text.trim();
if (approvalNumberRequired && approvalNumber.isEmpty) {
approvalNumberError = '결재번호를 입력하세요.';
isValid = false;
}
if (warehouseSelection == null) {
warehouseError = '창고를 선택하세요.';
isValid = false;
@@ -2426,6 +2508,8 @@ _InboundFormValidation _validateInboundForm({
return _InboundFormValidation(
isValid: isValid,
writerError: writerError,
transactionNumberError: transactionNumberError,
approvalNumberError: approvalNumberError,
warehouseError: warehouseError,
statusError: statusError,
headerNotice: headerNotice,
@@ -2441,6 +2525,8 @@ class _InboundFormValidation {
const _InboundFormValidation({
required this.isValid,
this.writerError,
this.transactionNumberError,
this.approvalNumberError,
this.warehouseError,
this.statusError,
this.headerNotice,
@@ -2448,6 +2534,8 @@ class _InboundFormValidation {
final bool isValid;
final String? writerError;
final String? transactionNumberError;
final String? approvalNumberError;
final String? warehouseError;
final String? statusError;
final String? headerNotice;

View File

@@ -1435,6 +1435,13 @@ class _OutboundPageState extends State<OutboundPage> {
final transactionTypeController = TextEditingController(
text: transactionTypeValue,
);
final transactionNumberController = TextEditingController(
text: initial?.transactionNumber ?? '',
);
final approvalNumberController = TextEditingController(
text: initial?.raw?.approval?.approvalNo ?? '',
);
final approvalNoteController = TextEditingController();
final drafts =
initial?.items
@@ -1448,6 +1455,8 @@ class _OutboundPageState extends State<OutboundPage> {
};
String? writerError;
String? transactionNumberError;
String? approvalNumberError;
String? customerError;
String? warehouseError;
String? statusError;
@@ -1477,6 +1486,10 @@ class _OutboundPageState extends State<OutboundPage> {
writerController: writerController,
writerSelection: writerSelection,
requireWriterSelection: initial == null,
transactionNumberController: transactionNumberController,
transactionNumberRequired: initial == null,
approvalNumberController: approvalNumberController,
approvalNumberRequired: initial == null,
warehouseSelection: warehouseSelection,
statusValue: statusValue.value,
selectedCustomers: customerSelection
@@ -1487,6 +1500,8 @@ class _OutboundPageState extends State<OutboundPage> {
);
writerError = validation.writerError;
transactionNumberError = validation.transactionNumberError;
approvalNumberError = validation.approvalNumberError;
customerError = validation.customerError;
warehouseError = validation.warehouseError;
statusError = validation.statusError;
@@ -1531,6 +1546,9 @@ class _OutboundPageState extends State<OutboundPage> {
final remarkText = remarkController.text.trim();
final remarkValue = remarkText.isEmpty ? null : remarkText;
final transactionNoValue = transactionNumberController.text.trim();
final approvalNoValue = approvalNumberController.text.trim();
final approvalNoteValue = approvalNoteController.text.trim();
final transactionId = initial?.id;
final lineDrafts = <TransactionLineDraft>[];
@@ -1656,6 +1674,7 @@ class _OutboundPageState extends State<OutboundPage> {
final created = await controller.createTransaction(
StockTransactionCreateInput(
transactionNo: transactionNoValue,
transactionTypeId: transactionTypeLookup.id,
transactionStatusId: statusItem.id,
warehouseId: warehouseId,
@@ -1664,6 +1683,11 @@ class _OutboundPageState extends State<OutboundPage> {
note: remarkValue,
lines: createLines,
customers: createCustomers,
approval: StockTransactionApprovalInput(
approvalNo: approvalNoValue,
requestedById: createdById,
note: approvalNoteValue.isEmpty ? null : approvalNoteValue,
),
),
);
result = created;
@@ -1808,6 +1832,48 @@ class _OutboundPageState extends State<OutboundPage> {
),
),
),
SizedBox(
width: 240,
child: SuperportFormField(
label: '트랜잭션번호',
required: true,
errorText: transactionNumberError,
child: ShadInput(
controller: transactionNumberController,
readOnly: initial != null,
enabled: initial == null,
placeholder: const Text('예: OUT-2024-0001'),
onChanged: (_) {
if (transactionNumberError != null) {
setState(() {
transactionNumberError = null;
});
}
},
),
),
),
SizedBox(
width: 240,
child: SuperportFormField(
label: '결재번호',
required: true,
errorText: approvalNumberError,
child: ShadInput(
controller: approvalNumberController,
readOnly: initial != null,
enabled: initial == null,
placeholder: const Text('예: APP-2024-0001'),
onChanged: (_) {
if (approvalNumberError != null) {
setState(() {
approvalNumberError = null;
});
}
},
),
),
),
SizedBox(
width: 240,
child: SuperportFormField(
@@ -1846,6 +1912,16 @@ class _OutboundPageState extends State<OutboundPage> {
),
),
),
SizedBox(
width: 500,
child: SuperportFormField(
label: '결재 메모',
child: ShadInput(
controller: approvalNoteController,
maxLines: 2,
),
),
),
SizedBox(
width: 360,
child: SuperportFormField(
@@ -2012,6 +2088,9 @@ class _OutboundPageState extends State<OutboundPage> {
writerController.dispose();
remarkController.dispose();
transactionTypeController.dispose();
transactionNumberController.dispose();
approvalNumberController.dispose();
approvalNoteController.dispose();
processedAt.dispose();
return result;
@@ -2460,6 +2539,10 @@ _OutboundFormValidation _validateOutboundForm({
required TextEditingController writerController,
required InventoryEmployeeSuggestion? writerSelection,
required bool requireWriterSelection,
required TextEditingController transactionNumberController,
required bool transactionNumberRequired,
required TextEditingController approvalNumberController,
required bool approvalNumberRequired,
required InventoryWarehouseOption? warehouseSelection,
required String statusValue,
required List<InventoryCustomerOption> selectedCustomers,
@@ -2468,6 +2551,8 @@ _OutboundFormValidation _validateOutboundForm({
}) {
var isValid = true;
String? writerError;
String? transactionNumberError;
String? approvalNumberError;
String? customerError;
String? warehouseError;
String? statusError;
@@ -2484,6 +2569,18 @@ _OutboundFormValidation _validateOutboundForm({
isValid = false;
}
final transactionNumber = transactionNumberController.text.trim();
if (transactionNumberRequired && transactionNumber.isEmpty) {
transactionNumberError = '거래번호를 입력하세요.';
isValid = false;
}
final approvalNumber = approvalNumberController.text.trim();
if (approvalNumberRequired && approvalNumber.isEmpty) {
approvalNumberError = '결재번호를 입력하세요.';
isValid = false;
}
if (warehouseSelection == null) {
warehouseError = '창고를 선택하세요.';
isValid = false;
@@ -2547,6 +2644,8 @@ _OutboundFormValidation _validateOutboundForm({
return _OutboundFormValidation(
isValid: isValid,
writerError: writerError,
transactionNumberError: transactionNumberError,
approvalNumberError: approvalNumberError,
customerError: customerError,
warehouseError: warehouseError,
statusError: statusError,
@@ -2558,6 +2657,8 @@ class _OutboundFormValidation {
const _OutboundFormValidation({
required this.isValid,
this.writerError,
this.transactionNumberError,
this.approvalNumberError,
this.customerError,
this.warehouseError,
this.statusError,
@@ -2566,6 +2667,8 @@ class _OutboundFormValidation {
final bool isValid;
final String? writerError;
final String? transactionNumberError;
final String? approvalNumberError;
final String? customerError;
final String? warehouseError;
final String? statusError;

View File

@@ -1411,6 +1411,13 @@ class _RentalPageState extends State<RentalPage> {
final transactionTypeController = TextEditingController(
text: _transactionTypeForRental(rentalTypeValue.value),
);
final transactionNumberController = TextEditingController(
text: initial?.transactionNumber ?? '',
);
final approvalNumberController = TextEditingController(
text: initial?.raw?.approval?.approvalNo ?? '',
);
final approvalNoteController = TextEditingController();
final drafts =
initial?.items
@@ -1421,6 +1428,8 @@ class _RentalPageState extends State<RentalPage> {
RentalRecord? result;
String? writerError;
String? transactionNumberError;
String? approvalNumberError;
String? customerError;
String? warehouseError;
String? statusError;
@@ -1452,6 +1461,10 @@ class _RentalPageState extends State<RentalPage> {
writerController: writerController,
writerSelection: writerSelection,
requireWriterSelection: initial == null,
transactionNumberController: transactionNumberController,
transactionNumberRequired: initial == null,
approvalNumberController: approvalNumberController,
approvalNumberRequired: initial == null,
warehouseSelection: warehouseSelection,
statusValue: statusValue.value,
selectedCustomers: customerSelection
@@ -1462,6 +1475,8 @@ class _RentalPageState extends State<RentalPage> {
);
writerError = validation.writerError;
transactionNumberError = validation.transactionNumberError;
approvalNumberError = validation.approvalNumberError;
customerError = validation.customerError;
warehouseError = validation.warehouseError;
statusError = validation.statusError;
@@ -1507,6 +1522,9 @@ class _RentalPageState extends State<RentalPage> {
final remarkText = remarkController.text.trim();
final remarkValue = remarkText.isEmpty ? null : remarkText;
final transactionNoValue = transactionNumberController.text.trim();
final approvalNoValue = approvalNumberController.text.trim();
final approvalNoteValue = approvalNoteController.text.trim();
final transactionId = initial?.id;
final initialRecord = initial;
@@ -1633,6 +1651,7 @@ class _RentalPageState extends State<RentalPage> {
final transactionTypeId = selectedLookup.id;
final created = await controller.createTransaction(
StockTransactionCreateInput(
transactionNo: transactionNoValue,
transactionTypeId: transactionTypeId,
transactionStatusId: statusItem.id,
warehouseId: warehouseId,
@@ -1642,6 +1661,11 @@ class _RentalPageState extends State<RentalPage> {
expectedReturnDate: returnDue.value,
lines: createLines,
customers: createCustomers,
approval: StockTransactionApprovalInput(
approvalNo: approvalNoValue,
requestedById: createdById,
note: approvalNoteValue.isEmpty ? null : approvalNoteValue,
),
),
);
result = created;
@@ -1815,6 +1839,74 @@ class _RentalPageState extends State<RentalPage> {
),
),
),
SizedBox(
width: 240,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_FormFieldLabel(
label: '트랜잭션번호',
child: ShadInput(
controller: transactionNumberController,
readOnly: initial != null,
enabled: initial == null,
placeholder: const Text('예: RENT-2024-0001'),
onChanged: (_) {
if (transactionNumberError != null) {
setState(() {
transactionNumberError = null;
});
}
},
),
),
if (transactionNumberError != null)
Padding(
padding: const EdgeInsets.only(top: 6),
child: Text(
transactionNumberError!,
style: theme.textTheme.small.copyWith(
color: theme.colorScheme.destructive,
),
),
),
],
),
),
SizedBox(
width: 240,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_FormFieldLabel(
label: '결재번호',
child: ShadInput(
controller: approvalNumberController,
readOnly: initial != null,
enabled: initial == null,
placeholder: const Text('예: APP-2024-0001'),
onChanged: (_) {
if (approvalNumberError != null) {
setState(() {
approvalNumberError = null;
});
}
},
),
),
if (approvalNumberError != null)
Padding(
padding: const EdgeInsets.only(top: 6),
child: Text(
approvalNumberError!,
style: theme.textTheme.small.copyWith(
color: theme.colorScheme.destructive,
),
),
),
],
),
),
SizedBox(
width: 360,
child: _FormFieldLabel(
@@ -1939,6 +2031,16 @@ class _RentalPageState extends State<RentalPage> {
),
),
),
SizedBox(
width: 500,
child: _FormFieldLabel(
label: '결재 메모',
child: ShadInput(
controller: approvalNoteController,
maxLines: 2,
),
),
),
SizedBox(
width: 500,
child: _FormFieldLabel(
@@ -2068,6 +2170,9 @@ class _RentalPageState extends State<RentalPage> {
writerController.dispose();
remarkController.dispose();
transactionTypeController.dispose();
transactionNumberController.dispose();
approvalNumberController.dispose();
approvalNoteController.dispose();
processedAt.dispose();
returnDue.dispose();
@@ -2543,6 +2648,10 @@ _RentalFormValidation _validateRentalForm({
required TextEditingController writerController,
required InventoryEmployeeSuggestion? writerSelection,
required bool requireWriterSelection,
required TextEditingController transactionNumberController,
required bool transactionNumberRequired,
required TextEditingController approvalNumberController,
required bool approvalNumberRequired,
required InventoryWarehouseOption? warehouseSelection,
required String statusValue,
required List<InventoryCustomerOption> selectedCustomers,
@@ -2551,6 +2660,8 @@ _RentalFormValidation _validateRentalForm({
}) {
var isValid = true;
String? writerError;
String? transactionNumberError;
String? approvalNumberError;
String? customerError;
String? warehouseError;
String? statusError;
@@ -2567,6 +2678,18 @@ _RentalFormValidation _validateRentalForm({
isValid = false;
}
final transactionNumber = transactionNumberController.text.trim();
if (transactionNumberRequired && transactionNumber.isEmpty) {
transactionNumberError = '거래번호를 입력하세요.';
isValid = false;
}
final approvalNumber = approvalNumberController.text.trim();
if (approvalNumberRequired && approvalNumber.isEmpty) {
approvalNumberError = '결재번호를 입력하세요.';
isValid = false;
}
if (warehouseSelection == null) {
warehouseError = '창고를 선택하세요.';
isValid = false;
@@ -2630,6 +2753,8 @@ _RentalFormValidation _validateRentalForm({
return _RentalFormValidation(
isValid: isValid,
writerError: writerError,
transactionNumberError: transactionNumberError,
approvalNumberError: approvalNumberError,
customerError: customerError,
warehouseError: warehouseError,
statusError: statusError,
@@ -2641,6 +2766,8 @@ class _RentalFormValidation {
const _RentalFormValidation({
required this.isValid,
this.writerError,
this.transactionNumberError,
this.approvalNumberError,
this.customerError,
this.warehouseError,
this.statusError,
@@ -2649,6 +2776,8 @@ class _RentalFormValidation {
final bool isValid;
final String? writerError;
final String? transactionNumberError;
final String? approvalNumberError;
final String? customerError;
final String? warehouseError;
final String? statusError;

View File

@@ -37,6 +37,7 @@ class InventoryEmployeeAutocompleteField extends StatefulWidget {
required this.onSuggestionSelected,
this.onChanged,
this.enabled = true,
this.placeholder = '작성자 이름 또는 사번 검색',
});
final TextEditingController controller;
@@ -44,6 +45,7 @@ class InventoryEmployeeAutocompleteField extends StatefulWidget {
final ValueChanged<InventoryEmployeeSuggestion?> onSuggestionSelected;
final VoidCallback? onChanged;
final bool enabled;
final String placeholder;
@override
State<InventoryEmployeeAutocompleteField> createState() =>
@@ -193,7 +195,7 @@ class _InventoryEmployeeAutocompleteFieldState
controller: textController,
focusNode: focusNode,
enabled: widget.enabled,
placeholder: const Text('작성자 이름 또는 사번 검색'),
placeholder: Text(widget.placeholder),
onChanged: (_) => widget.onChanged?.call(),
onSubmitted: (_) => onFieldSubmitted(),
);

View File

@@ -84,6 +84,7 @@ class StockTransactionRepositoryRemote implements StockTransactionRepository {
Future<StockTransaction> submit(int id) async {
final response = await _api.post<Map<String, dynamic>>(
'$_basePath/$id/submit',
data: {'id': id},
options: Options(responseType: ResponseType.json),
);
return _parseSingle(response.data);
@@ -93,6 +94,7 @@ class StockTransactionRepositoryRemote implements StockTransactionRepository {
Future<StockTransaction> complete(int id) async {
final response = await _api.post<Map<String, dynamic>>(
'$_basePath/$id/complete',
data: {'id': id},
options: Options(responseType: ResponseType.json),
);
return _parseSingle(response.data);
@@ -102,6 +104,7 @@ class StockTransactionRepositoryRemote implements StockTransactionRepository {
Future<StockTransaction> approve(int id) async {
final response = await _api.post<Map<String, dynamic>>(
'$_basePath/$id/approve',
data: {'id': id},
options: Options(responseType: ResponseType.json),
);
return _parseSingle(response.data);
@@ -111,6 +114,7 @@ class StockTransactionRepositoryRemote implements StockTransactionRepository {
Future<StockTransaction> reject(int id) async {
final response = await _api.post<Map<String, dynamic>>(
'$_basePath/$id/reject',
data: {'id': id},
options: Options(responseType: ResponseType.json),
);
return _parseSingle(response.data);
@@ -120,6 +124,7 @@ class StockTransactionRepositoryRemote implements StockTransactionRepository {
Future<StockTransaction> cancel(int id) async {
final response = await _api.post<Map<String, dynamic>>(
'$_basePath/$id/cancel',
data: {'id': id},
options: Options(responseType: ResponseType.json),
);
return _parseSingle(response.data);

View File

@@ -1,11 +1,8 @@
import 'package:dio/dio.dart';
import 'package:superport_v2/core/network/api_client.dart';
import 'package:superport_v2/core/network/api_routes.dart';
import '../../domain/entities/stock_transaction.dart';
import '../../domain/entities/stock_transaction_input.dart';
import '../../domain/repositories/stock_transaction_repository.dart';
import '../dtos/stock_transaction_dto.dart';
/// 재고 트랜잭션 고객 연결 API를 호출하는 원격 저장소 구현체.
class TransactionCustomerRepositoryRemote
@@ -19,11 +16,11 @@ class TransactionCustomerRepositoryRemote
static const _customerPath = '${ApiRoutes.apiV1}/transaction-customers';
@override
Future<List<StockTransactionCustomer>> addCustomers(
Future<void> addCustomers(
int transactionId,
List<TransactionCustomerCreateInput> customers,
) async {
final response = await _api.post<Map<String, dynamic>>(
await _api.post<void>(
'$_basePath/$transactionId/customers',
data: {
'id': transactionId,
@@ -31,17 +28,15 @@ class TransactionCustomerRepositoryRemote
.map((customer) => customer.toJson())
.toList(growable: false),
},
options: Options(responseType: ResponseType.json),
);
return _parseCustomers(response.data);
}
@override
Future<List<StockTransactionCustomer>> updateCustomers(
Future<void> updateCustomers(
int transactionId,
List<TransactionCustomerUpdateInput> customers,
) async {
final response = await _api.patch<Map<String, dynamic>>(
await _api.patch<void>(
'$_basePath/$transactionId/customers',
data: {
'id': transactionId,
@@ -49,38 +44,11 @@ class TransactionCustomerRepositoryRemote
.map((customer) => customer.toJson())
.toList(growable: false),
},
options: Options(responseType: ResponseType.json),
);
return _parseCustomers(response.data);
}
@override
Future<void> deleteCustomer(int customerLinkId) async {
await _api.delete<void>('$_customerPath/$customerLinkId');
}
List<StockTransactionCustomer> _parseCustomers(Map<String, dynamic>? body) {
final data = _extractData(body);
if (data['customers'] is List) {
final dto = StockTransactionDto.fromJson(data);
return dto.customers;
}
if (data.containsKey('id')) {
final dto = StockTransactionDto.fromJson({
'customers': [data],
});
return dto.customers;
}
return const [];
}
Map<String, dynamic> _extractData(Map<String, dynamic>? body) {
if (body == null) {
return <String, dynamic>{};
}
if (body['data'] is Map<String, dynamic>) {
return body['data'] as Map<String, dynamic>;
}
return body;
}
}

View File

@@ -1,11 +1,8 @@
import 'package:dio/dio.dart';
import 'package:superport_v2/core/network/api_client.dart';
import 'package:superport_v2/core/network/api_routes.dart';
import '../../domain/entities/stock_transaction.dart';
import '../../domain/entities/stock_transaction_input.dart';
import '../../domain/repositories/stock_transaction_repository.dart';
import '../dtos/stock_transaction_dto.dart';
/// 재고 트랜잭션 라인 API를 호출하는 원격 저장소 구현체.
class TransactionLineRepositoryRemote implements TransactionLineRepository {
@@ -18,35 +15,31 @@ class TransactionLineRepositoryRemote implements TransactionLineRepository {
static const _linePath = '${ApiRoutes.apiV1}/transaction-lines';
@override
Future<List<StockTransactionLine>> addLines(
Future<void> addLines(
int transactionId,
List<TransactionLineCreateInput> lines,
) async {
final response = await _api.post<Map<String, dynamic>>(
await _api.post<void>(
'$_basePath/$transactionId/lines',
data: {
'id': transactionId,
'lines': lines.map((line) => line.toJson()).toList(growable: false),
},
options: Options(responseType: ResponseType.json),
);
return _parseLines(response.data);
}
@override
Future<List<StockTransactionLine>> updateLines(
Future<void> updateLines(
int transactionId,
List<TransactionLineUpdateInput> lines,
) async {
final response = await _api.patch<Map<String, dynamic>>(
await _api.patch<void>(
'$_basePath/$transactionId/lines',
data: {
'id': transactionId,
'lines': lines.map((line) => line.toJson()).toList(growable: false),
},
options: Options(responseType: ResponseType.json),
);
return _parseLines(response.data);
}
@override
@@ -55,40 +48,10 @@ class TransactionLineRepositoryRemote implements TransactionLineRepository {
}
@override
Future<StockTransactionLine> restoreLine(int lineId) async {
final response = await _api.post<Map<String, dynamic>>(
Future<void> restoreLine(int lineId) async {
await _api.post<void>(
'$_linePath/$lineId/restore',
options: Options(responseType: ResponseType.json),
);
final lines = _parseLines(response.data);
if (lines.isEmpty) {
throw StateError('복구된 라인 정보를 찾을 수 없습니다.');
}
return lines.first;
}
List<StockTransactionLine> _parseLines(Map<String, dynamic>? body) {
final data = _extractData(body);
if (data['lines'] is List) {
final dto = StockTransactionDto.fromJson(data);
return dto.lines;
}
if (data.containsKey('id')) {
final dto = StockTransactionDto.fromJson({
'lines': [data],
});
return dto.lines;
}
return const [];
}
Map<String, dynamic> _extractData(Map<String, dynamic>? body) {
if (body == null) {
return <String, dynamic>{};
}
if (body['data'] is Map<String, dynamic>) {
return body['data'] as Map<String, dynamic>;
}
return body;
}
}

View File

@@ -11,6 +11,7 @@ class StockTransactionCreateInput {
this.expectedReturnDate,
this.lines = const [],
this.customers = const [],
this.approval,
});
final String? transactionNo;
@@ -23,6 +24,7 @@ class StockTransactionCreateInput {
final DateTime? expectedReturnDate;
final List<TransactionLineCreateInput> lines;
final List<TransactionCustomerCreateInput> customers;
final StockTransactionApprovalInput? approval;
Map<String, dynamic> toPayload() {
return {
@@ -42,6 +44,7 @@ class StockTransactionCreateInput {
'customers': customers
.map((customer) => customer.toJson())
.toList(growable: false),
if (approval != null) 'approval': approval!.toJson(),
};
}
}
@@ -200,3 +203,27 @@ class StockTransactionListFilter {
};
}
}
/// 재고 트랜잭션 생성 시 결재(Approval) 정보를 담는 입력 모델.
class StockTransactionApprovalInput {
StockTransactionApprovalInput({
required this.approvalNo,
required this.requestedById,
this.approvalStatusId,
this.note,
});
final String approvalNo;
final int requestedById;
final int? approvalStatusId;
final String? note;
Map<String, dynamic> toJson() {
return {
'approval_no': approvalNo,
if (approvalStatusId != null) 'approval_status_id': approvalStatusId,
'requested_by_id': requestedById,
if (note != null && note!.trim().isNotEmpty) 'note': note,
};
}
}

View File

@@ -47,13 +47,13 @@ abstract class StockTransactionRepository {
/// 재고 트랜잭션 라인 저장소 인터페이스.
abstract class TransactionLineRepository {
/// 라인을 추가한다.
Future<List<StockTransactionLine>> addLines(
Future<void> addLines(
int transactionId,
List<TransactionLineCreateInput> lines,
);
/// 라인 정보를 일괄 수정한다.
Future<List<StockTransactionLine>> updateLines(
Future<void> updateLines(
int transactionId,
List<TransactionLineUpdateInput> lines,
);
@@ -62,19 +62,19 @@ abstract class TransactionLineRepository {
Future<void> deleteLine(int lineId);
/// 삭제된 라인을 복구한다.
Future<StockTransactionLine> restoreLine(int lineId);
Future<void> restoreLine(int lineId);
}
/// 재고 트랜잭션 고객 연결 저장소 인터페이스.
abstract class TransactionCustomerRepository {
/// 고객 연결을 추가한다.
Future<List<StockTransactionCustomer>> addCustomers(
Future<void> addCustomers(
int transactionId,
List<TransactionCustomerCreateInput> customers,
);
/// 고객 연결 정보를 수정한다.
Future<List<StockTransactionCustomer>> updateCustomers(
Future<void> updateCustomers(
int transactionId,
List<TransactionCustomerUpdateInput> customers,
);