display what accouting would create

This commit is contained in:
Priec
2026-08-12 18:57:10 +02:00
parent 719bae0cd2
commit cdaa1b2b7d
11 changed files with 668 additions and 22 deletions

View File

@@ -18,6 +18,11 @@ use crate::{
schema::{ColumnCatalog, ColumnDraft, proto_columns, validate_identifier},
};
/// The compound type that also brings a system column with it. Which columns
/// it generates is the catalog's answer; the account foreign key is not one of
/// them, and [`TableDraft::preview_rows`] is where that is explained.
pub(crate) const ACCOUNTING_FIELD_TYPE: &str = "accounting";
/// One row of the "Table definition preview" — the schema as it will exist.
pub(crate) struct PreviewRow {
pub mark: String,
@@ -95,6 +100,15 @@ impl TableDraft {
Ok(format!("Column `{}` removed.", removed.name))
}
/// Moves one column one place up or down the list.
///
/// The display columns keep their own order, which is the order they were
/// chosen in rather than the order the columns are declared in, so nothing
/// here touches them.
pub(crate) fn move_column(&mut self, index: usize, offset: isize) -> Option<String> {
self.columns.move_column(index, offset)
}
/// Adds or removes one display-column candidate.
///
/// Index 0 is `id`, which is not a display column of its own: choosing it
@@ -238,7 +252,7 @@ impl TableDraft {
},
];
for column in &self.columns.added {
for (index, column) in self.columns.added.iter().enumerate() {
rows.push(PreviewRow {
mark: self
.row_display_position(&column.name)
@@ -249,6 +263,38 @@ impl TableDraft {
option: column.option_label(),
source: "user".to_string(),
});
// A compound column is a definition row: the columns the table
// really gets are the ones it expands into, created where the
// definition row sits. They are listed here so what a compound
// choice does is visible before the table exists.
for generated in self.columns.generated_columns_of(index) {
rows.push(PreviewRow {
mark: String::new(),
column: generated.name.clone(),
data_type: generated.data_type.clone(),
option: if generated.inherits_currency {
format!("{}, {}", column.currency, column.money_mode.label())
} else {
String::new()
},
source: "generated".to_string(),
});
}
// The one companion the catalog does not report, because it is a
// system column rather than a user one: ACCOUNTING's foreign key to
// the profile's accounts, which the data API exposes as the virtual
// `account` field.
if column.data_type == ACCOUNTING_FIELD_TYPE {
rows.push(PreviewRow {
mark: String::new(),
column: "account_id".to_string(),
data_type: "BIGINT".to_string(),
option: "not null, → accounts, written as account".to_string(),
source: "system".to_string(),
});
}
}
rows.push(PreviewRow {
@@ -471,6 +517,88 @@ mod tests {
assert_eq!(rows[0].mark, "[x]");
}
/// Moving a column changes the order the table is declared in and nothing
/// else — the display columns are shown in the order they were chosen in,
/// which is not this order.
#[test]
fn moving_a_column_leaves_the_display_columns_alone() {
let mut draft = draft_with_column("number", "text");
draft.columns.added.push(ColumnDefinition {
name: "issued_on".to_string(),
data_type: "date".to_string(),
indexed: false,
quantity_ledger: false,
money_mode: MoneyMode::Exact,
currency: String::new(),
});
draft.toggle_row_display_candidate(1); // number
draft.toggle_row_display_candidate(2); // issued_on
draft.move_column(0, 1).unwrap();
assert_eq!(
draft
.columns
.added
.iter()
.map(|column| column.name.as_str())
.collect::<Vec<_>>(),
["issued_on", "number"]
);
assert_eq!(draft.row_display_columns, vec!["number", "issued_on"]);
}
/// The point of the preview: a definition row is not one column, and the
/// columns it stands for are the ones the table will really have.
#[test]
fn the_preview_expands_an_accounting_column() {
let mut draft = draft_with_column("number", "text");
draft.columns.added.push(ColumnDefinition {
name: "accounting".to_string(),
data_type: "accounting".to_string(),
indexed: false,
quantity_ledger: false,
money_mode: MoneyMode::Rounded,
currency: "CZK".to_string(),
});
let rows = draft.preview_rows();
let columns = rows
.iter()
.map(|row| row.column.as_str())
.collect::<Vec<_>>();
assert_eq!(
columns,
vec![
"id",
"deleted",
"number",
"accounting",
"name",
"tax_point_date",
"debit",
"credit",
// The account foreign key, which the catalog does not report
// because it is a system column rather than a generated one.
"account_id",
"created_at",
]
);
let row = |name: &str| {
rows.iter()
.find(|row| row.column == name)
.unwrap_or_else(|| panic!("`{name}` is missing from the preview"))
};
assert_eq!(row("name").source, "generated");
assert_eq!(row("tax_point_date").data_type, "date");
// DEBIT and CREDIT are kept in the definition row's own currency.
assert_eq!(row("debit").option, "CZK, half-up");
assert_eq!(row("name").option, "");
assert_eq!(row("account_id").source, "system");
}
#[test]
fn indexed_columns_become_the_index_list() {
let mut draft = draft_with_column("number", "text");