Class: Ksef::FA3::Correction

Inherits:
Data
  • Object
show all
Includes:
Canonical
Defined in:
lib/ksef/fa3/correction.rb,
lib/ksef/fa3/correction.rb

Overview

Construction, canonicalisation and serialisation for Correction.

Constant Summary collapse

CORRECTING_TYPES =

The RodzajFaktury values these elements belong to — stated by the XSD itself, in the annotation on the anonymous sequence that holds them: "Dane dla przypadków, gdy pole RodzajFaktury przyjmuje wartości KOR, KOR_ZAL lub KOR_ROZ".

What the schema cannot do is enforce it: the group sits in the same sequence whatever the type, so a VAT invoice carrying DaneFaKorygowanej validates clean. That gap is tier 1's, and ModelValidator fills it (docs/REFERENCE.md §8.4).

%w[KOR KOR_ZAL KOR_ROZ].freeze
NAMES_NOTHING =
"A correction must name at least one corrected invoice. " \
"`DaneFaKorygowanej` is mandatory once any correction element is " \
"present, so a correction without one cannot be serialised."

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Canonical

#with

Constructor Details

#initialize(corrected:, reason: nil, effect: nil, period: nil, corrected_number: nil, previous_seller: nil, previous_buyers: [], paid_before: nil, exchange_rate_before: nil) ⇒ Correction

Returns a new instance of Correction.

Parameters:

  • corrected (CorrectedInvoice, Array<CorrectedInvoice>) —

    DaneFaKorygowanej, at least one

  • reason (String, nil) (defaults to: nil) —

    PrzyczynaKorekty — why the correction was issued

  • effect (Integer, String, nil) (defaults to: nil) —

    TypKorekty, when the correction takes effect in the VAT register: 1 at the date the corrected invoice was recorded, 2 at the date this correction was issued, 3 at some other date — including the case where different rows take effect on different dates

  • period (String, nil) (defaults to: nil) —

    OkresFaKorygowanej — the period a collective discount under art. 106j ust. 3 relates to

  • corrected_number (String, nil) (defaults to: nil) —

    NrFaKorygowany — the right number, for the case where the thing being corrected is the corrected invoice's own number. The wrong one stays in NrFaKorygowanej, so the pair reads "this, not that"

  • previous_seller (Subject, nil) (defaults to: nil) —

    Podmiot1K — the seller as the corrected invoice stated them

  • previous_buyers (Subject, Array<Subject>) (defaults to: []) —

    Podmiot2K — the buyer, and any additional buyers, as the corrected invoice stated them

Raises:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/ksef/fa3/correction.rb', line 65

def initialize(corrected:, reason: nil, effect: nil, period: nil, corrected_number: nil,
               previous_seller: nil, previous_buyers: [], paid_before: nil,
               exchange_rate_before: nil)
  entries = self.class.wrap(corrected)
  raise ValidationError, NAMES_NOTHING if entries.empty?

  # `dup` before freezing: `Array(x)` returns `x` itself when it is already an Array, so
  # freezing in place froze the *caller's* array and made their next `<<` raise.
  super(
    previous_seller: previous_seller,
    corrected: entries.dup.freeze,
    previous_buyers: self.class.wrap(previous_buyers).dup.freeze,
    **self.class.canonical_fields(reason, period, corrected_number, effect),
    **self.class.canonical_amounts(paid_before, exchange_rate_before)
  )
end

Instance Attribute Details

#corrected ⇒ Object (readonly)

Returns the value of attribute corrected

Returns:

  • (Object) —

    the current value of corrected



25
26
27
# File 'lib/ksef/fa3/correction.rb', line 25

def corrected
  @corrected
end

#corrected_number ⇒ Object (readonly)

Returns the value of attribute corrected_number

Returns:

  • (Object) —

    the current value of corrected_number



25
26
27
# File 'lib/ksef/fa3/correction.rb', line 25

def corrected_number
  @corrected_number
end

#effect ⇒ Object (readonly)

Returns the value of attribute effect

Returns:

  • (Object) —

    the current value of effect



25
26
27
# File 'lib/ksef/fa3/correction.rb', line 25

def effect
  @effect
end

#exchange_rate_before ⇒ Object (readonly)

Returns the value of attribute exchange_rate_before

Returns:

  • (Object) —

    the current value of exchange_rate_before



25
26
27
# File 'lib/ksef/fa3/correction.rb', line 25

def exchange_rate_before
  @exchange_rate_before
end

Returns the value of attribute paid_before

Returns:

  • (Object) —

    the current value of paid_before



25
26
27
# File 'lib/ksef/fa3/correction.rb', line 25

def paid_before
  @paid_before
end

#period ⇒ Object (readonly)

Returns the value of attribute period

Returns:

  • (Object) —

    the current value of period



25
26
27
# File 'lib/ksef/fa3/correction.rb', line 25

def period
  @period
end

#previous_buyers ⇒ Object (readonly)

Returns the value of attribute previous_buyers

Returns:

  • (Object) —

    the current value of previous_buyers



25
26
27
# File 'lib/ksef/fa3/correction.rb', line 25

def previous_buyers
  @previous_buyers
end

#previous_seller ⇒ Object (readonly)

Returns the value of attribute previous_seller

Returns:

  • (Object) —

    the current value of previous_seller



25
26
27
# File 'lib/ksef/fa3/correction.rb', line 25

def previous_seller
  @previous_seller
end

#reason ⇒ Object (readonly)

Returns the value of attribute reason

Returns:

  • (Object) —

    the current value of reason



25
26
27
# File 'lib/ksef/fa3/correction.rb', line 25

def reason
  @reason
end

Class Method Details

.canonical_amounts(paid_before, exchange_rate_before) ⇒ Object

P_15ZK is TKwotowy, so two places; KursWalutyZK is TIlosci, so six. Six is a ceiling, not a preference — a seven-place rate is not a value FA(3) can express, and #to_fa3 rounds it away at emit. Storing it unrounded made the model hold a figure the document cannot carry, which is §8.2b's rule broken in the one place the rest of the model keeps it: a Correction built with 4.12345678 emitted 4.123457 and then failed DESIGN.md §7.6's round-trip law against itself.



100
101
102
103
104
105
# File 'lib/ksef/fa3/correction.rb', line 100

def self.canonical_amounts(paid_before, exchange_rate_before)
  {
    paid_before: paid_before && Formatting.decimal(paid_before).round(Formatting::AMOUNT_SCALE),
    exchange_rate_before: exchange_rate_before && scaled_rate(exchange_rate_before)
  }
end

.canonical_fields(reason, period, corrected_number, effect) ⇒ Object

TypKorekty restricts xsd:integer, whose value space is integers — so "03" and 3 denote the same thing and the model stores the value, not the lexical form (§8.2b). Contrast Line#vat_rate, a token whose value space is strings.



85
86
87
88
89
90
91
92
# File 'lib/ksef/fa3/correction.rb', line 85

def self.canonical_fields(reason, period, corrected_number, effect)
  {
    reason: Formatting.text(reason),
    period: Formatting.text(period),
    corrected_number: Formatting.text(corrected_number),
    effect: effect.nil? ? nil : Formatting.integer(effect)
  }
end

.scaled_rate(value) ⇒ Object

TIlosci again, so Formatting::QUANTITY_SCALE — a rate is not an amount, but it is not unbounded either.



109
# File 'lib/ksef/fa3/correction.rb', line 109

def self.scaled_rate(value) = Formatting.decimal(value).round(Formatting::QUANTITY_SCALE)

.wrap(value) ⇒ Object

Array() is not usable here: it splats a Hash into an array of pairs, so Correction.new(corrected: { number: ..., issue_date: ... }) — a natural mistake — was accepted and then made #to_xml raise NoMethodError on an Array.



114
115
116
117
118
# File 'lib/ksef/fa3/correction.rb', line 114

def self.wrap(value)
  return [] if value.nil?

  value.is_a?(Array) ? value : [value]
end

Instance Method Details

#to_fa3 ⇒ Object



120
121
122
123
124
125
126
127
128
# File 'lib/ksef/fa3/correction.rb', line 120

def to_fa3
  {
    "PrzyczynaKorekty" => reason,
    "TypKorekty" => effect&.to_s,
    "DaneFaKorygowanej" => corrected.map(&:to_fa3),
    "OkresFaKorygowanej" => period,
    "NrFaKorygowany" => corrected_number
  }.merge(parties).merge(amounts).compact
end