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.
@@ -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
+ # ]