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

@@ -26,8 +26,9 @@ class DioClient {
DioClient._internal()
: dio = Dio(BaseOptions(
baseUrl: 'https://app.technobeesolutions.in/api/kifi-v2',
//baseUrl: 'https://app.technobeesolutions.in/api/kifi-v2',
//baseUrl: 'http://192.168.0.104:8080/api/kifi-v2', // Local Mac IP for physical device testing
baseUrl: 'http://192.168.1.5:8080/api/kifi-v2', // Current Local Mac IP (192.168.1.5)
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
)),

View File

@@ -0,0 +1,54 @@
class CommodityRateHistory {
final int? id;
final String commodityCode;
final String? commodityName;
final String? purity;
final double rate;
final DateTime effectiveAt;
final String? source;
final int? createdBy;
final DateTime? createdAt;
CommodityRateHistory({
this.id,
required this.commodityCode,
this.commodityName,
this.purity,
required this.rate,
required this.effectiveAt,
this.source,
this.createdBy,
this.createdAt,
});
factory CommodityRateHistory.fromJson(Map<String, dynamic> json) {
return CommodityRateHistory(
id: json['id'] is int ? json['id'] : int.tryParse(json['id']?.toString() ?? ''),
commodityCode: (json['commodityCode'] ?? json['commodity_code'] ?? json['commodityName'] ?? json['commodity_name'] ?? '').toString(),
commodityName: json['commodityName'] ?? json['commodity_name'],
purity: json['purity'],
rate: (json['rate'] as num?)?.toDouble() ?? 0.0,
effectiveAt: json['effectiveAt'] != null
? DateTime.parse(json['effectiveAt'].toString())
: (json['effective_at'] != null ? DateTime.parse(json['effective_at'].toString()) : DateTime.now()),
source: json['source'],
createdBy: json['createdBy'] ?? json['created_by'],
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'].toString())
: (json['created_at'] != null ? DateTime.parse(json['created_at'].toString()) : null),
);
}
Map<String, dynamic> toJson() {
return {
if (id != null) 'id': id,
'commodityCode': commodityCode,
'commodityName': commodityName ?? commodityCode,
if (purity != null) 'purity': purity,
'rate': rate,
'effectiveAt': effectiveAt.toIso8601String(),
if (source != null) 'source': source,
if (createdBy != null) 'createdBy': createdBy,
};
}
}

View File

@@ -243,9 +243,10 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
final _hsnController = TextEditingController();
final _gstController = TextEditingController();
final _makingChargeController = TextEditingController();
final _purityFactorController = TextEditingController(text: '1.0');
int? _selectedParentId;
bool _isLeaf = false;
bool _isLeaf = true;
bool _huidRequired = false;
String _commodityCode = 'XAU';
String _makingChargeType = 'PER_GRAM';
@@ -271,6 +272,7 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
_hsnController.text = c.defaultHsn ?? '';
_gstController.text = c.defaultGst?.toString() ?? '';
_makingChargeController.text = c.defaultMakingCharge?.toString() ?? '';
_purityFactorController.text = (c.purityFactor ?? 1.0).toString();
_huidRequired = c.huidRequired;
_commodityCode = c.commodityCode ?? 'XAU';
@@ -293,6 +295,7 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
_hsnController.dispose();
_gstController.dispose();
_makingChargeController.dispose();
_purityFactorController.dispose();
super.dispose();
}
@@ -304,6 +307,7 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
parentCategoryId: _selectedParentId,
hasChild: !_isLeaf,
commodityCode: _isLeaf ? _commodityCode : null,
purityFactor: _isLeaf ? (double.tryParse(_purityFactorController.text) ?? 1.0) : 1.0,
defaultHsn: _isLeaf ? _hsnController.text.trim() : null,
defaultGst: _isLeaf ? double.tryParse(_gstController.text) : null,
huidRequired: _isLeaf ? _huidRequired : false,
@@ -457,17 +461,34 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
const SizedBox(height: 16),
if (isBusinessMode) ...[
DropdownButtonFormField<String>(
value: _commodityCode,
decoration: InputDecoration(
labelText: 'Commodity Code',
prefixIcon: const Icon(LucideIcons.barChart2),
),
items: _commodities.map((c) => DropdownMenuItem(
value: c['code'],
child: Text(c['label']!),
)).toList(),
onChanged: (val) => setState(() => _commodityCode = val!),
Row(
children: [
Expanded(
flex: 3,
child: DropdownButtonFormField<String>(
value: _commodityCode,
decoration: const InputDecoration(
labelText: 'Commodity Code',
prefixIcon: Icon(LucideIcons.barChart2),
),
items: _commodities.map((c) => DropdownMenuItem(
value: c['code'],
child: Text(c['label']!),
)).toList(),
onChanged: (val) => setState(() => _commodityCode = val!),
),
),
const SizedBox(width: 16),
Expanded(
flex: 2,
child: _buildTextField(
controller: _purityFactorController,
label: 'Purity (e.g. 0.916)',
icon: LucideIcons.gem,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
),
),
],
),
const SizedBox(height: 16),
],

View File

@@ -4,6 +4,7 @@ import 'package:kifi_app/features/inventory/domain/inventory_item.dart';
import 'package:kifi_app/features/inventory/domain/product.dart';
import 'package:kifi_app/features/inventory/providers/products_provider.dart';
import 'package:kifi_app/features/inventory/providers/product_categories_provider.dart';
import 'package:kifi_app/features/inventory/providers/commodity_rates_provider.dart';
import 'package:kifi_app/core/network/dio_client.dart';
import 'package:intl/intl.dart';
import 'package:lucide_icons/lucide_icons.dart';
@@ -63,12 +64,26 @@ class _StockLedgerTabState extends ConsumerState<StockLedgerTab> {
}
final categoriesState = ref.watch(productCategoriesProvider);
final category = categoriesState.value?.firstWhere(
final commodityRatesState = ref.watch(commodityRatesProvider);
final category = categoriesState.value?.where(
(c) => c.id == widget.product.categoryId,
);
).firstOrNull;
final String unit = category?.baseUnit ?? 'g';
final double currentRate = category?.dailyRate ?? 0.0;
final double purityFactor = category?.purityFactor ?? (widget.product.purityFactor ?? 1.0);
double commodityRate = 0.0;
if (category?.commodityCode != null && commodityRatesState.value != null) {
final match = commodityRatesState.value!.where(
(r) => r.commodityCode.toUpperCase() == category!.commodityCode!.toUpperCase()
).firstOrNull;
if (match != null) {
commodityRate = match.rate;
}
}
final double currentRate = commodityRate > 0 ? (commodityRate * purityFactor) : (category?.dailyRate ?? 0.0);
return RefreshIndicator(
onRefresh: _fetchLedger,

View File

@@ -0,0 +1,88 @@
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/network/dio_client.dart';
import '../domain/commodity_rate.dart';
class CommodityRatesNotifier extends AsyncNotifier<List<CommodityRateHistory>> {
@override
FutureOr<List<CommodityRateHistory>> build() async {
return _fetchLatestRates();
}
Future<List<CommodityRateHistory>> _fetchLatestRates() async {
try {
final response = await DioClient().dio.get('/inventory/commodity-rates/latest');
if (response.statusCode == 200) {
final List<dynamic> data = response.data;
return data.map((e) => CommodityRateHistory.fromJson(e)).toList();
}
} catch (e) {
// Fallback if empty or endpoint fails
}
return [];
}
Future<List<CommodityRateHistory>> fetchRateHistory(String commodityCode) async {
try {
final response = await DioClient().dio.get('/inventory/commodity-rates/$commodityCode');
if (response.statusCode == 200) {
final List<dynamic> data = response.data;
return data.map((e) => CommodityRateHistory.fromJson(e)).toList();
}
} catch (e) {
// Error fetching history
}
return [];
}
Future<CommodityRateHistory?> addRate(
String commodityCode,
double rate, {
String? commodityName,
String? purity,
String? source,
}) async {
try {
final payload = {
'commodityCode': commodityCode,
'commodityName': commodityName ?? commodityCode,
if (purity != null) 'purity': purity,
'rate': rate,
'source': source ?? 'MANUAL',
};
final response = await DioClient().dio.post(
'/inventory/commodity-rates',
data: payload,
);
if (response.statusCode == 200 && response.data != null) {
final newRate = CommodityRateHistory.fromJson(response.data);
state = await AsyncValue.guard(() => _fetchLatestRates());
return newRate;
}
} catch (e) {
rethrow;
}
return null;
}
Future<int> syncRateToProducts(String commodityCode) async {
try {
final response = await DioClient().dio.post(
'/inventory/commodity-rates/$commodityCode/sync',
);
if (response.statusCode == 200 && response.data != null) {
return (response.data['updatedCount'] as num?)?.toInt() ?? 0;
}
} catch (e) {
rethrow;
}
return 0;
}
}
final commodityRatesProvider =
AsyncNotifierProvider<CommodityRatesNotifier, List<CommodityRateHistory>>(() {
return CommodityRatesNotifier();
});

View File

@@ -9,6 +9,7 @@ class ProductCategory {
final int? parentCategoryId;
final bool hasChild;
final String? commodityCode;
final double? purityFactor;
final String? defaultHsn;
final double? defaultGst;
final bool huidRequired;
@@ -26,6 +27,7 @@ class ProductCategory {
this.parentCategoryId,
this.hasChild = false,
this.commodityCode,
this.purityFactor = 1.0,
this.defaultHsn,
this.defaultGst,
this.huidRequired = false,
@@ -44,16 +46,17 @@ class ProductCategory {
name: json['name'],
parentCategoryId: json['parentCategoryId'],
hasChild: json['hasChild'] ?? false,
commodityCode: json['commodityCode'],
defaultHsn: json['defaultHsn'],
defaultGst: (json['defaultGst'] as num?)?.toDouble(),
huidRequired: json['huidRequired'] ?? false,
defaultMakingCharge: (json['defaultMakingCharge'] as num?)?.toDouble(),
makingChargeType: json['makingChargeType'],
baseUnit: json['baseUnit'] ?? 'pcs',
isActive: json['isActive'] ?? true,
sortOrder: json['sortOrder'] ?? 0,
dailyRate: (json['dailyRate'] as num?)?.toDouble(),
commodityCode: json['commodityCode'] ?? json['commodity_code'],
purityFactor: (json['purityFactor'] ?? json['purity_factor'] as num?)?.toDouble() ?? 1.0,
defaultHsn: json['defaultHsn'] ?? json['default_hsn'],
defaultGst: (json['defaultGst'] ?? json['default_gst'] as num?)?.toDouble(),
huidRequired: json['huidRequired'] ?? json['huid_required'] ?? false,
defaultMakingCharge: (json['defaultMakingCharge'] ?? json['default_making_charge'] as num?)?.toDouble(),
makingChargeType: json['makingChargeType'] ?? json['making_charge_type'],
baseUnit: json['baseUnit'] ?? json['base_unit'] ?? 'pcs',
isActive: json['isActive'] ?? json['is_active'] ?? true,
sortOrder: json['sortOrder'] ?? json['sort_order'] ?? 0,
dailyRate: (json['dailyRate'] ?? json['daily_rate'] as num?)?.toDouble(),
);
}
@@ -65,6 +68,7 @@ class ProductCategory {
'parentCategoryId': parentCategoryId,
'hasChild': hasChild,
'commodityCode': commodityCode,
'purityFactor': purityFactor ?? 1.0,
'defaultHsn': defaultHsn,
'defaultGst': defaultGst,
'huidRequired': huidRequired,

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);
}
}
}

View File

@@ -108,6 +108,11 @@ class PaginatedTransactionNotifier extends Notifier<PaginatedTransactionState> {
final updatedList = state.transactions.where((t) => t.id != id).toList();
state = state.copyWith(transactions: updatedList);
}
void updateTransactionInState(Transaction tx) {
final updatedList = state.transactions.map((t) => t.id == tx.id ? tx : t).toList();
state = state.copyWith(transactions: updatedList);
}
}
final paginatedTransactionProvider = NotifierProvider<PaginatedTransactionNotifier, PaginatedTransactionState>(() {

View File

@@ -52,7 +52,6 @@ class TransactionNotifier extends AsyncNotifier<List<Transaction>> {
Future<void> updateTransaction(Transaction transaction, {List<Map<String, String>>? base64Attachments, List<int>? deletedAttachmentIds}) async {
final updatedTransaction = await ref.read(apiRepositoryProvider).updateTransaction(transaction);
bool hasChanges = false;
if (base64Attachments != null && base64Attachments.isNotEmpty) {
for (var attachment in base64Attachments) {
await ref.read(apiRepositoryProvider).addAttachment(
@@ -62,28 +61,21 @@ class TransactionNotifier extends AsyncNotifier<List<Transaction>> {
attachment['base64Content']!
);
}
hasChanges = true;
}
if (deletedAttachmentIds != null && deletedAttachmentIds.isNotEmpty) {
for (var id in deletedAttachmentIds) {
await ref.read(apiRepositoryProvider).deleteAttachment(id);
}
hasChanges = true;
}
if (hasChanges) {
ref.invalidateSelf();
} else {
if (state.value != null) {
final updatedList = state.value!.map((t) => t.id == updatedTransaction.id ? updatedTransaction : t).toList();
state = AsyncValue.data(updatedList);
}
}
ref.invalidate(walletProvider);
ref.invalidateSelf();
}
Future<void> deleteTransaction(int id) async {
await ref.read(apiRepositoryProvider).deleteTransaction(id);
ref.invalidate(walletProvider);
if (state.value != null) {
state = AsyncValue.data(state.value!.where((t) => t.id != id).toList());
}

View File

@@ -10,6 +10,7 @@ import '../../../core/widgets/premium_text_field.dart';
import '../../../core/widgets/smart_search_dropdown.dart';
import '../../inventory/providers/products_provider.dart';
import '../../inventory/providers/product_categories_provider.dart';
import '../../inventory/providers/commodity_rates_provider.dart';
import '../providers/vendors_provider.dart';
import '../../business/providers/business_provider.dart';
import '../../transactions/presentation/widgets/attachment_gallery_screen.dart';
@@ -735,13 +736,34 @@ class _AddItemSheetState extends ConsumerState<_AddItemSheet> {
if (_selectedProductId == null) return;
final qty = int.tryParse(_qtyCtrl.text) ?? 1;
final products = ref.read(productsProvider).value ?? [];
final categories = ref.read(productCategoriesProvider).value ?? [];
final commodityRates = ref.read(commodityRatesProvider).value ?? [];
final selectedProduct = products.where((p) => p.id == _selectedProductId).firstOrNull;
final selectedCat = categories.where((c) => c.id == selectedProduct?.categoryId).firstOrNull;
double initialRate = 0.0;
if (selectedCat?.commodityCode != null) {
final matchRate = commodityRates.where(
(r) => r.commodityCode.toUpperCase() == selectedCat!.commodityCode!.toUpperCase()
).firstOrNull;
if (matchRate != null && matchRate.rate > 0) {
final purity = selectedCat?.purityFactor ?? (selectedProduct?.purityFactor ?? 1.0);
initialRate = matchRate.rate * purity;
}
}
if (initialRate == 0.0) {
initialRate = selectedProduct?.sellingPrice ?? 0.0;
}
final List<PurchaseOrderItem> items = [];
for (int i = 0; i < qty; i++) {
items.add(PurchaseOrderItem(
productId: _selectedProductId,
quantity: 1.0, // Each row is 1 unit initially, or could represent pieces/weight
unitPrice: 0.0,
total: 0.0,
quantity: 1.0,
unitPrice: initialRate,
total: initialRate > 0 ? initialRate : 0.0,
));
}
Navigator.pop(context, items);