decimal precision constrained
This commit is contained in:
@@ -61,6 +61,8 @@ pub(crate) struct BuilderForm {
|
||||
#[serde(default)]
|
||||
pub decimal_scale_input: String,
|
||||
#[serde(default)]
|
||||
pub fixed_decimal_input: String,
|
||||
#[serde(default)]
|
||||
pub column_indexing_input: String,
|
||||
#[serde(default)]
|
||||
pub column_quantity_ledger_input: String,
|
||||
@@ -128,6 +130,7 @@ impl BuilderForm {
|
||||
link_table_input: self.link_table_input.clone(),
|
||||
decimal_precision_input: self.decimal_precision_input.clone(),
|
||||
decimal_scale_input: self.decimal_scale_input.clone(),
|
||||
fixed_decimal_input: self.fixed_decimal_input.clone(),
|
||||
indexing_input: self.column_indexing_input.clone(),
|
||||
quantity_ledger_input: self.column_quantity_ledger_input.clone(),
|
||||
required_input: self.column_required_input.clone(),
|
||||
|
||||
@@ -245,11 +245,18 @@ mod tests {
|
||||
assert!(html.contains("Every profile can use this shared table."));
|
||||
}
|
||||
|
||||
/// Every type the server accepts has to be reachable from the picker, or
|
||||
/// the web UI silently offers less than the backend does.
|
||||
/// Fixed-scale decimal is the one intentionally gated type; enabling the
|
||||
/// advanced control makes it reachable without removing server support.
|
||||
#[test]
|
||||
fn the_type_picker_offers_the_parameterised_and_interval_types() {
|
||||
let html = render_builder(&page());
|
||||
let mut state = page();
|
||||
let html = render_builder(&state);
|
||||
|
||||
assert!(!html.contains(r#"<option value="decimal""#));
|
||||
assert!(html.contains(r#"name="fixed_decimal_input""#));
|
||||
|
||||
state.draft.columns.fixed_decimal_input = "yes".to_string();
|
||||
let html = render_builder(&state);
|
||||
|
||||
for column_type in ["decimal", "duration", "period", "accounting"] {
|
||||
assert!(
|
||||
@@ -273,10 +280,12 @@ mod tests {
|
||||
assert!(render_builder(&state).contains(r#"name="gtin_type_input""#));
|
||||
|
||||
// Decimal reveals its precision and scale.
|
||||
state.draft.columns.fixed_decimal_input = "yes".to_string();
|
||||
state.draft.columns.type_input = "decimal".to_string();
|
||||
let html = render_builder(&state);
|
||||
assert!(html.contains(r#"name="decimal_precision_input""#));
|
||||
assert!(html.contains(r#"name="decimal_scale_input""#));
|
||||
assert!(html.contains("Advanced fixed-scale decimal"));
|
||||
|
||||
// Money reveals its currency and rounding inputs.
|
||||
state.draft.columns.type_input = "money".to_string();
|
||||
|
||||
@@ -475,6 +475,10 @@ pub(crate) struct ColumnDraft {
|
||||
pub link_table_input: String,
|
||||
pub decimal_precision_input: String,
|
||||
pub decimal_scale_input: String,
|
||||
/// Explicit opt-in for the fixed-scale decimal type. It is intentionally
|
||||
/// absent from the normal type picker because PostgreSQL coerces values to
|
||||
/// its declared scale and populated tables cannot later change the type.
|
||||
pub fixed_decimal_input: String,
|
||||
pub indexing_input: String,
|
||||
pub quantity_ledger_input: String,
|
||||
pub required_input: String,
|
||||
@@ -528,6 +532,7 @@ impl ColumnDraft {
|
||||
required_input: "no".to_string(),
|
||||
rounding_input: "none".to_string(),
|
||||
currency_input: "EUR".to_string(),
|
||||
fixed_decimal_input: "no".to_string(),
|
||||
catalog,
|
||||
..Self::default()
|
||||
}
|
||||
@@ -536,7 +541,15 @@ impl ColumnDraft {
|
||||
/// The types this panel offers, which is the only place the creation-only
|
||||
/// rule shows up in the markup.
|
||||
pub(crate) fn offered_types(&self) -> Vec<String> {
|
||||
self.catalog.offered_types(self.creating_table, self.global)
|
||||
self.catalog
|
||||
.offered_types(self.creating_table, self.global)
|
||||
.into_iter()
|
||||
.filter(|column_type| column_type != "decimal" || self.fixed_decimal_enabled())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn fixed_decimal_enabled(&self) -> bool {
|
||||
self.fixed_decimal_input.trim().eq_ignore_ascii_case("yes")
|
||||
}
|
||||
|
||||
pub(crate) fn temporal_types(&self) -> Vec<String> {
|
||||
@@ -639,6 +652,9 @@ impl ColumnDraft {
|
||||
return Ok(None);
|
||||
}
|
||||
if self.catalog.is_parameterised(&column_type) {
|
||||
if !self.fixed_decimal_enabled() {
|
||||
return Err(crate::tr!(locale, "td-fixed-decimal-enable-first"));
|
||||
}
|
||||
let precision = self.decimal_precision_input.trim();
|
||||
let scale = self.decimal_scale_input.trim();
|
||||
if precision.is_empty() && scale.is_empty() {
|
||||
@@ -1442,6 +1458,8 @@ pub(crate) struct ColumnForm {
|
||||
#[serde(default)]
|
||||
pub decimal_scale_input: String,
|
||||
#[serde(default)]
|
||||
pub fixed_decimal_input: String,
|
||||
#[serde(default)]
|
||||
pub column_indexing_input: String,
|
||||
#[serde(default)]
|
||||
pub column_quantity_ledger_input: String,
|
||||
@@ -1487,6 +1505,7 @@ impl ColumnForm {
|
||||
link_table_input: self.link_table_input.clone(),
|
||||
decimal_precision_input: self.decimal_precision_input.clone(),
|
||||
decimal_scale_input: self.decimal_scale_input.clone(),
|
||||
fixed_decimal_input: self.fixed_decimal_input.clone(),
|
||||
indexing_input: self.column_indexing_input.clone(),
|
||||
quantity_ledger_input: self.column_quantity_ledger_input.clone(),
|
||||
required_input: self.column_required_input.clone(),
|
||||
@@ -1755,7 +1774,10 @@ pub(crate) mod tests {
|
||||
assert!(offered.contains(&"numeric".to_string()));
|
||||
assert!(offered.contains(&"account".to_string()));
|
||||
assert!(offered.contains(&"accounting_transfer".to_string()));
|
||||
assert!(offered.contains(&"decimal".to_string()));
|
||||
assert!(!offered.contains(&"decimal".to_string()));
|
||||
let mut advanced = draft();
|
||||
advanced.fixed_decimal_input = "yes".to_string();
|
||||
assert!(advanced.offered_types().contains(&"decimal".to_string()));
|
||||
// Families are one choice, resolved by a follow-up field.
|
||||
assert!(offered.contains(&"temporal".to_string()));
|
||||
assert!(offered.contains(&"gtin".to_string()));
|
||||
@@ -1803,6 +1825,7 @@ pub(crate) mod tests {
|
||||
assert_eq!(draft.added[1].data_type, "gtin_13");
|
||||
|
||||
draft.name_input = "weight".to_string();
|
||||
draft.fixed_decimal_input = "yes".to_string();
|
||||
draft.type_input = "decimal".to_string();
|
||||
assert_eq!(draft.canonical_type_input(crate::i18n::Locale::default()).unwrap(), None);
|
||||
assert!(draft.show_decimal_arguments());
|
||||
@@ -1843,6 +1866,7 @@ pub(crate) mod tests {
|
||||
fn decimal_arguments_follow_the_servers_rules() {
|
||||
let mut draft = draft();
|
||||
draft.name_input = "weight".to_string();
|
||||
draft.fixed_decimal_input = "yes".to_string();
|
||||
draft.type_input = "decimal".to_string();
|
||||
|
||||
for (precision, scale) in [("0", "0"), ("3", "5"), ("-2", "1"), ("08", "2"), ("4.5", "1")] {
|
||||
@@ -1946,6 +1970,7 @@ pub(crate) mod tests {
|
||||
|
||||
// A parameterised decimal counts, through its head.
|
||||
draft.name_input = "quantity".to_string();
|
||||
draft.fixed_decimal_input = "yes".to_string();
|
||||
draft.type_input = "decimal".to_string();
|
||||
draft.decimal_precision_input = "12".to_string();
|
||||
draft.decimal_scale_input = "3".to_string();
|
||||
|
||||
Reference in New Issue
Block a user