Class: Secretariat::Invoice

Inherits:
Struct
  • Object
show all
Includes:
Versioner
Defined in:
lib/secretariat/invoice.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Versioner

#by_version

Instance Attribute Details

#basis_amountObject

Returns the value of attribute basis_amount

Returns:

  • (Object)

    the current value of basis_amount



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def basis_amount
  @basis_amount
end

#buyerObject

Returns the value of attribute buyer

Returns:

  • (Object)

    the current value of buyer



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def buyer
  @buyer
end

#currency_codeObject

Returns the value of attribute currency_code

Returns:

  • (Object)

    the current value of currency_code



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def currency_code
  @currency_code
end

#due_amountObject

Returns the value of attribute due_amount

Returns:

  • (Object)

    the current value of due_amount



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def due_amount
  @due_amount
end

#grand_total_amountObject

Returns the value of attribute grand_total_amount

Returns:

  • (Object)

    the current value of grand_total_amount



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def grand_total_amount
  @grand_total_amount
end

#idObject

Returns the value of attribute id

Returns:

  • (Object)

    the current value of id



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def id
  @id
end

#issue_dateObject

Returns the value of attribute issue_date

Returns:

  • (Object)

    the current value of issue_date



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def issue_date
  @issue_date
end

#line_itemsObject

Returns the value of attribute line_items

Returns:

  • (Object)

    the current value of line_items



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def line_items
  @line_items
end

Returns the value of attribute paid_amount

Returns:

  • (Object)

    the current value of paid_amount



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def paid_amount
  @paid_amount
end

#payment_textObject

Returns the value of attribute payment_text

Returns:

  • (Object)

    the current value of payment_text



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def payment_text
  @payment_text
end

#payment_typeObject

Returns the value of attribute payment_type

Returns:

  • (Object)

    the current value of payment_type



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def payment_type
  @payment_type
end

#sellerObject

Returns the value of attribute seller

Returns:

  • (Object)

    the current value of seller



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def seller
  @seller
end

#tax_amountObject

Returns the value of attribute tax_amount

Returns:

  • (Object)

    the current value of tax_amount



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def tax_amount
  @tax_amount
end

#tax_categoryObject

Returns the value of attribute tax_category

Returns:

  • (Object)

    the current value of tax_category



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def tax_category
  @tax_category
end

#tax_percentObject

Returns the value of attribute tax_percent

Returns:

  • (Object)

    the current value of tax_percent



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def tax_percent
  @tax_percent
end

#tax_reasonObject

Returns the value of attribute tax_reason

Returns:

  • (Object)

    the current value of tax_reason



20
21
22
# File 'lib/secretariat/invoice.rb', line 20

def tax_reason
  @tax_reason
end

Instance Method Details

#errorsObject



43
44
45
# File 'lib/secretariat/invoice.rb', line 43

def errors
  @errors
end

#namespaces(version: 1) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/secretariat/invoice.rb', line 89

def namespaces(version: 1)
  by_version(version,
    {
      'xmlns:ram' => 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12',
      'xmlns:udt' => 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15',
      'xmlns:rsm' => 'urn:ferd:CrossIndustryDocument:invoice:1p0',
      'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
    },
    {
      'xmlns:qdt' => 'urn:un:unece:uncefact:data:standard:QualifiedDataType:100',
      'xmlns:ram' => 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100',
      'xmlns:udt' => 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100',
      'xmlns:rsm' => 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100',
      'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
    }
  )
end

#payment_codeObject



58
59
60
# File 'lib/secretariat/invoice.rb', line 58

def payment_code
  PAYMENT_CODES[payment_type] || '1'
end

#tax_category_code(version: 2) ⇒ Object



51
52
53
54
55
56
# File 'lib/secretariat/invoice.rb', line 51

def tax_category_code(version: 2)
  if version == 1
    return TAX_CATEGORY_CODES_1[tax_category] || 'S'
  end
  TAX_CATEGORY_CODES[tax_category] || 'S'
end

#tax_reason_textObject



47
48
49
# File 'lib/secretariat/invoice.rb', line 47

def tax_reason_text
  tax_reason || TAX_EXEMPTION_REASONS[tax_category]
end

#to_xml(version: 1) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/secretariat/invoice.rb', line 107

def to_xml(version: 1)
  if version < 1 || version > 2
    raise 'Unsupported Document Version'
  end

  unless valid?
    raise ValidationError.new("Invoice is invalid", errors)
  end

  builder = Nokogiri::XML::Builder.new do |xml|

    root = by_version(version, 'CrossIndustryDocument', 'CrossIndustryInvoice')

    xml['rsm'].send(root, namespaces(version: version)) do

      context = by_version(version, 'SpecifiedExchangedDocumentContext', 'ExchangedDocumentContext')

      xml['rsm'].send(context) do
        xml['ram'].GuidelineSpecifiedDocumentContextParameter do
          version_id = by_version(version, 'urn:ferd:CrossIndustryDocument:invoice:1p0:comfort', 'urn:cen.eu:en16931:2017')
          xml['ram'].ID version_id
        end
      end

      header = by_version(version, 'HeaderExchangedDocument', 'ExchangedDocument')

      xml['rsm'].send(header) do
        xml['ram'].ID id
        if version == 1
          xml['ram'].Name "RECHNUNG"
        end
        xml['ram'].TypeCode '380' # TODO: make configurable
        xml['ram'].IssueDateTime do
          xml['udt'].DateTimeString(format: '102') do
            xml.text(issue_date.strftime("%Y%m%d"))
          end
        end
      end
      transaction = by_version(version, 'SpecifiedSupplyChainTradeTransaction', 'SupplyChainTradeTransaction')
      xml['rsm'].send(transaction) do

        if version == 2
          line_items.each_with_index do |item, i|
            item.to_xml(xml, i + 1, version: version) # one indexed
          end
        end

        trade_agreement = by_version(version, 'ApplicableSupplyChainTradeAgreement', 'ApplicableHeaderTradeAgreement')

        xml['ram'].send(trade_agreement) do
          xml['ram'].SellerTradeParty do
            seller.to_xml(xml, version: version)
          end
          xml['ram'].BuyerTradeParty do
            buyer.to_xml(xml, version: version)
          end
        end

        delivery = by_version(version, 'ApplicableSupplyChainTradeDelivery', 'ApplicableHeaderTradeDelivery')

        xml['ram'].send(delivery) do
          if version == 2
            xml['ram'].ShipToTradeParty do
              buyer.to_xml(xml, exclude_tax: true, version: version)
            end
          end
          xml['ram'].ActualDeliverySupplyChainEvent do
            xml['ram'].OccurrenceDateTime do
              xml['udt'].DateTimeString(format: '102') do
                xml.text(issue_date.strftime("%Y%m%d"))
              end
            end
          end
        end
        trade_settlement = by_version(version, 'ApplicableSupplyChainTradeSettlement', 'ApplicableHeaderTradeSettlement')
        xml['ram'].send(trade_settlement) do
          xml['ram'].InvoiceCurrencyCode currency_code
          xml['ram'].SpecifiedTradeSettlementPaymentMeans do
            xml['ram'].TypeCode payment_code
            xml['ram'].Information payment_text
          end
          xml['ram'].ApplicableTradeTax do

            Helpers.currency_element(xml, 'ram', 'CalculatedAmount', tax_amount, currency_code, add_currency: version == 1)
            xml['ram'].TypeCode 'VAT'
            if tax_reason_text && tax_reason_text != ''
              xml['ram'].ExemptionReason tax_reason_text
            end
            Helpers.currency_element(xml, 'ram', 'BasisAmount', basis_amount, currency_code, add_currency: version == 1)
            xml['ram'].CategoryCode tax_category_code(version: version)

            percent = by_version(version, 'ApplicablePercent', 'RateApplicablePercent')
            xml['ram'].send(percent, Helpers.format(tax_percent))
          end
          xml['ram'].SpecifiedTradePaymentTerms do
            xml['ram'].Description "Paid"
          end

          monetary_summation = by_version(version, 'SpecifiedTradeSettlementMonetarySummation', 'SpecifiedTradeSettlementHeaderMonetarySummation')

          xml['ram'].send(monetary_summation) do
            Helpers.currency_element(xml, 'ram', 'LineTotalAmount', basis_amount, currency_code, add_currency: version == 1)
            # TODO: Fix this!
            Helpers.currency_element(xml, 'ram', 'ChargeTotalAmount', BigDecimal(0), currency_code, add_currency: version == 1)
            Helpers.currency_element(xml, 'ram', 'AllowanceTotalAmount', BigDecimal(0), currency_code, add_currency: version == 1)
            Helpers.currency_element(xml, 'ram', 'TaxBasisTotalAmount', basis_amount, currency_code, add_currency: version == 1)
            Helpers.currency_element(xml, 'ram', 'TaxTotalAmount', tax_amount, currency_code, add_currency: true)
            Helpers.currency_element(xml, 'ram', 'GrandTotalAmount', grand_total_amount, currency_code, add_currency: version == 1)
            Helpers.currency_element(xml, 'ram', 'TotalPrepaidAmount', paid_amount, currency_code, add_currency: version == 1)
            Helpers.currency_element(xml, 'ram', 'DuePayableAmount', due_amount, currency_code, add_currency: version == 1)
          end
        end
        if version == 1
          line_items.each_with_index do |item, i|
            item.to_xml(xml, i + 1, version: version) # one indexed
          end
        end
      end
    end
  end
  builder.to_xml
end

#valid?Boolean

Returns:

  • (Boolean)


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/secretariat/invoice.rb', line 62

def valid?
  @errors = []
  tax = BigDecimal(tax_amount)
  basis = BigDecimal(basis_amount)
  calc_tax = basis * BigDecimal(tax_percent) / BigDecimal(100)
  calc_tax = calc_tax.round(2, :down)
  if tax != calc_tax
    @errors << "Tax amount and calculated tax amount deviate: #{tax} / #{calc_tax}"
    return false
  end
  grand_total = BigDecimal(grand_total_amount)
  calc_grand_total = basis + tax
  if grand_total != calc_grand_total
    @errors << "Grand total amount and calculated grand total amount deviate: #{grand_total} / #{calc_grand_total}"
    return false
  end
  line_item_sum = line_items.inject(BigDecimal(0)) do |m, item|
    m + BigDecimal(item.charge_amount)
  end
  if line_item_sum != basis
    @errors << "Line items do not add up to basis amount #{line_item_sum} / #{basis}"
    return false
  end
  return true
end