conversions web3

This commit is contained in:
Filipriec
2026-08-24 10:25:00 +02:00
parent 77e409dce6
commit fbe05feed2
9 changed files with 9 additions and 390 deletions

View File

@@ -24,10 +24,6 @@ pub(crate) fn router() -> Router<AppState> {
"/admin/exchange-rates/configuration",
post(workspace_logic::add_configuration),
)
.route(
"/admin/exchange-rates/preview",
post(workspace_logic::preview),
)
.route(
"/admin/exchange-rates/evidence",
post(workspace_logic::evidence),

View File

@@ -128,8 +128,6 @@ pub(crate) async fn load_workspace(
settings,
can_configure: crate::authz::can_manage(&authorization, crate::authz::STRUCT_TABLE),
configuration: inputs.configuration,
preview_form: inputs.preview_form,
preview: inputs.preview,
evidence_form: inputs.evidence_form,
evidence: inputs.evidence,
evidence_has_exchange: inputs.evidence_has_exchange,

View File

@@ -9,10 +9,7 @@ use axum_extra::extract::Form;
use crate::{
AppState,
definitions::exchange_rates::{
AddProfileCurrencySourceRequest, ExchangeRateDateRule, ExchangeRateSource,
ListConversionEvidenceRequest,
},
definitions::exchange_rates::{AddProfileCurrencySourceRequest, ListConversionEvidenceRequest},
services::{authenticated_request, reject_cross_site},
};
@@ -20,8 +17,7 @@ use super::{
state::LoadError,
workspace_loader::load_workspace,
workspace_state::{
ConfigurationForm, EvidenceForm, EvidenceView, PreviewForm, PreviewView, WorkspaceInputs,
WorkspaceQuery,
ConfigurationForm, EvidenceForm, EvidenceView, WorkspaceInputs, WorkspaceQuery,
},
workspace_ui,
};
@@ -90,57 +86,6 @@ pub(crate) async fn add_configuration(
}
}
pub(crate) async fn preview(
State(state): State<AppState>,
headers: HeaderMap,
Form(form): Form<PreviewForm>,
) -> Response {
if let Some(rejection) = reject_cross_site(&headers) {
return rejection;
}
let mut inputs = WorkspaceInputs::for_profile(form.profile.clone());
inputs.preview_form = form.clone();
let request = match form.request() {
Ok(request) => request,
Err(key) => {
inputs.error = Some(crate::tr!(crate::i18n::Locale::from_headers(&headers), key));
return render_loaded(
&headers,
load_workspace(state, &headers, inputs).await,
StatusCode::UNPROCESSABLE_ENTITY,
);
}
};
let Ok(request) = authenticated_request(&headers, request) else {
return Redirect::to("/login").into_response();
};
let mut exchange_rates = state.exchange_rates.clone();
match exchange_rates.preview_direct_conversion(request).await {
Ok(response) => {
let response = response.into_inner();
let locale = crate::i18n::Locale::from_headers(&headers);
inputs.preview = Some(PreviewView {
applied_method: crate::tr!(locale, rate_method_key(response.applied_source)),
applied_date_rule: crate::tr!(locale, date_rule_key(response.applied_date_rule)),
response,
});
render_loaded(
&headers,
load_workspace(state, &headers, inputs).await,
StatusCode::OK,
)
}
Err(error) => {
inputs.error = Some(error.message().to_string());
render_loaded(
&headers,
load_workspace(state, &headers, inputs).await,
StatusCode::UNPROCESSABLE_ENTITY,
)
}
}
}
pub(crate) async fn evidence(
State(state): State<AppState>,
headers: HeaderMap,
@@ -204,24 +149,6 @@ pub(crate) async fn evidence(
}
}
fn rate_method_key(value: i32) -> &'static str {
match ExchangeRateSource::try_from(value).ok() {
Some(ExchangeRateSource::Official) => "rates-source-official",
Some(ExchangeRateSource::SavedCustom) => "rates-source-saved",
Some(ExchangeRateSource::Manual) => "rates-source-manual",
_ => "rates-unknown",
}
}
fn date_rule_key(value: i32) -> &'static str {
match ExchangeRateDateRule::try_from(value).ok() {
Some(ExchangeRateDateRule::PreviousPublication) => "rates-rule-previous",
Some(ExchangeRateDateRule::OnOrBeforeDate) => "rates-rule-on-or-before",
Some(ExchangeRateDateRule::SpecificPublicationDate) => "rates-rule-specific",
_ => "rates-unknown",
}
}
fn render_loaded(
headers: &HeaderMap,
result: Result<super::workspace_state::ExchangeRateWorkspaceState, LoadError>,

View File

@@ -1,9 +1,6 @@
//! State and form wire formats for the provider-neutral exchange-rate workspace.
use crate::definitions::exchange_rates::{
ConversionEvidence, ExchangeRateDateRule, ExchangeRateSelection, ExchangeRateSource,
PreviewDirectConversionRequest, PreviewDirectConversionResponse,
};
use crate::definitions::exchange_rates::ConversionEvidence;
use super::state::EcbPageState;
@@ -25,92 +22,6 @@ pub(crate) struct ConfigurationForm {
pub make_default: bool,
}
#[derive(Clone, Debug, serde::Deserialize)]
pub(crate) struct PreviewForm {
#[serde(default)]
pub profile: String,
#[serde(default)]
pub original_amount: String,
#[serde(default)]
pub original_currency: String,
#[serde(default)]
pub conversion_basis_date: String,
#[serde(default = "automatic_official")]
pub method: String,
#[serde(default = "ordinary_context")]
pub context: String,
#[serde(default)]
pub specific_rate_date: String,
#[serde(default)]
pub manual_foreign_units: String,
#[serde(default)]
pub rate_source_id: String,
#[serde(default)]
pub reason: String,
}
fn automatic_official() -> String {
"automatic-official".to_string()
}
fn ordinary_context() -> String {
"ordinary".to_string()
}
impl Default for PreviewForm {
fn default() -> Self {
Self {
profile: String::new(),
original_amount: String::new(),
original_currency: String::new(),
conversion_basis_date: String::new(),
method: automatic_official(),
context: ordinary_context(),
specific_rate_date: String::new(),
manual_foreign_units: String::new(),
rate_source_id: String::new(),
reason: String::new(),
}
}
}
impl PreviewForm {
pub(crate) fn request(&self) -> Result<PreviewDirectConversionRequest, &'static str> {
let date_rule = match self.method.as_str() {
"official-specific" | "manual" => ExchangeRateDateRule::SpecificPublicationDate,
_ if self.context == "statement" => ExchangeRateDateRule::OnOrBeforeDate,
_ => ExchangeRateDateRule::PreviousPublication,
};
let source = match self.method.as_str() {
"automatic-official" | "official-specific" => ExchangeRateSource::Official,
"saved-custom" => ExchangeRateSource::SavedCustom,
"manual" => ExchangeRateSource::Manual,
_ => return Err("rates-err-preview-method"),
};
let use_default_selection = self.method == "automatic-official"
&& self.context != "statement"
&& self.rate_source_id.trim().is_empty();
let selection = (!use_default_selection).then(|| ExchangeRateSelection {
date_rule: date_rule as i32,
source: source as i32,
specific_rate_date: (!self.specific_rate_date.trim().is_empty())
.then(|| self.specific_rate_date.trim().to_string()),
manual_foreign_units: (!self.manual_foreign_units.trim().is_empty())
.then(|| self.manual_foreign_units.trim().to_string()),
reason: self.reason.trim().to_string(),
rate_source_id: (!self.rate_source_id.trim().is_empty())
.then(|| self.rate_source_id.trim().to_string()),
});
Ok(PreviewDirectConversionRequest {
original_amount: self.original_amount.trim().to_string(),
original_currency: self.original_currency.trim().to_ascii_uppercase(),
conversion_basis_date: self.conversion_basis_date.trim().to_string(),
profile_name: self.profile.clone(),
exchange_rate_selection: selection,
})
}
}
#[derive(Clone, Debug, Default, serde::Deserialize)]
pub(crate) struct EvidenceForm {
#[serde(default)]
@@ -154,44 +65,6 @@ pub(crate) struct ProfileSettingsView {
pub foreign_currencies: Vec<CurrencySettingsView>,
}
#[derive(Clone, Debug)]
pub(crate) struct PreviewView {
pub response: PreviewDirectConversionResponse,
pub applied_method: String,
pub applied_date_rule: String,
}
impl PreviewView {
pub(crate) fn quote(&self) -> String {
match (
self.response.accounting_units.as_deref(),
self.response.foreign_units.as_deref(),
self.response.foreign_currency.as_deref(),
) {
(Some(accounting_units), Some(foreign_units), Some(foreign_currency)) => format!(
"{} {} = {} {}",
accounting_units,
self.response.accounting_currency,
foreign_units,
foreign_currency,
),
_ => "".to_string(),
}
}
pub(crate) fn observation_id(&self) -> String {
self.response
.official_observation_id
.map_or_else(|| "".to_string(), |id| id.to_string())
}
pub(crate) fn import_batch_id(&self) -> String {
self.response
.import_batch_id
.map_or_else(|| "".to_string(), |id| id.to_string())
}
}
#[derive(Clone, Debug)]
pub(crate) struct EvidenceView {
pub evidence: ConversionEvidence,
@@ -200,8 +73,6 @@ pub(crate) struct EvidenceView {
pub(crate) struct WorkspaceInputs {
pub selected_profile: String,
pub configuration: ConfigurationForm,
pub preview_form: PreviewForm,
pub preview: Option<PreviewView>,
pub evidence_form: EvidenceForm,
pub evidence: Vec<EvidenceView>,
pub evidence_has_exchange: Option<bool>,
@@ -215,8 +86,6 @@ impl WorkspaceInputs {
Self {
selected_profile: profile,
configuration: ConfigurationForm::default(),
preview_form: PreviewForm::default(),
preview: None,
evidence_form: EvidenceForm::default(),
evidence: Vec::new(),
evidence_has_exchange: None,
@@ -235,8 +104,6 @@ pub(crate) struct ExchangeRateWorkspaceState {
pub settings: Option<ProfileSettingsView>,
pub can_configure: bool,
pub configuration: ConfigurationForm,
pub preview_form: PreviewForm,
pub preview: Option<PreviewView>,
pub evidence_form: EvidenceForm,
pub evidence: Vec<EvidenceView>,
pub evidence_has_exchange: Option<bool>,

View File

@@ -45,7 +45,7 @@ mod tests {
state::{EcbPageState, ImportBatchView},
workspace_state::{
ConfigurationForm, CurrencySettingsView, CurrencySourceView, EvidenceForm,
ExchangeRateWorkspaceState, PreviewForm, ProfileSettingsView,
ExchangeRateWorkspaceState, ProfileSettingsView,
},
};
@@ -98,8 +98,6 @@ mod tests {
}),
can_configure: true,
configuration: ConfigurationForm::default(),
preview_form: PreviewForm::default(),
preview: None,
evidence_form: EvidenceForm::default(),
evidence: Vec::new(),
evidence_has_exchange: None,
@@ -116,7 +114,6 @@ mod tests {
assert!(!html.contains("Template error"), "{html}");
assert!(html.contains("Profile configuration"), "{html}");
assert!(html.contains("Conversion preview"), "{html}");
assert!(html.contains("Evidence lookup"), "{html}");
assert!(html.contains("Provider health"), "{html}");
assert!(html.contains("CZK"), "{html}");