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

#buyer_referenceObject

Returns the value of attribute buyer_reference

Returns:

  • (Object)

    the current value of buyer_reference



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

def buyer_reference
  @buyer_reference
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_due_dateObject

Returns the value of attribute payment_due_date

Returns:

  • (Object)

    the current value of payment_due_date



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

def payment_due_date
  @payment_due_date
end

#payment_ibanObject

Returns the value of attribute payment_iban

Returns:

  • (Object)

    the current value of payment_iban



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

def payment_iban
  @payment_iban
end

#payment_terms_textObject

Returns the value of attribute payment_terms_text

Returns:

  • (Object)

    the current value of payment_terms_text



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

def payment_terms_text
  @payment_terms_text
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

#service_period_endObject

Returns the value of attribute service_period_end

Returns:

  • (Object)

    the current value of service_period_end



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

def service_period_end
  @service_period_end
end

#service_period_startObject

Returns the value of attribute service_period_start

Returns:

  • (Object)

    the current value of service_period_start



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

def service_period_start
  @service_period_start
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



49
50
51
# File 'lib/secretariat/invoice.rb', line 49

def errors
  @errors
end

#namespaces(version: 1) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/secretariat/invoice.rb', line 117

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



74
75
76
# File 'lib/secretariat/invoice.rb', line 74

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

#tax_category_code(version: 2) ⇒ Object



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

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



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

def tax_reason_text
  tax_reason || TAX_EXEMPTION_REASONS[tax_category]
end

#taxesObject



64
65
66
67
68
69
70
71
72
# File 'lib/secretariat/invoice.rb', line 64

def taxes
  taxes = {}
  line_items.each do |line_item|
    taxes[line_item.tax_percent] = Tax.new(tax_percent: BigDecimal(line_item.tax_percent)) if taxes[line_item.tax_percent].nil?
    taxes[line_item.tax_percent].tax_amount += BigDecimal(line_item.tax_amount)
    taxes[line_item.tax_percent].base_amount += BigDecimal(line_item.net_amount) * line_item.quantity
  end
  taxes.values
end

#to_xml(version: 1, validate: true) ⇒ Object



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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/secretariat/invoice.rb', line 135

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

  if validate && !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, validate: validate) # one indexed
          end
        end

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

        xml['ram'].send(trade_agreement) do
          if buyer_reference
            xml['ram'].BuyerReference buyer_reference
          end
          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
            if payment_iban
              xml['ram'].PayeePartyCreditorFinancialAccount do
                xml['ram'].IBANID payment_iban
              end
            end
          end
          taxes.each do |tax|
            xml['ram'].ApplicableTradeTax do
              Helpers.currency_element(xml, 'ram', 'CalculatedAmount', tax.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', tax.base_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.tax_percent))
            end
          end
          if version == 2 && service_period_start && service_period_end
            xml['ram'].BillingSpecifiedPeriod do
              xml['ram'].StartDateTime do
                Helpers.date_element(xml, service_period_start)
              end
              xml['ram'].EndDateTime do
                Helpers.date_element(xml, service_period_end)
              end
            end
          end
          xml['ram'].SpecifiedTradePaymentTerms do
            xml['ram'].Description payment_terms_text || "Paid"
            if payment_due_date
              xml['ram'].DueDateDateTime do
                Helpers.date_element(xml, payment_due_date)
              end
            end
          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)


78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/secretariat/invoice.rb', line 78

def valid?
  @errors = []
  tax = BigDecimal(tax_amount)
  basis = BigDecimal(basis_amount)
  summed_tax_amount = taxes.sum(&:tax_amount)
  if tax != summed_tax_amount
    @errors << "Tax amount and summed tax amounts deviate: #{tax_amount} / #{summed_tax_amount}"
    return false
  end
  summed_tax_base_amount = taxes.sum(&:base_amount)
  if basis != summed_tax_base_amount
    @errors << "Base amount and summed tax base amount deviate: #{basis} / #{summed_tax_base_amount}"
    return false
  end
  taxes.each do |tax|
    calc_tax = tax.base_amount * BigDecimal(tax.tax_percent) / BigDecimal(100)
    calc_tax = calc_tax.round(2, :down)
    if tax.tax_amount != calc_tax
      @errors << "Tax amount and calculated tax amount deviate for rate #{tax.tax_percent}: #{tax.tax_amount} / #{calc_tax}"
      return false
    end
  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