colppy 0.1.0
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 +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
|
+
# ]
|