|
@@ -1,3 +1,13 @@
|
|
|
+//! # Working team pallet
|
|
|
+//! Working team pallet for the Joystream platform.
|
|
|
+//! Contains abstract working team workflow.
|
|
|
+//!
|
|
|
+//! Exact working group (eg.: forum working group) should create an instance of the Working group module.
|
|
|
+//!
|
|
|
+//! ## Supported extrinsics
|
|
|
+//!
|
|
|
+//! - [add_opening](./struct.Module.html#method.add_opening) - Add an opening for a regular worker/lead role.
|
|
|
+
|
|
|
// Ensure we're `no_std` when compiling for Wasm.
|
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
|
@@ -11,25 +21,32 @@ mod tests;
|
|
|
mod types;
|
|
|
|
|
|
use codec::Codec;
|
|
|
-use frame_support::dispatch::DispatchResult;
|
|
|
//use frame_support::storage::IterableStorageMap;
|
|
|
//use frame_support::traits::{Currency, ExistenceRequirement, Get, Imbalance, WithdrawReasons};
|
|
|
use frame_support::traits::Get;
|
|
|
-use frame_support::{decl_event, decl_module, decl_storage, Parameter, StorageValue}; // ensure, print,
|
|
|
+use frame_support::{decl_event, decl_module, decl_storage, Parameter, StorageValue, ensure}; // print,
|
|
|
use sp_arithmetic::traits::{BaseArithmetic, One};
|
|
|
use sp_runtime::traits::{Hash, MaybeSerialize, Member};
|
|
|
// use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
|
|
|
// use sp_std::vec;
|
|
|
// use sp_std::vec::Vec;
|
|
|
+use system::ensure_signed;
|
|
|
// use system::{ensure_root, ensure_signed};
|
|
|
|
|
|
use common::constraints::InputValidationLengthConstraint;
|
|
|
|
|
|
pub use errors::Error;
|
|
|
-pub use types::{JobOpening, JobOpeningType};
|
|
|
+pub use types::{JobApplication, JobOpening, JobOpeningType};
|
|
|
+
|
|
|
+// Type simplification
|
|
|
+type JobApplicationRecord<T, I> =
|
|
|
+ JobApplication<<T as system::Trait>::AccountId, <T as Trait<I>>::OpeningId, MemberId<T>>;
|
|
|
+
|
|
|
+/// Member identifier in membership::member module.
|
|
|
+pub type MemberId<T> = <T as membership::Trait>::MemberId;
|
|
|
|
|
|
/// The _Team_ main _Trait_
|
|
|
-pub trait Trait<I: Instance>: system::Trait {
|
|
|
+pub trait Trait<I: Instance>: system::Trait + membership::Trait {
|
|
|
/// OpeningId type
|
|
|
type OpeningId: Parameter
|
|
|
+ Member
|
|
@@ -40,6 +57,16 @@ pub trait Trait<I: Instance>: system::Trait {
|
|
|
+ MaybeSerialize
|
|
|
+ PartialEq;
|
|
|
|
|
|
+ /// ApplicationId type
|
|
|
+ type ApplicationId: Parameter
|
|
|
+ + Member
|
|
|
+ + BaseArithmetic
|
|
|
+ + Codec
|
|
|
+ + Default
|
|
|
+ + Copy
|
|
|
+ + MaybeSerialize
|
|
|
+ + PartialEq;
|
|
|
+
|
|
|
/// _Administration_ event type.
|
|
|
type Event: From<Event<Self, I>> + Into<<Self as system::Trait>::Event>;
|
|
|
|
|
@@ -52,11 +79,18 @@ decl_event!(
|
|
|
pub enum Event<T, I>
|
|
|
where
|
|
|
<T as Trait<I>>::OpeningId,
|
|
|
+ <T as Trait<I>>::ApplicationId,
|
|
|
{
|
|
|
/// Emits on adding new job opening.
|
|
|
/// Params:
|
|
|
/// - Opening id
|
|
|
OpeningAdded(OpeningId),
|
|
|
+
|
|
|
+ /// Emits on adding the application for the worker opening.
|
|
|
+ /// Params:
|
|
|
+ /// - Opening id
|
|
|
+ /// - Application id
|
|
|
+ AppliedOnOpening(OpeningId, ApplicationId),
|
|
|
}
|
|
|
);
|
|
|
|
|
@@ -75,6 +109,17 @@ decl_storage! {
|
|
|
/// Opening human readable text length limits.
|
|
|
pub OpeningDescriptionTextLimit get(fn opening_description_text_limit):
|
|
|
InputValidationLengthConstraint;
|
|
|
+
|
|
|
+ /// Maps identifier to worker application on opening.
|
|
|
+ pub ApplicationById get(fn application_by_id) : map hasher(blake2_128_concat)
|
|
|
+ T::ApplicationId => JobApplicationRecord<T, I>;
|
|
|
+
|
|
|
+ /// Next identifier value for new worker application.
|
|
|
+ pub NextApplicationId get(fn next_application_id) : T::ApplicationId;
|
|
|
+
|
|
|
+ /// Job application description text length limits.
|
|
|
+ pub ApplicationDescriptionTextLimit get(fn application_description_text_limit):
|
|
|
+ InputValidationLengthConstraint;
|
|
|
}
|
|
|
add_extra_genesis {
|
|
|
// config(phantom): sp_std::marker::PhantomData<I>;
|
|
@@ -99,7 +144,7 @@ decl_module! {
|
|
|
|
|
|
// ****************** Hiring flow **********************
|
|
|
|
|
|
- /// Add a job opening for a worker role.
|
|
|
+ /// Add a job opening for a regular worker/lead role.
|
|
|
/// Require signed leader origin or the root (to add opening for the leader position).
|
|
|
#[weight = 10_000_000] // TODO: adjust weight
|
|
|
pub fn add_opening(
|
|
@@ -139,6 +184,99 @@ decl_module! {
|
|
|
|
|
|
Self::deposit_event(RawEvent::OpeningAdded(new_opening_id));
|
|
|
}
|
|
|
+
|
|
|
+ /// Apply on a worker opening.
|
|
|
+ #[weight = 10_000_000] // TODO: adjust weight
|
|
|
+ pub fn apply_on_opening(
|
|
|
+ origin,
|
|
|
+ member_id: T::MemberId,
|
|
|
+ opening_id: T::OpeningId,
|
|
|
+ role_account_id: T::AccountId,
|
|
|
+// opt_role_stake_balance: Option<BalanceOf<T>>,
|
|
|
+// opt_application_stake_balance: Option<BalanceOf<T>>,
|
|
|
+ human_readable_text: Vec<u8>
|
|
|
+ ) {
|
|
|
+ // Ensure origin which will server as the source account for staked funds is signed
|
|
|
+ let source_account = ensure_signed(origin)?;
|
|
|
+
|
|
|
+ // // Ensure the source_account is either the controller or root account of member with given id
|
|
|
+ // ensure!(
|
|
|
+ // membership::Module::<T>::ensure_member_controller_account(&source_account, &member_id).is_ok() ||
|
|
|
+ // membership::Module::<T>::ensure_member_root_account(&source_account, &member_id).is_ok(),
|
|
|
+ // Error::<T, I>::OriginIsNeitherMemberControllerOrRoot
|
|
|
+ // );
|
|
|
+
|
|
|
+ // Ensure job opening exists.
|
|
|
+ let opening = Self::ensure_opening_exists(&opening_id)?;
|
|
|
+
|
|
|
+ // // Ensure that there is sufficient balance to cover stake proposed
|
|
|
+ // Self::ensure_can_make_stake_imbalance(
|
|
|
+ // vec![&opt_role_stake_balance, &opt_application_stake_balance],
|
|
|
+ // &source_account
|
|
|
+ // )
|
|
|
+ // .map_err(|_| Error::<T, I>::InsufficientBalanceToApply)?;
|
|
|
+
|
|
|
+ // Ensure application text is valid
|
|
|
+ //Self::ensure_application_text_is_valid(&human_readable_text)?;
|
|
|
+
|
|
|
+ // // Ensure application can actually be added
|
|
|
+ // ensure_on_wrapped_error!(
|
|
|
+ // hiring::Module::<T>::ensure_can_add_application(
|
|
|
+ // opening.hiring_opening_id,
|
|
|
+ // opt_role_stake_balance,
|
|
|
+ // opt_application_stake_balance)
|
|
|
+ // )?;
|
|
|
+
|
|
|
+ // // Ensure member does not have an active application to this opening
|
|
|
+ // Self::ensure_member_has_no_active_application_on_opening(
|
|
|
+ // opening.applications,
|
|
|
+ // member_id
|
|
|
+ // )?;
|
|
|
+
|
|
|
+ //
|
|
|
+ // == MUTATION SAFE ==
|
|
|
+ //
|
|
|
+
|
|
|
+ // // Make imbalances for staking
|
|
|
+ // let opt_role_stake_imbalance = Self::make_stake_opt_imbalance(&opt_role_stake_balance, &source_account);
|
|
|
+ // let opt_application_stake_imbalance = Self::make_stake_opt_imbalance(&opt_application_stake_balance, &source_account);
|
|
|
+
|
|
|
+ // // Call hiring module to add application
|
|
|
+ // let add_application = ensure_on_wrapped_error!(
|
|
|
+ // hiring::Module::<T>::add_application(
|
|
|
+ // opening.hiring_opening_id,
|
|
|
+ // opt_role_stake_imbalance,
|
|
|
+ // opt_application_stake_imbalance,
|
|
|
+ // human_readable_text
|
|
|
+ // )
|
|
|
+ // )?;
|
|
|
+
|
|
|
+// let hiring_application_id = add_application.application_id_added;
|
|
|
+
|
|
|
+ // Save member id to refund the stakes. This piece of date should outlive the 'worker'.
|
|
|
+// <MemberIdByHiringApplicationId<T, I>>::insert(hiring_application_id, member_id);
|
|
|
+
|
|
|
+ // Get id of new worker/lead application
|
|
|
+ let new_application_id = NextApplicationId::<T, I>::get();
|
|
|
+
|
|
|
+ // Make worker/lead application
|
|
|
+ let application = JobApplication::new(&role_account_id, &opening_id, &member_id);
|
|
|
+
|
|
|
+ // Store application
|
|
|
+ ApplicationById::<T, I>::insert(new_application_id, application);
|
|
|
+
|
|
|
+ // Update next application identifier value
|
|
|
+ NextApplicationId::<T, I>::mutate(|id| *id += <T::ApplicationId as One>::one());
|
|
|
+
|
|
|
+ // Add application to set of application in worker opening
|
|
|
+ // OpeningById::<T, I>::mutate(opening_id, |opening| {
|
|
|
+ // opening.applications.insert(new_application_id);
|
|
|
+ // });
|
|
|
+
|
|
|
+ // Trigger event
|
|
|
+ Self::deposit_event(RawEvent::AppliedOnOpening(opening_id, new_application_id));
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -155,4 +293,16 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
|
|
|
fn current_block() -> T::BlockNumber {
|
|
|
<system::Module<T>>::block_number()
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+ fn ensure_opening_exists(opening_id: &T::OpeningId) -> Result<JobOpening<T::BlockNumber>, Error<T, I>> {
|
|
|
+ ensure!(
|
|
|
+ OpeningById::<T, I>::contains_key(opening_id),
|
|
|
+ Error::<T, I>::OpeningDoesNotExist
|
|
|
+ );
|
|
|
+
|
|
|
+ let opening = OpeningById::<T, I>::get(opening_id);
|
|
|
+
|
|
|
+ Ok(opening)
|
|
|
+ }
|
|
|
}
|