764 lines
23 KiB
Dart
764 lines
23 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
enum MasterFieldType { text, number, boolean, dropdown }
|
|
|
|
const categoryTypeOptions = ['STOCK', 'ASSET'];
|
|
const termsNotesTypeOptions = ['PO', 'INVOICE'];
|
|
|
|
class MasterFieldDef {
|
|
const MasterFieldDef({
|
|
required this.key,
|
|
required this.label,
|
|
this.type = MasterFieldType.text,
|
|
this.required = false,
|
|
this.showInList = false,
|
|
this.showInForm = true,
|
|
this.readOnly = false,
|
|
this.optionsMasterKey,
|
|
this.optionsQueryParams,
|
|
this.staticOptions,
|
|
this.multiline = false,
|
|
this.filterByFieldKey,
|
|
this.filterByOptionKey,
|
|
this.visibleWhenFieldKey,
|
|
this.visibleWhenValue,
|
|
this.listNestedKey,
|
|
});
|
|
|
|
final String key;
|
|
final String label;
|
|
final MasterFieldType type;
|
|
final bool required;
|
|
final bool showInList;
|
|
/// When false, field is list/display-only and excluded from create/update payloads.
|
|
final bool showInForm;
|
|
/// When true, shown in the form but not editable (value set by other fields).
|
|
final bool readOnly;
|
|
/// Master key used to populate dropdown options (e.g. `locations` for FKs).
|
|
final String? optionsMasterKey;
|
|
/// Extra query parameters when loading [optionsMasterKey] options.
|
|
final Map<String, dynamic>? optionsQueryParams;
|
|
/// Fixed dropdown choices (e.g. category type) — no API lookup.
|
|
final List<String>? staticOptions;
|
|
final bool multiline;
|
|
/// Form field whose value filters this dropdown (e.g. `item_category_id`).
|
|
final String? filterByFieldKey;
|
|
/// Option-row key matched against [filterByFieldKey] (defaults to same key).
|
|
final String? filterByOptionKey;
|
|
/// Show this field only when [visibleWhenFieldKey] equals [visibleWhenValue].
|
|
final String? visibleWhenFieldKey;
|
|
final String? visibleWhenValue;
|
|
/// Nested object key on list rows for display (e.g. `gst_rate` for `gst_rate_id`).
|
|
final String? listNestedKey;
|
|
|
|
/// 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 {
|
|
const MasterDefinition({
|
|
required this.id,
|
|
required this.title,
|
|
required this.subtitle,
|
|
required this.category,
|
|
required this.routeKey,
|
|
required this.apiPath,
|
|
required this.module,
|
|
required this.icon,
|
|
required this.fields,
|
|
});
|
|
|
|
final String id;
|
|
final String title;
|
|
final String subtitle;
|
|
final String category;
|
|
final String routeKey;
|
|
final String apiPath;
|
|
final String module;
|
|
final IconData icon;
|
|
final List<MasterFieldDef> fields;
|
|
|
|
List<MasterFieldDef> get listFields =>
|
|
fields.where((field) => field.showInList).toList();
|
|
|
|
List<MasterFieldDef> get formFields =>
|
|
fields.where((field) => field.showInForm).toList();
|
|
|
|
String listRoute(String base) => '$base/$routeKey';
|
|
String addRoute(String base) => '$base/$routeKey/add';
|
|
String editRoute(String base, String id) => '$base/$routeKey/$id/edit';
|
|
}
|
|
|
|
const _activeField = MasterFieldDef(
|
|
key: 'is_active',
|
|
label: 'Active',
|
|
type: MasterFieldType.boolean,
|
|
);
|
|
|
|
MasterDefinition? masterDefinitionById(String id) {
|
|
for (final def in masterDefinitions) {
|
|
if (def.id == id) return def;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
MasterDefinition? masterDefinitionByRouteKey(String routeKey) {
|
|
for (final def in masterDefinitions) {
|
|
if (def.routeKey == routeKey) return def;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const masterDefinitions = <MasterDefinition>[
|
|
MasterDefinition(
|
|
id: 'uom',
|
|
title: 'UOM',
|
|
subtitle: 'Units of measure',
|
|
category: 'Inventory & Items',
|
|
routeKey: 'uom',
|
|
apiPath: '/masters/uom',
|
|
module: 'uom',
|
|
icon: Icons.straighten_outlined,
|
|
fields: [
|
|
MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true),
|
|
MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'item_categories',
|
|
title: 'Item Categories',
|
|
subtitle: 'Top-level item grouping',
|
|
category: 'Inventory & Items',
|
|
routeKey: 'item-categories',
|
|
apiPath: '/masters/item-categories',
|
|
module: 'item_categories',
|
|
icon: Icons.category_outlined,
|
|
fields: [
|
|
MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true),
|
|
MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true),
|
|
MasterFieldDef(
|
|
key: 'category_type',
|
|
label: 'Category Type',
|
|
type: MasterFieldType.dropdown,
|
|
required: true,
|
|
showInList: true,
|
|
staticOptions: categoryTypeOptions,
|
|
),
|
|
MasterFieldDef(
|
|
key: 'code_prefix',
|
|
label: 'Code Prefix',
|
|
showInList: true,
|
|
visibleWhenFieldKey: 'category_type',
|
|
visibleWhenValue: 'ASSET',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'default_useful_life_years',
|
|
label: 'Useful Life (Years)',
|
|
type: MasterFieldType.number,
|
|
visibleWhenFieldKey: 'category_type',
|
|
visibleWhenValue: 'ASSET',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'default_depreciation_method',
|
|
label: 'Depreciation Method',
|
|
type: MasterFieldType.dropdown,
|
|
showInList: true,
|
|
optionsMasterKey: 'asset_depreciation_methods',
|
|
visibleWhenFieldKey: 'category_type',
|
|
visibleWhenValue: 'ASSET',
|
|
),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'item_subcategories',
|
|
title: 'Item Sub Categories',
|
|
subtitle: 'Sub-grouping under item categories',
|
|
category: 'Inventory & Items',
|
|
routeKey: 'item-subcategories',
|
|
apiPath: '/masters/item-subcategories',
|
|
module: 'item_subcategories',
|
|
icon: Icons.subdirectory_arrow_right,
|
|
fields: [
|
|
MasterFieldDef(
|
|
key: 'item_category_id',
|
|
label: 'Item Category',
|
|
type: MasterFieldType.dropdown,
|
|
required: true,
|
|
showInList: true,
|
|
optionsMasterKey: 'item_categories',
|
|
),
|
|
MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true),
|
|
MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'items',
|
|
title: 'Items',
|
|
subtitle: 'Material and product master',
|
|
category: 'Inventory & Items',
|
|
routeKey: 'items',
|
|
apiPath: '/masters/items',
|
|
module: 'items',
|
|
icon: Icons.inventory_outlined,
|
|
fields: [
|
|
// Auto-generated by backend — list only; never sent on create/update.
|
|
MasterFieldDef(
|
|
key: 'item_code',
|
|
label: 'Item Code',
|
|
showInList: true,
|
|
showInForm: false,
|
|
),
|
|
MasterFieldDef(key: 'item_name', label: 'Item Name', required: true, showInList: true),
|
|
MasterFieldDef(
|
|
key: 'is_asset_item',
|
|
label: 'Asset Item',
|
|
type: MasterFieldType.boolean,
|
|
required: true,
|
|
showInList: true,
|
|
),
|
|
MasterFieldDef(
|
|
key: 'item_category_id',
|
|
label: 'Category',
|
|
type: MasterFieldType.dropdown,
|
|
required: true,
|
|
optionsMasterKey: 'item_categories',
|
|
// Resolved at runtime from is_asset_item (STOCK / ASSET).
|
|
),
|
|
MasterFieldDef(
|
|
key: 'item_subcategory_id',
|
|
label: 'Sub Category',
|
|
type: MasterFieldType.dropdown,
|
|
optionsMasterKey: 'item_subcategories',
|
|
filterByFieldKey: 'item_category_id',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'uom_id',
|
|
label: 'UOM',
|
|
type: MasterFieldType.dropdown,
|
|
required: true,
|
|
optionsMasterKey: 'uom',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'hsn_code_id',
|
|
label: 'HSN Code',
|
|
type: MasterFieldType.dropdown,
|
|
showInList: true,
|
|
optionsMasterKey: 'hsn_codes',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'gst_rate_id',
|
|
label: 'GST Rate',
|
|
type: MasterFieldType.dropdown,
|
|
showInList: true,
|
|
readOnly: true,
|
|
// Filled from selected HSN's nested gst_rate — no gst-rates API.
|
|
),
|
|
MasterFieldDef(
|
|
key: 'min_order_qty',
|
|
label: 'Min Order Qty',
|
|
type: MasterFieldType.number,
|
|
required: true,
|
|
// Stock items only — hidden when Asset Item is checked.
|
|
visibleWhenFieldKey: 'is_asset_item',
|
|
visibleWhenValue: 'false',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'reorder_level',
|
|
label: 'Reorder Level',
|
|
type: MasterFieldType.number,
|
|
required: true,
|
|
visibleWhenFieldKey: 'is_asset_item',
|
|
visibleWhenValue: 'false',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'tags',
|
|
label: 'Tags',
|
|
multiline: true,
|
|
showInList: true,
|
|
),
|
|
MasterFieldDef(key: 'description', label: 'Description', multiline: true),
|
|
MasterFieldDef(key: 'specification', label: 'Specification', multiline: true),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'hsn_codes',
|
|
title: 'HSN Codes',
|
|
subtitle: 'HSN/SAC tax classification codes',
|
|
category: 'Finance & Terms',
|
|
routeKey: 'hsn-codes',
|
|
apiPath: '/masters/hsn-codes',
|
|
module: 'hsn_codes',
|
|
icon: Icons.qr_code_outlined,
|
|
fields: [
|
|
MasterFieldDef(key: 'code', label: 'HSN/SAC Code', required: true, showInList: true),
|
|
MasterFieldDef(
|
|
key: 'description',
|
|
label: 'Description',
|
|
required: true,
|
|
showInList: true,
|
|
multiline: true,
|
|
),
|
|
MasterFieldDef(
|
|
key: 'gst_rate_id',
|
|
label: 'GST Rate',
|
|
type: MasterFieldType.dropdown,
|
|
showInList: true,
|
|
optionsMasterKey: 'gst_rates',
|
|
),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'document_series',
|
|
title: 'Document Series',
|
|
subtitle: 'Numbering series for documents',
|
|
category: 'Finance & Terms',
|
|
routeKey: 'document-series',
|
|
apiPath: '/masters/document-series',
|
|
module: 'document_series',
|
|
icon: Icons.numbers_outlined,
|
|
fields: [
|
|
MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true),
|
|
MasterFieldDef(key: 'prefix', label: 'Prefix', required: true, showInList: true),
|
|
MasterFieldDef(
|
|
key: 'current_number',
|
|
label: 'Current Number',
|
|
type: MasterFieldType.number,
|
|
required: true,
|
|
showInList: true,
|
|
),
|
|
MasterFieldDef(
|
|
key: 'padding',
|
|
label: 'Padding',
|
|
type: MasterFieldType.number,
|
|
required: true,
|
|
),
|
|
MasterFieldDef(key: 'description', label: 'Description', multiline: true),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'locations',
|
|
title: 'Locations',
|
|
subtitle: 'Plants and warehouses',
|
|
category: 'Organization',
|
|
routeKey: 'locations',
|
|
apiPath: '/masters/locations',
|
|
module: 'locations',
|
|
icon: Icons.place_outlined,
|
|
fields: [
|
|
MasterFieldDef(
|
|
key: 'type',
|
|
label: 'Type',
|
|
type: MasterFieldType.dropdown,
|
|
required: true,
|
|
showInList: true,
|
|
staticOptions: const ['plant', 'warehouse'],
|
|
),
|
|
MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true),
|
|
MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true),
|
|
MasterFieldDef(
|
|
key: 'gstin',
|
|
label: 'GSTIN',
|
|
showInList: true,
|
|
visibleWhenFieldKey: 'type',
|
|
visibleWhenValue: 'plant',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'city',
|
|
label: 'City',
|
|
showInList: true,
|
|
visibleWhenFieldKey: 'type',
|
|
visibleWhenValue: 'plant',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'state',
|
|
label: 'State',
|
|
type: MasterFieldType.dropdown,
|
|
optionsMasterKey: 'location_states',
|
|
showInList: true,
|
|
visibleWhenFieldKey: 'type',
|
|
visibleWhenValue: 'plant',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'address',
|
|
label: 'Address',
|
|
multiline: true,
|
|
visibleWhenFieldKey: 'type',
|
|
visibleWhenValue: 'plant',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'pincode',
|
|
label: 'Pincode',
|
|
visibleWhenFieldKey: 'type',
|
|
visibleWhenValue: 'plant',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'phone',
|
|
label: 'Phone',
|
|
visibleWhenFieldKey: 'type',
|
|
visibleWhenValue: 'plant',
|
|
),
|
|
MasterFieldDef(
|
|
key: 'location',
|
|
label: 'Location Detail',
|
|
showInList: true,
|
|
visibleWhenFieldKey: 'type',
|
|
visibleWhenValue: 'warehouse',
|
|
),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'designations',
|
|
title: 'Designations',
|
|
subtitle: 'Employee designations',
|
|
category: 'Organization',
|
|
routeKey: 'designations',
|
|
apiPath: '/masters/designations',
|
|
module: 'designations',
|
|
icon: Icons.badge_outlined,
|
|
fields: [
|
|
MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'departments',
|
|
title: 'Departments',
|
|
subtitle: 'Organisation departments',
|
|
category: 'Organization',
|
|
routeKey: 'departments',
|
|
apiPath: '/masters/departments',
|
|
module: 'departments',
|
|
icon: Icons.apartment_outlined,
|
|
fields: [
|
|
MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'delivery_terms',
|
|
title: 'Delivery Terms',
|
|
subtitle: 'Delivery incoterms and terms',
|
|
category: 'Finance & Terms',
|
|
routeKey: 'delivery-terms',
|
|
apiPath: '/masters/delivery-terms',
|
|
module: 'delivery_terms',
|
|
icon: Icons.local_shipping_outlined,
|
|
fields: [
|
|
MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true),
|
|
MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true),
|
|
MasterFieldDef(key: 'description', label: 'Description', multiline: true),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'payment_terms',
|
|
title: 'Payment Terms',
|
|
subtitle: 'Vendor payment terms',
|
|
category: 'Finance & Terms',
|
|
routeKey: 'payment-terms',
|
|
apiPath: '/masters/payment-terms',
|
|
module: 'payment_terms',
|
|
icon: Icons.payments_outlined,
|
|
fields: [
|
|
MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true),
|
|
MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true),
|
|
MasterFieldDef(
|
|
key: 'credit_days',
|
|
label: 'Credit Days',
|
|
type: MasterFieldType.number,
|
|
required: true,
|
|
showInList: true,
|
|
),
|
|
MasterFieldDef(key: 'description', label: 'Description', multiline: true),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'terms_notes',
|
|
title: 'Terms & Notes',
|
|
subtitle: 'Default terms by document type (PO, Invoice)',
|
|
category: 'Finance & Terms',
|
|
routeKey: 'terms-notes',
|
|
apiPath: '/masters/terms-notes',
|
|
module: 'terms_notes',
|
|
icon: Icons.notes_outlined,
|
|
fields: [
|
|
MasterFieldDef(
|
|
key: 'type',
|
|
label: 'Document Type',
|
|
type: MasterFieldType.dropdown,
|
|
required: true,
|
|
showInList: true,
|
|
staticOptions: termsNotesTypeOptions,
|
|
),
|
|
MasterFieldDef(
|
|
key: 'notes',
|
|
label: 'Notes',
|
|
required: true,
|
|
showInList: true,
|
|
multiline: true,
|
|
),
|
|
_activeField,
|
|
],
|
|
),
|
|
MasterDefinition(
|
|
id: 'gst_rates',
|
|
title: 'GST Rates',
|
|
subtitle: 'Tax rate master',
|
|
category: 'Finance & Terms',
|
|
routeKey: 'gst-rates',
|
|
apiPath: '/masters/gst-rates',
|
|
module: 'gst_rates',
|
|
icon: Icons.percent_outlined,
|
|
fields: [
|
|
MasterFieldDef(
|
|
key: 'rate_pct',
|
|
label: 'Rate (%)',
|
|
type: MasterFieldType.number,
|
|
required: true,
|
|
showInList: true,
|
|
),
|
|
MasterFieldDef(key: 'description', label: 'Description', showInList: true),
|
|
_activeField,
|
|
],
|
|
),
|
|
];
|
|
|
|
List<String> get masterCategories =>
|
|
masterDefinitions.map((def) => def.category).toSet().toList();
|
|
|
|
String masterRecordLabel(Map<String, dynamic> row) {
|
|
final ratePct = row['rate_pct'];
|
|
if (ratePct != null) {
|
|
final rate = ratePct is num
|
|
? ratePct.toDouble()
|
|
: double.tryParse(ratePct.toString());
|
|
if (rate != null) {
|
|
final rateLabel = rate % 1 == 0 ? '${rate.toInt()}%' : '$rate%';
|
|
final desc = row['description'];
|
|
if (desc != null && desc.toString().trim().isNotEmpty) {
|
|
return '$rateLabel — ${desc.toString().trim()}';
|
|
}
|
|
return rateLabel;
|
|
}
|
|
}
|
|
|
|
for (final key in ['name', 'item_name', 'item_code']) {
|
|
final value = row[key];
|
|
if (value != null && value.toString().trim().isNotEmpty) {
|
|
return value.toString();
|
|
}
|
|
}
|
|
|
|
final code = row['code'];
|
|
if (code != null && code.toString().trim().isNotEmpty) {
|
|
final desc = row['description'];
|
|
if (desc != null && desc.toString().trim().isNotEmpty) {
|
|
return '${code.toString().trim()} — ${desc.toString().trim()}';
|
|
}
|
|
return code.toString().trim();
|
|
}
|
|
|
|
final description = row['description'];
|
|
if (description != null && description.toString().trim().isNotEmpty) {
|
|
return description.toString().trim();
|
|
}
|
|
return row['id']?.toString() ?? 'Record';
|
|
}
|
|
|
|
String masterCellValue(Map<String, dynamic> row, MasterFieldDef field) {
|
|
final value = row[field.key];
|
|
if (value == null || value == '') return '—';
|
|
|
|
if (field.key == 'is_asset_item') {
|
|
return masterIsAssetItem(value) ? 'Asset' : 'Stock';
|
|
}
|
|
|
|
if (field.key == 'tags') {
|
|
if (value is List) {
|
|
final tags = value
|
|
.map((e) => e.toString().trim())
|
|
.where((e) => e.isNotEmpty)
|
|
.toList();
|
|
return tags.isEmpty ? '—' : tags.join(', ');
|
|
}
|
|
final text = value.toString().trim();
|
|
return text.isEmpty ? '—' : text;
|
|
}
|
|
|
|
if (field.type == MasterFieldType.boolean) {
|
|
return value == true ? 'Yes' : 'No';
|
|
}
|
|
|
|
if (field.type == MasterFieldType.dropdown) {
|
|
final label = row['${field.key}_label'];
|
|
if (label != null && label.toString().isNotEmpty) return label.toString();
|
|
|
|
if (field.key == 'gst_rate_id') {
|
|
final nested = row['gst_rate'];
|
|
if (nested is Map) {
|
|
final pct = nested['rate_pct'];
|
|
if (pct != null) {
|
|
final rate = pct is num
|
|
? pct.toDouble()
|
|
: double.tryParse(pct.toString());
|
|
if (rate != null) {
|
|
return rate % 1 == 0 ? '${rate.toInt()}%' : '$rate%';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prefer explicit "<base>_name" or nested "<base>.name" / flat code from API
|
|
// (e.g. asset_category_id -> asset_category_name; hsn_code_id -> hsn_code)
|
|
if (field.key.endsWith('_id')) {
|
|
final baseKey = field.key.substring(0, field.key.length - 3);
|
|
final explicitName = row['${baseKey}_name'];
|
|
if (explicitName != null && explicitName.toString().trim().isNotEmpty) {
|
|
return explicitName.toString().trim();
|
|
}
|
|
|
|
final nestedKeys = <String>[
|
|
if (field.listNestedKey != null) field.listNestedKey!,
|
|
baseKey,
|
|
];
|
|
for (final nestedKey in nestedKeys) {
|
|
final nested = row[nestedKey];
|
|
if (nested is Map) {
|
|
for (final nameKey in ['name', 'code', 'description']) {
|
|
final nestedValue = nested[nameKey];
|
|
if (nestedValue != null &&
|
|
nestedValue.toString().trim().isNotEmpty) {
|
|
return nestedValue.toString().trim();
|
|
}
|
|
}
|
|
} else if (nested != null && nested.toString().trim().isNotEmpty) {
|
|
// Flat denormalized value (e.g. items.hsn_code string)
|
|
return nested.toString().trim();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (field.staticOptions != null) {
|
|
return value.toString().replaceAll('_', ' ');
|
|
}
|
|
}
|
|
|
|
return value.toString();
|
|
}
|
|
|
|
String masterStatusValue(Map<String, dynamic> row) {
|
|
final active = row['is_active'];
|
|
if (active == false) return 'inactive';
|
|
return 'active';
|
|
}
|
|
|
|
bool masterIsAssetItem(Object? value) {
|
|
if (value == true || value == 1) return true;
|
|
if (value == false || value == 0) return false;
|
|
final text = value?.toString().trim().toLowerCase() ?? '';
|
|
return text == 'true' || text == '1' || text == 'yes';
|
|
}
|
|
|
|
/// 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';
|
|
}
|
|
|
|
/// Placeholder / hint casing for master field labels (keeps UOM, HSN, GST, etc.).
|
|
String masterFieldHintLabel(String label) {
|
|
const acronyms = {
|
|
'uom': 'UOM',
|
|
'hsn': 'HSN',
|
|
'gst': 'GST',
|
|
'sac': 'SAC',
|
|
'po': 'PO',
|
|
'grn': 'Purchase Receipt',
|
|
'amc': 'AMC',
|
|
};
|
|
return label
|
|
.trim()
|
|
.split(RegExp(r'\s+'))
|
|
.where((part) => part.isNotEmpty)
|
|
.map((part) {
|
|
final lower = part.toLowerCase();
|
|
return acronyms[lower] ?? lower;
|
|
})
|
|
.join(' ');
|
|
}
|
|
|
|
/// 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;
|
|
}
|