require 'spec_helper' module Afipws describe WSFE do let(:ta) { {token: 't', sign: 's'} } let(:ws) { WSFE.new(cuit: '1').tap { |ws| ws.wsaa.stubs auth: ta } } let(:auth) { {auth: ta.merge(cuit: '1')} } 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_concepto' do savon.expects(:fe_param_get_tipos_concepto).with(message: auth).returns(fixture('fe_param_get_tipos_concepto/success')) ws.tipos_concepto.should == [{ id: 1, desc: "Producto", 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_opcional' do savon.expects(:fe_param_get_tipos_opcional).with(message: auth).returns(fixture('fe_param_get_tipos_opcional/success')) ws.tipos_opcional.should == [] 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')) -> { ws.cotizacion('PES') }.should raise_error 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' => 30_521_189_203, '//FeCAEReq/FeDetReq/FECAEDetRequest[1]/CbteFch' => 20_110_113, '//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: 30_521_189_203, doc_tipo: 80, cbte_fch: Date.new(2011, 0o1, 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, 0o1, 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: 10_048, msg: 'Msg 1'}, {code: 10_018, 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: 10_048, 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, 0o3, 17), fch_vig_desde: Date.new(2011, 0o2, 0o1), fch_vig_hasta: Date.new(2011, 0o2, 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, 0o2, 0o1) end it 'cuando hay otro error debería burbujearlo' do savon.expects(:fecaea_solicitar).with(message: :any).returns(fixture('fecaea_solicitar/error_distinto')) -> { ws.solicitar_caea }.should raise_error 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, 0o3, 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 before { FileUtils.rm_rf Dir.glob('tmp/*-test-*-ta.dump') } it 'debería autenticarse usando el WSAA' do wsfe = 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 Client.expects(:new).with(wsdl: 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl') Client.expects(:new).with(wsdl: 'https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL', convert_request_keys_to: :camelcase) wsfe = WSFE.new env: :development wsfe.env.should == :development end it 'debería usar las url para production cuando el env es production' do Client.expects(:new).with(wsdl: 'https://wsaa.afip.gov.ar/ws/services/LoginCms?wsdl') Client.expects(:new).with(wsdl: 'https://servicios1.afip.gov.ar/wsfev1/service.asmx?WSDL', convert_request_keys_to: :camelcase) wsfe = 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')) -> { ws.tipos_comprobantes }.should raise_error { |e| e.should be_a 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')) -> { ws.tipos_comprobantes }.should raise_error { |e| e.should be_a 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 end