conversions5
This commit is contained in:
@@ -7,21 +7,21 @@ use axum::{
|
||||
routing::get,
|
||||
};
|
||||
|
||||
mod authz;
|
||||
mod i18n;
|
||||
mod pages;
|
||||
mod schema;
|
||||
mod services;
|
||||
mod ui;
|
||||
mod authz;
|
||||
mod i18n;
|
||||
// The server's system column vocabulary, read out of `common` the same way the
|
||||
// generated protos are: this crate compiles that source tree directly instead
|
||||
// of depending on the crate. Only `is_system_column` is used here.
|
||||
#[path = "../../common/src/system_column.rs"]
|
||||
#[allow(dead_code)]
|
||||
mod system_column;
|
||||
#[path = "../../common/src/grpc_error.rs"]
|
||||
#[allow(dead_code)]
|
||||
mod grpc_error;
|
||||
#[path = "../../common/src/system_column.rs"]
|
||||
#[allow(dead_code)]
|
||||
mod system_column;
|
||||
mod analytics {
|
||||
include!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
@@ -78,12 +78,17 @@ mod definitions {
|
||||
"/../common/src/proto/komp_ac.ecb.rs"
|
||||
));
|
||||
}
|
||||
pub mod exchange_rates {
|
||||
include!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../common/src/proto/komp_ac.exchange_rates.rs"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
use auth::auth_service_client::AuthServiceClient;
|
||||
use definitions::{
|
||||
ecb,
|
||||
table_definition::table_definition_client::TableDefinitionClient,
|
||||
ecb, table_definition::table_definition_client::TableDefinitionClient,
|
||||
table_script::table_script_client::TableScriptClient,
|
||||
table_structure::table_structure_service_client::TableStructureServiceClient,
|
||||
table_validation::table_validation_service_client::TableValidationServiceClient,
|
||||
|
||||
@@ -13,10 +13,8 @@
|
||||
|
||||
use crate::{
|
||||
definitions::table_definition::{GeneratedColumnAlias, PostTableDefinitionRequest},
|
||||
schema::{ColumnCatalog, ColumnDraft, proto_columns, validate_identifier, validate_table_name},
|
||||
{i18n::Locale, tr},
|
||||
schema::{
|
||||
ColumnCatalog, ColumnDraft, proto_columns, validate_identifier, validate_table_name,
|
||||
},
|
||||
};
|
||||
|
||||
/// The compound type that also brings a system column with it. Which columns
|
||||
@@ -68,6 +66,8 @@ pub(crate) struct TableDraft {
|
||||
pub creating_new_profile: bool,
|
||||
pub global: bool,
|
||||
pub accounting_currency: String,
|
||||
pub rate_source_id: String,
|
||||
pub foreign_currencies: String,
|
||||
|
||||
pub table_name: String,
|
||||
|
||||
@@ -98,6 +98,7 @@ impl TableDraft {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
accounting_currency: "EUR".to_string(),
|
||||
rate_source_id: "ecb".to_string(),
|
||||
columns: ColumnDraft::new(ColumnCatalog::default()),
|
||||
..Self::default()
|
||||
}
|
||||
@@ -116,11 +117,7 @@ impl TableDraft {
|
||||
// ---- mutations -------------------------------------------------------
|
||||
|
||||
/// Removes one column, and drops it from the display columns with it.
|
||||
pub(crate) fn remove_column(
|
||||
&mut self,
|
||||
locale: Locale,
|
||||
index: usize,
|
||||
) -> Result<String, String> {
|
||||
pub(crate) fn remove_column(&mut self, locale: Locale, index: usize) -> Result<String, String> {
|
||||
let removed = self.columns.remove(locale, index)?;
|
||||
self.row_display_columns
|
||||
.retain(|display| display != &removed.name);
|
||||
@@ -364,9 +361,7 @@ impl TableDraft {
|
||||
);
|
||||
|
||||
for (source, alias) in &renames {
|
||||
if let Some(error) =
|
||||
validate_identifier(locale, alias, "label-column-alias", true)
|
||||
{
|
||||
if let Some(error) = validate_identifier(locale, alias, "label-column-alias", true) {
|
||||
return Err(error);
|
||||
}
|
||||
if taken.iter().filter(|name| *name == alias).count() > 1 {
|
||||
@@ -574,6 +569,21 @@ impl TableDraft {
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
rate_source_id: if self.creating_new_profile && !self.global {
|
||||
self.rate_source_id.trim().to_ascii_lowercase()
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
foreign_currencies: if self.creating_new_profile && !self.global {
|
||||
self.foreign_currencies
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(str::to_ascii_uppercase)
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
},
|
||||
row_display_columns: self.row_display_columns.clone(),
|
||||
global: self.global,
|
||||
// The generated columns are named in the same request that creates
|
||||
@@ -634,7 +644,13 @@ mod tests {
|
||||
|
||||
draft.accounting_currency = "eur".to_string();
|
||||
assert!(draft.validate(crate::i18n::Locale::default()).is_ok());
|
||||
assert_eq!(draft.into_request(crate::i18n::Locale::default()).unwrap().accounting_currency, "EUR");
|
||||
assert_eq!(
|
||||
draft
|
||||
.into_request(crate::i18n::Locale::default())
|
||||
.unwrap()
|
||||
.accounting_currency,
|
||||
"EUR"
|
||||
);
|
||||
}
|
||||
|
||||
/// Every column an ACCOUNTING row generates may be aliased, including the
|
||||
@@ -740,16 +756,28 @@ mod tests {
|
||||
source: "debit".to_string(),
|
||||
alias: "Md".to_string(),
|
||||
}];
|
||||
assert!(draft.validate(crate::i18n::Locale::default()).is_err(), "an alias is a column name");
|
||||
assert!(
|
||||
draft.validate(crate::i18n::Locale::default()).is_err(),
|
||||
"an alias is a column name"
|
||||
);
|
||||
|
||||
draft.generated_aliases[0].alias = "note".to_string();
|
||||
assert!(draft.validate(crate::i18n::Locale::default()).is_err(), "a declared column holds the name");
|
||||
assert!(
|
||||
draft.validate(crate::i18n::Locale::default()).is_err(),
|
||||
"a declared column holds the name"
|
||||
);
|
||||
|
||||
draft.generated_aliases[0].alias = "credit".to_string();
|
||||
assert!(draft.validate(crate::i18n::Locale::default()).is_err(), "another generated column does");
|
||||
assert!(
|
||||
draft.validate(crate::i18n::Locale::default()).is_err(),
|
||||
"another generated column does"
|
||||
);
|
||||
|
||||
draft.generated_aliases[0].alias = "id".to_string();
|
||||
assert!(draft.validate(crate::i18n::Locale::default()).is_err(), "a system column does");
|
||||
assert!(
|
||||
draft.validate(crate::i18n::Locale::default()).is_err(),
|
||||
"a system column does"
|
||||
);
|
||||
|
||||
draft.generated_aliases[0].alias = "md".to_string();
|
||||
assert!(draft.validate(crate::i18n::Locale::default()).is_ok());
|
||||
@@ -791,7 +819,9 @@ mod tests {
|
||||
#[test]
|
||||
fn a_table_with_no_columns_is_refused() {
|
||||
let mut draft = draft_with_column("total", "int");
|
||||
draft.remove_column(crate::i18n::Locale::default(), 0).unwrap();
|
||||
draft
|
||||
.remove_column(crate::i18n::Locale::default(), 0)
|
||||
.unwrap();
|
||||
|
||||
assert!(draft.validate(crate::i18n::Locale::default()).is_err());
|
||||
}
|
||||
@@ -850,9 +880,20 @@ mod tests {
|
||||
// never the definition row itself.
|
||||
assert_eq!(
|
||||
draft.row_display_column_names(),
|
||||
["number", "name", "tax_point_date", "debit", "credit", "account"]
|
||||
[
|
||||
"number",
|
||||
"name",
|
||||
"tax_point_date",
|
||||
"debit",
|
||||
"credit",
|
||||
"account"
|
||||
]
|
||||
);
|
||||
assert!(
|
||||
!draft
|
||||
.row_display_column_names()
|
||||
.contains(&"accounting".to_string())
|
||||
);
|
||||
assert!(!draft.row_display_column_names().contains(&"accounting".to_string()));
|
||||
|
||||
// `name` is the second candidate, and choosing it names the generated
|
||||
// column rather than the row it came from.
|
||||
@@ -879,7 +920,9 @@ mod tests {
|
||||
draft.toggle_row_display_candidate(1);
|
||||
assert_eq!(draft.row_display_columns, vec!["number"]);
|
||||
|
||||
draft.remove_column(crate::i18n::Locale::default(), 0).unwrap();
|
||||
draft
|
||||
.remove_column(crate::i18n::Locale::default(), 0)
|
||||
.unwrap();
|
||||
assert!(draft.row_display_columns.is_empty());
|
||||
}
|
||||
|
||||
@@ -919,7 +962,9 @@ mod tests {
|
||||
draft.toggle_row_display_candidate(1); // number
|
||||
draft.toggle_row_display_candidate(2); // issued_on
|
||||
|
||||
draft.move_column(crate::i18n::Locale::default(), 0, 1).unwrap();
|
||||
draft
|
||||
.move_column(crate::i18n::Locale::default(), 0, 1)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
draft
|
||||
@@ -993,7 +1038,13 @@ mod tests {
|
||||
let mut draft = draft_with_column("number", "text");
|
||||
draft.columns.toggle_indexed(0);
|
||||
|
||||
assert_eq!(draft.into_request(crate::i18n::Locale::default()).unwrap().indexes, vec!["number"]);
|
||||
assert_eq!(
|
||||
draft
|
||||
.into_request(crate::i18n::Locale::default())
|
||||
.unwrap()
|
||||
.indexes,
|
||||
vec!["number"]
|
||||
);
|
||||
}
|
||||
|
||||
/// The request the builder sends is one the server will take: a link
|
||||
@@ -1090,6 +1141,10 @@ mod tests {
|
||||
source: "name".to_string(),
|
||||
alias: "row_revision".to_string(),
|
||||
}];
|
||||
assert!(draft.validate_generated_aliases(crate::i18n::Locale::default()).is_err());
|
||||
assert!(
|
||||
draft
|
||||
.validate_generated_aliases(crate::i18n::Locale::default())
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ pub(crate) struct BuilderForm {
|
||||
#[serde(default)]
|
||||
pub accounting_currency: String,
|
||||
#[serde(default)]
|
||||
pub rate_source_id: String,
|
||||
#[serde(default)]
|
||||
pub foreign_currencies: String,
|
||||
#[serde(default)]
|
||||
pub global: bool,
|
||||
#[serde(default)]
|
||||
pub table_name: String,
|
||||
@@ -163,6 +167,8 @@ impl BuilderForm {
|
||||
profile_name_input: self.profile_name_input.clone(),
|
||||
creating_new_profile,
|
||||
accounting_currency: self.accounting_currency.clone(),
|
||||
rate_source_id: self.rate_source_id.clone(),
|
||||
foreign_currencies: self.foreign_currencies.clone(),
|
||||
global: self.global,
|
||||
table_name: self.table_name.clone(),
|
||||
columns,
|
||||
@@ -233,7 +239,11 @@ impl AddTablePageState {
|
||||
tags.push("quantity ledger".to_string());
|
||||
}
|
||||
if !column.currency.is_empty() {
|
||||
tags.push(format!("{}, {}", column.currency, column.money_mode.label()));
|
||||
tags.push(format!(
|
||||
"{}, {}",
|
||||
column.currency,
|
||||
column.money_mode.label()
|
||||
));
|
||||
}
|
||||
rows.push(ColumnRow {
|
||||
index: Some(index),
|
||||
@@ -266,7 +276,11 @@ impl AddTablePageState {
|
||||
for generated in generated_columns {
|
||||
let mut tags = vec![format!("generated by {}", column.data_type)];
|
||||
if generated.inherits_currency {
|
||||
tags.push(format!("{}, {}", column.currency, column.money_mode.label()));
|
||||
tags.push(format!(
|
||||
"{}, {}",
|
||||
column.currency,
|
||||
column.money_mode.label()
|
||||
));
|
||||
}
|
||||
rows.push(ColumnRow {
|
||||
index: None,
|
||||
@@ -502,15 +516,18 @@ mod tests {
|
||||
error: None,
|
||||
};
|
||||
page.draft.columns.catalog = crate::schema::tests::catalog();
|
||||
page.draft.columns.added.push(crate::schema::ColumnDefinition {
|
||||
name: "accounting".to_string(),
|
||||
data_type: "accounting".to_string(),
|
||||
indexed: false,
|
||||
quantity_ledger: false,
|
||||
required: false,
|
||||
money_mode: MoneyMode::Exact,
|
||||
currency: "EUR".to_string(),
|
||||
});
|
||||
page.draft
|
||||
.columns
|
||||
.added
|
||||
.push(crate::schema::ColumnDefinition {
|
||||
name: "accounting".to_string(),
|
||||
data_type: "accounting".to_string(),
|
||||
indexed: false,
|
||||
quantity_ledger: false,
|
||||
required: false,
|
||||
money_mode: MoneyMode::Exact,
|
||||
currency: "EUR".to_string(),
|
||||
});
|
||||
|
||||
let rows = page.column_rows();
|
||||
assert_eq!(
|
||||
@@ -537,9 +554,16 @@ mod tests {
|
||||
);
|
||||
assert!(row("number").first);
|
||||
assert!(row("accounting").last);
|
||||
assert!(!row("accounting").indexable, "a definition row is no column");
|
||||
assert!(
|
||||
!row("accounting").indexable,
|
||||
"a definition row is no column"
|
||||
);
|
||||
assert!(row("debit").tags.contains(&"EUR, exact".to_string()));
|
||||
assert!(row("name").tags.contains(&"generated by accounting".to_string()));
|
||||
assert!(
|
||||
row("name")
|
||||
.tags
|
||||
.contains(&"generated by accounting".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
/// The generated columns of an ACCOUNTING row are the user's to alias, and
|
||||
@@ -586,7 +610,11 @@ mod tests {
|
||||
assert_eq!(row("account").alias_source.as_deref(), Some("account"));
|
||||
assert_eq!(row("account").alias, "ucet");
|
||||
assert!(row("account_id").alias_source.is_none());
|
||||
assert!(row("account_id").tags.contains(&"written as ucet".to_string()));
|
||||
assert!(
|
||||
row("account_id")
|
||||
.tags
|
||||
.contains(&"written as ucet".to_string())
|
||||
);
|
||||
assert!(
|
||||
row("accounting").alias_source.is_none(),
|
||||
"a declared column is named where it is described"
|
||||
|
||||
@@ -88,8 +88,19 @@
|
||||
#}
|
||||
<small>{{ nav.tr("builder-accounting-currency-hint") }}</small>
|
||||
</label>
|
||||
<label>Rate source
|
||||
<select name="rate_source_id">
|
||||
<option value="ecb" {% if page.draft.rate_source_id == "ecb" %}selected{% endif %}>ECB</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Initial foreign currencies
|
||||
<input name="foreign_currencies" value="{{ page.draft.foreign_currencies }}"
|
||||
placeholder="CZK, USD">
|
||||
</label>
|
||||
{% else %}
|
||||
<input type="hidden" name="accounting_currency" value="{{ page.draft.accounting_currency }}">
|
||||
<input type="hidden" name="rate_source_id" value="{{ page.draft.rate_source_id }}">
|
||||
<input type="hidden" name="foreign_currencies" value="{{ page.draft.foreign_currencies }}">
|
||||
{% endif %}
|
||||
|
||||
{#
|
||||
|
||||
Reference in New Issue
Block a user