item level change

This commit is contained in:
Surendiran 2026-07-16 17:14:23 +05:30
parent 3914e78465
commit 36847aaf87
44 changed files with 834 additions and 461 deletions

View File

@ -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';

View File

@ -104,10 +104,20 @@ class AssetRemoteDataSource {
return _parseList(response.data, AssetTransferHistoryModel.fromJson);
}
Future<List<AssetCategoryModel>> getCategories() async {
Future<List<AssetCategoryModel>> 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)

View File

@ -63,8 +63,10 @@ class AssetRepositoryImpl implements AssetRepository {
}
@override
Future<Result<List<AssetCategoryModel>>> getCategories() {
return safeApiCall(() => dataSource.getCategories());
Future<Result<List<AssetCategoryModel>>> getCategories({
bool dropdownCall = false,
}) {
return safeApiCall(() => dataSource.getCategories(dropdownCall: dropdownCall));
}
@override

View File

@ -13,7 +13,9 @@ abstract class AssetRepository {
Future<Result<void>> deleteAsset(String id);
Future<Result<void>> transferAsset(String id, Map<String, dynamic> data);
Future<Result<List<AssetTransferHistoryModel>>> getTransferHistory(String assetId);
Future<Result<List<AssetCategoryModel>>> getCategories();
Future<Result<List<AssetCategoryModel>>> getCategories({
bool dropdownCall = false,
});
Future<Result<AssetDropdownOptionsModel>> getAssetOptions();
Future<Result<List<AssetDropdownOption>>> getContractTypes();
Future<Result<List<AssetDropdownOption>>> getVisitTypes();

View File

@ -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<List<AssetCategoryModel>>((ref) async {
final repository = ref.watch(assetRepositoryProvider);
final result = await repository.getCategories();
@ -11,5 +11,14 @@ final itemCategoriesProvider = FutureProvider<List<AssetCategoryModel>>((ref) as
return result.data ?? [];
});
/// Categories for Asset form dropdowns (`dropdown_call=true`).
final itemCategoriesFormProvider =
FutureProvider<List<AssetCategoryModel>>((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;

View File

@ -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<List<FilterOptionModel>> _safeOptions(
Future<List<FilterOptionModel>> _safeVendorOptions(Ref ref) async {
try {
final vendors = <FilterOptionModel>[];
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<List<FilterOptionModel>> _safeVendorOptions(Ref ref) async {
Future<List<FilterOptionModel>> _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<List<FilterOptionModel>> _safeUserOptions(Ref ref) async {
Future<List<FilterOptionModel>> _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<List<FilterOptionModel>> _safePurchaseOrderOptions(Ref ref) async {
Future<List<FilterOptionModel>> _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,

View File

@ -466,7 +466,7 @@ class _AssetFormPanelState extends ConsumerState<AssetFormPanel> {
@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<AssetFormPanel> {
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<AssetFormPanel> {
.where((option) => option.value != 0)
.toList(),
refreshLookups: () {
ref.invalidate(itemCategoriesProvider);
ref.invalidate(itemCategoriesFormProvider);
},
parseCreatedId: int.tryParse,
onChanged: (v) => setState(() {

View File

@ -19,6 +19,26 @@ class GrnRemoteDataSource {
return _parsePaginated(response.data, GrnModel.fromJson);
}
/// Form-dropdown loader: all active GRNs (`dropdown_call=true`).
Future<List<GrnModel>> 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>()
.map((item) => GrnModel.fromJson(Map<String, dynamic>.from(item)))
.toList();
}
Future<GrnModel> getGrnById(String id) async {
final response = await dio.get(ApiEndpoints.grnById(id));
return GrnModel.fromJson(response.data['data'] as Map<String, dynamic>);

View File

@ -26,6 +26,11 @@ class GrnRepositoryImpl implements GrnRepository {
return safeApiCall(() => dataSource.getGrns(query));
}
@override
Future<Result<List<GrnModel>>> listGrnOptions() {
return safeApiCall(() => dataSource.listGrnOptions());
}
@override
Future<Result<ExportFileResult>> exportGrns(GrnListQuery query) {
return safeApiCall(() => dataSource.exportGrns(query));

View File

@ -5,6 +5,7 @@ import '../../../../shared/models/grn_model.dart';
abstract class GrnRepository {
Future<Result<PaginatedResponse<GrnModel>>> getGrns(GrnListQuery query);
Future<Result<List<GrnModel>>> listGrnOptions();
Future<Result<ExportFileResult>> exportGrns(GrnListQuery query);
Future<Result<GrnModel>> getGrnById(String id);
Future<Result<GrnModel>> createGrn(Map<String, dynamic> data);

View File

@ -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<GrnLookups>((ref) async {
final receivablePos = <PurchaseOrderModel>[];
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<List<FilterOptionModel>> _safeOptions(
Future<List<FilterOptionModel>> _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,

View File

@ -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<String, dynamic>? 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<List<Map<String, dynamic>>> listOptions(MasterDefinition definition) async {
final allItems = <Map<String, dynamic>>[];
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<List<Map<String, dynamic>>> listOptions(
MasterDefinition definition, {
Map<String, dynamic>? queryParameters,
}) async {
final response = await dio.get(
definition.apiPath,
queryParameters: {
'dropdown_call': true,
...?queryParameters,
},
);
final body = response.data as Map<String, dynamic>;
final raw = body['data'];
final list = raw is List<dynamic>
? raw
: raw is Map<String, dynamic>
? raw['items'] as List<dynamic>? ?? const []
: const [];
return list
.whereType<Map>()
.map((item) => Map<String, dynamic>.from(item))
.where(isActiveOptionRow)
.toList();
}
Future<Map<String, dynamic>> getById(

View File

@ -33,9 +33,12 @@ class MasterRepositoryImpl implements MasterRepository {
@override
Future<Result<List<Map<String, dynamic>>>> listOptions(
MasterDefinition definition,
) =>
safeApiCall(() => remote.listOptions(definition));
MasterDefinition definition, {
Map<String, dynamic>? queryParameters,
}) =>
safeApiCall(
() => remote.listOptions(definition, queryParameters: queryParameters),
);
@override
Future<Result<Map<String, dynamic>>> getById(

View File

@ -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<String, dynamic>? optionsQueryParams;
/// Fixed dropdown choices (e.g. category type) no API lookup.
final List<String>? 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<String, dynamic> values) {
final whenKey = visibleWhenFieldKey;
if (whenKey == null) return true;
return values[whenKey]?.toString() == visibleWhenValue;
}
}
class MasterDefinition {
@ -119,11 +148,27 @@ const masterDefinitions = <MasterDefinition>[
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 = <MasterDefinition>[
type: MasterFieldType.dropdown,
showInList: true,
optionsMasterKey: 'asset_depreciation_methods',
visibleWhenFieldKey: 'category_type',
visibleWhenValue: 'ASSET',
),
_activeField,
],
@ -175,6 +222,12 @@ const masterDefinitions = <MasterDefinition>[
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 = <MasterDefinition>[
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 = <MasterDefinition>[
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 = <MasterDefinition>[
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<String, dynamic> 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 "<base>_name" or nested "<base>.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<String, dynamic> row) {
if (active == false) return 'inactive';
return 'active';
}
/// Category list filter for Items form: ASSET when Asset Item is checked.
String itemCategoryTypeForValues(Map<String, dynamic> values) =>
values['is_asset_item'] == true ? 'ASSET' : 'STOCK';
/// Effective options query for a form field (may depend on other values).
Map<String, dynamic>? masterFieldOptionsQuery({
required String masterId,
required MasterFieldDef field,
required Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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;
}

View File

@ -11,7 +11,10 @@ abstract class MasterRepository {
String? search,
});
Future<Result<List<Map<String, dynamic>>>> listOptions(MasterDefinition definition);
Future<Result<List<Map<String, dynamic>>>> listOptions(
MasterDefinition definition, {
Map<String, dynamic>? queryParameters,
});
Future<Result<Map<String, dynamic>>> getById(
MasterDefinition definition,

View File

@ -250,7 +250,6 @@ class MasterFormNotifier extends FamilyAsyncNotifier<MasterFormState, MasterForm
@override
Future<MasterFormState> build(MasterFormArgs arg) async {
final dropdownOptions = await _loadDropdownOptions();
final existingRecords = await _loadExistingRecords();
Map<String, dynamic> values = {};
@ -272,6 +271,18 @@ class MasterFormNotifier extends FamilyAsyncNotifier<MasterFormState, MasterForm
}
}
// Load after values so Items category options use is_asset_item.
final dropdownOptions = await _loadDropdownOptions(values: values);
if (_definition.id == 'items') {
final syncState = MasterFormState(
values: values,
dropdownOptions: dropdownOptions,
existingRecords: existingRecords,
);
_applyGstFromHsn(values, syncState);
}
return MasterFormState(
values: values,
dropdownOptions: dropdownOptions,
@ -301,18 +312,25 @@ class MasterFormNotifier extends FamilyAsyncNotifier<MasterFormState, MasterForm
}
Future<Map<String, List<Map<String, dynamic>>>>
_loadDropdownOptions() async {
_loadDropdownOptions({Map<String, dynamic>? values}) async {
final formValues = values ?? state.valueOrNull?.values ?? const {};
final options = <String, List<Map<String, dynamic>>>{};
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) => <String, dynamic>{
@ -326,19 +344,93 @@ class MasterFormNotifier extends FamilyAsyncNotifier<MasterFormState, MasterForm
final def = masterDefinitionById(key);
if (def == null) continue;
final result = await ref.read(masterRepositoryProvider).listOptions(def);
final queryParameters = masterFieldOptionsQuery(
masterId: _definition.id,
field: field,
values: formValues,
);
final result = await ref.read(masterRepositoryProvider).listOptions(
def,
queryParameters: queryParameters,
);
if (result.failure != null) continue;
options[key] = result.data ?? const [];
options[lookupKey] = result.data ?? const [];
}
return options;
}
void _applyGstFromHsn(Map<String, dynamic> 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<String, dynamic>? 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<String, dynamic>.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<String, dynamic> 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<String, dynamic>.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<MasterFormState, MasterForm
if (dependentValue == null || dependentValue == '') continue;
final optionKey = field.filterByOptionKey ?? field.filterByFieldKey!;
final options =
current.dropdownOptions[field.optionsMasterKey] ?? const [];
final lookupKey = masterFieldDropdownLookupKey(
masterId: _definition.id,
field: field,
values: values,
);
final options = current.dropdownOptions[lookupKey] ?? const [];
final stillValid = options.any(
(item) =>
item['id']?.toString() == dependentValue.toString() &&
@ -362,22 +458,35 @@ class MasterFormNotifier extends FamilyAsyncNotifier<MasterFormState, MasterForm
state = AsyncData(current.copyWith(values: values));
}
Future<void> _reloadItemCategoryOptions(Map<String, dynamic> 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<void> 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<String, dynamic> _buildPayload(MasterFormState current) {
final payload = <String, dynamic>{};
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(),
};

View File

@ -119,20 +119,74 @@ class _MasterFormPanelState extends ConsumerState<MasterFormPanel> {
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<AppDropdownOption<String>> 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 <Map<String, dynamic>>[];
final filterField = field.filterByFieldKey;
if (filterField != null) {
@ -178,7 +232,9 @@ class _MasterFormPanelState extends ConsumerState<MasterFormPanel> {
if (canQuickAdd) {
return MasterQuickAddDropdown<String>(
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<MasterFormPanel> {
: 'Select ${field.label.toLowerCase()}',
searchHint: 'Search ${field.label.toLowerCase()}...',
enabled: parentSelected,
initialValues: filterField == null
? null
: {filterField: formState.values[filterField]},
initialValues: () {
final values = <String, dynamic>{};
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<MasterFormPanel> {
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<MasterFormPanel> {
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<MasterFormPanel> {
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(

View File

@ -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 <Map<String, dynamic>>[];
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')

View File

@ -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',

View File

@ -35,9 +35,6 @@ class MasterRemoteDataSource {
Future<List<FilterOptionModel>> listWarehouses() =>
_listOptions(ApiEndpoints.warehouses);
Future<List<FilterOptionModel>> listBrands() =>
_listOptions(ApiEndpoints.brands);
Future<List<FilterOptionModel>> listUom() => _listOptions(ApiEndpoints.uom);
Future<List<FilterOptionModel>> listItems() => _listOptions(ApiEndpoints.items);
@ -48,6 +45,32 @@ class MasterRemoteDataSource {
Future<List<FilterOptionModel>> listHsnCodes() =>
_listOptions(ApiEndpoints.hsnCodes);
/// HSN options with default `gst_rate_id` for GST autofill on item/PO lines.
Future<({List<FilterOptionModel> options, Map<String, int?> gstRateByHsnId})>
listHsnCodesWithGstRate() async {
final rows = await _listAllMaps(ApiEndpoints.hsnCodes);
final options = <FilterOptionModel>[];
final gstRateByHsnId = <String, int?>{};
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<String, int?> uomByItemId,
Map<String, int?> gstRateByItemId,
})> listItemsWithHsn() async {
final rows = await _listAllMaps(
ApiEndpoints.items,
queryParameters: {'is_active': true},
);
final rows = await _listAllMaps(ApiEndpoints.items);
final hsnByItemId = <String, int?>{};
final uomByItemId = <String, int?>{};
final gstRateByItemId = <String, int?>{};
@ -97,10 +117,7 @@ class MasterRemoteDataSource {
/// GST rate options with numeric `rate_pct` for tax calculations.
Future<({List<FilterOptionModel> options, Map<String, double> pctById})>
listGstRatesWithPct() async {
final rows = await _listAllMaps(
ApiEndpoints.gstRates,
queryParameters: {'is_active': true},
);
final rows = await _listAllMaps(ApiEndpoints.gstRates);
final options = <FilterOptionModel>[];
final pctById = <String, double>{};
@ -123,14 +140,29 @@ class MasterRemoteDataSource {
return (options: options, pctById: pctById);
}
Future<List<FilterOptionModel>> listItemCategories() =>
_listOptions(ApiEndpoints.itemCategories);
Future<List<FilterOptionModel>> 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<List<FilterOptionModel>> 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<List<FilterOptionModel>> _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<List<Map<String, dynamic>>> _listAllMaps(
String endpoint, {
Map<String, dynamic>? queryParameters,
}) async {
final all = <Map<String, dynamic>>[];
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<Map<String, dynamic>> items, int totalPages}) _parsePage(

View File

@ -22,6 +22,33 @@ class PurchaseOrderRemoteDataSource {
return _parsePaginated(response.data, PurchaseOrderModel.fromJson);
}
/// Form-dropdown loader (`dropdown_call=true`). Optional status filter.
Future<List<PurchaseOrderModel>> 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>()
.map(
(item) => PurchaseOrderModel.fromJson(Map<String, dynamic>.from(item)),
)
.toList();
}
Future<PurchaseOrderModel> 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,

View File

@ -32,6 +32,15 @@ class PurchaseOrderRepositoryImpl implements PurchaseOrderRepository {
return safeApiCall(() => dataSource.getPurchaseOrders(query));
}
@override
Future<Result<List<PurchaseOrderModel>>> listPurchaseOrderOptions({
String? status,
}) {
return safeApiCall(
() => dataSource.listPurchaseOrderOptions(status: status),
);
}
@override
Future<Result<ExportFileResult>> exportPurchaseOrders(
PurchaseOrderListQuery query,

View File

@ -8,6 +8,9 @@ abstract class PurchaseOrderRepository {
Future<Result<PaginatedResponse<PurchaseOrderModel>>> getPurchaseOrders(
PurchaseOrderListQuery query,
);
Future<Result<List<PurchaseOrderModel>>> listPurchaseOrderOptions({
String? status,
});
Future<Result<ExportFileResult>> exportPurchaseOrders(
PurchaseOrderListQuery query,
);

View File

@ -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<FilterOptionModel> vendors;
final List<FilterOptionModel> plants;
final List<FilterOptionModel> warehouses;
final List<FilterOptionModel> brands;
final List<FilterOptionModel> paymentTerms;
final List<FilterOptionModel> deliveryTerms;
final List<FilterOptionModel> items;
@ -44,6 +41,8 @@ class PurchaseOrderLookups {
/// GST rate id `rate_pct` for tax calculations.
final Map<String, double> gstRatePctById;
final List<FilterOptionModel> hsnCodes;
/// HSN code id default `gst_rate_id` from HSN master.
final Map<String, int?> 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<FilterOptionModel> options, Map<String, double> pctById})>
}
}
Future<({List<FilterOptionModel> options, Map<String, int?> gstRateByHsnId})>
_safeHsnWithGst(
Future<({List<FilterOptionModel> options, Map<String, int?> gstRateByHsnId})>
Function()
load,
) async {
try {
return await load();
} catch (_) {
return (options: <FilterOptionModel>[], gstRateByHsnId: <String, int?>{});
}
}
Future<List<FilterOptionModel>> _fetchActiveVendors(
VendorRepository vendorRepo,
) async {
final vendors = <FilterOptionModel>[];
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();
}

View File

@ -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;

View File

@ -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(),

View File

@ -50,11 +50,9 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
DateTime? _poDate;
DateTime? _expectedDeliveryDate;
String? _poType;
int? _vendorId;
int? _plantId;
int? _warehouseId;
int? _brandId;
int? _paymentTermId;
int? _deliveryTermId;
final List<PoLineItemDraft> _lines = [];
@ -99,11 +97,9 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
setState(() {
_poDate = order.poDate ?? DateTime.now();
_expectedDeliveryDate = order.expectedDeliveryDate;
_poType = order.poType;
_vendorId = order.vendorId;
_plantId = order.plantId;
_warehouseId = order.warehouseId;
_brandId = order.brandId;
_paymentTermId = order.paymentTermId;
_deliveryTermId = order.deliveryTermId;
_discountController.text =
@ -190,11 +186,9 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
Map<String, dynamic> _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<PurchaseOrderFormScree
return;
}
if (_poType == null || _vendorId == null || _plantId == null) {
if (_vendorId == null || _plantId == null) {
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Please complete all required fields')),
);
@ -391,23 +385,6 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
onPicked: (d) => setState(() => _poDate = d),
),
),
AppSearchableDropdown<String>(
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<int>(
label: 'Vendor *',
value: _dropdownValue(_vendorId, vendorIds),
@ -432,10 +409,6 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
validator: (v) =>
v == null ? 'Plant is required' : null,
),
],
),
FormRowFour(
children: [
MasterQuickAddDropdown<int?>(
masterId: 'warehouses',
label: 'Warehouse',
@ -449,18 +422,10 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
onChanged: (v) =>
setState(() => _warehouseId = v),
),
MasterQuickAddDropdown<int?>(
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<int?>(
masterId: 'payment_terms',
label: 'Payment Term',

View File

@ -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<PurchaseOrderListScree
onSearch: ref.read(purchaseOrdersListProvider.notifier).setSearch,
onStatusChanged:
ref.read(purchaseOrdersListProvider.notifier).setStatusFilter,
onPoTypeChanged:
ref.read(purchaseOrdersListProvider.notifier).setPoTypeFilter,
),
footer: AppPagination(
currentPage: state.query.page,
@ -273,7 +272,6 @@ class _FiltersBar extends StatelessWidget {
required this.statusOptions,
required this.onSearch,
required this.onStatusChanged,
required this.onPoTypeChanged,
this.showExport = false,
this.isExporting = false,
this.onExport,
@ -284,7 +282,6 @@ class _FiltersBar extends StatelessWidget {
final List<AppDropdownOption<String?>> statusOptions;
final ValueChanged<String> onSearch;
final ValueChanged<String?> onStatusChanged;
final ValueChanged<String?> onPoTypeChanged;
final bool showExport;
final bool isExporting;
final VoidCallback? onExport;
@ -306,19 +303,6 @@ class _FiltersBar extends StatelessWidget {
options: statusOptions,
onChanged: onStatusChanged,
),
AppSearchableDropdown<String?>(
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),

View File

@ -394,6 +394,18 @@ class _LineItemCardState extends ConsumerState<_LineItemCard> {
ref.read(purchaseOrderLookupsProvider).valueOrNull?.itemHsnById ??
widget.itemHsnById;
Map<String, int?> 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);
}
}
});
}

View File

@ -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<AddUserFormState, String?>
Future<AddUserFormState> 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<AddUserFormState, String?>
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) &&

View File

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

View File

@ -96,6 +96,26 @@ class UserRemoteDataSource {
);
}
/// Form-dropdown loader: all active users (`dropdown_call=true`).
Future<List<ManagedUserModel>> 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>()
.map((item) => ManagedUserModel.fromJson(Map<String, dynamic>.from(item)))
.toList();
}
Future<ManagedUserModel> getUserById(String id) async {
final response = await dio.get(ApiEndpoints.userById(id));
return ManagedUserModel.fromJson(response.data['data'] as Map<String, dynamic>);

View File

@ -35,6 +35,10 @@ class UserRepositoryImpl implements UserRepository {
) =>
safeApiCall(() => remote.getUsers(query));
@override
Future<Result<List<ManagedUserModel>>> listUserOptions() =>
safeApiCall(() => remote.listUserOptions());
@override
Future<Result<ManagedUserModel>> getUserById(String id) =>
safeApiCall(() => remote.getUserById(id));

View File

@ -7,6 +7,7 @@ abstract class UserRepository {
Future<Result<UserSummaryModel>> getSummary();
Future<Result<UserFiltersModel>> getFilters();
Future<Result<PaginatedResponse<ManagedUserModel>>> getUsers(UserListQuery query);
Future<Result<List<ManagedUserModel>>> listUserOptions();
Future<Result<ManagedUserModel>> getUserById(String id);
Future<Result<ManagedUserModel>> createUser(CreateUserRequest request);
Future<Result<ManagedUserModel>> updateUser(String id, UpdateUserRequest request);

View File

@ -20,6 +20,15 @@ class VendorRemoteDataSource {
return _parsePaginated(response.data, VendorModel.fromJson);
}
/// Form-dropdown loader: all active vendors (`dropdown_call=true`).
Future<List<VendorModel>> listVendorOptions() async {
final response = await dio.get(
ApiEndpoints.vendors,
queryParameters: const {'dropdown_call': true},
);
return _parseList(response.data, VendorModel.fromJson);
}
Future<VendorModel> getVendorById(String id) async {
final response = await dio.get(ApiEndpoints.vendorById(id));
return VendorModel.fromJson(response.data['data'] as Map<String, dynamic>);

View File

@ -26,6 +26,11 @@ class VendorRepositoryImpl implements VendorRepository {
return safeApiCall(() => dataSource.getVendors(query));
}
@override
Future<Result<List<VendorModel>>> listVendorOptions() {
return safeApiCall(() => dataSource.listVendorOptions());
}
@override
Future<Result<ExportFileResult>> exportVendors(VendorListQuery query) {
return safeApiCall(() => dataSource.exportVendors(query));

View File

@ -5,6 +5,7 @@ import '../../../../shared/models/vendor_model.dart';
abstract class VendorRepository {
Future<Result<PaginatedResponse<VendorModel>>> getVendors(VendorListQuery query);
Future<Result<List<VendorModel>>> listVendorOptions();
Future<Result<ExportFileResult>> exportVendors(VendorListQuery query);
Future<Result<VendorModel>> getVendorById(String id);
Future<Result<VendorModel>> createVendor(Map<String, dynamic> data);

View File

@ -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,

View File

@ -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

View File

@ -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<String, dynamic> _$$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,

View File

@ -38,6 +38,17 @@ Object? _readNestedName(Map<dynamic, dynamic> json, String flatKey, String neste
Object? _readVendorName(Map<dynamic, dynamic> json, String key) =>
_readNestedName(json, 'vendor_name', 'vendor');
Object? _readVendorType(Map<dynamic, dynamic> 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<dynamic, dynamic> 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(' ', '_');

View File

@ -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;

View File

@ -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<String, dynamic> _$$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(),

View File

@ -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<void> _loadOptions() async {
final options = <String, List<Map<String, dynamic>>>{};
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) => <String, dynamic>{
'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<String, dynamic> _buildPayload() {
final payload = <String, dynamic>{};
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) => <String, dynamic>{'id': v, 'name': v})
.toList();
}
final lookupKey = masterFieldDropdownLookupKey(
masterId: widget.masterId,
field: field,
values: _values,
);
var options =
_dropdownOptions[field.optionsMasterKey] ?? const <Map<String, dynamic>>[];
_dropdownOptions[lookupKey] ?? const <Map<String, dynamic>>[];
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),