ledger-import invoice_pdf (templates/invoice.typ): seller, bill-to, number, dates, terms, lines, total, each settled line and the balance due; money formatted in Rust (the template does no arithmetic), text as data, deterministic bytes. Stamps as a rubber stamp would mark it, in red: PAID (with the day it was paid in full: the invoice is then the customer's receipt), PARTIALLY PAID, PAST DUE, VOID; DRAFT in gray for S4's preview. ledger-package invoice_document builds it from the books as of a day: the derived figures (B-38), each payment that stands on its own line with its date, payment method and reference; re-created copies say so (the issued copy is kept from S4 on and never changes). books.document pdf hands it over as a download; the viewer's invoice page saves or opens it. The owner asked for the paid stamp with the payment's date and details (2026-10-24). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
12 files changed
+843
-4
No files matched your search
@@ -599,6 +599,10 @@ pub fn show_doc(ui: &mut Ui, b: &Books, doc: DocRef, shell: &mut Shell<'_>) {
|
||||
},
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
if let DocRef::Invoice(id) = doc {
|
||||
pdf_actions(ui, b, id);
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
if ui
|
||||
.link(format!(
|
||||
"{} Open the entry that recorded it (#{entry})",
|
||||
@@ -614,6 +618,51 @@ pub fn show_doc(ui: &mut Ui, b: &Books, doc: DocRef, shell: &mut Shell<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// "Save invoice PDF" and "Open": the invoice as of today, re-created
|
||||
/// from the books (ledger_package::invoice_document, the same data the
|
||||
/// server prints), saved in Downloads. Writes nothing to the books.
|
||||
fn pdf_actions(ui: &mut Ui, b: &Books, id: ledger_core::InvoiceId) {
|
||||
let p = pal(ui.ctx());
|
||||
let said = ui.id().with(("invoice-pdf-said", id));
|
||||
ui.horizontal(|ui| {
|
||||
let save = ui
|
||||
.button(format!("{} Save invoice PDF", icon::FILE_PDF))
|
||||
.on_hover_text("As of today, in your Downloads folder");
|
||||
let open = ui
|
||||
.button(format!("{} Open", icon::ARROW_SQUARE_OUT))
|
||||
.on_hover_text("As of today, in your Downloads folder, opened with its app");
|
||||
if save.clicked() || open.clicked() {
|
||||
let result = ledger_package::invoice_document(
|
||||
&b.ledger,
|
||||
&b.settings.company,
|
||||
id,
|
||||
b.today,
|
||||
b.today,
|
||||
)
|
||||
.and_then(|doc| {
|
||||
ledger_import::invoice_pdf::invoice_pdf(&doc).map_err(|e| e.to_string())
|
||||
})
|
||||
.and_then(|bytes| {
|
||||
let stem = format!("invoice-{id}");
|
||||
if open.clicked() {
|
||||
crate::export::save_and_open(&stem, "pdf", &bytes)
|
||||
} else {
|
||||
crate::export::save(&stem, "pdf", &bytes)
|
||||
}
|
||||
})
|
||||
.map(|path| path.display().to_string());
|
||||
ui.data_mut(|d| d.insert_temp(said, result));
|
||||
}
|
||||
});
|
||||
if let Some(result) = ui.data(|d| d.get_temp::<Result<String, String>>(said)) {
|
||||
let (text, color) = match result {
|
||||
Ok(path) => (format!("Saved {path}"), p.muted),
|
||||
Err(why) => (format!("Couldn't save it: {why}"), p.warning),
|
||||
};
|
||||
ui.add(egui::Label::new(egui::RichText::new(text).size(12.0).color(color)).wrap());
|
||||
}
|
||||
}
|
||||
|
||||
fn missing(ui: &mut Ui, title: &str) {
|
||||
widgets::card(ui, |ui| widgets::empty(ui, icon::RECEIPT, title, ""));
|
||||
}
|
||||
@@ -175,6 +175,8 @@ fn a_documents_page_shows_its_timeline() {
|
||||
);
|
||||
// Received: nothing stands.
|
||||
assert!(h.query_by_label("$0.00").is_some());
|
||||
// An invoice can be saved as PDF (a bill can't: it is the vendor's).
|
||||
assert!(h.query_by_label_contains("Save invoice PDF").is_some());
|
||||
// A timeline row opens its entry.
|
||||
h.get_by_label_contains(&format!("Payment (reversed later), entry #{pay},"))
|
||||
.click();
|
||||
|
||||
Reference in new issue
Block a user