285 lines
10 KiB
Dart
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('_', ' ');
|
|
}
|