1632 lines
57 KiB
Dart
1632 lines
57 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:nhance_partner/data/utils/toastNotification.dart';
|
|
|
|
import '../../../core/routing/routes.dart';
|
|
import '../../../core/services/api_service.dart';
|
|
import '../../layouts/main_layout.dart';
|
|
import '../../layouts/responsive_layout.dart';
|
|
import '../../providers/manager_provider.dart';
|
|
import '../../providers/userRoleProvider.dart';
|
|
import '../../themes/indicators/input_field_decoration.dart';
|
|
import '../../themes/indicators/search_field_theme.dart';
|
|
import 'FormFieldBox.dart';
|
|
import 'custom_dateRange.dart';
|
|
|
|
class PayOutDetails extends ConsumerStatefulWidget {
|
|
final Map<String, dynamic>? editItem;
|
|
|
|
const PayOutDetails({super.key, this.editItem});
|
|
|
|
@override
|
|
ConsumerState<PayOutDetails> createState() => _PayOutDetailsState();
|
|
}
|
|
|
|
class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|
static final List<TextInputFormatter> _commissionInputFormatter = [
|
|
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')),
|
|
];
|
|
static final List<TextInputFormatter> _utrInputFormatter = [
|
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9]')),
|
|
];
|
|
// --------------------------
|
|
// Invoice Fields
|
|
// --------------------------
|
|
final _formKey = GlobalKey<FormState>();
|
|
// final TextEditingController invoiceNoController;
|
|
DateTime invoiceDate = DateTime.now();
|
|
// dynamic selectedAgentId;
|
|
List<dynamic>? selectedAgentId;
|
|
dynamic selectedBrokerID;
|
|
DateTime? policyTillDate = DateTime.now();
|
|
|
|
dynamic totalPolicies = '0';
|
|
dynamic totalCommission = '0';
|
|
bool isEdit = false;
|
|
|
|
List<Map<String, dynamic>> masterPolicies = [];
|
|
|
|
final TextEditingController _searchStaffController = TextEditingController();
|
|
|
|
// --------------------------
|
|
// Filters
|
|
// --------------------------
|
|
DateTime? fromDate;
|
|
DateTime? toDate;
|
|
|
|
Map<String, TextEditingController> controllers = {};
|
|
List<String> tabHeader = [
|
|
'startDate',
|
|
'endDate',
|
|
'invoiceNo',
|
|
'invoiceDate',
|
|
'invoiceStatus',
|
|
'utrNumber',
|
|
];
|
|
|
|
// --------------------------
|
|
// Collapse States
|
|
// --------------------------
|
|
bool invoiceCollapsed = false;
|
|
bool policyCollapsed = false;
|
|
|
|
// --------------------------
|
|
// Sample Data (replace with API later)
|
|
// --------------------------
|
|
final List<Map<String, dynamic>> allPolicies = [];
|
|
|
|
List<Map<String, dynamic>> filteredPolicies = [];
|
|
Set<int> selectedPolicies = {};
|
|
|
|
List<Map<String, dynamic>> getBrokerData = [];
|
|
List<Map<String, dynamic>> filteredBrokerData = [];
|
|
|
|
List<Map<String, dynamic>> getAgentData = [];
|
|
List<Map<String, dynamic>> filteredAgentData = [];
|
|
|
|
bool isLoading = false;
|
|
bool isLoadingEditData = false;
|
|
bool hasFetchedTableData = false;
|
|
final Set<int> _inlineSavingIds = <int>{};
|
|
late ApiService apiService;
|
|
dynamic managerId;
|
|
dynamic userId;
|
|
dynamic roleId;
|
|
final DateFormat df = DateFormat('dd-MM-yyyy');
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
apiService = ApiService();
|
|
|
|
for (String field in tabHeader) {
|
|
controllers[field] = TextEditingController();
|
|
}
|
|
|
|
|
|
Future.microtask(() async {
|
|
managerId = ref.read(managerIdProvider);
|
|
roleId = ref.read(userRoleProvider);
|
|
userId = ref.read(userIdProvider);
|
|
|
|
print('PD =>PayOutDetails => managerId: $managerId');
|
|
|
|
if (managerId != null) {
|
|
getAgentList(managerId);
|
|
}
|
|
|
|
await getBroker();
|
|
|
|
// ✅ EDIT MODE (MUST be here)
|
|
if (widget.editItem != null) {
|
|
print('PD => editItemID ${widget.editItem}');
|
|
|
|
final invoiceID = widget.editItem!['id'];
|
|
|
|
controllers['invoiceNo']?.text =
|
|
widget.editItem!['invoice_no'];
|
|
|
|
controllers['invoiceDate']?.text =
|
|
widget.editItem!['invoice_date_ui_format'];
|
|
|
|
controllers['invoiceStatus']?.text =
|
|
widget.editItem!['payout_status'];
|
|
|
|
_loadEditData(invoiceID, managerId); // ✅ managerId ready
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
void filterPolicyData(String query) {
|
|
final lowerQuery = query.toLowerCase();
|
|
|
|
print('PD => filterPolicyData - $lowerQuery');
|
|
|
|
print('PD => filteredPolicies1 - $filteredPolicies');
|
|
setState(() {
|
|
if (query.trim().isEmpty) {
|
|
print('PD => filteredPolicies2');
|
|
filteredPolicies = List.from(masterPolicies);
|
|
return;
|
|
}
|
|
print('PD => filteredPolicies3');
|
|
filteredPolicies = masterPolicies.where((item) {
|
|
return (item['policy_no'] ?? '').toString().toLowerCase().contains(
|
|
lowerQuery,
|
|
) ||
|
|
(item['customer_name'] ?? '').toString().toLowerCase().contains(
|
|
lowerQuery,
|
|
) ||
|
|
(item['agent_name'] ?? '').toString().toLowerCase().contains(
|
|
lowerQuery,
|
|
) ||
|
|
(item['insurer_name'] ?? '').toString().toLowerCase().contains(
|
|
lowerQuery,
|
|
) ||
|
|
(item['premium_amount'] ?? '').toString().toLowerCase().contains(
|
|
lowerQuery,
|
|
) ||
|
|
(item['commission_amount'] ?? '').toString().toLowerCase().contains(
|
|
lowerQuery,
|
|
) ||
|
|
(_formatDate(item['issued_date']) ?? '').toLowerCase().contains(
|
|
lowerQuery,
|
|
);
|
|
}).toList();
|
|
});
|
|
}
|
|
|
|
void refresh() {
|
|
setState(() {
|
|
for (var controller in controllers.values) {
|
|
controller.clear();
|
|
controller.dispose();
|
|
}
|
|
|
|
selectedBrokerID = '';
|
|
selectedAgentId = [];
|
|
selectedPolicies = {};
|
|
totalPolicies = '';
|
|
totalCommission = '';
|
|
filteredPolicies = [];
|
|
hasFetchedTableData = false;
|
|
// dropDownKeyPartner.currentState?.clear();
|
|
});
|
|
}
|
|
|
|
void _loadEditData(String invoiceID, managerID) async {
|
|
setState(() => isLoadingEditData = true);
|
|
print("H010 => r : $roleId | mId: $managerID | ParmMID: $managerID | uId: $userId ");
|
|
print('PD => _loadEditData');
|
|
|
|
final jsondata = {
|
|
// "id": invoiceID,
|
|
"invoice_id": invoiceID,
|
|
"manager_id": managerId
|
|
|
|
// "issued_date": formattedTillDate,
|
|
};
|
|
print('PD => _loadEditData - $jsondata');
|
|
final response = await apiService.getCommissionRateList(jsondata);
|
|
// final response = await apiService.getInvoiceDetails(invoiceID);
|
|
|
|
if (response['status'] == 'success') {
|
|
print('PD => _loadEditData success');
|
|
print('PD => _loadEditData success - ${response['data']}');
|
|
final invoice = response['data'];
|
|
print("Invoice → $invoice");
|
|
// final items = List<Map<String, dynamic>>.from(response['data']['items']);
|
|
final items = response['data'];
|
|
final totPolicy = response['total_policies'].toString() ?? "0";
|
|
final totalCommiss = response['total_commission'];
|
|
|
|
setState(() {
|
|
// selectedPolicies = response['total_policies'];
|
|
isEdit = true;
|
|
totalPolicies = totPolicy;
|
|
totalCommission = totalCommiss;
|
|
|
|
// filteredPolicies = items.map((p) {
|
|
// final policyId = int.parse(p['policy_id']);
|
|
// return {...p, 'is_selected': selectedPolicies.contains(policyId)};
|
|
// }).toList();
|
|
|
|
final List<Map<String, dynamic>> items = (response['data'] as List)
|
|
.map((e) => Map<String, dynamic>.from(e))
|
|
.toList();
|
|
|
|
masterPolicies = List<Map<String, dynamic>>.from(response['data']);
|
|
filteredPolicies = List<Map<String, dynamic>>.from(response['data']);
|
|
|
|
print("selectedPolicies → $filteredPolicies ");
|
|
});
|
|
print(
|
|
"selectedPolicies → $selectedPolicies ->${response['total_policies']}",
|
|
);
|
|
print("Items → $items");
|
|
|
|
// -----------------------------
|
|
// 1. SET BASIC FIELDS
|
|
// -----------------------------
|
|
// setState(() {
|
|
// // selectedAgentId = int.tryParse(invoice['agent_id'].toString());
|
|
//
|
|
// // invoiceNoController.text = invoice['invoice_no'];
|
|
// // invoiceDate = DateTime.parse(invoice['invoice_date']);
|
|
//
|
|
// controllers['startDate']?.text = invoice['from_date'];
|
|
// controllers['endDate']?.text = invoice['to_date'];
|
|
// selectedBrokerID = int.tryParse(invoice['broker_id'].toString());
|
|
// if (invoice['agent_id'] is List) {
|
|
// selectedAgentId = (invoice['agent_id'] as List)
|
|
// .map((e) => e.toString())
|
|
// .toList();
|
|
// } else if (invoice['agent_id'] is String) {
|
|
// final raw = invoice['agent_id'].toString();
|
|
//
|
|
// if (raw.startsWith('[')) {
|
|
// // JSON string: "[1,2]"
|
|
// selectedAgentId = List<String>.from(
|
|
// jsonDecode(raw).map((e) => e.toString()),
|
|
// );
|
|
// } else if (raw.contains(',')) {
|
|
// // Comma separated: "1,2"
|
|
// selectedAgentId = raw.split(',').map((e) => e.trim()).toList();
|
|
// } else if (raw.isNotEmpty) {
|
|
// // Single value: "1"
|
|
// selectedAgentId = [raw];
|
|
// } else {
|
|
// selectedAgentId = [];
|
|
// }
|
|
// } else {
|
|
// selectedAgentId = [];
|
|
// }
|
|
//
|
|
// // if (invoice['till_date'] != "0000-00-00") {
|
|
// // policyTillDate = DateTime.parse(invoice['till_date']);
|
|
// // } else {
|
|
// // policyTillDate = DateTime.now();
|
|
// // }
|
|
// });
|
|
// print('PD => selectedBrokerID $selectedBrokerID');
|
|
|
|
// -----------------------------
|
|
// 3. LOAD ALL POLICIES FOR THIS BROKER + AGENT
|
|
// -----------------------------
|
|
// await loadPolicies();
|
|
|
|
// -----------------------------
|
|
// 2. SET SELECTED POLICIES
|
|
// -----------------------------
|
|
// selectedPolicies = items
|
|
// .map<int>((p) => int.parse(p['policy_id'].toString()))
|
|
// .toSet();
|
|
|
|
// -----------------------------
|
|
// 3. LOAD ALL POLICIES FOR THIS BROKER + AGENT
|
|
// -----------------------------
|
|
// await loadPolicies();
|
|
|
|
// -----------------------------
|
|
// 4. KEEP ONLY POLICIES THAT MATCH API DATA
|
|
// -----------------------------
|
|
// setState(() {
|
|
// filteredPolicies = filteredPolicies.map((p) {
|
|
// final policyId = int.parse(p['policy_id']);
|
|
// return {...p, 'is_selected': selectedPolicies.contains(policyId)};
|
|
// }).toList();
|
|
// });
|
|
}
|
|
|
|
setState(() => isLoadingEditData = false);
|
|
}
|
|
|
|
String formatDate(String date) {
|
|
final d = DateTime.parse(date);
|
|
return DateFormat('dd-MM-yyyy').format(d);
|
|
}
|
|
|
|
// Get Broker List
|
|
Future<void> getBroker() async {
|
|
print('PD =>getBroker called');
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
|
|
try {
|
|
final response = await apiService.fetchMasterDropDown('Broker');
|
|
|
|
if (response['status'] == 200) {
|
|
print('PD =>getBroker - ${response['data']}');
|
|
setState(() {
|
|
getBrokerData = List<Map<String, dynamic>>.from(response['data']);
|
|
print('PD =>API Data - $getBrokerData');
|
|
|
|
filteredBrokerData = List.from(getBrokerData);
|
|
print('PD =>originalData - $filteredBrokerData');
|
|
});
|
|
} else {
|
|
getBrokerData = [];
|
|
filteredBrokerData = [];
|
|
}
|
|
} catch (e) {
|
|
print('PD =>Exception occurred: $e');
|
|
} finally {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
//Get Agent List
|
|
Future<void> getAgentList(int id) async {
|
|
print('PD =>getClaimList called');
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
|
|
try {
|
|
final response = await apiService.fetchAgentUserList(managerId);
|
|
|
|
if (response['status'] == 'success') {
|
|
print('PD =>getAgentListData - ${response['data']}');
|
|
setState(() {
|
|
getAgentData = List<Map<String, dynamic>>.from(response['data']);
|
|
print('PD =>API Data - $getAgentData');
|
|
|
|
filteredAgentData = List.from(getAgentData);
|
|
print('PD =>originalData - $filteredAgentData');
|
|
});
|
|
} else {
|
|
getAgentData = [];
|
|
filteredAgentData = [];
|
|
}
|
|
} catch (e) {
|
|
print('PD =>Exception occurred: $e');
|
|
} finally {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
// Generate invoice number
|
|
String generateInvoiceNumber() {
|
|
final now = DateTime.now();
|
|
final random = now.millisecondsSinceEpoch % 1000;
|
|
return "INV${now.year}${now.month.toString().padLeft(2, '0')}${random.toString().padLeft(3, '0')}";
|
|
}
|
|
|
|
// Pick date helper
|
|
Future<DateTime?> pickDate(DateTime initial) async {
|
|
return await showDatePicker(
|
|
context: context,
|
|
initialDate: initial,
|
|
firstDate: DateTime(2000),
|
|
lastDate: DateTime(2100),
|
|
);
|
|
}
|
|
|
|
// if (dashboardKey == 'f' &&
|
|
// isDashboardInitialLoad &&
|
|
// SelectedStatus == "P") {
|
|
//
|
|
//
|
|
// fromDt = "";toDt = "";
|
|
// isDashboardInitialLoad = false;
|
|
//
|
|
// } else if (dashboardKey == 'f' && isDashboardInitialLoad && SelectedStatus != '') {
|
|
// fromDt = "";toDt = "";
|
|
// isDashboardInitialLoad = false;
|
|
// } else if (fromDateVal.isEmpty && toDateVal.isEmpty) {
|
|
// // Default fallback
|
|
// final today = DateTime.now();
|
|
// fromDt = DateFormat('dd-MM-yyyy').format(today.subtract(const Duration(days: 5)));
|
|
// toDt = DateFormat('dd-MM-yyyy').format(today);
|
|
// } else {
|
|
// // ✅ USER SELECTED DATE — ALWAYS RESPECT THIS
|
|
// fromDt = fromDateVal;
|
|
// toDt = toDateVal;
|
|
// }
|
|
|
|
// Load policies (simulate API)
|
|
Future<void> loadPolicies() async {
|
|
print('PD =>loadPolicies');
|
|
print("H13 => r : $roleId | mId: $managerId | uId: $userId ");
|
|
setState(() {
|
|
isLoading = true;
|
|
hasFetchedTableData = false;
|
|
});
|
|
try {
|
|
final List<int> agentIds = (selectedAgentId ?? [])
|
|
.map((e) => int.parse(e))
|
|
.toList();
|
|
|
|
final jsondata = {
|
|
"from_date": controllers['startDate']?.text,
|
|
"to_date": controllers['endDate']?.text,
|
|
"manager_id": managerId,
|
|
if (agentIds != null && agentIds.isNotEmpty) "agent_id": agentIds,
|
|
// "agent_id": agentIds,
|
|
// "issued_date": formattedTillDate,
|
|
};
|
|
final response = await apiService.getCommissionRateList(jsondata);
|
|
|
|
if (response['status'] == 'success') {
|
|
print('PD =>API Data response - $response');
|
|
setState(() {
|
|
filteredPolicies = List<Map<String, dynamic>>.from(response['data']);
|
|
hasFetchedTableData = true;
|
|
print('PD =>API Data - $filteredPolicies');
|
|
// filteredPolicies = allPolicies.where((p) {
|
|
// if (p["agentId"] != selectedAgentId) return false;
|
|
// if (policyTillDate != null &&
|
|
// DateTime.parse(p["date"]).isAfter(policyTillDate!))
|
|
// return false;
|
|
// return true;
|
|
// }).toList();
|
|
});
|
|
} else {
|
|
setState(() {
|
|
filteredPolicies = [];
|
|
hasFetchedTableData = true;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
print('PD =>Exception occurred: $e');
|
|
setState(() {
|
|
filteredPolicies = [];
|
|
hasFetchedTableData = false;
|
|
});
|
|
} finally {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
// Apply date filters
|
|
void filterPolicies() {
|
|
if (selectedAgentId == null) return;
|
|
|
|
setState(() {
|
|
filteredPolicies = allPolicies.where((p) {
|
|
final date = DateTime.parse(p["date"]);
|
|
// if (p["agentId"] != selectedAgentId) return false;
|
|
// if (policyTillDate != null && date.isAfter(policyTillDate!))
|
|
// return false;
|
|
if (fromDate != null && date.isBefore(fromDate!)) return false;
|
|
if (toDate != null && date.isAfter(toDate!)) return false;
|
|
return true;
|
|
}).toList();
|
|
});
|
|
}
|
|
|
|
// Save invoice (simulation)
|
|
Future<void> saveInvoice() async {
|
|
try {
|
|
if (selectedAgentId == null) {
|
|
ToastHelper.showWarningToast(
|
|
context,
|
|
"Please select a partner before saving.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (selectedPolicies.isEmpty) {
|
|
ToastHelper.showWarningToast(
|
|
context,
|
|
"Please select at least one policy before saving.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
final String utrInput = (controllers['utrNumber']?.text ?? '').trim();
|
|
if (utrInput.isEmpty) {
|
|
ToastHelper.showWarningToast(
|
|
context,
|
|
"Please enter the UTR number. It cannot be left empty.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Format dates for API
|
|
final String formattedInvoiceDate = DateFormat(
|
|
'yyyy-MM-dd',
|
|
).format(invoiceDate);
|
|
final String formattedTillDate = DateFormat(
|
|
'yyyy-MM-dd',
|
|
).format(policyTillDate!);
|
|
|
|
final List<Map<String, dynamic>> selectedRows = filteredPolicies
|
|
.where((p) {
|
|
final id = int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0;
|
|
return id != 0 && selectedPolicies.contains(id);
|
|
})
|
|
.toList();
|
|
|
|
if (selectedRows.isEmpty) {
|
|
ToastHelper.showWarningToast(
|
|
context,
|
|
"No matching policies found for your selection. Refresh the list and try again.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
for (final p in selectedRows) {
|
|
final policyNo = (p['policy_no'] ?? '-').toString();
|
|
final rawPayout = (p['commission_amount'] ?? '').toString().trim();
|
|
if (rawPayout.isEmpty) {
|
|
ToastHelper.showWarningToast(
|
|
context,
|
|
"Payout amount is missing for policy $policyNo. Enter an amount for every selected policy.",
|
|
);
|
|
return;
|
|
}
|
|
final payout = double.tryParse(rawPayout);
|
|
if (payout == null) {
|
|
ToastHelper.showWarningToast(
|
|
context,
|
|
"Payout amount for policy $policyNo is not a valid number. Use digits only (e.g. 1500 or 1500.50).",
|
|
);
|
|
return;
|
|
}
|
|
final premium = _parsePremiumAmount(p['premium_amount']);
|
|
if (premium != null && payout > premium + 0.001) {
|
|
ToastHelper.showWarningToast(
|
|
context,
|
|
"Payout for policy $policyNo (₹${payout.toStringAsFixed(2)}) cannot be greater than the premium (₹${premium.toStringAsFixed(2)}).",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
final double totalPayout = selectedRows.fold(
|
|
0.0,
|
|
(sum, p) => sum + _parseCommissionValue(p["commission_amount"]),
|
|
);
|
|
|
|
// Build items array
|
|
final List<Map<String, dynamic>> items = selectedRows
|
|
.map(
|
|
(p) => {
|
|
"policy_id": int.parse(p["policy_id"].toString()),
|
|
"policy_no": p["policy_no"],
|
|
"commission_amount": _parseCommissionValue(p["commission_amount"]),
|
|
},
|
|
)
|
|
.toList();
|
|
final List<Map<String, dynamic>> utrs = _buildUtrPayloadForSelectedPolicies(
|
|
defaultUtrDate: formattedInvoiceDate,
|
|
);
|
|
|
|
if (utrs.isEmpty) {
|
|
ToastHelper.showWarningToast(
|
|
context,
|
|
"UTR details could not be built. Check the UTR number and selected policies.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
final double totalUtrAmount = _sumUtrAmounts(utrs);
|
|
if (!_amountsMatch(totalPayout, totalUtrAmount)) {
|
|
ToastHelper.showWarningToast(
|
|
context,
|
|
"Total payout (₹${totalPayout.toStringAsFixed(2)}) must match the total UTR amount (₹${totalUtrAmount.toStringAsFixed(2)}). "
|
|
"Check each row's payout and the UTR field.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
final int payoutStatus =
|
|
_amountsMatch(totalPayout, totalUtrAmount) ? 2 : 1;
|
|
|
|
final jsondata = {
|
|
"invoice_no": controllers['invoiceNo']?.text,
|
|
"invoice_amount": totalPayout,
|
|
"agent_id": selectedAgentId,
|
|
"broker_id": selectedBrokerID,
|
|
"invoice_date": formattedInvoiceDate,
|
|
"till_date": formattedTillDate,
|
|
"payout_status": payoutStatus,
|
|
"pos_id": null,
|
|
"created_by": userId,
|
|
"updated_by": userId,
|
|
"items": items,
|
|
"utrs": utrs,
|
|
};
|
|
print('PD =>Commissions $jsondata');
|
|
final bool confirm = await showSaveConfirmation(context);
|
|
if (!confirm) return;
|
|
|
|
final response = await apiService.getCreateOrUpdate(jsondata);
|
|
|
|
if (response['status'] == 'success') {
|
|
setState(() {
|
|
print('PD =>API Data response - $response');
|
|
selectedPolicies.clear();
|
|
controllers['invoiceNo']?.clear();
|
|
controllers['utrNumber']?.clear();
|
|
invoiceDate = DateTime.now();
|
|
policyTillDate = DateTime.now();
|
|
filteredPolicies = [];
|
|
});
|
|
context.go(AppRoutes.payoutList);
|
|
} else {
|
|
final msg = (response['message'] ?? response['data'] ?? 'Save failed')
|
|
.toString();
|
|
ToastHelper.showErrorToast(context, msg);
|
|
}
|
|
} catch (e) {
|
|
print('PD =>Exception occurred: $e');
|
|
} finally {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
String _displayText(dynamic value) {
|
|
if (value == null) return '-';
|
|
final text = value.toString().trim();
|
|
if (text.isEmpty || text.toLowerCase() == 'null') return '-';
|
|
return text;
|
|
}
|
|
|
|
List<Map<String, dynamic>> _buildUtrPayloadForSelectedPolicies({
|
|
required String defaultUtrDate,
|
|
}) {
|
|
final Map<String, double> utrAmountMap = {};
|
|
final String enteredUtr = (controllers['utrNumber']?.text ?? '').trim();
|
|
|
|
for (final policy in filteredPolicies) {
|
|
final int policyId = int.tryParse(policy["policy_id"]?.toString() ?? "0") ?? 0;
|
|
if (!selectedPolicies.contains(policyId)) continue;
|
|
|
|
final String utrNo = enteredUtr.isNotEmpty
|
|
? enteredUtr
|
|
: (policy['utr_no'] ?? '').toString().trim();
|
|
if (utrNo.isEmpty) continue;
|
|
|
|
final double amount = _parseCommissionValue(policy["commission_amount"]);
|
|
utrAmountMap[utrNo] = (utrAmountMap[utrNo] ?? 0) + amount;
|
|
}
|
|
|
|
return utrAmountMap.entries
|
|
.map(
|
|
(e) => <String, dynamic>{
|
|
"utr_no": e.key,
|
|
"amount": e.value,
|
|
"utr_date": defaultUtrDate,
|
|
},
|
|
)
|
|
.toList();
|
|
}
|
|
|
|
|
|
void showMessage(String msg) {
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
|
|
}
|
|
|
|
Future<bool> showSaveConfirmation(BuildContext context) async {
|
|
return await showDialog<bool>(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
title: Text(
|
|
"Confirm Save",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
content: Text(
|
|
"Once saved, payout details cannot be edited.\nAre you sure you want to continue?",
|
|
style: GoogleFonts.inter(fontSize: 13, color: Colors.grey[700]),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
child: Text(
|
|
"No",
|
|
style: GoogleFonts.inter(
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.grey[700],
|
|
),
|
|
),
|
|
),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF059669), // emerald
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
onPressed: () => Navigator.pop(context, true),
|
|
child: Text(
|
|
"Yes, Save",
|
|
style: GoogleFonts.inter(
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
) ??
|
|
false;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final df = DateFormat("dd-MM-yyyy");
|
|
final int status =
|
|
int.tryParse(controllers['invoiceStatus']?.text ?? '0') ?? 0;
|
|
|
|
final bool isPending = status == 1;
|
|
|
|
return MainLayout(
|
|
title: "Pay Out",
|
|
body: !isLoadingEditData
|
|
? Container(
|
|
color: Colors.white,
|
|
padding: const EdgeInsets.all(8),
|
|
margin: isEdit
|
|
? EdgeInsets.symmetric(
|
|
horizontal: MediaQuery.of(context).size.width * 0.1,
|
|
)
|
|
: null,
|
|
child: Column(
|
|
children: [
|
|
// const SizedBox(height: 20),
|
|
Container(
|
|
height: 40,
|
|
|
|
width: MediaQuery.of(context).size.width,
|
|
child: GestureDetector(
|
|
onTap: null,
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: [
|
|
Tooltip(
|
|
message: 'Back',
|
|
child: IconButton(
|
|
icon: const Icon(
|
|
Icons.arrow_left_sharp,
|
|
size: 25,
|
|
color: Color(0xFF425B5B),
|
|
),
|
|
onPressed: () {
|
|
context.go(AppRoutes.payoutList);
|
|
},
|
|
splashRadius: 18,
|
|
hoverColor: Colors.black12,
|
|
padding: const EdgeInsets.all(4),
|
|
constraints: const BoxConstraints(),
|
|
),
|
|
),
|
|
const SizedBox(
|
|
width: 5,
|
|
), // spacing between icon and text
|
|
Text(
|
|
"Payout Details",
|
|
style: GoogleFonts.inter(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
|
|
// 🟢 Status Chip
|
|
if (isEdit) ...[
|
|
Spacer(),
|
|
Text(
|
|
controllers['invoiceNo']?.text ?? '',
|
|
style: GoogleFonts.inter(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w500,
|
|
color: const Color(0xFF374151),
|
|
),
|
|
),
|
|
SizedBox(width: 10),
|
|
Text(
|
|
// controllers['invoiceDate']?.text ?? '',
|
|
'(${controllers['invoiceDate']?.text ?? ''})',
|
|
style: GoogleFonts.inter(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w300,
|
|
color: const Color(0xFF6B7280),
|
|
),
|
|
),
|
|
// SizedBox(width: 10),
|
|
// Spacer(),
|
|
SizedBox(width: 10),
|
|
Text(
|
|
'Status: ${isPending ? 'Pending' : 'Completed'}',
|
|
style: GoogleFonts.inter(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w300,
|
|
color: const Color(0xFF6B7280),
|
|
// color: isPending
|
|
// ? Colors.blueAccent
|
|
// : const Color(0xFF047857),
|
|
),
|
|
),
|
|
|
|
Spacer(),
|
|
|
|
ThemedSearchField(
|
|
hintText: 'Search',
|
|
|
|
// backgroundColor: Color(0xFFF6F8F8),
|
|
onChanged: filterPolicyData,
|
|
|
|
controller: _searchStaffController,
|
|
backgroundColor: Color(0xFFFFFFFF),
|
|
txtHeight: 30,
|
|
txtwidth: ResponsiveLayout.isMobile(context)
|
|
? MediaQuery.of(context).size.width * 0.7
|
|
: MediaQuery.of(context).size.width * 0.15,
|
|
),
|
|
],
|
|
|
|
if (!isEdit) ...[
|
|
const Spacer(),
|
|
if (hasFetchedTableData) ...[
|
|
const SizedBox(width: 10),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
vertical: 6,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFECFDF5), // emerald bg
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Text(
|
|
"SELECTED POLICIES : ",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w500,
|
|
color: const Color(0xFF065F46),
|
|
letterSpacing: 0.4,
|
|
),
|
|
),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
totalPolicies,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
color: const Color(0xFF047857),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(width: 10),
|
|
|
|
// 💰 TOTAL COMMISSION
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 6,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF10B981), // emerald green
|
|
borderRadius: BorderRadius.circular(22),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: const Color(
|
|
0xFF10B981,
|
|
).withOpacity(0.25),
|
|
blurRadius: 6,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
const Icon(
|
|
Icons.account_balance_wallet_outlined,
|
|
size: 14,
|
|
color: Colors.white,
|
|
),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
"TOTAL PAYOUT AMOUNT :",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.white.withOpacity(0.9),
|
|
letterSpacing: 0.4,
|
|
),
|
|
),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
"₹$totalCommission",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
],
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// SizedBox(height: 5),
|
|
if (!isEdit) ...[
|
|
Container(
|
|
padding: EdgeInsets.all(8.0),
|
|
child: DateFilterRowPayout(
|
|
formKey: _formKey,
|
|
// key: ValueKey(SelectedStatus ?? ''),
|
|
role: roleId,
|
|
id: userId,
|
|
showBrokerFilter: false,
|
|
selectedParnter: selectedAgentId,
|
|
selectedBroker: selectedBrokerID,
|
|
|
|
onPartnerChanges: (val) {
|
|
setState(() {
|
|
selectedAgentId = val; // ✅ parent state updated
|
|
});
|
|
},
|
|
selectedStaffId: '',
|
|
startController: controllers['startDate']!,
|
|
endController: controllers['endDate']!,
|
|
|
|
isMobile: ResponsiveLayout.isMobile(context),
|
|
onFilter: () {
|
|
print(
|
|
'DATEROw- ${controllers['startDate']!} -${controllers['endDate']!}'
|
|
' - $selectedAgentId',
|
|
);
|
|
// call your filter logic
|
|
// filterDateRange();
|
|
|
|
loadPolicies();
|
|
},
|
|
onRefresh: () {
|
|
// call your refresh logic
|
|
refresh();
|
|
},
|
|
),
|
|
),
|
|
|
|
SizedBox(height: 10),
|
|
],
|
|
// Policy Selection
|
|
Card(
|
|
// color: Colors.amber,
|
|
color: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
elevation: 0,
|
|
child: Column(
|
|
children: [
|
|
AnimatedCrossFade(
|
|
duration: const Duration(milliseconds: 300),
|
|
crossFadeState: policyCollapsed
|
|
? CrossFadeState.showFirst
|
|
: CrossFadeState.showSecond,
|
|
firstChild: const SizedBox(),
|
|
secondChild: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: SizedBox(
|
|
width: _tableMinWidth,
|
|
child: Column(
|
|
children: [
|
|
_policyTableHeader(), // static header
|
|
_policyTableBody(), // vertical scroll rows
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
if (selectedPolicies.isNotEmpty) ...[
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
// color: Colors.amber,
|
|
color: Color(0xFFf9fafb),
|
|
// color: Colors.white,
|
|
child: Row(
|
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
// ---------------- SELECTED COUNT ----------------
|
|
Row(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
// selectedPolicies.length.toString(),
|
|
totalPolicies,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.blueGrey,
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
"policies selected",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.blueGrey,
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
"|",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.blueGrey,
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
"Total Payout Amount",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.blueGrey,
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
|
|
/// NULL-SAFE TOTAL COMMISSION CALCULATION
|
|
Text(
|
|
// "₹ ${_calculateTotalCommission()}",
|
|
"₹ $totalCommission",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.teal,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
Spacer(),
|
|
buildUTRNumber(context),
|
|
SizedBox(width: 20),
|
|
// ---------------- BUTTONS ----------------
|
|
Row(
|
|
children: [
|
|
ElevatedButton(
|
|
onPressed: saveInvoice,
|
|
style: ElevatedButton.styleFrom(
|
|
// backgroundColor: const Color(0xff45a049),
|
|
backgroundColor: Colors.teal,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(
|
|
4,
|
|
), // 🔽 reduced radius
|
|
),
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 10,
|
|
),
|
|
),
|
|
child: Text(
|
|
"Save Payout Details",
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.white,
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
TextButton(
|
|
onPressed: () =>
|
|
setState(() => selectedPolicies.clear()),
|
|
child: const Text("Clear"),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
|
|
if (isEdit) ...[
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
// color: Colors.amber,
|
|
color: Color(0xFFf9fafb),
|
|
// color: Colors.white,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
// selectedPolicies.length.toString(),
|
|
totalPolicies,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.blueGrey,
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
"policies selected",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.blueGrey,
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
"|",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.blueGrey,
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
"Total Payout Amount",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.blueGrey,
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
|
|
/// NULL-SAFE TOTAL COMMISSION CALCULATION
|
|
Text(
|
|
// "₹ ${_calculateTotalCommission()}",
|
|
"₹ $totalCommission",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.teal,
|
|
),
|
|
),
|
|
|
|
const SizedBox(width: 10),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
// ----------------------
|
|
// const SizedBox(height: 100),
|
|
],
|
|
),
|
|
)
|
|
: Container(
|
|
color: Colors.white,
|
|
height: MediaQuery.of(context).size.height * 0.7,
|
|
child: Center(child: CircularProgressIndicator()),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _policyTableHeader() {
|
|
final allSelected =
|
|
filteredPolicies.isNotEmpty &&
|
|
filteredPolicies.every(
|
|
(p) => selectedPolicies.contains(
|
|
int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0,
|
|
),
|
|
);
|
|
// filteredPolicies.every(
|
|
// (p) => selectedPolicies.contains(
|
|
// int.tryParse(p["policy_id"].toString()) ?? 0,
|
|
// ),
|
|
// );
|
|
|
|
final partiallySelected = selectedPolicies.isNotEmpty && !allSelected;
|
|
|
|
return Container(
|
|
height: 30,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
decoration: const BoxDecoration(
|
|
// color: Color(0xFFf9fafb),
|
|
color: Color(0xFFf9fafb),
|
|
// color: Color(0xFFf9fafb),
|
|
// color: Color(0xFFF6F8F7),
|
|
// border: Border(bottom: BorderSide(color: Color(0xFFE0E0E0))),
|
|
// border: Border(bottom: BorderSide(color: Color(0xFFECEFF1))),
|
|
border: Border(bottom: BorderSide(color: Color(0xFFf9fafb))),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
if (!isEdit) ...[
|
|
SizedBox(
|
|
width: colCheck,
|
|
child: Checkbox(
|
|
value: allSelected ? true : (partiallySelected ? null : false),
|
|
tristate: true,
|
|
onChanged: (v) {
|
|
setState(() {
|
|
if (v == true) {
|
|
selectedPolicies = filteredPolicies
|
|
.map(
|
|
(p) =>
|
|
int.tryParse(
|
|
p["policy_id"]?.toString() ?? "0",
|
|
) ??
|
|
0,
|
|
)
|
|
.where((id) => id != 0) // Filter out invalid IDs
|
|
.toSet();
|
|
// selectedPolicies = filteredPolicies
|
|
// .map(
|
|
// (p) => int.tryParse(p["policy_id"].toString()) ?? 0,
|
|
// )
|
|
// .toSet();
|
|
} else {
|
|
selectedPolicies.clear();
|
|
}
|
|
totalPolicies = selectedPolicies.length.toString();
|
|
});
|
|
_calculateTotalCommission();
|
|
},
|
|
),
|
|
),
|
|
],
|
|
_headerText("POLICY NO", colPolicy),
|
|
_headerText("POLICY DATE", colDate),
|
|
_headerText("PARTNER", colAgent),
|
|
_headerText("CUSTOMER", colCustomer),
|
|
_headerText("PREMIUM", colPremium),
|
|
_headerText("PAYOUT", colCommission),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _headerText(String text, double width) {
|
|
return SizedBox(
|
|
width: width,
|
|
child: Text(
|
|
text,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.blueGrey,
|
|
letterSpacing: 0.5,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _policyTableBody() {
|
|
return SizedBox(
|
|
// height: isEdit ? 400 : 340, // avoids overflow
|
|
height: isEdit
|
|
? MediaQuery.of(context).size.height * 0.65
|
|
: MediaQuery.of(context).size.height * 0.48, // avoids overflow
|
|
child: ListView.builder(
|
|
itemCount: filteredPolicies.length,
|
|
itemBuilder: (context, index) {
|
|
final p = filteredPolicies[index];
|
|
|
|
final int id = int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0;
|
|
final bool isSelected = selectedPolicies.contains(id);
|
|
|
|
return InkWell(
|
|
onTap: () {
|
|
setState(() {
|
|
isSelected
|
|
? selectedPolicies.remove(id)
|
|
: selectedPolicies.add(id);
|
|
totalPolicies = selectedPolicies.length.toString();
|
|
});
|
|
_calculateTotalCommission();
|
|
},
|
|
child: Container(
|
|
height: 38,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
decoration: BoxDecoration(
|
|
// const Color(0xFFE6F4EF)
|
|
color: isSelected ? const Color(0xFFF0FAF6) : Colors.white,
|
|
border: const Border(
|
|
bottom: BorderSide(color: Color(0xFFf9fafb)),
|
|
// bottom: BorderSide(color: Color(0xFFEDEDED)),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
if (!isEdit) ...[
|
|
SizedBox(
|
|
width: colCheck,
|
|
child: Checkbox(
|
|
value: isSelected,
|
|
onChanged: (v) {
|
|
setState(() {
|
|
v == true
|
|
? selectedPolicies.add(id)
|
|
: selectedPolicies.remove(id);
|
|
totalPolicies = selectedPolicies.length.toString();
|
|
});
|
|
_calculateTotalCommission();
|
|
},
|
|
),
|
|
),
|
|
],
|
|
_cell(p["policy_no"], colPolicy),
|
|
// _cell(p["issued_date"] ?? "-", colDate),
|
|
_cell(_formatDate(p['issued_date']), colDate),
|
|
// _cell(p["agent_name"] ?? "-", colAgent),
|
|
_cell(
|
|
(p["agent_code"] != null && p["agent_name"] != null)
|
|
? '${p["agent_code"]} - ${p["agent_name"]}'
|
|
: (p["agent_name"] ?? '-'),
|
|
colAgent,
|
|
),
|
|
_cell(p["customer_name"], colCustomer),
|
|
_cell("₹${p["premium_amount"]}", colPremium),
|
|
SizedBox(
|
|
width: colCommission,
|
|
child: Row(
|
|
children: [
|
|
const Text(
|
|
"₹",
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF009B77),
|
|
),
|
|
),
|
|
const SizedBox(width: 2),
|
|
Expanded(
|
|
child: TextFormField(
|
|
key: ValueKey(
|
|
'commission_${p["policy_id"]}_${p["commission_amount"]}',
|
|
),
|
|
initialValue: (p["commission_amount"] ?? '')
|
|
.toString(),
|
|
style: const TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF009B77),
|
|
),
|
|
keyboardType: const TextInputType.numberWithOptions(
|
|
decimal: true,
|
|
),
|
|
inputFormatters: _commissionInputFormatter,
|
|
decoration: const InputDecoration(
|
|
isDense: true,
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 6,
|
|
vertical: 6,
|
|
),
|
|
border: OutlineInputBorder(),
|
|
),
|
|
onChanged: (value) {
|
|
p["commission_amount"] = value.trim();
|
|
_calculateTotalCommission();
|
|
},
|
|
onFieldSubmitted: (value) =>
|
|
_updateInlineCommission(p, value),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
double _parseCommissionValue(dynamic value) {
|
|
if (value == null) return 0;
|
|
return double.tryParse(value.toString().trim()) ?? 0;
|
|
}
|
|
|
|
/// Premium from API / table (may include ₹ or commas).
|
|
double? _parsePremiumAmount(dynamic value) {
|
|
if (value == null) return null;
|
|
final cleaned = value
|
|
.toString()
|
|
.replaceAll('₹', '')
|
|
.replaceAll(',', '')
|
|
.replaceAll(RegExp(r'\s+'), '')
|
|
.trim();
|
|
if (cleaned.isEmpty) return null;
|
|
return double.tryParse(cleaned);
|
|
}
|
|
|
|
double _sumUtrAmounts(List<Map<String, dynamic>> utrs) {
|
|
double sum = 0;
|
|
for (final row in utrs) {
|
|
sum += _parseCommissionValue(row['amount']);
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
bool _amountsMatch(double a, double b, {double epsilon = 0.01}) {
|
|
return (a - b).abs() < epsilon;
|
|
}
|
|
|
|
Future<void> _updateInlineCommission(Map<String, dynamic> row, String value) async {
|
|
final trimmed = value.trim();
|
|
final amount = double.tryParse(trimmed);
|
|
if (amount == null) {
|
|
ToastHelper.showWarningToast(context, 'Invalid payout amount');
|
|
return;
|
|
}
|
|
|
|
final int id =
|
|
int.tryParse((row['id'] ?? row['policy_id'] ?? '').toString()) ?? 0;
|
|
if (id == 0 || _inlineSavingIds.contains(id)) return;
|
|
|
|
setState(() {
|
|
_inlineSavingIds.add(id);
|
|
});
|
|
|
|
try {
|
|
final response = await apiService.createUserData(
|
|
{
|
|
'id': id,
|
|
'commission_amount': amount,
|
|
},
|
|
'policy/payoutInilneEditUpdate',
|
|
);
|
|
if (response['status'] == 'success' || response['status'] == 200) {
|
|
row['commission_amount'] = trimmed;
|
|
_calculateTotalCommission();
|
|
} else {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
(response['message'] ?? 'Inline update failed').toString(),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
ToastHelper.showErrorToast(context, e.toString());
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_inlineSavingIds.remove(id);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Widget _cell(String? value, double width) {
|
|
return SizedBox(
|
|
width: width,
|
|
child: Text(
|
|
value ?? "-",
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(fontSize: 11),
|
|
),
|
|
);
|
|
}
|
|
|
|
double _calculateTotalCommission() {
|
|
print("ASD");
|
|
print(filteredPolicies);
|
|
double total = 0.0;
|
|
|
|
for (var p in filteredPolicies) {
|
|
// Safe policy_id parsing
|
|
final String idStr = p["policy_id"]?.toString() ?? "0";
|
|
final int id = int.tryParse(idStr) ?? 0;
|
|
|
|
if (!selectedPolicies.contains(id)) continue;
|
|
|
|
total += _parseCommissionValue(p["commission_amount"]);
|
|
}
|
|
setState(() {
|
|
totalCommission = total.roundToDouble();
|
|
});
|
|
// Remove decimal points (.00)
|
|
return total.roundToDouble();
|
|
}
|
|
|
|
Widget buildUTRNumber(BuildContext context) {
|
|
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Text('UTR Number', style: _textStyle),
|
|
SizedBox(width: 5),
|
|
SizedBox(
|
|
height: 35,
|
|
width: MediaQuery.of(context).size.width * 0.15,
|
|
child: TextFormField(
|
|
controller: controllers['utrNumber']!,
|
|
inputFormatters: _utrInputFormatter,
|
|
decoration: commonInputDecoration(hint: 'UTR Number'),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
InputDecoration commonInputDecoration({required String hint}) {
|
|
return InputDecoration(
|
|
hintText: hint,
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(6),
|
|
borderSide: const BorderSide(
|
|
color: Color(0xFFDDDDDD), // light gray
|
|
width: 1,
|
|
),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(6),
|
|
borderSide: const BorderSide(
|
|
color: Color(0xFFAAAAAA), // darker gray on focus
|
|
width: 1.2,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
static final _textStyle = GoogleFonts.poppins(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w500,
|
|
);
|
|
|
|
String _formatDate(String rawDate) {
|
|
try {
|
|
final dateTime = DateTime.parse(rawDate);
|
|
return DateFormat('dd-MM-yyyy').format(dateTime); // 24-hour format
|
|
} catch (e) {
|
|
return rawDate; // fallback if parsing fails
|
|
}
|
|
}
|
|
|
|
static final double colCheck = 44;
|
|
double get colPolicy => isEdit ? 150 : 200;
|
|
double get colDate => isEdit ? 120 : 200;
|
|
static final double colAgent = 200;
|
|
static final double colCustomer = 200;
|
|
static final double colPremium = 200;
|
|
// static final double colCommission = 200;
|
|
|
|
double get colCommission => isEdit ? 120 : 200;
|
|
double get _tableMinWidth =>
|
|
(isEdit ? 0 : colCheck) +
|
|
colPolicy +
|
|
colDate +
|
|
colAgent +
|
|
colCustomer +
|
|
colPremium +
|
|
colCommission +
|
|
32;
|
|
} |