sunat_invoice 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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