Class: Ksef::KsefNumber

Inherits:
Data
  • Object
show all
Defined in:
lib/ksef/ksef_number.rb,
lib/ksef/ksef_number.rb

Overview

Reopened rather than using a Data.define block so the constants land on the class.

Constant Summary collapse

LENGTH =

Both lengths the contract accepts. 35 is what KSeF 2.0 generates; 36 is the KSeF 1.0-era form, kept accepted for backward compatibility, with a hyphen splitting the technical part 6-6 (§13.1).

35
LEGACY_LENGTH =
36
LENGTHS =
[LENGTH, LEGACY_LENGTH].freeze
FORMAT =

Uppercase hex only, in both the technical part and the checksum. The optional hyphen is what admits the legacy form — taken from the contract's own pattern rather than invented.

/\A(\d{10})-(\d{8})-([0-9A-F]{6})-?([0-9A-F]{6})-([0-9A-F]{2})\z/
POLYNOMIAL =

CRC-8 with polynomial 0x07, initial value 0x00, no input or output reflection and no final XOR — computed over the first 32 characters, i.e. everything before the final hyphen (§13). Verified against the Ministry's own documented example, which doubles as this implementation's golden vector.

0x07
CHECKSUM_INPUT_LENGTH =
32

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#assigned_on ⇒ Object (readonly)

Returns the value of attribute assigned_on

Returns:

  • (Object) —

    the current value of assigned_on



27
28
29
# File 'lib/ksef/ksef_number.rb', line 27

def assigned_on
  @assigned_on
end

#checksum ⇒ Object (readonly)

Returns the value of attribute checksum

Returns:

  • (Object) —

    the current value of checksum



27
28
29
# File 'lib/ksef/ksef_number.rb', line 27

def checksum
  @checksum
end

#nip ⇒ Object (readonly)

Returns the value of attribute nip

Returns:

  • (Object) —

    the current value of nip



27
28
29
# File 'lib/ksef/ksef_number.rb', line 27

def nip
  @nip
end

#technical ⇒ Object (readonly)

Returns the value of attribute technical

Returns:

  • (Object) —

    the current value of technical



27
28
29
# File 'lib/ksef/ksef_number.rb', line 27

def technical
  @technical
end

#value ⇒ Object (readonly)

Returns the value of attribute value

Returns:

  • (Object) —

    the current value of value



27
28
29
# File 'lib/ksef/ksef_number.rb', line 27

def value
  @value
end

Class Method Details

.checksum_for(text) ⇒ String

The checksum a given number should carry, as the two uppercase hex characters the format uses.

Parameters:

  • text (String) —

    a full KSeF number, or just its first 32 characters

Returns:

  • (String)


102
103
104
# File 'lib/ksef/ksef_number.rb', line 102

def checksum_for(text)
  format("%02X", crc8(text.to_s[0, CHECKSUM_INPUT_LENGTH]))
end

.crc8(text) ⇒ Integer

Returns 0..255.

Parameters:

  • text (String) —

    the characters to run the CRC over

Returns:

  • (Integer) —

    0..255



83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/ksef/ksef_number.rb', line 83

def crc8(text)
  text.each_byte.reduce(0) do |crc, byte|
    register = crc ^ byte
    8.times do
      register = if register.nobits?(0x80)
                   (register << 1) & 0xFF
                 else
                   ((register << 1) ^ POLYNOMIAL) & 0xFF
                 end
    end
    register
  end
end

.parse(value) ⇒ KsefNumber

Parameters:

  • value (String)

Returns:

Raises:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/ksef/ksef_number.rb', line 55

def parse(value)
  text = value.to_s
  match = FORMAT.match(text) or raise ValidationError, malformed(text)
  nip, date, first, second, checksum = match.captures

  # Only the 35-character form has a known checksum rule (§13.1), so only it is
  # verified. Guessing the legacy rule would reject numbers the API accepts.
  verify_checksum!(text, checksum) if text.length == LENGTH

  new(
    value: text,
    nip: nip,
    assigned_on: parse_date(date, text),
    technical: "#{first}#{second}",
    checksum: checksum
  )
end

.valid?(value) ⇒ Boolean

Returns true when parse would succeed.

Returns:

  • (Boolean) —

    true when parse would succeed



74
75
76
77
78
79
# File 'lib/ksef/ksef_number.rb', line 74

def valid?(value)
  parse(value)
  true
rescue ValidationError
  false
end

Instance Method Details

#checksum_verified? ⇒ Boolean

Whether the CRC-8 was actually checked. False for a legacy number: §13's "first 32 characters" rule describes the 35-character layout, and nothing upstream states the input for the longer one — so it is accepted for lookup but not vouched for.

Returns:

  • (Boolean)


141
# File 'lib/ksef/ksef_number.rb', line 141

def checksum_verified? = !legacy?

#legacy? ⇒ Boolean

True for the KSeF 1.0-era 36-character form, which the API still accepts but never generates (§13.1).

Returns:

  • (Boolean)


136
# File 'lib/ksef/ksef_number.rb', line 136

def legacy? = value.length == LEGACY_LENGTH

#to_s ⇒ Object

The date KSeF accepted the invoice, which is its official receipt date — not the date it was downloaded, and not the invoice's own issue date.



145
# File 'lib/ksef/ksef_number.rb', line 145

def to_s = value

#to_str ⇒ Object

Convenience for the common case of holding a number only to look an invoice up.



148
# File 'lib/ksef/ksef_number.rb', line 148

def to_str = value