diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index f9374337..b03cec52 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -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. + + } diff --git a/lib/controllers/providers/api/parts_provider.dart b/lib/controllers/providers/api/parts_provider.dart index fc2829b8..e50b7a7b 100644 --- a/lib/controllers/providers/api/parts_provider.dart +++ b/lib/controllers/providers/api/parts_provider.dart @@ -88,32 +88,39 @@ 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 page = []; if (response.statusCode >= 200 && response.statusCode < 300) { // client's request was successfully received List categoriesListJson = json.decode(response.body)["data"]; - page = categoriesListJson.map((part) => SparePart.fromJson(part,false)).toList(); + page = categoriesListJson.map((part) => SparePart.fromJson(part, false)).toList(); } return page; } catch (error) { return []; } } + // Implement this only for spare part request for now show and search on display name .... Future> 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 page = []; if (response.statusCode >= 200 && response.statusCode < 300) { // client's request was successfully received List categoriesListJson = json.decode(response.body)["data"]; - page = categoriesListJson.map((part) => SparePart.fromJson(part,true)).toList(); + page = categoriesListJson.map((part) => SparePart.fromJson(part, true)).toList(); } return page; } catch (error) { diff --git a/lib/controllers/providers/api/ppm_provider.dart b/lib/controllers/providers/api/ppm_provider.dart index 81a7e066..19d222cc 100644 --- a/lib/controllers/providers/api/ppm_provider.dart +++ b/lib/controllers/providers/api/ppm_provider.dart @@ -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 = []; + + List get storeAvailability => _storeAvailability; + + set storeAvailability(List 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 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 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 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 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 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 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(); + } + } } diff --git a/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart b/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart index 1c51821f..548d47bb 100644 --- a/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart +++ b/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart @@ -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; + + List 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 ?? []; Map toJson() { final Map data = {}; @@ -36,13 +39,42 @@ class SparePartHelperModel { data['quantity'] = quantity; data['installQty'] = installQty; data['returnQty'] = returnQty; - if(activityStatus!=null){ + if (activityStatus != null) { 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 toPMJson() { + final Map data = {}; + 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; } @@ -65,4 +97,4 @@ class SparePartAttachments { data['name'] = name; return data; } -} \ No newline at end of file +} diff --git a/lib/models/helper_data_models/workorder/work_order_helper_models.dart b/lib/models/helper_data_models/workorder/work_order_helper_models.dart index b15924d6..5515c27d 100644 --- a/lib/models/helper_data_models/workorder/work_order_helper_models.dart +++ b/lib/models/helper_data_models/workorder/work_order_helper_models.dart @@ -178,4 +178,7 @@ class WorkOrderCostModel { Map toJson() { return {'workOrderId': workOrderId, 'sparePartCost': sparePartCost, 'laborCost': labourCost, 'travelCost': travelCost, 'qAmount': qAmount, 'prNo': prNo, 'poNo': poNo,'mrNo':mrNo,'exchangeCost':exchangeCost}; } + Map toPMCostJson() { + return {'planPreventiveVisitId': workOrderId, 'sparePartCost': sparePartCost, 'laborCost': labourCost, 'travelCost': travelCost, 'qAmount': qAmount, 'prNo': prNo, 'poNo': poNo,'mrNo':mrNo,'exchangeCost':exchangeCost}; + } } diff --git a/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart b/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart index b8f5fd94..87087c3b 100644 --- a/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart +++ b/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart @@ -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; List? preventiveVisitTimers; List? preventiveVisitSuppliers; + List? activities; + num? sparePartCost; + num? labourCost; + num? travelCost; + num? qAmount; + String? prNo; + String? poNo; + String? mrNo; + num? exchangeCost; TimerModel? tbsTimer = TimerModel(); List? 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 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 = []; json['preventiveVisitAttachments'].forEach((v) { @@ -167,6 +196,13 @@ class PlanPreventiveVisit { preventiveVisitSuppliers!.add(PreventiveVisitSuppliers.fromJson(v)); }); } + if (json['activitySparePartsPPM'] != null) { + log("activitySparePartsPPM: ${json['activitySparePartsPPM']}"); + activities = []; + json['activitySparePartsPPM'].forEach((v) { + activities!.add(PMSparePartActivityModel.fromJson(v)); + }); + } } Map 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,14 +238,12 @@ class PlanPreventiveVisit { if (preventiveVisitSuppliers != null) { data['preventiveVisitSuppliers'] = preventiveVisitSuppliers!.map((v) => v.toJson()).toList(); } + if (activities != null) { + data['activitySparePartsPPM'] = activities!.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 validate(BuildContext context) async { if (visitStatus?.id == null) { await Fluttertoast.showToast(msg: "${context.translation.youHaveToSelect} ${context.translation.status}"); @@ -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? preventiveVisitAttachments; -// List? preventiveVisitCalibrations; -// List? preventiveVisitChecklists; -// List? preventiveVisitKits; -// List? preventiveVisitTimers; -// List? 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 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 = []; -// json['preventiveVisitAttachments'].forEach((v) { -// preventiveVisitAttachments!.add(PreventiveVisitChecklists.fromJson(v)); -// }); -// } -// if (json['preventiveVisitCalibrations'] != null) { -// preventiveVisitCalibrations = []; -// json['preventiveVisitCalibrations'].forEach((v) { -// preventiveVisitCalibrations!.add( PreventiveVisitChecklists.fromJson(v)); -// }); -// } -// if (json['preventiveVisitChecklists'] != null) { -// preventiveVisitChecklists = []; -// json['preventiveVisitChecklists'].forEach((v) { -// preventiveVisitChecklists!.add( PreventiveVisitChecklists.fromJson(v)); -// }); -// } -// if (json['preventiveVisitKits'] != null) { -// preventiveVisitKits = []; -// json['preventiveVisitKits'].forEach((v) { -// preventiveVisitKits!.add( PreventiveVisitChecklists.fromJson(v)); -// }); -// } -// if (json['preventiveVisitTimers'] != null) { -// preventiveVisitTimers = []; -// json['preventiveVisitTimers'].forEach((v) { -// preventiveVisitTimers!.add( PreventiveVisitTimers.fromJson(v)); -// }); -// } -// if (json['preventiveVisitSuppliers'] != null) { -// preventiveVisitSuppliers = []; -// json['preventiveVisitSuppliers'].forEach((v) { -// preventiveVisitSuppliers!.add( PreventiveVisitChecklists.fromJson(v)); -// }); -// } -// } -// -// Map toJson() { -// final Map data = Map(); -// 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 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 json) { -// id = json['id']; -// assetNumber = json['assetNumber']; -// assetSerialNo = json['assetSerialNo']; -// } -// -// Map toJson() { -// final Map data = Map(); -// 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 json) { -// id = json['id']; -// name = json['name']; -// value = json['value']; -// } -// -// Map toJson() { -// final Map data = Map(); -// 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 json) { -// id = json['id']; -// name = json['name']; -// value = json['value']; -// } -// -// Map toJson() { -// final Map data = Map(); -// 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? telephones; -// List? faxes; -// List? addresses; -// List? attachments; -// List? suppPersons; -// List? 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 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 = []; -// json['telephones'].forEach((v) { -// telephones!.add(v); -// }); -// } -// if (json['faxes'] != null) { -// faxes = []; -// json['faxes'].forEach((v) { -// faxes!.add(v); -// }); -// } -// if (json['addresses'] != null) { -// addresses = []; -// json['addresses'].forEach((v) { -// addresses!.add(v); -// }); -// } -// if (json['attachments'] != null) { -// attachments = []; -// json['attachments'].forEach((v) { -// attachments!.add(v); -// }); -// } -// if (json['suppPersons'] != null) { -// suppPersons = []; -// json['suppPersons'].forEach((v) { -// suppPersons!.add(SuppPersons.fromJson(v)); -// }); -// } -// if (json['suppTCodes'] != null) { -// suppTCodes = []; -// json['suppTCodes'].forEach((v) { -// suppTCodes!.add(v); -// }); -// } -// } -// -// Map toJson() { -// final Map data = {}; -// 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 json) { -// id = json['id']; -// supplierId = json['supplierId']; -// personName = json['personName']; -// personRoleId = json['personRoleId']; -// contact = json['contact']; -// externalEngCode = json['externalEngCode']; -// email = json['email']; -// } -// -// Map toJson() { -// final Map data = Map(); -// 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 activitySparePartPPMAttachments; + + PMSparePartActivityModel({ + this.id, + this.partCatalogItem, + this.activityStatus, + this.quantity, + this.installQty, + this.returnQty, + this.comment, + this.price, + List? activitySparePartPPMAttachments, + }) : activitySparePartPPMAttachments = activitySparePartPPMAttachments ?? []; + + PMSparePartActivityModel.fromJson(Map 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 toJson() { + final Map 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; + } +} diff --git a/lib/modules/cm_module/views/components/activities_list_view.dart b/lib/modules/cm_module/views/components/activities_list_view.dart index d0294f6d..b589e787 100644 --- a/lib/modules/cm_module/views/components/activities_list_view.dart +++ b/lib/modules/cm_module/views/components/activities_list_view.dart @@ -251,7 +251,6 @@ class _ActivitiesListViewState extends State { label: context.translation.assistantEmployee, value: activity.activityMaintenance?.assistantEmployees?.first.user?.userName, ), - ] else ...[ InfoTextWidget( label: context.translation.assignedEmployee, @@ -344,29 +343,30 @@ class _ActivitiesListViewState extends State { } 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, sparePartAttachments: activity.activitySparePart?.acitiySparePartAttachments ?? [], 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), + 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, 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, ), )); } diff --git a/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart b/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart index 72e0b6f9..2272212a 100644 --- a/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart +++ b/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart @@ -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(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, - provider: requestDetailProvider, - )), + builder: (context) => SparePartRequest( + 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( @@ -808,16 +815,23 @@ class ServiceRequestBottomSheet { buttonColor: AppColor.primary10, loading: false, onPressed: () { - CMDetailProvider provider = Provider.of(context, listen: false); + dynamic provider = fromCm ? Provider.of(context, listen: false) : Provider.of(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, - provider: provider, - )), + builder: (context) => SparePartRequest( + model: model, + provider: provider, + fromCm: fromCm, + ), + ), ); }, ).expanded, diff --git a/lib/modules/cm_module/views/components/service_request_detail_view.dart b/lib/modules/cm_module/views/components/service_request_detail_view.dart index 766c5586..6e4e3bd3 100644 --- a/lib/modules/cm_module/views/components/service_request_detail_view.dart +++ b/lib/modules/cm_module/views/components/service_request_detail_view.dart @@ -355,7 +355,6 @@ class _ServiceRequestDetailViewState extends State { context, MaterialPageRoute( builder: (context) => CostDetailFormScreen( - isEdit: true, model: provider.workOrderCostModel, provider: provider, ))); diff --git a/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart b/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart index 1c21e035..94b7f842 100644 --- a/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart +++ b/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart @@ -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 with TickerProviderStateMixin { - final GlobalKey _scaffoldKey = GlobalKey(); +class _CostDetailFormScreenState extends State { + 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,142 +58,131 @@ class _CostDetailFormScreenState extends State with Ticker crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - AppTextFormField( - labelText: "Spare Part Cost", - backgroundColor: AppColor.neutral100, - initialValue: widget.model?.sparePartCost?.toString(), - 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(), - textAlign: TextAlign.center, - labelText: "Labor Cost", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.labourCost = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "Travel Cost", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.travelCost?.toString(), - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.travelCost = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "Quot Amount", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.qAmount?.toString(), - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.qAmount = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "PR No", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.prNo, - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.prNo = value; - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "PO No", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.poNo, - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.poNo = value; - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "MR No", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.mrNo, - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.mrNo = value; - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "Exchange Cost", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.exchangeCost != null ? widget.model?.exchangeCost.toString() : '', - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.exchangeCost = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ).toShadowContainer(context).paddingAll(16), - ).expanded, + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AppTextFormField( + labelText: "Spare Part Cost", + backgroundColor: AppColor.neutral100, + initialValue: _localModel.sparePartCost?.toString() ?? '0', + textAlign: TextAlign.center, + enable: false, + labelStyle: AppTextStyles.textFieldLabelStyle, + textInputType: TextInputType.number, + showShadow: false, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + initialValue: _localModel.labourCost?.toString() ?? '', + textAlign: TextAlign.center, + labelText: "Labor Cost", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + _localModel.labourCost = num.tryParse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "Travel Cost", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.travelCost?.toString() ?? '', + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + _localModel.travelCost = num.tryParse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "Quot Amount", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.qAmount?.toString() ?? '', + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + _localModel.qAmount = num.tryParse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "PR No", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.prNo ?? '', + textAlign: TextAlign.center, + textInputType: TextInputType.text, + showShadow: false, + onChange: (value) { + _localModel.prNo = value.isEmpty ? null : value; + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "PO No", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.poNo ?? '', + textAlign: TextAlign.center, + textInputType: TextInputType.text, + showShadow: false, + onChange: (value) { + _localModel.poNo = value.isEmpty ? null : value; + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "MR No", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.mrNo ?? '', + textAlign: TextAlign.center, + textInputType: TextInputType.text, + showShadow: false, + onChange: (value) { + _localModel.mrNo = value.isEmpty ? null : value; + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "Exchange Cost", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.exchangeCost?.toString() ?? '', + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + _localModel.exchangeCost = num.tryParse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ).toShadowContainer(context).paddingAll(16), + ), + ), 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 with Ticker ); } - bool validateForm() { - bool isValid = true; - if (!widget.isEdit) { - return true; + Future _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; } } diff --git a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart index 46a51f77..2cc85ef4 100644 --- a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart +++ b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart @@ -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'; @@ -33,339 +32,372 @@ 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 SparePartHelperModel? model; + 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 with TickerProviderStateMixin { +class _SparePartRequestState extends State { PartsProvider? _partsProvider; bool _isLoading = false; List _spareParts = []; + List _attachments = []; - List attachments = []; final GlobalKey _formKey = GlobalKey(); - final GlobalKey _scaffoldKey = GlobalKey(); - 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(context, listen: false); - - // Initialize model with existing values or defaults - widget.provider.sparePartHelperModel = SparePartHelperModel( - id: widget.model?.id ?? 0, - workOrderId: widget.model?.workOrderId ?? widget.provider.currentWorkOrder?.data?.requestId, - sparePartAttachments: widget.model?.sparePartAttachments ?? [], - sparePart: widget.model?.sparePart ?? SparePart(), - quantity: widget.model?.quantity, - installQty: widget.model?.installQty, - returnQty: widget.model?.returnQty, - activityStatusId: widget.model?.activityStatusId ?? statusLookup.id, - 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(() {}); - }); + _initializeModel(); + _loadInitialData(); } - 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 = []; + 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.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, + installQty: widget.model?.installQty, + returnQty: widget.model?.returnQty, + activityStatusId: widget.model?.activityStatusId ?? statusLookup.id, + activityStatus: widget.model?.activityStatus ?? statusLookup, + comment: widget.model?.comment ?? '', + ); + } + log('Initialized SparePartHelperModel: ${widget.provider.sparePartHelperModel?.toJson()}'); } - @override - void dispose() { - _partQtyController.dispose(); - _installQtyController.dispose(); - _returnQtyController.dispose(); - _oracleNoController.dispose(); - _descriptionController.dispose(); - super.dispose(); - } + Future _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(builder: (context, CMDetailProvider requestDetailProvider, child) { - return Form( - key: _formKey, - child: Column( - children: [ - SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: IgnorePointer( - ignoring: widget.provider.isReadOnlyRequest, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Column( + 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: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: IgnorePointer( + ignoring: isReadOnly, + child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - context.translation.sparePartDetails.heading5(context).custom(color: AppColor.textStyleColor(context)), - // 12.height, - // SingleItemDropDownMenu( - // 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( - context: context, - title: context.translation.part, - staticData: _spareParts, - showShadow: false, - showAsBottomSheet: true, - loading: _isLoading, - initialValue: requestDetailProvider.sparePartHelperModel?.sparePart, - // backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, - onSelect: (part) async { - if (part != null) { - requestDetailProvider.sparePartHelperModel?.sparePart = part; - _oracleNoController.text = part.oracleCode ?? ''; - requestDetailProvider.updateSparePartHelperModel(widget.provider.sparePartHelperModel); - } - }, - ), - - if (requestDetailProvider.sparePartHelperModel?.sparePart?.id != null) ...[ - 12.height, - SizedBox( - height: 30.toScreenHeight, - 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); - showModalBottomSheet( - context: context, - useSafeArea: true, - isScrollControlled: true, - isDismissible: true, - backgroundColor: AppColor.bottomSheetColor(context), - builder: (BuildContext context) => PartDetailBottomSheetSheet().bottomSheetContainer(context), - ); - }, - ), - ), - ], - 12.height, - AppTextFormField( - controller: _partQtyController, - 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, - validator: (value) => value == null || value.isEmpty - ? context.translation.requiredField - : Validator.isNumeric(value) - ? null - : context.translation.onlyNumbers, - onChange: (value) { - requestDetailProvider.sparePartHelperModel?.quantity = num.tryParse(value ?? ""); - }, - onSaved: (text) { - requestDetailProvider.sparePartHelperModel?.quantity = num.tryParse(text ?? ""); - }, - ), - 12.height, - if (showInstallAndReturnQty) ...[ - AppTextFormField( - controller: _installQtyController, - 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, - validator: (value) => value == null || value.isEmpty - ? context.translation.requiredField - : Validator.isNumeric(value) - ? null - : context.translation.onlyNumbers, - onChange: (value) { - requestDetailProvider.sparePartHelperModel?.installQty = num.tryParse(value ?? ""); - }, - onSaved: (text) { - requestDetailProvider.sparePartHelperModel?.installQty = num.tryParse(text ?? ""); - }, - ), - 12.height, - AppTextFormField( - controller: _returnQtyController, - 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, - validator: (value) => value == null || value.isEmpty - ? context.translation.requiredField - : Validator.isNumeric(value) - ? null - : context.translation.onlyNumbers, - onChange: (value) { - requestDetailProvider.sparePartHelperModel?.returnQty = num.tryParse(value ?? ""); - }, - onSaved: (text) { - requestDetailProvider.sparePartHelperModel?.returnQty = num.tryParse(text ?? ""); - }, - ), - 12.height, - ], - AppTextFormField( - 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, - 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; - }, - onSaved: (value) { - requestDetailProvider.sparePartHelperModel?.comment = value; - }, - ), - 12.height, - AttachmentPicker( - label: context.translation.attachQuotation, - attachment: attachments, - buttonIcon: 'quotation_icon'.toSvgAsset(color: AppColor.primary10), - buttonColor: AppColor.primary10, + _buildFormContent( + context, + model, + provider, + showInstallAndReturnQty, ), ], - ).toShadowContainer(context), - 8.height, - ], + ), + ), ), ), - ).expanded, - if (!requestDetailProvider.isReadOnlyRequest) - FooterActionButton.footerContainer( - context: context, - child: AppFilledButton( - label: widget.provider.sparePartHelperModel?.id == 0 ? context.translation.addSparePartActivity : context.translation.updateSparePartActivity, - buttonColor: AppColor.green70, - onPressed: () { - _onSubmit(requestDetailProvider: requestDetailProvider); - }, - )), - ], + 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( + context: context, + title: context.translation.part, + staticData: _spareParts, + showShadow: false, + showAsBottomSheet: true, + loading: _isLoading, + initialValue: model?.sparePart, + onSelect: (part) { + if (part != null) { + model?.sparePart = part; + provider.updateSparePartHelperModel(model); + } + }, + ), + if (model?.sparePart?.id != null) ...[ + 12.height, + SizedBox( + height: 30.toScreenHeight, + child: Text( + 'View Parts Availability', + style: TextStyle( + fontSize: 12.toScreenWidth, + fontWeight: FontWeight.w500, + color: AppColor.primary10, + decorationColor: AppColor.primary10, + decoration: TextDecoration.underline, + ), + ).onPress(() { + provider.getStoreAvailabilityById(partId: model?.sparePart?.id); + showModalBottomSheet( + context: context, + useSafeArea: true, + isScrollControlled: true, + isDismissible: true, + backgroundColor: AppColor.bottomSheetColor(context), + builder: (ctx) => PartDetailBottomSheetSheet(provider: provider).bottomSheetContainer(ctx), + ); + }), ), - ); - }), + ], + + 12.height, + AppTextFormField( + key: ValueKey('quantity_${model?.quantity}'), + initialValue: model?.quantity?.round().toString() ?? '', + labelText: context.translation.quantity, + textInputType: TextInputType.number, + labelStyle: AppTextStyles.textFieldLabelStyle, + enable: model?.sparePart?.id != null, + validator: (value) => value == null || value.isEmpty + ? context.translation.requiredField + : Validator.isNumeric(value) + ? null + : context.translation.onlyNumbers, + onChange: (value) { + model?.quantity = num.tryParse(value); + }, + onSaved: (text) { + model?.quantity = num.tryParse(text ?? ''); + }, + ), + + 12.height, + + if (showInstallAndReturnQty) ...[ + AppTextFormField( + key: ValueKey('installQty_${model?.installQty}'), + initialValue: model?.installQty?.round().toString() ?? '', + labelText: context.translation.installedQty, + textInputType: const TextInputType.numberWithOptions(decimal: true), + labelStyle: AppTextStyles.textFieldLabelStyle, + enable: model?.sparePart?.id != null, + validator: (value) => value == null || value.isEmpty + ? context.translation.requiredField + : Validator.isNumeric(value) + ? null + : context.translation.onlyNumbers, + onChange: (value) { + model?.installQty = num.tryParse(value); + }, + onSaved: (text) { + model?.installQty = num.tryParse(text ?? ''); + }, + ), + 12.height, + AppTextFormField( + key: ValueKey('returnQty_${model?.returnQty}'), + initialValue: model?.returnQty?.round().toString() ?? '', + labelText: context.translation.returnQty, + textInputType: TextInputType.number, + labelStyle: AppTextStyles.textFieldLabelStyle, + enable: model?.sparePart?.id != null, + validator: (value) => value == null || value.isEmpty + ? context.translation.requiredField + : Validator.isNumeric(value) + ? null + : context.translation.onlyNumbers, + onChange: (value) { + model?.returnQty = num.tryParse(value); + }, + onSaved: (text) { + model?.returnQty = num.tryParse(text ?? ''); + }, + ), + 12.height, + ], + AppTextFormField( + key: ValueKey('oracle_${model?.sparePart?.oracleCode}'), + initialValue: model?.sparePart?.oracleCode ?? '', + labelStyle: AppTextStyles.textFieldLabelStyle, + labelText: context.translation.oracleNo, + textInputType: TextInputType.number, + backgroundColor: AppColor.disableFieldBackground(context), + enable: false, + ), + + 12.height, + AppTextFormField( + key: ValueKey('comment_${model?.comment}'), + initialValue: model?.comment ?? '', + labelStyle: AppTextStyles.textFieldLabelStyle, + showSpeechToText: true, + labelText: "Engineer Comment", + alignLabelWithHint: true, + textInputType: TextInputType.multiline, + onChange: (value) { + model?.comment = value; + }, + onSaved: (value) { + model?.comment = value; + }, + ), + + 12.height, + AttachmentPicker( + label: context.translation.attachQuotation, + attachment: _attachments, + buttonIcon: 'quotation_icon'.toSvgAsset(color: AppColor.primary10), + buttonColor: AppColor.primary10, + ), + ], + ).toShadowContainer(context); + } + + Widget _buildSubmitButton(BuildContext context, dynamic provider) { + return FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + label: provider.sparePartHelperModel?.id == 0 ? context.translation.addSparePartActivity : context.translation.updateSparePartActivity, + buttonColor: AppColor.green70, + onPressed: () => _onSubmit(provider), + ), ); } - _onSubmit({required CMDetailProvider requestDetailProvider}) async { - if (requestDetailProvider.sparePartHelperModel?.sparePart?.id == null) { + Future _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 (mounted) Navigator.pop(context); - if (widget.provider.sparePartHelperModel?.id == 0) { - ServiceRequestBottomSheet.addAnotherSpareRequestBottomSheet(context: context); + 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 + ); const SizedBox().flushBar( context: context, title: context.translation.sparePartActivitySuccess, @@ -376,8 +408,14 @@ class _SparePartRequestState extends State 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(builder: (context, requestDetailProvider, child) { - return requestDetailProvider.isLoading - ? CircularProgressIndicator(color: AppColor.loadingColor(context)).center - : requestDetailProvider.storeAvailability.isEmpty - ? const NoDataFound().center - : ListView.separated( - padding: const EdgeInsets.only(top: 12), - itemCount: requestDetailProvider.storeAvailability.length, - separatorBuilder: (czt, index) => 12.height, - itemBuilder: (context, index) { - StoreAvailability model = requestDetailProvider.storeAvailability[index]; - return partAvailableQuantityCard(context: context, model: model); - }, - ); - }).expanded, + 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: provider.storeAvailability.length, + separatorBuilder: (czt, index) => 12.height, + itemBuilder: (context, index) { + StoreAvailability model = provider.storeAvailability[index]; + return _partAvailableQuantityCard( + context: context, + model: model, + ); + }, + ); + }, + ), + ), ], ), ); } - 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, + ); } } diff --git a/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart index 4d61b068..fa749597 100644 --- a/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart +++ b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart @@ -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 { super.dispose(); } - List reOrderActivities = []; - - List activities = []; + List activities = []; bool disableNewActivity = false; @@ -60,15 +53,12 @@ class _PmActivitiesListViewState extends State { Widget build(BuildContext context) { UserProvider userProvider = Provider.of(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(builder: (context, CMDetailProvider requestDetailProvider, child) { - activities = requestDetailProvider.currentWorkOrder!.data!.activities; - requestDetailProvider.lastVisitedRequestActivityId = (activities.isNotEmpty ? activities.last.id : -1)!; + body: Consumer(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 { 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 { ); } - 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 { 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 { ], ), 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, + ), + InfoTextWidget( + label: context.translation.oracleCode, + value: activity.partCatalogItem?.oracleCode, ), - if (activity.activitySparePart?.acitiySparePartAttachments?.isNotEmpty ?? false) ...[ - 6.height, + if (activity.comment?.isNotEmpty ?? false) ...[ const Divider().defaultStyle(context), - FilesList(images: activity.activitySparePart!.acitiySparePartAttachments!.map((toElement) => URLs.getFileUrl(toElement.name!)!).toList()), + InfoTextLabelWidget(label: activity.comment ?? ""), + ], + if (activity.activitySparePartPPMAttachments.isNotEmpty) ...[ + const Divider().defaultStyle(context), + 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 ), )); } diff --git a/lib/modules/pm_module/ppm_wo/activities/pm_cost_detail_page.dart b/lib/modules/pm_module/ppm_wo/activities/pm_cost_detail_page.dart deleted file mode 100644 index 24cd885b..00000000 --- a/lib/modules/pm_module/ppm_wo/activities/pm_cost_detail_page.dart +++ /dev/null @@ -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 createState() => _PmCostDetailsViewState(); -} - -class _PmCostDetailsViewState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - UserProvider userProvider = Provider.of(context, listen: false); - - ///TODO: replace with pm provider and data from provider and remove the extra widgets as well. - return Consumer(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); - } -} diff --git a/lib/modules/pm_module/ppm_wo/ppm_details_page.dart b/lib/modules/pm_module/ppm_wo/ppm_details_page.dart index 84bac35f..ffcc3679 100644 --- a/lib/modules/pm_module/ppm_wo/ppm_details_page.dart +++ b/lib/modules/pm_module/ppm_wo/ppm_details_page.dart @@ -117,7 +117,7 @@ class _PpmDetailsPageState extends State { ], ).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 { 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()); }), diff --git a/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart b/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart index a61a8efd..e431d5de 100644 --- a/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart +++ b/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart @@ -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 with TickerProviderStateMixin { @override void initState() { + super.initState(); ppmProvider = Provider.of(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,70 +218,84 @@ class _UpdatePpmState extends State with TickerProviderStateMixin { ).expanded, FooterActionButton.footerContainer( context: context, - child: Row( + child: Column( children: [ - if (tabIndex == 1) ...[ - AppFilledButton( - buttonColor: _tabController!.index == 0 ? null : AppColor.background(context), - // buttonColor: _tabController!.index == 0 ? null : AppColor.background(context), - textColor: _tabController!.index == 0 ? null : AppColor.blueStatus(context), - showBorder: true, - disableButton: _tabController!.index == 0, - onPressed: () { - if (_tabController!.index == 0) { - return; - } - _tabController?.animateTo(_tabController!.index - 1); - setState(() {}); - return; - }, - label: context.translation.back, - ).expanded, - 8.width, - ], - if (!ppmProvider.isReadOnly) ...[ - AppFilledButton( - // buttonColor: AppColor.white60, - // textColor: AppColor.neutral50, - buttonColor: context.isDark ? AppColor.neutral50 : AppColor.white60, - textColor: context.isDark ? Colors.white : AppColor.neutral50, - onPressed: () { - _onSubmit(status: 0); - }, - label: context.translation.save, - ).expanded, - 8.width, - AppFilledButton( - onPressed: () { - if (tabIndex == 0) { - _onSubmit(status: 1); - return; - } - if (ppmProvider.totalTabs == _tabController!.index + 1) { - _onSubmit(status: 1); - } else { - _tabController?.animateTo(_tabController!.index + 1); - setState(() {}); - } - }, - label: tabIndex == 0 ? "Complete".addTranslation : ((ppmProvider.totalTabs == _tabController!.index + 1) ? "Complete".addTranslation : context.translation.next), - ).expanded, - ] else ...[ - Visibility( - visible: tabIndex != 0, - child: AppFilledButton( - onPressed: () { - if (ppmProvider.totalTabs == _tabController!.index + 1) { - Navigator.pop(context); + Row( + children: [ + if (tabIndex == 1) ...[ + AppFilledButton( + buttonColor: _tabController!.index == 0 ? null : AppColor.background(context), + // buttonColor: _tabController!.index == 0 ? null : AppColor.background(context), + textColor: _tabController!.index == 0 ? null : AppColor.blueStatus(context), + showBorder: true, + disableButton: _tabController!.index == 0, + onPressed: () { + if (_tabController!.index == 0) { + return; + } + _tabController?.animateTo(_tabController!.index - 1); + setState(() {}); return; - } - _tabController?.animateTo(_tabController!.index + 1); - setState(() {}); - }, - label: ppmProvider.totalTabs == _tabController!.index + 1 ? context.translation.close : context.translation.next, - ).expanded, - ), - ] + }, + label: context.translation.back, + ).expanded, + 8.width, + ], + if (!ppmProvider.isReadOnly) ...[ + AppFilledButton( + // buttonColor: AppColor.white60, + // textColor: AppColor.neutral50, + buttonColor: context.isDark ? AppColor.neutral50 : AppColor.white60, + textColor: context.isDark ? Colors.white : AppColor.neutral50, + onPressed: () { + _onSubmit(status: 0); + }, + label: context.translation.save, + ).expanded, + 8.width, + AppFilledButton( + onPressed: () { + if (tabIndex == 0) { + _onSubmit(status: 1); + return; + } + if (ppmProvider.totalTabs == _tabController!.index + 1) { + _onSubmit(status: 1); + } else { + _tabController?.animateTo(_tabController!.index + 1); + setState(() {}); + } + }, + label: tabIndex == 0 ? "Complete".addTranslation : ((ppmProvider.totalTabs == _tabController!.index + 1) ? "Complete".addTranslation : context.translation.next), + ).expanded, + ] else ...[ + Visibility( + visible: tabIndex != 0, + child: AppFilledButton( + onPressed: () { + if (ppmProvider.totalTabs == _tabController!.index + 1) { + Navigator.pop(context); + return; + } + _tabController?.animateTo(_tabController!.index + 1); + setState(() {}); + }, + label: ppmProvider.totalTabs == _tabController!.index + 1 ? context.translation.close : context.translation.next, + ).expanded, + ), + ] + ], + ), + + 12.height, + AppFilledButton( + onPressed: () async { + Navigator.of(context).push(MaterialPageRoute(builder: (_) => const PmActivitiesListView())); + // getVisitData(); + }, + buttonColor: AppColor.neutral50, + label: context.translation.activities, + ), ], ), ), diff --git a/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart b/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart index 55170e3b..91eafc9d 100644 --- a/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart +++ b/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart @@ -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 { ), ], ).toShadowContainer(context), + 16.height, + costCard(context, ppmProvider), ], ), ), @@ -454,6 +458,79 @@ class _WoInfoFormState extends State { ); } + 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;