bharat_erp/lib/shared/models/grn_model.dart
2026-07-17 11:07:08 +05:30

285 lines
10 KiB
Dart

import 'package:freezed_annotation/freezed_annotation.dart';
part 'grn_model.freezed.dart';
part 'grn_model.g.dart';
String _idFromJson(Object? value) => value?.toString() ?? '';
int? _intFromJsonNullable(Object? value) {
if (value == null) return null;
if (value is int) return value;
if (value is num) return value.toInt();
return int.tryParse(value.toString());
}
double? _doubleFromJsonNullable(Object? value) {
if (value == null) return null;
if (value is double) return value;
if (value is num) return value.toDouble();
return double.tryParse(value.toString());
}
DateTime? _dateFromJsonNullable(Object? value) {
if (value == null) return null;
if (value is DateTime) return value;
return DateTime.tryParse(value.toString());
}
Object? _readNestedName(Map<dynamic, dynamic> json, String flatKey, String nestedKey) {
final flat = json[flatKey];
if (flat is String && flat.isNotEmpty) return flat;
final nested = json[nestedKey];
if (nested is Map) {
return nested['name'] ?? nested['vendor_name'];
}
return null;
}
Object? _readVendorName(Map<dynamic, dynamic> json, String key) =>
_readNestedName(json, 'vendor_name', 'vendor');
Object? _readLocationName(Map<dynamic, dynamic> json, String key) {
final flat = json['location_name'];
if (flat is String && flat.isNotEmpty) return flat;
final nested = json['location'];
if (nested is Map) {
return nested['name'] ?? nested['code'];
}
return null;
}
Object? _readLocationType(Map<dynamic, dynamic> json, String key) {
final flat = json['location_type'];
if (flat is String && flat.isNotEmpty) return flat;
final nested = json['location'];
if (nested is Map) return nested['type'];
return null;
}
Object? _readPoNumber(Map<dynamic, dynamic> json, String key) {
final grnNumber = json['grn_number'];
if (grnNumber != null && grnNumber.toString().trim().isNotEmpty) {
return grnNumber.toString();
}
final grnNo = json['grn_no'];
if (grnNo != null && grnNo.toString().trim().isNotEmpty) {
return grnNo.toString();
}
return null;
}
Object? _readPoRefNumber(Map<dynamic, dynamic> json, String key) {
final flat = json['po_number'];
if (flat is String && flat.isNotEmpty) return flat;
final nested = json['purchase_order'] ?? json['po'];
if (nested is Map) {
return nested['po_number'] ?? nested['po_no'];
}
return null;
}
Object? _readItemName(Map<dynamic, dynamic> json, String key) {
final flat = json['item_name'];
if (flat is String && flat.isNotEmpty) return flat;
final nested = json['item'];
if (nested is Map) return nested['name'] ?? nested['item_name'];
return null;
}
Object? _readItemCode(Map<dynamic, dynamic> json, String key) {
final flat = json['item_code'];
if (flat is String && flat.isNotEmpty) return flat;
final nested = json['item'];
if (nested is Map) return nested['code'] ?? nested['item_code'];
return null;
}
Object? _readUomName(Map<dynamic, dynamic> json, String key) {
final flat = json['uom_name'];
if (flat is String && flat.isNotEmpty) return flat;
final nested = json['uom'];
if (nested is Map) return nested['name'];
return null;
}
Object? _readGrnItemCategoryId(Map<dynamic, dynamic> json, String key) {
for (final flatKey in ['item_category_id', 'asset_category_id']) {
final flat = json[flatKey];
if (flat != null) return flat;
}
final nested = json['item_category'] ?? json['asset_category'];
if (nested is Map) return nested['id'];
return null;
}
Object? _readGrnItemSubcategoryId(Map<dynamic, dynamic> json, String key) {
for (final flatKey in ['item_subcategory_id', 'asset_subcategory_id']) {
final flat = json[flatKey];
if (flat != null) return flat;
}
final nested = json['item_subcategory'] ?? json['asset_subcategory'];
if (nested is Map) return nested['id'];
return null;
}
Object? _readUploadedByName(Map<dynamic, dynamic> json, String key) {
final flat = json['uploaded_by_name'];
if (flat is String && flat.trim().isNotEmpty) return flat;
final nested = json['uploaded_by_user'] ?? json['uploaded_by'];
if (nested is Map) {
return nested['full_name'] ?? nested['name'];
}
return null;
}
@freezed
class GrnModel with _$GrnModel {
const GrnModel._();
const factory GrnModel({
@JsonKey(fromJson: _idFromJson) required String id,
@JsonKey(name: 'grn_number', readValue: _readPoNumber) String? grnNumber,
@JsonKey(name: 'grn_date', fromJson: _dateFromJsonNullable) DateTime? grnDate,
@Default('POSTED') String status,
@JsonKey(name: 'po_id', fromJson: _intFromJsonNullable) int? poId,
@JsonKey(name: 'po_number', readValue: _readPoRefNumber) String? poNumber,
@JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId,
@JsonKey(name: 'vendor_name', readValue: _readVendorName) String? vendorName,
@JsonKey(name: 'location_id', fromJson: _intFromJsonNullable) int? locationId,
@JsonKey(name: 'location_name', readValue: _readLocationName)
String? locationName,
@JsonKey(name: 'location_type', readValue: _readLocationType)
String? locationType,
@JsonKey(name: 'vendor_invoice_no') String? vendorInvoiceNo,
@JsonKey(name: 'vendor_invoice_date', fromJson: _dateFromJsonNullable)
DateTime? vendorInvoiceDate,
@JsonKey(name: 'vendor_invoice_amount', fromJson: _doubleFromJsonNullable)
double? vendorInvoiceAmount,
@JsonKey(name: 'vehicle_no') String? vehicleNo,
@JsonKey(name: 'lr_no') String? lrNo,
@JsonKey(name: 'lr_date', fromJson: _dateFromJsonNullable) DateTime? lrDate,
@JsonKey(name: 'received_by', fromJson: _intFromJsonNullable) int? receivedBy,
@JsonKey(name: 'quality_checked_by', fromJson: _intFromJsonNullable)
int? qualityCheckedBy,
String? remarks,
@JsonKey(name: 'cancellation_reason') String? cancellationReason,
@JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt,
@JsonKey(name: 'updated_at', fromJson: _dateFromJsonNullable) DateTime? updatedAt,
@Default([]) List<GrnItemModel> items,
@Default([]) List<GrnAttachmentModel> attachments,
}) = _GrnModel;
factory GrnModel.fromJson(Map<String, dynamic> json) => _$GrnModelFromJson(json);
bool get canEdit => status.toUpperCase() == 'POSTED';
bool get canCancel => status.toUpperCase() == 'POSTED';
/// Upload/delete attachments only while GRN is POSTED.
bool get canManageAttachments => status.toUpperCase() == 'POSTED';
}
@freezed
class GrnAttachmentModel with _$GrnAttachmentModel {
const GrnAttachmentModel._();
const factory GrnAttachmentModel({
@JsonKey(fromJson: _idFromJson) required String id,
@JsonKey(name: 'grn_id', fromJson: _idFromJson) String? grnId,
@JsonKey(name: 'file_name') String? fileName,
@JsonKey(name: 'file_type') String? fileType,
@JsonKey(name: 'file_size', fromJson: _intFromJsonNullable) int? fileSize,
@JsonKey(name: 'uploaded_by_name', readValue: _readUploadedByName)
String? uploadedByName,
@JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt,
}) = _GrnAttachmentModel;
factory GrnAttachmentModel.fromJson(Map<String, dynamic> json) =>
_$GrnAttachmentModelFromJson(json);
bool get isPdf =>
(fileType ?? '').toLowerCase().contains('pdf') ||
(fileName ?? '').toLowerCase().endsWith('.pdf');
bool get isImage {
final type = (fileType ?? '').toLowerCase();
final name = (fileName ?? '').toLowerCase();
return type.startsWith('image/') ||
name.endsWith('.jpg') ||
name.endsWith('.jpeg') ||
name.endsWith('.png') ||
name.endsWith('.webp');
}
}
@freezed
class GrnItemModel with _$GrnItemModel {
const factory GrnItemModel({
@JsonKey(fromJson: _idFromJson) required String id,
@JsonKey(name: 'grn_id', fromJson: _idFromJson) String? grnId,
@JsonKey(name: 'po_item_id', fromJson: _intFromJsonNullable) int? poItemId,
@JsonKey(name: 'item_id', fromJson: _intFromJsonNullable) int? itemId,
@JsonKey(name: 'item_code', readValue: _readItemCode) String? itemCode,
@JsonKey(name: 'item_name', readValue: _readItemName) String? itemName,
@JsonKey(name: 'line_no', fromJson: _intFromJsonNullable) int? lineNo,
@JsonKey(name: 'current_qty', fromJson: _doubleFromJsonNullable) double? currentQty,
@JsonKey(name: 'accepted_qty', fromJson: _doubleFromJsonNullable) double? acceptedQty,
@JsonKey(name: 'damaged_qty', fromJson: _doubleFromJsonNullable) double? damagedQty,
@JsonKey(name: 'short_qty', fromJson: _doubleFromJsonNullable) double? shortQty,
@JsonKey(name: 'excess_qty', fromJson: _doubleFromJsonNullable) double? excessQty,
@JsonKey(name: 'rejected_qty', fromJson: _doubleFromJsonNullable) double? rejectedQty,
@JsonKey(name: 'rejection_reason') String? rejectionReason,
@JsonKey(fromJson: _doubleFromJsonNullable) double? rate,
@JsonKey(name: 'batch_no') String? batchNo,
@JsonKey(name: 'mfg_date', fromJson: _dateFromJsonNullable) DateTime? mfgDate,
@JsonKey(name: 'expiry_date', fromJson: _dateFromJsonNullable) DateTime? expiryDate,
@JsonKey(name: 'storage_location') String? storageLocation,
@JsonKey(
name: 'item_category_id',
readValue: _readGrnItemCategoryId,
fromJson: _intFromJsonNullable,
)
int? itemCategoryId,
@JsonKey(
name: 'item_subcategory_id',
readValue: _readGrnItemSubcategoryId,
fromJson: _intFromJsonNullable,
)
int? itemSubcategoryId,
@JsonKey(name: 'uom_id', fromJson: _intFromJsonNullable) int? uomId,
@JsonKey(name: 'uom_name', readValue: _readUomName) String? uomName,
String? remarks,
}) = _GrnItemModel;
factory GrnItemModel.fromJson(Map<String, dynamic> json) =>
_$GrnItemModelFromJson(json);
}
@freezed
class GrnListQuery with _$GrnListQuery {
const factory GrnListQuery({
@Default(1) int page,
@Default(20) int limit,
String? search,
String? status,
int? poId,
int? vendorId,
int? locationId,
String? dateFrom,
String? dateTo,
}) = _GrnListQuery;
}
const grnStatusOptions = [
('POSTED', 'Posted'),
('CANCELLED', 'Cancelled'),
];
String grnStatusLabel(String? value) {
if (value == null) return '';
return grnStatusOptions
.where((e) => e.$1 == value.toUpperCase())
.map((e) => e.$2)
.firstOrNull ??
value.replaceAll('_', ' ');
}