use crate::context::{ResourceContext, ServiceContext};
use crate::event::EventBus;
use crate::plugin::time::DayChanged;
use crate::system::System;
use async_trait::async_trait;
use std::any::Any;
use std::sync::Arc;
use super::config::AccountingConfig;
use super::events::*;
use super::hook::AccountingHook;
use super::resources::BudgetLedger;
use super::service::AccountingService;
use super::state::AccountingState;
pub struct AccountingSystem {
hook: Arc<dyn AccountingHook>,
}
impl AccountingSystem {
pub fn new(hook: Arc<dyn AccountingHook>) -> Self {
Self { hook }
}
pub async fn process_events(
&mut self,
services: &ServiceContext,
resources: &mut ResourceContext,
) {
self.process_day_changed_events(services, resources).await;
self.process_settlement_requests(services, resources).await;
self.process_transfer_requests(resources).await;
}
async fn process_day_changed_events(
&mut self,
services: &ServiceContext,
resources: &mut ResourceContext,
) {
let day_events = {
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
let reader = bus.reader::<DayChanged>();
reader.iter().cloned().collect::<Vec<_>>()
} else {
Vec::new()
}
};
for event in day_events {
self.check_and_run_settlement(event.day, services, resources)
.await;
}
}
async fn check_and_run_settlement(
&mut self,
current_day: u32,
services: &ServiceContext,
resources: &mut ResourceContext,
) {
let config = match resources.get::<AccountingConfig>().await {
Some(c) => c,
None => return,
};
let period = config.settlement_period_days;
drop(config);
let should_run = {
if let Some(state) = resources.get::<AccountingState>().await {
state.should_run_settlement(current_day, period)
} else {
false
}
};
if !should_run {
return;
}
self.run_settlement(current_day, services, resources).await;
}
async fn process_settlement_requests(
&mut self,
services: &ServiceContext,
resources: &mut ResourceContext,
) {
let requests = {
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
let reader = bus.reader::<SettlementRequested>();
reader.iter().cloned().collect::<Vec<_>>()
} else {
Vec::new()
}
};
for request in requests {
self.run_settlement(request.period, services, resources)
.await;
}
}
async fn run_settlement(
&mut self,
period: u32,
services: &ServiceContext,
resources: &mut ResourceContext,
) {
self.hook.before_settlement(period, resources).await;
let income = {
let resources_ref = resources as &ResourceContext;
self.hook.calculate_income(period, resources_ref).await
};
let expenses = {
let resources_ref = resources as &ResourceContext;
self.hook.calculate_expenses(period, resources_ref).await
};
let net = {
if let Some(service) = services.get_as::<AccountingService>("accounting_service") {
service.calculate_settlement_net(income, expenses)
} else {
income - expenses
}
};
{
if let Some(mut ledger) = resources.get_mut::<BudgetLedger>().await {
ledger.cash = ledger.cash.saturating_add(net);
}
}
{
if let Some(mut state) = resources.get_mut::<AccountingState>().await {
state.record_settlement(period);
}
}
self.hook
.after_settlement(period, income, expenses, net, resources)
.await;
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
bus.publish(SettlementCompletedEvent {
period,
income,
expenses,
net,
});
}
}
async fn process_transfer_requests(&mut self, resources: &mut ResourceContext) {
let requests = {
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
let reader = bus.reader::<BudgetTransferRequested>();
reader.iter().cloned().collect::<Vec<_>>()
} else {
Vec::new()
}
};
for request in requests {
let success = {
if let Some(mut ledger) = resources.get_mut::<BudgetLedger>().await {
ledger.transfer(request.from, request.to, request.amount)
} else {
false
}
};
if !success {
continue;
}
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
bus.publish(BudgetTransferredEvent {
from: request.from,
to: request.to,
amount: request.amount,
});
}
}
}
}
#[async_trait]
impl System for AccountingSystem {
fn name(&self) -> &'static str {
"accounting_system"
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}