sunat_invoice 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/README.mkd +1 -25
  4. data/lib/sunat_invoice/client.rb +3 -6
  5. data/lib/sunat_invoice/clients/consult_client.rb +17 -0
  6. data/lib/sunat_invoice/clients/invoice_client.rb +11 -3
  7. data/lib/sunat_invoice/configuration.rb +1 -1
  8. data/lib/sunat_invoice/credit_note.rb +59 -0
  9. data/lib/sunat_invoice/credit_note_line.rb +15 -0
  10. data/lib/sunat_invoice/customer.rb +1 -0
  11. data/lib/sunat_invoice/daily_summary.rb +46 -0
  12. data/lib/sunat_invoice/debit_note.rb +33 -0
  13. data/lib/sunat_invoice/debit_note_line.rb +15 -0
  14. data/lib/sunat_invoice/invoice.rb +23 -144
  15. data/lib/sunat_invoice/item.rb +40 -26
  16. data/lib/sunat_invoice/line.rb +18 -0
  17. data/lib/sunat_invoice/provider.rb +20 -14
  18. data/lib/sunat_invoice/response_parser.rb +64 -0
  19. data/lib/sunat_invoice/signature.rb +29 -15
  20. data/lib/sunat_invoice/summary_line.rb +75 -0
  21. data/lib/sunat_invoice/tax.rb +6 -1
  22. data/lib/sunat_invoice/trade_calculations.rb +68 -0
  23. data/lib/sunat_invoice/trade_document.rb +85 -0
  24. data/lib/sunat_invoice/utils.rb +48 -9
  25. data/lib/sunat_invoice/voided.rb +22 -0
  26. data/lib/sunat_invoice/voided_line.rb +20 -0
  27. data/lib/sunat_invoice/xml_document.rb +52 -0
  28. data/lib/sunat_invoice.rb +14 -0
  29. data/sunat_invoice.gemspec +5 -5
  30. data/test/credit_note_test.rb +15 -0
  31. data/test/daily_summary_test.rb +35 -0
  32. data/test/debit_note_test.rb +15 -0
  33. data/test/factories.rb +70 -0
  34. data/test/fixtures/responses/invoice_success.xml +2 -0
  35. data/test/fixtures/responses/summary_success.xml +2 -0
  36. data/test/fixtures/responses/ticket_invalid.xml +2 -0
  37. data/test/helper.rb +1 -0
  38. data/test/invoice_client_test.rb +68 -16
  39. data/test/invoice_test.rb +19 -10
  40. data/test/item_test.rb +3 -2
  41. data/test/response_parser_test.rb +28 -0
  42. data/test/support/response_helper.rb +18 -0
  43. data/test/support/signature_helper.rb +15 -8
  44. metadata +34 -13
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: false
2
+
3
+ module SunatInvoice
4
+ class InvalidResponseParser < StandardError; end
5
+
6
+ class ResponseParser
7
+ attr_reader :cdr, :status_code, :document_number, :message, :ticket
8
+
9
+ STATUS_CODES = {
10
+ 0 => 'process success',
11
+ 99 => 'in process',
12
+ 98 => 'process with errors'
13
+ }.freeze
14
+
15
+ ALLOWED_PARSERS = %w[invoice summary status].freeze
16
+ VALID_PROCESS = %w[0 99].freeze
17
+
18
+ def initialize(body, parser_type)
19
+ # body: SOAP body as a Hash. Typically Savon Response body.
20
+ # parser_type: kind of parser to use.
21
+ raise InvalidResponseParser unless ALLOWED_PARSERS.include?(parser_type)
22
+ send("parse_#{parser_type}", body)
23
+ end
24
+
25
+ private
26
+
27
+ def parse_invoice(body)
28
+ encrypted_zip = body[:send_bill_response][:application_response]
29
+ decrypt_zip(encrypted_zip)
30
+ end
31
+
32
+ def parse_xml(cdr_xml)
33
+ @cdr = Nokogiri::XML(cdr_xml)
34
+ response_node = @cdr.at('//cac:DocumentResponse/cac:Response')
35
+ @status_code = response_node.at('//cbc:ResponseCode').content
36
+ @document_number = response_node.at('//cbc:ReferenceID').content
37
+ @message = response_node.at('//cbc:Description').content
38
+ end
39
+
40
+ def parse_summary(body)
41
+ @ticket = body[:send_summary_response][:ticket]
42
+ end
43
+
44
+ def parse_status(body)
45
+ status_hash = body[:get_status_response][:status]
46
+ @status_code = status_hash[:status_code]
47
+ if VALID_PROCESS.include?(status_code)
48
+ encrypted_zip = status_hash[:content]
49
+ decrypt_zip(encrypted_zip)
50
+ else
51
+ @message = status_hash[:content]
52
+ end
53
+ end
54
+
55
+ def decrypt_zip(encrypted_zip)
56
+ decoded = Base64.decode64(encrypted_zip)
57
+ Zip::InputStream.open(StringIO.new(decoded)) do |io|
58
+ while (entry = io.get_next_entry)
59
+ parse_xml(io.read) if entry.name.include?('.xml')
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -18,24 +18,34 @@ module SunatInvoice
18
18
  def signer_data(xml)
19
19
  xml['cac'].Signature do
20
20
  xml['cbc'].ID provider.signature_id
21
- xml['cac'].SignatoryParty do
22
- xml['cac'].PartyIdentification do
23
- xml['cbc'].ID provider.ruc
24
- end
25
- xml['cac'].PartyName do
26
- xml['cbc'].Name provider.name
27
- end
21
+ build_signatory_party(xml)
22
+ build_digital_attachment(xml)
23
+ end
24
+ end
25
+
26
+ def build_signatory_party(xml)
27
+ xml['cac'].SignatoryParty do
28
+ xml['cac'].PartyIdentification do
29
+ xml['cbc'].ID provider.ruc
28
30
  end
29
- xml['cac'].DigitalSignatureAttachment do
30
- xml['cac'].ExternalReference do
31
- xml['cbc'].URI "##{provider.signature_location_id}"
32
- end
31
+ xml['cac'].PartyName do
32
+ xml['cbc'].Name provider.name
33
+ end
34
+ end
35
+ end
36
+
37
+ def build_digital_attachment(xml)
38
+ xml['cac'].DigitalSignatureAttachment do
39
+ xml['cac'].ExternalReference do
40
+ xml['cbc'].URI "##{provider.signature_location_id}"
33
41
  end
34
42
  end
35
43
  end
36
44
 
37
45
  def sign(invoice_xml)
38
- Xmldsig::SignedDocument.new(invoice_xml, id_attr: provider.signature_location_id).sign(private_key)
46
+ options = { id_attr: provider.signature_location_id }
47
+ doc = Xmldsig::SignedDocument.new(invoice_xml, options)
48
+ doc.sign(private_key)
39
49
  end
40
50
 
41
51
  def signature_ext(xml)
@@ -52,15 +62,19 @@ module SunatInvoice
52
62
  xml['ds'].CanonicalizationMethod Algorithm: C14N_ALGORITHM
53
63
  xml['ds'].SignatureMethod Algorithm: SIGNATURE_ALGORITHM
54
64
  xml['ds'].Reference URI: '' do
55
- xml['ds'].Transforms do
56
- xml['ds'].Transform Algorithm: TRANSFORMATION_ALGORITHM
57
- end
65
+ build_transforms(xml)
58
66
  xml['ds'].DigestMethod Algorithm: DIGEST_ALGORITHM
59
67
  xml['ds'].DigestValue
60
68
  end
61
69
  end
62
70
  end
63
71
 
72
+ def build_transforms(xml)
73
+ xml['ds'].Transforms do
74
+ xml['ds'].Transform Algorithm: TRANSFORMATION_ALGORITHM
75
+ end
76
+ end
77
+
64
78
  def signature_value(xml)
65
79
  xml['ds'].SignatureValue
66
80
  xml['ds'].KeyInfo do
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'line'
4
+
5
+ module SunatInvoice
6
+ class SummaryLine < Line
7
+ attr_accessor :document_type, :document_serial, :start_document_number,
8
+ :end_document_number, :total_amount, :taxable, :non_taxable,
9
+ :exempt, :other_charge, :charge_type
10
+
11
+ CHARGES = {
12
+ discount: false,
13
+ charge: true
14
+ }.freeze
15
+
16
+ def initialize(*args)
17
+ super(*args)
18
+ @taxable ||= 0.01
19
+ @non_taxable ||= 0.01
20
+ @exempt ||= 0.01
21
+ @other_charge ||= 0.01
22
+ end
23
+
24
+ def xml(xml, index, currency)
25
+ xml['sac'].SummaryDocumentsLine do
26
+ xml['cbc'].LineID(index + 1)
27
+ build_documents_info(xml)
28
+ amount_xml(xml['sac'], 'TotalAmount', total_amount, currency)
29
+ build_payments(xml, currency)
30
+ build_other_charge(xml, currency)
31
+ build_taxes_xml(xml, currency)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def payments
38
+ [{ amount: taxable, code: '01' },
39
+ { amount: exempt, code: '02' },
40
+ { amount: non_taxable, code: '03' }]
41
+ end
42
+
43
+ def build_documents_info(xml)
44
+ xml['cbc'].DocumentTypeCode document_type
45
+ xml['sac'].DocumentSerialID document_serial
46
+ xml['sac'].StartDocumentNumberID start_document_number
47
+ xml['sac'].EndDocumentNumberID end_document_number
48
+ end
49
+
50
+ def calculate_total_amount
51
+ return if total_amount
52
+ # TODO: sum(billing payments) + allowance charge + sum(taxes)
53
+ end
54
+
55
+ def build_payments(xml, currency)
56
+ payments.each do |payment|
57
+ xml['sac'].BillingPayment do
58
+ amount_xml(xml['cbc'], 'PaidAmount', payment[:amount], currency)
59
+ xml['cbc'].InstructionID payment[:code]
60
+ end
61
+ end
62
+ end
63
+
64
+ def build_other_charge(xml, currency)
65
+ xml['cac'].AllowanceCharge do
66
+ xml['cbc'].ChargeIndicator CHARGES.values.last
67
+ amount_xml(xml['cbc'], 'Amount', other_charge, currency)
68
+ end
69
+ end
70
+
71
+ def resolve_charge_type
72
+ charge_type ? CHARGES[charge_type] : CHARGES.values.first
73
+ end
74
+ end
75
+ end
@@ -41,7 +41,7 @@ module SunatInvoice
41
41
 
42
42
  def tax_category(xml)
43
43
  xml['cac'].TaxCategory do
44
- xml['cbc'].TaxExemptionReasonCode(tax_exemption_reason) if tax_exemption_reason
44
+ tax_exemption(xml)
45
45
  xml['cbc'].TierRange(tier_range) if tier_range
46
46
  tax_scheme(xml)
47
47
  end
@@ -58,5 +58,10 @@ module SunatInvoice
58
58
  def tax_data(attribute)
59
59
  TAXES[tax_type][attribute]
60
60
  end
61
+
62
+ def tax_exemption(xml)
63
+ return unless tax_exemption_reason
64
+ xml['cbc'].TaxExemptionReasonCode(tax_exemption_reason)
65
+ end
61
66
  end
62
67
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: false
2
+
3
+ require_relative 'catalogs'
4
+
5
+ module SunatInvoice
6
+ module TradeCalculations
7
+ def prepare_totals
8
+ calculate_taxes_totals
9
+ calculate_sale_totals
10
+ calculate_total
11
+ end
12
+
13
+ def calculate_total
14
+ # calculate invoice total
15
+ @total = 0
16
+ @total += @taxes_totals.values.sum
17
+ @total += @sale_totals.reject { |k, _v| k == '1004' }.values.sum
18
+ @total -= discount if discount
19
+ end
20
+
21
+ def calculate_sale_totals
22
+ @sale_totals = {}
23
+ # get bi totals according kind of sale (gravado, inafecto, exonerado ..)
24
+ lines&.each do |item|
25
+ # TODO: I think in most cases only be one tax for item, but should
26
+ # handle more cases
27
+ total_code = get_total_code(item.taxes.first)
28
+ if total_code
29
+ @sale_totals[total_code] = 0 unless @sale_totals[total_code]
30
+ @sale_totals[total_code] += item.bi_value
31
+ end
32
+ end
33
+ end
34
+
35
+ def calculate_taxes_totals
36
+ # concat item's sale_taxes
37
+ @taxes_totals = {}
38
+ taxes = lines&.map(&:sale_taxes)&.flatten
39
+ taxes&.each do |tax|
40
+ @taxes_totals[tax.keys.first] ||= 0
41
+ @taxes_totals[tax.keys.first] += tax.values.sum
42
+ end
43
+ end
44
+
45
+ def get_total_code(tax)
46
+ return unless tax
47
+ case tax.tax_type
48
+ # TODO: :isc
49
+ when :igv
50
+ get_total_igv_code(tax.tax_exemption_reason)
51
+ end
52
+ end
53
+
54
+ def get_total_igv_code(exemption_reason)
55
+ if Catalogs::CATALOG_07.first == exemption_reason
56
+ Catalogs::CATALOG_14.first
57
+ elsif Catalogs::CATALOG_07[1..6].include?(exemption_reason)
58
+ Catalogs::CATALOG_14[3]
59
+ elsif Catalogs::CATALOG_07[7] == exemption_reason
60
+ Catalogs::CATALOG_14[2]
61
+ elsif Catalogs::CATALOG_07[8] == exemption_reason
62
+ Catalogs::CATALOG_14[1]
63
+ elsif Catalogs::CATALOG_07[9..14].include?(exemption_reason)
64
+ Catalogs::CATALOG_14[3]
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: false
2
+
3
+ require_relative 'tax'
4
+ require_relative 'trade_calculations'
5
+
6
+ module SunatInvoice
7
+ class TradeDocument < XmlDocument
8
+ include TradeCalculations
9
+
10
+ attr_accessor :customer, :document_number, :document_type, :discount
11
+
12
+ INVOICE_TYPES = %w[01 03].freeze
13
+
14
+ def operation
15
+ :send_bill
16
+ end
17
+
18
+ def document_name
19
+ "#{@provider.ruc}-#{document_type}-#{document_number}"
20
+ end
21
+
22
+ private
23
+
24
+ def invoice?
25
+ INVOICE_TYPES.include?(document_type)
26
+ end
27
+
28
+ def build_document_data(xml)
29
+ build_number(xml)
30
+ xml['cbc'].IssueDate formatted_date(date)
31
+ xml['cbc'].InvoiceTypeCode document_type if invoice?
32
+ xml['cbc'].DocumentCurrencyCode currency
33
+ end
34
+
35
+ def build_ext(xml)
36
+ super(xml) do |xml_|
37
+ build_sale_totals(xml_)
38
+ end
39
+ end
40
+
41
+ def build_sale_totals(xml)
42
+ prepare_totals
43
+ ubl_ext(xml) do
44
+ xml['sac'].AdditionalInformation do
45
+ @sale_totals&.each do |code, amount|
46
+ build_monetary_total(xml, code, amount)
47
+ end
48
+ build_monetary_total(xml, '2005', discount) if discount
49
+ end
50
+ end
51
+ end
52
+
53
+ def build_monetary_total(xml, code, amount)
54
+ xml['sac'].AdditionalMonetaryTotal do
55
+ xml['cbc'].ID code
56
+ amount_xml(xml['cbc'], 'PayableAmount', amount, @currency)
57
+ end
58
+ end
59
+
60
+ def build_common_content(xml)
61
+ @signature.signer_data(xml)
62
+ @provider.info(xml)
63
+ @customer.info(xml)
64
+ build_taxes_totals(xml)
65
+ build_total(xml)
66
+ build_lines_xml(xml)
67
+ end
68
+
69
+ def build_taxes_totals(xml)
70
+ @taxes_totals.each do |key, value|
71
+ SunatInvoice::Tax.new(tax_type: key, amount: value).xml(xml, @currency)
72
+ end
73
+ end
74
+
75
+ def total_tag
76
+ 'LegalMonetaryTotal'
77
+ end
78
+
79
+ def build_total(xml)
80
+ xml['cac'].send(total_tag) do
81
+ amount_xml(xml['cbc'], 'PayableAmount', @total, @currency)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -4,18 +4,57 @@ module SunatInvoice
4
4
  module Utils
5
5
  @namespace_path = 'urn:oasis:names:specification:ubl:schema:xsd'
6
6
  @sunat_namespace_path = 'urn:sunat:names:specification:ubl:peru:schema:xsd'
7
+ @un_namespace_path = 'urn:un:unece:uncefact:data:specification'
7
8
 
8
- UBL_NAMESPACES = {
9
- 'xmlns' => "#{@namespace_path}:Invoice-2",
10
- 'xmlns:cac' => "#{@namespace_path}:CommonAggregateComponents-2",
11
- 'xmlns:cbc' => "#{@namespace_path}:CommonBasicComponents-2",
9
+ COMMON_NAMESPACES = {
10
+ cac: "#{@namespace_path}:CommonAggregateComponents-2",
11
+ cbc: "#{@namespace_path}:CommonBasicComponents-2",
12
+ ds: 'http://www.w3.org/2000/09/xmldsig#',
13
+ ext: "#{@namespace_path}:CommonExtensionComponents-2",
14
+ sac: "#{@sunat_namespace_path}:SunatAggregateComponents-1",
15
+ xsi: 'http://www.w3.org/2001/XMLSchema-instance'
16
+ }.freeze
17
+
18
+ TRADE_NAMESPACES = {
19
+ 'xmlns:cac' => COMMON_NAMESPACES[:cac],
20
+ 'xmlns:cbc' => COMMON_NAMESPACES[:cbc],
12
21
  'xmlns:ccts' => 'urn:un:unece:uncefact:documentation:2',
13
- 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#',
14
- 'xmlns:ext' => "#{@namespace_path}:CommonExtensionComponents-2",
22
+ 'xmlns:ds' => COMMON_NAMESPACES[:ds],
23
+ 'xmlns:ext' => COMMON_NAMESPACES[:ext],
15
24
  'xmlns:qdt' => "#{@namespace_path}:QualifiedDatatypes-2",
16
- 'xmlns:sac' => "#{@sunat_namespace_path}:SunatAggregateComponents-1",
17
- 'xmlns:udt' => 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2',
18
- 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
25
+ 'xmlns:sac' => COMMON_NAMESPACES[:sac],
26
+ 'xmlns:udt' => "#{@un_namespace_path}:UnqualifiedDataTypesSchemaModule:2",
27
+ 'xmlns:xsi' => COMMON_NAMESPACES[:xsi]
28
+ }.freeze
29
+
30
+ INVOICE_NAMESPACES = {
31
+ 'xmlns' => "#{@namespace_path}:Invoice-2"
32
+ }.freeze
33
+
34
+ CREDIT_NOTE_NAMESPACES = {
35
+ 'xmlns' => "#{@namespace_path}:CreditNote-2"
36
+ }.freeze
37
+
38
+ DEBIT_NOTE_NAMESPACES = {
39
+ 'xmlns' => "#{@namespace_path}:DebitNote-2"
40
+ }.freeze
41
+
42
+ SUMMARY_NAMESPACES = {
43
+ 'xmlns:cac' => COMMON_NAMESPACES[:cac],
44
+ 'xmlns:cbc' => COMMON_NAMESPACES[:cbc],
45
+ 'xmlns:ds' => COMMON_NAMESPACES[:ds],
46
+ 'xmlns:ext' => COMMON_NAMESPACES[:ext],
47
+ 'xmlns:sac' => COMMON_NAMESPACES[:sac],
48
+ 'xmlns:xsi' => COMMON_NAMESPACES[:xsi]
49
+ }.freeze
50
+
51
+ DAILY_SUMMARY_NAMESPACES = {
52
+ 'xmlns' => "#{@sunat_namespace_path}:SummaryDocuments-1",
53
+ 'xsi:schemaLocation' => 'urn:sunat:names:specification:ubl:peru:schema:xsd:InvoiceSummary-1 D:\UBL_SUNAT\SUNAT_xml_20110112\20110112\xsd\maindoc\UBLPE-InvoiceSummary-1.0.xsd'
54
+ }.freeze
55
+
56
+ VOIDED_NAMESPACES = {
57
+ 'xmlns' => "#{@sunat_namespace_path}:VoidedDocuments-1"
19
58
  }.freeze
20
59
 
21
60
  def ubl_ext(xml, &block)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: false
2
+
3
+ require_relative 'daily_summary'
4
+
5
+ module SunatInvoice
6
+ class Voided < DailySummary
7
+ private
8
+
9
+ def namespaces
10
+ VOIDED_NAMESPACES.merge(SUMMARY_NAMESPACES)
11
+ end
12
+
13
+ def root_name
14
+ 'VoidedDocuments'
15
+ end
16
+
17
+ def document_number
18
+ formatted = date.strftime('%Y%m%d') # YYYYMMDD
19
+ "RA-#{formatted}-1"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'line'
4
+
5
+ module SunatInvoice
6
+ class VoidedLine < Line
7
+ attr_accessor :document_type, :document_serial, :document_number,
8
+ :description
9
+
10
+ def xml(xml, index, _currency)
11
+ xml['sac'].VoidedDocumentsLine do
12
+ xml['cbc'].LineID(index + 1)
13
+ xml['cbc'].DocumentTypeCode document_type
14
+ xml['sac'].DocumentSerialID document_serial
15
+ xml['sac'].DocumentNumberID document_number
16
+ xml['sac'].VoidReasonDescription description
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: false
2
+
3
+ require_relative 'model'
4
+ require_relative 'utils'
5
+
6
+ module SunatInvoice
7
+ class XmlDocument < Model
8
+ include Utils
9
+
10
+ attr_accessor :date, :provider, :signature, :currency, :lines
11
+
12
+ UBL_VERSION = '2.0'.freeze
13
+ CUSTOMIZATION = '1.0'.freeze
14
+
15
+ def initialize(*args)
16
+ super(*args)
17
+ @date ||= Date.today
18
+ end
19
+
20
+ def build_xml(&block)
21
+ Nokogiri::XML::Builder.new do |xml|
22
+ xml.send(root_name, namespaces) do
23
+ build_ext(xml)
24
+ xml['cbc'].UBLVersionID UBL_VERSION
25
+ xml['cbc'].CustomizationID CUSTOMIZATION
26
+ yield(xml) if block
27
+ end
28
+ end
29
+ end
30
+
31
+ def build_ext(xml, &block)
32
+ xml['ext'].UBLExtensions do
33
+ yield(xml) if block
34
+ @signature.signature_ext(xml)
35
+ end
36
+ end
37
+
38
+ def build_number(xml)
39
+ xml['cbc'].ID document_number
40
+ end
41
+
42
+ def formatted_date(date)
43
+ date.strftime('%Y-%m-%d')
44
+ end
45
+
46
+ def build_lines_xml(xml)
47
+ lines&.each_with_index do |line, index|
48
+ line.xml(xml, index, currency)
49
+ end
50
+ end
51
+ end
52
+ end
data/lib/sunat_invoice.rb CHANGED
@@ -1,13 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # documents
4
+ require 'sunat_invoice/xml_document'
3
5
  require 'sunat_invoice/invoice'
4
6
  require 'sunat_invoice/provider'
5
7
  require 'sunat_invoice/customer'
6
8
  require 'sunat_invoice/item'
7
9
  require 'sunat_invoice/tax'
10
+ require 'sunat_invoice/daily_summary'
11
+ require 'sunat_invoice/summary_line'
12
+ require 'sunat_invoice/voided'
13
+ require 'sunat_invoice/voided_line'
14
+ require 'sunat_invoice/credit_note'
15
+ require 'sunat_invoice/credit_note_line'
16
+ require 'sunat_invoice/debit_note'
17
+ require 'sunat_invoice/debit_note_line'
18
+
19
+ # clients
8
20
  require 'sunat_invoice/configuration'
21
+ require 'sunat_invoice/response_parser'
9
22
  require 'sunat_invoice/client'
10
23
  require 'sunat_invoice/clients/invoice_client'
24
+ require 'sunat_invoice/clients/consult_client'
11
25
 
12
26
  module SunatInvoice
13
27
  class << self
@@ -2,9 +2,9 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'sunat_invoice'
5
- s.version = '0.0.1'
6
- s.summary = 'SOAP client to use SUNAT Electronic Invoice API'
7
- s.description = s.summary
5
+ s.version = '0.0.3'
6
+ s.summary = 'Ruby gem to use SUNAT Electronic Billing from your app'
7
+ s.description = 'Generate and send Electronic Invoices to SUNAT'
8
8
  s.authors = ['César Carruitero']
9
9
  s.email = ['cesar@mozilla.pe']
10
10
  s.homepage = 'https://github.com/ccarruitero/sunat_invoice'
@@ -12,9 +12,9 @@ Gem::Specification.new do |s|
12
12
 
13
13
  s.files = `git ls-files`.split("\n")
14
14
 
15
- s.add_dependency 'savon', '~> 2.11.2'
16
15
  s.add_dependency 'nokogiri', '~> 1.8'
17
- s.add_dependency 'rubyzip', '~> 1.2.1'
16
+ s.add_dependency 'rubyzip', '~> 1.2'
17
+ s.add_dependency 'savon', '~> 2.11'
18
18
  s.add_dependency 'xmldsig', '~> 0.6.5'
19
19
 
20
20
  s.add_development_dependency 'cutest', '~> 1.2'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: false
2
+
3
+ require_relative 'helper'
4
+
5
+ setup do
6
+ provider = FactoryBot.build(:provider)
7
+ signature = FactoryBot.build(:signature, provider: provider)
8
+ note = FactoryBot.build(:credit_note, provider: provider,
9
+ signature: signature)
10
+ @parsed_xml = Nokogiri::XML(note.xml, &:noblanks)
11
+ end
12
+
13
+ test 'start with CreditNote tag' do
14
+ assert_equal @parsed_xml.root.name, 'CreditNote'
15
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: false
2
+
3
+ require_relative 'helper'
4
+
5
+ setup do
6
+ provider = FactoryBot.build(:provider)
7
+ signature = FactoryBot.build(:signature, provider: provider)
8
+ ref_date = Date.today - 1
9
+ @line = FactoryBot.build(:summary_line)
10
+ summary = SunatInvoice::DailySummary.new(provider: provider,
11
+ signature: signature,
12
+ reference_date: ref_date,
13
+ currency: 'PEN',
14
+ lines: [@line])
15
+ @parsed_xml = Nokogiri::XML(summary.xml, &:noblanks)
16
+ end
17
+
18
+ test 'start with SummaryDocuments tag' do
19
+ assert_equal @parsed_xml.root.name, 'SummaryDocuments'
20
+ end
21
+
22
+ test 'line total amount has correct content' do
23
+ total = @parsed_xml.at('//sac:SummaryDocumentsLine/sac:TotalAmount').content
24
+ assert_equal total, @line.total_amount.to_s
25
+ end
26
+
27
+ test 'ChargeIndicator must not be empty when not charge_type' do
28
+ assert @line.charge_type.nil?
29
+ assert !@parsed_xml.at('//cbc:ChargeIndicator').content.empty?
30
+ end
31
+
32
+ test 'AllowanceCharge Amount is 0' do
33
+ amount = @parsed_xml.at('//cac:AllowanceCharge/cbc:Amount').content
34
+ assert_equal amount, '0.01'
35
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: false
2
+
3
+ require_relative 'helper'
4
+
5
+ setup do
6
+ provider = FactoryBot.build(:provider)
7
+ signature = FactoryBot.build(:signature, provider: provider)
8
+ note = FactoryBot.build(:debit_note, provider: provider,
9
+ signature: signature)
10
+ @parsed_xml = Nokogiri::XML(note.xml, &:noblanks)
11
+ end
12
+
13
+ test 'start with DebitNote tag' do
14
+ assert_equal @parsed_xml.root.name, 'DebitNote'
15
+ end