refactor: 인벤토리 테이블 스펙과 도메인 계층 정비
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:superport_v2/core/common/models/paginated_result.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 StockTransactionRepositoryRemote implements StockTransactionRepository {
|
||||
StockTransactionRepositoryRemote({required ApiClient apiClient})
|
||||
: _api = apiClient;
|
||||
|
||||
final ApiClient _api;
|
||||
|
||||
static const _basePath = '${ApiRoutes.apiV1}/stock-transactions';
|
||||
|
||||
@override
|
||||
Future<PaginatedResult<StockTransaction>> list({
|
||||
StockTransactionListFilter? filter,
|
||||
}) async {
|
||||
final effectiveFilter = filter ?? StockTransactionListFilter();
|
||||
final response = await _api.get<Map<String, dynamic>>(
|
||||
_basePath,
|
||||
query: effectiveFilter.toQuery(),
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
return StockTransactionDto.parsePaginated(response.data ?? const {});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StockTransaction> fetchDetail(
|
||||
int id, {
|
||||
List<String> include = const ['lines', 'customers', 'approval'],
|
||||
}) async {
|
||||
final response = await _api.get<Map<String, dynamic>>(
|
||||
'$_basePath/$id',
|
||||
query: {if (include.isNotEmpty) 'include': include.join(',')},
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
return _parseSingle(response.data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StockTransaction> create(StockTransactionCreateInput input) async {
|
||||
final response = await _api.post<Map<String, dynamic>>(
|
||||
_basePath,
|
||||
data: input.toPayload(),
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
return _parseSingle(response.data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StockTransaction> update(
|
||||
int id,
|
||||
StockTransactionUpdateInput input,
|
||||
) async {
|
||||
final response = await _api.patch<Map<String, dynamic>>(
|
||||
'$_basePath/$id',
|
||||
data: input.toPayload(),
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
return _parseSingle(response.data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(int id) async {
|
||||
await _api.delete<void>('$_basePath/$id');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StockTransaction> restore(int id) async {
|
||||
final response = await _api.post<Map<String, dynamic>>(
|
||||
'$_basePath/$id/restore',
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
return _parseSingle(response.data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StockTransaction> submit(int id) async {
|
||||
final response = await _api.post<Map<String, dynamic>>(
|
||||
'$_basePath/$id/submit',
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
return _parseSingle(response.data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StockTransaction> complete(int id) async {
|
||||
final response = await _api.post<Map<String, dynamic>>(
|
||||
'$_basePath/$id/complete',
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
return _parseSingle(response.data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StockTransaction> approve(int id) async {
|
||||
final response = await _api.post<Map<String, dynamic>>(
|
||||
'$_basePath/$id/approve',
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
return _parseSingle(response.data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StockTransaction> reject(int id) async {
|
||||
final response = await _api.post<Map<String, dynamic>>(
|
||||
'$_basePath/$id/reject',
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
return _parseSingle(response.data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StockTransaction> cancel(int id) async {
|
||||
final response = await _api.post<Map<String, dynamic>>(
|
||||
'$_basePath/$id/cancel',
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
return _parseSingle(response.data);
|
||||
}
|
||||
|
||||
StockTransaction _parseSingle(Map<String, dynamic>? body) {
|
||||
final data = _extractData(body);
|
||||
return StockTransactionDto.fromJson(data).toEntity();
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
if (body.containsKey('transaction')) {
|
||||
final transaction = body['transaction'];
|
||||
if (transaction is Map<String, dynamic>) {
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
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
|
||||
implements TransactionCustomerRepository {
|
||||
TransactionCustomerRepositoryRemote({required ApiClient apiClient})
|
||||
: _api = apiClient;
|
||||
|
||||
final ApiClient _api;
|
||||
|
||||
static const _basePath = '${ApiRoutes.apiV1}/stock-transactions';
|
||||
static const _customerPath = '${ApiRoutes.apiV1}/transaction-customers';
|
||||
|
||||
@override
|
||||
Future<List<StockTransactionCustomer>> addCustomers(
|
||||
int transactionId,
|
||||
List<TransactionCustomerCreateInput> customers,
|
||||
) async {
|
||||
final response = await _api.post<Map<String, dynamic>>(
|
||||
'$_basePath/$transactionId/customers',
|
||||
data: {
|
||||
'id': transactionId,
|
||||
'customers': customers
|
||||
.map((customer) => customer.toJson())
|
||||
.toList(growable: false),
|
||||
},
|
||||
options: Options(responseType: ResponseType.json),
|
||||
);
|
||||
return _parseCustomers(response.data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<StockTransactionCustomer>> updateCustomers(
|
||||
int transactionId,
|
||||
List<TransactionCustomerUpdateInput> customers,
|
||||
) async {
|
||||
final response = await _api.patch<Map<String, dynamic>>(
|
||||
'$_basePath/$transactionId/customers',
|
||||
data: {
|
||||
'id': transactionId,
|
||||
'customers': customers
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
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 {
|
||||
TransactionLineRepositoryRemote({required ApiClient apiClient})
|
||||
: _api = apiClient;
|
||||
|
||||
final ApiClient _api;
|
||||
|
||||
static const _basePath = '${ApiRoutes.apiV1}/stock-transactions';
|
||||
static const _linePath = '${ApiRoutes.apiV1}/transaction-lines';
|
||||
|
||||
@override
|
||||
Future<List<StockTransactionLine>> addLines(
|
||||
int transactionId,
|
||||
List<TransactionLineCreateInput> lines,
|
||||
) async {
|
||||
final response = await _api.post<Map<String, dynamic>>(
|
||||
'$_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(
|
||||
int transactionId,
|
||||
List<TransactionLineUpdateInput> lines,
|
||||
) async {
|
||||
final response = await _api.patch<Map<String, dynamic>>(
|
||||
'$_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<void> deleteLine(int lineId) async {
|
||||
await _api.delete<void>('$_linePath/$lineId');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StockTransactionLine> restoreLine(int lineId) async {
|
||||
final response = await _api.post<Map<String, dynamic>>(
|
||||
'$_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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user