Done Category, Product Catalogue, Customer, Vendor, Purchase Invoice Module

This commit is contained in:
2026-08-28 11:30:18 +05:30
parent 0d13833679
commit 189f49ed07
118 changed files with 12624 additions and 4004 deletions

View File

@@ -34,7 +34,9 @@ class BusinessFeature {
bomReductionStrategy: json['bomReductionStrategy'] ?? 'COMPONENTS_ONLY',
stockDeductionOnInvoice: json['stockDeductionOnInvoice'] ?? true,
barcodeSource: json['barcodeSource'] ?? 'SKU',
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt']) : null,
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'])
: null,
);
}
@@ -71,7 +73,8 @@ class BusinessFeature {
purchaseManagement: purchaseManagement ?? this.purchaseManagement,
multiLocation: multiLocation ?? this.multiLocation,
bomReductionStrategy: bomReductionStrategy ?? this.bomReductionStrategy,
stockDeductionOnInvoice: stockDeductionOnInvoice ?? this.stockDeductionOnInvoice,
stockDeductionOnInvoice:
stockDeductionOnInvoice ?? this.stockDeductionOnInvoice,
barcodeSource: barcodeSource ?? this.barcodeSource,
createdAt: createdAt,
);

View File

@@ -3,11 +3,7 @@ class IndianState {
final String name;
final String gstCode;
IndianState({
required this.id,
required this.name,
required this.gstCode,
});
IndianState({required this.id, required this.name, required this.gstCode});
factory IndianState.fromJson(Map<String, dynamic> json) {
return IndianState(
@@ -18,10 +14,6 @@ class IndianState {
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'gstCode': gstCode,
};
return {'id': id, 'name': name, 'gstCode': gstCode};
}
}

View File

@@ -3,8 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../../core/theme/nature_colors.dart';
import '../../../inventory/presentation/product_list_screen.dart';
import '../../../inventory/presentation/daily_rates_screen.dart';
import '../../../inventory/presentation/quick_adjust_stock_screen.dart';
import '../../../inventory/presentation/uoms_list_screen.dart';
import '../../../inventory/presentation/category_management_screen.dart';
import '../../../sales/presentation/customers_list_screen.dart';
import '../../../sales/presentation/invoices_list_screen.dart';
@@ -22,12 +22,13 @@ class BusinessHubScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final businessState = ref.watch(businessProfileProvider);
final profile = businessState.value;
final title = (profile?.businessName != null && profile!.businessName.isNotEmpty)
? profile.businessName
final title =
(profile?.businessName != null && profile!.businessName.isNotEmpty)
? profile.businessName
: 'Business Overview';
final subtitle = (profile?.address != null && profile!.address!.isNotEmpty)
? profile.address!
final subtitle = (profile?.address != null && profile!.address!.isNotEmpty)
? profile.address!
: 'Manage your inventory and stock';
final featureState = ref.watch(businessFeatureProvider).value;
@@ -35,236 +36,337 @@ class BusinessHubScreen extends ConsumerWidget {
final bool showSales = featureState?.salesManagement ?? false;
final bool showPurchase = featureState?.purchaseManagement ?? false;
return SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InkWell(
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => const BusinessProfileFormSheet(),
);
},
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Theme.of(context).colorScheme.primary.withOpacity(0.2)),
return RefreshIndicator(
onRefresh: () async {
ref.invalidate(businessProfileProvider);
ref.invalidate(businessFeatureProvider);
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InkWell(
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => const BusinessProfileFormSheet(),
);
},
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Theme.of(
context,
).colorScheme.primary.withOpacity(0.2),
),
),
child: Row(
children: [
Icon(
LucideIcons.briefcase,
color: Theme.of(context).colorScheme.primary,
size: 32,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleLarge
?.copyWith(fontWeight: FontWeight.bold),
),
Text(
subtitle,
style: const TextStyle(color: Colors.grey),
),
],
),
),
],
),
),
child: Row(
children: [
Icon(LucideIcons.briefcase, color: Theme.of(context).colorScheme.primary, size: 32),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold)),
Text(subtitle, style: const TextStyle(color: Colors.grey)),
],
),
const SizedBox(height: 24),
GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
childAspectRatio: 1.2,
children: [
if (showInventory) ...[
_buildActionCard(
context,
'Categories',
'Manage catalog structure',
LucideIcons.listTree,
Colors.teal,
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const CategoryManagementScreen(),
),
),
),
_buildActionCard(
context,
'Products Catalog',
'View all products',
LucideIcons.packageSearch,
Colors.blue,
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const ProductListScreen(),
),
),
),
],
),
if (showSales)
_buildActionCard(
context,
'Customers',
'Manage clients',
LucideIcons.users,
NatureColors.getColor('RECEIVABLES'),
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const CustomersListScreen(),
),
),
),
if (showPurchase)
_buildActionCard(
context,
'Vendors',
'Manage suppliers',
LucideIcons.truck,
Colors.orange,
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const VendorsListScreen(),
),
),
),
if (showPurchase)
_buildActionCard(
context,
'Purchases',
'Manage inward stock',
LucideIcons.clipboardList,
Colors.deepOrange,
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const PurchaseOrdersListScreen(),
),
),
),
if (showSales)
_buildActionCard(
context,
'Sales & Invoices',
'Create invoices',
LucideIcons.receipt,
Colors.green,
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const InvoicesListScreen(),
),
),
),
if (showInventory)
_buildActionCard(
context,
'Daily Rates',
'Sync live gold/silver rates',
LucideIcons.refreshCw,
Colors.orange,
() => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const DailyRatesScreen(),
),
),
),
_buildActionCard(
context,
'Reports',
'Analytics & Valuation',
LucideIcons.barChart2,
Colors.indigo,
() => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const ReportsScreen()),
),
),
],
),
),
const SizedBox(height: 24),
GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
childAspectRatio: 1.2,
children: [
if (showInventory) ...[
_buildActionCard(
context,
'Products Catalog',
'View all products',
LucideIcons.packageSearch,
Colors.blue,
() => Navigator.push(context, MaterialPageRoute(builder: (_) => const ProductListScreen())),
),
_buildActionCard(
context,
'Adjust Stock',
'Quick Stock Edit',
LucideIcons.arrowRightLeft,
Colors.orange,
() => Navigator.push(context, MaterialPageRoute(builder: (_) => const QuickAdjustStockScreen())),
),
_buildActionCard(
context,
'Units of Measure',
'Manage measurements',
LucideIcons.ruler,
Colors.purple,
() => Navigator.push(context, MaterialPageRoute(builder: (_) => const UomsListScreen())),
),
_buildActionCard(
context,
'Categories',
'Manage catalog structure',
LucideIcons.listTree,
Colors.teal,
() => Navigator.push(context, MaterialPageRoute(builder: (_) => const CategoryManagementScreen())),
),
],
if (showSales) ...[
_buildActionCard(
context,
'Sales & Invoices',
'Create invoices',
LucideIcons.receipt,
Colors.green,
() => Navigator.push(context, MaterialPageRoute(builder: (_) => const InvoicesListScreen())),
),
_buildActionCard(
context,
'Customers',
'Manage clients',
LucideIcons.users,
NatureColors.getColor('RECEIVABLES'),
() => Navigator.push(context, MaterialPageRoute(builder: (_) => const CustomersListScreen())),
),
],
if (showPurchase) ...[
_buildActionCard(
context,
'Vendors',
'Manage suppliers',
LucideIcons.truck,
Colors.orange,
() => Navigator.push(context, MaterialPageRoute(builder: (_) => const VendorsListScreen())),
),
_buildActionCard(
context,
'Purchase Orders',
'Manage inward stock',
LucideIcons.clipboardList,
Colors.deepOrange,
() => Navigator.push(context, MaterialPageRoute(builder: (_) => const PurchaseOrdersListScreen())),
),
],
_buildActionCard(
context,
'Reports',
'Analytics & Valuation',
LucideIcons.barChart2,
Colors.indigo,
() => Navigator.push(context, MaterialPageRoute(builder: (_) => const ReportsScreen())),
if (showInventory) ...[
const SizedBox(height: 32),
Text(
'Low Stock Alerts',
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
),
],
),
if (showInventory) ...[
const SizedBox(height: 32),
Text('Low Stock Alerts', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold)),
Consumer(
builder: (context, ref, child) {
final productsState = ref.watch(productsProvider);
return productsState.when(
data: (products) {
final lowStockProducts = products.where((p) {
if (!(p.trackInventory ?? false)) return false;
if (p.currentStock == null) return false;
final minStock = p.minStock ?? 0;
return p.currentStock! <= minStock;
}).toList();
Consumer(
builder: (context, ref, child) {
final productsState = ref.watch(productsProvider);
return productsState.when(
data: (products) {
final lowStockProducts = products.where((p) {
if (p.currentStock == null) return false;
if (p.currentStock == null) return false;
final minStock = 0;
return p.currentStock! <= minStock;
}).toList();
if (lowStockProducts.isEmpty) {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.05),
borderRadius: BorderRadius.circular(16),
),
child: const Center(
child: Text('All products are adequately stocked.', style: TextStyle(color: Colors.grey)),
),
);
}
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: lowStockProducts.length > 5 ? 5 : lowStockProducts.length,
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemBuilder: (context, index) {
final product = lowStockProducts[index];
final isOutOfStock = product.currentStock! <= 0;
if (lowStockProducts.isEmpty) {
return Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: isOutOfStock ? Colors.red.withOpacity(0.05) : Colors.orange.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: isOutOfStock ? Colors.red.withOpacity(0.2) : Colors.orange.withOpacity(0.2)),
color: Colors.grey.withOpacity(0.05),
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isOutOfStock ? Colors.red.withOpacity(0.1) : Colors.orange.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
LucideIcons.alertTriangle,
color: isOutOfStock ? Colors.red : Colors.orange,
size: 20,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(product.name, style: const TextStyle(fontWeight: FontWeight.bold)),
if (product.sku != null && product.sku!.isNotEmpty)
Text('SKU: ${product.sku}', style: TextStyle(fontSize: 12, color: Colors.grey.shade600)),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${product.currentStock} left',
style: TextStyle(
fontWeight: FontWeight.bold,
color: isOutOfStock ? Colors.red : Colors.orange.shade800,
),
),
Text(
'Min: ${product.minStock ?? 0}',
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
),
],
),
],
child: const Center(
child: Text(
'All products are adequately stocked.',
style: TextStyle(color: Colors.grey),
),
),
);
},
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
);
},
),
}
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: lowStockProducts.length > 5
? 5
: lowStockProducts.length,
separatorBuilder: (context, index) =>
const SizedBox(height: 12),
itemBuilder: (context, index) {
final product = lowStockProducts[index];
final isOutOfStock = product.currentStock! <= 0;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isOutOfStock
? Colors.red.withOpacity(0.05)
: Colors.orange.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isOutOfStock
? Colors.red.withOpacity(0.2)
: Colors.orange.withOpacity(0.2),
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isOutOfStock
? Colors.red.withOpacity(0.1)
: Colors.orange.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
LucideIcons.alertTriangle,
color: isOutOfStock
? Colors.red
: Colors.orange,
size: 20,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
product.name,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
if (product.sku != null &&
product.sku!.isNotEmpty)
Text(
'SKU: ${product.sku}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${product.currentStock} left',
style: TextStyle(
fontWeight: FontWeight.bold,
color: isOutOfStock
? Colors.red
: Colors.orange.shade800,
),
),
Text(
'Out of stock',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
],
),
);
},
);
},
loading: () =>
const Center(child: CircularProgressIndicator()),
error: (error, stack) =>
Center(child: Text('Error: $error')),
);
},
),
],
],
],
),
),
);
}
Widget _buildActionCard(BuildContext context, String title, String subtitle, IconData icon, Color color, VoidCallback onTap) {
Widget _buildActionCard(
BuildContext context,
String title,
String subtitle,
IconData icon,
Color color,
VoidCallback onTap,
) {
return GestureDetector(
onTap: onTap,
child: Container(
@@ -280,9 +382,19 @@ class BusinessHubScreen extends ConsumerWidget {
children: [
Icon(icon, color: color, size: 28),
const SizedBox(height: 12),
Text(title, style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 16)),
Text(
title,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const SizedBox(height: 4),
Text(subtitle, style: TextStyle(color: color.withOpacity(0.7), fontSize: 12)),
Text(
subtitle,
style: TextStyle(color: color.withOpacity(0.7), fontSize: 12),
),
],
),
),

View File

@@ -13,9 +13,7 @@ class ReportsScreen extends ConsumerWidget {
final formatCurrency = NumberFormat.currency(symbol: '');
return Scaffold(
appBar: AppBar(
title: const Text('Business Reports'),
),
appBar: AppBar(title: const Text('Business Reports')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
@@ -29,7 +27,10 @@ class ReportsScreen extends ConsumerWidget {
content: valuationState.when(
data: (val) => Text(
'Total Estimated Value: ${formatCurrency.format(val)}',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
loading: () => const CircularProgressIndicator(),
error: (e, s) => Text('Error: $e'),
@@ -41,7 +42,9 @@ class ReportsScreen extends ConsumerWidget {
title: 'GST Report (Coming Soon)',
icon: LucideIcons.fileText,
color: Colors.orange,
content: const Text('Export GSTR-1 & GSTR-3B formats based on invoices.'),
content: const Text(
'Export GSTR-1 & GSTR-3B formats based on invoices.',
),
),
const SizedBox(height: 16),
_buildReportCard(
@@ -57,7 +60,8 @@ class ReportsScreen extends ConsumerWidget {
);
}
Widget _buildReportCard(BuildContext context, {
Widget _buildReportCard(
BuildContext context, {
required String title,
required IconData icon,
required Color color,
@@ -81,7 +85,13 @@ class ReportsScreen extends ConsumerWidget {
child: Icon(icon, color: color),
),
const SizedBox(width: 12),
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),

View File

@@ -7,10 +7,12 @@ class BusinessSettingsScreen extends ConsumerStatefulWidget {
const BusinessSettingsScreen({super.key});
@override
ConsumerState<BusinessSettingsScreen> createState() => _BusinessSettingsScreenState();
ConsumerState<BusinessSettingsScreen> createState() =>
_BusinessSettingsScreenState();
}
class _BusinessSettingsScreenState extends ConsumerState<BusinessSettingsScreen> {
class _BusinessSettingsScreenState
extends ConsumerState<BusinessSettingsScreen> {
bool _taxIncludedInPrice = false;
bool _salesEnabled = true;
bool _inventoryEnabled = true;
@@ -48,11 +50,20 @@ class _BusinessSettingsScreenState extends ConsumerState<BusinessSettingsScreen>
body: ListView(
padding: const EdgeInsets.all(24.0),
children: [
const Text('Module Configuration', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey)),
const Text(
'Module Configuration',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
const SizedBox(height: 16),
SwitchListTile(
title: const Text('Inventory Management'),
subtitle: const Text('Track stock levels, multiple locations, and products'),
subtitle: const Text(
'Track stock levels, multiple locations, and products',
),
value: _inventoryEnabled,
onChanged: (val) {
setState(() => _inventoryEnabled = val);
@@ -62,7 +73,9 @@ class _BusinessSettingsScreenState extends ConsumerState<BusinessSettingsScreen>
),
SwitchListTile(
title: const Text('Sales & POS'),
subtitle: const Text('Enable point of sale, invoicing, and receivables'),
subtitle: const Text(
'Enable point of sale, invoicing, and receivables',
),
value: _salesEnabled,
onChanged: (val) {
setState(() => _salesEnabled = val);
@@ -72,7 +85,9 @@ class _BusinessSettingsScreenState extends ConsumerState<BusinessSettingsScreen>
),
SwitchListTile(
title: const Text('Vendor & Purchases'),
subtitle: const Text('Manage vendors, purchase orders, and inward stock'),
subtitle: const Text(
'Manage vendors, purchase invoices, and inward stock',
),
value: _purchaseEnabled,
onChanged: (val) {
setState(() => _purchaseEnabled = val);
@@ -81,64 +96,105 @@ class _BusinessSettingsScreenState extends ConsumerState<BusinessSettingsScreen>
secondary: const Icon(LucideIcons.truck),
),
const SizedBox(height: 32),
const Text('Inventory Strategy', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey)),
const Text(
'Inventory Strategy',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
const SizedBox(height: 16),
Consumer(
builder: (context, ref, child) {
final featureState = ref.watch(businessFeatureProvider);
final currentStrategy = featureState.value?.bomReductionStrategy ?? 'COMPONENTS_ONLY';
final deductParentStock = currentStrategy == 'PARENT_AND_COMPONENTS';
final stockDeductionOnInvoice = featureState.value?.stockDeductionOnInvoice ?? true;
final currentStrategy =
featureState.value?.bomReductionStrategy ?? 'COMPONENTS_ONLY';
final deductParentStock =
currentStrategy == 'PARENT_AND_COMPONENTS';
final stockDeductionOnInvoice =
featureState.value?.stockDeductionOnInvoice ?? true;
return Column(
children: [
SwitchListTile(
title: const Text('Deduct Parent Stock on BOM Sale'),
subtitle: const Text('If enabled, selling an assembled product reduces both parent and component stock.'),
subtitle: const Text(
'If enabled, selling an assembled product reduces both parent and component stock.',
),
value: deductParentStock,
secondary: const Icon(LucideIcons.gitMerge),
onChanged: (val) {
if (featureState.value != null) {
final newStrategy = val ? 'PARENT_AND_COMPONENTS' : 'COMPONENTS_ONLY';
final updated = featureState.value!.copyWith(bomReductionStrategy: newStrategy);
ref.read(businessFeatureProvider.notifier).updateFeatures(updated);
final newStrategy = val
? 'PARENT_AND_COMPONENTS'
: 'COMPONENTS_ONLY';
final updated = featureState.value!.copyWith(
bomReductionStrategy: newStrategy,
);
ref
.read(businessFeatureProvider.notifier)
.updateFeatures(updated);
}
},
),
SwitchListTile(
title: const Text('Stock Deduction on Invoice'),
subtitle: const Text('If enabled, finalizing an invoice automatically deducts product stock. Turn off for a two-step fulfillment process.'),
subtitle: const Text(
'If enabled, finalizing an invoice automatically deducts product stock. Turn off for a two-step fulfillment process.',
),
value: stockDeductionOnInvoice,
secondary: const Icon(LucideIcons.boxes),
onChanged: (val) {
if (featureState.value != null) {
final updated = featureState.value!.copyWith(stockDeductionOnInvoice: val);
ref.read(businessFeatureProvider.notifier).updateFeatures(updated);
final updated = featureState.value!.copyWith(
stockDeductionOnInvoice: val,
);
ref
.read(businessFeatureProvider.notifier)
.updateFeatures(updated);
}
},
),
SwitchListTile(
title: const Text('Use EAN/Barcode for Scanner'),
subtitle: const Text('If disabled, the barcode scanner will search using the SKU field instead.'),
value: (featureState.value?.barcodeSource ?? 'SKU') == 'BARCODE',
subtitle: const Text(
'If disabled, the barcode scanner will search using the SKU field instead.',
),
value:
(featureState.value?.barcodeSource ?? 'SKU') ==
'BARCODE',
secondary: const Icon(LucideIcons.scanLine),
onChanged: (val) {
if (featureState.value != null) {
final updated = featureState.value!.copyWith(barcodeSource: val ? 'BARCODE' : 'SKU');
ref.read(businessFeatureProvider.notifier).updateFeatures(updated);
final updated = featureState.value!.copyWith(
barcodeSource: val ? 'BARCODE' : 'SKU',
);
ref
.read(businessFeatureProvider.notifier)
.updateFeatures(updated);
}
},
),
],
);
}
},
),
const SizedBox(height: 32),
const Text('Pricing & Taxation', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey)),
const Text(
'Pricing & Taxation',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
const SizedBox(height: 16),
SwitchListTile(
title: const Text('Selling Price includes Tax'),
subtitle: const Text('If enabled, GST is assumed to be inclusive in the entered selling price.'),
subtitle: const Text(
'If enabled, GST is assumed to be inclusive in the entered selling price.',
),
value: _taxIncludedInPrice,
onChanged: (val) {
setState(() => _taxIncludedInPrice = val);
@@ -154,7 +210,9 @@ class _BusinessSettingsScreenState extends ConsumerState<BusinessSettingsScreen>
void _saveSettings() {
final profileState = ref.read(businessProfileProvider).value;
if (profileState != null) {
final updated = profileState.copyWith(taxIncludedInPrice: _taxIncludedInPrice);
final updated = profileState.copyWith(
taxIncludedInPrice: _taxIncludedInPrice,
);
ref.read(businessProfileProvider.notifier).updateProfile(updated);
}
}

View File

@@ -25,7 +25,7 @@ class BusinessProfileNotifier extends AsyncNotifier<BusinessProfile?> {
'/business/profile',
data: profile.toJson(),
);
if (response.statusCode == 200) {
state = AsyncValue.data(BusinessProfile.fromJson(response.data));
}
@@ -35,9 +35,10 @@ class BusinessProfileNotifier extends AsyncNotifier<BusinessProfile?> {
}
}
final businessProfileProvider = AsyncNotifierProvider<BusinessProfileNotifier, BusinessProfile?>(() {
return BusinessProfileNotifier();
});
final businessProfileProvider =
AsyncNotifierProvider<BusinessProfileNotifier, BusinessProfile?>(() {
return BusinessProfileNotifier();
});
class BusinessFeatureNotifier extends AsyncNotifier<BusinessFeature?> {
@override
@@ -64,7 +65,7 @@ class BusinessFeatureNotifier extends AsyncNotifier<BusinessFeature?> {
'/business/features',
data: feature.toJson(),
);
if (response.statusCode == 200) {
state = AsyncValue.data(BusinessFeature.fromJson(response.data));
}
@@ -74,6 +75,7 @@ class BusinessFeatureNotifier extends AsyncNotifier<BusinessFeature?> {
}
}
final businessFeatureProvider = AsyncNotifierProvider<BusinessFeatureNotifier, BusinessFeature?>(() {
return BusinessFeatureNotifier();
});
final businessFeatureProvider =
AsyncNotifierProvider<BusinessFeatureNotifier, BusinessFeature?>(() {
return BusinessFeatureNotifier();
});

View File

@@ -9,17 +9,27 @@ class IndianStatesNotifier extends AsyncNotifier<List<IndianState>> {
}
Future<List<IndianState>> _fetchStates() async {
final response = await DioClient().dio.get('/master/states').timeout(
const Duration(seconds: 5),
onTimeout: () => throw Exception('Connection timed out'),
);
final response = await DioClient().dio
.get('/master/states')
.timeout(
const Duration(seconds: 5),
onTimeout: () => throw Exception('Connection timed out'),
);
if (response.data == null || response.data.toString().isEmpty) {
return [];
}
return (response.data as List).map((e) => IndianState.fromJson(e)).toList();
final list = (response.data as List).map((e) => IndianState.fromJson(e)).toList();
final Map<String, IndianState> uniqueStates = {};
for (var state in list) {
uniqueStates[state.gstCode] = state;
}
final result = uniqueStates.values.toList();
result.sort((a, b) => a.name.compareTo(b.name));
return result;
}
}
final indianStatesProvider = AsyncNotifierProvider<IndianStatesNotifier, List<IndianState>>(() {
return IndianStatesNotifier();
});
final indianStatesProvider =
AsyncNotifierProvider<IndianStatesNotifier, List<IndianState>>(() {
return IndianStatesNotifier();
});