refactor: 인벤토리 테이블 스펙과 도메인 계층 정비
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:superport_v2/core/common/models/paginated_result.dart';
|
||||
import 'package:superport_v2/core/network/failure.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/outbound/presentation/models/outbound_record.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/transactions/presentation/services/transaction_detail_sync_service.dart';
|
||||
|
||||
/// 출고 목록을 서버에서 불러오고 상태를 제공하는 컨트롤러.
|
||||
class OutboundController extends ChangeNotifier {
|
||||
OutboundController({
|
||||
required StockTransactionRepository transactionRepository,
|
||||
required TransactionLineRepository lineRepository,
|
||||
required TransactionCustomerRepository customerRepository,
|
||||
required InventoryLookupRepository lookupRepository,
|
||||
List<String> fallbackStatusOptions = const ['작성중', '출고대기', '출고완료'],
|
||||
List<String> transactionTypeKeywords = const ['출고', 'outbound'],
|
||||
}) : _transactionRepository = transactionRepository,
|
||||
_lineRepository = lineRepository,
|
||||
_customerRepository = customerRepository,
|
||||
_lookupRepository = lookupRepository,
|
||||
_fallbackStatusOptions = List<String>.unmodifiable(
|
||||
fallbackStatusOptions,
|
||||
),
|
||||
_transactionTypeKeywords = List<String>.unmodifiable(
|
||||
transactionTypeKeywords,
|
||||
) {
|
||||
_statusOptions = List<String>.from(_fallbackStatusOptions);
|
||||
}
|
||||
|
||||
final StockTransactionRepository _transactionRepository;
|
||||
final TransactionLineRepository _lineRepository;
|
||||
final TransactionCustomerRepository _customerRepository;
|
||||
final InventoryLookupRepository _lookupRepository;
|
||||
final List<String> _fallbackStatusOptions;
|
||||
final List<String> _transactionTypeKeywords;
|
||||
|
||||
late List<String> _statusOptions;
|
||||
final Map<String, LookupItem> _statusLookup = {};
|
||||
LookupItem? _transactionType;
|
||||
PaginatedResult<StockTransaction>? _result;
|
||||
List<OutboundRecord> _records = const [];
|
||||
bool _isLoading = false;
|
||||
String? _errorMessage;
|
||||
StockTransactionListFilter? _lastFilter;
|
||||
final Set<int> _processingTransactionIds = <int>{};
|
||||
|
||||
UnmodifiableListView<String> get statusOptions =>
|
||||
UnmodifiableListView(_statusOptions);
|
||||
|
||||
UnmodifiableMapView<String, LookupItem> get statusLookup =>
|
||||
UnmodifiableMapView(_statusLookup);
|
||||
|
||||
LookupItem? get transactionType => _transactionType;
|
||||
|
||||
PaginatedResult<StockTransaction>? get result => _result;
|
||||
|
||||
UnmodifiableListView<OutboundRecord> get records =>
|
||||
UnmodifiableListView(_records);
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
String? get errorMessage => _errorMessage;
|
||||
|
||||
List<String> get fallbackStatusOptions => _fallbackStatusOptions;
|
||||
|
||||
StockTransactionListFilter? get lastFilter => _lastFilter;
|
||||
|
||||
UnmodifiableSetView<int> get processingTransactionIds =>
|
||||
UnmodifiableSetView(_processingTransactionIds);
|
||||
|
||||
/// 트랜잭션 상태 값을 불러온다.
|
||||
Future<void> loadStatusOptions() async {
|
||||
try {
|
||||
final items = await _lookupRepository.fetchTransactionStatuses();
|
||||
final normalized = items
|
||||
.map((item) => item.name.trim())
|
||||
.where((name) => name.isNotEmpty)
|
||||
.toSet()
|
||||
.toList(growable: false);
|
||||
if (normalized.isEmpty) {
|
||||
return;
|
||||
}
|
||||
_statusOptions = normalized;
|
||||
_statusLookup
|
||||
..clear()
|
||||
..addEntries(
|
||||
items
|
||||
.where((item) => item.name.trim().isNotEmpty)
|
||||
.map((item) => MapEntry(item.name.trim(), item)),
|
||||
);
|
||||
notifyListeners();
|
||||
} catch (_) {
|
||||
// 실패 시 폴백 값을 유지한다.
|
||||
}
|
||||
}
|
||||
|
||||
/// 출고 트랜잭션 타입을 조회한다.
|
||||
Future<bool> resolveTransactionType() async {
|
||||
if (_transactionType != null) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
final items = await _lookupRepository.fetchTransactionTypes();
|
||||
final matched = _matchLookup(items, _transactionTypeKeywords);
|
||||
if (matched == null) {
|
||||
return false;
|
||||
}
|
||||
_transactionType = matched;
|
||||
notifyListeners();
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 조건에 맞는 출고 트랜잭션 목록을 요청한다.
|
||||
Future<void> fetchTransactions({
|
||||
required StockTransactionListFilter filter,
|
||||
}) async {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
_lastFilter = filter;
|
||||
final result = await _transactionRepository.list(filter: filter);
|
||||
_result = result;
|
||||
_records = result.items
|
||||
.map(OutboundRecord.fromTransaction)
|
||||
.toList(growable: false);
|
||||
} catch (error) {
|
||||
_records = const [];
|
||||
_result = PaginatedResult<StockTransaction>(
|
||||
items: const [],
|
||||
page: filter.page,
|
||||
pageSize: filter.pageSize,
|
||||
total: 0,
|
||||
);
|
||||
final failure = Failure.from(error);
|
||||
_errorMessage = failure.describe();
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// 마지막 필터를 사용해 목록을 새로고침한다.
|
||||
Future<void> refresh({StockTransactionListFilter? filter}) async {
|
||||
final target = filter ?? _lastFilter;
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
await fetchTransactions(filter: target);
|
||||
}
|
||||
|
||||
/// 출고 트랜잭션을 생성한다.
|
||||
Future<OutboundRecord> createTransaction(
|
||||
StockTransactionCreateInput input, {
|
||||
bool refreshAfter = true,
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
}) {
|
||||
return _executeMutation(
|
||||
() => _transactionRepository.create(input),
|
||||
refreshAfter: refreshAfter,
|
||||
refreshFilter: refreshFilter,
|
||||
);
|
||||
}
|
||||
|
||||
/// 출고 트랜잭션을 수정한다.
|
||||
Future<OutboundRecord> updateTransaction(
|
||||
int id,
|
||||
StockTransactionUpdateInput input, {
|
||||
bool refreshAfter = true,
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
}) {
|
||||
return _executeMutation(
|
||||
() => _transactionRepository.update(id, input),
|
||||
trackedId: id,
|
||||
refreshAfter: refreshAfter,
|
||||
refreshFilter: refreshFilter,
|
||||
);
|
||||
}
|
||||
|
||||
/// 출고 트랜잭션을 삭제한다.
|
||||
Future<void> deleteTransaction(
|
||||
int id, {
|
||||
bool refreshAfter = true,
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
}) async {
|
||||
await _executeVoidMutation(
|
||||
() => _transactionRepository.delete(id),
|
||||
trackedId: id,
|
||||
refreshAfter: refreshAfter,
|
||||
refreshFilter: refreshFilter,
|
||||
);
|
||||
}
|
||||
|
||||
/// 삭제된 출고 트랜잭션을 복구한다.
|
||||
Future<OutboundRecord> restoreTransaction(
|
||||
int id, {
|
||||
bool refreshAfter = true,
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
}) {
|
||||
return _executeMutation(
|
||||
() => _transactionRepository.restore(id),
|
||||
trackedId: id,
|
||||
refreshAfter: refreshAfter,
|
||||
refreshFilter: refreshFilter,
|
||||
);
|
||||
}
|
||||
|
||||
/// 출고 트랜잭션을 상신한다.
|
||||
Future<OutboundRecord> submitTransaction(
|
||||
int id, {
|
||||
bool refreshAfter = true,
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
}) {
|
||||
return _executeMutation(
|
||||
() => _transactionRepository.submit(id),
|
||||
trackedId: id,
|
||||
refreshAfter: refreshAfter,
|
||||
refreshFilter: refreshFilter,
|
||||
);
|
||||
}
|
||||
|
||||
/// 출고 트랜잭션을 완료 처리한다.
|
||||
Future<OutboundRecord> completeTransaction(
|
||||
int id, {
|
||||
bool refreshAfter = true,
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
}) {
|
||||
return _executeMutation(
|
||||
() => _transactionRepository.complete(id),
|
||||
trackedId: id,
|
||||
refreshAfter: refreshAfter,
|
||||
refreshFilter: refreshFilter,
|
||||
);
|
||||
}
|
||||
|
||||
/// 출고 트랜잭션을 승인 처리한다.
|
||||
Future<OutboundRecord> approveTransaction(
|
||||
int id, {
|
||||
bool refreshAfter = true,
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
}) {
|
||||
return _executeMutation(
|
||||
() => _transactionRepository.approve(id),
|
||||
trackedId: id,
|
||||
refreshAfter: refreshAfter,
|
||||
refreshFilter: refreshFilter,
|
||||
);
|
||||
}
|
||||
|
||||
/// 출고 트랜잭션을 반려 처리한다.
|
||||
Future<OutboundRecord> rejectTransaction(
|
||||
int id, {
|
||||
bool refreshAfter = true,
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
}) {
|
||||
return _executeMutation(
|
||||
() => _transactionRepository.reject(id),
|
||||
trackedId: id,
|
||||
refreshAfter: refreshAfter,
|
||||
refreshFilter: refreshFilter,
|
||||
);
|
||||
}
|
||||
|
||||
/// 출고 트랜잭션을 취소 처리한다.
|
||||
Future<OutboundRecord> cancelTransaction(
|
||||
int id, {
|
||||
bool refreshAfter = true,
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
}) {
|
||||
return _executeMutation(
|
||||
() => _transactionRepository.cancel(id),
|
||||
trackedId: id,
|
||||
refreshAfter: refreshAfter,
|
||||
refreshFilter: refreshFilter,
|
||||
);
|
||||
}
|
||||
|
||||
Future<OutboundRecord> _executeMutation(
|
||||
Future<StockTransaction> Function() mutation, {
|
||||
int? trackedId,
|
||||
bool refreshAfter = true,
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
}) async {
|
||||
if (trackedId != null) {
|
||||
_processingTransactionIds.add(trackedId);
|
||||
notifyListeners();
|
||||
}
|
||||
_errorMessage = null;
|
||||
try {
|
||||
final transaction = await mutation();
|
||||
if (refreshAfter) {
|
||||
await _refreshAfterMutation(refreshFilter);
|
||||
} else {
|
||||
_upsertRecord(transaction);
|
||||
}
|
||||
return OutboundRecord.fromTransaction(transaction);
|
||||
} catch (error) {
|
||||
final failure = Failure.from(error);
|
||||
_errorMessage = failure.describe();
|
||||
notifyListeners();
|
||||
rethrow;
|
||||
} finally {
|
||||
if (trackedId != null) {
|
||||
_processingTransactionIds.remove(trackedId);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _executeVoidMutation(
|
||||
Future<void> Function() mutation, {
|
||||
required int trackedId,
|
||||
bool refreshAfter = true,
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
}) async {
|
||||
_processingTransactionIds.add(trackedId);
|
||||
notifyListeners();
|
||||
_errorMessage = null;
|
||||
try {
|
||||
await mutation();
|
||||
if (refreshAfter) {
|
||||
await _refreshAfterMutation(refreshFilter);
|
||||
} else {
|
||||
_removeRecord(trackedId);
|
||||
}
|
||||
} catch (error) {
|
||||
final failure = Failure.from(error);
|
||||
_errorMessage = failure.describe();
|
||||
notifyListeners();
|
||||
rethrow;
|
||||
} finally {
|
||||
_processingTransactionIds.remove(trackedId);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _refreshAfterMutation(
|
||||
StockTransactionListFilter? refreshFilter,
|
||||
) async {
|
||||
final filter = refreshFilter ?? _lastFilter;
|
||||
if (filter == null) {
|
||||
return;
|
||||
}
|
||||
await fetchTransactions(filter: filter);
|
||||
}
|
||||
|
||||
void _upsertRecord(StockTransaction transaction) {
|
||||
final record = OutboundRecord.fromTransaction(transaction);
|
||||
final updatedRecords = _records.toList();
|
||||
final transactionId = transaction.id;
|
||||
if (transactionId != null) {
|
||||
final index = updatedRecords.indexWhere(
|
||||
(item) => item.id == transactionId,
|
||||
);
|
||||
if (index >= 0) {
|
||||
updatedRecords[index] = record;
|
||||
} else {
|
||||
updatedRecords.insert(0, record);
|
||||
}
|
||||
} else {
|
||||
updatedRecords.insert(0, record);
|
||||
}
|
||||
_records = updatedRecords;
|
||||
|
||||
final currentResult = _result;
|
||||
if (currentResult != null) {
|
||||
final transactions = currentResult.items.toList();
|
||||
if (transactionId != null) {
|
||||
final index = transactions.indexWhere(
|
||||
(item) => item.id == transactionId,
|
||||
);
|
||||
if (index >= 0) {
|
||||
transactions[index] = transaction;
|
||||
} else {
|
||||
transactions.insert(0, transaction);
|
||||
}
|
||||
} else {
|
||||
transactions.insert(0, transaction);
|
||||
}
|
||||
_result = currentResult.copyWith(items: transactions);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _removeRecord(int transactionId) {
|
||||
final updatedRecords = _records.toList()
|
||||
..removeWhere((record) => record.id == transactionId);
|
||||
_records = updatedRecords;
|
||||
|
||||
final currentResult = _result;
|
||||
if (currentResult != null) {
|
||||
final transactions = currentResult.items
|
||||
.where((item) => item.id != transactionId)
|
||||
.toList();
|
||||
_result = currentResult.copyWith(items: transactions);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
LookupItem? _matchLookup(List<LookupItem> items, List<String> keywords) {
|
||||
if (items.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final normalizedKeywords = keywords
|
||||
.map((keyword) => keyword.toLowerCase())
|
||||
.toList(growable: false);
|
||||
LookupItem? fallback;
|
||||
for (final item in items) {
|
||||
final name = item.name.toLowerCase();
|
||||
final code = item.code?.toLowerCase();
|
||||
for (final keyword in normalizedKeywords) {
|
||||
if (name == keyword || name.contains(keyword)) {
|
||||
return item;
|
||||
}
|
||||
if (code != null && (code == keyword || code.contains(keyword))) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
if (fallback == null && item.isDefault) {
|
||||
fallback = item;
|
||||
}
|
||||
}
|
||||
return fallback ?? items.first;
|
||||
}
|
||||
|
||||
/// 라인 변경 계획을 실제 API 호출로 실행한다.
|
||||
Future<void> syncTransactionLines(
|
||||
int transactionId,
|
||||
TransactionLineSyncPlan plan,
|
||||
) async {
|
||||
if (!plan.hasChanges) {
|
||||
return;
|
||||
}
|
||||
if (plan.updatedLines.isNotEmpty) {
|
||||
await _lineRepository.updateLines(transactionId, plan.updatedLines);
|
||||
}
|
||||
for (final lineId in plan.deletedLineIds) {
|
||||
await _lineRepository.deleteLine(lineId);
|
||||
}
|
||||
if (plan.createdLines.isNotEmpty) {
|
||||
await _lineRepository.addLines(transactionId, plan.createdLines);
|
||||
}
|
||||
}
|
||||
|
||||
/// 고객 연결 변경 계획을 실제 API 호출로 실행한다.
|
||||
Future<void> syncTransactionCustomers(
|
||||
int transactionId,
|
||||
TransactionCustomerSyncPlan plan,
|
||||
) async {
|
||||
if (!plan.hasChanges) {
|
||||
return;
|
||||
}
|
||||
if (plan.updatedCustomers.isNotEmpty) {
|
||||
await _customerRepository.updateCustomers(
|
||||
transactionId,
|
||||
plan.updatedCustomers,
|
||||
);
|
||||
}
|
||||
for (final linkId in plan.deletedCustomerIds) {
|
||||
await _customerRepository.deleteCustomer(linkId);
|
||||
}
|
||||
if (plan.createdCustomers.isNotEmpty) {
|
||||
await _customerRepository.addCustomers(
|
||||
transactionId,
|
||||
plan.createdCustomers,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction.dart';
|
||||
|
||||
/// 출고 화면에서 사용하는 UI용 레코드 모델.
|
||||
class OutboundRecord {
|
||||
OutboundRecord({
|
||||
this.id,
|
||||
required this.number,
|
||||
required this.transactionNumber,
|
||||
required this.transactionType,
|
||||
this.transactionTypeId,
|
||||
required this.processedAt,
|
||||
required this.warehouse,
|
||||
this.warehouseId,
|
||||
required this.status,
|
||||
this.statusId,
|
||||
required this.writer,
|
||||
this.writerId,
|
||||
required this.remark,
|
||||
required this.items,
|
||||
required this.customers,
|
||||
this.raw,
|
||||
});
|
||||
|
||||
factory OutboundRecord.fromTransaction(StockTransaction transaction) {
|
||||
return OutboundRecord(
|
||||
id: transaction.id,
|
||||
number: transaction.transactionNo,
|
||||
transactionNumber: transaction.transactionNo,
|
||||
transactionType: transaction.type.name,
|
||||
transactionTypeId: transaction.type.id,
|
||||
processedAt: transaction.transactionDate,
|
||||
warehouse: transaction.warehouse.name,
|
||||
warehouseId: transaction.warehouse.id,
|
||||
status: transaction.status.name,
|
||||
statusId: transaction.status.id,
|
||||
writer: transaction.createdBy.name,
|
||||
writerId: transaction.createdBy.id,
|
||||
remark: transaction.note ?? '-',
|
||||
items: transaction.lines
|
||||
.map(OutboundLineItem.fromLine)
|
||||
.toList(growable: false),
|
||||
customers: transaction.customers
|
||||
.map(OutboundCustomer.fromLink)
|
||||
.toList(growable: false),
|
||||
raw: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
final int? id;
|
||||
final String number;
|
||||
final String transactionNumber;
|
||||
final String transactionType;
|
||||
final int? transactionTypeId;
|
||||
final DateTime processedAt;
|
||||
final String warehouse;
|
||||
final int? warehouseId;
|
||||
final String status;
|
||||
final int? statusId;
|
||||
final String writer;
|
||||
final int? writerId;
|
||||
final String remark;
|
||||
final List<OutboundLineItem> items;
|
||||
final List<OutboundCustomer> customers;
|
||||
final StockTransaction? raw;
|
||||
|
||||
int get customerCount => customers.length;
|
||||
|
||||
int get itemCount => items.length;
|
||||
|
||||
int get totalQuantity =>
|
||||
items.fold<int>(0, (sum, item) => sum + item.quantity);
|
||||
|
||||
double get totalAmount =>
|
||||
items.fold<double>(0, (sum, item) => sum + (item.price * item.quantity));
|
||||
}
|
||||
|
||||
/// 출고 상세 모달에 표시되는 품목 정보.
|
||||
class OutboundLineItem {
|
||||
OutboundLineItem({
|
||||
this.id,
|
||||
int? lineNo,
|
||||
int? productId,
|
||||
String? productCode,
|
||||
required this.product,
|
||||
required this.manufacturer,
|
||||
required this.unit,
|
||||
required this.quantity,
|
||||
required this.price,
|
||||
required this.remark,
|
||||
}) : lineNo = lineNo ?? 0,
|
||||
productId = productId ?? 0,
|
||||
productCode = productCode ?? '';
|
||||
|
||||
factory OutboundLineItem.fromLine(StockTransactionLine line) {
|
||||
final product = line.product;
|
||||
return OutboundLineItem(
|
||||
id: line.id,
|
||||
lineNo: line.lineNo,
|
||||
productId: product.id,
|
||||
productCode: product.code,
|
||||
product: product.name,
|
||||
manufacturer: product.vendor?.name ?? '-',
|
||||
unit: product.uom?.name ?? '-',
|
||||
quantity: line.quantity,
|
||||
price: line.unitPrice,
|
||||
remark: line.note ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
final int? id;
|
||||
final int lineNo;
|
||||
final int productId;
|
||||
final String productCode;
|
||||
final String product;
|
||||
final String manufacturer;
|
||||
final String unit;
|
||||
final int quantity;
|
||||
final double price;
|
||||
final String remark;
|
||||
}
|
||||
|
||||
/// 출고 고객 연결 요약 정보.
|
||||
class OutboundCustomer {
|
||||
OutboundCustomer({
|
||||
this.id,
|
||||
required this.customerId,
|
||||
required this.code,
|
||||
required this.name,
|
||||
this.note,
|
||||
});
|
||||
|
||||
factory OutboundCustomer.fromLink(StockTransactionCustomer link) {
|
||||
final target = link.customer;
|
||||
return OutboundCustomer(
|
||||
id: link.id,
|
||||
customerId: target.id,
|
||||
code: target.code,
|
||||
name: target.name,
|
||||
note: link.note,
|
||||
);
|
||||
}
|
||||
|
||||
final int? id;
|
||||
final int customerId;
|
||||
final String code;
|
||||
final String name;
|
||||
final String? note;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/// 출고 테이블과 필터 구성을 위한 정적 스펙을 정의한다.
|
||||
class OutboundTableSpec {
|
||||
const OutboundTableSpec._();
|
||||
|
||||
/// 목록 헤더 라벨.
|
||||
static const List<String> headers = [
|
||||
'번호',
|
||||
'처리일자',
|
||||
'창고',
|
||||
'트랜잭션번호',
|
||||
'제품',
|
||||
'제조사',
|
||||
'단위',
|
||||
'수량',
|
||||
'단가',
|
||||
'상태',
|
||||
'작성자',
|
||||
'고객수',
|
||||
'품목수',
|
||||
'총수량',
|
||||
'비고',
|
||||
];
|
||||
|
||||
/// 페이지네이션에서 제공하는 항목 수 옵션.
|
||||
static const List<int> pageSizeOptions = [10, 20, 50];
|
||||
|
||||
/// include 파라미터 기본값.
|
||||
static const List<String> defaultIncludeOptions = ['lines', 'customers'];
|
||||
|
||||
/// 백엔드 미응답 시 사용할 기본 상태 라벨.
|
||||
static const List<String> fallbackStatusOptions = ['작성중', '출고대기', '출고완료'];
|
||||
|
||||
/// 출고 트랜잭션 타입 탐색용 키워드.
|
||||
static const List<String> transactionTypeKeywords = ['출고', 'outbound'];
|
||||
|
||||
/// 검색 필드 플레이스홀더.
|
||||
static const String searchPlaceholder = '트랜잭션번호, 작성자, 제품, 고객사 검색';
|
||||
|
||||
/// 창고 전체 라벨.
|
||||
static const String allWarehouseLabel = '전체 창고';
|
||||
|
||||
/// 상태 전체 라벨.
|
||||
static const String allStatusLabel = '전체 상태';
|
||||
|
||||
/// include 선택이 비어 있을 때 보여줄 라벨.
|
||||
static const String includeEmptyLabel = 'Include 없음';
|
||||
|
||||
/// 테이블 열 폭 기준 값.
|
||||
static const double columnSpanWidth = 140;
|
||||
|
||||
/// 테이블 행 높이 기준 값.
|
||||
static const double rowSpanHeight = 56;
|
||||
|
||||
/// 날짜 필터 시작 범위.
|
||||
static final DateTime dateRangeFirstDate = DateTime(2020);
|
||||
|
||||
/// 날짜 필터 종료 범위.
|
||||
static final DateTime dateRangeLastDate = DateTime(2030);
|
||||
|
||||
/// include 파라미터에 대응하는 라벨을 반환한다.
|
||||
static String includeLabel(String value) {
|
||||
switch (value) {
|
||||
case 'lines':
|
||||
return '라인 포함';
|
||||
case 'customers':
|
||||
return '고객 포함';
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user