|
@@ -11,6 +11,7 @@ import '../../core/theme/app_colors.dart';
|
|
|
import '../../core/theme/app_colors_extension.dart';
|
|
import '../../core/theme/app_colors_extension.dart';
|
|
|
import '../../core/constants/enums.dart';
|
|
import '../../core/constants/enums.dart';
|
|
|
import '../../core/data/mock_api_data.dart';
|
|
import '../../core/data/mock_api_data.dart';
|
|
|
|
|
+import 'widgets/expense_detail_dialog.dart';
|
|
|
|
|
|
|
|
class ExpenseApplicationApplyPage extends ConsumerStatefulWidget {
|
|
class ExpenseApplicationApplyPage extends ConsumerStatefulWidget {
|
|
|
final String? id;
|
|
final String? id;
|
|
@@ -325,7 +326,17 @@ class _ExpenseApplicationApplyPageState
|
|
|
value: _estimatedEndDate,
|
|
value: _estimatedEndDate,
|
|
|
hint: l10n.get('pleaseSelect'),
|
|
hint: l10n.get('pleaseSelect'),
|
|
|
required: true,
|
|
required: true,
|
|
|
- onTap: () => _pickDate((d) => setState(() => _estimatedEndDate = d)),
|
|
|
|
|
|
|
+ onTap: () => _pickDate((d) {
|
|
|
|
|
+ if (_estimatedStartDate.isNotEmpty &&
|
|
|
|
|
+ _estimatedStartDate.compareTo(d) > 0) {
|
|
|
|
|
+ TDToast.showText(
|
|
|
|
|
+ l10n.get('startDateNotAfterEndDate'),
|
|
|
|
|
+ context: context,
|
|
|
|
|
+ );
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ setState(() => _estimatedEndDate = d);
|
|
|
|
|
+ }),
|
|
|
),
|
|
),
|
|
|
const SizedBox(height: 16),
|
|
const SizedBox(height: 16),
|
|
|
Row(
|
|
Row(
|
|
@@ -448,7 +459,17 @@ class _ExpenseApplicationApplyPageState
|
|
|
value: _meetingEndDate,
|
|
value: _meetingEndDate,
|
|
|
hint: l10n.get('pleaseSelect'),
|
|
hint: l10n.get('pleaseSelect'),
|
|
|
required: true,
|
|
required: true,
|
|
|
- onTap: () => _pickDate((d) => setState(() => _meetingEndDate = d)),
|
|
|
|
|
|
|
+ onTap: () => _pickDate((d) {
|
|
|
|
|
+ if (_meetingStartDate.isNotEmpty &&
|
|
|
|
|
+ _meetingStartDate.compareTo(d) > 0) {
|
|
|
|
|
+ TDToast.showText(
|
|
|
|
|
+ l10n.get('startDateNotAfterEndDate'),
|
|
|
|
|
+ context: context,
|
|
|
|
|
+ );
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ setState(() => _meetingEndDate = d);
|
|
|
|
|
+ }),
|
|
|
),
|
|
),
|
|
|
const SizedBox(height: 16),
|
|
const SizedBox(height: 16),
|
|
|
FormFieldRow(
|
|
FormFieldRow(
|
|
@@ -466,60 +487,72 @@ class _ExpenseApplicationApplyPageState
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ═══ 3. 关联管控 ═══
|
|
// ═══ 3. 关联管控 ═══
|
|
|
|
|
+ List<Map<String, dynamic>> _buildCascadeData() {
|
|
|
|
|
+ return mockProjects
|
|
|
|
|
+ .map(
|
|
|
|
|
+ (p) => <String, dynamic>{
|
|
|
|
|
+ 'label': p.name,
|
|
|
|
|
+ 'value': p.id.toString(),
|
|
|
|
|
+ 'children': mockBudgetSubjects
|
|
|
|
|
+ .map(
|
|
|
|
|
+ (s) => <String, dynamic>{
|
|
|
|
|
+ 'label': s.name,
|
|
|
|
|
+ 'value': s.id.toString(),
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ .toList(),
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ .toList();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
Widget _buildControlSection(AppLocalizations l10n) {
|
|
Widget _buildControlSection(AppLocalizations l10n) {
|
|
|
|
|
+ final cascadeLabel = _selectedProjectName != null &&
|
|
|
|
|
+ _selectedSubjectName != null
|
|
|
|
|
+ ? '$_selectedProjectName / $_selectedSubjectName'
|
|
|
|
|
+ : _selectedProjectName;
|
|
|
return FormSection(
|
|
return FormSection(
|
|
|
title: l10n.get('relatedControl'),
|
|
title: l10n.get('relatedControl'),
|
|
|
leadingIcon: Icons.link_outlined,
|
|
leadingIcon: Icons.link_outlined,
|
|
|
children: [
|
|
children: [
|
|
|
FormFieldRow(
|
|
FormFieldRow(
|
|
|
label: l10n.get('relatedProject'),
|
|
label: l10n.get('relatedProject'),
|
|
|
- value: _selectedProjectName,
|
|
|
|
|
- hint: l10n.get('selectProject'),
|
|
|
|
|
|
|
+ value: cascadeLabel,
|
|
|
|
|
+ hint: l10n.get('selectProjectAndSubject'),
|
|
|
required: true,
|
|
required: true,
|
|
|
onTap: () {
|
|
onTap: () {
|
|
|
- _showListPicker(
|
|
|
|
|
- l10n.get('selectProject'),
|
|
|
|
|
- mockProjects.map((p) => p.name).toList(),
|
|
|
|
|
- (v) {
|
|
|
|
|
- final p = mockProjects.firstWhere((x) => x.name == v);
|
|
|
|
|
- setState(() {
|
|
|
|
|
- _selectedProjectId = p.id;
|
|
|
|
|
- _selectedProjectName = p.name;
|
|
|
|
|
- _selectedSubjectName = null;
|
|
|
|
|
- _selectedSubjectId = null;
|
|
|
|
|
- _availableBudget = 0;
|
|
|
|
|
- });
|
|
|
|
|
- },
|
|
|
|
|
- );
|
|
|
|
|
- },
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(height: 16),
|
|
|
|
|
- FormFieldRow(
|
|
|
|
|
- label: l10n.get('budgetSubject'),
|
|
|
|
|
- value: _selectedSubjectName,
|
|
|
|
|
- hint: l10n.get('selectSubject'),
|
|
|
|
|
- required: true,
|
|
|
|
|
- onTap: _selectedProjectId != null
|
|
|
|
|
- ? () {
|
|
|
|
|
- _showListPicker(
|
|
|
|
|
- l10n.get('selectSubject'),
|
|
|
|
|
- mockBudgetSubjects.map((s) => s.name).toList(),
|
|
|
|
|
- (v) {
|
|
|
|
|
- final s = mockBudgetSubjects.firstWhere(
|
|
|
|
|
- (x) => x.name == v,
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ _unfocus();
|
|
|
|
|
+ TDCascader.showMultiCascader(
|
|
|
|
|
+ context,
|
|
|
|
|
+ title: l10n.get('selectProjectAndSubject'),
|
|
|
|
|
+ data: _buildCascadeData(),
|
|
|
|
|
+ subTitles: [
|
|
|
|
|
+ l10n.get('project'),
|
|
|
|
|
+ l10n.get('budgetSubject'),
|
|
|
|
|
+ ],
|
|
|
|
|
+ onClose: () => Navigator.of(context).pop(),
|
|
|
|
|
+ onChange: (selected) {
|
|
|
|
|
+ if (selected.length >= 2) {
|
|
|
|
|
+ final pId =
|
|
|
|
|
+ int.tryParse(selected[0].value ?? '');
|
|
|
|
|
+ final sId =
|
|
|
|
|
+ int.tryParse(selected[1].value ?? '');
|
|
|
|
|
+ if (pId != null && sId != null) {
|
|
|
setState(() {
|
|
setState(() {
|
|
|
- _selectedSubjectId = s.id;
|
|
|
|
|
- _selectedSubjectName = s.name;
|
|
|
|
|
- _availableBudget = getMockBudget(
|
|
|
|
|
- _selectedProjectId!,
|
|
|
|
|
- s.id,
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ _selectedProjectId = pId;
|
|
|
|
|
+ _selectedProjectName =
|
|
|
|
|
+ selected[0].label;
|
|
|
|
|
+ _selectedSubjectId = sId;
|
|
|
|
|
+ _selectedSubjectName =
|
|
|
|
|
+ selected[1].label;
|
|
|
|
|
+ _availableBudget =
|
|
|
|
|
+ getMockBudget(pId, sId);
|
|
|
});
|
|
});
|
|
|
- },
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- : null,
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
+ },
|
|
|
),
|
|
),
|
|
|
const SizedBox(height: 16),
|
|
const SizedBox(height: 16),
|
|
|
_buildBudgetRow(l10n),
|
|
_buildBudgetRow(l10n),
|
|
@@ -738,259 +771,30 @@ class _ExpenseApplicationApplyPageState
|
|
|
return mockCostCategories.where((c) => codes.contains(c.code)).toList();
|
|
return mockCostCategories.where((c) => codes.contains(c.code)).toList();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- void _showDetailDialog() {
|
|
|
|
|
- _unfocus();
|
|
|
|
|
|
|
+ Future<void> _showDetailDialog() async {
|
|
|
final l10n = AppLocalizations.of(context);
|
|
final l10n = AppLocalizations.of(context);
|
|
|
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
|
|
|
|
|
- final cats = _availableDetailCategories;
|
|
|
|
|
- String cat = cats.isNotEmpty ? cats.first.code : 'other';
|
|
|
|
|
- String unit = l10n.get('unitPiece');
|
|
|
|
|
- String catLabel = l10n.get(cats.firstWhere((c) => c.code == cat).nameKey);
|
|
|
|
|
- String unitLabel = l10n.get('unitPiece');
|
|
|
|
|
- final qtyCtrl = TextEditingController(text: '1');
|
|
|
|
|
- final priceCtrl = TextEditingController();
|
|
|
|
|
- final remarkCtrl = TextEditingController();
|
|
|
|
|
-
|
|
|
|
|
- showModalBottomSheet(
|
|
|
|
|
- context: context,
|
|
|
|
|
- isScrollControlled: true,
|
|
|
|
|
- backgroundColor: colors.bgCard,
|
|
|
|
|
- shape: const RoundedRectangleBorder(
|
|
|
|
|
- borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
|
|
|
|
- ),
|
|
|
|
|
- builder: (ctx) => StatefulBuilder(
|
|
|
|
|
- builder: (ctx, setDlg) => Padding(
|
|
|
|
|
- padding: EdgeInsets.only(
|
|
|
|
|
- left: 16,
|
|
|
|
|
- right: 16,
|
|
|
|
|
- top: 16,
|
|
|
|
|
- bottom: 16 + MediaQuery.of(ctx).viewInsets.bottom,
|
|
|
|
|
- ),
|
|
|
|
|
- child: Column(
|
|
|
|
|
- mainAxisSize: MainAxisSize.min,
|
|
|
|
|
- crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
|
|
|
- children: [
|
|
|
|
|
- Center(
|
|
|
|
|
- child: Text(
|
|
|
|
|
- l10n.get('addExpenseDetail'),
|
|
|
|
|
- style: TextStyle(
|
|
|
|
|
- fontSize: 18,
|
|
|
|
|
- fontWeight: FontWeight.w600,
|
|
|
|
|
- color: colors.textPrimary,
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(height: 16),
|
|
|
|
|
- // 费用类别 — 左右布局,必填
|
|
|
|
|
- Row(
|
|
|
|
|
- crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
- children: [
|
|
|
|
|
- SizedBox(
|
|
|
|
|
- width: 80,
|
|
|
|
|
- child: _label(l10n.get('expenseCategory'), required: true),
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(width: 8),
|
|
|
|
|
- Expanded(
|
|
|
|
|
- child: Container(
|
|
|
|
|
- clipBehavior: Clip.antiAlias,
|
|
|
|
|
- padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
|
|
|
- decoration: BoxDecoration(
|
|
|
|
|
- borderRadius: BorderRadius.circular(8),
|
|
|
|
|
- border: Border.all(color: colors.border, width: 1),
|
|
|
|
|
- ),
|
|
|
|
|
- child: TDDropdownMenu(
|
|
|
|
|
- items: [
|
|
|
|
|
- TDDropdownItem(
|
|
|
|
|
- label: catLabel,
|
|
|
|
|
- options: cats
|
|
|
|
|
- .map(
|
|
|
|
|
- (c) => TDDropdownItemOption(
|
|
|
|
|
- value: c.code,
|
|
|
|
|
- label: l10n.get(c.nameKey),
|
|
|
|
|
- ),
|
|
|
|
|
- )
|
|
|
|
|
- .toList(),
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
- closeOnClickOverlay: true,
|
|
|
|
|
- onMenuClosed: (index) {
|
|
|
|
|
- if (index >= 0 && index < cats.length) {
|
|
|
|
|
- cat = cats[index].code;
|
|
|
|
|
- setDlg(() {});
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(height: 12),
|
|
|
|
|
- // 数量 — 左右布局,必填
|
|
|
|
|
- Row(
|
|
|
|
|
- crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
- children: [
|
|
|
|
|
- SizedBox(
|
|
|
|
|
- width: 80,
|
|
|
|
|
- child: _label(l10n.get('quantity'), required: true),
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(width: 8),
|
|
|
|
|
- Expanded(
|
|
|
|
|
- child: Container(
|
|
|
|
|
- clipBehavior: Clip.antiAlias,
|
|
|
|
|
- padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
|
|
|
- decoration: BoxDecoration(
|
|
|
|
|
- borderRadius: BorderRadius.circular(8),
|
|
|
|
|
- border: Border.all(color: colors.border, width: 1),
|
|
|
|
|
- ),
|
|
|
|
|
- child: TDInput(
|
|
|
|
|
- controller: qtyCtrl,
|
|
|
|
|
- hintText: '>0',
|
|
|
|
|
- contentAlignment: TextAlign.center,
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(height: 12),
|
|
|
|
|
- // 单位 — 左右布局
|
|
|
|
|
- Row(
|
|
|
|
|
- crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
- children: [
|
|
|
|
|
- SizedBox(width: 80, child: _label(l10n.get('unit'))),
|
|
|
|
|
- const SizedBox(width: 8),
|
|
|
|
|
- Expanded(
|
|
|
|
|
- child: Container(
|
|
|
|
|
- clipBehavior: Clip.antiAlias,
|
|
|
|
|
- padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
|
|
|
- decoration: BoxDecoration(
|
|
|
|
|
- borderRadius: BorderRadius.circular(8),
|
|
|
|
|
- border: Border.all(color: colors.border, width: 1),
|
|
|
|
|
- ),
|
|
|
|
|
- child: TDDropdownMenu(
|
|
|
|
|
- items: [
|
|
|
|
|
- TDDropdownItem(
|
|
|
|
|
- label: unitLabel,
|
|
|
|
|
- options: unitOptions
|
|
|
|
|
- .map(
|
|
|
|
|
- (u) => TDDropdownItemOption(
|
|
|
|
|
- value: u,
|
|
|
|
|
- label: l10n.get(u),
|
|
|
|
|
- ),
|
|
|
|
|
- )
|
|
|
|
|
- .toList(),
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
- closeOnClickOverlay: true,
|
|
|
|
|
- onMenuClosed: (index) {
|
|
|
|
|
- if (index >= 0 && index < unitOptions.length) {
|
|
|
|
|
- unit = l10n.get(unitOptions[index]);
|
|
|
|
|
- setDlg(() {});
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(height: 12),
|
|
|
|
|
- // 单价 — 左右布局,必填
|
|
|
|
|
- Row(
|
|
|
|
|
- crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
- children: [
|
|
|
|
|
- SizedBox(
|
|
|
|
|
- width: 80,
|
|
|
|
|
- child: _label(l10n.get('unitPrice'), required: true),
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(width: 8),
|
|
|
|
|
- Expanded(
|
|
|
|
|
- child: Container(
|
|
|
|
|
- clipBehavior: Clip.antiAlias,
|
|
|
|
|
- padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
|
|
|
- decoration: BoxDecoration(
|
|
|
|
|
- borderRadius: BorderRadius.circular(8),
|
|
|
|
|
- border: Border.all(color: colors.border, width: 1),
|
|
|
|
|
- ),
|
|
|
|
|
- child: TDInput(
|
|
|
|
|
- controller: priceCtrl,
|
|
|
|
|
- hintText: '>0',
|
|
|
|
|
- contentAlignment: TextAlign.center,
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(height: 12),
|
|
|
|
|
- _label(l10n.get('detailRemark')),
|
|
|
|
|
- const SizedBox(height: 8),
|
|
|
|
|
- TDTextarea(
|
|
|
|
|
- controller: remarkCtrl,
|
|
|
|
|
- hintText: l10n.get('optional'),
|
|
|
|
|
- maxLines: 3,
|
|
|
|
|
- minLines: 1,
|
|
|
|
|
- maxLength: 200,
|
|
|
|
|
- indicator: true,
|
|
|
|
|
- padding: EdgeInsets.zero,
|
|
|
|
|
- bordered: true,
|
|
|
|
|
- backgroundColor: colors.bgPage,
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(height: 16),
|
|
|
|
|
- Row(
|
|
|
|
|
- children: [
|
|
|
|
|
- Expanded(
|
|
|
|
|
- child: TDButton(
|
|
|
|
|
- text: l10n.get('cancel'),
|
|
|
|
|
- size: TDButtonSize.large,
|
|
|
|
|
- type: TDButtonType.outline,
|
|
|
|
|
- shape: TDButtonShape.rectangle,
|
|
|
|
|
- theme: TDButtonTheme.defaultTheme,
|
|
|
|
|
- onTap: () => Navigator.pop(ctx),
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- const SizedBox(width: 12),
|
|
|
|
|
- Expanded(
|
|
|
|
|
- child: TDButton(
|
|
|
|
|
- text: l10n.get('confirm'),
|
|
|
|
|
- size: TDButtonSize.large,
|
|
|
|
|
- type: TDButtonType.fill,
|
|
|
|
|
- shape: TDButtonShape.rectangle,
|
|
|
|
|
- theme: TDButtonTheme.primary,
|
|
|
|
|
- onTap: () {
|
|
|
|
|
- final q = int.tryParse(qtyCtrl.text) ?? 0;
|
|
|
|
|
- final p = double.tryParse(priceCtrl.text) ?? 0;
|
|
|
|
|
- if (q <= 0 || p <= 0) {
|
|
|
|
|
- TDToast.showText(
|
|
|
|
|
- l10n.get('quantityPricePositive'),
|
|
|
|
|
- context: context,
|
|
|
|
|
- );
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- setState(
|
|
|
|
|
- () => _details.add(
|
|
|
|
|
- _DetailItem(
|
|
|
|
|
- id: _detailIdCounter++,
|
|
|
|
|
- category: cat,
|
|
|
|
|
- categoryName: l10n.get(
|
|
|
|
|
- cats.firstWhere((c) => c.code == cat).nameKey,
|
|
|
|
|
- ),
|
|
|
|
|
- quantity: q,
|
|
|
|
|
- unit: unit,
|
|
|
|
|
- unitPrice: p,
|
|
|
|
|
- amount: q * p.toDouble(),
|
|
|
|
|
- remark: remarkCtrl.text,
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- );
|
|
|
|
|
- Navigator.pop(ctx);
|
|
|
|
|
- },
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
|
|
+ final result = await ExpenseDetailDialog.show(
|
|
|
|
|
+ context,
|
|
|
|
|
+ categories: _availableDetailCategories,
|
|
|
|
|
+ unitKeys: unitOptions,
|
|
|
|
|
+ l10n: l10n,
|
|
|
|
|
+ );
|
|
|
|
|
+ if (result != null && mounted) {
|
|
|
|
|
+ setState(
|
|
|
|
|
+ () => _details.add(
|
|
|
|
|
+ _DetailItem(
|
|
|
|
|
+ id: _detailIdCounter++,
|
|
|
|
|
+ category: result.category,
|
|
|
|
|
+ categoryName: result.categoryName,
|
|
|
|
|
+ quantity: result.quantity,
|
|
|
|
|
+ unit: result.unit,
|
|
|
|
|
+ unitPrice: result.unitPrice,
|
|
|
|
|
+ amount: result.quantity * result.unitPrice,
|
|
|
|
|
+ remark: result.remark,
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
- ),
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ═══ 5. 附件上传 ═══
|
|
// ═══ 5. 附件上传 ═══
|
|
@@ -1137,7 +941,14 @@ class _ExpenseApplicationApplyPageState
|
|
|
if (_estimatedStartDate.isEmpty) {
|
|
if (_estimatedStartDate.isEmpty) {
|
|
|
e.add(l10n.get('selectEstimatedStartDate'));
|
|
e.add(l10n.get('selectEstimatedStartDate'));
|
|
|
}
|
|
}
|
|
|
- if (_estimatedEndDate.isEmpty) e.add(l10n.get('selectEstimatedEndDate'));
|
|
|
|
|
|
|
+ if (_estimatedEndDate.isEmpty) {
|
|
|
|
|
+ e.add(l10n.get('selectEstimatedEndDate'));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (_estimatedStartDate.isNotEmpty &&
|
|
|
|
|
+ _estimatedEndDate.isNotEmpty &&
|
|
|
|
|
+ _estimatedStartDate.compareTo(_estimatedEndDate) > 0) {
|
|
|
|
|
+ e.add(l10n.get('startDateNotAfterEndDate'));
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
if (_expenseTypes.contains('entertainment')) {
|
|
if (_expenseTypes.contains('entertainment')) {
|
|
|
if (_entertainmentTarget == null || _entertainmentTarget!.isEmpty) {
|
|
if (_entertainmentTarget == null || _entertainmentTarget!.isEmpty) {
|
|
@@ -1151,7 +962,14 @@ class _ExpenseApplicationApplyPageState
|
|
|
if (_meetingStartDate.isEmpty) {
|
|
if (_meetingStartDate.isEmpty) {
|
|
|
e.add(l10n.get('selectEstimatedStartDate'));
|
|
e.add(l10n.get('selectEstimatedStartDate'));
|
|
|
}
|
|
}
|
|
|
- if (_meetingEndDate.isEmpty) e.add(l10n.get('selectEstimatedEndDate'));
|
|
|
|
|
|
|
+ if (_meetingEndDate.isEmpty) {
|
|
|
|
|
+ e.add(l10n.get('selectEstimatedEndDate'));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (_meetingStartDate.isNotEmpty &&
|
|
|
|
|
+ _meetingEndDate.isNotEmpty &&
|
|
|
|
|
+ _meetingStartDate.compareTo(_meetingEndDate) > 0) {
|
|
|
|
|
+ e.add(l10n.get('startDateNotAfterEndDate'));
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
return e;
|
|
return e;
|
|
|
}
|
|
}
|
|
@@ -1212,6 +1030,7 @@ class _ExpenseApplicationApplyPageState
|
|
|
builder: (ctx) => TDAlertDialog(
|
|
builder: (ctx) => TDAlertDialog(
|
|
|
title: title,
|
|
title: title,
|
|
|
content: content,
|
|
content: content,
|
|
|
|
|
+ buttonStyle: TDDialogButtonStyle.text,
|
|
|
leftBtn: TDDialogButtonOptions(
|
|
leftBtn: TDDialogButtonOptions(
|
|
|
title: leftText,
|
|
title: leftText,
|
|
|
titleColor: colors.primary,
|
|
titleColor: colors.primary,
|
|
@@ -1405,7 +1224,7 @@ class _DetailItem {
|
|
|
final int id;
|
|
final int id;
|
|
|
final String category;
|
|
final String category;
|
|
|
final String categoryName;
|
|
final String categoryName;
|
|
|
- final int quantity;
|
|
|
|
|
|
|
+ final double quantity;
|
|
|
final String unit;
|
|
final String unit;
|
|
|
final double unitPrice;
|
|
final double unitPrice;
|
|
|
final double amount;
|
|
final double amount;
|