diff --git a/lib/core/constants/api_endpoints.dart b/lib/core/constants/api_endpoints.dart index 787a77e..edec7ed 100644 --- a/lib/core/constants/api_endpoints.dart +++ b/lib/core/constants/api_endpoints.dart @@ -56,8 +56,6 @@ class ApiEndpoints { '/masters/item-subcategories/$id'; static const String items = '/masters/items'; static String itemById(String id) => '/masters/items/$id'; - static const String brands = '/masters/brands'; - static String brandById(String id) => '/masters/brands/$id'; static const String documentSeries = '/masters/document-series'; static String documentSeriesById(String id) => '/masters/document-series/$id'; static const String deliveryTerms = '/masters/delivery-terms'; diff --git a/lib/modules/assets/data/datasources/asset_remote_data_source.dart b/lib/modules/assets/data/datasources/asset_remote_data_source.dart index add3d17..f85e051 100644 --- a/lib/modules/assets/data/datasources/asset_remote_data_source.dart +++ b/lib/modules/assets/data/datasources/asset_remote_data_source.dart @@ -104,10 +104,20 @@ class AssetRemoteDataSource { return _parseList(response.data, AssetTransferHistoryModel.fromJson); } - Future> getCategories() async { + Future> getCategories({ + bool dropdownCall = false, + }) async { final response = await dio.get( ApiEndpoints.itemCategories, - queryParameters: const {'limit': 100, 'is_active': true}, + queryParameters: { + if (dropdownCall) + 'dropdown_call': true + else ...const { + 'limit': 100, + 'is_active': true, + }, + 'category_type': 'ASSET', + }, ); return _parseList(response.data, AssetCategoryModel.fromJson) .where((category) => category.isActive) diff --git a/lib/modules/assets/data/repositories/asset_repository_impl.dart b/lib/modules/assets/data/repositories/asset_repository_impl.dart index 30316cb..826674b 100644 --- a/lib/modules/assets/data/repositories/asset_repository_impl.dart +++ b/lib/modules/assets/data/repositories/asset_repository_impl.dart @@ -63,8 +63,10 @@ class AssetRepositoryImpl implements AssetRepository { } @override - Future>> getCategories() { - return safeApiCall(() => dataSource.getCategories()); + Future>> getCategories({ + bool dropdownCall = false, + }) { + return safeApiCall(() => dataSource.getCategories(dropdownCall: dropdownCall)); } @override diff --git a/lib/modules/assets/domain/repositories/asset_repository.dart b/lib/modules/assets/domain/repositories/asset_repository.dart index 380cc05..84d6bf3 100644 --- a/lib/modules/assets/domain/repositories/asset_repository.dart +++ b/lib/modules/assets/domain/repositories/asset_repository.dart @@ -13,7 +13,9 @@ abstract class AssetRepository { Future> deleteAsset(String id); Future> transferAsset(String id, Map data); Future>> getTransferHistory(String assetId); - Future>> getCategories(); + Future>> getCategories({ + bool dropdownCall = false, + }); Future> getAssetOptions(); Future>> getContractTypes(); Future>> getVisitTypes(); diff --git a/lib/modules/assets/presentation/providers/asset_categories_provider.dart b/lib/modules/assets/presentation/providers/asset_categories_provider.dart index e770323..215af11 100644 --- a/lib/modules/assets/presentation/providers/asset_categories_provider.dart +++ b/lib/modules/assets/presentation/providers/asset_categories_provider.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../data/repositories/asset_repository_impl.dart'; import '../../../../shared/models/asset_model.dart'; -/// Item categories from `/masters/item-categories` (shared for items and assets). +/// Categories for Assets list filter (paginated list API — no dropdown_call). final itemCategoriesProvider = FutureProvider>((ref) async { final repository = ref.watch(assetRepositoryProvider); final result = await repository.getCategories(); @@ -11,5 +11,14 @@ final itemCategoriesProvider = FutureProvider>((ref) as return result.data ?? []; }); +/// Categories for Asset form dropdowns (`dropdown_call=true`). +final itemCategoriesFormProvider = + FutureProvider>((ref) async { + final repository = ref.watch(assetRepositoryProvider); + final result = await repository.getCategories(dropdownCall: true); + if (result.failure != null) throw result.failure!; + return result.data ?? []; +}); + @Deprecated('Use itemCategoriesProvider') final assetCategoriesProvider = itemCategoriesProvider; diff --git a/lib/modules/assets/presentation/providers/asset_form_lookups_provider.dart b/lib/modules/assets/presentation/providers/asset_form_lookups_provider.dart index b415e26..8216cce 100644 --- a/lib/modules/assets/presentation/providers/asset_form_lookups_provider.dart +++ b/lib/modules/assets/presentation/providers/asset_form_lookups_provider.dart @@ -1,19 +1,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../core/constants/app_constants.dart'; import '../../../../core/network/api_handler.dart'; import '../../../../core/utils/active_option.dart'; import '../../../../shared/models/asset_model.dart'; -import '../../../../shared/models/grn_model.dart'; -import '../../../../shared/models/purchase_order_model.dart'; import '../../../../shared/models/user_management_models.dart'; -import '../../../../shared/models/vendor_model.dart'; import '../../../../shared/widgets/app_dropdown.dart'; import '../../../assets/data/repositories/asset_repository_impl.dart'; import '../../../grn/data/repositories/grn_repository_impl.dart'; import '../../../masters/data/datasources/master_remote_data_source.dart'; import '../../../purchase_orders/data/repositories/purchase_order_repository_impl.dart'; -import '../../../users/presentation/providers/users_provider.dart'; +import '../../../users/data/repositories/user_repository_impl.dart'; import '../../../vendors/data/repositories/vendor_repository_impl.dart'; class AssetFormLookups { @@ -117,41 +113,24 @@ Future> _safeOptions( Future> _safeVendorOptions(Ref ref) async { try { - final vendors = []; - var page = 1; - var totalPages = 1; + final result = + await ref.read(vendorRepositoryProvider).listVendorOptions(); + if (result.failure != null || result.data == null) return const []; - while (page <= totalPages) { - final result = await ref.read(vendorRepositoryProvider).getVendors( - VendorListQuery( - page: page, - limit: AppConstants.maxPageSize, - isActive: true, - ), - ); - if (result.failure != null || result.data == null) return vendors; - - final data = result.data!; - vendors.addAll( - data.items - .where( - (vendor) => isActiveVendorOption( - isActive: vendor.isActive, - status: vendor.status, - ), - ) - .map( - (vendor) => FilterOptionModel( - id: vendor.id, - name: vendor.vendorName, - ), - ), - ); - totalPages = data.totalPages; - page++; - } - - return vendors; + return result.data! + .where( + (vendor) => isActiveVendorOption( + isActive: vendor.isActive, + status: vendor.status, + ), + ) + .map( + (vendor) => FilterOptionModel( + id: vendor.id, + name: vendor.vendorName, + ), + ) + .toList(); } catch (_) { return const []; } @@ -159,16 +138,9 @@ Future> _safeVendorOptions(Ref ref) async { Future> _safeUserOptions(Ref ref) async { try { - final result = await ref.read(getUsersUseCaseProvider)( - const UserListQuery( - page: 1, - limit: AppConstants.maxPageSize, - status: 'active', - isActive: true, - ), - ); + final result = await ref.read(userRepositoryProvider).listUserOptions(); if (result.failure != null || result.data == null) return const []; - return result.data!.items + return result.data! .where( (user) => isActiveUserOption( status: user.status, @@ -189,15 +161,11 @@ Future> _safeUserOptions(Ref ref) async { Future> _safePurchaseOrderOptions(Ref ref) async { try { - final result = - await ref.read(purchaseOrderRepositoryProvider).getPurchaseOrders( - const PurchaseOrderListQuery( - page: 1, - limit: AppConstants.maxPageSize, - ), - ); + final result = await ref + .read(purchaseOrderRepositoryProvider) + .listPurchaseOrderOptions(); if (result.failure != null || result.data == null) return const []; - return result.data!.items + return result.data! .map( (po) => FilterOptionModel( id: po.id, @@ -212,11 +180,9 @@ Future> _safePurchaseOrderOptions(Ref ref) async { Future> _safeGrnOptions(Ref ref) async { try { - final result = await ref.read(grnRepositoryProvider).getGrns( - const GrnListQuery(page: 1, limit: AppConstants.maxPageSize), - ); + final result = await ref.read(grnRepositoryProvider).listGrnOptions(); if (result.failure != null || result.data == null) return const []; - return result.data!.items + return result.data! .map( (grn) => FilterOptionModel( id: grn.id, diff --git a/lib/modules/assets/presentation/widgets/asset_form_panel.dart b/lib/modules/assets/presentation/widgets/asset_form_panel.dart index 02c4da9..bbfc59c 100644 --- a/lib/modules/assets/presentation/widgets/asset_form_panel.dart +++ b/lib/modules/assets/presentation/widgets/asset_form_panel.dart @@ -466,7 +466,7 @@ class _AssetFormPanelState extends ConsumerState { @override Widget build(BuildContext context) { - final categoriesAsync = ref.watch(itemCategoriesProvider); + final categoriesAsync = ref.watch(itemCategoriesFormProvider); final plantsAsync = ref.watch(assetPlantsProvider); if (widget.isEditing) { @@ -1091,6 +1091,7 @@ class _AssetFormPanelState extends ConsumerState { value: _dropdownValue(_categoryId, categoryIds), searchHint: 'Search category...', isDense: true, + initialValues: const {'category_type': 'ASSET'}, options: activeCategories .map( (c) => AppDropdownOption( @@ -1101,7 +1102,7 @@ class _AssetFormPanelState extends ConsumerState { .where((option) => option.value != 0) .toList(), refreshLookups: () { - ref.invalidate(itemCategoriesProvider); + ref.invalidate(itemCategoriesFormProvider); }, parseCreatedId: int.tryParse, onChanged: (v) => setState(() { diff --git a/lib/modules/grn/data/datasources/grn_remote_data_source.dart b/lib/modules/grn/data/datasources/grn_remote_data_source.dart index 58bc8fc..5162367 100644 --- a/lib/modules/grn/data/datasources/grn_remote_data_source.dart +++ b/lib/modules/grn/data/datasources/grn_remote_data_source.dart @@ -19,6 +19,26 @@ class GrnRemoteDataSource { return _parsePaginated(response.data, GrnModel.fromJson); } + /// Form-dropdown loader: all active GRNs (`dropdown_call=true`). + Future> listGrnOptions() async { + final response = await dio.get( + ApiEndpoints.grn, + queryParameters: const {'dropdown_call': true}, + ); + final body = response.data; + if (body is! Map) return const []; + final raw = body['data']; + final list = raw is List + ? raw + : raw is Map + ? (raw['items'] as List?) ?? const [] + : const []; + return list + .whereType() + .map((item) => GrnModel.fromJson(Map.from(item))) + .toList(); + } + Future getGrnById(String id) async { final response = await dio.get(ApiEndpoints.grnById(id)); return GrnModel.fromJson(response.data['data'] as Map); diff --git a/lib/modules/grn/data/repositories/grn_repository_impl.dart b/lib/modules/grn/data/repositories/grn_repository_impl.dart index 710aea0..11b592c 100644 --- a/lib/modules/grn/data/repositories/grn_repository_impl.dart +++ b/lib/modules/grn/data/repositories/grn_repository_impl.dart @@ -26,6 +26,11 @@ class GrnRepositoryImpl implements GrnRepository { return safeApiCall(() => dataSource.getGrns(query)); } + @override + Future>> listGrnOptions() { + return safeApiCall(() => dataSource.listGrnOptions()); + } + @override Future> exportGrns(GrnListQuery query) { return safeApiCall(() => dataSource.exportGrns(query)); diff --git a/lib/modules/grn/domain/repositories/grn_repository.dart b/lib/modules/grn/domain/repositories/grn_repository.dart index d9a6241..973d23a 100644 --- a/lib/modules/grn/domain/repositories/grn_repository.dart +++ b/lib/modules/grn/domain/repositories/grn_repository.dart @@ -5,6 +5,7 @@ import '../../../../shared/models/grn_model.dart'; abstract class GrnRepository { Future>> getGrns(GrnListQuery query); + Future>> listGrnOptions(); Future> exportGrns(GrnListQuery query); Future> getGrnById(String id); Future> createGrn(Map data); diff --git a/lib/modules/grn/presentation/providers/grn_lookups_provider.dart b/lib/modules/grn/presentation/providers/grn_lookups_provider.dart index 1800b82..bb5b047 100644 --- a/lib/modules/grn/presentation/providers/grn_lookups_provider.dart +++ b/lib/modules/grn/presentation/providers/grn_lookups_provider.dart @@ -1,12 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../core/constants/app_constants.dart'; import '../../../../core/utils/active_option.dart'; import '../../../../shared/models/purchase_order_model.dart'; import '../../../../shared/models/user_management_models.dart'; import '../../../masters/data/datasources/master_remote_data_source.dart'; import '../../../purchase_orders/data/repositories/purchase_order_repository_impl.dart'; -import '../../../users/presentation/providers/users_provider.dart'; +import '../../../users/data/repositories/user_repository_impl.dart'; class GrnLookups { const GrnLookups({ @@ -29,15 +28,9 @@ final grnLookupsProvider = FutureProvider.autoDispose((ref) async { final receivablePos = []; for (final status in ['APPROVED', 'PARTIALLY_RECEIVED']) { - final result = await poRepo.getPurchaseOrders( - PurchaseOrderListQuery( - page: 1, - limit: AppConstants.maxPageSize, - status: status, - ), - ); + final result = await poRepo.listPurchaseOrderOptions(status: status); if (result.failure == null && result.data != null) { - receivablePos.addAll(result.data!.items); + receivablePos.addAll(result.data!); } } @@ -60,16 +53,9 @@ Future> _safeOptions( Future> _safeUserOptions(Ref ref) async { try { - final result = await ref.read(getUsersUseCaseProvider)( - const UserListQuery( - page: 1, - limit: AppConstants.maxPageSize, - status: 'active', - isActive: true, - ), - ); + final result = await ref.read(userRepositoryProvider).listUserOptions(); if (result.failure != null || result.data == null) return const []; - return result.data!.items + return result.data! .where( (user) => isActiveUserOption( status: user.status, diff --git a/lib/modules/master_data/data/datasources/master_crud_remote_data_source.dart b/lib/modules/master_data/data/datasources/master_crud_remote_data_source.dart index 8183574..adf97f5 100644 --- a/lib/modules/master_data/data/datasources/master_crud_remote_data_source.dart +++ b/lib/modules/master_data/data/datasources/master_crud_remote_data_source.dart @@ -1,7 +1,6 @@ import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../core/constants/app_constants.dart'; import '../../../../core/network/dio_client.dart'; import '../../../../core/utils/active_option.dart'; import '../../../../shared/models/export_file_result.dart'; @@ -43,6 +42,7 @@ class MasterCrudRemoteDataSource { int limit = 20, String? search, bool? isActive, + Map? extraQueryParameters, }) async { final response = await dio.get( definition.apiPath, @@ -51,6 +51,7 @@ class MasterCrudRemoteDataSource { 'limit': limit, if (search != null && search.isNotEmpty) 'search': search, if (isActive != null) 'is_active': isActive, + ...?extraQueryParameters, }, ); @@ -80,21 +81,31 @@ class MasterCrudRemoteDataSource { ); } - Future>> listOptions(MasterDefinition definition) async { - final allItems = >[]; - var page = 1; - while (true) { - final result = await list( - definition, - page: page, - limit: AppConstants.defaultPageSize, - isActive: true, - ); - allItems.addAll(result.items.where(isActiveOptionRow)); - if (page >= result.totalPages) break; - page++; - } - return allItems; + Future>> listOptions( + MasterDefinition definition, { + Map? queryParameters, + }) async { + final response = await dio.get( + definition.apiPath, + queryParameters: { + 'dropdown_call': true, + ...?queryParameters, + }, + ); + + final body = response.data as Map; + final raw = body['data']; + final list = raw is List + ? raw + : raw is Map + ? raw['items'] as List? ?? const [] + : const []; + + return list + .whereType() + .map((item) => Map.from(item)) + .where(isActiveOptionRow) + .toList(); } Future> getById( diff --git a/lib/modules/master_data/data/repositories/master_repository_impl.dart b/lib/modules/master_data/data/repositories/master_repository_impl.dart index d5813b1..d8fbcd9 100644 --- a/lib/modules/master_data/data/repositories/master_repository_impl.dart +++ b/lib/modules/master_data/data/repositories/master_repository_impl.dart @@ -33,9 +33,12 @@ class MasterRepositoryImpl implements MasterRepository { @override Future>>> listOptions( - MasterDefinition definition, - ) => - safeApiCall(() => remote.listOptions(definition)); + MasterDefinition definition, { + Map? queryParameters, + }) => + safeApiCall( + () => remote.listOptions(definition, queryParameters: queryParameters), + ); @override Future>> getById( diff --git a/lib/modules/master_data/domain/entities/master_definition.dart b/lib/modules/master_data/domain/entities/master_definition.dart index 12fab4d..40ddc07 100644 --- a/lib/modules/master_data/domain/entities/master_definition.dart +++ b/lib/modules/master_data/domain/entities/master_definition.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; enum MasterFieldType { text, number, boolean, dropdown } -const brandTypes = ['OWN', 'OEM', 'THIRD_PARTY']; +const categoryTypeOptions = ['STOCK', 'ASSET']; class MasterFieldDef { const MasterFieldDef({ @@ -12,11 +12,15 @@ class MasterFieldDef { this.required = false, this.showInList = false, this.showInForm = true, + this.readOnly = false, this.optionsMasterKey, + this.optionsQueryParams, this.staticOptions, this.multiline = false, this.filterByFieldKey, this.filterByOptionKey, + this.visibleWhenFieldKey, + this.visibleWhenValue, }); final String key; @@ -26,15 +30,40 @@ class MasterFieldDef { final bool showInList; /// When false, field is list/display-only and excluded from create/update payloads. final bool showInForm; + /// When true, shown in the form but not editable (value set by other fields). + final bool readOnly; /// Master key used to populate dropdown options (e.g. `plants` for plant_id). final String? optionsMasterKey; - /// Fixed dropdown choices (e.g. brand type) — no API lookup. + /// Extra query parameters when loading [optionsMasterKey] options. + final Map? optionsQueryParams; + /// Fixed dropdown choices (e.g. category type) — no API lookup. final List? staticOptions; final bool multiline; /// Form field whose value filters this dropdown (e.g. `item_category_id`). final String? filterByFieldKey; /// Option-row key matched against [filterByFieldKey] (defaults to same key). final String? filterByOptionKey; + /// Show this field only when [visibleWhenFieldKey] equals [visibleWhenValue]. + final String? visibleWhenFieldKey; + final String? visibleWhenValue; + + /// Cache key for dropdown option rows (includes query params when set). + String get dropdownLookupKey { + final masterKey = optionsMasterKey; + if (masterKey == null) return key; + final params = optionsQueryParams; + if (params == null || params.isEmpty) return masterKey; + final parts = params.entries.toList() + ..sort((a, b) => a.key.compareTo(b.key)); + final query = parts.map((e) => '${e.key}=${e.value}').join('&'); + return '$masterKey?$query'; + } + + bool isVisibleInForm(Map values) { + final whenKey = visibleWhenFieldKey; + if (whenKey == null) return true; + return values[whenKey]?.toString() == visibleWhenValue; + } } class MasterDefinition { @@ -119,11 +148,27 @@ const masterDefinitions = [ fields: [ MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true), MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true), - MasterFieldDef(key: 'code_prefix', label: 'Code Prefix', showInList: true), + MasterFieldDef( + key: 'category_type', + label: 'Category Type', + type: MasterFieldType.dropdown, + required: true, + showInList: true, + staticOptions: categoryTypeOptions, + ), + MasterFieldDef( + key: 'code_prefix', + label: 'Code Prefix', + showInList: true, + visibleWhenFieldKey: 'category_type', + visibleWhenValue: 'ASSET', + ), MasterFieldDef( key: 'default_useful_life_years', label: 'Useful Life (Years)', type: MasterFieldType.number, + visibleWhenFieldKey: 'category_type', + visibleWhenValue: 'ASSET', ), MasterFieldDef( key: 'default_depreciation_method', @@ -131,6 +176,8 @@ const masterDefinitions = [ type: MasterFieldType.dropdown, showInList: true, optionsMasterKey: 'asset_depreciation_methods', + visibleWhenFieldKey: 'category_type', + visibleWhenValue: 'ASSET', ), _activeField, ], @@ -175,6 +222,12 @@ const masterDefinitions = [ showInList: true, showInForm: false, ), + MasterFieldDef( + key: 'is_asset_item', + label: 'Asset Item', + type: MasterFieldType.boolean, + required: true, + ), MasterFieldDef(key: 'item_name', label: 'Item Name', required: true, showInList: true), MasterFieldDef( key: 'item_category_id', @@ -182,6 +235,7 @@ const masterDefinitions = [ type: MasterFieldType.dropdown, required: true, optionsMasterKey: 'item_categories', + // Resolved at runtime from is_asset_item (STOCK / ASSET). ), MasterFieldDef( key: 'item_subcategory_id', @@ -208,19 +262,9 @@ const masterDefinitions = [ key: 'gst_rate_id', label: 'GST Rate', type: MasterFieldType.dropdown, - optionsMasterKey: 'gst_rates', - ), - MasterFieldDef( - key: 'brand_id', - label: 'Brand', - type: MasterFieldType.dropdown, - optionsMasterKey: 'brands', - ), - MasterFieldDef( - key: 'is_asset_item', - label: 'Asset Item', - type: MasterFieldType.boolean, - required: true, + showInList: true, + readOnly: true, + // Filled from selected HSN's nested gst_rate — no gst-rates API. ), MasterFieldDef( key: 'min_order_qty', @@ -257,32 +301,13 @@ const masterDefinitions = [ showInList: true, multiline: true, ), - _activeField, - ], - ), - MasterDefinition( - id: 'brands', - title: 'Brands', - subtitle: 'Own and third-party brands', - category: 'Inventory & Items', - routeKey: 'brands', - apiPath: '/masters/brands', - module: 'brands', - icon: Icons.loyalty_outlined, - fields: [ - MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true), - MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true), MasterFieldDef( - key: 'brand_type', - label: 'Brand Type', + key: 'gst_rate_id', + label: 'GST Rate', type: MasterFieldType.dropdown, - required: true, showInList: true, - staticOptions: brandTypes, + optionsMasterKey: 'gst_rates', ), - MasterFieldDef(key: 'contact_person', label: 'Contact Person'), - MasterFieldDef(key: 'phone', label: 'Phone'), - MasterFieldDef(key: 'email', label: 'Email', required: true), _activeField, ], ), @@ -504,6 +529,21 @@ String masterCellValue(Map row, MasterFieldDef field) { final label = row['${field.key}_label']; if (label != null && label.toString().isNotEmpty) return label.toString(); + if (field.key == 'gst_rate_id') { + final nested = row['gst_rate']; + if (nested is Map) { + final pct = nested['rate_pct']; + if (pct != null) { + final rate = pct is num + ? pct.toDouble() + : double.tryParse(pct.toString()); + if (rate != null) { + return rate % 1 == 0 ? '${rate.toInt()}%' : '$rate%'; + } + } + } + } + // Prefer explicit "_name" or nested ".name" / flat code from API // (e.g. asset_category_id -> asset_category_name; hsn_code_id -> hsn_code) if (field.key.endsWith('_id')) { @@ -540,3 +580,61 @@ String masterStatusValue(Map row) { if (active == false) return 'inactive'; return 'active'; } + +/// Category list filter for Items form: ASSET when Asset Item is checked. +String itemCategoryTypeForValues(Map values) => + values['is_asset_item'] == true ? 'ASSET' : 'STOCK'; + +/// Effective options query for a form field (may depend on other values). +Map? masterFieldOptionsQuery({ + required String masterId, + required MasterFieldDef field, + required Map values, +}) { + if (masterId == 'items' && field.key == 'item_category_id') { + return {'category_type': itemCategoryTypeForValues(values)}; + } + return field.optionsQueryParams; +} + +String masterFieldDropdownLookupKey({ + required String masterId, + required MasterFieldDef field, + required Map values, +}) { + final masterKey = field.optionsMasterKey; + if (masterKey == null) return field.key; + final params = masterFieldOptionsQuery( + masterId: masterId, + field: field, + values: values, + ); + if (params == null || params.isEmpty) return masterKey; + final parts = params.entries.toList() + ..sort((a, b) => a.key.compareTo(b.key)); + final query = parts.map((e) => '${e.key}=${e.value}').join('&'); + return '$masterKey?$query'; +} + +/// Display label for GST filled from HSN nested `gst_rate.description`. +String? gstRateDisplayFromValues(Map values) { + final nested = values['gst_rate']; + if (nested is Map) { + final desc = nested['description']; + if (desc != null && desc.toString().trim().isNotEmpty) { + return desc.toString().trim(); + } + final pct = nested['rate_pct']; + if (pct != null) { + final rate = pct is num ? pct.toDouble() : double.tryParse(pct.toString()); + if (rate != null) { + return rate % 1 == 0 ? '${rate.toInt()}%' : '$rate%'; + } + } + } + final label = values['gst_rate_label']; + if (label != null && label.toString().trim().isNotEmpty) { + return label.toString().trim(); + } + return null; +} diff --git a/lib/modules/master_data/domain/repositories/master_repository.dart b/lib/modules/master_data/domain/repositories/master_repository.dart index 8142fac..f9814c5 100644 --- a/lib/modules/master_data/domain/repositories/master_repository.dart +++ b/lib/modules/master_data/domain/repositories/master_repository.dart @@ -11,7 +11,10 @@ abstract class MasterRepository { String? search, }); - Future>>> listOptions(MasterDefinition definition); + Future>>> listOptions( + MasterDefinition definition, { + Map? queryParameters, + }); Future>> getById( MasterDefinition definition, diff --git a/lib/modules/master_data/presentation/providers/master_provider.dart b/lib/modules/master_data/presentation/providers/master_provider.dart index 394c114..c553b94 100644 --- a/lib/modules/master_data/presentation/providers/master_provider.dart +++ b/lib/modules/master_data/presentation/providers/master_provider.dart @@ -250,7 +250,6 @@ class MasterFormNotifier extends FamilyAsyncNotifier build(MasterFormArgs arg) async { - final dropdownOptions = await _loadDropdownOptions(); final existingRecords = await _loadExistingRecords(); Map values = {}; @@ -272,6 +271,18 @@ class MasterFormNotifier extends FamilyAsyncNotifier>>> - _loadDropdownOptions() async { + _loadDropdownOptions({Map? values}) async { + final formValues = values ?? state.valueOrNull?.values ?? const {}; final options = >>{}; - final keys = _definition.formFields - .where((field) => field.optionsMasterKey != null) - .map((field) => field.optionsMasterKey!) - .toSet(); + final fields = _definition.formFields + .where((field) => field.optionsMasterKey != null); - for (final key in keys) { + for (final field in fields) { + final lookupKey = masterFieldDropdownLookupKey( + masterId: _definition.id, + field: field, + values: formValues, + ); + if (options.containsKey(lookupKey)) continue; + + final key = field.optionsMasterKey!; if (key == 'asset_depreciation_methods') { final result = await ref.read(assetRepositoryProvider).getDepreciationMethods(); if (result.failure != null) continue; - options[key] = (result.data ?? const []) + options[lookupKey] = (result.data ?? const []) .where((option) => option.value.trim().isNotEmpty) .map( (option) => { @@ -326,19 +344,93 @@ class MasterFormNotifier extends FamilyAsyncNotifier values, MasterFormState current) { + if (_definition.id != 'items') return; + final hsnId = values['hsn_code_id']?.toString(); + if (hsnId == null || hsnId.isEmpty) { + values['gst_rate_id'] = null; + values['gst_rate'] = null; + values['gst_rate_label'] = null; + return; + } + + final hsnOptions = current.dropdownOptions['hsn_codes'] ?? const []; + Map? hsnRow; + for (final row in hsnOptions) { + if (row['id']?.toString() == hsnId) { + hsnRow = row; + break; + } + } + if (hsnRow == null) return; + + final nestedGst = hsnRow['gst_rate']; + final gstId = hsnRow['gst_rate_id'] ?? + (nestedGst is Map ? nestedGst['id'] : null); + if (gstId != null && gstId.toString().isNotEmpty) { + values['gst_rate_id'] = gstId.toString(); + } else { + values['gst_rate_id'] = null; + } + + if (nestedGst is Map) { + values['gst_rate'] = Map.from(nestedGst); + final desc = nestedGst['description']; + values['gst_rate_label'] = + desc != null && desc.toString().trim().isNotEmpty + ? desc.toString().trim() + : gstRateDisplayFromValues(values); + } else { + values['gst_rate'] = null; + values['gst_rate_label'] = null; + } + } + + void _clearHiddenFieldValues(Map values) { + for (final field in _definition.formFields) { + if (field.isVisibleInForm(values)) continue; + values[field.key] = null; + } + } + void updateValue(String key, dynamic value) { final current = state.valueOrNull; if (current == null) return; final values = Map.from(current.values); values[key] = value; + if (key == 'hsn_code_id') { + _applyGstFromHsn(values, current); + } + + if (key == 'category_type') { + _clearHiddenFieldValues(values); + } + + // Asset Item toggles Items category list between STOCK / ASSET. + if (key == 'is_asset_item' && _definition.id == 'items') { + values['item_category_id'] = null; + values['item_subcategory_id'] = null; + state = AsyncData(current.copyWith(values: values)); + _reloadItemCategoryOptions(values); + return; + } + // Clear dependent dropdowns when their parent filter value changes // (e.g. item_category_id → item_subcategory_id). for (final field in _definition.formFields) { @@ -347,8 +439,12 @@ class MasterFormNotifier extends FamilyAsyncNotifier item['id']?.toString() == dependentValue.toString() && @@ -362,22 +458,35 @@ class MasterFormNotifier extends FamilyAsyncNotifier _reloadItemCategoryOptions(Map values) async { + final current = state.valueOrNull; + if (current == null) return; + final options = await _loadDropdownOptions(values: values); + final latest = state.valueOrNull; + if (latest == null) return; + state = AsyncData(latest.copyWith(dropdownOptions: options)); + } + Future reloadDropdownOptions() async { final current = state.valueOrNull; if (current == null) return; - final options = await _loadDropdownOptions(); + final options = await _loadDropdownOptions(values: current.values); state = AsyncData(current.copyWith(dropdownOptions: options)); } Map _buildPayload(MasterFormState current) { final payload = {}; for (final field in _definition.formFields) { + if (!field.isVisibleInForm(current.values)) continue; final value = current.values[field.key]; if (value == null || value == '') continue; payload[field.key] = switch (field.type) { MasterFieldType.number => num.tryParse(value.toString()) ?? value, - MasterFieldType.dropdown => int.tryParse(value.toString()) ?? value, + MasterFieldType.dropdown => field.staticOptions != null || + field.optionsMasterKey == 'asset_depreciation_methods' + ? value.toString() + : int.tryParse(value.toString()) ?? value, MasterFieldType.boolean => value == true, MasterFieldType.text => value.toString().trim(), }; diff --git a/lib/modules/master_data/presentation/widgets/master_form_panel.dart b/lib/modules/master_data/presentation/widgets/master_form_panel.dart index 0f39ab6..beaffa4 100644 --- a/lib/modules/master_data/presentation/widgets/master_form_panel.dart +++ b/lib/modules/master_data/presentation/widgets/master_form_panel.dart @@ -119,20 +119,74 @@ class _MasterFormPanelState extends ConsumerState { onChanged: (checked) => notifier.updateValue(field.key, checked), ); } - return CheckboxListTile( - contentPadding: EdgeInsets.zero, - title: Text(field.label), - value: value == true, - onChanged: (checked) => - notifier.updateValue(field.key, checked ?? false), + return InkWell( + onTap: () => notifier.updateValue(field.key, value != true), + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 24, + height: 24, + child: Checkbox( + value: value == true, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: VisualDensity.compact, + onChanged: (checked) => + notifier.updateValue(field.key, checked ?? false), + ), + ), + const SizedBox(width: 8), + Text( + field.label, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ), ); case MasterFieldType.dropdown: + if (field.readOnly) { + final display = field.key == 'gst_rate_id' + ? (gstRateDisplayFromValues(formState.values) ?? '') + : (value?.toString() ?? ''); + final theme = Theme.of(context); + return _wrapField( + TextFormField( + key: ValueKey( + '$_formSessionId-${field.key}-readonly-$display', + ), + initialValue: display.isEmpty ? '' : display, + readOnly: true, + enableInteractiveSelection: false, + style: theme.textTheme.bodyLarge?.copyWith( + color: theme.colorScheme.onSurface, + ), + decoration: _inputDecoration(field).copyWith( + hintText: display.isEmpty + ? 'Select HSN code first' + : null, + filled: true, + fillColor: theme.colorScheme.surfaceContainerHighest + .withValues(alpha: 0.35), + ), + ), + ); + } + final List> dropdownOptions; if (field.staticOptions != null) { dropdownOptions = stringDropdownOptions(field.staticOptions!); } else { - var options = formState.dropdownOptions[field.optionsMasterKey] ?? + final lookupKey = masterFieldDropdownLookupKey( + masterId: widget.masterId, + field: field, + values: formState.values, + ); + var options = formState.dropdownOptions[lookupKey] ?? const >[]; final filterField = field.filterByFieldKey; if (filterField != null) { @@ -178,7 +232,9 @@ class _MasterFormPanelState extends ConsumerState { if (canQuickAdd) { return MasterQuickAddDropdown( key: ValueKey( - '$_formSessionId-${field.key}-${filterField == null ? '' : formState.values[filterField]}', + '$_formSessionId-${field.key}-' + '${filterField == null ? '' : formState.values[filterField]}-' + '${formState.values['is_asset_item']}', ), masterId: optionsMasterKey, label: _fieldLabel(field), @@ -191,9 +247,19 @@ class _MasterFormPanelState extends ConsumerState { : 'Select ${field.label.toLowerCase()}', searchHint: 'Search ${field.label.toLowerCase()}...', enabled: parentSelected, - initialValues: filterField == null - ? null - : {filterField: formState.values[filterField]}, + initialValues: () { + final values = {}; + final params = masterFieldOptionsQuery( + masterId: widget.masterId, + field: field, + values: formState.values, + ); + if (params != null) values.addAll(params); + if (filterField != null) { + values[filterField] = formState.values[filterField]; + } + return values.isEmpty ? null : values; + }(), refreshLookups: () { ref .read(masterFormProvider(_args).notifier) @@ -323,8 +389,10 @@ class _MasterFormPanelState extends ConsumerState { MasterFormState formState, ) { final def = _definition; - final regularFields = - def.formFields.where((field) => field.key != 'is_active').toList(); + final regularFields = def.formFields + .where((field) => field.key != 'is_active') + .where((field) => field.isVisibleInForm(formState.values)) + .toList(); MasterFieldDef? activeField; for (final field in def.formFields) { if (field.key == 'is_active') { @@ -339,8 +407,9 @@ class _MasterFormPanelState extends ConsumerState { while (i < regularFields.length) { final left = regularFields[i]; - // Multiline fields (e.g. HSN Description) always take a full row. - if (left.multiline) { + // Multiline / Asset Item take a full row. + final fullWidth = left.multiline || left.key == 'is_asset_item'; + if (fullWidth) { widgets.add( Padding( padding: const EdgeInsets.only(bottom: 12), @@ -354,8 +423,9 @@ class _MasterFormPanelState extends ConsumerState { continue; } - final hasRight = - i + 1 < regularFields.length && !regularFields[i + 1].multiline; + final hasRight = i + 1 < regularFields.length && + !regularFields[i + 1].multiline && + regularFields[i + 1].key != 'is_asset_item'; if (hasRight) { final right = regularFields[i + 1]; widgets.add( diff --git a/lib/modules/master_data/presentation/widgets/master_inline_create_form.dart b/lib/modules/master_data/presentation/widgets/master_inline_create_form.dart index 1d97e97..4bdc92f 100644 --- a/lib/modules/master_data/presentation/widgets/master_inline_create_form.dart +++ b/lib/modules/master_data/presentation/widgets/master_inline_create_form.dart @@ -124,7 +124,12 @@ class _MasterInlineCreateFormState if (field.staticOptions != null) { dropdownOptions = stringDropdownOptions(field.staticOptions!); } else { - var options = formState.dropdownOptions[field.optionsMasterKey] ?? + final lookupKey = masterFieldDropdownLookupKey( + masterId: widget.masterId, + field: field, + values: formState.values, + ); + var options = formState.dropdownOptions[lookupKey] ?? const >[]; final filterField = field.filterByFieldKey; if (filterField != null) { @@ -444,6 +449,7 @@ class _MasterInlineCreateFormState data: (formState) { final fields = def.formFields .where((f) => f.key != 'is_active') + .where((f) => f.isVisibleInForm(formState.values)) .toList(); final active = def.formFields .where((f) => f.key == 'is_active') diff --git a/lib/modules/master_data/presentation/widgets/master_quick_add.dart b/lib/modules/master_data/presentation/widgets/master_quick_add.dart index f4d203c..1999bc8 100644 --- a/lib/modules/master_data/presentation/widgets/master_quick_add.dart +++ b/lib/modules/master_data/presentation/widgets/master_quick_add.dart @@ -224,7 +224,6 @@ String masterQuickAddNoun(String masterId) { 'item_subcategories' => 'subcategory', 'items' => 'item', 'hsn_codes' => 'HSN code', - 'brands' => 'brand', 'plants' => 'plant', 'warehouses' => 'warehouse', 'designations' => 'designation', diff --git a/lib/modules/masters/data/datasources/master_remote_data_source.dart b/lib/modules/masters/data/datasources/master_remote_data_source.dart index f22290d..fa174db 100644 --- a/lib/modules/masters/data/datasources/master_remote_data_source.dart +++ b/lib/modules/masters/data/datasources/master_remote_data_source.dart @@ -35,9 +35,6 @@ class MasterRemoteDataSource { Future> listWarehouses() => _listOptions(ApiEndpoints.warehouses); - Future> listBrands() => - _listOptions(ApiEndpoints.brands); - Future> listUom() => _listOptions(ApiEndpoints.uom); Future> listItems() => _listOptions(ApiEndpoints.items); @@ -48,6 +45,32 @@ class MasterRemoteDataSource { Future> listHsnCodes() => _listOptions(ApiEndpoints.hsnCodes); + /// HSN options with default `gst_rate_id` for GST autofill on item/PO lines. + Future<({List options, Map gstRateByHsnId})> + listHsnCodesWithGstRate() async { + final rows = await _listAllMaps(ApiEndpoints.hsnCodes); + final options = []; + final gstRateByHsnId = {}; + + for (final item in rows) { + if (!isActiveOptionRow(item)) continue; + final id = item['id']?.toString() ?? ''; + if (id.isEmpty) continue; + + final nestedGst = item['gst_rate']; + gstRateByHsnId[id] = _asInt( + item['gst_rate_id'] ?? + (nestedGst is Map ? nestedGst['id'] : null), + ); + + final name = _optionLabel(item); + if (name.isEmpty) continue; + options.add(FilterOptionModel(id: id, name: name)); + } + + return (options: options, gstRateByHsnId: gstRateByHsnId); + } + /// Active items with default HSN / UOM / GST for PO line autofill. Future< ({ @@ -56,10 +79,7 @@ class MasterRemoteDataSource { Map uomByItemId, Map gstRateByItemId, })> listItemsWithHsn() async { - final rows = await _listAllMaps( - ApiEndpoints.items, - queryParameters: {'is_active': true}, - ); + final rows = await _listAllMaps(ApiEndpoints.items); final hsnByItemId = {}; final uomByItemId = {}; final gstRateByItemId = {}; @@ -97,10 +117,7 @@ class MasterRemoteDataSource { /// GST rate options with numeric `rate_pct` for tax calculations. Future<({List options, Map pctById})> listGstRatesWithPct() async { - final rows = await _listAllMaps( - ApiEndpoints.gstRates, - queryParameters: {'is_active': true}, - ); + final rows = await _listAllMaps(ApiEndpoints.gstRates); final options = []; final pctById = {}; @@ -123,14 +140,29 @@ class MasterRemoteDataSource { return (options: options, pctById: pctById); } - Future> listItemCategories() => - _listOptions(ApiEndpoints.itemCategories); + Future> listItemCategories({String? categoryType}) async { + final rows = await _listAllMaps( + ApiEndpoints.itemCategories, + queryParameters: { + if (categoryType != null) 'category_type': categoryType, + }, + ); + return rows + .where(isActiveOptionRow) + .map( + (item) => FilterOptionModel( + id: item['id']?.toString() ?? '', + name: _optionLabel(item), + ), + ) + .where((item) => item.id.isNotEmpty && item.name.isNotEmpty) + .toList(); + } Future> listItemSubcategories({int? itemCategoryId}) async { final rows = await _listAllMaps( ApiEndpoints.itemSubcategories, queryParameters: { - 'is_active': true, if (itemCategoryId != null) 'item_category_id': itemCategoryId, }, ); @@ -153,10 +185,7 @@ class MasterRemoteDataSource { } Future> _listOptions(String endpoint) async { - final rows = await _listAllMaps( - endpoint, - queryParameters: {'is_active': true}, - ); + final rows = await _listAllMaps(endpoint); return rows .where(isActiveOptionRow) .map( @@ -169,33 +198,23 @@ class MasterRemoteDataSource { .toList(); } - /// Loads every page of a masters list (API default page size = 20). + /// Form-dropdown loader: uses `dropdown_call=true` (all active, no pagination). Future>> _listAllMaps( String endpoint, { Map? queryParameters, }) async { - final all = >[]; - var page = 1; - var totalPages = 1; - final pageSize = AppConstants.defaultPageSize; - - while (page <= totalPages) { - final response = await dio.get( - endpoint, - queryParameters: { - 'page': page, - 'limit': pageSize, - ...?queryParameters, - }, - ); - final parsed = _parsePage(response.data, fallbackLimit: pageSize); - all.addAll(parsed.items); - totalPages = parsed.totalPages; - if (parsed.items.isEmpty) break; - page++; - } - - return all; + final response = await dio.get( + endpoint, + queryParameters: { + 'dropdown_call': true, + ...?queryParameters, + }, + ); + final parsed = _parsePage( + response.data, + fallbackLimit: AppConstants.defaultPageSize, + ); + return parsed.items; } ({List> items, int totalPages}) _parsePage( diff --git a/lib/modules/purchase_orders/data/datasources/purchase_order_remote_data_source.dart b/lib/modules/purchase_orders/data/datasources/purchase_order_remote_data_source.dart index 052a711..a0c8f82 100644 --- a/lib/modules/purchase_orders/data/datasources/purchase_order_remote_data_source.dart +++ b/lib/modules/purchase_orders/data/datasources/purchase_order_remote_data_source.dart @@ -22,6 +22,33 @@ class PurchaseOrderRemoteDataSource { return _parsePaginated(response.data, PurchaseOrderModel.fromJson); } + /// Form-dropdown loader (`dropdown_call=true`). Optional status filter. + Future> listPurchaseOrderOptions({ + String? status, + }) async { + final response = await dio.get( + ApiEndpoints.purchaseOrders, + queryParameters: { + 'dropdown_call': true, + if (status != null) 'status': status, + }, + ); + final body = response.data; + if (body is! Map) return const []; + final raw = body['data']; + final list = raw is List + ? raw + : raw is Map + ? (raw['items'] as List?) ?? const [] + : const []; + return list + .whereType() + .map( + (item) => PurchaseOrderModel.fromJson(Map.from(item)), + ) + .toList(); + } + Future getPurchaseOrderById(String id) async { final response = await dio.get(ApiEndpoints.purchaseOrderById(id)); final raw = response.data['data']; @@ -204,7 +231,6 @@ class PurchaseOrderRemoteDataSource { return { if (query.search != null && query.search!.isNotEmpty) 'search': query.search, if (query.status != null) 'status': query.status, - if (query.poType != null) 'po_type': query.poType, if (query.vendorId != null) 'vendor_id': query.vendorId, if (query.plantId != null) 'plant_id': query.plantId, if (query.dateFrom != null) 'date_from': query.dateFrom, diff --git a/lib/modules/purchase_orders/data/repositories/purchase_order_repository_impl.dart b/lib/modules/purchase_orders/data/repositories/purchase_order_repository_impl.dart index b93b9a0..7fa8aa5 100644 --- a/lib/modules/purchase_orders/data/repositories/purchase_order_repository_impl.dart +++ b/lib/modules/purchase_orders/data/repositories/purchase_order_repository_impl.dart @@ -32,6 +32,15 @@ class PurchaseOrderRepositoryImpl implements PurchaseOrderRepository { return safeApiCall(() => dataSource.getPurchaseOrders(query)); } + @override + Future>> listPurchaseOrderOptions({ + String? status, + }) { + return safeApiCall( + () => dataSource.listPurchaseOrderOptions(status: status), + ); + } + @override Future> exportPurchaseOrders( PurchaseOrderListQuery query, diff --git a/lib/modules/purchase_orders/domain/repositories/purchase_order_repository.dart b/lib/modules/purchase_orders/domain/repositories/purchase_order_repository.dart index 7e535a4..338e0e6 100644 --- a/lib/modules/purchase_orders/domain/repositories/purchase_order_repository.dart +++ b/lib/modules/purchase_orders/domain/repositories/purchase_order_repository.dart @@ -8,6 +8,9 @@ abstract class PurchaseOrderRepository { Future>> getPurchaseOrders( PurchaseOrderListQuery query, ); + Future>> listPurchaseOrderOptions({ + String? status, + }); Future> exportPurchaseOrders( PurchaseOrderListQuery query, ); diff --git a/lib/modules/purchase_orders/presentation/providers/purchase_order_lookups_provider.dart b/lib/modules/purchase_orders/presentation/providers/purchase_order_lookups_provider.dart index ce133ec..9eb0e05 100644 --- a/lib/modules/purchase_orders/presentation/providers/purchase_order_lookups_provider.dart +++ b/lib/modules/purchase_orders/presentation/providers/purchase_order_lookups_provider.dart @@ -1,9 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../core/constants/app_constants.dart'; import '../../../../core/utils/active_option.dart'; import '../../../../shared/models/user_management_models.dart'; -import '../../../../shared/models/vendor_model.dart'; import '../../../masters/data/datasources/master_remote_data_source.dart'; import '../../../vendors/data/repositories/vendor_repository_impl.dart'; import '../../../vendors/domain/repositories/vendor_repository.dart'; @@ -13,7 +11,6 @@ class PurchaseOrderLookups { this.vendors = const [], this.plants = const [], this.warehouses = const [], - this.brands = const [], this.paymentTerms = const [], this.deliveryTerms = const [], this.items = const [], @@ -24,12 +21,12 @@ class PurchaseOrderLookups { this.gstRates = const [], this.gstRatePctById = const {}, this.hsnCodes = const [], + this.hsnGstRateById = const {}, }); final List vendors; final List plants; final List warehouses; - final List brands; final List paymentTerms; final List deliveryTerms; final List items; @@ -44,6 +41,8 @@ class PurchaseOrderLookups { /// GST rate id → `rate_pct` for tax calculations. final Map gstRatePctById; final List hsnCodes; + /// HSN code id → default `gst_rate_id` from HSN master. + final Map hsnGstRateById; } final purchaseOrderLookupsProvider = @@ -55,32 +54,31 @@ final purchaseOrderLookupsProvider = final itemsWithDefaults = await _safeItemsWithDefaults(master.listItemsWithHsn); final gstWithPct = await _safeGstRatesWithPct(master.listGstRatesWithPct); + final hsnWithGst = await _safeHsnWithGst(master.listHsnCodesWithGstRate); final results = await Future.wait([ _safeOptions(master.listPlants), _safeOptions(master.listWarehouses), - _safeOptions(master.listBrands), _safeOptions(master.listPaymentTerms), _safeOptions(master.listDeliveryTerms), _safeOptions(master.listUom), - _safeOptions(master.listHsnCodes), ]); return PurchaseOrderLookups( vendors: vendors, plants: results[0], warehouses: results[1], - brands: results[2], - paymentTerms: results[3], - deliveryTerms: results[4], + paymentTerms: results[2], + deliveryTerms: results[3], items: itemsWithDefaults.options, itemHsnById: itemsWithDefaults.hsnByItemId, itemUomById: itemsWithDefaults.uomByItemId, itemGstRateById: itemsWithDefaults.gstRateByItemId, - uom: results[5], + uom: results[4], gstRates: gstWithPct.options, gstRatePctById: gstWithPct.pctById, - hsnCodes: results[6], + hsnCodes: hsnWithGst.options, + hsnGstRateById: hsnWithGst.gstRateByHsnId, ); }); @@ -136,42 +134,36 @@ Future<({List options, Map pctById})> } } +Future<({List options, Map gstRateByHsnId})> + _safeHsnWithGst( + Future<({List options, Map gstRateByHsnId})> + Function() + load, +) async { + try { + return await load(); + } catch (_) { + return (options: [], gstRateByHsnId: {}); + } +} + Future> _fetchActiveVendors( VendorRepository vendorRepo, ) async { - final vendors = []; - var page = 1; - var totalPages = 1; - - while (page <= totalPages) { - final result = await vendorRepo.getVendors( - VendorListQuery( - page: page, - limit: AppConstants.maxPageSize, - isActive: true, - ), - ); - if (result.failure != null) { - throw result.failure!; - } - - final data = result.data!; - vendors.addAll( - data.items - .where( - (vendor) => isActiveVendorOption( - isActive: vendor.isActive, - status: vendor.status, - ), - ) - .map( - (vendor) => - FilterOptionModel(id: vendor.id, name: vendor.vendorName), - ), - ); - totalPages = data.totalPages; - page++; + final result = await vendorRepo.listVendorOptions(); + if (result.failure != null) { + throw result.failure!; } - return vendors; + return (result.data ?? const []) + .where( + (vendor) => isActiveVendorOption( + isActive: vendor.isActive, + status: vendor.status, + ), + ) + .map( + (vendor) => FilterOptionModel(id: vendor.id, name: vendor.vendorName), + ) + .toList(); } diff --git a/lib/modules/purchase_orders/presentation/providers/purchase_orders_provider.dart b/lib/modules/purchase_orders/presentation/providers/purchase_orders_provider.dart index 60b274a..a174d5f 100644 --- a/lib/modules/purchase_orders/presentation/providers/purchase_orders_provider.dart +++ b/lib/modules/purchase_orders/presentation/providers/purchase_orders_provider.dart @@ -112,12 +112,6 @@ class PurchaseOrdersListNotifier applyQuery(current.query.copyWith(status: status, page: 1)); } - void setPoTypeFilter(String? poType) { - final current = state.valueOrNull; - if (current == null) return; - applyQuery(current.query.copyWith(poType: poType, page: 1)); - } - void setPage(int page) { final current = state.valueOrNull; if (current == null) return; diff --git a/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart b/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart index f2604ed..b2dab74 100644 --- a/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart +++ b/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart @@ -8,6 +8,7 @@ import '../../../../core/errors/failure.dart'; import '../../../../core/theme/app_colors.dart'; import '../../../../core/utils/formatters.dart'; import '../../../../shared/models/purchase_order_model.dart'; +import '../../../../shared/models/vendor_model.dart'; import '../../../../shared/models/user_management_models.dart'; import '../../../../shared/providers/permissions_provider.dart'; import '../../../../shared/utils/file_download_helper.dart'; @@ -418,7 +419,7 @@ class _DetailHeader extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final subtitleParts = [ - poTypeLabel(order.poType), + vendorTypeLabel(order.vendorType), if (order.vendorName?.trim().isNotEmpty == true) order.vendorName!.trim(), if (order.plantName?.trim().isNotEmpty == true) order.plantName!.trim(), ]; @@ -702,7 +703,6 @@ class _OrderDetailsCard extends StatelessWidget { @override Widget build(BuildContext context) { - final brand = _lookupName(lookups?.brands, order.brandId); final paymentTerm = _lookupName(lookups?.paymentTerms, order.paymentTermId); final deliveryTerm = @@ -733,8 +733,8 @@ class _OrderDetailsCard extends StatelessWidget { value: _displayOrDash(order.vendorName), ), _DetailField( - label: 'PO Type', - value: poTypeLabel(order.poType), + label: 'Vendor Type', + value: vendorTypeLabel(order.vendorType), ), _DetailField( label: 'Plant', @@ -744,7 +744,6 @@ class _OrderDetailsCard extends StatelessWidget { label: 'Warehouse', value: _displayOrDash(order.warehouseName), ), - _DetailField(label: 'Brand', value: brand), _DetailField(label: 'Payment Term', value: paymentTerm), _DetailField(label: 'Delivery Term', value: deliveryTerm), ]; @@ -1275,7 +1274,7 @@ class _DetailFooter extends StatelessWidget { child: Row( children: [ Text( - '${poTypeLabel(order.poType)} · ${poStatusLabel(order.status)}', + '${vendorTypeLabel(order.vendorType)} · ${poStatusLabel(order.status)}', style: style, ), const Spacer(), diff --git a/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart b/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart index a842b60..6a5fc3a 100644 --- a/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart +++ b/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart @@ -50,11 +50,9 @@ class _PurchaseOrderFormScreenState extends ConsumerState _lines = []; @@ -99,11 +97,9 @@ class _PurchaseOrderFormScreenState extends ConsumerState _buildPayload() { return { 'po_date': DateFormatter.toApiDate(_poDate ?? DateTime.now()), - 'po_type': _poType, 'vendor_id': _vendorId, 'plant_id': _plantId, if (_warehouseId != null) 'warehouse_id': _warehouseId, - if (_brandId != null) 'brand_id': _brandId, if (_paymentTermId != null) 'payment_term_id': _paymentTermId, if (_deliveryTermId != null) 'delivery_term_id': _deliveryTermId, if (_expectedDeliveryDate != null) @@ -244,7 +238,7 @@ class _PurchaseOrderFormScreenState extends ConsumerState setState(() => _poDate = d), ), ), - AppSearchableDropdown( - label: 'PO Type *', - value: _poType, - hint: 'Select PO type', - searchHint: 'Search type...', - options: poTypeOptions - .map( - (e) => AppDropdownOption( - value: e.$1, - label: e.$2, - ), - ) - .toList(), - onChanged: (v) => setState(() => _poType = v), - validator: (v) => - v == null ? 'PO type is required' : null, - ), AppSearchableDropdown( label: 'Vendor *', value: _dropdownValue(_vendorId, vendorIds), @@ -432,10 +409,6 @@ class _PurchaseOrderFormScreenState extends ConsumerState v == null ? 'Plant is required' : null, ), - ], - ), - FormRowFour( - children: [ MasterQuickAddDropdown( masterId: 'warehouses', label: 'Warehouse', @@ -449,18 +422,10 @@ class _PurchaseOrderFormScreenState extends ConsumerState setState(() => _warehouseId = v), ), - MasterQuickAddDropdown( - masterId: 'brands', - label: 'Brand', - value: _brandId, - hint: 'Select brand', - searchHint: 'Search brand...', - options: _nullableIntOptions(lookups.brands), - refreshLookups: () => - ref.invalidate(purchaseOrderLookupsProvider), - parseCreatedId: int.tryParse, - onChanged: (v) => setState(() => _brandId = v), - ), + ], + ), + FormRowFour( + children: [ MasterQuickAddDropdown( masterId: 'payment_terms', label: 'Payment Term', diff --git a/lib/modules/purchase_orders/presentation/screens/purchase_order_list_screen.dart b/lib/modules/purchase_orders/presentation/screens/purchase_order_list_screen.dart index c54157c..5899799 100644 --- a/lib/modules/purchase_orders/presentation/screens/purchase_order_list_screen.dart +++ b/lib/modules/purchase_orders/presentation/screens/purchase_order_list_screen.dart @@ -8,6 +8,7 @@ import '../../../../core/errors/failure.dart'; import '../../../../core/utils/formatters.dart'; import '../../../../core/utils/responsive_utils.dart'; import '../../../../shared/models/purchase_order_model.dart'; +import '../../../../shared/models/vendor_model.dart'; import '../../../../shared/providers/permissions_provider.dart'; import '../../../../shared/widgets/app_card.dart'; import '../../../../shared/widgets/app_confirmation_dialog.dart'; @@ -114,8 +115,6 @@ class _PurchaseOrderListScreenState extends ConsumerState> statusOptions; final ValueChanged onSearch; final ValueChanged onStatusChanged; - final ValueChanged onPoTypeChanged; final bool showExport; final bool isExporting; final VoidCallback? onExport; @@ -306,19 +303,6 @@ class _FiltersBar extends StatelessWidget { options: statusOptions, onChanged: onStatusChanged, ), - AppSearchableDropdown( - label: 'PO Type', - value: query.poType, - searchHint: 'Search type...', - isDense: true, - options: [ - const AppDropdownOption(value: null, label: 'All Types'), - ...poTypeOptions.map( - (e) => AppDropdownOption(value: e.$1, label: e.$2), - ), - ], - onChanged: onPoTypeChanged, - ), ]; return AppResponsiveFilterBar( @@ -456,7 +440,7 @@ class _PoCardList extends StatelessWidget { child: ListTile( title: Text(order.poNo ?? 'PO #${order.id}'), subtitle: Text( - '${order.vendorName ?? '—'} · ${poTypeLabel(order.poType)}', + '${order.vendorName ?? '—'} · ${vendorTypeLabel(order.vendorType)}', ), trailing: PoStatusChip(status: order.status, compact: true), onTap: () => onView(order), diff --git a/lib/modules/purchase_orders/presentation/widgets/purchase_order_line_items_editor.dart b/lib/modules/purchase_orders/presentation/widgets/purchase_order_line_items_editor.dart index 4aa00e2..b6ec31d 100644 --- a/lib/modules/purchase_orders/presentation/widgets/purchase_order_line_items_editor.dart +++ b/lib/modules/purchase_orders/presentation/widgets/purchase_order_line_items_editor.dart @@ -394,6 +394,18 @@ class _LineItemCardState extends ConsumerState<_LineItemCard> { ref.read(purchaseOrderLookupsProvider).valueOrNull?.itemHsnById ?? widget.itemHsnById; + Map get _hsnGstRateById => + ref.read(purchaseOrderLookupsProvider).valueOrNull?.hsnGstRateById ?? + const {}; + + void _applyGstFromHsn(int? hsnId) { + if (hsnId == null) return; + final gst = _hsnGstRateById[hsnId.toString()]; + if (gst != null) { + widget.line.gstRateId = gst; + } + } + bool _fillMissingItemDefaults() { final itemId = widget.line.itemId; if (itemId == null) return false; @@ -415,6 +427,10 @@ class _LineItemCardState extends ConsumerState<_LineItemCard> { widget.line.hsnCodeId = defaultHsn; changed = true; } + if (widget.line.gstRateId == null && widget.line.hsnCodeId != null) { + _applyGstFromHsn(widget.line.hsnCodeId); + if (widget.line.gstRateId != null) changed = true; + } return changed; } @@ -434,6 +450,9 @@ class _LineItemCardState extends ConsumerState<_LineItemCard> { final defaultHsn = _itemHsnById[key]; if (defaultHsn != null) { widget.line.hsnCodeId = defaultHsn; + if (widget.line.gstRateId == null) { + _applyGstFromHsn(defaultHsn); + } } }); } diff --git a/lib/modules/rbac/presentation/providers/add_user_form_provider.dart b/lib/modules/rbac/presentation/providers/add_user_form_provider.dart index 7c58bcf..3ac9ab1 100644 --- a/lib/modules/rbac/presentation/providers/add_user_form_provider.dart +++ b/lib/modules/rbac/presentation/providers/add_user_form_provider.dart @@ -1,10 +1,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../core/constants/app_constants.dart'; import '../../../../core/utils/active_option.dart'; import '../../../../shared/models/user_management_models.dart'; import '../../../masters/data/datasources/master_remote_data_source.dart'; import '../../../roles/data/repositories/role_repository_impl.dart'; +import '../../../users/data/repositories/user_repository_impl.dart'; import '../../../users/presentation/providers/users_provider.dart'; class AddUserFormState { @@ -60,7 +60,7 @@ class AddUserFormNotifier extends FamilyAsyncNotifier Future build(String? userId) async { final masterRemote = ref.read(masterRemoteDataSourceProvider); final roleRepository = ref.read(roleRepositoryProvider); - final getUsers = ref.read(getUsersUseCaseProvider); + final userRepository = ref.read(userRepositoryProvider); final rolesResult = await roleRepository.listRoleOptions(); if (rolesResult.failure != null) throw rolesResult.failure!; @@ -69,16 +69,10 @@ class AddUserFormNotifier extends FamilyAsyncNotifier final plants = await masterRemote.listPlants(); final designations = await masterRemote.listDesignations(); - final usersResult = await getUsers( - const UserListQuery( - limit: AppConstants.maxPageSize, - status: 'active', - isActive: true, - ), - ); + final usersResult = await userRepository.listUserOptions(); if (usersResult.failure != null) throw usersResult.failure!; - final managers = usersResult.data!.items + final managers = (usersResult.data ?? const []) .where( (user) => isReportingManagerRole(user.roleName) && diff --git a/lib/modules/roles/data/datasources/role_remote_data_source.dart b/lib/modules/roles/data/datasources/role_remote_data_source.dart index 6c062d7..8cb5aec 100644 --- a/lib/modules/roles/data/datasources/role_remote_data_source.dart +++ b/lib/modules/roles/data/datasources/role_remote_data_source.dart @@ -37,9 +37,7 @@ class RoleRemoteDataSource { final response = await dio.get( ApiEndpoints.roles, queryParameters: { - 'page': 1, - 'limit': 100, - 'is_active': true, + 'dropdown_call': true, if (search != null && search.isNotEmpty) 'search': search, }, ); diff --git a/lib/modules/users/data/datasources/user_remote_data_source.dart b/lib/modules/users/data/datasources/user_remote_data_source.dart index d3cbcf7..74693f1 100644 --- a/lib/modules/users/data/datasources/user_remote_data_source.dart +++ b/lib/modules/users/data/datasources/user_remote_data_source.dart @@ -96,6 +96,26 @@ class UserRemoteDataSource { ); } + /// Form-dropdown loader: all active users (`dropdown_call=true`). + Future> listUserOptions() async { + final response = await dio.get( + ApiEndpoints.users, + queryParameters: const {'dropdown_call': true}, + ); + final body = response.data; + if (body is! Map) return const []; + final raw = body['data']; + final list = raw is List + ? raw + : raw is Map + ? (raw['items'] as List?) ?? const [] + : const []; + return list + .whereType() + .map((item) => ManagedUserModel.fromJson(Map.from(item))) + .toList(); + } + Future getUserById(String id) async { final response = await dio.get(ApiEndpoints.userById(id)); return ManagedUserModel.fromJson(response.data['data'] as Map); diff --git a/lib/modules/users/data/repositories/user_repository_impl.dart b/lib/modules/users/data/repositories/user_repository_impl.dart index 36623e5..eaf1d4e 100644 --- a/lib/modules/users/data/repositories/user_repository_impl.dart +++ b/lib/modules/users/data/repositories/user_repository_impl.dart @@ -35,6 +35,10 @@ class UserRepositoryImpl implements UserRepository { ) => safeApiCall(() => remote.getUsers(query)); + @override + Future>> listUserOptions() => + safeApiCall(() => remote.listUserOptions()); + @override Future> getUserById(String id) => safeApiCall(() => remote.getUserById(id)); diff --git a/lib/modules/users/domain/repositories/user_repository.dart b/lib/modules/users/domain/repositories/user_repository.dart index fc717b3..496baee 100644 --- a/lib/modules/users/domain/repositories/user_repository.dart +++ b/lib/modules/users/domain/repositories/user_repository.dart @@ -7,6 +7,7 @@ abstract class UserRepository { Future> getSummary(); Future> getFilters(); Future>> getUsers(UserListQuery query); + Future>> listUserOptions(); Future> getUserById(String id); Future> createUser(CreateUserRequest request); Future> updateUser(String id, UpdateUserRequest request); diff --git a/lib/modules/vendors/data/datasources/vendor_remote_data_source.dart b/lib/modules/vendors/data/datasources/vendor_remote_data_source.dart index 7aa689a..1d98ca8 100644 --- a/lib/modules/vendors/data/datasources/vendor_remote_data_source.dart +++ b/lib/modules/vendors/data/datasources/vendor_remote_data_source.dart @@ -20,6 +20,15 @@ class VendorRemoteDataSource { return _parsePaginated(response.data, VendorModel.fromJson); } + /// Form-dropdown loader: all active vendors (`dropdown_call=true`). + Future> listVendorOptions() async { + final response = await dio.get( + ApiEndpoints.vendors, + queryParameters: const {'dropdown_call': true}, + ); + return _parseList(response.data, VendorModel.fromJson); + } + Future getVendorById(String id) async { final response = await dio.get(ApiEndpoints.vendorById(id)); return VendorModel.fromJson(response.data['data'] as Map); diff --git a/lib/modules/vendors/data/repositories/vendor_repository_impl.dart b/lib/modules/vendors/data/repositories/vendor_repository_impl.dart index bf71507..3e7f96a 100644 --- a/lib/modules/vendors/data/repositories/vendor_repository_impl.dart +++ b/lib/modules/vendors/data/repositories/vendor_repository_impl.dart @@ -26,6 +26,11 @@ class VendorRepositoryImpl implements VendorRepository { return safeApiCall(() => dataSource.getVendors(query)); } + @override + Future>> listVendorOptions() { + return safeApiCall(() => dataSource.listVendorOptions()); + } + @override Future> exportVendors(VendorListQuery query) { return safeApiCall(() => dataSource.exportVendors(query)); diff --git a/lib/modules/vendors/domain/repositories/vendor_repository.dart b/lib/modules/vendors/domain/repositories/vendor_repository.dart index 179d1cb..6c83428 100644 --- a/lib/modules/vendors/domain/repositories/vendor_repository.dart +++ b/lib/modules/vendors/domain/repositories/vendor_repository.dart @@ -5,6 +5,7 @@ import '../../../../shared/models/vendor_model.dart'; abstract class VendorRepository { Future>> getVendors(VendorListQuery query); + Future>> listVendorOptions(); Future> exportVendors(VendorListQuery query); Future> getVendorById(String id); Future> createVendor(Map data); diff --git a/lib/shared/models/asset_model.dart b/lib/shared/models/asset_model.dart index 6333635..1d4aa14 100644 --- a/lib/shared/models/asset_model.dart +++ b/lib/shared/models/asset_model.dart @@ -117,6 +117,7 @@ class AssetCategoryModel with _$AssetCategoryModel { @JsonKey(fromJson: _idFromJson) required String id, required String code, required String name, + @JsonKey(name: 'category_type') String? categoryType, @JsonKey(name: 'code_prefix') String? codePrefix, @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) int? defaultUsefulLifeYears, diff --git a/lib/shared/models/asset_model.freezed.dart b/lib/shared/models/asset_model.freezed.dart index 32991d2..100c2f1 100644 --- a/lib/shared/models/asset_model.freezed.dart +++ b/lib/shared/models/asset_model.freezed.dart @@ -25,6 +25,8 @@ mixin _$AssetCategoryModel { String get id => throw _privateConstructorUsedError; String get code => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; + @JsonKey(name: 'category_type') + String? get categoryType => throw _privateConstructorUsedError; @JsonKey(name: 'code_prefix') String? get codePrefix => throw _privateConstructorUsedError; @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) @@ -57,6 +59,7 @@ abstract class $AssetCategoryModelCopyWith<$Res> { @JsonKey(fromJson: _idFromJson) String id, String code, String name, + @JsonKey(name: 'category_type') String? categoryType, @JsonKey(name: 'code_prefix') String? codePrefix, @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) int? defaultUsefulLifeYears, @@ -86,6 +89,7 @@ class _$AssetCategoryModelCopyWithImpl<$Res, $Val extends AssetCategoryModel> Object? id = null, Object? code = null, Object? name = null, + Object? categoryType = freezed, Object? codePrefix = freezed, Object? defaultUsefulLifeYears = freezed, Object? defaultDepreciationMethod = freezed, @@ -107,6 +111,10 @@ class _$AssetCategoryModelCopyWithImpl<$Res, $Val extends AssetCategoryModel> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String, + categoryType: freezed == categoryType + ? _value.categoryType + : categoryType // ignore: cast_nullable_to_non_nullable + as String?, codePrefix: freezed == codePrefix ? _value.codePrefix : codePrefix // ignore: cast_nullable_to_non_nullable @@ -150,6 +158,7 @@ abstract class _$$AssetCategoryModelImplCopyWith<$Res> @JsonKey(fromJson: _idFromJson) String id, String code, String name, + @JsonKey(name: 'category_type') String? categoryType, @JsonKey(name: 'code_prefix') String? codePrefix, @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) int? defaultUsefulLifeYears, @@ -178,6 +187,7 @@ class __$$AssetCategoryModelImplCopyWithImpl<$Res> Object? id = null, Object? code = null, Object? name = null, + Object? categoryType = freezed, Object? codePrefix = freezed, Object? defaultUsefulLifeYears = freezed, Object? defaultDepreciationMethod = freezed, @@ -199,6 +209,10 @@ class __$$AssetCategoryModelImplCopyWithImpl<$Res> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String, + categoryType: freezed == categoryType + ? _value.categoryType + : categoryType // ignore: cast_nullable_to_non_nullable + as String?, codePrefix: freezed == codePrefix ? _value.codePrefix : codePrefix // ignore: cast_nullable_to_non_nullable @@ -235,6 +249,7 @@ class _$AssetCategoryModelImpl implements _AssetCategoryModel { @JsonKey(fromJson: _idFromJson) required this.id, required this.code, required this.name, + @JsonKey(name: 'category_type') this.categoryType, @JsonKey(name: 'code_prefix') this.codePrefix, @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) this.defaultUsefulLifeYears, @@ -256,6 +271,9 @@ class _$AssetCategoryModelImpl implements _AssetCategoryModel { @override final String name; @override + @JsonKey(name: 'category_type') + final String? categoryType; + @override @JsonKey(name: 'code_prefix') final String? codePrefix; @override @@ -274,7 +292,7 @@ class _$AssetCategoryModelImpl implements _AssetCategoryModel { @override String toString() { - return 'AssetCategoryModel(id: $id, code: $code, name: $name, codePrefix: $codePrefix, defaultUsefulLifeYears: $defaultUsefulLifeYears, defaultDepreciationMethod: $defaultDepreciationMethod, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt)'; + return 'AssetCategoryModel(id: $id, code: $code, name: $name, categoryType: $categoryType, codePrefix: $codePrefix, defaultUsefulLifeYears: $defaultUsefulLifeYears, defaultDepreciationMethod: $defaultDepreciationMethod, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt)'; } @override @@ -285,6 +303,8 @@ class _$AssetCategoryModelImpl implements _AssetCategoryModel { (identical(other.id, id) || other.id == id) && (identical(other.code, code) || other.code == code) && (identical(other.name, name) || other.name == name) && + (identical(other.categoryType, categoryType) || + other.categoryType == categoryType) && (identical(other.codePrefix, codePrefix) || other.codePrefix == codePrefix) && (identical(other.defaultUsefulLifeYears, defaultUsefulLifeYears) || @@ -309,6 +329,7 @@ class _$AssetCategoryModelImpl implements _AssetCategoryModel { id, code, name, + categoryType, codePrefix, defaultUsefulLifeYears, defaultDepreciationMethod, @@ -339,6 +360,7 @@ abstract class _AssetCategoryModel implements AssetCategoryModel { @JsonKey(fromJson: _idFromJson) required final String id, required final String code, required final String name, + @JsonKey(name: 'category_type') final String? categoryType, @JsonKey(name: 'code_prefix') final String? codePrefix, @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) final int? defaultUsefulLifeYears, @@ -360,6 +382,9 @@ abstract class _AssetCategoryModel implements AssetCategoryModel { @override String get name; @override + @JsonKey(name: 'category_type') + String? get categoryType; + @override @JsonKey(name: 'code_prefix') String? get codePrefix; @override diff --git a/lib/shared/models/asset_model.g.dart b/lib/shared/models/asset_model.g.dart index 9a6b34d..afd35b4 100644 --- a/lib/shared/models/asset_model.g.dart +++ b/lib/shared/models/asset_model.g.dart @@ -12,6 +12,7 @@ _$AssetCategoryModelImpl _$$AssetCategoryModelImplFromJson( id: _idFromJson(json['id']), code: json['code'] as String, name: json['name'] as String, + categoryType: json['category_type'] as String?, codePrefix: json['code_prefix'] as String?, defaultUsefulLifeYears: _intFromJsonNullable( json['default_useful_life_years'], @@ -32,6 +33,7 @@ Map _$$AssetCategoryModelImplToJson( 'id': instance.id, 'code': instance.code, 'name': instance.name, + 'category_type': instance.categoryType, 'code_prefix': instance.codePrefix, 'default_useful_life_years': instance.defaultUsefulLifeYears, 'default_depreciation_method': instance.defaultDepreciationMethod, diff --git a/lib/shared/models/purchase_order_model.dart b/lib/shared/models/purchase_order_model.dart index 2c64c40..7e2dd13 100644 --- a/lib/shared/models/purchase_order_model.dart +++ b/lib/shared/models/purchase_order_model.dart @@ -38,6 +38,17 @@ Object? _readNestedName(Map json, String flatKey, String neste Object? _readVendorName(Map json, String key) => _readNestedName(json, 'vendor_name', 'vendor'); +Object? _readVendorType(Map json, String key) { + final flat = json['vendor_type']; + if (flat is String && flat.isNotEmpty) return flat; + final nested = json['vendor']; + if (nested is Map) { + final type = nested['vendor_type']; + if (type != null && type.toString().isNotEmpty) return type; + } + return null; +} + Object? _readPlantName(Map json, String key) => _readNestedName(json, 'plant_name', 'plant'); @@ -139,15 +150,14 @@ class PurchaseOrderModel with _$PurchaseOrderModel { @JsonKey(fromJson: _idFromJson) required String id, @JsonKey(name: 'po_number', readValue: _readPoNumber) String? poNo, @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) DateTime? poDate, - @JsonKey(name: 'po_type') String? poType, @Default('DRAFT') String status, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorName) String? vendorName, + @JsonKey(name: 'vendor_type', readValue: _readVendorType) String? vendorType, @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? plantId, @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) int? warehouseId, @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) String? warehouseName, - @JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable) int? brandId, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) int? paymentTermId, @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) int? deliveryTermId, @JsonKey(name: 'expected_delivery_date', fromJson: _dateFromJsonNullable) @@ -254,7 +264,6 @@ class PurchaseOrderListQuery with _$PurchaseOrderListQuery { @Default(20) int limit, String? search, String? status, - String? poType, int? vendorId, int? plantId, String? dateFrom, @@ -262,14 +271,6 @@ class PurchaseOrderListQuery with _$PurchaseOrderListQuery { }) = _PurchaseOrderListQuery; } -const poTypeOptions = [ - ('RAW_MATERIAL', 'Raw Material'), - ('PACKING_MATERIAL', 'Packing Material'), - ('ASSET_CAPITAL', 'Asset / Capital'), - ('SERVICE', 'Service'), - ('GENERAL', 'General'), -]; - const poStatusOptions = [ ('DRAFT', 'Draft'), // ('SUBMITTED', 'Submitted'), @@ -281,15 +282,6 @@ const poStatusOptions = [ ('FULLY_RECEIVED', 'Fully Received'), ]; -String poTypeLabel(String? value) { - if (value == null) return '—'; - return poTypeOptions - .where((e) => e.$1 == value) - .map((e) => e.$2) - .firstOrNull ?? - value; -} - String poStatusLabel(String? value) { if (value == null) return '—'; final normalizedKey = value.trim().toUpperCase().replaceAll(' ', '_'); diff --git a/lib/shared/models/purchase_order_model.freezed.dart b/lib/shared/models/purchase_order_model.freezed.dart index 9e924c0..80feaae 100644 --- a/lib/shared/models/purchase_order_model.freezed.dart +++ b/lib/shared/models/purchase_order_model.freezed.dart @@ -27,13 +27,13 @@ mixin _$PurchaseOrderModel { String? get poNo => throw _privateConstructorUsedError; @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) DateTime? get poDate => throw _privateConstructorUsedError; - @JsonKey(name: 'po_type') - String? get poType => throw _privateConstructorUsedError; String get status => throw _privateConstructorUsedError; @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? get vendorId => throw _privateConstructorUsedError; @JsonKey(name: 'vendor_name', readValue: _readVendorName) String? get vendorName => throw _privateConstructorUsedError; + @JsonKey(name: 'vendor_type', readValue: _readVendorType) + String? get vendorType => throw _privateConstructorUsedError; @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? get plantId => throw _privateConstructorUsedError; @JsonKey(name: 'plant_name', readValue: _readPlantName) @@ -42,8 +42,6 @@ mixin _$PurchaseOrderModel { int? get warehouseId => throw _privateConstructorUsedError; @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) String? get warehouseName => throw _privateConstructorUsedError; - @JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable) - int? get brandId => throw _privateConstructorUsedError; @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) int? get paymentTermId => throw _privateConstructorUsedError; @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @@ -94,18 +92,18 @@ abstract class $PurchaseOrderModelCopyWith<$Res> { @JsonKey(fromJson: _idFromJson) String id, @JsonKey(name: 'po_number', readValue: _readPoNumber) String? poNo, @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) DateTime? poDate, - @JsonKey(name: 'po_type') String? poType, String status, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorName) String? vendorName, + @JsonKey(name: 'vendor_type', readValue: _readVendorType) + String? vendorType, @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? plantId, @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) int? warehouseId, @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) String? warehouseName, - @JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable) int? brandId, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) int? paymentTermId, @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @@ -152,15 +150,14 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel> Object? id = null, Object? poNo = freezed, Object? poDate = freezed, - Object? poType = freezed, Object? status = null, Object? vendorId = freezed, Object? vendorName = freezed, + Object? vendorType = freezed, Object? plantId = freezed, Object? plantName = freezed, Object? warehouseId = freezed, Object? warehouseName = freezed, - Object? brandId = freezed, Object? paymentTermId = freezed, Object? deliveryTermId = freezed, Object? expectedDeliveryDate = freezed, @@ -191,10 +188,6 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel> ? _value.poDate : poDate // ignore: cast_nullable_to_non_nullable as DateTime?, - poType: freezed == poType - ? _value.poType - : poType // ignore: cast_nullable_to_non_nullable - as String?, status: null == status ? _value.status : status // ignore: cast_nullable_to_non_nullable @@ -207,6 +200,10 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel> ? _value.vendorName : vendorName // ignore: cast_nullable_to_non_nullable as String?, + vendorType: freezed == vendorType + ? _value.vendorType + : vendorType // ignore: cast_nullable_to_non_nullable + as String?, plantId: freezed == plantId ? _value.plantId : plantId // ignore: cast_nullable_to_non_nullable @@ -223,10 +220,6 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel> ? _value.warehouseName : warehouseName // ignore: cast_nullable_to_non_nullable as String?, - brandId: freezed == brandId - ? _value.brandId - : brandId // ignore: cast_nullable_to_non_nullable - as int?, paymentTermId: freezed == paymentTermId ? _value.paymentTermId : paymentTermId // ignore: cast_nullable_to_non_nullable @@ -306,18 +299,18 @@ abstract class _$$PurchaseOrderModelImplCopyWith<$Res> @JsonKey(fromJson: _idFromJson) String id, @JsonKey(name: 'po_number', readValue: _readPoNumber) String? poNo, @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) DateTime? poDate, - @JsonKey(name: 'po_type') String? poType, String status, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorName) String? vendorName, + @JsonKey(name: 'vendor_type', readValue: _readVendorType) + String? vendorType, @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? plantId, @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) int? warehouseId, @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) String? warehouseName, - @JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable) int? brandId, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) int? paymentTermId, @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @@ -363,15 +356,14 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res> Object? id = null, Object? poNo = freezed, Object? poDate = freezed, - Object? poType = freezed, Object? status = null, Object? vendorId = freezed, Object? vendorName = freezed, + Object? vendorType = freezed, Object? plantId = freezed, Object? plantName = freezed, Object? warehouseId = freezed, Object? warehouseName = freezed, - Object? brandId = freezed, Object? paymentTermId = freezed, Object? deliveryTermId = freezed, Object? expectedDeliveryDate = freezed, @@ -402,10 +394,6 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res> ? _value.poDate : poDate // ignore: cast_nullable_to_non_nullable as DateTime?, - poType: freezed == poType - ? _value.poType - : poType // ignore: cast_nullable_to_non_nullable - as String?, status: null == status ? _value.status : status // ignore: cast_nullable_to_non_nullable @@ -418,6 +406,10 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res> ? _value.vendorName : vendorName // ignore: cast_nullable_to_non_nullable as String?, + vendorType: freezed == vendorType + ? _value.vendorType + : vendorType // ignore: cast_nullable_to_non_nullable + as String?, plantId: freezed == plantId ? _value.plantId : plantId // ignore: cast_nullable_to_non_nullable @@ -434,10 +426,6 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res> ? _value.warehouseName : warehouseName // ignore: cast_nullable_to_non_nullable as String?, - brandId: freezed == brandId - ? _value.brandId - : brandId // ignore: cast_nullable_to_non_nullable - as int?, paymentTermId: freezed == paymentTermId ? _value.paymentTermId : paymentTermId // ignore: cast_nullable_to_non_nullable @@ -510,17 +498,16 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { @JsonKey(fromJson: _idFromJson) required this.id, @JsonKey(name: 'po_number', readValue: _readPoNumber) this.poNo, @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) this.poDate, - @JsonKey(name: 'po_type') this.poType, this.status = 'DRAFT', @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) this.vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorName) this.vendorName, + @JsonKey(name: 'vendor_type', readValue: _readVendorType) this.vendorType, @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) this.plantId, @JsonKey(name: 'plant_name', readValue: _readPlantName) this.plantName, @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) this.warehouseId, @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) this.warehouseName, - @JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable) this.brandId, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) this.paymentTermId, @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @@ -561,9 +548,6 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) final DateTime? poDate; @override - @JsonKey(name: 'po_type') - final String? poType; - @override @JsonKey() final String status; @override @@ -573,6 +557,9 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { @JsonKey(name: 'vendor_name', readValue: _readVendorName) final String? vendorName; @override + @JsonKey(name: 'vendor_type', readValue: _readVendorType) + final String? vendorType; + @override @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) final int? plantId; @override @@ -585,9 +572,6 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) final String? warehouseName; @override - @JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable) - final int? brandId; - @override @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) final int? paymentTermId; @override @@ -639,7 +623,7 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { @override String toString() { - return 'PurchaseOrderModel(id: $id, poNo: $poNo, poDate: $poDate, poType: $poType, status: $status, vendorId: $vendorId, vendorName: $vendorName, plantId: $plantId, plantName: $plantName, warehouseId: $warehouseId, warehouseName: $warehouseName, brandId: $brandId, paymentTermId: $paymentTermId, deliveryTermId: $deliveryTermId, expectedDeliveryDate: $expectedDeliveryDate, discountAmount: $discountAmount, freightCharges: $freightCharges, otherCharges: $otherCharges, taxableAmount: $taxableAmount, taxAmount: $taxAmount, totalAmount: $totalAmount, termsAndConditions: $termsAndConditions, remarks: $remarks, revisionNo: $revisionNo, createdAt: $createdAt, updatedAt: $updatedAt, items: $items)'; + return 'PurchaseOrderModel(id: $id, poNo: $poNo, poDate: $poDate, status: $status, vendorId: $vendorId, vendorName: $vendorName, vendorType: $vendorType, plantId: $plantId, plantName: $plantName, warehouseId: $warehouseId, warehouseName: $warehouseName, paymentTermId: $paymentTermId, deliveryTermId: $deliveryTermId, expectedDeliveryDate: $expectedDeliveryDate, discountAmount: $discountAmount, freightCharges: $freightCharges, otherCharges: $otherCharges, taxableAmount: $taxableAmount, taxAmount: $taxAmount, totalAmount: $totalAmount, termsAndConditions: $termsAndConditions, remarks: $remarks, revisionNo: $revisionNo, createdAt: $createdAt, updatedAt: $updatedAt, items: $items)'; } @override @@ -650,12 +634,13 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { (identical(other.id, id) || other.id == id) && (identical(other.poNo, poNo) || other.poNo == poNo) && (identical(other.poDate, poDate) || other.poDate == poDate) && - (identical(other.poType, poType) || other.poType == poType) && (identical(other.status, status) || other.status == status) && (identical(other.vendorId, vendorId) || other.vendorId == vendorId) && (identical(other.vendorName, vendorName) || other.vendorName == vendorName) && + (identical(other.vendorType, vendorType) || + other.vendorType == vendorType) && (identical(other.plantId, plantId) || other.plantId == plantId) && (identical(other.plantName, plantName) || other.plantName == plantName) && @@ -663,7 +648,6 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { other.warehouseId == warehouseId) && (identical(other.warehouseName, warehouseName) || other.warehouseName == warehouseName) && - (identical(other.brandId, brandId) || other.brandId == brandId) && (identical(other.paymentTermId, paymentTermId) || other.paymentTermId == paymentTermId) && (identical(other.deliveryTermId, deliveryTermId) || @@ -701,15 +685,14 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { id, poNo, poDate, - poType, status, vendorId, vendorName, + vendorType, plantId, plantName, warehouseId, warehouseName, - brandId, paymentTermId, deliveryTermId, expectedDeliveryDate, @@ -750,12 +733,13 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel { @JsonKey(name: 'po_number', readValue: _readPoNumber) final String? poNo, @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) final DateTime? poDate, - @JsonKey(name: 'po_type') final String? poType, final String status, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) final int? vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorName) final String? vendorName, + @JsonKey(name: 'vendor_type', readValue: _readVendorType) + final String? vendorType, @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) final int? plantId, @JsonKey(name: 'plant_name', readValue: _readPlantName) @@ -764,8 +748,6 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel { final int? warehouseId, @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) final String? warehouseName, - @JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable) - final int? brandId, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) final int? paymentTermId, @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @@ -809,9 +791,6 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel { @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) DateTime? get poDate; @override - @JsonKey(name: 'po_type') - String? get poType; - @override String get status; @override @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) @@ -820,6 +799,9 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel { @JsonKey(name: 'vendor_name', readValue: _readVendorName) String? get vendorName; @override + @JsonKey(name: 'vendor_type', readValue: _readVendorType) + String? get vendorType; + @override @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? get plantId; @override @@ -832,9 +814,6 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel { @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) String? get warehouseName; @override - @JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable) - int? get brandId; - @override @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) int? get paymentTermId; @override @@ -1662,7 +1641,6 @@ mixin _$PurchaseOrderListQuery { int get limit => throw _privateConstructorUsedError; String? get search => throw _privateConstructorUsedError; String? get status => throw _privateConstructorUsedError; - String? get poType => throw _privateConstructorUsedError; int? get vendorId => throw _privateConstructorUsedError; int? get plantId => throw _privateConstructorUsedError; String? get dateFrom => throw _privateConstructorUsedError; @@ -1687,7 +1665,6 @@ abstract class $PurchaseOrderListQueryCopyWith<$Res> { int limit, String? search, String? status, - String? poType, int? vendorId, int? plantId, String? dateFrom, @@ -1717,7 +1694,6 @@ class _$PurchaseOrderListQueryCopyWithImpl< Object? limit = null, Object? search = freezed, Object? status = freezed, - Object? poType = freezed, Object? vendorId = freezed, Object? plantId = freezed, Object? dateFrom = freezed, @@ -1741,10 +1717,6 @@ class _$PurchaseOrderListQueryCopyWithImpl< ? _value.status : status // ignore: cast_nullable_to_non_nullable as String?, - poType: freezed == poType - ? _value.poType - : poType // ignore: cast_nullable_to_non_nullable - as String?, vendorId: freezed == vendorId ? _value.vendorId : vendorId // ignore: cast_nullable_to_non_nullable @@ -1781,7 +1753,6 @@ abstract class _$$PurchaseOrderListQueryImplCopyWith<$Res> int limit, String? search, String? status, - String? poType, int? vendorId, int? plantId, String? dateFrom, @@ -1808,7 +1779,6 @@ class __$$PurchaseOrderListQueryImplCopyWithImpl<$Res> Object? limit = null, Object? search = freezed, Object? status = freezed, - Object? poType = freezed, Object? vendorId = freezed, Object? plantId = freezed, Object? dateFrom = freezed, @@ -1832,10 +1802,6 @@ class __$$PurchaseOrderListQueryImplCopyWithImpl<$Res> ? _value.status : status // ignore: cast_nullable_to_non_nullable as String?, - poType: freezed == poType - ? _value.poType - : poType // ignore: cast_nullable_to_non_nullable - as String?, vendorId: freezed == vendorId ? _value.vendorId : vendorId // ignore: cast_nullable_to_non_nullable @@ -1865,7 +1831,6 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery { this.limit = 20, this.search, this.status, - this.poType, this.vendorId, this.plantId, this.dateFrom, @@ -1883,8 +1848,6 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery { @override final String? status; @override - final String? poType; - @override final int? vendorId; @override final int? plantId; @@ -1895,7 +1858,7 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery { @override String toString() { - return 'PurchaseOrderListQuery(page: $page, limit: $limit, search: $search, status: $status, poType: $poType, vendorId: $vendorId, plantId: $plantId, dateFrom: $dateFrom, dateTo: $dateTo)'; + return 'PurchaseOrderListQuery(page: $page, limit: $limit, search: $search, status: $status, vendorId: $vendorId, plantId: $plantId, dateFrom: $dateFrom, dateTo: $dateTo)'; } @override @@ -1907,7 +1870,6 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery { (identical(other.limit, limit) || other.limit == limit) && (identical(other.search, search) || other.search == search) && (identical(other.status, status) || other.status == status) && - (identical(other.poType, poType) || other.poType == poType) && (identical(other.vendorId, vendorId) || other.vendorId == vendorId) && (identical(other.plantId, plantId) || other.plantId == plantId) && @@ -1923,7 +1885,6 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery { limit, search, status, - poType, vendorId, plantId, dateFrom, @@ -1949,7 +1910,6 @@ abstract class _PurchaseOrderListQuery implements PurchaseOrderListQuery { final int limit, final String? search, final String? status, - final String? poType, final int? vendorId, final int? plantId, final String? dateFrom, @@ -1965,8 +1925,6 @@ abstract class _PurchaseOrderListQuery implements PurchaseOrderListQuery { @override String? get status; @override - String? get poType; - @override int? get vendorId; @override int? get plantId; diff --git a/lib/shared/models/purchase_order_model.g.dart b/lib/shared/models/purchase_order_model.g.dart index cccadb4..286e7dc 100644 --- a/lib/shared/models/purchase_order_model.g.dart +++ b/lib/shared/models/purchase_order_model.g.dart @@ -12,15 +12,14 @@ _$PurchaseOrderModelImpl _$$PurchaseOrderModelImplFromJson( id: _idFromJson(json['id']), poNo: _readPoNumber(json, 'po_number') as String?, poDate: _dateFromJsonNullable(json['po_date']), - poType: json['po_type'] as String?, status: json['status'] as String? ?? 'DRAFT', vendorId: _intFromJsonNullable(json['vendor_id']), vendorName: _readVendorName(json, 'vendor_name') as String?, + vendorType: _readVendorType(json, 'vendor_type') as String?, plantId: _intFromJsonNullable(json['plant_id']), plantName: _readPlantName(json, 'plant_name') as String?, warehouseId: _intFromJsonNullable(json['warehouse_id']), warehouseName: _readWarehouseName(json, 'warehouse_name') as String?, - brandId: _intFromJsonNullable(json['brand_id']), paymentTermId: _intFromJsonNullable(json['payment_term_id']), deliveryTermId: _intFromJsonNullable(json['delivery_term_id']), expectedDeliveryDate: _dateFromJsonNullable(json['expected_delivery_date']), @@ -50,15 +49,14 @@ Map _$$PurchaseOrderModelImplToJson( 'id': instance.id, 'po_number': instance.poNo, 'po_date': instance.poDate?.toIso8601String(), - 'po_type': instance.poType, 'status': instance.status, 'vendor_id': instance.vendorId, 'vendor_name': instance.vendorName, + 'vendor_type': instance.vendorType, 'plant_id': instance.plantId, 'plant_name': instance.plantName, 'warehouse_id': instance.warehouseId, 'warehouse_name': instance.warehouseName, - 'brand_id': instance.brandId, 'payment_term_id': instance.paymentTermId, 'delivery_term_id': instance.deliveryTermId, 'expected_delivery_date': instance.expectedDeliveryDate?.toIso8601String(), diff --git a/lib/shared/widgets/master_inline_quick_add_form.dart b/lib/shared/widgets/master_inline_quick_add_form.dart index 6b9a52c..4b08172 100644 --- a/lib/shared/widgets/master_inline_quick_add_form.dart +++ b/lib/shared/widgets/master_inline_quick_add_form.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/utils/validators.dart'; +import '../../modules/assets/data/repositories/asset_repository_impl.dart'; import '../../modules/master_data/data/repositories/master_repository_impl.dart'; import '../../modules/master_data/domain/entities/master_definition.dart'; import 'app_form_toggle_field.dart'; @@ -77,17 +78,46 @@ class _MasterInlineQuickAddFormState Future _loadOptions() async { final options = >>{}; - final keys = _definition.formFields - .where((f) => f.optionsMasterKey != null && f.staticOptions == null) - .map((f) => f.optionsMasterKey!) - .toSet(); + final fields = _definition.formFields + .where((f) => f.optionsMasterKey != null && f.staticOptions == null); + + for (final field in fields) { + final key = field.optionsMasterKey!; + final lookupKey = masterFieldDropdownLookupKey( + masterId: widget.masterId, + field: field, + values: _values, + ); + if (options.containsKey(lookupKey)) continue; + + if (key == 'asset_depreciation_methods') { + final result = + await ref.read(assetRepositoryProvider).getDepreciationMethods(); + if (result.failure != null) continue; + options[lookupKey] = (result.data ?? const []) + .where((option) => option.value.trim().isNotEmpty) + .map( + (option) => { + 'id': option.value, + 'name': option.label, + }, + ) + .toList(); + continue; + } - for (final key in keys) { final def = masterDefinitionById(key); if (def == null) continue; - final result = await ref.read(masterRepositoryProvider).listOptions(def); + final result = await ref.read(masterRepositoryProvider).listOptions( + def, + queryParameters: masterFieldOptionsQuery( + masterId: widget.masterId, + field: field, + values: _values, + ), + ); if (result.failure == null) { - options[key] = result.data ?? const []; + options[lookupKey] = result.data ?? const []; } } @@ -98,9 +128,23 @@ class _MasterInlineQuickAddFormState }); } + void _updateValue(String key, dynamic value) { + setState(() { + _values[key] = value; + if (key == 'category_type') { + for (final field in _definition.formFields) { + if (field.isVisibleInForm(_values)) continue; + _values[field.key] = null; + _controllers[field.key]?.text = ''; + } + } + }); + } + Map _buildPayload() { final payload = {}; for (final field in _definition.formFields) { + if (!field.isVisibleInForm(_values)) continue; var value = _values[field.key]; if (field.type == MasterFieldType.text || field.type == MasterFieldType.number) { @@ -109,7 +153,11 @@ class _MasterInlineQuickAddFormState if (value == null || value == '') continue; payload[field.key] = switch (field.type) { MasterFieldType.number => num.tryParse(value.toString()) ?? value, - MasterFieldType.dropdown => int.tryParse(value.toString()) ?? value, + MasterFieldType.dropdown => field.staticOptions != null + ? value.toString() + : (field.optionsMasterKey == 'asset_depreciation_methods' + ? value.toString() + : int.tryParse(value.toString()) ?? value), MasterFieldType.boolean => value == true, MasterFieldType.text => value.toString().trim(), }; @@ -157,8 +205,13 @@ class _MasterInlineQuickAddFormState .map((v) => {'id': v, 'name': v}) .toList(); } + final lookupKey = masterFieldDropdownLookupKey( + masterId: widget.masterId, + field: field, + values: _values, + ); var options = - _dropdownOptions[field.optionsMasterKey] ?? const >[]; + _dropdownOptions[lookupKey] ?? const >[]; final filterField = field.filterByFieldKey; if (filterField != null) { final parentId = _values[filterField]?.toString(); @@ -182,7 +235,7 @@ class _MasterInlineQuickAddFormState value: _values[field.key] == true, onChanged: _submitting ? null - : (v) => setState(() => _values[field.key] = v), + : (v) => _updateValue(field.key, v), ); } return CheckboxListTile( @@ -192,7 +245,7 @@ class _MasterInlineQuickAddFormState value: _values[field.key] == true, onChanged: _submitting ? null - : (v) => setState(() => _values[field.key] = v ?? false), + : (v) => _updateValue(field.key, v ?? false), ); case MasterFieldType.dropdown: @@ -221,7 +274,7 @@ class _MasterInlineQuickAddFormState ], onChanged: (_submitting || prefilled) ? null - : (v) => setState(() => _values[field.key] = v), + : (v) => _updateValue(field.key, v), validator: field.required ? (v) => v == null ? '${field.label} is required' : null : null, @@ -252,9 +305,12 @@ class _MasterInlineQuickAddFormState Widget build(BuildContext context) { final theme = Theme.of(context); final fields = _definition.formFields; - final activeField = fields.where((field) => field.key == 'is_active').firstOrNull; - final regularFields = - fields.where((field) => field.key != 'is_active').toList(); + final activeField = + fields.where((field) => field.key == 'is_active').firstOrNull; + final regularFields = fields + .where((field) => field.key != 'is_active') + .where((field) => field.isVisibleInForm(_values)) + .toList(); return Material( color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.45),