//! # Unit Jobs Pallet
//! <!-- Original author of paragraph: @faiz
//!
//! ## Overview
//!
//! Pallet that allows a user to post a job , users can create a job seeker profile and apply for jobs ,
//! users who had posted the job can see list of job seekers applied for that job.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! * create job offer.
//! * add job seeker profile.
//! * delete job seeker.
//! * delete job offering.
//! * apply for a job
//! * update a job offer
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! - `add_job_offer`: add a job offer.
//! - `add_job_seeker` : users can create a job seeker profile .
//! - `delete_job_seeker`: delete job seeker profile.
//! - `delete_job_offering`: delete a job offering.
//! - `apply_for_a_job`: apply for a job.
//! - `update_job_offer`: update the job.
//!
//!//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function.
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;
mod types;
pub use types::*;
use traits::profile::ProfileInspect;
use traits::subaccounts::{ SubAccounts, AccountOrigin };
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
// Definition of the pallet logic, to be aggregated at runtime definition through
// `construct_runtime`.
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
pub type BalanceOf<T> = <T as pallet_assets::Config>::Balance;
use frame_support::dispatch::Vec;
// Import various types used to declare pallet in scope
use super::*;
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
/// The module configuration trait.
#[pallet::config]
pub trait Config: frame_system::Config + pallet_assets::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
#[pallet::constant]
type StringLimit: Get<u32>;
/// Type to access the profile pallet
type Profile: ProfileInspect<
Self::AccountId,
<Self as pallet::Config>::StringLimit
>;
/// The maximum length of data stored on-chain.
#[pallet::constant]
type DataLimit: Get<u32>;
/// Type to access the sub account pallet
type SubAccounts: SubAccounts<Self::AccountId, AccountOrigin>;
}
#[pallet::storage]
#[pallet::getter(fn user_id)]
/// current user id
pub type JobId<T> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn job_offering)]
/// Job Offerings for a Job
pub type JobOffering<T: Config> = StorageMap<
_,
Blake2_128Concat,
u32,
OfferingJob<T::AccountId, BalanceOf<T>, T::AssetId, BoundedVec<u8, T::DataLimit>>
>;
#[pallet::storage]
#[pallet::getter(fn job_seeking)]
/// All Job Seekers
pub type JobSeeker<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
SeekingJob<T::AccountId, BalanceOf<T>, T::AssetId, BoundedVec<u8, T::DataLimit>>
>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Job Applicant Added
JobApplicantAdded {
applicant: T::AccountId,
},
/// Job Seeker Added
JobSeekerAdded {
applicant: T::AccountId,
},
/// Job Seeker deleted
JobSeekerDeleted {
applicant: T::AccountId,
},
/// Job Offering Added
JobOfferingAdded {
job_id: u32,
},
/// Job Offering deleted
JobOfferingDeleted {
applicant: T::AccountId,
},
}
#[pallet::error]
pub enum Error<T> {
/// Invalid Owner For This Job
InvalidOwner,
/// Job Seeker Already Exists
AlreadyExists,
/// Already Applied For This Job
AlreadyApplied,
/// Job Account Not Found
JobAccountNotFound,
/// Overflow While performing calculation
Overflow,
/// Job Not Found
NotFound,
/// Profile Not Found
ProfileNotFound,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Creates an Job Offer from a public origin.
///
/// This adds a Job Offering and the owner of that will be the Origin.
///
/// Origin must be Signed.
///
/// Parameters:
/// - `title`: The of the job offering
/// - `description`: The description for that job offering
/// - `expected_compensation`: Expected Compensation for that job offer
/// - `asset`: The asset id for that job offering
///
/// Emits `JobOfferingAdded` event when successful.
#[pallet::call_index(0)]
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
pub fn add_job_offer(
origin: OriginFor<T>,
title: BoundedVec<u8, T::DataLimit>,
description: BoundedVec<u8, T::DataLimit>,
expected_compensation: BalanceOf<T>,
asset: T::AssetId
) -> DispatchResult {
let mut _who = ensure_signed(origin.clone())?;
// Mutate the origin to transfer funds from the main account
_who = <T as pallet::Config>::SubAccounts::get_main_account(_who)?;
let current_job_id = JobId::<T>::get();
let new_id = current_job_id.checked_add(1).ok_or(Error::<T>::Overflow)?;
let job_applicants: Vec<T::AccountId> = Vec::new(); // vector of job seekers id
let job = OfferingJob {
owner: _who,
title,
description,
job_id: new_id,
expected_compensation,
asset,
job_applicants,
};
JobOffering::<T>::insert(new_id, job);
// update the job id
JobId::<T>::put(new_id);
Self::deposit_event(Event::JobOfferingAdded { job_id: new_id });
Ok(())
}
/// Adds a Job Seeker from a public origin.
///
/// This adds a Job Seeker and the owner of that will be the Origin.
///
/// Origin must be Signed.
///
/// Parameters:
/// - `role_name`: The name of the role
/// - `cv_link`: The cv link for the job seeker
/// - `expected_compensation`: Expected Compensation for that job seeker
/// - `asset`: The asset id for that job seeker
///
/// Emits `JobSeekerAdded` event when successful.
#[pallet::call_index(1)]
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
pub fn add_job_seeker(
origin: OriginFor<T>,
role_name: BoundedVec<u8, T::DataLimit>,
cv_link: BoundedVec<u8, T::DataLimit>,
expected_compensation: BalanceOf<T>,
asset: T::AssetId
) -> DispatchResult {
let mut _who = ensure_signed(origin.clone())?;
// Mutate the origin to transfer funds from the main account
_who = <T as pallet::Config>::SubAccounts::get_main_account(_who)?;
ensure!(JobSeeker::<T>::contains_key(_who.clone()) == false, Error::<T>::AlreadyExists);
let job = SeekingJob {
owner: _who.clone(),
role_name,
cv_link,
asset,
expected_compensation,
};
JobSeeker::<T>::insert(_who.clone(), job);
Self::deposit_event(Event::JobSeekerAdded { applicant: _who });
Ok(())
}
/// Delete's a Job Seeker from a public origin.
///
/// This delete's a Job Seeker and the owner of that will be the Origin.
///
/// Origin must be Signed.
///
/// Parameters:
/// -
///
/// Emits `JobSeekerDeleted` event when successful.
#[pallet::call_index(2)]
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
pub fn delete_job_seeker(origin: OriginFor<T>) -> DispatchResult {
let mut _who = ensure_signed(origin.clone())?;
// Mutate the origin to transfer funds from the main account
_who = <T as pallet::Config>::SubAccounts::get_main_account(_who)?;
ensure!(JobSeeker::<T>::contains_key(_who.clone()), Error::<T>::InvalidOwner);
JobSeeker::<T>::remove(_who.clone());
Self::deposit_event(Event::JobSeekerDeleted { applicant: _who });
Ok(())
}
/// Delete's a Job offering from a public origin.
///
/// This delete's a Job Offering and the owner of that will be the Origin.
///
/// Origin must be Signed.
///
/// Parameters:
/// - `id`: The id of that job
///
/// Emits `JobSeekerDeleted` event when successful.
#[pallet::call_index(3)]
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
pub fn delete_job_offering(origin: OriginFor<T>, id: u32) -> DispatchResult {
let mut _who = ensure_signed(origin.clone())?;
// Mutate the origin to transfer funds from the main account
_who = <T as pallet::Config>::SubAccounts::get_main_account(_who)?;
ensure!(JobOffering::<T>::contains_key(id), Error::<T>::NotFound);
let obj = JobOffering::<T>::get(id).ok_or(Error::<T>::NotFound)?;
ensure!(obj.owner == _who.clone(), Error::<T>::InvalidOwner);
JobOffering::<T>::remove(id);
Self::deposit_event(Event::JobOfferingDeleted { applicant: _who });
Ok(())
}
/// Applies for a Job offering from a public origin.
///
/// This Applies's for a Job Offering and the owner of that will be the Origin and must be a job seeker.
///
/// Origin must be Signed.
///
/// Parameters:
/// - `job_id`: The id of that job
///
#[pallet::call_index(4)]
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
pub fn apply_for_a_job(origin: OriginFor<T>, job_id: u32) -> DispatchResult {
let mut _who = ensure_signed(origin.clone())?;
// Mutate the origin to transfer funds from the main account
_who = <T as pallet::Config>::SubAccounts::get_main_account(_who)?;
ensure!(JobSeeker::<T>::contains_key(_who.clone()), Error::<T>::JobAccountNotFound);
ensure!(JobOffering::<T>::contains_key(job_id), Error::<T>::NotFound);
let mut obj = JobOffering::<T>::get(job_id).ok_or(Error::<T>::NotFound)?;
let if_found = obj.job_applicants.iter().find(|x| **x == _who);
ensure!(if_found.is_none(), Error::<T>::AlreadyApplied);
obj.job_applicants.push(_who.clone());
JobOffering::<T>::insert(job_id, obj);
Self::deposit_event(Event::JobApplicantAdded { applicant: _who });
Ok(())
}
/// Updates a Job offering from a public origin.
///
/// This Updates a Job Offering and the owner of that will be the Origin.
///
/// Origin must be Signed.
///
/// Parameters:
/// - `id`: The id of that job
/// - `title`: The title of that job offering
/// - `description`: The description of that job offering
/// - `expected_compensation`: The expected_compensation of that job offering
/// - `asset`: The asset for that job offering
///
#[pallet::call_index(5)]
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
pub fn update_job_offer(
origin: OriginFor<T>,
id: u32,
title: BoundedVec<u8, T::DataLimit>,
description: BoundedVec<u8, T::DataLimit>,
expected_compensation: BalanceOf<T>,
asset: T::AssetId
) -> DispatchResult {
let mut _who = ensure_signed(origin.clone())?;
// Mutate the origin to transfer funds from the main account
_who = <T as pallet::Config>::SubAccounts::get_main_account(_who)?;
ensure!(JobOffering::<T>::contains_key(id), Error::<T>::NotFound);
let obj = JobOffering::<T>::get(id).ok_or(Error::<T>::NotFound)?;
ensure!(obj.owner == _who, Error::<T>::InvalidOwner);
JobOffering::<T>::remove(id);
let job = OfferingJob {
owner: _who,
title,
description,
job_id: id,
expected_compensation,
asset,
job_applicants: obj.job_applicants,
};
JobOffering::<T>::insert(id, job);
Ok(())
}
}
}