결재 비활성 안내 개선 및 테이블 기능 보강

This commit is contained in:
JiWoong Sul
2025-09-29 15:49:06 +09:00
parent fef7108479
commit 98724762ec
18 changed files with 1134 additions and 297 deletions

View File

@@ -837,6 +837,8 @@ class _InboundPageState extends State<InboundPage> {
};
String? writerError;
String? warehouseError;
String? statusError;
String? headerNotice;
void Function(VoidCallback fn)? refreshForm;
@@ -847,10 +849,14 @@ class _InboundPageState extends State<InboundPage> {
void handleSubmit() {
final validationResult = _validateInboundForm(
writerController: writerController,
warehouseValue: warehouseController.text,
statusValue: statusValue.value,
drafts: drafts,
lineErrors: lineErrors,
);
writerError = validationResult.writerError;
warehouseError = validationResult.warehouseError;
statusError = validationResult.statusError;
headerNotice = validationResult.headerNotice;
refreshForm?.call(() {});
@@ -871,6 +877,7 @@ class _InboundPageState extends State<InboundPage> {
),
)
.toList();
items.sort((a, b) => a.product.compareTo(b.product));
result = InboundRecord(
number: initial?.number ?? _generateInboundNumber(processedAt.value),
transactionNumber:
@@ -936,6 +943,7 @@ class _InboundPageState extends State<InboundPage> {
child: SuperportFormField(
label: '창고',
required: true,
errorText: warehouseError,
child: ShadSelect<String>(
initialValue: warehouseController.text,
selectedOptionBuilder: (context, value) =>
@@ -943,7 +951,11 @@ class _InboundPageState extends State<InboundPage> {
onChanged: (value) {
if (value != null) {
warehouseController.text = value;
setState(() {});
setState(() {
if (warehouseError != null) {
warehouseError = null;
}
});
}
},
options: _warehouseOptions
@@ -962,6 +974,7 @@ class _InboundPageState extends State<InboundPage> {
child: SuperportFormField(
label: '상태',
required: true,
errorText: statusError,
child: ShadSelect<String>(
initialValue: statusValue.value,
selectedOptionBuilder: (context, value) =>
@@ -969,7 +982,11 @@ class _InboundPageState extends State<InboundPage> {
onChanged: (value) {
if (value != null) {
statusValue.value = value;
setState(() {});
setState(() {
if (statusError != null) {
statusError = null;
}
});
}
},
enabled: initial?.status != '승인완료',
@@ -1525,11 +1542,15 @@ class _LineItemFieldErrors {
_InboundFormValidation _validateInboundForm({
required TextEditingController writerController,
required String warehouseValue,
required String statusValue,
required List<_LineItemDraft> drafts,
required Map<_LineItemDraft, _LineItemFieldErrors> lineErrors,
}) {
var isValid = true;
String? writerError;
String? warehouseError;
String? statusError;
String? headerNotice;
if (writerController.text.trim().isEmpty) {
@@ -1537,12 +1558,24 @@ _InboundFormValidation _validateInboundForm({
isValid = false;
}
if (warehouseValue.trim().isEmpty) {
warehouseError = '창고를 선택하세요.';
isValid = false;
}
if (statusValue.trim().isEmpty) {
statusError = '상태를 선택하세요.';
isValid = false;
}
var hasLineError = false;
final seenProductKeys = <String>{};
for (final draft in drafts) {
final errors = lineErrors.putIfAbsent(draft, _LineItemFieldErrors.empty);
errors.clearAll();
if (draft.product.text.trim().isEmpty) {
final productText = draft.product.text.trim();
if (productText.isEmpty) {
errors.product = '제품을 입력하세요.';
hasLineError = true;
isValid = false;
@@ -1552,8 +1585,15 @@ _InboundFormValidation _validateInboundForm({
isValid = false;
}
final productKey = (draft.catalogMatch?.code ?? productText.toLowerCase());
if (productKey.isNotEmpty && !seenProductKeys.add(productKey)) {
errors.product = '동일 제품이 중복되었습니다.';
hasLineError = true;
isValid = false;
}
final quantity = int.tryParse(
draft.quantity.text.trim() == '' ? '0' : draft.quantity.text.trim(),
draft.quantity.text.trim().isEmpty ? '0' : draft.quantity.text.trim(),
);
if (quantity == null || quantity < 1) {
errors.quantity = '수량은 1 이상 정수여야 합니다.';
@@ -1576,6 +1616,8 @@ _InboundFormValidation _validateInboundForm({
return _InboundFormValidation(
isValid: isValid,
writerError: writerError,
warehouseError: warehouseError,
statusError: statusError,
headerNotice: headerNotice,
);
}
@@ -1589,11 +1631,15 @@ class _InboundFormValidation {
const _InboundFormValidation({
required this.isValid,
this.writerError,
this.warehouseError,
this.statusError,
this.headerNotice,
});
final bool isValid;
final String? writerError;
final String? warehouseError;
final String? statusError;
final String? headerNotice;
}

View File

@@ -844,6 +844,8 @@ class _OutboundPageState extends State<OutboundPage> {
String? writerError;
String? customerError;
String? warehouseError;
String? statusError;
String? headerNotice;
String customerSearchQuery = '';
StateSetter? refreshForm;
@@ -855,6 +857,8 @@ class _OutboundPageState extends State<OutboundPage> {
void handleSubmit() {
final validation = _validateOutboundForm(
writerController: writerController,
warehouseValue: warehouseController.text,
statusValue: statusValue.value,
customerController: customerController,
drafts: drafts,
lineErrors: lineErrors,
@@ -862,6 +866,8 @@ class _OutboundPageState extends State<OutboundPage> {
writerError = validation.writerError;
customerError = validation.customerError;
warehouseError = validation.warehouseError;
statusError = validation.statusError;
headerNotice = validation.headerNotice;
refreshForm?.call(() {});
@@ -882,6 +888,7 @@ class _OutboundPageState extends State<OutboundPage> {
),
)
.toList();
items.sort((a, b) => a.product.compareTo(b.product));
result = OutboundRecord(
number: initial?.number ?? _generateOutboundNumber(processedAt.value),
transactionNumber:
@@ -948,6 +955,7 @@ class _OutboundPageState extends State<OutboundPage> {
child: SuperportFormField(
label: '창고',
required: true,
errorText: warehouseError,
child: ShadSelect<String>(
initialValue: warehouseController.text,
selectedOptionBuilder: (context, value) =>
@@ -955,7 +963,11 @@ class _OutboundPageState extends State<OutboundPage> {
onChanged: (value) {
if (value != null) {
warehouseController.text = value;
setState(() {});
setState(() {
if (warehouseError != null) {
warehouseError = null;
}
});
}
},
options: [
@@ -970,6 +982,7 @@ class _OutboundPageState extends State<OutboundPage> {
child: SuperportFormField(
label: '상태',
required: true,
errorText: statusError,
child: ShadSelect<String>(
initialValue: statusValue.value,
selectedOptionBuilder: (context, value) =>
@@ -977,7 +990,11 @@ class _OutboundPageState extends State<OutboundPage> {
onChanged: (value) {
if (value != null) {
statusValue.value = value;
setState(() {});
setState(() {
if (statusError != null) {
statusError = null;
}
});
}
},
options: [
@@ -1609,6 +1626,8 @@ double _parseCurrency(String input) {
_OutboundFormValidation _validateOutboundForm({
required TextEditingController writerController,
required String warehouseValue,
required String statusValue,
required ShadSelectController<String> customerController,
required List<_OutboundLineItemDraft> drafts,
required Map<_OutboundLineItemDraft, _OutboundLineErrors> lineErrors,
@@ -1616,6 +1635,8 @@ _OutboundFormValidation _validateOutboundForm({
var isValid = true;
String? writerError;
String? customerError;
String? warehouseError;
String? statusError;
String? headerNotice;
if (writerController.text.trim().isEmpty) {
@@ -1623,17 +1644,29 @@ _OutboundFormValidation _validateOutboundForm({
isValid = false;
}
if (warehouseValue.trim().isEmpty) {
warehouseError = '창고를 선택하세요.';
isValid = false;
}
if (statusValue.trim().isEmpty) {
statusError = '상태를 선택하세요.';
isValid = false;
}
if (customerController.value.isEmpty) {
customerError = '최소 1개의 고객사를 선택하세요.';
isValid = false;
}
var hasLineError = false;
final seenProductKeys = <String>{};
for (final draft in drafts) {
final errors = lineErrors.putIfAbsent(draft, _OutboundLineErrors.empty);
errors.clearAll();
if (draft.product.text.trim().isEmpty) {
final productText = draft.product.text.trim();
if (productText.isEmpty) {
errors.product = '제품을 입력하세요.';
hasLineError = true;
isValid = false;
@@ -1643,6 +1676,13 @@ _OutboundFormValidation _validateOutboundForm({
isValid = false;
}
final productKey = draft.catalogMatch?.code ?? productText.toLowerCase();
if (productKey.isNotEmpty && !seenProductKeys.add(productKey)) {
errors.product = '동일 제품이 중복되었습니다.';
hasLineError = true;
isValid = false;
}
final quantity = int.tryParse(
draft.quantity.text.trim().isEmpty ? '0' : draft.quantity.text.trim(),
);
@@ -1668,6 +1708,8 @@ _OutboundFormValidation _validateOutboundForm({
isValid: isValid,
writerError: writerError,
customerError: customerError,
warehouseError: warehouseError,
statusError: statusError,
headerNotice: headerNotice,
);
}
@@ -1677,12 +1719,16 @@ class _OutboundFormValidation {
required this.isValid,
this.writerError,
this.customerError,
this.warehouseError,
this.statusError,
this.headerNotice,
});
final bool isValid;
final String? writerError;
final String? customerError;
final String? warehouseError;
final String? statusError;
final String? headerNotice;
}

View File

@@ -928,6 +928,8 @@ class _RentalPageState extends State<RentalPage> {
String customerSearchQuery = '';
String? writerError;
String? customerError;
String? warehouseError;
String? statusError;
String? headerNotice;
void Function(VoidCallback fn)? refreshForm;
@@ -940,6 +942,8 @@ class _RentalPageState extends State<RentalPage> {
void handleSubmit() {
final validation = _validateRentalForm(
writerController: writerController,
warehouseValue: warehouseController.text,
statusValue: statusValue.value,
customerController: customerController,
drafts: drafts,
lineErrors: lineErrors,
@@ -947,6 +951,8 @@ class _RentalPageState extends State<RentalPage> {
writerError = validation.writerError;
customerError = validation.customerError;
warehouseError = validation.warehouseError;
statusError = validation.statusError;
headerNotice = validation.headerNotice;
refreshForm?.call(() {});
@@ -972,6 +978,7 @@ class _RentalPageState extends State<RentalPage> {
),
)
.toList();
items.sort((a, b) => a.product.compareTo(b.product));
result = RentalRecord(
number: initial?.number ?? _generateRentalNumber(processedAt.value),
transactionNumber:
@@ -1062,8 +1069,10 @@ class _RentalPageState extends State<RentalPage> {
),
SizedBox(
width: 220,
child: _FormFieldLabel(
child: SuperportFormField(
label: '창고',
required: true,
errorText: warehouseError,
child: ShadSelect<String>(
initialValue: warehouseController.text,
selectedOptionBuilder: (context, value) =>
@@ -1071,7 +1080,11 @@ class _RentalPageState extends State<RentalPage> {
onChanged: (value) {
if (value != null) {
warehouseController.text = value;
setState(() {});
setState(() {
if (warehouseError != null) {
warehouseError = null;
}
});
}
},
options: _warehouseOptions
@@ -1087,8 +1100,10 @@ class _RentalPageState extends State<RentalPage> {
),
SizedBox(
width: 240,
child: _FormFieldLabel(
child: SuperportFormField(
label: '상태',
required: true,
errorText: statusError,
child: ShadSelect<String>(
initialValue: statusValue.value,
selectedOptionBuilder: (context, value) =>
@@ -1096,7 +1111,11 @@ class _RentalPageState extends State<RentalPage> {
onChanged: (value) {
if (value != null) {
statusValue.value = value;
setState(() {});
setState(() {
if (statusError != null) {
statusError = null;
}
});
}
},
enabled: initial?.status != '완료',
@@ -1833,6 +1852,8 @@ class RentalRecord {
_RentalFormValidation _validateRentalForm({
required TextEditingController writerController,
required String warehouseValue,
required String statusValue,
required ShadSelectController<String> customerController,
required List<_RentalLineItemDraft> drafts,
required Map<_RentalLineItemDraft, _RentalLineItemErrors> lineErrors,
@@ -1840,6 +1861,8 @@ _RentalFormValidation _validateRentalForm({
var isValid = true;
String? writerError;
String? customerError;
String? warehouseError;
String? statusError;
String? headerNotice;
if (writerController.text.trim().isEmpty) {
@@ -1847,17 +1870,29 @@ _RentalFormValidation _validateRentalForm({
isValid = false;
}
if (warehouseValue.trim().isEmpty) {
warehouseError = '창고를 선택하세요.';
isValid = false;
}
if (statusValue.trim().isEmpty) {
statusError = '상태를 선택하세요.';
isValid = false;
}
if (customerController.value.isEmpty) {
customerError = '최소 1개의 고객사를 선택하세요.';
isValid = false;
}
var hasLineError = false;
final seenProductKeys = <String>{};
for (final draft in drafts) {
final errors = lineErrors.putIfAbsent(draft, _RentalLineItemErrors.empty);
errors.clearAll();
if (draft.product.text.trim().isEmpty) {
final productText = draft.product.text.trim();
if (productText.isEmpty) {
errors.product = '제품을 입력하세요.';
hasLineError = true;
isValid = false;
@@ -1867,6 +1902,13 @@ _RentalFormValidation _validateRentalForm({
isValid = false;
}
final productKey = draft.catalogMatch?.code ?? productText.toLowerCase();
if (productKey.isNotEmpty && !seenProductKeys.add(productKey)) {
errors.product = '동일 제품이 중복되었습니다.';
hasLineError = true;
isValid = false;
}
final quantity = int.tryParse(
draft.quantity.text.trim().isEmpty ? '0' : draft.quantity.text.trim(),
);
@@ -1892,6 +1934,8 @@ _RentalFormValidation _validateRentalForm({
isValid: isValid,
writerError: writerError,
customerError: customerError,
warehouseError: warehouseError,
statusError: statusError,
headerNotice: headerNotice,
);
}
@@ -1901,12 +1945,16 @@ class _RentalFormValidation {
required this.isValid,
this.writerError,
this.customerError,
this.warehouseError,
this.statusError,
this.headerNotice,
});
final bool isValid;
final String? writerError;
final String? customerError;
final String? warehouseError;
final String? statusError;
final String? headerNotice;
}