sunat_invoice 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +11 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/Makefile +8 -0
- data/README.mkd +26 -0
- data/lib/sunat_invoice/catalogs.rb +177 -0
- data/lib/sunat_invoice/client.rb +36 -0
- data/lib/sunat_invoice/clients/consult_client.rb +11 -0
- data/lib/sunat_invoice/clients/invoice_client.rb +40 -0
- data/lib/sunat_invoice/clients/validate_client.rb +18 -0
- data/lib/sunat_invoice/configuration.rb +7 -0
- data/lib/sunat_invoice/customer.rb +18 -0
- data/lib/sunat_invoice/invoice.rb +171 -0
- data/lib/sunat_invoice/item.rb +79 -0
- data/lib/sunat_invoice/model.rb +11 -0
- data/lib/sunat_invoice/provider.rb +52 -0
- data/lib/sunat_invoice/signature.rb +87 -0
- data/lib/sunat_invoice/tax.rb +62 -0
- data/lib/sunat_invoice/tributer.rb +10 -0
- data/lib/sunat_invoice/utils.rb +31 -0
- data/lib/sunat_invoice.rb +21 -0
- data/sunat_invoice.gemspec +25 -0
- data/test/factories.rb +48 -0
- data/test/helper.rb +11 -0
- data/test/invoice_client_test.rb +41 -0
- data/test/invoice_test.rb +145 -0
- data/test/item_test.rb +110 -0
- data/test/support/signature_helper.rb +30 -0
- metadata +199 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b49429704d8a0fd453f1210ee274c0b111dbb96e
|
4
|
+
data.tar.gz: ecae8df2666c3e508abea79a594c375b12b03153
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 83d17d8b14f0bfb1bcbdea6e449346d7ac0b819859d434695f93ba306e8f979f0885683454fd655d1bfb54f4ee9f60cbe16e0266e429b1507df75d6745365d82
|
7
|
+
data.tar.gz: c1992a23ae4817da9539e980bc3ee745335d70b802d1a18a32793eee7c6918140af4915ae84cd72c6e30a7b8edb2077808f656f596c0e8b4446cdf3eba362865
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create use 2.4.1@sunat_invoice
|
data/Gemfile
ADDED
data/Makefile
ADDED
data/README.mkd
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Sunat Invoice
|
2
|
+
Ruby wrapper to use SUNAT Electronic Invoice API
|
3
|
+
|
4
|
+
homologacion
|
5
|
+
https://www.sunat.gob.pe/ol-ti-itcpgem-sqa/billService?wsdl
|
6
|
+
|
7
|
+
valid_service
|
8
|
+
|
9
|
+
http://www.sunat.gob.pe/ol-ti-itconsvalicpe/ConsValiCpe.htm
|
10
|
+
|
11
|
+
{:validaCDPcriterios=>{:rucEmisor=>"string", :tipoCDP=>"string", :serieCDP=>"string", :numeroCDP=>"string", :tipoDocIdReceptor=>"string", :numeroDocIdReceptor=>"string", :fechaEmision=>"string", :importeTotal=>"double", :nroAutorizacion=>"string"}}
|
12
|
+
|
13
|
+
http://www.sunat.gob.pe/ol-ti-itconsverixml/ConsVeriXml.htm
|
14
|
+
|
15
|
+
{:verificaCPEarchivo=>{:nombre=>"string", :archivo=>"string"}}
|
16
|
+
|
17
|
+
errors
|
18
|
+
https://www.facturacionperu.ws/?p=138
|
19
|
+
|
20
|
+
|
21
|
+
To write:
|
22
|
+
https://www.xml.com/pub/a/2001/08/08/xmldsig.html
|
23
|
+
https://www.di-mgt.com.au/xmldsig.html
|
24
|
+
|
25
|
+
https://github.com/ebeigarts/signer
|
26
|
+
https://github.com/benoist/xmldsig
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
module SunatInvoice
|
4
|
+
module Catalogs
|
5
|
+
# SUNAT codes
|
6
|
+
|
7
|
+
# Tipo de documento
|
8
|
+
CATALOG_01 = [
|
9
|
+
'01', # FACTURA
|
10
|
+
'03', # BOLETA DE VENTA
|
11
|
+
'07', # NOTA DE CREDITO
|
12
|
+
'08', # NOTA DE DEBITO 383
|
13
|
+
'09', # GUIA DE REMISIÓN REMITENTE
|
14
|
+
'12', # TICKET DE MAQUINA REGISTRADORA
|
15
|
+
'13', # DOCUMENTO EMITIDO POR BANCOS, INSTITUCIONES FINANCIERAS
|
16
|
+
'18', # DOCUMENTOS EMITIDOS POR LAS AFP
|
17
|
+
'31', # GUIA DE REMISIÓN TRANSPORTISTA
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
# Tipo de Tributo
|
21
|
+
CATALOG_05 = [
|
22
|
+
'1000', # IGV
|
23
|
+
'2000', # ISC
|
24
|
+
'9999' # Otros
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
# Tipo de Documentos de Identidad
|
28
|
+
CATALOG_06 = [
|
29
|
+
'0', # DOC.TRIB.NO.DOM.SIN.RUC
|
30
|
+
'1', # DOC. NACIONAL DE IDENTIDAD
|
31
|
+
'4', # CARNET DE EXTRANJERIA
|
32
|
+
'6', # REG. UNICO DE CONTRIBUYENTES
|
33
|
+
'7', # PASAPORTE
|
34
|
+
'A' # CED. DIPLOMATICA DE IDENTIDAD
|
35
|
+
].freeze
|
36
|
+
|
37
|
+
# Tipo de Afectación del IGV
|
38
|
+
CATALOG_07 = [
|
39
|
+
'10', # Gravado - Operación Onerosa
|
40
|
+
'11', # Gravado – Retiro por premio
|
41
|
+
'12', # Gravado – Retiro por donación
|
42
|
+
'13', # Gravado – Retiro
|
43
|
+
'14', # Gravado – Retiro por publicidad
|
44
|
+
'15', # Gravado – Bonificaciones
|
45
|
+
'16', # Gravado – Retiro por entrega a trabajadores
|
46
|
+
'20', # Exonerado - Operación Onerosa
|
47
|
+
'30', # Inafecto - Operación Onerosa
|
48
|
+
'31', # Inafecto – Retiro por Bonificación
|
49
|
+
'32', # Inafecto – Retiro
|
50
|
+
'33', # Inafecto – Retiro por Muestras Médicas
|
51
|
+
'34', # Inafecto - Retiro por Convenio Colectivo
|
52
|
+
'35', # Inafecto – Retiro por premio
|
53
|
+
'36', # Inafecto - Retiro por publicidad
|
54
|
+
'40' # Exportación
|
55
|
+
].freeze
|
56
|
+
|
57
|
+
# Tipo de Sistema de Cáclulo del ISC
|
58
|
+
CATALOG_08 = [
|
59
|
+
'01', # Sistema al valor
|
60
|
+
'02', # Aplicación del Monto Fijo
|
61
|
+
'03', # Sistema de Precios de Venta al Público
|
62
|
+
].freeze
|
63
|
+
|
64
|
+
# Tipo de Nota de Crédito Electrónica
|
65
|
+
CATALOG_09 = [
|
66
|
+
'01', # Anulación de la operación
|
67
|
+
'02', # Anulación por error en el RUC
|
68
|
+
'03', # Corrección por error en la descripción
|
69
|
+
'04', # Descuento global
|
70
|
+
'05', # Descuento por ítem
|
71
|
+
'06', # Devolución total
|
72
|
+
'07', # Devolución por ítem
|
73
|
+
'08', # Bonificación
|
74
|
+
'09' # Disminución en el valor
|
75
|
+
].freeze
|
76
|
+
|
77
|
+
# Tipo de Nota de Débito Electrónica
|
78
|
+
CATALOG_10 = [
|
79
|
+
'01', # Intereses por mora
|
80
|
+
'02' # Aumento en el valor
|
81
|
+
].freeze
|
82
|
+
|
83
|
+
# Resumen Diario de Boletas de Ventas Electrónicas y Notas Electrónicas
|
84
|
+
# Tipo de Valor de Venta
|
85
|
+
CATALOG_11 = [
|
86
|
+
'01', # Gravado
|
87
|
+
'02', # Exonerado
|
88
|
+
'03', # Inafecto
|
89
|
+
'04', # Exportación
|
90
|
+
'05' # Gratuitas
|
91
|
+
].freeze
|
92
|
+
|
93
|
+
# Documentos Relacionados Tributarios
|
94
|
+
CATALOG_12 = [
|
95
|
+
'01', # Factura – emitida para corregir error en el RUC
|
96
|
+
'04', # Ticket de Salida - ENAPU
|
97
|
+
'05', # Código SCOP
|
98
|
+
'99' # Otros
|
99
|
+
].freeze
|
100
|
+
|
101
|
+
# Otros conceptos tributarios
|
102
|
+
CATALOG_14 = [
|
103
|
+
'1001', # Total valor de venta - operaciones gravadas
|
104
|
+
'1002', # Total valor de venta - operaciones inafectas
|
105
|
+
'1003', # Total valor de venta - operaciones exoneradas
|
106
|
+
'1004', # Total valor de venta - Operaciones gratuitas
|
107
|
+
'1005', # Sub total de venta
|
108
|
+
'2001', # Percepciones
|
109
|
+
'2002', # Retenciones
|
110
|
+
'2003', # Detracciones
|
111
|
+
'2004', # Bonificaciones
|
112
|
+
'2005' # Total descuentos
|
113
|
+
].freeze
|
114
|
+
|
115
|
+
# Elementos adicionales en la Factura y/o Boleta de Venta Electrónica
|
116
|
+
CATALOG_15 = [
|
117
|
+
'1000', # Monto en Letras
|
118
|
+
'1002',
|
119
|
+
# Leyenda “TRANSFERENCIA GRATUITA DE UN BIEN Y/O SERVICIO PRESTADO
|
120
|
+
# GRATUITAMENTE”
|
121
|
+
'2000', # Leyenda “COMPROBANTE DE PERCEPCIÓN”
|
122
|
+
'2001',
|
123
|
+
# Leyenda “BIENES TRANSFERIDOS EN LA AMAZONÍA REGIÓN SELVAPARA SER
|
124
|
+
# CONSUMIDOS EN LA MISMA”
|
125
|
+
'2002',
|
126
|
+
# Leyenda “SERVICIOS PRESTADOS EN LA AMAZONÍA REGIÓN SELVA PARA SER
|
127
|
+
# CONSUMIDOS EN LA MISMA”
|
128
|
+
'2003',
|
129
|
+
# Leyenda “CONTRATOS DE CONSTRUCCIÓN EJECUTADOS EN LA AMAZONÍA REGIÓN
|
130
|
+
# SELVA”
|
131
|
+
'2004', # Leyenda “Agencia de Viaje - Paquete turístico”
|
132
|
+
'3000', # Detracciones: CODIGO DE BB Y SS SUJETOS A DETRACCION
|
133
|
+
'3001', # Detracciones: NUMERO DE CTA EN EL BN
|
134
|
+
'3002',
|
135
|
+
# Detracciones: Recursos Hidrobiológicos-Nombre y matrícula de la
|
136
|
+
# embarcación
|
137
|
+
'3003',
|
138
|
+
# Detracciones: Recursos Hidrobiológicos-Tipo y cantidad de especie
|
139
|
+
# vendida
|
140
|
+
'3004', # Detracciones: Recursos Hidrobiológicos -Lugar de descarga
|
141
|
+
'3005', # Detracciones: Recursos Hidrobiológicos -Fecha de descarga
|
142
|
+
'3006',
|
143
|
+
# Detracciones: Transporte Bienes vía terrestre – Numero Registro MTC
|
144
|
+
'3007',
|
145
|
+
# Detracciones: Transporte Bienes vía terrestre -configuración vehicular
|
146
|
+
'3008', # Detracciones: Transporte Bienes vía terrestre – punto de origen
|
147
|
+
'3009', # Detracciones: Transporte Bienes vía terrestre – punto destino
|
148
|
+
'3010',
|
149
|
+
# Detracciones: Transporte Bienes vía terrestre – valor referencial
|
150
|
+
# preliminar
|
151
|
+
'4000', # Beneficio hospedajes: Código País de emisión del pasaporte
|
152
|
+
'4001',
|
153
|
+
# Beneficio hospedajes: Código País de residencia del sujeto no
|
154
|
+
# domiciliado
|
155
|
+
'4002', # Beneficio Hospedajes: Fecha de ingreso al país
|
156
|
+
'4003', # Beneficio Hospedajes: Fecha de ingreso al establecimiento
|
157
|
+
'4004', # Beneficio Hospedajes: Fecha de salida del establecimiento
|
158
|
+
'4005', # Beneficio Hospedajes: Número de días de permanencia
|
159
|
+
'4006', # Beneficio Hospedajes: Fecha de consumo
|
160
|
+
'4007',
|
161
|
+
# Beneficio Hospedajes: Paquete turístico - Nombres y Apellidos del
|
162
|
+
# Huésped
|
163
|
+
'4008',
|
164
|
+
# Beneficio Hospedajes: Paquete turístico – Tipo documento identidad del
|
165
|
+
# huésped
|
166
|
+
'4009'
|
167
|
+
# Beneficio Hospedajes: Paquete turístico – Numero de documento identidad
|
168
|
+
# de huésped
|
169
|
+
].freeze
|
170
|
+
|
171
|
+
# Tipo de Precio de Venta Unitario
|
172
|
+
CATALOG_16 = [
|
173
|
+
'01', # Precio unitario (incluye el IGV)
|
174
|
+
'02' # Valor referencial unitario en operaciones no onerosas
|
175
|
+
].freeze
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zip'
|
4
|
+
require 'savon'
|
5
|
+
|
6
|
+
module SunatInvoice
|
7
|
+
class Client
|
8
|
+
def initialize(env = 'dev')
|
9
|
+
@env = env
|
10
|
+
@soap_client = savon_client
|
11
|
+
end
|
12
|
+
|
13
|
+
def log
|
14
|
+
(@env != 'prod')
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def config
|
20
|
+
SunatInvoice.configuration
|
21
|
+
end
|
22
|
+
|
23
|
+
def authentication
|
24
|
+
login = config.account_ruc + config.account_user
|
25
|
+
password = config.account_password
|
26
|
+
[login, password]
|
27
|
+
end
|
28
|
+
|
29
|
+
def savon_client
|
30
|
+
Savon.client(wsdl: wsdl,
|
31
|
+
wsse_auth: authentication,
|
32
|
+
namespace: 'http://service.sunat.gob.pe',
|
33
|
+
log: log)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SunatInvoice
|
4
|
+
class InvoiceClient < Client
|
5
|
+
def wsdl
|
6
|
+
send("#{@env}_server")
|
7
|
+
end
|
8
|
+
|
9
|
+
def prod_server
|
10
|
+
'https://e-factura.sunat.gob.pe/ol-ti-itcpfegem/billService?wsdl'
|
11
|
+
end
|
12
|
+
|
13
|
+
def dev_server
|
14
|
+
'https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService?wsdl'
|
15
|
+
end
|
16
|
+
|
17
|
+
def prepare_zip(invoice, name)
|
18
|
+
# * invoice - xml document to zip
|
19
|
+
# * name - xml document name
|
20
|
+
zip_file = Zip::OutputStream.write_buffer do |zip|
|
21
|
+
zip.put_next_entry name
|
22
|
+
zip.write invoice
|
23
|
+
end
|
24
|
+
zip_file.rewind
|
25
|
+
encode(zip_file.sysread)
|
26
|
+
end
|
27
|
+
|
28
|
+
def send_invoice(invoice, name)
|
29
|
+
zip = prepare_zip(invoice, "#{name}.xml")
|
30
|
+
@soap_client.call(:send_bill,
|
31
|
+
message: { fileName: "#{name}.zip", contentFile: zip })
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def encode(zip_str)
|
37
|
+
Base64.encode64(zip_str)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SunatInvoice
|
4
|
+
class ValidateClient < Client
|
5
|
+
# verify invoice generated
|
6
|
+
|
7
|
+
def wsdl
|
8
|
+
'https://www.sunat.gob.pe/ol-it-wsconsvalidcpe/billValidService?wsdl'
|
9
|
+
end
|
10
|
+
|
11
|
+
def verify_file(invoice, name)
|
12
|
+
cli = savon_client(valid_server)
|
13
|
+
cli.call(:verifica_cp_earchivo, message: {
|
14
|
+
nombre: name, archivo: invoice
|
15
|
+
})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'tributer'
|
3
|
+
|
4
|
+
module SunatInvoice
|
5
|
+
class Customer < Tributer
|
6
|
+
def info(xml)
|
7
|
+
xml['cac'].AccountingCustomerParty do
|
8
|
+
xml['cbc'].CustomerAssignedAccountID @ruc
|
9
|
+
xml['cbc'].AdditionalAccountID @document_type
|
10
|
+
xml['cac'].Party do
|
11
|
+
xml['cac'].PartyLegalEntity do
|
12
|
+
xml['cbc'].RegistrationName @name
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
require_relative 'utils'
|
3
|
+
require_relative 'provider'
|
4
|
+
require_relative 'customer'
|
5
|
+
require_relative 'signature'
|
6
|
+
require_relative 'tax'
|
7
|
+
require_relative 'catalogs'
|
8
|
+
|
9
|
+
module SunatInvoice
|
10
|
+
class Invoice < Model
|
11
|
+
include Utils
|
12
|
+
|
13
|
+
attr_accessor :document_type, :document_number, :items
|
14
|
+
|
15
|
+
UBL_VERSION = '2.0'
|
16
|
+
CUSTOMIZATION = '1.0'
|
17
|
+
|
18
|
+
def initialize(*args)
|
19
|
+
super(*args)
|
20
|
+
opts = args[0] || {}
|
21
|
+
@provider = opts[:provider] || SunatInvoice::Provider.new
|
22
|
+
@customer = opts[:customer] || SunatInvoice::Customer.new
|
23
|
+
@date = opts[:date] || DateTime.now.strftime('%Y-%m-%d')
|
24
|
+
@document_type = opts[:document_type] || '01'
|
25
|
+
@document_number = opts[:document_number] || 'F001-1'
|
26
|
+
@currency = opts[:currency] || 'PEN'
|
27
|
+
@items ||= []
|
28
|
+
@signature = SunatInvoice::Signature.new(provider: @provider)
|
29
|
+
end
|
30
|
+
|
31
|
+
def xml
|
32
|
+
prepare_totals
|
33
|
+
|
34
|
+
build = Nokogiri::XML::Builder.new do |xml|
|
35
|
+
xml.Invoice(UBL_NAMESPACES) do
|
36
|
+
xml['ext'].UBLExtensions do
|
37
|
+
build_sale_totals(xml)
|
38
|
+
@signature.signature_ext(xml)
|
39
|
+
end
|
40
|
+
|
41
|
+
xml['cbc'].UBLVersionID UBL_VERSION
|
42
|
+
xml['cbc'].CustomizationID CUSTOMIZATION
|
43
|
+
xml['cbc'].ID @document_number
|
44
|
+
xml['cbc'].IssueDate @date
|
45
|
+
xml['cbc'].InvoiceTypeCode @document_type
|
46
|
+
xml['cbc'].DocumentCurrencyCode @currency
|
47
|
+
|
48
|
+
@signature.signer_data(xml)
|
49
|
+
@provider.info(xml)
|
50
|
+
@customer.info(xml)
|
51
|
+
build_tax_totals(xml)
|
52
|
+
build_total(xml)
|
53
|
+
build_items(xml)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
invoice_xml = build.to_xml
|
57
|
+
@signature.sign(invoice_xml)
|
58
|
+
end
|
59
|
+
|
60
|
+
def prepare_totals
|
61
|
+
calculate_tax_totals
|
62
|
+
calculate_sale_totals
|
63
|
+
calculate_total
|
64
|
+
end
|
65
|
+
|
66
|
+
def calculate_total
|
67
|
+
# calculate invoice total
|
68
|
+
@total = 0
|
69
|
+
@total += @tax_totals.values.sum
|
70
|
+
@total += @sale_totals.values.sum
|
71
|
+
end
|
72
|
+
|
73
|
+
def calculate_sale_totals
|
74
|
+
@sale_totals = {}
|
75
|
+
# get bi totals according kind of sale (gravado, inafecto, exonerado ..)
|
76
|
+
items.each do |item|
|
77
|
+
# TODO: I think in most cases only be one tax for item, but should
|
78
|
+
# handle more cases
|
79
|
+
total_code = get_total_code(item.taxes.first)
|
80
|
+
if total_code
|
81
|
+
@sale_totals[total_code] = 0 unless @sale_totals[total_code]
|
82
|
+
@sale_totals[total_code] += item.bi_value
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def calculate_tax_totals
|
88
|
+
# concat item's sale_taxes
|
89
|
+
@tax_totals = {}
|
90
|
+
taxes = items.map(&:sale_taxes).flatten
|
91
|
+
taxes.each do |tax|
|
92
|
+
@tax_totals[tax.keys.first] ||= 0
|
93
|
+
@tax_totals[tax.keys.first] += tax.values.sum
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def build_items(xml)
|
98
|
+
items.each_with_index do |item, index|
|
99
|
+
item.xml(xml, index, @currency)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def document_name
|
104
|
+
"#{@provider.ruc}-#{document_type}-#{document_number}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_tax_totals(xml)
|
108
|
+
@tax_totals.each do |key, value|
|
109
|
+
SunatInvoice::Tax.new(tax_type: key, amount: value).xml(xml, @currency)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_sale_totals(xml)
|
114
|
+
ubl_ext(xml) do
|
115
|
+
xml['sac'].AdditionalInformation do
|
116
|
+
@sale_totals.each do |code, amount|
|
117
|
+
xml['sac'].AdditionalMonetaryTotal do
|
118
|
+
xml['cbc'].ID code
|
119
|
+
amount_xml(xml['cbc'], 'PayableAmount', amount, @currency)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def build_total(xml)
|
127
|
+
xml['cac'].LegalMonetaryTotal do
|
128
|
+
amount_xml(xml['cbc'], 'PayableAmount', @total, @currency)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def add_item(item)
|
133
|
+
items << item if item.is_a?(SunatInvoice::Item)
|
134
|
+
end
|
135
|
+
|
136
|
+
def get_total_code(tax)
|
137
|
+
return unless tax
|
138
|
+
case tax.tax_type
|
139
|
+
when :igv
|
140
|
+
get_total_igv_code(tax.tax_exemption_reason)
|
141
|
+
# when :isc
|
142
|
+
# tax.tier_range
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def get_total_igv_code(exemption_reason)
|
147
|
+
if Catalogs::CATALOG_07.first == exemption_reason
|
148
|
+
Catalogs::CATALOG_14.first
|
149
|
+
elsif Catalogs::CATALOG_07[1..6].include?(exemption_reason)
|
150
|
+
Catalogs::CATALOG_14[3]
|
151
|
+
elsif Catalogs::CATALOG_07[7] == exemption_reason
|
152
|
+
Catalogs::CATALOG_14[2]
|
153
|
+
elsif Catalogs::CATALOG_07[8..14].include?(exemption_reason)
|
154
|
+
Catalogs::CATALOG_14[1]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def attributes
|
159
|
+
{
|
160
|
+
attributes!: {
|
161
|
+
'cbc:InvoicedQuantity' => { '@unitCode' => :unit },
|
162
|
+
'cbc:PriceAmount' => { '@currencyID' => :currency }, # PEN
|
163
|
+
'cbc:TaxAmount' => { '@currencyID' => :currency }, # PEN
|
164
|
+
'cbc:PayableAmount' => { '@currencyID' => :currency }, # PEN
|
165
|
+
'cbc:LineExtensionAmount' => { '@currencyID' => :currency }, # PEN
|
166
|
+
'cbc:ChargeTotalAmount' => { '@currencyID' => :currency }, # PEN
|
167
|
+
}
|
168
|
+
}
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'utils'
|
3
|
+
|
4
|
+
module SunatInvoice
|
5
|
+
class Item < Model
|
6
|
+
include Utils
|
7
|
+
|
8
|
+
attr_accessor :quantity, :description, :price, :price_code, :unit_code,
|
9
|
+
:taxes
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
# * quantity - quantity of item
|
13
|
+
# * description - name or description of product or service
|
14
|
+
# * price - unit price without taxes
|
15
|
+
# * price_code - type unit price (Catalogs::CATALOG_16)
|
16
|
+
# * unit_code - unit of measure
|
17
|
+
# UN/ECE rec 20- Unit Of Measure
|
18
|
+
# http://www.unece.org/fileadmin/DAM/cefact/recommendations/rec20/rec20_rev3_Annex2e.pdf
|
19
|
+
# * taxes - An array of SunatInvoice::Tax
|
20
|
+
super(*args)
|
21
|
+
@taxes ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def bi_value
|
25
|
+
# bi of sale = price without taxes * quantity
|
26
|
+
(@price.to_f * @quantity.to_f).round(2)
|
27
|
+
end
|
28
|
+
|
29
|
+
def sale_taxes
|
30
|
+
# generate and object with taxes sum by type
|
31
|
+
sums = {}
|
32
|
+
taxes.each do |tax|
|
33
|
+
sums[tax.tax_type] ||= 0
|
34
|
+
sums[tax.tax_type] = (tax.amount.to_f * quantity.to_f).round(2)
|
35
|
+
end
|
36
|
+
sums
|
37
|
+
end
|
38
|
+
|
39
|
+
def sale_price
|
40
|
+
# unit price with tax
|
41
|
+
(@price.to_f + sum_taxes).round(2)
|
42
|
+
end
|
43
|
+
|
44
|
+
def sum_taxes
|
45
|
+
taxes.map(&:amount).sum
|
46
|
+
end
|
47
|
+
|
48
|
+
def xml(xml, index, currency)
|
49
|
+
xml['cac'].InvoiceLine do
|
50
|
+
xml['cbc'].ID(index + 1)
|
51
|
+
xml['cbc'].InvoicedQuantity(@quantity, unitCode: unit_code)
|
52
|
+
amount_xml(xml['cbc'], 'LineExtensionAmount', bi_value, currency)
|
53
|
+
xml['cac'].PricingReference do
|
54
|
+
xml['cac'].AlternativeConditionPrice do
|
55
|
+
amount_xml(xml['cbc'], 'PriceAmount', sale_price, currency)
|
56
|
+
xml['cbc'].PriceTypeCode price_code
|
57
|
+
end
|
58
|
+
end
|
59
|
+
taxes_xml(xml, currency)
|
60
|
+
build_item(xml)
|
61
|
+
xml['cac'].Price do
|
62
|
+
amount_xml(xml['cbc'], 'PriceAmount', price, currency)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_item(xml)
|
68
|
+
xml['cac'].Item do
|
69
|
+
xml['cbc'].Description description
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def taxes_xml(xml, currency)
|
74
|
+
taxes&.each do |tax|
|
75
|
+
tax.xml(xml, currency)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|