colppy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +36 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +38 -0
- data/Rakefile +39 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/colppy.gemspec +30 -0
- data/lib/colppy.rb +22 -0
- data/lib/colppy/client.rb +69 -0
- data/lib/colppy/core/gateway.rb +75 -0
- data/lib/colppy/core/services.rb +72 -0
- data/lib/colppy/core/support/hash.rb +49 -0
- data/lib/colppy/digest.rb +16 -0
- data/lib/colppy/errors.rb +7 -0
- data/lib/colppy/resource.rb +83 -0
- data/lib/colppy/resources/company.rb +123 -0
- data/lib/colppy/resources/customer.rb +169 -0
- data/lib/colppy/resources/product.rb +152 -0
- data/lib/colppy/resources/sell_invoice.rb +343 -0
- data/lib/colppy/resources/user.rb +97 -0
- data/lib/colppy/version.rb +3 -0
- metadata +174 -0
@@ -0,0 +1,152 @@
|
|
1
|
+
module Colppy
|
2
|
+
class Product < Resource
|
3
|
+
attr_reader :id, :name, :sku, :data
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def all(client, company)
|
7
|
+
list(client, company)
|
8
|
+
end
|
9
|
+
|
10
|
+
def list(client, company, parameters = {})
|
11
|
+
call_parameters = base_params.merge(parameters)
|
12
|
+
response = client.call(
|
13
|
+
:product,
|
14
|
+
:list,
|
15
|
+
extended_parameters(client, company, call_parameters)
|
16
|
+
)
|
17
|
+
if response[:success]
|
18
|
+
results = response[:data].map do |params|
|
19
|
+
new(params.merge(client: client, company: company))
|
20
|
+
end
|
21
|
+
parse_list_response(results, response[:total].to_i, call_parameters)
|
22
|
+
else
|
23
|
+
response
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def extended_parameters(client, company, parameters)
|
30
|
+
[ client.session_params,
|
31
|
+
company.params,
|
32
|
+
parameters
|
33
|
+
].inject(&:merge)
|
34
|
+
end
|
35
|
+
|
36
|
+
def base_params
|
37
|
+
{
|
38
|
+
filter:[],
|
39
|
+
order: [{
|
40
|
+
field: "codigo",
|
41
|
+
dir: "asc"
|
42
|
+
}]
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(client: nil, company: nil, **params)
|
49
|
+
@client = client if client && client.is_a?(Colppy::Client)
|
50
|
+
@company = company if company && company.is_a?(Colppy::Company)
|
51
|
+
@id = params.delete(:idItem)
|
52
|
+
@name = params.delete(:descripcion)
|
53
|
+
@sku = params.delete(:codigo)
|
54
|
+
@data = params
|
55
|
+
end
|
56
|
+
|
57
|
+
def new?
|
58
|
+
id.nil? || id.empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
def save
|
62
|
+
ensure_client_valid! && ensure_company_valid!
|
63
|
+
|
64
|
+
response = @client.call(
|
65
|
+
:product,
|
66
|
+
operation,
|
67
|
+
save_parameters
|
68
|
+
)
|
69
|
+
if response[:success]
|
70
|
+
response_data = response[:data]
|
71
|
+
case operation
|
72
|
+
when :create
|
73
|
+
@id = response_data[:idItem]
|
74
|
+
end
|
75
|
+
self
|
76
|
+
else
|
77
|
+
false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def name=(new_name)
|
82
|
+
@name = new_name
|
83
|
+
end
|
84
|
+
|
85
|
+
def sku=(new_sku)
|
86
|
+
@sku = new_sku
|
87
|
+
end
|
88
|
+
|
89
|
+
def []=(key, value)
|
90
|
+
key_sym = key.to_sym
|
91
|
+
if protected_data_keys.include?(key_sym)
|
92
|
+
raise ResourceError.new("You cannot change any of this values: #{protected_data_keys.join(", ")} manually")
|
93
|
+
end
|
94
|
+
@data[key_sym] = value
|
95
|
+
end
|
96
|
+
|
97
|
+
def params_for_invoice
|
98
|
+
{
|
99
|
+
idItem: id || "",
|
100
|
+
minimo: @data[:minimo] || "0",
|
101
|
+
codigo: sku || "",
|
102
|
+
tipoItem: @data[:tipoItem] || "P",
|
103
|
+
unidadMedida: @data[:unidadMedida] || "u",
|
104
|
+
Descripcion: name || "",
|
105
|
+
ImporteUnitario: @data[:precioVenta],
|
106
|
+
IVA: @data[:iva] || "21",
|
107
|
+
Comentario: @data[:detalle] || "",
|
108
|
+
idPlanCuenta: @data[:ctaIngresoVentas]
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def attr_inspect
|
115
|
+
[:id, :sku, :name]
|
116
|
+
end
|
117
|
+
|
118
|
+
def protected_data_keys
|
119
|
+
[:idItem, :idEmpresa]
|
120
|
+
end
|
121
|
+
|
122
|
+
def operation
|
123
|
+
new? ? :create : :update
|
124
|
+
end
|
125
|
+
|
126
|
+
def save_parameters
|
127
|
+
[
|
128
|
+
@client.session_params,
|
129
|
+
params
|
130
|
+
].inject(&:merge)
|
131
|
+
end
|
132
|
+
|
133
|
+
def params
|
134
|
+
{
|
135
|
+
idItem: id || "",
|
136
|
+
idEmpresa: @company.id,
|
137
|
+
codigo: sku || "",
|
138
|
+
descripcion: name || "",
|
139
|
+
detalle: @data[:detalle] || "",
|
140
|
+
precioVenta: @data[:precioVenta] || "0",
|
141
|
+
ultimoPrecioCompra: @data[:minimo] || "0",
|
142
|
+
ctaInventario: @data[:ctaInventario] || "",
|
143
|
+
ctaCostoVentas: @data[:ctaCostoVentas] || "",
|
144
|
+
ctaIngresoVentas: @data[:ctaIngresoVentas] || "",
|
145
|
+
iva: @data[:iva] || "21",
|
146
|
+
tipoItem: @data[:tipoItem] || "P",
|
147
|
+
unidadMedida: @data[:unidadMedida] || "u",
|
148
|
+
minimo: @data[:minimo] || "0"
|
149
|
+
}
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
module Colppy
|
2
|
+
class SellInvoice < Resource
|
3
|
+
attr_reader :id, :number, :cae, :items, :data, :url
|
4
|
+
attr_accessor :payments
|
5
|
+
|
6
|
+
VALID_RECEIPT_TYPES = %w(4 6 8 NCV)
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def all(client, company)
|
10
|
+
list(client, company)
|
11
|
+
end
|
12
|
+
|
13
|
+
def list(client, company, parameters = {})
|
14
|
+
call_parameters = base_params.merge(parameters)
|
15
|
+
response = client.call(
|
16
|
+
:sell_invoice,
|
17
|
+
:list,
|
18
|
+
extended_parameters(client, company, call_parameters)
|
19
|
+
)
|
20
|
+
if response[:success]
|
21
|
+
results = response[:data].map do |params|
|
22
|
+
new(params.merge(client: client, company: company))
|
23
|
+
end
|
24
|
+
parse_list_response(results, response[:total].to_i, call_parameters)
|
25
|
+
else
|
26
|
+
response
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(client, company, id)
|
31
|
+
response = client.call(
|
32
|
+
:sell_invoice,
|
33
|
+
:read,
|
34
|
+
extended_parameters(client, company, { idFactura: id })
|
35
|
+
)
|
36
|
+
if response[:success]
|
37
|
+
new(extended_response(response, client, company))
|
38
|
+
else
|
39
|
+
response
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def extended_parameters(client, company, parameters)
|
46
|
+
[ client.session_params,
|
47
|
+
company.params,
|
48
|
+
parameters
|
49
|
+
].inject(&:merge)
|
50
|
+
end
|
51
|
+
|
52
|
+
def base_params
|
53
|
+
{
|
54
|
+
filter:[],
|
55
|
+
order: {
|
56
|
+
field: ["nroFactura"],
|
57
|
+
order: "desc"
|
58
|
+
}
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def extended_response(response, client, company)
|
63
|
+
[ response[:infofactura],
|
64
|
+
items: response[:itemsFactura],
|
65
|
+
url: response[:UrlFacturaPdf],
|
66
|
+
client: client,
|
67
|
+
company: company
|
68
|
+
].inject(&:merge)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(client: nil, company: nil, customer: nil, **params)
|
74
|
+
@client = client if client && client.is_a?(Colppy::Client)
|
75
|
+
@company = company if company && company.is_a?(Colppy::Company)
|
76
|
+
@customer = customer if customer && customer.is_a?(Colppy::Customer)
|
77
|
+
|
78
|
+
@id = params.delete(:idFactura)
|
79
|
+
@number = params.delete(:nroFactura)
|
80
|
+
@cae = params.delete(:cae)
|
81
|
+
|
82
|
+
@items = parse_items(params.delete(:items))
|
83
|
+
@payments = params.delete(:ItemsCobro) || []
|
84
|
+
|
85
|
+
@data = params
|
86
|
+
@url = build_pdf_url
|
87
|
+
end
|
88
|
+
|
89
|
+
def new?
|
90
|
+
id.nil? || id.empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
def editable?
|
94
|
+
cae.nil? || cae.empty?
|
95
|
+
end
|
96
|
+
|
97
|
+
def save
|
98
|
+
ensure_editability! && ensure_client_valid! && ensure_company_valid! && ensure_customer_valid!
|
99
|
+
|
100
|
+
response = @client.call(
|
101
|
+
:sell_invoice,
|
102
|
+
:create,
|
103
|
+
save_parameters
|
104
|
+
)
|
105
|
+
binding.pry
|
106
|
+
if response[:success]
|
107
|
+
@id = response[:data][:idFactura]
|
108
|
+
self
|
109
|
+
else
|
110
|
+
false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def []=(key, value)
|
115
|
+
ensure_editability!
|
116
|
+
|
117
|
+
key_sym = key.to_sym
|
118
|
+
if protected_data_keys.include?(key_sym)
|
119
|
+
raise ResourceError.new("You cannot change any of this values: #{protected_data_keys.join(", ")} manually")
|
120
|
+
end
|
121
|
+
@data[key_sym] = value
|
122
|
+
end
|
123
|
+
|
124
|
+
def items=(new_items)
|
125
|
+
@charged_details = nil
|
126
|
+
@items = parse_items(new_items)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def attr_inspect
|
132
|
+
[:id, :number, :cae]
|
133
|
+
end
|
134
|
+
|
135
|
+
def protected_data_keys
|
136
|
+
[:idFactura, :idEmpresa, :nroFactura]
|
137
|
+
end
|
138
|
+
|
139
|
+
def parse_items(new_items)
|
140
|
+
return [] if new_items.nil? || new_items.empty?
|
141
|
+
|
142
|
+
new_items.map do |item|
|
143
|
+
item.tap do |hash|
|
144
|
+
if item[:idItem] && (item[:product].nil? || item[:product].empty?)
|
145
|
+
item[:product] = @company.product(item[:idItem])
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def build_pdf_url
|
152
|
+
return if data[:url].nil? || data[:url].empty?
|
153
|
+
|
154
|
+
data[:url] = "#{data[:url]}?usuario=#{@client.username}&claveSesion=#{@client.session_key}&idEmpresa=#{@company.id}&idFactura=#{id}&idCliente=#{data[:idCliente]}"
|
155
|
+
end
|
156
|
+
|
157
|
+
def save_parameters
|
158
|
+
[
|
159
|
+
@client.session_params,
|
160
|
+
general_params,
|
161
|
+
invoice_payments_params,
|
162
|
+
itemsFactura: invoice_items_params,
|
163
|
+
totalesiva: total_taxes_params
|
164
|
+
].inject(&:merge)
|
165
|
+
end
|
166
|
+
|
167
|
+
def general_params
|
168
|
+
{
|
169
|
+
idCliente: @customer.id || @data[:idCliente] || "",
|
170
|
+
idEmpresa: @company.id,
|
171
|
+
descripcion: @data[:descripcion] || "",
|
172
|
+
fechaFactura: valid_date(@data[:fechaFactura]),
|
173
|
+
idCondicionPago: @data[:idCondicionPago] || "Contado",
|
174
|
+
fechaPago: valid_date(@data[:fechaPago]),
|
175
|
+
idEstadoAnterior: @data[:idEstadoAnterior] || "",
|
176
|
+
idEstadoFactura: @data[:idEstadoFactura] || "Cobrada",
|
177
|
+
idFactura: id || "",
|
178
|
+
idMedioCobro: @data[:idMedioCobro] || "Efectivo",
|
179
|
+
idMoneda: @data[:idMoneda] || "1",
|
180
|
+
idTipoComprobante: receipt_type,
|
181
|
+
idTipoFactura: invoice_type,
|
182
|
+
netoGravado: (charged_details[:total_taxed] || 0.00).to_s,
|
183
|
+
netoNoGravado: (charged_details[:total_non_taxed] || 0.00).to_s,
|
184
|
+
nroFactura1: @data[:nroFactura1] || "0001",
|
185
|
+
nroFactura2: @data[:nroFactura2] || "",
|
186
|
+
percepcionIVA: (@data[:percepcionIVA] || 0.00).to_s,
|
187
|
+
percepcionIIBB: (@data[:percepcionIIBB] || 0.00).to_s,
|
188
|
+
totalFactura: (charged_details[:total] || 0.00).to_s,
|
189
|
+
totalIVA: (charged_details[:tax_total] || 0.00).to_s,
|
190
|
+
valorCambio: @data[:valorCambio] || "1",
|
191
|
+
}.tap do |params|
|
192
|
+
params[:labelfe] = "Factura Electrónica" if @data[:labelfe]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def invoice_items_params
|
197
|
+
items.map do |item|
|
198
|
+
if item[:product] && item[:product].is_a?(Colppy::Product)
|
199
|
+
product = item[:product]
|
200
|
+
[
|
201
|
+
product.params_for_invoice,
|
202
|
+
item_params(item, false)
|
203
|
+
].inject(&:merge)
|
204
|
+
elsif item.is_a?(Hash)
|
205
|
+
item_params(item)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def item_params(item, fill_empty = true)
|
211
|
+
{
|
212
|
+
Cantidad: item[:Cantidad],
|
213
|
+
porcDesc: item[:porcDesc] || "0.00"
|
214
|
+
}.tap do |params|
|
215
|
+
if value = item[:ImporteUnitario] || fill_empty
|
216
|
+
params[:ImporteUnitario] = value || ""
|
217
|
+
end
|
218
|
+
if value = item[:idPlanCuenta] || fill_empty
|
219
|
+
params[:idPlanCuenta] = value || ""
|
220
|
+
end
|
221
|
+
if value = item[:IVA] || fill_empty
|
222
|
+
params[:IVA] = value || "21"
|
223
|
+
end
|
224
|
+
if value = item[:Comentario] || fill_empty
|
225
|
+
params[:Comentario] = value || ""
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def invoice_payments_params
|
231
|
+
return {} unless payments
|
232
|
+
payment_items = payments.map do |payment|
|
233
|
+
{
|
234
|
+
idMedioCobro: payment[:idMedioCobro] || "Efectivo",
|
235
|
+
idPlanCuenta: payment[:idPlanCuenta] || "Caja en pesos",
|
236
|
+
Banco: payment[:Banco] || "",
|
237
|
+
nroCheque: payment[:nroCheque] || "",
|
238
|
+
fechaValidez: payment[:fechaValidez] || "",
|
239
|
+
importe: payment[:importe] || "0",
|
240
|
+
VAD: payment[:VAD] || "S"
|
241
|
+
}
|
242
|
+
end
|
243
|
+
{ ItemsCobro: payment_items }
|
244
|
+
end
|
245
|
+
|
246
|
+
def total_taxes_params
|
247
|
+
charged_details[:tax_details].map do |tax, values|
|
248
|
+
{
|
249
|
+
alicuotaIva: tax.to_s,
|
250
|
+
baseImpIva: values[:total_taxed].to_s,
|
251
|
+
importeIva: values[:tax_total].to_s
|
252
|
+
}
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def charged_details
|
257
|
+
return @charged_details unless @charged_details.nil? || @charged_details.empty?
|
258
|
+
|
259
|
+
if items.nil? || items.empty?
|
260
|
+
raise DataError.new("In order to save an SellInvoice you should at least have one item in it")
|
261
|
+
else
|
262
|
+
default_object = {
|
263
|
+
total_taxed: 0.00,
|
264
|
+
total_nontaxed: 0.00,
|
265
|
+
total: 0.00,
|
266
|
+
tax_total: 0.00,
|
267
|
+
tax_details:{}
|
268
|
+
}
|
269
|
+
@charged_details = items.each_with_object(default_object) do |item, result|
|
270
|
+
product_data = (item[:product] && item[:product].data) || {}
|
271
|
+
iva = (item[:IVA] || product_data[:iva] || 0).to_f
|
272
|
+
charged = (item[:ImporteUnitario] || product_data[:precioVenta] || 0).to_f
|
273
|
+
quantity = (item[:Cantidad] || 0).to_i
|
274
|
+
total = ( charged * quantity ).round(2)
|
275
|
+
if iva > 0
|
276
|
+
tax = (( total * iva ) / 100).round(2)
|
277
|
+
result[:tax_total] += tax
|
278
|
+
result[:total_taxed] += total - tax
|
279
|
+
result[:tax_details][iva] = {
|
280
|
+
tax_total: result[:tax_total],
|
281
|
+
total_taxed: result[:total_taxed]
|
282
|
+
}
|
283
|
+
else
|
284
|
+
result[:total_nontaxed] += total
|
285
|
+
end
|
286
|
+
result[:total] += total
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def ensure_editability!
|
292
|
+
unless editable?
|
293
|
+
raise ResourceError.new("You cannot change any value of this invoice, because it's already processed by AFIP. You should create a new one instead")
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def invoice_status
|
298
|
+
type = @data[:idEstadoFactura]
|
299
|
+
if type && VALID_INVOICE_STATUS.include?(type)
|
300
|
+
type
|
301
|
+
else
|
302
|
+
raise DataError.new("The value of idEstadoFactura:#{type} is invalid. The value should be any of this ones: #{VALID_INVOICE_STATUS.join(", ")}")
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def invoice_type
|
307
|
+
type = @data[:idTipoFactura]
|
308
|
+
if type && VALID_INVOICE_TYPES.include?(type)
|
309
|
+
type
|
310
|
+
else
|
311
|
+
raise DataError.new("The idTipoFactura:#{type} is invalid. The value should be any of this ones: #{VALID_INVOICE_TYPES.join(", ")}")
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def receipt_type
|
316
|
+
type = @data[:idTipoComprobante]
|
317
|
+
if type && VALID_RECEIPT_TYPES.include?(type)
|
318
|
+
type
|
319
|
+
else
|
320
|
+
raise DataError.new("The idTipoComprobante:#{type} is invalid. The value should be any of this ones: #{VALID_RECEIPT_TYPES.join(", ")}")
|
321
|
+
""
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# "totalesiva":[
|
328
|
+
# {
|
329
|
+
# alicuotaIva: "0",
|
330
|
+
# baseImpIva: "0.00",
|
331
|
+
# importeIva: "0.00"
|
332
|
+
# },
|
333
|
+
# {
|
334
|
+
# alicuotaIva: "21",
|
335
|
+
# baseImpIva: "101.65",
|
336
|
+
# importeIva: "21.35"
|
337
|
+
# },
|
338
|
+
# {
|
339
|
+
# alicuotaIva: "27",
|
340
|
+
# baseImpIva: "0.00",
|
341
|
+
# importeIva: "0.00"
|
342
|
+
# }
|
343
|
+
# ]
|