feat(arap): record an invoice's sends as invoice.sent (P4b S5)
The books never send invoices (A-4), but they must remember that one
went out: who sent it, how, to whom, when, whether it was the original
or a reminder, and the channel's own reference. That record is a fact
of the document's life, not a figure: a send moves no money, so it is
one journal line (`invoice.sent`) and no entry, and every derived
figure (outstanding, aging, DocState) is exactly as it was.
`Ledger::mark_sent` refuses a send on a void invoice (it should never
have reached the customer), a send dated before the invoice, no
recipients, and any recipient, reference or note that would put a full
TIN or control characters into the journal. Open, partial, paid and
written-off invoices can be sent (a copy for the customer's records, a
reminder).
The event is `documents_v2`'s (D-52): an older version refuses the
type rather than dropping the record. Replay restores it and the
projection keeps it in schema 16's `invoice_sends` table, so live,
replay and the viewer agree. The new old-books fixture `p4_s5_before`
was written by eeee9658 and opens to the same figures; a hand-written
send in `books_written_under_older_rules_still_open` proves an older
journal with the new line still opens.
Tests: mark_sent_waits_for_documents_v2, sends_replay_and_project,
a_send_changes_no_figure, mark_sent_on_void_refused, Op::MarkSent in
status_is_a_function_of_journal_and_date,
an_older_version_stops_at_invoice_sent.
This commit is contained in:
1 parent
ed637f0e27
commit
d1fb512e4b
16 files changed
+979
-5
No files matched your search
@@ -742,6 +742,36 @@ pub enum SentVia {
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl SentVia {
|
||||
/// Its key as the journal, the tools and the viewer name it: `email`,
|
||||
/// `mail`, `portal`, `hand`, or `other:NAME` for another channel,
|
||||
/// named (plan P4b S5).
|
||||
pub fn key(&self) -> String {
|
||||
match self {
|
||||
SentVia::Email => "email".to_string(),
|
||||
SentVia::Mail => "mail".to_string(),
|
||||
SentVia::Portal => "portal".to_string(),
|
||||
SentVia::Hand => "hand".to_string(),
|
||||
SentVia::Other(name) => format!("other:{name}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Self::key`] the other way; `None` for a channel this version
|
||||
/// doesn't know (a newer version's `sent_via`).
|
||||
pub fn parse(key: &str) -> Option<SentVia> {
|
||||
match key {
|
||||
"email" => Some(SentVia::Email),
|
||||
"mail" => Some(SentVia::Mail),
|
||||
"portal" => Some(SentVia::Portal),
|
||||
"hand" => Some(SentVia::Hand),
|
||||
other => other
|
||||
.strip_prefix("other:")
|
||||
.filter(|name| !name.trim().is_empty())
|
||||
.map(|name| SentVia::Other(name.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a send was the original or a reminder (plan P4b S5).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -752,6 +782,26 @@ pub enum SendKind {
|
||||
Reminder,
|
||||
}
|
||||
|
||||
impl SendKind {
|
||||
/// Its key as the journal, the tools and the viewer name it.
|
||||
pub fn key(self) -> &'static str {
|
||||
match self {
|
||||
SendKind::Original => "original",
|
||||
SendKind::Reminder => "reminder",
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Self::key`] the other way; `None` for a kind this version
|
||||
/// doesn't know.
|
||||
pub fn parse(key: &str) -> Option<SendKind> {
|
||||
match key {
|
||||
"original" => Some(SendKind::Original),
|
||||
"reminder" => Some(SendKind::Reminder),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One recorded send of an invoice (plan P4b S5): who sent it, how, to
|
||||
/// whom, and the channel's own reference. A send changes no figure.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
Reference in new issue
Block a user