Fixed upto purchases and rate sync process

This commit is contained in:
2026-08-28 19:45:19 +05:30
parent 189f49ed07
commit eacca6aaea
21 changed files with 1203 additions and 401 deletions

View File

@@ -570,117 +570,135 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen> {
final amount = double.tryParse(amountText);
if (amount == null) return;
if (fromWallet != null && fromWallet!.balance < amount) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Insufficient balance in From Account')));
return;
final isFromPayableOrLiability = fromWallet != null && (fromWallet!.nature == 'PAYABLES' || fromWallet!.nature == 'LOAN');
if (fromWallet != null && !isFromPayableOrLiability) {
double effectiveBalance = fromWallet!.balance;
if (widget.transaction != null && widget.transaction!.fromWalletId == fromWallet!.id) {
effectiveBalance += widget.transaction!.amount;
}
if (effectiveBalance < amount) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Insufficient balance in From Account')));
return;
}
}
setState(() => isSaving = true);
// Determine internal type based on natures
String type = 'TRANSFER';
if (fromWallet == null) {
type = 'INCOME';
} else if (toWallet == null) { // Though toWallet is mandatory, just in case
type = 'EXPENSE';
} else if (fromWallet?.nature == 'INCOME') {
type = 'INCOME';
} else if (toWallet?.nature == 'PAYABLES' || toWallet?.nature == 'EXPENSE') {
type = 'EXPENSE';
} else if (toWallet?.nature == 'INVESTMENTS') {
type = 'INVESTMENT';
}
if (isRecurring && widget.transaction == null) {
DateTime nextDate = selectedDate;
if (recurringFrequency == 'DAILY') {
nextDate = nextDate.add(const Duration(days: 1));
} else if (recurringFrequency == 'WEEKLY') {
nextDate = nextDate.add(const Duration(days: 7));
} else if (recurringFrequency == 'MONTHLY') {
nextDate = DateTime(nextDate.year, nextDate.month + 1, nextDate.day);
try {
// Determine internal type based on natures
String type = 'TRANSFER';
if (fromWallet == null) {
type = 'INCOME';
} else if (toWallet == null) { // Though toWallet is mandatory, just in case
type = 'EXPENSE';
} else if (fromWallet?.nature == 'INCOME') {
type = 'INCOME';
} else if (toWallet?.nature == 'PAYABLES' || toWallet?.nature == 'EXPENSE') {
type = 'EXPENSE';
} else if (toWallet?.nature == 'INVESTMENTS') {
type = 'INVESTMENT';
}
final rt = RecurringTransaction(
id: 0,
type: type,
amount: amount,
categoryId: selectedCategory?.id,
fromWalletId: fromWallet?.id,
toWalletId: toWallet?.id,
frequency: recurringFrequency,
description: descriptionController.text.trim(),
nextExecutionDate: nextDate,
status: 'ACTIVE',
);
await ref.read(recurringTransactionProvider.notifier).addRecurringTransaction(rt);
final tx = Transaction(
id: 0,
type: type,
amount: amount,
date: selectedDate,
description: descriptionController.text.trim(),
categoryId: selectedCategory?.id,
fromWalletId: fromWallet?.id,
toWalletId: toWallet?.id,
dueDate: dueDate,
alertSchedule: alertSchedule == 'NONE' ? null : alertSchedule,
alertTime: alertSchedule == 'NONE' ? null : '${alertTime.hour.toString().padLeft(2, '0')}:${alertTime.minute.toString().padLeft(2, '0')}:00',
items: lineItems.isNotEmpty ? lineItems : null,
);
await ref.read(transactionProvider.notifier).addTransaction(tx, base64Attachments: base64Attachments);
} else {
final tx = Transaction(
id: widget.transaction?.id ?? 0,
type: type,
amount: amount,
date: selectedDate,
description: descriptionController.text.trim(),
categoryId: selectedCategory?.id,
fromWalletId: fromWallet?.id,
toWalletId: toWallet?.id,
dueDate: dueDate,
alertSchedule: alertSchedule == 'NONE' ? null : alertSchedule,
alertTime: alertSchedule == 'NONE' ? null : '${alertTime.hour.toString().padLeft(2, '0')}:${alertTime.minute.toString().padLeft(2, '0')}:00',
items: lineItems.isNotEmpty ? lineItems : null,
);
if (isRecurring && widget.transaction == null) {
DateTime nextDate = selectedDate;
if (recurringFrequency == 'DAILY') {
nextDate = nextDate.add(const Duration(days: 1));
} else if (recurringFrequency == 'WEEKLY') {
nextDate = nextDate.add(const Duration(days: 7));
} else if (recurringFrequency == 'MONTHLY') {
nextDate = DateTime(nextDate.year, nextDate.month + 1, nextDate.day);
}
if (widget.transaction == null) {
final rt = RecurringTransaction(
id: 0,
type: type,
amount: amount,
categoryId: selectedCategory?.id,
fromWalletId: fromWallet?.id,
toWalletId: toWallet?.id,
frequency: recurringFrequency,
description: descriptionController.text.trim(),
nextExecutionDate: nextDate,
status: 'ACTIVE',
);
await ref.read(recurringTransactionProvider.notifier).addRecurringTransaction(rt);
final tx = Transaction(
id: 0,
type: type,
amount: amount,
date: selectedDate,
description: descriptionController.text.trim(),
categoryId: selectedCategory?.id,
fromWalletId: fromWallet?.id,
toWalletId: toWallet?.id,
dueDate: dueDate,
alertSchedule: alertSchedule == 'NONE' ? null : alertSchedule,
alertTime: alertSchedule == 'NONE' ? null : '${alertTime.hour.toString().padLeft(2, '0')}:${alertTime.minute.toString().padLeft(2, '0')}:00',
items: lineItems.isNotEmpty ? lineItems : null,
);
await ref.read(transactionProvider.notifier).addTransaction(tx, base64Attachments: base64Attachments);
} else {
await ref.read(transactionProvider.notifier).updateTransaction(
tx,
base64Attachments: base64Attachments,
deletedAttachmentIds: deletedAttachmentIds,
final tx = Transaction(
id: widget.transaction?.id ?? 0,
type: type,
amount: amount,
date: selectedDate,
description: descriptionController.text.trim(),
categoryId: selectedCategory?.id,
fromWalletId: fromWallet?.id,
toWalletId: toWallet?.id,
dueDate: dueDate,
alertSchedule: alertSchedule == 'NONE' ? null : alertSchedule,
alertTime: alertSchedule == 'NONE' ? null : '${alertTime.hour.toString().padLeft(2, '0')}:${alertTime.minute.toString().padLeft(2, '0')}:00',
items: lineItems.isNotEmpty ? lineItems : null,
);
}
if (alertSchedule != 'NONE' && dueDate != null) {
DateTime alertDate = dueDate!;
if (alertSchedule == '1_DAY_BEFORE') {
alertDate = dueDate!.subtract(const Duration(days: 1));
} else if (alertSchedule == '2_DAYS_BEFORE') {
alertDate = dueDate!.subtract(const Duration(days: 2));
} else if (alertSchedule == '1_WEEK_BEFORE') {
alertDate = dueDate!.subtract(const Duration(days: 7));
}
final notifyDate = DateTime(alertDate.year, alertDate.month, alertDate.day, alertTime.hour, alertTime.minute);
if (notifyDate.isAfter(DateTime.now())) {
NotificationService().scheduleNotification(
id: DateTime.now().millisecondsSinceEpoch.remainder(100000),
title: 'Payment Due!',
body: 'Rs. ${amount.toStringAsFixed(0)} is due for ${toWallet?.name}',
scheduledDate: notifyDate,
if (widget.transaction == null) {
await ref.read(transactionProvider.notifier).addTransaction(tx, base64Attachments: base64Attachments);
} else {
await ref.read(transactionProvider.notifier).updateTransaction(
tx,
base64Attachments: base64Attachments,
deletedAttachmentIds: deletedAttachmentIds,
);
}
if (alertSchedule != 'NONE' && dueDate != null) {
DateTime alertDate = dueDate!;
if (alertSchedule == '1_DAY_BEFORE') {
alertDate = dueDate!.subtract(const Duration(days: 1));
} else if (alertSchedule == '2_DAYS_BEFORE') {
alertDate = dueDate!.subtract(const Duration(days: 2));
} else if (alertSchedule == '1_WEEK_BEFORE') {
alertDate = dueDate!.subtract(const Duration(days: 7));
}
final notifyDate = DateTime(alertDate.year, alertDate.month, alertDate.day, alertTime.hour, alertTime.minute);
if (notifyDate.isAfter(DateTime.now())) {
NotificationService().scheduleNotification(
id: DateTime.now().millisecondsSinceEpoch.remainder(100000),
title: 'Payment Due!',
body: 'Rs. ${amount.toStringAsFixed(0)} is due for ${toWallet?.name}',
scheduledDate: notifyDate,
);
}
}
}
}
if (mounted) {
SnackBarService.showSuccess(context, 'Transaction saved successfully');
Navigator.pop(context);
if (mounted) {
SnackBarService.showSuccess(context, 'Transaction saved successfully');
Navigator.pop(context);
}
} catch (e) {
debugPrint('Error saving transaction: $e');
if (mounted) {
SnackBarService.showError(context, 'Failed to save transaction: ${e.toString()}');
}
} finally {
if (mounted) {
setState(() => isSaving = false);
}
}
}