feat: enhance UI and functionality for price rules, quotes, and suppliers

- Updated PriceRulesPage.vue to replace "Neu" button with a more intuitive "+" button for creating new rules.
- Enhanced QuotesPage.vue with improved handling of quote lines, including read-only states, unique line validation, and better formatting for monetary values.
- Added accordion sections for better organization of quote details and improved user experience.
- Updated SuppliersPage.vue to replace "Neu" button with a "+" button for adding new suppliers.
- Introduced new database migrations to add price category and default sales price fields to activities, and to include 'invoice_created' status in quotes.
This commit is contained in:
Torsten Schulz (local)
2026-06-04 21:47:42 +02:00
parent d5b6f39177
commit af928a838f
18 changed files with 970 additions and 185 deletions

View File

@@ -761,6 +761,15 @@ fn apply_quote_item_defaults(line: &mut QuoteItemForm, items: &[Item]) {
}
}
fn item_unit_label(item_id: &str, items: &[Item]) -> String {
items
.iter()
.find(|item| item.id == item_id)
.map(|item| item.unit.clone())
.filter(|unit| !unit.trim().is_empty())
.unwrap_or_else(|| "-".to_string())
}
fn apply_quote_customer_defaults(form: &mut QuoteForm, customers: &[Customer]) {
if let Some(customer) = customers.iter().find(|customer| customer.id == form.customer_id) {
form.customer_discount_percent = customer.standard_discount_percent.clone();
@@ -817,6 +826,9 @@ fn invoice_items_editor(
ui.label("Menge");
ui.text_edit_singleline(&mut line.quantity);
ui.end_row();
ui.label("Einheit");
ui.label(item_unit_label(&line.item_id, items));
ui.end_row();
ui.label("Preis");
ui.text_edit_singleline(&mut line.unit_price);
ui.end_row();
@@ -873,6 +885,9 @@ fn incoming_invoice_items_editor(
ui.label("Menge");
ui.text_edit_singleline(&mut line.quantity);
ui.end_row();
ui.label("Einheit");
ui.label(item_unit_label(line.item_id.as_deref().unwrap_or_default(), items));
ui.end_row();
ui.label("Preis");
ui.text_edit_singleline(&mut line.unit_price);
ui.end_row();
@@ -1617,7 +1632,7 @@ impl CompanyToolApp {
},
AdminEvent::ItemDeleted(result) => {
self.items_status = result
.map(|_| "Artikel deaktiviert.".to_string())
.map(|_| "Artikel gelöscht.".to_string())
.unwrap_or_else(|message| message);
self.load_items();
}
@@ -1711,7 +1726,7 @@ impl CompanyToolApp {
},
AdminEvent::ActivityDeleted(result) => {
self.activities_status = result
.map(|_| "Aktivität deaktiviert.".to_string())
.map(|_| "Aktivität gelöscht.".to_string())
.unwrap_or_else(|message| message);
self.load_activities();
}
@@ -4305,7 +4320,7 @@ impl CompanyToolApp {
if ui
.add_enabled(
self.selected_item_id.is_some(),
egui::Button::new("Deaktivieren"),
egui::Button::new("Löschen"),
)
.clicked()
{
@@ -4869,6 +4884,9 @@ impl CompanyToolApp {
ui.label("Menge");
ui.text_edit_singleline(&mut line.quantity);
ui.end_row();
ui.label("Einheit");
ui.label(item_unit_label(&line.item_id, &items));
ui.end_row();
ui.label("Preis");
ui.text_edit_singleline(&mut line.unit_price);
ui.end_row();
@@ -5245,9 +5263,6 @@ impl CompanyToolApp {
self.activity_form.activity_number.as_deref().unwrap_or(""),
));
});
form_row(ui, "Typ", |ui| {
ui.text_edit_singleline(&mut self.activity_form.activity_type);
});
form_row(ui, "Status", |ui| {
egui::ComboBox::from_id_salt("activity_status")
.selected_text(match self.activity_form.status.as_str() {
@@ -5270,6 +5285,34 @@ impl CompanyToolApp {
form_row(ui, "Priorität", |ui| {
ui.text_edit_singleline(&mut self.activity_form.priority);
});
form_row(ui, "Preiskategorie", |ui| {
egui::ComboBox::from_id_salt("activity_price_category")
.selected_text(match self.activity_form.price_category.as_str() {
"tag" => "Tag",
"pauschal" => "Pauschal",
_ => "h",
})
.show_ui(ui, |ui| {
ui.selectable_value(
&mut self.activity_form.price_category,
"h".to_string(),
"h",
);
ui.selectable_value(
&mut self.activity_form.price_category,
"tag".to_string(),
"Tag",
);
ui.selectable_value(
&mut self.activity_form.price_category,
"pauschal".to_string(),
"Pauschal",
);
});
});
form_row(ui, "Verkaufspreis", |ui| {
ui.text_edit_singleline(&mut self.activity_form.default_sales_price);
});
form_row(ui, "Beschreibung", |ui| {
ui.text_edit_multiline(&mut self.activity_form.body);
});
@@ -5280,7 +5323,7 @@ impl CompanyToolApp {
if ui
.add_enabled(
self.selected_activity_id.is_some(),
egui::Button::new("Deaktivieren"),
egui::Button::new("Löschen"),
)
.clicked()
{
@@ -6182,6 +6225,8 @@ struct Activity {
title: String,
body: String,
status: String,
price_category: String,
default_sales_price: Option<String>,
priority: String,
due_at: Option<String>,
}
@@ -6192,6 +6237,8 @@ struct ActivityForm {
title: String,
body: String,
status: String,
price_category: String,
default_sales_price: String,
priority: String,
due_at: Option<String>,
}
@@ -6199,10 +6246,12 @@ impl Default for ActivityForm {
fn default() -> Self {
Self {
activity_number: None,
activity_type: "task".to_string(),
activity_type: "work_step".to_string(),
title: String::new(),
body: String::new(),
status: "active".to_string(),
price_category: "h".to_string(),
default_sales_price: "0".to_string(),
priority: "normal".to_string(),
due_at: None,
}
@@ -6216,6 +6265,8 @@ impl From<&Activity> for ActivityForm {
title: value.title.clone(),
body: value.body.clone(),
status: value.status.clone(),
price_category: value.price_category.clone(),
default_sales_price: value.default_sales_price.clone().unwrap_or_default(),
priority: value.priority.clone(),
due_at: value.due_at.clone(),
}