diff --git a/v1/src/apps/private/loads/services.js b/v1/src/apps/private/loads/services.js index 4260763..d52d2a7 100644 --- a/v1/src/apps/private/loads/services.js +++ b/v1/src/apps/private/loads/services.js @@ -2,6 +2,7 @@ const { getModel } = require( '../../../lib/Models' ); const { getPagination, genKey } = require( '../../../lib/Misc.js' ); const { GenericHandler } = require( '../../../lib/Handlers/Generic.handler.js' ); +const { onPatchEvent } = require('../../../lib/Handlers/Loads.handler'); const Model = getModel('loads'); const CompanyModel = getModel('companies'); const ProposalsModel = getModel('proposals'); @@ -274,19 +275,81 @@ const getById = async(req, res) => { } }; +function getDataToModify( data ){ + /** + * Take the only fields from model that + * should be modifiable by the client. + * The rest are populated on demand by the event handlers. + */ + let data_fields = { + alert_list : null, + origin_warehouse : null, + destination_warehouse : null, + origin: null, + origin_geo: null, + destination: null, + destination_geo: null, + categories : null, + product : null, + truck_type : null, + tyre_type : null, + weight : null, + estimated_cost : null, + distance : null, + actual_cost : null, + status : null, + load_status : null, + contract_start_date : null, + contract_end_date : null, + est_loading_date : null, + est_unloading_date : null, + published_date : null, + loaded_date : null, + transit_date : null, + delivered_date : null, + load_status_updated : null, + notes : null, + payment_term : null, + terms_and_conditions : null, + }; + + let filtered_data = {}; + + if( Object.keys( data_fields ).length === 0 ){ + throw "nothing to change"; + } + + for ( const [key, value] of Object.entries( data_fields ) ) { + if( Object.hasOwn( data, key ) ){ + filtered_data[ key ] = data[ key ]; + } + } + + if( Object.keys( filtered_data ).length === 0 ){ + throw "nothing to change"; + } + + return filtered_data; +} const patchLoad = async(req, res) => { try{ const elementId = req.params.id; - const permissions = req.context.permissions; - const data = req.body; const load = await findElementById( elementId ); + if( !load ){ throw "You can't modify this load"; } - if( !data ){ + + if( !req.body ){ throw "load data not sent"; } + + const data = getDataToModify( req.body ); + await Model.findByIdAndUpdate( elementId , data ); + + await onPatchEvent( req.context.userId, elementId, data ); + return res.send( await Model.findById( elementId ) ); }catch(error){ console.error( error ); @@ -310,7 +373,7 @@ const postLoad = async(req, res) => { data.company = companyId; data.posted_by = userId; data.name = user_name; - const load = new Model( data ); + const load = new Model( data );/// ToDo, check data content and normalize Dates!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! await load.save(); const id = "" + load._id; diff --git a/v1/src/apps/private/proposals/services.js b/v1/src/apps/private/proposals/services.js index 4524b38..cbbc36f 100644 --- a/v1/src/apps/private/proposals/services.js +++ b/v1/src/apps/private/proposals/services.js @@ -108,19 +108,55 @@ const getById = async(req, res) => { } }; +function getDataToModify( data ){ + /** + * Take the only fields from model that + * should be modifiable by the client. + * The rest are populated on demand by the event handlers. + */ + let data_fields = { + vehicle : null, + comment : null, + is_accepted : null, + }; + + let filtered_data = {}; + + if( Object.keys( data_fields ).length === 0 ){ + throw "nothing to change"; + } + + for ( const [key, value] of Object.entries( data_fields ) ) { + if( Object.hasOwn( data, key ) ){ + filtered_data[ key ] = data[ key ]; + } + } + + if( Object.keys( filtered_data ).length === 0 ){ + throw "nothing to change"; + } + + return filtered_data; +} const patchProposal = async(req, res) => { try{ const elementId = req.params.id; const proposal = await Model.findById( elementId ); - const data = req.body; + if( !proposal ){ throw "You can't modify this proposal"; } - if( !data ){ + if( !req.body ){ throw "proposal data not sent"; } + + /// Only get data to apply, filter out the rest + const data = getDataToModify( req.body ); + await Model.findByIdAndUpdate( elementId , data ); + await onPatchEvent( req.context.userId, elementId, data ); + return res.send( await Model.findById( elementId ) ); }catch(error){ console.error( error ); @@ -130,13 +166,16 @@ const patchProposal = async(req, res) => { const postProposal = async(req, res) => { try{ - const data = req.body; - if( !data ){ + if( !req.body ){ throw "proposal data not sent"; } - const proposal = new Model( data ); - await proposal.save(); + let data = req.body; + data.bidder = req.context.userId; + data.carrier = req.context.companyId; + const proposal = new Model( data ); + + await proposal.save(); await onPostEvent( req.context.userId, proposal.id, data); return res.send( proposal ); }catch(error){ diff --git a/v1/src/apps/private/vehicles/services.js b/v1/src/apps/private/vehicles/services.js index 6977221..722ff36 100644 --- a/v1/src/apps/private/vehicles/services.js +++ b/v1/src/apps/private/vehicles/services.js @@ -2,6 +2,7 @@ const { getModel } = require( '../../../lib/Models' ); const { getPagination, genKey } = require( '../../../lib/Misc' ); const { GenericHandler } = require( '../../../lib/Handlers/Generic.handler' ); +const { onPatchEvent } = require('../../../lib/Handlers/Vehicles.handler'); const Model = getModel('vehicles'); const CompanyModel = getModel('companies'); @@ -139,6 +140,59 @@ const getById = async(req, res) => { } }; + +function getDataToModify( data ){ + /** + * Take the only fields from model that + * should be modifiable by the client. + * The rest are populated on demand by the event handlers. + */ + let data_fields = { + vehicle_name: null, + vehicle_number: null, + circulation_serial_number: null, + trailer_plate_1: null, + trailer_plate_2: null, + truck_type: null, + tyre_type: null, + city: null, + state: null, + background_tracking: null, + status: null, + categories: null, + posted_by: null, + published_date: null, + available_date: null, + is_available: null, + active_load: null, + load_shipper: null, + available_in: null, + destino: null, + driver: null, + notes: null, + last_location_lat: null, + last_location_lng: null, + last_location_time: null, + }; + + let filtered_data = {}; + + if( Object.keys( data_fields ).length === 0 ){ + throw "nothing to change"; + } + + for ( const [key, value] of Object.entries( data_fields ) ) { + if( Object.hasOwn( data, key ) ){ + filtered_data[ key ] = data[ key ]; + } + } + + if( Object.keys( filtered_data ).length === 0 ){ + throw "nothing to change"; + } + + return filtered_data; +} const patchVehicle = async(req, res) => { try{ const companyId = req.context.companyId; @@ -146,20 +200,27 @@ const patchVehicle = async(req, res) => { const permissions = req.context.permissions; const company = await CompanyModel.findById( companyId ); const vehicle = await findElementById( elementId , companyId ); - const data = req.body; + if( !vehicle ){ throw "You can't modify this vehicle"; } - if( !data ){ + if( !req.body ){ throw "Vehicle data not sent"; } if( permissions !== "role_carrier" ){ throw "You can't modify vehicles"; } + /// Only get data to apply, filter out the rest + const data = getDataToModify( req.body ); + data.company = companyId; data.company_name = company.company_name; + await Model.findByIdAndUpdate( elementId , data ); + + await onPatchEvent( req.context.userId, elementId, data ); + return res.send( await Model.findById( elementId ) ); }catch(error){ console.error( error ); diff --git a/v1/src/config/apiConfig.json b/v1/src/config/apiConfig.json index e913f0f..f0b1d28 100644 --- a/v1/src/config/apiConfig.json +++ b/v1/src/config/apiConfig.json @@ -16,7 +16,7 @@ } }, "version" : { - "version" : "1.5.0", + "version" : "1.5.1", "name": "ETA Beta", "date":"06/2025" }, diff --git a/v1/src/lib/Handlers/Events/Loads/index.js b/v1/src/lib/Handlers/Events/Loads/index.js new file mode 100644 index 0000000..42eabb0 --- /dev/null +++ b/v1/src/lib/Handlers/Events/Loads/index.js @@ -0,0 +1,60 @@ +'user strict'; +const { getModel } = require( '../../../Models' ); + +const loadsModel = getModel('loads'); +const vehiclesModel = getModel('vehicles'); +const proposalsModel = getModel('proposals'); + +/** + * When a load is delivered, notify all involved parties + * @param {*} userId -> Responsible of the change + * @param {*} elementId -> Element Affected + */ +async function onDelivered( userId, elementId ){ + const load = await loadsModel.findById( elementId ); + const proposal_list = await proposalsModel.find({ + load: load.id, + is_accepted: true, + is_completed: false + }); + const vehicle_list = await vehiclesModel.find({ + active_load: load.id + }); + + const current_date = new Date(); + + /// Update Load: Remove vehicle and driver reference for data safety. + await loadsModel.findByIdAndUpdate( proposal.load, { + driver: null, + vehicle: null, + status: "Closed", + delivered_date: current_date, + load_status_updated: current_date, + }); + + /// Update proposals related to this load. Ideally, just one. + // remove vehicle for data safety. + for( const proposal of proposal_list ){ + await proposalsModel.findByIdAndUpdate( + proposal.id, + { + is_completed : true, + vehicle : null, + } + ); + } + + /// Update vehicles related to this load. Ideally, just one. + for( const vehicle of vehicle_list ){ + await vehiclesModel.findByIdAndUpdate( + vehicle.id, + { + active_load: null, + load_shipper: null, + status: "Free" + } + ); + } +} + +module.exports = { onDelivered }; diff --git a/v1/src/lib/Handlers/Events/Proposals/index.js b/v1/src/lib/Handlers/Events/Proposals/index.js index 8aae3b4..9c3b38e 100644 --- a/v1/src/lib/Handlers/Events/Proposals/index.js +++ b/v1/src/lib/Handlers/Events/Proposals/index.js @@ -13,7 +13,9 @@ const notificationsModel = getModel('notifications'); const productsModel = getModel('products'); /** - * When the proposal is created then the load owner should be notified + * When the proposal is created then the load owner should be notified. + * @param {*} userId -> Responsible of the change + * @param {*} proposalId -> Proposal Affected */ async function onProposalCreate( userId, proposalId ){ const proposal = await proposalsModel.findById( proposalId ); @@ -32,7 +34,9 @@ async function onProposalCreate( userId, proposalId ){ } /** - * When a proposal is removed from the load, it is considered as rejected + * When a proposal is removed from the load, it is considered as rejected. + * @param {*} userId -> Responsible of the change + * @param {*} proposalId -> Proposal Affected */ async function onProposalRejected( userId, proposalId ){ const proposal = await proposalsModel.findById( proposalId ); @@ -56,7 +60,9 @@ async function onProposalRejected( userId, proposalId ){ } /** - * When a proposal is accepted by the shipper + * When a proposal is accepted by the shipper. + * @param {*} userId -> Responsible of the change + * @param {*} proposalId -> Proposal Affected */ async function onProposalAccepted( userId, proposalId ){ const shipper_user = await usersModel.findById( userId ); @@ -86,4 +92,21 @@ async function onProposalAccepted( userId, proposalId ){ await onAcceptedEvents.sendNotification( proposal, load ); } -module.exports = { onProposalCreate, onProposalRejected, onProposalAccepted }; +/** + * When a vehicle changes from the original proposal + * @param {*} userId -> Responsible of the change + * @param {*} proposalId -> Proposal Affected + */ +async function onProposalVehicleChanged( userId, proposalId ){ + const proposal = await proposalsModel.findById( proposalId ); + const vehicle = await vehiclesModel.findById( proposal.vehicle ); + + /// Update Load: + /// Add driver and vehicle + await loadsModel.findByIdAndUpdate( proposal.load, { + driver : vehicle.driver, + vehicle : proposal.vehicle + } ); +} + +module.exports = { onProposalCreate, onProposalRejected, onProposalAccepted, onProposalVehicleChanged }; diff --git a/v1/src/lib/Handlers/Events/Vehicles/index.js b/v1/src/lib/Handlers/Events/Vehicles/index.js new file mode 100644 index 0000000..a452928 --- /dev/null +++ b/v1/src/lib/Handlers/Events/Vehicles/index.js @@ -0,0 +1,35 @@ +'user strict'; +const { getModel } = require( '../../../Models' ); + +const usersModel = getModel('users'); +const vehiclesModel = getModel('vehicles'); +const proposalsModel = getModel('proposals'); +const loadsModel = getModel('loads'); + +/** + * When a vehicle's driver changes, notify all involved parties + * @param {*} userId -> Responsible of the change + * @param {*} vehicleId -> Proposal Affected + */ +async function onVehicleDriverChanged( userId, vehicleId ){ + const vehicle = await vehiclesModel.findById( vehicleId ); + const driver = await usersModel.findById( vehicle.driver ); + const proposal_list = await proposalsModel.find({ + vehicle: vehicleId, + is_accepted: true, + is_completed: false + }); + + /// Update proposals related to this load. Ideally, just one. + // remove vehicle for data safety. + for( const proposal of proposal_list ){ + /// Update Load: + /// Add driver and vehicle + await loadsModel.findByIdAndUpdate( proposal.load, { + driver : driver, + vehicle : vehicle + } ); + } +} + +module.exports = { onVehicleDriverChanged }; diff --git a/v1/src/lib/Handlers/Loads.handler.js b/v1/src/lib/Handlers/Loads.handler.js new file mode 100644 index 0000000..67de0df --- /dev/null +++ b/v1/src/lib/Handlers/Loads.handler.js @@ -0,0 +1,16 @@ +'user strict'; +const LoadsEvents = require( './Events/Loads' ); + +/** + * When the element is modified + * @param {*} userId -> Responsible of the change + * @param {*} elementId -> Element Affected + * @param {*} newData -> New Data Applied + */ +async function onPatchEvent( userId, elementId, newData ){ + if( newData.load_status === "Delivered" ){ + LoadsEvents.onDelivered( userId, elementId ); + } +} + +module.exports = { onPatchEvent }; diff --git a/v1/src/lib/Handlers/Proposals.handler.js b/v1/src/lib/Handlers/Proposals.handler.js index 8d5c958..1d2a17b 100644 --- a/v1/src/lib/Handlers/Proposals.handler.js +++ b/v1/src/lib/Handlers/Proposals.handler.js @@ -1,19 +1,11 @@ 'user strict'; -const { getModel } = require( '../Models' ); const ProposalsEvents = require( './Events/Proposals' ); -const vehiclesModel = getModel('vehicles'); -const proposalsModel = getModel('proposals'); -const loadsModel = getModel('loads'); -const usersModel = getModel('users'); -const companiesModel = getModel('companies'); -const notificationsModel = getModel('notifications'); - - /** * When the proposal is created then the load owner should be notified - * @param {*} id - * @param {*} newProposalData + * @param {*} userId -> Responsible of the change + * @param {*} proposalId -> Proposal Affected + * @param {*} newProposalData -> New Data Applied * @returns */ async function onPostEvent( userId, proposalId ,newProposalData ){ @@ -24,15 +16,25 @@ async function onPostEvent( userId, proposalId ,newProposalData ){ /** * When the proposal is accepted then the load should be updated to have the latest * shipper, vehicle, etc. - * @param {*} id - * @param {*} newProposalData + * @param {*} userId -> Responsible of the change + * @param {*} proposalId -> Proposal Affected + * @param {*} newProposalData -> New Data Applied * @returns */ async function onPatchEvent( userId, proposalId , newProposalData ){ - if( !newProposalData.is_accepted ){ - await ProposalsEvents.onProposalRejected( userId, proposalId ); - }else{ - await ProposalsEvents.onProposalAccepted( userId, proposalId ); + + /** When proposal is accepted, then the user is assumed to be a shipper, and the data is populated */ + if( Object.hasOwn( newProposalData, "is_accepted" ) ){ + if( newProposalData.is_accepted ){ + await ProposalsEvents.onProposalAccepted( userId, proposalId ); + }else{ + await ProposalsEvents.onProposalRejected( userId, proposalId ); + } + } + + /** When vehicle have changed, notify the change */ + if( Object.hasOwn( newProposalData, "vehicle" ) ){ + await ProposalsEvents.onProposalVehicleChanged( userId, proposalId ); } } diff --git a/v1/src/lib/Handlers/Vehicles.handler.js b/v1/src/lib/Handlers/Vehicles.handler.js new file mode 100644 index 0000000..9a73d28 --- /dev/null +++ b/v1/src/lib/Handlers/Vehicles.handler.js @@ -0,0 +1,17 @@ +'user strict'; +const VehiclesEvents = require( './Events/Vehicles' ); + +/** + * When the element is modified + * @param {*} userId -> Responsible of the change + * @param {*} elementId -> Element Affected + * @param {*} newData -> New Data Applied + */ +async function onPatchEvent( userId, elementId , newData ){ + /** When driver have changed, notify the change */ + if( newData.driver ){ + await VehiclesEvents.onVehicleDriverChanged( userId, elementId ); + } +} + +module.exports = { onPatchEvent };