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