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'; '/masters/item-subcategories/$id';
static const String items = '/masters/items'; static const String items = '/masters/items';
static String itemById(String id) => '/masters/items/$id'; 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 const String documentSeries = '/masters/document-series';
static String documentSeriesById(String id) => '/masters/document-series/$id'; static String documentSeriesById(String id) => '/masters/document-series/$id';
static const String deliveryTerms = '/masters/delivery-terms'; static const String deliveryTerms = '/masters/delivery-terms';

View File

@ -104,10 +104,20 @@ class AssetRemoteDataSource {
return _parseList(response.data, AssetTransferHistoryModel.fromJson); return _parseList(response.data, AssetTransferHistoryModel.fromJson);
} }
Future<List<AssetCategoryModel>> getCategories() async { Future<List<AssetCategoryModel>> getCategories({
bool dropdownCall = false,
}) async {
final response = await dio.get( final response = await dio.get(
ApiEndpoints.itemCategories, 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) return _parseList(response.data, AssetCategoryModel.fromJson)
.where((category) => category.isActive) .where((category) => category.isActive)

View File

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

View File

@ -13,7 +13,9 @@ abstract class AssetRepository {
Future<Result<void>> deleteAsset(String id); Future<Result<void>> deleteAsset(String id);
Future<Result<void>> transferAsset(String id, Map<String, dynamic> data); Future<Result<void>> transferAsset(String id, Map<String, dynamic> data);
Future<Result<List<AssetTransferHistoryModel>>> getTransferHistory(String assetId); 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<AssetDropdownOptionsModel>> getAssetOptions();
Future<Result<List<AssetDropdownOption>>> getContractTypes(); Future<Result<List<AssetDropdownOption>>> getContractTypes();
Future<Result<List<AssetDropdownOption>>> getVisitTypes(); 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 '../../data/repositories/asset_repository_impl.dart';
import '../../../../shared/models/asset_model.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 itemCategoriesProvider = FutureProvider<List<AssetCategoryModel>>((ref) async {
final repository = ref.watch(assetRepositoryProvider); final repository = ref.watch(assetRepositoryProvider);
final result = await repository.getCategories(); final result = await repository.getCategories();
@ -11,5 +11,14 @@ final itemCategoriesProvider = FutureProvider<List<AssetCategoryModel>>((ref) as
return result.data ?? []; 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') @Deprecated('Use itemCategoriesProvider')
final assetCategoriesProvider = itemCategoriesProvider; final assetCategoriesProvider = itemCategoriesProvider;

View File

@ -1,19 +1,15 @@
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/constants/app_constants.dart';
import '../../../../core/network/api_handler.dart'; import '../../../../core/network/api_handler.dart';
import '../../../../core/utils/active_option.dart'; import '../../../../core/utils/active_option.dart';
import '../../../../shared/models/asset_model.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/user_management_models.dart';
import '../../../../shared/models/vendor_model.dart';
import '../../../../shared/widgets/app_dropdown.dart'; import '../../../../shared/widgets/app_dropdown.dart';
import '../../../assets/data/repositories/asset_repository_impl.dart'; import '../../../assets/data/repositories/asset_repository_impl.dart';
import '../../../grn/data/repositories/grn_repository_impl.dart'; import '../../../grn/data/repositories/grn_repository_impl.dart';
import '../../../masters/data/datasources/master_remote_data_source.dart'; import '../../../masters/data/datasources/master_remote_data_source.dart';
import '../../../purchase_orders/data/repositories/purchase_order_repository_impl.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'; import '../../../vendors/data/repositories/vendor_repository_impl.dart';
class AssetFormLookups { class AssetFormLookups {
@ -117,41 +113,24 @@ Future<List<FilterOptionModel>> _safeOptions(
Future<List<FilterOptionModel>> _safeVendorOptions(Ref ref) async { Future<List<FilterOptionModel>> _safeVendorOptions(Ref ref) async {
try { try {
final vendors = <FilterOptionModel>[]; final result =
var page = 1; await ref.read(vendorRepositoryProvider).listVendorOptions();
var totalPages = 1; if (result.failure != null || result.data == null) return const [];
while (page <= totalPages) { return result.data!
final result = await ref.read(vendorRepositoryProvider).getVendors( .where(
VendorListQuery( (vendor) => isActiveVendorOption(
page: page, isActive: vendor.isActive,
limit: AppConstants.maxPageSize, status: vendor.status,
isActive: true, ),
), )
); .map(
if (result.failure != null || result.data == null) return vendors; (vendor) => FilterOptionModel(
id: vendor.id,
final data = result.data!; name: vendor.vendorName,
vendors.addAll( ),
data.items )
.where( .toList();
(vendor) => isActiveVendorOption(
isActive: vendor.isActive,
status: vendor.status,
),
)
.map(
(vendor) => FilterOptionModel(
id: vendor.id,
name: vendor.vendorName,
),
),
);
totalPages = data.totalPages;
page++;
}
return vendors;
} catch (_) { } catch (_) {
return const []; return const [];
} }
@ -159,16 +138,9 @@ Future<List<FilterOptionModel>> _safeVendorOptions(Ref ref) async {
Future<List<FilterOptionModel>> _safeUserOptions(Ref ref) async { Future<List<FilterOptionModel>> _safeUserOptions(Ref ref) async {
try { try {
final result = await ref.read(getUsersUseCaseProvider)( final result = await ref.read(userRepositoryProvider).listUserOptions();
const UserListQuery(
page: 1,
limit: AppConstants.maxPageSize,
status: 'active',
isActive: true,
),
);
if (result.failure != null || result.data == null) return const []; if (result.failure != null || result.data == null) return const [];
return result.data!.items return result.data!
.where( .where(
(user) => isActiveUserOption( (user) => isActiveUserOption(
status: user.status, status: user.status,
@ -189,15 +161,11 @@ Future<List<FilterOptionModel>> _safeUserOptions(Ref ref) async {
Future<List<FilterOptionModel>> _safePurchaseOrderOptions(Ref ref) async { Future<List<FilterOptionModel>> _safePurchaseOrderOptions(Ref ref) async {
try { try {
final result = final result = await ref
await ref.read(purchaseOrderRepositoryProvider).getPurchaseOrders( .read(purchaseOrderRepositoryProvider)
const PurchaseOrderListQuery( .listPurchaseOrderOptions();
page: 1,
limit: AppConstants.maxPageSize,
),
);
if (result.failure != null || result.data == null) return const []; if (result.failure != null || result.data == null) return const [];
return result.data!.items return result.data!
.map( .map(
(po) => FilterOptionModel( (po) => FilterOptionModel(
id: po.id, id: po.id,
@ -212,11 +180,9 @@ Future<List<FilterOptionModel>> _safePurchaseOrderOptions(Ref ref) async {
Future<List<FilterOptionModel>> _safeGrnOptions(Ref ref) async { Future<List<FilterOptionModel>> _safeGrnOptions(Ref ref) async {
try { try {
final result = await ref.read(grnRepositoryProvider).getGrns( final result = await ref.read(grnRepositoryProvider).listGrnOptions();
const GrnListQuery(page: 1, limit: AppConstants.maxPageSize),
);
if (result.failure != null || result.data == null) return const []; if (result.failure != null || result.data == null) return const [];
return result.data!.items return result.data!
.map( .map(
(grn) => FilterOptionModel( (grn) => FilterOptionModel(
id: grn.id, id: grn.id,

View File

@ -466,7 +466,7 @@ class _AssetFormPanelState extends ConsumerState<AssetFormPanel> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final categoriesAsync = ref.watch(itemCategoriesProvider); final categoriesAsync = ref.watch(itemCategoriesFormProvider);
final plantsAsync = ref.watch(assetPlantsProvider); final plantsAsync = ref.watch(assetPlantsProvider);
if (widget.isEditing) { if (widget.isEditing) {
@ -1091,6 +1091,7 @@ class _AssetFormPanelState extends ConsumerState<AssetFormPanel> {
value: _dropdownValue(_categoryId, categoryIds), value: _dropdownValue(_categoryId, categoryIds),
searchHint: 'Search category...', searchHint: 'Search category...',
isDense: true, isDense: true,
initialValues: const {'category_type': 'ASSET'},
options: activeCategories options: activeCategories
.map( .map(
(c) => AppDropdownOption( (c) => AppDropdownOption(
@ -1101,7 +1102,7 @@ class _AssetFormPanelState extends ConsumerState<AssetFormPanel> {
.where((option) => option.value != 0) .where((option) => option.value != 0)
.toList(), .toList(),
refreshLookups: () { refreshLookups: () {
ref.invalidate(itemCategoriesProvider); ref.invalidate(itemCategoriesFormProvider);
}, },
parseCreatedId: int.tryParse, parseCreatedId: int.tryParse,
onChanged: (v) => setState(() { onChanged: (v) => setState(() {

View File

@ -19,6 +19,26 @@ class GrnRemoteDataSource {
return _parsePaginated(response.data, GrnModel.fromJson); 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 { Future<GrnModel> getGrnById(String id) async {
final response = await dio.get(ApiEndpoints.grnById(id)); final response = await dio.get(ApiEndpoints.grnById(id));
return GrnModel.fromJson(response.data['data'] as Map<String, dynamic>); 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)); return safeApiCall(() => dataSource.getGrns(query));
} }
@override
Future<Result<List<GrnModel>>> listGrnOptions() {
return safeApiCall(() => dataSource.listGrnOptions());
}
@override @override
Future<Result<ExportFileResult>> exportGrns(GrnListQuery query) { Future<Result<ExportFileResult>> exportGrns(GrnListQuery query) {
return safeApiCall(() => dataSource.exportGrns(query)); return safeApiCall(() => dataSource.exportGrns(query));

View File

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

View File

@ -1,12 +1,11 @@
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/constants/app_constants.dart';
import '../../../../core/utils/active_option.dart'; import '../../../../core/utils/active_option.dart';
import '../../../../shared/models/purchase_order_model.dart'; import '../../../../shared/models/purchase_order_model.dart';
import '../../../../shared/models/user_management_models.dart'; import '../../../../shared/models/user_management_models.dart';
import '../../../masters/data/datasources/master_remote_data_source.dart'; import '../../../masters/data/datasources/master_remote_data_source.dart';
import '../../../purchase_orders/data/repositories/purchase_order_repository_impl.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 { class GrnLookups {
const GrnLookups({ const GrnLookups({
@ -29,15 +28,9 @@ final grnLookupsProvider = FutureProvider.autoDispose<GrnLookups>((ref) async {
final receivablePos = <PurchaseOrderModel>[]; final receivablePos = <PurchaseOrderModel>[];
for (final status in ['APPROVED', 'PARTIALLY_RECEIVED']) { for (final status in ['APPROVED', 'PARTIALLY_RECEIVED']) {
final result = await poRepo.getPurchaseOrders( final result = await poRepo.listPurchaseOrderOptions(status: status);
PurchaseOrderListQuery(
page: 1,
limit: AppConstants.maxPageSize,
status: status,
),
);
if (result.failure == null && result.data != null) { 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 { Future<List<FilterOptionModel>> _safeUserOptions(Ref ref) async {
try { try {
final result = await ref.read(getUsersUseCaseProvider)( final result = await ref.read(userRepositoryProvider).listUserOptions();
const UserListQuery(
page: 1,
limit: AppConstants.maxPageSize,
status: 'active',
isActive: true,
),
);
if (result.failure != null || result.data == null) return const []; if (result.failure != null || result.data == null) return const [];
return result.data!.items return result.data!
.where( .where(
(user) => isActiveUserOption( (user) => isActiveUserOption(
status: user.status, status: user.status,

View File

@ -1,7 +1,6 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/constants/app_constants.dart';
import '../../../../core/network/dio_client.dart'; import '../../../../core/network/dio_client.dart';
import '../../../../core/utils/active_option.dart'; import '../../../../core/utils/active_option.dart';
import '../../../../shared/models/export_file_result.dart'; import '../../../../shared/models/export_file_result.dart';
@ -43,6 +42,7 @@ class MasterCrudRemoteDataSource {
int limit = 20, int limit = 20,
String? search, String? search,
bool? isActive, bool? isActive,
Map<String, dynamic>? extraQueryParameters,
}) async { }) async {
final response = await dio.get( final response = await dio.get(
definition.apiPath, definition.apiPath,
@ -51,6 +51,7 @@ class MasterCrudRemoteDataSource {
'limit': limit, 'limit': limit,
if (search != null && search.isNotEmpty) 'search': search, if (search != null && search.isNotEmpty) 'search': search,
if (isActive != null) 'is_active': isActive, if (isActive != null) 'is_active': isActive,
...?extraQueryParameters,
}, },
); );
@ -80,21 +81,31 @@ class MasterCrudRemoteDataSource {
); );
} }
Future<List<Map<String, dynamic>>> listOptions(MasterDefinition definition) async { Future<List<Map<String, dynamic>>> listOptions(
final allItems = <Map<String, dynamic>>[]; MasterDefinition definition, {
var page = 1; Map<String, dynamic>? queryParameters,
while (true) { }) async {
final result = await list( final response = await dio.get(
definition, definition.apiPath,
page: page, queryParameters: {
limit: AppConstants.defaultPageSize, 'dropdown_call': true,
isActive: true, ...?queryParameters,
); },
allItems.addAll(result.items.where(isActiveOptionRow)); );
if (page >= result.totalPages) break;
page++; final body = response.data as Map<String, dynamic>;
} final raw = body['data'];
return allItems; 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( Future<Map<String, dynamic>> getById(

View File

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

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
enum MasterFieldType { text, number, boolean, dropdown } enum MasterFieldType { text, number, boolean, dropdown }
const brandTypes = ['OWN', 'OEM', 'THIRD_PARTY']; const categoryTypeOptions = ['STOCK', 'ASSET'];
class MasterFieldDef { class MasterFieldDef {
const MasterFieldDef({ const MasterFieldDef({
@ -12,11 +12,15 @@ class MasterFieldDef {
this.required = false, this.required = false,
this.showInList = false, this.showInList = false,
this.showInForm = true, this.showInForm = true,
this.readOnly = false,
this.optionsMasterKey, this.optionsMasterKey,
this.optionsQueryParams,
this.staticOptions, this.staticOptions,
this.multiline = false, this.multiline = false,
this.filterByFieldKey, this.filterByFieldKey,
this.filterByOptionKey, this.filterByOptionKey,
this.visibleWhenFieldKey,
this.visibleWhenValue,
}); });
final String key; final String key;
@ -26,15 +30,40 @@ class MasterFieldDef {
final bool showInList; final bool showInList;
/// When false, field is list/display-only and excluded from create/update payloads. /// When false, field is list/display-only and excluded from create/update payloads.
final bool showInForm; 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). /// Master key used to populate dropdown options (e.g. `plants` for plant_id).
final String? optionsMasterKey; 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 List<String>? staticOptions;
final bool multiline; final bool multiline;
/// Form field whose value filters this dropdown (e.g. `item_category_id`). /// Form field whose value filters this dropdown (e.g. `item_category_id`).
final String? filterByFieldKey; final String? filterByFieldKey;
/// Option-row key matched against [filterByFieldKey] (defaults to same key). /// Option-row key matched against [filterByFieldKey] (defaults to same key).
final String? filterByOptionKey; 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 { class MasterDefinition {
@ -119,11 +148,27 @@ const masterDefinitions = <MasterDefinition>[
fields: [ fields: [
MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true), MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true),
MasterFieldDef(key: 'name', label: 'Name', 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( MasterFieldDef(
key: 'default_useful_life_years', key: 'default_useful_life_years',
label: 'Useful Life (Years)', label: 'Useful Life (Years)',
type: MasterFieldType.number, type: MasterFieldType.number,
visibleWhenFieldKey: 'category_type',
visibleWhenValue: 'ASSET',
), ),
MasterFieldDef( MasterFieldDef(
key: 'default_depreciation_method', key: 'default_depreciation_method',
@ -131,6 +176,8 @@ const masterDefinitions = <MasterDefinition>[
type: MasterFieldType.dropdown, type: MasterFieldType.dropdown,
showInList: true, showInList: true,
optionsMasterKey: 'asset_depreciation_methods', optionsMasterKey: 'asset_depreciation_methods',
visibleWhenFieldKey: 'category_type',
visibleWhenValue: 'ASSET',
), ),
_activeField, _activeField,
], ],
@ -175,6 +222,12 @@ const masterDefinitions = <MasterDefinition>[
showInList: true, showInList: true,
showInForm: false, 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_name', label: 'Item Name', required: true, showInList: true),
MasterFieldDef( MasterFieldDef(
key: 'item_category_id', key: 'item_category_id',
@ -182,6 +235,7 @@ const masterDefinitions = <MasterDefinition>[
type: MasterFieldType.dropdown, type: MasterFieldType.dropdown,
required: true, required: true,
optionsMasterKey: 'item_categories', optionsMasterKey: 'item_categories',
// Resolved at runtime from is_asset_item (STOCK / ASSET).
), ),
MasterFieldDef( MasterFieldDef(
key: 'item_subcategory_id', key: 'item_subcategory_id',
@ -208,19 +262,9 @@ const masterDefinitions = <MasterDefinition>[
key: 'gst_rate_id', key: 'gst_rate_id',
label: 'GST Rate', label: 'GST Rate',
type: MasterFieldType.dropdown, type: MasterFieldType.dropdown,
optionsMasterKey: 'gst_rates', showInList: true,
), readOnly: true,
MasterFieldDef( // Filled from selected HSN's nested gst_rate — no gst-rates API.
key: 'brand_id',
label: 'Brand',
type: MasterFieldType.dropdown,
optionsMasterKey: 'brands',
),
MasterFieldDef(
key: 'is_asset_item',
label: 'Asset Item',
type: MasterFieldType.boolean,
required: true,
), ),
MasterFieldDef( MasterFieldDef(
key: 'min_order_qty', key: 'min_order_qty',
@ -257,32 +301,13 @@ const masterDefinitions = <MasterDefinition>[
showInList: true, showInList: true,
multiline: 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( MasterFieldDef(
key: 'brand_type', key: 'gst_rate_id',
label: 'Brand Type', label: 'GST Rate',
type: MasterFieldType.dropdown, type: MasterFieldType.dropdown,
required: true,
showInList: 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, _activeField,
], ],
), ),
@ -504,6 +529,21 @@ String masterCellValue(Map<String, dynamic> row, MasterFieldDef field) {
final label = row['${field.key}_label']; final label = row['${field.key}_label'];
if (label != null && label.toString().isNotEmpty) return label.toString(); 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 // 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) // (e.g. asset_category_id -> asset_category_name; hsn_code_id -> hsn_code)
if (field.key.endsWith('_id')) { if (field.key.endsWith('_id')) {
@ -540,3 +580,61 @@ String masterStatusValue(Map<String, dynamic> row) {
if (active == false) return 'inactive'; if (active == false) return 'inactive';
return 'active'; 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, 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( Future<Result<Map<String, dynamic>>> getById(
MasterDefinition definition, MasterDefinition definition,

View File

@ -250,7 +250,6 @@ class MasterFormNotifier extends FamilyAsyncNotifier<MasterFormState, MasterForm
@override @override
Future<MasterFormState> build(MasterFormArgs arg) async { Future<MasterFormState> build(MasterFormArgs arg) async {
final dropdownOptions = await _loadDropdownOptions();
final existingRecords = await _loadExistingRecords(); final existingRecords = await _loadExistingRecords();
Map<String, dynamic> values = {}; 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( return MasterFormState(
values: values, values: values,
dropdownOptions: dropdownOptions, dropdownOptions: dropdownOptions,
@ -301,18 +312,25 @@ class MasterFormNotifier extends FamilyAsyncNotifier<MasterFormState, MasterForm
} }
Future<Map<String, List<Map<String, dynamic>>>> 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 options = <String, List<Map<String, dynamic>>>{};
final keys = _definition.formFields final fields = _definition.formFields
.where((field) => field.optionsMasterKey != null) .where((field) => field.optionsMasterKey != null);
.map((field) => field.optionsMasterKey!)
.toSet();
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') { if (key == 'asset_depreciation_methods') {
final result = await ref.read(assetRepositoryProvider).getDepreciationMethods(); final result = await ref.read(assetRepositoryProvider).getDepreciationMethods();
if (result.failure != null) continue; if (result.failure != null) continue;
options[key] = (result.data ?? const []) options[lookupKey] = (result.data ?? const [])
.where((option) => option.value.trim().isNotEmpty) .where((option) => option.value.trim().isNotEmpty)
.map( .map(
(option) => <String, dynamic>{ (option) => <String, dynamic>{
@ -326,19 +344,93 @@ class MasterFormNotifier extends FamilyAsyncNotifier<MasterFormState, MasterForm
final def = masterDefinitionById(key); final def = masterDefinitionById(key);
if (def == null) continue; 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; if (result.failure != null) continue;
options[key] = result.data ?? const []; options[lookupKey] = result.data ?? const [];
} }
return options; 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) { void updateValue(String key, dynamic value) {
final current = state.valueOrNull; final current = state.valueOrNull;
if (current == null) return; if (current == null) return;
final values = Map<String, dynamic>.from(current.values); final values = Map<String, dynamic>.from(current.values);
values[key] = value; 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 // Clear dependent dropdowns when their parent filter value changes
// (e.g. item_category_id item_subcategory_id). // (e.g. item_category_id item_subcategory_id).
for (final field in _definition.formFields) { for (final field in _definition.formFields) {
@ -347,8 +439,12 @@ class MasterFormNotifier extends FamilyAsyncNotifier<MasterFormState, MasterForm
if (dependentValue == null || dependentValue == '') continue; if (dependentValue == null || dependentValue == '') continue;
final optionKey = field.filterByOptionKey ?? field.filterByFieldKey!; final optionKey = field.filterByOptionKey ?? field.filterByFieldKey!;
final options = final lookupKey = masterFieldDropdownLookupKey(
current.dropdownOptions[field.optionsMasterKey] ?? const []; masterId: _definition.id,
field: field,
values: values,
);
final options = current.dropdownOptions[lookupKey] ?? const [];
final stillValid = options.any( final stillValid = options.any(
(item) => (item) =>
item['id']?.toString() == dependentValue.toString() && item['id']?.toString() == dependentValue.toString() &&
@ -362,22 +458,35 @@ class MasterFormNotifier extends FamilyAsyncNotifier<MasterFormState, MasterForm
state = AsyncData(current.copyWith(values: values)); 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 { Future<void> reloadDropdownOptions() async {
final current = state.valueOrNull; final current = state.valueOrNull;
if (current == null) return; if (current == null) return;
final options = await _loadDropdownOptions(); final options = await _loadDropdownOptions(values: current.values);
state = AsyncData(current.copyWith(dropdownOptions: options)); state = AsyncData(current.copyWith(dropdownOptions: options));
} }
Map<String, dynamic> _buildPayload(MasterFormState current) { Map<String, dynamic> _buildPayload(MasterFormState current) {
final payload = <String, dynamic>{}; final payload = <String, dynamic>{};
for (final field in _definition.formFields) { for (final field in _definition.formFields) {
if (!field.isVisibleInForm(current.values)) continue;
final value = current.values[field.key]; final value = current.values[field.key];
if (value == null || value == '') continue; if (value == null || value == '') continue;
payload[field.key] = switch (field.type) { payload[field.key] = switch (field.type) {
MasterFieldType.number => num.tryParse(value.toString()) ?? value, 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.boolean => value == true,
MasterFieldType.text => value.toString().trim(), MasterFieldType.text => value.toString().trim(),
}; };

View File

@ -119,20 +119,74 @@ class _MasterFormPanelState extends ConsumerState<MasterFormPanel> {
onChanged: (checked) => notifier.updateValue(field.key, checked), onChanged: (checked) => notifier.updateValue(field.key, checked),
); );
} }
return CheckboxListTile( return InkWell(
contentPadding: EdgeInsets.zero, onTap: () => notifier.updateValue(field.key, value != true),
title: Text(field.label), borderRadius: BorderRadius.circular(8),
value: value == true, child: Padding(
onChanged: (checked) => padding: const EdgeInsets.symmetric(vertical: 4),
notifier.updateValue(field.key, checked ?? false), 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: 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; final List<AppDropdownOption<String>> dropdownOptions;
if (field.staticOptions != null) { if (field.staticOptions != null) {
dropdownOptions = stringDropdownOptions(field.staticOptions!); dropdownOptions = stringDropdownOptions(field.staticOptions!);
} else { } 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>>[]; const <Map<String, dynamic>>[];
final filterField = field.filterByFieldKey; final filterField = field.filterByFieldKey;
if (filterField != null) { if (filterField != null) {
@ -178,7 +232,9 @@ class _MasterFormPanelState extends ConsumerState<MasterFormPanel> {
if (canQuickAdd) { if (canQuickAdd) {
return MasterQuickAddDropdown<String>( return MasterQuickAddDropdown<String>(
key: ValueKey( 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, masterId: optionsMasterKey,
label: _fieldLabel(field), label: _fieldLabel(field),
@ -191,9 +247,19 @@ class _MasterFormPanelState extends ConsumerState<MasterFormPanel> {
: 'Select ${field.label.toLowerCase()}', : 'Select ${field.label.toLowerCase()}',
searchHint: 'Search ${field.label.toLowerCase()}...', searchHint: 'Search ${field.label.toLowerCase()}...',
enabled: parentSelected, enabled: parentSelected,
initialValues: filterField == null initialValues: () {
? null final values = <String, dynamic>{};
: {filterField: formState.values[filterField]}, 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: () { refreshLookups: () {
ref ref
.read(masterFormProvider(_args).notifier) .read(masterFormProvider(_args).notifier)
@ -323,8 +389,10 @@ class _MasterFormPanelState extends ConsumerState<MasterFormPanel> {
MasterFormState formState, MasterFormState formState,
) { ) {
final def = _definition; final def = _definition;
final regularFields = final regularFields = def.formFields
def.formFields.where((field) => field.key != 'is_active').toList(); .where((field) => field.key != 'is_active')
.where((field) => field.isVisibleInForm(formState.values))
.toList();
MasterFieldDef? activeField; MasterFieldDef? activeField;
for (final field in def.formFields) { for (final field in def.formFields) {
if (field.key == 'is_active') { if (field.key == 'is_active') {
@ -339,8 +407,9 @@ class _MasterFormPanelState extends ConsumerState<MasterFormPanel> {
while (i < regularFields.length) { while (i < regularFields.length) {
final left = regularFields[i]; final left = regularFields[i];
// Multiline fields (e.g. HSN Description) always take a full row. // Multiline / Asset Item take a full row.
if (left.multiline) { final fullWidth = left.multiline || left.key == 'is_asset_item';
if (fullWidth) {
widgets.add( widgets.add(
Padding( Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
@ -354,8 +423,9 @@ class _MasterFormPanelState extends ConsumerState<MasterFormPanel> {
continue; continue;
} }
final hasRight = final hasRight = i + 1 < regularFields.length &&
i + 1 < regularFields.length && !regularFields[i + 1].multiline; !regularFields[i + 1].multiline &&
regularFields[i + 1].key != 'is_asset_item';
if (hasRight) { if (hasRight) {
final right = regularFields[i + 1]; final right = regularFields[i + 1];
widgets.add( widgets.add(

View File

@ -124,7 +124,12 @@ class _MasterInlineCreateFormState
if (field.staticOptions != null) { if (field.staticOptions != null) {
dropdownOptions = stringDropdownOptions(field.staticOptions!); dropdownOptions = stringDropdownOptions(field.staticOptions!);
} else { } 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>>[]; const <Map<String, dynamic>>[];
final filterField = field.filterByFieldKey; final filterField = field.filterByFieldKey;
if (filterField != null) { if (filterField != null) {
@ -444,6 +449,7 @@ class _MasterInlineCreateFormState
data: (formState) { data: (formState) {
final fields = def.formFields final fields = def.formFields
.where((f) => f.key != 'is_active') .where((f) => f.key != 'is_active')
.where((f) => f.isVisibleInForm(formState.values))
.toList(); .toList();
final active = def.formFields final active = def.formFields
.where((f) => f.key == 'is_active') .where((f) => f.key == 'is_active')

View File

@ -224,7 +224,6 @@ String masterQuickAddNoun(String masterId) {
'item_subcategories' => 'subcategory', 'item_subcategories' => 'subcategory',
'items' => 'item', 'items' => 'item',
'hsn_codes' => 'HSN code', 'hsn_codes' => 'HSN code',
'brands' => 'brand',
'plants' => 'plant', 'plants' => 'plant',
'warehouses' => 'warehouse', 'warehouses' => 'warehouse',
'designations' => 'designation', 'designations' => 'designation',

View File

@ -35,9 +35,6 @@ class MasterRemoteDataSource {
Future<List<FilterOptionModel>> listWarehouses() => Future<List<FilterOptionModel>> listWarehouses() =>
_listOptions(ApiEndpoints.warehouses); _listOptions(ApiEndpoints.warehouses);
Future<List<FilterOptionModel>> listBrands() =>
_listOptions(ApiEndpoints.brands);
Future<List<FilterOptionModel>> listUom() => _listOptions(ApiEndpoints.uom); Future<List<FilterOptionModel>> listUom() => _listOptions(ApiEndpoints.uom);
Future<List<FilterOptionModel>> listItems() => _listOptions(ApiEndpoints.items); Future<List<FilterOptionModel>> listItems() => _listOptions(ApiEndpoints.items);
@ -48,6 +45,32 @@ class MasterRemoteDataSource {
Future<List<FilterOptionModel>> listHsnCodes() => Future<List<FilterOptionModel>> listHsnCodes() =>
_listOptions(ApiEndpoints.hsnCodes); _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. /// Active items with default HSN / UOM / GST for PO line autofill.
Future< Future<
({ ({
@ -56,10 +79,7 @@ class MasterRemoteDataSource {
Map<String, int?> uomByItemId, Map<String, int?> uomByItemId,
Map<String, int?> gstRateByItemId, Map<String, int?> gstRateByItemId,
})> listItemsWithHsn() async { })> listItemsWithHsn() async {
final rows = await _listAllMaps( final rows = await _listAllMaps(ApiEndpoints.items);
ApiEndpoints.items,
queryParameters: {'is_active': true},
);
final hsnByItemId = <String, int?>{}; final hsnByItemId = <String, int?>{};
final uomByItemId = <String, int?>{}; final uomByItemId = <String, int?>{};
final gstRateByItemId = <String, int?>{}; final gstRateByItemId = <String, int?>{};
@ -97,10 +117,7 @@ class MasterRemoteDataSource {
/// GST rate options with numeric `rate_pct` for tax calculations. /// GST rate options with numeric `rate_pct` for tax calculations.
Future<({List<FilterOptionModel> options, Map<String, double> pctById})> Future<({List<FilterOptionModel> options, Map<String, double> pctById})>
listGstRatesWithPct() async { listGstRatesWithPct() async {
final rows = await _listAllMaps( final rows = await _listAllMaps(ApiEndpoints.gstRates);
ApiEndpoints.gstRates,
queryParameters: {'is_active': true},
);
final options = <FilterOptionModel>[]; final options = <FilterOptionModel>[];
final pctById = <String, double>{}; final pctById = <String, double>{};
@ -123,14 +140,29 @@ class MasterRemoteDataSource {
return (options: options, pctById: pctById); return (options: options, pctById: pctById);
} }
Future<List<FilterOptionModel>> listItemCategories() => Future<List<FilterOptionModel>> listItemCategories({String? categoryType}) async {
_listOptions(ApiEndpoints.itemCategories); 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 { Future<List<FilterOptionModel>> listItemSubcategories({int? itemCategoryId}) async {
final rows = await _listAllMaps( final rows = await _listAllMaps(
ApiEndpoints.itemSubcategories, ApiEndpoints.itemSubcategories,
queryParameters: { queryParameters: {
'is_active': true,
if (itemCategoryId != null) 'item_category_id': itemCategoryId, if (itemCategoryId != null) 'item_category_id': itemCategoryId,
}, },
); );
@ -153,10 +185,7 @@ class MasterRemoteDataSource {
} }
Future<List<FilterOptionModel>> _listOptions(String endpoint) async { Future<List<FilterOptionModel>> _listOptions(String endpoint) async {
final rows = await _listAllMaps( final rows = await _listAllMaps(endpoint);
endpoint,
queryParameters: {'is_active': true},
);
return rows return rows
.where(isActiveOptionRow) .where(isActiveOptionRow)
.map( .map(
@ -169,33 +198,23 @@ class MasterRemoteDataSource {
.toList(); .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( Future<List<Map<String, dynamic>>> _listAllMaps(
String endpoint, { String endpoint, {
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
}) async { }) async {
final all = <Map<String, dynamic>>[]; final response = await dio.get(
var page = 1; endpoint,
var totalPages = 1; queryParameters: {
final pageSize = AppConstants.defaultPageSize; 'dropdown_call': true,
...?queryParameters,
while (page <= totalPages) { },
final response = await dio.get( );
endpoint, final parsed = _parsePage(
queryParameters: { response.data,
'page': page, fallbackLimit: AppConstants.defaultPageSize,
'limit': pageSize, );
...?queryParameters, return parsed.items;
},
);
final parsed = _parsePage(response.data, fallbackLimit: pageSize);
all.addAll(parsed.items);
totalPages = parsed.totalPages;
if (parsed.items.isEmpty) break;
page++;
}
return all;
} }
({List<Map<String, dynamic>> items, int totalPages}) _parsePage( ({List<Map<String, dynamic>> items, int totalPages}) _parsePage(

View File

@ -22,6 +22,33 @@ class PurchaseOrderRemoteDataSource {
return _parsePaginated(response.data, PurchaseOrderModel.fromJson); 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 { Future<PurchaseOrderModel> getPurchaseOrderById(String id) async {
final response = await dio.get(ApiEndpoints.purchaseOrderById(id)); final response = await dio.get(ApiEndpoints.purchaseOrderById(id));
final raw = response.data['data']; final raw = response.data['data'];
@ -204,7 +231,6 @@ class PurchaseOrderRemoteDataSource {
return { return {
if (query.search != null && query.search!.isNotEmpty) 'search': query.search, if (query.search != null && query.search!.isNotEmpty) 'search': query.search,
if (query.status != null) 'status': query.status, 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.vendorId != null) 'vendor_id': query.vendorId,
if (query.plantId != null) 'plant_id': query.plantId, if (query.plantId != null) 'plant_id': query.plantId,
if (query.dateFrom != null) 'date_from': query.dateFrom, if (query.dateFrom != null) 'date_from': query.dateFrom,

View File

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

View File

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

View File

@ -1,9 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/constants/app_constants.dart';
import '../../../../core/utils/active_option.dart'; import '../../../../core/utils/active_option.dart';
import '../../../../shared/models/user_management_models.dart'; import '../../../../shared/models/user_management_models.dart';
import '../../../../shared/models/vendor_model.dart';
import '../../../masters/data/datasources/master_remote_data_source.dart'; import '../../../masters/data/datasources/master_remote_data_source.dart';
import '../../../vendors/data/repositories/vendor_repository_impl.dart'; import '../../../vendors/data/repositories/vendor_repository_impl.dart';
import '../../../vendors/domain/repositories/vendor_repository.dart'; import '../../../vendors/domain/repositories/vendor_repository.dart';
@ -13,7 +11,6 @@ class PurchaseOrderLookups {
this.vendors = const [], this.vendors = const [],
this.plants = const [], this.plants = const [],
this.warehouses = const [], this.warehouses = const [],
this.brands = const [],
this.paymentTerms = const [], this.paymentTerms = const [],
this.deliveryTerms = const [], this.deliveryTerms = const [],
this.items = const [], this.items = const [],
@ -24,12 +21,12 @@ class PurchaseOrderLookups {
this.gstRates = const [], this.gstRates = const [],
this.gstRatePctById = const {}, this.gstRatePctById = const {},
this.hsnCodes = const [], this.hsnCodes = const [],
this.hsnGstRateById = const {},
}); });
final List<FilterOptionModel> vendors; final List<FilterOptionModel> vendors;
final List<FilterOptionModel> plants; final List<FilterOptionModel> plants;
final List<FilterOptionModel> warehouses; final List<FilterOptionModel> warehouses;
final List<FilterOptionModel> brands;
final List<FilterOptionModel> paymentTerms; final List<FilterOptionModel> paymentTerms;
final List<FilterOptionModel> deliveryTerms; final List<FilterOptionModel> deliveryTerms;
final List<FilterOptionModel> items; final List<FilterOptionModel> items;
@ -44,6 +41,8 @@ class PurchaseOrderLookups {
/// GST rate id `rate_pct` for tax calculations. /// GST rate id `rate_pct` for tax calculations.
final Map<String, double> gstRatePctById; final Map<String, double> gstRatePctById;
final List<FilterOptionModel> hsnCodes; final List<FilterOptionModel> hsnCodes;
/// HSN code id default `gst_rate_id` from HSN master.
final Map<String, int?> hsnGstRateById;
} }
final purchaseOrderLookupsProvider = final purchaseOrderLookupsProvider =
@ -55,32 +54,31 @@ final purchaseOrderLookupsProvider =
final itemsWithDefaults = await _safeItemsWithDefaults(master.listItemsWithHsn); final itemsWithDefaults = await _safeItemsWithDefaults(master.listItemsWithHsn);
final gstWithPct = await _safeGstRatesWithPct(master.listGstRatesWithPct); final gstWithPct = await _safeGstRatesWithPct(master.listGstRatesWithPct);
final hsnWithGst = await _safeHsnWithGst(master.listHsnCodesWithGstRate);
final results = await Future.wait([ final results = await Future.wait([
_safeOptions(master.listPlants), _safeOptions(master.listPlants),
_safeOptions(master.listWarehouses), _safeOptions(master.listWarehouses),
_safeOptions(master.listBrands),
_safeOptions(master.listPaymentTerms), _safeOptions(master.listPaymentTerms),
_safeOptions(master.listDeliveryTerms), _safeOptions(master.listDeliveryTerms),
_safeOptions(master.listUom), _safeOptions(master.listUom),
_safeOptions(master.listHsnCodes),
]); ]);
return PurchaseOrderLookups( return PurchaseOrderLookups(
vendors: vendors, vendors: vendors,
plants: results[0], plants: results[0],
warehouses: results[1], warehouses: results[1],
brands: results[2], paymentTerms: results[2],
paymentTerms: results[3], deliveryTerms: results[3],
deliveryTerms: results[4],
items: itemsWithDefaults.options, items: itemsWithDefaults.options,
itemHsnById: itemsWithDefaults.hsnByItemId, itemHsnById: itemsWithDefaults.hsnByItemId,
itemUomById: itemsWithDefaults.uomByItemId, itemUomById: itemsWithDefaults.uomByItemId,
itemGstRateById: itemsWithDefaults.gstRateByItemId, itemGstRateById: itemsWithDefaults.gstRateByItemId,
uom: results[5], uom: results[4],
gstRates: gstWithPct.options, gstRates: gstWithPct.options,
gstRatePctById: gstWithPct.pctById, 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( Future<List<FilterOptionModel>> _fetchActiveVendors(
VendorRepository vendorRepo, VendorRepository vendorRepo,
) async { ) async {
final vendors = <FilterOptionModel>[]; final result = await vendorRepo.listVendorOptions();
var page = 1; if (result.failure != null) {
var totalPages = 1; throw result.failure!;
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++;
} }
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)); 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) { void setPage(int page) {
final current = state.valueOrNull; final current = state.valueOrNull;
if (current == null) return; if (current == null) return;

View File

@ -8,6 +8,7 @@ import '../../../../core/errors/failure.dart';
import '../../../../core/theme/app_colors.dart'; import '../../../../core/theme/app_colors.dart';
import '../../../../core/utils/formatters.dart'; import '../../../../core/utils/formatters.dart';
import '../../../../shared/models/purchase_order_model.dart'; import '../../../../shared/models/purchase_order_model.dart';
import '../../../../shared/models/vendor_model.dart';
import '../../../../shared/models/user_management_models.dart'; import '../../../../shared/models/user_management_models.dart';
import '../../../../shared/providers/permissions_provider.dart'; import '../../../../shared/providers/permissions_provider.dart';
import '../../../../shared/utils/file_download_helper.dart'; import '../../../../shared/utils/file_download_helper.dart';
@ -418,7 +419,7 @@ class _DetailHeader extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final subtitleParts = [ final subtitleParts = [
poTypeLabel(order.poType), vendorTypeLabel(order.vendorType),
if (order.vendorName?.trim().isNotEmpty == true) order.vendorName!.trim(), if (order.vendorName?.trim().isNotEmpty == true) order.vendorName!.trim(),
if (order.plantName?.trim().isNotEmpty == true) order.plantName!.trim(), if (order.plantName?.trim().isNotEmpty == true) order.plantName!.trim(),
]; ];
@ -702,7 +703,6 @@ class _OrderDetailsCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final brand = _lookupName(lookups?.brands, order.brandId);
final paymentTerm = final paymentTerm =
_lookupName(lookups?.paymentTerms, order.paymentTermId); _lookupName(lookups?.paymentTerms, order.paymentTermId);
final deliveryTerm = final deliveryTerm =
@ -733,8 +733,8 @@ class _OrderDetailsCard extends StatelessWidget {
value: _displayOrDash(order.vendorName), value: _displayOrDash(order.vendorName),
), ),
_DetailField( _DetailField(
label: 'PO Type', label: 'Vendor Type',
value: poTypeLabel(order.poType), value: vendorTypeLabel(order.vendorType),
), ),
_DetailField( _DetailField(
label: 'Plant', label: 'Plant',
@ -744,7 +744,6 @@ class _OrderDetailsCard extends StatelessWidget {
label: 'Warehouse', label: 'Warehouse',
value: _displayOrDash(order.warehouseName), value: _displayOrDash(order.warehouseName),
), ),
_DetailField(label: 'Brand', value: brand),
_DetailField(label: 'Payment Term', value: paymentTerm), _DetailField(label: 'Payment Term', value: paymentTerm),
_DetailField(label: 'Delivery Term', value: deliveryTerm), _DetailField(label: 'Delivery Term', value: deliveryTerm),
]; ];
@ -1275,7 +1274,7 @@ class _DetailFooter extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
Text( Text(
'${poTypeLabel(order.poType)} · ${poStatusLabel(order.status)}', '${vendorTypeLabel(order.vendorType)} · ${poStatusLabel(order.status)}',
style: style, style: style,
), ),
const Spacer(), const Spacer(),

View File

@ -50,11 +50,9 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
DateTime? _poDate; DateTime? _poDate;
DateTime? _expectedDeliveryDate; DateTime? _expectedDeliveryDate;
String? _poType;
int? _vendorId; int? _vendorId;
int? _plantId; int? _plantId;
int? _warehouseId; int? _warehouseId;
int? _brandId;
int? _paymentTermId; int? _paymentTermId;
int? _deliveryTermId; int? _deliveryTermId;
final List<PoLineItemDraft> _lines = []; final List<PoLineItemDraft> _lines = [];
@ -99,11 +97,9 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
setState(() { setState(() {
_poDate = order.poDate ?? DateTime.now(); _poDate = order.poDate ?? DateTime.now();
_expectedDeliveryDate = order.expectedDeliveryDate; _expectedDeliveryDate = order.expectedDeliveryDate;
_poType = order.poType;
_vendorId = order.vendorId; _vendorId = order.vendorId;
_plantId = order.plantId; _plantId = order.plantId;
_warehouseId = order.warehouseId; _warehouseId = order.warehouseId;
_brandId = order.brandId;
_paymentTermId = order.paymentTermId; _paymentTermId = order.paymentTermId;
_deliveryTermId = order.deliveryTermId; _deliveryTermId = order.deliveryTermId;
_discountController.text = _discountController.text =
@ -190,11 +186,9 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
Map<String, dynamic> _buildPayload() { Map<String, dynamic> _buildPayload() {
return { return {
'po_date': DateFormatter.toApiDate(_poDate ?? DateTime.now()), 'po_date': DateFormatter.toApiDate(_poDate ?? DateTime.now()),
'po_type': _poType,
'vendor_id': _vendorId, 'vendor_id': _vendorId,
'plant_id': _plantId, 'plant_id': _plantId,
if (_warehouseId != null) 'warehouse_id': _warehouseId, if (_warehouseId != null) 'warehouse_id': _warehouseId,
if (_brandId != null) 'brand_id': _brandId,
if (_paymentTermId != null) 'payment_term_id': _paymentTermId, if (_paymentTermId != null) 'payment_term_id': _paymentTermId,
if (_deliveryTermId != null) 'delivery_term_id': _deliveryTermId, if (_deliveryTermId != null) 'delivery_term_id': _deliveryTermId,
if (_expectedDeliveryDate != null) if (_expectedDeliveryDate != null)
@ -244,7 +238,7 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
return; return;
} }
if (_poType == null || _vendorId == null || _plantId == null) { if (_vendorId == null || _plantId == null) {
showAppToastFromSnackBar(context, showAppToastFromSnackBar(context,
const SnackBar(content: Text('Please complete all required fields')), const SnackBar(content: Text('Please complete all required fields')),
); );
@ -391,23 +385,6 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
onPicked: (d) => setState(() => _poDate = d), 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>( AppSearchableDropdown<int>(
label: 'Vendor *', label: 'Vendor *',
value: _dropdownValue(_vendorId, vendorIds), value: _dropdownValue(_vendorId, vendorIds),
@ -432,10 +409,6 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
validator: (v) => validator: (v) =>
v == null ? 'Plant is required' : null, v == null ? 'Plant is required' : null,
), ),
],
),
FormRowFour(
children: [
MasterQuickAddDropdown<int?>( MasterQuickAddDropdown<int?>(
masterId: 'warehouses', masterId: 'warehouses',
label: 'Warehouse', label: 'Warehouse',
@ -449,18 +422,10 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
onChanged: (v) => onChanged: (v) =>
setState(() => _warehouseId = v), setState(() => _warehouseId = v),
), ),
MasterQuickAddDropdown<int?>( ],
masterId: 'brands', ),
label: 'Brand', FormRowFour(
value: _brandId, children: [
hint: 'Select brand',
searchHint: 'Search brand...',
options: _nullableIntOptions(lookups.brands),
refreshLookups: () =>
ref.invalidate(purchaseOrderLookupsProvider),
parseCreatedId: int.tryParse,
onChanged: (v) => setState(() => _brandId = v),
),
MasterQuickAddDropdown<int?>( MasterQuickAddDropdown<int?>(
masterId: 'payment_terms', masterId: 'payment_terms',
label: 'Payment Term', label: 'Payment Term',

View File

@ -8,6 +8,7 @@ import '../../../../core/errors/failure.dart';
import '../../../../core/utils/formatters.dart'; import '../../../../core/utils/formatters.dart';
import '../../../../core/utils/responsive_utils.dart'; import '../../../../core/utils/responsive_utils.dart';
import '../../../../shared/models/purchase_order_model.dart'; import '../../../../shared/models/purchase_order_model.dart';
import '../../../../shared/models/vendor_model.dart';
import '../../../../shared/providers/permissions_provider.dart'; import '../../../../shared/providers/permissions_provider.dart';
import '../../../../shared/widgets/app_card.dart'; import '../../../../shared/widgets/app_card.dart';
import '../../../../shared/widgets/app_confirmation_dialog.dart'; import '../../../../shared/widgets/app_confirmation_dialog.dart';
@ -114,8 +115,6 @@ class _PurchaseOrderListScreenState extends ConsumerState<PurchaseOrderListScree
onSearch: ref.read(purchaseOrdersListProvider.notifier).setSearch, onSearch: ref.read(purchaseOrdersListProvider.notifier).setSearch,
onStatusChanged: onStatusChanged:
ref.read(purchaseOrdersListProvider.notifier).setStatusFilter, ref.read(purchaseOrdersListProvider.notifier).setStatusFilter,
onPoTypeChanged:
ref.read(purchaseOrdersListProvider.notifier).setPoTypeFilter,
), ),
footer: AppPagination( footer: AppPagination(
currentPage: state.query.page, currentPage: state.query.page,
@ -273,7 +272,6 @@ class _FiltersBar extends StatelessWidget {
required this.statusOptions, required this.statusOptions,
required this.onSearch, required this.onSearch,
required this.onStatusChanged, required this.onStatusChanged,
required this.onPoTypeChanged,
this.showExport = false, this.showExport = false,
this.isExporting = false, this.isExporting = false,
this.onExport, this.onExport,
@ -284,7 +282,6 @@ class _FiltersBar extends StatelessWidget {
final List<AppDropdownOption<String?>> statusOptions; final List<AppDropdownOption<String?>> statusOptions;
final ValueChanged<String> onSearch; final ValueChanged<String> onSearch;
final ValueChanged<String?> onStatusChanged; final ValueChanged<String?> onStatusChanged;
final ValueChanged<String?> onPoTypeChanged;
final bool showExport; final bool showExport;
final bool isExporting; final bool isExporting;
final VoidCallback? onExport; final VoidCallback? onExport;
@ -306,19 +303,6 @@ class _FiltersBar extends StatelessWidget {
options: statusOptions, options: statusOptions,
onChanged: onStatusChanged, 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( return AppResponsiveFilterBar(
@ -456,7 +440,7 @@ class _PoCardList extends StatelessWidget {
child: ListTile( child: ListTile(
title: Text(order.poNo ?? 'PO #${order.id}'), title: Text(order.poNo ?? 'PO #${order.id}'),
subtitle: Text( subtitle: Text(
'${order.vendorName ?? ''} · ${poTypeLabel(order.poType)}', '${order.vendorName ?? ''} · ${vendorTypeLabel(order.vendorType)}',
), ),
trailing: PoStatusChip(status: order.status, compact: true), trailing: PoStatusChip(status: order.status, compact: true),
onTap: () => onView(order), onTap: () => onView(order),

View File

@ -394,6 +394,18 @@ class _LineItemCardState extends ConsumerState<_LineItemCard> {
ref.read(purchaseOrderLookupsProvider).valueOrNull?.itemHsnById ?? ref.read(purchaseOrderLookupsProvider).valueOrNull?.itemHsnById ??
widget.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() { bool _fillMissingItemDefaults() {
final itemId = widget.line.itemId; final itemId = widget.line.itemId;
if (itemId == null) return false; if (itemId == null) return false;
@ -415,6 +427,10 @@ class _LineItemCardState extends ConsumerState<_LineItemCard> {
widget.line.hsnCodeId = defaultHsn; widget.line.hsnCodeId = defaultHsn;
changed = true; changed = true;
} }
if (widget.line.gstRateId == null && widget.line.hsnCodeId != null) {
_applyGstFromHsn(widget.line.hsnCodeId);
if (widget.line.gstRateId != null) changed = true;
}
return changed; return changed;
} }
@ -434,6 +450,9 @@ class _LineItemCardState extends ConsumerState<_LineItemCard> {
final defaultHsn = _itemHsnById[key]; final defaultHsn = _itemHsnById[key];
if (defaultHsn != null) { if (defaultHsn != null) {
widget.line.hsnCodeId = defaultHsn; 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 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/constants/app_constants.dart';
import '../../../../core/utils/active_option.dart'; import '../../../../core/utils/active_option.dart';
import '../../../../shared/models/user_management_models.dart'; import '../../../../shared/models/user_management_models.dart';
import '../../../masters/data/datasources/master_remote_data_source.dart'; import '../../../masters/data/datasources/master_remote_data_source.dart';
import '../../../roles/data/repositories/role_repository_impl.dart'; import '../../../roles/data/repositories/role_repository_impl.dart';
import '../../../users/data/repositories/user_repository_impl.dart';
import '../../../users/presentation/providers/users_provider.dart'; import '../../../users/presentation/providers/users_provider.dart';
class AddUserFormState { class AddUserFormState {
@ -60,7 +60,7 @@ class AddUserFormNotifier extends FamilyAsyncNotifier<AddUserFormState, String?>
Future<AddUserFormState> build(String? userId) async { Future<AddUserFormState> build(String? userId) async {
final masterRemote = ref.read(masterRemoteDataSourceProvider); final masterRemote = ref.read(masterRemoteDataSourceProvider);
final roleRepository = ref.read(roleRepositoryProvider); final roleRepository = ref.read(roleRepositoryProvider);
final getUsers = ref.read(getUsersUseCaseProvider); final userRepository = ref.read(userRepositoryProvider);
final rolesResult = await roleRepository.listRoleOptions(); final rolesResult = await roleRepository.listRoleOptions();
if (rolesResult.failure != null) throw rolesResult.failure!; if (rolesResult.failure != null) throw rolesResult.failure!;
@ -69,16 +69,10 @@ class AddUserFormNotifier extends FamilyAsyncNotifier<AddUserFormState, String?>
final plants = await masterRemote.listPlants(); final plants = await masterRemote.listPlants();
final designations = await masterRemote.listDesignations(); final designations = await masterRemote.listDesignations();
final usersResult = await getUsers( final usersResult = await userRepository.listUserOptions();
const UserListQuery(
limit: AppConstants.maxPageSize,
status: 'active',
isActive: true,
),
);
if (usersResult.failure != null) throw usersResult.failure!; if (usersResult.failure != null) throw usersResult.failure!;
final managers = usersResult.data!.items final managers = (usersResult.data ?? const [])
.where( .where(
(user) => (user) =>
isReportingManagerRole(user.roleName) && isReportingManagerRole(user.roleName) &&

View File

@ -37,9 +37,7 @@ class RoleRemoteDataSource {
final response = await dio.get( final response = await dio.get(
ApiEndpoints.roles, ApiEndpoints.roles,
queryParameters: { queryParameters: {
'page': 1, 'dropdown_call': true,
'limit': 100,
'is_active': true,
if (search != null && search.isNotEmpty) 'search': search, 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 { Future<ManagedUserModel> getUserById(String id) async {
final response = await dio.get(ApiEndpoints.userById(id)); final response = await dio.get(ApiEndpoints.userById(id));
return ManagedUserModel.fromJson(response.data['data'] as Map<String, dynamic>); return ManagedUserModel.fromJson(response.data['data'] as Map<String, dynamic>);

View File

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

View File

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

View File

@ -20,6 +20,15 @@ class VendorRemoteDataSource {
return _parsePaginated(response.data, VendorModel.fromJson); 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 { Future<VendorModel> getVendorById(String id) async {
final response = await dio.get(ApiEndpoints.vendorById(id)); final response = await dio.get(ApiEndpoints.vendorById(id));
return VendorModel.fromJson(response.data['data'] as Map<String, dynamic>); 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)); return safeApiCall(() => dataSource.getVendors(query));
} }
@override
Future<Result<List<VendorModel>>> listVendorOptions() {
return safeApiCall(() => dataSource.listVendorOptions());
}
@override @override
Future<Result<ExportFileResult>> exportVendors(VendorListQuery query) { Future<Result<ExportFileResult>> exportVendors(VendorListQuery query) {
return safeApiCall(() => dataSource.exportVendors(query)); return safeApiCall(() => dataSource.exportVendors(query));

View File

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

View File

@ -117,6 +117,7 @@ class AssetCategoryModel with _$AssetCategoryModel {
@JsonKey(fromJson: _idFromJson) required String id, @JsonKey(fromJson: _idFromJson) required String id,
required String code, required String code,
required String name, required String name,
@JsonKey(name: 'category_type') String? categoryType,
@JsonKey(name: 'code_prefix') String? codePrefix, @JsonKey(name: 'code_prefix') String? codePrefix,
@JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable)
int? defaultUsefulLifeYears, int? defaultUsefulLifeYears,

View File

@ -25,6 +25,8 @@ mixin _$AssetCategoryModel {
String get id => throw _privateConstructorUsedError; String get id => throw _privateConstructorUsedError;
String get code => throw _privateConstructorUsedError; String get code => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError;
@JsonKey(name: 'category_type')
String? get categoryType => throw _privateConstructorUsedError;
@JsonKey(name: 'code_prefix') @JsonKey(name: 'code_prefix')
String? get codePrefix => throw _privateConstructorUsedError; String? get codePrefix => throw _privateConstructorUsedError;
@JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable)
@ -57,6 +59,7 @@ abstract class $AssetCategoryModelCopyWith<$Res> {
@JsonKey(fromJson: _idFromJson) String id, @JsonKey(fromJson: _idFromJson) String id,
String code, String code,
String name, String name,
@JsonKey(name: 'category_type') String? categoryType,
@JsonKey(name: 'code_prefix') String? codePrefix, @JsonKey(name: 'code_prefix') String? codePrefix,
@JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable)
int? defaultUsefulLifeYears, int? defaultUsefulLifeYears,
@ -86,6 +89,7 @@ class _$AssetCategoryModelCopyWithImpl<$Res, $Val extends AssetCategoryModel>
Object? id = null, Object? id = null,
Object? code = null, Object? code = null,
Object? name = null, Object? name = null,
Object? categoryType = freezed,
Object? codePrefix = freezed, Object? codePrefix = freezed,
Object? defaultUsefulLifeYears = freezed, Object? defaultUsefulLifeYears = freezed,
Object? defaultDepreciationMethod = freezed, Object? defaultDepreciationMethod = freezed,
@ -107,6 +111,10 @@ class _$AssetCategoryModelCopyWithImpl<$Res, $Val extends AssetCategoryModel>
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
as String, as String,
categoryType: freezed == categoryType
? _value.categoryType
: categoryType // ignore: cast_nullable_to_non_nullable
as String?,
codePrefix: freezed == codePrefix codePrefix: freezed == codePrefix
? _value.codePrefix ? _value.codePrefix
: codePrefix // ignore: cast_nullable_to_non_nullable : codePrefix // ignore: cast_nullable_to_non_nullable
@ -150,6 +158,7 @@ abstract class _$$AssetCategoryModelImplCopyWith<$Res>
@JsonKey(fromJson: _idFromJson) String id, @JsonKey(fromJson: _idFromJson) String id,
String code, String code,
String name, String name,
@JsonKey(name: 'category_type') String? categoryType,
@JsonKey(name: 'code_prefix') String? codePrefix, @JsonKey(name: 'code_prefix') String? codePrefix,
@JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable)
int? defaultUsefulLifeYears, int? defaultUsefulLifeYears,
@ -178,6 +187,7 @@ class __$$AssetCategoryModelImplCopyWithImpl<$Res>
Object? id = null, Object? id = null,
Object? code = null, Object? code = null,
Object? name = null, Object? name = null,
Object? categoryType = freezed,
Object? codePrefix = freezed, Object? codePrefix = freezed,
Object? defaultUsefulLifeYears = freezed, Object? defaultUsefulLifeYears = freezed,
Object? defaultDepreciationMethod = freezed, Object? defaultDepreciationMethod = freezed,
@ -199,6 +209,10 @@ class __$$AssetCategoryModelImplCopyWithImpl<$Res>
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
as String, as String,
categoryType: freezed == categoryType
? _value.categoryType
: categoryType // ignore: cast_nullable_to_non_nullable
as String?,
codePrefix: freezed == codePrefix codePrefix: freezed == codePrefix
? _value.codePrefix ? _value.codePrefix
: codePrefix // ignore: cast_nullable_to_non_nullable : codePrefix // ignore: cast_nullable_to_non_nullable
@ -235,6 +249,7 @@ class _$AssetCategoryModelImpl implements _AssetCategoryModel {
@JsonKey(fromJson: _idFromJson) required this.id, @JsonKey(fromJson: _idFromJson) required this.id,
required this.code, required this.code,
required this.name, required this.name,
@JsonKey(name: 'category_type') this.categoryType,
@JsonKey(name: 'code_prefix') this.codePrefix, @JsonKey(name: 'code_prefix') this.codePrefix,
@JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable)
this.defaultUsefulLifeYears, this.defaultUsefulLifeYears,
@ -256,6 +271,9 @@ class _$AssetCategoryModelImpl implements _AssetCategoryModel {
@override @override
final String name; final String name;
@override @override
@JsonKey(name: 'category_type')
final String? categoryType;
@override
@JsonKey(name: 'code_prefix') @JsonKey(name: 'code_prefix')
final String? codePrefix; final String? codePrefix;
@override @override
@ -274,7 +292,7 @@ class _$AssetCategoryModelImpl implements _AssetCategoryModel {
@override @override
String toString() { 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 @override
@ -285,6 +303,8 @@ class _$AssetCategoryModelImpl implements _AssetCategoryModel {
(identical(other.id, id) || other.id == id) && (identical(other.id, id) || other.id == id) &&
(identical(other.code, code) || other.code == code) && (identical(other.code, code) || other.code == code) &&
(identical(other.name, name) || other.name == name) && (identical(other.name, name) || other.name == name) &&
(identical(other.categoryType, categoryType) ||
other.categoryType == categoryType) &&
(identical(other.codePrefix, codePrefix) || (identical(other.codePrefix, codePrefix) ||
other.codePrefix == codePrefix) && other.codePrefix == codePrefix) &&
(identical(other.defaultUsefulLifeYears, defaultUsefulLifeYears) || (identical(other.defaultUsefulLifeYears, defaultUsefulLifeYears) ||
@ -309,6 +329,7 @@ class _$AssetCategoryModelImpl implements _AssetCategoryModel {
id, id,
code, code,
name, name,
categoryType,
codePrefix, codePrefix,
defaultUsefulLifeYears, defaultUsefulLifeYears,
defaultDepreciationMethod, defaultDepreciationMethod,
@ -339,6 +360,7 @@ abstract class _AssetCategoryModel implements AssetCategoryModel {
@JsonKey(fromJson: _idFromJson) required final String id, @JsonKey(fromJson: _idFromJson) required final String id,
required final String code, required final String code,
required final String name, required final String name,
@JsonKey(name: 'category_type') final String? categoryType,
@JsonKey(name: 'code_prefix') final String? codePrefix, @JsonKey(name: 'code_prefix') final String? codePrefix,
@JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable) @JsonKey(name: 'default_useful_life_years', fromJson: _intFromJsonNullable)
final int? defaultUsefulLifeYears, final int? defaultUsefulLifeYears,
@ -360,6 +382,9 @@ abstract class _AssetCategoryModel implements AssetCategoryModel {
@override @override
String get name; String get name;
@override @override
@JsonKey(name: 'category_type')
String? get categoryType;
@override
@JsonKey(name: 'code_prefix') @JsonKey(name: 'code_prefix')
String? get codePrefix; String? get codePrefix;
@override @override

View File

@ -12,6 +12,7 @@ _$AssetCategoryModelImpl _$$AssetCategoryModelImplFromJson(
id: _idFromJson(json['id']), id: _idFromJson(json['id']),
code: json['code'] as String, code: json['code'] as String,
name: json['name'] as String, name: json['name'] as String,
categoryType: json['category_type'] as String?,
codePrefix: json['code_prefix'] as String?, codePrefix: json['code_prefix'] as String?,
defaultUsefulLifeYears: _intFromJsonNullable( defaultUsefulLifeYears: _intFromJsonNullable(
json['default_useful_life_years'], json['default_useful_life_years'],
@ -32,6 +33,7 @@ Map<String, dynamic> _$$AssetCategoryModelImplToJson(
'id': instance.id, 'id': instance.id,
'code': instance.code, 'code': instance.code,
'name': instance.name, 'name': instance.name,
'category_type': instance.categoryType,
'code_prefix': instance.codePrefix, 'code_prefix': instance.codePrefix,
'default_useful_life_years': instance.defaultUsefulLifeYears, 'default_useful_life_years': instance.defaultUsefulLifeYears,
'default_depreciation_method': instance.defaultDepreciationMethod, '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) => Object? _readVendorName(Map<dynamic, dynamic> json, String key) =>
_readNestedName(json, 'vendor_name', 'vendor'); _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) => Object? _readPlantName(Map<dynamic, dynamic> json, String key) =>
_readNestedName(json, 'plant_name', 'plant'); _readNestedName(json, 'plant_name', 'plant');
@ -139,15 +150,14 @@ class PurchaseOrderModel with _$PurchaseOrderModel {
@JsonKey(fromJson: _idFromJson) required String id, @JsonKey(fromJson: _idFromJson) required String id,
@JsonKey(name: 'po_number', readValue: _readPoNumber) String? poNo, @JsonKey(name: 'po_number', readValue: _readPoNumber) String? poNo,
@JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) DateTime? poDate, @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) DateTime? poDate,
@JsonKey(name: 'po_type') String? poType,
@Default('DRAFT') String status, @Default('DRAFT') String status,
@JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId,
@JsonKey(name: 'vendor_name', readValue: _readVendorName) String? vendorName, @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_id', fromJson: _intFromJsonNullable) int? plantId,
@JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName,
@JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) int? warehouseId, @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) int? warehouseId,
@JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) String? warehouseName, @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: 'payment_term_id', fromJson: _intFromJsonNullable) int? paymentTermId,
@JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) int? deliveryTermId, @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) int? deliveryTermId,
@JsonKey(name: 'expected_delivery_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'expected_delivery_date', fromJson: _dateFromJsonNullable)
@ -254,7 +264,6 @@ class PurchaseOrderListQuery with _$PurchaseOrderListQuery {
@Default(20) int limit, @Default(20) int limit,
String? search, String? search,
String? status, String? status,
String? poType,
int? vendorId, int? vendorId,
int? plantId, int? plantId,
String? dateFrom, String? dateFrom,
@ -262,14 +271,6 @@ class PurchaseOrderListQuery with _$PurchaseOrderListQuery {
}) = _PurchaseOrderListQuery; }) = _PurchaseOrderListQuery;
} }
const poTypeOptions = [
('RAW_MATERIAL', 'Raw Material'),
('PACKING_MATERIAL', 'Packing Material'),
('ASSET_CAPITAL', 'Asset / Capital'),
('SERVICE', 'Service'),
('GENERAL', 'General'),
];
const poStatusOptions = [ const poStatusOptions = [
('DRAFT', 'Draft'), ('DRAFT', 'Draft'),
// ('SUBMITTED', 'Submitted'), // ('SUBMITTED', 'Submitted'),
@ -281,15 +282,6 @@ const poStatusOptions = [
('FULLY_RECEIVED', 'Fully Received'), ('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) { String poStatusLabel(String? value) {
if (value == null) return ''; if (value == null) return '';
final normalizedKey = value.trim().toUpperCase().replaceAll(' ', '_'); final normalizedKey = value.trim().toUpperCase().replaceAll(' ', '_');

View File

@ -27,13 +27,13 @@ mixin _$PurchaseOrderModel {
String? get poNo => throw _privateConstructorUsedError; String? get poNo => throw _privateConstructorUsedError;
@JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable)
DateTime? get poDate => throw _privateConstructorUsedError; DateTime? get poDate => throw _privateConstructorUsedError;
@JsonKey(name: 'po_type')
String? get poType => throw _privateConstructorUsedError;
String get status => throw _privateConstructorUsedError; String get status => throw _privateConstructorUsedError;
@JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable)
int? get vendorId => throw _privateConstructorUsedError; int? get vendorId => throw _privateConstructorUsedError;
@JsonKey(name: 'vendor_name', readValue: _readVendorName) @JsonKey(name: 'vendor_name', readValue: _readVendorName)
String? get vendorName => throw _privateConstructorUsedError; String? get vendorName => throw _privateConstructorUsedError;
@JsonKey(name: 'vendor_type', readValue: _readVendorType)
String? get vendorType => throw _privateConstructorUsedError;
@JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable)
int? get plantId => throw _privateConstructorUsedError; int? get plantId => throw _privateConstructorUsedError;
@JsonKey(name: 'plant_name', readValue: _readPlantName) @JsonKey(name: 'plant_name', readValue: _readPlantName)
@ -42,8 +42,6 @@ mixin _$PurchaseOrderModel {
int? get warehouseId => throw _privateConstructorUsedError; int? get warehouseId => throw _privateConstructorUsedError;
@JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName)
String? get warehouseName => throw _privateConstructorUsedError; String? get warehouseName => throw _privateConstructorUsedError;
@JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable)
int? get brandId => throw _privateConstructorUsedError;
@JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable)
int? get paymentTermId => throw _privateConstructorUsedError; int? get paymentTermId => throw _privateConstructorUsedError;
@JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable)
@ -94,18 +92,18 @@ abstract class $PurchaseOrderModelCopyWith<$Res> {
@JsonKey(fromJson: _idFromJson) String id, @JsonKey(fromJson: _idFromJson) String id,
@JsonKey(name: 'po_number', readValue: _readPoNumber) String? poNo, @JsonKey(name: 'po_number', readValue: _readPoNumber) String? poNo,
@JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) DateTime? poDate, @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) DateTime? poDate,
@JsonKey(name: 'po_type') String? poType,
String status, String status,
@JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId,
@JsonKey(name: 'vendor_name', readValue: _readVendorName) @JsonKey(name: 'vendor_name', readValue: _readVendorName)
String? vendorName, String? vendorName,
@JsonKey(name: 'vendor_type', readValue: _readVendorType)
String? vendorType,
@JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? plantId, @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? plantId,
@JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName,
@JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable)
int? warehouseId, int? warehouseId,
@JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName)
String? warehouseName, String? warehouseName,
@JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable) int? brandId,
@JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable)
int? paymentTermId, int? paymentTermId,
@JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable)
@ -152,15 +150,14 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel>
Object? id = null, Object? id = null,
Object? poNo = freezed, Object? poNo = freezed,
Object? poDate = freezed, Object? poDate = freezed,
Object? poType = freezed,
Object? status = null, Object? status = null,
Object? vendorId = freezed, Object? vendorId = freezed,
Object? vendorName = freezed, Object? vendorName = freezed,
Object? vendorType = freezed,
Object? plantId = freezed, Object? plantId = freezed,
Object? plantName = freezed, Object? plantName = freezed,
Object? warehouseId = freezed, Object? warehouseId = freezed,
Object? warehouseName = freezed, Object? warehouseName = freezed,
Object? brandId = freezed,
Object? paymentTermId = freezed, Object? paymentTermId = freezed,
Object? deliveryTermId = freezed, Object? deliveryTermId = freezed,
Object? expectedDeliveryDate = freezed, Object? expectedDeliveryDate = freezed,
@ -191,10 +188,6 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel>
? _value.poDate ? _value.poDate
: poDate // ignore: cast_nullable_to_non_nullable : poDate // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime?,
poType: freezed == poType
? _value.poType
: poType // ignore: cast_nullable_to_non_nullable
as String?,
status: null == status status: null == status
? _value.status ? _value.status
: status // ignore: cast_nullable_to_non_nullable : status // ignore: cast_nullable_to_non_nullable
@ -207,6 +200,10 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel>
? _value.vendorName ? _value.vendorName
: vendorName // ignore: cast_nullable_to_non_nullable : vendorName // ignore: cast_nullable_to_non_nullable
as String?, as String?,
vendorType: freezed == vendorType
? _value.vendorType
: vendorType // ignore: cast_nullable_to_non_nullable
as String?,
plantId: freezed == plantId plantId: freezed == plantId
? _value.plantId ? _value.plantId
: plantId // ignore: cast_nullable_to_non_nullable : plantId // ignore: cast_nullable_to_non_nullable
@ -223,10 +220,6 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel>
? _value.warehouseName ? _value.warehouseName
: warehouseName // ignore: cast_nullable_to_non_nullable : warehouseName // ignore: cast_nullable_to_non_nullable
as String?, as String?,
brandId: freezed == brandId
? _value.brandId
: brandId // ignore: cast_nullable_to_non_nullable
as int?,
paymentTermId: freezed == paymentTermId paymentTermId: freezed == paymentTermId
? _value.paymentTermId ? _value.paymentTermId
: paymentTermId // ignore: cast_nullable_to_non_nullable : paymentTermId // ignore: cast_nullable_to_non_nullable
@ -306,18 +299,18 @@ abstract class _$$PurchaseOrderModelImplCopyWith<$Res>
@JsonKey(fromJson: _idFromJson) String id, @JsonKey(fromJson: _idFromJson) String id,
@JsonKey(name: 'po_number', readValue: _readPoNumber) String? poNo, @JsonKey(name: 'po_number', readValue: _readPoNumber) String? poNo,
@JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) DateTime? poDate, @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) DateTime? poDate,
@JsonKey(name: 'po_type') String? poType,
String status, String status,
@JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId,
@JsonKey(name: 'vendor_name', readValue: _readVendorName) @JsonKey(name: 'vendor_name', readValue: _readVendorName)
String? vendorName, String? vendorName,
@JsonKey(name: 'vendor_type', readValue: _readVendorType)
String? vendorType,
@JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? plantId, @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? plantId,
@JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName,
@JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable)
int? warehouseId, int? warehouseId,
@JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName)
String? warehouseName, String? warehouseName,
@JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable) int? brandId,
@JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable)
int? paymentTermId, int? paymentTermId,
@JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable)
@ -363,15 +356,14 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res>
Object? id = null, Object? id = null,
Object? poNo = freezed, Object? poNo = freezed,
Object? poDate = freezed, Object? poDate = freezed,
Object? poType = freezed,
Object? status = null, Object? status = null,
Object? vendorId = freezed, Object? vendorId = freezed,
Object? vendorName = freezed, Object? vendorName = freezed,
Object? vendorType = freezed,
Object? plantId = freezed, Object? plantId = freezed,
Object? plantName = freezed, Object? plantName = freezed,
Object? warehouseId = freezed, Object? warehouseId = freezed,
Object? warehouseName = freezed, Object? warehouseName = freezed,
Object? brandId = freezed,
Object? paymentTermId = freezed, Object? paymentTermId = freezed,
Object? deliveryTermId = freezed, Object? deliveryTermId = freezed,
Object? expectedDeliveryDate = freezed, Object? expectedDeliveryDate = freezed,
@ -402,10 +394,6 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res>
? _value.poDate ? _value.poDate
: poDate // ignore: cast_nullable_to_non_nullable : poDate // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime?,
poType: freezed == poType
? _value.poType
: poType // ignore: cast_nullable_to_non_nullable
as String?,
status: null == status status: null == status
? _value.status ? _value.status
: status // ignore: cast_nullable_to_non_nullable : status // ignore: cast_nullable_to_non_nullable
@ -418,6 +406,10 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res>
? _value.vendorName ? _value.vendorName
: vendorName // ignore: cast_nullable_to_non_nullable : vendorName // ignore: cast_nullable_to_non_nullable
as String?, as String?,
vendorType: freezed == vendorType
? _value.vendorType
: vendorType // ignore: cast_nullable_to_non_nullable
as String?,
plantId: freezed == plantId plantId: freezed == plantId
? _value.plantId ? _value.plantId
: plantId // ignore: cast_nullable_to_non_nullable : plantId // ignore: cast_nullable_to_non_nullable
@ -434,10 +426,6 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res>
? _value.warehouseName ? _value.warehouseName
: warehouseName // ignore: cast_nullable_to_non_nullable : warehouseName // ignore: cast_nullable_to_non_nullable
as String?, as String?,
brandId: freezed == brandId
? _value.brandId
: brandId // ignore: cast_nullable_to_non_nullable
as int?,
paymentTermId: freezed == paymentTermId paymentTermId: freezed == paymentTermId
? _value.paymentTermId ? _value.paymentTermId
: paymentTermId // ignore: cast_nullable_to_non_nullable : paymentTermId // ignore: cast_nullable_to_non_nullable
@ -510,17 +498,16 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel {
@JsonKey(fromJson: _idFromJson) required this.id, @JsonKey(fromJson: _idFromJson) required this.id,
@JsonKey(name: 'po_number', readValue: _readPoNumber) this.poNo, @JsonKey(name: 'po_number', readValue: _readPoNumber) this.poNo,
@JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) this.poDate, @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) this.poDate,
@JsonKey(name: 'po_type') this.poType,
this.status = 'DRAFT', this.status = 'DRAFT',
@JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) this.vendorId, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) this.vendorId,
@JsonKey(name: 'vendor_name', readValue: _readVendorName) this.vendorName, @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_id', fromJson: _intFromJsonNullable) this.plantId,
@JsonKey(name: 'plant_name', readValue: _readPlantName) this.plantName, @JsonKey(name: 'plant_name', readValue: _readPlantName) this.plantName,
@JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable)
this.warehouseId, this.warehouseId,
@JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName)
this.warehouseName, this.warehouseName,
@JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable) this.brandId,
@JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable)
this.paymentTermId, this.paymentTermId,
@JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable)
@ -561,9 +548,6 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel {
@JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable)
final DateTime? poDate; final DateTime? poDate;
@override @override
@JsonKey(name: 'po_type')
final String? poType;
@override
@JsonKey() @JsonKey()
final String status; final String status;
@override @override
@ -573,6 +557,9 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel {
@JsonKey(name: 'vendor_name', readValue: _readVendorName) @JsonKey(name: 'vendor_name', readValue: _readVendorName)
final String? vendorName; final String? vendorName;
@override @override
@JsonKey(name: 'vendor_type', readValue: _readVendorType)
final String? vendorType;
@override
@JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable)
final int? plantId; final int? plantId;
@override @override
@ -585,9 +572,6 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel {
@JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName)
final String? warehouseName; final String? warehouseName;
@override @override
@JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable)
final int? brandId;
@override
@JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable)
final int? paymentTermId; final int? paymentTermId;
@override @override
@ -639,7 +623,7 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel {
@override @override
String toString() { 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 @override
@ -650,12 +634,13 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel {
(identical(other.id, id) || other.id == id) && (identical(other.id, id) || other.id == id) &&
(identical(other.poNo, poNo) || other.poNo == poNo) && (identical(other.poNo, poNo) || other.poNo == poNo) &&
(identical(other.poDate, poDate) || other.poDate == poDate) && (identical(other.poDate, poDate) || other.poDate == poDate) &&
(identical(other.poType, poType) || other.poType == poType) &&
(identical(other.status, status) || other.status == status) && (identical(other.status, status) || other.status == status) &&
(identical(other.vendorId, vendorId) || (identical(other.vendorId, vendorId) ||
other.vendorId == vendorId) && other.vendorId == vendorId) &&
(identical(other.vendorName, vendorName) || (identical(other.vendorName, vendorName) ||
other.vendorName == vendorName) && other.vendorName == vendorName) &&
(identical(other.vendorType, vendorType) ||
other.vendorType == vendorType) &&
(identical(other.plantId, plantId) || other.plantId == plantId) && (identical(other.plantId, plantId) || other.plantId == plantId) &&
(identical(other.plantName, plantName) || (identical(other.plantName, plantName) ||
other.plantName == plantName) && other.plantName == plantName) &&
@ -663,7 +648,6 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel {
other.warehouseId == warehouseId) && other.warehouseId == warehouseId) &&
(identical(other.warehouseName, warehouseName) || (identical(other.warehouseName, warehouseName) ||
other.warehouseName == warehouseName) && other.warehouseName == warehouseName) &&
(identical(other.brandId, brandId) || other.brandId == brandId) &&
(identical(other.paymentTermId, paymentTermId) || (identical(other.paymentTermId, paymentTermId) ||
other.paymentTermId == paymentTermId) && other.paymentTermId == paymentTermId) &&
(identical(other.deliveryTermId, deliveryTermId) || (identical(other.deliveryTermId, deliveryTermId) ||
@ -701,15 +685,14 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel {
id, id,
poNo, poNo,
poDate, poDate,
poType,
status, status,
vendorId, vendorId,
vendorName, vendorName,
vendorType,
plantId, plantId,
plantName, plantName,
warehouseId, warehouseId,
warehouseName, warehouseName,
brandId,
paymentTermId, paymentTermId,
deliveryTermId, deliveryTermId,
expectedDeliveryDate, expectedDeliveryDate,
@ -750,12 +733,13 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel {
@JsonKey(name: 'po_number', readValue: _readPoNumber) final String? poNo, @JsonKey(name: 'po_number', readValue: _readPoNumber) final String? poNo,
@JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable)
final DateTime? poDate, final DateTime? poDate,
@JsonKey(name: 'po_type') final String? poType,
final String status, final String status,
@JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable)
final int? vendorId, final int? vendorId,
@JsonKey(name: 'vendor_name', readValue: _readVendorName) @JsonKey(name: 'vendor_name', readValue: _readVendorName)
final String? vendorName, final String? vendorName,
@JsonKey(name: 'vendor_type', readValue: _readVendorType)
final String? vendorType,
@JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable)
final int? plantId, final int? plantId,
@JsonKey(name: 'plant_name', readValue: _readPlantName) @JsonKey(name: 'plant_name', readValue: _readPlantName)
@ -764,8 +748,6 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel {
final int? warehouseId, final int? warehouseId,
@JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName)
final String? warehouseName, final String? warehouseName,
@JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable)
final int? brandId,
@JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable)
final int? paymentTermId, final int? paymentTermId,
@JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable)
@ -809,9 +791,6 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel {
@JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'po_date', fromJson: _dateFromJsonNullable)
DateTime? get poDate; DateTime? get poDate;
@override @override
@JsonKey(name: 'po_type')
String? get poType;
@override
String get status; String get status;
@override @override
@JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable)
@ -820,6 +799,9 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel {
@JsonKey(name: 'vendor_name', readValue: _readVendorName) @JsonKey(name: 'vendor_name', readValue: _readVendorName)
String? get vendorName; String? get vendorName;
@override @override
@JsonKey(name: 'vendor_type', readValue: _readVendorType)
String? get vendorType;
@override
@JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable)
int? get plantId; int? get plantId;
@override @override
@ -832,9 +814,6 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel {
@JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName)
String? get warehouseName; String? get warehouseName;
@override @override
@JsonKey(name: 'brand_id', fromJson: _intFromJsonNullable)
int? get brandId;
@override
@JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable)
int? get paymentTermId; int? get paymentTermId;
@override @override
@ -1662,7 +1641,6 @@ mixin _$PurchaseOrderListQuery {
int get limit => throw _privateConstructorUsedError; int get limit => throw _privateConstructorUsedError;
String? get search => throw _privateConstructorUsedError; String? get search => throw _privateConstructorUsedError;
String? get status => throw _privateConstructorUsedError; String? get status => throw _privateConstructorUsedError;
String? get poType => throw _privateConstructorUsedError;
int? get vendorId => throw _privateConstructorUsedError; int? get vendorId => throw _privateConstructorUsedError;
int? get plantId => throw _privateConstructorUsedError; int? get plantId => throw _privateConstructorUsedError;
String? get dateFrom => throw _privateConstructorUsedError; String? get dateFrom => throw _privateConstructorUsedError;
@ -1687,7 +1665,6 @@ abstract class $PurchaseOrderListQueryCopyWith<$Res> {
int limit, int limit,
String? search, String? search,
String? status, String? status,
String? poType,
int? vendorId, int? vendorId,
int? plantId, int? plantId,
String? dateFrom, String? dateFrom,
@ -1717,7 +1694,6 @@ class _$PurchaseOrderListQueryCopyWithImpl<
Object? limit = null, Object? limit = null,
Object? search = freezed, Object? search = freezed,
Object? status = freezed, Object? status = freezed,
Object? poType = freezed,
Object? vendorId = freezed, Object? vendorId = freezed,
Object? plantId = freezed, Object? plantId = freezed,
Object? dateFrom = freezed, Object? dateFrom = freezed,
@ -1741,10 +1717,6 @@ class _$PurchaseOrderListQueryCopyWithImpl<
? _value.status ? _value.status
: status // ignore: cast_nullable_to_non_nullable : status // ignore: cast_nullable_to_non_nullable
as String?, as String?,
poType: freezed == poType
? _value.poType
: poType // ignore: cast_nullable_to_non_nullable
as String?,
vendorId: freezed == vendorId vendorId: freezed == vendorId
? _value.vendorId ? _value.vendorId
: vendorId // ignore: cast_nullable_to_non_nullable : vendorId // ignore: cast_nullable_to_non_nullable
@ -1781,7 +1753,6 @@ abstract class _$$PurchaseOrderListQueryImplCopyWith<$Res>
int limit, int limit,
String? search, String? search,
String? status, String? status,
String? poType,
int? vendorId, int? vendorId,
int? plantId, int? plantId,
String? dateFrom, String? dateFrom,
@ -1808,7 +1779,6 @@ class __$$PurchaseOrderListQueryImplCopyWithImpl<$Res>
Object? limit = null, Object? limit = null,
Object? search = freezed, Object? search = freezed,
Object? status = freezed, Object? status = freezed,
Object? poType = freezed,
Object? vendorId = freezed, Object? vendorId = freezed,
Object? plantId = freezed, Object? plantId = freezed,
Object? dateFrom = freezed, Object? dateFrom = freezed,
@ -1832,10 +1802,6 @@ class __$$PurchaseOrderListQueryImplCopyWithImpl<$Res>
? _value.status ? _value.status
: status // ignore: cast_nullable_to_non_nullable : status // ignore: cast_nullable_to_non_nullable
as String?, as String?,
poType: freezed == poType
? _value.poType
: poType // ignore: cast_nullable_to_non_nullable
as String?,
vendorId: freezed == vendorId vendorId: freezed == vendorId
? _value.vendorId ? _value.vendorId
: vendorId // ignore: cast_nullable_to_non_nullable : vendorId // ignore: cast_nullable_to_non_nullable
@ -1865,7 +1831,6 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery {
this.limit = 20, this.limit = 20,
this.search, this.search,
this.status, this.status,
this.poType,
this.vendorId, this.vendorId,
this.plantId, this.plantId,
this.dateFrom, this.dateFrom,
@ -1883,8 +1848,6 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery {
@override @override
final String? status; final String? status;
@override @override
final String? poType;
@override
final int? vendorId; final int? vendorId;
@override @override
final int? plantId; final int? plantId;
@ -1895,7 +1858,7 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery {
@override @override
String toString() { 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 @override
@ -1907,7 +1870,6 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery {
(identical(other.limit, limit) || other.limit == limit) && (identical(other.limit, limit) || other.limit == limit) &&
(identical(other.search, search) || other.search == search) && (identical(other.search, search) || other.search == search) &&
(identical(other.status, status) || other.status == status) && (identical(other.status, status) || other.status == status) &&
(identical(other.poType, poType) || other.poType == poType) &&
(identical(other.vendorId, vendorId) || (identical(other.vendorId, vendorId) ||
other.vendorId == vendorId) && other.vendorId == vendorId) &&
(identical(other.plantId, plantId) || other.plantId == plantId) && (identical(other.plantId, plantId) || other.plantId == plantId) &&
@ -1923,7 +1885,6 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery {
limit, limit,
search, search,
status, status,
poType,
vendorId, vendorId,
plantId, plantId,
dateFrom, dateFrom,
@ -1949,7 +1910,6 @@ abstract class _PurchaseOrderListQuery implements PurchaseOrderListQuery {
final int limit, final int limit,
final String? search, final String? search,
final String? status, final String? status,
final String? poType,
final int? vendorId, final int? vendorId,
final int? plantId, final int? plantId,
final String? dateFrom, final String? dateFrom,
@ -1965,8 +1925,6 @@ abstract class _PurchaseOrderListQuery implements PurchaseOrderListQuery {
@override @override
String? get status; String? get status;
@override @override
String? get poType;
@override
int? get vendorId; int? get vendorId;
@override @override
int? get plantId; int? get plantId;

View File

@ -12,15 +12,14 @@ _$PurchaseOrderModelImpl _$$PurchaseOrderModelImplFromJson(
id: _idFromJson(json['id']), id: _idFromJson(json['id']),
poNo: _readPoNumber(json, 'po_number') as String?, poNo: _readPoNumber(json, 'po_number') as String?,
poDate: _dateFromJsonNullable(json['po_date']), poDate: _dateFromJsonNullable(json['po_date']),
poType: json['po_type'] as String?,
status: json['status'] as String? ?? 'DRAFT', status: json['status'] as String? ?? 'DRAFT',
vendorId: _intFromJsonNullable(json['vendor_id']), vendorId: _intFromJsonNullable(json['vendor_id']),
vendorName: _readVendorName(json, 'vendor_name') as String?, vendorName: _readVendorName(json, 'vendor_name') as String?,
vendorType: _readVendorType(json, 'vendor_type') as String?,
plantId: _intFromJsonNullable(json['plant_id']), plantId: _intFromJsonNullable(json['plant_id']),
plantName: _readPlantName(json, 'plant_name') as String?, plantName: _readPlantName(json, 'plant_name') as String?,
warehouseId: _intFromJsonNullable(json['warehouse_id']), warehouseId: _intFromJsonNullable(json['warehouse_id']),
warehouseName: _readWarehouseName(json, 'warehouse_name') as String?, warehouseName: _readWarehouseName(json, 'warehouse_name') as String?,
brandId: _intFromJsonNullable(json['brand_id']),
paymentTermId: _intFromJsonNullable(json['payment_term_id']), paymentTermId: _intFromJsonNullable(json['payment_term_id']),
deliveryTermId: _intFromJsonNullable(json['delivery_term_id']), deliveryTermId: _intFromJsonNullable(json['delivery_term_id']),
expectedDeliveryDate: _dateFromJsonNullable(json['expected_delivery_date']), expectedDeliveryDate: _dateFromJsonNullable(json['expected_delivery_date']),
@ -50,15 +49,14 @@ Map<String, dynamic> _$$PurchaseOrderModelImplToJson(
'id': instance.id, 'id': instance.id,
'po_number': instance.poNo, 'po_number': instance.poNo,
'po_date': instance.poDate?.toIso8601String(), 'po_date': instance.poDate?.toIso8601String(),
'po_type': instance.poType,
'status': instance.status, 'status': instance.status,
'vendor_id': instance.vendorId, 'vendor_id': instance.vendorId,
'vendor_name': instance.vendorName, 'vendor_name': instance.vendorName,
'vendor_type': instance.vendorType,
'plant_id': instance.plantId, 'plant_id': instance.plantId,
'plant_name': instance.plantName, 'plant_name': instance.plantName,
'warehouse_id': instance.warehouseId, 'warehouse_id': instance.warehouseId,
'warehouse_name': instance.warehouseName, 'warehouse_name': instance.warehouseName,
'brand_id': instance.brandId,
'payment_term_id': instance.paymentTermId, 'payment_term_id': instance.paymentTermId,
'delivery_term_id': instance.deliveryTermId, 'delivery_term_id': instance.deliveryTermId,
'expected_delivery_date': instance.expectedDeliveryDate?.toIso8601String(), '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 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/utils/validators.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/data/repositories/master_repository_impl.dart';
import '../../modules/master_data/domain/entities/master_definition.dart'; import '../../modules/master_data/domain/entities/master_definition.dart';
import 'app_form_toggle_field.dart'; import 'app_form_toggle_field.dart';
@ -77,17 +78,46 @@ class _MasterInlineQuickAddFormState
Future<void> _loadOptions() async { Future<void> _loadOptions() async {
final options = <String, List<Map<String, dynamic>>>{}; final options = <String, List<Map<String, dynamic>>>{};
final keys = _definition.formFields final fields = _definition.formFields
.where((f) => f.optionsMasterKey != null && f.staticOptions == null) .where((f) => f.optionsMasterKey != null && f.staticOptions == null);
.map((f) => f.optionsMasterKey!)
.toSet(); 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); final def = masterDefinitionById(key);
if (def == null) continue; 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) { 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() { Map<String, dynamic> _buildPayload() {
final payload = <String, dynamic>{}; final payload = <String, dynamic>{};
for (final field in _definition.formFields) { for (final field in _definition.formFields) {
if (!field.isVisibleInForm(_values)) continue;
var value = _values[field.key]; var value = _values[field.key];
if (field.type == MasterFieldType.text || if (field.type == MasterFieldType.text ||
field.type == MasterFieldType.number) { field.type == MasterFieldType.number) {
@ -109,7 +153,11 @@ class _MasterInlineQuickAddFormState
if (value == null || value == '') continue; if (value == null || value == '') continue;
payload[field.key] = switch (field.type) { payload[field.key] = switch (field.type) {
MasterFieldType.number => num.tryParse(value.toString()) ?? value, 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.boolean => value == true,
MasterFieldType.text => value.toString().trim(), MasterFieldType.text => value.toString().trim(),
}; };
@ -157,8 +205,13 @@ class _MasterInlineQuickAddFormState
.map((v) => <String, dynamic>{'id': v, 'name': v}) .map((v) => <String, dynamic>{'id': v, 'name': v})
.toList(); .toList();
} }
final lookupKey = masterFieldDropdownLookupKey(
masterId: widget.masterId,
field: field,
values: _values,
);
var options = var options =
_dropdownOptions[field.optionsMasterKey] ?? const <Map<String, dynamic>>[]; _dropdownOptions[lookupKey] ?? const <Map<String, dynamic>>[];
final filterField = field.filterByFieldKey; final filterField = field.filterByFieldKey;
if (filterField != null) { if (filterField != null) {
final parentId = _values[filterField]?.toString(); final parentId = _values[filterField]?.toString();
@ -182,7 +235,7 @@ class _MasterInlineQuickAddFormState
value: _values[field.key] == true, value: _values[field.key] == true,
onChanged: _submitting onChanged: _submitting
? null ? null
: (v) => setState(() => _values[field.key] = v), : (v) => _updateValue(field.key, v),
); );
} }
return CheckboxListTile( return CheckboxListTile(
@ -192,7 +245,7 @@ class _MasterInlineQuickAddFormState
value: _values[field.key] == true, value: _values[field.key] == true,
onChanged: _submitting onChanged: _submitting
? null ? null
: (v) => setState(() => _values[field.key] = v ?? false), : (v) => _updateValue(field.key, v ?? false),
); );
case MasterFieldType.dropdown: case MasterFieldType.dropdown:
@ -221,7 +274,7 @@ class _MasterInlineQuickAddFormState
], ],
onChanged: (_submitting || prefilled) onChanged: (_submitting || prefilled)
? null ? null
: (v) => setState(() => _values[field.key] = v), : (v) => _updateValue(field.key, v),
validator: field.required validator: field.required
? (v) => v == null ? '${field.label} is required' : null ? (v) => v == null ? '${field.label} is required' : null
: null, : null,
@ -252,9 +305,12 @@ class _MasterInlineQuickAddFormState
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final fields = _definition.formFields; final fields = _definition.formFields;
final activeField = fields.where((field) => field.key == 'is_active').firstOrNull; final activeField =
final regularFields = fields.where((field) => field.key == 'is_active').firstOrNull;
fields.where((field) => field.key != 'is_active').toList(); final regularFields = fields
.where((field) => field.key != 'is_active')
.where((field) => field.isVisibleInForm(_values))
.toList();
return Material( return Material(
color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.45), color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.45),