Module: Ksef::FA3::RoundingInference
- Defined in:
- lib/ksef/fa3/rounding_inference.rb
Overview
Works out which rounding strategy produced a document's tax summaries.
The strategy is not a field in an FA(3) invoice. It is how the summaries were
computed, and the two legal ways of computing them — round each line, or round each
bucket once — differ by at most a grosz (DESIGN.md §7.3). So it cannot be read, only
inferred: whichever strategy reproduces the P_14_* values the document carries is
the one that made it.
Lives apart from Parser because it is the one piece of parsing that is not reading. Everything else there maps an element to a field; this reasons backwards from an arithmetic result to the procedure that produced it.
Constant Summary collapse
- TAX_ELEMENTS =
The tax-summary elements, taken from VatRate::BUCKETS rather than matched by name.
A
start_with?("P_14")scan would also pick upP_14_1Wand friends — the PLN-equivalent twins that exist for foreign-currency invoices — and those are not what either strategy computes. Deriving the list from the bucket table also means a schema revision cannot leave it stale. VatRate::BUCKETS.values.filter_map { |(_net, tax)| tax }.uniq.freeze
Class Method Summary collapse
-
.stated_from(node, &reader) ⇒ Hash{String => BigDecimal}
The
P_14_*values a document states. -
.strategy_for(lines, stated) ⇒ Invoice, Symbol
:per_linewins a tie, and a tie is the common case — the two agree on most invoices.
Class Method Details
.stated_from(node, &reader) ⇒ Hash{String => BigDecimal}
Returns the P_14_* values a document states.
56 57 58 59 60 61 |
# File 'lib/ksef/fa3/rounding_inference.rb', line 56 def stated_from(node, &reader) TAX_ELEMENTS.each_with_object({}) do |element, acc| value = reader.call(node, element) acc[element] = Formatting.decimal(value) unless value.nil? end end |
.strategy_for(lines, stated) ⇒ Invoice, Symbol
:per_line wins a tie, and a tie is the common case — the two agree on most
invoices. When neither matches, the document's summaries reconcile with its own
lines under no strategy at all. That is a business-rule violation (tier 3, still
unbuilt — docs/REFERENCE.md §15.6), not a parse failure: the document exists and may
well be sitting in KSeF, so it is left as :per_line and not argued with.
Decided from the lines alone, so the parser can settle the strategy before
constructing the invoice rather than constructing one and copying it.
That ordering is load-bearing. Invoice.scaled_gross drops stated_gross when it
equals what the rows derive — and "what the rows derive" depends on the strategy. A
parser that built with a provisional :per_line, let the constructor drop the
document's P_15, and then copied the invoice to :per_summary lost the figure
for good: the copy re-ran the constructor with stated_gross already nil. Przykład 1
would have shown it, except every one of the twenty-six pinned samples infers
:per_line, so the corpus is structurally blind to it (docs/REFERENCE.md §17.2).
49 50 51 52 53 |
# File 'lib/ksef/fa3/rounding_inference.rb', line 49 def strategy_for(lines, stated) return :per_line if stated.empty? || bucketed(lines, :per_line) == stated bucketed(lines, :per_summary) == stated ? :per_summary : :per_line end |