removing decimal with precision and decimal count
This commit is contained in:
4
AGENTS.md
Normal file
4
AGENTS.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Repository agent rules
|
||||
|
||||
- Never run `cargo fmt`, `rustfmt`, or any other automatic code-formatting command in this repository, including commands scoped to individual files or packages.
|
||||
- Preserve existing formatting. Make only the smallest hand-edited changes required for the task.
|
||||
@@ -3,10 +3,6 @@
|
||||
TUI accounting system. Client/server application with two extracted open-source
|
||||
libraries.
|
||||
|
||||
Column designers default to unconstrained exact decimals. The legacy-compatible
|
||||
fixed-scale form is an explicit advanced option; see
|
||||
[`docs/fixed-scale-decimal.md`](docs/fixed-scale-decimal.md).
|
||||
|
||||
## Crates
|
||||
|
||||
| Crate | What | Published |
|
||||
|
||||
2
client
2
client
Submodule client updated: 1e0b6f9ede...fbf3ff93bd
@@ -36,8 +36,8 @@ So legal columns are blocked at creation, hidden in forms, or misrendered as lin
|
||||
**7. Link detection is by name, not by type.**
|
||||
Links are now a declarable `LINK(table)` column type with arbitrary names and multiple links to the same target allowed (751bfd9, 0d78556). The client decides `is_link` purely from the `_id` suffix: a LINK column named `customer` renders as a plain BIGINT text field with no picker, and a non-link column named e.g. `invoice_id` is hidden or misdetected as a link. (Child-reference resolution does use the new `Dependency.column_name` — `ui_service.rs:204–222` — that part is synced.)
|
||||
|
||||
**8. The add-table form cannot create LINK or parameterized DECIMAL columns.**
|
||||
The Relations pane lists available tables but selection is display-only (`add_table/data.rs:376`, `ui.rs:311`) — nothing is added to the request. The column-type input only accepts bare spellings (`supports_column_type` requires `ColumnTypeSpelling::Bare`), and a test asserts `decimal(10,2)` is rejected (`add_table/state.rs:589`). The server's `ListColumnTypes` advertises the Decimal and Link spellings. The two argument-taking types the server supports are uncreatable from the client.
|
||||
**8. The add-table form cannot create LINK columns.**
|
||||
The Relations pane lists available tables but selection is display-only (`add_table/data.rs:376`, `ui.rs:311`) — nothing is added to the request. The server advertises the Link spelling, but the client cannot create it.
|
||||
|
||||
**9. `required` flag is not exposed in the add-table UI.**
|
||||
Commit 29ccd8d added `ColumnDefinition.required` end-to-end (definition → validation → insert/update enforcement: `post_table_definition.rs:372`, `table_validation/runtime.rs:75`). The client always sends `required: false` (`add_table/logic.rs:33`) with no UI to mark a column required. The data-entry form does honor `required` returned by `GetTableValidation` — that path is synced.
|
||||
|
||||
@@ -275,15 +275,12 @@ message ColumnDefinition {
|
||||
// DATE
|
||||
// DURATION
|
||||
// PERIOD
|
||||
// DECIMAL(p,s) → NUMERIC(p,s)
|
||||
// LINK(table) → BIGINT referencing that table in the same profile, indexed
|
||||
// automatically. A table may hold several links to the same
|
||||
// target as long as the columns are named differently.
|
||||
// FROM(link.source) → stored, read-only copy of an atomic column reached
|
||||
// through another column in this definition whose type is
|
||||
// LINK(...). SQL type and money metadata are inferred.
|
||||
// DECIMAL args must be integers (no sign, no dot, no leading zeros);
|
||||
// s ≤ p and p ≥ 1.
|
||||
string field_type = 2;
|
||||
|
||||
// MONEY rounding applied before a value is stored.
|
||||
@@ -530,10 +527,7 @@ enum ColumnTypeSpelling {
|
||||
// The name is the whole spelling: "text", "money", "gtin_13".
|
||||
COLUMN_TYPE_SPELLING_BARE = 0;
|
||||
|
||||
// The name takes a precision and a scale: "decimal(12,3)". Precision must be
|
||||
// at least 1 and scale no greater than precision; neither may carry a sign,
|
||||
// a decimal point, or leading zeros.
|
||||
COLUMN_TYPE_SPELLING_DECIMAL = 1;
|
||||
reserved 1;
|
||||
|
||||
// The name takes the name of another table in the same profile:
|
||||
// "link(adresar)". The column holds that table's id, and the server creates
|
||||
@@ -558,8 +552,7 @@ message ListColumnTypesResponse {
|
||||
string name = 1;
|
||||
|
||||
// Underlying PostgreSQL type the logical type maps to (e.g. "NUMERIC").
|
||||
// Empty when `compound` is true. For COLUMN_TYPE_SPELLING_DECIMAL this is
|
||||
// the unparameterised type; the declared precision and scale are appended.
|
||||
// Empty when `compound` is true.
|
||||
string sql_type = 2;
|
||||
|
||||
// False for types the server generates on its own and rejects when a client
|
||||
|
||||
@@ -37,17 +37,12 @@ pub fn parse_decimal_exact(value: &str) -> Result<Decimal, String> {
|
||||
Decimal::from_str_exact(value).map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
/// True for the `data_type` spellings `GetTableStructure` reports for a decimal
|
||||
/// column: `NUMERIC` (from `numeric` and `money`), `NUMERIC(p)` and
|
||||
/// `NUMERIC(p,s)` (from `decimal(p,s)`).
|
||||
/// True for the `NUMERIC` data type reported by `GetTableStructure`.
|
||||
pub fn is_decimal_data_type(data_type: &str) -> bool {
|
||||
data_type
|
||||
.trim()
|
||||
.to_ascii_uppercase()
|
||||
.starts_with(DECIMAL_DATA_TYPE_PREFIX)
|
||||
data_type.trim().eq_ignore_ascii_case(DECIMAL_DATA_TYPE)
|
||||
}
|
||||
|
||||
const DECIMAL_DATA_TYPE_PREFIX: &str = "NUMERIC";
|
||||
const DECIMAL_DATA_TYPE: &str = "NUMERIC";
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -101,11 +96,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decimal_data_type_covers_every_numeric_spelling() {
|
||||
for data_type in ["NUMERIC", "NUMERIC(12)", "NUMERIC(12,3)", "numeric(12,3)"] {
|
||||
assert!(is_decimal_data_type(data_type), "missed {data_type}");
|
||||
}
|
||||
for data_type in ["TEXT", "INT8", "TIMESTAMPTZ", "VARCHAR(255)", ""] {
|
||||
fn decimal_data_type_matches_only_unconstrained_numeric() {
|
||||
assert!(is_decimal_data_type("NUMERIC"));
|
||||
assert!(is_decimal_data_type("numeric"));
|
||||
for data_type in ["NUMERIC(12)", "NUMERIC(12,3)", "TEXT", "INT8", ""] {
|
||||
assert!(!is_decimal_data_type(data_type), "matched {data_type}");
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -239,15 +239,12 @@ pub struct ColumnDefinition {
|
||||
/// DATE
|
||||
/// DURATION
|
||||
/// PERIOD
|
||||
/// DECIMAL(p,s) → NUMERIC(p,s)
|
||||
/// LINK(table) → BIGINT referencing that table in the same profile, indexed
|
||||
/// automatically. A table may hold several links to the same
|
||||
/// target as long as the columns are named differently.
|
||||
/// FROM(link.source) → stored, read-only copy of an atomic column reached
|
||||
/// through another column in this definition whose type is
|
||||
/// LINK(...). SQL type and money metadata are inferred.
|
||||
/// DECIMAL args must be integers (no sign, no dot, no leading zeros);
|
||||
/// s ≤ p and p ≥ 1.
|
||||
#[prost(string, tag = "2")]
|
||||
pub field_type: ::prost::alloc::string::String,
|
||||
/// MONEY rounding applied before a value is stored.
|
||||
@@ -584,8 +581,7 @@ pub mod list_column_types_response {
|
||||
#[prost(string, tag = "1")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
/// Underlying PostgreSQL type the logical type maps to (e.g. "NUMERIC").
|
||||
/// Empty when `compound` is true. For COLUMN_TYPE_SPELLING_DECIMAL this is
|
||||
/// the unparameterised type; the declared precision and scale are appended.
|
||||
/// Empty when `compound` is true.
|
||||
#[prost(string, tag = "2")]
|
||||
pub sql_type: ::prost::alloc::string::String,
|
||||
/// False for types the server generates on its own and rejects when a client
|
||||
@@ -682,10 +678,6 @@ impl MoneyRounding {
|
||||
pub enum ColumnTypeSpelling {
|
||||
/// The name is the whole spelling: "text", "money", "gtin_13".
|
||||
Bare = 0,
|
||||
/// The name takes a precision and a scale: "decimal(12,3)". Precision must be
|
||||
/// at least 1 and scale no greater than precision; neither may carry a sign,
|
||||
/// a decimal point, or leading zeros.
|
||||
Decimal = 1,
|
||||
/// The name takes the name of another table in the same profile:
|
||||
/// "link(adresar)". The column holds that table's id, and the server creates
|
||||
/// the foreign key and its index. A picker offers the profile's other tables
|
||||
@@ -700,7 +692,6 @@ impl ColumnTypeSpelling {
|
||||
pub fn as_str_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Bare => "COLUMN_TYPE_SPELLING_BARE",
|
||||
Self::Decimal => "COLUMN_TYPE_SPELLING_DECIMAL",
|
||||
Self::Link => "COLUMN_TYPE_SPELLING_LINK",
|
||||
}
|
||||
}
|
||||
@@ -708,7 +699,6 @@ impl ColumnTypeSpelling {
|
||||
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
|
||||
match value {
|
||||
"COLUMN_TYPE_SPELLING_BARE" => Some(Self::Bare),
|
||||
"COLUMN_TYPE_SPELLING_DECIMAL" => Some(Self::Decimal),
|
||||
"COLUMN_TYPE_SPELLING_LINK" => Some(Self::Link),
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -93,9 +93,7 @@ pub fn normalize_exact(input: &str) -> String {
|
||||
/// names, so this deliberately matches the catalog vocabulary.
|
||||
pub fn canonical_exact_search_value(input: &str, field_type: &str) -> Result<String, String> {
|
||||
let normalized_type = field_type.trim().to_ascii_lowercase();
|
||||
if matches!(normalized_type.as_str(), "numeric" | "money")
|
||||
|| normalized_type.starts_with("decimal(")
|
||||
{
|
||||
if matches!(normalized_type.as_str(), "numeric" | "money") {
|
||||
return input
|
||||
.parse::<rust_decimal::Decimal>()
|
||||
.map(|value| value.normalize().to_string())
|
||||
@@ -288,10 +286,6 @@ mod tests {
|
||||
assert_eq!(canonical_exact_search_value("001", "link(adresar)").unwrap(), "1");
|
||||
assert_eq!(canonical_exact_search_value("10.50", "numeric").unwrap(), "10.5");
|
||||
assert_eq!(canonical_exact_search_value("10.50", "money").unwrap(), "10.5");
|
||||
assert_eq!(
|
||||
canonical_exact_search_value("10.50", "decimal(12, 2)").unwrap(),
|
||||
"10.5"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,9 +41,7 @@ pub fn parse_archived_search_row_key(row_key: &str) -> Option<(i64, i64, i64)> {
|
||||
|
||||
pub fn canonical_exact_search_value(input: &str, field_type: &str) -> Result<String, String> {
|
||||
let normalized_type = field_type.trim().to_ascii_lowercase();
|
||||
if matches!(normalized_type.as_str(), "numeric" | "money")
|
||||
|| normalized_type.starts_with("decimal(")
|
||||
{
|
||||
if matches!(normalized_type.as_str(), "numeric" | "money") {
|
||||
return input
|
||||
.parse::<rust_decimal::Decimal>()
|
||||
.map(|value| value.normalize().to_string())
|
||||
@@ -100,9 +98,5 @@ mod tests {
|
||||
canonical_exact_search_value("10.50", "money").unwrap(),
|
||||
"10.5"
|
||||
);
|
||||
assert_eq!(
|
||||
canonical_exact_search_value("10.50", "decimal(12, 2)").unwrap(),
|
||||
"10.5"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# Decimal column policy
|
||||
|
||||
Use the bare `numeric` column type for ordinary exact base-10 values. It stores
|
||||
the value without a schema-defined decimal-place limit and is the recommended
|
||||
choice for quantities, percentages, rates, ratios and measurements. Use
|
||||
`money` for currency amounts; its currency and rounding policy are separate
|
||||
from this document.
|
||||
|
||||
`decimal(p,s)` is an advanced, opt-in compatibility type for strict external
|
||||
data contracts. `p` limits the total digits and `s` limits the fractional
|
||||
digits. It is hidden from the normal table-designer type list until fixed-scale
|
||||
decimal is enabled for that editing session.
|
||||
|
||||
Fixed scale is intentionally strict:
|
||||
|
||||
- API input outside the declared precision or scale is rejected, never
|
||||
silently rounded by PostgreSQL.
|
||||
- A table script targeting a fixed-scale column must round explicitly before
|
||||
returning its value.
|
||||
- A fixed-scale type cannot be changed through the table editor after the
|
||||
table contains data.
|
||||
- Display precision, allowed increments, minimums and maximums are business
|
||||
validation concerns; `decimal(p,s)` is not a substitute for them.
|
||||
|
||||
Existing fixed-scale columns remain supported. The opt-in changes discovery,
|
||||
not storage compatibility.
|
||||
@@ -178,10 +178,10 @@ grpcurl -plaintext -H "$AUTH_HEADER" \
|
||||
"links":[{"linked_table_name":"faktura","required":true}],
|
||||
"columns":[
|
||||
{"name":"nazov","field_type":"TEXT"},
|
||||
{"name":"mnozstvo","field_type":"DECIMAL(12,3)"},
|
||||
{"name":"mnozstvo","field_type":"numeric"},
|
||||
{"name":"mj","field_type":"TEXT"},
|
||||
{"name":"cena","field_type":"MONEY","currency":"EUR","rounding":"MONEY_ROUNDING_HALF_UP"},
|
||||
{"name":"dph","field_type":"DECIMAL(5,2)"},
|
||||
{"name":"dph","field_type":"numeric"},
|
||||
{"name":"bez","field_type":"MONEY","currency":"EUR","rounding":"MONEY_ROUNDING_HALF_UP","recompute_on_dependency_change":true},
|
||||
{"name":"dph_suma","field_type":"MONEY","currency":"EUR","rounding":"MONEY_ROUNDING_HALF_UP","recompute_on_dependency_change":true},
|
||||
{"name":"spolu","field_type":"MONEY","currency":"EUR","rounding":"MONEY_ROUNDING_HALF_UP","recompute_on_dependency_change":true}
|
||||
|
||||
2
server
2
server
Submodule server updated: cf752c7ee9...4ea5a4e80d
@@ -125,11 +125,11 @@ Create profile table `products`:
|
||||
| `unit` | link(units) | required, auto-indexed |
|
||||
| `sale_price` | money | EUR, exact rounding |
|
||||
| `purchase_price` | money | USD, half-up rounding |
|
||||
| `vat_rate` | decimal(5,2) | required |
|
||||
| `vat_rate` | numeric | required |
|
||||
| `package_count` | int | optional |
|
||||
| `legacy_id` | bigint | indexed |
|
||||
| `measured_weight` | numeric | optional |
|
||||
| `stock` | decimal(18,3) | quantity ledger enabled |
|
||||
| `stock` | numeric | quantity ledger enabled |
|
||||
| `active` | boolean | required |
|
||||
|
||||
Confirm `stock` is displayed as quantity-ledger/read-only and starts at zero.
|
||||
@@ -143,7 +143,7 @@ Create `stock_receipts` with:
|
||||
- `product`: required link(products);
|
||||
- `warehouse`: required link(warehouses);
|
||||
- `supplier`: required link(partners);
|
||||
- `quantity`: required decimal(18,3);
|
||||
- `quantity`: required numeric;
|
||||
- `received_on`: required date;
|
||||
- `processed`: int.
|
||||
|
||||
@@ -176,9 +176,9 @@ Create `sales_order_lines`:
|
||||
|
||||
- `sales_order`: required link(sales_orders);
|
||||
- `product`: required link(products);
|
||||
- `quantity`: required decimal(18,3);
|
||||
- `quantity`: required numeric;
|
||||
- `unit_price`: required EUR money, exact rounding;
|
||||
- `discount_rate`: decimal(5,2);
|
||||
- `discount_rate`: numeric;
|
||||
- `line_position`: required int.
|
||||
|
||||
This supplies a parent → child → product link chain and composite business
|
||||
@@ -346,8 +346,8 @@ The live tables deliberately cover text, boolean, int, bigint, numeric,
|
||||
decimal, EUR/USD money with both rounding modes, date/time/instant, duration,
|
||||
period, phone, IBAN, email, credit card, GTIN-8/12/13/14, account, FK links,
|
||||
accounting, accounting transfer, generated companions, and a
|
||||
`products.stock` `decimal(12,3)` quantity ledger. The stock transaction tables
|
||||
have `stock_effect decimal(12,3)` computed targets, because a script cannot
|
||||
`products.stock` `numeric` quantity ledger. The stock transaction tables
|
||||
have `stock_effect numeric` computed targets, because a script cannot
|
||||
target its own source `quantity` column.
|
||||
|
||||
The saved Steel scripts are:
|
||||
|
||||
@@ -43,8 +43,6 @@ label-validation-set-name = název validační sady
|
||||
label-minimum = Minimum
|
||||
label-maximum = Maximum
|
||||
label-warning-threshold = Hranice upozornění
|
||||
label-precision = Přesnost
|
||||
label-scale = Desetinná místa
|
||||
label-column-name = Název sloupce
|
||||
label-table-name = Název tabulky
|
||||
label-linked-table = Odkazovaná tabulka
|
||||
@@ -208,19 +206,6 @@ td-temporal-type = Časový typ
|
||||
td-choose-temporal-type = Vyberte časový typ
|
||||
td-gtin-type = Typ GTIN
|
||||
td-choose-gtin-length = Vyberte délku GTIN
|
||||
td-precision = Přesnost
|
||||
td-precision-hint = Celkový počet uložených číslic, alespoň 1.
|
||||
td-scale = Desetinná místa
|
||||
td-scale-hint = Číslice za desetinnou čárkou, nejvýše tolik jako přesnost.
|
||||
td-fixed-decimal-access = Pokročilé číselné typy
|
||||
td-fixed-decimal-disabled = Pouze doporučené typy
|
||||
td-fixed-decimal-enabled = Povolit desetinné číslo s pevným měřítkem
|
||||
td-fixed-decimal-access-hint = Doporučuje se přesný typ NUMERIC. Tuto možnost povolte pouze pro přísný externí datový kontrakt.
|
||||
td-fixed-decimal-warning-title = Pokročilé desetinné číslo s pevným měřítkem
|
||||
td-fixed-decimal-warning = Tento typ omezuje celkový počet číslic i desetinná místa. Hodnoty mimo měřítko se odmítnou, výpočty musí zaokrouhlovat explicitně a typ po vložení dat nelze změnit.
|
||||
td-fixed-decimal-enable-first = Před použitím tohoto typu povolte pokročilé desetinné číslo s pevným měřítkem.
|
||||
td-exact-decimal = Desetinné číslo — přesné (doporučeno)
|
||||
td-fixed-decimal-type = Desetinné číslo s pevným měřítkem — pokročilé
|
||||
td-referenced-table = Odkazovaná tabulka
|
||||
td-choose-table = Vyberte tabulku
|
||||
td-group-global = Globální
|
||||
@@ -844,9 +829,7 @@ schema-err-generated-exists = `{ $type }` generuje sloupec s názvem `{ $generat
|
||||
schema-err-ql-types = Sloupce kvantitativní evidence musí používat { $types }
|
||||
schema-err-column-ql-types = Sloupec `{ $name }`: sloupce kvantitativní evidence musí používat { $types }
|
||||
schema-err-column-prefix = Sloupec `{ $name }`: { $error }
|
||||
schema-err-decimal-not-valid = `decimal` není platný typ sloupce.
|
||||
schema-err-link-not-valid = `link` není platný typ sloupce.
|
||||
schema-err-decimal-args-needed = `{ $type }` vyžaduje přesnost i desetinná místa.
|
||||
schema-err-link-target-needed = `link` vyžaduje odkazovanou tabulku.
|
||||
schema-err-generated-type = `{ $type }` je typ sloupce, který generuje backend.
|
||||
schema-err-invalid-type = `{ $type }` není platný typ sloupce.
|
||||
@@ -871,12 +854,6 @@ error-identifier-number = { $label } nesmí začínat číslicí.
|
||||
error-identifier-too-long = { $label } nesmí být delší než { $limit } znaků.
|
||||
error-identifier-charset = { $label } může obsahovat jen malá písmena, číslice a podtržítko.
|
||||
error-identifier-reserved = { $label } používá vyhrazený název.
|
||||
error-decimal-required = { $label } je povinné pro desetinný sloupec.
|
||||
error-decimal-sign = { $label } nesmí obsahovat znaménko.
|
||||
error-decimal-whole = { $label } musí být celé číslo.
|
||||
error-decimal-leading-zeros = { $label } nesmí mít úvodní nuly.
|
||||
error-precision-min = Přesnost musí být alespoň 1.
|
||||
error-scale-gt-precision = Desetinná místa nemohou být více než přesnost.
|
||||
|
||||
# --- Přidat validaci ----------------------------------------------------------------
|
||||
validation-eyebrow = Validace
|
||||
|
||||
@@ -48,8 +48,6 @@ label-validation-set-name = validation set name
|
||||
label-minimum = Minimum
|
||||
label-maximum = Maximum
|
||||
label-warning-threshold = Warning threshold
|
||||
label-precision = Precision
|
||||
label-scale = Scale
|
||||
label-column-name = Column name
|
||||
label-table-name = Table name
|
||||
label-linked-table = Linked table
|
||||
@@ -212,19 +210,6 @@ td-temporal-type = Temporal type
|
||||
td-choose-temporal-type = Choose a temporal type
|
||||
td-gtin-type = GTIN type
|
||||
td-choose-gtin-length = Choose a GTIN length
|
||||
td-precision = Precision
|
||||
td-precision-hint = Total digits stored, at least 1.
|
||||
td-scale = Scale
|
||||
td-scale-hint = Digits after the point, no more than the precision.
|
||||
td-fixed-decimal-access = Advanced number types
|
||||
td-fixed-decimal-disabled = Recommended types only
|
||||
td-fixed-decimal-enabled = Enable fixed-scale decimal
|
||||
td-fixed-decimal-access-hint = Exact NUMERIC is recommended. Enable this only for a strict external data contract.
|
||||
td-fixed-decimal-warning-title = Advanced fixed-scale decimal
|
||||
td-fixed-decimal-warning = This type limits total and fractional digits. Values outside the declared scale are refused, calculations must round explicitly, and the type cannot be changed after the table contains data.
|
||||
td-fixed-decimal-enable-first = Enable advanced fixed-scale decimal before using this type.
|
||||
td-exact-decimal = Decimal — exact (recommended)
|
||||
td-fixed-decimal-type = Fixed-scale decimal — advanced
|
||||
td-referenced-table = Referenced table
|
||||
td-choose-table = Choose a table
|
||||
td-group-global = Global
|
||||
@@ -829,9 +814,7 @@ schema-err-generated-exists = `{ $type }` generates a column named `{ $generated
|
||||
schema-err-ql-types = Quantity-ledger columns must use { $types }
|
||||
schema-err-column-ql-types = Column `{ $name }`: quantity-ledger columns must use { $types }
|
||||
schema-err-column-prefix = Column `{ $name }`: { $error }
|
||||
schema-err-decimal-not-valid = `decimal` is not a valid field type.
|
||||
schema-err-link-not-valid = `link` is not a valid field type.
|
||||
schema-err-decimal-args-needed = `{ $type }` needs both a precision and a scale.
|
||||
schema-err-link-target-needed = `link` needs a referenced table.
|
||||
schema-err-generated-type = `{ $type }` is a column type the backend generates itself.
|
||||
schema-err-invalid-type = `{ $type }` is not a valid field type.
|
||||
@@ -856,12 +839,6 @@ error-identifier-number = { $label } cannot start with a number.
|
||||
error-identifier-too-long = { $label } cannot be longer than { $limit } characters.
|
||||
error-identifier-charset = { $label } may only use lowercase letters, digits and underscores.
|
||||
error-identifier-reserved = { $label } uses a reserved name.
|
||||
error-decimal-required = { $label } is required for a decimal column.
|
||||
error-decimal-sign = { $label } cannot carry a sign.
|
||||
error-decimal-whole = { $label } must be a whole number.
|
||||
error-decimal-leading-zeros = { $label } cannot have leading zeros.
|
||||
error-precision-min = Precision must be at least 1.
|
||||
error-scale-gt-precision = Scale cannot be greater than precision.
|
||||
|
||||
# --- Add validation --------------------------------------------------------
|
||||
validation-eyebrow = Validation
|
||||
|
||||
@@ -43,8 +43,6 @@ label-validation-set-name = názov validačnej sady
|
||||
label-minimum = Minimum
|
||||
label-maximum = Maximum
|
||||
label-warning-threshold = Hranica upozornenia
|
||||
label-precision = Presnosť
|
||||
label-scale = Desatinné miesta
|
||||
label-column-name = Názov stĺpca
|
||||
label-table-name = Názov tabuľky
|
||||
label-linked-table = Odkazovaná tabuľka
|
||||
@@ -208,19 +206,6 @@ td-temporal-type = Časový typ
|
||||
td-choose-temporal-type = Vyberte časový typ
|
||||
td-gtin-type = Typ GTIN
|
||||
td-choose-gtin-length = Vyberte dĺžku GTIN
|
||||
td-precision = Presnosť
|
||||
td-precision-hint = Celkový počet uložených číslic, aspoň 1.
|
||||
td-scale = Desatinné miesta
|
||||
td-scale-hint = Číslice za desatinnou čiarkou, najviac toľko ako presnosť.
|
||||
td-fixed-decimal-access = Pokročilé číselné typy
|
||||
td-fixed-decimal-disabled = Iba odporúčané typy
|
||||
td-fixed-decimal-enabled = Povoliť desatinné číslo s pevnou mierkou
|
||||
td-fixed-decimal-access-hint = Odporúča sa presný typ NUMERIC. Túto možnosť povoľte iba pre prísnu externú dátovú zmluvu.
|
||||
td-fixed-decimal-warning-title = Pokročilé desatinné číslo s pevnou mierkou
|
||||
td-fixed-decimal-warning = Tento typ obmedzuje celkový počet číslic aj desatinné miesta. Hodnoty mimo mierky sa odmietnu, výpočty musia zaokrúhľovať explicitne a typ sa po vložení údajov nedá zmeniť.
|
||||
td-fixed-decimal-enable-first = Pred použitím tohto typu povoľte pokročilé desatinné číslo s pevnou mierkou.
|
||||
td-exact-decimal = Desatinné číslo — presné (odporúčané)
|
||||
td-fixed-decimal-type = Desatinné číslo s pevnou mierkou — pokročilé
|
||||
td-referenced-table = Odkazovaná tabuľka
|
||||
td-choose-table = Vyberte tabuľku
|
||||
td-group-global = Globálne
|
||||
@@ -842,9 +827,7 @@ schema-err-generated-exists = `{ $type }` generuje stĺpec s názvom `{ $generat
|
||||
schema-err-ql-types = Stĺpce kvantitatívnej evidencie musia používať { $types }
|
||||
schema-err-column-ql-types = Stĺpec `{ $name }`: stĺpce kvantitatívnej evidencie musia používať { $types }
|
||||
schema-err-column-prefix = Stĺpec `{ $name }`: { $error }
|
||||
schema-err-decimal-not-valid = `decimal` nie je platný typ stĺpca.
|
||||
schema-err-link-not-valid = `link` nie je platný typ stĺpca.
|
||||
schema-err-decimal-args-needed = `{ $type }` vyžaduje presnosť aj desatinné miesta.
|
||||
schema-err-link-target-needed = `link` vyžaduje odkazovanú tabuľku.
|
||||
schema-err-generated-type = `{ $type }` je typ stĺpca, ktorý generuje backend.
|
||||
schema-err-invalid-type = `{ $type }` nie je platný typ stĺpca.
|
||||
@@ -869,12 +852,6 @@ error-identifier-number = { $label } nesmie začínať číslom.
|
||||
error-identifier-too-long = { $label } nesmie byť dlhšie ako { $limit } znakov.
|
||||
error-identifier-charset = { $label } môže obsahovať len malé písmená, číslice a podčiarkovník.
|
||||
error-identifier-reserved = { $label } používa vyhradený názov.
|
||||
error-decimal-required = { $label } je povinné pre desatinný stĺpec.
|
||||
error-decimal-sign = { $label } nesmie obsahovať znamienko.
|
||||
error-decimal-whole = { $label } musí byť celé číslo.
|
||||
error-decimal-leading-zeros = { $label } nesmie mať vedúce nuly.
|
||||
error-precision-min = Presnosť musí byť aspoň 1.
|
||||
error-scale-gt-precision = Desatinné miesta nemôžu byť viac ako presnosť.
|
||||
|
||||
# --- Pridať validáciu ----------------------------------------------------------
|
||||
validation-eyebrow = Validácia
|
||||
|
||||
@@ -57,12 +57,6 @@ pub(crate) struct BuilderForm {
|
||||
#[serde(default)]
|
||||
pub link_table_input: String,
|
||||
#[serde(default)]
|
||||
pub decimal_precision_input: String,
|
||||
#[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,9 +122,6 @@ impl BuilderForm {
|
||||
temporal_type_input: self.temporal_type_input.clone(),
|
||||
gtin_type_input: self.gtin_type_input.clone(),
|
||||
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,20 +245,13 @@ mod tests {
|
||||
assert!(html.contains("Every profile can use this shared table."));
|
||||
}
|
||||
|
||||
/// Fixed-scale decimal is the one intentionally gated type; enabling the
|
||||
/// advanced control makes it reachable without removing server support.
|
||||
/// Every type the server accepts has to be reachable from the picker, or
|
||||
/// the web UI silently offers less than the backend does.
|
||||
#[test]
|
||||
fn the_type_picker_offers_the_parameterised_and_interval_types() {
|
||||
let mut state = page();
|
||||
let html = render_builder(&state);
|
||||
fn the_type_picker_offers_interval_and_accounting_types() {
|
||||
let html = render_builder(&page());
|
||||
|
||||
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"] {
|
||||
for column_type in ["duration", "period", "accounting"] {
|
||||
assert!(
|
||||
html.contains(&format!(r#"<option value="{column_type}""#)),
|
||||
"the type picker is missing {column_type}"
|
||||
@@ -279,14 +272,6 @@ mod tests {
|
||||
state.draft.columns.type_input = "gtin".to_string();
|
||||
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();
|
||||
let html = render_builder(&state);
|
||||
|
||||
@@ -63,7 +63,6 @@ const TYPE_DISPLAY_ORDER: &[&str] = &[
|
||||
"accounting_transfer",
|
||||
"int",
|
||||
"bigint",
|
||||
"decimal",
|
||||
"numeric",
|
||||
"temporal",
|
||||
"duration",
|
||||
@@ -90,8 +89,6 @@ pub(crate) struct ColumnType {
|
||||
/// A definition row rather than a column: it expands into schema-managed
|
||||
/// companions and leaves no column of its own name behind.
|
||||
pub compound: bool,
|
||||
/// The name takes a precision and a scale: `decimal(12,3)`.
|
||||
pub parameterised: bool,
|
||||
/// The name takes the target table: `link(customer)`.
|
||||
pub link: bool,
|
||||
pub requires_currency: bool,
|
||||
@@ -242,12 +239,6 @@ impl ColumnCatalog {
|
||||
.is_some_and(|column_type| column_type.creation_only)
|
||||
}
|
||||
|
||||
/// Whether the type takes a precision and a scale.
|
||||
fn is_parameterised(&self, field_type: &str) -> bool {
|
||||
self.find(field_type)
|
||||
.is_some_and(|column_type| column_type.parameterised)
|
||||
}
|
||||
|
||||
fn is_link(&self, field_type: &str) -> bool {
|
||||
self.find(field_type)
|
||||
.is_some_and(|column_type| column_type.link)
|
||||
@@ -258,23 +249,13 @@ impl ColumnCatalog {
|
||||
/// catalog does not know.
|
||||
pub(crate) fn sql_type(&self, field_type: &str) -> String {
|
||||
let field_type = field_type.trim();
|
||||
match decimal_arguments(&field_type.to_lowercase()) {
|
||||
// `decimal(12,3)` is stored as its head's SQL type, parameterised.
|
||||
Some((precision, scale)) => match self.find("decimal") {
|
||||
Some(column_type) if !column_type.sql_type.is_empty() => {
|
||||
format!("{}({precision},{scale})", column_type.sql_type)
|
||||
}
|
||||
_ => String::new(),
|
||||
},
|
||||
None => self
|
||||
.find(if link_argument(&field_type.to_lowercase()).is_some() {
|
||||
self.find(if link_argument(&field_type.to_lowercase()).is_some() {
|
||||
"link"
|
||||
} else {
|
||||
field_type
|
||||
})
|
||||
.map(|column_type| column_type.sql_type.clone())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The types a quantity-ledger column may use, spelled for the hint under
|
||||
@@ -292,14 +273,8 @@ impl ColumnCatalog {
|
||||
}
|
||||
|
||||
fn allows_quantity_ledger(&self, field_type: &str) -> bool {
|
||||
match decimal_arguments(&field_type.to_lowercase()) {
|
||||
Some(_) => self
|
||||
.find("decimal")
|
||||
.is_some_and(|column_type| column_type.allows_quantity_ledger),
|
||||
None => self
|
||||
.find(field_type)
|
||||
.is_some_and(|column_type| column_type.allows_quantity_ledger),
|
||||
}
|
||||
self.find(field_type)
|
||||
.is_some_and(|column_type| column_type.allows_quantity_ledger)
|
||||
}
|
||||
|
||||
/// Whether the server would accept this as a column's declared type.
|
||||
@@ -309,15 +284,6 @@ impl ColumnCatalog {
|
||||
field_type: &str,
|
||||
) -> Option<String> {
|
||||
let field_type = field_type.to_lowercase();
|
||||
if let Some((precision, scale)) = decimal_arguments(&field_type) {
|
||||
if !self.is_parameterised("decimal") {
|
||||
return Some(crate::tr!(
|
||||
locale,
|
||||
"schema-err-decimal-not-valid"
|
||||
));
|
||||
}
|
||||
return validate_decimal_arguments(locale, precision, scale).err();
|
||||
}
|
||||
if let Some(target) = link_argument(&field_type) {
|
||||
if !self.is_link("link") {
|
||||
return Some(crate::tr!(locale, "schema-err-link-not-valid"));
|
||||
@@ -325,12 +291,6 @@ impl ColumnCatalog {
|
||||
return validate_identifier(locale, target, "label-linked-table", true);
|
||||
}
|
||||
match self.find(&field_type) {
|
||||
// A parameterised type spelled bare is missing its arguments.
|
||||
Some(column_type) if column_type.parameterised => Some(crate::tr!(
|
||||
locale,
|
||||
"schema-err-decimal-args-needed",
|
||||
"type" => field_type.clone(),
|
||||
)),
|
||||
Some(column_type) if column_type.link => {
|
||||
Some(crate::tr!(locale, "schema-err-link-target-needed"))
|
||||
}
|
||||
@@ -349,20 +309,6 @@ impl ColumnCatalog {
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits `decimal(p,s)` into its arguments, which is the one spelling that is
|
||||
/// not simply a type name.
|
||||
fn decimal_arguments(field_type: &str) -> Option<(&str, &str)> {
|
||||
let arguments = field_type
|
||||
.strip_prefix("decimal(")
|
||||
.and_then(|rest| rest.strip_suffix(')'))?;
|
||||
Some(match arguments.split_once(',') {
|
||||
Some((precision, scale)) => (precision.trim(), scale.trim()),
|
||||
// No comma at all: the scale is missing, and the emptiness is what
|
||||
// `validate_decimal_arguments` reports.
|
||||
None => (arguments.trim(), ""),
|
||||
})
|
||||
}
|
||||
|
||||
fn link_argument(field_type: &str) -> Option<&str> {
|
||||
field_type
|
||||
.strip_prefix("link(")
|
||||
@@ -473,12 +419,6 @@ pub(crate) struct ColumnDraft {
|
||||
pub temporal_type_input: String,
|
||||
pub gtin_type_input: String,
|
||||
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,
|
||||
@@ -532,7 +472,6 @@ 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()
|
||||
}
|
||||
@@ -541,15 +480,7 @@ 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)
|
||||
.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")
|
||||
self.catalog.offered_types(self.creating_table, self.global)
|
||||
}
|
||||
|
||||
pub(crate) fn temporal_types(&self) -> Vec<String> {
|
||||
@@ -580,10 +511,6 @@ impl ColumnDraft {
|
||||
self.pending_group().is_some_and(|group| group == "gtin")
|
||||
}
|
||||
|
||||
pub(crate) fn show_decimal_arguments(&self) -> bool {
|
||||
self.catalog.is_parameterised(&self.type_input)
|
||||
}
|
||||
|
||||
pub(crate) fn show_link_target(&self) -> bool {
|
||||
self.catalog.is_link(&self.type_input)
|
||||
}
|
||||
@@ -642,8 +569,8 @@ impl ColumnDraft {
|
||||
|
||||
// ---- the pending column ---------------------------------------------
|
||||
|
||||
/// The storable type the pending inputs describe, resolving a group choice
|
||||
/// and the `decimal` arguments to their canonical form. `None` while the
|
||||
/// The storable type the pending inputs describe, resolving a group choice.
|
||||
/// `None` while the
|
||||
/// choice is still incomplete, `Err` when the follow-up fields are filled
|
||||
/// in but wrong.
|
||||
fn canonical_type_input(&self, locale: crate::i18n::Locale) -> Result<Option<String>, String> {
|
||||
@@ -651,18 +578,6 @@ impl ColumnDraft {
|
||||
if column_type.is_empty() {
|
||||
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() {
|
||||
return Ok(None);
|
||||
}
|
||||
validate_decimal_arguments(locale, precision, scale)?;
|
||||
return Ok(Some(format!("{column_type}({precision},{scale})")));
|
||||
}
|
||||
if self.catalog.is_link(&column_type) {
|
||||
let target = self.link_table_input.trim().to_ascii_lowercase();
|
||||
return Ok((!target.is_empty()).then(|| format!("{column_type}({target})")));
|
||||
@@ -812,8 +727,6 @@ impl ColumnDraft {
|
||||
self.temporal_type_input.clear();
|
||||
self.gtin_type_input.clear();
|
||||
self.link_table_input.clear();
|
||||
self.decimal_precision_input.clear();
|
||||
self.decimal_scale_input.clear();
|
||||
self.indexing_input = "no".to_string();
|
||||
self.quantity_ledger_input = "no".to_string();
|
||||
self.required_input = "no".to_string();
|
||||
@@ -1286,67 +1199,6 @@ pub(crate) fn validate_table_name(
|
||||
None
|
||||
}
|
||||
|
||||
/// The precision and scale rules the server applies to `decimal(p,s)`:
|
||||
/// whole numbers, no sign, no leading zeros, `1 <= p` and `s <= p`.
|
||||
fn validate_decimal_arguments(
|
||||
locale: crate::i18n::Locale,
|
||||
precision: &str,
|
||||
scale: &str,
|
||||
) -> Result<(), String> {
|
||||
let precision = validate_decimal_number(locale, "label-precision", precision)?;
|
||||
let scale = validate_decimal_number(locale, "label-scale", scale)?;
|
||||
if precision < 1 {
|
||||
return Err(crate::tr!(locale, "error-precision-min"));
|
||||
}
|
||||
if scale > precision {
|
||||
return Err(crate::tr!(locale, "error-scale-gt-precision"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_decimal_number(
|
||||
locale: crate::i18n::Locale,
|
||||
label_key: &str,
|
||||
value: &str,
|
||||
) -> Result<u32, String> {
|
||||
let label = crate::tr!(locale, label_key);
|
||||
if value.is_empty() {
|
||||
return Err(crate::tr!(
|
||||
locale,
|
||||
"error-decimal-required",
|
||||
"label" => label,
|
||||
));
|
||||
}
|
||||
if value.starts_with('+') || value.starts_with('-') {
|
||||
return Err(crate::tr!(
|
||||
locale,
|
||||
"error-decimal-sign",
|
||||
"label" => label,
|
||||
));
|
||||
}
|
||||
if value.contains('.') {
|
||||
return Err(crate::tr!(
|
||||
locale,
|
||||
"error-decimal-whole",
|
||||
"label" => label,
|
||||
));
|
||||
}
|
||||
if value.len() > 1 && value.starts_with('0') {
|
||||
return Err(crate::tr!(
|
||||
locale,
|
||||
"error-decimal-leading-zeros",
|
||||
"label" => label,
|
||||
));
|
||||
}
|
||||
value.parse::<u32>().map_err(|_| {
|
||||
crate::tr!(
|
||||
locale,
|
||||
"error-decimal-whole",
|
||||
"label" => label,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// The seam where the rules above meet the generated request types.
|
||||
pub(crate) fn proto_columns(columns: &[ColumnDefinition]) -> Vec<ProtoColumnDefinition> {
|
||||
columns
|
||||
@@ -1372,7 +1224,6 @@ pub(crate) fn column_catalog(column_types: Vec<ProtoColumnType>) -> ColumnCatalo
|
||||
column_types
|
||||
.into_iter()
|
||||
.map(|column_type| ColumnType {
|
||||
parameterised: column_type.spelling() == ColumnTypeSpelling::Decimal,
|
||||
link: column_type.spelling() == ColumnTypeSpelling::Link,
|
||||
name: column_type.name,
|
||||
sql_type: column_type.sql_type,
|
||||
@@ -1454,12 +1305,6 @@ pub(crate) struct ColumnForm {
|
||||
#[serde(default)]
|
||||
pub link_table_input: String,
|
||||
#[serde(default)]
|
||||
pub decimal_precision_input: String,
|
||||
#[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,
|
||||
@@ -1503,9 +1348,6 @@ impl ColumnForm {
|
||||
temporal_type_input: self.temporal_type_input.clone(),
|
||||
gtin_type_input: self.gtin_type_input.clone(),
|
||||
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(),
|
||||
@@ -1606,7 +1448,6 @@ pub(crate) mod tests {
|
||||
sql_type: "TEXT".to_string(),
|
||||
declarable: true,
|
||||
compound: false,
|
||||
parameterised: false,
|
||||
link: false,
|
||||
requires_currency: false,
|
||||
creation_only: false,
|
||||
@@ -1690,10 +1531,6 @@ pub(crate) mod tests {
|
||||
sql_type: "DATE".to_string(),
|
||||
..grouped("date", "temporal")
|
||||
},
|
||||
ColumnType {
|
||||
parameterised: true,
|
||||
..numeric("decimal")
|
||||
},
|
||||
declarable("duration"),
|
||||
declarable("email_address"),
|
||||
grouped("gtin_8", "gtin"),
|
||||
@@ -1774,10 +1611,6 @@ 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()));
|
||||
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,7 +1636,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn temporal_gtin_and_decimal_pickers_resolve_to_canonical_types() {
|
||||
fn temporal_and_gtin_pickers_resolve_to_canonical_types() {
|
||||
let mut draft = draft();
|
||||
draft.name_input = "occurred_at".to_string();
|
||||
draft.type_input = "temporal".to_string();
|
||||
@@ -1824,15 +1657,6 @@ pub(crate) mod tests {
|
||||
draft.add_from_inputs(crate::i18n::Locale::default()).unwrap();
|
||||
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());
|
||||
draft.decimal_precision_input = "12".to_string();
|
||||
draft.decimal_scale_input = "3".to_string();
|
||||
draft.add_from_inputs(crate::i18n::Locale::default()).unwrap();
|
||||
assert_eq!(draft.added[2].data_type, "decimal(12,3)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1860,29 +1684,6 @@ pub(crate) mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// The precision and scale rules are the server's, so a draft that would
|
||||
/// be refused there is refused here first.
|
||||
#[test]
|
||||
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")] {
|
||||
draft.decimal_precision_input = precision.to_string();
|
||||
draft.decimal_scale_input = scale.to_string();
|
||||
assert!(
|
||||
draft.add_from_inputs(crate::i18n::Locale::default()).is_err(),
|
||||
"decimal({precision},{scale}) should be refused"
|
||||
);
|
||||
}
|
||||
|
||||
draft.decimal_precision_input = "10".to_string();
|
||||
draft.decimal_scale_input = "0".to_string();
|
||||
assert!(draft.add_from_inputs(crate::i18n::Locale::default()).is_ok());
|
||||
}
|
||||
|
||||
/// `duration` and `period` are storable types on their own — the picker
|
||||
/// offers them and nothing has to be resolved.
|
||||
#[test]
|
||||
@@ -1968,17 +1769,8 @@ pub(crate) mod tests {
|
||||
assert!(draft.add_from_inputs(crate::i18n::Locale::default()).is_ok());
|
||||
assert!(draft.added[0].quantity_ledger);
|
||||
|
||||
// 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();
|
||||
draft.quantity_ledger_input = "yes".to_string();
|
||||
assert!(draft.add_from_inputs(crate::i18n::Locale::default()).is_ok());
|
||||
|
||||
// And the hint under the input names exactly that set.
|
||||
assert_eq!(draft.quantity_ledger_types(), "BIGINT, DECIMAL, INT, MONEY");
|
||||
assert_eq!(draft.quantity_ledger_types(), "BIGINT, INT, MONEY, NUMERIC");
|
||||
}
|
||||
|
||||
/// A compound column is a definition row, not a column: it takes its
|
||||
@@ -2099,7 +1891,6 @@ pub(crate) mod tests {
|
||||
|
||||
assert_eq!(catalog.sql_type("instant"), "TIMESTAMPTZ(0)");
|
||||
assert_eq!(catalog.sql_type("phone_calling_code"), "INTEGER");
|
||||
assert_eq!(catalog.sql_type("decimal(12,3)"), "NUMERIC(12,3)");
|
||||
// A compound type has no column, so it has no SQL type of its own.
|
||||
assert_eq!(catalog.sql_type("accounting"), "");
|
||||
assert_eq!(catalog.sql_type("nonsense"), "");
|
||||
|
||||
@@ -127,22 +127,13 @@
|
||||
{% if page.draft.columns.show_link_target() %}<small>{{ nav.tr("td-link-alias-hint") }}</small>{% endif %}
|
||||
</label>
|
||||
{% endif %}
|
||||
<label>{{ nav.tr("td-fixed-decimal-access") }}
|
||||
<select name="fixed_decimal_input" hx-post="/admin/tables/builder" hx-trigger="change"
|
||||
hx-include="#table-form" hx-target="#builder" hx-swap="innerHTML"
|
||||
hx-vals='{"action": "refresh"}'>
|
||||
<option value="no" {% if !page.draft.columns.fixed_decimal_enabled() %}selected{% endif %}>{{ nav.tr("td-fixed-decimal-disabled") }}</option>
|
||||
<option value="yes" {% if page.draft.columns.fixed_decimal_enabled() %}selected{% endif %}>{{ nav.tr("td-fixed-decimal-enabled") }}</option>
|
||||
</select>
|
||||
<small>{{ nav.tr("td-fixed-decimal-access-hint") }}</small>
|
||||
</label>
|
||||
<label>{{ nav.tr("td-column-type") }}
|
||||
<select name="column_type_input" hx-post="/admin/tables/builder" hx-trigger="change"
|
||||
hx-include="#table-form" hx-target="#builder" hx-swap="innerHTML"
|
||||
hx-vals='{"action": "refresh"}'>
|
||||
<option value="">{{ nav.tr("td-choose-type") }}</option>
|
||||
{% for column_type in column_types %}
|
||||
<option value="{{ column_type }}" {% if page.draft.columns.type_input == *column_type %}selected{% endif %}>{% if *column_type == "link" %}{{ nav.tr("td-fk-link") }}{% else if *column_type == "numeric" %}{{ nav.tr("td-exact-decimal") }}{% else if *column_type == "decimal" %}{{ nav.tr("td-fixed-decimal-type") }}{% else %}{{ column_type }}{% endif %}</option>
|
||||
<option value="{{ column_type }}" {% if page.draft.columns.type_input == *column_type %}selected{% endif %}>{% if *column_type == "link" %}{{ nav.tr("td-fk-link") }}{% else %}{{ column_type }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
@@ -169,23 +160,6 @@
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
{% if page.draft.columns.show_decimal_arguments() %}
|
||||
<div class="field-note col-span-full">
|
||||
<span class="field-label">{{ nav.tr("td-fixed-decimal-warning-title") }}</span>
|
||||
<p class="hint">{{ nav.tr("td-fixed-decimal-warning") }}</p>
|
||||
</div>
|
||||
<label>{{ nav.tr("td-precision") }}
|
||||
<input name="decimal_precision_input" value="{{ page.draft.columns.decimal_precision_input }}"
|
||||
inputmode="numeric" placeholder="12">
|
||||
<small>{{ nav.tr("td-precision-hint") }}</small>
|
||||
</label>
|
||||
<label>{{ nav.tr("td-scale") }}
|
||||
<input name="decimal_scale_input" value="{{ page.draft.columns.decimal_scale_input }}"
|
||||
inputmode="numeric" placeholder="3">
|
||||
<small>{{ nav.tr("td-scale-hint") }}</small>
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
{% if page.draft.columns.show_link_target() %}
|
||||
<label>{{ nav.tr("td-referenced-table") }}
|
||||
<select name="link_table_input">
|
||||
|
||||
@@ -29,17 +29,6 @@
|
||||
<input name="column_name_input" value="{{ page.columns.name_input }}" placeholder="issued_on">
|
||||
{% if page.columns.show_link_target() %}<small>{{ nav.tr("td-link-alias-hint") }}</small>{% endif %}
|
||||
</label>
|
||||
<label>{{ nav.tr("td-fixed-decimal-access") }}
|
||||
<select name="fixed_decimal_input"
|
||||
hx-post="/admin/tables/columns/add/builder{{ page.selection.query() }}"
|
||||
hx-trigger="change" hx-include="#column-form"
|
||||
hx-target="#column-panel" hx-swap="innerHTML"
|
||||
hx-vals='{"action": "refresh"}'>
|
||||
<option value="no" {% if !page.columns.fixed_decimal_enabled() %}selected{% endif %}>{{ nav.tr("td-fixed-decimal-disabled") }}</option>
|
||||
<option value="yes" {% if page.columns.fixed_decimal_enabled() %}selected{% endif %}>{{ nav.tr("td-fixed-decimal-enabled") }}</option>
|
||||
</select>
|
||||
<small>{{ nav.tr("td-fixed-decimal-access-hint") }}</small>
|
||||
</label>
|
||||
<label>{{ nav.tr("td-column-type") }}
|
||||
<select name="column_type_input"
|
||||
hx-post="/admin/tables/columns/add/builder{{ page.selection.query() }}"
|
||||
@@ -48,7 +37,7 @@
|
||||
hx-vals='{"action": "refresh"}'>
|
||||
<option value="">{{ nav.tr("td-choose-type") }}</option>
|
||||
{% for column_type in column_types %}
|
||||
<option value="{{ column_type }}" {% if page.columns.type_input == *column_type %}selected{% endif %}>{% if *column_type == "link" %}{{ nav.tr("td-fk-link") }}{% else if *column_type == "numeric" %}{{ nav.tr("td-exact-decimal") }}{% else if *column_type == "decimal" %}{{ nav.tr("td-fixed-decimal-type") }}{% else %}{{ column_type }}{% endif %}</option>
|
||||
<option value="{{ column_type }}" {% if page.columns.type_input == *column_type %}selected{% endif %}>{% if *column_type == "link" %}{{ nav.tr("td-fk-link") }}{% else %}{{ column_type }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
@@ -75,23 +64,6 @@
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
{% if page.columns.show_decimal_arguments() %}
|
||||
<div class="field-note col-span-full">
|
||||
<span class="field-label">{{ nav.tr("td-fixed-decimal-warning-title") }}</span>
|
||||
<p class="hint">{{ nav.tr("td-fixed-decimal-warning") }}</p>
|
||||
</div>
|
||||
<label>{{ nav.tr("td-precision") }}
|
||||
<input name="decimal_precision_input" value="{{ page.columns.decimal_precision_input }}"
|
||||
inputmode="numeric" placeholder="12">
|
||||
<small>{{ nav.tr("td-precision-hint") }}</small>
|
||||
</label>
|
||||
<label>{{ nav.tr("td-scale") }}
|
||||
<input name="decimal_scale_input" value="{{ page.columns.decimal_scale_input }}"
|
||||
inputmode="numeric" placeholder="3">
|
||||
<small>{{ nav.tr("td-scale-hint") }}</small>
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
{% if page.columns.show_link_target() %}
|
||||
<label>{{ nav.tr("td-referenced-table") }}
|
||||
<select name="link_table_input">
|
||||
|
||||
Reference in New Issue
Block a user