PM activity and cost flow implemented

main_production_spare_part
WaseemAbbasi22 7 days ago
parent b56b998125
commit 7f4a06b0db

@ -254,8 +254,13 @@ class URLs {
static get getRegularVisits => "$_baseUrl/Visit/GetVisits"; // get
static get updateRegularVisits => "$_baseUrl/Visit/UpdateVisits"; // get
static get getPlanPreventiveVisitById => "$_baseUrl/PlanPreventiveVisit/GetPlanPreventiveVisitById"; // get
static get updateVisitByEngineer => "$_baseUrl/PlanPreventiveVisit/UpdateVisitByEngineer"; // get
static get getPlanPreventiveVisitById => "$_baseUrl/PlanPreventiveVisit/GetPlanPreventiveVisitById";
static get updateVisitByEngineer => "$_baseUrl/PlanPreventiveVisit/UpdateVisitByEngineer";
static get createPmActivitySparePart => "$_baseUrl/PlanPreventiveVisit/CreateActivitySparePart";
static get updatePmActivitySparePart => "$_baseUrl/PlanPreventiveVisit/UpdateActivitySparePart";
static get deletePmActivitySparePart => "$_baseUrl/PlanPreventiveVisit/DeleteActivitySparePart";
static get updatePmActivityCost => "$_baseUrl/PlanPreventiveVisit/EngineerUpdateCost";
static get getSingleServiceRequest => "$_baseUrl/return/call/information"; // get
static get getSuppliersAutoComplete => "$_baseUrl/Supplier/GetSuppliersAutoComplete";
@ -604,4 +609,8 @@ class URLs {
static get submitApplicationDemoRequest => "$_baseUrl/DemoUser/CreateDemoUser"; // post submit application demo request// get application demo request history
static get sendDemoUserOtp => "$_baseUrl/DemoUser/EmailVerification"; // post send OTP to user
static get verifyDemoUserOtp => "$_baseUrl/DemoUser/VerifyOtp"; // post verify OTP
// spare part request endpoint.
}

@ -88,9 +88,13 @@ class PartsProvider extends ChangeNotifier {
late Response response;
try {
if (partNo != null) {
response = await ApiManager.instance.post(URLs.getPartNumber, body: {"partNo": partNo, });
response = await ApiManager.instance.post(URLs.getPartNumber, body: {
"partNo": partNo,
});
} else {
response = await ApiManager.instance.post(URLs.getPartNumber, body: {"partName": partName, });
response = await ApiManager.instance.post(URLs.getPartNumber, body: {
"partName": partName,
});
}
List<SparePart> page = [];
if (response.statusCode >= 200 && response.statusCode < 300) {
@ -103,12 +107,15 @@ class PartsProvider extends ChangeNotifier {
return [];
}
}
// Implement this only for spare part request for now show and search on display name ....
Future<List<SparePart>> getPartsListByDisplayName({num? assetId, String? displayName}) async {
late Response response;
try {
response = await ApiManager.instance.post(URLs.getPartNumber, body: {"displayName": displayName, });
response = await ApiManager.instance.post(URLs.getPartNumber, body: {
"displayName": displayName,
});
List<SparePart> page = [];
if (response.statusCode >= 200 && response.statusCode < 300) {
// client's request was successfully received

@ -9,9 +9,12 @@ import 'package:test_sa/controllers/api_routes/api_manager.dart';
import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/models/generic_attachment_model.dart';
import 'package:test_sa/models/helper_data_models/spare_part/activity_spare_part_model.dart';
import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart';
import 'package:test_sa/models/plan_preventive_visit/plan_preventive_visit_model.dart';
import 'package:test_sa/models/ppm/ppm.dart';
import 'package:test_sa/models/ppm/ppm_search.dart';
import 'package:test_sa/models/service_request/spare_parts.dart';
import 'package:test_sa/models/user.dart';
import '../../../new_views/common_widgets/app_lazy_loading.dart';
@ -20,6 +23,15 @@ class PpmProvider extends ChangeNotifier {
// number of items call in each request
final pageItemNumber = 12;
bool _isReadOnlyRequest = false;
bool get isReadOnlyRequest => _isReadOnlyRequest;
set isReadOnlyRequest(bool value) {
_isReadOnlyRequest = value;
notifyListeners();
}
PlanPreventiveVisit? _planPreventiveVisit;
late int _totalTabs = 4;
TabController? tabController;
@ -60,6 +72,14 @@ class PpmProvider extends ChangeNotifier {
nextPage = true;
stateCode = null;
}
List<StoreAvailability> _storeAvailability = [];
List<StoreAvailability> get storeAvailability => _storeAvailability;
set storeAvailability(List<StoreAvailability> value) {
_storeAvailability = value;
notifyListeners();
}
// state code of current request to defied error message
// like 400 customer request failed
@ -78,6 +98,13 @@ class PpmProvider extends ChangeNotifier {
bool isLoading = false;
PpmSearch visitsSearch = PpmSearch();
WorkOrderCostModel? workOrderCostModel;
SparePartHelperModel? sparePartHelperModel;
void updateSparePartHelperModel(SparePartHelperModel? value) {
sparePartHelperModel = value;
notifyListeners();
}
/// return -2 if request in progress
/// return -1 if error happen when sending request
@ -164,11 +191,24 @@ class PpmProvider extends ChangeNotifier {
if (response.statusCode >= 200 && response.statusCode < 300) {
ppmPlanAttachments = [];
planPreventiveVisit = PlanPreventiveVisit.fromJson(json.decode(response.body)["data"]);
// Set isReadOnly based on visit status (1 or 3 means closed/completed)
if (planPreventiveVisit?.visitStatus?.value! == 1 || planPreventiveVisit?.visitStatus?.value == 3) {
isReadOnly = true;
} else {
isReadOnly = false;
}
// Set isReadOnlyRequest based on visit status - same logic as CMDetailProvider
//Need to confirm this with backend Status values: 3 = Closed, 5 = Completed, 6 = Cancelled (adjust based on your PM status values)
if (planPreventiveVisit?.visitStatus?.value == 3 ||
planPreventiveVisit?.visitStatus?.value == 5 ||
planPreventiveVisit?.visitStatus?.value == 6) {
isReadOnlyRequest = true;
} else {
isReadOnlyRequest = false;
}
isLoading = false;
notifyListeners();
return planPreventiveVisit;
@ -364,4 +404,123 @@ class PpmProvider extends ChangeNotifier {
return true;
}
Future<bool> createActivitySparePart() async {
isLoading = true;
Response response;
try {
response = await ApiManager.instance.post(
URLs.createPmActivitySparePart,
body: sparePartHelperModel!.toPMJson(),
);
stateCode = response.statusCode;
isLoading = false;
notifyListeners();
if (stateCode == 200) {
return true;
}
return false;
} catch (error) {
isLoading = false;
stateCode = -1;
notifyListeners();
return false;
}
}
Future<bool> updateActivitySparePart() async {
isLoading = true;
Response response;
try {
response = await ApiManager.instance.post(URLs.updatePmActivitySparePart, body: sparePartHelperModel!.toPMJson());
stateCode = response.statusCode;
isLoading = false;
notifyListeners();
if (stateCode == 200) {
return true;
}
return false;
} catch (error) {
isLoading = false;
stateCode = -1;
notifyListeners();
return false;
}
}
Future<int> deleteActivitySparePart({required int id}) async {
isLoading = true;
notifyListeners();
try {
final response = await ApiManager.instance.delete('${URLs.deletePmActivitySparePart}?activitySparePartPPMId=$id', body: {});
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
}
isLoading = false;
notifyListeners();
return response.statusCode;
} catch (error) {
isLoading = false;
notifyListeners();
return -1;
}
}
Future<bool> engineerUpdateCost() async {
isLoading = true;
Response response;
try {
response = await ApiManager.instance.post(URLs.updatePmActivityCost, body: workOrderCostModel!.toPMCostJson());
stateCode = response.statusCode;
isLoading = false;
notifyListeners();
if (stateCode == 200) {
planPreventiveVisit = PlanPreventiveVisit.fromJson(json.decode(response.body)["data"]);
// Update isReadOnlyRequest after fetching updated data
if (planPreventiveVisit?.visitStatus?.value == 3 ||
planPreventiveVisit?.visitStatus?.value == 5 ||
planPreventiveVisit?.visitStatus?.value == 6) {
isReadOnlyRequest = true;
} else {
isReadOnlyRequest = false;
}
notifyListeners();
return true;
}
return false;
} catch (error) {
isLoading = false;
stateCode = -1;
notifyListeners();
return false;
}
}
Future<void> getStoreAvailabilityById({required num? partId}) async {
try {
isLoading = true;
notifyListeners();
final response = await ApiManager.instance.post(
URLs.getSparePartAvailableQuantity + "?PartId=$partId",
body: {},
);
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
final List<dynamic> data = json.decode(response.body)["data"];
storeAvailability = data.map((item) => StoreAvailability.fromJson(item)).toList();
}
isLoading = false;
notifyListeners();
} catch (e) {
log("getStoreAvalivility [error] : $e");
isLoading = false;
notifyListeners();
}
}
}

@ -4,6 +4,7 @@ import 'package:test_sa/models/service_request/spare_parts.dart';
class SparePartHelperModel {
int? id;
int? workOrderId;
String? pmVisitId;
int? partCatalogItemId;
int? activityStatusId;
SparePart? sparePart;
@ -12,11 +13,13 @@ class SparePartHelperModel {
num? installQty;
num? returnQty;
String? comment;
List<SparePartAttachments>? sparePartAttachments;
List<SparePartAttachments> sparePartAttachments;
SparePartHelperModel({
this.id,
this.workOrderId,
this.pmVisitId,
this.partCatalogItemId,
this.quantity,
this.installQty,
@ -24,9 +27,9 @@ class SparePartHelperModel {
this.sparePart,
this.activityStatusId,
this.activityStatus,
this.comment,this.sparePartAttachments,
});
this.comment,
List<SparePartAttachments>? sparePartAttachments,
}) : sparePartAttachments = sparePartAttachments ?? [];
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
@ -40,9 +43,38 @@ class SparePartHelperModel {
data['activityStatusId'] = activityStatus?.id;
}
data['comment'] = comment;
if (sparePartAttachments != null) {
data['acitiySparePartAttachments'] =
sparePartAttachments!.map((v) => v.toJson()).toList();
if (sparePartAttachments.isNotEmpty) {
data['acitiySparePartAttachments'] = sparePartAttachments!.map((v) => v.toJson()).toList();
}
return data;
}
//{
// "id": 0,
// "planPreventiveVisitId": 0,
// "partCatalogItemId": 0,
// "quantity": 0,
// "comment": "string",
// "acitiySparePartAttachments": [
// {
// "id": 0,
// "name": "string",
// "originalName": "string",
// "attachmentTypeId": 0,
// "attachmentName": "string",
// "attachmentURL": "string"
// }
// ]
// }
Map<String, dynamic> toPMJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['planPreventiveVisitId'] = pmVisitId;
data['partCatalogItemId'] = sparePart?.id;
data['quantity'] = quantity;
data['comment'] = comment;
if (sparePartAttachments.isNotEmpty) {
data['acitiySparePartAttachments'] = sparePartAttachments!.map((v) => v.toJson()).toList();
}
return data;
}

@ -178,4 +178,7 @@ class WorkOrderCostModel {
Map<String, dynamic> toJson() {
return {'workOrderId': workOrderId, 'sparePartCost': sparePartCost, 'laborCost': labourCost, 'travelCost': travelCost, 'qAmount': qAmount, 'prNo': prNo, 'poNo': poNo,'mrNo':mrNo,'exchangeCost':exchangeCost};
}
Map<String, dynamic> toPMCostJson() {
return {'planPreventiveVisitId': workOrderId, 'sparePartCost': sparePartCost, 'laborCost': labourCost, 'travelCost': travelCost, 'qAmount': qAmount, 'prNo': prNo, 'poNo': poNo,'mrNo':mrNo,'exchangeCost':exchangeCost};
}
}

@ -1,9 +1,11 @@
import 'dart:developer';
import 'dart:io';
import 'package:flutter/widgets.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/models/device/asset.dart';
import 'package:test_sa/models/helper_data_models/spare_part/activity_spare_part_model.dart';
import 'package:test_sa/models/lookup.dart';
import 'package:test_sa/models/service_request/supplier_details.dart';
import 'package:test_sa/models/timer_model.dart';
@ -49,10 +51,20 @@ class PlanPreventiveVisit {
List<PreventiveVisitKits>? preventiveVisitKits;
List<PreventiveVisitTimers>? preventiveVisitTimers;
List<PreventiveVisitSuppliers>? preventiveVisitSuppliers;
List<PMSparePartActivityModel>? activities;
num? sparePartCost;
num? labourCost;
num? travelCost;
num? qAmount;
String? prNo;
String? poNo;
String? mrNo;
num? exchangeCost;
TimerModel? tbsTimer = TimerModel();
List<TimerModel>? timerModelList = [];
TimerModel? ppMTimePicker;
PlanPreventiveVisit(
{this.id,
this.visitNo,
@ -95,6 +107,15 @@ class PlanPreventiveVisit {
this.preventiveVisitTimers,
this.timerModelList,
this.ppMTimePicker,
this.activities,
this.labourCost,
this.sparePartCost,
this.travelCost,
this.qAmount,
this.prNo,
this.poNo,
this.mrNo,
this.exchangeCost,
this.preventiveVisitSuppliers});
PlanPreventiveVisit.fromJson(Map<String, dynamic> json) {
@ -131,6 +152,14 @@ class PlanPreventiveVisit {
safety = json['safety'] != null ? Lookup.fromJson(json['safety']) : null;
engSignature = json['engSignature'];
nurseSignature = json['nurseSignature'];
travelCost= json['travelCost'];
sparePartCost= json['sparePartCost'];
labourCost =json['laborCost'];
qAmount= json['qAmount'];
prNo= json['prNo'];
poNo= json['poNo'];
mrNo= json['mrNo'];
exchangeCost= json['exchangeCost'];
if (json['preventiveVisitAttachments'] != null) {
preventiveVisitAttachments = <PreventiveVisitAttachments>[];
json['preventiveVisitAttachments'].forEach((v) {
@ -167,6 +196,13 @@ class PlanPreventiveVisit {
preventiveVisitSuppliers!.add(PreventiveVisitSuppliers.fromJson(v));
});
}
if (json['activitySparePartsPPM'] != null) {
log("activitySparePartsPPM: ${json['activitySparePartsPPM']}");
activities = <PMSparePartActivityModel>[];
json['activitySparePartsPPM'].forEach((v) {
activities!.add(PMSparePartActivityModel.fromJson(v));
});
}
}
Map<String, dynamic> toJson({required int status}) {
@ -183,50 +219,6 @@ class PlanPreventiveVisit {
data['safetyId'] = safety?.id;
data['engSignature'] = engSignature;
data['nurseSignature'] = nurseSignature;
//
// if (asset != null) {
// data['asset'] = asset!.toJson();
// }
// data['visitNo'] = visitNo;
// data['planNo'] = planNo;
// data['planName'] = planName;
// data['nextPMDate'] = nextPMDate;
// data['assetName'] = assetName;
// data['model'] = model;
// data['manufacturer'] = manufacturer;
// data['supplierName'] = supplierName;
// data['siteName'] = siteName;
// data['buildingName'] = buildingName;
// data['floorName'] = floorName;
// data['departmentName'] = departmentName;
// data['roomName'] = roomName;
// data['fromDate'] = fromDate;
// data['toDate'] = toDate;
// if (assignedEmployee != null) {
// data['assignedEmployee'] = assignedEmployee!.toJson();
// }
// data['acutalDateOfVisit'] = acutalDateOfVisit;
// if (typeOfService != null) {
// data['typeOfService'] = typeOfService!.toJson();
// }
// if (visitStatus != null) {
// data['visitStatus'] = visitStatus!.toJson();
// }
// data['travelingHours'] = travelingHours;
// data['comments'] = comments;
// data['executionTimeFrame'] = executionTimeFrame;
// if (taskStatus != null) {
// data['taskStatus'] = taskStatus!.toJson();
// }
// if (deviceStatus != null) {
// data['deviceStatus'] = deviceStatus!.toJson();
// }
// data['assetAvailability'] = assetAvailability;
// if (safety != null) {
// data['safety'] = safety!.toJson();
// }
// data['engSignature'] = engSignature;
// data['nurseSignature'] = nurseSignature;
if (preventiveVisitAttachments != null) {
data['preventiveVisitAttachments'] = preventiveVisitAttachments!.map((v) => v.toJson()).toList();
@ -246,12 +238,10 @@ class PlanPreventiveVisit {
if (preventiveVisitSuppliers != null) {
data['preventiveVisitSuppliers'] = preventiveVisitSuppliers!.map((v) => v.toJson()).toList();
}
return data;
if (activities != null) {
data['activitySparePartsPPM'] = activities!.map((v) => v.toJson()).toList();
}
bool _isLocalUrl(String url) {
if (url.isEmpty != false) return false;
return url.startsWith("/") || url.startsWith("file://") || url.substring(1).startsWith(':\\');
return data;
}
Future<bool> validate(BuildContext context) async {
@ -290,276 +280,6 @@ class PlanPreventiveVisit {
}
}
// class PlanPreventiveVisit {
// String? id;
// String? visitNo;
// Asset? asset;
// String? planNo;
// String? planName;
// String? nextPMDate;
// String? assetName;
// String? model;
// String? manufacturer;
// String? supplierName;
// String? siteName;
// String? buildingName;
// String? floorName;
// String? departmentName;
// String? roomName;
// String? fromDate;
// String? toDate;
// AssignedEmployee? assignedEmployee;
// String? acutalDateOfVisit;
// TypeOfService? typeOfService;
// VisitStatus? visitStatus;
// String? travelingHours;
// String? comments;
// int? executionTimeFrame;
// Lookup? taskStatus;
// String? deviceStatus;
// Lookup? assetAvailability;
// Lookup? safety;
// String? engSignature;
// String? nurseSignature;
// List<PreventiveVisitChecklists>? preventiveVisitAttachments;
// List<PreventiveVisitChecklists>? preventiveVisitCalibrations;
// List<PreventiveVisitChecklists>? preventiveVisitChecklists;
// List<PreventiveVisitChecklists>? preventiveVisitKits;
// List<PreventiveVisitTimers>? preventiveVisitTimers;
// List<PreventiveVisitChecklists>? preventiveVisitSuppliers;
// TimerModel? tbsTimer = TimerModel();
//
// PlanPreventiveVisit(
// {this.id,
// this.visitNo,
// this.asset,
// this.planNo,
// this.planName,
// this.nextPMDate,
// this.assetName,
// this.model,
// this.manufacturer,
// this.supplierName,
// this.siteName,
// this.buildingName,
// this.floorName,
// this.departmentName,
// this.roomName,
// this.fromDate,
// this.toDate,
// this.assignedEmployee,
// this.acutalDateOfVisit,
// this.typeOfService,
// this.visitStatus,
// this.travelingHours,
// this.comments,
// this.executionTimeFrame,
// this.taskStatus,
// this.deviceStatus,
// this.assetAvailability,
// this.safety,
// this.engSignature,
// this.nurseSignature,
// this.preventiveVisitAttachments,
// this.preventiveVisitCalibrations,
// this.preventiveVisitChecklists,
// this.preventiveVisitKits,
// this.preventiveVisitTimers,
// this.preventiveVisitSuppliers});
//
// PlanPreventiveVisit.fromJson(Map<String, dynamic> json) {
// id = json['id'];
// visitNo = json['visitNo'];
// asset = json['asset'] != null ? Asset.fromJson(json['asset']) : null;
// planNo = json['planNo'];
// planName = json['planName'];
// nextPMDate = json['nextPMDate'];
// assetName = json['assetName'];
// model = json['model'];
// manufacturer = json['manufacturer'];
// supplierName = json['supplierName'];
// siteName = json['siteName'];
// buildingName = json['buildingName'];
// floorName = json['floorName'];
// departmentName = json['departmentName'];
// roomName = json['roomName'];
// fromDate = json['fromDate'];
// toDate = json['toDate'];
// assignedEmployee = json['assignedEmployee'] != null ? AssignedEmployee.fromJson(json['assignedEmployee']) : null;
// acutalDateOfVisit = json['acutalDateOfVisit'];
// typeOfService = json['typeOfService'] != null ? TypeOfService.fromJson(json['typeOfService']) : null;
// visitStatus = json['visitStatus'] != null ? VisitStatus.fromJson(json['visitStatus']) : null;
// travelingHours = json['travelingHours'];
// comments = json['comments'];
// executionTimeFrame = json['executionTimeFrame'];
// taskStatus = json['taskStatus'];
// deviceStatus = json['deviceStatus'];
// assetAvailability = json['assetAvailability'];
// safety = json['safety'];
// engSignature = json['engSignature'];
// nurseSignature = json['nurseSignature'];
// if (json['preventiveVisitAttachments'] != null) {
// preventiveVisitAttachments = <PreventiveVisitChecklists>[];
// json['preventiveVisitAttachments'].forEach((v) {
// preventiveVisitAttachments!.add(PreventiveVisitChecklists.fromJson(v));
// });
// }
// if (json['preventiveVisitCalibrations'] != null) {
// preventiveVisitCalibrations = <PreventiveVisitChecklists>[];
// json['preventiveVisitCalibrations'].forEach((v) {
// preventiveVisitCalibrations!.add( PreventiveVisitChecklists.fromJson(v));
// });
// }
// if (json['preventiveVisitChecklists'] != null) {
// preventiveVisitChecklists = <PreventiveVisitChecklists>[];
// json['preventiveVisitChecklists'].forEach((v) {
// preventiveVisitChecklists!.add( PreventiveVisitChecklists.fromJson(v));
// });
// }
// if (json['preventiveVisitKits'] != null) {
// preventiveVisitKits = <PreventiveVisitChecklists>[];
// json['preventiveVisitKits'].forEach((v) {
// preventiveVisitKits!.add( PreventiveVisitChecklists.fromJson(v));
// });
// }
// if (json['preventiveVisitTimers'] != null) {
// preventiveVisitTimers = <PreventiveVisitTimers>[];
// json['preventiveVisitTimers'].forEach((v) {
// preventiveVisitTimers!.add( PreventiveVisitTimers.fromJson(v));
// });
// }
// if (json['preventiveVisitSuppliers'] != null) {
// preventiveVisitSuppliers = <PreventiveVisitChecklists>[];
// json['preventiveVisitSuppliers'].forEach((v) {
// preventiveVisitSuppliers!.add( PreventiveVisitChecklists.fromJson(v));
// });
// }
// }
//
// Map<String, dynamic> toJson() {
// final Map<String, dynamic> data = Map<String, dynamic>();
// data['id'] = this.id;
// data['visitNo'] = this.visitNo;
// if (this.asset != null) {
// data['asset'] = this.asset!.toJson();
// }
// data['planNo'] = this.planNo;
// data['planName'] = this.planName;
// data['nextPMDate'] = this.nextPMDate;
// data['assetName'] = this.assetName;
// data['model'] = this.model;
// data['manufacturer'] = this.manufacturer;
// data['supplierName'] = this.supplierName;
// data['siteName'] = this.siteName;
// data['buildingName'] = this.buildingName;
// data['floorName'] = this.floorName;
// data['departmentName'] = this.departmentName;
// data['roomName'] = this.roomName;
// data['fromDate'] = this.fromDate;
// data['toDate'] = this.toDate;
// if (this.assignedEmployee != null) {
// data['assignedEmployee'] = this.assignedEmployee!.toJson();
// }
// data['acutalDateOfVisit'] = this.acutalDateOfVisit;
// if (this.typeOfService != null) {
// data['typeOfService'] = this.typeOfService!.toJson();
// }
// if (this.visitStatus != null) {
// data['visitStatus'] = this.visitStatus!.toJson();
// }
// data['travelingHours'] = this.travelingHours;
// data['comments'] = this.comments;
// data['executionTimeFrame'] = this.executionTimeFrame;
// data['taskStatus'] = this.taskStatus;
// data['deviceStatus'] = this.deviceStatus;
// data['assetAvailability'] = this.assetAvailability;
// data['safety'] = this.safety;
// data['engSignature'] = this.engSignature;
// data['nurseSignature'] = this.nurseSignature;
// if (this.preventiveVisitAttachments != null) {
// data['preventiveVisitAttachments'] = this.preventiveVisitAttachments!.map((v) => v.toJson()).toList();
// }
// if (this.preventiveVisitCalibrations != null) {
// data['preventiveVisitCalibrations'] = this.preventiveVisitCalibrations!.map((v) => v.toJson()).toList();
// }
// if (this.preventiveVisitChecklists != null) {
// data['preventiveVisitChecklists'] = this.preventiveVisitChecklists!.map((v) => v.toJson()).toList();
// }
// if (this.preventiveVisitKits != null) {
// data['preventiveVisitKits'] = this.preventiveVisitKits!.map((v) => v.toJson()).toList();
// }
// if (this.preventiveVisitTimers != null) {
// data['preventiveVisitTimers'] = this.preventiveVisitTimers!.map((v) => v.toJson()).toList();
// }
// if (this.preventiveVisitSuppliers != null) {
// data['preventiveVisitSuppliers'] = this.preventiveVisitSuppliers!.map((v) => v.toJson()).toList();
// }
// return data;
// }
//
// bool _isLocalUrl(String url) {
// if (url.isEmpty != false) return false;
// return url.startsWith("/") || url.startsWith("file://") || url.substring(1).startsWith(':\\');
// }
//
// Future<bool> validate(BuildContext context) async {
// if (visitStatus?.id == null) {
// await Fluttertoast.showToast(msg: "${context.translation.youHaveToSelect} ${context.translation.status}");
// return false;
// }
// if (acutalDateOfVisit == null) {
// await Fluttertoast.showToast(msg: "${context.translation.youHaveToSelect} ${context.translation.actualDate}");
// return false;
// }
// // if (expectedDate == null) {
// // await Fluttertoast.showToast(msg: "${context.translation.youHaveToSelect} ${context.translation.visitDate}");
// // return false;
// // }
// if (tbsTimer?.startAt == null) {
// await Fluttertoast.showToast(msg: "Working Hours Required");
// return false;
// }
// if (tbsTimer?.endAt == null) {
// await Fluttertoast.showToast(msg: "Please Stop The Timer");
// return false;
// }
//
// // if (externalEngineerTimer?.startAt != null && externalEngineerTimer?.endAt == null) {
// // await Fluttertoast.showToast(msg: "Please Stop External Engineer Timer");
// // return false;
// // }
//
// return true;
// }
//
// void removeEmptyObjects() {
// // if (vCalibrationTools?.isNotEmpty ?? false) vCalibrationTools!.removeWhere((element) => element.assetId == null && element.calibrationDateOfTesters == null);
// // if (vKits?.isNotEmpty ?? false) vKits!.removeWhere((element) => element.partName == null && element.partNumber == null);
// }
// }
// class Asset {
// num? id;
// String? assetNumber;
// String? assetSerialNo;
//
// Asset({this.id, this.assetNumber, this.assetSerialNo});
//
// Asset.fromJson(Map<String, dynamic> json) {
// id = json['id'];
// assetNumber = json['assetNumber'];
// assetSerialNo = json['assetSerialNo'];
// }
//
// Map<String, dynamic> toJson() {
// final Map<String, dynamic> data = Map<String, dynamic>();
// data['id'] = this.id;
// data['assetNumber'] = this.assetNumber;
// data['assetSerialNo'] = this.assetSerialNo;
// return data;
// }
// }
class AssignedEmployee {
String? userId;
String? userName;
@ -588,50 +308,6 @@ class AssignedEmployee {
}
}
// class Lookup {
// int? id;
// String? name;
// int? value;
//
// Lookup({this.id, this.name, this.value});
//
// Lookup.fromJson(Map<String, dynamic> json) {
// id = json['id'];
// name = json['name'];
// value = json['value'];
// }
//
// Map<String, dynamic> toJson() {
// final Map<String, dynamic> data = Map<String, dynamic>();
// data['id'] = this.id;
// data['name'] = this.name;
// data['value'] = this.value;
// return data;
// }
// }
// class VisitStatus {
// int? id;
// String? name;
// int? value;
//
// VisitStatus({this.id, this.name, this.value});
//
// VisitStatus.fromJson(Map<String, dynamic> json) {
// id = json['id'];
// name = json['name'];
// value = json['value'];
// }
//
// Map<String, dynamic> toJson() {
// final Map<String, dynamic> data = Map<String, dynamic>();
// data['id'] = this.id;
// data['name'] = this.name;
// data['value'] = this.value;
// return data;
// }
// }
class PreventiveVisitChecklists {
int? id;
Lookup? taskStatus;
@ -788,171 +464,6 @@ class PreventiveVisitSuppliers {
}
}
// class Supplier {
// int? id;
// String? suppliername;
// String? name;
// String? website;
// String? email;
// String? code;
// int? suppNo;
// String? suppStatusId;
// String? cityId;
// String? person;
// String? comment;
// String? zipcode;
// String? contact;
// List<String>? telephones;
// List<String>? faxes;
// List<String>? addresses;
// List<String>? attachments;
// List<SuppPersons>? suppPersons;
// List<String>? suppTCodes;
//
// Supplier(
// {this.id,
// this.suppliername,
// this.name,
// this.website,
// this.email,
// this.code,
// this.suppNo,
// this.suppStatusId,
// this.cityId,
// this.person,
// this.comment,
// this.zipcode,
// this.contact,
// this.telephones,
// this.faxes,
// this.addresses,
// this.attachments,
// this.suppPersons,
// this.suppTCodes});
//
// Supplier.fromJson(Map<String, dynamic> json) {
// id = json['id'];
// suppliername = json['suppliername'];
// name = json['name'];
// website = json['website'];
// email = json['email'];
// code = json['code'];
// suppNo = json['suppNo'];
// suppStatusId = json['suppStatusId'];
// cityId = json['cityId'];
// person = json['person'];
// comment = json['comment'];
// zipcode = json['zipcode'];
// contact = json['contact'];
// if (json['telephones'] != null) {
// telephones = <String>[];
// json['telephones'].forEach((v) {
// telephones!.add(v);
// });
// }
// if (json['faxes'] != null) {
// faxes = <String>[];
// json['faxes'].forEach((v) {
// faxes!.add(v);
// });
// }
// if (json['addresses'] != null) {
// addresses = <String>[];
// json['addresses'].forEach((v) {
// addresses!.add(v);
// });
// }
// if (json['attachments'] != null) {
// attachments = <String>[];
// json['attachments'].forEach((v) {
// attachments!.add(v);
// });
// }
// if (json['suppPersons'] != null) {
// suppPersons = <SuppPersons>[];
// json['suppPersons'].forEach((v) {
// suppPersons!.add(SuppPersons.fromJson(v));
// });
// }
// if (json['suppTCodes'] != null) {
// suppTCodes = <String>[];
// json['suppTCodes'].forEach((v) {
// suppTCodes!.add(v);
// });
// }
// }
//
// Map<String, dynamic> toJson() {
// final Map<String, dynamic> data = <String, dynamic>{};
// data['id'] = id;
// data['suppliername'] = suppliername;
// data['name'] = name;
// data['website'] = website;
// data['email'] = email;
// data['code'] = code;
// data['suppNo'] = suppNo;
// data['suppStatusId'] = suppStatusId;
// data['cityId'] = cityId;
// data['person'] = person;
// data['comment'] = comment;
// data['zipcode'] = zipcode;
// data['contact'] = contact;
// if (telephones != null) {
// data['telephones'] = telephones!.map((v) => v).toList();
// }
// if (faxes != null) {
// data['faxes'] = faxes!.map((v) => v).toList();
// }
// if (addresses != null) {
// data['addresses'] = addresses!.map((v) => v).toList();
// }
// if (attachments != null) {
// data['attachments'] = attachments!.map((v) => v).toList();
// }
// if (suppPersons != null) {
// data['suppPersons'] = suppPersons!.map((v) => v.toJson()).toList();
// }
// if (suppTCodes != null) {
// data['suppTCodes'] = suppTCodes!.map((v) => v).toList();
// }
// return data;
// }
// }
// class SuppPersons {
// int? id;
// int? supplierId;
// String? personName;
// int? personRoleId;
// String? contact;
// String? externalEngCode;
// String? email;
//
// SuppPersons({this.id, this.supplierId, this.personName, this.personRoleId, this.contact, this.externalEngCode, this.email});
//
// SuppPersons.fromJson(Map<String, dynamic> json) {
// id = json['id'];
// supplierId = json['supplierId'];
// personName = json['personName'];
// personRoleId = json['personRoleId'];
// contact = json['contact'];
// externalEngCode = json['externalEngCode'];
// email = json['email'];
// }
//
// Map<String, dynamic> toJson() {
// final Map<String, dynamic> data = Map<String, dynamic>();
// data['id'] = this.id;
// data['supplierId'] = this.supplierId;
// data['personName'] = this.personName;
// data['personRoleId'] = this.personRoleId;
// data['contact'] = this.contact;
// data['externalEngCode'] = this.externalEngCode;
// data['email'] = this.email;
// return data;
// }
// }
class PreventiveVisitKits {
int? id;
PartCatalogItem? partCatalogItem;
@ -999,3 +510,80 @@ class PartCatalogItem {
return data;
}
}
class PMSparePartActivityModel {
int? id;
PartCatalogItem? partCatalogItem;
Lookup? activityStatus;
double? quantity;
double? installQty;
double? returnQty;
String? comment;
dynamic price;
List<SparePartAttachments> activitySparePartPPMAttachments;
PMSparePartActivityModel({
this.id,
this.partCatalogItem,
this.activityStatus,
this.quantity,
this.installQty,
this.returnQty,
this.comment,
this.price,
List<SparePartAttachments>? activitySparePartPPMAttachments,
}) : activitySparePartPPMAttachments = activitySparePartPPMAttachments ?? [];
PMSparePartActivityModel.fromJson(Map<String, dynamic> json) : activitySparePartPPMAttachments = [] {
id = json['id'];
partCatalogItem = json['partCatalogItem'] != null ? PartCatalogItem.fromJson(json['partCatalogItem']) : null;
activityStatus = json['activityStatus'] != null ? Lookup.fromJson(json['activityStatus']) : null;
quantity = json['quantity']?.toDouble();
installQty = json['installQty']?.toDouble();
returnQty = json['returnQty']?.toDouble();
comment = json['comment'];
price = json['price'];
// Handle typo in API response: "acitiySparePartPPMAttachments" instead of "activitySparePartPPMAttachments"
final attachmentsKey = json.containsKey('acitiySparePartPPMAttachments')
? 'acitiySparePartPPMAttachments'
: 'activitySparePartPPMAttachments';
activitySparePartPPMAttachments = json[attachmentsKey] == null
? []
: List.from(json[attachmentsKey]).map((e) => SparePartAttachments.fromJson(e)).toList();
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = {};
data['id'] = id;
if (partCatalogItem != null) {
data['partCatalogItem'] = partCatalogItem!.toJson();
}
if (activityStatus != null) {
data['activityStatus'] = activityStatus!.toJson();
}
data['quantity'] = quantity;
data['installQty'] = installQty;
data['returnQty'] = returnQty;
data['comment'] = comment;
data['price'] = price;
data['activitySparePartPPMAttachments'] = activitySparePartPPMAttachments
.map(
(v) => v.toJson(),
)
.toList();
return data;
}
}

@ -251,7 +251,6 @@ class _ActivitiesListViewState extends State<ActivitiesListView> {
label: context.translation.assistantEmployee,
value: activity.activityMaintenance?.assistantEmployees?.first.user?.userName,
),
] else ...[
InfoTextWidget(
label: context.translation.assignedEmployee,
@ -344,7 +343,7 @@ class _ActivitiesListViewState extends State<ActivitiesListView> {
}
void editSparePartRequest({required BuildContext context, required CMDetailProvider requestDetailProvider, required Activities activity}) async {
requestDetailProvider.sparePartHelperModel = SparePartHelperModel(
final model = SparePartHelperModel(
id: activity.id,
workOrderId: requestDetailProvider.currentWorkOrder?.data?.requestId,
comment: activity.activitySparePart?.comment,
@ -354,19 +353,20 @@ class _ActivitiesListViewState extends State<ActivitiesListView> {
id: activity.activitySparePart?.partCatalogItem?.id,
partName: activity.activitySparePart?.partCatalogItem?.partName,
partNo: activity.activitySparePart?.partCatalogItem?.partNumber,
oracleCode: activity.activitySparePart?.partCatalogItem?.oracleCode),
oracleCode: activity.activitySparePart?.partCatalogItem?.oracleCode,
),
quantity: activity.activitySparePart?.quantity,
installQty: activity.activitySparePart?.installQty,
returnQty: activity.activitySparePart?.returnQty,
activityStatusId: activity.activityStatus?.id,
);
requestDetailProvider.updateSparePartHelperModel(requestDetailProvider.sparePartHelperModel);
// Navigate and let SparePartRequest._initializeModel() handle provider setup
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => SparePartRequest(
model: requestDetailProvider.sparePartHelperModel,
model: model,
provider: requestDetailProvider,
fromCm: true,
),
));
}

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/app_strings/app_asset.dart';
import 'package:test_sa/controllers/providers/api/ppm_provider.dart';
import 'package:test_sa/controllers/providers/api/service_requests_provider.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart';
@ -671,14 +672,20 @@ class ServiceRequestBottomSheet {
CMDetailProvider requestDetailProvider = Provider.of<CMDetailProvider>(context, listen: false);
switch (index) {
case 0:
requestDetailProvider.sparePartHelperModel = SparePartHelperModel();
// Create fresh model - SparePartRequest will handle setting it in provider
final model = SparePartHelperModel(
id: 0,
workOrderId: requestDetailProvider.currentWorkOrder?.data?.requestId,
);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SparePartRequest(
model: requestDetailProvider.sparePartHelperModel,
model: model,
provider: requestDetailProvider,
)),
fromCm: true,
),
),
);
break;
case 1:
@ -772,7 +779,7 @@ class ServiceRequestBottomSheet {
}));
}
static Future addAnotherSpareRequestBottomSheet({required BuildContext context}) {
static Future addAnotherSpareRequestBottomSheet({required BuildContext context, bool fromCm = false}) {
return buildBottomSheetParent(
context: context,
childWidget: Consumer<ServiceRequestsProvider>(
@ -808,16 +815,23 @@ class ServiceRequestBottomSheet {
buttonColor: AppColor.primary10,
loading: false,
onPressed: () {
CMDetailProvider provider = Provider.of<CMDetailProvider>(context, listen: false);
dynamic provider = fromCm ? Provider.of<CMDetailProvider>(context, listen: false) : Provider.of<PpmProvider>(context, listen: false);
Navigator.pop(context);
provider.sparePartHelperModel = SparePartHelperModel();
final model = SparePartHelperModel(
id: 0,
workOrderId: fromCm? provider.currentWorkOrder?.data?.requestId:null,
pmVisitId: !fromCm? provider.planPreventiveVisit?.id:null,
);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SparePartRequest(
model: provider.sparePartHelperModel,
model: model,
provider: provider,
)),
fromCm: fromCm,
),
),
);
},
).expanded,

@ -355,7 +355,6 @@ class _ServiceRequestDetailViewState extends State<ServiceRequestDetailView> {
context,
MaterialPageRoute(
builder: (context) => CostDetailFormScreen(
isEdit: true,
model: provider.workOrderCostModel,
provider: provider,
)));

@ -1,9 +1,10 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart';
import 'package:test_sa/modules/cm_module/cm_detail_provider.dart';
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
@ -13,13 +14,11 @@ import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
class CostDetailFormScreen extends StatefulWidget {
static const String id = "/cost-detail-form-screen";
final bool isEdit;
final WorkOrderCostModel? model;
final CMDetailProvider provider;
final dynamic provider; // CMDetailProvider or PpmProvider
const CostDetailFormScreen({
Key? key,
required this.isEdit,
required this.model,
required this.provider,
}) : super(key: key);
@ -28,13 +27,29 @@ class CostDetailFormScreen extends StatefulWidget {
_CostDetailFormScreenState createState() => _CostDetailFormScreenState();
}
class _CostDetailFormScreenState extends State<CostDetailFormScreen> with TickerProviderStateMixin {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
class _CostDetailFormScreenState extends State<CostDetailFormScreen> {
late WorkOrderCostModel _localModel;
@override
void initState() {
super.initState();
_localModel = WorkOrderCostModel(
workOrderId: widget.model?.workOrderId,
labourCost: widget.model?.labourCost,
sparePartCost: widget.model?.sparePartCost,
travelCost: widget.model?.travelCost,
qAmount: widget.model?.qAmount,
prNo: widget.model?.prNo,
poNo: widget.model?.poNo,
mrNo: widget.model?.mrNo,
exchangeCost: widget.model?.exchangeCost,
);
log('Initialized Cost Model: ${_localModel.toJson()}');
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: const DefaultAppBar(title: "Cost Details"),
body: ListenableBuilder(
listenable: widget.provider,
@ -43,7 +58,8 @@ class _CostDetailFormScreenState extends State<CostDetailFormScreen> with Ticker
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SingleChildScrollView(
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
@ -51,20 +67,17 @@ class _CostDetailFormScreenState extends State<CostDetailFormScreen> with Ticker
AppTextFormField(
labelText: "Spare Part Cost",
backgroundColor: AppColor.neutral100,
initialValue: widget.model?.sparePartCost?.toString(),
initialValue: _localModel.sparePartCost?.toString() ?? '0',
textAlign: TextAlign.center,
enable: false,
labelStyle: AppTextStyles.textFieldLabelStyle,
textInputType: TextInputType.number,
showShadow: false,
onChange: (value) {
widget.model?.sparePartCost = num.parse(value);
},
style: Theme.of(context).textTheme.titleMedium,
),
8.height,
AppTextFormField(
initialValue: widget.model?.labourCost?.toString(),
initialValue: _localModel.labourCost?.toString() ?? '',
textAlign: TextAlign.center,
labelText: "Labor Cost",
backgroundColor: AppColor.fieldBgColor(context),
@ -72,7 +85,7 @@ class _CostDetailFormScreenState extends State<CostDetailFormScreen> with Ticker
textInputType: const TextInputType.numberWithOptions(decimal: true),
showShadow: false,
onChange: (value) {
widget.model?.labourCost = num.parse(value);
_localModel.labourCost = num.tryParse(value);
},
style: Theme.of(context).textTheme.titleMedium,
),
@ -81,12 +94,12 @@ class _CostDetailFormScreenState extends State<CostDetailFormScreen> with Ticker
labelText: "Travel Cost",
backgroundColor: AppColor.fieldBgColor(context),
labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)),
initialValue: widget.model?.travelCost?.toString(),
initialValue: _localModel.travelCost?.toString() ?? '',
textAlign: TextAlign.center,
textInputType: const TextInputType.numberWithOptions(decimal: true),
showShadow: false,
onChange: (value) {
widget.model?.travelCost = num.parse(value);
_localModel.travelCost = num.tryParse(value);
},
style: Theme.of(context).textTheme.titleMedium,
),
@ -95,12 +108,12 @@ class _CostDetailFormScreenState extends State<CostDetailFormScreen> with Ticker
labelText: "Quot Amount",
backgroundColor: AppColor.fieldBgColor(context),
labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)),
initialValue: widget.model?.qAmount?.toString(),
initialValue: _localModel.qAmount?.toString() ?? '',
textAlign: TextAlign.center,
textInputType: const TextInputType.numberWithOptions(decimal: true),
showShadow: false,
onChange: (value) {
widget.model?.qAmount = num.parse(value);
_localModel.qAmount = num.tryParse(value);
},
style: Theme.of(context).textTheme.titleMedium,
),
@ -109,12 +122,12 @@ class _CostDetailFormScreenState extends State<CostDetailFormScreen> with Ticker
labelText: "PR No",
backgroundColor: AppColor.fieldBgColor(context),
labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)),
initialValue: widget.model?.prNo,
initialValue: _localModel.prNo ?? '',
textAlign: TextAlign.center,
textInputType: const TextInputType.numberWithOptions(decimal: true),
textInputType: TextInputType.text,
showShadow: false,
onChange: (value) {
widget.model?.prNo = value;
_localModel.prNo = value.isEmpty ? null : value;
},
style: Theme.of(context).textTheme.titleMedium,
),
@ -123,12 +136,12 @@ class _CostDetailFormScreenState extends State<CostDetailFormScreen> with Ticker
labelText: "PO No",
backgroundColor: AppColor.fieldBgColor(context),
labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)),
initialValue: widget.model?.poNo,
initialValue: _localModel.poNo ?? '',
textAlign: TextAlign.center,
textInputType: const TextInputType.numberWithOptions(decimal: true),
textInputType: TextInputType.text,
showShadow: false,
onChange: (value) {
widget.model?.poNo = value;
_localModel.poNo = value.isEmpty ? null : value;
},
style: Theme.of(context).textTheme.titleMedium,
),
@ -137,12 +150,12 @@ class _CostDetailFormScreenState extends State<CostDetailFormScreen> with Ticker
labelText: "MR No",
backgroundColor: AppColor.fieldBgColor(context),
labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)),
initialValue: widget.model?.mrNo,
initialValue: _localModel.mrNo ?? '',
textAlign: TextAlign.center,
textInputType: const TextInputType.numberWithOptions(decimal: true),
textInputType: TextInputType.text,
showShadow: false,
onChange: (value) {
widget.model?.mrNo = value;
_localModel.mrNo = value.isEmpty ? null : value;
},
style: Theme.of(context).textTheme.titleMedium,
),
@ -151,34 +164,25 @@ class _CostDetailFormScreenState extends State<CostDetailFormScreen> with Ticker
labelText: "Exchange Cost",
backgroundColor: AppColor.fieldBgColor(context),
labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)),
initialValue: widget.model?.exchangeCost != null ? widget.model?.exchangeCost.toString() : '',
initialValue: _localModel.exchangeCost?.toString() ?? '',
textAlign: TextAlign.center,
textInputType: const TextInputType.numberWithOptions(decimal: true),
showShadow: false,
onChange: (value) {
widget.model?.exchangeCost = num.parse(value);
_localModel.exchangeCost = num.tryParse(value);
},
style: Theme.of(context).textTheme.titleMedium,
),
],
).toShadowContainer(context).paddingAll(16),
).expanded,
),
),
FooterActionButton.footerContainer(
context: context,
child: AppFilledButton(
label: "Update Cost Details",
buttonColor: AppColor.primary10,
onPressed: () async {
if (validateForm()) {
showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading());
await widget.provider.engineerUpdateCost().then((success) {
Navigator.pop(context);
if (success) {
Navigator.pop(context);
}
});
}
},
onPressed: () => _onSubmit(),
),
),
],
@ -188,11 +192,25 @@ class _CostDetailFormScreenState extends State<CostDetailFormScreen> with Ticker
);
}
bool validateForm() {
bool isValid = true;
if (!widget.isEdit) {
return true;
Future<void> _onSubmit() async {
widget.provider.workOrderCostModel = _localModel;
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const AppLazyLoading(),
);
try {
final success = await widget.provider.engineerUpdateCost();
if (mounted) Navigator.pop(context); // Close loading
if (success && mounted) {
Navigator.pop(context);
}
} catch (e) {
if (mounted) Navigator.pop(context);
log('Error updating cost: $e');
}
return isValid;
}
}

@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/controllers/providers/api/parts_provider.dart';
@ -16,7 +16,6 @@ import 'package:test_sa/models/helper_data_models/spare_part/activity_spare_part
import 'package:test_sa/models/lookup.dart';
import 'package:test_sa/models/service_request/spare_parts.dart';
import 'package:test_sa/models/size_config.dart';
import 'package:test_sa/modules/cm_module/cm_detail_provider.dart';
import 'package:test_sa/modules/cm_module/cm_request_utils.dart';
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
import 'package:test_sa/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart';
@ -34,44 +33,59 @@ import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
class SparePartRequest extends StatefulWidget {
static const String id = "/spare-part-request";
final SparePartHelperModel? model;
final CMDetailProvider provider;
final dynamic provider;
final bool fromCm;
const SparePartRequest({
Key? key,
required this.model,
required this.provider,
this.fromCm = false,
}) : super(key: key);
@override
_SparePartRequestState createState() => _SparePartRequestState();
}
class _SparePartRequestState extends State<SparePartRequest> with TickerProviderStateMixin {
class _SparePartRequestState extends State<SparePartRequest> {
PartsProvider? _partsProvider;
bool _isLoading = false;
List<SparePart> _spareParts = [];
List<GenericAttachmentModel> _attachments = [];
List<GenericAttachmentModel> attachments = [];
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final TextEditingController _partQtyController = TextEditingController();
final TextEditingController _installQtyController = TextEditingController();
final TextEditingController _returnQtyController = TextEditingController();
final TextEditingController _oracleNoController = TextEditingController();
final TextEditingController _descriptionController = TextEditingController();
Lookup statusLookup = Lookup.fromJson({"id": 5619, "name": "New", "value": 1});
int? activityStatus;
@override
void initState() {
super.initState();
_partsProvider = Provider.of<PartsProvider>(context, listen: false);
_initializeModel();
_loadInitialData();
}
// Initialize model with existing values or defaults
void _initializeModel() {
final bool isNewActivity = widget.model?.id == null || widget.model?.id == 0;
if (isNewActivity) {
widget.provider.sparePartHelperModel = SparePartHelperModel(
id: 0,
workOrderId: widget.fromCm ? widget.provider.currentWorkOrder?.data?.requestId : null,
pmVisitId: !widget.fromCm ? widget.provider.planPreventiveVisit?.id : null,
sparePartAttachments: [],
sparePart: SparePart(),
quantity: null,
installQty: null,
returnQty: null,
activityStatusId: statusLookup.id,
activityStatus: statusLookup,
comment: '',
);
} else if (widget.provider.sparePartHelperModel == null ||
widget.provider.sparePartHelperModel?.id != widget.model?.id) {
widget.provider.sparePartHelperModel = SparePartHelperModel(
id: widget.model?.id ?? 0,
workOrderId: widget.model?.workOrderId ?? widget.provider.currentWorkOrder?.data?.requestId,
workOrderId: widget.fromCm ? widget.model?.workOrderId ?? widget.provider.currentWorkOrder?.data?.requestId : null,
pmVisitId: !widget.fromCm ? widget.model?.pmVisitId : null,
sparePartAttachments: widget.model?.sparePartAttachments ?? [],
sparePart: widget.model?.sparePart ?? SparePart(),
quantity: widget.model?.quantity,
@ -81,83 +95,96 @@ class _SparePartRequestState extends State<SparePartRequest> with TickerProvider
activityStatus: widget.model?.activityStatus ?? statusLookup,
comment: widget.model?.comment ?? '',
);
_partQtyController.text = widget.provider.sparePartHelperModel?.quantity != null ? widget.provider.sparePartHelperModel!.quantity!.round().toString() : '';
_installQtyController.text = widget.provider.sparePartHelperModel?.installQty != null ? widget.provider.sparePartHelperModel!.installQty!.round().toString() : '';
_returnQtyController.text = widget.provider.sparePartHelperModel?.returnQty != null ? widget.provider.sparePartHelperModel!.returnQty!.round().toString() : '';
_oracleNoController.text = widget.provider.sparePartHelperModel?.sparePart?.oracleCode != null ? widget.provider.sparePartHelperModel!.sparePart!.oracleCode! : '';
_descriptionController.text = widget.provider.sparePartHelperModel?.comment != null ? widget.provider.sparePartHelperModel!.comment! : '';
activityStatus = widget.provider.sparePartHelperModel?.activityStatus?.value;
scheduleMicrotask(() async {
_isLoading = true;
attachments = widget.provider.sparePartHelperModel?.sparePartAttachments?.map((e) => GenericAttachmentModel(id: e.id!, name: e.name ?? '')).toList() ?? [];
setState(() {});
_spareParts = await _partsProvider!.getPartsListByDisplayName(assetId: widget.provider.currentWorkOrder?.data?.asset?.id);
_isLoading = false;
setState(() {});
});
}
void restValues({required CMDetailProvider requestDetailProvider}) {
requestDetailProvider.sparePartHelperModel = SparePartHelperModel(id: 0, workOrderId: widget.provider.currentWorkOrder?.data?.requestId, sparePartAttachments: []);
_partQtyController.clear();
_installQtyController.clear();
_returnQtyController.clear();
_oracleNoController.clear();
_descriptionController.clear();
attachments = [];
log('Initialized SparePartHelperModel: ${widget.provider.sparePartHelperModel?.toJson()}');
}
@override
void dispose() {
_partQtyController.dispose();
_installQtyController.dispose();
_returnQtyController.dispose();
_oracleNoController.dispose();
_descriptionController.dispose();
super.dispose();
Future<void> _loadInitialData() async {
setState(() => _isLoading = true);
_spareParts = await _partsProvider!.getPartsListByDisplayName(
assetId: widget.fromCm
? widget.provider.currentWorkOrder?.data?.asset?.id
: widget.provider.planPreventiveVisit.asset.id,
);
final bool isNewActivity = widget.model?.id == null || widget.model?.id == 0;
_attachments = [];
if (!isNewActivity) {
try {
final attachmentsList = widget.model?.sparePartAttachments;
if (attachmentsList != null && attachmentsList.isNotEmpty) {
for (var item in attachmentsList) {
_attachments.add(GenericAttachmentModel(
id: item.id ?? 0,
name: item.name ?? '',
));
}
}
log('Loaded ${_attachments.length} attachments for existing activity');
} catch (e) {
log('Error loading attachments: $e');
_attachments = [];
}
} else {
log('New activity - starting with empty attachments');
}
bool _isLocalUrl(String url) {
if (url.isEmpty != false) return false;
return url.startsWith("/") || url.startsWith("file://") || url.substring(1).startsWith(':\\');
if (mounted) setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
bool showInstallAndReturnQty = activityStatus == 3 || activityStatus == 4;
return Scaffold(
key: _scaffoldKey,
appBar: DefaultAppBar(title: context.translation.sparePartRequest),
body: Consumer<CMDetailProvider>(builder: (context, CMDetailProvider requestDetailProvider, child) {
body: ListenableBuilder(
listenable: widget.provider,
builder: (context, _) {
final provider = widget.provider;
final model = provider.sparePartHelperModel;
final bool showInstallAndReturnQty = (model?.activityStatus?.value == 3 || model?.activityStatus?.value == 4);
final bool isReadOnly = provider.isReadOnlyRequest ?? false;
return Form(
key: _formKey,
child: Column(
children: [
SingleChildScrollView(
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: IgnorePointer(
ignoring: widget.provider.isReadOnlyRequest,
ignoring: isReadOnly,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Column(
_buildFormContent(
context,
model,
provider,
showInstallAndReturnQty,
),
],
),
),
),
),
if (!isReadOnly) _buildSubmitButton(context, provider),
],
),
);
},
),
);
}
Widget _buildFormContent(
BuildContext context,
SparePartHelperModel? model,
dynamic provider,
bool showInstallAndReturnQty,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
context.translation.sparePartDetails.heading5(context).custom(color: AppColor.textStyleColor(context)),
// 12.height,
// SingleItemDropDownMenu<Lookup, ActivityStatusProvider>(
// context: context,
// height: 56.toScreenHeight,
// title: context.translation.activityStatus,
// showShadow: false,
// backgroundColor: AppColor.neutral100,
// initialValue: requestDetailProvider.sparePartHelperModel?.activityStatus,
// onSelect: (status) {
// requestDetailProvider.sparePartHelperModel?.activityStatus = status;
// },
// ),
12.height,
SingleItemDropDownMenu<SparePart, NullableLoadingProvider>(
context: context,
@ -166,206 +193,211 @@ class _SparePartRequestState extends State<SparePartRequest> with TickerProvider
showShadow: false,
showAsBottomSheet: true,
loading: _isLoading,
initialValue: requestDetailProvider.sparePartHelperModel?.sparePart,
// backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90,
onSelect: (part) async {
initialValue: model?.sparePart,
onSelect: (part) {
if (part != null) {
requestDetailProvider.sparePartHelperModel?.sparePart = part;
_oracleNoController.text = part.oracleCode ?? '';
requestDetailProvider.updateSparePartHelperModel(widget.provider.sparePartHelperModel);
model?.sparePart = part;
provider.updateSparePartHelperModel(model);
}
},
),
if (requestDetailProvider.sparePartHelperModel?.sparePart?.id != null) ...[
if (model?.sparePart?.id != null) ...[
12.height,
SizedBox(
height: 30.toScreenHeight,
child: Text('View Parts Availability',
child: Text(
'View Parts Availability',
style: TextStyle(
fontSize: 12.toScreenWidth,
fontWeight: FontWeight.w500,
fontStyle: FontStyle.normal,
color: AppColor.primary10,
decorationColor: AppColor.primary10,
decoration: TextDecoration.underline,
)).onPress(
() async {
requestDetailProvider.getStoreAvailabilityById(partId: requestDetailProvider.sparePartHelperModel?.sparePart?.id);
),
).onPress(() {
provider.getStoreAvailabilityById(partId: model?.sparePart?.id);
showModalBottomSheet(
context: context,
useSafeArea: true,
isScrollControlled: true,
isDismissible: true,
backgroundColor: AppColor.bottomSheetColor(context),
builder: (BuildContext context) => PartDetailBottomSheetSheet().bottomSheetContainer(context),
builder: (ctx) => PartDetailBottomSheetSheet(provider: provider).bottomSheetContainer(ctx),
);
},
),
}),
),
],
12.height,
AppTextFormField(
controller: _partQtyController,
key: ValueKey('quantity_${model?.quantity}'),
initialValue: model?.quantity?.round().toString() ?? '',
labelText: context.translation.quantity,
textInputType: TextInputType.number,
labelStyle: AppTextStyles.textFieldLabelStyle,
// showWithoutDecoration: true,
// backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90,
enable: requestDetailProvider.sparePartHelperModel?.sparePart?.id != null,
enable: model?.sparePart?.id != null,
validator: (value) => value == null || value.isEmpty
? context.translation.requiredField
: Validator.isNumeric(value)
? null
: context.translation.onlyNumbers,
onChange: (value) {
requestDetailProvider.sparePartHelperModel?.quantity = num.tryParse(value ?? "");
model?.quantity = num.tryParse(value);
},
onSaved: (text) {
requestDetailProvider.sparePartHelperModel?.quantity = num.tryParse(text ?? "");
model?.quantity = num.tryParse(text ?? '');
},
),
12.height,
if (showInstallAndReturnQty) ...[
AppTextFormField(
controller: _installQtyController,
key: ValueKey('installQty_${model?.installQty}'),
initialValue: model?.installQty?.round().toString() ?? '',
labelText: context.translation.installedQty,
textInputType: const TextInputType.numberWithOptions(decimal: true),
labelStyle: AppTextStyles.textFieldLabelStyle,
// showWithoutDecoration: true,
// backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90,
enable: requestDetailProvider.sparePartHelperModel?.sparePart?.id != null,
enable: model?.sparePart?.id != null,
validator: (value) => value == null || value.isEmpty
? context.translation.requiredField
: Validator.isNumeric(value)
? null
: context.translation.onlyNumbers,
onChange: (value) {
requestDetailProvider.sparePartHelperModel?.installQty = num.tryParse(value ?? "");
model?.installQty = num.tryParse(value);
},
onSaved: (text) {
requestDetailProvider.sparePartHelperModel?.installQty = num.tryParse(text ?? "");
model?.installQty = num.tryParse(text ?? '');
},
),
12.height,
AppTextFormField(
controller: _returnQtyController,
key: ValueKey('returnQty_${model?.returnQty}'),
initialValue: model?.returnQty?.round().toString() ?? '',
labelText: context.translation.returnQty,
textInputType: TextInputType.number,
labelStyle: AppTextStyles.textFieldLabelStyle,
// showWithoutDecoration: true,
// backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90,
enable: requestDetailProvider.sparePartHelperModel?.sparePart?.id != null,
enable: model?.sparePart?.id != null,
validator: (value) => value == null || value.isEmpty
? context.translation.requiredField
: Validator.isNumeric(value)
? null
: context.translation.onlyNumbers,
onChange: (value) {
requestDetailProvider.sparePartHelperModel?.returnQty = num.tryParse(value ?? "");
model?.returnQty = num.tryParse(value);
},
onSaved: (text) {
requestDetailProvider.sparePartHelperModel?.returnQty = num.tryParse(text ?? "");
model?.returnQty = num.tryParse(text ?? '');
},
),
12.height,
],
AppTextFormField(
key: ValueKey('oracle_${model?.sparePart?.oracleCode}'),
initialValue: model?.sparePart?.oracleCode ?? '',
labelStyle: AppTextStyles.textFieldLabelStyle,
controller: _oracleNoController,
labelText: context.translation.oracleNo,
textInputType: TextInputType.number,
// showWithoutDecoration: true,
backgroundColor: AppColor.disableFieldBackground(context),
enable: false,
),
12.height,
AppTextFormField(
initialValue: requestDetailProvider.sparePartHelperModel?.comment,
key: ValueKey('comment_${model?.comment}'),
initialValue: model?.comment ?? '',
labelStyle: AppTextStyles.textFieldLabelStyle,
controller: _descriptionController,
showSpeechToText: true,
// backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90,
labelText: "Engineer Comment",
alignLabelWithHint: true,
// showWithoutDecoration: true,
textInputType: TextInputType.multiline,
onChange: (value) {
requestDetailProvider.sparePartHelperModel?.comment = value;
model?.comment = value;
},
onSaved: (value) {
requestDetailProvider.sparePartHelperModel?.comment = value;
model?.comment = value;
},
),
12.height,
AttachmentPicker(
label: context.translation.attachQuotation,
attachment: attachments,
attachment: _attachments,
buttonIcon: 'quotation_icon'.toSvgAsset(color: AppColor.primary10),
buttonColor: AppColor.primary10,
),
],
).toShadowContainer(context),
8.height,
],
),
),
).expanded,
if (!requestDetailProvider.isReadOnlyRequest)
FooterActionButton.footerContainer(
).toShadowContainer(context);
}
Widget _buildSubmitButton(BuildContext context, dynamic provider) {
return FooterActionButton.footerContainer(
context: context,
child: AppFilledButton(
label: widget.provider.sparePartHelperModel?.id == 0 ? context.translation.addSparePartActivity : context.translation.updateSparePartActivity,
label: provider.sparePartHelperModel?.id == 0 ? context.translation.addSparePartActivity : context.translation.updateSparePartActivity,
buttonColor: AppColor.green70,
onPressed: () {
_onSubmit(requestDetailProvider: requestDetailProvider);
},
)),
],
onPressed: () => _onSubmit(provider),
),
);
}),
);
}
_onSubmit({required CMDetailProvider requestDetailProvider}) async {
if (requestDetailProvider.sparePartHelperModel?.sparePart?.id == null) {
Future<void> _onSubmit(dynamic provider) async {
final model = provider.sparePartHelperModel;
if (model?.sparePart?.id == null) {
"Please select spare part".showToast;
return;
} else if (requestDetailProvider.sparePartHelperModel?.quantity == null) {
} else if (model?.quantity == null) {
"Please enter quantity".showToast;
return;
}
requestDetailProvider.sparePartHelperModel?.sparePartAttachments?.clear();
for (var item in attachments) {
model?.sparePartAttachments?.clear();
for (var item in _attachments) {
String fileName = CMRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? '';
requestDetailProvider.sparePartHelperModel?.sparePartAttachments?.add(
model?.sparePartAttachments?.add(
SparePartAttachments(id: item.id, name: fileName),
);
}
// Show loading dialog
// Show loading
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const AppLazyLoading(),
);
// Submit
bool success = false;
if (widget.provider.sparePartHelperModel?.id == 0) {
success = await requestDetailProvider.createActivitySparePart();
if (model?.id == 0) {
success = await provider.createActivitySparePart();
} else {
success = await requestDetailProvider.updateActivitySparePart();
success = await provider.updateActivitySparePart();
}
if (mounted) Navigator.pop(context);
if (mounted) Navigator.pop(context); // Close loading
if (success) {
requestDetailProvider.getWorkOrderById(
id: requestDetailProvider.currentWorkOrder!.data!.requestId!,
if (widget.fromCm) {
provider.getWorkOrderById(
id: provider.currentWorkOrder!.data!.requestId!,
);
}
else{
provider.getPlanPreventiveVisitById(
int.tryParse(provider.planPreventiveVisit?.id ?? ''),
);
}
if (mounted) Navigator.pop(context); // Close form
if (model?.id == 0) {
ServiceRequestBottomSheet.addAnotherSpareRequestBottomSheet(
context: context,
fromCm: widget.fromCm
);
if (mounted) Navigator.pop(context);
if (widget.provider.sparePartHelperModel?.id == 0) {
ServiceRequestBottomSheet.addAnotherSpareRequestBottomSheet(context: context);
const SizedBox().flushBar(
context: context,
title: context.translation.sparePartActivitySuccess,
@ -376,8 +408,14 @@ class _SparePartRequestState extends State<SparePartRequest> with TickerProvider
}
}
/// Bottom Sheet for showing parts availability
class PartDetailBottomSheetSheet extends StatelessWidget {
PartDetailBottomSheetSheet({Key? key}) : super(key: key);
final dynamic provider;
const PartDetailBottomSheetSheet({
Key? key,
required this.provider,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -390,31 +428,61 @@ class PartDetailBottomSheetSheet extends StatelessWidget {
children: [
8.height,
'Parts Availability'.heading6(context).paddingOnly(top: 12, start: 12),
Consumer<CMDetailProvider>(builder: (context, requestDetailProvider, child) {
return requestDetailProvider.isLoading
? CircularProgressIndicator(color: AppColor.loadingColor(context)).center
: requestDetailProvider.storeAvailability.isEmpty
Expanded(
child: ListenableBuilder(
listenable: provider,
builder: (context, _) {
return provider.isLoading
? CircularProgressIndicator(
color: AppColor.loadingColor(context),
).center
: provider.storeAvailability.isEmpty
? const NoDataFound().center
: ListView.separated(
padding: const EdgeInsets.only(top: 12),
itemCount: requestDetailProvider.storeAvailability.length,
itemCount: provider.storeAvailability.length,
separatorBuilder: (czt, index) => 12.height,
itemBuilder: (context, index) {
StoreAvailability model = requestDetailProvider.storeAvailability[index];
return partAvailableQuantityCard(context: context, model: model);
StoreAvailability model = provider.storeAvailability[index];
return _partAvailableQuantityCard(
context: context,
model: model,
);
},
);
}).expanded,
},
),
),
],
),
);
}
Widget partAvailableQuantityCard({required StoreAvailability model, required BuildContext context}) {
return Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
InfoTextWidget(label: "Site Name:".addTranslation, value: model.siteName ?? ''),
InfoTextWidget(label: "Store Name:".addTranslation, value: model.storeName ?? ''),
InfoTextWidget(label: "Available Quantity:".addTranslation, value: model.availablityQty != null ? model.availablityQty.toString() : ''),
]).toShadowContainer(context, padding: 12, showShadow: false,);
Widget _partAvailableQuantityCard({
required StoreAvailability model,
required BuildContext context,
}) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoTextWidget(
label: "Site Name:".addTranslation,
value: model.siteName ?? '',
),
InfoTextWidget(
label: "Store Name:".addTranslation,
value: model.storeName ?? '',
),
InfoTextWidget(
label: "Available Quantity:".addTranslation,
value: model.availablityQty?.toString() ?? '',
),
],
).toShadowContainer(
context,
padding: 12,
showShadow: false,
);
}
}

@ -1,29 +1,24 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/controllers/providers/api/ppm_provider.dart';
import 'package:test_sa/controllers/providers/api/user_provider.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/models/enums/user_types.dart';
import 'package:test_sa/models/helper_data_models/maintenance_request/activity_maintenance_model.dart';
import 'package:test_sa/models/helper_data_models/spare_part/activity_spare_part_model.dart';
import 'package:test_sa/models/new_models/work_order_detail_model.dart';
import 'package:test_sa/models/plan_preventive_visit/plan_preventive_visit_model.dart';
import 'package:test_sa/models/service_request/spare_parts.dart';
import 'package:test_sa/models/service_request/supp_engineer_work_orders.dart';
import 'package:test_sa/models/service_request/supplier_details.dart';
import 'package:test_sa/modules/cm_module/cm_detail_provider.dart';
import 'package:test_sa/modules/cm_module/cm_request_utils.dart';
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
import 'package:test_sa/modules/cm_module/views/forms/maintenance_request/maintenance_request_main.dart';
import 'package:test_sa/modules/cm_module/views/forms/spare_part/spare_part_request.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart';
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
import 'package:test_sa/views/widgets/images/files_list.dart';
import 'package:test_sa/views/widgets/item_views/info_header_widget.dart';
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
import 'package:test_sa/views/widgets/requests/request_status.dart';
@ -50,9 +45,7 @@ class _PmActivitiesListViewState extends State<PmActivitiesListView> {
super.dispose();
}
List<Activities> reOrderActivities = [];
List<Activities> activities = [];
List<PMSparePartActivityModel> activities = [];
bool disableNewActivity = false;
@ -60,15 +53,12 @@ class _PmActivitiesListViewState extends State<PmActivitiesListView> {
Widget build(BuildContext context) {
UserProvider userProvider = Provider.of<UserProvider>(context);
return Scaffold(
appBar: const DefaultAppBar(
title: 'PM Activities',
// actions: [if (!showReorderView) IconButton(onPressed: () => activities.isEmpty ? null : setState(() => showReorderView = true), icon: const Icon(Icons.reorder_rounded))],
appBar: DefaultAppBar(
showHomeActionButton: true,
title: context.translation.activities,
),
///TODO replace with pm provider and data from pm provider
body: Consumer<CMDetailProvider>(builder: (context, CMDetailProvider requestDetailProvider, child) {
activities = requestDetailProvider.currentWorkOrder!.data!.activities;
requestDetailProvider.lastVisitedRequestActivityId = (activities.isNotEmpty ? activities.last.id : -1)!;
body: Consumer<PpmProvider>(builder: (context, PpmProvider pmProvider, child) {
activities = pmProvider.planPreventiveVisit?.activities ?? [];
if (activities.isNotEmpty && (activities.last.activityStatus!.value == 14 || activities.last.activityStatus!.value == 19)) {
disableNewActivity = true;
} else {
@ -77,28 +67,42 @@ class _PmActivitiesListViewState extends State<PmActivitiesListView> {
return Column(
children: [
(activities.isEmpty && reOrderActivities.isEmpty)
(activities.isEmpty)
? NoDataFound(message: context.translation.noDataFound).center.expanded
: ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: activities.length,
separatorBuilder: (czt, index) => 16.height,
separatorBuilder: (czt, index) => 12.height,
itemBuilder: (context, index) {
return sparePartActivityCard(requestDetailProvider: requestDetailProvider, userProvider: userProvider, context: context, activity: activities[index]);
return sparePartActivityCard(pmProvider: pmProvider, userProvider: userProvider, context: context, activity: activities[index]);
},
).expanded,
if (userProvider.user!.type == UsersTypes.engineer &&
(requestDetailProvider.currentWorkOrder?.data?.status?.value != 5 && requestDetailProvider.currentWorkOrder?.data?.status?.value != 3))
(pmProvider.planPreventiveVisit?.visitStatus?.value != 5 && pmProvider.planPreventiveVisit?.visitStatus?.value != 3))
FooterActionButton.footerContainer(
context: context,
child: AppFilledButton(
label: 'Create New Spare Part',
label: context.translation.createNewActivity,
maxWidth: true,
buttonColor: AppColor.primary10,
loading: requestDetailProvider.isLoading,
loading: pmProvider.isLoading,
disableButton: disableNewActivity,
onPressed: () async {
// ServiceRequestBottomSheet.activityTypeBottomSheet(context: context);
final model = SparePartHelperModel(
id: 0,
pmVisitId: pmProvider.planPreventiveVisit?.id,
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SparePartRequest(
model: model,
provider: pmProvider,
fromCm: false,
),
),
);
},
))
],
@ -107,11 +111,11 @@ class _PmActivitiesListViewState extends State<PmActivitiesListView> {
);
}
Widget sparePartActivityCard({required CMDetailProvider requestDetailProvider, required UserProvider userProvider, required BuildContext context, required Activities activity}) {
bool showActionButtons = userProvider.user!.userID == requestDetailProvider.currentWorkOrder?.data?.assignedEmployee?.userId &&
Widget sparePartActivityCard({required PpmProvider pmProvider, required UserProvider userProvider, required BuildContext context, required PMSparePartActivityModel activity}) {
bool showActionButtons = userProvider.user!.userID == pmProvider.planPreventiveVisit?.assignedEmployee?.userId &&
userProvider.user!.type == UsersTypes.engineer &&
requestDetailProvider.currentWorkOrder?.data?.status?.value != 5 &&
requestDetailProvider.currentWorkOrder?.data?.status?.value != 3;
pmProvider.planPreventiveVisit?.visitStatus?.value != 5 &&
pmProvider.planPreventiveVisit?.visitStatus?.value != 3;
return Column(
mainAxisSize: MainAxisSize.min,
@ -131,14 +135,14 @@ class _PmActivitiesListViewState extends State<PmActivitiesListView> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"edit_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).onPress(() {
editSparePartRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity);
editSparePartRequest(context: context, pmProvider: pmProvider, activity: activity);
}),
12.width,
"delete_icon".toSvgAsset(height: 24, width: 24).onPress(() async {
showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading());
int status = await requestDetailProvider.deleteActivitySparePart(id: activity.id!, workOrderId: requestDetailProvider.currentWorkOrder!.data!.requestId!);
int status = await pmProvider.deleteActivitySparePart(id: activity.id!);
if (status == 200) {
await requestDetailProvider.getWorkOrderById(id: requestDetailProvider.currentWorkOrder!.data!.requestId!);
await pmProvider.getPlanPreventiveVisitById((int.tryParse(pmProvider.planPreventiveVisit?.id ?? '')) as int);
Navigator.pop(context);
} else {
Navigator.pop(context);
@ -149,58 +153,61 @@ class _PmActivitiesListViewState extends State<PmActivitiesListView> {
],
),
6.height,
InfoHeaderWidget(activity.activityType?.cleanupWhitespace.capitalizeFirstOfEach ?? '-'),
InfoTextWidget(
label: context.translation.partName,
value: activity.activitySparePart?.partCatalogItem?.partName,
value: activity.partCatalogItem?.partName,
),
InfoTextWidget(
label: context.translation.partNo,
value: activity.activitySparePart?.partCatalogItem?.partNumber,
showArrow: true,
value: activity.partCatalogItem?.partNumber,
),
if (activity.activitySparePart?.acitiySparePartAttachments?.isNotEmpty ?? false) ...[
6.height,
InfoTextWidget(
label: context.translation.oracleCode,
value: activity.partCatalogItem?.oracleCode,
),
if (activity.comment?.isNotEmpty ?? false) ...[
const Divider().defaultStyle(context),
InfoTextLabelWidget(label: activity.comment ?? ""),
],
if (activity.activitySparePartPPMAttachments.isNotEmpty) ...[
const Divider().defaultStyle(context),
FilesList(images: activity.activitySparePart!.acitiySparePartAttachments!.map((toElement) => URLs.getFileUrl(toElement.name!)!).toList()),
FilesList(images: activity.activitySparePartPPMAttachments.map((toElement) => URLs.getFileUrl(toElement.name??'')??'').toList()),
],
],
).toShadowContainer(context, showShadow: false).onPress(() {
///Move to cost page ..
// if (showActionButtons) return;
// if (requestDetailProvider.isReadOnlyRequest) {
// editSparePartRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity);
// } else if (userProvider.user!.userID == requestDetailProvider.currentWorkOrder?.data?.assignedEmployee?.userId) {
// requestDetailProvider.isReadOnlyRequest = true;
// editSparePartRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity);
// }
if (showActionButtons) return;
if (pmProvider.isReadOnlyRequest) {
editSparePartRequest(context: context, pmProvider: pmProvider, activity: activity);
} else if (userProvider.user!.userID == pmProvider.planPreventiveVisit?.assignedEmployee?.userId) {
pmProvider.isReadOnlyRequest = true;
editSparePartRequest(context: context, pmProvider: pmProvider, activity: activity);
}
});
}
void editSparePartRequest({required BuildContext context, required CMDetailProvider requestDetailProvider, required Activities activity}) async {
requestDetailProvider.sparePartHelperModel = SparePartHelperModel(
void editSparePartRequest({required BuildContext context, required PpmProvider pmProvider, required PMSparePartActivityModel activity}) async {
final model = SparePartHelperModel(
id: activity.id,
workOrderId: requestDetailProvider.currentWorkOrder?.data?.requestId,
comment: activity.activitySparePart?.comment,
sparePartAttachments: activity.activitySparePart?.acitiySparePartAttachments ?? [],
pmVisitId: pmProvider.planPreventiveVisit?.id,
comment: activity.comment,
sparePartAttachments: activity.activitySparePartPPMAttachments,
activityStatus: activity.activityStatus,
sparePart: SparePart(
id: activity.activitySparePart?.partCatalogItem?.id,
partName: activity.activitySparePart?.partCatalogItem?.partName,
partNo: activity.activitySparePart?.partCatalogItem?.partNumber,
oracleCode: activity.activitySparePart?.partCatalogItem?.oracleCode),
quantity: activity.activitySparePart?.quantity,
installQty: activity.activitySparePart?.installQty,
returnQty: activity.activitySparePart?.returnQty,
id: activity.partCatalogItem?.id,
partName: activity.partCatalogItem?.partName,
partNo: activity.partCatalogItem?.partNumber,
oracleCode: activity.partCatalogItem?.oracleCode,
),
quantity: activity.quantity,
installQty: activity.installQty,
returnQty: activity.returnQty,
activityStatusId: activity.activityStatus?.id,
);
requestDetailProvider.updateSparePartHelperModel(requestDetailProvider.sparePartHelperModel);
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => SparePartRequest(
model: requestDetailProvider.sparePartHelperModel,
provider: requestDetailProvider,
model: model,
provider: pmProvider,
fromCm: false, // This is PM, not CM
),
));
}

@ -1,127 +0,0 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/controllers/providers/api/user_provider.dart';
import 'package:test_sa/controllers/providers/settings/setting_provider.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/string_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/models/enums/user_types.dart';
import 'package:test_sa/models/enums/work_order_next_step.dart';
import 'package:test_sa/models/generic_attachment_model.dart';
import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart';
import 'package:test_sa/models/new_models/work_order_detail_model.dart';
import 'package:test_sa/modules/cm_module/cm_detail_provider.dart';
import 'package:test_sa/modules/cm_module/views/forms/asset_retired/asset_retired.dart';
import 'package:test_sa/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
import 'package:test_sa/views/widgets/equipment/asset_detail_page.dart';
import 'package:test_sa/views/widgets/images/files_list.dart';
import 'package:test_sa/views/widgets/images/multi_image_picker.dart';
import 'package:test_sa/views/widgets/item_views/info_date_widget.dart';
import 'package:test_sa/views/widgets/item_views/info_header_widget.dart';
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
import 'package:test_sa/views/widgets/requests/request_status.dart';
import 'package:test_sa/views/widgets/sound/sound_player.dart';
class PmCostDetailsView extends StatefulWidget {
PmCostDetailsView({Key? key}) : super(key: key);
@override
State<PmCostDetailsView> createState() => _PmCostDetailsViewState();
}
class _PmCostDetailsViewState extends State<PmCostDetailsView> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
UserProvider userProvider = Provider.of<UserProvider>(context, listen: false);
///TODO: replace with pm provider and data from provider and remove the extra widgets as well.
return Consumer<CMDetailProvider>(builder: (pContext, requestProvider, _) {
bool showCostCard = context.userProvider.user!.type == UsersTypes.engineer &&
!requestProvider.isReadOnlyRequest &&
requestProvider.currentWorkOrder?.data?.nextStep?.workOrderNextStepEnum == WorkOrderNextStepEnum.activity;
return requestProvider.isLoading
? CircularProgressIndicator(color: AppColor.loadingColor(context)).center
: requestProvider.currentWorkOrder == null
? const NoDataFound()
: Column(
children: [
SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showCostCard) ...[
16.height,
costCard(context, requestProvider),
],
],
),
).expanded,
],
);
});
}
Widget costCard(BuildContext context, CMDetailProvider provider) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
InfoHeader16Widget("Cost"),
const Spacer(),
if (!provider.isReadOnlyRequest)
///TODO replace with exact provider and data from provider and remove the extra widgets as well.
"edit_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).onPress(() async {
WorkOrderData currentWorkOrderData = provider.currentWorkOrder!.data!;
provider.workOrderCostModel = WorkOrderCostModel(
workOrderId: currentWorkOrderData.requestId,
labourCost: currentWorkOrderData.labourCost,
sparePartCost: currentWorkOrderData.sparePartCost,
travelCost: currentWorkOrderData.travelCost,
qAmount: currentWorkOrderData.qAmount,
prNo: currentWorkOrderData.prNo,
poNo: currentWorkOrderData.poNo,
mrNo: currentWorkOrderData.mrNo,
exchangeCost: currentWorkOrderData.exchangeCost,
);
provider.refreshTimer = false;
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CostDetailFormScreen(
isEdit: true,
model: provider.workOrderCostModel,
provider: provider,
)));
provider.refreshTimer = true;
}),
],
),
InfoTextWidget(label: 'Spare Part Cost'.addTranslation, value: provider.currentWorkOrder?.data?.sparePartCost?.toString() ?? '-'),
InfoTextWidget(label: 'Labor Cost'.addTranslation, value: provider.currentWorkOrder?.data?.labourCost?.toString() ?? '-'),
InfoTextWidget(label: 'Travel Cost'.addTranslation, value: provider.currentWorkOrder?.data?.travelCost?.toString() ?? '-'),
InfoTextWidget(label: 'Quot Amount'.addTranslation, value: provider.currentWorkOrder?.data?.qAmount?.toString() ?? '-'),
InfoTextWidget(label: 'PR No'.addTranslation, value: provider.currentWorkOrder?.data?.prNo?.toString() ?? '-'),
InfoTextWidget(label: 'PO No'.addTranslation, value: provider.currentWorkOrder?.data?.poNo?.toString() ?? '-'),
InfoTextWidget(label: 'MR No'.addTranslation, value: provider.currentWorkOrder?.data?.mrNo?.toString() ?? '-'),
InfoTextWidget(label: 'Exchange Cost'.addTranslation, value: provider.currentWorkOrder?.data?.exchangeCost?.toString() ?? '-'),
],
).toShadowContainer(context);
}
}

@ -117,7 +117,7 @@ class _PpmDetailsPageState extends State<PpmDetailsPage> {
],
).toShadowContainer(context),
).expanded,
if (userProvider!.user!.type == UsersTypes.engineer && (!ppmProvider.isReadOnly)) ...[
// if (userProvider!.user!.type == UsersTypes.engineer && (!ppmProvider.isReadOnly)) ...[
FooterActionButton.footerContainer(
child: Column(
children: [
@ -126,22 +126,14 @@ class _PpmDetailsPageState extends State<PpmDetailsPage> {
await Navigator.of(context).push(MaterialPageRoute(builder: (_) => UpdatePpm(ppm: null, planPreventiveVisit: planPreventiveVisit)));
getVisitData();
},
label: context.translation.updateWorkOrder,
),
12.height,
AppFilledButton(
onPressed: () async {
await Navigator.of(context).push(MaterialPageRoute(builder: (_) => const PmActivitiesListView()));
getVisitData();
},
buttonColor: AppColor.neutral50,
label: context.translation.activities,
label: userProvider!.user!.type == UsersTypes.engineer && (!ppmProvider.isReadOnly)? context.translation.updateWorkOrder:context.translation.viewDetails,
),
],
),
context: context,
)
]
// ]
])
: const Center(child: NoDataFound());
}),

@ -14,6 +14,7 @@ import 'package:test_sa/models/plan_preventive_visit/plan_preventive_visit_model
import 'package:test_sa/models/ppm/ppm.dart';
import 'package:test_sa/modules/cm_module/cm_request_utils.dart';
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
import 'package:test_sa/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart';
@ -94,27 +95,36 @@ class _UpdatePpmState extends State<UpdatePpm> with TickerProviderStateMixin {
@override
void initState() {
super.initState();
ppmProvider = Provider.of<PpmProvider>(context, listen: false);
_planPreventiveVisit = widget.planPreventiveVisit;
// WidgetsBinding.instance.addPostFrameCallback((_) {
initTabs(_planPreventiveVisit.typeOfService);
// });
super.initState();
int initialTabs = (_planPreventiveVisit.typeOfService?.value == 2) ? 4 : 3;
_tabController = TabController(length: initialTabs, vsync: this);
WidgetsBinding.instance.addPostFrameCallback((_) {
ppmProvider.totalTabs = initialTabs;
});
}
void initTabs(typeOfService) {
if (typeOfService == null) {
return;
}
if (typeOfService?.value == 2) {
ppmProvider.totalTabs = 4;
} else {
ppmProvider.totalTabs = 3;
int newTabCount = (typeOfService?.value == 2) ? 4 : 3;
// Only recreate TabController if tab count has changed
if (_tabController?.length != newTabCount) {
_tabController?.dispose();
_tabController = TabController(length: newTabCount, vsync: this);
// Update provider safely after current build
WidgetsBinding.instance.addPostFrameCallback((_) {
ppmProvider.totalTabs = newTabCount;
});
}
_tabController?.dispose;
_tabController = TabController(length: ppmProvider.totalTabs, vsync: this);
}
int tabIndex = 0;
@ -208,7 +218,9 @@ class _UpdatePpmState extends State<UpdatePpm> with TickerProviderStateMixin {
).expanded,
FooterActionButton.footerContainer(
context: context,
child: Row(
child: Column(
children: [
Row(
children: [
if (tabIndex == 1) ...[
AppFilledButton(
@ -274,6 +286,18 @@ class _UpdatePpmState extends State<UpdatePpm> with TickerProviderStateMixin {
]
],
),
12.height,
AppFilledButton(
onPressed: () async {
Navigator.of(context).push(MaterialPageRoute(builder: (_) => const PmActivitiesListView()));
// getVisitData();
},
buttonColor: AppColor.neutral50,
label: context.translation.activities,
),
],
),
),
],
),

@ -13,6 +13,7 @@ import 'package:test_sa/models/device/asset.dart';
import 'package:test_sa/models/device/model_definition.dart';
import 'package:test_sa/models/device/supplier.dart';
import 'package:test_sa/models/generic_attachment_model.dart';
import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart';
import 'package:test_sa/models/lookup.dart';
import 'package:test_sa/models/new_models/building.dart';
import 'package:test_sa/models/new_models/department.dart';
@ -21,6 +22,7 @@ import 'package:test_sa/models/new_models/room_model.dart';
import 'package:test_sa/models/new_models/site.dart';
import 'package:test_sa/models/plan_preventive_visit/plan_preventive_visit_model.dart';
import 'package:test_sa/modules/cm_module/cm_request_utils.dart';
import 'package:test_sa/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart';
import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart';
@ -357,6 +359,8 @@ class _WoInfoFormState extends State<WoInfoForm> {
),
],
).toShadowContainer(context),
16.height,
costCard(context, ppmProvider),
],
),
),
@ -454,6 +458,79 @@ class _WoInfoFormState extends State<WoInfoForm> {
);
}
Widget costCard(BuildContext context, PpmProvider provider) {
///Need to update this when actual flow decided...
PlanPreventiveVisit? model = provider.planPreventiveVisit;
final costModel = WorkOrderCostModel(
workOrderId: int.tryParse(model?.id ?? ''),
labourCost: model?.labourCost,
sparePartCost: model?.sparePartCost,
travelCost: model?.travelCost,
qAmount: model?.qAmount,
prNo: model?.prNo,
poNo: model?.poNo,
mrNo: model?.mrNo,
exchangeCost: model?.exchangeCost,
);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
InfoHeader16Widget("Cost"),
const Spacer(),
if (!provider.isReadOnlyRequest)
"edit_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).onPress(() async {
provider.workOrderCostModel = costModel;
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CostDetailFormScreen(
model: provider.workOrderCostModel,
provider: provider,
),
),
);
}),
],
),
InfoTextWidget(
label: 'Spare Part Cost'.addTranslation,
value: costModel.sparePartCost?.toString() ?? '-',
),
InfoTextWidget(
label: 'Labor Cost'.addTranslation,
value: costModel.labourCost?.toString() ?? '-',
),
InfoTextWidget(
label: 'Travel Cost'.addTranslation,
value: costModel.travelCost?.toString() ?? '-',
),
InfoTextWidget(
label: 'Quot Amount'.addTranslation,
value: costModel.qAmount?.toString() ?? '-',
),
InfoTextWidget(
label: 'PR No'.addTranslation,
value: costModel.prNo?.toString() ?? '-',
),
InfoTextWidget(
label: 'PO No'.addTranslation,
value: costModel.poNo?.toString() ?? '-',
),
InfoTextWidget(
label: 'MR No'.addTranslation,
value: costModel.mrNo?.toString() ?? '-',
),
InfoTextWidget(
label: 'Exchange Cost'.addTranslation,
value: costModel.exchangeCost?.toString() ?? '-',
),
],
).toShadowContainer(context);
}
// String formatDuration1(int seconds) {
// int hours = seconds ~/ 3600;
// int minutes = (seconds % 3600) ~/ 60;

Loading…
Cancel
Save