From 5e9b86c1fb459511948b7660380f737590c3fafa Mon Sep 17 00:00:00 2001 From: Filipriec Date: Mon, 24 Aug 2026 21:31:06 +0200 Subject: [PATCH] removing decimal with precision and decimal count --- AGENTS.md | 4 + README.md | 4 - client | 2 +- client-server-drift.md | 4 +- common/proto/table_definition.proto | 11 +- common/src/decimal.rs | 20 +- common/src/proto/descriptor.bin | Bin 194703 -> 194257 bytes common/src/proto/komp_ac.table_definition.rs | 12 +- common/src/search.rs | 8 +- common/src/search_light.rs | 8 +- docs/fixed-scale-decimal.md | 26 -- invoice-print-setup.md | 4 +- server | 2 +- test_luna/README.md | 14 +- web/locales/cs/main.ftl | 23 -- web/locales/en/main.ftl | 23 -- web/locales/sk/main.ftl | 23 -- web/src/pages/add_table/state.rs | 9 - web/src/pages/add_table/ui.rs | 25 +- web/src/schema/mod.rs | 237 ++---------------- web/templates/pages/add_table/builder.html | 28 +-- .../admin/table_definition/column_panel.html | 30 +-- 22 files changed, 50 insertions(+), 467 deletions(-) create mode 100644 AGENTS.md delete mode 100644 docs/fixed-scale-decimal.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..03ca2431 --- /dev/null +++ b/AGENTS.md @@ -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. diff --git a/README.md b/README.md index 5522e43c..6e1f5d06 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/client b/client index 1e0b6f9e..fbf3ff93 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit 1e0b6f9edea67aaafc99acdbec0cd55dcadb8f05 +Subproject commit fbf3ff93bd5486a893203d03ca422474201c0649 diff --git a/client-server-drift.md b/client-server-drift.md index 0ce2d7b9..9ff94b9a 100644 --- a/client-server-drift.md +++ b/client-server-drift.md @@ -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. diff --git a/common/proto/table_definition.proto b/common/proto/table_definition.proto index 73247c71..a53446e7 100644 --- a/common/proto/table_definition.proto +++ b/common/proto/table_definition.proto @@ -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 diff --git a/common/src/decimal.rs b/common/src/decimal.rs index 00d1ac0e..cc1f817e 100644 --- a/common/src/decimal.rs +++ b/common/src/decimal.rs @@ -37,17 +37,12 @@ pub fn parse_decimal_exact(value: &str) -> Result { 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}"); } } diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 3f754625449de26924569167fc425a9f7ccfe55b..5e87427806675ceaf6e2c2824ebbf58eec99d25d 100644 GIT binary patch delta 6388 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 aD*OuogvKeAu&+XZP&n04_MLeC=KlbI@H1lo delta 6781 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 diff --git a/common/src/proto/komp_ac.table_definition.rs b/common/src/proto/komp_ac.table_definition.rs index e8163110..e47565e4 100644 --- a/common/src/proto/komp_ac.table_definition.rs +++ b/common/src/proto/komp_ac.table_definition.rs @@ -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 { match value { "COLUMN_TYPE_SPELLING_BARE" => Some(Self::Bare), - "COLUMN_TYPE_SPELLING_DECIMAL" => Some(Self::Decimal), "COLUMN_TYPE_SPELLING_LINK" => Some(Self::Link), _ => None, } diff --git a/common/src/search.rs b/common/src/search.rs index 8c7f0309..358d358c 100644 --- a/common/src/search.rs +++ b/common/src/search.rs @@ -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 { 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::() .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" - ); } } diff --git a/common/src/search_light.rs b/common/src/search_light.rs index 743765c0..7242de63 100644 --- a/common/src/search_light.rs +++ b/common/src/search_light.rs @@ -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 { 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::() .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" - ); } } diff --git a/docs/fixed-scale-decimal.md b/docs/fixed-scale-decimal.md deleted file mode 100644 index ecf03c87..00000000 --- a/docs/fixed-scale-decimal.md +++ /dev/null @@ -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. diff --git a/invoice-print-setup.md b/invoice-print-setup.md index 373ae277..35871cbf 100644 --- a/invoice-print-setup.md +++ b/invoice-print-setup.md @@ -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} diff --git a/server b/server index cf752c7e..4ea5a4e8 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit cf752c7ee96c3019573e7c2f5f54571e5c4f93fc +Subproject commit 4ea5a4e80dbbee9bd3e80777ca757d4d09765ed7 diff --git a/test_luna/README.md b/test_luna/README.md index 344c9230..6e4f860a 100644 --- a/test_luna/README.md +++ b/test_luna/README.md @@ -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: diff --git a/web/locales/cs/main.ftl b/web/locales/cs/main.ftl index 3c037f1d..76753864 100644 --- a/web/locales/cs/main.ftl +++ b/web/locales/cs/main.ftl @@ -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 diff --git a/web/locales/en/main.ftl b/web/locales/en/main.ftl index 4781bb54..a6a402a9 100644 --- a/web/locales/en/main.ftl +++ b/web/locales/en/main.ftl @@ -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 diff --git a/web/locales/sk/main.ftl b/web/locales/sk/main.ftl index fed69c74..28ea08c2 100644 --- a/web/locales/sk/main.ftl +++ b/web/locales/sk/main.ftl @@ -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 diff --git a/web/src/pages/add_table/state.rs b/web/src/pages/add_table/state.rs index 69dbde55..c8cdc0c6 100644 --- a/web/src/pages/add_table/state.rs +++ b/web/src/pages/add_table/state.rs @@ -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(), diff --git a/web/src/pages/add_table/ui.rs b/web/src/pages/add_table/ui.rs index 459766fb..69f6916c 100644 --- a/web/src/pages/add_table/ui.rs +++ b/web/src/pages/add_table/ui.rs @@ -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#"