diff --git a/pallets/fund-admin/src/functions.rs b/pallets/fund-admin/src/functions.rs index 755c73b4..f1e26fd5 100644 --- a/pallets/fund-admin/src/functions.rs +++ b/pallets/fund-admin/src/functions.rs @@ -11,3418 +11,3598 @@ use pallet_rbac::types::*; use frame_support::traits::Time; impl Pallet { - // M A I N F U N C T I O N S - // ================================================================================================ - - // I N I T I A L S E T U P - // ================================================================================================ - - pub fn do_initial_setup() -> DispatchResult { - // Create a global scope for the administrator role - let pallet_id = Self::pallet_id(); - let global_scope = pallet_id.using_encoded(blake2_256); - >::put(global_scope); - T::Rbac::create_scope(Self::pallet_id(), global_scope)?; - - // Admin rol & permissions - let administrator_role_id = T::Rbac::create_and_set_roles( - pallet_id.clone(), - [ProxyRole::Administrator.to_vec()].to_vec(), - )?; - T::Rbac::create_and_set_permissions( - pallet_id.clone(), - administrator_role_id[0], - ProxyPermission::administrator_permissions(), - )?; - - // Builder rol & permissions - let builder_role_id = T::Rbac::create_and_set_roles( - pallet_id.clone(), - [ProxyRole::Builder.to_vec()].to_vec(), - )?; - T::Rbac::create_and_set_permissions( - pallet_id.clone(), - builder_role_id[0], - ProxyPermission::builder_permissions(), - )?; - - // Investor rol & permissions - let investor_role_id = T::Rbac::create_and_set_roles( - pallet_id.clone(), - [ProxyRole::Investor.to_vec()].to_vec(), - )?; - T::Rbac::create_and_set_permissions( - pallet_id.clone(), - investor_role_id[0], - ProxyPermission::investor_permissions(), - )?; - - // Issuer rol & permissions - let issuer_role_id = T::Rbac::create_and_set_roles( - pallet_id.clone(), - [ProxyRole::Issuer.to_vec()].to_vec(), - )?; - T::Rbac::create_and_set_permissions( - pallet_id.clone(), - issuer_role_id[0], - ProxyPermission::issuer_permissions(), - )?; - - // Regional center rol & permissions - let regional_center_role_id = T::Rbac::create_and_set_roles( - pallet_id.clone(), - [ProxyRole::RegionalCenter.to_vec()].to_vec(), - )?; - T::Rbac::create_and_set_permissions( - pallet_id, - regional_center_role_id[0], - ProxyPermission::regional_center_permissions(), - )?; - - // Event - Self::deposit_event(Event::ProxySetupCompleted); - Ok(()) - } - - pub fn do_sudo_add_administrator(admin: T::AccountId, name: FieldName) -> DispatchResult { - // Ensure name is not empty - ensure!(!name.is_empty(), Error::::EmptyFieldName); - // Create a administrator user account & register it in the rbac pallet - Self::sudo_register_admin(admin.clone(), name)?; - - // Event - Self::deposit_event(Event::AdministratorAssigned(admin)); - Ok(()) - } - - pub fn do_sudo_remove_administrator(admin: T::AccountId) -> DispatchResult { - // Remove administrator user account & remove it from the rbac pallet - Self::sudo_delete_admin(admin.clone())?; - - // Event - Self::deposit_event(Event::AdministratorRemoved(admin)); - Ok(()) - } - - // P R O J E C T S - // ================================================================================================ - pub fn do_create_project( - admin: T::AccountId, - title: FieldName, - description: FieldDescription, - image: Option, - address: FieldName, - banks: Option>, - creation_date: CreationDate, - completion_date: CompletionDate, - expenditures: Expenditures, - job_eligibles: Option>, - users: Option>, - private_group_id: PrivateGroupId, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized( - admin.clone(), - &Self::get_global_scope(), - ProxyPermission::CreateProject, - )?; - - // Validations - ensure!(!title.is_empty(), Error::::EmptyFieldName); - ensure!(!description.is_empty(), Error::::EmptyFieldDescription); - if let Some(image) = image.clone() { - ensure!(!image.is_empty(), Error::::EmptyFieldCID); - } - ensure!(!address.is_empty(), Error::::EmptyFieldName); - if let Some(banks) = banks.clone() { - ensure!(!banks.is_empty(), Error::::EmptyFieldBanks); - } - ensure!(!address.is_empty(), Error::::EmptyProjectAddress); - ensure!(!private_group_id.is_empty(), Error::::PrivateGroupIdEmpty); - - // Add timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Create project_id - let project_id: ProjectId = (title.clone(), timestamp).using_encoded(blake2_256); - - // Ensure completion_date is in the future - ensure!(completion_date > creation_date, Error::::CompletionDateMustBeLater); - - // Ensuree private group id is not empty - ensure!(!private_group_id.is_empty(), Error::::PrivateGroupIdEmpty); - - // Create project data - let project_data = ProjectData:: { - builder: Some(BoundedVec::::default()), - investor: Some(BoundedVec::::default()), - issuer: Some(BoundedVec::::default()), - regional_center: Some( - BoundedVec::::default(), - ), - title, - description, - image, - address, - status: ProjectStatus::default(), - inflation_rate: None, - banks, - registration_date: timestamp, - creation_date, - completion_date, - updated_date: timestamp, - construction_loan_drawdown_status: None, - developer_equity_drawdown_status: None, - eb5_drawdown_status: None, - revenue_status: None, - private_group_id, - }; - - // Create the scope for the given project_id - T::Rbac::create_scope(Self::pallet_id(), project_id)?; - - // Insert project data - // Ensure that the project_id is not already in use - ensure!(!ProjectsInfo::::contains_key(project_id), Error::::ProjectIdAlreadyInUse); - ProjectsInfo::::insert(project_id, project_data); - - // Add expenditures - Self::do_execute_expenditures(admin.clone(), project_id, expenditures)?; - - // Add job_eligibles - if let Some(mod_job_eligibles) = job_eligibles { - Self::do_execute_job_eligibles(admin.clone(), project_id, mod_job_eligibles)?; - } - - // Add users - if let Some(mod_users) = users { - Self::do_execute_assign_users(admin.clone(), project_id, mod_users)?; - } - - // Initialize drawdowns - Self::do_initialize_drawdowns(admin.clone(), project_id)?; - - // Initialize revenue - Self::do_initialize_revenue(project_id)?; - - // Event - Self::deposit_event(Event::ProjectCreated(admin, project_id)); - Ok(()) - } - - pub fn do_edit_project( - admin: T::AccountId, - project_id: ProjectId, - title: Option, - description: Option, - image: Option, - address: Option, - banks: Option>, - creation_date: Option, - completion_date: Option, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized(admin.clone(), &project_id, ProxyPermission::EditProject)?; - - // Ensure project exists - ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); - - // Ensure project is not completed - Self::is_project_completed(project_id)?; - - // Get current timestamp - let current_timestamp = - Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Mutate project data - >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { - let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; - - if let Some(title) = title { - // Ensure title is not empty - ensure!(!title.is_empty(), Error::::EmptyFieldName); - project.title = title; - } - if let Some(description) = description { - // Ensure description is not empty - ensure!(!description.is_empty(), Error::::EmptyFieldDescription); - project.description = description; - } - if let Some(image) = image { - // Ensure image is not empty - ensure!(!image.is_empty(), Error::::EmptyFieldCID); - project.image = Some(image); - } - if let Some(address) = address { - // Ensure address is not empty - ensure!(!address.is_empty(), Error::::EmptyProjectAddress); - project.address = address; - } - if let Some(banks) = banks { - // Ensure banks is not empty - ensure!(!banks.is_empty(), Error::::EmptyFieldBanks); - project.banks = Some(banks); - } - if let Some(creation_date) = creation_date { - project.creation_date = creation_date; - } - if let Some(completion_date) = completion_date { - project.completion_date = completion_date; - } - // Update modified date - project.updated_date = current_timestamp; - - Ok(()) - })?; - - // Ensure completion_date is later than creation_date - Self::is_project_completion_date_later(project_id)?; - - // Event - Self::deposit_event(Event::ProjectEdited(admin, project_id)); - Ok(()) - } - - pub fn do_delete_project(admin: T::AccountId, project_id: ProjectId) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized(admin.clone(), &project_id, ProxyPermission::DeleteProject)?; - - // Ensure project exists & get project data - let project_data = ProjectsInfo::::get(project_id).ok_or(Error::::ProjectNotFound)?; - - // Ensure project is not completed - ensure!( - project_data.status != ProjectStatus::Completed, - Error::::CannotDeleteCompletedProject - ); - - if UsersByProject::::contains_key(project_id) { - // Get users by project - let users_by_project = UsersByProject::::get(project_id); - // Unassign all users from project - // Create a UsersAssignation boundedvec with all users in the project - let mut users_assignation: UsersAssignation = UsersAssignation::::default(); - for user in users_by_project.iter().cloned() { - // Get user data - let user_data = >::try_get(user.clone()) - .map_err(|_| Error::::UserNotRegistered)?; - - users_assignation - .try_push((user, user_data.role, AssignAction::Unassign)) - .map_err(|_| Error::::MaxRegistrationsAtATimeReached)?; - } - - // Unassign all users from project - Self::do_execute_assign_users(admin.clone(), project_id, users_assignation)?; - - // Remove project from users - for user in users_by_project.iter().cloned() { - >::try_mutate::<_, _, DispatchError, _>(user, |projects| { - projects.retain(|project| *project != project_id); - Ok(()) - })?; - } - } - - // Delete from ProjectsInfo storagemap - >::remove(project_id); - - // Delete from UsersByProject storagemap - >::remove(project_id); - - // Delete expenditures from ExpendituresInfo storagemap - let expenditures_by_project = Self::expenditures_by_project(project_id) - .iter() - .cloned() - .collect::>(); - for expenditure_id in expenditures_by_project.iter().cloned() { - >::remove(expenditure_id); - } - - // Deletes all expenditures from ExpendituresByProject storagemap - >::remove(project_id); - - let drawdowns_by_project = Self::drawdowns_by_project(project_id) - .iter() - .cloned() - .collect::>(); - for drawdown_id in drawdowns_by_project.iter().cloned() { - // Delete transactions from TransactionsInfo storagemap - let transactions_by_drawdown = Self::transactions_by_drawdown(project_id, drawdown_id) - .iter() - .cloned() - .collect::>(); - for transaction_id in transactions_by_drawdown.iter().cloned() { - >::remove(transaction_id); - } - - // Deletes all transactions from TransactionsByDrawdown storagemap - >::remove(project_id, drawdown_id); - - // Delete drawdown from DrawdownsInfo storagemap - >::remove(drawdown_id); - } - - // Deletes all drawdowns from DrawdownsByProject storagemap - >::remove(project_id); - - // Delete job eligibles from JobEligiblesInfo storagemap - let job_eligibles_by_project = Self::job_eligibles_by_project(project_id) - .iter() - .cloned() - .collect::>(); - for job_eligible_id in job_eligibles_by_project.iter().cloned() { - >::remove(job_eligible_id); - } - - // Deletes all job eligibles from JobEligiblesByProject storagemap - >::remove(project_id); - - // Delete job from RevenuesInfo storagemap - let revenues_by_project = - Self::revenues_by_project(project_id).iter().cloned().collect::>(); - for revenue_id in revenues_by_project.iter().cloned() { - // Delete revenue transactions from RevenueTransactionsInfo storagemap - let transactions_by_revenue = Self::transactions_by_revenue(project_id, revenue_id) - .iter() - .cloned() - .collect::>(); - for transaction_id in transactions_by_revenue.iter().cloned() { - >::remove(transaction_id); - } - - // Deletes all revenue transactions from TransactionsByRevenue storagemap - >::remove(project_id, revenue_id); - - // Delete revenue from RevenuesInfo storagemap - >::remove(revenue_id); - } - - // Deletes all revenues from RevenuesByProject storagemap - >::remove(project_id); - - // Delete scope from rbac pallet - T::Rbac::remove_scope(Self::pallet_id(), project_id)?; - - // Event - Self::deposit_event(Event::ProjectDeleted(admin, project_id)); - Ok(()) - } - - pub fn do_execute_assign_users( - admin: T::AccountId, - project_id: ProjectId, - users: UsersAssignation, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized(admin.clone(), &project_id, ProxyPermission::AssignUsers)?; - - // Ensure UsersAssignation is not empty - ensure!(!users.is_empty(), Error::::EmptyUsersAssignation); - - // Ensure project exists & is not completed - Self::is_project_completed(project_id)?; - - // Assign users - for user in users.iter().cloned() { - match user.2 { - AssignAction::Assign => { - Self::do_assign_user(project_id, user.0, user.1)?; - }, - AssignAction::Unassign => { - Self::do_unassign_user(project_id, user.0, user.1)?; - }, - } - } - - // Event - Self::deposit_event(Event::UsersAssignationExecuted(admin, project_id)); - Ok(()) - } - - fn do_assign_user( - project_id: ProjectId, - user: T::AccountId, - role: ProxyRole, - ) -> DispatchResult { - // Basic validations prior to assign the given user - Self::check_user_role(user.clone(), role)?; - - // Ensure user is not already assigned to the project - ensure!( - !>::get(project_id).contains(&user), - Error::::UserAlreadyAssignedToProject - ); - ensure!( - !>::get(user.clone()).contains(&project_id), - Error::::UserAlreadyAssignedToProject - ); - - // Ensure user is not assigened to the selected scope (project_id) with the selected role - ensure!( - !T::Rbac::has_role(user.clone(), Self::pallet_id(), &project_id, [role.id()].to_vec()) - .is_ok(), - Error::::UserAlreadyAssignedToProject - ); - - // Update project data depending on the role assigned - Self::add_project_role(project_id, user.clone(), role)?; - - // Insert project to ProjectsByUser storagemap - >::try_mutate::<_, _, DispatchError, _>(user.clone(), |projects| { - projects - .try_push(project_id) - .map_err(|_| Error::::MaxProjectsPerUserReached)?; - Ok(()) - })?; - - // Insert user in UsersByProject storagemap - >::try_mutate::<_, _, DispatchError, _>(project_id, |users| { - users - .try_push(user.clone()) - .map_err(|_| Error::::MaxUsersPerProjectReached)?; - Ok(()) - })?; - - // Give a set of permissions to the given user based on the role assigned - T::Rbac::assign_role_to_user(user.clone(), Self::pallet_id(), &project_id, role.id())?; - - // Event - Self::deposit_event(Event::UserAssignmentCompleted(user, project_id)); - Ok(()) - } - - fn do_unassign_user( - project_id: ProjectId, - user: T::AccountId, - role: ProxyRole, - ) -> DispatchResult { - // Ensure user is registered - ensure!(>::contains_key(user.clone()), Error::::UserNotRegistered); - - // Ensure user is assigned to the project - ensure!( - >::get(project_id).contains(&user.clone()), - Error::::UserNotAssignedToProject - ); - ensure!( - >::get(user.clone()).contains(&project_id), - Error::::UserNotAssignedToProject - ); - - // Ensure user has the specified role assigned in the selected project - ensure!( - T::Rbac::has_role(user.clone(), Self::pallet_id(), &project_id, [role.id()].to_vec()) - .is_ok(), - Error::::UserDoesNotHaveRole - ); - - // Update project data depending on the role unassigned - Self::remove_project_role(project_id, user.clone(), role)?; - - // Remove user from UsersByProject storagemap. - >::try_mutate_exists::<_, _, DispatchError, _>(project_id, - |users_option| { - let users = users_option.as_mut().ok_or(Error::::ProjectHasNoUsers)?; - users.retain(|u| u != &user); - if users.is_empty() { - users_option.clone_from(&None); - } - Ok(()) - })?; - - // Remove user from ProjectsByUser storagemap - >::try_mutate_exists::<_, _, DispatchError, _>(user.clone(), - |projects_option| { - let projects = projects_option.as_mut().ok_or(Error::::UserHasNoProjects)?; - projects.retain(|project| project != &project_id); - if projects.is_empty() { - projects_option.clone_from(&None); - } - Ok(()) - })?; - - // Remove user from the scope rbac pallet - T::Rbac::remove_role_from_user(user.clone(), Self::pallet_id(), &project_id, role.id())?; - - // Event - Self::deposit_event(Event::UserUnassignmentCompleted(user, project_id)); - Ok(()) - } - - // U S E R S - // ================================================================================================ - pub fn do_execute_users(admin: T::AccountId, users: Users) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized( - admin.clone(), - &Self::get_global_scope(), - ProxyPermission::ExecuteUsers, - )?; - - // Ensure users list is not empty - ensure!(!users.is_empty(), Error::::EmptyUsers); - - for user in users.iter().cloned() { - match user.3 { - CUDAction::Create => { - Self::do_create_user( - user.0.clone(), - user.1.clone().ok_or(Error::::UserNameRequired)?, - user.2.ok_or(Error::::UserRoleRequired)?, - )?; - - //Send funds to the user - Self::send_funds(admin.clone(), user.0.clone())?; - }, - CUDAction::Update => { - Self::do_update_user(user.0.clone(), user.1.clone(), user.2)?; - - //Send funds to the user - Self::send_funds(admin.clone(), user.0.clone())?; - }, - CUDAction::Delete => { - ensure!(user.0 != admin, Error::::AdministratorsCannotDeleteThemselves,); - - Self::do_delete_user(user.0.clone())?; - }, - } - } - - // Event - Self::deposit_event(Event::UsersExecuted(admin)); - Ok(()) - } - - fn do_create_user(user: T::AccountId, name: FieldName, role: ProxyRole) -> DispatchResult { - // Get current timestamp - let current_timestamp = - Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Ensure user is not registered - ensure!(!>::contains_key(user.clone()), Error::::UserAlreadyRegistered); - - // Ensure name is not empty - ensure!(!name.is_empty(), Error::::UserNameRequired); - - match role { - ProxyRole::Administrator => { - Self::do_sudo_add_administrator(user.clone(), name)?; - }, - _ => { - // Create user data - let user_data = UserData:: { - name, - role, - image: CID::default(), - date_registered: current_timestamp, - email: FieldName::default(), - documents: None, - }; - - // Insert user data in UsersInfo storagemap - >::insert(user.clone(), user_data); - }, - } - - // Event - Self::deposit_event(Event::UserCreated(user)); - Ok(()) - } - - fn do_update_user( - user: T::AccountId, - name: Option, - role: Option, - ) -> DispatchResult { - // Ensure user is registered - ensure!(>::contains_key(user.clone()), Error::::UserNotRegistered); - - // Update user data - >::try_mutate::<_, _, DispatchError, _>(user.clone(), |user_data| { - let user_info = user_data.as_mut().ok_or(Error::::UserNotRegistered)?; - - if let Some(mod_name) = name { - // Ensure name is not empty - ensure!(!mod_name.is_empty(), Error::::UserNameRequired); - user_info.name = mod_name; - } - if let Some(mod_role) = role { - // If user has assigned projects, its role cannot be updated - ensure!( - >::get(user.clone()).is_empty(), - Error::::UserHasAssignedProjectsCannotUpdateRole - ); - user_info.role = mod_role; - } - Ok(()) - })?; - - // Event - Self::deposit_event(Event::UserUpdated(user)); - Ok(()) - } - - fn do_delete_user(user: T::AccountId) -> DispatchResult { - // Ensure user is registered & get user data - let user_data = >::get(user.clone()).ok_or(Error::::UserNotRegistered)?; - - match user_data.role { - ProxyRole::Administrator => { - Self::do_sudo_remove_administrator(user.clone())?; - }, - _ => { - // Can not delete a user if the user has assigned projects - ensure!( - >::get(user.clone()).is_empty(), - Error::::UserHasAssignedProjectsCannotDelete - ); - - // Remove user from ProjectsByUser storagemap. No longer required, the admnistator first needs to - // unassign the user from all its projects. - - // Remove user from UsersByProject storagemap. No longer required, the admnistator first needs to - // unassign the user from all its projects. - - // Remove user from UsersInfo storagemap - >::remove(user.clone()); - - }, - } - - // Event - Self::deposit_event(Event::UserDeleted(user)); - Ok(()) - } - // E D I T U S E R - // ================================================================================================ - - /// Editing your own user data does not require any kind of RBAC permissions, it only requires - /// that the user is registered. This is because permissions are granted to the - /// user's account when the user is assigned to a project. - /// - /// WARNING: Editing your own user data does not allow you to change your role. Only the administrator can do it usign the `users` extrinsic. - pub fn do_edit_user( - user: T::AccountId, - name: Option, - image: Option, - email: Option, - documents: Option>, - ) -> DispatchResult { - // Ensure user is registered - ensure!(>::contains_key(user.clone()), Error::::UserNotRegistered); - - // Update user data - >::try_mutate::<_, _, DispatchError, _>(user.clone(), |user_data| { - let user_info = user_data.as_mut().ok_or(Error::::UserNotRegistered)?; - - if let Some(mod_name) = name { - // Ensure name is not empty - ensure!(!mod_name.is_empty(), Error::::UserNameRequired); - user_info.name = mod_name; - } - if let Some(mod_image) = image { - // Ensure image is not empty - ensure!(!mod_image.is_empty(), Error::::UserImageRequired); - user_info.image = mod_image; - } - if let Some(mod_email) = email { - // Ensure email is not empty - ensure!(!mod_email.is_empty(), Error::::UserEmailRequired); - user_info.email = mod_email; - } - // Only investors can upload documents - if let Some(mod_documents) = documents { - // Ensure user is an investor - ensure!(user_info.role == ProxyRole::Investor, Error::::UserIsNotAnInvestor); - // Ensure documents is not empty - ensure!(!mod_documents.is_empty(), Error::::DocumentsEmpty); - user_info.documents = Some(mod_documents); - } - Ok(()) - })?; - - Self::deposit_event(Event::UserUpdated(user)); - - Ok(()) - } - - // B U D G E T E X P E N D I T U R E S - // ================================================================================================ - pub fn do_execute_expenditures( - admin: T::AccountId, - project_id: ProjectId, - expenditures: Expenditures, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized(admin.clone(), &project_id, ProxyPermission::Expenditures)?; - - // Ensure project exists - ensure!(>::contains_key(project_id), Error::::ProjectNotFound); - - // Ensure expenditures are not empty - ensure!(!expenditures.is_empty(), Error::::EmptyExpenditures); - - for expenditure in expenditures.iter().cloned() { - match expenditure.5 { - CUDAction::Create => { - Self::do_create_expenditure( - project_id, - expenditure.0.ok_or(Error::::ExpenditureNameRequired)?, - expenditure.1.ok_or(Error::::ExpenditureTypeRequired)?, - expenditure.2.ok_or(Error::::ExpenditureAmountRequired)?, - expenditure.3, - expenditure.4, - )?; - }, - CUDAction::Update => { - Self::do_update_expenditure( - project_id, - expenditure.6.ok_or(Error::::ExpenditureIdRequired)?, - expenditure.0, - expenditure.2, - expenditure.3, - expenditure.4, - )?; - }, - CUDAction::Delete => { - Self::do_delete_expenditure( - expenditure.6.ok_or(Error::::ExpenditureIdRequired)?, - )?; - }, - } - } - - // Event - Self::deposit_event(Event::ExpendituresExecuted(admin, project_id)); - Ok(()) - } - - /// Create a new budget expenditure - /// - /// # Arguments - /// - /// * `admin` - The admin user that creates the budget expenditure - /// * `project_id` - The project id where the budget expenditure will be created - /// - /// Then we add the budget expenditure data - /// * `name` - The name of the budget expenditure - /// * `type` - The type of the budget expenditure - /// * `budget amount` - The amount of the budget expenditure - /// * `naics code` - The naics code of the budget expenditure - /// * `jobs_multiplier` - The jobs multiplier of the budget expenditure - fn do_create_expenditure( - project_id: [u8; 32], - name: FieldName, - expenditure_type: ExpenditureType, - expenditure_amount: ExpenditureAmount, - naics_code: Option, - jobs_multiplier: Option, - ) -> DispatchResult { - // Ensure project exists & is not completed - Self::is_project_completed(project_id)?; - - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Ensure expenditure name is not empty - ensure!(!name.is_empty(), Error::::EmptyExpenditureName); - - // Create expenditure id - let expenditure_id: ExpenditureId = - (project_id, name.clone(), expenditure_type, timestamp).using_encoded(blake2_256); - - // Create expenditure data - let expenditure_data = ExpenditureData { - project_id, - name, - expenditure_type, - expenditure_amount, - naics_code, - jobs_multiplier, - }; - - // Insert expenditure data into ExpendituresInfo - // Ensure expenditure_id is unique - ensure!( - !>::contains_key(expenditure_id), - Error::::ExpenditureAlreadyExists - ); - >::insert(expenditure_id, expenditure_data); - - // Insert expenditure_id into ExpendituresByProject - >::try_mutate::<_, _, DispatchError, _>( - project_id, - |expenditures| { - expenditures - .try_push(expenditure_id) - .map_err(|_| Error::::MaxExpendituresPerProjectReached)?; - Ok(()) - }, - )?; - - Self::deposit_event(Event::ExpenditureCreated(project_id, expenditure_id)); - Ok(()) - } - - fn do_update_expenditure( - project_id: ProjectId, - expenditure_id: ExpenditureId, - name: Option, - expenditure_amount: Option, - naics_code: Option, - jobs_multiplier: Option, - ) -> DispatchResult { - // Ensure project exists & is not completed - Self::is_project_completed(project_id)?; - - // Ensure expenditure_id exists - ensure!( - >::contains_key(expenditure_id), - Error::::ExpenditureNotFound - ); - - // Mutate expenditure data - >::try_mutate::<_, _, DispatchError, _>( - expenditure_id, - |expenditure_data| { - let expenditure = - expenditure_data.as_mut().ok_or(Error::::ExpenditureNotFound)?; - - // Ensure expenditure belongs to the project - ensure!( - expenditure.project_id == project_id, - Error::::ExpenditureDoesNotBelongToProject - ); - - if let Some(mod_name) = name { - expenditure.name = mod_name; - } - if let Some(mod_expenditure_amount) = expenditure_amount { - expenditure.expenditure_amount = mod_expenditure_amount; - } - if let Some(mod_naics_code) = naics_code { - expenditure.naics_code = Some(mod_naics_code); - } - if let Some(mod_jobs_multiplier) = jobs_multiplier { - expenditure.jobs_multiplier = Some(mod_jobs_multiplier); - } - - Ok(()) - }, - )?; - - Self::deposit_event(Event::ExpenditureUpdated(project_id, expenditure_id)); - Ok(()) - } - - fn do_delete_expenditure(expenditure_id: ExpenditureId) -> DispatchResult { - // Ensure expenditure_id exists & get expenditure data - let expenditure_data = - ExpendituresInfo::::get(&expenditure_id).ok_or(Error::::ExpenditureNotFound)?; - - // Ensure expenditure_id is contained in ExpendituresByProject - ensure!( - >::get(expenditure_data.project_id).contains(&expenditure_id), - Error::::ExpenditureNotFoundForSelectedProjectId - ); - - Self::do_delete_expenditure_transactions(expenditure_id)?; - - // Delete expenditure data from ExpendituresInfo - >::remove(expenditure_id); - - // Delete expenditure_id from ExpendituresByProject - >::try_mutate_exists::<_, _, DispatchError, _>( - expenditure_data.project_id, - |expenditures_option| { - let expenditures = expenditures_option.as_mut().ok_or(Error::::ProjectHasNoExpenditures)?; - expenditures.retain(|expenditure| expenditure != &expenditure_id); - if expenditures.is_empty() { - expenditures_option.clone_from(&None) - } - Ok(()) - }, - )?; - - Self::deposit_event(Event::ExpenditureDeleted(expenditure_data.project_id, expenditure_id)); - Ok(()) - } - - // D R A W D O W N S - // ================================================================================================ - fn do_create_drawdown( - project_id: ProjectId, - drawdown_type: DrawdownType, - drawdown_number: DrawdownNumber, - ) -> DispatchResult { - // Ensure project exists - ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); - - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Create drawdown id - let drawdown_id = - (project_id, drawdown_type, drawdown_number, timestamp).using_encoded(blake2_256); - - // Create drawdown data - let drawdown_data = DrawdownData:: { - project_id, - drawdown_number, - drawdown_type, - total_amount: 0, - status: DrawdownStatus::default(), - bulkupload_documents: None, - bank_documents: None, - description: None, - feedback: None, - status_changes: DrawdownStatusChanges::::default(), - created_date: timestamp, - closed_date: 0, - }; - - // Insert drawdown data - // Ensure drawdown id is unique - ensure!(!DrawdownsInfo::::contains_key(drawdown_id), Error::::DrawdownAlreadyExists); - >::insert(drawdown_id, drawdown_data); - - // Insert drawdown id into DrawdownsByProject - >::try_mutate::<_, _, DispatchError, _>(project_id, |drawdowns| { - drawdowns - .try_push(drawdown_id) - .map_err(|_| Error::::MaxDrawdownsPerProjectReached)?; - Ok(()) - })?; - - // Update project drawdown status - Self::do_update_drawdown_status_in_project_info( - project_id, - drawdown_id, - DrawdownStatus::default(), - )?; - - // Event - Self::deposit_event(Event::DrawdownCreated(project_id, drawdown_id)); - Ok(()) - } - - fn do_initialize_drawdowns(admin: T::AccountId, project_id: ProjectId) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized(admin.clone(), &project_id, ProxyPermission::Expenditures)?; - - // Ensure project exists - ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); - - // Create a EB5 drawdown - Self::do_create_drawdown(project_id, DrawdownType::EB5, 1)?; - - // Create a Construction Loan drawdown - Self::do_create_drawdown(project_id, DrawdownType::ConstructionLoan, 1)?; - - // Create a Developer Equity drawdown - Self::do_create_drawdown(project_id, DrawdownType::DeveloperEquity, 1)?; - - // Event - Self::deposit_event(Event::DrawdownsInitialized(admin, project_id)); - Ok(()) - } - - pub fn do_submit_drawdown( - user: T::AccountId, - project_id: ProjectId, - drawdown_id: DrawdownId, - ) -> DispatchResult { - // Ensure user permissions - Self::is_authorized(user, &project_id, ProxyPermission::SubmitDrawdown)?; - - // Ensure project exists & is not completed - Self::is_project_completed(project_id)?; - - // Check if drawdown exists & is editable - Self::is_drawdown_editable(drawdown_id)?; - - // Ensure drawdown has transactions - ensure!( - !>::get(project_id, drawdown_id).is_empty(), - Error::::DrawdownHasNoTransactions - ); - - // Get drawdown transactions - let drawdown_transactions = TransactionsByDrawdown::::try_get(project_id, drawdown_id) - .map_err(|_| Error::::DrawdownHasNoTransactions)?; - - // Update each transaction status to submitted - for transaction_id in drawdown_transactions.iter().cloned() { - // Ensure transaction exists - ensure!( - TransactionsInfo::::contains_key(transaction_id), - Error::::TransactionNotFound - ); - - // Update transaction status to submitted - >::try_mutate::<_, _, DispatchError, _>( - transaction_id, - |transaction_data| { - let transaction_data = - transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; - transaction_data.status = TransactionStatus::Submitted; - transaction_data.feedback = None; - Ok(()) - }, - )?; - } - - // Update drawdown status - >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { - let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; - drawdown_data.status = DrawdownStatus::Submitted; - drawdown_data.feedback = None; - Ok(()) - })?; - - // Update drawdown status in project info - Self::do_update_drawdown_status_in_project_info( - project_id, - drawdown_id, - DrawdownStatus::Submitted, - )?; - - // Event - Self::deposit_event(Event::DrawdownSubmitted(project_id, drawdown_id)); - Ok(()) - } - - pub fn do_approve_drawdown( - admin: T::AccountId, - project_id: ProjectId, - drawdown_id: DrawdownId, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized(admin, &project_id, ProxyPermission::ApproveDrawdown)?; - - // Ensure project exists - ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); - - // Get drawdown data & ensure drawdown exists - let drawdown_data = - DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; - - // Ensure drawdown has transactions - ensure!( - !>::get(project_id, drawdown_id).is_empty(), - Error::::DrawdownHasNoTransactions - ); - - // Ensure drawdown is in submitted status - ensure!( - drawdown_data.status == DrawdownStatus::Submitted, - Error::::DrawdownNotSubmitted - ); - - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Get drawdown transactions - let drawdown_transactions = TransactionsByDrawdown::::try_get(project_id, drawdown_id) - .map_err(|_| Error::::DrawdownHasNoTransactions)?; - - // Update each transaction status to approved - for transaction_id in drawdown_transactions.iter().cloned() { - // Ensure transaction exits - ensure!( - TransactionsInfo::::contains_key(transaction_id), - Error::::TransactionNotFound - ); - - // Update transaction status to approved - >::try_mutate::<_, _, DispatchError, _>( - transaction_id, - |transaction_data| { - let transaction_data = - transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; - transaction_data.status = TransactionStatus::Approved; - transaction_data.closed_date = timestamp; - Ok(()) - }, - )?; - } - - // Update drawdown status to approved - >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { - let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; - drawdown_data.status = DrawdownStatus::Approved; - drawdown_data.closed_date = timestamp; - Ok(()) - })?; - - // Update drawdown status in project info - Self::do_update_drawdown_status_in_project_info( - project_id, - drawdown_id, - DrawdownStatus::Approved, - )?; - - // Generate the next drawdown - // TOREVIEW: After a project is completed, there is no need to generate the next drawdown - // Add a validation to check project status before generating the next drawdown - Self::do_create_drawdown( - project_id, - drawdown_data.drawdown_type, - drawdown_data.drawdown_number + 1, - )?; - - // Event - Self::deposit_event(Event::DrawdownApproved(project_id, drawdown_id)); - Ok(()) - } - - pub fn do_reject_drawdown( - admin: T::AccountId, - project_id: ProjectId, - drawdown_id: DrawdownId, - transactions_feedback: Option>, - drawdown_feedback: Option, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized(admin, &project_id, ProxyPermission::RejectDrawdown)?; - - // Ensure project exists - ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); - - // Get drawdown data - let drawdown_data = - DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; - - // Ensure drawdown is in submitted status - ensure!( - drawdown_data.status == DrawdownStatus::Submitted, - Error::::DrawdownNotSubmitted - ); - - // Match drawdown type in order to update transactions status - match drawdown_data.drawdown_type { - DrawdownType::EB5 => { - // Ensure drawdown has transactions - ensure!( - !>::get(project_id, drawdown_id).is_empty(), - Error::::DrawdownHasNoTransactions - ); - - // Get drawdown transactions - let drawdown_transactions = - TransactionsByDrawdown::::try_get(project_id, drawdown_id) - .map_err(|_| Error::::DrawdownHasNoTransactions)?; - - // Update each transaction status to rejected - for transaction_id in drawdown_transactions.iter().cloned() { - // Ensure transaction exits - ensure!( - TransactionsInfo::::contains_key(transaction_id), - Error::::TransactionNotFound - ); - - // Update transaction status to rejected - >::try_mutate::<_, _, DispatchError, _>( - transaction_id, - |transaction_data| { - let transaction_data = - transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; - transaction_data.status = TransactionStatus::Rejected; - Ok(()) - }, - )?; - } - - // Ensure transactions feedback is provided - let mod_transactions_feedback = - transactions_feedback.ok_or(Error::::EB5MissingFeedback)?; - - // Ensure feedback is not empty - ensure!(!mod_transactions_feedback.is_empty(), Error::::EmptyEb5Feedback); - - for (transaction_id, feedback) in mod_transactions_feedback.iter().cloned() { - // Update transaction feedback - >::try_mutate::<_, _, DispatchError, _>( - transaction_id, - |transaction_data| { - let transaction_data = - transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; - transaction_data.feedback = Some(feedback); - Ok(()) - }, - )?; - } - }, - _ => { - // Ensure drawdown feedback is provided - let mod_drawdown_feedback = - drawdown_feedback.ok_or(Error::::NoFeedbackProvidedForBulkUpload)?; - - // Esnure feedback is not empty - ensure!(!mod_drawdown_feedback.is_empty(), Error::::EmptyBulkUploadFeedback); - - // Update drawdown feedback - >::try_mutate::<_, _, DispatchError, _>( - drawdown_id, - |drawdown_data| { - let drawdown_data = - drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; - drawdown_data.feedback = Some(mod_drawdown_feedback.clone()); - Ok(()) - }, - )?; - }, - } - - // Update drawdown status to rejected - >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { - let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; - drawdown_data.status = DrawdownStatus::Rejected; - Ok(()) - })?; - - // Update drawdown status in project info - Self::do_update_drawdown_status_in_project_info( - project_id, - drawdown_id, - DrawdownStatus::Rejected, - )?; - - // Event - Self::deposit_event(Event::DrawdownRejected(project_id, drawdown_id)); - Ok(()) - } - - pub fn do_reset_drawdown( - user: T::AccountId, - project_id: ProjectId, - drawdown_id: DrawdownId, - ) -> DispatchResult { - // Ensure builder permissions - Self::is_authorized(user.clone(), &project_id, ProxyPermission::CancelDrawdownSubmission)?; - - // Ensure project exists - ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); - - // Get drawdown data & ensure drawdown exists - let drawdown_data = - DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; - - // Ensure drawdown is in submitted status - ensure!( - drawdown_data.status == DrawdownStatus::Submitted, - Error::::DrawdownNotSubmitted - ); - - if drawdown_data.drawdown_type == DrawdownType::EB5 { - // Get drawdown transactions - let drawdown_transactions = - TransactionsByDrawdown::::try_get(project_id, drawdown_id) - .map_err(|_| Error::::DrawdownNotFound)?; - - // Delete drawdown transactions from TransactionsInfo - for transaction_id in drawdown_transactions.iter().cloned() { - // Delete transaction - >::remove(transaction_id); - } - } - - // Delete drawdown transactions from TransactionsByDrawdown - >::remove(project_id, drawdown_id); - - // Update drawdown status to default - >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { - let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; - drawdown_data.total_amount = 0; - drawdown_data.status = DrawdownStatus::default(); - drawdown_data.bulkupload_documents = None; - drawdown_data.bank_documents = None; - drawdown_data.description = None; - drawdown_data.feedback = None; - drawdown_data.status_changes = DrawdownStatusChanges::::default(); - Ok(()) - })?; - - // Update drawdown status in project info - Self::do_update_drawdown_status_in_project_info( - project_id, - drawdown_id, - DrawdownStatus::default(), - )?; - - // Event - Self::deposit_event(Event::DrawdownSubmissionCancelled(project_id, drawdown_id)); - Ok(()) - } - - // T R A N S A C T I O N S - // ================================================================================================ - pub fn do_execute_transactions( - user: T::AccountId, - project_id: ProjectId, - drawdown_id: DrawdownId, - transactions: Transactions, - ) -> DispatchResult { - // Ensure admin or builder permissions - Self::is_authorized(user, &project_id, ProxyPermission::ExecuteTransactions)?; - - // Ensure project exists & is not completed so helper private functions doesn't need to check it again - Self::is_project_completed(project_id)?; - - // Ensure drawdown exists so helper private functions doesn't need to check it again - ensure!(DrawdownsInfo::::contains_key(drawdown_id), Error::::DrawdownNotFound); - - // Ensure transactions are not empty - ensure!(!transactions.is_empty(), Error::::EmptyTransactions); - - // Ensure if the selected drawdown is editable - Self::is_drawdown_editable(drawdown_id)?; - - for transaction in transactions.iter().cloned() { - match transaction.3 { - CUDAction::Create => { - Self::do_create_transaction( - project_id, - drawdown_id, - transaction.0.ok_or(Error::::ExpenditureIdRequired)?, - transaction.1.ok_or(Error::::AmountRequired)?, - transaction.2, - )?; - }, - CUDAction::Update => { - Self::do_update_transaction( - transaction.1, - transaction.2, - transaction.4.ok_or(Error::::TransactionIdRequired)?, - )?; - }, - CUDAction::Delete => { - Self::do_delete_transaction( - transaction.4.ok_or(Error::::TransactionIdRequired)?, - )?; - }, - } - } - - // Update total amount for the given drawdown - Self::do_calculate_drawdown_total_amount(project_id, drawdown_id)?; - - // Event - Self::deposit_event(Event::TransactionsExecuted(project_id, drawdown_id)); - Ok(()) - } - - fn do_create_transaction( - project_id: ProjectId, - drawdown_id: DrawdownId, - expenditure_id: ExpenditureId, - amount: Amount, - documents: Option>, - ) -> DispatchResult { - // TOREVIEW: If documents are mandatory, we need to check if they are provided - - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Create transaction id - let transaction_id = - (drawdown_id, amount, expenditure_id, timestamp, project_id).using_encoded(blake2_256); - - // Ensure expenditure id does not exist - ensure!( - ExpendituresInfo::::contains_key(expenditure_id), - Error::::ExpenditureNotFound - ); - - // Create transaction data - let transaction_data = TransactionData:: { - project_id, - drawdown_id, - expenditure_id, - created_date: timestamp, - updated_date: timestamp, - closed_date: 0, - feedback: None, - amount, - status: TransactionStatus::default(), - documents, - }; - - // Insert transaction data - // Ensure transaction id is unique - ensure!( - !TransactionsInfo::::contains_key(transaction_id), - Error::::TransactionAlreadyExists - ); - >::insert(transaction_id, transaction_data); - - // Insert transaction id into TransactionsByDrawdown - >::try_mutate::<_, _, _, DispatchError, _>( - project_id, - drawdown_id, - |transactions| { - transactions - .try_push(transaction_id) - .map_err(|_| Error::::MaxTransactionsPerDrawdownReached)?; - Ok(()) - }, - )?; - - // Event - Self::deposit_event(Event::TransactionCreated(project_id, drawdown_id, transaction_id)); - Ok(()) - } - - fn do_update_transaction( - amount: Option, - documents: Option>, - transaction_id: TransactionId, - ) -> DispatchResult { - // Get transaction data & ensure it exists - let transaction_data = - Self::transactions_info(transaction_id).ok_or(Error::::TransactionNotFound)?; - - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Try mutate transaction data - >::try_mutate::<_, _, DispatchError, _>( - transaction_id, - |transaction_data| { - let mod_transaction_data = - transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; - - // Ensure expenditure exists - ensure!( - ExpendituresInfo::::contains_key(mod_transaction_data.expenditure_id), - Error::::ExpenditureNotFound - ); - - // Update amount - if let Some(mod_amount) = amount { - mod_transaction_data.amount = mod_amount; - } - - // Update documents - if let Some(mod_documents) = documents { - mod_transaction_data.documents = Some(mod_documents); - } - - // Update updated date - mod_transaction_data.updated_date = timestamp; - Ok(()) - }, - )?; - - // Event - Self::deposit_event(Event::TransactionEdited( - transaction_data.project_id, - transaction_data.drawdown_id, - transaction_id, - )); - Ok(()) - } - - fn do_delete_transaction(transaction_id: TransactionId) -> DispatchResult { - // Ensure transaction exists and get transaction data - let transaction_data = - TransactionsInfo::::get(transaction_id).ok_or(Error::::TransactionNotFound)?; - - ensure!( - >::get(transaction_data.project_id, transaction_data.drawdown_id) - .contains(&transaction_id), - Error::::TransactionNotFoundForSelectedDrawdownId - ); - - // Ensure drawdown is deletable - Self::is_drawdown_editable(transaction_data.drawdown_id)?; - - // Ensure transaction is deletable - Self::is_transaction_editable(transaction_id)?; - - >::try_mutate_exists::<_, _, _, DispatchError, _>( - transaction_data.project_id, - transaction_data.drawdown_id, - |transactions_option| { - let transactions = transactions_option.as_mut().ok_or(Error::::DrawdownHasNoTransactions)?; - transactions.retain(|transaction| transaction != &transaction_id); - if transactions.is_empty() { - transactions_option.clone_from(&None); - } - Ok(()) - }, - )?; - - // Remove transaction from TransactionsInfo - >::remove(transaction_id); - - // Event - Self::deposit_event(Event::TransactionDeleted( - transaction_data.project_id, - transaction_data.drawdown_id, - transaction_id, - )); - Ok(()) - } - - // B U L K U P L O A D T R A N S A C T I O N S - // ================================================================================================ - pub fn do_up_bulk_upload( - user: T::AccountId, - project_id: ProjectId, - drawdown_id: DrawdownId, - description: FieldDescription, - total_amount: TotalAmount, - documents: Documents, - ) -> DispatchResult { - // Ensure builder permissions - Self::is_authorized(user, &project_id, ProxyPermission::UpBulkupload)?; - - // Ensure project is not completed - Self::is_project_completed(project_id)?; - - // Ensure drawdown is not completed - Self::is_drawdown_editable(drawdown_id)?; - - // Ensure only Construction loan & developer equity drawdowns are able to call bulk upload extrinsic - let drawdown_data = - DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; - - ensure!( - drawdown_data.drawdown_type == DrawdownType::ConstructionLoan || - drawdown_data.drawdown_type == DrawdownType::DeveloperEquity, - Error::::DrawdownTypeNotSupportedForBulkUpload - ); - - // Ensure documents is not empty - ensure!(!documents.is_empty(), Error::::BulkUploadDocumentsRequired); - - // Ensure description is not empty - ensure!(!description.is_empty(), Error::::BulkUploadDescriptionRequired); - - // Mutate drawdown data - >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { - let mod_drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; - mod_drawdown_data.total_amount = total_amount; - mod_drawdown_data.description = Some(description); - mod_drawdown_data.bulkupload_documents = Some(documents); - mod_drawdown_data.status = DrawdownStatus::Submitted; - mod_drawdown_data.feedback = None; - Ok(()) - })?; - - // Update drawdown status in project info - Self::do_update_drawdown_status_in_project_info( - project_id, - drawdown_id, - DrawdownStatus::Submitted, - )?; - - // Event - Self::deposit_event(Event::BulkUploadSubmitted(project_id, drawdown_id)); - Ok(()) - } - - // I N F L A T I O N A D J U S T M E N T - // ================================================================================================ - pub fn do_execute_inflation_adjustment( - admin: T::AccountId, - projects: ProjectsInflation, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized( - admin.clone(), - &Self::get_global_scope(), - ProxyPermission::InflationRate, - )?; - - // Ensure projects array is not empty - ensure!(!projects.is_empty(), Error::::ProjectsInflationRateEmpty); - - // Match each CUD action - for project in projects.iter().cloned() { - // Ensure project exists - ensure!(ProjectsInfo::::contains_key(project.0), Error::::ProjectNotFound); - match project.2 { - CUDAction::Create => { - // Ensure inflation rate is provided - let inflation_rate = project.1.ok_or(Error::::InflationRateRequired)?; - - // Get project data - let project_data = - ProjectsInfo::::get(project.0).ok_or(Error::::ProjectNotFound)?; - - // Ensure project has no inflation rate - ensure!( - project_data.inflation_rate.is_none(), - Error::::InflationRateAlreadySet - ); - - // Set inflation rate - >::try_mutate::<_, _, DispatchError, _>( - project.0, - |project_info| { - let mod_project_data = - project_info.as_mut().ok_or(Error::::ProjectNotFound)?; - mod_project_data.inflation_rate = Some(inflation_rate); - Ok(()) - }, - )?; - }, - CUDAction::Update => { - // Ensure inflation rate is provided - let inflation_rate = project.1.ok_or(Error::::InflationRateRequired)?; - - // Get project data - let project_data = - ProjectsInfo::::get(project.0).ok_or(Error::::ProjectNotFound)?; - - // Ensure project has inflation rate - ensure!(project_data.inflation_rate.is_some(), Error::::InflationRateNotSet); - - // Set inflation rate - >::try_mutate::<_, _, DispatchError, _>( - project.0, - |project_info| { - let mod_project_data = - project_info.as_mut().ok_or(Error::::ProjectNotFound)?; - mod_project_data.inflation_rate = Some(inflation_rate); - Ok(()) - }, - )?; - }, - CUDAction::Delete => { - // Get project data - let project_data = - ProjectsInfo::::get(project.0).ok_or(Error::::ProjectNotFound)?; - - // Ensure project has inflation rate - ensure!(project_data.inflation_rate.is_some(), Error::::InflationRateNotSet); - - // Delete inflation rate - >::try_mutate::<_, _, DispatchError, _>( - project.0, - |project_info| { - let mod_project_data = - project_info.as_mut().ok_or(Error::::ProjectNotFound)?; - mod_project_data.inflation_rate = None; - Ok(()) - }, - )?; - }, - } - } - - // Event - Self::deposit_event(Event::InflationRateAdjusted(admin)); - Ok(()) - } - - // J O B E L I G I B L E S - // ================================================================================================ - pub fn do_execute_job_eligibles( - admin: T::AccountId, - project_id: ProjectId, - job_eligibles: JobEligibles, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized(admin.clone(), &project_id, ProxyPermission::JobEligible)?; - - // Ensure project exists - ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); - - // Ensure job eligibles is not empty - ensure!(!job_eligibles.is_empty(), Error::::JobEligiblesEmpty); - - for job_eligible in job_eligibles.iter().cloned() { - match job_eligible.4 { - CUDAction::Create => { - Self::do_create_job_eligible( - project_id, - job_eligible.0.ok_or(Error::::JobEligibleNameRequired)?, - job_eligible.1.ok_or(Error::::JobEligibleAmountRequired)?, - job_eligible.2, - job_eligible.3, - )?; - }, - CUDAction::Update => { - Self::do_update_job_eligible( - project_id, - job_eligible.5.ok_or(Error::::JobEligibleIdRequired)?, - job_eligible.0, - job_eligible.1, - job_eligible.2, - job_eligible.3, - )?; - }, - CUDAction::Delete => { - Self::do_delete_job_eligible( - job_eligible.5.ok_or(Error::::JobEligibleIdRequired)?, - )?; - }, - } - } - - // Event - Self::deposit_event(Event::JobEligiblesExecuted(admin, project_id)); - Ok(()) - } - - fn do_create_job_eligible( - project_id: [u8; 32], - name: FieldName, - job_eligible_amount: JobEligibleAmount, - naics_code: Option, - jobs_multiplier: Option, - ) -> DispatchResult { - // Ensure project exists & is not completed - Self::is_project_completed(project_id)?; - - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Ensure job eligible name is not empty - ensure!(!name.is_empty(), Error::::JobEligiblesNameRequired); - - // Create job eligible id - let job_eligible_id: JobEligibleId = - (project_id, name.clone(), timestamp).using_encoded(blake2_256); - - // Create job eligible data - let job_eligible_data = - JobEligibleData { project_id, name, job_eligible_amount, naics_code, jobs_multiplier }; - - // Insert job eligible data into JobEligiblesInfo - // Ensure job eligible id does not exist - ensure!( - !JobEligiblesInfo::::contains_key(job_eligible_id), - Error::::JobEligibleIdAlreadyExists - ); - >::insert(job_eligible_id, job_eligible_data); - - // Insert job eligible id into JobEligiblesByProject - >::try_mutate::<_, _, DispatchError, _>( - project_id, - |job_eligibles| { - job_eligibles - .try_push(job_eligible_id) - .map_err(|_| Error::::MaxJobEligiblesPerProjectReached)?; - Ok(()) - }, - )?; - - // Event - Self::deposit_event(Event::JobEligibleCreated(project_id, job_eligible_id)); - Ok(()) - } - - fn do_update_job_eligible( - project_id: ProjectId, - job_eligible_id: JobEligibleId, - name: Option, - job_eligible_amount: Option, - naics_code: Option, - jobs_multiplier: Option, - ) -> DispatchResult { - // Ensure project exists & is not completed - Self::is_project_completed(project_id)?; - - // Ensure job eligible exists - ensure!( - JobEligiblesInfo::::contains_key(job_eligible_id), - Error::::JobEligibleNotFound - ); - - // Mutate job eligible data - >::try_mutate::<_, _, DispatchError, _>( - job_eligible_id, - |job_eligible_data| { - let job_eligible = - job_eligible_data.as_mut().ok_or(Error::::JobEligibleNotFound)?; - - // Ensure job eligible belongs to the project - ensure!( - job_eligible.project_id == project_id, - Error::::JobEligibleDoesNotBelongToProject - ); - - if let Some(mod_name) = name { - job_eligible.name = mod_name; - } - if let Some(mod_job_eligible_amount) = job_eligible_amount { - job_eligible.job_eligible_amount = mod_job_eligible_amount; - } - if let Some(mod_naics_code) = naics_code { - job_eligible.naics_code = Some(mod_naics_code); - } - if let Some(mod_jobs_multiplier) = jobs_multiplier { - job_eligible.jobs_multiplier = Some(mod_jobs_multiplier); - } - Ok(()) - }, - )?; - - // Event - Self::deposit_event(Event::JobEligibleUpdated(project_id, job_eligible_id)); - Ok(()) - } - - fn do_delete_job_eligible(job_eligible_id: JobEligibleId) -> DispatchResult { - // Ensure job eligible exists & get job eligible data - let job_eligible_data = - JobEligiblesInfo::::get(job_eligible_id).ok_or(Error::::JobEligibleNotFound)?; - - // Ensure job_eligible_id is contained in JobEligiblesByProject - ensure!( - JobEligiblesByProject::::get(job_eligible_data.project_id) - .contains(&job_eligible_id), - Error::::JobEligibleNotFoundForSelectedProjectId - ); - - Self::do_delete_job_eligible_transactions(job_eligible_id)?; - - // Delete job eligible data from JobEligiblesInfo - >::remove(job_eligible_id); - - // Delete job eligible id from JobEligiblesByProject - >::try_mutate_exists::<_, _, DispatchError, _>( - job_eligible_data.project_id, - |job_eligibles_option| { - let job_eligibles = job_eligibles_option.as_mut().ok_or(Error::::ProjectHasNoJobEligibles)?; - job_eligibles.retain(|job_eligible| job_eligible != &job_eligible_id); - if job_eligibles.is_empty() { - job_eligibles_option.clone_from(&None); - } - Ok(()) - }, - )?; - - Self::deposit_event(Event::JobEligibleDeleted( - job_eligible_data.project_id, - job_eligible_id, - )); - - Ok(()) - } - - // R E V E N U E S - // ================================================================================================ - pub fn do_execute_revenue_transactions( - user: T::AccountId, - project_id: ProjectId, - revenue_id: RevenueId, - revenue_transactions: RevenueTransactions, - ) -> DispatchResult { - // Ensure builder permission - Self::is_authorized(user, &project_id, ProxyPermission::RevenueTransaction)?; - - // Ensure project exists & is not completed so helper private functions doesn't need to check it again - Self::is_project_completed(project_id)?; - - // Ensure revenue exists so helper private functions doesn't need to check it again - ensure!(RevenuesInfo::::contains_key(revenue_id), Error::::RevenueNotFound); - - // Ensure revenue transactions are not empty - ensure!(!revenue_transactions.is_empty(), Error::::RevenueTransactionsEmpty); - - // Ensure if the selected revenue is editable - Self::is_revenue_editable(revenue_id)?; - - for transaction in revenue_transactions.iter().cloned() { - match transaction.3 { - CUDAction::Create => { - Self::do_create_revenue_transaction( - project_id, - revenue_id, - transaction.0.ok_or(Error::::JobEligibleIdRequired)?, - transaction.1.ok_or(Error::::RevenueAmountRequired)?, - transaction.2, - )?; - }, - CUDAction::Update => { - Self::do_update_revenue_transaction( - transaction.1, - transaction.2, - transaction.4.ok_or(Error::::RevenueTransactionIdRequired)?, - )?; - }, - CUDAction::Delete => { - Self::do_delete_revenue_transaction( - transaction.4.ok_or(Error::::RevenueTransactionIdRequired)?, - )?; - }, - } - } - - //Update total amount for the given revenue - Self::do_calculate_revenue_total_amount(project_id, revenue_id)?; - - // Event - Self::deposit_event(Event::RevenueTransactionsExecuted(project_id, revenue_id)); - Ok(()) - } - - fn do_create_revenue_transaction( - project_id: ProjectId, - revenue_id: RevenueId, - job_eligible_id: JobEligibleId, - revenue_amount: RevenueAmount, - documents: Option>, - ) -> DispatchResult { - // TOREVIEW: If documents are mandatory, then we need to check if they are empty - - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Create revenue transaction id - let revenue_transaction_id = - (revenue_id, job_eligible_id, project_id, timestamp).using_encoded(blake2_256); - - // Ensure revenue transaction id doesn't exist - ensure!( - !RevenueTransactionsInfo::::contains_key(revenue_transaction_id), - Error::::RevenueTransactionIdAlreadyExists - ); - - // Create revenue transaction data - let revenue_transaction_data = RevenueTransactionData { - project_id, - revenue_id, - job_eligible_id, - created_date: timestamp, - updated_date: timestamp, - closed_date: 0, - feedback: None, - amount: revenue_amount, - status: RevenueTransactionStatus::default(), - documents, - }; - - // Insert revenue transaction data into RevenueTransactionsInfo - // Ensure revenue transaction id doesn't exist - ensure!( - !RevenueTransactionsInfo::::contains_key(revenue_transaction_id), - Error::::RevenueTransactionIdAlreadyExists - ); - >::insert(revenue_transaction_id, revenue_transaction_data); - - // Insert revenue transaction id into TransactionsByRevenue - >::try_mutate::<_, _, _, DispatchError, _>( - project_id, - revenue_id, - |revenue_transactions| { - revenue_transactions - .try_push(revenue_transaction_id) - .map_err(|_| Error::::MaxTransactionsPerRevenueReached)?; - Ok(()) - }, - )?; - - // Event - Self::deposit_event(Event::RevenueTransactionCreated( - project_id, - revenue_id, - revenue_transaction_id, - )); - Ok(()) - } - - fn do_update_revenue_transaction( - amount: Option, - documents: Option>, - revenue_transaction_id: RevenueTransactionId, - ) -> DispatchResult { - // Get revenue transaction data & ensure revenue transaction exists - let revenue_transaction_data = RevenueTransactionsInfo::::get(revenue_transaction_id) - .ok_or(Error::::RevenueTransactionNotFound)?; - - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Try mutate revenue transaction data - >::try_mutate::<_, _, DispatchError, _>( - revenue_transaction_id, - |revenue_transaction_data| { - let mod_revenue_transaction_data = revenue_transaction_data - .as_mut() - .ok_or(Error::::RevenueTransactionNotFound)?; - - // Ensure job eligible exists - ensure!( - JobEligiblesInfo::::contains_key( - mod_revenue_transaction_data.job_eligible_id - ), - Error::::JobEligibleNotFound - ); - - // Update amount - if let Some(mod_amount) = amount { - mod_revenue_transaction_data.amount = mod_amount; - } - - // Update documents - if let Some(mod_documents) = documents { - mod_revenue_transaction_data.documents = Some(mod_documents); - } - - // Update updated_date - mod_revenue_transaction_data.updated_date = timestamp; - Ok(()) - }, - )?; - - // Event - Self::deposit_event(Event::RevenueTransactionUpdated( - revenue_transaction_data.project_id, - revenue_transaction_data.revenue_id, - revenue_transaction_id, - )); - Ok(()) - } - - fn do_delete_revenue_transaction( - revenue_transaction_id: RevenueTransactionId, - ) -> DispatchResult { - // Ensure revenue transaction exists & get revenue transaction data - let revenue_transaction_data = RevenueTransactionsInfo::::get(revenue_transaction_id) - .ok_or(Error::::RevenueTransactionNotFound)?; - - // Ensure revenue transaction belongs to the given revenue - ensure!( - TransactionsByRevenue::::get( - revenue_transaction_data.project_id, - revenue_transaction_data.revenue_id - ).contains(&revenue_transaction_id), - Error::::RevenueTransactionNotFoundForSelectedRevenueId - ); - - // Ensure revenue is deletable - Self::is_revenue_editable(revenue_transaction_data.revenue_id)?; - - // Ensure revenue transaction is deletable - Self::is_revenue_transaction_editable(revenue_transaction_id)?; - - // Remove revenue transaction from TransactionsByRevenue - >::try_mutate_exists::<_, _, _, DispatchError, _>( - revenue_transaction_data.project_id, - revenue_transaction_data.revenue_id, - |revenue_transactions_option| { - let revenue_transactions = revenue_transactions_option - .as_mut() - .ok_or(Error::::RevenueHasNoTransactions)?; - revenue_transactions - .retain(|revenue_transaction| revenue_transaction != &revenue_transaction_id); - if revenue_transactions.is_empty() { - revenue_transactions_option.clone_from(&None); - } - Ok(()) - }, - )?; - - // Remove revenue transaction from RevenueTransactionsInfo - >::remove(revenue_transaction_id); - - // Event - Self::deposit_event(Event::RevenueTransactionDeleted( - revenue_transaction_data.project_id, - revenue_transaction_data.revenue_id, - revenue_transaction_id, - )); - Ok(()) - } - - pub fn do_submit_revenue( - user: T::AccountId, - project_id: ProjectId, - revenue_id: RevenueId, - ) -> DispatchResult { - // Ensure builder permissions - Self::is_authorized(user, &project_id, ProxyPermission::SubmitRevenue)?; - - // Ensure project exists & is not completed - Self::is_project_completed(project_id)?; - - // Check if revenue exists & is editable - Self::is_revenue_editable(revenue_id)?; - - // Ensure revenue has transactions - ensure!( - !TransactionsByRevenue::::get(project_id, revenue_id).is_empty(), - Error::::RevenueHasNoTransactions - ); - - // Get revenue transactions - let revenue_transactions = TransactionsByRevenue::::try_get(project_id, revenue_id) - .map_err(|_| Error::::RevenueNotFound)?; - - // Update each revenue transaction status to Submitted - for transaction_id in revenue_transactions.iter().cloned() { - // Ensure revenue transaction is editable - Self::is_revenue_transaction_editable(transaction_id)?; - - // Update revenue transaction status - >::try_mutate::<_, _, DispatchError, _>( - transaction_id, - |revenue_transaction_data| { - let revenue_transaction_data = revenue_transaction_data - .as_mut() - .ok_or(Error::::RevenueTransactionNotFound)?; - revenue_transaction_data.status = RevenueTransactionStatus::Submitted; - revenue_transaction_data.feedback = None; - Ok(()) - }, - )?; - } - - // Update revenue status - >::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| { - let revenue_data = revenue_data.as_mut().ok_or(Error::::RevenueNotFound)?; - revenue_data.status = RevenueStatus::Submitted; - Ok(()) - })?; - - // Update revenue status in project info - Self::do_update_revenue_status_in_project_info( - project_id, - revenue_id, - RevenueStatus::Submitted, - )?; - - // Event - Self::deposit_event(Event::RevenueSubmitted(project_id, revenue_id)); - - Ok(()) - } - - pub fn do_approve_revenue( - admin: T::AccountId, - project_id: ProjectId, - revenue_id: RevenueId, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized(admin, &project_id, ProxyPermission::ApproveRevenue)?; - - // Get revenue data - let revenue_data = Self::revenues_info(revenue_id).ok_or(Error::::RevenueNotFound)?; - - // Ensure revenue is submitted - ensure!(revenue_data.status == RevenueStatus::Submitted, Error::::RevenueNotSubmitted); - - ensure!( - TransactionsByRevenue::::contains_key(project_id, revenue_id), - Error::::RevenueHasNoTransactions - ); - - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Get revenue transactions - let revenue_transactions = TransactionsByRevenue::::try_get(project_id, revenue_id) - .map_err(|_| Error::::RevenueNotFound)?; - - // Update each revenue transaction status to Approved - for transaction_id in revenue_transactions.iter().cloned() { - // Ensure revenue transaction is editable - let revenue_transaction_data = RevenueTransactionsInfo::::get(transaction_id) - .ok_or(Error::::RevenueTransactionNotFound)?; - ensure!( - revenue_transaction_data.status == RevenueTransactionStatus::Submitted, - Error::::RevenueTransactionNotSubmitted - ); - - // Update revenue transaction status to Approved & update closed date - >::try_mutate::<_, _, DispatchError, _>( - transaction_id, - |revenue_transaction_data| { - let revenue_transaction_data = revenue_transaction_data - .as_mut() - .ok_or(Error::::RevenueTransactionNotFound)?; - revenue_transaction_data.status = RevenueTransactionStatus::Approved; - revenue_transaction_data.closed_date = timestamp; - Ok(()) - }, - )?; - } - - // Update revenue status to Approved - >::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| { - let revenue_data = revenue_data.as_mut().ok_or(Error::::RevenueNotFound)?; - revenue_data.status = RevenueStatus::Approved; - revenue_data.closed_date = timestamp; - Ok(()) - })?; - - // Update revenue status in project info - Self::do_update_revenue_status_in_project_info( - project_id, - revenue_id, - RevenueStatus::Approved, - )?; - - // Generate the next revenue - Self::do_create_revenue(project_id, revenue_data.revenue_number + 1)?; - - // Event - Self::deposit_event(Event::RevenueApproved(project_id, revenue_id)); - - Ok(()) - } - - pub fn do_reject_revenue( - admin: T::AccountId, - project_id: ProjectId, - revenue_id: RevenueId, - revenue_transactions_feedback: TransactionsFeedback, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized(admin, &project_id, ProxyPermission::RejectRevenue)?; - - // Get revenue data - let revenue_data = Self::revenues_info(revenue_id).ok_or(Error::::RevenueNotFound)?; - - // Ensure revenue is submitted - ensure!(revenue_data.status == RevenueStatus::Submitted, Error::::RevenueNotSubmitted); - - // Ensure revenue has transactions - ensure!( - !TransactionsByRevenue::::get(project_id, revenue_id).is_empty(), - Error::::RevenueHasNoTransactions - ); - - // Get revenue transactions - let revenue_transactions = TransactionsByRevenue::::try_get(project_id, revenue_id) - .map_err(|_| Error::::RevenueNotFound)?; - - // Update each revenue transaction status to Rejected - for transaction_id in revenue_transactions.iter().cloned() { - // Ensure revenue transaction is editable - let revenue_transaction_data = RevenueTransactionsInfo::::get(transaction_id) - .ok_or(Error::::RevenueTransactionNotFound)?; - ensure!( - revenue_transaction_data.status == RevenueTransactionStatus::Submitted, - Error::::RevenueTransactionNotSubmitted - ); - - // Update revenue transaction status to Rejected - >::try_mutate::<_, _, DispatchError, _>( - transaction_id, - |revenue_transaction_data| { - let revenue_transaction_data = revenue_transaction_data - .as_mut() - .ok_or(Error::::RevenueTransactionNotFound)?; - revenue_transaction_data.status = RevenueTransactionStatus::Rejected; - Ok(()) - }, - )?; - } - - // Ensure revenue transactions feedback is not empty - ensure!( - !revenue_transactions_feedback.is_empty(), - Error::::RevenueTransactionsFeedbackEmpty - ); - // Update revenue transactions feedback - for (transaction_id, feedback) in revenue_transactions_feedback.iter().cloned() { - // Update revenue transaction feedback - >::try_mutate::<_, _, DispatchError, _>( - transaction_id, - |revenue_transaction_data| { - let revenue_transaction_data = revenue_transaction_data - .as_mut() - .ok_or(Error::::RevenueTransactionNotFound)?; - revenue_transaction_data.feedback = Some(feedback); - Ok(()) - }, - )?; - } - - // Update revenue status to Rejected - >::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| { - let revenue_data = revenue_data.as_mut().ok_or(Error::::RevenueNotFound)?; - revenue_data.status = RevenueStatus::Rejected; - Ok(()) - })?; - - // Update revenue status in project info - Self::do_update_revenue_status_in_project_info( - project_id, - revenue_id, - RevenueStatus::Rejected, - )?; - - // Event - Self::deposit_event(Event::RevenueRejected(project_id, revenue_id)); - - Ok(()) - } - - // B A N K C O N F I R M I N G D O C U M E N T S - // ------------------------------------------------------------------------ - pub fn do_bank_confirming_documents( - admin: T::AccountId, - project_id: ProjectId, - drawdown_id: DrawdownId, - confirming_documents: Option>, - action: CUDAction, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_authorized(admin, &project_id, ProxyPermission::BankConfirming)?; - - // Ensure project exists - ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); - - // Get drawdown data & ensure drawdown exists - let drawdown_data = - DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; - - // Ensure drawdown is EB5 - ensure!( - drawdown_data.drawdown_type == DrawdownType::EB5, - Error::::OnlyEB5DrawdownsCanUploadBankDocuments - ); - - match action { - CUDAction::Create => { - // Ensure bank confirming documents are provided - let mod_confirming_documents = - confirming_documents.ok_or(Error::::BankConfirmingDocumentsNotProvided)?; - - // Ensure confirming documents are not empty - ensure!( - !mod_confirming_documents.is_empty(), - Error::::BankConfirmingDocumentsEmpty - ); - - // Create drawdown bank confirming documents - Self::do_create_bank_confirming_documents( - project_id, - drawdown_id, - mod_confirming_documents, - ) - }, - CUDAction::Update => { - // Ensure bank confirming documents are provided - let mod_confirming_documents = - confirming_documents.ok_or(Error::::BankConfirmingDocumentsNotProvided)?; - - // Ensure confirming documents are not empty - ensure!( - !mod_confirming_documents.is_empty(), - Error::::BankConfirmingDocumentsEmpty - ); - - // Update drawdown bank confirming documents - Self::do_update_bank_confirming_documents(drawdown_id, mod_confirming_documents) - }, - CUDAction::Delete => { - // Delete drawdown bank confirming documents - Self::do_delete_bank_confirming_documents(project_id, drawdown_id) - }, - } - } - - fn do_create_bank_confirming_documents( - project_id: ProjectId, - drawdown_id: DrawdownId, - confirming_documents: Documents, - ) -> DispatchResult { - // Get drawdown data & ensure drawdown exists - let drawdown_data = - DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; - - // Ensure drawdown has no bank confirming documents - ensure!( - drawdown_data.bank_documents.is_none(), - Error::::DrawdownHasAlreadyBankConfirmingDocuments - ); - - // Ensure drawdown status is Approved - ensure!( - drawdown_data.status == DrawdownStatus::Approved, - Error::::DrawdowMustBeInApprovedStatus - ); - - // Mutate drawdown data: Upload bank documents & update drawdown status to Confirmed - >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { - let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; - drawdown_data.bank_documents = Some(confirming_documents); - drawdown_data.status = DrawdownStatus::Confirmed; - Ok(()) - })?; - - // Get drawdown transactions - let drawdown_transactions = TransactionsByDrawdown::::try_get(project_id, drawdown_id) - .map_err(|_| Error::::DrawdownHasNoTransactions)?; - - // Mutate individual drawdown transactions status to Confirmed - for transaction_id in drawdown_transactions.iter().cloned() { - // Ensure transaction exists - ensure!( - TransactionsInfo::::contains_key(transaction_id), - Error::::TransactionNotFound - ); - - // Update drawdown transaction status to Confirmed - >::try_mutate::<_, _, DispatchError, _>( - transaction_id, - |transaction_data| { - let transaction_data = - transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; - transaction_data.status = TransactionStatus::Confirmed; - Ok(()) - }, - )?; - } - - // Update drawdown status changes in drawdown info - Self::do_create_drawdown_status_change_record(drawdown_id, DrawdownStatus::Confirmed)?; - - // Event - Self::deposit_event(Event::BankDocumentsUploaded(project_id, drawdown_id)); - Ok(()) - } - - fn do_update_bank_confirming_documents( - drawdown_id: DrawdownId, - confirming_documents: Documents, - ) -> DispatchResult { - // Get drawdown data & ensure drawdown exists - let drawdown_data = - DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; - - // Ensure drawdown status is Confirmed - ensure!( - drawdown_data.status == DrawdownStatus::Confirmed, - Error::::DrawdowMustBeInConfirmedStatus - ); - - // Ensure drawdown has bank confirming documents - ensure!( - drawdown_data.bank_documents.is_some(), - Error::::DrawdownHasNoBankConfirmingDocuments - ); - - // Mutate drawdown data: Update bank documents - >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { - let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; - drawdown_data.bank_documents = Some(confirming_documents); - Ok(()) - })?; - - // Event - Self::deposit_event(Event::BankDocumentsUpdated(drawdown_data.project_id, drawdown_id)); - Ok(()) - } - - fn do_delete_bank_confirming_documents( - project_id: ProjectId, - drawdown_id: DrawdownId, - ) -> DispatchResult { - // Get drawdown data & ensure drawdown exists - let drawdown_data = - DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; - - // Ensure drawdown status is Confirmed - ensure!( - drawdown_data.status == DrawdownStatus::Confirmed, - Error::::DrawdowMustBeInConfirmedStatus - ); - - // Ensure drawdown has bank confirming documents - ensure!( - drawdown_data.bank_documents.is_some(), - Error::::DrawdownHasNoBankConfirmingDocuments - ); - - // Rollback drawdown status to Approved & remove bank confirming documents - >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { - let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; - drawdown_data.bank_documents = None; - drawdown_data.status = DrawdownStatus::Approved; - Ok(()) - })?; - - // Get drawdown transactions - let drawdown_transactions = TransactionsByDrawdown::::try_get(project_id, drawdown_id) - .map_err(|_| Error::::DrawdownHasNoTransactions)?; - - // Mutate individual drawdown transactions status to Approved - for transaction_id in drawdown_transactions.iter().cloned() { - // Ensure transaction exists - ensure!( - TransactionsInfo::::contains_key(transaction_id), - Error::::TransactionNotFound - ); - - // Update drawdown transaction status to Approved - >::try_mutate::<_, _, DispatchError, _>( - transaction_id, - |transaction_data| { - let transaction_data = - transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; - transaction_data.status = TransactionStatus::Approved; - Ok(()) - }, - )?; - } - - // Update drawdown status changes in drawdown info - Self::do_create_drawdown_status_change_record(drawdown_id, DrawdownStatus::Approved)?; - - // Event - Self::deposit_event(Event::BankDocumentsDeleted(project_id, drawdown_id)); - Ok(()) - } - - // H E L P E R S - // ================================================================================================ - - /// Get the current timestamp in milliseconds - fn get_timestamp_in_milliseconds() -> Option { - let timestamp: u64 = T::Timestamp::now().into(); - - Some(timestamp) - } - - /// Get the pallet_id - pub fn pallet_id() -> IdOrVec { - IdOrVec::Vec(Self::module_name().as_bytes().to_vec()) - } - - /// Get global scope - pub fn get_global_scope() -> [u8; 32] { - >::try_get() - .map_err(|_| Error::::NoGlobalScopeValueWasFound) - .unwrap() - } - - #[allow(dead_code)] - fn change_project_status( - admin: T::AccountId, - project_id: ProjectId, - status: ProjectStatus, - ) -> DispatchResult { - // Ensure admin permissions - Self::is_superuser(admin, &Self::get_global_scope(), ProxyRole::Administrator.id())?; - - // Ensure project exists - ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); - - // Check project status is not completed - Self::is_project_completed(project_id)?; - - // Mutate project data - >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { - let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; - project.status = status; - Ok(()) - })?; - - Ok(()) - } - - fn is_project_completion_date_later(project_id: ProjectId) -> DispatchResult { - // Get project data & ensure project exists - let project_data = ProjectsInfo::::get(project_id).ok_or(Error::::ProjectNotFound)?; - - // Ensure completion date is later than start date - ensure!( - project_data.completion_date > project_data.creation_date, - Error::::CompletionDateMustBeLater - ); - Ok(()) - } - - fn add_project_role( - project_id: ProjectId, - user: T::AccountId, - role: ProxyRole, - ) -> DispatchResult { - match role { - ProxyRole::Administrator => { - return Err(Error::::CannotRegisterAdminRole.into()) - }, - ProxyRole::Builder => { - // Mutate project data - >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { - let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; - match project.builder.as_mut() { - Some(builder) => { - builder - .try_push(user.clone()) - .map_err(|_| Error::::MaxBuildersPerProjectReached)?; - }, - None => { - let devs = project - .builder - .get_or_insert( - BoundedVec::::default(), - ); - devs.try_push(user.clone()) - .map_err(|_| Error::::MaxBuildersPerProjectReached)?; - }, - } - Ok(()) - })?; - }, - ProxyRole::Investor => { - // Mutate project data - >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { - let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; - match project.investor.as_mut() { - Some(investor) => { - investor - .try_push(user.clone()) - .map_err(|_| Error::::MaxInvestorsPerProjectReached)?; - }, - None => { - let investors = project.investor.get_or_insert(BoundedVec::< - T::AccountId, - T::MaxInvestorsPerProject, - >::default()); - investors - .try_push(user.clone()) - .map_err(|_| Error::::MaxInvestorsPerProjectReached)?; - }, - } - Ok(()) - })?; - }, - ProxyRole::Issuer => { - // Mutate project data - >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { - let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; - match project.issuer.as_mut() { - Some(issuer) => { - issuer - .try_push(user.clone()) - .map_err(|_| Error::::MaxIssuersPerProjectReached)?; - }, - None => { - let issuers = project - .issuer - .get_or_insert( - BoundedVec::::default(), - ); - issuers - .try_push(user.clone()) - .map_err(|_| Error::::MaxIssuersPerProjectReached)?; - }, - } - Ok(()) - })?; - }, - ProxyRole::RegionalCenter => { - // Mutate project data - >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { - let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; - match project.regional_center.as_mut() { - Some(regional_center) => { - regional_center - .try_push(user.clone()) - .map_err(|_| Error::::MaxRegionalCenterPerProjectReached)?; - }, - None => { - let regional_centers = - project.regional_center.get_or_insert(BoundedVec::< - T::AccountId, - T::MaxRegionalCenterPerProject, - >::default()); - regional_centers - .try_push(user.clone()) - .map_err(|_| Error::::MaxRegionalCenterPerProjectReached)?; - }, - } - Ok(()) - })?; - }, - } - - Ok(()) - } - - pub fn remove_project_role( - project_id: ProjectId, - user: T::AccountId, - role: ProxyRole, - ) -> DispatchResult { - match role { - ProxyRole::Administrator => { - return Err(Error::::CannotRemoveAdminRole.into()) - }, - ProxyRole::Builder => { - // Mutate project data - >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { - let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; - match project.builder.as_mut() { - Some(builder) => { - builder.retain(|u| *u != user); - }, - None => { - return Err(Error::::UserNotAssignedToProject.into()) - }, - } - Ok(()) - })?; - }, - ProxyRole::Investor => { - // Mutate project data - >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { - let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; - match project.investor.as_mut() { - Some(investor) => { - investor.retain(|u| *u != user); - }, - None => { - return Err(Error::::UserNotAssignedToProject.into()) - }, - } - Ok(()) - })?; - }, - ProxyRole::Issuer => { - // Mutate project data - >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { - let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; - match project.issuer.as_mut() { - Some(issuer) => { - issuer.retain(|u| *u != user); - }, - None => { - return Err(Error::::UserNotAssignedToProject.into()) - }, - } - Ok(()) - })?; - }, - ProxyRole::RegionalCenter => { - // Mutate project data - >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { - let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; - match project.regional_center.as_mut() { - Some(regional_center) => { - regional_center.retain(|u| *u != user); - }, - None => { - return Err(Error::::UserNotAssignedToProject.into()) - }, - } - Ok(()) - })?; - }, - } - Ok(()) - } - - /// Helper function to check the following: - /// - /// 1. Checks if the user is registered in the system - /// 2. Checks if the user has the required role from UsersInfo storage - /// 3. Checks if the user is trying to assign an admin role - fn check_user_role(user: T::AccountId, role: ProxyRole) -> DispatchResult { - // Ensure user is registered & get user data - let user_data = UsersInfo::::get(user.clone()).ok_or(Error::::UserNotRegistered)?; - - // Check if the user role trying to be assigned matches the actual user role from UsersInfo storage - if user_data.role != role { - return Err(Error::::UserCannotHaveMoreThanOneRole.into()) - } - - // Match user role. Check the max numbers of projects a user can be assigned to - match user_data.role { - ProxyRole::Administrator => { - // Can't assign an administrator role account to a project, admins are scoped globally - return Err(Error::::CannotAddAdminRole.into()); - }, - ProxyRole::Investor => { - // Investors can be assigned to a maximum of 1 project - // Get how many projects the user is assigned to - let projects_count = >::get(user.clone()).len(); - ensure!( - projects_count < T::MaxProjectsPerInvestor::get() as usize, - Error::::MaxProjectsPerInvestorReached - ); - Ok(()) - }, - // Builders, Issuers & Regional Centers don't have a limit on how many projects they can be assigned to - _ => Ok(()), - } - } - - // TOREVIEW: Refactor this function when implementing the Error recovery workflow - fn is_project_completed(project_id: ProjectId) -> DispatchResult { - // Get project data & ensure project exists - let project_data = ProjectsInfo::::get(project_id).ok_or(Error::::ProjectNotFound)?; - - // Ensure project is not completed - ensure!( - project_data.status != ProjectStatus::Completed, - Error::::ProjectIsAlreadyCompleted - ); - - Ok(()) - } - - fn is_drawdown_editable(drawdown_id: DrawdownId) -> DispatchResult { - // Get drawdown data & ensure drawdown exists - let drawdown_data = - DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; - - // Match drawdown type - match drawdown_data.drawdown_type { - DrawdownType::EB5 => { - // Match drawdown status - // Ensure drawdown is in draft or rejected status - match drawdown_data.status { - DrawdownStatus::Draft => Ok(()), - DrawdownStatus::Rejected => Ok(()), - DrawdownStatus::Submitted => - Err(Error::::CannotPerformActionOnSubmittedDrawdown.into()), - DrawdownStatus::Approved => - Err(Error::::CannotPerformActionOnApprovedDrawdown.into()), - DrawdownStatus::Confirmed => - Err(Error::::CannotPerformActionOnConfirmedDrawdown.into()), - } - }, - _ => { - // Match drawdown status - match drawdown_data.status { - DrawdownStatus::Approved => - Err(Error::::CannotPerformActionOnApprovedDrawdown.into()), - DrawdownStatus::Confirmed => - Err(Error::::CannotPerformActionOnConfirmedDrawdown.into()), - _ => Ok(()), - } - }, - } - } - - fn is_transaction_editable(transaction_id: TransactionId) -> DispatchResult { - // Get transaction data & ensure transaction exists - let transaction_data = - TransactionsInfo::::get(transaction_id).ok_or(Error::::TransactionNotFound)?; - - // Ensure transaction is in draft or rejected status - // Match transaction status - match transaction_data.status { - TransactionStatus::Draft => Ok(()), - TransactionStatus::Rejected => Ok(()), - TransactionStatus::Submitted => - Err(Error::::CannotPerformActionOnSubmittedTransaction.into()), - TransactionStatus::Approved => - Err(Error::::CannotPerformActionOnApprovedTransaction.into()), - TransactionStatus::Confirmed => - Err(Error::::CannotPerformActionOnConfirmedTransaction.into()), - } - } - - /// # Checks if the caller has the permission to perform an action - /// - /// - This version of is_authorized checks if the caller is an Administrator and if so, it - /// checks the global scope - /// otherwise it checks the project scope - /// - This is useful for functions that are called by both administrators and project users - /// - Scope is always required. In workflows where the caller is an administrator, - /// we can get it from the helper private function `get_global_scope()` - pub fn is_authorized( - authority: T::AccountId, - scope: &[u8; 32], - permission: ProxyPermission, - ) -> DispatchResult { - // Get user data - let user_data = >::try_get(authority.clone()) - .map_err(|_| Error::::UserNotRegistered)?; - - match user_data.role { - ProxyRole::Administrator => T::Rbac::is_authorized( - authority, - Self::pallet_id(), - &Self::get_global_scope(), - &permission.id(), - ), - _ => T::Rbac::is_authorized(authority, Self::pallet_id(), scope, &permission.id()), - } - } - - #[allow(dead_code)] - fn is_superuser( - authority: T::AccountId, - scope_global: &[u8; 32], - rol_id: RoleId, - ) -> DispatchResult { - T::Rbac::has_role(authority, Self::pallet_id(), scope_global, vec![rol_id]) - } - - fn sudo_register_admin(admin: T::AccountId, name: FieldName) -> DispatchResult { - // Check if user is already registered - ensure!(!>::contains_key(admin.clone()), Error::::UserAlreadyRegistered); - - // Get current timestamp - let current_timestamp = - Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - let user_data = UserData:: { - name, - role: ProxyRole::Administrator, - image: CID::default(), - date_registered: current_timestamp, - email: FieldName::default(), - documents: None, - }; - - // Insert user data - >::insert(admin.clone(), user_data); - - // Add administrator to rbac pallet - T::Rbac::assign_role_to_user( - admin, - Self::pallet_id(), - &Self::get_global_scope(), - ProxyRole::Administrator.id(), - )?; - - Ok(()) - } - - fn sudo_delete_admin(admin: T::AccountId) -> DispatchResult { - // Check if user is already registered - ensure!(>::contains_key(admin.clone()), Error::::UserNotRegistered); - - // Remove user from UsersInfo storagemap - >::remove(admin.clone()); - - // Remove administrator from rbac pallet - T::Rbac::remove_role_from_user( - admin, - Self::pallet_id(), - &Self::get_global_scope(), - ProxyRole::Administrator.id(), - )?; - - Ok(()) - } - - fn do_calculate_drawdown_total_amount( - project_id: [u8; 32], - drawdown_id: [u8; 32], - ) -> DispatchResult { - // Ensure drawdown exists - ensure!(>::contains_key(drawdown_id), Error::::DrawdownNotFound); - - // Calculate drawdown total amount - let mut drawdown_total_amount: u64 = 0; - - if !TransactionsByDrawdown::::get(project_id, drawdown_id).is_empty() { - // Get transactions by drawdown - let transactions_by_drawdown = TransactionsByDrawdown::::get(project_id, drawdown_id); - - // Iterate through transactions - for transaction_id in transactions_by_drawdown.iter().cloned() { - // Get transaction data - let transaction_data = - TransactionsInfo::::get(transaction_id).ok_or(Error::::TransactionNotFound)?; - - // Add transaction amount to drawdown total amount - drawdown_total_amount += transaction_data.amount; - } - } - - // Update drawdown total amount - >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { - let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; - drawdown_data.total_amount = drawdown_total_amount; - Ok(()) - })?; - - Ok(()) - } - - fn do_update_drawdown_status_in_project_info( - project_id: ProjectId, - drawdown_id: DrawdownId, - drawdown_status: DrawdownStatus, - ) -> DispatchResult { - // Ensure project exists - ensure!(>::contains_key(project_id), Error::::ProjectNotFound); - - // Get drawdown data & ensure drawdown exists - let drawdown_data = - DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; - - // Match drawdown type - match drawdown_data.drawdown_type { - DrawdownType::EB5 => { - // Update EB5 drawdown status in project info - >::try_mutate::<_, _, DispatchError, _>( - project_id, - |project_data| { - let project_data = - project_data.as_mut().ok_or(Error::::ProjectNotFound)?; - project_data.eb5_drawdown_status = Some(drawdown_status); - Ok(()) - }, - )?; - }, - DrawdownType::ConstructionLoan => { - // Update Construction Loan drawdown status in project info - >::try_mutate::<_, _, DispatchError, _>( - project_id, - |project_data| { - let project_data = - project_data.as_mut().ok_or(Error::::ProjectNotFound)?; - project_data.construction_loan_drawdown_status = Some(drawdown_status); - Ok(()) - }, - )?; - }, - DrawdownType::DeveloperEquity => { - // Update Developer Equity drawdown status in project info - >::try_mutate::<_, _, DispatchError, _>( - project_id, - |project_data| { - let project_data = - project_data.as_mut().ok_or(Error::::ProjectNotFound)?; - project_data.developer_equity_drawdown_status = Some(drawdown_status); - Ok(()) - }, - )?; - }, - } - - // Update drawdown status changes in drawdown info - Self::do_create_drawdown_status_change_record(drawdown_id, drawdown_status)?; - Ok(()) - } - - fn is_revenue_editable(revenue_id: RevenueId) -> DispatchResult { - // Get revenue data & ensure revenue exists - let revenue_data = RevenuesInfo::::get(revenue_id).ok_or(Error::::RevenueNotFound)?; - - // Match revenue status - match revenue_data.status { - RevenueStatus::Draft => Ok(()), - RevenueStatus::Rejected => Ok(()), - RevenueStatus::Submitted => - Err(Error::::CannotPerformActionOnSubmittedRevenue.into()), - RevenueStatus::Approved => Err(Error::::CannotPerformActionOnApprovedRevenue.into()), - } - } - - fn is_revenue_transaction_editable( - revenue_transaction_id: RevenueTransactionId, - ) -> DispatchResult { - // Get revenue transaction data & ensure revenue transaction exists - let revenue_transaction_data = RevenueTransactionsInfo::::get(revenue_transaction_id) - .ok_or(Error::::RevenueTransactionNotFound)?; - - // Ensure transaction is in draft or rejected status - // Match revenue transaction status - match revenue_transaction_data.status { - RevenueTransactionStatus::Draft => Ok(()), - RevenueTransactionStatus::Rejected => Ok(()), - RevenueTransactionStatus::Submitted => - Err(Error::::CannotPerformActionOnSubmittedRevenueTransaction.into()), - RevenueTransactionStatus::Approved => - Err(Error::::CannotPerformActionOnApprovedRevenueTransaction.into()), - } - } - - fn do_calculate_revenue_total_amount( - project_id: ProjectId, - revenue_id: RevenueId, - ) -> DispatchResult { - // Ensure revenue exists - ensure!(>::contains_key(revenue_id), Error::::RevenueNotFound); - - // Calculate revenue total amount - let mut revenue_total_amount: Amount = 0; - - if !TransactionsByRevenue::::get(project_id, revenue_id).is_empty() { - // Get revenue transactions - let transactions_by_revenue = TransactionsByRevenue::::get(project_id, revenue_id); - - // Iterate over revenue transactions - for revenue_transaction_id in transactions_by_revenue { - // Get revenue transaction data - let revenue_transaction_data = - RevenueTransactionsInfo::::get(revenue_transaction_id) - .ok_or(Error::::RevenueTransactionNotFound)?; - - // Add revenue transaction amount to revenue total amount - revenue_total_amount += revenue_transaction_data.amount; - } - } - - // Update revenue total amount - >::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| { - let revenue_data = revenue_data.as_mut().ok_or(Error::::RevenueNotFound)?; - revenue_data.total_amount = revenue_total_amount; - Ok(()) - })?; - - Ok(()) - } - - fn do_initialize_revenue(project_id: ProjectId) -> DispatchResult { - // Ensure project exists - ensure!(>::contains_key(project_id), Error::::ProjectNotFound); - - // Create revenue - Self::do_create_revenue(project_id, 1)?; - - Ok(()) - } - - fn do_create_revenue(project_id: ProjectId, revenue_number: RevenueNumber) -> DispatchResult { - // Ensure project exists - ensure!(>::contains_key(project_id), Error::::ProjectNotFound); - - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Create revenue id - let revenue_id = (project_id, revenue_number, timestamp).using_encoded(blake2_256); - - // Create revenue data - let revenue_data = RevenueData:: { - project_id, - revenue_number, - total_amount: 0, - status: RevenueStatus::default(), - status_changes: RevenueStatusChanges::::default(), - created_date: timestamp, - closed_date: 0, - }; - - // Insert revenue data - // Ensure revenue id is unique - ensure!(!>::contains_key(revenue_id), Error::::RevenueIdAlreadyExists); - >::insert(revenue_id, revenue_data); - - // Insert revenue id into RevenuesByProject - >::try_mutate::<_, _, DispatchError, _>(project_id, |revenues| { - revenues - .try_push(revenue_id) - .map_err(|_| Error::::MaxRevenuesPerProjectReached)?; - Ok(()) - })?; - - // Update revenue status in project info - Self::do_update_revenue_status_in_project_info( - project_id, - revenue_id, - RevenueStatus::default(), - )?; - - // Event - Self::deposit_event(Event::RevenueCreated(project_id, revenue_id)); - - Ok(()) - } - - fn do_update_revenue_status_in_project_info( - project_id: ProjectId, - revenue_id: RevenueId, - revenue_status: RevenueStatus, - ) -> DispatchResult { - // Ensure project exists - ensure!(>::contains_key(project_id), Error::::ProjectNotFound); - - // Ensure revenue exists - ensure!(>::contains_key(revenue_id), Error::::RevenueNotFound); - - // Update revenue status in project info - >::try_mutate::<_, _, DispatchError, _>(project_id, |project_data| { - let project_data = project_data.as_mut().ok_or(Error::::ProjectNotFound)?; - project_data.revenue_status = Some(revenue_status); - Ok(()) - })?; - - // Update revenue status changes in revenue info - Self::do_create_revenue_status_change_record(revenue_id, revenue_status)?; - Ok(()) - } - - fn do_create_drawdown_status_change_record( - drawdown_id: DrawdownId, - drawdown_status: DrawdownStatus, - ) -> DispatchResult { - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Update drawdown status changes in drawdown info - >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { - let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; - drawdown_data - .status_changes - .try_push((drawdown_status, timestamp)) - .map_err(|_| Error::::MaxStatusChangesPerDrawdownReached)?; - Ok(()) - })?; - - Ok(()) - } - - fn do_create_revenue_status_change_record( - revenue_id: RevenueId, - revenue_status: RevenueStatus, - ) -> DispatchResult { - // Get timestamp - let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; - - // Update revenue status changes in revenue info - >::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| { - let revenue_data = revenue_data.as_mut().ok_or(Error::::RevenueNotFound)?; - revenue_data - .status_changes - .try_push((revenue_status, timestamp)) - .map_err(|_| Error::::MaxStatusChangesPerRevenueReached)?; - Ok(()) - })?; - - Ok(()) - } - - fn send_funds(admin: T::AccountId, user: T::AccountId) -> DispatchResult { - // Ensure admin has enough funds to perform transfer without reaping the account - ensure!( - T::Currency::free_balance(&admin) > T::Currency::minimum_balance(), - Error::::AdminHasNoFreeBalance - ); - - //Ensure admin has enough funds to transfer & keep some balance to perform other operations - ensure!( - T::Currency::free_balance(&admin) > T::MinAdminBalance::get(), - Error::::InsufficientFundsToTransfer - ); - - //TODO: Check if user has enough funds to receive transfer, refactor else arm - // If user has no funds, then transfer funds to user - if T::Currency::free_balance(&user) < T::Currency::minimum_balance() { - // Transfer funds to user - T::Currency::transfer(&admin, &user, T::TransferAmount::get(), KeepAlive)?; - Ok(()) - } else { - return Ok(()) - } - } - - fn do_delete_expenditure_transactions(expenditure_id: ExpenditureId) -> DispatchResult { - // Get expenditure data - let expenditure_data = - >::get(expenditure_id).ok_or(Error::::ExpenditureNotFound)?; - - // Ensure project exists - ensure!( - >::contains_key(expenditure_data.project_id), - Error::::ProjectNotFound - ); - - // Ensure project contains drawdowns and get them - let drawdowns = >::try_get(expenditure_data.project_id) - .map_err(|_| Error::::ProjectHasNoDrawdowns)?; - - for drawdown_id in drawdowns.iter().cloned() { - // Ensure drawdown exists - ensure!(>::contains_key(drawdown_id), Error::::DrawdownNotFound); - - // If drawdown has transactions, check that every transaction exists & its amount is zero - if !>::get(expenditure_data.project_id, drawdown_id).is_empty() { - for transaction_id in - >::get(expenditure_data.project_id, drawdown_id).iter().cloned() - { - // Ensure transaction exists & get transaction data - let transaction_data = >::get(transaction_id) - .ok_or(Error::::TransactionNotFound)?; - - if transaction_data.expenditure_id == expenditure_id { - // Ensure transaction amount is zero - ensure!( - transaction_data.amount == 0, - Error::::ExpenditureHasNonZeroTransactions - ); - - // Delete transaction from TransactionsInfo - >::remove(transaction_id); - - // Delete transaction from TransactionsByDrawdown - >::try_mutate_exists::<_, _, _, DispatchError, _>( - expenditure_data.project_id, - drawdown_id, - |transactions_option| { - let transactions = transactions_option.as_mut().ok_or(Error::::DrawdownHasNoTransactions)?; - transactions.retain(|transaction| transaction != &transaction_id); - if transactions.is_empty() { - transactions_option.clone_from(&None); - } - Ok(()) - }, - )?; - } - } - } - } - Ok(()) - } - - fn do_delete_job_eligible_transactions(job_eligible_id: JobEligibleId,) -> DispatchResult { - // Get job eligible data - let job_eligible_data = - >::get(job_eligible_id).ok_or(Error::::JobEligibleNotFound)?; - - // Ensure project exists - ensure!( - >::contains_key(job_eligible_data.project_id), - Error::::ProjectNotFound - ); - - // Ensure project contains revenues and get them - let revenues = >::try_get(job_eligible_data.project_id) - .map_err(|_| Error::::ProjectHasNoRevenues)?; - - for revenue_id in revenues.iter().cloned() { - // Ensure revenue exists - ensure!(>::contains_key(revenue_id), Error::::RevenueNotFound); - - // If revenue has transactions, check that every transaction exists & its amount is zero - if !>::get(job_eligible_data.project_id, revenue_id).is_empty() { - for transaction_id in - >::get(job_eligible_data.project_id, revenue_id).iter().cloned() - { - // Ensure transaction exists & get transaction data - let transaction_data = >::get(transaction_id) - .ok_or(Error::::RevenueTransactionNotFound)?; - - if transaction_data.job_eligible_id == job_eligible_id { - // Ensure transaction amount is zero - ensure!( - transaction_data.amount == 0, - Error::::JobEligibleHasNonZeroTransactions - ); - - // Delete transaction from RevenueTransactionsInfo - >::remove(transaction_id); - - // Delete transaction from TransactionsByRevenue - >::try_mutate_exists::<_, _, _, DispatchError, _>( - job_eligible_data.project_id, - revenue_id, - |transactions_option| { - let transactions = transactions_option.as_mut().ok_or(Error::::RevenueHasNoTransactions)?; - transactions.retain(|transaction| transaction != &transaction_id); - if transactions.is_empty() { - transactions_option.clone_from(&None); - } - Ok(()) - }, - )?; - } - } - } - } - Ok(()) - } - - // Do not code beyond this line + // M A I N F U N C T I O N S + // ================================================================================================ + + // I N I T I A L S E T U P + // ================================================================================================ + + pub fn do_initial_setup() -> DispatchResult { + // Create a global scope for the administrator role + let pallet_id = Self::pallet_id(); + let global_scope = pallet_id.using_encoded(blake2_256); + >::put(global_scope); + T::Rbac::create_scope(Self::pallet_id(), global_scope)?; + + // Admin rol & permissions + let administrator_role_id = T::Rbac::create_and_set_roles( + pallet_id.clone(), + [ProxyRole::Administrator.to_vec()].to_vec(), + )?; + T::Rbac::create_and_set_permissions( + pallet_id.clone(), + administrator_role_id[0], + ProxyPermission::administrator_permissions(), + )?; + + // Builder rol & permissions + let builder_role_id = + T::Rbac::create_and_set_roles(pallet_id.clone(), [ProxyRole::Builder.to_vec()].to_vec())?; + T::Rbac::create_and_set_permissions( + pallet_id.clone(), + builder_role_id[0], + ProxyPermission::builder_permissions(), + )?; + + // Investor rol & permissions + let investor_role_id = + T::Rbac::create_and_set_roles(pallet_id.clone(), [ProxyRole::Investor.to_vec()].to_vec())?; + T::Rbac::create_and_set_permissions( + pallet_id.clone(), + investor_role_id[0], + ProxyPermission::investor_permissions(), + )?; + + // Issuer rol & permissions + let issuer_role_id = + T::Rbac::create_and_set_roles(pallet_id.clone(), [ProxyRole::Issuer.to_vec()].to_vec())?; + T::Rbac::create_and_set_permissions( + pallet_id.clone(), + issuer_role_id[0], + ProxyPermission::issuer_permissions(), + )?; + + // Regional center rol & permissions + let regional_center_role_id = T::Rbac::create_and_set_roles( + pallet_id.clone(), + [ProxyRole::RegionalCenter.to_vec()].to_vec(), + )?; + T::Rbac::create_and_set_permissions( + pallet_id, + regional_center_role_id[0], + ProxyPermission::regional_center_permissions(), + )?; + + // Event + Self::deposit_event(Event::ProxySetupCompleted); + Ok(()) + } + + pub fn do_sudo_add_administrator(admin: T::AccountId, name: FieldName) -> DispatchResult { + // Ensure name is not empty + ensure!(!name.is_empty(), Error::::EmptyFieldName); + // Create a administrator user account & register it in the rbac pallet + Self::sudo_register_admin(admin.clone(), name)?; + + // Event + Self::deposit_event(Event::AdministratorAssigned(admin)); + Ok(()) + } + + pub fn do_sudo_remove_administrator(admin: T::AccountId) -> DispatchResult { + // Remove administrator user account & remove it from the rbac pallet + Self::sudo_delete_admin(admin.clone())?; + + // Event + Self::deposit_event(Event::AdministratorRemoved(admin)); + Ok(()) + } + + // P R O J E C T S + // ================================================================================================ + pub fn do_create_project( + admin: T::AccountId, + title: FieldName, + description: FieldDescription, + image: Option, + address: FieldName, + banks: Option>, + creation_date: CreationDate, + completion_date: CompletionDate, + expenditures: Expenditures, + job_eligibles: Option>, + users: Option>, + private_group_id: PrivateGroupId, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin.clone(), &Self::get_global_scope(), ProxyPermission::CreateProject)?; + + // Validations + ensure!(!title.is_empty(), Error::::EmptyFieldName); + ensure!(!description.is_empty(), Error::::EmptyFieldDescription); + if let Some(image) = image.clone() { + ensure!(!image.is_empty(), Error::::EmptyFieldCID); + } + ensure!(!address.is_empty(), Error::::EmptyFieldName); + if let Some(banks) = banks.clone() { + ensure!(!banks.is_empty(), Error::::EmptyFieldBanks); + } + ensure!(!address.is_empty(), Error::::EmptyProjectAddress); + ensure!(!private_group_id.is_empty(), Error::::PrivateGroupIdEmpty); + + // Add timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Create project_id + let project_id: ProjectId = (title.clone(), timestamp).using_encoded(blake2_256); + + // Ensure completion_date is in the future + ensure!(completion_date > creation_date, Error::::CompletionDateMustBeLater); + + // Ensuree private group id is not empty + ensure!(!private_group_id.is_empty(), Error::::PrivateGroupIdEmpty); + + // Create project data + let project_data = ProjectData:: { + builder: Some(BoundedVec::::default()), + investor: Some(BoundedVec::::default()), + issuer: Some(BoundedVec::::default()), + regional_center: Some(BoundedVec::::default()), + title, + description, + image, + address, + status: ProjectStatus::default(), + inflation_rate: None, + banks, + registration_date: timestamp, + creation_date, + completion_date, + updated_date: timestamp, + construction_loan_drawdown_status: None, + developer_equity_drawdown_status: None, + eb5_drawdown_status: None, + revenue_status: None, + private_group_id, + }; + + // Create the scope for the given project_id + T::Rbac::create_scope(Self::pallet_id(), project_id)?; + + // Insert project data + // Ensure that the project_id is not already in use + ensure!(!ProjectsInfo::::contains_key(project_id), Error::::ProjectIdAlreadyInUse); + ProjectsInfo::::insert(project_id, project_data); + + // Add expenditures + Self::do_execute_expenditures(admin.clone(), project_id, expenditures)?; + + // Add job_eligibles + if let Some(mod_job_eligibles) = job_eligibles { + Self::do_execute_job_eligibles(admin.clone(), project_id, mod_job_eligibles)?; + } + + // Add users + if let Some(mod_users) = users { + Self::do_execute_assign_users(admin.clone(), project_id, mod_users)?; + } + + // Initialize drawdowns + Self::do_initialize_drawdowns(admin.clone(), project_id)?; + + // Initialize revenue + Self::do_initialize_revenue(project_id)?; + + // Event + Self::deposit_event(Event::ProjectCreated(admin, project_id)); + Ok(()) + } + + pub fn do_edit_project( + admin: T::AccountId, + project_id: ProjectId, + title: Option, + description: Option, + image: Option, + address: Option, + banks: Option>, + creation_date: Option, + completion_date: Option, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin.clone(), &project_id, ProxyPermission::EditProject)?; + + // Ensure project exists + ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); + + // Ensure project is not completed + Self::is_project_completed(project_id)?; + + // Get current timestamp + let current_timestamp = + Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Mutate project data + >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { + let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; + + if let Some(title) = title { + // Ensure title is not empty + ensure!(!title.is_empty(), Error::::EmptyFieldName); + project.title = title; + } + if let Some(description) = description { + // Ensure description is not empty + ensure!(!description.is_empty(), Error::::EmptyFieldDescription); + project.description = description; + } + if let Some(image) = image { + // Ensure image is not empty + ensure!(!image.is_empty(), Error::::EmptyFieldCID); + project.image = Some(image); + } + if let Some(address) = address { + // Ensure address is not empty + ensure!(!address.is_empty(), Error::::EmptyProjectAddress); + project.address = address; + } + if let Some(banks) = banks { + // Ensure banks is not empty + ensure!(!banks.is_empty(), Error::::EmptyFieldBanks); + project.banks = Some(banks); + } + if let Some(creation_date) = creation_date { + project.creation_date = creation_date; + } + if let Some(completion_date) = completion_date { + project.completion_date = completion_date; + } + // Update modified date + project.updated_date = current_timestamp; + + Ok(()) + })?; + + // Ensure completion_date is later than creation_date + Self::is_project_completion_date_later(project_id)?; + + // Event + Self::deposit_event(Event::ProjectEdited(admin, project_id)); + Ok(()) + } + + pub fn do_delete_project(admin: T::AccountId, project_id: ProjectId) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin.clone(), &project_id, ProxyPermission::DeleteProject)?; + + // Ensure project exists & get project data + let project_data = ProjectsInfo::::get(project_id).ok_or(Error::::ProjectNotFound)?; + + // Ensure project is not completed + ensure!( + project_data.status != ProjectStatus::Completed, + Error::::CannotDeleteCompletedProject + ); + + if UsersByProject::::contains_key(project_id) { + // Get users by project + let users_by_project = UsersByProject::::get(project_id); + // Unassign all users from project + // Create a UsersAssignation boundedvec with all users in the project + let mut users_assignation: UsersAssignation = UsersAssignation::::default(); + for user in users_by_project.iter().cloned() { + // Get user data + let user_data = + >::try_get(user.clone()).map_err(|_| Error::::UserNotRegistered)?; + + users_assignation + .try_push((user, user_data.role, AssignAction::Unassign)) + .map_err(|_| Error::::MaxRegistrationsAtATimeReached)?; + } + + // Unassign all users from project + Self::do_execute_assign_users(admin.clone(), project_id, users_assignation)?; + + // Remove project from users + for user in users_by_project.iter().cloned() { + >::try_mutate::<_, _, DispatchError, _>(user, |projects| { + projects.retain(|project| *project != project_id); + Ok(()) + })?; + } + } + + // Delete from ProjectsInfo storagemap + >::remove(project_id); + + // Delete from UsersByProject storagemap + >::remove(project_id); + + // Delete expenditures from ExpendituresInfo storagemap + let expenditures_by_project = Self::expenditures_by_project(project_id) + .iter() + .cloned() + .collect::>(); + for expenditure_id in expenditures_by_project.iter().cloned() { + >::remove(expenditure_id); + } + + // Deletes all expenditures from ExpendituresByProject storagemap + >::remove(project_id); + + let drawdowns_by_project = Self::drawdowns_by_project(project_id) + .iter() + .cloned() + .collect::>(); + for drawdown_id in drawdowns_by_project.iter().cloned() { + // Delete transactions from TransactionsInfo storagemap + let transactions_by_drawdown = Self::transactions_by_drawdown(project_id, drawdown_id) + .iter() + .cloned() + .collect::>(); + for transaction_id in transactions_by_drawdown.iter().cloned() { + >::remove(transaction_id); + } + + // Deletes all transactions from TransactionsByDrawdown storagemap + >::remove(project_id, drawdown_id); + + // Delete drawdown from DrawdownsInfo storagemap + >::remove(drawdown_id); + } + + // Deletes all drawdowns from DrawdownsByProject storagemap + >::remove(project_id); + + // Delete job eligibles from JobEligiblesInfo storagemap + let job_eligibles_by_project = Self::job_eligibles_by_project(project_id) + .iter() + .cloned() + .collect::>(); + for job_eligible_id in job_eligibles_by_project.iter().cloned() { + >::remove(job_eligible_id); + } + + // Deletes all job eligibles from JobEligiblesByProject storagemap + >::remove(project_id); + + // Delete job from RevenuesInfo storagemap + let revenues_by_project = + Self::revenues_by_project(project_id).iter().cloned().collect::>(); + for revenue_id in revenues_by_project.iter().cloned() { + // Delete revenue transactions from RevenueTransactionsInfo storagemap + let transactions_by_revenue = Self::transactions_by_revenue(project_id, revenue_id) + .iter() + .cloned() + .collect::>(); + for transaction_id in transactions_by_revenue.iter().cloned() { + >::remove(transaction_id); + } + + // Deletes all revenue transactions from TransactionsByRevenue storagemap + >::remove(project_id, revenue_id); + + // Delete revenue from RevenuesInfo storagemap + >::remove(revenue_id); + } + + // Deletes all revenues from RevenuesByProject storagemap + >::remove(project_id); + + // Delete scope from rbac pallet + T::Rbac::remove_scope(Self::pallet_id(), project_id)?; + + // Event + Self::deposit_event(Event::ProjectDeleted(admin, project_id)); + Ok(()) + } + + pub fn do_execute_assign_users( + admin: T::AccountId, + project_id: ProjectId, + users: UsersAssignation, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin.clone(), &project_id, ProxyPermission::AssignUsers)?; + + // Ensure UsersAssignation is not empty + ensure!(!users.is_empty(), Error::::EmptyUsersAssignation); + + // Ensure project exists & is not completed + Self::is_project_completed(project_id)?; + + // Assign users + for user in users.iter().cloned() { + match user.2 { + AssignAction::Assign => { + Self::do_assign_user(project_id, user.0, user.1)?; + }, + AssignAction::Unassign => { + Self::do_unassign_user(project_id, user.0, user.1)?; + }, + } + } + + // Event + Self::deposit_event(Event::UsersAssignationExecuted(admin, project_id)); + Ok(()) + } + + fn do_assign_user(project_id: ProjectId, user: T::AccountId, role: ProxyRole) -> DispatchResult { + // Basic validations prior to assign the given user + Self::check_user_role(user.clone(), role)?; + + // Ensure user is not already assigned to the project + ensure!( + !>::get(project_id).contains(&user), + Error::::UserAlreadyAssignedToProject + ); + ensure!( + !>::get(user.clone()).contains(&project_id), + Error::::UserAlreadyAssignedToProject + ); + + // Ensure user is not assigened to the selected scope (project_id) with the selected role + ensure!( + !T::Rbac::has_role(user.clone(), Self::pallet_id(), &project_id, [role.id()].to_vec()) + .is_ok(), + Error::::UserAlreadyAssignedToProject + ); + + // Update project data depending on the role assigned + Self::add_project_role(project_id, user.clone(), role)?; + + // Insert project to ProjectsByUser storagemap + >::try_mutate::<_, _, DispatchError, _>(user.clone(), |projects| { + projects + .try_push(project_id) + .map_err(|_| Error::::MaxProjectsPerUserReached)?; + Ok(()) + })?; + + // Insert user in UsersByProject storagemap + >::try_mutate::<_, _, DispatchError, _>(project_id, |users| { + users + .try_push(user.clone()) + .map_err(|_| Error::::MaxUsersPerProjectReached)?; + Ok(()) + })?; + + // Give a set of permissions to the given user based on the role assigned + T::Rbac::assign_role_to_user(user.clone(), Self::pallet_id(), &project_id, role.id())?; + + // Event + Self::deposit_event(Event::UserAssignmentCompleted(user, project_id)); + Ok(()) + } + + fn do_unassign_user( + project_id: ProjectId, + user: T::AccountId, + role: ProxyRole, + ) -> DispatchResult { + // Ensure user is registered + ensure!(>::contains_key(user.clone()), Error::::UserNotRegistered); + + // Ensure user is assigned to the project + ensure!( + >::get(project_id).contains(&user.clone()), + Error::::UserNotAssignedToProject + ); + ensure!( + >::get(user.clone()).contains(&project_id), + Error::::UserNotAssignedToProject + ); + + // Ensure user has the specified role assigned in the selected project + ensure!( + T::Rbac::has_role(user.clone(), Self::pallet_id(), &project_id, [role.id()].to_vec()).is_ok(), + Error::::UserDoesNotHaveRole + ); + + // Update project data depending on the role unassigned + Self::remove_project_role(project_id, user.clone(), role)?; + + // Remove user from UsersByProject storagemap. + >::try_mutate_exists::<_, _, DispatchError, _>(project_id, |users_option| { + let users = users_option.as_mut().ok_or(Error::::ProjectHasNoUsers)?; + users.retain(|u| u != &user); + if users.is_empty() { + users_option.clone_from(&None); + } + Ok(()) + })?; + + // Remove user from ProjectsByUser storagemap + >::try_mutate_exists::<_, _, DispatchError, _>( + user.clone(), + |projects_option| { + let projects = projects_option.as_mut().ok_or(Error::::UserHasNoProjects)?; + projects.retain(|project| project != &project_id); + if projects.is_empty() { + projects_option.clone_from(&None); + } + Ok(()) + }, + )?; + + // Remove user from the scope rbac pallet + T::Rbac::remove_role_from_user(user.clone(), Self::pallet_id(), &project_id, role.id())?; + + // Event + Self::deposit_event(Event::UserUnassignmentCompleted(user, project_id)); + Ok(()) + } + + // U S E R S + // ================================================================================================ + pub fn do_execute_users(admin: T::AccountId, users: Users) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin.clone(), &Self::get_global_scope(), ProxyPermission::ExecuteUsers)?; + + // Ensure users list is not empty + ensure!(!users.is_empty(), Error::::EmptyUsers); + + for user in users.iter().cloned() { + match user.3 { + CUDAction::Create => { + Self::do_create_user( + user.0.clone(), + user.1.clone().ok_or(Error::::UserNameRequired)?, + user.2.ok_or(Error::::UserRoleRequired)?, + )?; + + //Send funds to the user + Self::send_funds(admin.clone(), user.0.clone())?; + }, + CUDAction::Update => { + Self::do_update_user(user.0.clone(), user.1.clone(), user.2)?; + + //Send funds to the user + Self::send_funds(admin.clone(), user.0.clone())?; + }, + CUDAction::Delete => { + ensure!(user.0 != admin, Error::::AdministratorsCannotDeleteThemselves,); + + Self::do_delete_user(user.0.clone())?; + }, + } + } + + // Event + Self::deposit_event(Event::UsersExecuted(admin)); + Ok(()) + } + + fn do_create_user(user: T::AccountId, name: FieldName, role: ProxyRole) -> DispatchResult { + // Get current timestamp + let current_timestamp = + Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Ensure user is not registered + ensure!(!>::contains_key(user.clone()), Error::::UserAlreadyRegistered); + + // Ensure name is not empty + ensure!(!name.is_empty(), Error::::UserNameRequired); + + match role { + ProxyRole::Administrator => { + Self::do_sudo_add_administrator(user.clone(), name)?; + }, + _ => { + // Create user data + let user_data = UserData:: { + name, + role, + image: CID::default(), + date_registered: current_timestamp, + email: FieldName::default(), + documents: None, + }; + + // Insert user data in UsersInfo storagemap + >::insert(user.clone(), user_data); + }, + } + + // Event + Self::deposit_event(Event::UserCreated(user)); + Ok(()) + } + + fn do_update_user( + user: T::AccountId, + name: Option, + role: Option, + ) -> DispatchResult { + // Ensure user is registered + ensure!(>::contains_key(user.clone()), Error::::UserNotRegistered); + + // Update user data + >::try_mutate::<_, _, DispatchError, _>(user.clone(), |user_data| { + let user_info = user_data.as_mut().ok_or(Error::::UserNotRegistered)?; + + if let Some(mod_name) = name { + // Ensure name is not empty + ensure!(!mod_name.is_empty(), Error::::UserNameRequired); + user_info.name = mod_name; + } + if let Some(mod_role) = role { + // If user has assigned projects, its role cannot be updated + ensure!( + >::get(user.clone()).is_empty(), + Error::::UserHasAssignedProjectsCannotUpdateRole + ); + user_info.role = mod_role; + } + Ok(()) + })?; + + // Event + Self::deposit_event(Event::UserUpdated(user)); + Ok(()) + } + + fn do_delete_user(user: T::AccountId) -> DispatchResult { + // Ensure user is registered & get user data + let user_data = >::get(user.clone()).ok_or(Error::::UserNotRegistered)?; + + match user_data.role { + ProxyRole::Administrator => { + Self::do_sudo_remove_administrator(user.clone())?; + }, + _ => { + // Can not delete a user if the user has assigned projects + ensure!( + >::get(user.clone()).is_empty(), + Error::::UserHasAssignedProjectsCannotDelete + ); + + // Remove user from ProjectsByUser storagemap. No longer required, the admnistator first needs to + // unassign the user from all its projects. + + // Remove user from UsersByProject storagemap. No longer required, the admnistator first needs to + // unassign the user from all its projects. + + // Remove user from UsersInfo storagemap + >::remove(user.clone()); + }, + } + + // Event + Self::deposit_event(Event::UserDeleted(user)); + Ok(()) + } + // E D I T U S E R + // ================================================================================================ + + /// Editing your own user data does not require any kind of RBAC permissions, it only requires + /// that the user is registered. This is because permissions are granted to the + /// user's account when the user is assigned to a project. + /// + /// WARNING: Editing your own user data does not allow you to change your role. Only the administrator can do it usign the `users` extrinsic. + pub fn do_edit_user( + user: T::AccountId, + name: Option, + image: Option, + email: Option, + documents: Option>, + ) -> DispatchResult { + // Ensure user is registered + ensure!(>::contains_key(user.clone()), Error::::UserNotRegistered); + + // Update user data + >::try_mutate::<_, _, DispatchError, _>(user.clone(), |user_data| { + let user_info = user_data.as_mut().ok_or(Error::::UserNotRegistered)?; + + if let Some(mod_name) = name { + // Ensure name is not empty + ensure!(!mod_name.is_empty(), Error::::UserNameRequired); + user_info.name = mod_name; + } + if let Some(mod_image) = image { + // Ensure image is not empty + ensure!(!mod_image.is_empty(), Error::::UserImageRequired); + user_info.image = mod_image; + } + if let Some(mod_email) = email { + // Ensure email is not empty + ensure!(!mod_email.is_empty(), Error::::UserEmailRequired); + user_info.email = mod_email; + } + // Only investors can upload documents + if let Some(mod_documents) = documents { + // Ensure user is an investor + ensure!(user_info.role == ProxyRole::Investor, Error::::UserIsNotAnInvestor); + // Ensure documents is not empty + ensure!(!mod_documents.is_empty(), Error::::DocumentsEmpty); + user_info.documents = Some(mod_documents); + } + Ok(()) + })?; + + Self::deposit_event(Event::UserUpdated(user)); + + Ok(()) + } + + // B U D G E T E X P E N D I T U R E S + // ================================================================================================ + pub fn do_execute_expenditures( + admin: T::AccountId, + project_id: ProjectId, + expenditures: Expenditures, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin.clone(), &project_id, ProxyPermission::Expenditures)?; + + // Ensure project exists + ensure!(>::contains_key(project_id), Error::::ProjectNotFound); + + // Ensure expenditures are not empty + ensure!(!expenditures.is_empty(), Error::::EmptyExpenditures); + + for expenditure in expenditures.iter().cloned() { + match expenditure.5 { + CUDAction::Create => { + Self::do_create_expenditure( + project_id, + expenditure.0.ok_or(Error::::ExpenditureNameRequired)?, + expenditure.1.ok_or(Error::::ExpenditureTypeRequired)?, + expenditure.2.ok_or(Error::::ExpenditureAmountRequired)?, + expenditure.3, + expenditure.4, + )?; + }, + CUDAction::Update => { + Self::do_update_expenditure( + project_id, + expenditure.6.ok_or(Error::::ExpenditureIdRequired)?, + expenditure.0, + expenditure.2, + expenditure.3, + expenditure.4, + )?; + }, + CUDAction::Delete => { + Self::do_delete_expenditure(expenditure.6.ok_or(Error::::ExpenditureIdRequired)?)?; + }, + } + } + + // Event + Self::deposit_event(Event::ExpendituresExecuted(admin, project_id)); + Ok(()) + } + + /// Create a new budget expenditure + /// + /// # Arguments + /// + /// * `admin` - The admin user that creates the budget expenditure + /// * `project_id` - The project id where the budget expenditure will be created + /// + /// Then we add the budget expenditure data + /// * `name` - The name of the budget expenditure + /// * `type` - The type of the budget expenditure + /// * `budget amount` - The amount of the budget expenditure + /// * `naics code` - The naics code of the budget expenditure + /// * `jobs_multiplier` - The jobs multiplier of the budget expenditure + fn do_create_expenditure( + project_id: [u8; 32], + name: FieldName, + expenditure_type: ExpenditureType, + expenditure_amount: ExpenditureAmount, + naics_code: Option, + jobs_multiplier: Option, + ) -> DispatchResult { + // Ensure project exists & is not completed + Self::is_project_completed(project_id)?; + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Ensure expenditure name is not empty + ensure!(!name.is_empty(), Error::::EmptyExpenditureName); + + // Create expenditure id + let expenditure_id: ExpenditureId = + (project_id, name.clone(), expenditure_type, timestamp).using_encoded(blake2_256); + + // Create expenditure data + let expenditure_data = ExpenditureData { + project_id, + name, + expenditure_type, + expenditure_amount, + naics_code, + jobs_multiplier, + }; + + // Insert expenditure data into ExpendituresInfo + // Ensure expenditure_id is unique + ensure!( + !>::contains_key(expenditure_id), + Error::::ExpenditureAlreadyExists + ); + >::insert(expenditure_id, expenditure_data); + + // Insert expenditure_id into ExpendituresByProject + >::try_mutate::<_, _, DispatchError, _>(project_id, |expenditures| { + expenditures + .try_push(expenditure_id) + .map_err(|_| Error::::MaxExpendituresPerProjectReached)?; + Ok(()) + })?; + + Self::deposit_event(Event::ExpenditureCreated(project_id, expenditure_id)); + Ok(()) + } + + fn do_update_expenditure( + project_id: ProjectId, + expenditure_id: ExpenditureId, + name: Option, + expenditure_amount: Option, + naics_code: Option, + jobs_multiplier: Option, + ) -> DispatchResult { + // Ensure project exists & is not completed + Self::is_project_completed(project_id)?; + + // Ensure expenditure_id exists + ensure!(>::contains_key(expenditure_id), Error::::ExpenditureNotFound); + + // Mutate expenditure data + >::try_mutate::<_, _, DispatchError, _>( + expenditure_id, + |expenditure_data| { + let expenditure = expenditure_data.as_mut().ok_or(Error::::ExpenditureNotFound)?; + + // Ensure expenditure belongs to the project + ensure!( + expenditure.project_id == project_id, + Error::::ExpenditureDoesNotBelongToProject + ); + + if let Some(mod_name) = name { + expenditure.name = mod_name; + } + if let Some(mod_expenditure_amount) = expenditure_amount { + expenditure.expenditure_amount = mod_expenditure_amount; + } + if let Some(mod_naics_code) = naics_code { + expenditure.naics_code = Some(mod_naics_code); + } + if let Some(mod_jobs_multiplier) = jobs_multiplier { + expenditure.jobs_multiplier = Some(mod_jobs_multiplier); + } + + Ok(()) + }, + )?; + + Self::deposit_event(Event::ExpenditureUpdated(project_id, expenditure_id)); + Ok(()) + } + + fn do_delete_expenditure(expenditure_id: ExpenditureId) -> DispatchResult { + // Ensure expenditure_id exists & get expenditure data + let expenditure_data = + ExpendituresInfo::::get(&expenditure_id).ok_or(Error::::ExpenditureNotFound)?; + + // Ensure expenditure_id is contained in ExpendituresByProject + ensure!( + >::get(expenditure_data.project_id).contains(&expenditure_id), + Error::::ExpenditureNotFoundForSelectedProjectId + ); + + Self::do_delete_expenditure_transactions(expenditure_id)?; + + // Delete expenditure data from ExpendituresInfo + >::remove(expenditure_id); + + // Delete expenditure_id from ExpendituresByProject + >::try_mutate_exists::<_, _, DispatchError, _>( + expenditure_data.project_id, + |expenditures_option| { + let expenditures = + expenditures_option.as_mut().ok_or(Error::::ProjectHasNoExpenditures)?; + expenditures.retain(|expenditure| expenditure != &expenditure_id); + if expenditures.is_empty() { + expenditures_option.clone_from(&None) + } + Ok(()) + }, + )?; + + Self::deposit_event(Event::ExpenditureDeleted(expenditure_data.project_id, expenditure_id)); + Ok(()) + } + + // D R A W D O W N S + // ================================================================================================ + fn do_create_drawdown( + project_id: ProjectId, + drawdown_type: DrawdownType, + drawdown_number: DrawdownNumber, + ) -> DispatchResult { + // Ensure project exists + ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Create drawdown id + let drawdown_id = + (project_id, drawdown_type, drawdown_number, timestamp).using_encoded(blake2_256); + + // Create drawdown data + let drawdown_data = DrawdownData:: { + project_id, + drawdown_number, + drawdown_type, + total_amount: 0, + status: DrawdownStatus::default(), + bulkupload_documents: None, + bank_documents: None, + description: None, + feedback: None, + status_changes: DrawdownStatusChanges::::default(), + recovery_record: RecoveryRecord::::default(), + created_date: timestamp, + closed_date: 0, + }; + + // Insert drawdown data + // Ensure drawdown id is unique + ensure!(!DrawdownsInfo::::contains_key(drawdown_id), Error::::DrawdownAlreadyExists); + >::insert(drawdown_id, drawdown_data); + + // Insert drawdown id into DrawdownsByProject + >::try_mutate::<_, _, DispatchError, _>(project_id, |drawdowns| { + drawdowns + .try_push(drawdown_id) + .map_err(|_| Error::::MaxDrawdownsPerProjectReached)?; + Ok(()) + })?; + + // Update project drawdown status + Self::do_update_drawdown_status_in_project_info( + project_id, + drawdown_id, + DrawdownStatus::default(), + )?; + + // Event + Self::deposit_event(Event::DrawdownCreated(project_id, drawdown_id)); + Ok(()) + } + + fn do_initialize_drawdowns(admin: T::AccountId, project_id: ProjectId) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin.clone(), &project_id, ProxyPermission::Expenditures)?; + + // Ensure project exists + ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); + + // Create a EB5 drawdown + Self::do_create_drawdown(project_id, DrawdownType::EB5, 1)?; + + // Create a Construction Loan drawdown + Self::do_create_drawdown(project_id, DrawdownType::ConstructionLoan, 1)?; + + // Create a Developer Equity drawdown + Self::do_create_drawdown(project_id, DrawdownType::DeveloperEquity, 1)?; + + // Event + Self::deposit_event(Event::DrawdownsInitialized(admin, project_id)); + Ok(()) + } + + pub fn do_submit_drawdown( + user: T::AccountId, + project_id: ProjectId, + drawdown_id: DrawdownId, + ) -> DispatchResult { + // Ensure user permissions + Self::is_authorized(user.clone(), &project_id, ProxyPermission::SubmitDrawdown)?; + + // Ensure project exists & is not completed + Self::is_project_completed(project_id)?; + + // Check if drawdown exists & is editable + Self::is_drawdown_editable(user, drawdown_id)?; + + // Ensure drawdown has transactions + ensure!( + !>::get(project_id, drawdown_id).is_empty(), + Error::::DrawdownHasNoTransactions + ); + + // Get drawdown transactions + let drawdown_transactions = TransactionsByDrawdown::::try_get(project_id, drawdown_id) + .map_err(|_| Error::::DrawdownHasNoTransactions)?; + + // Update each transaction status to submitted + for transaction_id in drawdown_transactions.iter().cloned() { + // Ensure transaction exists + ensure!(TransactionsInfo::::contains_key(transaction_id), Error::::TransactionNotFound); + + // Update transaction status to submitted + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |transaction_data| { + let transaction_data = + transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; + transaction_data.status = TransactionStatus::Submitted; + transaction_data.feedback = None; + Ok(()) + }, + )?; + } + + // Update drawdown status + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + drawdown_data.status = DrawdownStatus::Submitted; + drawdown_data.feedback = None; + Ok(()) + })?; + + // Update drawdown status in project info + Self::do_update_drawdown_status_in_project_info( + project_id, + drawdown_id, + DrawdownStatus::Submitted, + )?; + + // Event + Self::deposit_event(Event::DrawdownSubmitted(project_id, drawdown_id)); + Ok(()) + } + + pub fn do_approve_drawdown( + admin: T::AccountId, + project_id: ProjectId, + drawdown_id: DrawdownId, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin, &project_id, ProxyPermission::ApproveDrawdown)?; + + // Ensure project exists + ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); + + // Get drawdown data & ensure drawdown exists + let drawdown_data = DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; + + // Ensure drawdown has transactions + ensure!( + !>::get(project_id, drawdown_id).is_empty(), + Error::::DrawdownHasNoTransactions + ); + + // Ensure drawdown is in submitted status + ensure!(drawdown_data.status == DrawdownStatus::Submitted, Error::::DrawdownNotSubmitted); + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Get drawdown transactions + let drawdown_transactions = TransactionsByDrawdown::::try_get(project_id, drawdown_id) + .map_err(|_| Error::::DrawdownHasNoTransactions)?; + + // Update each transaction status to approved + for transaction_id in drawdown_transactions.iter().cloned() { + // Ensure transaction exits + ensure!(TransactionsInfo::::contains_key(transaction_id), Error::::TransactionNotFound); + + // Update transaction status to approved + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |transaction_data| { + let transaction_data = + transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; + transaction_data.status = TransactionStatus::Approved; + transaction_data.closed_date = timestamp; + Ok(()) + }, + )?; + } + + // Update drawdown status to approved + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + drawdown_data.status = DrawdownStatus::Approved; + drawdown_data.closed_date = timestamp; + Ok(()) + })?; + + // Update drawdown status in project info + Self::do_update_drawdown_status_in_project_info( + project_id, + drawdown_id, + DrawdownStatus::Approved, + )?; + + // Generate the next drawdown + // TOREVIEW: After a project is completed, there is no need to generate the next drawdown + // Add a validation to check project status before generating the next drawdown + Self::do_create_drawdown( + project_id, + drawdown_data.drawdown_type, + drawdown_data.drawdown_number + 1, + )?; + + // Event + Self::deposit_event(Event::DrawdownApproved(project_id, drawdown_id)); + Ok(()) + } + + pub fn do_reject_drawdown( + admin: T::AccountId, + project_id: ProjectId, + drawdown_id: DrawdownId, + transactions_feedback: Option>, + drawdown_feedback: Option, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin, &project_id, ProxyPermission::RejectDrawdown)?; + + // Ensure project exists + ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); + + // Get drawdown data + let drawdown_data = DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; + + // Ensure drawdown is in submitted status + ensure!(drawdown_data.status == DrawdownStatus::Submitted, Error::::DrawdownNotSubmitted); + + // Match drawdown type in order to update transactions status + match drawdown_data.drawdown_type { + DrawdownType::EB5 => { + // Ensure drawdown has transactions + ensure!( + !>::get(project_id, drawdown_id).is_empty(), + Error::::DrawdownHasNoTransactions + ); + + // Get drawdown transactions + let drawdown_transactions = TransactionsByDrawdown::::try_get(project_id, drawdown_id) + .map_err(|_| Error::::DrawdownHasNoTransactions)?; + + // Update each transaction status to rejected + for transaction_id in drawdown_transactions.iter().cloned() { + // Ensure transaction exits + ensure!( + TransactionsInfo::::contains_key(transaction_id), + Error::::TransactionNotFound + ); + + // Update transaction status to rejected + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |transaction_data| { + let transaction_data = + transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; + transaction_data.status = TransactionStatus::Rejected; + Ok(()) + }, + )?; + } + + // Ensure transactions feedback is provided + let mod_transactions_feedback = + transactions_feedback.ok_or(Error::::EB5MissingFeedback)?; + + // Ensure feedback is not empty + ensure!(!mod_transactions_feedback.is_empty(), Error::::EmptyEb5Feedback); + + for (transaction_id, feedback) in mod_transactions_feedback.iter().cloned() { + // Update transaction feedback + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |transaction_data| { + let transaction_data = + transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; + transaction_data.feedback = Some(feedback); + Ok(()) + }, + )?; + } + }, + _ => { + // Ensure drawdown feedback is provided + let mod_drawdown_feedback = + drawdown_feedback.ok_or(Error::::NoFeedbackProvidedForBulkUpload)?; + + // Esnure feedback is not empty + ensure!(!mod_drawdown_feedback.is_empty(), Error::::EmptyBulkUploadFeedback); + + // Update drawdown feedback + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + drawdown_data.feedback = Some(mod_drawdown_feedback.clone()); + Ok(()) + })?; + }, + } + + // Update drawdown status to rejected + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + drawdown_data.status = DrawdownStatus::Rejected; + Ok(()) + })?; + + // Update drawdown status in project info + Self::do_update_drawdown_status_in_project_info( + project_id, + drawdown_id, + DrawdownStatus::Rejected, + )?; + + // Event + Self::deposit_event(Event::DrawdownRejected(project_id, drawdown_id)); + Ok(()) + } + + pub fn do_reset_drawdown( + user: T::AccountId, + project_id: ProjectId, + drawdown_id: DrawdownId, + ) -> DispatchResult { + // Ensure builder permissions + Self::is_authorized(user.clone(), &project_id, ProxyPermission::CancelDrawdownSubmission)?; + + // Ensure project exists + ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); + + // Get drawdown data & ensure drawdown exists + let drawdown_data = DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; + + // Ensure drawdown is in submitted status + ensure!(drawdown_data.status == DrawdownStatus::Submitted, Error::::DrawdownNotSubmitted); + + if drawdown_data.drawdown_type == DrawdownType::EB5 { + // Get drawdown transactions + let drawdown_transactions = TransactionsByDrawdown::::try_get(project_id, drawdown_id) + .map_err(|_| Error::::DrawdownNotFound)?; + + // Delete drawdown transactions from TransactionsInfo + for transaction_id in drawdown_transactions.iter().cloned() { + // Delete transaction + >::remove(transaction_id); + } + } + + // Delete drawdown transactions from TransactionsByDrawdown + >::remove(project_id, drawdown_id); + + // Update drawdown status to default + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + drawdown_data.total_amount = 0; + drawdown_data.status = DrawdownStatus::default(); + drawdown_data.bulkupload_documents = None; + drawdown_data.bank_documents = None; + drawdown_data.description = None; + drawdown_data.feedback = None; + drawdown_data.status_changes = DrawdownStatusChanges::::default(); + Ok(()) + })?; + + // Update drawdown status in project info + Self::do_update_drawdown_status_in_project_info( + project_id, + drawdown_id, + DrawdownStatus::default(), + )?; + + // Event + Self::deposit_event(Event::DrawdownSubmissionCancelled(project_id, drawdown_id)); + Ok(()) + } + + // T R A N S A C T I O N S + // ================================================================================================ + pub fn do_execute_transactions( + user: T::AccountId, + project_id: ProjectId, + drawdown_id: DrawdownId, + transactions: Transactions, + ) -> DispatchResult { + // Ensure admin or builder permissions + Self::is_authorized(user.clone(), &project_id, ProxyPermission::ExecuteTransactions)?; + + // Ensure project exists & is not completed so helper private functions doesn't need to check it again + Self::is_project_completed(project_id)?; + + // Ensure drawdown exists so helper private functions doesn't need to check it again + ensure!(DrawdownsInfo::::contains_key(drawdown_id), Error::::DrawdownNotFound); + + // Ensure transactions are not empty + ensure!(!transactions.is_empty(), Error::::EmptyTransactions); + + // Ensure if the selected drawdown is editable + Self::is_drawdown_editable(user.clone(), drawdown_id)?; + + for transaction in transactions.iter().cloned() { + match transaction.3 { + CUDAction::Create => { + Self::do_create_transaction( + project_id, + drawdown_id, + transaction.0.ok_or(Error::::ExpenditureIdRequired)?, + transaction.1.ok_or(Error::::AmountRequired)?, + transaction.2, + )?; + }, + CUDAction::Update => { + // Ensure transaction is editable + Self::is_transaction_editable( + user.clone(), + transaction.4.ok_or(Error::::TransactionIdRequired)?, + )?; + Self::do_update_transaction( + transaction.1, + transaction.2, + transaction.4.ok_or(Error::::TransactionIdRequired)?, + )?; + }, + CUDAction::Delete => { + // Ensure transaction is editable + Self::is_transaction_editable( + user.clone(), + transaction.4.ok_or(Error::::TransactionIdRequired)?, + )?; + Self::do_delete_transaction(transaction.4.ok_or(Error::::TransactionIdRequired)?)?; + }, + } + } + + // Update total amount for the given drawdown + Self::do_calculate_drawdown_total_amount(project_id, drawdown_id)?; + + // Event + Self::deposit_event(Event::TransactionsExecuted(project_id, drawdown_id)); + Ok(()) + } + + fn do_create_transaction( + project_id: ProjectId, + drawdown_id: DrawdownId, + expenditure_id: ExpenditureId, + amount: Amount, + documents: Option>, + ) -> DispatchResult { + // TOREVIEW: If documents are mandatory, we need to check if they are provided + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Create transaction id + let transaction_id = + (drawdown_id, amount, expenditure_id, timestamp, project_id).using_encoded(blake2_256); + + // Ensure expenditure id does not exist + ensure!(ExpendituresInfo::::contains_key(expenditure_id), Error::::ExpenditureNotFound); + + // Create transaction data + let transaction_data = TransactionData:: { + project_id, + drawdown_id, + expenditure_id, + created_date: timestamp, + updated_date: timestamp, + closed_date: 0, + feedback: None, + amount, + status: TransactionStatus::default(), + documents, + }; + + // Insert transaction data + // Ensure transaction id is unique + ensure!( + !TransactionsInfo::::contains_key(transaction_id), + Error::::TransactionAlreadyExists + ); + >::insert(transaction_id, transaction_data); + + // Insert transaction id into TransactionsByDrawdown + >::try_mutate::<_, _, _, DispatchError, _>( + project_id, + drawdown_id, + |transactions| { + transactions + .try_push(transaction_id) + .map_err(|_| Error::::MaxTransactionsPerDrawdownReached)?; + Ok(()) + }, + )?; + + // Event + Self::deposit_event(Event::TransactionCreated(project_id, drawdown_id, transaction_id)); + Ok(()) + } + + fn do_update_transaction( + amount: Option, + documents: Option>, + transaction_id: TransactionId, + ) -> DispatchResult { + // Get transaction data & ensure it exists + let transaction_data = + Self::transactions_info(transaction_id).ok_or(Error::::TransactionNotFound)?; + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Try mutate transaction data + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |transaction_data| { + let mod_transaction_data = + transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; + + // Ensure expenditure exists + ensure!( + ExpendituresInfo::::contains_key(mod_transaction_data.expenditure_id), + Error::::ExpenditureNotFound + ); + + // Update amount + if let Some(mod_amount) = amount { + mod_transaction_data.amount = mod_amount; + } + + // Update documents + if let Some(mod_documents) = documents { + mod_transaction_data.documents = Some(mod_documents); + } + + // Update updated date + mod_transaction_data.updated_date = timestamp; + Ok(()) + }, + )?; + + // Event + Self::deposit_event(Event::TransactionEdited( + transaction_data.project_id, + transaction_data.drawdown_id, + transaction_id, + )); + Ok(()) + } + + fn do_delete_transaction(transaction_id: TransactionId) -> DispatchResult { + // Ensure transaction exists and get transaction data + let transaction_data = + TransactionsInfo::::get(transaction_id).ok_or(Error::::TransactionNotFound)?; + + ensure!( + >::get(transaction_data.project_id, transaction_data.drawdown_id) + .contains(&transaction_id), + Error::::TransactionNotFoundForSelectedDrawdownId + ); + + >::try_mutate_exists::<_, _, _, DispatchError, _>( + transaction_data.project_id, + transaction_data.drawdown_id, + |transactions_option| { + let transactions = + transactions_option.as_mut().ok_or(Error::::DrawdownHasNoTransactions)?; + transactions.retain(|transaction| transaction != &transaction_id); + if transactions.is_empty() { + transactions_option.clone_from(&None); + } + Ok(()) + }, + )?; + + // Remove transaction from TransactionsInfo + >::remove(transaction_id); + + // Event + Self::deposit_event(Event::TransactionDeleted( + transaction_data.project_id, + transaction_data.drawdown_id, + transaction_id, + )); + Ok(()) + } + + // B U L K U P L O A D T R A N S A C T I O N S + // ================================================================================================ + pub fn do_up_bulk_upload( + user: T::AccountId, + project_id: ProjectId, + drawdown_id: DrawdownId, + description: FieldDescription, + total_amount: TotalAmount, + documents: Documents, + ) -> DispatchResult { + // Ensure builder permissions + Self::is_authorized(user.clone(), &project_id, ProxyPermission::UpBulkupload)?; + + // Ensure project is not completed + Self::is_project_completed(project_id)?; + + // Ensure drawdown is not completed + Self::is_drawdown_editable(user, drawdown_id)?; + + // Ensure only Construction loan & developer equity drawdowns are able to call bulk upload extrinsic + let drawdown_data = DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; + + ensure!( + drawdown_data.drawdown_type == DrawdownType::ConstructionLoan + || drawdown_data.drawdown_type == DrawdownType::DeveloperEquity, + Error::::DrawdownTypeNotSupportedForBulkUpload + ); + + // Ensure documents is not empty + ensure!(!documents.is_empty(), Error::::BulkUploadDocumentsRequired); + + // Ensure description is not empty + ensure!(!description.is_empty(), Error::::BulkUploadDescriptionRequired); + + // Mutate drawdown data + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let mod_drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + mod_drawdown_data.total_amount = total_amount; + mod_drawdown_data.description = Some(description); + mod_drawdown_data.bulkupload_documents = Some(documents); + mod_drawdown_data.status = DrawdownStatus::Submitted; + mod_drawdown_data.feedback = None; + Ok(()) + })?; + + // Update drawdown status in project info + Self::do_update_drawdown_status_in_project_info( + project_id, + drawdown_id, + DrawdownStatus::Submitted, + )?; + + // Event + Self::deposit_event(Event::BulkUploadSubmitted(project_id, drawdown_id)); + Ok(()) + } + + // I N F L A T I O N A D J U S T M E N T + // ================================================================================================ + pub fn do_execute_inflation_adjustment( + admin: T::AccountId, + projects: ProjectsInflation, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin.clone(), &Self::get_global_scope(), ProxyPermission::InflationRate)?; + + // Ensure projects array is not empty + ensure!(!projects.is_empty(), Error::::ProjectsInflationRateEmpty); + + // Match each CUD action + for project in projects.iter().cloned() { + // Ensure project exists + ensure!(ProjectsInfo::::contains_key(project.0), Error::::ProjectNotFound); + match project.2 { + CUDAction::Create => { + // Ensure inflation rate is provided + let inflation_rate = project.1.ok_or(Error::::InflationRateRequired)?; + + // Get project data + let project_data = + ProjectsInfo::::get(project.0).ok_or(Error::::ProjectNotFound)?; + + // Ensure project has no inflation rate + ensure!(project_data.inflation_rate.is_none(), Error::::InflationRateAlreadySet); + + // Set inflation rate + >::try_mutate::<_, _, DispatchError, _>(project.0, |project_info| { + let mod_project_data = project_info.as_mut().ok_or(Error::::ProjectNotFound)?; + mod_project_data.inflation_rate = Some(inflation_rate); + Ok(()) + })?; + }, + CUDAction::Update => { + // Ensure inflation rate is provided + let inflation_rate = project.1.ok_or(Error::::InflationRateRequired)?; + + // Get project data + let project_data = + ProjectsInfo::::get(project.0).ok_or(Error::::ProjectNotFound)?; + + // Ensure project has inflation rate + ensure!(project_data.inflation_rate.is_some(), Error::::InflationRateNotSet); + + // Set inflation rate + >::try_mutate::<_, _, DispatchError, _>(project.0, |project_info| { + let mod_project_data = project_info.as_mut().ok_or(Error::::ProjectNotFound)?; + mod_project_data.inflation_rate = Some(inflation_rate); + Ok(()) + })?; + }, + CUDAction::Delete => { + // Get project data + let project_data = + ProjectsInfo::::get(project.0).ok_or(Error::::ProjectNotFound)?; + + // Ensure project has inflation rate + ensure!(project_data.inflation_rate.is_some(), Error::::InflationRateNotSet); + + // Delete inflation rate + >::try_mutate::<_, _, DispatchError, _>(project.0, |project_info| { + let mod_project_data = project_info.as_mut().ok_or(Error::::ProjectNotFound)?; + mod_project_data.inflation_rate = None; + Ok(()) + })?; + }, + } + } + + // Event + Self::deposit_event(Event::InflationRateAdjusted(admin)); + Ok(()) + } + + // J O B E L I G I B L E S + // ================================================================================================ + pub fn do_execute_job_eligibles( + admin: T::AccountId, + project_id: ProjectId, + job_eligibles: JobEligibles, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin.clone(), &project_id, ProxyPermission::JobEligible)?; + + // Ensure project exists + ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); + + // Ensure job eligibles is not empty + ensure!(!job_eligibles.is_empty(), Error::::JobEligiblesEmpty); + + for job_eligible in job_eligibles.iter().cloned() { + match job_eligible.4 { + CUDAction::Create => { + Self::do_create_job_eligible( + project_id, + job_eligible.0.ok_or(Error::::JobEligibleNameRequired)?, + job_eligible.1.ok_or(Error::::JobEligibleAmountRequired)?, + job_eligible.2, + job_eligible.3, + )?; + }, + CUDAction::Update => { + Self::do_update_job_eligible( + project_id, + job_eligible.5.ok_or(Error::::JobEligibleIdRequired)?, + job_eligible.0, + job_eligible.1, + job_eligible.2, + job_eligible.3, + )?; + }, + CUDAction::Delete => { + Self::do_delete_job_eligible(job_eligible.5.ok_or(Error::::JobEligibleIdRequired)?)?; + }, + } + } + + // Event + Self::deposit_event(Event::JobEligiblesExecuted(admin, project_id)); + Ok(()) + } + + fn do_create_job_eligible( + project_id: [u8; 32], + name: FieldName, + job_eligible_amount: JobEligibleAmount, + naics_code: Option, + jobs_multiplier: Option, + ) -> DispatchResult { + // Ensure project exists & is not completed + Self::is_project_completed(project_id)?; + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Ensure job eligible name is not empty + ensure!(!name.is_empty(), Error::::JobEligiblesNameRequired); + + // Create job eligible id + let job_eligible_id: JobEligibleId = + (project_id, name.clone(), timestamp).using_encoded(blake2_256); + + // Create job eligible data + let job_eligible_data = + JobEligibleData { project_id, name, job_eligible_amount, naics_code, jobs_multiplier }; + + // Insert job eligible data into JobEligiblesInfo + // Ensure job eligible id does not exist + ensure!( + !JobEligiblesInfo::::contains_key(job_eligible_id), + Error::::JobEligibleIdAlreadyExists + ); + >::insert(job_eligible_id, job_eligible_data); + + // Insert job eligible id into JobEligiblesByProject + >::try_mutate::<_, _, DispatchError, _>( + project_id, + |job_eligibles| { + job_eligibles + .try_push(job_eligible_id) + .map_err(|_| Error::::MaxJobEligiblesPerProjectReached)?; + Ok(()) + }, + )?; + + // Event + Self::deposit_event(Event::JobEligibleCreated(project_id, job_eligible_id)); + Ok(()) + } + + fn do_update_job_eligible( + project_id: ProjectId, + job_eligible_id: JobEligibleId, + name: Option, + job_eligible_amount: Option, + naics_code: Option, + jobs_multiplier: Option, + ) -> DispatchResult { + // Ensure project exists & is not completed + Self::is_project_completed(project_id)?; + + // Ensure job eligible exists + ensure!(JobEligiblesInfo::::contains_key(job_eligible_id), Error::::JobEligibleNotFound); + + // Mutate job eligible data + >::try_mutate::<_, _, DispatchError, _>( + job_eligible_id, + |job_eligible_data| { + let job_eligible = job_eligible_data.as_mut().ok_or(Error::::JobEligibleNotFound)?; + + // Ensure job eligible belongs to the project + ensure!( + job_eligible.project_id == project_id, + Error::::JobEligibleDoesNotBelongToProject + ); + + if let Some(mod_name) = name { + job_eligible.name = mod_name; + } + if let Some(mod_job_eligible_amount) = job_eligible_amount { + job_eligible.job_eligible_amount = mod_job_eligible_amount; + } + if let Some(mod_naics_code) = naics_code { + job_eligible.naics_code = Some(mod_naics_code); + } + if let Some(mod_jobs_multiplier) = jobs_multiplier { + job_eligible.jobs_multiplier = Some(mod_jobs_multiplier); + } + Ok(()) + }, + )?; + + // Event + Self::deposit_event(Event::JobEligibleUpdated(project_id, job_eligible_id)); + Ok(()) + } + + fn do_delete_job_eligible(job_eligible_id: JobEligibleId) -> DispatchResult { + // Ensure job eligible exists & get job eligible data + let job_eligible_data = + JobEligiblesInfo::::get(job_eligible_id).ok_or(Error::::JobEligibleNotFound)?; + + // Ensure job_eligible_id is contained in JobEligiblesByProject + ensure!( + JobEligiblesByProject::::get(job_eligible_data.project_id).contains(&job_eligible_id), + Error::::JobEligibleNotFoundForSelectedProjectId + ); + + Self::do_delete_job_eligible_transactions(job_eligible_id)?; + + // Delete job eligible data from JobEligiblesInfo + >::remove(job_eligible_id); + + // Delete job eligible id from JobEligiblesByProject + >::try_mutate_exists::<_, _, DispatchError, _>( + job_eligible_data.project_id, + |job_eligibles_option| { + let job_eligibles = + job_eligibles_option.as_mut().ok_or(Error::::ProjectHasNoJobEligibles)?; + job_eligibles.retain(|job_eligible| job_eligible != &job_eligible_id); + if job_eligibles.is_empty() { + job_eligibles_option.clone_from(&None); + } + Ok(()) + }, + )?; + + Self::deposit_event(Event::JobEligibleDeleted(job_eligible_data.project_id, job_eligible_id)); + + Ok(()) + } + + // R E V E N U E S + // ================================================================================================ + pub fn do_execute_revenue_transactions( + user: T::AccountId, + project_id: ProjectId, + revenue_id: RevenueId, + revenue_transactions: RevenueTransactions, + ) -> DispatchResult { + // Ensure builder permission + Self::is_authorized(user.clone(), &project_id, ProxyPermission::RevenueTransaction)?; + + // Ensure project exists & is not completed so helper private functions doesn't need to check it again + Self::is_project_completed(project_id)?; + + // Ensure revenue exists so helper private functions doesn't need to check it again + ensure!(RevenuesInfo::::contains_key(revenue_id), Error::::RevenueNotFound); + + // Ensure revenue transactions are not empty + ensure!(!revenue_transactions.is_empty(), Error::::RevenueTransactionsEmpty); + + // Ensure if the selected revenue is editable + Self::is_revenue_editable(user.clone(), revenue_id)?; + + for transaction in revenue_transactions.iter().cloned() { + match transaction.3 { + CUDAction::Create => { + Self::do_create_revenue_transaction( + project_id, + revenue_id, + transaction.0.ok_or(Error::::JobEligibleIdRequired)?, + transaction.1.ok_or(Error::::RevenueAmountRequired)?, + transaction.2, + )?; + }, + CUDAction::Update => { + // Ensure transaction is editable + Self::is_revenue_transaction_editable( + user.clone(), + transaction.4.ok_or(Error::::RevenueTransactionIdRequired)?, + )?; + // Update transaction + Self::do_update_revenue_transaction( + transaction.1, + transaction.2, + transaction.4.ok_or(Error::::RevenueTransactionIdRequired)?, + )?; + }, + CUDAction::Delete => { + // Ensure transaction is editable + Self::is_revenue_transaction_editable( + user.clone(), + transaction.4.ok_or(Error::::RevenueTransactionIdRequired)?, + )?; + // Delete transaction + Self::do_delete_revenue_transaction( + transaction.4.ok_or(Error::::RevenueTransactionIdRequired)?, + )?; + }, + } + } + + //Update total amount for the given revenue + Self::do_calculate_revenue_total_amount(project_id, revenue_id)?; + + // Event + Self::deposit_event(Event::RevenueTransactionsExecuted(project_id, revenue_id)); + Ok(()) + } + + fn do_create_revenue_transaction( + project_id: ProjectId, + revenue_id: RevenueId, + job_eligible_id: JobEligibleId, + revenue_amount: RevenueAmount, + documents: Option>, + ) -> DispatchResult { + // TOREVIEW: If documents are mandatory, then we need to check if they are empty + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Create revenue transaction id + let revenue_transaction_id = + (revenue_id, job_eligible_id, project_id, timestamp).using_encoded(blake2_256); + + // Ensure revenue transaction id doesn't exist + ensure!( + !RevenueTransactionsInfo::::contains_key(revenue_transaction_id), + Error::::RevenueTransactionIdAlreadyExists + ); + + // Create revenue transaction data + let revenue_transaction_data = RevenueTransactionData { + project_id, + revenue_id, + job_eligible_id, + created_date: timestamp, + updated_date: timestamp, + closed_date: 0, + feedback: None, + amount: revenue_amount, + status: RevenueTransactionStatus::default(), + documents, + }; + + // Insert revenue transaction data into RevenueTransactionsInfo + // Ensure revenue transaction id doesn't exist + ensure!( + !RevenueTransactionsInfo::::contains_key(revenue_transaction_id), + Error::::RevenueTransactionIdAlreadyExists + ); + >::insert(revenue_transaction_id, revenue_transaction_data); + + // Insert revenue transaction id into TransactionsByRevenue + >::try_mutate::<_, _, _, DispatchError, _>( + project_id, + revenue_id, + |revenue_transactions| { + revenue_transactions + .try_push(revenue_transaction_id) + .map_err(|_| Error::::MaxTransactionsPerRevenueReached)?; + Ok(()) + }, + )?; + + // Event + Self::deposit_event(Event::RevenueTransactionCreated( + project_id, + revenue_id, + revenue_transaction_id, + )); + Ok(()) + } + + fn do_update_revenue_transaction( + amount: Option, + documents: Option>, + revenue_transaction_id: RevenueTransactionId, + ) -> DispatchResult { + // Get revenue transaction data & ensure revenue transaction exists + let revenue_transaction_data = RevenueTransactionsInfo::::get(revenue_transaction_id) + .ok_or(Error::::RevenueTransactionNotFound)?; + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Try mutate revenue transaction data + >::try_mutate::<_, _, DispatchError, _>( + revenue_transaction_id, + |revenue_transaction_data| { + let mod_revenue_transaction_data = revenue_transaction_data + .as_mut() + .ok_or(Error::::RevenueTransactionNotFound)?; + + // Ensure job eligible exists + ensure!( + JobEligiblesInfo::::contains_key(mod_revenue_transaction_data.job_eligible_id), + Error::::JobEligibleNotFound + ); + + // Update amount + if let Some(mod_amount) = amount { + mod_revenue_transaction_data.amount = mod_amount; + } + + // Update documents + if let Some(mod_documents) = documents { + mod_revenue_transaction_data.documents = Some(mod_documents); + } + + // Update updated_date + mod_revenue_transaction_data.updated_date = timestamp; + Ok(()) + }, + )?; + + // Event + Self::deposit_event(Event::RevenueTransactionUpdated( + revenue_transaction_data.project_id, + revenue_transaction_data.revenue_id, + revenue_transaction_id, + )); + Ok(()) + } + + fn do_delete_revenue_transaction(revenue_transaction_id: RevenueTransactionId) -> DispatchResult { + // Ensure revenue transaction exists & get revenue transaction data + let revenue_transaction_data = RevenueTransactionsInfo::::get(revenue_transaction_id) + .ok_or(Error::::RevenueTransactionNotFound)?; + + // Ensure revenue transaction belongs to the given revenue + ensure!( + TransactionsByRevenue::::get( + revenue_transaction_data.project_id, + revenue_transaction_data.revenue_id + ) + .contains(&revenue_transaction_id), + Error::::RevenueTransactionNotFoundForSelectedRevenueId + ); + + // Remove revenue transaction from TransactionsByRevenue + >::try_mutate_exists::<_, _, _, DispatchError, _>( + revenue_transaction_data.project_id, + revenue_transaction_data.revenue_id, + |revenue_transactions_option| { + let revenue_transactions = revenue_transactions_option + .as_mut() + .ok_or(Error::::RevenueHasNoTransactions)?; + revenue_transactions + .retain(|revenue_transaction| revenue_transaction != &revenue_transaction_id); + if revenue_transactions.is_empty() { + revenue_transactions_option.clone_from(&None); + } + Ok(()) + }, + )?; + + // Remove revenue transaction from RevenueTransactionsInfo + >::remove(revenue_transaction_id); + + // Event + Self::deposit_event(Event::RevenueTransactionDeleted( + revenue_transaction_data.project_id, + revenue_transaction_data.revenue_id, + revenue_transaction_id, + )); + Ok(()) + } + + pub fn do_submit_revenue( + user: T::AccountId, + project_id: ProjectId, + revenue_id: RevenueId, + ) -> DispatchResult { + // Ensure builder permissions + Self::is_authorized(user.clone(), &project_id, ProxyPermission::SubmitRevenue)?; + + // Ensure project exists & is not completed + Self::is_project_completed(project_id)?; + + // Check if revenue exists & is editable + Self::is_revenue_editable(user, revenue_id)?; + + // Ensure revenue has transactions + ensure!( + !TransactionsByRevenue::::get(project_id, revenue_id).is_empty(), + Error::::RevenueHasNoTransactions + ); + + // Get revenue transactions + let revenue_transactions = TransactionsByRevenue::::try_get(project_id, revenue_id) + .map_err(|_| Error::::RevenueNotFound)?; + + // Update each revenue transaction status to Submitted + for transaction_id in revenue_transactions.iter().cloned() { + // Ensure revenue transaction exists + ensure!( + RevenueTransactionsInfo::::contains_key(transaction_id), + Error::::RevenueTransactionNotFound + ); + + // Update revenue transaction status + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |revenue_transaction_data| { + let revenue_transaction_data = revenue_transaction_data + .as_mut() + .ok_or(Error::::RevenueTransactionNotFound)?; + revenue_transaction_data.status = RevenueTransactionStatus::Submitted; + revenue_transaction_data.feedback = None; + Ok(()) + }, + )?; + } + + // Update revenue status + >::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| { + let revenue_data = revenue_data.as_mut().ok_or(Error::::RevenueNotFound)?; + revenue_data.status = RevenueStatus::Submitted; + Ok(()) + })?; + + // Update revenue status in project info + Self::do_update_revenue_status_in_project_info( + project_id, + revenue_id, + RevenueStatus::Submitted, + )?; + + // Event + Self::deposit_event(Event::RevenueSubmitted(project_id, revenue_id)); + + Ok(()) + } + + pub fn do_approve_revenue( + admin: T::AccountId, + project_id: ProjectId, + revenue_id: RevenueId, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin, &project_id, ProxyPermission::ApproveRevenue)?; + + // Get revenue data + let revenue_data = Self::revenues_info(revenue_id).ok_or(Error::::RevenueNotFound)?; + + // Ensure revenue is submitted + ensure!(revenue_data.status == RevenueStatus::Submitted, Error::::RevenueNotSubmitted); + + ensure!( + TransactionsByRevenue::::contains_key(project_id, revenue_id), + Error::::RevenueHasNoTransactions + ); + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Get revenue transactions + let revenue_transactions = TransactionsByRevenue::::try_get(project_id, revenue_id) + .map_err(|_| Error::::RevenueNotFound)?; + + // Update each revenue transaction status to Approved + for transaction_id in revenue_transactions.iter().cloned() { + // Ensure revenue transaction is editable + let revenue_transaction_data = RevenueTransactionsInfo::::get(transaction_id) + .ok_or(Error::::RevenueTransactionNotFound)?; + ensure!( + revenue_transaction_data.status == RevenueTransactionStatus::Submitted, + Error::::RevenueTransactionNotSubmitted + ); + + // Update revenue transaction status to Approved & update closed date + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |revenue_transaction_data| { + let revenue_transaction_data = revenue_transaction_data + .as_mut() + .ok_or(Error::::RevenueTransactionNotFound)?; + revenue_transaction_data.status = RevenueTransactionStatus::Approved; + revenue_transaction_data.closed_date = timestamp; + Ok(()) + }, + )?; + } + + // Update revenue status to Approved + >::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| { + let revenue_data = revenue_data.as_mut().ok_or(Error::::RevenueNotFound)?; + revenue_data.status = RevenueStatus::Approved; + revenue_data.closed_date = timestamp; + Ok(()) + })?; + + // Update revenue status in project info + Self::do_update_revenue_status_in_project_info( + project_id, + revenue_id, + RevenueStatus::Approved, + )?; + + // Generate the next revenue + Self::do_create_revenue(project_id, revenue_data.revenue_number + 1)?; + + // Event + Self::deposit_event(Event::RevenueApproved(project_id, revenue_id)); + + Ok(()) + } + + pub fn do_reject_revenue( + admin: T::AccountId, + project_id: ProjectId, + revenue_id: RevenueId, + revenue_transactions_feedback: TransactionsFeedback, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin, &project_id, ProxyPermission::RejectRevenue)?; + + // Get revenue data + let revenue_data = Self::revenues_info(revenue_id).ok_or(Error::::RevenueNotFound)?; + + // Ensure revenue is submitted + ensure!(revenue_data.status == RevenueStatus::Submitted, Error::::RevenueNotSubmitted); + + // Ensure revenue has transactions + ensure!( + !TransactionsByRevenue::::get(project_id, revenue_id).is_empty(), + Error::::RevenueHasNoTransactions + ); + + // Get revenue transactions + let revenue_transactions = TransactionsByRevenue::::try_get(project_id, revenue_id) + .map_err(|_| Error::::RevenueNotFound)?; + + // Update each revenue transaction status to Rejected + for transaction_id in revenue_transactions.iter().cloned() { + // Ensure revenue transaction is editable + let revenue_transaction_data = RevenueTransactionsInfo::::get(transaction_id) + .ok_or(Error::::RevenueTransactionNotFound)?; + ensure!( + revenue_transaction_data.status == RevenueTransactionStatus::Submitted, + Error::::RevenueTransactionNotSubmitted + ); + + // Update revenue transaction status to Rejected + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |revenue_transaction_data| { + let revenue_transaction_data = revenue_transaction_data + .as_mut() + .ok_or(Error::::RevenueTransactionNotFound)?; + revenue_transaction_data.status = RevenueTransactionStatus::Rejected; + Ok(()) + }, + )?; + } + + // Ensure revenue transactions feedback is not empty + ensure!( + !revenue_transactions_feedback.is_empty(), + Error::::RevenueTransactionsFeedbackEmpty + ); + // Update revenue transactions feedback + for (transaction_id, feedback) in revenue_transactions_feedback.iter().cloned() { + // Update revenue transaction feedback + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |revenue_transaction_data| { + let revenue_transaction_data = revenue_transaction_data + .as_mut() + .ok_or(Error::::RevenueTransactionNotFound)?; + revenue_transaction_data.feedback = Some(feedback); + Ok(()) + }, + )?; + } + + // Update revenue status to Rejected + >::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| { + let revenue_data = revenue_data.as_mut().ok_or(Error::::RevenueNotFound)?; + revenue_data.status = RevenueStatus::Rejected; + Ok(()) + })?; + + // Update revenue status in project info + Self::do_update_revenue_status_in_project_info( + project_id, + revenue_id, + RevenueStatus::Rejected, + )?; + + // Event + Self::deposit_event(Event::RevenueRejected(project_id, revenue_id)); + + Ok(()) + } + + // B A N K C O N F I R M I N G D O C U M E N T S + // ------------------------------------------------------------------------ + pub fn do_bank_confirming_documents( + admin: T::AccountId, + project_id: ProjectId, + drawdown_id: DrawdownId, + confirming_documents: Option>, + action: CUDAction, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_authorized(admin, &project_id, ProxyPermission::BankConfirming)?; + + // Ensure project exists + ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); + + // Get drawdown data & ensure drawdown exists + let drawdown_data = DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; + + // Ensure drawdown is EB5 + ensure!( + drawdown_data.drawdown_type == DrawdownType::EB5, + Error::::OnlyEB5DrawdownsCanUploadBankDocuments + ); + + match action { + CUDAction::Create => { + // Ensure bank confirming documents are provided + let mod_confirming_documents = + confirming_documents.ok_or(Error::::BankConfirmingDocumentsNotProvided)?; + + // Ensure confirming documents are not empty + ensure!(!mod_confirming_documents.is_empty(), Error::::BankConfirmingDocumentsEmpty); + + // Create drawdown bank confirming documents + Self::do_create_bank_confirming_documents(project_id, drawdown_id, mod_confirming_documents) + }, + CUDAction::Update => { + // Ensure bank confirming documents are provided + let mod_confirming_documents = + confirming_documents.ok_or(Error::::BankConfirmingDocumentsNotProvided)?; + + // Ensure confirming documents are not empty + ensure!(!mod_confirming_documents.is_empty(), Error::::BankConfirmingDocumentsEmpty); + + // Update drawdown bank confirming documents + Self::do_update_bank_confirming_documents(drawdown_id, mod_confirming_documents) + }, + CUDAction::Delete => { + // Delete drawdown bank confirming documents + Self::do_delete_bank_confirming_documents(project_id, drawdown_id) + }, + } + } + + fn do_create_bank_confirming_documents( + project_id: ProjectId, + drawdown_id: DrawdownId, + confirming_documents: Documents, + ) -> DispatchResult { + // Get drawdown data & ensure drawdown exists + let drawdown_data = DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; + + // Ensure drawdown has no bank confirming documents + ensure!( + drawdown_data.bank_documents.is_none(), + Error::::DrawdownHasAlreadyBankConfirmingDocuments + ); + + // Ensure drawdown status is Approved + ensure!( + drawdown_data.status == DrawdownStatus::Approved, + Error::::DrawdowMustBeInApprovedStatus + ); + + // Mutate drawdown data: Upload bank documents & update drawdown status to Confirmed + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + drawdown_data.bank_documents = Some(confirming_documents); + drawdown_data.status = DrawdownStatus::Confirmed; + Ok(()) + })?; + + // Get drawdown transactions + let drawdown_transactions = TransactionsByDrawdown::::try_get(project_id, drawdown_id) + .map_err(|_| Error::::DrawdownHasNoTransactions)?; + + // Mutate individual drawdown transactions status to Confirmed + for transaction_id in drawdown_transactions.iter().cloned() { + // Ensure transaction exists + ensure!(TransactionsInfo::::contains_key(transaction_id), Error::::TransactionNotFound); + + // Update drawdown transaction status to Confirmed + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |transaction_data| { + let transaction_data = + transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; + transaction_data.status = TransactionStatus::Confirmed; + Ok(()) + }, + )?; + } + + // Update drawdown status changes in drawdown info + Self::do_create_drawdown_status_change_record(drawdown_id, DrawdownStatus::Confirmed)?; + + // Event + Self::deposit_event(Event::BankDocumentsUploaded(project_id, drawdown_id)); + Ok(()) + } + + fn do_update_bank_confirming_documents( + drawdown_id: DrawdownId, + confirming_documents: Documents, + ) -> DispatchResult { + // Get drawdown data & ensure drawdown exists + let drawdown_data = DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; + + // Ensure drawdown status is Confirmed + ensure!( + drawdown_data.status == DrawdownStatus::Confirmed, + Error::::DrawdowMustBeInConfirmedStatus + ); + + // Ensure drawdown has bank confirming documents + ensure!( + drawdown_data.bank_documents.is_some(), + Error::::DrawdownHasNoBankConfirmingDocuments + ); + + // Mutate drawdown data: Update bank documents + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + drawdown_data.bank_documents = Some(confirming_documents); + Ok(()) + })?; + + // Event + Self::deposit_event(Event::BankDocumentsUpdated(drawdown_data.project_id, drawdown_id)); + Ok(()) + } + + fn do_delete_bank_confirming_documents( + project_id: ProjectId, + drawdown_id: DrawdownId, + ) -> DispatchResult { + // Get drawdown data & ensure drawdown exists + let drawdown_data = DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; + + // Ensure drawdown status is Confirmed + ensure!( + drawdown_data.status == DrawdownStatus::Confirmed, + Error::::DrawdowMustBeInConfirmedStatus + ); + + // Ensure drawdown has bank confirming documents + ensure!( + drawdown_data.bank_documents.is_some(), + Error::::DrawdownHasNoBankConfirmingDocuments + ); + + // Rollback drawdown status to Approved & remove bank confirming documents + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + drawdown_data.bank_documents = None; + drawdown_data.status = DrawdownStatus::Approved; + Ok(()) + })?; + + // Get drawdown transactions + let drawdown_transactions = TransactionsByDrawdown::::try_get(project_id, drawdown_id) + .map_err(|_| Error::::DrawdownHasNoTransactions)?; + + // Mutate individual drawdown transactions status to Approved + for transaction_id in drawdown_transactions.iter().cloned() { + // Ensure transaction exists + ensure!(TransactionsInfo::::contains_key(transaction_id), Error::::TransactionNotFound); + + // Update drawdown transaction status to Approved + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |transaction_data| { + let transaction_data = + transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; + transaction_data.status = TransactionStatus::Approved; + Ok(()) + }, + )?; + } + + // Update drawdown status changes in drawdown info + Self::do_create_drawdown_status_change_record(drawdown_id, DrawdownStatus::Approved)?; + + // Event + Self::deposit_event(Event::BankDocumentsDeleted(project_id, drawdown_id)); + Ok(()) + } + + // H E L P E R S + // ================================================================================================ + + /// Get the current timestamp in milliseconds + fn get_timestamp_in_milliseconds() -> Option { + let timestamp: u64 = T::Timestamp::now().into(); + + Some(timestamp) + } + + /// Get the pallet_id + pub fn pallet_id() -> IdOrVec { + IdOrVec::Vec(Self::module_name().as_bytes().to_vec()) + } + + /// Get global scope + pub fn get_global_scope() -> [u8; 32] { + >::try_get() + .map_err(|_| Error::::NoGlobalScopeValueWasFound) + .unwrap() + } + + #[allow(dead_code)] + fn change_project_status( + admin: T::AccountId, + project_id: ProjectId, + status: ProjectStatus, + ) -> DispatchResult { + // Ensure admin permissions + Self::is_superuser(admin, &Self::get_global_scope(), ProxyRole::Administrator.id())?; + + // Ensure project exists + ensure!(ProjectsInfo::::contains_key(project_id), Error::::ProjectNotFound); + + // Check project status is not completed + Self::is_project_completed(project_id)?; + + // Mutate project data + >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { + let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; + project.status = status; + Ok(()) + })?; + + Ok(()) + } + + fn is_project_completion_date_later(project_id: ProjectId) -> DispatchResult { + // Get project data & ensure project exists + let project_data = ProjectsInfo::::get(project_id).ok_or(Error::::ProjectNotFound)?; + + // Ensure completion date is later than start date + ensure!( + project_data.completion_date > project_data.creation_date, + Error::::CompletionDateMustBeLater + ); + Ok(()) + } + + fn add_project_role( + project_id: ProjectId, + user: T::AccountId, + role: ProxyRole, + ) -> DispatchResult { + match role { + ProxyRole::Administrator => return Err(Error::::CannotRegisterAdminRole.into()), + ProxyRole::Builder => { + // Mutate project data + >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { + let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; + match project.builder.as_mut() { + Some(builder) => { + builder + .try_push(user.clone()) + .map_err(|_| Error::::MaxBuildersPerProjectReached)?; + }, + None => { + let devs = project + .builder + .get_or_insert(BoundedVec::::default()); + devs + .try_push(user.clone()) + .map_err(|_| Error::::MaxBuildersPerProjectReached)?; + }, + } + Ok(()) + })?; + }, + ProxyRole::Investor => { + // Mutate project data + >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { + let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; + match project.investor.as_mut() { + Some(investor) => { + investor + .try_push(user.clone()) + .map_err(|_| Error::::MaxInvestorsPerProjectReached)?; + }, + None => { + let investors = project + .investor + .get_or_insert(BoundedVec::::default()); + investors + .try_push(user.clone()) + .map_err(|_| Error::::MaxInvestorsPerProjectReached)?; + }, + } + Ok(()) + })?; + }, + ProxyRole::Issuer => { + // Mutate project data + >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { + let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; + match project.issuer.as_mut() { + Some(issuer) => { + issuer + .try_push(user.clone()) + .map_err(|_| Error::::MaxIssuersPerProjectReached)?; + }, + None => { + let issuers = project + .issuer + .get_or_insert(BoundedVec::::default()); + issuers + .try_push(user.clone()) + .map_err(|_| Error::::MaxIssuersPerProjectReached)?; + }, + } + Ok(()) + })?; + }, + ProxyRole::RegionalCenter => { + // Mutate project data + >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { + let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; + match project.regional_center.as_mut() { + Some(regional_center) => { + regional_center + .try_push(user.clone()) + .map_err(|_| Error::::MaxRegionalCenterPerProjectReached)?; + }, + None => { + let regional_centers = project.regional_center.get_or_insert(BoundedVec::< + T::AccountId, + T::MaxRegionalCenterPerProject, + >::default( + )); + regional_centers + .try_push(user.clone()) + .map_err(|_| Error::::MaxRegionalCenterPerProjectReached)?; + }, + } + Ok(()) + })?; + }, + } + + Ok(()) + } + + pub fn remove_project_role( + project_id: ProjectId, + user: T::AccountId, + role: ProxyRole, + ) -> DispatchResult { + match role { + ProxyRole::Administrator => return Err(Error::::CannotRemoveAdminRole.into()), + ProxyRole::Builder => { + // Mutate project data + >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { + let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; + match project.builder.as_mut() { + Some(builder) => { + builder.retain(|u| *u != user); + }, + None => return Err(Error::::UserNotAssignedToProject.into()), + } + Ok(()) + })?; + }, + ProxyRole::Investor => { + // Mutate project data + >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { + let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; + match project.investor.as_mut() { + Some(investor) => { + investor.retain(|u| *u != user); + }, + None => return Err(Error::::UserNotAssignedToProject.into()), + } + Ok(()) + })?; + }, + ProxyRole::Issuer => { + // Mutate project data + >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { + let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; + match project.issuer.as_mut() { + Some(issuer) => { + issuer.retain(|u| *u != user); + }, + None => return Err(Error::::UserNotAssignedToProject.into()), + } + Ok(()) + })?; + }, + ProxyRole::RegionalCenter => { + // Mutate project data + >::try_mutate::<_, _, DispatchError, _>(project_id, |project| { + let project = project.as_mut().ok_or(Error::::ProjectNotFound)?; + match project.regional_center.as_mut() { + Some(regional_center) => { + regional_center.retain(|u| *u != user); + }, + None => return Err(Error::::UserNotAssignedToProject.into()), + } + Ok(()) + })?; + }, + } + Ok(()) + } + + /// Helper function to check the following: + /// + /// 1. Checks if the user is registered in the system + /// 2. Checks if the user has the required role from UsersInfo storage + /// 3. Checks if the user is trying to assign an admin role + fn check_user_role(user: T::AccountId, role: ProxyRole) -> DispatchResult { + // Ensure user is registered & get user data + let user_data = UsersInfo::::get(user.clone()).ok_or(Error::::UserNotRegistered)?; + + // Check if the user role trying to be assigned matches the actual user role from UsersInfo storage + if user_data.role != role { + return Err(Error::::UserCannotHaveMoreThanOneRole.into()); + } + + // Match user role. Check the max numbers of projects a user can be assigned to + match user_data.role { + ProxyRole::Administrator => { + // Can't assign an administrator role account to a project, admins are scoped globally + return Err(Error::::CannotAddAdminRole.into()); + }, + ProxyRole::Investor => { + // Investors can be assigned to a maximum of 1 project + // Get how many projects the user is assigned to + let projects_count = >::get(user.clone()).len(); + ensure!( + projects_count < T::MaxProjectsPerInvestor::get() as usize, + Error::::MaxProjectsPerInvestorReached + ); + Ok(()) + }, + // Builders, Issuers & Regional Centers don't have a limit on how many projects they can be assigned to + _ => Ok(()), + } + } + + // TOREVIEW: Refactor this function when implementing the Error recovery workflow + fn is_project_completed(project_id: ProjectId) -> DispatchResult { + // Get project data & ensure project exists + let project_data = ProjectsInfo::::get(project_id).ok_or(Error::::ProjectNotFound)?; + + // Ensure project is not completed + ensure!(project_data.status != ProjectStatus::Completed, Error::::ProjectIsAlreadyCompleted); + + Ok(()) + } + + fn is_drawdown_editable(user: T::AccountId, drawdown_id: DrawdownId) -> DispatchResult { + // Get drawdown data & ensure drawdown exists + let drawdown_data = DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; + + // Match drawdown type + match drawdown_data.drawdown_type { + DrawdownType::EB5 => { + // Match drawdown status + // Ensure drawdown is in draft or rejected status + match drawdown_data.status { + DrawdownStatus::Draft => Ok(()), + DrawdownStatus::Rejected => Ok(()), + DrawdownStatus::Submitted => { + Err(Error::::CannotPerformActionOnSubmittedDrawdown.into()) + }, + DrawdownStatus::Approved => { + // Ensure admin permissions + if Self::is_authorized( + user.clone(), + &drawdown_data.project_id, + ProxyPermission::RecoveryDrawdown, + ) + .is_ok() + { + Ok(()) + } else { + Err(Error::::CannotPerformActionOnApprovedDrawdown.into()) + } + }, + DrawdownStatus::Confirmed => { + // Ensure admin permissions + if Self::is_authorized( + user.clone(), + &drawdown_data.project_id, + ProxyPermission::RecoveryDrawdown, + ) + .is_ok() + { + Ok(()) + } else { + Err(Error::::CannotPerformActionOnConfirmedDrawdown.into()) + } + }, + } + }, + _ => { + // Match drawdown status + match drawdown_data.status { + DrawdownStatus::Draft => Ok(()), + DrawdownStatus::Rejected => Ok(()), + DrawdownStatus::Submitted => { + // Ensure admin permissions + if Self::is_authorized( + user.clone(), + &drawdown_data.project_id, + ProxyPermission::BulkUploadTransaction, + ) + .is_ok() + { + Ok(()) + } else { + Err(Error::::CannotPerformActionOnSubmittedDrawdown.into()) + } + }, + DrawdownStatus::Approved => { + // Ensure admin permissions + if Self::is_authorized( + user.clone(), + &drawdown_data.project_id, + ProxyPermission::RecoveryDrawdown, + ) + .is_ok() + { + Ok(()) + } else { + Err(Error::::CannotPerformActionOnApprovedDrawdown.into()) + } + }, + DrawdownStatus::Confirmed => { + // Ensure admin permissions + if Self::is_authorized( + user.clone(), + &drawdown_data.project_id, + ProxyPermission::RecoveryDrawdown, + ) + .is_ok() + { + Ok(()) + } else { + Err(Error::::CannotPerformActionOnConfirmedDrawdown.into()) + } + }, + } + }, + } + } + + fn is_transaction_editable(user: T::AccountId, transaction_id: TransactionId) -> DispatchResult { + // Get transaction data & ensure transaction exists + let transaction_data = + TransactionsInfo::::get(transaction_id).ok_or(Error::::TransactionNotFound)?; + + // Ensure transaction is in draft or rejected status + // Match transaction status + match transaction_data.status { + TransactionStatus::Draft => Ok(()), + TransactionStatus::Rejected => Ok(()), + TransactionStatus::Submitted => { + // Ensure admin permissions + if Self::is_authorized( + user.clone(), + &transaction_data.project_id, + ProxyPermission::BulkUploadTransaction, + ) + .is_ok() + { + Ok(()) + } else { + Err(Error::::CannotPerformActionOnSubmittedTransaction.into()) + } + }, + TransactionStatus::Approved => { + // Ensure admin permissions + if Self::is_authorized( + user.clone(), + &transaction_data.project_id, + ProxyPermission::RecoveryTransaction, + ) + .is_ok() + { + Ok(()) + } else { + Err(Error::::CannotPerformActionOnApprovedTransaction.into()) + } + }, + TransactionStatus::Confirmed => { + // Ensure admin permissions + if Self::is_authorized( + user.clone(), + &transaction_data.project_id, + ProxyPermission::RecoveryTransaction, + ) + .is_ok() + { + Ok(()) + } else { + Err(Error::::CannotPerformActionOnConfirmedTransaction.into()) + } + }, + } + } + + /// # Checks if the caller has the permission to perform an action + /// + /// - This version of is_authorized checks if the caller is an Administrator and if so, it + /// checks the global scope + /// otherwise it checks the project scope + /// - This is useful for functions that are called by both administrators and project users + /// - Scope is always required. In workflows where the caller is an administrator, + /// we can get it from the helper private function `get_global_scope()` + pub fn is_authorized( + authority: T::AccountId, + scope: &[u8; 32], + permission: ProxyPermission, + ) -> DispatchResult { + // Get user data + let user_data = + >::try_get(authority.clone()).map_err(|_| Error::::UserNotRegistered)?; + + match user_data.role { + ProxyRole::Administrator => T::Rbac::is_authorized( + authority, + Self::pallet_id(), + &Self::get_global_scope(), + &permission.id(), + ), + _ => T::Rbac::is_authorized(authority, Self::pallet_id(), scope, &permission.id()), + } + } + + #[allow(dead_code)] + fn is_superuser( + authority: T::AccountId, + scope_global: &[u8; 32], + rol_id: RoleId, + ) -> DispatchResult { + T::Rbac::has_role(authority, Self::pallet_id(), scope_global, vec![rol_id]) + } + + fn sudo_register_admin(admin: T::AccountId, name: FieldName) -> DispatchResult { + // Check if user is already registered + ensure!(!>::contains_key(admin.clone()), Error::::UserAlreadyRegistered); + + // Get current timestamp + let current_timestamp = + Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + let user_data = UserData:: { + name, + role: ProxyRole::Administrator, + image: CID::default(), + date_registered: current_timestamp, + email: FieldName::default(), + documents: None, + }; + + // Insert user data + >::insert(admin.clone(), user_data); + + // Add administrator to rbac pallet + T::Rbac::assign_role_to_user( + admin, + Self::pallet_id(), + &Self::get_global_scope(), + ProxyRole::Administrator.id(), + )?; + + Ok(()) + } + + fn sudo_delete_admin(admin: T::AccountId) -> DispatchResult { + // Check if user is already registered + ensure!(>::contains_key(admin.clone()), Error::::UserNotRegistered); + + // Remove user from UsersInfo storagemap + >::remove(admin.clone()); + + // Remove administrator from rbac pallet + T::Rbac::remove_role_from_user( + admin, + Self::pallet_id(), + &Self::get_global_scope(), + ProxyRole::Administrator.id(), + )?; + + Ok(()) + } + + fn do_calculate_drawdown_total_amount( + project_id: [u8; 32], + drawdown_id: [u8; 32], + ) -> DispatchResult { + // Ensure drawdown exists + ensure!(>::contains_key(drawdown_id), Error::::DrawdownNotFound); + + // Calculate drawdown total amount + let mut drawdown_total_amount: u64 = 0; + + if !TransactionsByDrawdown::::get(project_id, drawdown_id).is_empty() { + // Get transactions by drawdown + let transactions_by_drawdown = TransactionsByDrawdown::::get(project_id, drawdown_id); + + // Iterate through transactions + for transaction_id in transactions_by_drawdown.iter().cloned() { + // Get transaction data + let transaction_data = + TransactionsInfo::::get(transaction_id).ok_or(Error::::TransactionNotFound)?; + + // Add transaction amount to drawdown total amount + drawdown_total_amount += transaction_data.amount; + } + } + + // Update drawdown total amount + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + drawdown_data.total_amount = drawdown_total_amount; + Ok(()) + })?; + + Ok(()) + } + + fn do_update_drawdown_status_in_project_info( + project_id: ProjectId, + drawdown_id: DrawdownId, + drawdown_status: DrawdownStatus, + ) -> DispatchResult { + // Ensure project exists + ensure!(>::contains_key(project_id), Error::::ProjectNotFound); + + // Get drawdown data & ensure drawdown exists + let drawdown_data = DrawdownsInfo::::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; + + // Match drawdown type + match drawdown_data.drawdown_type { + DrawdownType::EB5 => { + // Update EB5 drawdown status in project info + >::try_mutate::<_, _, DispatchError, _>(project_id, |project_data| { + let project_data = project_data.as_mut().ok_or(Error::::ProjectNotFound)?; + project_data.eb5_drawdown_status = Some(drawdown_status); + Ok(()) + })?; + }, + DrawdownType::ConstructionLoan => { + // Update Construction Loan drawdown status in project info + >::try_mutate::<_, _, DispatchError, _>(project_id, |project_data| { + let project_data = project_data.as_mut().ok_or(Error::::ProjectNotFound)?; + project_data.construction_loan_drawdown_status = Some(drawdown_status); + Ok(()) + })?; + }, + DrawdownType::DeveloperEquity => { + // Update Developer Equity drawdown status in project info + >::try_mutate::<_, _, DispatchError, _>(project_id, |project_data| { + let project_data = project_data.as_mut().ok_or(Error::::ProjectNotFound)?; + project_data.developer_equity_drawdown_status = Some(drawdown_status); + Ok(()) + })?; + }, + } + + // Update drawdown status changes in drawdown info + Self::do_create_drawdown_status_change_record(drawdown_id, drawdown_status)?; + Ok(()) + } + + fn is_revenue_editable(user: T::AccountId, revenue_id: RevenueId) -> DispatchResult { + // Get revenue data & ensure revenue exists + let revenue_data = RevenuesInfo::::get(revenue_id).ok_or(Error::::RevenueNotFound)?; + + // Match revenue status + match revenue_data.status { + RevenueStatus::Draft => Ok(()), + RevenueStatus::Rejected => Ok(()), + RevenueStatus::Submitted => Err(Error::::CannotPerformActionOnSubmittedRevenue.into()), + RevenueStatus::Approved => { + // Ensure admin permission + if Self::is_authorized( + user.clone(), + &revenue_data.project_id, + ProxyPermission::RecoveryRevenue, + ) + .is_ok() + { + Ok(()) + } else { + Err(Error::::CannotPerformActionOnApprovedRevenue.into()) + } + }, + } + } + + fn is_revenue_transaction_editable( + user: T::AccountId, + revenue_transaction_id: RevenueTransactionId, + ) -> DispatchResult { + // Get revenue transaction data & ensure revenue transaction exists + let revenue_transaction_data = RevenueTransactionsInfo::::get(revenue_transaction_id) + .ok_or(Error::::RevenueTransactionNotFound)?; + + // Ensure transaction is in draft or rejected status + // Match revenue transaction status + match revenue_transaction_data.status { + RevenueTransactionStatus::Draft => Ok(()), + RevenueTransactionStatus::Rejected => Ok(()), + RevenueTransactionStatus::Submitted => { + Err(Error::::CannotPerformActionOnSubmittedRevenueTransaction.into()) + }, + RevenueTransactionStatus::Approved => { + // Ensure admin permissions + if Self::is_authorized( + user.clone(), + &revenue_transaction_data.project_id, + ProxyPermission::RecoveryRevenueTransaction, + ) + .is_ok() + { + Ok(()) + } else { + Err(Error::::CannotPerformActionOnApprovedRevenueTransaction.into()) + } + }, + } + } + + fn do_calculate_revenue_total_amount( + project_id: ProjectId, + revenue_id: RevenueId, + ) -> DispatchResult { + // Ensure revenue exists + ensure!(>::contains_key(revenue_id), Error::::RevenueNotFound); + + // Calculate revenue total amount + let mut revenue_total_amount: Amount = 0; + + if !TransactionsByRevenue::::get(project_id, revenue_id).is_empty() { + // Get revenue transactions + let transactions_by_revenue = TransactionsByRevenue::::get(project_id, revenue_id); + + // Iterate over revenue transactions + for revenue_transaction_id in transactions_by_revenue { + // Get revenue transaction data + let revenue_transaction_data = RevenueTransactionsInfo::::get(revenue_transaction_id) + .ok_or(Error::::RevenueTransactionNotFound)?; + + // Add revenue transaction amount to revenue total amount + revenue_total_amount += revenue_transaction_data.amount; + } + } + + // Update revenue total amount + >::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| { + let revenue_data = revenue_data.as_mut().ok_or(Error::::RevenueNotFound)?; + revenue_data.total_amount = revenue_total_amount; + Ok(()) + })?; + + Ok(()) + } + + fn do_initialize_revenue(project_id: ProjectId) -> DispatchResult { + // Ensure project exists + ensure!(>::contains_key(project_id), Error::::ProjectNotFound); + + // Create revenue + Self::do_create_revenue(project_id, 1)?; + + Ok(()) + } + + fn do_create_revenue(project_id: ProjectId, revenue_number: RevenueNumber) -> DispatchResult { + // Ensure project exists + ensure!(>::contains_key(project_id), Error::::ProjectNotFound); + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Create revenue id + let revenue_id = (project_id, revenue_number, timestamp).using_encoded(blake2_256); + + // Create revenue data + let revenue_data = RevenueData:: { + project_id, + revenue_number, + total_amount: 0, + status: RevenueStatus::default(), + status_changes: RevenueStatusChanges::::default(), + recovery_record: RecoveryRecord::::default(), + created_date: timestamp, + closed_date: 0, + }; + + // Insert revenue data + // Ensure revenue id is unique + ensure!(!>::contains_key(revenue_id), Error::::RevenueIdAlreadyExists); + >::insert(revenue_id, revenue_data); + + // Insert revenue id into RevenuesByProject + >::try_mutate::<_, _, DispatchError, _>(project_id, |revenues| { + revenues + .try_push(revenue_id) + .map_err(|_| Error::::MaxRevenuesPerProjectReached)?; + Ok(()) + })?; + + // Update revenue status in project info + Self::do_update_revenue_status_in_project_info( + project_id, + revenue_id, + RevenueStatus::default(), + )?; + + // Event + Self::deposit_event(Event::RevenueCreated(project_id, revenue_id)); + + Ok(()) + } + + fn do_update_revenue_status_in_project_info( + project_id: ProjectId, + revenue_id: RevenueId, + revenue_status: RevenueStatus, + ) -> DispatchResult { + // Ensure project exists + ensure!(>::contains_key(project_id), Error::::ProjectNotFound); + + // Ensure revenue exists + ensure!(>::contains_key(revenue_id), Error::::RevenueNotFound); + + // Update revenue status in project info + >::try_mutate::<_, _, DispatchError, _>(project_id, |project_data| { + let project_data = project_data.as_mut().ok_or(Error::::ProjectNotFound)?; + project_data.revenue_status = Some(revenue_status); + Ok(()) + })?; + + // Update revenue status changes in revenue info + Self::do_create_revenue_status_change_record(revenue_id, revenue_status)?; + Ok(()) + } + + fn do_create_drawdown_status_change_record( + drawdown_id: DrawdownId, + drawdown_status: DrawdownStatus, + ) -> DispatchResult { + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Update drawdown status changes in drawdown info + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let drawdown_data = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + drawdown_data + .status_changes + .try_push((drawdown_status, timestamp)) + .map_err(|_| Error::::MaxStatusChangesPerDrawdownReached)?; + Ok(()) + })?; + + Ok(()) + } + + fn do_create_revenue_status_change_record( + revenue_id: RevenueId, + revenue_status: RevenueStatus, + ) -> DispatchResult { + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Update revenue status changes in revenue info + >::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| { + let revenue_data = revenue_data.as_mut().ok_or(Error::::RevenueNotFound)?; + revenue_data + .status_changes + .try_push((revenue_status, timestamp)) + .map_err(|_| Error::::MaxStatusChangesPerRevenueReached)?; + Ok(()) + })?; + + Ok(()) + } + + fn send_funds(admin: T::AccountId, user: T::AccountId) -> DispatchResult { + // Ensure admin has enough funds to perform transfer without reaping the account + ensure!( + T::Currency::free_balance(&admin) > T::Currency::minimum_balance(), + Error::::AdminHasNoFreeBalance + ); + + //Ensure admin has enough funds to transfer & keep some balance to perform other operations + ensure!( + T::Currency::free_balance(&admin) > T::MinAdminBalance::get(), + Error::::InsufficientFundsToTransfer + ); + + //TODO: Check if user has enough funds to receive transfer, refactor else arm + // If user has no funds, then transfer funds to user + if T::Currency::free_balance(&user) < T::Currency::minimum_balance() { + // Transfer funds to user + T::Currency::transfer(&admin, &user, T::TransferAmount::get(), KeepAlive)?; + Ok(()) + } else { + return Ok(()); + } + } + + fn do_delete_expenditure_transactions(expenditure_id: ExpenditureId) -> DispatchResult { + // Get expenditure data + let expenditure_data = + >::get(expenditure_id).ok_or(Error::::ExpenditureNotFound)?; + + // Ensure project exists + ensure!( + >::contains_key(expenditure_data.project_id), + Error::::ProjectNotFound + ); + + // Ensure project contains drawdowns and get them + let drawdowns = >::try_get(expenditure_data.project_id) + .map_err(|_| Error::::ProjectHasNoDrawdowns)?; + + for drawdown_id in drawdowns.iter().cloned() { + // Ensure drawdown exists + ensure!(>::contains_key(drawdown_id), Error::::DrawdownNotFound); + + // If drawdown has transactions, check that every transaction exists & its amount is zero + if !>::get(expenditure_data.project_id, drawdown_id).is_empty() { + for transaction_id in + >::get(expenditure_data.project_id, drawdown_id) + .iter() + .cloned() + { + // Ensure transaction exists & get transaction data + let transaction_data = + >::get(transaction_id).ok_or(Error::::TransactionNotFound)?; + + if transaction_data.expenditure_id == expenditure_id { + // Ensure transaction amount is zero + ensure!(transaction_data.amount == 0, Error::::ExpenditureHasNonZeroTransactions); + + // Delete transaction from TransactionsInfo + >::remove(transaction_id); + + // Delete transaction from TransactionsByDrawdown + >::try_mutate_exists::<_, _, _, DispatchError, _>( + expenditure_data.project_id, + drawdown_id, + |transactions_option| { + let transactions = + transactions_option.as_mut().ok_or(Error::::DrawdownHasNoTransactions)?; + transactions.retain(|transaction| transaction != &transaction_id); + if transactions.is_empty() { + transactions_option.clone_from(&None); + } + Ok(()) + }, + )?; + } + } + } + } + Ok(()) + } + + fn do_delete_job_eligible_transactions(job_eligible_id: JobEligibleId) -> DispatchResult { + // Get job eligible data + let job_eligible_data = + >::get(job_eligible_id).ok_or(Error::::JobEligibleNotFound)?; + + // Ensure project exists + ensure!( + >::contains_key(job_eligible_data.project_id), + Error::::ProjectNotFound + ); + + // Ensure project contains revenues and get them + let revenues = >::try_get(job_eligible_data.project_id) + .map_err(|_| Error::::ProjectHasNoRevenues)?; + + for revenue_id in revenues.iter().cloned() { + // Ensure revenue exists + ensure!(>::contains_key(revenue_id), Error::::RevenueNotFound); + + // If revenue has transactions, check that every transaction exists & its amount is zero + if !>::get(job_eligible_data.project_id, revenue_id).is_empty() { + for transaction_id in + >::get(job_eligible_data.project_id, revenue_id) + .iter() + .cloned() + { + // Ensure transaction exists & get transaction data + let transaction_data = >::get(transaction_id) + .ok_or(Error::::RevenueTransactionNotFound)?; + + if transaction_data.job_eligible_id == job_eligible_id { + // Ensure transaction amount is zero + ensure!(transaction_data.amount == 0, Error::::JobEligibleHasNonZeroTransactions); + + // Delete transaction from RevenueTransactionsInfo + >::remove(transaction_id); + + // Delete transaction from TransactionsByRevenue + >::try_mutate_exists::<_, _, _, DispatchError, _>( + job_eligible_data.project_id, + revenue_id, + |transactions_option| { + let transactions = + transactions_option.as_mut().ok_or(Error::::RevenueHasNoTransactions)?; + transactions.retain(|transaction| transaction != &transaction_id); + if transactions.is_empty() { + transactions_option.clone_from(&None); + } + Ok(()) + }, + )?; + } + } + } + } + Ok(()) + } + + // E R R O R R E C O V E R Y + // ================================================================================================= + pub fn do_recovery_drawdown( + user: T::AccountId, + project_id: ProjectId, + drawdown_id: DrawdownId, + transactions: Transactions, + ) -> DispatchResult { + // Ensure user permissions + Self::is_authorized(user.clone(), &project_id, ProxyPermission::RecoveryDrawdown)?; + + // Ensure project exists & is not completed + Self::is_project_completed(project_id)?; + + // Check if drawdown exists & is editable + Self::is_drawdown_editable(user.clone(), drawdown_id)?; + + // Ensure drawdown belongs to project + ensure!( + >::get(project_id).contains(&drawdown_id), + Error::::DrawdownDoesNotBelongToProject + ); + + // Ensure drawdown has transactions + ensure!( + !>::get(project_id, drawdown_id).is_empty(), + Error::::DrawdownHasNoTransactions + ); + + // Do execute transactions + Self::do_execute_transactions(user.clone(), project_id, drawdown_id, transactions)?; + + // If the administrator adds more transactions to the given drawdown, update the added transaction to + // the drawdown's transactions status + // Get drawdown transactions + if !>::get(project_id, drawdown_id).is_empty() { + // If a transaction is in a diffferent status than Approved or Confirmed, set it to the current drawdown status + for transaction_id in + >::get(project_id, drawdown_id).iter().cloned() + { + let transaction_data = + TransactionsInfo::::get(transaction_id).ok_or(Error::::TransactionNotFound)?; + + if transaction_data.status != TransactionStatus::Approved + && transaction_data.status != TransactionStatus::Confirmed + { + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |transaction_data| { + let transaction_data = + transaction_data.as_mut().ok_or(Error::::TransactionNotFound)?; + transaction_data.status = + Self::get_transaction_status_for_a_given_drawdown(drawdown_id)?; + Ok(()) + }, + )?; + } + } + } + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Create a record in DrawdownsInfo + >::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| { + let drawdown = drawdown_data.as_mut().ok_or(Error::::DrawdownNotFound)?; + drawdown + .recovery_record + .try_push((user, timestamp)) + .map_err(|_| Error::::MaxRecoveryChangesReached)?; + Ok(()) + })?; + + // Event + Self::deposit_event(Event::DrawdownErrorRecoveryExecuted(project_id, drawdown_id)); + + Ok(()) + } + + pub fn do_recovery_revenue( + user: T::AccountId, + project_id: ProjectId, + revenue_id: RevenueId, + transactions: Transactions, + ) -> DispatchResult { + // Ensure user permissions + Self::is_authorized(user.clone(), &project_id, ProxyPermission::RecoveryRevenue)?; + + // Ensure project exists & is not completed + Self::is_project_completed(project_id)?; + + // Check if revenue exists & is editable + Self::is_revenue_editable(user.clone(), revenue_id)?; + + // Ensure revenue belongs to project + ensure!( + >::get(project_id).contains(&revenue_id), + Error::::RevenueDoesNotBelongToProject + ); + + // Ensure revenue has transactions + ensure!( + !>::get(project_id, revenue_id).is_empty(), + Error::::RevenueHasNoTransactions + ); + + // Do execute revenue transactions + Self::do_execute_revenue_transactions(user.clone(), project_id, revenue_id, transactions)?; + + // If the administrator adds more transactions to the given revenue, update the added transaction to + // the revenue's transactions status + // Get revenue transactions + if !>::get(project_id, revenue_id).is_empty() { + // If a transaction is in a diffferent status than Approved, set it to the current revenue status + for transaction_id in >::get(project_id, revenue_id).iter().cloned() + { + let transaction_data = RevenueTransactionsInfo::::get(transaction_id) + .ok_or(Error::::RevenueTransactionNotFound)?; + + if transaction_data.status != RevenueTransactionStatus::Approved { + >::try_mutate::<_, _, DispatchError, _>( + transaction_id, + |transaction_data| { + let transaction_data = + transaction_data.as_mut().ok_or(Error::::RevenueTransactionNotFound)?; + transaction_data.status = + Self::get_transaction_status_for_a_given_revenue(revenue_id)?; + Ok(()) + }, + )?; + } + } + } + + // Get timestamp + let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::::TimestampError)?; + + // Create a record in RevenuesInfo + >::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| { + let revenue = revenue_data.as_mut().ok_or(Error::::RevenueNotFound)?; + revenue + .recovery_record + .try_push((user, timestamp)) + .map_err(|_| Error::::MaxRecoveryChangesReached)?; + Ok(()) + })?; + + // Event + Self::deposit_event(Event::RevenueErrorRecoveryExecuted(project_id, revenue_id)); + + Ok(()) + } + + fn get_transaction_status_for_a_given_drawdown( + drawdown_id: DrawdownId, + ) -> Result { + // Get drawdown data + let drawdown_data = >::get(drawdown_id).ok_or(Error::::DrawdownNotFound)?; + + match drawdown_data.status { + DrawdownStatus::Draft => Ok(TransactionStatus::Draft), + DrawdownStatus::Submitted => Ok(TransactionStatus::Submitted), + DrawdownStatus::Approved => Ok(TransactionStatus::Approved), + DrawdownStatus::Rejected => Ok(TransactionStatus::Rejected), + DrawdownStatus::Confirmed => Ok(TransactionStatus::Confirmed), + } + } + + fn get_transaction_status_for_a_given_revenue( + revenue_id: RevenueId, + ) -> Result { + // Get revenue data + let revenue_data = >::get(revenue_id).ok_or(Error::::RevenueNotFound)?; + + match revenue_data.status { + RevenueStatus::Draft => Ok(RevenueTransactionStatus::Draft), + RevenueStatus::Submitted => Ok(RevenueTransactionStatus::Submitted), + RevenueStatus::Approved => Ok(RevenueTransactionStatus::Approved), + RevenueStatus::Rejected => Ok(RevenueTransactionStatus::Rejected), + } + } + + // Do not code beyond this line } diff --git a/pallets/fund-admin/src/lib.rs b/pallets/fund-admin/src/lib.rs index 650e3d8b..776de932 100644 --- a/pallets/fund-admin/src/lib.rs +++ b/pallets/fund-admin/src/lib.rs @@ -9,1688 +9,1749 @@ mod mock; mod tests; mod functions; +pub mod migration; mod types; - #[frame_support::pallet] pub mod pallet { - use frame_support::{ - pallet_prelude::{ValueQuery, *}, - traits::{Currency, Time}, - BoundedVec, - }; - use frame_system::pallet_prelude::*; - use sp_runtime::traits::Scale; + use frame_support::{ + pallet_prelude::{ValueQuery, *}, + traits::{Currency, Time}, + BoundedVec, + }; + use frame_system::pallet_prelude::*; + use scale_info::prelude::vec; + use sp_runtime::sp_std::vec::Vec; + use sp_runtime::traits::Scale; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + use crate::types::*; + use pallet_rbac::types::RoleBasedAccessControl; + pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; - const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); - use crate::types::*; - use pallet_rbac::types::RoleBasedAccessControl; - pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; - #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type Moment: Parameter + + Default + + Scale + + Copy + + MaxEncodedLen + + scale_info::StaticTypeInfo + + Into; - type Moment: Parameter - + Default - + Scale - + Copy - + MaxEncodedLen - + scale_info::StaticTypeInfo - + Into; + type Timestamp: Time; - type Timestamp: Time; + type Rbac: RoleBasedAccessControl; - type Rbac: RoleBasedAccessControl; + type RemoveOrigin: EnsureOrigin; - type RemoveOrigin: EnsureOrigin; + type Currency: Currency; - type Currency: Currency; + #[pallet::constant] + type MaxDocuments: Get; - #[pallet::constant] - type MaxDocuments: Get; - - #[pallet::constant] - type MaxProjectsPerUser: Get; - - #[pallet::constant] - type MaxUserPerProject: Get; - - #[pallet::constant] - type MaxBuildersPerProject: Get; - - #[pallet::constant] - type MaxInvestorsPerProject: Get; - - #[pallet::constant] - type MaxIssuersPerProject: Get; - - #[pallet::constant] - type MaxRegionalCenterPerProject: Get; - - #[pallet::constant] - type MaxDrawdownsPerProject: Get; - - #[pallet::constant] - type MaxTransactionsPerDrawdown: Get; - - #[pallet::constant] - type MaxRegistrationsAtTime: Get; - - #[pallet::constant] - type MaxExpendituresPerProject: Get; - - #[pallet::constant] - type MaxProjectsPerInvestor: Get; - - #[pallet::constant] - type MaxBanksPerProject: Get; - - #[pallet::constant] - type MaxJobEligiblesByProject: Get; - - #[pallet::constant] - type MaxRevenuesByProject: Get; - - #[pallet::constant] - type MaxTransactionsPerRevenue: Get; - - #[pallet::constant] - type MaxStatusChangesPerDrawdown: Get; - - #[pallet::constant] - type MaxStatusChangesPerRevenue: Get; - - #[pallet::constant] - type MinAdminBalance: Get>; - - #[pallet::constant] - type TransferAmount: Get>; - } - - #[pallet::pallet] - #[pallet::storage_version(STORAGE_VERSION)] - - pub struct Pallet(_); - - /* --- Onchain storage section --- */ - - #[pallet::storage] - #[pallet::getter(fn global_scope)] - pub(super) type GlobalScope = StorageValue< - _, - [u8; 32], // Value global scope id - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn users_info)] - pub(super) type UsersInfo = StorageMap< - _, - Blake2_128Concat, - T::AccountId, // Key account_id - UserData, // Value UserData - OptionQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn projects_info)] - pub(super) type ProjectsInfo = StorageMap< - _, - Identity, - ProjectId, // Key project_id - ProjectData, // Value ProjectData - OptionQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn users_by_project)] - pub(super) type UsersByProject = StorageMap< - _, - Identity, - ProjectId, // Key project_id - BoundedVec, // Value users - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn projects_by_user)] - pub(super) type ProjectsByUser = StorageMap< - _, - Blake2_128Concat, - T::AccountId, // Key account_id - BoundedVec<[u8; 32], T::MaxProjectsPerUser>, // Value projects - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn expenditures_info)] - pub(super) type ExpendituresInfo = StorageMap< - _, - Identity, - ExpenditureId, // Key expenditure_id - ExpenditureData, // Value ExpenditureData - OptionQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn expenditures_by_project)] - pub(super) type ExpendituresByProject = StorageMap< - _, - Identity, - ProjectId, // Key project_id - BoundedVec<[u8; 32], T::MaxExpendituresPerProject>, // Value expenditures - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn drawdowns_info)] - pub(super) type DrawdownsInfo = StorageMap< - _, - Identity, - DrawdownId, // Key drawdown id - DrawdownData, // Value DrawdownData - OptionQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn drawdowns_by_project)] - pub(super) type DrawdownsByProject = StorageMap< - _, - Identity, - ProjectId, // Key project_id - BoundedVec, // Value Drawdowns - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn transactions_info)] - pub(super) type TransactionsInfo = StorageMap< - _, - Identity, - TransactionId, // Key transaction id - TransactionData, // Value TransactionData - OptionQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn transactions_by_drawdown)] - pub(super) type TransactionsByDrawdown = StorageDoubleMap< - _, - Identity, - ProjectId, //K1: project id - Identity, - DrawdownId, //K2: drawdown id - BoundedVec, // Value transactions - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn job_eligibles_info)] - pub(super) type JobEligiblesInfo = StorageMap< - _, - Identity, - JobEligibleId, // Key transaction id - JobEligibleData, // Value JobEligibleData - OptionQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn job_eligibles_by_project)] - pub(super) type JobEligiblesByProject = StorageMap< - _, - Identity, - ProjectId, // Key project_id - BoundedVec, // Value job eligibles - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn revenues_info)] - pub(super) type RevenuesInfo = StorageMap< - _, - Identity, - RevenueId, // Key revenue id - RevenueData, // Value RevenueData - OptionQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn revenues_by_project)] - pub(super) type RevenuesByProject = StorageMap< - _, - Identity, - ProjectId, // Key project_id - BoundedVec, // Value Revenues - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn revenue_transactions_info)] - pub(super) type RevenueTransactionsInfo = StorageMap< - _, - Identity, - RevenueTransactionId, // Key revenue transaction id - RevenueTransactionData, // Value RevenueTransactionData - OptionQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn transactions_by_revenue)] - pub(super) type TransactionsByRevenue = StorageDoubleMap< - _, - Identity, - ProjectId, //K1: project id - Identity, - RevenueId, //K2: revenue id - BoundedVec, // Value revenue transactions - ValueQuery, - >; - - // E V E N T S - // ------------------------------------------------------------------------------------------------------------ - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// Proxy initial setup completed using the sudo pallet - ProxySetupCompleted, - /// Project was created successfully - ProjectCreated(T::AccountId, ProjectId), - /// The selected roject was edited successfully - ProjectEdited(T::AccountId, ProjectId), - /// The selected project was deleted successfully - ProjectDeleted(T::AccountId, ProjectId), - /// Administrator was registered successfully using the sudo pallet - AdministratorAssigned(T::AccountId), - /// Administrator was removed successfully using the sudo pallet - AdministratorRemoved(T::AccountId), - /// The user was assigned to the selected project - UserAssignmentCompleted(T::AccountId, ProjectId), - /// The user was unassigned to the selected project - UserUnassignmentCompleted(T::AccountId, ProjectId), - /// Users extrinsic was executed, individual CUDActions were applied - UsersExecuted(T::AccountId), - /// A new user account was created successfully - UserCreated(T::AccountId), - /// The selected user was edited successfully - UserUpdated(T::AccountId), - /// The selected user was deleted successfully - UserDeleted(T::AccountId), - /// An array of expenditures was executed depending on the CUDAction - ExpendituresExecuted(T::AccountId, ProjectId), - /// Expenditure was created successfully - ExpenditureCreated(ProjectId, ExpenditureId), - /// Expenditure was updated successfully - ExpenditureUpdated(ProjectId, ExpenditureId), - /// Expenditure was deleted successfully - ExpenditureDeleted(ProjectId, ExpenditureId), - /// An array of transactions was executed depending on the CUDAction - TransactionsExecuted(ProjectId, DrawdownId), - /// Transaction was created successfully - TransactionCreated(ProjectId, DrawdownId, TransactionId), - /// Transaction was edited successfully - TransactionEdited(ProjectId, DrawdownId, TransactionId), - /// Transaction was deleted successfully - TransactionDeleted(ProjectId, DrawdownId, TransactionId), - /// Assign users extrinsic was completed successfully - UsersAssignationExecuted(T::AccountId, ProjectId), - /// Drawdowns were initialized successfully at the beginning of the project - DrawdownsInitialized(T::AccountId, ProjectId), - /// Drawdown was created successfully - DrawdownCreated(ProjectId, DrawdownId), - /// Drawdown was submitted successfully - DrawdownSubmitted(ProjectId, DrawdownId), - /// Drawdown was approved successfully - DrawdownApproved(ProjectId, DrawdownId), - /// Drawdown was rejected successfully - DrawdownRejected(ProjectId, DrawdownId), - /// Drawdown was cancelled successfully - DrawdownSubmissionCancelled(ProjectId, DrawdownId), - /// Bulkupload drawdown was submitted successfully - BulkUploadSubmitted(ProjectId, DrawdownId), - /// An array of adjustments was executed depending on the CUDAction - InflationRateAdjusted(T::AccountId), - /// An array of job eligibles was executed depending on the CUDAction - JobEligiblesExecuted(T::AccountId, ProjectId), - /// Job eligible was created successfully - JobEligibleCreated(ProjectId, JobEligibleId), - /// Job eligible was updated successfully - JobEligibleUpdated(ProjectId, JobEligibleId), - /// Job eligible was deleted successfully - JobEligibleDeleted(ProjectId, JobEligibleId), - /// Revenue transaction was created successfully - RevenueTransactionCreated(ProjectId, RevenueId, RevenueTransactionId), - /// Revenue transaction was updated successfully - RevenueTransactionUpdated(ProjectId, RevenueId, RevenueTransactionId), - /// Revenue transaction was deleted successfully - RevenueTransactionDeleted(ProjectId, RevenueId, RevenueTransactionId), - /// An array of revenue transactions was executed depending on the CUDAction - RevenueTransactionsExecuted(ProjectId, RevenueId), - /// Revenue was created successfully - RevenueCreated(ProjectId, RevenueId), - /// Revenue was submitted successfully - RevenueSubmitted(ProjectId, RevenueId), - /// Revenue was approved successfully - RevenueApproved(ProjectId, RevenueId), - /// Revenue was rejected successfully - RevenueRejected(ProjectId, RevenueId), - /// Bank's confirming documents were uploaded successfully - BankDocumentsUploaded(ProjectId, DrawdownId), - /// Bank's confirming documents were updated successfully - BankDocumentsUpdated(ProjectId, DrawdownId), - /// Bank's confirming documents were deleted successfully - BankDocumentsDeleted(ProjectId, DrawdownId), - } - - // E R R O R S - // ------------------------------------------------------------------------------------------------------------ - #[pallet::error] - pub enum Error { - /// FieldName is empty - EmptyFieldName, - /// FieldDescription is empty - EmptyFieldDescription, - /// FieldName is too long - FieldNameTooLong, - /// Array of users is empty - EmptyUsers, - /// CID is empty - EmptyFieldCID, - /// Array of banks is empty - EmptyFieldBanks, - /// The private group id is empty - PrivateGroupIdEmpty, - /// Array of users to be assigned to a project is empty - EmptyUsersAssignation, - /// Field address project is empty - EmptyProjectAddress, - /// No value was found for the global scope - NoGlobalScopeValueWasFound, - /// Project ID is already in use - ProjectIdAlreadyInUse, - /// Timestamp was not genereated correctly - TimestampError, - /// Completion date must be later than creation date - CompletionDateMustBeLater, - /// User is already registered in the site - UserAlreadyRegistered, - /// Project was not found - ProjectNotFound, - /// Project is not active anymore - ProjectIsAlreadyCompleted, - /// Project has no drawdowns - ProjectHasNoDrawdowns, - /// Project has no expenditures - ProjectHasNoExpenditures, - /// Project has no users - ProjectHasNoUsers, - /// Project has no job eligibles - ProjectHasNoJobEligibles, - /// Project has no revenues - ProjectHasNoRevenues, - /// Can not delete a completed project - CannotDeleteCompletedProject, - /// User is not registered - UserNotRegistered, - /// User has been already added to the project - UserAlreadyAssignedToProject, - /// Max number of users per project reached - MaxUsersPerProjectReached, - /// Max number of projects per user reached - MaxProjectsPerUserReached, - /// User is not assigned to the project - UserNotAssignedToProject, - /// Can not register administrator role - CannotRegisterAdminRole, - /// Max number of builders per project reached - MaxBuildersPerProjectReached, - /// Max number of investors per project reached - MaxInvestorsPerProjectReached, - /// Max number of issuers per project reached - MaxIssuersPerProjectReached, - /// Max number of regional centers per project reached - MaxRegionalCenterPerProjectReached, - /// Can not remove administrator role - CannotRemoveAdminRole, - /// Can not add admin role at user project assignment - CannotAddAdminRole, - /// User can not have more than one role at the same time - UserCannotHaveMoreThanOneRole, - /// Expenditure not found - ExpenditureNotFound, - /// Expenditure not found for the selected project_id - ExpenditureNotFoundForSelectedProjectId, - /// Expenditure already exist - ExpenditureAlreadyExists, - /// Expenditure is already in a transaction - ExpenditureHasNonZeroTransactions, - /// Max number of expenditures per project reached - MaxExpendituresPerProjectReached, - /// Field name can not be empty - EmptyExpenditureName, - /// Expenditure does not belong to the project - ExpenditureDoesNotBelongToProject, - /// Drawdown id is not found - DrawdownNotFound, - /// Invalid amount - InvalidAmount, - /// Documents field is empty - DocumentsEmpty, - /// Transaction id is not found - TransactionNotFound, - /// Transaction was not found for the selected Drawdown_id - TransactionNotFoundForSelectedDrawdownId, - /// Transaction already exist - TransactionAlreadyExists, - /// Transaction is already in a drawdown - TransactionInUse, - /// Max number of transactions per drawdown reached - MaxTransactionsPerDrawdownReached, - /// Drawdown already exist - DrawdownAlreadyExists, - /// Max number of drawdowns per project reached - MaxDrawdownsPerProjectReached, - /// Max number of status changes per drawdown reached - MaxStatusChangesPerDrawdownReached, - /// Can not modify a completed drawdown - CannotEditDrawdown, - /// Can not perform any action on a submitted transaction - CannotPerformActionOnSubmittedTransaction, - /// Can not perform any action on a approved transaction - CannotPerformActionOnApprovedTransaction, - /// Can not perform any action on a confirmed transaction - CannotPerformActionOnConfirmedTransaction, - /// Can not perform any action on a submitted drawdown - CannotPerformActionOnSubmittedDrawdown, - /// Can not perform any action on a approved drawdown - CannotPerformActionOnApprovedDrawdown, - /// Can not perform any action on a confirmed drawdown - CannotPerformActionOnConfirmedDrawdown, - /// Transaction is already completed - TransactionIsAlreadyCompleted, - /// User does not have the specified role - UserDoesNotHaveRole, - /// Transactions vector is empty - EmptyTransactions, - /// Transactions are required for the current workflow - TransactionsRequired, - /// Transaction ID was not found in do_execute_transaction - TransactionIdRequired, - /// Drawdown can not be submitted if does not has any transactions - DrawdownHasNoTransactions, - /// Cannot submit transaction - CannotSubmitTransaction, - /// Drawdown can not be approved if is not in submitted status - DrawdownIsNotInSubmittedStatus, - /// Transactions is not in submitted status - TransactionIsNotInSubmittedStatus, - /// Array of expenditures is empty - EmptyExpenditures, - /// Expenditure name is required - ExpenditureNameRequired, - /// Expenditure type is required - ExpenditureTypeRequired, - /// Expenditure amount is required - ExpenditureAmountRequired, - /// Expenditure id is required - ExpenditureIdRequired, - /// User name is required - UserNameRequired, - /// User role is required - UserRoleRequired, - /// User image is required - UserImageRequired, - /// User email is required - UserEmailRequired, - /// Amount is required - AmountRequired, - /// Can not delete a user if the user is assigned to a project - UserHasAssignedProjects, - /// User has no projects assigned - UserHasNoProjects, - /// Can not send a drawdown to submitted status if it has no transactions - NoTransactionsToSubmit, - /// Bulk upload description is required - BulkUploadDescriptionRequired, - /// Bulk upload documents are required - BulkUploadDocumentsRequired, - /// Administrator can not delete themselves - AdministratorsCannotDeleteThemselves, - /// No feedback was provided for bulk upload - NoFeedbackProvidedForBulkUpload, - /// Bulkupload feedback is empty - EmptyBulkUploadFeedback, - /// NO feedback for EN5 drawdown was provided - EB5MissingFeedback, - /// EB5 feedback is empty - EmptyEb5Feedback, - /// Inflation rate extrinsic is missing an array of project ids - ProjectsInflationRateEmpty, - /// Inflation rate was not provided - InflationRateRequired, - /// Inflation rate has been already set for the selected project - InflationRateAlreadySet, - /// Inflation rate was not set for the selected project - InflationRateNotSet, - /// Bulkupload drawdowns are only allowed for Construction Loan & Developer Equity - DrawdownTypeNotSupportedForBulkUpload, - /// Cannot edit user role if the user is assigned to a project - UserHasAssignedProjectsCannotUpdateRole, - /// Cannot delete user if the user is assigned to a project - UserHasAssignedProjectsCannotDelete, - /// Cannot send a bulkupload drawdown if the drawdown status isn't in draft or rejected - DrawdownStatusNotSupportedForBulkUpload, - /// Cannot submit a drawdown if the drawdown status isn't in draft or rejected - DrawdownIsNotInDraftOrRejectedStatus, - /// Only investors can update/edit their documents - UserIsNotAnInvestor, - /// Max number of projects per investor has been reached - MaxProjectsPerInvestorReached, - /// Jobs eligibles array is empty - JobEligiblesEmpty, - /// JOb eligible name is empty - JobEligiblesNameRequired, - /// Job eligible id already exists - JobEligibleIdAlreadyExists, - /// Max number of job eligibles per project reached - MaxJobEligiblesPerProjectReached, - /// Job eligible id not found - JobEligibleNotFound, - /// Jopb eligible does not belong to the project - JobEligibleDoesNotBelongToProject, - /// Job eligible name is required - JobEligibleNameRequired, - /// Job eligible amount is required - JobEligibleAmountRequired, - /// Job eligible id is required - JobEligibleIdRequired, - /// Job eligible not found for the given project id - JobEligibleNotFoundForSelectedProjectId, - /// Job eligible has non zero transactions - JobEligibleHasNonZeroTransactions, - /// Revenue id was not found - RevenueNotFound, - /// Transactions revenue array is empty - RevenueTransactionsEmpty, - /// An array of revenue transactions is required - RevenueTransactionsRequired, - /// Revenue transaction is not in submitted status - RevenueTransactionNotSubmitted, - /// Revenue can not be edited - CannotEditRevenue, - /// Revenue transaction id already exists - RevenueTransactionIdAlreadyExists, - /// Max number of transactions per revenue reached - MaxTransactionsPerRevenueReached, - /// Revenue transaction id not found - RevenueTransactionNotFound, - /// Revenue transaction was not found for the selected revenue_id - RevenueTransactionNotFoundForSelectedRevenueId, - /// Revenue transaction can not be edited - CannotEditRevenueTransaction, - /// Max number of status changes per revenue reached - MaxStatusChangesPerRevenueReached, - /// Can not perform any action on a submitted revenue - CannotPerformActionOnSubmittedRevenue, - /// Can not perform any action on a approved revenue - CannotPerformActionOnApprovedRevenue, - /// Can not perform any action on a submitted revenue transaction - CannotPerformActionOnApprovedRevenueTransaction, - /// Can not perform any action on a approved revenue transaction - CannotPerformActionOnSubmittedRevenueTransaction, - /// Revenue amoun is required - RevenueAmountRequired, - /// Revenue transaction id is required - RevenueTransactionIdRequired, - /// Revenue Id already exists - RevenueIdAlreadyExists, - /// Maximun number of revenues per project reached - MaxRevenuesPerProjectReached, - /// Can not send a revenue to submitted status if it has no transactions - RevenueHasNoTransactions, - /// Revenue is not in submitted status - RevenueIsNotInSubmittedStatus, - /// Revenue transaction is not in submitted status - RevenueTransactionIsNotInSubmittedStatus, - /// Revenue transactions feedback is empty - RevenueTransactionsFeedbackEmpty, - /// The revenue is not in submitted status - RevenueNotSubmitted, - /// Can not upload bank confirming documents if the drawdown is not in Approved status - DrawdowMustBeInApprovedStatus, - /// Drawdown is not in Confirmed status - DrawdowMustBeInConfirmedStatus, - /// Drawdown is not in Submitted status - DrawdownNotSubmitted, - /// Can not insert (CUDAction: Create) bank confmirng documents if the drawdown has already bank confirming documents - DrawdownHasAlreadyBankConfirmingDocuments, - /// Drawdown has no bank confirming documents (CUDAction: Update or Delete) - DrawdownHasNoBankConfirmingDocuments, - /// Bank confirming documents are required - BankConfirmingDocumentsNotProvided, - /// Banck confirming documents array is empty - BankConfirmingDocumentsEmpty, - /// Only eb5 drawdowns are allowed to upload bank documentation - OnlyEB5DrawdownsCanUploadBankDocuments, - /// Maximun number of registrations at a time reached - MaxRegistrationsAtATimeReached, - /// Administrator account has insuficiente balance to register a new user - AdminHasNoFreeBalance, - /// Administrator account has insuficiente balance to register a new user - InsufficientFundsToTransfer, - } - - // E X T R I N S I C S - // ------------------------------------------------------------------------------------------------------------ - #[pallet::call] - impl Pallet { - // I N I T I A L - // -------------------------------------------------------------------------------------------- - /// Initialize the pallet by setting the permissions for each role - /// & the global scope - /// - /// # Considerations: - /// - This function can only be called once - /// - This function can only be called usinf the sudo pallet - #[pallet::call_index(1)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn initial_setup(origin: OriginFor) -> DispatchResult { - T::RemoveOrigin::ensure_origin(origin.clone())?; - Self::do_initial_setup()?; - Ok(()) - } - - /// Adds an administrator account to the site - /// - /// # Parameters: - /// - origin: The sudo account - /// - admin: The administrator account to be added - /// - name: The name of the administrator account - /// - /// # Considerations: - /// - This function can only be called using the sudo pallet - /// - This function is used to add the first administrator to the site - /// - If the user is already registered, the function will return an error: UserAlreadyRegistered - /// - This function grants administrator permissions to the user from the rbac pallet - /// - administrator role have global scope permissions - #[pallet::call_index(2)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn sudo_add_administrator( - origin: OriginFor, - admin: T::AccountId, - name: FieldName, - ) -> DispatchResult { - T::RemoveOrigin::ensure_origin(origin.clone())?; - Self::do_sudo_add_administrator(admin, name)?; - Ok(()) - } - - /// Removes an administrator account from the site - /// - /// # Parameters: - /// - origin: The sudo account - /// - admin: The administrator account to be removed - /// - /// # Considerations: - /// - This function can only be called using the sudo pallet - /// - This function is used to remove any administrator from the site - /// - If the user is not registered, the function will return an error: UserNotFound - /// - This function removes administrator permissions of the user from the rbac pallet - /// - /// # Note: - /// WARNING: Administrators can remove themselves from the site, - /// but they can add themselves back - #[pallet::call_index(3)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn sudo_remove_administrator( - origin: OriginFor, - admin: T::AccountId, - ) -> DispatchResult { - T::RemoveOrigin::ensure_origin(origin.clone())?; - Self::do_sudo_remove_administrator(admin)?; - Ok(()) - } - - // U S E R S - // -------------------------------------------------------------------------------------------- - /// This extrinsic is used to create, update, or delete a user account - /// - /// # Parameters: - /// - origin: The administrator account - /// - user: The target user account to be registered, updated, or deleted. - /// It is an array of user accounts where each entry it should be a tuple of the following: - /// - 0: The user account - /// - 1: The user name - /// - 2: The user role - /// - 3: The CUD operation to be performed on the user account. CUD action is ALWAYS - /// required. - /// - /// # Considerations: - /// - Users parameters are optional because depends on the CUD action as follows: - /// * **Create**: The user account, user name, user role & CUD action are required - /// * **Update**: The user account & CUD action are required. The user name & user role are - /// optionals. - /// * **Delete**: The user account & CUD action are required. - /// - This function can only be called by an administrator account - /// - Multiple users can be registered, updated, or deleted at the same time, but - /// the user account must be unique. Multiple actions over the same user account - /// in the same call, it could result in an unexpected behavior. - /// - If the user is already registered, the function will return an error: - /// UserAlreadyRegistered - /// - If the user is not registered, the function will return an error: UserNotFound - /// - /// # Note: - /// - WARNING: It is possible to register, update, or delete administrators accounts using - /// this extrinsic, - /// but administrators can not delete themselves. - /// - WARNING: This function only registers, updates, or deletes users from the site. - /// - WARNING: The only way to grant or remove permissions of a user account is assigning or - /// unassigning - /// a user from a selected project. - #[pallet::call_index(4)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn users(origin: OriginFor, users: Users) -> DispatchResult { - let who = ensure_signed(origin)?; // origin need to be an admin - - Self::do_execute_users(who, users) - } - - /// Edits an user account - /// - /// # Parameters: - /// - origin: The user account which is being edited - /// - name: The name of the user account which is being edited - /// - image: The image of the user account which is being edited - /// - email: The email of the user account which is being edited - /// - documents: The documents of the user account which is being edited. - /// ONLY available for the investor role. - /// - /// # Considerations: - /// - If the user is not registered, the function will return an error: UserNotFound - /// - This function can only be called by a registered user account - /// - This function will be called by the user account itself - /// - ALL parameters are optional because depends on what is being edited - /// - ONLY the investor role can edit or update the documents - #[pallet::call_index(5)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn users_edit_user( - origin: OriginFor, - name: Option, - image: Option, - email: Option, - documents: Option>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - Self::do_edit_user(who, name, image, email, documents) - } - - // P R O J E C T S - // -------------------------------------------------------------------------------------------- - /// Registers a new project. - /// - /// # Parameters: - /// - origin: The administrator account - /// - title: The title of the project - /// - description: The description of the project - /// - image: The image of the project (CID) - /// - address: The address of the project - /// - creation_date: The creation date of the project - /// - completion_date: The completion date of the project - /// - expenditures: The expenditures of the project. It is an array of tuples where each - /// entry - /// is a tuple of the following: - /// * 0: The expenditure name - /// * 1: The expenditure type - /// * 2: The expenditure amount - /// * 3: The expenditure NAICS code - /// * 4: The expenditure jobs multiplier - /// * 5: The CUD action to be performed on the expenditure. CUD action is ALWAYS required. - /// * 6: The expenditure id. It is optional because it is only required when updating or - /// deleting - /// - job_eligibles: The job eligibles to be created/updated/deleted. This is a vector of - /// tuples - /// where each entry is composed by: - /// * 0: The job eligible name - /// * 1: The amount of the job eligible - /// * 2: The NAICS code of the job eligible - /// * 3: The jobs multiplier of the job eligible - /// * 4: The job eligible action to be performed. (Create, Update or Delete) - /// * 5: The job eligible id. This is only used when updating or deleting a job eligible. - /// - users: The users who will be assigned to the project. It is an array of tuples where - /// each entry - /// is a tuple of the following: - /// * 0: The user account - /// * 1: The user role - /// * 2: The AssignAction to be performed on the user. - /// - /// # Considerations: - /// - This function can only be called by an administrator account - /// - For users assignation, the user account must be registered. If the user is not - /// registered, - /// the function will return an error. ALL parameters are required. - /// - For expenditures, apart from the expenditure id, naics code & jopbs multiplier, ALL - /// parameters are required because for this - /// flow, the expenditures are always created. The naics code & the jobs multiplier - /// can be added later by the administrator. - /// - Creating a project will automatically create a scope for the project. - /// - /// # Note: - /// WARNING: If users are provided, the function will assign the users to the project, - /// granting them permissions in the rbac pallet. - #[pallet::call_index(6)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn projects_create_project( - origin: OriginFor, - title: FieldName, - description: FieldDescription, - image: Option, - address: FieldName, - banks: Option>, - creation_date: CreationDate, - completion_date: CompletionDate, - expenditures: Expenditures, - job_eligibles: Option>, - users: Option>, - private_group_id: PrivateGroupId, - ) -> DispatchResult { - let who = ensure_signed(origin)?; // origin need to be an admin - - Self::do_create_project( - who, - title, - description, - image, - address, - banks, - creation_date, - completion_date, - expenditures, - job_eligibles, - users, - private_group_id, - ) - } - - /// Edits a project. - /// - /// # Parameters: - /// - origin: The administrator account - /// - project_id: The selected project id that will be edited - /// - title: The title of the project to be edited - /// - description: The description of the project to be edited - /// - image: The image of the project to be edited - /// - address: The address of the project to be edited - /// - creation_date: The creation date of the project to be edited - /// - completion_date: The completion date of the project to be edited - /// - /// # Considerations: - /// - This function can only be called by an administrator account - /// - ALL parameters are optional because depends on what is being edited - /// - The project id is required because it is the only way to identify the project - /// - The project id must be registered. If the project is not registered, - /// the function will return an error: ProjectNotFound - /// - It is not possible to edit the expenditures or the users assigned to the project - /// through this function. For that, the administrator must use the extrinsics: - /// * expenditures - /// * projects_assign_user - /// - Project can only be edited in the Started status - /// - Completion date must be greater than creation date - #[pallet::call_index(7)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn projects_edit_project( - origin: OriginFor, - project_id: ProjectId, - title: Option, - description: Option, - image: Option, - address: Option, - banks: Option>, - creation_date: Option, - completion_date: Option, - ) -> DispatchResult { - let who = ensure_signed(origin)?; // origin need to be an admin - - Self::do_edit_project( - who, - project_id, - title, - description, - image, - address, - banks, - creation_date, - completion_date, - ) - } - - /// Deletes a project. - /// - /// # Parameters: - /// - origin: The administrator account - /// - project_id: The selected project id that will be deleted - /// - /// # Considerations: - /// - This function can only be called by an administrator account - /// - The project id is required because it is the only way to identify the project - /// - The project id must be registered. If the project is not registered, - /// the function will return an error: ProjectNotFound - /// - /// # Note: - /// - WARNING: Deleting a project will also delete ALL stored information associated with - /// the project. - /// BE CAREFUL. - #[pallet::call_index(8)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn projects_delete_project( - origin: OriginFor, - project_id: ProjectId, - ) -> DispatchResult { - let who = ensure_signed(origin)?; // origin need to be an admin - - Self::do_delete_project(who, project_id) - } - - /// Assigns a user to a project. - /// - /// # Parameters: - /// - origin: The administrator account - /// - project_id: The selected project id where user will be assigned - /// - users: The users to be assigned to the project. This is a vector of tuples - /// where each entry is composed by: - /// * 0: The user account id - /// * 1: The user role - /// * 2: The AssignAction to be performed. (Assign or Unassign) - /// - /// # Considerations: - /// - This function can only be called by an administrator account - /// - This extrinsic allows multiple users to be assigned/unassigned at the same time. - /// - The project id is required because it is the only way to identify the project - /// - This extrinsic is used for both assigning and unassigning users to a project - /// depending on the AssignAction. - /// - After a user is assigned to a project, the user will be able to perform actions - /// in the project depending on the role assigned to the user. - /// - After a user is unassigned from a project, the user will not be able to perform - /// actions - /// in the project anymore. - /// - If the user is already assigned to the project, the function will return an error. - /// - /// # Note: - /// - WARNING: ALL provided users needs to be registered in the site. If any of the users - /// is not registered, the function will return an error. - /// - Assigning or unassigning a user to a project will add or remove permissions to the - /// user - /// from the RBAC pallet. - /// - Warning: Cannot assign a user to a project with a different role than the one they - /// have in UsersInfo. If the user has a different role, the function will return an error. - /// - Warning: Cannot unassign a user from a project with a different role than the one they - /// have in UsersInfo. If the user has a different role, the function will return an error. - /// - Warning: Do not perform multiple actions over the same user in the same call, it could - /// result in an unexpected behavior. - #[pallet::call_index(9)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn projects_assign_user( - origin: OriginFor, - project_id: ProjectId, - users: UsersAssignation, - ) -> DispatchResult { - let who = ensure_signed(origin)?; // origin need to be an admin - - Self::do_execute_assign_users(who, project_id, users) - } - - // B U D G E T E X P E N D I T U R E & J O B E L I G I B L E S - // -------------------------------------------------------------------------------------------- - /// This extrinsic is used to create, update or delete expenditures & job eligibles. - /// - /// # Parameters: - /// - origin: The administrator account - /// - project_id: The selected project id where the expenditures will be - /// created/updated/deleted - /// - expenditures: The expenditures to be created/updated/deleted. This is a vector of - /// tuples - /// where each entry is composed by: - /// * 0: The name of the expenditure - /// * 1: The expenditure type - /// * 2: The amount of the expenditure - /// * 3: The naics code of the expenditure - /// * 4: The jobs multiplier of the expenditure - /// * 5: The expenditure action to be performed. (Create, Update or Delete) - /// * 6: The expenditure id. This is only used when updating or deleting an expenditure. - /// - job_eligibles: The job eligibles to be created/updated/deleted. This is a vector of - /// tuples - /// where each entry is composed by: - /// * 0: The job eligible name - /// * 1: The amount of the job eligible - /// * 2: The NAICS code of the job eligible - /// * 3: The jobs multiplier of the job eligible - /// * 4: The job eligible action to be performed. (Create, Update or Delete) - /// * 5: The job eligible id. This is only used when updating or deleting a job eligible. - /// - /// # Considerations: - /// - Naics code and jobs multiplier are always optional. - /// - This function can only be called by an administrator account - /// - This extrinsic allows multiple expenditures to be created/updated/deleted at the same - /// time. - /// - The project id is required because it is the only way to identify the project - /// - Expenditure parameters are optional because depends on the action to be performed: - /// * **Create**: Name, Type & Amount are required. Nacis code & Jobs multiplier are - /// optional. - /// * **Update**: Except for the expenditure id & action, all parameters are optional. - /// * **Delete**: Only the expenditure id & action is required. - /// - Multiple actions can be performed at the same time. For example, you can create a new - /// expenditure and update another one at the same time. - /// - Do not perform multiple actions over the same expenditure in the same call, it could - /// result in an unexpected behavior. - #[pallet::call_index(10)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn expenditures_and_job_eligibles( - origin: OriginFor, - project_id: ProjectId, - expenditures: Option>, - job_eligibles: Option>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; // origin need to be an admin - - if let Some(mod_expenditures) = expenditures { - Self::do_execute_expenditures(who.clone(), project_id, mod_expenditures)?; - } - - if let Some(mod_job_eligibles) = job_eligibles { - Self::do_execute_job_eligibles(who, project_id, mod_job_eligibles)?; - } - - Ok(()) - } - - // T R A N S A C T I O N S & D R A W D O W N S - // -------------------------------------------------------------------------------------------- - - /// Submit a drawdown - /// This extrinsic is used to create, update or delete transactions. - /// It also allows that an array of transactions to be saved as a draft or as submitted. - /// - /// # Parameters: - /// - origin: The user account who is creating the transactions - /// - project_id: The selected project id where the transactions will be created - /// - drawdown_id: The selected drawdown id where the transactions will be created - /// - transactions: The transactions to be created/updated/deleted. This entry is a vector - /// of tuples - /// where each entry is composed by: - /// * 0: The expenditure id where the transaction will be created - /// * 1: The amount of the transaction - /// * 2: Documents associated to the transaction - /// * 3: The action to be performed on the transaction. (Create, Update or Delete) - /// * 4: The transaction id. This is only used when updating or deleting a transaction. - /// - submit: If true, transactions associated to the selected - /// drawdown will be submitted to the administrator. - /// If false, the array of transactions will be saved as a draft. - /// - /// # Considerations: - /// - This function is only callable by a builder role account - /// - This extrinsic allows multiple transactions to be created/updated/deleted at the same - /// time. - /// - The project id and drawdown id are required for the reports. - /// - Transaction parameters are optional because depends on the action to be performed: - /// * **Create**: Expenditure id, Amount, Documents & action are required. - /// * **Update**: Except for the transaction id & action, all other parameters are optional. - /// * **Delete**: Only the transaction id & action are required. - /// - Multiple actions can be performed at the same time, but each must be performed on - /// a different transaction. For example, you can create a new - /// transaction and update another one at the same time. - /// - Do not perform multiple actions over the same transaction in the same call, it could - /// result in an unexpected behavior. - /// - If a drawdown is submitted, all transactions must be submitted too. If the drawdown do - /// not contain - /// any transaction, it will return an error. - /// - After a drawdown is submitted, it can not be updated or deleted. - /// - After a drawdown is rejected, builders will use again this extrinsic to update the - /// transactions associated to a given drawdown. - #[pallet::call_index(11)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn submit_drawdown( - origin: OriginFor, - project_id: ProjectId, - drawdown_id: DrawdownId, - transactions: Option>, - submit: bool, - ) -> DispatchResult { - let who = ensure_signed(origin)?; // origin need to be an admin - - match submit { - // Save transactions as draft - false => { - // Do execute transactions - Self::do_execute_transactions( - who, - project_id, - drawdown_id, - transactions.ok_or(Error::::TransactionsRequired)?, - ) - }, - // Submit transactions - true => { - // Check if there are transactions to execute - if let Some(mod_transactions) = transactions { - // Ensure transactions are not empty - ensure!(!mod_transactions.is_empty(), Error::::EmptyTransactions); - - // Do execute transactions - Self::do_execute_transactions( - who.clone(), - project_id, - drawdown_id, - mod_transactions, - )?; - } - - // Do submit drawdown - Self::do_submit_drawdown(who, project_id, drawdown_id) - }, - } - } - - /// Approve a drawdown - /// - /// # Parameters: - /// ### For EB5 drawdowns: - /// - origin: The administrator account who is approving the drawdown - /// - project_id: The selected project id where the drawdown will be approved - /// - drawdown_id: The selected drawdown id to be approved - /// - /// ### For Construction Loan & Developer Equity (bulk uploads) drawdowns: - /// - origin: The administrator account who is approving the drawdown - /// - project_id: The selected project id where the drawdown will be approved - /// - drawdown_id: The selected drawdown id to be approved. - /// - bulkupload: Optional bulkupload parameter. If true, the drawdown will be saved in a - /// pseudo - /// draft status. If false, the drawdown will be approved directly. - /// - transactions: The transactions to be created/updated/deleted. This is a vector of - /// tuples - /// where each entry is composed by: - /// * 0: The expenditure id where the transaction will be created - /// * 1: The transaction amount - /// * 2: Documents associated to the transaction - /// * 3: The transaction action to be performed. (Create, Update or Delete) - /// * 4: The transaction id. This is only used when updating or deleting a transaction. - /// - This extrinsic allows multiple transactions to be created/updated/deleted at the same - /// time - /// (only for Construction Loan & Developer Equity drawdowns). - /// - Transaction parameters are optional because depends on the action to be performed: - /// * **Create**: Expenditure id, Amount, Documents & action are required. - /// * **Update**: Except for the transaction id & action, all parameters are optional. - /// * **Delete**: Only the transaction id & action are required. - /// - Multiple actions can be performed at the same time. For example, you can create a new - /// transaction and update another one at the same time (only for Construction Loan & - /// Developer Equity drawdowns). - /// - Do not perform multiple actions over the same transaction in the same call, it could - /// result in an unexpected behavior (only for Construction Loan & Developer Equity - /// drawdowns). - /// - /// # Considerations: - /// - This function is only callable by an administrator account - /// - All transactions associated to the drawdown will be approved too. It's - /// not possible to approve a drawdown without approving all of its transactions. - /// - After a drawdown is approved, it can not be updated or deleted. - /// - After a drawdown is approved, the next drawdown will be automatically created. - /// - The drawdown status will be updated to "Approved" after the extrinsic is executed. - /// - After a drawdown is rejected, administrators will use again this extrinsic to approve - /// the - /// new drawdown version uploaded by the builder. - #[pallet::call_index(12)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn approve_drawdown( - origin: OriginFor, - project_id: ProjectId, - drawdown_id: DrawdownId, - bulkupload: Option, - transactions: Option>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; // origin need to be an admin - - // Match bulkupload parameter - match bulkupload { - Some(approval) => { - // Ensure admin permissions - Self::is_authorized( - who.clone(), - &project_id, - ProxyPermission::ApproveDrawdown, - )?; - - // Execute bulkupload flow (construction loan & developer equity) - match approval { - false => { - // 1. Do execute transactions - Self::do_execute_transactions( - who.clone(), - project_id, - drawdown_id, - transactions.ok_or(Error::::TransactionsRequired)?, - )?; - - // 2. Do submit drawdown - Self::do_submit_drawdown(who, project_id, drawdown_id) - }, - true => { - // 1.Execute transactions if provided - if let Some(mod_transactions) = transactions { - // Ensure transactions are not empty - ensure!( - !mod_transactions.is_empty(), - Error::::EmptyTransactions - ); - - // Do execute transactions - Self::do_execute_transactions( - who.clone(), - project_id, - drawdown_id, - mod_transactions, - )?; - - // 2. Submit drawdown - Self::do_submit_drawdown(who.clone(), project_id, drawdown_id)?; - } - - // 3. Approve drawdown - Self::do_approve_drawdown(who, project_id, drawdown_id) - }, - } - }, - None => { - // Execute normal flow (EB5) - Self::do_approve_drawdown(who, project_id, drawdown_id) - }, - } - } - - /// Reject a drawdown - /// - /// # Parameters: - /// - origin: The administrator account who is rejecting the drawdown - /// - project_id: The selected project id where the drawdown will be rejected - /// - drawdown_id: The selected drawdown id to be rejected - /// - /// Then the next two feedback parameters are optional because depends on the drawdown type: - /// #### EB5 drawdowns: - /// - transactions_feedback: Administrator will provide feedback for each rejected - /// transacion. This is a vector of tuples where each entry is composed by: - /// * 0: The transaction id - /// * 1: The transaction feedback - /// - /// #### Construction Loan & Developer Equity drawdowns: - /// - drawdown_feedback: Administrator will provide feedback for the WHOLE drawdown. - /// - /// # Considerations: - /// - This function can only be called by an administrator account - /// - All transactions associated to the drawdown will be rejected too. It's - /// not possible to reject a drawdown without rejecting all of its transactions. - /// (only for EB5 drawdowns). - /// - For EB5 drawdowns, the administrator needs to provide feedback for - /// each rejected transaction. - /// - For Construction Loan & Developer Equity drawdowns, the administrator can provide - /// feedback for the WHOLE drawdown. - /// - After a builder re-submits a drawdown, the administrator will have to review - /// the drawdown again. - /// - After a builder re-submits a drawdown, the feedback field will be cleared - /// automatically. - /// - If a single EB5 transaction is wrong, the administrator WILL reject the WHOLE - /// drawdown. - /// There is no way to reject a single transaction. - #[pallet::call_index(13)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn reject_drawdown( - origin: OriginFor, - project_id: ProjectId, - drawdown_id: DrawdownId, - transactions_feedback: Option>, - drawdown_feedback: Option, - ) -> DispatchResult { - let who = ensure_signed(origin)?; // origin need to be an admin - - Self::do_reject_drawdown( - who, - project_id, - drawdown_id, - transactions_feedback, - drawdown_feedback, - ) - } - - /// Bulk upload drawdowns. - /// - /// # Parameters: - /// - origin: The administrator account who is uploading the drawdowns - /// - project_id: The selected project id where the drawdowns will be uploaded - /// - drawdown_id: The drawdowns to be uploaded - /// - description: The description of the drawdown provided by the builder - /// - total_amount: The total amount of the drawdown - /// - documents: The documents provided by the builder for the drawdown - /// - /// # Considerations: - /// - This function can only be called by a builder account - /// - This extrinsic allows only one drawdown to be uploaded at the same time. - /// - The drawdown will be automatically submitted. - /// - Only available for Construction Loan & Developer Equity drawdowns. - /// - After a builder uploads a drawdown, the administrator will have to review it. - /// - After a builder re-submits a drawdown, the feedback field will be cleared - /// automatically. - /// - Bulkuploads does not allow individual transactions. - /// - After a builder uploads a drawdown, the administrator will have to - /// insert each transaction manually. - #[pallet::call_index(14)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn up_bulkupload( - origin: OriginFor, - project_id: ProjectId, - drawdown_id: DrawdownId, - description: FieldDescription, - total_amount: TotalAmount, - documents: Documents, - ) -> DispatchResult { - let who = ensure_signed(origin)?; // origin need to be a builder - - Self::do_up_bulk_upload( - who, - project_id, - drawdown_id, - description, - total_amount, - documents, - ) - } - - /// Modifies the inflation rate of a project. - /// - /// # Parameters: - /// - origin: The administrator account who is modifying the inflation rate - /// - projects: The projects where the inflation rate will be modified. - /// This is a vector of tuples where each entry is composed by: - /// * 0: The project id - /// * 1: The inflation rate - /// * 2: The action to be performed (Create, Update or Delete) - /// - /// # Considerations: - /// - This function can only be called by an administrator account - /// - This extrinsic allows multiple projects to be modified at the same time. - /// - The inflation rate can be created, updated or deleted. - /// - The inflation rate is optional because depends on the CUDAction parameter: - /// * **Create**: The inflation rate will be created. Project id, inflation rate and action - /// are required. - /// * **Update**: The inflation rate will be updated. Project id, inflation rate and action - /// are required. - /// * **Delete**: The inflation rate will be deleted. Project id and action are required. - /// - The inflation rate can only be modified if the project is in the "started" status. - #[pallet::call_index(15)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn inflation_rate( - origin: OriginFor, - projects: ProjectsInflation, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - Self::do_execute_inflation_adjustment(who, projects) - } - - // R E V E N U E S - // -------------------------------------------------------------------------------------------- - - /// This extrinsic is used to create, update or delete revenue transactions. - /// It also allows that an array of revenue transactions - /// to be saved as a draft or as submitted. - /// - /// # Parameters: - /// */ - origin: The user account who is creating the revenue transactions - /// - project_id: The selected project id where the revenue transactions will be created - /// - revenue_id: The selected revenue id where the revenue transactions will be created - /// - revenue_transactions: The revenue transactions to be created/updated/deleted. - /// This entry is a vector of tuples where each entry is composed by: - /// * 0: The job eligible id where the revenue transaction will be created - /// * 1: The amount of the revenue transaction - /// * 2: Documents associated to the revenue transaction - /// * 3: The action to be performed on the revenue transaction (Create, Update or Delete) - /// * 4: The revenue transaction id. This is required only if the action is being updated or - /// deleted. - /// - submit: If true, the array of revenue transactions will be submitted to the - /// administrator. - /// If false, the array of revenue transactions will be saved as a draft. - /// - /// # Considerations: - /// - This function is only callable by a builder role account - /// - This extrinsic allows multiple revenue transactions to be created/updated/deleted at - /// the same time. - /// - The project id and revenue id are required for the reports. - /// - revenue_transactions parameters are optional because depends on the action to be - /// performed: - /// * **Create**: Job eligible id, Amount, Documents & action are required. - /// * **Update**: Except for the revenue transaction id & action, all other parameters are - /// optional. - /// * **Delete**: Only the revenue transaction id & action are required. - /// - Multiple actions can be performed at the same time, but each must be performed on - /// a different transaction. For example, you can create a new - /// transaction and update another one at the same time. - /// - Do not perform multiple actions over the same transaction in the same call, it could - /// result in an unexpected behavior. - /// - If a revenue is submitted, all transactions must be submitted too. If the revenue do - /// not contain - /// any transaction, it will return an error. - /// - After a revenue is submitted, it can not be updated or deleted. - /// - After a revenue is rejected, builders will use again this extrinsic to update the - /// transactions associated to a given revenue. - #[pallet::call_index(16)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn submit_revenue( - origin: OriginFor, - project_id: ProjectId, - revenue_id: RevenueId, - revenue_transactions: Option>, - submit: bool, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - match submit { - // Save revenue transactions as draft - false => { - // Do execute transactions - Self::do_execute_revenue_transactions( - who, - project_id, - revenue_id, - revenue_transactions.ok_or(Error::::RevenueTransactionsRequired)?, - ) - }, - // Submit revenue transactions - true => { - // Check if there are transactions to execute - if let Some(mod_revenue_transactions) = revenue_transactions { - // Ensure transactions are not empty - ensure!( - !mod_revenue_transactions.is_empty(), - Error::::RevenueTransactionsEmpty - ); - - // Do execute transactions - Self::do_execute_revenue_transactions( - who.clone(), - project_id, - revenue_id, - mod_revenue_transactions, - )?; - } - - // Do submit revenue - Self::do_submit_revenue(who, project_id, revenue_id) - }, - } - } - - /// Approve a revenue - /// - /// # Parameters: - /// - origin: The administrator account who is approving the revenue - /// - project_id: The selected project id where the revenue will be approved - /// - revenue_id: The selected revenue id to be approved - /// - /// # Considerations: - /// - This function is only callable by an administrator role account - /// - All transactions associated to the revenue will be approved too. It's - /// not possible to approve a revenue without approving all of its transactions. - /// - After a revenue is approved, it can not be updated or deleted. - /// - After a revenue is approved, the next revenue will be created automatically. - /// - After a revenue is rejected, administrators will use again this extrinsic to approve - /// the rejected revenue - /// new revenue version uploaded by the builder. - /// - The revenue status will be updated to Approved. - #[pallet::call_index(17)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn approve_revenue( - origin: OriginFor, - project_id: ProjectId, - revenue_id: RevenueId, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - Self::do_approve_revenue(who, project_id, revenue_id) - } - - /// Reject a revenue - /// - /// # Parameters: - /// - origin: The administrator account who is rejecting the revenue - /// - project_id: The selected project id where the revenue will be rejected - /// - revenue_id: The selected revenue id to be rejected - /// - revenue_transactions_feedback: Administrator will provide feedback for each rejected - /// transacion. This is a vector of tuples where each entry is composed by: - /// * 0: The revenue transaction id - /// * 1: The revenue transaction feedback - /// - /// # Considerations: - /// - This function is only callable by an administrator role account - /// - All transactions associated to the revenue will be rejected too. It's - /// not possible to reject a revenue without rejecting all of its transactions. - /// - Administrator needs to provide a feedback for each rejected transaction. - /// - After a builder re-submits a revenue, the feedback field will be cleared - /// automatically. - /// - If a single revenue transaction is wrong, the administrator WILL reject the WHOLE - /// revenue. - /// There is no way to reject a single revenue transaction. - #[pallet::call_index(18)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn reject_revenue( - origin: OriginFor, - project_id: ProjectId, - revenue_id: RevenueId, - revenue_transactions_feedback: TransactionsFeedback, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - Self::do_reject_revenue(who, project_id, revenue_id, revenue_transactions_feedback) - } - - /// The following extrinsic is used to upload the bank confirming documents - /// for a given drawdown. - /// - /// # Parameters: - /// - origin: The administrator account who is uploading the confirming documents - /// - project_id: The selected project id where the drawdown exists - /// - drawdown_id: The selected drawdown id where the confirming documents will be uploaded - /// - confirming_documents: The confirming documents to be uploaded. This field is optional - /// because are required only when the action is Create or Update. - /// - action: The action to be performed. It can be Create, Update or Delete - /// * Create: project_id, drawdown_id and confirming_documents are required - /// * Update: project_id, drawdown_id and confirming_documents are required - /// * Delete: project_id and drawdown_id are required - /// - /// # Considerations: - /// - This function is only callable by an administrator role account - /// - The confirming documents are required only when the action is Create or Update. - /// - The confirming documents are optional when the action is Delete. - /// - After the confirming documents are uploaded, the drawdown status will be updated to - /// "Confirmed". It will also update the status of all of its transactions to "Confirmed". - /// - Update action will replace the existing confirming documents with the new ones. - /// - Delete action will remove the existing confirming documents. It will also update the - /// drawdown status to "Approved" and the status of all of its transactions to "Approved". - /// It does a rollback of the drawdown. - #[pallet::call_index(19)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn bank_confirming_documents( - origin: OriginFor, - project_id: ProjectId, - drawdown_id: DrawdownId, - confirming_documents: Option>, - action: CUDAction, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - Self::do_bank_confirming_documents( - who, - project_id, - drawdown_id, - confirming_documents, - action, - ) - } - - /// The following extrinsic is used to cancel a drawdown submission. - /// - /// # Parameters: - /// - origin: The builder account who is cancelling the drawdown submission - /// - project_id: The selected project id where the drawdown exists - /// - drawdown_id: The selected drawdown id to be cancelled - /// - /// # Considerations: - /// - This function is only callable by a builder role account - /// - The drawdown status will be rolled back to "Draft". - /// - All of its transactions will be deleted. - /// - The whole drawdown will be reset to its initial state, so be careful when using this - #[pallet::call_index(20)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn reset_drawdown( - origin: OriginFor, - project_id: ProjectId, - drawdown_id: DrawdownId, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - Self::do_reset_drawdown(who, project_id, drawdown_id) - } - - /// Kill all the stored data. - /// - /// This function is used to kill ALL the stored data. - /// Use it with caution! - /// - /// ### Parameters: - /// - `origin`: The user who performs the action. - /// - /// ### Considerations: - /// - This function is only available to the `admin` with sudo access. - #[pallet::call_index(22)] - #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] - pub fn kill_storage(origin: OriginFor) -> DispatchResult { - T::RemoveOrigin::ensure_origin(origin.clone())?; - let _ = >::kill(); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - let _ = >::clear(1000, None); - - T::Rbac::remove_pallet_storage(Self::pallet_id())?; - Ok(()) - } - } + #[pallet::constant] + type MaxProjectsPerUser: Get; + + #[pallet::constant] + type MaxUserPerProject: Get; + + #[pallet::constant] + type MaxBuildersPerProject: Get; + + #[pallet::constant] + type MaxInvestorsPerProject: Get; + + #[pallet::constant] + type MaxIssuersPerProject: Get; + + #[pallet::constant] + type MaxRegionalCenterPerProject: Get; + + #[pallet::constant] + type MaxDrawdownsPerProject: Get; + + #[pallet::constant] + type MaxTransactionsPerDrawdown: Get; + + #[pallet::constant] + type MaxRegistrationsAtTime: Get; + + #[pallet::constant] + type MaxExpendituresPerProject: Get; + + #[pallet::constant] + type MaxProjectsPerInvestor: Get; + + #[pallet::constant] + type MaxBanksPerProject: Get; + + #[pallet::constant] + type MaxJobEligiblesByProject: Get; + + #[pallet::constant] + type MaxRevenuesByProject: Get; + + #[pallet::constant] + type MaxTransactionsPerRevenue: Get; + + #[pallet::constant] + type MaxStatusChangesPerDrawdown: Get; + + #[pallet::constant] + type MaxStatusChangesPerRevenue: Get; + + #[pallet::constant] + type MaxRecoveryChanges: Get; + + #[pallet::constant] + type MinAdminBalance: Get>; + + #[pallet::constant] + type TransferAmount: Get>; + } + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + /* --- Onchain storage section --- */ + + #[pallet::storage] + #[pallet::getter(fn global_scope)] + pub(super) type GlobalScope = StorageValue< + _, + [u8; 32], // Value global scope id + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn users_info)] + pub(super) type UsersInfo = StorageMap< + _, + Blake2_128Concat, + T::AccountId, // Key account_id + UserData, // Value UserData + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn projects_info)] + pub(super) type ProjectsInfo = StorageMap< + _, + Identity, + ProjectId, // Key project_id + ProjectData, // Value ProjectData + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn users_by_project)] + pub(super) type UsersByProject = StorageMap< + _, + Identity, + ProjectId, // Key project_id + BoundedVec, // Value users + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn projects_by_user)] + pub(super) type ProjectsByUser = StorageMap< + _, + Blake2_128Concat, + T::AccountId, // Key account_id + BoundedVec<[u8; 32], T::MaxProjectsPerUser>, // Value projects + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn expenditures_info)] + pub(super) type ExpendituresInfo = StorageMap< + _, + Identity, + ExpenditureId, // Key expenditure_id + ExpenditureData, // Value ExpenditureData + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn expenditures_by_project)] + pub(super) type ExpendituresByProject = StorageMap< + _, + Identity, + ProjectId, // Key project_id + BoundedVec<[u8; 32], T::MaxExpendituresPerProject>, // Value expenditures + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn drawdowns_info)] + pub(super) type DrawdownsInfo = StorageMap< + _, + Identity, + DrawdownId, // Key drawdown id + DrawdownData, // Value DrawdownData + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn drawdowns_by_project)] + pub(super) type DrawdownsByProject = StorageMap< + _, + Identity, + ProjectId, // Key project_id + BoundedVec, // Value Drawdowns + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn transactions_info)] + pub(super) type TransactionsInfo = StorageMap< + _, + Identity, + TransactionId, // Key transaction id + TransactionData, // Value TransactionData + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn transactions_by_drawdown)] + pub(super) type TransactionsByDrawdown = StorageDoubleMap< + _, + Identity, + ProjectId, //K1: project id + Identity, + DrawdownId, //K2: drawdown id + BoundedVec, // Value transactions + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn job_eligibles_info)] + pub(super) type JobEligiblesInfo = StorageMap< + _, + Identity, + JobEligibleId, // Key transaction id + JobEligibleData, // Value JobEligibleData + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn job_eligibles_by_project)] + pub(super) type JobEligiblesByProject = StorageMap< + _, + Identity, + ProjectId, // Key project_id + BoundedVec, // Value job eligibles + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn revenues_info)] + pub(super) type RevenuesInfo = StorageMap< + _, + Identity, + RevenueId, // Key revenue id + RevenueData, // Value RevenueData + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn revenues_by_project)] + pub(super) type RevenuesByProject = StorageMap< + _, + Identity, + ProjectId, // Key project_id + BoundedVec, // Value Revenues + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn revenue_transactions_info)] + pub(super) type RevenueTransactionsInfo = StorageMap< + _, + Identity, + RevenueTransactionId, // Key revenue transaction id + RevenueTransactionData, // Value RevenueTransactionData + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn transactions_by_revenue)] + pub(super) type TransactionsByRevenue = StorageDoubleMap< + _, + Identity, + ProjectId, //K1: project id + Identity, + RevenueId, //K2: revenue id + BoundedVec, // Value revenue transactions + ValueQuery, + >; + + // E V E N T S + // ------------------------------------------------------------------------------------------------------------ + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Proxy initial setup completed using the sudo pallet + ProxySetupCompleted, + /// Project was created successfully + ProjectCreated(T::AccountId, ProjectId), + /// The selected roject was edited successfully + ProjectEdited(T::AccountId, ProjectId), + /// The selected project was deleted successfully + ProjectDeleted(T::AccountId, ProjectId), + /// Administrator was registered successfully using the sudo pallet + AdministratorAssigned(T::AccountId), + /// Administrator was removed successfully using the sudo pallet + AdministratorRemoved(T::AccountId), + /// The user was assigned to the selected project + UserAssignmentCompleted(T::AccountId, ProjectId), + /// The user was unassigned to the selected project + UserUnassignmentCompleted(T::AccountId, ProjectId), + /// Users extrinsic was executed, individual CUDActions were applied + UsersExecuted(T::AccountId), + /// A new user account was created successfully + UserCreated(T::AccountId), + /// The selected user was edited successfully + UserUpdated(T::AccountId), + /// The selected user was deleted successfully + UserDeleted(T::AccountId), + /// An array of expenditures was executed depending on the CUDAction + ExpendituresExecuted(T::AccountId, ProjectId), + /// Expenditure was created successfully + ExpenditureCreated(ProjectId, ExpenditureId), + /// Expenditure was updated successfully + ExpenditureUpdated(ProjectId, ExpenditureId), + /// Expenditure was deleted successfully + ExpenditureDeleted(ProjectId, ExpenditureId), + /// An array of transactions was executed depending on the CUDAction + TransactionsExecuted(ProjectId, DrawdownId), + /// Transaction was created successfully + TransactionCreated(ProjectId, DrawdownId, TransactionId), + /// Transaction was edited successfully + TransactionEdited(ProjectId, DrawdownId, TransactionId), + /// Transaction was deleted successfully + TransactionDeleted(ProjectId, DrawdownId, TransactionId), + /// Assign users extrinsic was completed successfully + UsersAssignationExecuted(T::AccountId, ProjectId), + /// Drawdowns were initialized successfully at the beginning of the project + DrawdownsInitialized(T::AccountId, ProjectId), + /// Drawdown was created successfully + DrawdownCreated(ProjectId, DrawdownId), + /// Drawdown was submitted successfully + DrawdownSubmitted(ProjectId, DrawdownId), + /// Drawdown was approved successfully + DrawdownApproved(ProjectId, DrawdownId), + /// Drawdown was rejected successfully + DrawdownRejected(ProjectId, DrawdownId), + /// Drawdown was cancelled successfully + DrawdownSubmissionCancelled(ProjectId, DrawdownId), + /// Bulkupload drawdown was submitted successfully + BulkUploadSubmitted(ProjectId, DrawdownId), + /// An array of adjustments was executed depending on the CUDAction + InflationRateAdjusted(T::AccountId), + /// An array of job eligibles was executed depending on the CUDAction + JobEligiblesExecuted(T::AccountId, ProjectId), + /// Job eligible was created successfully + JobEligibleCreated(ProjectId, JobEligibleId), + /// Job eligible was updated successfully + JobEligibleUpdated(ProjectId, JobEligibleId), + /// Job eligible was deleted successfully + JobEligibleDeleted(ProjectId, JobEligibleId), + /// Revenue transaction was created successfully + RevenueTransactionCreated(ProjectId, RevenueId, RevenueTransactionId), + /// Revenue transaction was updated successfully + RevenueTransactionUpdated(ProjectId, RevenueId, RevenueTransactionId), + /// Revenue transaction was deleted successfully + RevenueTransactionDeleted(ProjectId, RevenueId, RevenueTransactionId), + /// An array of revenue transactions was executed depending on the CUDAction + RevenueTransactionsExecuted(ProjectId, RevenueId), + /// Revenue was created successfully + RevenueCreated(ProjectId, RevenueId), + /// Revenue was submitted successfully + RevenueSubmitted(ProjectId, RevenueId), + /// Revenue was approved successfully + RevenueApproved(ProjectId, RevenueId), + /// Revenue was rejected successfully + RevenueRejected(ProjectId, RevenueId), + /// Bank's confirming documents were uploaded successfully + BankDocumentsUploaded(ProjectId, DrawdownId), + /// Bank's confirming documents were updated successfully + BankDocumentsUpdated(ProjectId, DrawdownId), + /// Bank's confirming documents were deleted successfully + BankDocumentsDeleted(ProjectId, DrawdownId), + /// Error recovery for revenues was executed successfully + RevenueErrorRecoveryExecuted(ProjectId, RevenueId), + /// Error recovery for drawdowns was executed successfully + DrawdownErrorRecoveryExecuted(ProjectId, DrawdownId), + } + + // E R R O R S + // ------------------------------------------------------------------------------------------------------------ + #[pallet::error] + pub enum Error { + /// FieldName is empty + EmptyFieldName, + /// FieldDescription is empty + EmptyFieldDescription, + /// FieldName is too long + FieldNameTooLong, + /// Array of users is empty + EmptyUsers, + /// CID is empty + EmptyFieldCID, + /// Array of banks is empty + EmptyFieldBanks, + /// The private group id is empty + PrivateGroupIdEmpty, + /// Array of users to be assigned to a project is empty + EmptyUsersAssignation, + /// Field address project is empty + EmptyProjectAddress, + /// No value was found for the global scope + NoGlobalScopeValueWasFound, + /// Project ID is already in use + ProjectIdAlreadyInUse, + /// Timestamp was not genereated correctly + TimestampError, + /// Completion date must be later than creation date + CompletionDateMustBeLater, + /// User is already registered in the site + UserAlreadyRegistered, + /// Project was not found + ProjectNotFound, + /// Project is not active anymore + ProjectIsAlreadyCompleted, + /// Project has no drawdowns + ProjectHasNoDrawdowns, + /// Project has no expenditures + ProjectHasNoExpenditures, + /// Project has no users + ProjectHasNoUsers, + /// Project has no job eligibles + ProjectHasNoJobEligibles, + /// Project has no revenues + ProjectHasNoRevenues, + /// Can not delete a completed project + CannotDeleteCompletedProject, + /// User is not registered + UserNotRegistered, + /// User has been already added to the project + UserAlreadyAssignedToProject, + /// Max number of users per project reached + MaxUsersPerProjectReached, + /// Max number of projects per user reached + MaxProjectsPerUserReached, + /// User is not assigned to the project + UserNotAssignedToProject, + /// Can not register administrator role + CannotRegisterAdminRole, + /// Max number of builders per project reached + MaxBuildersPerProjectReached, + /// Max number of investors per project reached + MaxInvestorsPerProjectReached, + /// Max number of issuers per project reached + MaxIssuersPerProjectReached, + /// Max number of regional centers per project reached + MaxRegionalCenterPerProjectReached, + /// Can not remove administrator role + CannotRemoveAdminRole, + /// Can not add admin role at user project assignment + CannotAddAdminRole, + /// User can not have more than one role at the same time + UserCannotHaveMoreThanOneRole, + /// Expenditure not found + ExpenditureNotFound, + /// Expenditure not found for the selected project_id + ExpenditureNotFoundForSelectedProjectId, + /// Expenditure already exist + ExpenditureAlreadyExists, + /// Expenditure is already in a transaction + ExpenditureHasNonZeroTransactions, + /// Max number of expenditures per project reached + MaxExpendituresPerProjectReached, + /// Field name can not be empty + EmptyExpenditureName, + /// Expenditure does not belong to the project + ExpenditureDoesNotBelongToProject, + /// Drawdown id is not found + DrawdownNotFound, + /// Invalid amount + InvalidAmount, + /// Documents field is empty + DocumentsEmpty, + /// Transaction id is not found + TransactionNotFound, + /// Transaction was not found for the selected Drawdown_id + TransactionNotFoundForSelectedDrawdownId, + /// Transaction already exist + TransactionAlreadyExists, + /// Transaction is already in a drawdown + TransactionInUse, + /// Max number of transactions per drawdown reached + MaxTransactionsPerDrawdownReached, + /// Drawdown already exist + DrawdownAlreadyExists, + /// Max number of drawdowns per project reached + MaxDrawdownsPerProjectReached, + /// Max number of status changes per drawdown reached + MaxStatusChangesPerDrawdownReached, + /// Max number of recovery chnages per drawdown reached + MaxRecoveryChangesReached, + /// Can not modify a completed drawdown + CannotEditDrawdown, + /// Can not perform any action on a submitted transaction + CannotPerformActionOnSubmittedTransaction, + /// Can not perform any action on a approved transaction + CannotPerformActionOnApprovedTransaction, + /// Can not perform any action on a confirmed transaction + CannotPerformActionOnConfirmedTransaction, + /// Can not perform any action on a submitted drawdown + CannotPerformActionOnSubmittedDrawdown, + /// Can not perform any action on a approved drawdown + CannotPerformActionOnApprovedDrawdown, + /// Can not perform any action on a confirmed drawdown + CannotPerformActionOnConfirmedDrawdown, + /// Transaction is already completed + TransactionIsAlreadyCompleted, + /// User does not have the specified role + UserDoesNotHaveRole, + /// Transactions vector is empty + EmptyTransactions, + /// Transactions are required for the current workflow + TransactionsRequired, + /// Transaction ID was not found in do_execute_transaction + TransactionIdRequired, + /// Drawdown can not be submitted if does not has any transactions + DrawdownHasNoTransactions, + /// Cannot submit transaction + CannotSubmitTransaction, + /// Drawdown can not be approved if is not in submitted status + DrawdownIsNotInSubmittedStatus, + /// Transactions is not in submitted status + TransactionIsNotInSubmittedStatus, + /// Array of expenditures is empty + EmptyExpenditures, + /// Expenditure name is required + ExpenditureNameRequired, + /// Expenditure type is required + ExpenditureTypeRequired, + /// Expenditure amount is required + ExpenditureAmountRequired, + /// Expenditure id is required + ExpenditureIdRequired, + /// User name is required + UserNameRequired, + /// User role is required + UserRoleRequired, + /// User image is required + UserImageRequired, + /// User email is required + UserEmailRequired, + /// Amount is required + AmountRequired, + /// Can not delete a user if the user is assigned to a project + UserHasAssignedProjects, + /// User has no projects assigned + UserHasNoProjects, + /// Can not send a drawdown to submitted status if it has no transactions + NoTransactionsToSubmit, + /// Bulk upload description is required + BulkUploadDescriptionRequired, + /// Bulk upload documents are required + BulkUploadDocumentsRequired, + /// Administrator can not delete themselves + AdministratorsCannotDeleteThemselves, + /// No feedback was provided for bulk upload + NoFeedbackProvidedForBulkUpload, + /// Bulkupload feedback is empty + EmptyBulkUploadFeedback, + /// NO feedback for EN5 drawdown was provided + EB5MissingFeedback, + /// EB5 feedback is empty + EmptyEb5Feedback, + /// Inflation rate extrinsic is missing an array of project ids + ProjectsInflationRateEmpty, + /// Inflation rate was not provided + InflationRateRequired, + /// Inflation rate has been already set for the selected project + InflationRateAlreadySet, + /// Inflation rate was not set for the selected project + InflationRateNotSet, + /// Bulkupload drawdowns are only allowed for Construction Loan & Developer Equity + DrawdownTypeNotSupportedForBulkUpload, + /// Cannot edit user role if the user is assigned to a project + UserHasAssignedProjectsCannotUpdateRole, + /// Cannot delete user if the user is assigned to a project + UserHasAssignedProjectsCannotDelete, + /// Cannot send a bulkupload drawdown if the drawdown status isn't in draft or rejected + DrawdownStatusNotSupportedForBulkUpload, + /// Cannot submit a drawdown if the drawdown status isn't in draft or rejected + DrawdownIsNotInDraftOrRejectedStatus, + /// Only investors can update/edit their documents + UserIsNotAnInvestor, + /// Max number of projects per investor has been reached + MaxProjectsPerInvestorReached, + /// Jobs eligibles array is empty + JobEligiblesEmpty, + /// JOb eligible name is empty + JobEligiblesNameRequired, + /// Job eligible id already exists + JobEligibleIdAlreadyExists, + /// Max number of job eligibles per project reached + MaxJobEligiblesPerProjectReached, + /// Job eligible id not found + JobEligibleNotFound, + /// Jopb eligible does not belong to the project + JobEligibleDoesNotBelongToProject, + /// Job eligible name is required + JobEligibleNameRequired, + /// Job eligible amount is required + JobEligibleAmountRequired, + /// Job eligible id is required + JobEligibleIdRequired, + /// Job eligible not found for the given project id + JobEligibleNotFoundForSelectedProjectId, + /// Job eligible has non zero transactions + JobEligibleHasNonZeroTransactions, + /// Revenue id was not found + RevenueNotFound, + /// Transactions revenue array is empty + RevenueTransactionsEmpty, + /// An array of revenue transactions is required + RevenueTransactionsRequired, + /// Revenue transaction is not in submitted status + RevenueTransactionNotSubmitted, + /// Revenue can not be edited + CannotEditRevenue, + /// Revenue transaction id already exists + RevenueTransactionIdAlreadyExists, + /// Max number of transactions per revenue reached + MaxTransactionsPerRevenueReached, + /// Revenue transaction id not found + RevenueTransactionNotFound, + /// Revenue transaction was not found for the selected revenue_id + RevenueTransactionNotFoundForSelectedRevenueId, + /// Revenue transaction can not be edited + CannotEditRevenueTransaction, + /// Max number of status changes per revenue reached + MaxStatusChangesPerRevenueReached, + /// Can not perform any action on a submitted revenue + CannotPerformActionOnSubmittedRevenue, + /// Can not perform any action on a approved revenue + CannotPerformActionOnApprovedRevenue, + /// Can not perform any action on a submitted revenue transaction + CannotPerformActionOnApprovedRevenueTransaction, + /// Can not perform any action on a approved revenue transaction + CannotPerformActionOnSubmittedRevenueTransaction, + /// Revenue amoun is required + RevenueAmountRequired, + /// Revenue transaction id is required + RevenueTransactionIdRequired, + /// Revenue Id already exists + RevenueIdAlreadyExists, + /// Maximun number of revenues per project reached + MaxRevenuesPerProjectReached, + /// Can not send a revenue to submitted status if it has no transactions + RevenueHasNoTransactions, + /// Revenue is not in submitted status + RevenueIsNotInSubmittedStatus, + /// Revenue transaction is not in submitted status + RevenueTransactionIsNotInSubmittedStatus, + /// Revenue transactions feedback is empty + RevenueTransactionsFeedbackEmpty, + /// The revenue is not in submitted status + RevenueNotSubmitted, + /// The revenue id does not belong to the project + RevenueDoesNotBelongToProject, + /// Can not upload bank confirming documents if the drawdown is not in Approved status + DrawdowMustBeInApprovedStatus, + /// Drawdown is not in Confirmed status + DrawdowMustBeInConfirmedStatus, + /// Drawdown is not in Submitted status + DrawdownNotSubmitted, + /// Can not insert (CUDAction: Create) bank confmirng documents if the drawdown has already bank confirming documents + DrawdownHasAlreadyBankConfirmingDocuments, + /// Drawdown has no bank confirming documents (CUDAction: Update or Delete) + DrawdownHasNoBankConfirmingDocuments, + /// Drawdown id does not belong to the selected project + DrawdownDoesNotBelongToProject, + /// Bank confirming documents are required + BankConfirmingDocumentsNotProvided, + /// Banck confirming documents array is empty + BankConfirmingDocumentsEmpty, + /// Only eb5 drawdowns are allowed to upload bank documentation + OnlyEB5DrawdownsCanUploadBankDocuments, + /// Maximun number of registrations at a time reached + MaxRegistrationsAtATimeReached, + /// Administrator account has insuficiente balance to register a new user + AdminHasNoFreeBalance, + /// Administrator account has insuficiente balance to register a new user + InsufficientFundsToTransfer, + } + + // E X T R I N S I C S + // ------------------------------------------------------------------------------------------------------------ + #[pallet::call] + impl Pallet { + // I N I T I A L + // -------------------------------------------------------------------------------------------- + /// Initialize the pallet by setting the permissions for each role + /// & the global scope + /// + /// # Considerations: + /// - This function can only be called once + /// - This function can only be called usinf the sudo pallet + #[pallet::call_index(1)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn initial_setup(origin: OriginFor) -> DispatchResult { + T::RemoveOrigin::ensure_origin(origin.clone())?; + Self::do_initial_setup()?; + Ok(()) + } + + /// Adds an administrator account to the site + /// + /// # Parameters: + /// - origin: The sudo account + /// - admin: The administrator account to be added + /// - name: The name of the administrator account + /// + /// # Considerations: + /// - This function can only be called using the sudo pallet + /// - This function is used to add the first administrator to the site + /// - If the user is already registered, the function will return an error: UserAlreadyRegistered + /// - This function grants administrator permissions to the user from the rbac pallet + /// - administrator role have global scope permissions + #[pallet::call_index(2)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn sudo_add_administrator( + origin: OriginFor, + admin: T::AccountId, + name: FieldName, + ) -> DispatchResult { + T::RemoveOrigin::ensure_origin(origin.clone())?; + Self::do_sudo_add_administrator(admin, name)?; + Ok(()) + } + + /// Removes an administrator account from the site + /// + /// # Parameters: + /// - origin: The sudo account + /// - admin: The administrator account to be removed + /// + /// # Considerations: + /// - This function can only be called using the sudo pallet + /// - This function is used to remove any administrator from the site + /// - If the user is not registered, the function will return an error: UserNotFound + /// - This function removes administrator permissions of the user from the rbac pallet + /// + /// # Note: + /// WARNING: Administrators can remove themselves from the site, + /// but they can add themselves back + #[pallet::call_index(3)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn sudo_remove_administrator(origin: OriginFor, admin: T::AccountId) -> DispatchResult { + T::RemoveOrigin::ensure_origin(origin.clone())?; + Self::do_sudo_remove_administrator(admin)?; + Ok(()) + } + + // U S E R S + // -------------------------------------------------------------------------------------------- + /// This extrinsic is used to create, update, or delete a user account + /// + /// # Parameters: + /// - origin: The administrator account + /// - user: The target user account to be registered, updated, or deleted. + /// It is an array of user accounts where each entry it should be a tuple of the following: + /// - 0: The user account + /// - 1: The user name + /// - 2: The user role + /// - 3: The CUD operation to be performed on the user account. CUD action is ALWAYS + /// required. + /// + /// # Considerations: + /// - Users parameters are optional because depends on the CUD action as follows: + /// * **Create**: The user account, user name, user role & CUD action are required + /// * **Update**: The user account & CUD action are required. The user name & user role are + /// optionals. + /// * **Delete**: The user account & CUD action are required. + /// - This function can only be called by an administrator account + /// - Multiple users can be registered, updated, or deleted at the same time, but + /// the user account must be unique. Multiple actions over the same user account + /// in the same call, it could result in an unexpected behavior. + /// - If the user is already registered, the function will return an error: + /// UserAlreadyRegistered + /// - If the user is not registered, the function will return an error: UserNotFound + /// + /// # Note: + /// - WARNING: It is possible to register, update, or delete administrators accounts using + /// this extrinsic, + /// but administrators can not delete themselves. + /// - WARNING: This function only registers, updates, or deletes users from the site. + /// - WARNING: The only way to grant or remove permissions of a user account is assigning or + /// unassigning + /// a user from a selected project. + #[pallet::call_index(4)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn users(origin: OriginFor, users: Users) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be an admin + + Self::do_execute_users(who, users) + } + + /// Edits an user account + /// + /// # Parameters: + /// - origin: The user account which is being edited + /// - name: The name of the user account which is being edited + /// - image: The image of the user account which is being edited + /// - email: The email of the user account which is being edited + /// - documents: The documents of the user account which is being edited. + /// ONLY available for the investor role. + /// + /// # Considerations: + /// - If the user is not registered, the function will return an error: UserNotFound + /// - This function can only be called by a registered user account + /// - This function will be called by the user account itself + /// - ALL parameters are optional because depends on what is being edited + /// - ONLY the investor role can edit or update the documents + #[pallet::call_index(5)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn users_edit_user( + origin: OriginFor, + name: Option, + image: Option, + email: Option, + documents: Option>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + Self::do_edit_user(who, name, image, email, documents) + } + + // P R O J E C T S + // -------------------------------------------------------------------------------------------- + /// Registers a new project. + /// + /// # Parameters: + /// - origin: The administrator account + /// - title: The title of the project + /// - description: The description of the project + /// - image: The image of the project (CID) + /// - address: The address of the project + /// - creation_date: The creation date of the project + /// - completion_date: The completion date of the project + /// - expenditures: The expenditures of the project. It is an array of tuples where each + /// entry + /// is a tuple of the following: + /// * 0: The expenditure name + /// * 1: The expenditure type + /// * 2: The expenditure amount + /// * 3: The expenditure NAICS code + /// * 4: The expenditure jobs multiplier + /// * 5: The CUD action to be performed on the expenditure. CUD action is ALWAYS required. + /// * 6: The expenditure id. It is optional because it is only required when updating or + /// deleting + /// - job_eligibles: The job eligibles to be created/updated/deleted. This is a vector of + /// tuples + /// where each entry is composed by: + /// * 0: The job eligible name + /// * 1: The amount of the job eligible + /// * 2: The NAICS code of the job eligible + /// * 3: The jobs multiplier of the job eligible + /// * 4: The job eligible action to be performed. (Create, Update or Delete) + /// * 5: The job eligible id. This is only used when updating or deleting a job eligible. + /// - users: The users who will be assigned to the project. It is an array of tuples where + /// each entry + /// is a tuple of the following: + /// * 0: The user account + /// * 1: The user role + /// * 2: The AssignAction to be performed on the user. + /// + /// # Considerations: + /// - This function can only be called by an administrator account + /// - For users assignation, the user account must be registered. If the user is not + /// registered, + /// the function will return an error. ALL parameters are required. + /// - For expenditures, apart from the expenditure id, naics code & jopbs multiplier, ALL + /// parameters are required because for this + /// flow, the expenditures are always created. The naics code & the jobs multiplier + /// can be added later by the administrator. + /// - Creating a project will automatically create a scope for the project. + /// + /// # Note: + /// WARNING: If users are provided, the function will assign the users to the project, + /// granting them permissions in the rbac pallet. + #[pallet::call_index(6)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn projects_create_project( + origin: OriginFor, + title: FieldName, + description: FieldDescription, + image: Option, + address: FieldName, + banks: Option>, + creation_date: CreationDate, + completion_date: CompletionDate, + expenditures: Expenditures, + job_eligibles: Option>, + users: Option>, + private_group_id: PrivateGroupId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be an admin + + Self::do_create_project( + who, + title, + description, + image, + address, + banks, + creation_date, + completion_date, + expenditures, + job_eligibles, + users, + private_group_id, + ) + } + + /// Edits a project. + /// + /// # Parameters: + /// - origin: The administrator account + /// - project_id: The selected project id that will be edited + /// - title: The title of the project to be edited + /// - description: The description of the project to be edited + /// - image: The image of the project to be edited + /// - address: The address of the project to be edited + /// - creation_date: The creation date of the project to be edited + /// - completion_date: The completion date of the project to be edited + /// + /// # Considerations: + /// - This function can only be called by an administrator account + /// - ALL parameters are optional because depends on what is being edited + /// - The project id is required because it is the only way to identify the project + /// - The project id must be registered. If the project is not registered, + /// the function will return an error: ProjectNotFound + /// - It is not possible to edit the expenditures or the users assigned to the project + /// through this function. For that, the administrator must use the extrinsics: + /// * expenditures + /// * projects_assign_user + /// - Project can only be edited in the Started status + /// - Completion date must be greater than creation date + #[pallet::call_index(7)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn projects_edit_project( + origin: OriginFor, + project_id: ProjectId, + title: Option, + description: Option, + image: Option, + address: Option, + banks: Option>, + creation_date: Option, + completion_date: Option, + ) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be an admin + + Self::do_edit_project( + who, + project_id, + title, + description, + image, + address, + banks, + creation_date, + completion_date, + ) + } + + /// Deletes a project. + /// + /// # Parameters: + /// - origin: The administrator account + /// - project_id: The selected project id that will be deleted + /// + /// # Considerations: + /// - This function can only be called by an administrator account + /// - The project id is required because it is the only way to identify the project + /// - The project id must be registered. If the project is not registered, + /// the function will return an error: ProjectNotFound + /// + /// # Note: + /// - WARNING: Deleting a project will also delete ALL stored information associated with + /// the project. + /// BE CAREFUL. + #[pallet::call_index(8)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn projects_delete_project(origin: OriginFor, project_id: ProjectId) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be an admin + + Self::do_delete_project(who, project_id) + } + + /// Assigns a user to a project. + /// + /// # Parameters: + /// - origin: The administrator account + /// - project_id: The selected project id where user will be assigned + /// - users: The users to be assigned to the project. This is a vector of tuples + /// where each entry is composed by: + /// * 0: The user account id + /// * 1: The user role + /// * 2: The AssignAction to be performed. (Assign or Unassign) + /// + /// # Considerations: + /// - This function can only be called by an administrator account + /// - This extrinsic allows multiple users to be assigned/unassigned at the same time. + /// - The project id is required because it is the only way to identify the project + /// - This extrinsic is used for both assigning and unassigning users to a project + /// depending on the AssignAction. + /// - After a user is assigned to a project, the user will be able to perform actions + /// in the project depending on the role assigned to the user. + /// - After a user is unassigned from a project, the user will not be able to perform + /// actions + /// in the project anymore. + /// - If the user is already assigned to the project, the function will return an error. + /// + /// # Note: + /// - WARNING: ALL provided users needs to be registered in the site. If any of the users + /// is not registered, the function will return an error. + /// - Assigning or unassigning a user to a project will add or remove permissions to the + /// user + /// from the RBAC pallet. + /// - Warning: Cannot assign a user to a project with a different role than the one they + /// have in UsersInfo. If the user has a different role, the function will return an error. + /// - Warning: Cannot unassign a user from a project with a different role than the one they + /// have in UsersInfo. If the user has a different role, the function will return an error. + /// - Warning: Do not perform multiple actions over the same user in the same call, it could + /// result in an unexpected behavior. + #[pallet::call_index(9)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn projects_assign_user( + origin: OriginFor, + project_id: ProjectId, + users: UsersAssignation, + ) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be an admin + + Self::do_execute_assign_users(who, project_id, users) + } + + // B U D G E T E X P E N D I T U R E & J O B E L I G I B L E S + // -------------------------------------------------------------------------------------------- + /// This extrinsic is used to create, update or delete expenditures & job eligibles. + /// + /// # Parameters: + /// - origin: The administrator account + /// - project_id: The selected project id where the expenditures will be + /// created/updated/deleted + /// - expenditures: The expenditures to be created/updated/deleted. This is a vector of + /// tuples + /// where each entry is composed by: + /// * 0: The name of the expenditure + /// * 1: The expenditure type + /// * 2: The amount of the expenditure + /// * 3: The naics code of the expenditure + /// * 4: The jobs multiplier of the expenditure + /// * 5: The expenditure action to be performed. (Create, Update or Delete) + /// * 6: The expenditure id. This is only used when updating or deleting an expenditure. + /// - job_eligibles: The job eligibles to be created/updated/deleted. This is a vector of + /// tuples + /// where each entry is composed by: + /// * 0: The job eligible name + /// * 1: The amount of the job eligible + /// * 2: The NAICS code of the job eligible + /// * 3: The jobs multiplier of the job eligible + /// * 4: The job eligible action to be performed. (Create, Update or Delete) + /// * 5: The job eligible id. This is only used when updating or deleting a job eligible. + /// + /// # Considerations: + /// - Naics code and jobs multiplier are always optional. + /// - This function can only be called by an administrator account + /// - This extrinsic allows multiple expenditures to be created/updated/deleted at the same + /// time. + /// - The project id is required because it is the only way to identify the project + /// - Expenditure parameters are optional because depends on the action to be performed: + /// * **Create**: Name, Type & Amount are required. Nacis code & Jobs multiplier are + /// optional. + /// * **Update**: Except for the expenditure id & action, all parameters are optional. + /// * **Delete**: Only the expenditure id & action is required. + /// - Multiple actions can be performed at the same time. For example, you can create a new + /// expenditure and update another one at the same time. + /// - Do not perform multiple actions over the same expenditure in the same call, it could + /// result in an unexpected behavior. + #[pallet::call_index(10)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn expenditures_and_job_eligibles( + origin: OriginFor, + project_id: ProjectId, + expenditures: Option>, + job_eligibles: Option>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be an admin + + if let Some(mod_expenditures) = expenditures { + Self::do_execute_expenditures(who.clone(), project_id, mod_expenditures)?; + } + + if let Some(mod_job_eligibles) = job_eligibles { + Self::do_execute_job_eligibles(who, project_id, mod_job_eligibles)?; + } + + Ok(()) + } + + // T R A N S A C T I O N S & D R A W D O W N S + // -------------------------------------------------------------------------------------------- + + /// Submit a drawdown + /// This extrinsic is used to create, update or delete transactions. + /// It also allows that an array of transactions to be saved as a draft or as submitted. + /// + /// # Parameters: + /// - origin: The user account who is creating the transactions + /// - project_id: The selected project id where the transactions will be created + /// - drawdown_id: The selected drawdown id where the transactions will be created + /// - transactions: The transactions to be created/updated/deleted. This entry is a vector + /// of tuples + /// where each entry is composed by: + /// * 0: The expenditure id where the transaction will be created + /// * 1: The amount of the transaction + /// * 2: Documents associated to the transaction + /// * 3: The action to be performed on the transaction. (Create, Update or Delete) + /// * 4: The transaction id. This is only used when updating or deleting a transaction. + /// - submit: If true, transactions associated to the selected + /// drawdown will be submitted to the administrator. + /// If false, the array of transactions will be saved as a draft. + /// + /// # Considerations: + /// - This function is only callable by a builder role account + /// - This extrinsic allows multiple transactions to be created/updated/deleted at the same + /// time. + /// - The project id and drawdown id are required for the reports. + /// - Transaction parameters are optional because depends on the action to be performed: + /// * **Create**: Expenditure id, Amount, Documents & action are required. + /// * **Update**: Except for the transaction id & action, all other parameters are optional. + /// * **Delete**: Only the transaction id & action are required. + /// - Multiple actions can be performed at the same time, but each must be performed on + /// a different transaction. For example, you can create a new + /// transaction and update another one at the same time. + /// - Do not perform multiple actions over the same transaction in the same call, it could + /// result in an unexpected behavior. + /// - If a drawdown is submitted, all transactions must be submitted too. If the drawdown do + /// not contain + /// any transaction, it will return an error. + /// - After a drawdown is submitted, it can not be updated or deleted. + /// - After a drawdown is rejected, builders will use again this extrinsic to update the + /// transactions associated to a given drawdown. + #[pallet::call_index(11)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn submit_drawdown( + origin: OriginFor, + project_id: ProjectId, + drawdown_id: DrawdownId, + transactions: Option>, + submit: bool, + ) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be an admin + + match submit { + // Save transactions as draft + false => { + // Do execute transactions + Self::do_execute_transactions( + who, + project_id, + drawdown_id, + transactions.ok_or(Error::::TransactionsRequired)?, + ) + }, + // Submit transactions + true => { + // Check if there are transactions to execute + if let Some(mod_transactions) = transactions { + // Ensure transactions are not empty + ensure!(!mod_transactions.is_empty(), Error::::EmptyTransactions); + + // Do execute transactions + Self::do_execute_transactions(who.clone(), project_id, drawdown_id, mod_transactions)?; + } + + // Do submit drawdown + Self::do_submit_drawdown(who, project_id, drawdown_id) + }, + } + } + + /// Approve a drawdown + /// + /// # Parameters: + /// ### For EB5 drawdowns: + /// - origin: The administrator account who is approving the drawdown + /// - project_id: The selected project id where the drawdown will be approved + /// - drawdown_id: The selected drawdown id to be approved + /// + /// ### For Construction Loan & Developer Equity (bulk uploads) drawdowns: + /// - origin: The administrator account who is approving the drawdown + /// - project_id: The selected project id where the drawdown will be approved + /// - drawdown_id: The selected drawdown id to be approved. + /// - bulkupload: Optional bulkupload parameter. If true, the drawdown will be saved in a + /// pseudo + /// draft status. If false, the drawdown will be approved directly. + /// - transactions: The transactions to be created/updated/deleted. This is a vector of + /// tuples + /// where each entry is composed by: + /// * 0: The expenditure id where the transaction will be created + /// * 1: The transaction amount + /// * 2: Documents associated to the transaction + /// * 3: The transaction action to be performed. (Create, Update or Delete) + /// * 4: The transaction id. This is only used when updating or deleting a transaction. + /// - This extrinsic allows multiple transactions to be created/updated/deleted at the same + /// time + /// (only for Construction Loan & Developer Equity drawdowns). + /// - Transaction parameters are optional because depends on the action to be performed: + /// * **Create**: Expenditure id, Amount, Documents & action are required. + /// * **Update**: Except for the transaction id & action, all parameters are optional. + /// * **Delete**: Only the transaction id & action are required. + /// - Multiple actions can be performed at the same time. For example, you can create a new + /// transaction and update another one at the same time (only for Construction Loan & + /// Developer Equity drawdowns). + /// - Do not perform multiple actions over the same transaction in the same call, it could + /// result in an unexpected behavior (only for Construction Loan & Developer Equity + /// drawdowns). + /// + /// # Considerations: + /// - This function is only callable by an administrator account + /// - All transactions associated to the drawdown will be approved too. It's + /// not possible to approve a drawdown without approving all of its transactions. + /// - After a drawdown is approved, it can not be updated or deleted. + /// - After a drawdown is approved, the next drawdown will be automatically created. + /// - The drawdown status will be updated to "Approved" after the extrinsic is executed. + /// - After a drawdown is rejected, administrators will use again this extrinsic to approve + /// the + /// new drawdown version uploaded by the builder. + #[pallet::call_index(12)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn approve_drawdown( + origin: OriginFor, + project_id: ProjectId, + drawdown_id: DrawdownId, + bulkupload: Option, + transactions: Option>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be an admin + + // Match bulkupload parameter + match bulkupload { + Some(approval) => { + // Ensure admin permissions + Self::is_authorized(who.clone(), &project_id, ProxyPermission::ApproveDrawdown)?; + + // Execute bulkupload flow (construction loan & developer equity) + match approval { + false => { + // 1. Do execute transactions + Self::do_execute_transactions( + who.clone(), + project_id, + drawdown_id, + transactions.ok_or(Error::::TransactionsRequired)?, + )?; + + // 2. Do submit drawdown + Self::do_submit_drawdown(who, project_id, drawdown_id) + }, + true => { + // 1.Execute transactions if provided + if let Some(mod_transactions) = transactions { + // Ensure transactions are not empty + ensure!(!mod_transactions.is_empty(), Error::::EmptyTransactions); + + // Do execute transactions + Self::do_execute_transactions( + who.clone(), + project_id, + drawdown_id, + mod_transactions, + )?; + + // 2. Submit drawdown + Self::do_submit_drawdown(who.clone(), project_id, drawdown_id)?; + } + + // 3. Approve drawdown + Self::do_approve_drawdown(who, project_id, drawdown_id) + }, + } + }, + None => { + // Execute normal flow (EB5) + Self::do_approve_drawdown(who, project_id, drawdown_id) + }, + } + } + + /// Reject a drawdown + /// + /// # Parameters: + /// - origin: The administrator account who is rejecting the drawdown + /// - project_id: The selected project id where the drawdown will be rejected + /// - drawdown_id: The selected drawdown id to be rejected + /// + /// Then the next two feedback parameters are optional because depends on the drawdown type: + /// #### EB5 drawdowns: + /// - transactions_feedback: Administrator will provide feedback for each rejected + /// transacion. This is a vector of tuples where each entry is composed by: + /// * 0: The transaction id + /// * 1: The transaction feedback + /// + /// #### Construction Loan & Developer Equity drawdowns: + /// - drawdown_feedback: Administrator will provide feedback for the WHOLE drawdown. + /// + /// # Considerations: + /// - This function can only be called by an administrator account + /// - All transactions associated to the drawdown will be rejected too. It's + /// not possible to reject a drawdown without rejecting all of its transactions. + /// (only for EB5 drawdowns). + /// - For EB5 drawdowns, the administrator needs to provide feedback for + /// each rejected transaction. + /// - For Construction Loan & Developer Equity drawdowns, the administrator can provide + /// feedback for the WHOLE drawdown. + /// - After a builder re-submits a drawdown, the administrator will have to review + /// the drawdown again. + /// - After a builder re-submits a drawdown, the feedback field will be cleared + /// automatically. + /// - If a single EB5 transaction is wrong, the administrator WILL reject the WHOLE + /// drawdown. + /// There is no way to reject a single transaction. + #[pallet::call_index(13)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn reject_drawdown( + origin: OriginFor, + project_id: ProjectId, + drawdown_id: DrawdownId, + transactions_feedback: Option>, + drawdown_feedback: Option, + ) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be an admin + + Self::do_reject_drawdown( + who, + project_id, + drawdown_id, + transactions_feedback, + drawdown_feedback, + ) + } + + /// Bulk upload drawdowns. + /// + /// # Parameters: + /// - origin: The administrator account who is uploading the drawdowns + /// - project_id: The selected project id where the drawdowns will be uploaded + /// - drawdown_id: The drawdowns to be uploaded + /// - description: The description of the drawdown provided by the builder + /// - total_amount: The total amount of the drawdown + /// - documents: The documents provided by the builder for the drawdown + /// + /// # Considerations: + /// - This function can only be called by a builder account + /// - This extrinsic allows only one drawdown to be uploaded at the same time. + /// - The drawdown will be automatically submitted. + /// - Only available for Construction Loan & Developer Equity drawdowns. + /// - After a builder uploads a drawdown, the administrator will have to review it. + /// - After a builder re-submits a drawdown, the feedback field will be cleared + /// automatically. + /// - Bulkuploads does not allow individual transactions. + /// - After a builder uploads a drawdown, the administrator will have to + /// insert each transaction manually. + #[pallet::call_index(14)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn up_bulkupload( + origin: OriginFor, + project_id: ProjectId, + drawdown_id: DrawdownId, + description: FieldDescription, + total_amount: TotalAmount, + documents: Documents, + ) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be a builder + + Self::do_up_bulk_upload(who, project_id, drawdown_id, description, total_amount, documents) + } + + /// Modifies the inflation rate of a project. + /// + /// # Parameters: + /// - origin: The administrator account who is modifying the inflation rate + /// - projects: The projects where the inflation rate will be modified. + /// This is a vector of tuples where each entry is composed by: + /// * 0: The project id + /// * 1: The inflation rate + /// * 2: The action to be performed (Create, Update or Delete) + /// + /// # Considerations: + /// - This function can only be called by an administrator account + /// - This extrinsic allows multiple projects to be modified at the same time. + /// - The inflation rate can be created, updated or deleted. + /// - The inflation rate is optional because depends on the CUDAction parameter: + /// * **Create**: The inflation rate will be created. Project id, inflation rate and action + /// are required. + /// * **Update**: The inflation rate will be updated. Project id, inflation rate and action + /// are required. + /// * **Delete**: The inflation rate will be deleted. Project id and action are required. + /// - The inflation rate can only be modified if the project is in the "started" status. + #[pallet::call_index(15)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn inflation_rate(origin: OriginFor, projects: ProjectsInflation) -> DispatchResult { + let who = ensure_signed(origin)?; + + Self::do_execute_inflation_adjustment(who, projects) + } + + // R E V E N U E S + // -------------------------------------------------------------------------------------------- + + /// This extrinsic is used to create, update or delete revenue transactions. + /// It also allows that an array of revenue transactions + /// to be saved as a draft or as submitted. + /// + /// # Parameters: + /// */ - origin: The user account who is creating the revenue transactions + /// - project_id: The selected project id where the revenue transactions will be created + /// - revenue_id: The selected revenue id where the revenue transactions will be created + /// - revenue_transactions: The revenue transactions to be created/updated/deleted. + /// This entry is a vector of tuples where each entry is composed by: + /// * 0: The job eligible id where the revenue transaction will be created + /// * 1: The amount of the revenue transaction + /// * 2: Documents associated to the revenue transaction + /// * 3: The action to be performed on the revenue transaction (Create, Update or Delete) + /// * 4: The revenue transaction id. This is required only if the action is being updated or + /// deleted. + /// - submit: If true, the array of revenue transactions will be submitted to the + /// administrator. + /// If false, the array of revenue transactions will be saved as a draft. + /// + /// # Considerations: + /// - This function is only callable by a builder role account + /// - This extrinsic allows multiple revenue transactions to be created/updated/deleted at + /// the same time. + /// - The project id and revenue id are required for the reports. + /// - revenue_transactions parameters are optional because depends on the action to be + /// performed: + /// * **Create**: Job eligible id, Amount, Documents & action are required. + /// * **Update**: Except for the revenue transaction id & action, all other parameters are + /// optional. + /// * **Delete**: Only the revenue transaction id & action are required. + /// - Multiple actions can be performed at the same time, but each must be performed on + /// a different transaction. For example, you can create a new + /// transaction and update another one at the same time. + /// - Do not perform multiple actions over the same transaction in the same call, it could + /// result in an unexpected behavior. + /// - If a revenue is submitted, all transactions must be submitted too. If the revenue do + /// not contain + /// any transaction, it will return an error. + /// - After a revenue is submitted, it can not be updated or deleted. + /// - After a revenue is rejected, builders will use again this extrinsic to update the + /// transactions associated to a given revenue. + #[pallet::call_index(16)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn submit_revenue( + origin: OriginFor, + project_id: ProjectId, + revenue_id: RevenueId, + revenue_transactions: Option>, + submit: bool, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + match submit { + // Save revenue transactions as draft + false => { + // Do execute transactions + Self::do_execute_revenue_transactions( + who, + project_id, + revenue_id, + revenue_transactions.ok_or(Error::::RevenueTransactionsRequired)?, + ) + }, + // Submit revenue transactions + true => { + // Check if there are transactions to execute + if let Some(mod_revenue_transactions) = revenue_transactions { + // Ensure transactions are not empty + ensure!(!mod_revenue_transactions.is_empty(), Error::::RevenueTransactionsEmpty); + + // Do execute transactions + Self::do_execute_revenue_transactions( + who.clone(), + project_id, + revenue_id, + mod_revenue_transactions, + )?; + } + + // Do submit revenue + Self::do_submit_revenue(who, project_id, revenue_id) + }, + } + } + + /// Approve a revenue + /// + /// # Parameters: + /// - origin: The administrator account who is approving the revenue + /// - project_id: The selected project id where the revenue will be approved + /// - revenue_id: The selected revenue id to be approved + /// + /// # Considerations: + /// - This function is only callable by an administrator role account + /// - All transactions associated to the revenue will be approved too. It's + /// not possible to approve a revenue without approving all of its transactions. + /// - After a revenue is approved, it can not be updated or deleted. + /// - After a revenue is approved, the next revenue will be created automatically. + /// - After a revenue is rejected, administrators will use again this extrinsic to approve + /// the rejected revenue + /// new revenue version uploaded by the builder. + /// - The revenue status will be updated to Approved. + #[pallet::call_index(17)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn approve_revenue( + origin: OriginFor, + project_id: ProjectId, + revenue_id: RevenueId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + Self::do_approve_revenue(who, project_id, revenue_id) + } + + /// Reject a revenue + /// + /// # Parameters: + /// - origin: The administrator account who is rejecting the revenue + /// - project_id: The selected project id where the revenue will be rejected + /// - revenue_id: The selected revenue id to be rejected + /// - revenue_transactions_feedback: Administrator will provide feedback for each rejected + /// transacion. This is a vector of tuples where each entry is composed by: + /// * 0: The revenue transaction id + /// * 1: The revenue transaction feedback + /// + /// # Considerations: + /// - This function is only callable by an administrator role account + /// - All transactions associated to the revenue will be rejected too. It's + /// not possible to reject a revenue without rejecting all of its transactions. + /// - Administrator needs to provide a feedback for each rejected transaction. + /// - After a builder re-submits a revenue, the feedback field will be cleared + /// automatically. + /// - If a single revenue transaction is wrong, the administrator WILL reject the WHOLE + /// revenue. + /// There is no way to reject a single revenue transaction. + #[pallet::call_index(18)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn reject_revenue( + origin: OriginFor, + project_id: ProjectId, + revenue_id: RevenueId, + revenue_transactions_feedback: TransactionsFeedback, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + Self::do_reject_revenue(who, project_id, revenue_id, revenue_transactions_feedback) + } + + /// The following extrinsic is used to upload the bank confirming documents + /// for a given drawdown. + /// + /// # Parameters: + /// - origin: The administrator account who is uploading the confirming documents + /// - project_id: The selected project id where the drawdown exists + /// - drawdown_id: The selected drawdown id where the confirming documents will be uploaded + /// - confirming_documents: The confirming documents to be uploaded. This field is optional + /// because are required only when the action is Create or Update. + /// - action: The action to be performed. It can be Create, Update or Delete + /// * Create: project_id, drawdown_id and confirming_documents are required + /// * Update: project_id, drawdown_id and confirming_documents are required + /// * Delete: project_id and drawdown_id are required + /// + /// # Considerations: + /// - This function is only callable by an administrator role account + /// - The confirming documents are required only when the action is Create or Update. + /// - The confirming documents are optional when the action is Delete. + /// - After the confirming documents are uploaded, the drawdown status will be updated to + /// "Confirmed". It will also update the status of all of its transactions to "Confirmed". + /// - Update action will replace the existing confirming documents with the new ones. + /// - Delete action will remove the existing confirming documents. It will also update the + /// drawdown status to "Approved" and the status of all of its transactions to "Approved". + /// It does a rollback of the drawdown. + #[pallet::call_index(19)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn bank_confirming_documents( + origin: OriginFor, + project_id: ProjectId, + drawdown_id: DrawdownId, + confirming_documents: Option>, + action: CUDAction, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + Self::do_bank_confirming_documents(who, project_id, drawdown_id, confirming_documents, action) + } + + /// The following extrinsic is used to cancel a drawdown submission. + /// + /// # Parameters: + /// - origin: The builder account who is cancelling the drawdown submission + /// - project_id: The selected project id where the drawdown exists + /// - drawdown_id: The selected drawdown id to be cancelled + /// + /// # Considerations: + /// - This function is only callable by a builder role account + /// - The drawdown status will be rolled back to "Draft". + /// - All of its transactions will be deleted. + /// - The whole drawdown will be reset to its initial state, so be careful when using this + #[pallet::call_index(20)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn reset_drawdown( + origin: OriginFor, + project_id: ProjectId, + drawdown_id: DrawdownId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + Self::do_reset_drawdown(who, project_id, drawdown_id) + } + + /// Execute a recovery drawdown on a project. This function can only be called by an admin. + /// + /// Parameters: + /// - `origin`: The administrator account who is executing the recovery drawdown + /// - `project_id`: The ID of the project from which the recovery drawdown will be executed + /// - `drawdown_id`: The ID of the drawdown from which the recovery drawdown will be executed + /// - `transactions`: The list of transactions that will be executed in the recovery drawdown + /// + /// # Errors + /// + /// This function returns an error if: + /// + /// - The transaction origin is not a signed message from an admin account. + /// - The project with the given ID does not exist. + /// - The drawdown with the given ID does not exist. + /// + /// # Considerations: + /// - This function is only callable by an administrator role account + /// - The drawdown status won't be changed + #[pallet::call_index(21)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn recovery_drawdown( + origin: OriginFor, + project_id: ProjectId, + drawdown_id: DrawdownId, + transactions: Transactions, + ) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be an admin + + Self::do_recovery_drawdown(who, project_id, drawdown_id, transactions) + } + /// Execute a recovery revenue on a project. This function can only be called by an admin. + /// + /// Parameters: + /// - `origin`: The administrator account who is executing the recovery revenue + /// - `project_id`: The ID of the project from which the recovery revenue will be executed + /// - `revenue_id`: The ID of the revenue from which the recovery revenue will be executed + /// - `transactions`: The list of transactions that will be executed in the recovery revenue + /// + /// # Errors + /// + /// This function returns an error if: + /// + /// - The transaction origin is not a signed message from an admin account. + /// - The project with the given ID does not exist. + /// - The revenue with the given ID does not exist. + /// + /// ### Considerations: + /// - This function is only callable by an administrator role account + /// - The revenue status won't be changed + #[pallet::call_index(22)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn recovery_revenue( + origin: OriginFor, + project_id: ProjectId, + revenue_id: RevenueId, + transactions: Transactions, + ) -> DispatchResult { + let who = ensure_signed(origin)?; // origin need to be an admin + + Self::do_recovery_revenue(who, project_id, revenue_id, transactions) + } + + /// Kill all the stored data. + /// + /// This function is used to kill ALL the stored data. + /// Use it with caution! + /// + /// ### Parameters: + /// - `origin`: The user who performs the action. + /// + /// ### Considerations: + /// - This function is only available to the `admin` with sudo access. + #[pallet::call_index(23)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn kill_storage(origin: OriginFor) -> DispatchResult { + T::RemoveOrigin::ensure_origin(origin.clone())?; + let _ = >::kill(); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + let _ = >::clear(1000, None); + + T::Rbac::remove_pallet_storage(Self::pallet_id())?; + Ok(()) + } + + #[pallet::call_index(24)] + #[pallet::weight(Weight::from_parts(10_000,0) + T::DbWeight::get().writes(10))] + pub fn set_new_admin_permissions(origin: OriginFor) -> DispatchResult { + T::RemoveOrigin::ensure_origin(origin.clone())?; + // New permissions for fund admin administrator role + let admin_id: [u8; 32] = ProxyRole::Administrator.id(); + let pallet_id = Self::pallet_id(); + + let new_admin_permissions: Vec> = vec![ + ProxyPermission::RecoveryDrawdown.to_vec(), + ProxyPermission::RecoveryRevenue.to_vec(), + ProxyPermission::RecoveryTransaction.to_vec(), + ProxyPermission::RecoveryRevenueTransaction.to_vec(), + ProxyPermission::BulkUploadTransaction.to_vec(), + ]; + + T::Rbac::create_and_set_permissions(pallet_id.clone(), admin_id, new_admin_permissions)?; + + Ok(()) + } + } } diff --git a/pallets/fund-admin/src/migration.rs b/pallets/fund-admin/src/migration.rs new file mode 100644 index 00000000..c727c046 --- /dev/null +++ b/pallets/fund-admin/src/migration.rs @@ -0,0 +1,244 @@ +//! Various pieces of common functionality. +use super::*; + +const LOG_TARGET: &str = "\nFund Admin pallet migration "; +use crate::types::*; +use frame_support::{log, pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade, Identity}; +use sp_runtime::Saturating; +use sp_runtime::sp_std::vec::Vec; + +mod v0 { + use super::*; + + #[derive(Decode, Encode)] + pub struct OldDrawdownData { + pub project_id: ProjectId, + pub drawdown_number: DrawdownNumber, + pub drawdown_type: DrawdownType, + pub total_amount: TotalAmount, + pub status: DrawdownStatus, + pub bulkupload_documents: Option>, + pub bank_documents: Option>, + pub description: Option, + pub feedback: Option, + pub status_changes: DrawdownStatusChanges, + pub created_date: CreatedDate, + pub closed_date: CloseDate, + } + + // #[cfg(feature = "try-runtime")] + #[storage_alias] + pub(super) type DrawdownsInfo = + StorageMap, Identity, DrawdownId, OldDrawdownData>; + + #[derive(Decode, Encode)] + pub struct OldRevenueData { + pub project_id: ProjectId, + pub revenue_number: RevenueNumber, + pub total_amount: RevenueAmount, + pub status: RevenueStatus, + pub status_changes: RevenueStatusChanges, + pub created_date: CreatedDate, + pub closed_date: CloseDate, + } + + // #[cfg(feature = "try-runtime")] + #[storage_alias] + pub(super) type RevenuesInfo = + StorageMap, Identity, RevenueId, OldRevenueData>; +} + +pub mod v1 { + pub use super::v0::OldDrawdownData; + pub use super::v0::OldRevenueData; + use super::*; + + impl OldDrawdownData { + fn migrate_to_v1_drawdown(self) -> DrawdownData { + DrawdownData { + project_id: self.project_id, + drawdown_number: self.drawdown_number, + drawdown_type: self.drawdown_type, + total_amount: self.total_amount, + status: self.status, + bulkupload_documents: self.bulkupload_documents, + bank_documents: self.bank_documents, + description: self.description, + feedback: self.feedback, + status_changes: self.status_changes, + recovery_record: RecoveryRecord::::default(), + created_date: self.created_date, + closed_date: self.closed_date, + } + } + } + + impl OldRevenueData { + pub fn migrate_to_v1_revenue(self) -> RevenueData { + RevenueData { + project_id: self.project_id, + revenue_number: self.revenue_number, + total_amount: self.total_amount, + status: self.status, + status_changes: self.status_changes, + recovery_record: RecoveryRecord::::default(), + created_date: self.created_date, + closed_date: self.closed_date, + } + } + } + + pub struct MigrateToV1(sp_runtime::sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + #[allow(deprecated)] + fn on_runtime_upgrade() -> Weight { + let onchain_version = Pallet::::on_chain_storage_version(); + let current_version = Pallet::::current_storage_version(); + + log::info!( + target: LOG_TARGET, + "Running migration with current storage version: {:?} / onchain version: {:?}", + current_version, + onchain_version + ); + + if onchain_version == 0 && current_version == 1 { + // migrate to v1 + // Very inefficient, mostly here for illustration purposes. + let count_drawdowns = v0::DrawdownsInfo::::iter().count(); + let mut translated_drawdowns = 0u64; + + let count_revenues = v0::RevenuesInfo::::iter().count(); + let mut translated_revenues = 0u64; + + DrawdownsInfo::::translate::, _>( + |_key: DrawdownId, value: OldDrawdownData| { + translated_drawdowns.saturating_inc(); + Some(value.migrate_to_v1_drawdown()) + }, + ); + + RevenuesInfo::::translate::, _>( + |_key: RevenueId, value: OldRevenueData| { + translated_revenues.saturating_inc(); + Some(value.migrate_to_v1_revenue()) + }, + ); + + // Update storage version + current_version.put::>(); + + log::info!( + target: LOG_TARGET, + "Upgraded {} DrawdownData from {} initial drawdowns, storage to version {:?}", + count_drawdowns, + translated_drawdowns, + current_version + ); + + log::info!( + target: LOG_TARGET, + "Upgraded {} RevenueData from {} initial revenues, storage to version {:?}", + count_revenues, + translated_revenues, + current_version + ); + + T::DbWeight::get().reads_writes(translated_drawdowns + 1, translated_revenues + 1) + } else { + log::info!( + target: LOG_TARGET, + "Migration did not execute. This probably should be removed" + ); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + log::info!( + target: LOG_TARGET, + "pre_upgrade: current storage version {:?}", + Pallet::::current_storage_version() + ); + + ensure!(Pallet::::on_chain_storage_version() == 0, "must upgrade linearly"); + ensure!(Pallet::::current_storage_version() == 1, "migration from version 0 to 1"); + + let prev_count_drawdowns = v0::DrawdownsInfo::::iter().count(); + let keys_drawdowns = v0::DrawdownsInfo::::iter_keys().count() as u32; + let decodable_drawdowns = v0::DrawdownsInfo::::iter_values().count() as u32; + + let prev_count_revenues = v0::RevenuesInfo::::iter().count(); + let keys_revenues = v0::RevenuesInfo::::iter_keys().count() as u32; + let decodable_revenues = v0::RevenuesInfo::::iter_values().count() as u32; + + log::info!( + target: LOG_TARGET, + "pre_upgrade: {:?} drawdowns, {:?} decodable drawdowns, {:?} total", + keys_drawdowns, + decodable_drawdowns, + prev_count_drawdowns, + ); + + log::info!( + target: LOG_TARGET, + "pre_upgrade: {:?} revenues, {:?} decodable revenues, {:?} total", + keys_revenues, + decodable_revenues, + prev_count_revenues, + ); + + ensure!(keys_drawdowns == decodable_drawdowns, "Not all drawdown values are decodable."); + + ensure!(keys_revenues == decodable_revenues, "Not all revenue values are decodable."); + + Ok(((prev_count_drawdowns as u32, prev_count_revenues as u32)).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev_count: Vec) -> Result<(), &'static str> { + // Split the encoded data into two u32s + let (prev_count_drawdowns, prev_count_revenues) = + <(u32, u32)>::decode(&mut &prev_count[..]).map_err(|_| "Unable to decode prev_count")?; + + let post_count_drawdowns = crate::DrawdownsInfo::::iter().count() as u32; + let post_count_revenues = crate::RevenuesInfo::::iter().count() as u32; + + assert_eq!( + prev_count_drawdowns, post_count_drawdowns, + "the records count before and after the migration should be the same" + ); + assert_eq!( + prev_count_revenues, post_count_revenues, + "the records count before and after the migration should be the same" + ); + + let current_version = Pallet::::current_storage_version(); + let onchain_version = Pallet::::on_chain_storage_version(); + + ensure!(current_version == 1, "must upgrade to v1"); + assert_eq!( + current_version, onchain_version, + "after migration, the current_version and onchain_version should be the same" + ); + + crate::DrawdownsInfo::::iter().for_each(|(_key, value)| { + assert!( + value.recovery_record == RecoveryRecord::::default(), + "recovery record should be default value" + ); + assert!(value.recovery_record.len() == 0, "recovery record should be empty"); + }); + + crate::RevenuesInfo::::iter().for_each(|(_key, value)| { + assert!( + value.recovery_record == RecoveryRecord::::default(), + "recovery record should be default value" + ); + assert!(value.recovery_record.len() == 0, "recovery record should be empty"); + }); + Ok(()) + } + } +} diff --git a/pallets/fund-admin/src/mock.rs b/pallets/fund-admin/src/mock.rs index d9ec11ae..41724bf7 100644 --- a/pallets/fund-admin/src/mock.rs +++ b/pallets/fund-admin/src/mock.rs @@ -3,8 +3,8 @@ use frame_support::parameter_types; use frame_system as system; use sp_core::H256; use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, }; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -13,159 +13,160 @@ use frame_system::EnsureRoot; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - FundAdmin: pallet_fund_admin::{Pallet, Call, Storage, Event}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - RBAC: pallet_rbac::{Pallet, Call, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Event}, - } + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + FundAdmin: pallet_fund_admin::{Pallet, Call, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + RBAC: pallet_rbac::{Pallet, Call, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Event}, + } ); parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxReserves: u32 = 50; + pub const ExistentialDeposit: u64 = 1; + pub const MaxReserves: u32 = 50; } impl pallet_balances::Config for Test { - type Balance = u64; - type DustRemoval = (); - type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = (); - type MaxLocks = (); - type MaxReserves = MaxReserves; - type ReserveIdentifier = [u8; 8]; + type Balance = u64; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; } parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; } impl system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - type AccountData = pallet_balances::AccountData; + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type AccountData = pallet_balances::AccountData; } parameter_types! { - pub const MaxDocuments:u32 = 5; - pub const MaxProjectsPerUser:u32 = 10; - pub const MaxUserPerProject:u32 = 2000; // should be the sum of the max number of builders, investors, issuers, regional centers - pub const MaxBuildersPerProject:u32 = 500; - pub const MaxInvestorsPerProject:u32 = 500; - pub const MaxIssuersPerProject:u32 = 500; - pub const MaxRegionalCenterPerProject:u32 = 500; - pub const MaxProjectsPerInvestor:u32 = 1; - pub const MaxDrawdownsPerProject:u32 = 1000; - pub const MaxTransactionsPerDrawdown:u32 = 500; - pub const MaxRegistrationsAtTime:u32 = 50; - pub const MaxExpendituresPerProject:u32 = 1000; - pub const MaxBanksPerProject:u32 = 200; - pub const MaxJobEligiblesByProject:u32 = 1000; - pub const MaxRevenuesByProject:u32 = 1000; - pub const MaxTransactionsPerRevenue:u32 = 500; - pub const MaxStatusChangesPerDrawdown:u32 = 100; - pub const MaxStatusChangesPerRevenue:u32 = 100; - pub const MinAdminBalance:u64 = 10; - pub const TransferAmount:u64 = 10; - pub const InitialAdminBalance:u64 = 1_000_000; + pub const MaxDocuments:u32 = 5; + pub const MaxProjectsPerUser:u32 = 10; + pub const MaxUserPerProject:u32 = 2000; // should be the sum of the max number of builders, investors, issuers, regional centers + pub const MaxBuildersPerProject:u32 = 500; + pub const MaxInvestorsPerProject:u32 = 500; + pub const MaxIssuersPerProject:u32 = 500; + pub const MaxRegionalCenterPerProject:u32 = 500; + pub const MaxProjectsPerInvestor:u32 = 1; + pub const MaxDrawdownsPerProject:u32 = 1000; + pub const MaxTransactionsPerDrawdown:u32 = 500; + pub const MaxRegistrationsAtTime:u32 = 50; + pub const MaxExpendituresPerProject:u32 = 1000; + pub const MaxBanksPerProject:u32 = 200; + pub const MaxJobEligiblesByProject:u32 = 1000; + pub const MaxRevenuesByProject:u32 = 1000; + pub const MaxTransactionsPerRevenue:u32 = 500; + pub const MaxStatusChangesPerDrawdown:u32 = 100; + pub const MaxStatusChangesPerRevenue:u32 = 100; + pub const MaxRecoveryChanges:u32 = 100; + pub const MinAdminBalance:u64 = 10; + pub const TransferAmount:u64 = 10; + pub const InitialAdminBalance:u64 = 1_000_000; } impl pallet_fund_admin::Config for Test { - type RuntimeEvent = RuntimeEvent; - type RemoveOrigin = EnsureRoot; - type Timestamp = Timestamp; - type Moment = u64; - type Rbac = RBAC; - type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type RemoveOrigin = EnsureRoot; + type Timestamp = Timestamp; + type Moment = u64; + type Rbac = RBAC; + type Currency = Balances; - type MaxDocuments = MaxDocuments; - type MaxProjectsPerUser = MaxProjectsPerUser; - type MaxUserPerProject = MaxUserPerProject; - type MaxBuildersPerProject = MaxBuildersPerProject; - type MaxInvestorsPerProject = MaxInvestorsPerProject; - type MaxIssuersPerProject = MaxIssuersPerProject; - type MaxRegionalCenterPerProject = MaxRegionalCenterPerProject; - type MaxDrawdownsPerProject = MaxDrawdownsPerProject; - type MaxTransactionsPerDrawdown = MaxTransactionsPerDrawdown; - type MaxRegistrationsAtTime = MaxRegistrationsAtTime; - type MaxExpendituresPerProject = MaxExpendituresPerProject; - type MaxProjectsPerInvestor = MaxProjectsPerInvestor; - type MaxBanksPerProject = MaxBanksPerProject; - type MaxJobEligiblesByProject = MaxJobEligiblesByProject; - type MaxRevenuesByProject = MaxRevenuesByProject; - type MaxTransactionsPerRevenue = MaxTransactionsPerRevenue; - type MaxStatusChangesPerDrawdown = MaxStatusChangesPerDrawdown; - type MaxStatusChangesPerRevenue = MaxStatusChangesPerRevenue; - type MinAdminBalance = MinAdminBalance; - type TransferAmount = TransferAmount; + type MaxDocuments = MaxDocuments; + type MaxProjectsPerUser = MaxProjectsPerUser; + type MaxUserPerProject = MaxUserPerProject; + type MaxBuildersPerProject = MaxBuildersPerProject; + type MaxInvestorsPerProject = MaxInvestorsPerProject; + type MaxIssuersPerProject = MaxIssuersPerProject; + type MaxRegionalCenterPerProject = MaxRegionalCenterPerProject; + type MaxDrawdownsPerProject = MaxDrawdownsPerProject; + type MaxTransactionsPerDrawdown = MaxTransactionsPerDrawdown; + type MaxRegistrationsAtTime = MaxRegistrationsAtTime; + type MaxExpendituresPerProject = MaxExpendituresPerProject; + type MaxProjectsPerInvestor = MaxProjectsPerInvestor; + type MaxBanksPerProject = MaxBanksPerProject; + type MaxJobEligiblesByProject = MaxJobEligiblesByProject; + type MaxRevenuesByProject = MaxRevenuesByProject; + type MaxTransactionsPerRevenue = MaxTransactionsPerRevenue; + type MaxStatusChangesPerDrawdown = MaxStatusChangesPerDrawdown; + type MaxStatusChangesPerRevenue = MaxStatusChangesPerRevenue; + type MaxRecoveryChanges = MaxRecoveryChanges; + type MinAdminBalance = MinAdminBalance; + type TransferAmount = TransferAmount; } - impl pallet_timestamp::Config for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = (); - type WeightInfo = (); + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = (); + type WeightInfo = (); } parameter_types! { - pub const MaxScopesPerPallet: u32 = 1000; - pub const MaxRolesPerPallet: u32 = 50; - pub const RoleMaxLen: u32 = 50; - pub const PermissionMaxLen: u32 = 50; - pub const MaxPermissionsPerRole: u32 = 100; - pub const MaxRolesPerUser: u32 = 10; - pub const MaxUsersPerRole: u32 = 2500; + pub const MaxScopesPerPallet: u32 = 1000; + pub const MaxRolesPerPallet: u32 = 50; + pub const RoleMaxLen: u32 = 50; + pub const PermissionMaxLen: u32 = 50; + pub const MaxPermissionsPerRole: u32 = 100; + pub const MaxRolesPerUser: u32 = 10; + pub const MaxUsersPerRole: u32 = 2500; } impl pallet_rbac::Config for Test { - type RuntimeEvent = RuntimeEvent; - type RemoveOrigin = EnsureRoot; - type MaxScopesPerPallet = MaxScopesPerPallet; - type MaxRolesPerPallet = MaxRolesPerPallet; - type RoleMaxLen = RoleMaxLen; - type PermissionMaxLen = PermissionMaxLen; - type MaxPermissionsPerRole = MaxPermissionsPerRole; - type MaxRolesPerUser = MaxRolesPerUser; - type MaxUsersPerRole = MaxUsersPerRole; + type RuntimeEvent = RuntimeEvent; + type MaxScopesPerPallet = MaxScopesPerPallet; + type MaxRolesPerPallet = MaxRolesPerPallet; + type RoleMaxLen = RoleMaxLen; + type PermissionMaxLen = PermissionMaxLen; + type MaxPermissionsPerRole = MaxPermissionsPerRole; + type MaxRolesPerUser = MaxRolesPerUser; + type MaxUsersPerRole = MaxUsersPerRole; + type RemoveOrigin = EnsureRoot; } // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { - let balance_amount = InitialAdminBalance::get(); - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { - balances: vec![(1, balance_amount)], - }.assimilate_storage(&mut t).expect("assimilate_storage failed"); - let mut t: sp_io::TestExternalities = t.into(); - t.execute_with(|| FundAdmin::do_initial_setup().expect("Error on configuring initial setup")); - t + let balance_amount = InitialAdminBalance::get(); + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { balances: vec![(1, balance_amount)] } + .assimilate_storage(&mut t) + .expect("assimilate_storage failed"); + let mut t: sp_io::TestExternalities = t.into(); + t.execute_with(|| FundAdmin::do_initial_setup().expect("Error on configuring initial setup")); + t } diff --git a/pallets/fund-admin/src/tests.rs b/pallets/fund-admin/src/tests.rs index 935ed74c..8b1d5ac0 100644 --- a/pallets/fund-admin/src/tests.rs +++ b/pallets/fund-admin/src/tests.rs @@ -1,333 +1,337 @@ -use crate::{mock::*, types::*, Error, ProjectsInfo, GlobalScope, UsersInfo, UsersByProject, -ProjectsByUser, ExpendituresInfo, ExpendituresByProject, DrawdownsInfo, DrawdownsByProject, TransactionsInfo, -TransactionsByDrawdown, JobEligiblesInfo, JobEligiblesByProject, RevenuesInfo, -RevenuesByProject, RevenueTransactionsInfo, TransactionsByRevenue}; -use frame_support::{assert_noop, assert_ok, bounded_vec, error::BadOrigin, traits::{ConstU32}, BoundedVec}; +use crate::{ + mock::*, types::*, DrawdownsByProject, DrawdownsInfo, Error, ExpendituresByProject, + ExpendituresInfo, GlobalScope, JobEligiblesByProject, JobEligiblesInfo, ProjectsByUser, + ProjectsInfo, RevenueTransactionsInfo, RevenuesByProject, RevenuesInfo, TransactionsByDrawdown, + TransactionsByRevenue, TransactionsInfo, UsersByProject, UsersInfo, +}; +use frame_support::{ + assert_noop, assert_ok, bounded_vec, error::BadOrigin, traits::ConstU32, BoundedVec, +}; use sp_runtime::DispatchResult; - type RbacErr = pallet_rbac::Error; #[allow(dead_code)] -fn pallet_id () -> [u8;32] { - FundAdmin::pallet_id().to_id() +fn pallet_id() -> [u8; 32] { + FundAdmin::pallet_id().to_id() } #[allow(dead_code)] -fn pallet_name () -> pallet_rbac::types::IdOrVec { - pallet_rbac::types::IdOrVec::Vec( - "pallet_test".as_bytes().to_vec() - ) +fn pallet_name() -> pallet_rbac::types::IdOrVec { + pallet_rbac::types::IdOrVec::Vec("pallet_test".as_bytes().to_vec()) } fn make_field_name(name: &str) -> FieldName { - let name: BoundedVec> = name.as_bytes().to_vec().try_into().unwrap_or_default(); - name + let name: BoundedVec> = name.as_bytes().to_vec().try_into().unwrap_or_default(); + name } fn make_field_description(description: &str) -> FieldDescription { - let description: BoundedVec> = description.as_bytes().to_vec().try_into().unwrap_or_default(); - description + let description: BoundedVec> = + description.as_bytes().to_vec().try_into().unwrap_or_default(); + description } -fn make_documents( n_files: u32) -> Documents{ - let mut documents: Documents = bounded_vec![]; - for i in 0..n_files{ - let file_name: &str = &format!("file_{}", i); - let file_description: &str = &format!("file_{}_description", i); - documents.try_push(( - make_field_name(file_name), - make_field_name(file_description), - )).unwrap_or_default(); - } - documents +fn make_documents(n_files: u32) -> Documents { + let mut documents: Documents = bounded_vec![]; + for i in 0..n_files { + let file_name: &str = &format!("file_{}", i); + let file_description: &str = &format!("file_{}_description", i); + documents + .try_push((make_field_name(file_name), make_field_name(file_description))) + .unwrap_or_default(); + } + documents } fn field_name_to_string(boundedvec: &BoundedVec>) -> String { - let mut s = String::new(); - for b in boundedvec.iter() { - s.push(*b as char); - } - s + let mut s = String::new(); + for b in boundedvec.iter() { + s.push(*b as char); + } + s } #[allow(dead_code)] fn field_description_to_string(boundedvec: &BoundedVec>) -> String { - let mut s = String::new(); - for b in boundedvec.iter() { - s.push(*b as char); - } - s + let mut s = String::new(); + for b in boundedvec.iter() { + s.push(*b as char); + } + s } fn register_administrator() -> DispatchResult { - FundAdmin::sudo_add_administrator( - RuntimeOrigin::root(), - 1, - make_field_name("Administrator Test"), - ).map_err(|_| Error::::UserAlreadyRegistered - )?; - Ok(()) + FundAdmin::sudo_add_administrator( + RuntimeOrigin::root(), + 1, + make_field_name("Administrator Test"), + ) + .map_err(|_| Error::::UserAlreadyRegistered)?; + Ok(()) } fn make_user( - user_account: u64, - user_name: Option, - user_role: Option, - action: CUDAction + user_account: u64, + user_name: Option, + user_role: Option, + action: CUDAction, ) -> Users { - let mut users: Users = bounded_vec![]; - users.try_push(( - user_account, user_name, user_role, action - )).unwrap_or_default(); - users + let mut users: Users = bounded_vec![]; + users.try_push((user_account, user_name, user_role, action)).unwrap_or_default(); + users } fn make_default_users() -> Users { - let mut users: Users = bounded_vec![]; - let users_account = [2, 3, 4, 5]; - let users_name: Vec = ["Builder Test", "Investor Test", "Issuer Test", "Regional Center Test"].iter().map(|s| make_field_name(s)).collect(); - let users_role: Vec = [ProxyRole::Builder, ProxyRole::Investor, ProxyRole::Issuer, ProxyRole::RegionalCenter].iter().map(|s| *s).collect(); - let cud_action = CUDAction::Create; - - for i in 0..users_account.len() { - users - .try_push(( - users_account[i], - Some(users_name[i].clone()), - Some(users_role[i]), - cud_action, - )) - .unwrap_or_default(); - }; - users + let mut users: Users = bounded_vec![]; + let users_account = [2, 3, 4, 5]; + let users_name: Vec = + ["Builder Test", "Investor Test", "Issuer Test", "Regional Center Test"] + .iter() + .map(|s| make_field_name(s)) + .collect(); + let users_role: Vec = + [ProxyRole::Builder, ProxyRole::Investor, ProxyRole::Issuer, ProxyRole::RegionalCenter] + .iter() + .map(|s| *s) + .collect(); + let cud_action = CUDAction::Create; + + for i in 0..users_account.len() { + users + .try_push((users_account[i], Some(users_name[i].clone()), Some(users_role[i]), cud_action)) + .unwrap_or_default(); + } + users } #[allow(dead_code)] fn make_expenditure( - expenditure_name: Option, - expenditure_type: Option, - expenditure_amount: Option, - naics_code: Option, - jobs_multiplier: Option, - action: CUDAction, - budget_expenditure_id: Option + expenditure_name: Option, + expenditure_type: Option, + expenditure_amount: Option, + naics_code: Option, + jobs_multiplier: Option, + action: CUDAction, + budget_expenditure_id: Option, ) -> Expenditures { - let mut expenditures: Expenditures = bounded_vec![]; - expenditures.try_push(( - expenditure_name, expenditure_type, expenditure_amount, naics_code, jobs_multiplier, action, budget_expenditure_id - )).unwrap_or_default(); - expenditures + let mut expenditures: Expenditures = bounded_vec![]; + expenditures + .try_push(( + expenditure_name, + expenditure_type, + expenditure_amount, + naics_code, + jobs_multiplier, + action, + budget_expenditure_id, + )) + .unwrap_or_default(); + expenditures } fn make_default_expenditures() -> Expenditures { - let mut expenditures: Expenditures = bounded_vec![]; - let expenditure_name: Vec = ["Expenditure Test 1", "Expenditure Test 2", "Expenditure Test 3", "Expenditure Test 4"].iter().map(|s| make_field_name(s)).collect(); - let expenditure_type: Vec = [ExpenditureType::HardCost, ExpenditureType::SoftCost, ExpenditureType::Operational, ExpenditureType::Others].iter().map(|s| *s).collect(); - let expenditure_amount: Vec = [100, 200, 300, 400].iter().map(|s| *s).collect(); - let naics_code: Vec = [1231, 1232, 1233, 1234].iter().map(|s| make_field_description(&s.to_string())).collect(); - let jobs_multiplier: Vec = [20, 30, 40, 50].iter().map(|s| *s).collect(); - let cud_action = CUDAction::Create; - let budget_expenditure_id = None; - - for i in 0..expenditure_name.len() { - expenditures - .try_push(( - Some(expenditure_name[i].clone()), - Some(expenditure_type[i]), - Some(expenditure_amount[i]), - Some(naics_code[i].clone()), - Some(jobs_multiplier[i]), - cud_action, - budget_expenditure_id, - )) - .unwrap_or_default(); - }; - expenditures + let mut expenditures: Expenditures = bounded_vec![]; + let expenditure_name: Vec = + ["Expenditure Test 1", "Expenditure Test 2", "Expenditure Test 3", "Expenditure Test 4"] + .iter() + .map(|s| make_field_name(s)) + .collect(); + let expenditure_type: Vec = [ + ExpenditureType::HardCost, + ExpenditureType::SoftCost, + ExpenditureType::Operational, + ExpenditureType::Others, + ] + .iter() + .map(|s| *s) + .collect(); + let expenditure_amount: Vec = [100, 200, 300, 400].iter().map(|s| *s).collect(); + let naics_code: Vec = [1231, 1232, 1233, 1234] + .iter() + .map(|s| make_field_description(&s.to_string())) + .collect(); + let jobs_multiplier: Vec = [20, 30, 40, 50].iter().map(|s| *s).collect(); + let cud_action = CUDAction::Create; + let budget_expenditure_id = None; + + for i in 0..expenditure_name.len() { + expenditures + .try_push(( + Some(expenditure_name[i].clone()), + Some(expenditure_type[i]), + Some(expenditure_amount[i]), + Some(naics_code[i].clone()), + Some(jobs_multiplier[i]), + cud_action, + budget_expenditure_id, + )) + .unwrap_or_default(); + } + expenditures } #[allow(dead_code)] fn make_job_eligible( - field_name: Option, - job_eligible_amount: Option, - naics_code: Option, - jobs_multiplier: Option, - action: CUDAction, - budget_job_eligible_id: Option + field_name: Option, + job_eligible_amount: Option, + naics_code: Option, + jobs_multiplier: Option, + action: CUDAction, + budget_job_eligible_id: Option, ) -> JobEligibles { - let mut job_eligibles: JobEligibles = bounded_vec![]; - job_eligibles.try_push(( - field_name, job_eligible_amount, naics_code, jobs_multiplier, action, budget_job_eligible_id - )).unwrap_or_default(); - job_eligibles + let mut job_eligibles: JobEligibles = bounded_vec![]; + job_eligibles + .try_push(( + field_name, + job_eligible_amount, + naics_code, + jobs_multiplier, + action, + budget_job_eligible_id, + )) + .unwrap_or_default(); + job_eligibles } fn make_default_job_eligibles() -> JobEligibles { - let mut job_eligibles: JobEligibles = bounded_vec![]; - let field_name = make_field_name("Job Eligible Test"); - let job_eligible_amount = 100; - let naics_code = make_field_description("1293, 1231"); - let jobs_multiplier = 100; - let budget_job_eligible_id = None; - job_eligibles - .try_push(( - Some(field_name), - Some(job_eligible_amount), - Some(naics_code), - Some(jobs_multiplier), - CUDAction::Create, - budget_job_eligible_id, - )) - .unwrap_or_default(); - job_eligibles + let mut job_eligibles: JobEligibles = bounded_vec![]; + let field_name = make_field_name("Job Eligible Test"); + let job_eligible_amount = 100; + let naics_code = make_field_description("1293, 1231"); + let jobs_multiplier = 100; + let budget_job_eligible_id = None; + job_eligibles + .try_push(( + Some(field_name), + Some(job_eligible_amount), + Some(naics_code), + Some(jobs_multiplier), + CUDAction::Create, + budget_job_eligible_id, + )) + .unwrap_or_default(); + job_eligibles } #[allow(dead_code)] fn make_user_assignation( - user_account: u64, - user_role: ProxyRole, - action: AssignAction, + user_account: u64, + user_role: ProxyRole, + action: AssignAction, ) -> UsersAssignation { - let mut users_assignation: UsersAssignation = bounded_vec![]; - users_assignation.try_push(( - user_account, user_role, action - )).unwrap_or_default(); - users_assignation -} - -fn make_default_user_assignation() -> UsersAssignation { - let mut users_assignation: UsersAssignation = bounded_vec![]; - let user_account: Vec = [2, 3, 4, 5].iter().map(|s| *s).collect(); - let user_role: Vec = [ProxyRole::Builder, ProxyRole::Investor, ProxyRole::Issuer, ProxyRole::RegionalCenter].iter().map(|s| *s).collect(); - let action = AssignAction::Assign; - - for i in 0..user_account.len() { - users_assignation - .try_push(( - user_account[i], - user_role[i], - action, - )) - .unwrap_or_default(); - }; + let mut users_assignation: UsersAssignation = bounded_vec![]; + users_assignation + .try_push((user_account, user_role, action)) + .unwrap_or_default(); + users_assignation +} + +fn make_default_user_assignation() -> UsersAssignation { + let mut users_assignation: UsersAssignation = bounded_vec![]; + let user_account: Vec = [2, 3, 4, 5].iter().map(|s| *s).collect(); + let user_role: Vec = + [ProxyRole::Builder, ProxyRole::Investor, ProxyRole::Issuer, ProxyRole::RegionalCenter] + .iter() + .map(|s| *s) + .collect(); + let action = AssignAction::Assign; + + for i in 0..user_account.len() { users_assignation + .try_push((user_account[i], user_role[i], action)) + .unwrap_or_default(); + } + users_assignation } #[allow(dead_code)] -fn make_allowed_bank( - bank_name: BankName, - bank_address: BankAddress, -) -> Banks { - let mut banks: Banks = bounded_vec![]; - banks.try_push(( - bank_name, bank_address - )).unwrap_or_default(); - banks +fn make_allowed_bank(bank_name: BankName, bank_address: BankAddress) -> Banks { + let mut banks: Banks = bounded_vec![]; + banks.try_push((bank_name, bank_address)).unwrap_or_default(); + banks } fn make_default_allowed_banks() -> Banks { - let mut banks: Banks = bounded_vec![]; - let bank_name = make_field_name("Luxury Bank"); - let bank_address = make_field_name("San Francisco"); - banks - .try_push(( - bank_name, - bank_address, - )) - .unwrap_or_default(); - banks -} - -fn make_transaction_feedback(transaction_id: TransactionId, feedback: FieldDescription) -> TransactionsFeedback { - let mut transaction_feedback: TransactionsFeedback = bounded_vec![]; - transaction_feedback.try_push(( - transaction_id, feedback - )).unwrap_or_default(); - transaction_feedback + let mut banks: Banks = bounded_vec![]; + let bank_name = make_field_name("Luxury Bank"); + let bank_address = make_field_name("San Francisco"); + banks.try_push((bank_name, bank_address)).unwrap_or_default(); + banks +} + +fn make_transaction_feedback( + transaction_id: TransactionId, + feedback: FieldDescription, +) -> TransactionsFeedback { + let mut transaction_feedback: TransactionsFeedback = bounded_vec![]; + transaction_feedback.try_push((transaction_id, feedback)).unwrap_or_default(); + transaction_feedback } fn make_transaction( - expenditure_id: Option, - expenditure_amount: Option, - action: CUDAction, - transaction_id: Option, + expenditure_id: Option, + expenditure_amount: Option, + action: CUDAction, + transaction_id: Option, ) -> Transactions { - let mut transactions: Transactions = bounded_vec![]; - let documents = Some(make_documents(1)); - transactions - .try_push(( - expenditure_id, - expenditure_amount, - documents, - action, - transaction_id, - )) - .unwrap_or_default(); - transactions + let mut transactions: Transactions = bounded_vec![]; + let documents = Some(make_documents(1)); + transactions + .try_push((expenditure_id, expenditure_amount, documents, action, transaction_id)) + .unwrap_or_default(); + transactions } fn make_revenue_transaction( - job_eligible_id: Option, - job_eligible_amount: Option, - action: CUDAction, - revenue_transaction_id: Option, + job_eligible_id: Option, + job_eligible_amount: Option, + action: CUDAction, + revenue_transaction_id: Option, ) -> RevenueTransactions { - let mut revenue_transactions: RevenueTransactions = bounded_vec![]; - let documents = Some(make_documents(1)); - revenue_transactions - .try_push(( - job_eligible_id, - job_eligible_amount, - documents, - action, - revenue_transaction_id, - )) - .unwrap_or_default(); - revenue_transactions + let mut revenue_transactions: RevenueTransactions = bounded_vec![]; + let documents = Some(make_documents(1)); + revenue_transactions + .try_push((job_eligible_id, job_eligible_amount, documents, action, revenue_transaction_id)) + .unwrap_or_default(); + revenue_transactions } fn make_project_inflation( - project_id: ProjectId, - inflation: Option, - action: CUDAction, + project_id: ProjectId, + inflation: Option, + action: CUDAction, ) -> ProjectsInflation { - let mut projects_inflation: ProjectsInflation = bounded_vec![]; - projects_inflation - .try_push(( - project_id, - inflation, - action, - )) - .unwrap_or_default(); - projects_inflation + let mut projects_inflation: ProjectsInflation = bounded_vec![]; + projects_inflation.try_push((project_id, inflation, action)).unwrap_or_default(); + projects_inflation } fn make_default_simple_project() -> DispatchResult { - - register_administrator()?; - - FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 1"), - make_field_description("Project 1 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - None, - 1000, - 2000, - make_default_expenditures(), - None, - None, - make_field_description("P9f5wbr13BK74p1"), - )?; - Ok(()) + register_administrator()?; + + FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 1"), + make_field_description("Project 1 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("New York"), + None, + 1000, + 2000, + make_default_expenditures(), + None, + None, + make_field_description("P9f5wbr13BK74p1"), + )?; + Ok(()) } /// Create a project with all the fields /// This project will be used to test all the functions /// of the project pallet /// This project will be created by the admin -/// +/// /// ### Default users: /// - user_account: 1 -> admin /// - user_account: 2 -> builder @@ -335,6751 +339,7183 @@ fn make_default_simple_project() -> DispatchResult { /// - user_account: 4 -> issuer /// - user_account: 5 -> regional center fn make_default_full_project() -> DispatchResult { - - register_administrator()?; - - FundAdmin::users( - RuntimeOrigin::signed(1), - make_default_users(), - )?; - - FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 1"), - make_field_description("Project 1 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - Some(make_default_allowed_banks()), - 1000, - 2000, - make_default_expenditures(), - Some(make_default_job_eligibles()), - Some(make_default_user_assignation()), - make_field_description("P9f5wbr13BK74p1"), - )?; - Ok(()) + register_administrator()?; + + FundAdmin::users(RuntimeOrigin::signed(1), make_default_users())?; + + FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 1"), + make_field_description("Project 1 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("New York"), + Some(make_default_allowed_banks()), + 1000, + 2000, + make_default_expenditures(), + Some(make_default_job_eligibles()), + Some(make_default_user_assignation()), + make_field_description("P9f5wbr13BK74p1"), + )?; + Ok(()) } fn get_drawdown_id( - project_id: ProjectId, - drawdown_type: DrawdownType, - drawdown_number: DrawdownNumber, + project_id: ProjectId, + drawdown_type: DrawdownType, + drawdown_number: DrawdownNumber, ) -> DrawdownId { - let mut drawdown_id: DrawdownId = [0;32]; - let drawdonws_by_project = DrawdownsByProject::::get(project_id); - - for i in 0..drawdonws_by_project.len() { - let drawdown_data = DrawdownsInfo::::get(drawdonws_by_project[i]).unwrap(); - if drawdown_data.drawdown_type == drawdown_type && drawdown_data.drawdown_number == drawdown_number { - drawdown_id = drawdonws_by_project[i]; - } + let mut drawdown_id: DrawdownId = [0; 32]; + let drawdonws_by_project = DrawdownsByProject::::get(project_id); + + for i in 0..drawdonws_by_project.len() { + let drawdown_data = DrawdownsInfo::::get(drawdonws_by_project[i]).unwrap(); + if drawdown_data.drawdown_type == drawdown_type + && drawdown_data.drawdown_number == drawdown_number + { + drawdown_id = drawdonws_by_project[i]; } - drawdown_id + } + drawdown_id } fn get_budget_expenditure_id( - project_id: ProjectId, - name: FieldName, - expenditure_type: ExpenditureType, + project_id: ProjectId, + name: FieldName, + expenditure_type: ExpenditureType, ) -> ExpenditureId { - let mut expenditure_id: [u8;32] = [0;32]; - let expenditures_by_project = ExpendituresByProject::::get(project_id); - - for i in 0..expenditures_by_project.len() { - let expenditure_data = ExpendituresInfo::::get(expenditures_by_project[i]).unwrap(); - if expenditure_data.name == name && expenditure_data.expenditure_type == expenditure_type { - expenditure_id = expenditures_by_project[i]; - } + let mut expenditure_id: [u8; 32] = [0; 32]; + let expenditures_by_project = ExpendituresByProject::::get(project_id); + + for i in 0..expenditures_by_project.len() { + let expenditure_data = ExpendituresInfo::::get(expenditures_by_project[i]).unwrap(); + if expenditure_data.name == name && expenditure_data.expenditure_type == expenditure_type { + expenditure_id = expenditures_by_project[i]; } - expenditure_id + } + expenditure_id } fn get_transaction_id( - project_id: ProjectId, - drawdown_id: DrawdownId, - expenditure_id: ExpenditureId, + project_id: ProjectId, + drawdown_id: DrawdownId, + expenditure_id: ExpenditureId, ) -> TransactionId { - let mut transaction_id: [u8;32] = [0;32]; - let transactions_by_drawdown = TransactionsByDrawdown::::get(project_id, drawdown_id); - - for i in 0..transactions_by_drawdown.len() { - let transaction_data = TransactionsInfo::::get(transactions_by_drawdown[i]).unwrap(); - if transaction_data.project_id == project_id && transaction_data.drawdown_id == drawdown_id && transaction_data.expenditure_id == expenditure_id { - transaction_id = transactions_by_drawdown[i]; - } + let mut transaction_id: [u8; 32] = [0; 32]; + let transactions_by_drawdown = TransactionsByDrawdown::::get(project_id, drawdown_id); + + for i in 0..transactions_by_drawdown.len() { + let transaction_data = TransactionsInfo::::get(transactions_by_drawdown[i]).unwrap(); + if transaction_data.project_id == project_id + && transaction_data.drawdown_id == drawdown_id + && transaction_data.expenditure_id == expenditure_id + { + transaction_id = transactions_by_drawdown[i]; } - transaction_id + } + transaction_id } -fn get_revenue_id( - project_id: ProjectId, - revenue_number: RevenueNumber, -) -> RevenueId { - let mut revenue_id: RevenueId = [0;32]; - let revenues_by_project = RevenuesByProject::::get(project_id); +fn get_revenue_id(project_id: ProjectId, revenue_number: RevenueNumber) -> RevenueId { + let mut revenue_id: RevenueId = [0; 32]; + let revenues_by_project = RevenuesByProject::::get(project_id); - for i in 0..revenues_by_project.len() { - let revenue_data = RevenuesInfo::::get(revenues_by_project[i]).unwrap(); - if revenue_data.revenue_number == revenue_number { - revenue_id = revenues_by_project[i]; - } + for i in 0..revenues_by_project.len() { + let revenue_data = RevenuesInfo::::get(revenues_by_project[i]).unwrap(); + if revenue_data.revenue_number == revenue_number { + revenue_id = revenues_by_project[i]; } - revenue_id + } + revenue_id } -fn get_job_eligible_id( - project_id: ProjectId, - name: FieldName, -) -> JobEligibleId { - let mut job_eligible_id: [u8;32] = [0;32]; - let job_eligibles_by_project = JobEligiblesByProject::::get(project_id); +fn get_job_eligible_id(project_id: ProjectId, name: FieldName) -> JobEligibleId { + let mut job_eligible_id: [u8; 32] = [0; 32]; + let job_eligibles_by_project = JobEligiblesByProject::::get(project_id); - for i in 0..job_eligibles_by_project.len() { - let job_eligible_data = JobEligiblesInfo::::get(job_eligibles_by_project[i]).unwrap(); - if job_eligible_data.name == name { - job_eligible_id = job_eligibles_by_project[i]; - } + for i in 0..job_eligibles_by_project.len() { + let job_eligible_data = JobEligiblesInfo::::get(job_eligibles_by_project[i]).unwrap(); + if job_eligible_data.name == name { + job_eligible_id = job_eligibles_by_project[i]; } - job_eligible_id + } + job_eligible_id } fn get_revenue_transaction_id( - project_id: ProjectId, - revenue_id: RevenueId, - job_eligible_id: JobEligibleId, + project_id: ProjectId, + revenue_id: RevenueId, + job_eligible_id: JobEligibleId, ) -> RevenueTransactionId { - let mut revenue_transaction_id: RevenueTransactionId = [0;32]; - let transactions_by_revenue = TransactionsByRevenue::::get(project_id, revenue_id); - - for i in 0..transactions_by_revenue.len() { - let revenue_transaction_data = RevenueTransactionsInfo::::get(transactions_by_revenue[i]).unwrap(); - if revenue_transaction_data.project_id == project_id && revenue_transaction_data.revenue_id == revenue_id && revenue_transaction_data.job_eligible_id == job_eligible_id { - revenue_transaction_id = transactions_by_revenue[i]; - } + let mut revenue_transaction_id: RevenueTransactionId = [0; 32]; + let transactions_by_revenue = TransactionsByRevenue::::get(project_id, revenue_id); + + for i in 0..transactions_by_revenue.len() { + let revenue_transaction_data = + RevenueTransactionsInfo::::get(transactions_by_revenue[i]).unwrap(); + if revenue_transaction_data.project_id == project_id + && revenue_transaction_data.revenue_id == revenue_id + && revenue_transaction_data.job_eligible_id == job_eligible_id + { + revenue_transaction_id = transactions_by_revenue[i]; } - revenue_transaction_id + } + revenue_transaction_id } // I N I T I A L // ----------------------------------------------------------------------------------------- #[test] fn global_scope_is_created_after_pallet_initialization() { - new_test_ext().execute_with(|| { - assert!(GlobalScope::::exists()); - }); + new_test_ext().execute_with(|| { + assert!(GlobalScope::::exists()); + }); } #[test] fn cannon_initialize_pallet_twice_shouldnt_work() { - new_test_ext().execute_with(|| { - assert_noop!( - FundAdmin::initial_setup( - RuntimeOrigin::root()), - RbacErr::ScopeAlreadyExists - ); - }); + new_test_ext().execute_with(|| { + assert_noop!(FundAdmin::initial_setup(RuntimeOrigin::root()), RbacErr::ScopeAlreadyExists); + }); } #[test] fn sudo_register_administrator_account_works() { - new_test_ext().execute_with(|| { - let alice_name = make_field_name("Alice Keys"); - assert_ok!(FundAdmin::sudo_add_administrator( - RuntimeOrigin::root(), - 2, - alice_name.clone() - )); - assert!(UsersInfo::::contains_key(2)); - }); + new_test_ext().execute_with(|| { + let alice_name = make_field_name("Alice Keys"); + assert_ok!(FundAdmin::sudo_add_administrator(RuntimeOrigin::root(), 2, alice_name.clone())); + assert!(UsersInfo::::contains_key(2)); + }); } #[test] fn sudo_a_non_sudo_user_cannot_register_administrator_account_shouldnt_work() { - new_test_ext().execute_with(|| { - let alice_name = make_field_name("Alice Keys"); - assert_noop!( - FundAdmin::sudo_add_administrator(RuntimeOrigin::signed(1), 2, alice_name.clone()), - BadOrigin - ); - }); + new_test_ext().execute_with(|| { + let alice_name = make_field_name("Alice Keys"); + assert_noop!( + FundAdmin::sudo_add_administrator(RuntimeOrigin::signed(1), 2, alice_name.clone()), + BadOrigin + ); + }); } #[test] fn sudo_cannot_register_an_administrator_account_twice_shouldnt_work() { - new_test_ext().execute_with(|| { - let alice_name = make_field_name("Alice Keys"); - assert_ok!(FundAdmin::sudo_add_administrator( - RuntimeOrigin::root(), - 2, - alice_name.clone() - )); - assert_noop!( - FundAdmin::sudo_add_administrator(RuntimeOrigin::root(), 2, alice_name.clone()), - Error::::UserAlreadyRegistered - ); - }); + new_test_ext().execute_with(|| { + let alice_name = make_field_name("Alice Keys"); + assert_ok!(FundAdmin::sudo_add_administrator(RuntimeOrigin::root(), 2, alice_name.clone())); + assert_noop!( + FundAdmin::sudo_add_administrator(RuntimeOrigin::root(), 2, alice_name.clone()), + Error::::UserAlreadyRegistered + ); + }); } #[test] fn sudo_delete_administrator_account_works() { - new_test_ext().execute_with(|| { - let alice_name = make_field_name("Alice Keys"); - assert_ok!(FundAdmin::sudo_add_administrator( - RuntimeOrigin::root(), - 2, - alice_name.clone() - )); - assert!(FundAdmin::users_info(2).is_some()); + new_test_ext().execute_with(|| { + let alice_name = make_field_name("Alice Keys"); + assert_ok!(FundAdmin::sudo_add_administrator(RuntimeOrigin::root(), 2, alice_name.clone())); + assert!(FundAdmin::users_info(2).is_some()); - assert_ok!(FundAdmin::sudo_remove_administrator( - RuntimeOrigin::root(), - 2, - )); - assert!(FundAdmin::users_info(2).is_none()); - }); + assert_ok!(FundAdmin::sudo_remove_administrator(RuntimeOrigin::root(), 2,)); + assert!(FundAdmin::users_info(2).is_none()); + }); } #[test] fn sudo_cannot_delete_an_administrator_account_that_doesnt_exist_shouldnt_work() { - new_test_ext().execute_with(|| { - assert_noop!( - FundAdmin::sudo_remove_administrator(RuntimeOrigin::root(), 2), - Error::::UserNotRegistered - ); - }); + new_test_ext().execute_with(|| { + assert_noop!( + FundAdmin::sudo_remove_administrator(RuntimeOrigin::root(), 2), + Error::::UserNotRegistered + ); + }); } #[test] fn sudo_administrator_can_remove_another_administrator_account_works() { - new_test_ext().execute_with(|| { - assert_ok!(FundAdmin::sudo_add_administrator( - RuntimeOrigin::root(), - 2, - make_field_name("Alice Keys") - )); - assert!(FundAdmin::users_info(2).is_some()); - - assert_ok!(FundAdmin::sudo_add_administrator( - RuntimeOrigin::root(), - 3, - make_field_name("Bob Keys") - )); - assert!(FundAdmin::users_info(3).is_some()); - - assert_ok!(FundAdmin::sudo_remove_administrator( - RuntimeOrigin::root(), - 2, - )); - assert!(FundAdmin::users_info(2).is_none()); - }); + new_test_ext().execute_with(|| { + assert_ok!(FundAdmin::sudo_add_administrator( + RuntimeOrigin::root(), + 2, + make_field_name("Alice Keys") + )); + assert!(FundAdmin::users_info(2).is_some()); + + assert_ok!(FundAdmin::sudo_add_administrator( + RuntimeOrigin::root(), + 3, + make_field_name("Bob Keys") + )); + assert!(FundAdmin::users_info(3).is_some()); + + assert_ok!(FundAdmin::sudo_remove_administrator(RuntimeOrigin::root(), 2,)); + assert!(FundAdmin::users_info(2).is_none()); + }); } #[test] fn sudo_administrator_can_remove_themselves_works() { - new_test_ext().execute_with(|| { - assert_ok!(FundAdmin::sudo_add_administrator( - RuntimeOrigin::root(), - 2, - make_field_name("Alice Keys") - )); - assert!(FundAdmin::users_info(2).is_some()); - - assert_ok!(FundAdmin::sudo_remove_administrator( - RuntimeOrigin::root(), - 2, - )); - assert!(FundAdmin::users_info(2).is_none()); - }); + new_test_ext().execute_with(|| { + assert_ok!(FundAdmin::sudo_add_administrator( + RuntimeOrigin::root(), + 2, + make_field_name("Alice Keys") + )); + assert!(FundAdmin::users_info(2).is_some()); + + assert_ok!(FundAdmin::sudo_remove_administrator(RuntimeOrigin::root(), 2,)); + assert!(FundAdmin::users_info(2).is_none()); + }); } // B A L A N C E S -// ============================================================================ +// ================================================================================================= #[test] -fn balances_main_account_has_an_initial_balance_works(){ - new_test_ext().execute_with(|| { - // Get administrator free balance - let free_balance = Balances::free_balance(1); - assert_eq!(free_balance, InitialAdminBalance::get()); - }); +fn balances_main_account_has_an_initial_balance_works() { + new_test_ext().execute_with(|| { + // Get administrator free balance + let free_balance = Balances::free_balance(1); + assert_eq!(free_balance, InitialAdminBalance::get()); + }); } #[test] -fn balances_any_other_account_should_have_a_zero_balance_works(){ - new_test_ext().execute_with(|| { - // Get non-registered user free balance - let free_balance = Balances::free_balance(1); - let free_balance_2 = Balances::free_balance(2); - let free_balance_3 = Balances::free_balance(3); +fn balances_any_other_account_should_have_a_zero_balance_works() { + new_test_ext().execute_with(|| { + // Get non-registered user free balance + let free_balance = Balances::free_balance(1); + let free_balance_2 = Balances::free_balance(2); + let free_balance_3 = Balances::free_balance(3); - assert_eq!(free_balance, InitialAdminBalance::get()); - assert_eq!(free_balance_2, 0); - assert_eq!(free_balance_3, 0); - }); + assert_eq!(free_balance, InitialAdminBalance::get()); + assert_eq!(free_balance_2, 0); + assert_eq!(free_balance_3, 0); + }); } #[test] fn balances_a_new_registered_user_should_have_a_initial_balance_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Builder")), Some(ProxyRole::Builder), CUDAction::Create) - )); + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Builder")), + Some(ProxyRole::Builder), + CUDAction::Create + ) + )); - assert!(FundAdmin::users_info(2).is_some()); + assert!(FundAdmin::users_info(2).is_some()); - // Get non-registered user free balance - let admin_free_balance = Balances::free_balance(1); - let user_free_balance = Balances::free_balance(2); - assert_eq!(admin_free_balance, InitialAdminBalance::get() - TransferAmount::get()); - assert_eq!(user_free_balance, TransferAmount::get()); - }); + // Get non-registered user free balance + let admin_free_balance = Balances::free_balance(1); + let user_free_balance = Balances::free_balance(2); + assert_eq!(admin_free_balance, InitialAdminBalance::get() - TransferAmount::get()); + assert_eq!(user_free_balance, TransferAmount::get()); + }); } #[test] fn balances_an_administrator_goes_out_of_balance_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Builder")), Some(ProxyRole::Builder), CUDAction::Create) - )); - - let admin_free_balance = Balances::free_balance(1); - assert_eq!(admin_free_balance, InitialAdminBalance::get() - TransferAmount::get()); - - Balances::transfer(RuntimeOrigin::signed(1), 2, admin_free_balance - TransferAmount::get()/2).unwrap(); - - assert_noop!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(3, Some(make_field_name("Bob Investor")), Some(ProxyRole::Investor), CUDAction::Create), - ), Error::::InsufficientFundsToTransfer); - - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Builder")), + Some(ProxyRole::Builder), + CUDAction::Create + ) + )); + + let admin_free_balance = Balances::free_balance(1); + assert_eq!(admin_free_balance, InitialAdminBalance::get() - TransferAmount::get()); + + Balances::transfer(RuntimeOrigin::signed(1), 2, admin_free_balance - TransferAmount::get() / 2) + .unwrap(); + + assert_noop!( + FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 3, + Some(make_field_name("Bob Investor")), + Some(ProxyRole::Investor), + CUDAction::Create + ), + ), + Error::::InsufficientFundsToTransfer + ); + }); } #[test] fn balances_an_administrator_does_not_have_anough_free_balance_to_perform_a_user_registration() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Builder")), Some(ProxyRole::Builder), CUDAction::Create) - )); - - let admin_free_balance = Balances::free_balance(1); - assert_eq!(admin_free_balance, InitialAdminBalance::get() - TransferAmount::get()); - - Balances::transfer(RuntimeOrigin::signed(1), 2, admin_free_balance).unwrap(); - - assert_noop!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(3, Some(make_field_name("Bob Investor")), Some(ProxyRole::Investor), CUDAction::Create), - ), Error::::AdminHasNoFreeBalance); - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Builder")), + Some(ProxyRole::Builder), + CUDAction::Create + ) + )); + + let admin_free_balance = Balances::free_balance(1); + assert_eq!(admin_free_balance, InitialAdminBalance::get() - TransferAmount::get()); + + Balances::transfer(RuntimeOrigin::signed(1), 2, admin_free_balance).unwrap(); + + assert_noop!( + FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 3, + Some(make_field_name("Bob Investor")), + Some(ProxyRole::Investor), + CUDAction::Create + ), + ), + Error::::AdminHasNoFreeBalance + ); + }); } // U S E R S // ----------------------------------------------------------------------------------------- #[test] fn users_register_administrator_account_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Administrator")), Some(ProxyRole::Administrator), CUDAction::Create) - )); + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Administrator")), + Some(ProxyRole::Administrator), + CUDAction::Create + ) + )); - assert!(FundAdmin::users_info(2).is_some()); - }); + assert!(FundAdmin::users_info(2).is_some()); + }); } #[test] fn users_register_builder_account_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Builder")), Some(ProxyRole::Builder), CUDAction::Create) - )); + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Builder")), + Some(ProxyRole::Builder), + CUDAction::Create + ) + )); - assert!(FundAdmin::users_info(2).is_some()); - }); + assert!(FundAdmin::users_info(2).is_some()); + }); } #[test] fn users_register_investor_account_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Investor")), Some(ProxyRole::Investor), CUDAction::Create) - )); + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Investor")), + Some(ProxyRole::Investor), + CUDAction::Create + ) + )); - assert!(FundAdmin::users_info(2).is_some()); - }); + assert!(FundAdmin::users_info(2).is_some()); + }); } #[test] fn users_register_issuer_account_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Issuer")), Some(ProxyRole::Issuer), CUDAction::Create) - )); + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Issuer")), + Some(ProxyRole::Issuer), + CUDAction::Create + ) + )); - assert!(FundAdmin::users_info(2).is_some()); - }); + assert!(FundAdmin::users_info(2).is_some()); + }); } #[test] fn users_register_regional_center_account_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Regional Center")), Some(ProxyRole::RegionalCenter), CUDAction::Create) - )); + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Regional Center")), + Some(ProxyRole::RegionalCenter), + CUDAction::Create + ) + )); - assert!(FundAdmin::users_info(2).is_some()); - }); + assert!(FundAdmin::users_info(2).is_some()); + }); } #[test] fn users_register_multiple_accounts_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_default_users() - )); + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), make_default_users())); - assert!(FundAdmin::users_info(2).is_some()); - assert!(FundAdmin::users_info(3).is_some()); - assert!(FundAdmin::users_info(4).is_some()); - assert!(FundAdmin::users_info(5).is_some()); - - }); + assert!(FundAdmin::users_info(2).is_some()); + assert!(FundAdmin::users_info(3).is_some()); + assert!(FundAdmin::users_info(4).is_some()); + assert!(FundAdmin::users_info(5).is_some()); + }); } #[test] fn users_a_non_registered_admin_tries_to_register_an_account_shouldnt_work() { - new_test_ext().execute_with(|| { - assert_noop!( - FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Regional Center")), Some(ProxyRole::RegionalCenter), CUDAction::Create) - ), - Error::::UserNotRegistered - ); - }); + new_test_ext().execute_with(|| { + assert_noop!( + FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Regional Center")), + Some(ProxyRole::RegionalCenter), + CUDAction::Create + ) + ), + Error::::UserNotRegistered + ); + }); } #[test] fn users_a_registered_admin_tries_to_register_an_account_twice_shouldnt_work() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Regional Center")), Some(ProxyRole::RegionalCenter), CUDAction::Create) - )); - - assert_noop!( - FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Regional Center")), Some(ProxyRole::RegionalCenter), CUDAction::Create) - ), - Error::::UserAlreadyRegistered - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Regional Center")), + Some(ProxyRole::RegionalCenter), + CUDAction::Create + ) + )); + + assert_noop!( + FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Regional Center")), + Some(ProxyRole::RegionalCenter), + CUDAction::Create + ) + ), + Error::::UserAlreadyRegistered + ); + }); } #[test] fn users_update_a_registered_account_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Regional Center")), Some(ProxyRole::RegionalCenter), CUDAction::Create) - )); + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Regional Center")), + Some(ProxyRole::RegionalCenter), + CUDAction::Create + ) + )); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Regional Center Updated")), None, CUDAction::Update) - )); + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user(2, Some(make_field_name("Alice Regional Center Updated")), None, CUDAction::Update) + )); - assert_eq!(field_name_to_string(&FundAdmin::users_info(2).unwrap().name), String::from("Alice Regional Center Updated")); - }); + assert_eq!( + field_name_to_string(&FundAdmin::users_info(2).unwrap().name), + String::from("Alice Regional Center Updated") + ); + }); } #[test] fn users_admnistrator_updates_role_of_a_registered_account_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Regional Center")), Some(ProxyRole::RegionalCenter), CUDAction::Create) - )); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Investor")), Some(ProxyRole::Investor), CUDAction::Update) - )); - - assert_eq!(FundAdmin::users_info(2).unwrap().role, ProxyRole::Investor); - assert_eq!(FundAdmin::users_info(2).unwrap().name, make_field_name("Alice Investor"));00 - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Regional Center")), + Some(ProxyRole::RegionalCenter), + CUDAction::Create + ) + )); + + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Investor")), + Some(ProxyRole::Investor), + CUDAction::Update + ) + )); + + assert_eq!(FundAdmin::users_info(2).unwrap().role, ProxyRole::Investor); + assert_eq!(FundAdmin::users_info(2).unwrap().name, make_field_name("Alice Investor")); + 00 + }); } - #[test] fn users_update_a_non_registered_account_shouldnt_work() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); - assert_noop!( - FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Regional Center")), Some(ProxyRole::RegionalCenter), CUDAction::Update) - ), - Error::::UserNotRegistered - ); - }); + assert_noop!( + FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Regional Center")), + Some(ProxyRole::RegionalCenter), + CUDAction::Update + ) + ), + Error::::UserNotRegistered + ); + }); } #[test] fn users_delete_a_registered_account_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Alice Regional Center")), Some(ProxyRole::RegionalCenter), CUDAction::Create) - )); + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Alice Regional Center")), + Some(ProxyRole::RegionalCenter), + CUDAction::Create + ) + )); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, None, None, CUDAction::Delete) - )); + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user(2, None, None, CUDAction::Delete) + )); - assert!(FundAdmin::users_info(2).is_none()); - }); + assert!(FundAdmin::users_info(2).is_none()); + }); } #[test] fn users_delete_a_non_registered_account_shouldnt_work() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); - assert_noop!( - FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, None, None, CUDAction::Delete) - ), - Error::::UserNotRegistered - ); - }); + assert_noop!( + FundAdmin::users(RuntimeOrigin::signed(1), make_user(2, None, None, CUDAction::Delete)), + Error::::UserNotRegistered + ); + }); } #[test] fn users_user_updates_their_own_account_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Bob Regional Center")), Some(ProxyRole::RegionalCenter), CUDAction::Create) - )); - - assert_ok!(FundAdmin::users_edit_user( - RuntimeOrigin::signed(2), - Some(make_field_name("Bob Regiona Center New York")), - Some(make_field_name("image.png")), - Some(make_field_name("bob.regionalcenter@fundadmin.com")), - None, - )); - - assert_eq!(FundAdmin::users_info(2).unwrap().role, ProxyRole::RegionalCenter); - assert_eq!(FundAdmin::users_info(2).unwrap().name, make_field_name("Bob Regiona Center New York")); - assert_eq!(FundAdmin::users_info(2).unwrap().image, make_field_name("image.png")); - assert_eq!(FundAdmin::users_info(2).unwrap().email, make_field_name("bob.regionalcenter@fundadmin.com")); - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Bob Regional Center")), + Some(ProxyRole::RegionalCenter), + CUDAction::Create + ) + )); + + assert_ok!(FundAdmin::users_edit_user( + RuntimeOrigin::signed(2), + Some(make_field_name("Bob Regiona Center New York")), + Some(make_field_name("image.png")), + Some(make_field_name("bob.regionalcenter@fundadmin.com")), + None, + )); + + assert_eq!(FundAdmin::users_info(2).unwrap().role, ProxyRole::RegionalCenter); + assert_eq!( + FundAdmin::users_info(2).unwrap().name, + make_field_name("Bob Regiona Center New York") + ); + assert_eq!(FundAdmin::users_info(2).unwrap().image, make_field_name("image.png")); + assert_eq!( + FundAdmin::users_info(2).unwrap().email, + make_field_name("bob.regionalcenter@fundadmin.com") + ); + }); } #[test] fn users_only_investors_can_upload_documentation_to_their_account_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user(2, Some(make_field_name("Bob Investor")), Some(ProxyRole::Investor), CUDAction::Create) - )); - - assert_ok!( - FundAdmin::users_edit_user( - RuntimeOrigin::signed(2), - None, - None, - None, - Some(make_documents(1)), - ) - ); - assert_eq!(FundAdmin::users_info(2).unwrap().name, make_field_name("Bob Investor")); - assert_eq!(FundAdmin::users_info(2).unwrap().documents, Some(make_documents(1))); - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Bob Investor")), + Some(ProxyRole::Investor), + CUDAction::Create + ) + )); + + assert_ok!(FundAdmin::users_edit_user( + RuntimeOrigin::signed(2), + None, + None, + None, + Some(make_documents(1)), + )); + assert_eq!(FundAdmin::users_info(2).unwrap().name, make_field_name("Bob Investor")); + assert_eq!(FundAdmin::users_info(2).unwrap().documents, Some(make_documents(1))); + }); } // P R O J E C T S // ----------------------------------------------------------------------------------------- #[test] fn projects_register_a_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 1"), - make_field_description("Project 1 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - None, - 1000, - 2000, - make_default_expenditures(), - None, - None, - make_field_description("P9f5wbr13BK74p1"), - )); - - assert_eq!(ProjectsInfo::::iter().count(), 1); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_eq!(ProjectsInfo::::get(project_id).unwrap().title, make_field_name("Project 1")); - - assert_eq!(ExpendituresInfo::::iter().count(), ExpendituresByProject::::get(project_id).len()); - let get_expenditure_ids: Vec<[u8; 32]> = ExpendituresByProject::::get(project_id).iter().cloned().collect(); - for i in get_expenditure_ids { - assert_eq!(ExpendituresInfo::::get(i).unwrap().project_id, project_id); - } - - assert_eq!(DrawdownsInfo::::iter().count(), DrawdownsByProject::::get(project_id).len()); - let get_drawdown_ids: Vec<[u8; 32]> = DrawdownsByProject::::get(project_id).iter().cloned().collect(); - for i in get_drawdown_ids { - assert_eq!(DrawdownsInfo::::get(i).unwrap().project_id, project_id); - } - - assert_eq!(RevenuesInfo::::iter().count(), RevenuesByProject::::get(project_id).len()); - let get_revenue_ids: Vec<[u8; 32]> = RevenuesByProject::::get(project_id).iter().cloned().collect(); - for i in get_revenue_ids { - assert_eq!(RevenuesInfo::::get(i).unwrap().project_id, project_id); - } - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 1"), + make_field_description("Project 1 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("New York"), + None, + 1000, + 2000, + make_default_expenditures(), + None, + None, + make_field_description("P9f5wbr13BK74p1"), + )); + + assert_eq!(ProjectsInfo::::iter().count(), 1); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + assert_eq!(ProjectsInfo::::get(project_id).unwrap().title, make_field_name("Project 1")); + + assert_eq!( + ExpendituresInfo::::iter().count(), + ExpendituresByProject::::get(project_id).len() + ); + let get_expenditure_ids: Vec<[u8; 32]> = + ExpendituresByProject::::get(project_id).iter().cloned().collect(); + for i in get_expenditure_ids { + assert_eq!(ExpendituresInfo::::get(i).unwrap().project_id, project_id); + } + + assert_eq!( + DrawdownsInfo::::iter().count(), + DrawdownsByProject::::get(project_id).len() + ); + let get_drawdown_ids: Vec<[u8; 32]> = + DrawdownsByProject::::get(project_id).iter().cloned().collect(); + for i in get_drawdown_ids { + assert_eq!(DrawdownsInfo::::get(i).unwrap().project_id, project_id); + } + + assert_eq!( + RevenuesInfo::::iter().count(), + RevenuesByProject::::get(project_id).len() + ); + let get_revenue_ids: Vec<[u8; 32]> = + RevenuesByProject::::get(project_id).iter().cloned().collect(); + for i in get_revenue_ids { + assert_eq!(RevenuesInfo::::get(i).unwrap().project_id, project_id); + } + }); } #[test] fn projects_register_a_project_with_job_eligibles_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 1"), - make_field_description("Project 1 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - None, - 1000, - 2000, - make_default_expenditures(), - Some(make_default_job_eligibles()), - None, - make_field_description("P9f5wbr13BK74p1"), - )); - - assert_eq!(ProjectsInfo::::iter_values().count(), 1); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_eq!(JobEligiblesInfo::::iter().count(), JobEligiblesByProject::::get(project_id).len()); - - let get_job_eligible_ids: Vec<[u8; 32]> = JobEligiblesByProject::::get(project_id).iter().cloned().collect(); - for i in get_job_eligible_ids { - assert_eq!(JobEligiblesInfo::::get(i).unwrap().project_id, project_id); - } - - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 1"), + make_field_description("Project 1 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("New York"), + None, + 1000, + 2000, + make_default_expenditures(), + Some(make_default_job_eligibles()), + None, + make_field_description("P9f5wbr13BK74p1"), + )); + + assert_eq!(ProjectsInfo::::iter_values().count(), 1); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + assert_eq!( + JobEligiblesInfo::::iter().count(), + JobEligiblesByProject::::get(project_id).len() + ); + + let get_job_eligible_ids: Vec<[u8; 32]> = + JobEligiblesByProject::::get(project_id).iter().cloned().collect(); + for i in get_job_eligible_ids { + assert_eq!(JobEligiblesInfo::::get(i).unwrap().project_id, project_id); + } + }); } #[test] fn projects_register_a_project_with_assigned_users_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_default_users(), - )); - - assert_ok!(FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 1"), - make_field_description("Project 1 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - None, - 1000, - 2000, - make_default_expenditures(), - None, - Some(make_default_user_assignation()), - make_field_description("P9f5wbr13BK74p1"), - )); - - assert_eq!(ProjectsInfo::::iter_values().count(), 1); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_eq!(UsersByProject::::get(project_id).len(), 4); - - let get_assigned_user_ids: Vec = UsersByProject::::get(project_id).iter().cloned().collect(); - for i in get_assigned_user_ids { - assert_eq!(ProjectsByUser::::get(i).len(), 1); - assert_eq!(ProjectsByUser::::get(i).iter().next().unwrap(), &project_id); - } - - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), make_default_users(),)); + + assert_ok!(FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 1"), + make_field_description("Project 1 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("New York"), + None, + 1000, + 2000, + make_default_expenditures(), + None, + Some(make_default_user_assignation()), + make_field_description("P9f5wbr13BK74p1"), + )); + + assert_eq!(ProjectsInfo::::iter_values().count(), 1); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + assert_eq!(UsersByProject::::get(project_id).len(), 4); + + let get_assigned_user_ids: Vec = + UsersByProject::::get(project_id).iter().cloned().collect(); + for i in get_assigned_user_ids { + assert_eq!(ProjectsByUser::::get(i).len(), 1); + assert_eq!(ProjectsByUser::::get(i).iter().next().unwrap(), &project_id); + } + }); } #[test] fn projects_register_a_project_with_allowed_banks_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 1"), - make_field_description("Project 1 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - Some(make_default_allowed_banks()), - 1000, - 2000, - make_default_expenditures(), - None, - None, - make_field_description("P9f5wbr13BK74p1"), - )); - - assert_eq!(ProjectsInfo::::iter_values().count(), 1); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - assert_eq!(ProjectsInfo::::get(project_id).unwrap().banks, Some(make_default_allowed_banks())); - - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 1"), + make_field_description("Project 1 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("New York"), + Some(make_default_allowed_banks()), + 1000, + 2000, + make_default_expenditures(), + None, + None, + make_field_description("P9f5wbr13BK74p1"), + )); + + assert_eq!(ProjectsInfo::::iter_values().count(), 1); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().banks, + Some(make_default_allowed_banks()) + ); + }); } #[test] fn projects_register_a_project_without_a_group_id_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_noop!( - FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 1"), - make_field_description("Project 1 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - None, - 1000, - 2000, - make_default_expenditures(), - None, - None, - make_field_description(""), - ), - Error::::PrivateGroupIdEmpty - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_noop!( + FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 1"), + make_field_description("Project 1 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("New York"), + None, + 1000, + 2000, + make_default_expenditures(), + None, + None, + make_field_description(""), + ), + Error::::PrivateGroupIdEmpty + ); + }); } #[test] fn projects_a_non_authorized_user_registers_a_project_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_default_users(), - )); - - let unauthorized_users: Vec = vec![2, 3, 4, 5]; - - for i in unauthorized_users { - assert_noop!( - FundAdmin::projects_create_project( - RuntimeOrigin::signed(i), - make_field_name("Project 1"), - make_field_description("Project 1 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - None, - 1000, - 2000, - make_default_expenditures(), - None, - None, - make_field_description("P9f5wbr13BK74p1"), - ), - RbacErr::NotAuthorized - ); - } - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), make_default_users(),)); + + let unauthorized_users: Vec = vec![2, 3, 4, 5]; + + for i in unauthorized_users { + assert_noop!( + FundAdmin::projects_create_project( + RuntimeOrigin::signed(i), + make_field_name("Project 1"), + make_field_description("Project 1 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("New York"), + None, + 1000, + 2000, + make_default_expenditures(), + None, + None, + make_field_description("P9f5wbr13BK74p1"), + ), + RbacErr::NotAuthorized + ); + } + }); } - #[test] fn projects_investors_can_be_only_assigned_to_one_project_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_default_users(), - )); - - let investor_data = make_user_assignation(3, ProxyRole::Investor, AssignAction::Assign); - - assert_ok!(FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 1"), - make_field_description("Project 1 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - None, - 1000, - 2000, - make_default_expenditures(), - None, - Some(investor_data.clone()), - make_field_description("P9f5wbr13BK74p1"), - )); - - assert_noop!( - FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 2"), - make_field_description("Project 2 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - None, - 1000, - 2000, - make_default_expenditures(), - None, - Some(investor_data), - make_field_description("P9f5wbr13BK74p1"), - ), - Error::::MaxProjectsPerInvestorReached - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), make_default_users(),)); + + let investor_data = make_user_assignation(3, ProxyRole::Investor, AssignAction::Assign); + + assert_ok!(FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 1"), + make_field_description("Project 1 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("New York"), + None, + 1000, + 2000, + make_default_expenditures(), + None, + Some(investor_data.clone()), + make_field_description("P9f5wbr13BK74p1"), + )); + + assert_noop!( + FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 2"), + make_field_description("Project 2 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("New York"), + None, + 1000, + 2000, + make_default_expenditures(), + None, + Some(investor_data), + make_field_description("P9f5wbr13BK74p1"), + ), + Error::::MaxProjectsPerInvestorReached + ); + }); } #[test] fn projects_edit_a_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_default_users(), - )); - - assert_ok!(FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 1"), - make_field_description("Project 1 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - None, - 1000, - 2000, - make_default_expenditures(), - None, - None, - make_field_description("P9f5wbr13BK74p1"), - )); - - assert_eq!(ProjectsInfo::::iter_values().count(), 1); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - assert_ok!(FundAdmin::projects_edit_project( - RuntimeOrigin::signed(1), - project_id, - Some(make_field_name("Project 1 edited")), - Some(make_field_description("Project 1 description edited")), - Some(make_field_name("project_image.jpeg")), - Some(make_field_name("California")), - None, - Some(5000u64), - Some(10000u64), - )); - - assert_eq!(ProjectsInfo::::get(project_id).unwrap().title, make_field_name("Project 1 edited")); - }); + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), make_default_users(),)); + + assert_ok!(FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 1"), + make_field_description("Project 1 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("New York"), + None, + 1000, + 2000, + make_default_expenditures(), + None, + None, + make_field_description("P9f5wbr13BK74p1"), + )); + + assert_eq!(ProjectsInfo::::iter_values().count(), 1); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + assert_ok!(FundAdmin::projects_edit_project( + RuntimeOrigin::signed(1), + project_id, + Some(make_field_name("Project 1 edited")), + Some(make_field_description("Project 1 description edited")), + Some(make_field_name("project_image.jpeg")), + Some(make_field_name("California")), + None, + Some(5000u64), + Some(10000u64), + )); + + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().title, + make_field_name("Project 1 edited") + ); + }); } #[test] fn projects_delete_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - assert_eq!(ProjectsInfo::::iter_values().count(), 1); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let get_expenditure_ids: Vec<[u8; 32]> = ExpendituresByProject::::get(project_id).iter().cloned().collect(); - let get_drawdown_ids: Vec<[u8; 32]> = DrawdownsByProject::::get(project_id).iter().cloned().collect(); - let get_revenue_ids: Vec<[u8; 32]> = RevenuesByProject::::get(project_id).iter().cloned().collect(); - let get_job_eligible_ids: Vec<[u8; 32]> = JobEligiblesByProject::::get(project_id).iter().cloned().collect(); - let get_assigned_user_ids: Vec = UsersByProject::::get(project_id).iter().cloned().collect(); - - assert_ok!(FundAdmin::projects_delete_project( - RuntimeOrigin::signed(1), - project_id, - )); - - // Ensure project data was deleted - assert_eq!(ProjectsInfo::::contains_key(project_id), false); - assert_eq!(ExpendituresInfo::::contains_key(project_id), false); - for expenditure_id in get_expenditure_ids { - assert_eq!(ExpendituresInfo::::contains_key(expenditure_id), false); - } - for drawdown_id in get_drawdown_ids { - assert_eq!(DrawdownsInfo::::contains_key(drawdown_id), false); - } - for revenue_id in get_revenue_ids { - assert_eq!(RevenuesInfo::::contains_key(revenue_id), false); - } - for job_eligible_id in get_job_eligible_ids { - assert_eq!(JobEligiblesInfo::::contains_key(job_eligible_id), false); - } - for assigned_user_id in get_assigned_user_ids { - assert_eq!(UsersByProject::::get(project_id).contains(&assigned_user_id), false); - assert_eq!(ProjectsByUser::::get(assigned_user_id).contains(&project_id), false); - } - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + assert_eq!(ProjectsInfo::::iter_values().count(), 1); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let get_expenditure_ids: Vec<[u8; 32]> = + ExpendituresByProject::::get(project_id).iter().cloned().collect(); + let get_drawdown_ids: Vec<[u8; 32]> = + DrawdownsByProject::::get(project_id).iter().cloned().collect(); + let get_revenue_ids: Vec<[u8; 32]> = + RevenuesByProject::::get(project_id).iter().cloned().collect(); + let get_job_eligible_ids: Vec<[u8; 32]> = + JobEligiblesByProject::::get(project_id).iter().cloned().collect(); + let get_assigned_user_ids: Vec = + UsersByProject::::get(project_id).iter().cloned().collect(); + + assert_ok!(FundAdmin::projects_delete_project(RuntimeOrigin::signed(1), project_id,)); + + // Ensure project data was deleted + assert_eq!(ProjectsInfo::::contains_key(project_id), false); + assert_eq!(ExpendituresInfo::::contains_key(project_id), false); + for expenditure_id in get_expenditure_ids { + assert_eq!(ExpendituresInfo::::contains_key(expenditure_id), false); + } + for drawdown_id in get_drawdown_ids { + assert_eq!(DrawdownsInfo::::contains_key(drawdown_id), false); + } + for revenue_id in get_revenue_ids { + assert_eq!(RevenuesInfo::::contains_key(revenue_id), false); + } + for job_eligible_id in get_job_eligible_ids { + assert_eq!(JobEligiblesInfo::::contains_key(job_eligible_id), false); + } + for assigned_user_id in get_assigned_user_ids { + assert_eq!(UsersByProject::::get(project_id).contains(&assigned_user_id), false); + assert_eq!(ProjectsByUser::::get(assigned_user_id).contains(&project_id), false); + } + }); } #[test] fn projects_assign_a_builder_to_a_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); - let builder_data = make_user( - 2, - Some(make_field_name("Builder Test")), - Some(ProxyRole::Builder), - CUDAction::Create, - ); + let builder_data = make_user( + 2, + Some(make_field_name("Builder Test")), + Some(ProxyRole::Builder), + CUDAction::Create, + ); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - builder_data, - )); + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), builder_data,)); - let builder_assignment = make_user_assignation( - 2, - ProxyRole::Builder, - AssignAction::Assign, - ); + let builder_assignment = make_user_assignation(2, ProxyRole::Builder, AssignAction::Assign); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - builder_assignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + builder_assignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&2), true); - assert_eq!(ProjectsByUser::::get(2).contains(&project_id), true); - }); + assert_eq!(UsersByProject::::get(project_id).contains(&2), true); + assert_eq!(ProjectsByUser::::get(2).contains(&project_id), true); + }); } #[test] fn projects_assign_an_investor_to_a_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let investor_data = make_user( - 3, - Some(make_field_name("Investor Test")), - Some(ProxyRole::Investor), - CUDAction::Create, - ); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let investor_data = make_user( + 3, + Some(make_field_name("Investor Test")), + Some(ProxyRole::Investor), + CUDAction::Create, + ); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - investor_data, - )); + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), investor_data,)); - let investor_assignment = make_user_assignation( - 3, - ProxyRole::Investor, - AssignAction::Assign, - ); + let investor_assignment = make_user_assignation(3, ProxyRole::Investor, AssignAction::Assign); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - investor_assignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + investor_assignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&3), true); - assert_eq!(ProjectsByUser::::get(3).contains(&project_id), true); - }); + assert_eq!(UsersByProject::::get(project_id).contains(&3), true); + assert_eq!(ProjectsByUser::::get(3).contains(&project_id), true); + }); } #[test] fn projects_assign_an_issuer_to_a_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); - let issuer_data = make_user( - 4, - Some(make_field_name("Issuer Test")), - Some(ProxyRole::Issuer), - CUDAction::Create, - ); + let issuer_data = make_user( + 4, + Some(make_field_name("Issuer Test")), + Some(ProxyRole::Issuer), + CUDAction::Create, + ); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - issuer_data, - )); + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), issuer_data,)); - let issuer_assignment = make_user_assignation( - 4, - ProxyRole::Issuer, - AssignAction::Assign, - ); + let issuer_assignment = make_user_assignation(4, ProxyRole::Issuer, AssignAction::Assign); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - issuer_assignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + issuer_assignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&4), true); - assert_eq!(ProjectsByUser::::get(4).contains(&project_id), true); - }); + assert_eq!(UsersByProject::::get(project_id).contains(&4), true); + assert_eq!(ProjectsByUser::::get(4).contains(&project_id), true); + }); } #[test] fn projects_assign_a_regional_center_to_a_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); - let regional_center_data = make_user( - 5, - Some(make_field_name("Regional Center Test")), - Some(ProxyRole::RegionalCenter), - CUDAction::Create, - ); + let regional_center_data = make_user( + 5, + Some(make_field_name("Regional Center Test")), + Some(ProxyRole::RegionalCenter), + CUDAction::Create, + ); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - regional_center_data, - )); + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), regional_center_data,)); - let regional_center_assignment = make_user_assignation( - 5, - ProxyRole::RegionalCenter, - AssignAction::Assign, - ); + let regional_center_assignment = + make_user_assignation(5, ProxyRole::RegionalCenter, AssignAction::Assign); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - regional_center_assignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + regional_center_assignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&5), true); - assert_eq!(ProjectsByUser::::get(5).contains(&project_id), true); - }); + assert_eq!(UsersByProject::::get(project_id).contains(&5), true); + assert_eq!(ProjectsByUser::::get(5).contains(&project_id), true); + }); } #[test] fn projects_unassign_a_builder_from_a_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); - let builder_data = make_user( - 2, - Some(make_field_name("Builder Test")), - Some(ProxyRole::Builder), - CUDAction::Create, - ); + let builder_data = make_user( + 2, + Some(make_field_name("Builder Test")), + Some(ProxyRole::Builder), + CUDAction::Create, + ); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - builder_data, - )); + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), builder_data,)); - let builder_assignment = make_user_assignation( - 2, - ProxyRole::Builder, - AssignAction::Assign, - ); + let builder_assignment = make_user_assignation(2, ProxyRole::Builder, AssignAction::Assign); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - builder_assignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + builder_assignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&2), true); - assert_eq!(ProjectsByUser::::get(2).contains(&project_id), true); + assert_eq!(UsersByProject::::get(project_id).contains(&2), true); + assert_eq!(ProjectsByUser::::get(2).contains(&project_id), true); - let builder_unassignment = make_user_assignation( - 2, - ProxyRole::Builder, - AssignAction::Unassign, - ); + let builder_unassignment = make_user_assignation(2, ProxyRole::Builder, AssignAction::Unassign); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - builder_unassignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + builder_unassignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&2), false); - assert_eq!(ProjectsByUser::::get(2).contains(&project_id), false); - }); + assert_eq!(UsersByProject::::get(project_id).contains(&2), false); + assert_eq!(ProjectsByUser::::get(2).contains(&project_id), false); + }); } #[test] fn projects_unassign_an_investor_from_a_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); - let investor_data = make_user( - 3, - Some(make_field_name("Investor Test")), - Some(ProxyRole::Investor), - CUDAction::Create, - ); + let investor_data = make_user( + 3, + Some(make_field_name("Investor Test")), + Some(ProxyRole::Investor), + CUDAction::Create, + ); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - investor_data, - )); + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), investor_data,)); - let investor_assignment = make_user_assignation( - 3, - ProxyRole::Investor, - AssignAction::Assign, - ); + let investor_assignment = make_user_assignation(3, ProxyRole::Investor, AssignAction::Assign); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - investor_assignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + investor_assignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&3), true); - assert_eq!(ProjectsByUser::::get(3).contains(&project_id), true); + assert_eq!(UsersByProject::::get(project_id).contains(&3), true); + assert_eq!(ProjectsByUser::::get(3).contains(&project_id), true); - let investor_unassignment = make_user_assignation( - 3, - ProxyRole::Investor, - AssignAction::Unassign, - ); + let investor_unassignment = + make_user_assignation(3, ProxyRole::Investor, AssignAction::Unassign); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - investor_unassignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + investor_unassignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&3), false); - assert_eq!(ProjectsByUser::::get(3).contains(&project_id), false); - }); + assert_eq!(UsersByProject::::get(project_id).contains(&3), false); + assert_eq!(ProjectsByUser::::get(3).contains(&project_id), false); + }); } #[test] fn projects_unassign_an_issuer_from_a_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); - let issuer_data = make_user( - 4, - Some(make_field_name("Issuer Test")), - Some(ProxyRole::Issuer), - CUDAction::Create, - ); + let issuer_data = make_user( + 4, + Some(make_field_name("Issuer Test")), + Some(ProxyRole::Issuer), + CUDAction::Create, + ); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - issuer_data, - )); + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), issuer_data,)); - let issuer_assignment = make_user_assignation( - 4, - ProxyRole::Issuer, - AssignAction::Assign, - ); + let issuer_assignment = make_user_assignation(4, ProxyRole::Issuer, AssignAction::Assign); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - issuer_assignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + issuer_assignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&4), true); - assert_eq!(ProjectsByUser::::get(4).contains(&project_id), true); + assert_eq!(UsersByProject::::get(project_id).contains(&4), true); + assert_eq!(ProjectsByUser::::get(4).contains(&project_id), true); - let issuer_unassignment = make_user_assignation( - 4, - ProxyRole::Issuer, - AssignAction::Unassign, - ); + let issuer_unassignment = make_user_assignation(4, ProxyRole::Issuer, AssignAction::Unassign); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - issuer_unassignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + issuer_unassignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&4), false); - assert_eq!(ProjectsByUser::::get(4).contains(&project_id), false); - }); + assert_eq!(UsersByProject::::get(project_id).contains(&4), false); + assert_eq!(ProjectsByUser::::get(4).contains(&project_id), false); + }); } #[test] fn projects_unassign_a_regional_center_from_a_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); - let regional_center_data = make_user( - 5, - Some(make_field_name("Regional Center Test")), - Some(ProxyRole::RegionalCenter), - CUDAction::Create, - ); + let regional_center_data = make_user( + 5, + Some(make_field_name("Regional Center Test")), + Some(ProxyRole::RegionalCenter), + CUDAction::Create, + ); - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - regional_center_data, - )); + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), regional_center_data,)); - let regional_center_assignment = make_user_assignation( - 5, - ProxyRole::RegionalCenter, - AssignAction::Assign, - ); + let regional_center_assignment = + make_user_assignation(5, ProxyRole::RegionalCenter, AssignAction::Assign); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - regional_center_assignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + regional_center_assignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&5), true); - assert_eq!(ProjectsByUser::::get(5).contains(&project_id), true); + assert_eq!(UsersByProject::::get(project_id).contains(&5), true); + assert_eq!(ProjectsByUser::::get(5).contains(&project_id), true); - let regional_center_unassignment = make_user_assignation( - 5, - ProxyRole::RegionalCenter, - AssignAction::Unassign, - ); + let regional_center_unassignment = + make_user_assignation(5, ProxyRole::RegionalCenter, AssignAction::Unassign); - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - regional_center_unassignment, - )); + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + regional_center_unassignment, + )); - assert_eq!(UsersByProject::::get(project_id).contains(&5), false); - assert_eq!(ProjectsByUser::::get(5).contains(&project_id), false); - }); + assert_eq!(UsersByProject::::get(project_id).contains(&5), false); + assert_eq!(ProjectsByUser::::get(5).contains(&project_id), false); + }); } #[test] fn projects_cannot_assign_a_user_to_a_project_twice_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let builder_data = make_user( - 2, - Some(make_field_name("Builder Test")), - Some(ProxyRole::Builder), - CUDAction::Create, - ); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - builder_data, - )); - - let builder_assignment = make_user_assignation( - 2, - ProxyRole::Builder, - AssignAction::Assign, - ); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - builder_assignment.clone(), - )); - - assert_eq!(UsersByProject::::get(project_id).contains(&2), true); - assert_eq!(ProjectsByUser::::get(2).contains(&project_id), true); - - assert_noop!( - FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - builder_assignment, - ), - Error::::UserAlreadyAssignedToProject - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let builder_data = make_user( + 2, + Some(make_field_name("Builder Test")), + Some(ProxyRole::Builder), + CUDAction::Create, + ); + + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), builder_data,)); + + let builder_assignment = make_user_assignation(2, ProxyRole::Builder, AssignAction::Assign); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + builder_assignment.clone(), + )); + + assert_eq!(UsersByProject::::get(project_id).contains(&2), true); + assert_eq!(ProjectsByUser::::get(2).contains(&project_id), true); + + assert_noop!( + FundAdmin::projects_assign_user(RuntimeOrigin::signed(1), project_id, builder_assignment,), + Error::::UserAlreadyAssignedToProject + ); + }); } #[test] fn user_cannot_be_assigned_to_a_project_with_a_different_role_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let builder_data = make_user( - 2, - Some(make_field_name("Builder Test")), - Some(ProxyRole::Builder), - CUDAction::Create, - ); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - builder_data, - )); - - let investor_assignment = make_user_assignation( - 2, - ProxyRole::Investor, - AssignAction::Assign, - ); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - assert_noop!( - FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - investor_assignment, - ), - Error::::UserCannotHaveMoreThanOneRole - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let builder_data = make_user( + 2, + Some(make_field_name("Builder Test")), + Some(ProxyRole::Builder), + CUDAction::Create, + ); + + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), builder_data,)); + + let investor_assignment = make_user_assignation(2, ProxyRole::Investor, AssignAction::Assign); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + assert_noop!( + FundAdmin::projects_assign_user(RuntimeOrigin::signed(1), project_id, investor_assignment,), + Error::::UserCannotHaveMoreThanOneRole + ); + }); } #[test] fn projects_a_user_cannot_have_more_than_one_role_in_a_project_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let builder_data = make_user( - 2, - Some(make_field_name("Builder Test")), - Some(ProxyRole::Builder), - CUDAction::Create, - ); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - builder_data, - )); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let builder_assignment = make_user_assignation( - 2, - ProxyRole::Builder, - AssignAction::Assign, - ); - - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - builder_assignment, - )); - - let investor_assignment = make_user_assignation( - 2, - ProxyRole::Investor, - AssignAction::Assign, - ); - - assert_noop!( - FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - investor_assignment, - ), - Error::::UserCannotHaveMoreThanOneRole - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let builder_data = make_user( + 2, + Some(make_field_name("Builder Test")), + Some(ProxyRole::Builder), + CUDAction::Create, + ); + + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), builder_data,)); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let builder_assignment = make_user_assignation(2, ProxyRole::Builder, AssignAction::Assign); + + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + builder_assignment, + )); + + let investor_assignment = make_user_assignation(2, ProxyRole::Investor, AssignAction::Assign); + + assert_noop!( + FundAdmin::projects_assign_user(RuntimeOrigin::signed(1), project_id, investor_assignment,), + Error::::UserCannotHaveMoreThanOneRole + ); + }); } #[test] fn projects_cannot_delete_a_user_who_has_assigned_projects_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let builder_data = make_user( - 2, - Some(make_field_name("Builder Test")), - Some(ProxyRole::Builder), - CUDAction::Create, - ); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - builder_data, - )); - - let builder_assignment = make_user_assignation( - 2, - ProxyRole::Builder, - AssignAction::Assign, - ); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - builder_assignment, - )); - - assert_noop!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user( - 2, - None, - None, - CUDAction::Delete, - ), - ), - Error::::UserHasAssignedProjectsCannotDelete - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let builder_data = make_user( + 2, + Some(make_field_name("Builder Test")), + Some(ProxyRole::Builder), + CUDAction::Create, + ); + + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), builder_data,)); + + let builder_assignment = make_user_assignation(2, ProxyRole::Builder, AssignAction::Assign); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + builder_assignment, + )); + + assert_noop!( + FundAdmin::users(RuntimeOrigin::signed(1), make_user(2, None, None, CUDAction::Delete,),), + Error::::UserHasAssignedProjectsCannotDelete + ); + }); } #[test] fn users_cannot_update_user_role_from_an_account_with_assigned_projects_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let builder_data = make_user( - 2, - Some(make_field_name("Builder Test")), - Some(ProxyRole::Builder), - CUDAction::Create, - ); - - assert_ok!(FundAdmin::users( - RuntimeOrigin::signed(1), - builder_data, - )); - - let builder_assignment = make_user_assignation( - 2, - ProxyRole::Builder, - AssignAction::Assign, - ); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - assert_ok!(FundAdmin::projects_assign_user( - RuntimeOrigin::signed(1), - project_id, - builder_assignment, - )); - - assert_noop!(FundAdmin::users( - RuntimeOrigin::signed(1), - make_user( - 2, - Some(make_field_name("Builder Test")), - Some(ProxyRole::Investor), - CUDAction::Update, - ), - ), - Error::::UserHasAssignedProjectsCannotUpdateRole - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let builder_data = make_user( + 2, + Some(make_field_name("Builder Test")), + Some(ProxyRole::Builder), + CUDAction::Create, + ); + + assert_ok!(FundAdmin::users(RuntimeOrigin::signed(1), builder_data,)); + + let builder_assignment = make_user_assignation(2, ProxyRole::Builder, AssignAction::Assign); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + assert_ok!(FundAdmin::projects_assign_user( + RuntimeOrigin::signed(1), + project_id, + builder_assignment, + )); + + assert_noop!( + FundAdmin::users( + RuntimeOrigin::signed(1), + make_user( + 2, + Some(make_field_name("Builder Test")), + Some(ProxyRole::Investor), + CUDAction::Update, + ), + ), + Error::::UserHasAssignedProjectsCannotUpdateRole + ); + }); } // E X P E N D I T U R E S -// ============================================================================ +// ================================================================================================= #[test] fn expenditures_add_a_hard_cost_budget_expenditure_for_a_given_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: HardCost")), - Some(ExpenditureType::HardCost), - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - )); - - let get_expenditure_ids: Vec<[u8; 32]> = ExpendituresByProject::::get(project_id).iter().cloned().collect(); - let mut target_expenditure_id: [u8; 32] = [0; 32]; - - for expenditure_id in get_expenditure_ids { - let expenditure_data = ExpendituresInfo::::get(expenditure_id).ok_or(Error::::ExpenditureNotFound).unwrap(); - if expenditure_data.name == make_field_name("Expenditure Test: HardCost") { - target_expenditure_id = expenditure_id; - break; - } - } - - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().name, make_field_name("Expenditure Test: HardCost")); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_type, ExpenditureType::HardCost); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_amount, 100); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().naics_code, Some(make_field_description("16344, 45862, 57143"))); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().jobs_multiplier, Some(200)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: HardCost")), + Some(ExpenditureType::HardCost), + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + )); + + let get_expenditure_ids: Vec<[u8; 32]> = + ExpendituresByProject::::get(project_id).iter().cloned().collect(); + let mut target_expenditure_id: [u8; 32] = [0; 32]; + + for expenditure_id in get_expenditure_ids { + let expenditure_data = ExpendituresInfo::::get(expenditure_id) + .ok_or(Error::::ExpenditureNotFound) + .unwrap(); + if expenditure_data.name == make_field_name("Expenditure Test: HardCost") { + target_expenditure_id = expenditure_id; + break; + } + } + + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().name, + make_field_name("Expenditure Test: HardCost") + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_type, + ExpenditureType::HardCost + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_amount, + 100 + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().naics_code, + Some(make_field_description("16344, 45862, 57143")) + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().jobs_multiplier, + Some(200) + ); + }); } #[test] fn expenditures_add_a_softcost_budget_expenditure_for_a_given_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: SoftCost")), - Some(ExpenditureType::SoftCost), - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - )); - - let get_expenditure_ids: Vec<[u8; 32]> = ExpendituresByProject::::get(project_id).iter().cloned().collect(); - let mut target_expenditure_id: [u8; 32] = [0; 32]; - - for expenditure_id in get_expenditure_ids { - let expenditure_data = ExpendituresInfo::::get(expenditure_id).ok_or(Error::::ExpenditureNotFound).unwrap(); - if expenditure_data.name == make_field_name("Expenditure Test: SoftCost") { - target_expenditure_id = expenditure_id; - break; - } - } - - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().name, make_field_name("Expenditure Test: SoftCost")); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_type, ExpenditureType::SoftCost); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_amount, 100); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().naics_code, Some(make_field_description("16344, 45862, 57143"))); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().jobs_multiplier, Some(200)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: SoftCost")), + Some(ExpenditureType::SoftCost), + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + )); + + let get_expenditure_ids: Vec<[u8; 32]> = + ExpendituresByProject::::get(project_id).iter().cloned().collect(); + let mut target_expenditure_id: [u8; 32] = [0; 32]; + + for expenditure_id in get_expenditure_ids { + let expenditure_data = ExpendituresInfo::::get(expenditure_id) + .ok_or(Error::::ExpenditureNotFound) + .unwrap(); + if expenditure_data.name == make_field_name("Expenditure Test: SoftCost") { + target_expenditure_id = expenditure_id; + break; + } + } + + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().name, + make_field_name("Expenditure Test: SoftCost") + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_type, + ExpenditureType::SoftCost + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_amount, + 100 + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().naics_code, + Some(make_field_description("16344, 45862, 57143")) + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().jobs_multiplier, + Some(200) + ); + }); } #[test] fn expenditures_add_an_operational_budget_expenditure_for_a_given_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Operational")), - Some(ExpenditureType::Operational), - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - )); - - let get_expenditure_ids: Vec<[u8; 32]> = ExpendituresByProject::::get(project_id).iter().cloned().collect(); - let mut target_expenditure_id: [u8; 32] = [0; 32]; - - for expenditure_id in get_expenditure_ids { - let expenditure_data = ExpendituresInfo::::get(expenditure_id).ok_or(Error::::ExpenditureNotFound).unwrap(); - if expenditure_data.name == make_field_name("Expenditure Test: Operational") { - target_expenditure_id = expenditure_id; - break; - } - } - - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().name, make_field_name("Expenditure Test: Operational")); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_type, ExpenditureType::Operational); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_amount, 100); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().naics_code, Some(make_field_description("16344, 45862, 57143"))); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().jobs_multiplier, Some(200)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Operational")), + Some(ExpenditureType::Operational), + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + )); + + let get_expenditure_ids: Vec<[u8; 32]> = + ExpendituresByProject::::get(project_id).iter().cloned().collect(); + let mut target_expenditure_id: [u8; 32] = [0; 32]; + + for expenditure_id in get_expenditure_ids { + let expenditure_data = ExpendituresInfo::::get(expenditure_id) + .ok_or(Error::::ExpenditureNotFound) + .unwrap(); + if expenditure_data.name == make_field_name("Expenditure Test: Operational") { + target_expenditure_id = expenditure_id; + break; + } + } + + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().name, + make_field_name("Expenditure Test: Operational") + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_type, + ExpenditureType::Operational + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_amount, + 100 + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().naics_code, + Some(make_field_description("16344, 45862, 57143")) + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().jobs_multiplier, + Some(200) + ); + }); } #[test] fn expenditures_add_an_others_budget_expenditure_for_a_given_project_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Others")), - Some(ExpenditureType::Others), - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - )); - - let get_expenditure_ids: Vec<[u8; 32]> = ExpendituresByProject::::get(project_id).iter().cloned().collect(); - let mut target_expenditure_id: [u8; 32] = [0; 32]; - - for expenditure_id in get_expenditure_ids { - let expenditure_data = ExpendituresInfo::::get(expenditure_id).ok_or(Error::::ExpenditureNotFound).unwrap(); - if expenditure_data.name == make_field_name("Expenditure Test: Others") { - target_expenditure_id = expenditure_id; - break; - } - } - - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().name, make_field_name("Expenditure Test: Others")); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_type, ExpenditureType::Others); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_amount, 100); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().naics_code, Some(make_field_description("16344, 45862, 57143"))); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().jobs_multiplier, Some(200)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Others")), + Some(ExpenditureType::Others), + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + )); + + let get_expenditure_ids: Vec<[u8; 32]> = + ExpendituresByProject::::get(project_id).iter().cloned().collect(); + let mut target_expenditure_id: [u8; 32] = [0; 32]; + + for expenditure_id in get_expenditure_ids { + let expenditure_data = ExpendituresInfo::::get(expenditure_id) + .ok_or(Error::::ExpenditureNotFound) + .unwrap(); + if expenditure_data.name == make_field_name("Expenditure Test: Others") { + target_expenditure_id = expenditure_id; + break; + } + } + + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().name, + make_field_name("Expenditure Test: Others") + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_type, + ExpenditureType::Others + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_amount, + 100 + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().naics_code, + Some(make_field_description("16344, 45862, 57143")) + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().jobs_multiplier, + Some(200) + ); + }); } #[test] fn expenditures_cannot_send_an_empty_array_of_expenditures_for_a_given_project_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let expenditure_data: Expenditures = bounded_vec![]; + let expenditure_data: Expenditures = bounded_vec![]; - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - ), - Error::::EmptyExpenditures - ); - }); + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + ), + Error::::EmptyExpenditures + ); + }); } #[test] fn expenditures_cannot_create_a_budget_expenditure_without_a_name_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - None, - Some(ExpenditureType::HardCost), - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - ), - Error::::ExpenditureNameRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + None, + Some(ExpenditureType::HardCost), + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + ), + Error::::ExpenditureNameRequired + ); + }); } #[test] fn expenditures_cannot_create_a_budget_without_expenditure_type_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost")), - None, - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - ), - Error::::ExpenditureTypeRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost")), + None, + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + ), + Error::::ExpenditureTypeRequired + ); + }); } #[test] fn expenditures_cannot_create_a_budget_expenditute_without_an_amount_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost")), - Some(ExpenditureType::HardCost), - None, - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - ), - Error::::ExpenditureAmountRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost")), + Some(ExpenditureType::HardCost), + None, + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + ), + Error::::ExpenditureAmountRequired + ); + }); } #[test] fn expenditures_cannot_create_a_budget_expenditure_with_an_empty_name_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("")), - Some(ExpenditureType::HardCost), - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - ), - Error::::EmptyExpenditureName - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("")), + Some(ExpenditureType::HardCost), + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + ), + Error::::EmptyExpenditureName + ); + }); } #[test] fn expenditures_edit_a_given_expenditure_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost")), - Some(ExpenditureType::HardCost), - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - )); - - let get_expenditure_ids = ExpendituresByProject::::get(project_id); - - let mut target_expenditure_id: [u8; 32] = [0; 32]; - - for expenditure_id in get_expenditure_ids { - let expenditure_data = ExpendituresInfo::::get(expenditure_id).ok_or(Error::::ExpenditureNotFound).unwrap(); - - if expenditure_data.name == make_field_name("Expenditure Test: Hard Cost") { - target_expenditure_id = expenditure_id; - break; - } - } - - let mod_expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost Modified")), - Some(ExpenditureType::HardCost), - Some(1000000), - Some(make_field_description("16344, 57143")), - Some(200), - CUDAction::Update, - Some(target_expenditure_id), - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(mod_expenditure_data), - None, - )); - - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().name, make_field_name("Expenditure Test: Hard Cost Modified")); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_type, ExpenditureType::HardCost); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_amount, 1000000); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().naics_code, Some(make_field_description("16344, 57143"))); - assert_eq!(ExpendituresInfo::::get(target_expenditure_id).unwrap().jobs_multiplier, Some(200)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost")), + Some(ExpenditureType::HardCost), + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + )); + + let get_expenditure_ids = ExpendituresByProject::::get(project_id); + + let mut target_expenditure_id: [u8; 32] = [0; 32]; + + for expenditure_id in get_expenditure_ids { + let expenditure_data = ExpendituresInfo::::get(expenditure_id) + .ok_or(Error::::ExpenditureNotFound) + .unwrap(); + + if expenditure_data.name == make_field_name("Expenditure Test: Hard Cost") { + target_expenditure_id = expenditure_id; + break; + } + } + + let mod_expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost Modified")), + Some(ExpenditureType::HardCost), + Some(1000000), + Some(make_field_description("16344, 57143")), + Some(200), + CUDAction::Update, + Some(target_expenditure_id), + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(mod_expenditure_data), + None, + )); + + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().name, + make_field_name("Expenditure Test: Hard Cost Modified") + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_type, + ExpenditureType::HardCost + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().expenditure_amount, + 1000000 + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().naics_code, + Some(make_field_description("16344, 57143")) + ); + assert_eq!( + ExpendituresInfo::::get(target_expenditure_id).unwrap().jobs_multiplier, + Some(200) + ); + }); } #[test] fn expenditures_edit_a_given_expenditure_from_another_project_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - assert_ok!(FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 2"), - make_field_description("Project 2 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("Brooklyn"), - None, - 1000, - 2000, - make_default_expenditures(), - None, - None, - make_field_description("P9f5wbr13BK74p1"), - )); - - let mut project_ids: Vec = ProjectsInfo::::iter_keys().collect(); - let first_project_id = project_ids.pop().unwrap(); - let second_project_id = project_ids.pop().unwrap(); - - let second_expenditure_id = ExpendituresByProject::::get(second_project_id).pop().unwrap(); - - let mod_expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost Modified")), - Some(ExpenditureType::HardCost), - Some(1000000), - Some(make_field_description("16344, 57143")), - Some(200), - CUDAction::Update, - Some(second_expenditure_id), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - first_project_id, - Some(mod_expenditure_data), - None, - ), - Error::::ExpenditureDoesNotBelongToProject - ); - - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + assert_ok!(FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 2"), + make_field_description("Project 2 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("Brooklyn"), + None, + 1000, + 2000, + make_default_expenditures(), + None, + None, + make_field_description("P9f5wbr13BK74p1"), + )); + + let mut project_ids: Vec = ProjectsInfo::::iter_keys().collect(); + let first_project_id = project_ids.pop().unwrap(); + let second_project_id = project_ids.pop().unwrap(); + + let second_expenditure_id = + ExpendituresByProject::::get(second_project_id).pop().unwrap(); + + let mod_expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost Modified")), + Some(ExpenditureType::HardCost), + Some(1000000), + Some(make_field_description("16344, 57143")), + Some(200), + CUDAction::Update, + Some(second_expenditure_id), + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + first_project_id, + Some(mod_expenditure_data), + None, + ), + Error::::ExpenditureDoesNotBelongToProject + ); + }); } #[test] fn expenditures_expenditure_id_is_required_while_editing_a_given_expenditure_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost")), - Some(ExpenditureType::HardCost), - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - )); - - let mod_expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost Modified")), - Some(ExpenditureType::HardCost), - Some(1000000), - Some(make_field_description("16344, 57143")), - Some(200), - CUDAction::Update, - None, - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(mod_expenditure_data), - None, - ), - Error::::ExpenditureIdRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost")), + Some(ExpenditureType::HardCost), + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + )); + + let mod_expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost Modified")), + Some(ExpenditureType::HardCost), + Some(1000000), + Some(make_field_description("16344, 57143")), + Some(200), + CUDAction::Update, + None, + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(mod_expenditure_data), + None, + ), + Error::::ExpenditureIdRequired + ); + }); } #[test] fn expenditures_admnistrator_tries_to_update_a_non_existent_expenditure_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost")), - Some(ExpenditureType::HardCost), - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - )); - - let get_expenditure_ids = ExpendituresByProject::::get(project_id); - - let mut target_expenditure_id: [u8; 32] = [0; 32]; - - for expenditure_id in get_expenditure_ids { - let expenditure_data = ExpendituresInfo::::get(expenditure_id).ok_or(Error::::ExpenditureNotFound).unwrap(); - - if expenditure_data.name == make_field_name("Expenditure Test: Hard Cost") { - target_expenditure_id = expenditure_id; - break; - } - } - - let del_expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost Modified")), - Some(ExpenditureType::HardCost), - Some(1000000), - Some(make_field_description("16344, 57143")), - Some(200), - CUDAction::Delete, - Some(target_expenditure_id), - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - )); - - let mod_expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost Modified")), - Some(ExpenditureType::HardCost), - Some(1000000), - Some(make_field_description("16344, 57143")), - Some(200), - CUDAction::Update, - Some(target_expenditure_id), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(mod_expenditure_data), - None, - ), - Error::::ExpenditureNotFound - ); - - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost")), + Some(ExpenditureType::HardCost), + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + )); + + let get_expenditure_ids = ExpendituresByProject::::get(project_id); + + let mut target_expenditure_id: [u8; 32] = [0; 32]; + + for expenditure_id in get_expenditure_ids { + let expenditure_data = ExpendituresInfo::::get(expenditure_id) + .ok_or(Error::::ExpenditureNotFound) + .unwrap(); + + if expenditure_data.name == make_field_name("Expenditure Test: Hard Cost") { + target_expenditure_id = expenditure_id; + break; + } + } + + let del_expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost Modified")), + Some(ExpenditureType::HardCost), + Some(1000000), + Some(make_field_description("16344, 57143")), + Some(200), + CUDAction::Delete, + Some(target_expenditure_id), + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + )); + + let mod_expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost Modified")), + Some(ExpenditureType::HardCost), + Some(1000000), + Some(make_field_description("16344, 57143")), + Some(200), + CUDAction::Update, + Some(target_expenditure_id), + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(mod_expenditure_data), + None, + ), + Error::::ExpenditureNotFound + ); + }); } #[test] fn expenditures_delete_a_selected_expenditure_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost")), - Some(ExpenditureType::HardCost), - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - )); - - let get_expenditure_ids = ExpendituresByProject::::get(project_id); - - let mut target_expenditure_id: [u8; 32] = [0; 32]; - - for expenditure_id in get_expenditure_ids { - let expenditure_data = ExpendituresInfo::::get(expenditure_id).ok_or(Error::::ExpenditureNotFound).unwrap(); - - if expenditure_data.name == make_field_name("Expenditure Test: Hard Cost") { - target_expenditure_id = expenditure_id; - break; - } - } - - let del_expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost Modified")), - Some(ExpenditureType::HardCost), - Some(1000000), - Some(make_field_description("16344, 57143")), - Some(200), - CUDAction::Delete, - Some(target_expenditure_id), - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - )); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost")), + Some(ExpenditureType::HardCost), + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + )); + + let get_expenditure_ids = ExpendituresByProject::::get(project_id); + + let mut target_expenditure_id: [u8; 32] = [0; 32]; + + for expenditure_id in get_expenditure_ids { + let expenditure_data = ExpendituresInfo::::get(expenditure_id) + .ok_or(Error::::ExpenditureNotFound) + .unwrap(); + + if expenditure_data.name == make_field_name("Expenditure Test: Hard Cost") { + target_expenditure_id = expenditure_id; + break; + } + } + + let del_expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost Modified")), + Some(ExpenditureType::HardCost), + Some(1000000), + Some(make_field_description("16344, 57143")), + Some(200), + CUDAction::Delete, + Some(target_expenditure_id), + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + )); + }); } #[test] fn expenditures_expenditure_id_es_required_to_delete_an_expenditure() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost")), - Some(ExpenditureType::HardCost), - Some(100), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(expenditure_data), - None, - )); - - let del_expenditure_data = make_expenditure( - Some(make_field_name("Expenditure Test: Hard Cost Modified")), - Some(ExpenditureType::HardCost), - Some(1000000), - Some(make_field_description("16344, 57143")), - Some(200), - CUDAction::Delete, - None, - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - ), - Error::::ExpenditureIdRequired - ); - }); -} - -#[test] -fn expenditures_an_admin_can_delete_an_expenditure_containing_transactions_with_zero_amount_works(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(0), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - let del_expenditure_data = make_expenditure( - None, - None, - None, - None, - None, - CUDAction::Delete, - Some(expenditure_id), - ); - - assert_eq!(ExpendituresByProject::::get(project_id).len(), 4); - - assert_ok!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - )); - - assert_eq!(ExpendituresByProject::::contains_key(project_id), true); - assert_eq!(ExpendituresByProject::::get(project_id).len(), 3); - assert_eq!(ExpendituresInfo::::get(expenditure_id).is_some(), false); - }); -} - - -#[test] -fn expenditures_an_administrator_deletes_an_expenditure_given_a_drawdown_with_multiple_expenditures_work(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(0), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 2"), ExpenditureType::SoftCost); - assert_eq!(ExpendituresInfo::::get(expenditure_id).is_some(), true); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(0), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - assert_eq!(ExpendituresByProject::::get(project_id).len(), 4); - assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 2); - - let del_expenditure_data = make_expenditure( - None, - None, - None, - None, - None, - CUDAction::Delete, - Some(expenditure_id), - ); - - assert_ok!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - )); - - assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); - assert_eq!(ExpendituresByProject::::contains_key(project_id), true); - assert_eq!(ExpendituresByProject::::get(project_id).len(), 3); - assert_eq!(ExpendituresInfo::::get(expenditure_id).is_some(), false); - }); -} - -#[test] -fn expenditures_an_admin_deletes_all_expenditures_for_a_given_project_works(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let del_expenditure_data = make_expenditure( - None, - None, - None, - None, - None, - CUDAction::Delete, - Some(expenditure_id), - ); - - assert_eq!(ExpendituresByProject::::iter_keys().count(), 1); - assert_eq!(ExpendituresByProject::::get(project_id).len(), 4); - - assert_ok!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - )); - - assert_eq!(ExpendituresByProject::::iter_keys().count(), 1); - assert_eq!(ExpendituresByProject::::get(project_id).len(), 3); - assert_eq!(ExpendituresInfo::::get(expenditure_id).is_some(), false); - - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 2"), ExpenditureType::SoftCost); - - let del_expenditure_data = make_expenditure( - None, - None, - None, - None, - None, - CUDAction::Delete, - Some(expenditure_id), - ); - - assert_ok!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - )); - assert_eq!(ExpendituresByProject::::iter_keys().count(), 1); - assert_eq!(ExpendituresByProject::::get(project_id).len(), 2); - - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 3"), ExpenditureType::Operational); - - let del_expenditure_data = make_expenditure( - None, - None, - None, - None, - None, - CUDAction::Delete, - Some(expenditure_id), - ); - - assert_ok!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - )); - assert_eq!(ExpendituresByProject::::iter_keys().count(), 1); - assert_eq!(ExpendituresByProject::::get(project_id).len(), 1); - - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 4"), ExpenditureType::Others); - - let del_expenditure_data = make_expenditure( - None, - None, - None, - None, - None, - CUDAction::Delete, - Some(expenditure_id), - ); - - assert_ok!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - )); - - assert_eq!(ExpendituresByProject::::iter_keys().count(), 0); - assert_eq!(ExpendituresByProject::::get(project_id).len(), 0); - - }); -} - -#[test] -fn expenditures_an_admin_cannot_delete_a_expenditure_that_is_being_used_draft_status_should_fail(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); - - let del_expenditure_data = make_expenditure( - None, - None, - None, - None, - None, - CUDAction::Delete, - Some(expenditure_id), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - ), - Error::::ExpenditureHasNonZeroTransactions - ); - - }); -} - -#[test] -fn expenditures_an_admin_cannot_delete_a_expenditure_that_is_in_use_submitted_status_should_fail(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); - - let del_expenditure_data = make_expenditure( - None, - None, - None, - None, - None, - CUDAction::Delete, - Some(expenditure_id), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - ), - Error::::ExpenditureHasNonZeroTransactions - ); - - }); -} - -#[test] -fn expenditures_an_admin_cannot_delete_a_expenditure_that_is_in_use_approved_status_should_fail(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); - - let del_expenditure_data = make_expenditure( - None, - None, - None, - None, - None, - CUDAction::Delete, - Some(expenditure_id), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - ), - Error::::ExpenditureHasNonZeroTransactions - ); - - }); -} - -#[test] -fn expenditures_an_admin_cannot_delete_a_expenditure_that_is_in_use_confirmed_status_should_fail(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - let del_expenditure_data = make_expenditure( - None, - None, - None, - None, - None, - CUDAction::Delete, - Some(expenditure_id), - ); - - let bank_documents = make_documents(1); - - assert_ok!(FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents.clone()), - CUDAction::Create, - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Confirmed); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - Some(del_expenditure_data), - None, - ), - Error::::ExpenditureHasNonZeroTransactions - ); - }); -} - -// J O B E L I G I B L E S -// ================================================================================================= + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost")), + Some(ExpenditureType::HardCost), + Some(100), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(expenditure_data), + None, + )); + + let del_expenditure_data = make_expenditure( + Some(make_field_name("Expenditure Test: Hard Cost Modified")), + Some(ExpenditureType::HardCost), + Some(1000000), + Some(make_field_description("16344, 57143")), + Some(200), + CUDAction::Delete, + None, + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + ), + Error::::ExpenditureIdRequired + ); + }); +} + +#[test] +fn expenditures_an_admin_can_delete_an_expenditure_containing_transactions_with_zero_amount_works() +{ + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = make_transaction(Some(expenditure_id), Some(0), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + let del_expenditure_data = + make_expenditure(None, None, None, None, None, CUDAction::Delete, Some(expenditure_id)); + + assert_eq!(ExpendituresByProject::::get(project_id).len(), 4); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + )); + + assert_eq!(ExpendituresByProject::::contains_key(project_id), true); + assert_eq!(ExpendituresByProject::::get(project_id).len(), 3); + assert_eq!(ExpendituresInfo::::get(expenditure_id).is_some(), false); + }); +} + +#[test] +fn expenditures_an_administrator_deletes_an_expenditure_given_a_drawdown_with_multiple_expenditures_work( +) { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = make_transaction(Some(expenditure_id), Some(0), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 2"), + ExpenditureType::SoftCost, + ); + assert_eq!(ExpendituresInfo::::get(expenditure_id).is_some(), true); + + let transaction_data = make_transaction(Some(expenditure_id), Some(0), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + assert_eq!(ExpendituresByProject::::get(project_id).len(), 4); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 2); + + let del_expenditure_data = + make_expenditure(None, None, None, None, None, CUDAction::Delete, Some(expenditure_id)); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + )); + + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + assert_eq!(ExpendituresByProject::::contains_key(project_id), true); + assert_eq!(ExpendituresByProject::::get(project_id).len(), 3); + assert_eq!(ExpendituresInfo::::get(expenditure_id).is_some(), false); + }); +} + +#[test] +fn expenditures_an_admin_deletes_all_expenditures_for_a_given_project_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let del_expenditure_data = + make_expenditure(None, None, None, None, None, CUDAction::Delete, Some(expenditure_id)); + + assert_eq!(ExpendituresByProject::::iter_keys().count(), 1); + assert_eq!(ExpendituresByProject::::get(project_id).len(), 4); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + )); + + assert_eq!(ExpendituresByProject::::iter_keys().count(), 1); + assert_eq!(ExpendituresByProject::::get(project_id).len(), 3); + assert_eq!(ExpendituresInfo::::get(expenditure_id).is_some(), false); + + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 2"), + ExpenditureType::SoftCost, + ); + + let del_expenditure_data = + make_expenditure(None, None, None, None, None, CUDAction::Delete, Some(expenditure_id)); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + )); + assert_eq!(ExpendituresByProject::::iter_keys().count(), 1); + assert_eq!(ExpendituresByProject::::get(project_id).len(), 2); + + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 3"), + ExpenditureType::Operational, + ); + + let del_expenditure_data = + make_expenditure(None, None, None, None, None, CUDAction::Delete, Some(expenditure_id)); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + )); + assert_eq!(ExpendituresByProject::::iter_keys().count(), 1); + assert_eq!(ExpendituresByProject::::get(project_id).len(), 1); + + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 4"), + ExpenditureType::Others, + ); + + let del_expenditure_data = + make_expenditure(None, None, None, None, None, CUDAction::Delete, Some(expenditure_id)); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + )); + + assert_eq!(ExpendituresByProject::::iter_keys().count(), 0); + assert_eq!(ExpendituresByProject::::get(project_id).len(), 0); + }); +} + +#[test] +fn expenditures_an_admin_cannot_delete_a_expenditure_that_is_being_used_draft_status_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); + + let del_expenditure_data = + make_expenditure(None, None, None, None, None, CUDAction::Delete, Some(expenditure_id)); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + ), + Error::::ExpenditureHasNonZeroTransactions + ); + }); +} + #[test] -fn job_eligibles_create_a_job_eligible_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); +fn expenditures_an_admin_cannot_delete_a_expenditure_that_is_in_use_submitted_status_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); - let job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction")), - Some(1000), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - )); + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); - let get_job_eligible_id: [u8; 32] = JobEligiblesByProject::::get(project_id).pop().unwrap(); - - assert!(JobEligiblesInfo::::contains_key(get_job_eligible_id)); - assert_eq!(JobEligiblesInfo::::get(get_job_eligible_id).unwrap().name, make_field_name("Job Eligible Test: Construction")); - assert_eq!(JobEligiblesInfo::::get(get_job_eligible_id).unwrap().job_eligible_amount, 1000); - assert_eq!(JobEligiblesInfo::::get(get_job_eligible_id).unwrap().naics_code, Some(make_field_description("16344, 45862, 57143"))); - assert_eq!(JobEligiblesInfo::::get(get_job_eligible_id).unwrap().jobs_multiplier, Some(200)); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); - }); + let del_expenditure_data = + make_expenditure(None, None, None, None, None, CUDAction::Delete, Some(expenditure_id)); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + ), + Error::::ExpenditureHasNonZeroTransactions + ); + }); +} + +#[test] +fn expenditures_an_admin_cannot_delete_a_expenditure_that_is_in_use_approved_status_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + + let del_expenditure_data = + make_expenditure(None, None, None, None, None, CUDAction::Delete, Some(expenditure_id)); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + ), + Error::::ExpenditureHasNonZeroTransactions + ); + }); +} + +#[test] +fn expenditures_an_admin_cannot_delete_a_expenditure_that_is_in_use_confirmed_status_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + let del_expenditure_data = + make_expenditure(None, None, None, None, None, CUDAction::Delete, Some(expenditure_id)); + + let bank_documents = make_documents(1); + + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Confirmed); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + Some(del_expenditure_data), + None, + ), + Error::::ExpenditureHasNonZeroTransactions + ); + }); +} + +// J O B E L I G I B L E S +// ================================================================================================= +#[test] +fn job_eligibles_create_a_job_eligible_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction")), + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + )); + + let get_job_eligible_id: [u8; 32] = + JobEligiblesByProject::::get(project_id).pop().unwrap(); + + assert!(JobEligiblesInfo::::contains_key(get_job_eligible_id)); + assert_eq!( + JobEligiblesInfo::::get(get_job_eligible_id).unwrap().name, + make_field_name("Job Eligible Test: Construction") + ); + assert_eq!( + JobEligiblesInfo::::get(get_job_eligible_id).unwrap().job_eligible_amount, + 1000 + ); + assert_eq!( + JobEligiblesInfo::::get(get_job_eligible_id).unwrap().naics_code, + Some(make_field_description("16344, 45862, 57143")) + ); + assert_eq!( + JobEligiblesInfo::::get(get_job_eligible_id).unwrap().jobs_multiplier, + Some(200) + ); + }); } #[test] fn job_eligibles_cannot_send_an_empty_array_of_job_eligibles_for_a_given_project() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let job_eligible_data: JobEligibles = bounded_vec![]; + let job_eligible_data: JobEligibles = bounded_vec![]; - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - ), - Error::::JobEligiblesEmpty - ); - }); + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + ), + Error::::JobEligiblesEmpty + ); + }); } #[test] fn job_eligibles_cannot_create_a_job_eligible_without_a_name_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let job_eligible_data = make_job_eligible( - None, - Some(1000), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - ), - Error::::JobEligibleNameRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let job_eligible_data = make_job_eligible( + None, + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + ), + Error::::JobEligibleNameRequired + ); + }); } #[test] fn job_eligibles_cannot_create_a_job_eligible_without_an_amount_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Hard Cost")), - None, - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - ), - Error::::JobEligibleAmountRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Hard Cost")), + None, + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + ), + Error::::JobEligibleAmountRequired + ); + }); } #[test] fn job_eligibles_cannot_create_a_job_eligible_with_an_empty_name_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let job_eligible_data = make_job_eligible( - Some(make_field_name("")), - Some(1000), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - ), - Error::::JobEligiblesNameRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let job_eligible_data = make_job_eligible( + Some(make_field_name("")), + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + ), + Error::::JobEligiblesNameRequired + ); + }); } #[test] fn job_eligibles_edit_a_job_eligible_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction")), - Some(1000), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - )); - - let get_job_eligible_id: [u8; 32] = JobEligiblesByProject::::get(project_id).pop().unwrap(); - - let mod_job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction Modified")), - Some(5000), - Some(make_field_description("16344, 57143")), - Some(320), - CUDAction::Update, - Some(get_job_eligible_id), - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(mod_job_eligible_data), - )); - - assert!(JobEligiblesInfo::::contains_key(get_job_eligible_id)); - assert_eq!(JobEligiblesInfo::::get(get_job_eligible_id).unwrap().name, make_field_name("Job Eligible Test: Construction Modified")); - assert_eq!(JobEligiblesInfo::::get(get_job_eligible_id).unwrap().job_eligible_amount, 5000); - assert_eq!(JobEligiblesInfo::::get(get_job_eligible_id).unwrap().naics_code, Some(make_field_description("16344, 57143"))); - assert_eq!(JobEligiblesInfo::::get(get_job_eligible_id).unwrap().jobs_multiplier, Some(320)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction")), + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + )); + + let get_job_eligible_id: [u8; 32] = + JobEligiblesByProject::::get(project_id).pop().unwrap(); + + let mod_job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction Modified")), + Some(5000), + Some(make_field_description("16344, 57143")), + Some(320), + CUDAction::Update, + Some(get_job_eligible_id), + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(mod_job_eligible_data), + )); + + assert!(JobEligiblesInfo::::contains_key(get_job_eligible_id)); + assert_eq!( + JobEligiblesInfo::::get(get_job_eligible_id).unwrap().name, + make_field_name("Job Eligible Test: Construction Modified") + ); + assert_eq!( + JobEligiblesInfo::::get(get_job_eligible_id).unwrap().job_eligible_amount, + 5000 + ); + assert_eq!( + JobEligiblesInfo::::get(get_job_eligible_id).unwrap().naics_code, + Some(make_field_description("16344, 57143")) + ); + assert_eq!( + JobEligiblesInfo::::get(get_job_eligible_id).unwrap().jobs_multiplier, + Some(320) + ); + }); } #[test] fn job_eligibles_edit_a_given_job_eligible_from_another_project_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - assert_ok!(FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("Project 2"), - make_field_description("Project 2 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("Brooklyn"), - None, - 1000, - 2000, - make_default_expenditures(), - None, - None, - make_field_description("P9f5wbr13BK74p1"), - )); - - let mut project_ids: Vec = ProjectsInfo::::iter_keys().collect(); - let first_project_id = project_ids.pop().unwrap(); - let second_project_id = project_ids.pop().unwrap(); - - let first_job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction")), - Some(1000), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - let second_job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Development")), - Some(22000), - Some(make_field_description("45612, 97856, 43284")), - Some(540), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - first_project_id, - None, - Some(first_job_eligible_data), - )); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - second_project_id, - None, - Some(second_job_eligible_data), - )); - - let second_job_eligible_id = JobEligiblesByProject::::get(second_project_id).pop().unwrap(); - - let mod_first_job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction Modified")), - Some(5000), - Some(make_field_description("16344, 57143")), - Some(320), - CUDAction::Update, - Some(second_job_eligible_id), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - first_project_id, - None, - Some(mod_first_job_eligible_data), - ), - Error::::JobEligibleDoesNotBelongToProject - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + assert_ok!(FundAdmin::projects_create_project( + RuntimeOrigin::signed(1), + make_field_name("Project 2"), + make_field_description("Project 2 description"), + Some(make_field_name("project_image.jpeg")), + make_field_name("Brooklyn"), + None, + 1000, + 2000, + make_default_expenditures(), + None, + None, + make_field_description("P9f5wbr13BK74p1"), + )); + + let mut project_ids: Vec = ProjectsInfo::::iter_keys().collect(); + let first_project_id = project_ids.pop().unwrap(); + let second_project_id = project_ids.pop().unwrap(); + + let first_job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction")), + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + let second_job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Development")), + Some(22000), + Some(make_field_description("45612, 97856, 43284")), + Some(540), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + first_project_id, + None, + Some(first_job_eligible_data), + )); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + second_project_id, + None, + Some(second_job_eligible_data), + )); + + let second_job_eligible_id = + JobEligiblesByProject::::get(second_project_id).pop().unwrap(); + + let mod_first_job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction Modified")), + Some(5000), + Some(make_field_description("16344, 57143")), + Some(320), + CUDAction::Update, + Some(second_job_eligible_id), + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + first_project_id, + None, + Some(mod_first_job_eligible_data), + ), + Error::::JobEligibleDoesNotBelongToProject + ); + }); } #[test] fn job_eligibles_edit_a_given_job_eligible_with_an_invalid_id_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction")), - Some(1000), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - )); - - let mod_job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction Modified")), - Some(5000), - Some(make_field_description("16344, 57143")), - Some(320), - CUDAction::Update, - Some([0; 32]), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(mod_job_eligible_data), - ), - Error::::JobEligibleNotFound - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction")), + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + )); + + let mod_job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction Modified")), + Some(5000), + Some(make_field_description("16344, 57143")), + Some(320), + CUDAction::Update, + Some([0; 32]), + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(mod_job_eligible_data), + ), + Error::::JobEligibleNotFound + ); + }); } #[test] fn job_eligibles_job_eligible_id_is_required_to_update_a_given_job_eligible_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction")), - Some(1000), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - )); - - let mod_job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction Modified")), - Some(5000), - Some(make_field_description("16344, 57143")), - Some(320), - CUDAction::Update, - None, - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(mod_job_eligible_data), - ), - Error::::JobEligibleIdRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction")), + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + )); + + let mod_job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction Modified")), + Some(5000), + Some(make_field_description("16344, 57143")), + Some(320), + CUDAction::Update, + None, + ); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(mod_job_eligible_data), + ), + Error::::JobEligibleIdRequired + ); + }); } #[test] fn job_eligibles_delete_a_given_job_eligible_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction")), - Some(1000), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - )); - - let job_eligible_id = JobEligiblesByProject::::get(project_id).pop().unwrap(); - - let del_job_eligible_data = make_job_eligible( - None, - None, - None, - None, - CUDAction::Delete, - Some(job_eligible_id), - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(del_job_eligible_data), - )); - - assert_eq!(JobEligiblesByProject::::get(project_id).len(), 0); - assert_eq!(JobEligiblesInfo::::iter().count(), 0); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction")), + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + )); + + let job_eligible_id = JobEligiblesByProject::::get(project_id).pop().unwrap(); + + let del_job_eligible_data = + make_job_eligible(None, None, None, None, CUDAction::Delete, Some(job_eligible_id)); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(del_job_eligible_data), + )); + + assert_eq!(JobEligiblesByProject::::get(project_id).len(), 0); + assert_eq!(JobEligiblesInfo::::iter().count(), 0); + }); } #[test] fn job_eligibles_delete_a_given_job_eligible_with_an_invalid_id_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction")), - Some(1000), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - )); - - let del_job_eligible_data = make_job_eligible( - None, - None, - None, - None, - CUDAction::Delete, - Some([0; 32]), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(del_job_eligible_data), - ), - Error::::JobEligibleNotFound - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction")), + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + )); + + let del_job_eligible_data = + make_job_eligible(None, None, None, None, CUDAction::Delete, Some([0; 32])); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(del_job_eligible_data), + ), + Error::::JobEligibleNotFound + ); + }); } #[test] fn job_eligibles_deleting_a_job_eligible_requires_a_job_eligible_id_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction")), - Some(1000), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - )); - - let del_job_eligible_data = make_job_eligible( - None, - None, - None, - None, - CUDAction::Delete, - None, - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(del_job_eligible_data), - ), - Error::::JobEligibleIdRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction")), + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + )); + + let del_job_eligible_data = make_job_eligible(None, None, None, None, CUDAction::Delete, None); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(del_job_eligible_data), + ), + Error::::JobEligibleIdRequired + ); + }); } #[test] // fn job_eligibles_admin_cannot_delete_a_job_eligible_if_has_non_zero_transactions_should_fail() fn job_eligibles_admin_can_delete_a_job_eligible_if_has_non_zero_transactions_draft_status_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(0), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - )); - - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); - assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); - - let job_eligible_data = make_job_eligible( - None, - None, - None, - None, - CUDAction::Delete, - Some(job_eligible_id), - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - )); - - }); -} - -#[test] -fn job_eligibles_admin_cannnot_delete_a_job_eligible_if_has_non_zero_transactions_draft_status_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(1000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - )); - - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); - assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); - - let job_eligible_data = make_job_eligible( - None, - None, - None, - None, - CUDAction::Delete, - Some(job_eligible_id), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - ), - Error::::JobEligibleHasNonZeroTransactions - ); - }); -} - -#[test] -fn job_eligibles_an_administrator_deletes_a_job_eligible_given_a_revenue_with_multiple_job_eligibles_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(0), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - )); - - let job_eligible_data = make_job_eligible( - Some(make_field_name("Job Eligible Test: Construction")), - Some(1000), - Some(make_field_description("16344, 45862, 57143")), - Some(200), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(job_eligible_data), - )); - - let job_eligible_id_2 = get_job_eligible_id(project_id, make_field_name("Job Eligible Test: Construction")); - assert_eq!(JobEligiblesInfo::::get(job_eligible_id_2).is_some(), true); - - let revenue_transaction_data_2 = make_revenue_transaction( - Some(job_eligible_id_2), - Some(1000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data_2), - false, - )); - - assert_eq!(JobEligiblesByProject::::get(project_id).len(), 2); - assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 2); - - let del_job_eligible_data = make_job_eligible( - None, - None, - None, - None, - CUDAction::Delete, - Some(job_eligible_id), - ); - - assert_ok!(FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(del_job_eligible_data), - )); - - assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); - assert_eq!(JobEligiblesByProject::::get(project_id).len(), 1); - assert_eq!(JobEligiblesInfo::::get(job_eligible_id).is_some(), false); - }); -} - -#[test] -fn job_eligibles_an_admin_cannot_delete_a_job_eligible_that_is_being_used_draft_status_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - )); - - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); - assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); - - let del_job_eligible_data = make_job_eligible( - None, - None, - None, - None, - CUDAction::Delete, - Some(job_eligible_id), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(del_job_eligible_data), - ), - Error::::JobEligibleHasNonZeroTransactions - ); - }); -} - -#[test] -fn job_eligibles_an_admin_cannot_delete_a_job_eligible_that_is_being_used_submitted_status_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - true, - )); - - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Submitted); - assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); - - let del_job_eligible_data = make_job_eligible( - None, - None, - None, - None, - CUDAction::Delete, - Some(job_eligible_id), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(del_job_eligible_data), - ), - Error::::JobEligibleHasNonZeroTransactions - ); - }); -} - -#[test] - -fn job_eligibles_an_admin_cannot_delete_a_job_eligible_that_is_being_used_approved_status_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_revenue( - RuntimeOrigin::signed(1), - project_id, - revenue_id, - )); - - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Approved); - assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); - - let del_job_eligible_data = make_job_eligible( - None, - None, - None, - None, - CUDAction::Delete, - Some(job_eligible_id), - ); - - assert_noop!( - FundAdmin::expenditures_and_job_eligibles( - RuntimeOrigin::signed(1), - project_id, - None, - Some(del_job_eligible_data), - ), - Error::::JobEligibleHasNonZeroTransactions - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(0), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + )); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + + let job_eligible_data = + make_job_eligible(None, None, None, None, CUDAction::Delete, Some(job_eligible_id)); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + )); + }); } -// D R A W D O W N S -// ============================================================================ #[test] -fn drawdowns_drawdowns_are_initialized_correctly_after_a_project_is_created_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_simple_project()); +fn job_eligibles_admin_cannnot_delete_a_job_eligible_if_has_non_zero_transactions_draft_status_should_fail( +) { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(1000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + )); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + + let job_eligible_data = + make_job_eligible(None, None, None, None, CUDAction::Delete, Some(job_eligible_id)); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + ), + Error::::JobEligibleHasNonZeroTransactions + ); + }); +} + +#[test] +fn job_eligibles_an_administrator_deletes_a_job_eligible_given_a_revenue_with_multiple_job_eligibles_works( +) { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(0), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + )); + + let job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction")), + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + )); + + let job_eligible_id_2 = + get_job_eligible_id(project_id, make_field_name("Job Eligible Test: Construction")); + assert_eq!(JobEligiblesInfo::::get(job_eligible_id_2).is_some(), true); + + let revenue_transaction_data_2 = + make_revenue_transaction(Some(job_eligible_id_2), Some(1000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data_2), + false, + )); + + assert_eq!(JobEligiblesByProject::::get(project_id).len(), 2); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 2); + + let del_job_eligible_data = + make_job_eligible(None, None, None, None, CUDAction::Delete, Some(job_eligible_id)); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(del_job_eligible_data), + )); + + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + assert_eq!(JobEligiblesByProject::::get(project_id).len(), 1); + assert_eq!(JobEligiblesInfo::::get(job_eligible_id).is_some(), false); + }); +} + +#[test] +fn job_eligibles_an_admin_cannot_delete_a_job_eligible_that_is_being_used_draft_status_should_fail() +{ + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + )); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + + let del_job_eligible_data = + make_job_eligible(None, None, None, None, CUDAction::Delete, Some(job_eligible_id)); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(del_job_eligible_data), + ), + Error::::JobEligibleHasNonZeroTransactions + ); + }); +} + +#[test] +fn job_eligibles_an_admin_cannot_delete_a_job_eligible_that_is_being_used_submitted_status_should_fail( +) { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + )); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Submitted); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + + let del_job_eligible_data = + make_job_eligible(None, None, None, None, CUDAction::Delete, Some(job_eligible_id)); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(del_job_eligible_data), + ), + Error::::JobEligibleHasNonZeroTransactions + ); + }); +} + +#[test] + +fn job_eligibles_an_admin_cannot_delete_a_job_eligible_that_is_being_used_approved_status_should_fail( +) { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + )); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_eq!(DrawdownsByProject::::get(project_id).len(), 3); - let drawdowns_ids = DrawdownsByProject::::get(project_id); + assert_ok!(FundAdmin::approve_revenue(RuntimeOrigin::signed(1), project_id, revenue_id,)); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Approved); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + + let del_job_eligible_data = + make_job_eligible(None, None, None, None, CUDAction::Delete, Some(job_eligible_id)); + + assert_noop!( + FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(del_job_eligible_data), + ), + Error::::JobEligibleHasNonZeroTransactions + ); + }); +} - for drawdown_id in drawdowns_ids { - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().project_id, project_id); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().drawdown_number, 1); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 0); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().description, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().feedback, None); - } - }); +// D R A W D O W N S +// ================================================================================================= +#[test] +fn drawdowns_drawdowns_are_initialized_correctly_after_a_project_is_created_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_simple_project()); + + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + assert_eq!(DrawdownsByProject::::get(project_id).len(), 3); + let drawdowns_ids = DrawdownsByProject::::get(project_id); + + for drawdown_id in drawdowns_ids { + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().project_id, project_id); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().drawdown_number, 1); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().description, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().feedback, None); + } + }); } #[test] fn drawdowns_a_builder_saves_a_drawdown_as_a_draft_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); - - assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); - let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().project_id, project_id); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().drawdown_id, drawdown_id); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().expenditure_id, expenditure_id); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().closed_date, 0); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().feedback, None); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().amount, 10000); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().status, TransactionStatus::Draft); - }); -} - - -#[test] -fn drawdowns_a_user_modifies_a_transaction_in_draft_status_works(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); - let mod_transaction_data = make_transaction( - Some(expenditure_id), - Some(20000), - CUDAction::Update, - Some(transaction_id), - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(mod_transaction_data), - false, - )); - - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().status, TransactionStatus::Draft); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().amount, 20000); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); + + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().project_id, project_id); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().drawdown_id, drawdown_id); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().expenditure_id, + expenditure_id + ); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().closed_date, 0); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().feedback, None); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().amount, 10000); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Draft + ); + }); +} + +#[test] +fn drawdowns_a_user_modifies_a_transaction_in_draft_status_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + let mod_transaction_data = + make_transaction(Some(expenditure_id), Some(20000), CUDAction::Update, Some(transaction_id)); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(mod_transaction_data), + false, + )); + + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Draft + ); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().amount, 20000); + }); } #[test] fn drawdowns_a_builder_cannot_submit_a_drawdown_twice_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); - let mod_transaction_data = make_transaction( - Some(expenditure_id), - Some(20000), - CUDAction::Update, - Some(transaction_id), - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(mod_transaction_data.clone()), - true, - )); - - assert_noop!( - FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(mod_transaction_data), - true, - ), - Error::::CannotPerformActionOnSubmittedDrawdown - ); - }); -} - -#[test] -fn drawdowns_a_user_deletes_a_transaction_in_draft_status_works(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); - let del_transaction_data = make_transaction( - None, - None, - CUDAction::Delete, - Some(transaction_id), - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(del_transaction_data), - false, - )); - - assert_eq!(TransactionsInfo::::contains_key(transaction_id), false); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 0); - }); -} - -#[test] -fn drawdowns_a_user_cannot_save_transactions_as_draft_if_transactions_are_not_provided_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - - assert_noop!( - FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - None, - false, - ), - Error::::TransactionsRequired - ); - }); -} - -#[test] -fn drawdowns_a_user_cannot_send_an_empty_array_of_transactions_when_saving_as_a_draft_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - - let empty_transaction_data: Transactions = bounded_vec![]; - - assert_noop!( - FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(empty_transaction_data), - false, - ), - Error::::EmptyTransactions - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + let mod_transaction_data = + make_transaction(Some(expenditure_id), Some(20000), CUDAction::Update, Some(transaction_id)); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(mod_transaction_data.clone()), + true, + )); + + assert_noop!( + FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(mod_transaction_data), + true, + ), + Error::::CannotPerformActionOnSubmittedDrawdown + ); + }); +} + +#[test] +fn drawdowns_a_user_deletes_a_transaction_in_draft_status_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + let del_transaction_data = + make_transaction(None, None, CUDAction::Delete, Some(transaction_id)); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(del_transaction_data), + false, + )); + + assert_eq!(TransactionsInfo::::contains_key(transaction_id), false); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 0); + }); +} + +#[test] +fn drawdowns_a_user_cannot_save_transactions_as_draft_if_transactions_are_not_provided_should_fail() +{ + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + + assert_noop!( + FundAdmin::submit_drawdown(RuntimeOrigin::signed(2), project_id, drawdown_id, None, false,), + Error::::TransactionsRequired + ); + }); +} + +#[test] +fn drawdowns_a_user_cannot_send_an_empty_array_of_transactions_when_saving_as_a_draft_should_fail() +{ + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + + let empty_transaction_data: Transactions = bounded_vec![]; + + assert_noop!( + FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(empty_transaction_data), + false, + ), + Error::::EmptyTransactions + ); + }); } #[test] fn drawdowns_a_user_cannot_send_a_transaction_without_the_expenditure_id_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let transaction_data = make_transaction( - None, - Some(10000), - CUDAction::Create, - None, - ); + let transaction_data = make_transaction(None, Some(10000), CUDAction::Create, None); - assert_noop!( - FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - ), - Error::::ExpenditureIdRequired - ); - }); + assert_noop!( + FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + ), + Error::::ExpenditureIdRequired + ); + }); } #[test] fn drawdowns_a_user_cannot_create_a_transaction_without_an_amount_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - None, - CUDAction::Create, - None, - ); - - assert_noop!( - FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - ), - Error::::AmountRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = make_transaction(Some(expenditure_id), None, CUDAction::Create, None); + + assert_noop!( + FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + ), + Error::::AmountRequired + ); + }); } #[test] fn drawdowns_transaction_id_is_required_when_editing_a_transaction_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - let mod_transaction_data = make_transaction( - Some(expenditure_id), - Some(20000), - CUDAction::Update, - None, - ); - - assert_noop!( - FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(mod_transaction_data), - false, - ), - Error::::TransactionIdRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + let mod_transaction_data = + make_transaction(Some(expenditure_id), Some(20000), CUDAction::Update, None); + + assert_noop!( + FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(mod_transaction_data), + false, + ), + Error::::TransactionIdRequired + ); + }); } #[test] fn drawdowns_transaction_id_is_required_when_deleting_a_transaction_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - let del_transaction_data = make_transaction( - None, - None, - CUDAction::Delete, - None, - ); - - assert_noop!( - FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(del_transaction_data), - false, - ), - Error::::TransactionIdRequired - ); - }); -} - -#[test] -fn drawdowns_a_user_submits_a_drawdown_for_approval_works(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - let drawdown_data = DrawdownsInfo::::get(drawdown_id).unwrap(); - - assert_eq!(drawdown_data.status, DrawdownStatus::Submitted); - assert_eq!(drawdown_data.total_amount, 10000); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + let del_transaction_data = make_transaction(None, None, CUDAction::Delete, None); + + assert_noop!( + FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(del_transaction_data), + false, + ), + Error::::TransactionIdRequired + ); + }); +} + +#[test] +fn drawdowns_a_user_submits_a_drawdown_for_approval_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + let drawdown_data = DrawdownsInfo::::get(drawdown_id).unwrap(); + + assert_eq!(drawdown_data.status, DrawdownStatus::Submitted); + assert_eq!(drawdown_data.total_amount, 10000); + }); } #[test] fn drawdowns_a_user_submits_a_draft_drawdown_for_approval_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - None, - true, - )); + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + None, + true, + )); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); - }); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); + }); } #[test] -fn drawdowns_a_user_tries_to_add_transactions_using_an_empty_array_before_submitting_the_drawdown_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); +fn drawdowns_a_user_tries_to_add_transactions_using_an_empty_array_before_submitting_the_drawdown_should_fail( +) { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let transaction_data: Transactions = bounded_vec![]; + let transaction_data: Transactions = bounded_vec![]; - assert_noop!( - FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - ), - Error::::EmptyTransactions - ); - }); + assert_noop!( + FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + ), + Error::::EmptyTransactions + ); + }); } #[test] fn drawdowns_a_drawdown_cannot_be_submitted_if_has_no_transactions_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - assert_noop!( - FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - None, - true, - ), - Error::::DrawdownHasNoTransactions - ); - }); + assert_noop!( + FundAdmin::submit_drawdown(RuntimeOrigin::signed(2), project_id, drawdown_id, None, true,), + Error::::DrawdownHasNoTransactions + ); + }); } #[test] fn drawdowns_a_builder_deletes_all_transactions_while_submitting_a_drawdown_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); - - let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); - - let del_transaction_data = make_transaction( - Some(expenditure_id), - None, - CUDAction::Delete, - Some(transaction_id), - ); - - assert_noop!( - FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(del_transaction_data), - true, - ), - Error::::DrawdownHasNoTransactions - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + + let del_transaction_data = + make_transaction(Some(expenditure_id), None, CUDAction::Delete, Some(transaction_id)); + + assert_noop!( + FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(del_transaction_data), + true, + ), + Error::::DrawdownHasNoTransactions + ); + }); } #[test] fn drawdowns_after_a_drawdown_is_submitted_the_status_is_updated_in_project_data_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); - assert_eq!(ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, Some(DrawdownStatus::Submitted)); - }); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Submitted) + ); + }); } #[test] fn drawdowns_an_administrators_approves_a_submitted_drawdown_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); - }); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + }); } #[test] fn drawdowns_an_administrator_cannot_aproves_a_drawdown_that_is_not_submitted_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - assert_noop!( - FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - ), - Error::::DrawdownNotSubmitted - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + assert_noop!( + FundAdmin::approve_drawdown(RuntimeOrigin::signed(1), project_id, drawdown_id, None, None,), + Error::::DrawdownNotSubmitted + ); + }); } #[test] fn drawdowns_after_a_drawdown_is_approved_the_next_one_is_generated_autoamtically_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); - let next_drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 2); + let next_drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 2); - assert_eq!(DrawdownsInfo::::get(next_drawdown_id).unwrap().status, DrawdownStatus::Draft); - }); + assert_eq!(DrawdownsInfo::::get(next_drawdown_id).unwrap().status, DrawdownStatus::Draft); + }); } #[test] fn drawdowns_an_administrator_rejects_a_given_drawdown_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); - let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); - let feedback = make_field_description("Transaction was rejected bacause it was not valid"); + let feedback = make_field_description("Transaction was rejected bacause it was not valid"); - let transaction_feedback = make_transaction_feedback(transaction_id, feedback.clone()); + let transaction_feedback = make_transaction_feedback(transaction_id, feedback.clone()); - assert_ok!(FundAdmin::reject_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(transaction_feedback), - None, - )); + assert_ok!(FundAdmin::reject_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(transaction_feedback), + None, + )); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Rejected); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().status, TransactionStatus::Rejected); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().feedback, Some(feedback)); - }); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Rejected); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Rejected + ); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().feedback, Some(feedback)); + }); } #[test] fn drawdowns_an_administrator_cannot_rejects_a_drawdown_that_is_not_submitted_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - false, - )); - - let transaction_feedback = make_transaction_feedback( - get_transaction_id(project_id, drawdown_id, expenditure_id), - make_field_description("Transaction was rejected bacause it was not valid"), - ); - - assert_noop!( - FundAdmin::reject_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(transaction_feedback), - None, - ), - Error::::DrawdownNotSubmitted - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + false, + )); + + let transaction_feedback = make_transaction_feedback( + get_transaction_id(project_id, drawdown_id, expenditure_id), + make_field_description("Transaction was rejected bacause it was not valid"), + ); + + assert_noop!( + FundAdmin::reject_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(transaction_feedback), + None, + ), + Error::::DrawdownNotSubmitted + ); + }); } #[test] fn drawdowns_an_administrator_cannot_rejects_a_drawdown_without_a_feedback_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_noop!( - FundAdmin::reject_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - ), - Error::::EB5MissingFeedback - ); - }); -} - -#[test] -fn drawdowns_an_administrator_rejects_a_eb5_drawdown_with_an_empty_feedback_should_fail(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - let transaction_feedback: TransactionsFeedback = bounded_vec![]; - - assert_noop!( - FundAdmin::reject_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(transaction_feedback), - None, - ), - Error::::EmptyEb5Feedback - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_noop!( + FundAdmin::reject_drawdown(RuntimeOrigin::signed(1), project_id, drawdown_id, None, None,), + Error::::EB5MissingFeedback + ); + }); +} + +#[test] +fn drawdowns_an_administrator_rejects_a_eb5_drawdown_with_an_empty_feedback_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + let transaction_feedback: TransactionsFeedback = bounded_vec![]; + + assert_noop!( + FundAdmin::reject_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(transaction_feedback), + None, + ), + Error::::EmptyEb5Feedback + ); + }); } // B U L K D R A W D O W N S -// ============================================================================ +// ================================================================================================= #[test] fn bulkupload_a_builder_submits_a_construction_loan_drawdown_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - - let drawdown_description = make_field_description("Construction Loan Drawdown 1"); - let total_amount = 100000u64; - let documents = make_documents(1); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - drawdown_description.clone(), - total_amount, - documents.clone(), - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().description, Some(drawdown_description)); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, total_amount); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, Some(documents)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + + let drawdown_description = make_field_description("Construction Loan Drawdown 1"); + let total_amount = 100000u64; + let documents = make_documents(1); + + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + drawdown_description.clone(), + total_amount, + documents.clone(), + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); + assert_eq!( + DrawdownsInfo::::get(drawdown_id).unwrap().description, + Some(drawdown_description) + ); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, total_amount); + assert_eq!( + DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, + Some(documents) + ); + }); } #[test] fn bulkupload_a_builder_submits_a_developer_equity_drawdown_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::DeveloperEquity, 1); - - let drawdown_description = make_field_description("Developer Equity Drawdown 1"); - let total_amount = 100000u64; - let documents = make_documents(1); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - drawdown_description.clone(), - total_amount, - documents.clone(), - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().description, Some(drawdown_description)); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, total_amount); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, Some(documents)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::DeveloperEquity, 1); + + let drawdown_description = make_field_description("Developer Equity Drawdown 1"); + let total_amount = 100000u64; + let documents = make_documents(1); + + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + drawdown_description.clone(), + total_amount, + documents.clone(), + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); + assert_eq!( + DrawdownsInfo::::get(drawdown_id).unwrap().description, + Some(drawdown_description) + ); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, total_amount); + assert_eq!( + DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, + Some(documents) + ); + }); } #[test] fn bulkupload_a_builder_submits_a_eb5_drawdown_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - - let drawdown_description = make_field_description("EB5 Drawdown 1"); - let total_amount = 100000u64; - let documents = make_documents(1); - - assert_noop!( - FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - drawdown_description.clone(), - total_amount, - documents.clone(), - ), - Error::::DrawdownTypeNotSupportedForBulkUpload - ); - }); -} - -#[test] -fn bulkupload_a_builder_submits_an_empty_array_of_documents_for_a_construction_loan_drawdown_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - - let drawdown_description = make_field_description("Construction Loan Drawdown 1"); - let total_amount = 100000u64; - let documents: Documents = bounded_vec![]; - - assert_noop!( - FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - drawdown_description.clone(), - total_amount, - documents.clone(), - ), - Error::::BulkUploadDocumentsRequired - ); - }); -} - -#[test] -fn bulkupload_a_builder_submits_an_empty_adescription_for_a_construction_loan_drawdown_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - - let drawdown_description: FieldDescription = bounded_vec![]; - let total_amount = 100000u64; - let documents = make_documents(1); - - assert_noop!( - FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - drawdown_description.clone(), - total_amount, - documents.clone(), - ), - Error::::BulkUploadDescriptionRequired - ); - }); -} - -#[test] -fn bulkupload_after_a_contruction_loan_is_submitted_their_status_is_updated_in_project_data_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - - let drawdown_description = make_field_description("Construction Loan Drawdown 1"); - let total_amount = 100000u64; - let documents = make_documents(1); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - drawdown_description.clone(), - total_amount, - documents.clone(), - )); - - assert_eq!(ProjectsInfo::::get(project_id).unwrap().construction_loan_drawdown_status, Some(DrawdownStatus::Submitted)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + + let drawdown_description = make_field_description("EB5 Drawdown 1"); + let total_amount = 100000u64; + let documents = make_documents(1); + + assert_noop!( + FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + drawdown_description.clone(), + total_amount, + documents.clone(), + ), + Error::::DrawdownTypeNotSupportedForBulkUpload + ); + }); +} + +#[test] +fn bulkupload_a_builder_submits_an_empty_array_of_documents_for_a_construction_loan_drawdown_should_fail( +) { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + + let drawdown_description = make_field_description("Construction Loan Drawdown 1"); + let total_amount = 100000u64; + let documents: Documents = bounded_vec![]; + + assert_noop!( + FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + drawdown_description.clone(), + total_amount, + documents.clone(), + ), + Error::::BulkUploadDocumentsRequired + ); + }); +} + +#[test] +fn bulkupload_a_builder_submits_an_empty_adescription_for_a_construction_loan_drawdown_should_fail() +{ + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + + let drawdown_description: FieldDescription = bounded_vec![]; + let total_amount = 100000u64; + let documents = make_documents(1); + + assert_noop!( + FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + drawdown_description.clone(), + total_amount, + documents.clone(), + ), + Error::::BulkUploadDescriptionRequired + ); + }); +} + +#[test] +fn bulkupload_after_a_contruction_loan_is_submitted_their_status_is_updated_in_project_data_works() +{ + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + + let drawdown_description = make_field_description("Construction Loan Drawdown 1"); + let total_amount = 100000u64; + let documents = make_documents(1); + + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + drawdown_description.clone(), + total_amount, + documents.clone(), + )); + + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().construction_loan_drawdown_status, + Some(DrawdownStatus::Submitted) + ); + }); +} + +#[test] +fn bulkupload_after_a_developer_equity_is_submitted_their_status_is_updated_in_project_data_works() +{ + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::DeveloperEquity, 1); + + let drawdown_description = make_field_description("Developer Equity Drawdown 1"); + let total_amount = 100000u64; + let documents = make_documents(1); + + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + drawdown_description.clone(), + total_amount, + documents.clone(), + )); + + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().developer_equity_drawdown_status, + Some(DrawdownStatus::Submitted) + ); + }); +} + +#[test] +fn bulkupload_an_administrator_saves_transactions_without_approving_the_drawdown_pseudo_draft_works( +) { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + make_field_description("Construction Loan Drawdown 1"), + 100000u64, + make_documents(1), + )); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(false), + Some(transaction_data), + )); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Submitted + ); + }); } #[test] -fn bulkupload_after_a_developer_equity_is_submitted_their_status_is_updated_in_project_data_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::DeveloperEquity, 1); - - let drawdown_description = make_field_description("Developer Equity Drawdown 1"); - let total_amount = 100000u64; - let documents = make_documents(1); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - drawdown_description.clone(), - total_amount, - documents.clone(), - )); - - assert_eq!(ProjectsInfo::::get(project_id).unwrap().developer_equity_drawdown_status, Some(DrawdownStatus::Submitted)); - }); +fn bulkupload_an_administrator_saves_transactions_and_approves_the_drawdown_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + make_field_description("Construction Loan Drawdown 1"), + 100000u64, + make_documents(1), + )); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(true), + Some(transaction_data), + )); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Approved + ); + }); +} + +#[test] +fn bulkupload_an_array_of_transactions_is_required_to_save_transactions_as_a_pseudo_draft_should_fail( +) { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + make_field_description("Construction Loan Drawdown 1"), + 100000u64, + make_documents(1), + )); + + assert_noop!( + FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(false), + None, + ), + Error::::TransactionsRequired + ); + }); } #[test] -fn bulkupload_an_administrator_saves_transactions_without_approving_the_drawdown_pseudo_draft_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - make_field_description("Construction Loan Drawdown 1"), - 100000u64, - make_documents(1), - )); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); +fn bulkupload_an_administrator_sends_an_empty_array_of_transactions_as_a_pseudo_draft_should_fail() +{ + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(false), - Some(transaction_data), - )); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + make_field_description("Construction Loan Drawdown 1"), + 100000u64, + make_documents(1), + )); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().status, TransactionStatus::Submitted); - }); -} + let transaction_data: Transactions = bounded_vec![]; -#[test] -fn bulkupload_an_administrator_saves_transactions_and_approves_the_drawdown_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - make_field_description("Construction Loan Drawdown 1"), - 100000u64, - make_documents(1), - )); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(true), - Some(transaction_data), - )); - - let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().status, TransactionStatus::Approved); - }); -} - -#[test] -fn bulkupload_an_array_of_transactions_is_required_to_save_transactions_as_a_pseudo_draft_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - make_field_description("Construction Loan Drawdown 1"), - 100000u64, - make_documents(1), - )); - - assert_noop!( - FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(false), - None, - ), - Error::::TransactionsRequired - ); - }); -} - -#[test] -fn bulkupload_an_administrator_sends_an_empty_array_of_transactions_as_a_pseudo_draft_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - make_field_description("Construction Loan Drawdown 1"), - 100000u64, - make_documents(1), - )); - - let transaction_data: Transactions = bounded_vec![]; - - assert_noop!( - FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(false), - Some(transaction_data), - ), - Error::::EmptyTransactions - ); - }); + assert_noop!( + FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(false), + Some(transaction_data), + ), + Error::::EmptyTransactions + ); + }); } #[test] fn bulkupload_an_administrator_sends_an_empty_array_while_approving_a_drawdown_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - make_field_description("Construction Loan Drawdown 1"), - 100000u64, - make_documents(1), - )); + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + make_field_description("Construction Loan Drawdown 1"), + 100000u64, + make_documents(1), + )); - let transaction_data: Transactions = bounded_vec![]; + let transaction_data: Transactions = bounded_vec![]; - assert_noop!( - FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(true), - Some(transaction_data), - ), - Error::::EmptyTransactions - ); - }); + assert_noop!( + FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(true), + Some(transaction_data), + ), + Error::::EmptyTransactions + ); + }); } #[test] fn bulkupload_an_administrator_rejects_a_contruction_loan_drawdown_with_a_feedback_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - make_field_description("Construction Loan Drawdown 1"), - 100000u64, - make_documents(1), - )); + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + make_field_description("Construction Loan Drawdown 1"), + 100000u64, + make_documents(1), + )); - let bulkupload_feedback = make_field_description("Bulkupload Feedback"); + let bulkupload_feedback = make_field_description("Bulkupload Feedback"); - assert_ok!(FundAdmin::reject_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - Some(bulkupload_feedback.clone()), - )); + assert_ok!(FundAdmin::reject_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + Some(bulkupload_feedback.clone()), + )); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Rejected); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().feedback, Some(bulkupload_feedback)); - }); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Rejected); + assert_eq!( + DrawdownsInfo::::get(drawdown_id).unwrap().feedback, + Some(bulkupload_feedback) + ); + }); } #[test] fn bulkupload_an_administrator_rejects_a_developer_equity_drawdown_with_a_feedback_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::DeveloperEquity, 1); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::DeveloperEquity, 1); - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - make_field_description("Developer Equity Drawdown 1"), - 100000u64, - make_documents(1), - )); + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + make_field_description("Developer Equity Drawdown 1"), + 100000u64, + make_documents(1), + )); - let bulkupload_feedback = make_field_description("Bulkupload Feedback"); + let bulkupload_feedback = make_field_description("Bulkupload Feedback"); - assert_ok!(FundAdmin::reject_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - Some(bulkupload_feedback.clone()), - )); + assert_ok!(FundAdmin::reject_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + Some(bulkupload_feedback.clone()), + )); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Rejected); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().feedback, Some(bulkupload_feedback)); - }); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Rejected); + assert_eq!( + DrawdownsInfo::::get(drawdown_id).unwrap().feedback, + Some(bulkupload_feedback) + ); + }); } #[test] fn bulkupload_an_administrator_rejects_a_bulkupload_drawdown_without_a_feedback_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - make_field_description("Construction Loan Drawdown 1"), - 100000u64, - make_documents(1), - )); - - assert_noop!( - FundAdmin::reject_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - ), - Error::::NoFeedbackProvidedForBulkUpload - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + make_field_description("Construction Loan Drawdown 1"), + 100000u64, + make_documents(1), + )); + + assert_noop!( + FundAdmin::reject_drawdown(RuntimeOrigin::signed(1), project_id, drawdown_id, None, None,), + Error::::NoFeedbackProvidedForBulkUpload + ); + }); } #[test] fn bulkupload_an_administrator_rejects_a_bulkupload_drawdown_with_an_empty_feedback_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - make_field_description("Construction Loan Drawdown 1"), - 100000u64, - make_documents(1), - )); - - assert_noop!( - FundAdmin::reject_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - Some(make_field_description("")), - ), - Error::::EmptyBulkUploadFeedback - ); - }); -} - -//TODO: A rejected drawdown changes its status from rejected to submitted after a builder submits again the drawdown + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + make_field_description("Construction Loan Drawdown 1"), + 100000u64, + make_documents(1), + )); + + assert_noop!( + FundAdmin::reject_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + Some(make_field_description("")), + ), + Error::::EmptyBulkUploadFeedback + ); + }); +} + +//TODO: A rejected drawdown changes its status from rejected to submitted after a builder submits +// again the drawdown // R E V E N U E S // ================================================================================================= #[test] fn revenues_are_initialized_correctly_after_a_project_is_created_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_eq!(RevenuesInfo::::iter().count(), 1); + assert_eq!(RevenuesInfo::::iter().count(), 1); - let revenue_id = RevenuesInfo::::iter_keys().next().unwrap(); + let revenue_id = RevenuesInfo::::iter_keys().next().unwrap(); - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().project_id, project_id); - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().revenue_number, 1); - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().total_amount, 0); - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); - }); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().project_id, project_id); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().revenue_number, 1); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().total_amount, 0); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); + }); } #[test] fn revenues_a_builder_saves_a_revenue_as_draft_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - )); - - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); - - let revenue_transaction_id = get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); - assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().project_id, project_id); - assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().revenue_id, revenue_id); - assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().job_eligible_id, job_eligible_id); - assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().closed_date, 0); - assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().feedback, None); - assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().amount, 10000); - assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().status, RevenueTransactionStatus::Draft); - assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().documents, Some(make_documents(1))); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + )); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); + + let revenue_transaction_id = + get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); + assert_eq!( + RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().project_id, + project_id + ); + assert_eq!( + RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().revenue_id, + revenue_id + ); + assert_eq!( + RevenueTransactionsInfo::::get(revenue_transaction_id) + .unwrap() + .job_eligible_id, + job_eligible_id + ); + assert_eq!( + RevenueTransactionsInfo::::get(revenue_transaction_id) + .unwrap() + .closed_date, + 0 + ); + assert_eq!( + RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().feedback, + None + ); + assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().amount, 10000); + assert_eq!( + RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().status, + RevenueTransactionStatus::Draft + ); + assert_eq!( + RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().documents, + Some(make_documents(1)) + ); + }); } #[test] fn revenues_a_builder_modifies_a_transaction_in_draft_status_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - )); - - let revenue_transaction_id = get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); - let mod_revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(20000), - CUDAction::Update, - Some(revenue_transaction_id), - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(mod_revenue_transaction_data), - false, - )); - - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); - assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().amount, 20000); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + )); + + let revenue_transaction_id = + get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); + let mod_revenue_transaction_data = make_revenue_transaction( + Some(job_eligible_id), + Some(20000), + CUDAction::Update, + Some(revenue_transaction_id), + ); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(mod_revenue_transaction_data), + false, + )); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); + assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().amount, 20000); + }); } #[test] fn revenues_a_user_deletes_a_transaction_in_draft_status_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - )); - - let revenue_transaction_id = get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); - let del_revenue_transaction_data = make_revenue_transaction( - None, - None, - CUDAction::Delete, - Some(revenue_transaction_id), - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(del_revenue_transaction_data), - false, - )); - - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); - assert_eq!(RevenueTransactionsInfo::::contains_key(revenue_transaction_id), false); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + )); + + let revenue_transaction_id = + get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); + let del_revenue_transaction_data = + make_revenue_transaction(None, None, CUDAction::Delete, Some(revenue_transaction_id)); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(del_revenue_transaction_data), + false, + )); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); + assert_eq!(RevenueTransactionsInfo::::contains_key(revenue_transaction_id), false); + }); } #[test] fn revenues_a_builder_cannot_submit_a_revenue_if_there_is_no_revenue_transaction_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); + let revenue_id = get_revenue_id(project_id, 1); - assert_noop!( - FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - None, - false, - ), - Error::::RevenueTransactionsRequired - ); - }); + assert_noop!( + FundAdmin::submit_revenue(RuntimeOrigin::signed(2), project_id, revenue_id, None, false,), + Error::::RevenueTransactionsRequired + ); + }); } #[test] -fn revenues_a_user_cannot_submit_a_revenue_as_draft_with_an_empty_array_of_transactions_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); +fn revenues_a_user_cannot_submit_a_revenue_as_draft_with_an_empty_array_of_transactions_should_fail( +) { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); + let revenue_id = get_revenue_id(project_id, 1); - let empty_transaction_data: RevenueTransactions = bounded_vec![]; + let empty_transaction_data: RevenueTransactions = bounded_vec![]; - assert_noop!( - FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(empty_transaction_data), - false, - ), - Error::::RevenueTransactionsEmpty - ); - }); + assert_noop!( + FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(empty_transaction_data), + false, + ), + Error::::RevenueTransactionsEmpty + ); + }); } #[test] fn revenues_a_user_cannot_create_a_revenue_transaction_with_no_job_eligible_id_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); + let revenue_id = get_revenue_id(project_id, 1); - let revenue_transaction_data = make_revenue_transaction( - None, - Some(10000), - CUDAction::Create, - None, - ); + let revenue_transaction_data = + make_revenue_transaction(None, Some(10000), CUDAction::Create, None); - assert_noop!( - FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - ), - Error::::JobEligibleIdRequired - ); - }); + assert_noop!( + FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + ), + Error::::JobEligibleIdRequired + ); + }); } #[test] fn revenues_a_user_cannot_create_a_revenue_transaction_with_no_amount_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - None, - CUDAction::Create, - None, - ); - - assert_noop!( - FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - ), - Error::::RevenueAmountRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), None, CUDAction::Create, None); + + assert_noop!( + FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + ), + Error::::RevenueAmountRequired + ); + }); } #[test] fn revenues_transaction_id_is_required_for_updating_a_transaction_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - )); - - let mod_revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(20000), - CUDAction::Update, - None, - ); - - assert_noop!( - FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(mod_revenue_transaction_data), - false, - ), - Error::::RevenueTransactionIdRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + )); + + let mod_revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(20000), CUDAction::Update, None); + + assert_noop!( + FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(mod_revenue_transaction_data), + false, + ), + Error::::RevenueTransactionIdRequired + ); + }); } #[test] fn revenues_transaction_id_is_required_for_deleting_a_transaction_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - )); - - let del_revenue_transaction_data = make_revenue_transaction( - None, - None, - CUDAction::Delete, - None, - ); - - assert_noop!( - FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(del_revenue_transaction_data), - false, - ), - Error::::RevenueTransactionIdRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + )); + + let del_revenue_transaction_data = + make_revenue_transaction(None, None, CUDAction::Delete, None); + + assert_noop!( + FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(del_revenue_transaction_data), + false, + ), + Error::::RevenueTransactionIdRequired + ); + }); } #[test] fn revenues_a_builder_submits_a_revenue_for_approval_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - true, - )); + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + )); - let revenue_data = RevenuesInfo::::get(revenue_id).unwrap(); + let revenue_data = RevenuesInfo::::get(revenue_id).unwrap(); - assert_eq!(revenue_data.status, RevenueStatus::Submitted); - assert_eq!(revenue_data.total_amount, 10000); - }); + assert_eq!(revenue_data.status, RevenueStatus::Submitted); + assert_eq!(revenue_data.total_amount, 10000); + }); } #[test] fn revenues_a_builder_submits_a_draft_revenue_for_approval_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - )); + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + )); - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Draft); - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - None, - true, - )); + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + None, + true, + )); - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Submitted); - }); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Submitted); + }); } #[test] fn revenues_a_user_tries_to_submit_a_revenue_for_approval_without_being_a_builder_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_noop!( - FundAdmin::submit_revenue( - RuntimeOrigin::signed(3), - project_id, - revenue_id, - Some(revenue_transaction_data), - true, - ), - RbacErr::NotAuthorized - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_noop!( + FundAdmin::submit_revenue( + RuntimeOrigin::signed(3), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + ), + RbacErr::NotAuthorized + ); + }); } #[test] fn revenues_a_revenue_cannot_be_submitted_for_approval_if_it_is_already_submitted_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data.clone()), - true, - )); - - assert_noop!( - FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - true, - ), - Error::::CannotPerformActionOnSubmittedRevenue - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data.clone()), + true, + )); + + assert_noop!( + FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + ), + Error::::CannotPerformActionOnSubmittedRevenue + ); + }); } #[test] fn revenues_a_revenue_cannot_be_submitted_if_has_no_transactions_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); + let revenue_id = get_revenue_id(project_id, 1); - assert_noop!( - FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - None, - true, - ), - Error::::RevenueHasNoTransactions - ); - }); + assert_noop!( + FundAdmin::submit_revenue(RuntimeOrigin::signed(2), project_id, revenue_id, None, true,), + Error::::RevenueHasNoTransactions + ); + }); } #[test] fn revenues_a_builder_deletes_all_transactions_while_submitting_a_revenue_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - false, - )); - - let revenue_transaction_id = get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); - let del_revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Delete, - Some(revenue_transaction_id), - ); - - assert_noop!( - FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(del_revenue_transaction_data), - true, - ), - Error::::RevenueHasNoTransactions - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + false, + )); + + let revenue_transaction_id = + get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); + let del_revenue_transaction_data = make_revenue_transaction( + Some(job_eligible_id), + Some(10000), + CUDAction::Delete, + Some(revenue_transaction_id), + ); + + assert_noop!( + FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(del_revenue_transaction_data), + true, + ), + Error::::RevenueHasNoTransactions + ); + }); } #[test] fn revenues_a_builder_tries_to_submit_a_revenue_with_an_empty_array_of_transactions_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let empty_revenue_transaction_data: RevenueTransactions = bounded_vec![]; + let revenue_id = get_revenue_id(project_id, 1); + let empty_revenue_transaction_data: RevenueTransactions = bounded_vec![]; - assert_noop!( - FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(empty_revenue_transaction_data), - true, - ), - Error::::RevenueTransactionsEmpty - ); - }); + assert_noop!( + FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(empty_revenue_transaction_data), + true, + ), + Error::::RevenueTransactionsEmpty + ); + }); } #[test] fn revenues_after_a_revenue_is_submitted_the_status_is_updated_in_project_data_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - true, - )); + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + )); - assert_eq!(ProjectsInfo::::get(project_id).unwrap().revenue_status, Some(RevenueStatus::Submitted)); - }); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().revenue_status, + Some(RevenueStatus::Submitted) + ); + }); } #[test] fn revenues_an_administrator_approves_a_submitted_revenue_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - true, - )); + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + )); - assert_ok!(FundAdmin::approve_revenue( - RuntimeOrigin::signed(1), - project_id, - revenue_id, - )); + assert_ok!(FundAdmin::approve_revenue(RuntimeOrigin::signed(1), project_id, revenue_id,)); - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Approved); - }); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Approved); + }); } #[test] fn revenues_an_administrator_cannot_approve_a_revenue_if_it_is_not_submitted_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); + let revenue_id = get_revenue_id(project_id, 1); - assert_noop!( - FundAdmin::approve_revenue( - RuntimeOrigin::signed(1), - project_id, - revenue_id, - ), - Error::::RevenueNotSubmitted - ); - }); + assert_noop!( + FundAdmin::approve_revenue(RuntimeOrigin::signed(1), project_id, revenue_id,), + Error::::RevenueNotSubmitted + ); + }); } #[test] fn revenues_after_a_revenue_is_submitted_the_next_one_is_generated_automaticaly_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - true, - )); + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + )); - assert_ok!(FundAdmin::approve_revenue( - RuntimeOrigin::signed(1), - project_id, - revenue_id, - )); + assert_ok!(FundAdmin::approve_revenue(RuntimeOrigin::signed(1), project_id, revenue_id,)); - let next_revenue_id = get_revenue_id(project_id, 2); + let next_revenue_id = get_revenue_id(project_id, 2); - assert_eq!(RevenuesInfo::::get(next_revenue_id).unwrap().status, RevenueStatus::Draft); - }); + assert_eq!(RevenuesInfo::::get(next_revenue_id).unwrap().status, RevenueStatus::Draft); + }); } #[test] fn revenues_an_administrator_rejects_a_given_revenue_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - let revenue_transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(revenue_transaction_data), - true, - )); + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + )); - let revenue_transaction_id = get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); + let revenue_transaction_id = + get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); - let feedback = make_field_description("Transaction was rejected because it was not valid"); + let feedback = make_field_description("Transaction was rejected because it was not valid"); - let transaction_feedback = make_transaction_feedback(revenue_transaction_id, feedback.clone()); + let transaction_feedback = make_transaction_feedback(revenue_transaction_id, feedback.clone()); - assert_ok!(FundAdmin::reject_revenue( - RuntimeOrigin::signed(1), - project_id, - revenue_id, - transaction_feedback, - )); + assert_ok!(FundAdmin::reject_revenue( + RuntimeOrigin::signed(1), + project_id, + revenue_id, + transaction_feedback, + )); - assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Rejected); - assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().status, RevenueTransactionStatus::Rejected); - assert_eq!(RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().feedback, Some(feedback)); - }); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Rejected); + assert_eq!( + RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().status, + RevenueTransactionStatus::Rejected + ); + assert_eq!( + RevenueTransactionsInfo::::get(revenue_transaction_id).unwrap().feedback, + Some(feedback) + ); + }); } #[test] fn revenues_an_administrator_cannot_reject_a_revenue_if_it_is_not_submitted_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(transaction_data), - false, - )); - - let revenue_transaction_id = get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); - let feedback = make_field_description("Transaction was rejected because it was not valid"); - let transaction_feedback = make_transaction_feedback(revenue_transaction_id, feedback.clone()); - - assert_noop!( - FundAdmin::reject_revenue( - RuntimeOrigin::signed(1), - project_id, - revenue_id, - transaction_feedback, - ), - Error::::RevenueNotSubmitted - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(transaction_data), + false, + )); + + let revenue_transaction_id = + get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); + let feedback = make_field_description("Transaction was rejected because it was not valid"); + let transaction_feedback = make_transaction_feedback(revenue_transaction_id, feedback.clone()); + + assert_noop!( + FundAdmin::reject_revenue( + RuntimeOrigin::signed(1), + project_id, + revenue_id, + transaction_feedback, + ), + Error::::RevenueNotSubmitted + ); + }); } #[test] fn revenues_an_administrator_cannot_reject_a_revenue_with_an_empty_array_of_feedback_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(transaction_data), - true, - )); - - let transaction_feedback: TransactionsFeedback = bounded_vec![]; - - assert_noop!( - FundAdmin::reject_revenue( - RuntimeOrigin::signed(1), - project_id, - revenue_id, - transaction_feedback, - ), - Error::::RevenueTransactionsFeedbackEmpty - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(transaction_data), + true, + )); + + let transaction_feedback: TransactionsFeedback = bounded_vec![]; + + assert_noop!( + FundAdmin::reject_revenue( + RuntimeOrigin::signed(1), + project_id, + revenue_id, + transaction_feedback, + ), + Error::::RevenueTransactionsFeedbackEmpty + ); + }); } #[test] fn revenues_after_a_revenue_is_rejected_the_status_is_updated_in_project_data_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let revenue_id = get_revenue_id(project_id, 1); - let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); - - let transaction_data = make_revenue_transaction( - Some(job_eligible_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_revenue( - RuntimeOrigin::signed(2), - project_id, - revenue_id, - Some(transaction_data), - true, - )); - - let revenue_transaction_id = get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); - let feedback = make_field_description("Transaction was rejected because it was not valid"); - let transaction_feedback = make_transaction_feedback(revenue_transaction_id, feedback.clone()); - - assert_ok!(FundAdmin::reject_revenue( - RuntimeOrigin::signed(1), - project_id, - revenue_id, - transaction_feedback, - )); - - assert_eq!(ProjectsInfo::::get(project_id).unwrap().revenue_status, Some(RevenueStatus::Rejected)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(transaction_data), + true, + )); + + let revenue_transaction_id = + get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); + let feedback = make_field_description("Transaction was rejected because it was not valid"); + let transaction_feedback = make_transaction_feedback(revenue_transaction_id, feedback.clone()); + + assert_ok!(FundAdmin::reject_revenue( + RuntimeOrigin::signed(1), + project_id, + revenue_id, + transaction_feedback, + )); + + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().revenue_status, + Some(RevenueStatus::Rejected) + ); + }); } // I N F L A T I O N R A T E -// ============================================================================ +// ================================================================================================= #[test] fn inflation_rate_an_administrator_can_set_the_inflation_rate_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let inflation_rate = 70; - let inflation_rate_data = make_project_inflation( - project_id, - Some(inflation_rate), - CUDAction::Create, - ); + let inflation_rate = 70; + let inflation_rate_data = + make_project_inflation(project_id, Some(inflation_rate), CUDAction::Create); - assert_ok!(FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data, - )); + assert_ok!(FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data,)); - assert_eq!(ProjectsInfo::::get(project_id).unwrap().inflation_rate, Some(inflation_rate)); - }); + assert_eq!(ProjectsInfo::::get(project_id).unwrap().inflation_rate, Some(inflation_rate)); + }); } #[test] fn inflation_rate_an_administrator_cannot_submit_an_empty_array_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); - let inflation_rate_data: ProjectsInflation = bounded_vec![]; + let inflation_rate_data: ProjectsInflation = bounded_vec![]; - assert_noop!( - FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data, - ), - Error::::ProjectsInflationRateEmpty - ); - }); + assert_noop!( + FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data,), + Error::::ProjectsInflationRateEmpty + ); + }); } #[test] fn inflation_rate_an_administrator_cannot_set_the_inflation_rate_without_a_value_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let inflation_rate_data = make_project_inflation( - project_id, - None, - CUDAction::Create, - ); - - assert_noop!( - FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data, - ), - Error::::InflationRateRequired - ); - }); -} - -#[test] -fn inflation_rate_an_administrator_cannot_set_the_inflation_rate_if_it_is_already_set_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let inflation_rate = 70; - let inflation_rate_data = make_project_inflation( - project_id, - Some(inflation_rate), - CUDAction::Create, - ); - - assert_ok!(FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data.clone(), - )); - - assert_noop!( - FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data, - ), - Error::::InflationRateAlreadySet - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let inflation_rate_data = make_project_inflation(project_id, None, CUDAction::Create); + + assert_noop!( + FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data,), + Error::::InflationRateRequired + ); + }); +} + +#[test] +fn inflation_rate_an_administrator_cannot_set_the_inflation_rate_if_it_is_already_set_should_fail() +{ + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let inflation_rate = 70; + let inflation_rate_data = + make_project_inflation(project_id, Some(inflation_rate), CUDAction::Create); + + assert_ok!(FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data.clone(),)); + + assert_noop!( + FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data,), + Error::::InflationRateAlreadySet + ); + }); } #[test] fn inflation_rate_an_administrator_updates_the_inflation_rate_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let inflation_rate = 70; - let inflation_rate_data = make_project_inflation( - project_id, - Some(inflation_rate), - CUDAction::Create, - ); + let inflation_rate = 70; + let inflation_rate_data = + make_project_inflation(project_id, Some(inflation_rate), CUDAction::Create); - assert_ok!(FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data.clone(), - )); + assert_ok!(FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data.clone(),)); - let inflation_rate = 80; - let inflation_rate_data = make_project_inflation( - project_id, - Some(inflation_rate), - CUDAction::Update, - ); + let inflation_rate = 80; + let inflation_rate_data = + make_project_inflation(project_id, Some(inflation_rate), CUDAction::Update); - assert_ok!(FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data, - )); + assert_ok!(FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data,)); - assert_eq!(ProjectsInfo::::get(project_id).unwrap().inflation_rate, Some(inflation_rate)); - }); + assert_eq!(ProjectsInfo::::get(project_id).unwrap().inflation_rate, Some(inflation_rate)); + }); } #[test] fn inflation_rate_an_administrator_cannot_update_the_inflation_rate_if_it_is_not_set_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let inflation_rate = 80; - let inflation_rate_data = make_project_inflation( - project_id, - Some(inflation_rate), - CUDAction::Update, - ); + let inflation_rate = 80; + let inflation_rate_data = + make_project_inflation(project_id, Some(inflation_rate), CUDAction::Update); - assert_noop!( - FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data, - ), - Error::::InflationRateNotSet - ); - }); + assert_noop!( + FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data,), + Error::::InflationRateNotSet + ); + }); } #[test] fn inflation_rate_inflation_value_is_required_while_updating_the_inflation_rate_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let inflation_rate = 70; - let inflation_rate_data = make_project_inflation( - project_id, - Some(inflation_rate), - CUDAction::Create, - ); - - assert_ok!(FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data.clone(), - )); - - let inflation_rate_data = make_project_inflation( - project_id, - None, - CUDAction::Update, - ); - - assert_noop!( - FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data, - ), - Error::::InflationRateRequired - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let inflation_rate = 70; + let inflation_rate_data = + make_project_inflation(project_id, Some(inflation_rate), CUDAction::Create); + + assert_ok!(FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data.clone(),)); + + let inflation_rate_data = make_project_inflation(project_id, None, CUDAction::Update); + + assert_noop!( + FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data,), + Error::::InflationRateRequired + ); + }); } #[test] fn inflation_rate_an_administrator_deletes_the_inflation_rate_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let inflation_rate = 70; - let inflation_rate_data = make_project_inflation( - project_id, - Some(inflation_rate), - CUDAction::Create, - ); + let inflation_rate = 70; + let inflation_rate_data = + make_project_inflation(project_id, Some(inflation_rate), CUDAction::Create); - assert_ok!(FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data.clone(), - )); + assert_ok!(FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data.clone(),)); - let inflation_rate_data = make_project_inflation( - project_id, - None, - CUDAction::Delete, - ); + let inflation_rate_data = make_project_inflation(project_id, None, CUDAction::Delete); - assert_ok!(FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data, - )); + assert_ok!(FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data,)); - assert_eq!(ProjectsInfo::::get(project_id).unwrap().inflation_rate, None); - }); + assert_eq!(ProjectsInfo::::get(project_id).unwrap().inflation_rate, None); + }); } #[test] fn inflation_rate_an_administrator_cannot_delete_the_inflation_rate_if_it_is_not_set_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let inflation_rate_data = make_project_inflation( - project_id, - None, - CUDAction::Delete, - ); - - assert_noop!( - FundAdmin::inflation_rate( - RuntimeOrigin::signed(1), - inflation_rate_data, - ), - Error::::InflationRateNotSet - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let inflation_rate_data = make_project_inflation(project_id, None, CUDAction::Delete); + + assert_noop!( + FundAdmin::inflation_rate(RuntimeOrigin::signed(1), inflation_rate_data,), + Error::::InflationRateNotSet + ); + }); } // B A N K D O C U M E N T S -// ============================================================================ +// ================================================================================================= #[test] fn bank_documents_an_administrator_uploads_bank_documents_for_a_given_eb5_drawdown_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - let bank_documents = make_documents(1); - - assert_ok!(FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents.clone()), - CUDAction::Create, - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, Some(bank_documents)); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Confirmed); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + let bank_documents = make_documents(1); + + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + )); + + assert_eq!( + DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, + Some(bank_documents) + ); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Confirmed); + }); } #[test] fn bank_documents_cannot_upload_documents_for_a_contruction_loan_drawdown_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - let bank_documents = make_documents(1); - - assert_noop!( - FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents.clone()), - CUDAction::Create, - ), - Error::::OnlyEB5DrawdownsCanUploadBankDocuments - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + let bank_documents = make_documents(1); + + assert_noop!( + FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + ), + Error::::OnlyEB5DrawdownsCanUploadBankDocuments + ); + }); } #[test] fn bank_documents_cannot_upload_documents_for_a_developer_equity_drawdown_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::DeveloperEquity, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - let bank_documents = make_documents(1); - - assert_noop!( - FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents.clone()), - CUDAction::Create, - ), - Error::::OnlyEB5DrawdownsCanUploadBankDocuments - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::DeveloperEquity, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + let bank_documents = make_documents(1); + + assert_noop!( + FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + ), + Error::::OnlyEB5DrawdownsCanUploadBankDocuments + ); + }); } #[test] fn bank_documents_cannot_upload_documents_without_an_array_of_documents_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - assert_noop!( - FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - CUDAction::Create, - ), - Error::::BankConfirmingDocumentsNotProvided - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + assert_noop!( + FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + CUDAction::Create, + ), + Error::::BankConfirmingDocumentsNotProvided + ); + }); } #[test] fn bank_documents_cannot_upload_documents_with_an_empty_array_of_documents_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - let bank_documents: Documents = bounded_vec![]; - - assert_noop!( - FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents), - CUDAction::Create, - ), - Error::::BankConfirmingDocumentsEmpty - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + let bank_documents: Documents = bounded_vec![]; + + assert_noop!( + FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents), + CUDAction::Create, + ), + Error::::BankConfirmingDocumentsEmpty + ); + }); } #[test] fn bank_documents_cannot_upload_documents_if_the_drawdown_is_not_approved_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - let bank_documents = make_documents(1); - - assert_noop!( - FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents), - CUDAction::Create, - ), - Error::::DrawdowMustBeInApprovedStatus - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + let bank_documents = make_documents(1); + + assert_noop!( + FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents), + CUDAction::Create, + ), + Error::::DrawdowMustBeInApprovedStatus + ); + }); } #[test] fn bank_documents_cannot_upload_documents_twice_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - let bank_documents = make_documents(1); - - assert_ok!(FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents.clone()), - CUDAction::Create, - )); - - assert_noop!( - FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents), - CUDAction::Create, - ), - Error::::DrawdownHasAlreadyBankConfirmingDocuments - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + let bank_documents = make_documents(1); + + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + )); + + assert_noop!( + FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents), + CUDAction::Create, + ), + Error::::DrawdownHasAlreadyBankConfirmingDocuments + ); + }); } #[test] fn bank_documents_an_administrator_updates_the_bank_documents_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - let bank_documents = make_documents(1); - - assert_ok!(FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents.clone()), - CUDAction::Create, - )); - - let bank_documents = make_documents(2); - - assert_ok!(FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents.clone()), - CUDAction::Update, - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, Some(bank_documents)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + let bank_documents = make_documents(1); + + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + )); + + let bank_documents = make_documents(2); + + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Update, + )); + + assert_eq!( + DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, + Some(bank_documents) + ); + }); } #[test] fn bank_documents_cannot_update_documents_without_uploading_documents_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - let bank_documents = make_documents(1); - - assert_ok!(FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents.clone()), - CUDAction::Create, - )); - - assert_noop!( - FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - CUDAction::Update, - ), - Error::::BankConfirmingDocumentsNotProvided - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + let bank_documents = make_documents(1); + + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + )); + + assert_noop!( + FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + CUDAction::Update, + ), + Error::::BankConfirmingDocumentsNotProvided + ); + }); } #[test] fn bank_documents_cannot_update_documents_with_an_empty_array_of_documents_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - let bank_documents = make_documents(1); - - assert_ok!(FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents.clone()), - CUDAction::Create, - )); - - let bank_documents = make_documents(0); - - assert_noop!( - FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents), - CUDAction::Update, - ), - Error::::BankConfirmingDocumentsEmpty - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + let bank_documents = make_documents(1); + + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + )); + + let bank_documents = make_documents(0); + + assert_noop!( + FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents), + CUDAction::Update, + ), + Error::::BankConfirmingDocumentsEmpty + ); + }); } #[test] fn bank_documents_cannot_update_bank_documents_if_the_drawdown_is_not_confirmed_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - let bank_documents = make_documents(2); - - assert_noop!( - FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents), - CUDAction::Update, - ), - Error::::DrawdowMustBeInConfirmedStatus - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + let bank_documents = make_documents(2); + + assert_noop!( + FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents), + CUDAction::Update, + ), + Error::::DrawdowMustBeInConfirmedStatus + ); + }); } #[test] fn bank_documents_an_administrator_deletes_bank_documents_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - let bank_documents = make_documents(1); - - assert_ok!(FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - Some(bank_documents.clone()), - CUDAction::Create, - )); - - assert_ok!(FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - CUDAction::Delete, - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + let bank_documents = make_documents(1); + + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + )); + + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + CUDAction::Delete, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + }); } #[test] fn bank_documents_cannot_delete_documents_if_the_drawdown_is_not_confirmed_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_noop!( - FundAdmin::bank_confirming_documents( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - CUDAction::Delete, - ), - Error::::DrawdowMustBeInConfirmedStatus - ); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_noop!( + FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + CUDAction::Delete, + ), + Error::::DrawdowMustBeInConfirmedStatus + ); + }); } // R E S E T D R A W D O W N -// =================================================================================================3 +// ================================================================================================= #[test] fn reset_drawdown_a_builder_resets_a_eb5_drawdown_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - let transactions_by_drawdown = TransactionsByDrawdown::::get(project_id, drawdown_id); - - assert_ok!(FundAdmin::reset_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - )); - - for transaction in transactions_by_drawdown { - assert_eq!(TransactionsInfo::::contains_key(transaction), false); - } - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 0); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().description, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().feedback, None); - assert_eq!(ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, Some(DrawdownStatus::Draft)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + let transactions_by_drawdown = TransactionsByDrawdown::::get(project_id, drawdown_id); + + assert_ok!(FundAdmin::reset_drawdown(RuntimeOrigin::signed(2), project_id, drawdown_id,)); + + for transaction in transactions_by_drawdown { + assert_eq!(TransactionsInfo::::contains_key(transaction), false); + } + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().description, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().feedback, None); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Draft) + ); + }); } #[test] fn reset_drawdown_a_builder_resets_a_construction_loan_drawdown_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); - - let drawdown_description = make_field_description("Construction Loan Drawdown 1"); - let total_amount = 100000u64; - let documents = make_documents(1); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - drawdown_description.clone(), - total_amount, - documents.clone(), - )); - - assert_ok!(FundAdmin::reset_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 0); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().description, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().feedback, None); - assert_eq!(ProjectsInfo::::get(project_id).unwrap().construction_loan_drawdown_status, Some(DrawdownStatus::Draft)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::ConstructionLoan, 1); + + let drawdown_description = make_field_description("Construction Loan Drawdown 1"); + let total_amount = 100000u64; + let documents = make_documents(1); + + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + drawdown_description.clone(), + total_amount, + documents.clone(), + )); + + assert_ok!(FundAdmin::reset_drawdown(RuntimeOrigin::signed(2), project_id, drawdown_id,)); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().description, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().feedback, None); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().construction_loan_drawdown_status, + Some(DrawdownStatus::Draft) + ); + }); } #[test] fn reset_drawdown_a_builder_resets_a_developer_equity_drawdown_works() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::DeveloperEquity, 1); - - let drawdown_description = make_field_description("Developer Equity Drawdown 1"); - let total_amount = 100000u64; - let documents = make_documents(1); - - assert_ok!(FundAdmin::up_bulkupload( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - drawdown_description.clone(), - total_amount, - documents.clone(), - )); - - assert_ok!(FundAdmin::reset_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - )); - - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 0); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().description, None); - assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().feedback, None); - assert_eq!(ProjectsInfo::::get(project_id).unwrap().developer_equity_drawdown_status, Some(DrawdownStatus::Draft)); - }); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::DeveloperEquity, 1); + + let drawdown_description = make_field_description("Developer Equity Drawdown 1"); + let total_amount = 100000u64; + let documents = make_documents(1); + + assert_ok!(FundAdmin::up_bulkupload( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + drawdown_description.clone(), + total_amount, + documents.clone(), + )); + + assert_ok!(FundAdmin::reset_drawdown(RuntimeOrigin::signed(2), project_id, drawdown_id,)); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Draft); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bulkupload_documents, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().bank_documents, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().description, None); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().feedback, None); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().developer_equity_drawdown_status, + Some(DrawdownStatus::Draft) + ); + }); } #[test] fn reset_drawdown_a_builder_cannot_reset_a_drawdown_if_it_is_not_submitted_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - assert_noop!( - FundAdmin::reset_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - ), - Error::::DrawdownNotSubmitted - ); - }); + assert_noop!( + FundAdmin::reset_drawdown(RuntimeOrigin::signed(2), project_id, drawdown_id,), + Error::::DrawdownNotSubmitted + ); + }); } #[test] fn reset_drawdown_a_builder_cannot_reset_an_approved_drawdown() { - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction( - Some(expenditure_id), - Some(10000), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - assert_ok!(FundAdmin::approve_drawdown( - RuntimeOrigin::signed(1), - project_id, - drawdown_id, - None, - None, - )); - - assert_noop!( - FundAdmin::reset_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - ), - Error::::DrawdownNotSubmitted - ); - }); -} - -// E X C E E D E D B O U N D S + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + assert_noop!( + FundAdmin::reset_drawdown(RuntimeOrigin::signed(2), project_id, drawdown_id,), + Error::::DrawdownNotSubmitted + ); + }); +} + +// E R R O R R E C O V E R Y D R A W D O W N S // ================================================================================================= -// todo: Create helper functions to make boundedvecs that exceeds the bounds and use them -// in the tests below. -fn make_transaction_exceed_bounds( - expenditure_id: Option, - expenditure_amount: Option, - action: CUDAction, - transaction_id: Option, -) -> Transactions { - let mut transactions: Transactions = bounded_vec![]; - let documents = Some(make_documents(10)); - transactions - .try_push(( - expenditure_id, - expenditure_amount, - documents, - action, - transaction_id, - )) - .unwrap_or_default(); - transactions -} - -#[test] -fn transactions_a_user_uploads_more_documents_than_the_allowed_limit_should_fail(){ - new_test_ext().execute_with(|| { - assert_ok!(make_default_full_project()); - - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); - let expenditure_id = get_budget_expenditure_id(project_id, make_field_name("Expenditure Test 1"), ExpenditureType::HardCost); - - let transaction_data = make_transaction_exceed_bounds( - Some(expenditure_id), - Some(0), - CUDAction::Create, - None, - ); - - assert_ok!(FundAdmin::submit_drawdown( - RuntimeOrigin::signed(2), - project_id, - drawdown_id, - Some(transaction_data), - true, - )); - - let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); - - assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().documents.unwrap().len(), 5); - - }); -} - -#[test] -fn projects_register_a_project_with_a_long_name_should_fail() { - new_test_ext().execute_with(|| { - assert_ok!(register_administrator()); - - assert_ok!(FundAdmin::projects_create_project( - RuntimeOrigin::signed(1), - make_field_name("project_imagwerwerwerwerwrwerwerbhpkwbupwkucfweklñcjwelfjwelfjwlefhwelfhwelfj.jpeg"), - make_field_description("Project 1 description"), - Some(make_field_name("project_image.jpeg")), - make_field_name("New York"), - None, - 1000, - 2000, - make_default_expenditures(), - None, - None, - make_field_description("P9f5wbr13BK74p1"), - )); - - assert_eq!(ProjectsInfo::::iter().count(), 1); - let project_id = ProjectsInfo::::iter_keys().next().unwrap(); - assert_eq!(ProjectsInfo::::get(project_id).unwrap().title, make_field_name("project_imagwerwerwerwerwrwerwerbhpkwbupwkucfweklñcjwelfjwelfjwlefhwelfhwelfj.jpeg")); - assert_eq!(ExpendituresInfo::::iter().count(), ExpendituresByProject::::get(project_id).len()); - let get_expenditure_ids: Vec<[u8; 32]> = ExpendituresByProject::::get(project_id).iter().cloned().collect(); - for i in get_expenditure_ids { - assert_eq!(ExpendituresInfo::::get(i).unwrap().project_id, project_id); - } - - assert_eq!(DrawdownsInfo::::iter().count(), DrawdownsByProject::::get(project_id).len()); - let get_drawdown_ids: Vec<[u8; 32]> = DrawdownsByProject::::get(project_id).iter().cloned().collect(); - for i in get_drawdown_ids { - assert_eq!(DrawdownsInfo::::get(i).unwrap().project_id, project_id); - } - - assert_eq!(RevenuesInfo::::iter().count(), RevenuesByProject::::get(project_id).len()); - let get_revenue_ids: Vec<[u8; 32]> = RevenuesByProject::::get(project_id).iter().cloned().collect(); - for i in get_revenue_ids { - assert_eq!(RevenuesInfo::::get(i).unwrap().project_id, project_id); - } - }); +#[test] +fn an_administrators_updates_a_transaction_for_an_approved_drawdown_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Draft) + ); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 10000); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Submitted + ); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().amount, 10000); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Submitted) + ); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Approved + ); + + let new_total_amount = 200000u64; + let transaction_data = make_transaction( + Some(expenditure_id), + Some(new_total_amount), + CUDAction::Update, + Some(transaction_id), + ); + + assert_ok!(FundAdmin::recovery_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + transaction_data, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 1); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, new_total_amount); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Approved + ); + }); +} + +#[test] +fn an_administrators_adds_a_new_transaction_to_an_approved_drawdown_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Draft) + ); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 10000); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Submitted + ); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().amount, 10000); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Submitted) + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Approved + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + let expenditure_id_2 = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 2"), + ExpenditureType::SoftCost, + ); + + let transaction_data = + make_transaction(Some(expenditure_id_2), Some(2000), CUDAction::Create, None); + + assert_ok!(FundAdmin::recovery_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + transaction_data, + )); + + let transaction_id_2 = get_transaction_id(project_id, drawdown_id, expenditure_id_2); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 1); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 2); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Approved + ); + assert_eq!( + TransactionsInfo::::get(transaction_id_2).unwrap().status, + TransactionStatus::Approved + ); + }); +} + +#[test] +fn an_administrators_deletes_a_transaction_for_an_approved_drawdown_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Draft) + ); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 10000); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Submitted + ); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().amount, 10000); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Submitted) + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Approved + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + let transaction_data = + make_transaction(Some(expenditure_id), None, CUDAction::Delete, Some(transaction_id)); + + assert_ok!(FundAdmin::recovery_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + transaction_data, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 1); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + assert_eq!(TransactionsInfo::::get(transaction_id).is_none(), true); + }); +} + +#[test] +fn an_administrators_updates_a_transaction_for_a_confirmed_drawdown_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Draft) + ); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 10000); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Submitted + ); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().amount, 10000); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Submitted) + ); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Approved + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + let bank_documents = make_documents(1); + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Confirmed); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Confirmed + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + let new_total_amount = 200000u64; + let transaction_data = make_transaction( + Some(expenditure_id), + Some(new_total_amount), + CUDAction::Update, + Some(transaction_id), + ); + + assert_ok!(FundAdmin::recovery_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + transaction_data, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, new_total_amount); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 1); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Confirmed); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Confirmed + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + }); +} + +#[test] +fn an_administrators_adds_a_new_transaction_to_a_confirmed_drawdown_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Draft) + ); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 10000); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Submitted + ); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().amount, 10000); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Submitted) + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Approved + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + let bank_documents = make_documents(1); + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Confirmed); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Confirmed + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + let expenditure_id_2 = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 2"), + ExpenditureType::SoftCost, + ); + let transaction_data = + make_transaction(Some(expenditure_id_2), Some(2000), CUDAction::Create, None); + + assert_ok!(FundAdmin::recovery_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + transaction_data, + )); + + let transaction_id_2 = get_transaction_id(project_id, drawdown_id, expenditure_id_2); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 1); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 2); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Confirmed); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Confirmed + ); + assert_eq!( + TransactionsInfo::::get(transaction_id_2).unwrap().status, + TransactionStatus::Confirmed + ); + }); +} + +#[test] +fn an_administrators_deletes_a_transaction_for_a_confirmed_drawdown_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + let drawdown_id = get_drawdown_id(project_id, DrawdownType::EB5, 1); + + let expenditure_id = get_budget_expenditure_id( + project_id, + make_field_name("Expenditure Test 1"), + ExpenditureType::HardCost, + ); + let transaction_data = + make_transaction(Some(expenditure_id), Some(10000), CUDAction::Create, None); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Draft) + ); + + assert_ok!(FundAdmin::submit_drawdown( + RuntimeOrigin::signed(2), + project_id, + drawdown_id, + Some(transaction_data), + true, + )); + + let transaction_id = get_transaction_id(project_id, drawdown_id, expenditure_id); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Submitted); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().total_amount, 10000); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Submitted + ); + assert_eq!(TransactionsInfo::::get(transaction_id).unwrap().amount, 10000); + assert_eq!( + ProjectsInfo::::get(project_id).unwrap().eb5_drawdown_status, + Some(DrawdownStatus::Submitted) + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + assert_ok!(FundAdmin::approve_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + None, + None, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Approved); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Approved + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + let bank_documents = make_documents(1); + assert_ok!(FundAdmin::bank_confirming_documents( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + Some(bank_documents.clone()), + CUDAction::Create, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Confirmed); + assert_eq!( + TransactionsInfo::::get(transaction_id).unwrap().status, + TransactionStatus::Confirmed + ); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 1); + + let transaction_data = + make_transaction(Some(expenditure_id), None, CUDAction::Delete, Some(transaction_id)); + + assert_ok!(FundAdmin::recovery_drawdown( + RuntimeOrigin::signed(1), + project_id, + drawdown_id, + transaction_data, + )); + + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().recovery_record.len(), 1); + assert_eq!(TransactionsByDrawdown::::get(project_id, drawdown_id).len(), 0); + assert_eq!(DrawdownsInfo::::get(drawdown_id).unwrap().status, DrawdownStatus::Confirmed); + assert_eq!(TransactionsInfo::::get(transaction_id).is_none(), true); + }); +} + +// E R R O R R E C O V E R Y R E V E N U E S +// ================================================================================================= +#[test] +fn an_administrators_updates_a_transaction_for_an_approved_revenue_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + )); + + let transaction_id = get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Submitted); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().total_amount, 10000); + assert_eq!( + RevenueTransactionsInfo::::get(transaction_id).unwrap().status, + RevenueTransactionStatus::Submitted + ); + assert_eq!(RevenueTransactionsInfo::::get(transaction_id).unwrap().amount, 10000); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + + assert_ok!(FundAdmin::approve_revenue(RuntimeOrigin::signed(1), project_id, revenue_id,)); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().recovery_record.len(), 0); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Approved); + assert_eq!( + RevenueTransactionsInfo::::get(transaction_id).unwrap().status, + RevenueTransactionStatus::Approved + ); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + + let new_total_amount = 200000u64; + let revenue_transaction_data = make_revenue_transaction( + Some(job_eligible_id), + Some(new_total_amount), + CUDAction::Update, + Some(transaction_id), + ); + + assert_ok!(FundAdmin::recovery_revenue( + RuntimeOrigin::signed(1), + project_id, + revenue_id, + revenue_transaction_data, + )); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().recovery_record.len(), 1); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Approved); + assert_eq!( + RevenueTransactionsInfo::::get(transaction_id).unwrap().status, + RevenueTransactionStatus::Approved + ); + }); +} + +#[test] +fn an_administrators_adds_a_transaction_for_an_approved_revenue_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + )); + + let transaction_id = get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Submitted); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().total_amount, 10000); + assert_eq!( + RevenueTransactionsInfo::::get(transaction_id).unwrap().status, + RevenueTransactionStatus::Submitted + ); + assert_eq!(RevenueTransactionsInfo::::get(transaction_id).unwrap().amount, 10000); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + + assert_ok!(FundAdmin::approve_revenue(RuntimeOrigin::signed(1), project_id, revenue_id,)); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().recovery_record.len(), 0); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Approved); + assert_eq!( + RevenueTransactionsInfo::::get(transaction_id).unwrap().status, + RevenueTransactionStatus::Approved + ); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + + let job_eligible_data = make_job_eligible( + Some(make_field_name("Job Eligible Test: Construction")), + Some(1000), + Some(make_field_description("16344, 45862, 57143")), + Some(200), + CUDAction::Create, + None, + ); + + assert_ok!(FundAdmin::expenditures_and_job_eligibles( + RuntimeOrigin::signed(1), + project_id, + None, + Some(job_eligible_data), + )); + + let job_eligible_id_2 = + get_job_eligible_id(project_id, make_field_name("Job Eligible Test: Construction")); + assert_eq!(JobEligiblesInfo::::get(job_eligible_id_2).is_some(), true); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id_2), Some(20000), CUDAction::Create, None); + + assert_ok!(FundAdmin::recovery_revenue( + RuntimeOrigin::signed(1), + project_id, + revenue_id, + revenue_transaction_data, + )); + + let transaction_id_2 = get_revenue_transaction_id(project_id, revenue_id, job_eligible_id_2); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().recovery_record.len(), 1); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 2); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Approved); + assert_eq!( + RevenueTransactionsInfo::::get(transaction_id).unwrap().status, + RevenueTransactionStatus::Approved + ); + assert_eq!( + RevenueTransactionsInfo::::get(transaction_id_2).unwrap().status, + RevenueTransactionStatus::Approved + ); + }); +} + +#[test] +fn an_administrators_deletes_a_transaction_for_an_approved_revenue_works() { + new_test_ext().execute_with(|| { + assert_ok!(make_default_full_project()); + let project_id = ProjectsInfo::::iter_keys().next().unwrap(); + + let revenue_id = get_revenue_id(project_id, 1); + let job_eligible_id = get_job_eligible_id(project_id, make_field_name("Job Eligible Test")); + + let revenue_transaction_data = + make_revenue_transaction(Some(job_eligible_id), Some(10000), CUDAction::Create, None); + + assert_ok!(FundAdmin::submit_revenue( + RuntimeOrigin::signed(2), + project_id, + revenue_id, + Some(revenue_transaction_data), + true, + )); + + let transaction_id = get_revenue_transaction_id(project_id, revenue_id, job_eligible_id); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Submitted); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().total_amount, 10000); + assert_eq!( + RevenueTransactionsInfo::::get(transaction_id).unwrap().status, + RevenueTransactionStatus::Submitted + ); + assert_eq!(RevenueTransactionsInfo::::get(transaction_id).unwrap().amount, 10000); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + + assert_ok!(FundAdmin::approve_revenue(RuntimeOrigin::signed(1), project_id, revenue_id,)); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().recovery_record.len(), 0); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Approved); + assert_eq!( + RevenueTransactionsInfo::::get(transaction_id).unwrap().status, + RevenueTransactionStatus::Approved + ); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 1); + + let revenue_transaction_data = make_revenue_transaction( + Some(job_eligible_id), + Some(20000), + CUDAction::Delete, + Some(transaction_id), + ); + + assert_ok!(FundAdmin::recovery_revenue( + RuntimeOrigin::signed(1), + project_id, + revenue_id, + revenue_transaction_data, + )); + + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().recovery_record.len(), 1); + assert_eq!(TransactionsByRevenue::::get(project_id, revenue_id).len(), 0); + assert_eq!(RevenuesInfo::::get(revenue_id).unwrap().status, RevenueStatus::Approved); + assert_eq!(RevenueTransactionsInfo::::get(transaction_id).is_none(), true); + }); } diff --git a/pallets/fund-admin/src/types.rs b/pallets/fund-admin/src/types.rs index 20a2be46..628f8348 100644 --- a/pallets/fund-admin/src/types.rs +++ b/pallets/fund-admin/src/types.rs @@ -1,12 +1,11 @@ use super::*; -use frame_support::pallet_prelude::*; -use frame_support::sp_io::hashing::blake2_256; +use frame_support::{pallet_prelude::*, sp_io::hashing::blake2_256}; use sp_runtime::sp_std::vec::Vec; pub type FieldName = BoundedVec>; pub type FieldDescription = BoundedVec>; pub type CID = BoundedVec>; -pub type Documents = BoundedVec<(FieldName,CID), ::MaxDocuments>; +pub type Documents = BoundedVec<(FieldName, CID), ::MaxDocuments>; type AccountIdOf = ::AccountId; // Projects @@ -17,66 +16,62 @@ pub type UpdatedDate = u64; pub type RegistrationDate = u64; pub type BankName = BoundedVec>; pub type BankAddress = BoundedVec>; -pub type UsersAssignation = BoundedVec<( - AccountIdOf, - ProxyRole, - AssignAction, -), ::MaxRegistrationsAtTime>; -pub type Banks = BoundedVec<( - BankName, - BankAddress, -), ::MaxBanksPerProject>; +pub type UsersAssignation = + BoundedVec<(AccountIdOf, ProxyRole, AssignAction), ::MaxRegistrationsAtTime>; +pub type Banks = BoundedVec<(BankName, BankAddress), ::MaxBanksPerProject>; pub type PrivateGroupId = BoundedVec>; pub type InflationRate = u32; -pub type ProjectsInflation = BoundedVec<( - ProjectId, - Option, - CUDAction, -), ::MaxRegistrationsAtTime>; +pub type ProjectsInflation = + BoundedVec<(ProjectId, Option, CUDAction), ::MaxRegistrationsAtTime>; // Users pub type DateRegistered = u64; -pub type Users = BoundedVec<( - AccountIdOf, - Option, - Option, - CUDAction, -), ::MaxRegistrationsAtTime>; +pub type Users = BoundedVec< + (AccountIdOf, Option, Option, CUDAction), + ::MaxRegistrationsAtTime, +>; // Transactions pub type TransactionId = [u8; 32]; pub type Amount = u64; -pub type Transactions = BoundedVec<( +pub type Transactions = BoundedVec< + ( Option, Option, Option>, CUDAction, Option, -), ::MaxRegistrationsAtTime>; -pub type TransactionsFeedback = BoundedVec<( - TransactionId, - FieldDescription -), ::MaxRegistrationsAtTime>; + ), + ::MaxRegistrationsAtTime, +>; +pub type TransactionsFeedback = + BoundedVec<(TransactionId, FieldDescription), ::MaxRegistrationsAtTime>; // Drawdowns pub type DrawdownId = [u8; 32]; pub type DrawdownNumber = u32; -pub type DrawdownStatusChanges = BoundedVec<(DrawdownStatus, UpdatedDate), ::MaxStatusChangesPerDrawdown>; +pub type DrawdownStatusChanges = + BoundedVec<(DrawdownStatus, UpdatedDate), ::MaxStatusChangesPerDrawdown>; +pub type RecoveryRecord = + BoundedVec<(AccountIdOf, UpdatedDate), ::MaxRecoveryChanges>; // Budget expenditures pub type ExpenditureId = [u8; 32]; pub type ExpenditureAmount = Amount; pub type NAICSCode = BoundedVec>; pub type JobsMultiplier = u32; -pub type Expenditures = BoundedVec<( +pub type Expenditures = BoundedVec< + ( Option, Option, Option, Option, Option, CUDAction, - Option -), ::MaxRegistrationsAtTime>; + Option, + ), + ::MaxRegistrationsAtTime, +>; // Miscellaneous pub type CreatedDate = u64; @@ -86,402 +81,451 @@ pub type TotalAmount = Amount; // Job Elgibles pub type JobEligibleId = [u8; 32]; pub type JobEligibleAmount = Amount; -pub type JobEligibles = BoundedVec<( +pub type JobEligibles = BoundedVec< + ( Option, Option, Option, Option, CUDAction, Option, -), ::MaxRegistrationsAtTime>; + ), + ::MaxRegistrationsAtTime, +>; // Revenues pub type RevenueAmount = Amount; pub type RevenueId = [u8; 32]; pub type RevenueNumber = u32; -pub type RevenueStatusChanges = BoundedVec<(RevenueStatus, UpdatedDate), ::MaxStatusChangesPerRevenue>; +pub type RevenueStatusChanges = + BoundedVec<(RevenueStatus, UpdatedDate), ::MaxStatusChangesPerRevenue>; // Revenue Transactions pub type RevenueTransactionId = [u8; 32]; pub type RevenueTransactionAmount = Amount; -pub type RevenueTransactions = BoundedVec<( +pub type RevenueTransactions = BoundedVec< + ( Option, Option, Option>, CUDAction, Option, -), ::MaxRegistrationsAtTime>; + ), + ::MaxRegistrationsAtTime, +>; -#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen,)] +#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct ProjectData { - pub builder: Option>, - pub investor: Option>, - pub issuer: Option>, - pub regional_center: Option>, - pub title: FieldName, - pub description: FieldDescription, - pub image: Option, - pub address: FieldName, - pub status: ProjectStatus, - pub inflation_rate: Option, - pub banks: Option>, - pub creation_date: CreationDate, - pub completion_date: CompletionDate, - pub registration_date: RegistrationDate, - pub updated_date: UpdatedDate, - pub eb5_drawdown_status: Option, - pub construction_loan_drawdown_status: Option, - pub developer_equity_drawdown_status: Option, - pub revenue_status: Option, - pub private_group_id: PrivateGroupId, + pub builder: Option>, + pub investor: Option>, + pub issuer: Option>, + pub regional_center: Option>, + pub title: FieldName, + pub description: FieldDescription, + pub image: Option, + pub address: FieldName, + pub status: ProjectStatus, + pub inflation_rate: Option, + pub banks: Option>, + pub creation_date: CreationDate, + pub completion_date: CompletionDate, + pub registration_date: RegistrationDate, + pub updated_date: UpdatedDate, + pub eb5_drawdown_status: Option, + pub construction_loan_drawdown_status: Option, + pub developer_equity_drawdown_status: Option, + pub revenue_status: Option, + pub private_group_id: PrivateGroupId, } -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy)] +#[derive( + Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy, +)] pub enum ProjectStatus { - Started, - Completed, + Started, + Completed, } impl Default for ProjectStatus { - fn default() -> Self { - ProjectStatus::Started - } + fn default() -> Self { + ProjectStatus::Started + } } - -#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen,)] +#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct UserData { - pub name: FieldName, - pub role: ProxyRole, - pub image: CID, - pub date_registered: DateRegistered, - pub email: FieldName, - pub documents: Option>, + pub name: FieldName, + pub role: ProxyRole, + pub image: CID, + pub date_registered: DateRegistered, + pub email: FieldName, + pub documents: Option>, } #[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen)] pub struct ExpenditureData { - pub project_id: ProjectId, - pub name: FieldName, - pub expenditure_type: ExpenditureType, - pub expenditure_amount: ExpenditureAmount, - pub naics_code: Option, - pub jobs_multiplier: Option, + pub project_id: ProjectId, + pub name: FieldName, + pub expenditure_type: ExpenditureType, + pub expenditure_amount: ExpenditureAmount, + pub naics_code: Option, + pub jobs_multiplier: Option, } -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy)] +#[derive( + Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy, +)] pub enum ExpenditureType { - HardCost, - SoftCost, - Operational, - Others, + HardCost, + SoftCost, + Operational, + Others, } impl Default for ExpenditureType { - fn default() -> Self { - ExpenditureType::HardCost - } + fn default() -> Self { + ExpenditureType::HardCost + } } -#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen,)] +#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct DrawdownData { - pub project_id: ProjectId, - pub drawdown_number: DrawdownNumber, - pub drawdown_type: DrawdownType, - pub total_amount: TotalAmount, - pub status: DrawdownStatus, - pub bulkupload_documents: Option>, - pub bank_documents: Option>, - pub description: Option, - pub feedback: Option, - pub status_changes: DrawdownStatusChanges, - pub created_date: CreatedDate, - pub closed_date: CloseDate, + pub project_id: ProjectId, + pub drawdown_number: DrawdownNumber, + pub drawdown_type: DrawdownType, + pub total_amount: TotalAmount, + pub status: DrawdownStatus, + pub bulkupload_documents: Option>, + pub bank_documents: Option>, + pub description: Option, + pub feedback: Option, + pub status_changes: DrawdownStatusChanges, + pub recovery_record: RecoveryRecord, + pub created_date: CreatedDate, + pub closed_date: CloseDate, } -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy)] +#[derive( + Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy, +)] pub enum DrawdownType { - EB5, - ConstructionLoan, - DeveloperEquity, + EB5, + ConstructionLoan, + DeveloperEquity, } impl Default for DrawdownType { - fn default() -> Self { - DrawdownType::EB5 - } + fn default() -> Self { + DrawdownType::EB5 + } } -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy)] +#[derive( + Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy, +)] pub enum DrawdownStatus { - Draft, - Submitted, - Approved, - Rejected, - Confirmed, + Draft, + Submitted, + Approved, + Rejected, + Confirmed, } impl Default for DrawdownStatus { - fn default() -> Self { - DrawdownStatus::Draft - } + fn default() -> Self { + DrawdownStatus::Draft + } } -#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen,)] +#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct TransactionData { - pub project_id: ProjectId, - pub drawdown_id: DrawdownId, - pub expenditure_id: ExpenditureId, - pub created_date: CreatedDate, - pub updated_date: UpdatedDate, - pub closed_date: CloseDate, - pub feedback: Option, - pub amount: ExpenditureAmount, - pub status: TransactionStatus, - pub documents: Option>, + pub project_id: ProjectId, + pub drawdown_id: DrawdownId, + pub expenditure_id: ExpenditureId, + pub created_date: CreatedDate, + pub updated_date: UpdatedDate, + pub closed_date: CloseDate, + pub feedback: Option, + pub amount: ExpenditureAmount, + pub status: TransactionStatus, + pub documents: Option>, } -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy)] +#[derive( + Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy, +)] pub enum TransactionStatus { - Draft, - Submitted, - Approved, - Rejected, - Confirmed + Draft, + Submitted, + Approved, + Rejected, + Confirmed, } impl Default for TransactionStatus { - fn default() -> Self { - TransactionStatus::Draft - } + fn default() -> Self { + TransactionStatus::Draft + } } #[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen)] pub struct JobEligibleData { - pub project_id: ProjectId, - pub name: FieldName, - pub job_eligible_amount: JobEligibleAmount, - pub naics_code: Option, - pub jobs_multiplier: Option, + pub project_id: ProjectId, + pub name: FieldName, + pub job_eligible_amount: JobEligibleAmount, + pub naics_code: Option, + pub jobs_multiplier: Option, } -#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen,)] +#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct RevenueData { - pub project_id: ProjectId, - pub revenue_number: RevenueNumber, - pub total_amount: RevenueAmount, - pub status: RevenueStatus, - pub status_changes: RevenueStatusChanges, - pub created_date: CreatedDate, - pub closed_date: CloseDate, + pub project_id: ProjectId, + pub revenue_number: RevenueNumber, + pub total_amount: RevenueAmount, + pub status: RevenueStatus, + pub status_changes: RevenueStatusChanges, + pub recovery_record: RecoveryRecord, + pub created_date: CreatedDate, + pub closed_date: CloseDate, } -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy)] +#[derive( + Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy, +)] pub enum RevenueStatus { - Draft, - Submitted, - Approved, - Rejected, + Draft, + Submitted, + Approved, + Rejected, } impl Default for RevenueStatus { - fn default() -> Self { - RevenueStatus::Draft - } + fn default() -> Self { + RevenueStatus::Draft + } } -#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen,)] +#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct RevenueTransactionData { - pub project_id: ProjectId, - pub revenue_id: RevenueId, - pub job_eligible_id: JobEligibleId, - pub created_date: CreatedDate, - pub updated_date: UpdatedDate, - pub closed_date: CloseDate, - pub feedback: Option, - pub amount: RevenueTransactionAmount, - pub status: RevenueTransactionStatus, - pub documents: Option>, + pub project_id: ProjectId, + pub revenue_id: RevenueId, + pub job_eligible_id: JobEligibleId, + pub created_date: CreatedDate, + pub updated_date: UpdatedDate, + pub closed_date: CloseDate, + pub feedback: Option, + pub amount: RevenueTransactionAmount, + pub status: RevenueTransactionStatus, + pub documents: Option>, } -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy)] +#[derive( + Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy, +)] pub enum RevenueTransactionStatus { - Draft, - Submitted, - Approved, - Rejected, + Draft, + Submitted, + Approved, + Rejected, } impl Default for RevenueTransactionStatus { - fn default() -> Self { - RevenueTransactionStatus::Draft - } + fn default() -> Self { + RevenueTransactionStatus::Draft + } } -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy)] +#[derive( + Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy, +)] pub enum CUDAction { - Create, - Update, - Delete, + Create, + Update, + Delete, } -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy)] +#[derive( + Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy, +)] pub enum AssignAction { - Assign, - Unassign, + Assign, + Unassign, } -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy)] +#[derive( + Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy, +)] pub enum ProxyRole { - Administrator, - Builder, - Investor, - Issuer, - RegionalCenter, + Administrator, + Builder, + Investor, + Issuer, + RegionalCenter, } impl ProxyRole { - pub fn to_vec(self) -> Vec{ - match self{ - Self::Administrator => "Administrator".as_bytes().to_vec(), - Self::Builder => "Builder".as_bytes().to_vec(), - Self::Investor => "Investor".as_bytes().to_vec(), - Self::Issuer => "Issuer".as_bytes().to_vec(), - Self::RegionalCenter => "RegionalCenter".as_bytes().to_vec(), - } - } - - pub fn id(&self) -> [u8;32]{ - self.to_vec().using_encoded(blake2_256) - } - - pub fn enum_to_vec() -> Vec>{ - use crate::types::ProxyRole::*; - [Administrator.to_vec(), Builder.to_vec(), Investor.to_vec(), Issuer.to_vec(), RegionalCenter.to_vec()].to_vec() + pub fn to_vec(self) -> Vec { + match self { + Self::Administrator => "Administrator".as_bytes().to_vec(), + Self::Builder => "Builder".as_bytes().to_vec(), + Self::Investor => "Investor".as_bytes().to_vec(), + Self::Issuer => "Issuer".as_bytes().to_vec(), + Self::RegionalCenter => "RegionalCenter".as_bytes().to_vec(), } - + } + + pub fn id(&self) -> [u8; 32] { + self.to_vec().using_encoded(blake2_256) + } + + pub fn enum_to_vec() -> Vec> { + use crate::types::ProxyRole::*; + [ + Administrator.to_vec(), + Builder.to_vec(), + Investor.to_vec(), + Issuer.to_vec(), + RegionalCenter.to_vec(), + ] + .to_vec() + } } - - -#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy)] +#[derive( + Encode, Decode, Clone, Eq, PartialEq, RuntimeDebugNoBound, MaxEncodedLen, TypeInfo, Copy, +)] pub enum ProxyPermission { - CreateProject, // projects_create_project: admin - EditProject, // projects_edit_project: admin - DeleteProject, // projects_delete_project: admin - AssignUsers, // projects_assign_user: admin - ExecuteUsers, // users: admin - EditUser, // users_edit_user: all - Expenditures, // expenditures: admin - SubmitDrawdown, // submit_drawdown: admin, builder - ApproveDrawdown, // approve_drawdown: admin - RejectDrawdown, // reject_drawdown: admin - ExecuteTransactions, // transactions: admin, builder - UpBulkupload, // up_bulkupload: builder - InflationRate, // inflation: admin - JobEligible, // job_eligible: admin - RevenueTransaction, // revenue_transaction: builder - SubmitRevenue, // submit_revenue: builder - ApproveRevenue, // approve_revenue: admin - RejectRevenue, // reject_revenue: admin - BankConfirming, // bank_confirming: admin - CancelDrawdownSubmission, // cancel_drawdown_submission: builder + CreateProject, // projects_create_project: admin + EditProject, // projects_edit_project: admin + DeleteProject, // projects_delete_project: admin + AssignUsers, // projects_assign_user: admin + ExecuteUsers, // users: admin + EditUser, // users_edit_user: all + Expenditures, // expenditures: admin + SubmitDrawdown, // submit_drawdown: admin, builder + ApproveDrawdown, // approve_drawdown: admin + RejectDrawdown, // reject_drawdown: admin + ExecuteTransactions, // transactions: admin, builder + UpBulkupload, // up_bulkupload: builder + InflationRate, // inflation: admin + JobEligible, // job_eligible: admin + RevenueTransaction, // revenue_transaction: builder + SubmitRevenue, // submit_revenue: builder + ApproveRevenue, // approve_revenue: admin + RejectRevenue, // reject_revenue: admin + BankConfirming, // bank_confirming: admin + CancelDrawdownSubmission, // cancel_drawdown_submission: builder + RecoveryDrawdown, // recovery_drawdown: admin + RecoveryRevenue, // recovery_revenue: admin + RecoveryTransaction, // recovery_drawdown_transaction: admin + RecoveryRevenueTransaction, // recovery_revenue_transaction: admin + BulkUploadTransaction, // bulk_upload_transaction: admin } impl ProxyPermission { - pub fn to_vec(self) -> Vec{ - match self{ - Self::CreateProject => "CreateProject".as_bytes().to_vec(), - Self::EditProject => "EditProject".as_bytes().to_vec(), - Self::DeleteProject => "DeleteProject".as_bytes().to_vec(), - Self::AssignUsers => "AssignUsers".as_bytes().to_vec(), - Self::ExecuteUsers => "ExecuteUsers".as_bytes().to_vec(), - Self::EditUser => "Edituser".as_bytes().to_vec(), - Self::Expenditures => "Expenditures".as_bytes().to_vec(), - Self::SubmitDrawdown => "SubmitDrawdown".as_bytes().to_vec(), - Self::ApproveDrawdown => "ApproveDrawdown".as_bytes().to_vec(), - Self::RejectDrawdown => "RejectDrawdown".as_bytes().to_vec(), - Self::ExecuteTransactions => "ExecuteTransactions".as_bytes().to_vec(), - Self::UpBulkupload => "UpBulkupload".as_bytes().to_vec(), - Self::InflationRate => "InflationRate".as_bytes().to_vec(), - Self::JobEligible => "JobEligible".as_bytes().to_vec(), - Self::RevenueTransaction => "RevenueTransaction".as_bytes().to_vec(), - Self::SubmitRevenue => "SubmitRevenue".as_bytes().to_vec(), - Self::ApproveRevenue => "ApproveRevenue".as_bytes().to_vec(), - Self::RejectRevenue => "RejectRevenue".as_bytes().to_vec(), - Self::BankConfirming => "BankConfirming".as_bytes().to_vec(), - Self::CancelDrawdownSubmission => "CancelDrawdownSubmission".as_bytes().to_vec(), - } - } - - pub fn id(&self) -> [u8;32]{ - self.to_vec().using_encoded(blake2_256) - } - - pub fn administrator_permissions() -> Vec>{ - use crate::types::ProxyPermission::*; - [ - CreateProject.to_vec(), - EditProject.to_vec(), - DeleteProject.to_vec(), - AssignUsers.to_vec(), - ExecuteUsers.to_vec(), - EditUser.to_vec(), - Expenditures.to_vec(), - SubmitDrawdown.to_vec(), - ApproveDrawdown.to_vec(), - RejectDrawdown.to_vec(), - ExecuteTransactions.to_vec(), - UpBulkupload.to_vec(), - InflationRate.to_vec(), - JobEligible.to_vec(), - RevenueTransaction.to_vec(), - SubmitRevenue.to_vec(), - ApproveRevenue.to_vec(), - RejectRevenue.to_vec(), - BankConfirming.to_vec(), - CancelDrawdownSubmission.to_vec(), - ].to_vec() - } - - pub fn builder_permissions() -> Vec>{ - use crate::types::ProxyPermission::*; - [ - EditUser.to_vec(), - SubmitDrawdown.to_vec(), - ExecuteTransactions.to_vec(), - UpBulkupload.to_vec(), - RevenueTransaction.to_vec(), - SubmitRevenue.to_vec(), - CancelDrawdownSubmission.to_vec(), - ].to_vec() + pub fn to_vec(self) -> Vec { + match self { + Self::CreateProject => "CreateProject".as_bytes().to_vec(), + Self::EditProject => "EditProject".as_bytes().to_vec(), + Self::DeleteProject => "DeleteProject".as_bytes().to_vec(), + Self::AssignUsers => "AssignUsers".as_bytes().to_vec(), + Self::ExecuteUsers => "ExecuteUsers".as_bytes().to_vec(), + Self::EditUser => "Edituser".as_bytes().to_vec(), + Self::Expenditures => "Expenditures".as_bytes().to_vec(), + Self::SubmitDrawdown => "SubmitDrawdown".as_bytes().to_vec(), + Self::ApproveDrawdown => "ApproveDrawdown".as_bytes().to_vec(), + Self::RejectDrawdown => "RejectDrawdown".as_bytes().to_vec(), + Self::ExecuteTransactions => "ExecuteTransactions".as_bytes().to_vec(), + Self::UpBulkupload => "UpBulkupload".as_bytes().to_vec(), + Self::InflationRate => "InflationRate".as_bytes().to_vec(), + Self::JobEligible => "JobEligible".as_bytes().to_vec(), + Self::RevenueTransaction => "RevenueTransaction".as_bytes().to_vec(), + Self::SubmitRevenue => "SubmitRevenue".as_bytes().to_vec(), + Self::ApproveRevenue => "ApproveRevenue".as_bytes().to_vec(), + Self::RejectRevenue => "RejectRevenue".as_bytes().to_vec(), + Self::BankConfirming => "BankConfirming".as_bytes().to_vec(), + Self::CancelDrawdownSubmission => "CancelDrawdownSubmission".as_bytes().to_vec(), + Self::RecoveryDrawdown => "RecoveryDrawdown".as_bytes().to_vec(), + Self::RecoveryRevenue => "RecoveryRevenue".as_bytes().to_vec(), + Self::RecoveryTransaction => "RecoveryTransaction".as_bytes().to_vec(), + Self::RecoveryRevenueTransaction => "RecoveryRevenueTransaction".as_bytes().to_vec(), + Self::BulkUploadTransaction => "BulkUploadTransaction".as_bytes().to_vec(), } - - pub fn investor_permissions() -> Vec>{ - use crate::types::ProxyPermission::*; - [EditUser.to_vec(),].to_vec() - } - - pub fn issuer_permissions() -> Vec>{ - use crate::types::ProxyPermission::*; - [EditUser.to_vec(),].to_vec() - } - - pub fn regional_center_permissions() -> Vec>{ - use crate::types::ProxyPermission::*; - [EditUser.to_vec(),].to_vec() - } - - + } + + pub fn id(&self) -> [u8; 32] { + self.to_vec().using_encoded(blake2_256) + } + + pub fn administrator_permissions() -> Vec> { + use crate::types::ProxyPermission::*; + [ + CreateProject.to_vec(), + EditProject.to_vec(), + DeleteProject.to_vec(), + AssignUsers.to_vec(), + ExecuteUsers.to_vec(), + EditUser.to_vec(), + Expenditures.to_vec(), + SubmitDrawdown.to_vec(), + ApproveDrawdown.to_vec(), + RejectDrawdown.to_vec(), + ExecuteTransactions.to_vec(), + UpBulkupload.to_vec(), + InflationRate.to_vec(), + JobEligible.to_vec(), + RevenueTransaction.to_vec(), + SubmitRevenue.to_vec(), + ApproveRevenue.to_vec(), + RejectRevenue.to_vec(), + BankConfirming.to_vec(), + CancelDrawdownSubmission.to_vec(), + RecoveryDrawdown.to_vec(), + RecoveryRevenue.to_vec(), + RecoveryTransaction.to_vec(), + RecoveryRevenueTransaction.to_vec(), + BulkUploadTransaction.to_vec(), + ] + .to_vec() + } + + pub fn builder_permissions() -> Vec> { + use crate::types::ProxyPermission::*; + [ + EditUser.to_vec(), + SubmitDrawdown.to_vec(), + ExecuteTransactions.to_vec(), + UpBulkupload.to_vec(), + RevenueTransaction.to_vec(), + SubmitRevenue.to_vec(), + CancelDrawdownSubmission.to_vec(), + ] + .to_vec() + } + + pub fn investor_permissions() -> Vec> { + use crate::types::ProxyPermission::*; + [EditUser.to_vec()].to_vec() + } + + pub fn issuer_permissions() -> Vec> { + use crate::types::ProxyPermission::*; + [EditUser.to_vec()].to_vec() + } + + pub fn regional_center_permissions() -> Vec> { + use crate::types::ProxyPermission::*; + [EditUser.to_vec()].to_vec() + } } diff --git a/pallets/rbac/src/functions.rs b/pallets/rbac/src/functions.rs index 711a8322..84c72e7d 100644 --- a/pallets/rbac/src/functions.rs +++ b/pallets/rbac/src/functions.rs @@ -7,7 +7,7 @@ use sp_runtime::sp_std::vec::Vec; use crate::types::*; impl RoleBasedAccessControl for Pallet { - /* ---- Basic Insertion of individual storage maps --- */ + /*---- Basic Insertion of individual storage maps ---*/ /// Scope creation /// /// Creates a scope within a external pallet using the pallet index. @@ -410,13 +410,12 @@ impl RoleBasedAccessControl for Pallet { Self::deposit_event(Event::PermissionRemovedFromPallet( pallet_id, permission, - Self::bound(affected_roles, Error::::ExceedMaxRolesPerPallet)?, /* this bound should never - * fail */ + Self::bound(affected_roles, Error::::ExceedMaxRolesPerPallet)?, // this bound should never fail )); Ok(()) } - /* ---- Helper functions ---- */ + /*---- Helper functions ----*/ /// Authorization function ///