require 'spec_helper' describe Afipws::WSFE do let(:auth) { {auth: {token: 't', sign: 's', expiration_time: 12.hours.from_now}} } let(:ws) { Afipws::WSFE.new cuit: '1', wsaa: Afipws::WSAA.new.tap { |wsaa| wsaa.stubs auth: auth } } context "Métodos de negocio" do it "dummy" do savon.expects(:fe_dummy).returns(fixture('fe_dummy/success')) ws.dummy.should == { app_server: "OK", db_server: "OK", auth_server: "OK" } end it "tipos_comprobantes" do savon.expects(:fe_param_get_tipos_cbte).with(message: auth).returns(fixture('fe_param_get_tipos_cbte/success')) ws.tipos_comprobantes.should == [ { id: 1, desc: "Factura A", fch_desde: Date.new(2010,9,17), fch_hasta: nil }, { id: 2, desc: "Nota de Débito A", fch_desde: Date.new(2010,9,18), fch_hasta: Date.new(2011,9,18) }] end it "tipos_documentos" do savon.expects(:fe_param_get_tipos_doc).with(message: auth).returns(fixture('fe_param_get_tipos_doc/success')) ws.tipos_documentos.should == [{ id: 80, desc: "CUIT", fch_desde: Date.new(2008,7,25), fch_hasta: nil }] end it "tipos_monedas" do savon.expects(:fe_param_get_tipos_monedas).with(message: auth).returns(fixture('fe_param_get_tipos_monedas/success')) ws.tipos_monedas.should == [ { id: 'PES', desc: "Pesos Argentinos", fch_desde: Date.new(2009,4,3), fch_hasta: nil }, { id: '002', desc: "Dólar Libre EEUU", fch_desde: Date.new(2009,4,16), fch_hasta: nil }] end it "tipos_iva" do savon.expects(:fe_param_get_tipos_iva).with(message: auth).returns(fixture('fe_param_get_tipos_iva/success')) ws.tipos_iva.should == [{ id: 5, desc: "21%", fch_desde: Date.new(2009,2,20), fch_hasta: nil }] end it "tipos_tributos" do savon.expects(:fe_param_get_tipos_tributos).with(message: auth).returns(fixture('fe_param_get_tipos_tributos/success')) ws.tipos_tributos.should == [{ id: 2, desc: "Impuestos provinciales", fch_desde: Date.new(2010,9,17), fch_hasta: nil }] end it "puntos_venta" do savon.expects(:fe_param_get_ptos_venta).with(message: auth).returns(fixture('fe_param_get_ptos_venta/success')) ws.puntos_venta.should == [ { nro: 1, emision_tipo: "CAE", bloqueado: false, fch_baja: nil }, { nro: 2, emision_tipo: "CAEA", bloqueado: true, fch_baja: Date.new(2011,1,31) }] end context "cotizacion" do it "cuando la moneda solicitada existe" do savon.expects(:fe_param_get_cotizacion).with(message: auth.merge(mon_id: 'DOL')).returns(fixture('fe_param_get_cotizacion/dolar')) ws.cotizacion('DOL').should == 3.976 end it "cuando la moneda no existe" do savon.expects(:fe_param_get_cotizacion).with(message: auth.merge(mon_id: 'PES')).returns(fixture('fe_param_get_cotizacion/inexistente')) lambda { ws.cotizacion('PES') }.should raise_error Afipws::WSError, /602: Sin Resultados/ end end it "cant_max_registros_x_lote" do savon.expects(:fe_comp_tot_x_request).with(message: auth).returns(fixture('fe_comp_tot_x_request/success')) ws.cant_max_registros_x_lote.should == 250 end context "autorizar_comprobante" do it "debería devolver un hash con el CAE y su fecha de vencimiento" do savon.expects(:fecae_solicitar).with(message: has_path( '//Auth/Token' => 't', '//FeCAEReq/FeCabReq/CantReg' => 1, '//FeCAEReq/FeCabReq/PtoVta' => 2, '//FeCAEReq/FeCabReq/CbteTipo' => 1, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/DocTipo' => 80, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/DocNro' => 30521189203, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/CbteFch' => 20110113, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/ImpTotal' => 1270.48, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/ImpIVA' => 220.5, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/Iva/AlicIva[1]/Id' => 5, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/Iva/AlicIva[1]/Importe' => 220.5, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/Tributos/Tributo[1]/Id' => 0, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/Tributos/Tributo[1]/BaseImp' => 150, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/Tributos/Tributo[1]/Alic' => 5.2, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/Tributos/Tributo[1]/Importe' => 7.8 )).returns(fixture('fecae_solicitar/autorizacion_1_cbte')) rta = ws.autorizar_comprobantes(cbte_tipo: 1, pto_vta: 2, comprobantes: [{cbte_nro: 1, concepto: 1, doc_nro: 30521189203, doc_tipo: 80, cbte_fch: Date.new(2011,01,13), imp_total: 1270.48, imp_neto: 1049.98, imp_iva: 220.50, mon_id: 'PES', mon_cotiz: 1, iva: { alic_iva: [{ id: 5, base_imp: 1049.98, importe: 220.50 }]}, tributos: { tributo: [{ id: 0, base_imp: 150, alic: 5.2, importe: 7.8 }] } }]) rta[0].should have_entries cae: '61023008595705', cae_fch_vto: Date.new(2011,01,23), cbte_nro: 1, resultado: 'A', observaciones: [] rta.size.should == 1 end it "con varias alicuotas iva" do savon.expects(:fecae_solicitar).with(message: has_path( '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/Iva/AlicIva[1]/Id' => 5, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/Iva/AlicIva[1]/Importe' => 21, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/Iva/AlicIva[2]/Id' => 4, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/Iva/AlicIva[2]/Importe' => 5.25 )).returns(fixture('fecae_solicitar/autorizacion_1_cbte')) ws.autorizar_comprobantes(cbte_tipo: 1, pto_vta: 2, comprobantes: [{iva: {alic_iva: [ { id: 5, base_imp: 100, importe: 21 }, { id: 4, base_imp: 50, importe: 5.25 } ]}}]) end it "con varios comprobantes aprobados" do savon.expects(:fecae_solicitar).with(message: has_path( '//FeCAEReq/FeCabReq/CantReg' => 2, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/CbteDesde' => 5, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/CbteHasta' => 5, '//FeCAEReq/FeDetReq/FECAEDetRequest[2]/CbteDesde' => 6, '//FeCAEReq/FeDetReq/FECAEDetRequest[2]/CbteHasta' => 6, )).returns(fixture('fecae_solicitar/autorizacion_2_cbtes')) rta = ws.autorizar_comprobantes(cbte_tipo: 1, pto_vta: 2, comprobantes: [{ cbte_nro: 5 }, { cbte_nro: 6 }]) rta[0].should have_entries cbte_nro: 5, cae: '61033008894096' rta[1].should have_entries cbte_nro: 6, cae: '61033008894101' end it "con 2 observaciones" do savon.expects(:fecae_solicitar).with(message: :any).returns(fixture 'fecae_solicitar/dos_observaciones') rta = ws.autorizar_comprobantes comprobantes: [] rta[0].should have_entries cbte_nro: 3, cae: nil, resultado: 'R', observaciones: [ {code: 10048, msg: 'Msg 1'}, {code: 10018, msg: 'Msg 2'}] end it "con 1 observación" do savon.expects(:fecae_solicitar).with(message: :any).returns(fixture 'fecae_solicitar/una_observacion') rta = ws.autorizar_comprobantes comprobantes: [] rta[0].should have_entries observaciones: [{code: 10048, msg: 'Msg 1'}] end end context "solicitar_caea" do it "debería mandar automáticamente el período y orden" do Date.stubs today: Date.new(2011,1,27) savon.expects(:fecaea_solicitar).with(message: has_path('//Periodo' => '201102', '//Orden' => 1)).returns(fixture 'fecaea_solicitar/success') ws.solicitar_caea.should have_entries caea: '21043476341977', fch_tope_inf: Date.new(2011,03,17), fch_vig_desde: Date.new(2011,02,01), fch_vig_hasta: Date.new(2011,02,15) end context "periodo_para_solicitud_caea" do it "cuando estoy en la primer quincena" do Date.stubs today: Date.new(2011,1,12) ws.periodo_para_solicitud_caea.should == { periodo: '201101', orden: 2 } Date.stubs today: Date.new(2011,1,15) ws.periodo_para_solicitud_caea.should == { periodo: '201101', orden: 2 } end it "cuando estoy en la segunda quincena" do Date.stubs today: Date.new(2011,1,16) ws.periodo_para_solicitud_caea.should == { periodo: '201102', orden: 1 } Date.stubs today: Date.new(2011,1,31) ws.periodo_para_solicitud_caea.should == { periodo: '201102', orden: 1 } end end it "cuando el caea ya fue otorgado debería consultarlo y devolverlo" do Date.stubs today: Date.new(2011,1,27) savon.expects(:fecaea_solicitar).with(message: has_path('//Periodo' => '201102', '//Orden' => 1)).returns(fixture 'fecaea_solicitar/caea_ya_otorgado') savon.expects(:fecaea_consultar).with(message: has_path('//Periodo' => '201102', '//Orden' => 1)).returns(fixture 'fecaea_consultar/success') ws.solicitar_caea.should have_entries caea: '21043476341977', fch_vig_desde: Date.new(2011,02,01) end it "cuando hay otro error debería burbujearlo" do savon.expects(:fecaea_solicitar).with(message: :any).returns(fixture 'fecaea_solicitar/error_distinto') lambda { ws.solicitar_caea }.should raise_error Afipws::WSError, /15007/ end end it "informar_comprobantes_caea" do savon.expects(:fecaea_reg_informativo).with(message: has_path( '//Auth/Token' => 't', '//FeCAEARegInfReq/FeCabReq/CantReg' => 2, '//FeCAEARegInfReq/FeCabReq/PtoVta' => 3, '//FeCAEARegInfReq/FeCabReq/CbteTipo' => 1, '//FeCAEARegInfReq/FeDetReq/FECAEADetRequest[1]/CbteDesde' => 1, '//FeCAEARegInfReq/FeDetReq/FECAEADetRequest[1]/CbteHasta' => 1, '//FeCAEARegInfReq/FeDetReq/FECAEADetRequest[1]/CAEA' => '21043476341977', '//FeCAEARegInfReq/FeDetReq/FECAEADetRequest[2]/CbteDesde' => 2, '//FeCAEARegInfReq/FeDetReq/FECAEADetRequest[2]/CbteHasta' => 2, '//FeCAEARegInfReq/FeDetReq/FECAEADetRequest[2]/CAEA' => '21043476341977', )).returns(fixture 'fecaea_reg_informativo/informe_rtdo_parcial') rta = ws.informar_comprobantes_caea(cbte_tipo: 1, pto_vta: 3, comprobantes: [ { cbte_nro: 1, caea: '21043476341977' }, { cbte_nro: 2, caea: '21043476341977' }, ]) rta[0].should have_entries cbte_nro: 1, caea: '21043476341977', resultado: 'A', observaciones: [] rta[1].should have_entries cbte_nro: 2, caea: '21043476341977', resultado: 'R', observaciones: [{code: 724, msg: 'Msg'}] end it "informar_caea_sin_movimientos" do savon.expects(:fecaea_sin_movimiento_informar).with(message: has_path( '//Auth/Token' => 't', '//PtoVta' => 4, '//CAEA' => '21043476341977' )).returns(fixture 'fecaea_sin_movimiento_informar/success') rta = ws.informar_caea_sin_movimientos('21043476341977', 4) rta.should have_entries caea: '21043476341977', resultado: 'A' end context "consultar_caea" do it "consultar_caea" do savon.expects(:fecaea_consultar).with(message: has_path('//Periodo' => '201101', '//Orden' => 1)).returns(fixture 'fecaea_consultar/success') ws.consultar_caea(Date.new(2011,1,1)).should have_entries caea: '21043476341977', fch_tope_inf: Date.new(2011,03,17) end end it "ultimo_comprobante_autorizado" do savon.expects(:fe_comp_ultimo_autorizado).with(message: has_path('//PtoVta' => 1, '//CbteTipo' => 1)).returns(fixture 'fe_comp_ultimo_autorizado/success') ws.ultimo_comprobante_autorizado(pto_vta: 1, cbte_tipo: 1).should == 20 end it "consultar_comprobante" do savon.expects(:fe_comp_consultar).with(message: has_path( '//Auth/Token' => 't', '//FeCompConsReq/PtoVta' => 1, '//FeCompConsReq/CbteTipo' => 2, '//FeCompConsReq/CbteNro' => 3 )).returns(fixture 'fe_comp_consultar/success') rta = ws.consultar_comprobante(pto_vta: 1, cbte_tipo: 2, cbte_nro: 3) rta[:cod_autorizacion].should == '61023008595705' rta[:emision_tipo].should == 'CAE' end end context "autenticacion" do it "debería autenticarse usando el WSAA" do wsfe = Afipws::WSFE.new cuit: '1', cert: 'cert', key: 'key' wsfe.wsaa.cert.should == 'cert' wsfe.wsaa.key.should == 'key' wsfe.wsaa.service.should == 'wsfe' wsfe.wsaa.expects(:login).returns({ token: 't', sign: 's' }) savon.expects(:fe_param_get_tipos_cbte).with(message: has_path( '//Auth/Token' => 't', '//Auth/Sign' => 's', '//Auth/Cuit' => '1' )).returns(fixture 'fe_param_get_tipos_cbte/success') wsfe.tipos_comprobantes end end context "entorno" do it "debería usar las url para development cuando el env es development" do Afipws::Client.expects(:new).with(wsdl: 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl') Afipws::Client.expects(:new).with(wsdl: 'https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL', ssl_version: :TLSv1, convert_request_keys_to: :camelcase) wsfe = Afipws::WSFE.new env: :development wsfe.env.should == :development end it "debería usar las url para production cuando el env es production" do Afipws::Client.expects(:new).with(wsdl: 'https://wsaa.afip.gov.ar/ws/services/LoginCms?wsdl') Afipws::Client.expects(:new).with(has_entries wsdl: File.expand_path(File.dirname(__FILE__) + '/../../') + "/lib/afipws/wsfev1.wsdl") wsfe = Afipws::WSFE.new env: 'production' wsfe.env.should == :production end end context "manejo de errores" do it "cuando hay un error" do savon.expects(:fe_param_get_tipos_cbte).with(message: :any).returns(fixture 'fe_param_get_tipos_cbte/failure_1_error') lambda { ws.tipos_comprobantes }.should raise_error { |e| e.should be_a Afipws::WSError e.errors.should == [{ code: "600", msg: "No se corresponden token con firma" }] e.message.should == "600: No se corresponden token con firma" } end it "cuando hay varios errores" do savon.expects(:fe_param_get_tipos_cbte).with(message: :any).returns(fixture 'fe_param_get_tipos_cbte/failure_2_errors') lambda { ws.tipos_comprobantes }.should raise_error { |e| e.should be_a Afipws::WSError e.errors.should == [{ code: "600", msg: "No se corresponden token con firma" }, { code: "601", msg: "CUIT representada no incluida en token" }] e.message.should == "600: No se corresponden token con firma; 601: CUIT representada no incluida en token" } end end context "cálculo de fechas y períodos" do it "periodo_para_consulta_caea" do ws.periodo_para_consulta_caea(Date.new(2011,1,1)).should == { periodo: '201101', orden: 1 } ws.periodo_para_consulta_caea(Date.new(2011,1,15)).should == { periodo: '201101', orden: 1 } ws.periodo_para_consulta_caea(Date.new(2011,1,16)).should == { periodo: '201101', orden: 2 } ws.periodo_para_consulta_caea(Date.new(2011,1,31)).should == { periodo: '201101', orden: 2 } ws.periodo_para_consulta_caea(Date.new(2011,2,2)).should == { periodo: '201102', orden: 1 } end it "fecha_inicio_quincena_siguiente" do fecha_inicio_quincena_siguiente(Date.new(2010,1,1)).should == Date.new(2010,1,16) fecha_inicio_quincena_siguiente(Date.new(2010,1,10)).should == Date.new(2010,1,16) fecha_inicio_quincena_siguiente(Date.new(2010,1,15)).should == Date.new(2010,1,16) fecha_inicio_quincena_siguiente(Date.new(2010,1,16)).should == Date.new(2010,2,1) fecha_inicio_quincena_siguiente(Date.new(2010,1,20)).should == Date.new(2010,2,1) fecha_inicio_quincena_siguiente(Date.new(2010,1,31)).should == Date.new(2010,2,1) fecha_inicio_quincena_siguiente(Date.new(2010,12,31)).should == Date.new(2011,1,1) end def fecha_inicio_quincena_siguiente fecha Date.stubs(today: fecha) subject.fecha_inicio_quincena_siguiente end end context "comprobante_to_request" do def c2r comprobante subject.comprobante_to_request comprobante end it "no debería enviar tag tributos si el impTrib es 0" do c2r(imp_trib: 0.0, tributos: { tributo: [] }).should_not have_key :tributos end end end