refactor: 인벤토리 테이블 스펙과 도메인 계층 정비
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
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';
|
||||
|
||||
/// 라인 편집 폼에서 수집한 입력 값을 정규화한 모델.
|
||||
class TransactionLineDraft {
|
||||
TransactionLineDraft({
|
||||
this.id,
|
||||
required this.lineNo,
|
||||
required this.productId,
|
||||
required this.quantity,
|
||||
required this.unitPrice,
|
||||
this.note,
|
||||
});
|
||||
|
||||
/// 기존 라인의 식별자. 신규 라인은 `null`이다.
|
||||
final int? id;
|
||||
|
||||
/// 서버에 전달할 라인 순번.
|
||||
final int lineNo;
|
||||
|
||||
/// 선택된 제품의 식별자.
|
||||
final int productId;
|
||||
|
||||
/// 수량.
|
||||
final int quantity;
|
||||
|
||||
/// 단가.
|
||||
final double unitPrice;
|
||||
|
||||
/// 비고. 공백일 경우 `null`로 정규화한다.
|
||||
final String? note;
|
||||
}
|
||||
|
||||
/// 라인 변경 사항을 서버 호출 단위로 정리한 결과.
|
||||
class TransactionLineSyncPlan {
|
||||
TransactionLineSyncPlan({
|
||||
this.createdLines = const [],
|
||||
this.updatedLines = const [],
|
||||
this.deletedLineIds = const [],
|
||||
});
|
||||
|
||||
/// 새로 추가해야 할 라인 목록.
|
||||
final List<TransactionLineCreateInput> createdLines;
|
||||
|
||||
/// 수정이 필요한 기존 라인 목록.
|
||||
final List<TransactionLineUpdateInput> updatedLines;
|
||||
|
||||
/// 삭제해야 할 라인 ID 목록.
|
||||
final List<int> deletedLineIds;
|
||||
|
||||
/// 변경 사항이 있는지 여부.
|
||||
bool get hasChanges =>
|
||||
createdLines.isNotEmpty ||
|
||||
updatedLines.isNotEmpty ||
|
||||
deletedLineIds.isNotEmpty;
|
||||
}
|
||||
|
||||
/// 고객 연결 입력 값을 정규화한 모델.
|
||||
class TransactionCustomerDraft {
|
||||
TransactionCustomerDraft({this.id, required this.customerId, this.note});
|
||||
|
||||
/// 기존 고객 연결 ID. 신규 연결은 `null`이다.
|
||||
final int? id;
|
||||
|
||||
/// 선택된 고객 ID.
|
||||
final int customerId;
|
||||
|
||||
/// 비고. 공백일 경우 `null`.
|
||||
final String? note;
|
||||
}
|
||||
|
||||
/// 고객 연결 변경 사항을 서버 호출 단위로 정리한 결과.
|
||||
class TransactionCustomerSyncPlan {
|
||||
TransactionCustomerSyncPlan({
|
||||
this.createdCustomers = const [],
|
||||
this.updatedCustomers = const [],
|
||||
this.deletedCustomerIds = const [],
|
||||
});
|
||||
|
||||
/// 새로 추가할 고객 연결 입력.
|
||||
final List<TransactionCustomerCreateInput> createdCustomers;
|
||||
|
||||
/// 비고 등 속성이 변경된 고객 연결 입력.
|
||||
final List<TransactionCustomerUpdateInput> updatedCustomers;
|
||||
|
||||
/// 삭제 대상 고객 연결 ID.
|
||||
final List<int> deletedCustomerIds;
|
||||
|
||||
/// 변경 사항이 있는지 여부.
|
||||
bool get hasChanges =>
|
||||
createdCustomers.isNotEmpty ||
|
||||
updatedCustomers.isNotEmpty ||
|
||||
deletedCustomerIds.isNotEmpty;
|
||||
}
|
||||
|
||||
/// 재고 트랜잭션 라인·고객 입력을 기존 데이터와 비교해 동기화 계획을 산출하는 유틸리티.
|
||||
class TransactionDetailSyncService {
|
||||
const TransactionDetailSyncService();
|
||||
|
||||
/// 현재 서버 상태와 폼 입력을 비교해 라인 변경 계획을 생성한다.
|
||||
TransactionLineSyncPlan buildLinePlan({
|
||||
required List<TransactionLineDraft> drafts,
|
||||
required List<StockTransactionLine> currentLines,
|
||||
}) {
|
||||
final createdLines = <TransactionLineCreateInput>[];
|
||||
final updatedLines = <TransactionLineUpdateInput>[];
|
||||
final deletedLineIds = <int>{};
|
||||
|
||||
final currentById = <int, StockTransactionLine>{};
|
||||
for (final line in currentLines) {
|
||||
final id = line.id;
|
||||
if (id != null) {
|
||||
currentById[id] = line;
|
||||
}
|
||||
}
|
||||
final remainingIds = currentById.keys.toSet();
|
||||
|
||||
for (final draft in drafts) {
|
||||
final draftId = draft.id;
|
||||
final normalizedNote = _normalizeOptionalText(draft.note);
|
||||
if (draftId == null) {
|
||||
createdLines.add(
|
||||
TransactionLineCreateInput(
|
||||
lineNo: draft.lineNo,
|
||||
productId: draft.productId,
|
||||
quantity: draft.quantity,
|
||||
unitPrice: draft.unitPrice,
|
||||
note: normalizedNote,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
final current = currentById[draftId];
|
||||
if (current == null) {
|
||||
createdLines.add(
|
||||
TransactionLineCreateInput(
|
||||
lineNo: draft.lineNo,
|
||||
productId: draft.productId,
|
||||
quantity: draft.quantity,
|
||||
unitPrice: draft.unitPrice,
|
||||
note: normalizedNote,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
remainingIds.remove(draftId);
|
||||
|
||||
if (current.product.id != draft.productId) {
|
||||
deletedLineIds.add(draftId);
|
||||
createdLines.add(
|
||||
TransactionLineCreateInput(
|
||||
lineNo: draft.lineNo,
|
||||
productId: draft.productId,
|
||||
quantity: draft.quantity,
|
||||
unitPrice: draft.unitPrice,
|
||||
note: normalizedNote,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
int? nextLineNo;
|
||||
if (current.lineNo != draft.lineNo) {
|
||||
nextLineNo = draft.lineNo;
|
||||
}
|
||||
|
||||
int? nextQuantity;
|
||||
if (current.quantity != draft.quantity) {
|
||||
nextQuantity = draft.quantity;
|
||||
}
|
||||
|
||||
double? nextUnitPrice;
|
||||
if ((current.unitPrice - draft.unitPrice).abs() > 0.0001) {
|
||||
nextUnitPrice = draft.unitPrice;
|
||||
}
|
||||
|
||||
final currentNote = _normalizeOptionalText(current.note);
|
||||
String? nextNote;
|
||||
if (currentNote != normalizedNote) {
|
||||
nextNote = normalizedNote;
|
||||
}
|
||||
|
||||
if (nextLineNo != null ||
|
||||
nextQuantity != null ||
|
||||
nextUnitPrice != null ||
|
||||
nextNote != null) {
|
||||
updatedLines.add(
|
||||
TransactionLineUpdateInput(
|
||||
id: draftId,
|
||||
lineNo: nextLineNo,
|
||||
quantity: nextQuantity,
|
||||
unitPrice: nextUnitPrice,
|
||||
note: nextNote,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (final id in remainingIds) {
|
||||
deletedLineIds.add(id);
|
||||
}
|
||||
|
||||
return TransactionLineSyncPlan(
|
||||
createdLines: createdLines,
|
||||
updatedLines: updatedLines,
|
||||
deletedLineIds: deletedLineIds.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 현재 서버 상태와 폼 입력을 비교해 고객 연결 변경 계획을 생성한다.
|
||||
TransactionCustomerSyncPlan buildCustomerPlan({
|
||||
required List<TransactionCustomerDraft> drafts,
|
||||
required List<StockTransactionCustomer> currentCustomers,
|
||||
}) {
|
||||
final createdCustomers = <TransactionCustomerCreateInput>[];
|
||||
final updatedCustomers = <TransactionCustomerUpdateInput>[];
|
||||
final deletedCustomerIds = <int>{};
|
||||
|
||||
final currentById = <int, StockTransactionCustomer>{};
|
||||
final currentByCustomerId = <int, StockTransactionCustomer>{};
|
||||
|
||||
for (final customer in currentCustomers) {
|
||||
final id = customer.id;
|
||||
if (id != null) {
|
||||
currentById[id] = customer;
|
||||
}
|
||||
currentByCustomerId[customer.customer.id] = customer;
|
||||
}
|
||||
final remainingIds = currentById.keys.toSet();
|
||||
|
||||
for (final draft in drafts) {
|
||||
final draftId = draft.id;
|
||||
final normalizedNote = _normalizeOptionalText(draft.note);
|
||||
if (draftId != null) {
|
||||
final currentByLink = currentById[draftId];
|
||||
if (currentByLink != null) {
|
||||
remainingIds.remove(draftId);
|
||||
final currentNote = _normalizeOptionalText(currentByLink.note);
|
||||
if (currentNote != normalizedNote) {
|
||||
updatedCustomers.add(
|
||||
TransactionCustomerUpdateInput(id: draftId, note: normalizedNote),
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
final fallback = currentByCustomerId[draft.customerId];
|
||||
if (fallback != null && fallback.id != null) {
|
||||
final fallbackId = fallback.id!;
|
||||
remainingIds.remove(fallbackId);
|
||||
final currentNote = _normalizeOptionalText(fallback.note);
|
||||
if (currentNote != normalizedNote) {
|
||||
updatedCustomers.add(
|
||||
TransactionCustomerUpdateInput(
|
||||
id: fallbackId,
|
||||
note: normalizedNote,
|
||||
),
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
createdCustomers.add(
|
||||
TransactionCustomerCreateInput(
|
||||
customerId: draft.customerId,
|
||||
note: normalizedNote,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
for (final id in remainingIds) {
|
||||
deletedCustomerIds.add(id);
|
||||
}
|
||||
|
||||
return TransactionCustomerSyncPlan(
|
||||
createdCustomers: createdCustomers,
|
||||
updatedCustomers: updatedCustomers,
|
||||
deletedCustomerIds: deletedCustomerIds.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String? _normalizeOptionalText(String? value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
final trimmed = value.trim();
|
||||
if (trimmed.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
Reference in New Issue
Block a user