use super::*;
use frame_support::{pallet_prelude::*, sp_io::hashing::blake2_256};
use scale_info::prelude::vec;
use sp_runtime::sp_std::vec::Vec; use frame_support::traits::{Currency, ExistenceRequirement::KeepAlive};
use crate::types::*;
use pallet_rbac::types::*;
use frame_support::traits::Time;
impl<T: Config> Pallet<T> {
pub fn do_initial_setup() -> DispatchResult {
let pallet_id = Self::pallet_id();
let global_scope = pallet_id.using_encoded(blake2_256);
<GlobalScope<T>>::put(global_scope);
T::Rbac::create_scope(Self::pallet_id(), global_scope)?;
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(),
)?;
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(),
)?;
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(),
)?;
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(),
)?;
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(),
)?;
Self::deposit_event(Event::ProxySetupCompleted);
Ok(())
}
pub fn do_sudo_add_administrator(admin: T::AccountId, name: FieldName) -> DispatchResult {
ensure!(!name.is_empty(), Error::<T>::EmptyFieldName);
Self::sudo_register_admin(admin.clone(), name)?;
Self::deposit_event(Event::AdministratorAssigned(admin));
Ok(())
}
pub fn do_sudo_remove_administrator(admin: T::AccountId) -> DispatchResult {
Self::sudo_delete_admin(admin.clone())?;
Self::deposit_event(Event::AdministratorRemoved(admin));
Ok(())
}
pub fn do_create_project(
admin: T::AccountId,
title: FieldName,
description: FieldDescription,
image: Option<CID>,
address: FieldName,
banks: Option<Banks<T>>,
creation_date: CreationDate,
completion_date: CompletionDate,
expenditures: Expenditures<T>,
job_eligibles: Option<JobEligibles<T>>,
users: Option<UsersAssignation<T>>,
private_group_id: PrivateGroupId,
) -> DispatchResult {
Self::is_authorized(admin.clone(), &Self::get_global_scope(), ProxyPermission::CreateProject)?;
ensure!(!title.is_empty(), Error::<T>::EmptyFieldName);
ensure!(!description.is_empty(), Error::<T>::EmptyFieldDescription);
if let Some(image) = image.clone() {
ensure!(!image.is_empty(), Error::<T>::EmptyFieldCID);
}
ensure!(!address.is_empty(), Error::<T>::EmptyFieldName);
if let Some(banks) = banks.clone() {
ensure!(!banks.is_empty(), Error::<T>::EmptyFieldBanks);
}
ensure!(!address.is_empty(), Error::<T>::EmptyProjectAddress);
ensure!(!private_group_id.is_empty(), Error::<T>::PrivateGroupIdEmpty);
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
let project_id: ProjectId = (title.clone(), timestamp).using_encoded(blake2_256);
ensure!(completion_date > creation_date, Error::<T>::CompletionDateMustBeLater);
ensure!(!private_group_id.is_empty(), Error::<T>::PrivateGroupIdEmpty);
let project_data = ProjectData::<T> {
builder: Some(BoundedVec::<T::AccountId, T::MaxBuildersPerProject>::default()),
investor: Some(BoundedVec::<T::AccountId, T::MaxInvestorsPerProject>::default()),
issuer: Some(BoundedVec::<T::AccountId, T::MaxIssuersPerProject>::default()),
regional_center: Some(BoundedVec::<T::AccountId, T::MaxRegionalCenterPerProject>::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,
};
T::Rbac::create_scope(Self::pallet_id(), project_id)?;
ensure!(!ProjectsInfo::<T>::contains_key(project_id), Error::<T>::ProjectIdAlreadyInUse);
ProjectsInfo::<T>::insert(project_id, project_data);
Self::do_execute_expenditures(admin.clone(), project_id, expenditures)?;
if let Some(mod_job_eligibles) = job_eligibles {
Self::do_execute_job_eligibles(admin.clone(), project_id, mod_job_eligibles)?;
}
if let Some(mod_users) = users {
Self::do_execute_assign_users(admin.clone(), project_id, mod_users)?;
}
Self::do_initialize_drawdowns(admin.clone(), project_id)?;
Self::do_initialize_revenue(project_id)?;
Self::deposit_event(Event::ProjectCreated(admin, project_id));
Ok(())
}
pub fn do_edit_project(
admin: T::AccountId,
project_id: ProjectId,
title: Option<FieldName>,
description: Option<FieldDescription>,
image: Option<CID>,
address: Option<FieldName>,
banks: Option<Banks<T>>,
creation_date: Option<CreationDate>,
completion_date: Option<CompletionDate>,
) -> DispatchResult {
Self::is_authorized(admin.clone(), &project_id, ProxyPermission::EditProject)?;
ensure!(ProjectsInfo::<T>::contains_key(project_id), Error::<T>::ProjectNotFound);
Self::is_project_completed(project_id)?;
let current_timestamp =
Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project| {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
if let Some(title) = title {
ensure!(!title.is_empty(), Error::<T>::EmptyFieldName);
project.title = title;
}
if let Some(description) = description {
ensure!(!description.is_empty(), Error::<T>::EmptyFieldDescription);
project.description = description;
}
if let Some(image) = image {
ensure!(!image.is_empty(), Error::<T>::EmptyFieldCID);
project.image = Some(image);
}
if let Some(address) = address {
ensure!(!address.is_empty(), Error::<T>::EmptyProjectAddress);
project.address = address;
}
if let Some(banks) = banks {
ensure!(!banks.is_empty(), Error::<T>::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;
}
project.updated_date = current_timestamp;
Ok(())
})?;
Self::is_project_completion_date_later(project_id)?;
Self::deposit_event(Event::ProjectEdited(admin, project_id));
Ok(())
}
pub fn do_delete_project(admin: T::AccountId, project_id: ProjectId) -> DispatchResult {
Self::is_authorized(admin.clone(), &project_id, ProxyPermission::DeleteProject)?;
let project_data = ProjectsInfo::<T>::get(project_id).ok_or(Error::<T>::ProjectNotFound)?;
ensure!(
project_data.status != ProjectStatus::Completed,
Error::<T>::CannotDeleteCompletedProject
);
if UsersByProject::<T>::contains_key(project_id) {
let users_by_project = UsersByProject::<T>::get(project_id);
let mut users_assignation: UsersAssignation<T> = UsersAssignation::<T>::default();
for user in users_by_project.iter().cloned() {
let user_data =
<UsersInfo<T>>::try_get(user.clone()).map_err(|_| Error::<T>::UserNotRegistered)?;
users_assignation
.try_push((user, user_data.role, AssignAction::Unassign))
.map_err(|_| Error::<T>::MaxRegistrationsAtATimeReached)?;
}
Self::do_execute_assign_users(admin.clone(), project_id, users_assignation)?;
for user in users_by_project.iter().cloned() {
<ProjectsByUser<T>>::try_mutate::<_, _, DispatchError, _>(user, |projects| {
projects.retain(|project| *project != project_id);
Ok(())
})?;
}
}
<ProjectsInfo<T>>::remove(project_id);
<UsersByProject<T>>::remove(project_id);
let expenditures_by_project = Self::expenditures_by_project(project_id)
.iter()
.cloned()
.collect::<Vec<[u8; 32]>>();
for expenditure_id in expenditures_by_project.iter().cloned() {
<ExpendituresInfo<T>>::remove(expenditure_id);
}
<ExpendituresByProject<T>>::remove(project_id);
let drawdowns_by_project = Self::drawdowns_by_project(project_id)
.iter()
.cloned()
.collect::<Vec<[u8; 32]>>();
for drawdown_id in drawdowns_by_project.iter().cloned() {
let transactions_by_drawdown = Self::transactions_by_drawdown(project_id, drawdown_id)
.iter()
.cloned()
.collect::<Vec<[u8; 32]>>();
for transaction_id in transactions_by_drawdown.iter().cloned() {
<TransactionsInfo<T>>::remove(transaction_id);
}
<TransactionsByDrawdown<T>>::remove(project_id, drawdown_id);
<DrawdownsInfo<T>>::remove(drawdown_id);
}
<DrawdownsByProject<T>>::remove(project_id);
let job_eligibles_by_project = Self::job_eligibles_by_project(project_id)
.iter()
.cloned()
.collect::<Vec<[u8; 32]>>();
for job_eligible_id in job_eligibles_by_project.iter().cloned() {
<JobEligiblesInfo<T>>::remove(job_eligible_id);
}
<JobEligiblesByProject<T>>::remove(project_id);
let revenues_by_project =
Self::revenues_by_project(project_id).iter().cloned().collect::<Vec<[u8; 32]>>();
for revenue_id in revenues_by_project.iter().cloned() {
let transactions_by_revenue = Self::transactions_by_revenue(project_id, revenue_id)
.iter()
.cloned()
.collect::<Vec<[u8; 32]>>();
for transaction_id in transactions_by_revenue.iter().cloned() {
<RevenueTransactionsInfo<T>>::remove(transaction_id);
}
<TransactionsByRevenue<T>>::remove(project_id, revenue_id);
<RevenuesInfo<T>>::remove(revenue_id);
}
<RevenuesByProject<T>>::remove(project_id);
T::Rbac::remove_scope(Self::pallet_id(), project_id)?;
Self::deposit_event(Event::ProjectDeleted(admin, project_id));
Ok(())
}
pub fn do_execute_assign_users(
admin: T::AccountId,
project_id: ProjectId,
users: UsersAssignation<T>,
) -> DispatchResult {
Self::is_authorized(admin.clone(), &project_id, ProxyPermission::AssignUsers)?;
ensure!(!users.is_empty(), Error::<T>::EmptyUsersAssignation);
Self::is_project_completed(project_id)?;
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)?;
},
}
}
Self::deposit_event(Event::UsersAssignationExecuted(admin, project_id));
Ok(())
}
fn do_assign_user(project_id: ProjectId, user: T::AccountId, role: ProxyRole) -> DispatchResult {
Self::check_user_role(user.clone(), role)?;
ensure!(
!<UsersByProject<T>>::get(project_id).contains(&user),
Error::<T>::UserAlreadyAssignedToProject
);
ensure!(
!<ProjectsByUser<T>>::get(user.clone()).contains(&project_id),
Error::<T>::UserAlreadyAssignedToProject
);
ensure!(
!T::Rbac::has_role(user.clone(), Self::pallet_id(), &project_id, [role.id()].to_vec())
.is_ok(),
Error::<T>::UserAlreadyAssignedToProject
);
Self::add_project_role(project_id, user.clone(), role)?;
<ProjectsByUser<T>>::try_mutate::<_, _, DispatchError, _>(user.clone(), |projects| {
projects
.try_push(project_id)
.map_err(|_| Error::<T>::MaxProjectsPerUserReached)?;
Ok(())
})?;
<UsersByProject<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |users| {
users
.try_push(user.clone())
.map_err(|_| Error::<T>::MaxUsersPerProjectReached)?;
Ok(())
})?;
T::Rbac::assign_role_to_user(user.clone(), Self::pallet_id(), &project_id, role.id())?;
Self::deposit_event(Event::UserAssignmentCompleted(user, project_id));
Ok(())
}
fn do_unassign_user(
project_id: ProjectId,
user: T::AccountId,
role: ProxyRole,
) -> DispatchResult {
ensure!(<UsersInfo<T>>::contains_key(user.clone()), Error::<T>::UserNotRegistered);
ensure!(
<UsersByProject<T>>::get(project_id).contains(&user.clone()),
Error::<T>::UserNotAssignedToProject
);
ensure!(
<ProjectsByUser<T>>::get(user.clone()).contains(&project_id),
Error::<T>::UserNotAssignedToProject
);
ensure!(
T::Rbac::has_role(user.clone(), Self::pallet_id(), &project_id, [role.id()].to_vec()).is_ok(),
Error::<T>::UserDoesNotHaveRole
);
Self::remove_project_role(project_id, user.clone(), role)?;
<UsersByProject<T>>::try_mutate_exists::<_, _, DispatchError, _>(project_id, |users_option| {
let users = users_option.as_mut().ok_or(Error::<T>::ProjectHasNoUsers)?;
users.retain(|u| u != &user);
if users.is_empty() {
users_option.clone_from(&None);
}
Ok(())
})?;
<ProjectsByUser<T>>::try_mutate_exists::<_, _, DispatchError, _>(
user.clone(),
|projects_option| {
let projects = projects_option.as_mut().ok_or(Error::<T>::UserHasNoProjects)?;
projects.retain(|project| project != &project_id);
if projects.is_empty() {
projects_option.clone_from(&None);
}
Ok(())
},
)?;
T::Rbac::remove_role_from_user(user.clone(), Self::pallet_id(), &project_id, role.id())?;
Self::deposit_event(Event::UserUnassignmentCompleted(user, project_id));
Ok(())
}
pub fn do_execute_users(admin: T::AccountId, users: Users<T>) -> DispatchResult {
Self::is_authorized(admin.clone(), &Self::get_global_scope(), ProxyPermission::ExecuteUsers)?;
ensure!(!users.is_empty(), Error::<T>::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::<T>::UserNameRequired)?,
user.2.ok_or(Error::<T>::UserRoleRequired)?,
)?;
Self::send_funds(admin.clone(), user.0.clone())?;
},
CUDAction::Update => {
Self::do_update_user(user.0.clone(), user.1.clone(), user.2)?;
Self::send_funds(admin.clone(), user.0.clone())?;
},
CUDAction::Delete => {
ensure!(user.0 != admin, Error::<T>::AdministratorsCannotDeleteThemselves,);
Self::do_delete_user(user.0.clone())?;
},
}
}
Self::deposit_event(Event::UsersExecuted(admin));
Ok(())
}
fn do_create_user(user: T::AccountId, name: FieldName, role: ProxyRole) -> DispatchResult {
let current_timestamp =
Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
ensure!(!<UsersInfo<T>>::contains_key(user.clone()), Error::<T>::UserAlreadyRegistered);
ensure!(!name.is_empty(), Error::<T>::UserNameRequired);
match role {
ProxyRole::Administrator => {
Self::do_sudo_add_administrator(user.clone(), name)?;
},
_ => {
let user_data = UserData::<T> {
name,
role,
image: CID::default(),
date_registered: current_timestamp,
email: FieldName::default(),
documents: None,
};
<UsersInfo<T>>::insert(user.clone(), user_data);
},
}
Self::deposit_event(Event::UserCreated(user));
Ok(())
}
fn do_update_user(
user: T::AccountId,
name: Option<FieldName>,
role: Option<ProxyRole>,
) -> DispatchResult {
ensure!(<UsersInfo<T>>::contains_key(user.clone()), Error::<T>::UserNotRegistered);
<UsersInfo<T>>::try_mutate::<_, _, DispatchError, _>(user.clone(), |user_data| {
let user_info = user_data.as_mut().ok_or(Error::<T>::UserNotRegistered)?;
if let Some(mod_name) = name {
ensure!(!mod_name.is_empty(), Error::<T>::UserNameRequired);
user_info.name = mod_name;
}
if let Some(mod_role) = role {
ensure!(
<ProjectsByUser<T>>::get(user.clone()).is_empty(),
Error::<T>::UserHasAssignedProjectsCannotUpdateRole
);
user_info.role = mod_role;
}
Ok(())
})?;
Self::deposit_event(Event::UserUpdated(user));
Ok(())
}
fn do_delete_user(user: T::AccountId) -> DispatchResult {
let user_data = <UsersInfo<T>>::get(user.clone()).ok_or(Error::<T>::UserNotRegistered)?;
match user_data.role {
ProxyRole::Administrator => {
Self::do_sudo_remove_administrator(user.clone())?;
},
_ => {
ensure!(
<ProjectsByUser<T>>::get(user.clone()).is_empty(),
Error::<T>::UserHasAssignedProjectsCannotDelete
);
<UsersInfo<T>>::remove(user.clone());
},
}
Self::deposit_event(Event::UserDeleted(user));
Ok(())
}
pub fn do_edit_user(
user: T::AccountId,
name: Option<FieldName>,
image: Option<CID>,
email: Option<FieldName>,
documents: Option<Documents<T>>,
) -> DispatchResult {
ensure!(<UsersInfo<T>>::contains_key(user.clone()), Error::<T>::UserNotRegistered);
<UsersInfo<T>>::try_mutate::<_, _, DispatchError, _>(user.clone(), |user_data| {
let user_info = user_data.as_mut().ok_or(Error::<T>::UserNotRegistered)?;
if let Some(mod_name) = name {
ensure!(!mod_name.is_empty(), Error::<T>::UserNameRequired);
user_info.name = mod_name;
}
if let Some(mod_image) = image {
ensure!(!mod_image.is_empty(), Error::<T>::UserImageRequired);
user_info.image = mod_image;
}
if let Some(mod_email) = email {
ensure!(!mod_email.is_empty(), Error::<T>::UserEmailRequired);
user_info.email = mod_email;
}
if let Some(mod_documents) = documents {
ensure!(user_info.role == ProxyRole::Investor, Error::<T>::UserIsNotAnInvestor);
ensure!(!mod_documents.is_empty(), Error::<T>::DocumentsEmpty);
user_info.documents = Some(mod_documents);
}
Ok(())
})?;
Self::deposit_event(Event::UserUpdated(user));
Ok(())
}
pub fn do_execute_expenditures(
admin: T::AccountId,
project_id: ProjectId,
expenditures: Expenditures<T>,
) -> DispatchResult {
Self::is_authorized(admin.clone(), &project_id, ProxyPermission::Expenditures)?;
ensure!(<ProjectsInfo<T>>::contains_key(project_id), Error::<T>::ProjectNotFound);
ensure!(!expenditures.is_empty(), Error::<T>::EmptyExpenditures);
for expenditure in expenditures.iter().cloned() {
match expenditure.5 {
CUDAction::Create => {
Self::do_create_expenditure(
project_id,
expenditure.0.ok_or(Error::<T>::ExpenditureNameRequired)?,
expenditure.1.ok_or(Error::<T>::ExpenditureTypeRequired)?,
expenditure.2.ok_or(Error::<T>::ExpenditureAmountRequired)?,
expenditure.3,
expenditure.4,
)?;
},
CUDAction::Update => {
Self::do_update_expenditure(
project_id,
expenditure.6.ok_or(Error::<T>::ExpenditureIdRequired)?,
expenditure.0,
expenditure.2,
expenditure.3,
expenditure.4,
)?;
},
CUDAction::Delete => {
Self::do_delete_expenditure(expenditure.6.ok_or(Error::<T>::ExpenditureIdRequired)?)?;
},
}
}
Self::deposit_event(Event::ExpendituresExecuted(admin, project_id));
Ok(())
}
fn do_create_expenditure(
project_id: [u8; 32],
name: FieldName,
expenditure_type: ExpenditureType,
expenditure_amount: ExpenditureAmount,
naics_code: Option<NAICSCode>,
jobs_multiplier: Option<JobsMultiplier>,
) -> DispatchResult {
Self::is_project_completed(project_id)?;
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
ensure!(!name.is_empty(), Error::<T>::EmptyExpenditureName);
let expenditure_id: ExpenditureId =
(project_id, name.clone(), expenditure_type, timestamp).using_encoded(blake2_256);
let expenditure_data = ExpenditureData {
project_id,
name,
expenditure_type,
expenditure_amount,
naics_code,
jobs_multiplier,
};
ensure!(
!<ExpendituresInfo<T>>::contains_key(expenditure_id),
Error::<T>::ExpenditureAlreadyExists
);
<ExpendituresInfo<T>>::insert(expenditure_id, expenditure_data);
<ExpendituresByProject<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |expenditures| {
expenditures
.try_push(expenditure_id)
.map_err(|_| Error::<T>::MaxExpendituresPerProjectReached)?;
Ok(())
})?;
Self::deposit_event(Event::ExpenditureCreated(project_id, expenditure_id));
Ok(())
}
fn do_update_expenditure(
project_id: ProjectId,
expenditure_id: ExpenditureId,
name: Option<FieldName>,
expenditure_amount: Option<ExpenditureAmount>,
naics_code: Option<NAICSCode>,
jobs_multiplier: Option<JobsMultiplier>,
) -> DispatchResult {
Self::is_project_completed(project_id)?;
ensure!(<ExpendituresInfo<T>>::contains_key(expenditure_id), Error::<T>::ExpenditureNotFound);
<ExpendituresInfo<T>>::try_mutate::<_, _, DispatchError, _>(
expenditure_id,
|expenditure_data| {
let expenditure = expenditure_data.as_mut().ok_or(Error::<T>::ExpenditureNotFound)?;
ensure!(
expenditure.project_id == project_id,
Error::<T>::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 {
let expenditure_data =
ExpendituresInfo::<T>::get(&expenditure_id).ok_or(Error::<T>::ExpenditureNotFound)?;
ensure!(
<ExpendituresByProject<T>>::get(expenditure_data.project_id).contains(&expenditure_id),
Error::<T>::ExpenditureNotFoundForSelectedProjectId
);
Self::do_delete_expenditure_transactions(expenditure_id)?;
<ExpendituresInfo<T>>::remove(expenditure_id);
<ExpendituresByProject<T>>::try_mutate_exists::<_, _, DispatchError, _>(
expenditure_data.project_id,
|expenditures_option| {
let expenditures =
expenditures_option.as_mut().ok_or(Error::<T>::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(())
}
fn do_create_drawdown(
project_id: ProjectId,
drawdown_type: DrawdownType,
drawdown_number: DrawdownNumber,
) -> DispatchResult {
ensure!(ProjectsInfo::<T>::contains_key(project_id), Error::<T>::ProjectNotFound);
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
let drawdown_id =
(project_id, drawdown_type, drawdown_number, timestamp).using_encoded(blake2_256);
let drawdown_data = DrawdownData::<T> {
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::<T>::default(),
recovery_record: RecoveryRecord::<T>::default(),
created_date: timestamp,
closed_date: 0,
};
ensure!(!DrawdownsInfo::<T>::contains_key(drawdown_id), Error::<T>::DrawdownAlreadyExists);
<DrawdownsInfo<T>>::insert(drawdown_id, drawdown_data);
<DrawdownsByProject<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |drawdowns| {
drawdowns
.try_push(drawdown_id)
.map_err(|_| Error::<T>::MaxDrawdownsPerProjectReached)?;
Ok(())
})?;
Self::do_update_drawdown_status_in_project_info(
project_id,
drawdown_id,
DrawdownStatus::default(),
)?;
Self::deposit_event(Event::DrawdownCreated(project_id, drawdown_id));
Ok(())
}
fn do_initialize_drawdowns(admin: T::AccountId, project_id: ProjectId) -> DispatchResult {
Self::is_authorized(admin.clone(), &project_id, ProxyPermission::Expenditures)?;
ensure!(ProjectsInfo::<T>::contains_key(project_id), Error::<T>::ProjectNotFound);
Self::do_create_drawdown(project_id, DrawdownType::EB5, 1)?;
Self::do_create_drawdown(project_id, DrawdownType::ConstructionLoan, 1)?;
Self::do_create_drawdown(project_id, DrawdownType::DeveloperEquity, 1)?;
Self::deposit_event(Event::DrawdownsInitialized(admin, project_id));
Ok(())
}
pub fn do_submit_drawdown(
user: T::AccountId,
project_id: ProjectId,
drawdown_id: DrawdownId,
) -> DispatchResult {
Self::is_authorized(user.clone(), &project_id, ProxyPermission::SubmitDrawdown)?;
Self::is_project_completed(project_id)?;
Self::is_drawdown_editable(user, drawdown_id)?;
ensure!(
!<TransactionsByDrawdown<T>>::get(project_id, drawdown_id).is_empty(),
Error::<T>::DrawdownHasNoTransactions
);
let drawdown_transactions = TransactionsByDrawdown::<T>::try_get(project_id, drawdown_id)
.map_err(|_| Error::<T>::DrawdownHasNoTransactions)?;
for transaction_id in drawdown_transactions.iter().cloned() {
ensure!(TransactionsInfo::<T>::contains_key(transaction_id), Error::<T>::TransactionNotFound);
<TransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|transaction_data| {
let transaction_data =
transaction_data.as_mut().ok_or(Error::<T>::TransactionNotFound)?;
transaction_data.status = TransactionStatus::Submitted;
transaction_data.feedback = None;
Ok(())
},
)?;
}
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let drawdown_data = drawdown_data.as_mut().ok_or(Error::<T>::DrawdownNotFound)?;
drawdown_data.status = DrawdownStatus::Submitted;
drawdown_data.feedback = None;
Ok(())
})?;
Self::do_update_drawdown_status_in_project_info(
project_id,
drawdown_id,
DrawdownStatus::Submitted,
)?;
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 {
Self::is_authorized(admin, &project_id, ProxyPermission::ApproveDrawdown)?;
ensure!(ProjectsInfo::<T>::contains_key(project_id), Error::<T>::ProjectNotFound);
let drawdown_data = DrawdownsInfo::<T>::get(drawdown_id).ok_or(Error::<T>::DrawdownNotFound)?;
ensure!(
!<TransactionsByDrawdown<T>>::get(project_id, drawdown_id).is_empty(),
Error::<T>::DrawdownHasNoTransactions
);
ensure!(drawdown_data.status == DrawdownStatus::Submitted, Error::<T>::DrawdownNotSubmitted);
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
let drawdown_transactions = TransactionsByDrawdown::<T>::try_get(project_id, drawdown_id)
.map_err(|_| Error::<T>::DrawdownHasNoTransactions)?;
for transaction_id in drawdown_transactions.iter().cloned() {
ensure!(TransactionsInfo::<T>::contains_key(transaction_id), Error::<T>::TransactionNotFound);
<TransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|transaction_data| {
let transaction_data =
transaction_data.as_mut().ok_or(Error::<T>::TransactionNotFound)?;
transaction_data.status = TransactionStatus::Approved;
transaction_data.closed_date = timestamp;
Ok(())
},
)?;
}
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let drawdown_data = drawdown_data.as_mut().ok_or(Error::<T>::DrawdownNotFound)?;
drawdown_data.status = DrawdownStatus::Approved;
drawdown_data.closed_date = timestamp;
Ok(())
})?;
Self::do_update_drawdown_status_in_project_info(
project_id,
drawdown_id,
DrawdownStatus::Approved,
)?;
Self::do_create_drawdown(
project_id,
drawdown_data.drawdown_type,
drawdown_data.drawdown_number + 1,
)?;
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<TransactionsFeedback<T>>,
drawdown_feedback: Option<FieldDescription>,
) -> DispatchResult {
Self::is_authorized(admin, &project_id, ProxyPermission::RejectDrawdown)?;
ensure!(ProjectsInfo::<T>::contains_key(project_id), Error::<T>::ProjectNotFound);
let drawdown_data = DrawdownsInfo::<T>::get(drawdown_id).ok_or(Error::<T>::DrawdownNotFound)?;
ensure!(drawdown_data.status == DrawdownStatus::Submitted, Error::<T>::DrawdownNotSubmitted);
match drawdown_data.drawdown_type {
DrawdownType::EB5 => {
ensure!(
!<TransactionsByDrawdown<T>>::get(project_id, drawdown_id).is_empty(),
Error::<T>::DrawdownHasNoTransactions
);
let drawdown_transactions = TransactionsByDrawdown::<T>::try_get(project_id, drawdown_id)
.map_err(|_| Error::<T>::DrawdownHasNoTransactions)?;
for transaction_id in drawdown_transactions.iter().cloned() {
ensure!(
TransactionsInfo::<T>::contains_key(transaction_id),
Error::<T>::TransactionNotFound
);
<TransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|transaction_data| {
let transaction_data =
transaction_data.as_mut().ok_or(Error::<T>::TransactionNotFound)?;
transaction_data.status = TransactionStatus::Rejected;
Ok(())
},
)?;
}
let mod_transactions_feedback =
transactions_feedback.ok_or(Error::<T>::EB5MissingFeedback)?;
ensure!(!mod_transactions_feedback.is_empty(), Error::<T>::EmptyEb5Feedback);
for (transaction_id, feedback) in mod_transactions_feedback.iter().cloned() {
<TransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|transaction_data| {
let transaction_data =
transaction_data.as_mut().ok_or(Error::<T>::TransactionNotFound)?;
transaction_data.feedback = Some(feedback);
Ok(())
},
)?;
}
},
_ => {
let mod_drawdown_feedback =
drawdown_feedback.ok_or(Error::<T>::NoFeedbackProvidedForBulkUpload)?;
ensure!(!mod_drawdown_feedback.is_empty(), Error::<T>::EmptyBulkUploadFeedback);
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let drawdown_data = drawdown_data.as_mut().ok_or(Error::<T>::DrawdownNotFound)?;
drawdown_data.feedback = Some(mod_drawdown_feedback.clone());
Ok(())
})?;
},
}
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let drawdown_data = drawdown_data.as_mut().ok_or(Error::<T>::DrawdownNotFound)?;
drawdown_data.status = DrawdownStatus::Rejected;
Ok(())
})?;
Self::do_update_drawdown_status_in_project_info(
project_id,
drawdown_id,
DrawdownStatus::Rejected,
)?;
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 {
Self::is_authorized(user.clone(), &project_id, ProxyPermission::CancelDrawdownSubmission)?;
ensure!(ProjectsInfo::<T>::contains_key(project_id), Error::<T>::ProjectNotFound);
let drawdown_data = DrawdownsInfo::<T>::get(drawdown_id).ok_or(Error::<T>::DrawdownNotFound)?;
ensure!(drawdown_data.status == DrawdownStatus::Submitted, Error::<T>::DrawdownNotSubmitted);
if drawdown_data.drawdown_type == DrawdownType::EB5 {
let drawdown_transactions = TransactionsByDrawdown::<T>::try_get(project_id, drawdown_id)
.map_err(|_| Error::<T>::DrawdownNotFound)?;
for transaction_id in drawdown_transactions.iter().cloned() {
<TransactionsInfo<T>>::remove(transaction_id);
}
}
<TransactionsByDrawdown<T>>::remove(project_id, drawdown_id);
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let drawdown_data = drawdown_data.as_mut().ok_or(Error::<T>::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::<T>::default();
Ok(())
})?;
Self::do_update_drawdown_status_in_project_info(
project_id,
drawdown_id,
DrawdownStatus::default(),
)?;
Self::deposit_event(Event::DrawdownSubmissionCancelled(project_id, drawdown_id));
Ok(())
}
pub fn do_execute_transactions(
user: T::AccountId,
project_id: ProjectId,
drawdown_id: DrawdownId,
transactions: Transactions<T>,
) -> DispatchResult {
Self::is_authorized(user.clone(), &project_id, ProxyPermission::ExecuteTransactions)?;
Self::is_project_completed(project_id)?;
ensure!(DrawdownsInfo::<T>::contains_key(drawdown_id), Error::<T>::DrawdownNotFound);
ensure!(!transactions.is_empty(), Error::<T>::EmptyTransactions);
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::<T>::ExpenditureIdRequired)?,
transaction.1.ok_or(Error::<T>::AmountRequired)?,
transaction.2,
)?;
},
CUDAction::Update => {
Self::is_transaction_editable(
user.clone(),
transaction.4.ok_or(Error::<T>::TransactionIdRequired)?,
)?;
Self::do_update_transaction(
transaction.1,
transaction.2,
transaction.4.ok_or(Error::<T>::TransactionIdRequired)?,
)?;
},
CUDAction::Delete => {
Self::is_transaction_editable(
user.clone(),
transaction.4.ok_or(Error::<T>::TransactionIdRequired)?,
)?;
Self::do_delete_transaction(transaction.4.ok_or(Error::<T>::TransactionIdRequired)?)?;
},
}
}
Self::do_calculate_drawdown_total_amount(project_id, drawdown_id)?;
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<Documents<T>>,
) -> DispatchResult {
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
let transaction_id =
(drawdown_id, amount, expenditure_id, timestamp, project_id).using_encoded(blake2_256);
ensure!(ExpendituresInfo::<T>::contains_key(expenditure_id), Error::<T>::ExpenditureNotFound);
let transaction_data = TransactionData::<T> {
project_id,
drawdown_id,
expenditure_id,
created_date: timestamp,
updated_date: timestamp,
closed_date: 0,
feedback: None,
amount,
status: TransactionStatus::default(),
documents,
};
ensure!(
!TransactionsInfo::<T>::contains_key(transaction_id),
Error::<T>::TransactionAlreadyExists
);
<TransactionsInfo<T>>::insert(transaction_id, transaction_data);
<TransactionsByDrawdown<T>>::try_mutate::<_, _, _, DispatchError, _>(
project_id,
drawdown_id,
|transactions| {
transactions
.try_push(transaction_id)
.map_err(|_| Error::<T>::MaxTransactionsPerDrawdownReached)?;
Ok(())
},
)?;
Self::deposit_event(Event::TransactionCreated(project_id, drawdown_id, transaction_id));
Ok(())
}
fn do_update_transaction(
amount: Option<ExpenditureAmount>,
documents: Option<Documents<T>>,
transaction_id: TransactionId,
) -> DispatchResult {
let transaction_data =
Self::transactions_info(transaction_id).ok_or(Error::<T>::TransactionNotFound)?;
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
<TransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|transaction_data| {
let mod_transaction_data =
transaction_data.as_mut().ok_or(Error::<T>::TransactionNotFound)?;
ensure!(
ExpendituresInfo::<T>::contains_key(mod_transaction_data.expenditure_id),
Error::<T>::ExpenditureNotFound
);
if let Some(mod_amount) = amount {
mod_transaction_data.amount = mod_amount;
}
if let Some(mod_documents) = documents {
mod_transaction_data.documents = Some(mod_documents);
}
mod_transaction_data.updated_date = timestamp;
Ok(())
},
)?;
Self::deposit_event(Event::TransactionEdited(
transaction_data.project_id,
transaction_data.drawdown_id,
transaction_id,
));
Ok(())
}
fn do_delete_transaction(transaction_id: TransactionId) -> DispatchResult {
let transaction_data =
TransactionsInfo::<T>::get(transaction_id).ok_or(Error::<T>::TransactionNotFound)?;
ensure!(
<TransactionsByDrawdown<T>>::get(transaction_data.project_id, transaction_data.drawdown_id)
.contains(&transaction_id),
Error::<T>::TransactionNotFoundForSelectedDrawdownId
);
<TransactionsByDrawdown<T>>::try_mutate_exists::<_, _, _, DispatchError, _>(
transaction_data.project_id,
transaction_data.drawdown_id,
|transactions_option| {
let transactions =
transactions_option.as_mut().ok_or(Error::<T>::DrawdownHasNoTransactions)?;
transactions.retain(|transaction| transaction != &transaction_id);
if transactions.is_empty() {
transactions_option.clone_from(&None);
}
Ok(())
},
)?;
<TransactionsInfo<T>>::remove(transaction_id);
Self::deposit_event(Event::TransactionDeleted(
transaction_data.project_id,
transaction_data.drawdown_id,
transaction_id,
));
Ok(())
}
pub fn do_up_bulk_upload(
user: T::AccountId,
project_id: ProjectId,
drawdown_id: DrawdownId,
description: FieldDescription,
total_amount: TotalAmount,
documents: Documents<T>,
) -> DispatchResult {
Self::is_authorized(user.clone(), &project_id, ProxyPermission::UpBulkupload)?;
Self::is_project_completed(project_id)?;
Self::is_drawdown_editable(user, drawdown_id)?;
let drawdown_data = DrawdownsInfo::<T>::get(drawdown_id).ok_or(Error::<T>::DrawdownNotFound)?;
ensure!(
drawdown_data.drawdown_type == DrawdownType::ConstructionLoan
|| drawdown_data.drawdown_type == DrawdownType::DeveloperEquity,
Error::<T>::DrawdownTypeNotSupportedForBulkUpload
);
ensure!(!documents.is_empty(), Error::<T>::BulkUploadDocumentsRequired);
ensure!(!description.is_empty(), Error::<T>::BulkUploadDescriptionRequired);
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let mod_drawdown_data = drawdown_data.as_mut().ok_or(Error::<T>::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(())
})?;
Self::do_update_drawdown_status_in_project_info(
project_id,
drawdown_id,
DrawdownStatus::Submitted,
)?;
Self::deposit_event(Event::BulkUploadSubmitted(project_id, drawdown_id));
Ok(())
}
pub fn do_execute_inflation_adjustment(
admin: T::AccountId,
projects: ProjectsInflation<T>,
) -> DispatchResult {
Self::is_authorized(admin.clone(), &Self::get_global_scope(), ProxyPermission::InflationRate)?;
ensure!(!projects.is_empty(), Error::<T>::ProjectsInflationRateEmpty);
for project in projects.iter().cloned() {
ensure!(ProjectsInfo::<T>::contains_key(project.0), Error::<T>::ProjectNotFound);
match project.2 {
CUDAction::Create => {
let inflation_rate = project.1.ok_or(Error::<T>::InflationRateRequired)?;
let project_data =
ProjectsInfo::<T>::get(project.0).ok_or(Error::<T>::ProjectNotFound)?;
ensure!(project_data.inflation_rate.is_none(), Error::<T>::InflationRateAlreadySet);
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project.0, |project_info| {
let mod_project_data = project_info.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
mod_project_data.inflation_rate = Some(inflation_rate);
Ok(())
})?;
},
CUDAction::Update => {
let inflation_rate = project.1.ok_or(Error::<T>::InflationRateRequired)?;
let project_data =
ProjectsInfo::<T>::get(project.0).ok_or(Error::<T>::ProjectNotFound)?;
ensure!(project_data.inflation_rate.is_some(), Error::<T>::InflationRateNotSet);
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project.0, |project_info| {
let mod_project_data = project_info.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
mod_project_data.inflation_rate = Some(inflation_rate);
Ok(())
})?;
},
CUDAction::Delete => {
let project_data =
ProjectsInfo::<T>::get(project.0).ok_or(Error::<T>::ProjectNotFound)?;
ensure!(project_data.inflation_rate.is_some(), Error::<T>::InflationRateNotSet);
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project.0, |project_info| {
let mod_project_data = project_info.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
mod_project_data.inflation_rate = None;
Ok(())
})?;
},
}
}
Self::deposit_event(Event::InflationRateAdjusted(admin));
Ok(())
}
pub fn do_execute_job_eligibles(
admin: T::AccountId,
project_id: ProjectId,
job_eligibles: JobEligibles<T>,
) -> DispatchResult {
Self::is_authorized(admin.clone(), &project_id, ProxyPermission::JobEligible)?;
ensure!(ProjectsInfo::<T>::contains_key(project_id), Error::<T>::ProjectNotFound);
ensure!(!job_eligibles.is_empty(), Error::<T>::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::<T>::JobEligibleNameRequired)?,
job_eligible.1.ok_or(Error::<T>::JobEligibleAmountRequired)?,
job_eligible.2,
job_eligible.3,
)?;
},
CUDAction::Update => {
Self::do_update_job_eligible(
project_id,
job_eligible.5.ok_or(Error::<T>::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::<T>::JobEligibleIdRequired)?)?;
},
}
}
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<NAICSCode>,
jobs_multiplier: Option<JobsMultiplier>,
) -> DispatchResult {
Self::is_project_completed(project_id)?;
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
ensure!(!name.is_empty(), Error::<T>::JobEligiblesNameRequired);
let job_eligible_id: JobEligibleId =
(project_id, name.clone(), timestamp).using_encoded(blake2_256);
let job_eligible_data =
JobEligibleData { project_id, name, job_eligible_amount, naics_code, jobs_multiplier };
ensure!(
!JobEligiblesInfo::<T>::contains_key(job_eligible_id),
Error::<T>::JobEligibleIdAlreadyExists
);
<JobEligiblesInfo<T>>::insert(job_eligible_id, job_eligible_data);
<JobEligiblesByProject<T>>::try_mutate::<_, _, DispatchError, _>(
project_id,
|job_eligibles| {
job_eligibles
.try_push(job_eligible_id)
.map_err(|_| Error::<T>::MaxJobEligiblesPerProjectReached)?;
Ok(())
},
)?;
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<FieldName>,
job_eligible_amount: Option<JobEligibleAmount>,
naics_code: Option<NAICSCode>,
jobs_multiplier: Option<JobsMultiplier>,
) -> DispatchResult {
Self::is_project_completed(project_id)?;
ensure!(JobEligiblesInfo::<T>::contains_key(job_eligible_id), Error::<T>::JobEligibleNotFound);
<JobEligiblesInfo<T>>::try_mutate::<_, _, DispatchError, _>(
job_eligible_id,
|job_eligible_data| {
let job_eligible = job_eligible_data.as_mut().ok_or(Error::<T>::JobEligibleNotFound)?;
ensure!(
job_eligible.project_id == project_id,
Error::<T>::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(())
},
)?;
Self::deposit_event(Event::JobEligibleUpdated(project_id, job_eligible_id));
Ok(())
}
fn do_delete_job_eligible(job_eligible_id: JobEligibleId) -> DispatchResult {
let job_eligible_data =
JobEligiblesInfo::<T>::get(job_eligible_id).ok_or(Error::<T>::JobEligibleNotFound)?;
ensure!(
JobEligiblesByProject::<T>::get(job_eligible_data.project_id).contains(&job_eligible_id),
Error::<T>::JobEligibleNotFoundForSelectedProjectId
);
Self::do_delete_job_eligible_transactions(job_eligible_id)?;
<JobEligiblesInfo<T>>::remove(job_eligible_id);
<JobEligiblesByProject<T>>::try_mutate_exists::<_, _, DispatchError, _>(
job_eligible_data.project_id,
|job_eligibles_option| {
let job_eligibles =
job_eligibles_option.as_mut().ok_or(Error::<T>::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(())
}
pub fn do_execute_revenue_transactions(
user: T::AccountId,
project_id: ProjectId,
revenue_id: RevenueId,
revenue_transactions: RevenueTransactions<T>,
) -> DispatchResult {
Self::is_authorized(user.clone(), &project_id, ProxyPermission::RevenueTransaction)?;
Self::is_project_completed(project_id)?;
ensure!(RevenuesInfo::<T>::contains_key(revenue_id), Error::<T>::RevenueNotFound);
ensure!(!revenue_transactions.is_empty(), Error::<T>::RevenueTransactionsEmpty);
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::<T>::JobEligibleIdRequired)?,
transaction.1.ok_or(Error::<T>::RevenueAmountRequired)?,
transaction.2,
)?;
},
CUDAction::Update => {
Self::is_revenue_transaction_editable(
user.clone(),
transaction.4.ok_or(Error::<T>::RevenueTransactionIdRequired)?,
)?;
Self::do_update_revenue_transaction(
transaction.1,
transaction.2,
transaction.4.ok_or(Error::<T>::RevenueTransactionIdRequired)?,
)?;
},
CUDAction::Delete => {
Self::is_revenue_transaction_editable(
user.clone(),
transaction.4.ok_or(Error::<T>::RevenueTransactionIdRequired)?,
)?;
Self::do_delete_revenue_transaction(
transaction.4.ok_or(Error::<T>::RevenueTransactionIdRequired)?,
)?;
},
}
}
Self::do_calculate_revenue_total_amount(project_id, revenue_id)?;
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<Documents<T>>,
) -> DispatchResult {
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
let revenue_transaction_id =
(revenue_id, job_eligible_id, project_id, timestamp).using_encoded(blake2_256);
ensure!(
!RevenueTransactionsInfo::<T>::contains_key(revenue_transaction_id),
Error::<T>::RevenueTransactionIdAlreadyExists
);
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,
};
ensure!(
!RevenueTransactionsInfo::<T>::contains_key(revenue_transaction_id),
Error::<T>::RevenueTransactionIdAlreadyExists
);
<RevenueTransactionsInfo<T>>::insert(revenue_transaction_id, revenue_transaction_data);
<TransactionsByRevenue<T>>::try_mutate::<_, _, _, DispatchError, _>(
project_id,
revenue_id,
|revenue_transactions| {
revenue_transactions
.try_push(revenue_transaction_id)
.map_err(|_| Error::<T>::MaxTransactionsPerRevenueReached)?;
Ok(())
},
)?;
Self::deposit_event(Event::RevenueTransactionCreated(
project_id,
revenue_id,
revenue_transaction_id,
));
Ok(())
}
fn do_update_revenue_transaction(
amount: Option<RevenueAmount>,
documents: Option<Documents<T>>,
revenue_transaction_id: RevenueTransactionId,
) -> DispatchResult {
let revenue_transaction_data = RevenueTransactionsInfo::<T>::get(revenue_transaction_id)
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
<RevenueTransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
revenue_transaction_id,
|revenue_transaction_data| {
let mod_revenue_transaction_data = revenue_transaction_data
.as_mut()
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
ensure!(
JobEligiblesInfo::<T>::contains_key(mod_revenue_transaction_data.job_eligible_id),
Error::<T>::JobEligibleNotFound
);
if let Some(mod_amount) = amount {
mod_revenue_transaction_data.amount = mod_amount;
}
if let Some(mod_documents) = documents {
mod_revenue_transaction_data.documents = Some(mod_documents);
}
mod_revenue_transaction_data.updated_date = timestamp;
Ok(())
},
)?;
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 {
let revenue_transaction_data = RevenueTransactionsInfo::<T>::get(revenue_transaction_id)
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
ensure!(
TransactionsByRevenue::<T>::get(
revenue_transaction_data.project_id,
revenue_transaction_data.revenue_id
)
.contains(&revenue_transaction_id),
Error::<T>::RevenueTransactionNotFoundForSelectedRevenueId
);
<TransactionsByRevenue<T>>::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::<T>::RevenueHasNoTransactions)?;
revenue_transactions
.retain(|revenue_transaction| revenue_transaction != &revenue_transaction_id);
if revenue_transactions.is_empty() {
revenue_transactions_option.clone_from(&None);
}
Ok(())
},
)?;
<RevenueTransactionsInfo<T>>::remove(revenue_transaction_id);
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 {
Self::is_authorized(user.clone(), &project_id, ProxyPermission::SubmitRevenue)?;
Self::is_project_completed(project_id)?;
Self::is_revenue_editable(user, revenue_id)?;
ensure!(
!TransactionsByRevenue::<T>::get(project_id, revenue_id).is_empty(),
Error::<T>::RevenueHasNoTransactions
);
let revenue_transactions = TransactionsByRevenue::<T>::try_get(project_id, revenue_id)
.map_err(|_| Error::<T>::RevenueNotFound)?;
for transaction_id in revenue_transactions.iter().cloned() {
ensure!(
RevenueTransactionsInfo::<T>::contains_key(transaction_id),
Error::<T>::RevenueTransactionNotFound
);
<RevenueTransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|revenue_transaction_data| {
let revenue_transaction_data = revenue_transaction_data
.as_mut()
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
revenue_transaction_data.status = RevenueTransactionStatus::Submitted;
revenue_transaction_data.feedback = None;
Ok(())
},
)?;
}
<RevenuesInfo<T>>::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| {
let revenue_data = revenue_data.as_mut().ok_or(Error::<T>::RevenueNotFound)?;
revenue_data.status = RevenueStatus::Submitted;
Ok(())
})?;
Self::do_update_revenue_status_in_project_info(
project_id,
revenue_id,
RevenueStatus::Submitted,
)?;
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 {
Self::is_authorized(admin, &project_id, ProxyPermission::ApproveRevenue)?;
let revenue_data = Self::revenues_info(revenue_id).ok_or(Error::<T>::RevenueNotFound)?;
ensure!(revenue_data.status == RevenueStatus::Submitted, Error::<T>::RevenueNotSubmitted);
ensure!(
TransactionsByRevenue::<T>::contains_key(project_id, revenue_id),
Error::<T>::RevenueHasNoTransactions
);
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
let revenue_transactions = TransactionsByRevenue::<T>::try_get(project_id, revenue_id)
.map_err(|_| Error::<T>::RevenueNotFound)?;
for transaction_id in revenue_transactions.iter().cloned() {
let revenue_transaction_data = RevenueTransactionsInfo::<T>::get(transaction_id)
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
ensure!(
revenue_transaction_data.status == RevenueTransactionStatus::Submitted,
Error::<T>::RevenueTransactionNotSubmitted
);
<RevenueTransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|revenue_transaction_data| {
let revenue_transaction_data = revenue_transaction_data
.as_mut()
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
revenue_transaction_data.status = RevenueTransactionStatus::Approved;
revenue_transaction_data.closed_date = timestamp;
Ok(())
},
)?;
}
<RevenuesInfo<T>>::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| {
let revenue_data = revenue_data.as_mut().ok_or(Error::<T>::RevenueNotFound)?;
revenue_data.status = RevenueStatus::Approved;
revenue_data.closed_date = timestamp;
Ok(())
})?;
Self::do_update_revenue_status_in_project_info(
project_id,
revenue_id,
RevenueStatus::Approved,
)?;
Self::do_create_revenue(project_id, revenue_data.revenue_number + 1)?;
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<T>,
) -> DispatchResult {
Self::is_authorized(admin, &project_id, ProxyPermission::RejectRevenue)?;
let revenue_data = Self::revenues_info(revenue_id).ok_or(Error::<T>::RevenueNotFound)?;
ensure!(revenue_data.status == RevenueStatus::Submitted, Error::<T>::RevenueNotSubmitted);
ensure!(
!TransactionsByRevenue::<T>::get(project_id, revenue_id).is_empty(),
Error::<T>::RevenueHasNoTransactions
);
let revenue_transactions = TransactionsByRevenue::<T>::try_get(project_id, revenue_id)
.map_err(|_| Error::<T>::RevenueNotFound)?;
for transaction_id in revenue_transactions.iter().cloned() {
let revenue_transaction_data = RevenueTransactionsInfo::<T>::get(transaction_id)
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
ensure!(
revenue_transaction_data.status == RevenueTransactionStatus::Submitted,
Error::<T>::RevenueTransactionNotSubmitted
);
<RevenueTransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|revenue_transaction_data| {
let revenue_transaction_data = revenue_transaction_data
.as_mut()
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
revenue_transaction_data.status = RevenueTransactionStatus::Rejected;
Ok(())
},
)?;
}
ensure!(
!revenue_transactions_feedback.is_empty(),
Error::<T>::RevenueTransactionsFeedbackEmpty
);
for (transaction_id, feedback) in revenue_transactions_feedback.iter().cloned() {
<RevenueTransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|revenue_transaction_data| {
let revenue_transaction_data = revenue_transaction_data
.as_mut()
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
revenue_transaction_data.feedback = Some(feedback);
Ok(())
},
)?;
}
<RevenuesInfo<T>>::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| {
let revenue_data = revenue_data.as_mut().ok_or(Error::<T>::RevenueNotFound)?;
revenue_data.status = RevenueStatus::Rejected;
Ok(())
})?;
Self::do_update_revenue_status_in_project_info(
project_id,
revenue_id,
RevenueStatus::Rejected,
)?;
Self::deposit_event(Event::RevenueRejected(project_id, revenue_id));
Ok(())
}
pub fn do_bank_confirming_documents(
admin: T::AccountId,
project_id: ProjectId,
drawdown_id: DrawdownId,
confirming_documents: Option<Documents<T>>,
action: CUDAction,
) -> DispatchResult {
Self::is_authorized(admin, &project_id, ProxyPermission::BankConfirming)?;
ensure!(ProjectsInfo::<T>::contains_key(project_id), Error::<T>::ProjectNotFound);
let drawdown_data = DrawdownsInfo::<T>::get(drawdown_id).ok_or(Error::<T>::DrawdownNotFound)?;
ensure!(
drawdown_data.drawdown_type == DrawdownType::EB5,
Error::<T>::OnlyEB5DrawdownsCanUploadBankDocuments
);
match action {
CUDAction::Create => {
let mod_confirming_documents =
confirming_documents.ok_or(Error::<T>::BankConfirmingDocumentsNotProvided)?;
ensure!(!mod_confirming_documents.is_empty(), Error::<T>::BankConfirmingDocumentsEmpty);
Self::do_create_bank_confirming_documents(project_id, drawdown_id, mod_confirming_documents)
},
CUDAction::Update => {
let mod_confirming_documents =
confirming_documents.ok_or(Error::<T>::BankConfirmingDocumentsNotProvided)?;
ensure!(!mod_confirming_documents.is_empty(), Error::<T>::BankConfirmingDocumentsEmpty);
Self::do_update_bank_confirming_documents(drawdown_id, mod_confirming_documents)
},
CUDAction::Delete => {
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<T>,
) -> DispatchResult {
let drawdown_data = DrawdownsInfo::<T>::get(drawdown_id).ok_or(Error::<T>::DrawdownNotFound)?;
ensure!(
drawdown_data.bank_documents.is_none(),
Error::<T>::DrawdownHasAlreadyBankConfirmingDocuments
);
ensure!(
drawdown_data.status == DrawdownStatus::Approved,
Error::<T>::DrawdowMustBeInApprovedStatus
);
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let drawdown_data = drawdown_data.as_mut().ok_or(Error::<T>::DrawdownNotFound)?;
drawdown_data.bank_documents = Some(confirming_documents);
drawdown_data.status = DrawdownStatus::Confirmed;
Ok(())
})?;
let drawdown_transactions = TransactionsByDrawdown::<T>::try_get(project_id, drawdown_id)
.map_err(|_| Error::<T>::DrawdownHasNoTransactions)?;
for transaction_id in drawdown_transactions.iter().cloned() {
ensure!(TransactionsInfo::<T>::contains_key(transaction_id), Error::<T>::TransactionNotFound);
<TransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|transaction_data| {
let transaction_data =
transaction_data.as_mut().ok_or(Error::<T>::TransactionNotFound)?;
transaction_data.status = TransactionStatus::Confirmed;
Ok(())
},
)?;
}
Self::do_create_drawdown_status_change_record(drawdown_id, DrawdownStatus::Confirmed)?;
Self::deposit_event(Event::BankDocumentsUploaded(project_id, drawdown_id));
Ok(())
}
fn do_update_bank_confirming_documents(
drawdown_id: DrawdownId,
confirming_documents: Documents<T>,
) -> DispatchResult {
let drawdown_data = DrawdownsInfo::<T>::get(drawdown_id).ok_or(Error::<T>::DrawdownNotFound)?;
ensure!(
drawdown_data.status == DrawdownStatus::Confirmed,
Error::<T>::DrawdowMustBeInConfirmedStatus
);
ensure!(
drawdown_data.bank_documents.is_some(),
Error::<T>::DrawdownHasNoBankConfirmingDocuments
);
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let drawdown_data = drawdown_data.as_mut().ok_or(Error::<T>::DrawdownNotFound)?;
drawdown_data.bank_documents = Some(confirming_documents);
Ok(())
})?;
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 {
let drawdown_data = DrawdownsInfo::<T>::get(drawdown_id).ok_or(Error::<T>::DrawdownNotFound)?;
ensure!(
drawdown_data.status == DrawdownStatus::Confirmed,
Error::<T>::DrawdowMustBeInConfirmedStatus
);
ensure!(
drawdown_data.bank_documents.is_some(),
Error::<T>::DrawdownHasNoBankConfirmingDocuments
);
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let drawdown_data = drawdown_data.as_mut().ok_or(Error::<T>::DrawdownNotFound)?;
drawdown_data.bank_documents = None;
drawdown_data.status = DrawdownStatus::Approved;
Ok(())
})?;
let drawdown_transactions = TransactionsByDrawdown::<T>::try_get(project_id, drawdown_id)
.map_err(|_| Error::<T>::DrawdownHasNoTransactions)?;
for transaction_id in drawdown_transactions.iter().cloned() {
ensure!(TransactionsInfo::<T>::contains_key(transaction_id), Error::<T>::TransactionNotFound);
<TransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|transaction_data| {
let transaction_data =
transaction_data.as_mut().ok_or(Error::<T>::TransactionNotFound)?;
transaction_data.status = TransactionStatus::Approved;
Ok(())
},
)?;
}
Self::do_create_drawdown_status_change_record(drawdown_id, DrawdownStatus::Approved)?;
Self::deposit_event(Event::BankDocumentsDeleted(project_id, drawdown_id));
Ok(())
}
fn get_timestamp_in_milliseconds() -> Option<u64> {
let timestamp: u64 = T::Timestamp::now().into();
Some(timestamp)
}
pub fn pallet_id() -> IdOrVec {
IdOrVec::Vec(Self::module_name().as_bytes().to_vec())
}
pub fn get_global_scope() -> [u8; 32] {
<GlobalScope<T>>::try_get()
.map_err(|_| Error::<T>::NoGlobalScopeValueWasFound)
.unwrap()
}
#[allow(dead_code)]
fn change_project_status(
admin: T::AccountId,
project_id: ProjectId,
status: ProjectStatus,
) -> DispatchResult {
Self::is_superuser(admin, &Self::get_global_scope(), ProxyRole::Administrator.id())?;
ensure!(ProjectsInfo::<T>::contains_key(project_id), Error::<T>::ProjectNotFound);
Self::is_project_completed(project_id)?;
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project| {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
project.status = status;
Ok(())
})?;
Ok(())
}
fn is_project_completion_date_later(project_id: ProjectId) -> DispatchResult {
let project_data = ProjectsInfo::<T>::get(project_id).ok_or(Error::<T>::ProjectNotFound)?;
ensure!(
project_data.completion_date > project_data.creation_date,
Error::<T>::CompletionDateMustBeLater
);
Ok(())
}
fn add_project_role(
project_id: ProjectId,
user: T::AccountId,
role: ProxyRole,
) -> DispatchResult {
match role {
ProxyRole::Administrator => return Err(Error::<T>::CannotRegisterAdminRole.into()),
ProxyRole::Builder => {
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project| {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
match project.builder.as_mut() {
Some(builder) => {
builder
.try_push(user.clone())
.map_err(|_| Error::<T>::MaxBuildersPerProjectReached)?;
},
None => {
let devs = project
.builder
.get_or_insert(BoundedVec::<T::AccountId, T::MaxBuildersPerProject>::default());
devs
.try_push(user.clone())
.map_err(|_| Error::<T>::MaxBuildersPerProjectReached)?;
},
}
Ok(())
})?;
},
ProxyRole::Investor => {
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project| {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
match project.investor.as_mut() {
Some(investor) => {
investor
.try_push(user.clone())
.map_err(|_| Error::<T>::MaxInvestorsPerProjectReached)?;
},
None => {
let investors = project
.investor
.get_or_insert(BoundedVec::<T::AccountId, T::MaxInvestorsPerProject>::default());
investors
.try_push(user.clone())
.map_err(|_| Error::<T>::MaxInvestorsPerProjectReached)?;
},
}
Ok(())
})?;
},
ProxyRole::Issuer => {
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project| {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
match project.issuer.as_mut() {
Some(issuer) => {
issuer
.try_push(user.clone())
.map_err(|_| Error::<T>::MaxIssuersPerProjectReached)?;
},
None => {
let issuers = project
.issuer
.get_or_insert(BoundedVec::<T::AccountId, T::MaxIssuersPerProject>::default());
issuers
.try_push(user.clone())
.map_err(|_| Error::<T>::MaxIssuersPerProjectReached)?;
},
}
Ok(())
})?;
},
ProxyRole::RegionalCenter => {
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project| {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
match project.regional_center.as_mut() {
Some(regional_center) => {
regional_center
.try_push(user.clone())
.map_err(|_| Error::<T>::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::<T>::MaxRegionalCenterPerProjectReached)?;
},
}
Ok(())
})?;
},
}
Ok(())
}
pub fn remove_project_role(
project_id: ProjectId,
user: T::AccountId,
role: ProxyRole,
) -> DispatchResult {
match role {
ProxyRole::Administrator => return Err(Error::<T>::CannotRemoveAdminRole.into()),
ProxyRole::Builder => {
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project| {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
match project.builder.as_mut() {
Some(builder) => {
builder.retain(|u| *u != user);
},
None => return Err(Error::<T>::UserNotAssignedToProject.into()),
}
Ok(())
})?;
},
ProxyRole::Investor => {
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project| {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
match project.investor.as_mut() {
Some(investor) => {
investor.retain(|u| *u != user);
},
None => return Err(Error::<T>::UserNotAssignedToProject.into()),
}
Ok(())
})?;
},
ProxyRole::Issuer => {
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project| {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
match project.issuer.as_mut() {
Some(issuer) => {
issuer.retain(|u| *u != user);
},
None => return Err(Error::<T>::UserNotAssignedToProject.into()),
}
Ok(())
})?;
},
ProxyRole::RegionalCenter => {
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project| {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
match project.regional_center.as_mut() {
Some(regional_center) => {
regional_center.retain(|u| *u != user);
},
None => return Err(Error::<T>::UserNotAssignedToProject.into()),
}
Ok(())
})?;
},
}
Ok(())
}
fn check_user_role(user: T::AccountId, role: ProxyRole) -> DispatchResult {
let user_data = UsersInfo::<T>::get(user.clone()).ok_or(Error::<T>::UserNotRegistered)?;
if user_data.role != role {
return Err(Error::<T>::UserCannotHaveMoreThanOneRole.into());
}
match user_data.role {
ProxyRole::Administrator => {
return Err(Error::<T>::CannotAddAdminRole.into());
},
ProxyRole::Investor => {
let projects_count = <ProjectsByUser<T>>::get(user.clone()).len();
ensure!(
projects_count < T::MaxProjectsPerInvestor::get() as usize,
Error::<T>::MaxProjectsPerInvestorReached
);
Ok(())
},
_ => Ok(()),
}
}
fn is_project_completed(project_id: ProjectId) -> DispatchResult {
let project_data = ProjectsInfo::<T>::get(project_id).ok_or(Error::<T>::ProjectNotFound)?;
ensure!(project_data.status != ProjectStatus::Completed, Error::<T>::ProjectIsAlreadyCompleted);
Ok(())
}
fn is_drawdown_editable(user: T::AccountId, drawdown_id: DrawdownId) -> DispatchResult {
let drawdown_data = DrawdownsInfo::<T>::get(drawdown_id).ok_or(Error::<T>::DrawdownNotFound)?;
match drawdown_data.drawdown_type {
DrawdownType::EB5 => {
match drawdown_data.status {
DrawdownStatus::Draft => Ok(()),
DrawdownStatus::Rejected => Ok(()),
DrawdownStatus::Submitted => {
Err(Error::<T>::CannotPerformActionOnSubmittedDrawdown.into())
},
DrawdownStatus::Approved => {
if Self::is_authorized(
user.clone(),
&drawdown_data.project_id,
ProxyPermission::RecoveryDrawdown,
)
.is_ok()
{
Ok(())
} else {
Err(Error::<T>::CannotPerformActionOnApprovedDrawdown.into())
}
},
DrawdownStatus::Confirmed => {
if Self::is_authorized(
user.clone(),
&drawdown_data.project_id,
ProxyPermission::RecoveryDrawdown,
)
.is_ok()
{
Ok(())
} else {
Err(Error::<T>::CannotPerformActionOnConfirmedDrawdown.into())
}
},
}
},
_ => {
match drawdown_data.status {
DrawdownStatus::Draft => Ok(()),
DrawdownStatus::Rejected => Ok(()),
DrawdownStatus::Submitted => {
if Self::is_authorized(
user.clone(),
&drawdown_data.project_id,
ProxyPermission::BulkUploadTransaction,
)
.is_ok()
{
Ok(())
} else {
Err(Error::<T>::CannotPerformActionOnSubmittedDrawdown.into())
}
},
DrawdownStatus::Approved => {
if Self::is_authorized(
user.clone(),
&drawdown_data.project_id,
ProxyPermission::RecoveryDrawdown,
)
.is_ok()
{
Ok(())
} else {
Err(Error::<T>::CannotPerformActionOnApprovedDrawdown.into())
}
},
DrawdownStatus::Confirmed => {
if Self::is_authorized(
user.clone(),
&drawdown_data.project_id,
ProxyPermission::RecoveryDrawdown,
)
.is_ok()
{
Ok(())
} else {
Err(Error::<T>::CannotPerformActionOnConfirmedDrawdown.into())
}
},
}
},
}
}
fn is_transaction_editable(user: T::AccountId, transaction_id: TransactionId) -> DispatchResult {
let transaction_data =
TransactionsInfo::<T>::get(transaction_id).ok_or(Error::<T>::TransactionNotFound)?;
match transaction_data.status {
TransactionStatus::Draft => Ok(()),
TransactionStatus::Rejected => Ok(()),
TransactionStatus::Submitted => {
if Self::is_authorized(
user.clone(),
&transaction_data.project_id,
ProxyPermission::BulkUploadTransaction,
)
.is_ok()
{
Ok(())
} else {
Err(Error::<T>::CannotPerformActionOnSubmittedTransaction.into())
}
},
TransactionStatus::Approved => {
if Self::is_authorized(
user.clone(),
&transaction_data.project_id,
ProxyPermission::RecoveryTransaction,
)
.is_ok()
{
Ok(())
} else {
Err(Error::<T>::CannotPerformActionOnApprovedTransaction.into())
}
},
TransactionStatus::Confirmed => {
if Self::is_authorized(
user.clone(),
&transaction_data.project_id,
ProxyPermission::RecoveryTransaction,
)
.is_ok()
{
Ok(())
} else {
Err(Error::<T>::CannotPerformActionOnConfirmedTransaction.into())
}
},
}
}
pub fn is_authorized(
authority: T::AccountId,
scope: &[u8; 32],
permission: ProxyPermission,
) -> DispatchResult {
let user_data =
<UsersInfo<T>>::try_get(authority.clone()).map_err(|_| Error::<T>::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 {
ensure!(!<UsersInfo<T>>::contains_key(admin.clone()), Error::<T>::UserAlreadyRegistered);
let current_timestamp =
Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
let user_data = UserData::<T> {
name,
role: ProxyRole::Administrator,
image: CID::default(),
date_registered: current_timestamp,
email: FieldName::default(),
documents: None,
};
<UsersInfo<T>>::insert(admin.clone(), user_data);
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 {
ensure!(<UsersInfo<T>>::contains_key(admin.clone()), Error::<T>::UserNotRegistered);
<UsersInfo<T>>::remove(admin.clone());
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!(<DrawdownsInfo<T>>::contains_key(drawdown_id), Error::<T>::DrawdownNotFound);
let mut drawdown_total_amount: u64 = 0;
if !TransactionsByDrawdown::<T>::get(project_id, drawdown_id).is_empty() {
let transactions_by_drawdown = TransactionsByDrawdown::<T>::get(project_id, drawdown_id);
for transaction_id in transactions_by_drawdown.iter().cloned() {
let transaction_data =
TransactionsInfo::<T>::get(transaction_id).ok_or(Error::<T>::TransactionNotFound)?;
drawdown_total_amount += transaction_data.amount;
}
}
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let drawdown_data = drawdown_data.as_mut().ok_or(Error::<T>::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!(<ProjectsInfo<T>>::contains_key(project_id), Error::<T>::ProjectNotFound);
let drawdown_data = DrawdownsInfo::<T>::get(drawdown_id).ok_or(Error::<T>::DrawdownNotFound)?;
match drawdown_data.drawdown_type {
DrawdownType::EB5 => {
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project_data| {
let project_data = project_data.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
project_data.eb5_drawdown_status = Some(drawdown_status);
Ok(())
})?;
},
DrawdownType::ConstructionLoan => {
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project_data| {
let project_data = project_data.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
project_data.construction_loan_drawdown_status = Some(drawdown_status);
Ok(())
})?;
},
DrawdownType::DeveloperEquity => {
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project_data| {
let project_data = project_data.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
project_data.developer_equity_drawdown_status = Some(drawdown_status);
Ok(())
})?;
},
}
Self::do_create_drawdown_status_change_record(drawdown_id, drawdown_status)?;
Ok(())
}
fn is_revenue_editable(user: T::AccountId, revenue_id: RevenueId) -> DispatchResult {
let revenue_data = RevenuesInfo::<T>::get(revenue_id).ok_or(Error::<T>::RevenueNotFound)?;
match revenue_data.status {
RevenueStatus::Draft => Ok(()),
RevenueStatus::Rejected => Ok(()),
RevenueStatus::Submitted => Err(Error::<T>::CannotPerformActionOnSubmittedRevenue.into()),
RevenueStatus::Approved => {
if Self::is_authorized(
user.clone(),
&revenue_data.project_id,
ProxyPermission::RecoveryRevenue,
)
.is_ok()
{
Ok(())
} else {
Err(Error::<T>::CannotPerformActionOnApprovedRevenue.into())
}
},
}
}
fn is_revenue_transaction_editable(
user: T::AccountId,
revenue_transaction_id: RevenueTransactionId,
) -> DispatchResult {
let revenue_transaction_data = RevenueTransactionsInfo::<T>::get(revenue_transaction_id)
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
match revenue_transaction_data.status {
RevenueTransactionStatus::Draft => Ok(()),
RevenueTransactionStatus::Rejected => Ok(()),
RevenueTransactionStatus::Submitted => {
Err(Error::<T>::CannotPerformActionOnSubmittedRevenueTransaction.into())
},
RevenueTransactionStatus::Approved => {
if Self::is_authorized(
user.clone(),
&revenue_transaction_data.project_id,
ProxyPermission::RecoveryRevenueTransaction,
)
.is_ok()
{
Ok(())
} else {
Err(Error::<T>::CannotPerformActionOnApprovedRevenueTransaction.into())
}
},
}
}
fn do_calculate_revenue_total_amount(
project_id: ProjectId,
revenue_id: RevenueId,
) -> DispatchResult {
ensure!(<RevenuesInfo<T>>::contains_key(revenue_id), Error::<T>::RevenueNotFound);
let mut revenue_total_amount: Amount = 0;
if !TransactionsByRevenue::<T>::get(project_id, revenue_id).is_empty() {
let transactions_by_revenue = TransactionsByRevenue::<T>::get(project_id, revenue_id);
for revenue_transaction_id in transactions_by_revenue {
let revenue_transaction_data = RevenueTransactionsInfo::<T>::get(revenue_transaction_id)
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
revenue_total_amount += revenue_transaction_data.amount;
}
}
<RevenuesInfo<T>>::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| {
let revenue_data = revenue_data.as_mut().ok_or(Error::<T>::RevenueNotFound)?;
revenue_data.total_amount = revenue_total_amount;
Ok(())
})?;
Ok(())
}
fn do_initialize_revenue(project_id: ProjectId) -> DispatchResult {
ensure!(<ProjectsInfo<T>>::contains_key(project_id), Error::<T>::ProjectNotFound);
Self::do_create_revenue(project_id, 1)?;
Ok(())
}
fn do_create_revenue(project_id: ProjectId, revenue_number: RevenueNumber) -> DispatchResult {
ensure!(<ProjectsInfo<T>>::contains_key(project_id), Error::<T>::ProjectNotFound);
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
let revenue_id = (project_id, revenue_number, timestamp).using_encoded(blake2_256);
let revenue_data = RevenueData::<T> {
project_id,
revenue_number,
total_amount: 0,
status: RevenueStatus::default(),
status_changes: RevenueStatusChanges::<T>::default(),
recovery_record: RecoveryRecord::<T>::default(),
created_date: timestamp,
closed_date: 0,
};
ensure!(!<RevenuesInfo<T>>::contains_key(revenue_id), Error::<T>::RevenueIdAlreadyExists);
<RevenuesInfo<T>>::insert(revenue_id, revenue_data);
<RevenuesByProject<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |revenues| {
revenues
.try_push(revenue_id)
.map_err(|_| Error::<T>::MaxRevenuesPerProjectReached)?;
Ok(())
})?;
Self::do_update_revenue_status_in_project_info(
project_id,
revenue_id,
RevenueStatus::default(),
)?;
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!(<ProjectsInfo<T>>::contains_key(project_id), Error::<T>::ProjectNotFound);
ensure!(<RevenuesInfo<T>>::contains_key(revenue_id), Error::<T>::RevenueNotFound);
<ProjectsInfo<T>>::try_mutate::<_, _, DispatchError, _>(project_id, |project_data| {
let project_data = project_data.as_mut().ok_or(Error::<T>::ProjectNotFound)?;
project_data.revenue_status = Some(revenue_status);
Ok(())
})?;
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 {
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let drawdown_data = drawdown_data.as_mut().ok_or(Error::<T>::DrawdownNotFound)?;
drawdown_data
.status_changes
.try_push((drawdown_status, timestamp))
.map_err(|_| Error::<T>::MaxStatusChangesPerDrawdownReached)?;
Ok(())
})?;
Ok(())
}
fn do_create_revenue_status_change_record(
revenue_id: RevenueId,
revenue_status: RevenueStatus,
) -> DispatchResult {
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
<RevenuesInfo<T>>::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| {
let revenue_data = revenue_data.as_mut().ok_or(Error::<T>::RevenueNotFound)?;
revenue_data
.status_changes
.try_push((revenue_status, timestamp))
.map_err(|_| Error::<T>::MaxStatusChangesPerRevenueReached)?;
Ok(())
})?;
Ok(())
}
fn send_funds(admin: T::AccountId, user: T::AccountId) -> DispatchResult {
ensure!(
T::Currency::free_balance(&admin) > T::Currency::minimum_balance(),
Error::<T>::AdminHasNoFreeBalance
);
ensure!(
T::Currency::free_balance(&admin) > T::MinAdminBalance::get(),
Error::<T>::InsufficientFundsToTransfer
);
if T::Currency::free_balance(&user) < T::Currency::minimum_balance() {
T::Currency::transfer(&admin, &user, T::TransferAmount::get(), KeepAlive)?;
Ok(())
} else {
return Ok(());
}
}
fn do_delete_expenditure_transactions(expenditure_id: ExpenditureId) -> DispatchResult {
let expenditure_data =
<ExpendituresInfo<T>>::get(expenditure_id).ok_or(Error::<T>::ExpenditureNotFound)?;
ensure!(
<ProjectsInfo<T>>::contains_key(expenditure_data.project_id),
Error::<T>::ProjectNotFound
);
let drawdowns = <DrawdownsByProject<T>>::try_get(expenditure_data.project_id)
.map_err(|_| Error::<T>::ProjectHasNoDrawdowns)?;
for drawdown_id in drawdowns.iter().cloned() {
ensure!(<DrawdownsInfo<T>>::contains_key(drawdown_id), Error::<T>::DrawdownNotFound);
if !<TransactionsByDrawdown<T>>::get(expenditure_data.project_id, drawdown_id).is_empty() {
for transaction_id in
<TransactionsByDrawdown<T>>::get(expenditure_data.project_id, drawdown_id)
.iter()
.cloned()
{
let transaction_data =
<TransactionsInfo<T>>::get(transaction_id).ok_or(Error::<T>::TransactionNotFound)?;
if transaction_data.expenditure_id == expenditure_id {
ensure!(transaction_data.amount == 0, Error::<T>::ExpenditureHasNonZeroTransactions);
<TransactionsInfo<T>>::remove(transaction_id);
<TransactionsByDrawdown<T>>::try_mutate_exists::<_, _, _, DispatchError, _>(
expenditure_data.project_id,
drawdown_id,
|transactions_option| {
let transactions =
transactions_option.as_mut().ok_or(Error::<T>::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 {
let job_eligible_data =
<JobEligiblesInfo<T>>::get(job_eligible_id).ok_or(Error::<T>::JobEligibleNotFound)?;
ensure!(
<ProjectsInfo<T>>::contains_key(job_eligible_data.project_id),
Error::<T>::ProjectNotFound
);
let revenues = <RevenuesByProject<T>>::try_get(job_eligible_data.project_id)
.map_err(|_| Error::<T>::ProjectHasNoRevenues)?;
for revenue_id in revenues.iter().cloned() {
ensure!(<RevenuesInfo<T>>::contains_key(revenue_id), Error::<T>::RevenueNotFound);
if !<TransactionsByRevenue<T>>::get(job_eligible_data.project_id, revenue_id).is_empty() {
for transaction_id in
<TransactionsByRevenue<T>>::get(job_eligible_data.project_id, revenue_id)
.iter()
.cloned()
{
let transaction_data = <RevenueTransactionsInfo<T>>::get(transaction_id)
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
if transaction_data.job_eligible_id == job_eligible_id {
ensure!(transaction_data.amount == 0, Error::<T>::JobEligibleHasNonZeroTransactions);
<RevenueTransactionsInfo<T>>::remove(transaction_id);
<TransactionsByRevenue<T>>::try_mutate_exists::<_, _, _, DispatchError, _>(
job_eligible_data.project_id,
revenue_id,
|transactions_option| {
let transactions =
transactions_option.as_mut().ok_or(Error::<T>::RevenueHasNoTransactions)?;
transactions.retain(|transaction| transaction != &transaction_id);
if transactions.is_empty() {
transactions_option.clone_from(&None);
}
Ok(())
},
)?;
}
}
}
}
Ok(())
}
pub fn do_recovery_drawdown(
user: T::AccountId,
project_id: ProjectId,
drawdown_id: DrawdownId,
transactions: Transactions<T>,
) -> DispatchResult {
Self::is_authorized(user.clone(), &project_id, ProxyPermission::RecoveryDrawdown)?;
Self::is_project_completed(project_id)?;
Self::is_drawdown_editable(user.clone(), drawdown_id)?;
ensure!(
<DrawdownsByProject<T>>::get(project_id).contains(&drawdown_id),
Error::<T>::DrawdownDoesNotBelongToProject
);
ensure!(
!<TransactionsByDrawdown<T>>::get(project_id, drawdown_id).is_empty(),
Error::<T>::DrawdownHasNoTransactions
);
Self::do_execute_transactions(user.clone(), project_id, drawdown_id, transactions)?;
if !<TransactionsByDrawdown<T>>::get(project_id, drawdown_id).is_empty() {
for transaction_id in
<TransactionsByDrawdown<T>>::get(project_id, drawdown_id).iter().cloned()
{
let transaction_data =
TransactionsInfo::<T>::get(transaction_id).ok_or(Error::<T>::TransactionNotFound)?;
if transaction_data.status != TransactionStatus::Approved
&& transaction_data.status != TransactionStatus::Confirmed
{
<TransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|transaction_data| {
let transaction_data =
transaction_data.as_mut().ok_or(Error::<T>::TransactionNotFound)?;
transaction_data.status =
Self::get_transaction_status_for_a_given_drawdown(drawdown_id)?;
Ok(())
},
)?;
}
}
}
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
<DrawdownsInfo<T>>::try_mutate::<_, _, DispatchError, _>(drawdown_id, |drawdown_data| {
let drawdown = drawdown_data.as_mut().ok_or(Error::<T>::DrawdownNotFound)?;
drawdown
.recovery_record
.try_push((user, timestamp))
.map_err(|_| Error::<T>::MaxRecoveryChangesReached)?;
Ok(())
})?;
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<T>,
) -> DispatchResult {
Self::is_authorized(user.clone(), &project_id, ProxyPermission::RecoveryRevenue)?;
Self::is_project_completed(project_id)?;
Self::is_revenue_editable(user.clone(), revenue_id)?;
ensure!(
<RevenuesByProject<T>>::get(project_id).contains(&revenue_id),
Error::<T>::RevenueDoesNotBelongToProject
);
ensure!(
!<TransactionsByRevenue<T>>::get(project_id, revenue_id).is_empty(),
Error::<T>::RevenueHasNoTransactions
);
Self::do_execute_revenue_transactions(user.clone(), project_id, revenue_id, transactions)?;
if !<TransactionsByRevenue<T>>::get(project_id, revenue_id).is_empty() {
for transaction_id in <TransactionsByRevenue<T>>::get(project_id, revenue_id).iter().cloned()
{
let transaction_data = RevenueTransactionsInfo::<T>::get(transaction_id)
.ok_or(Error::<T>::RevenueTransactionNotFound)?;
if transaction_data.status != RevenueTransactionStatus::Approved {
<RevenueTransactionsInfo<T>>::try_mutate::<_, _, DispatchError, _>(
transaction_id,
|transaction_data| {
let transaction_data =
transaction_data.as_mut().ok_or(Error::<T>::RevenueTransactionNotFound)?;
transaction_data.status =
Self::get_transaction_status_for_a_given_revenue(revenue_id)?;
Ok(())
},
)?;
}
}
}
let timestamp = Self::get_timestamp_in_milliseconds().ok_or(Error::<T>::TimestampError)?;
<RevenuesInfo<T>>::try_mutate::<_, _, DispatchError, _>(revenue_id, |revenue_data| {
let revenue = revenue_data.as_mut().ok_or(Error::<T>::RevenueNotFound)?;
revenue
.recovery_record
.try_push((user, timestamp))
.map_err(|_| Error::<T>::MaxRecoveryChangesReached)?;
Ok(())
})?;
Self::deposit_event(Event::RevenueErrorRecoveryExecuted(project_id, revenue_id));
Ok(())
}
fn get_transaction_status_for_a_given_drawdown(
drawdown_id: DrawdownId,
) -> Result<TransactionStatus, DispatchError> {
let drawdown_data = <DrawdownsInfo<T>>::get(drawdown_id).ok_or(Error::<T>::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<RevenueTransactionStatus, DispatchError> {
let revenue_data = <RevenuesInfo<T>>::get(revenue_id).ok_or(Error::<T>::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),
}
}
}