import observaciones as obs def _item(numero, line_total=100.0, quantity=10.0, error=None): item = {"guia": {"tipo": "09", "serie": "T001", "numero": numero}, "line_total": line_total, "quantity": quantity} if error: item["error"] = error return item def _resultado(ok, guias, sap_price=100.0, label="LineTotal (FL*) vs total_linea"): return {"ok": ok, "guias_lt": guias, "sap_price": sap_price, "label": label} FACTURA = {"lineas": [{"total_linea": 624.38, "descripcion": "Servicio A"}]} def test_formato(): assert obs.moneda(1250) == "S/ 1,250.00" assert obs.moneda(None) == "S/ 0.00" assert obs.texto_observaciones(1) == "1 observación" assert obs.texto_observaciones(3) == "3 observaciones" def test_lineas_conformes_no_generan_observaciones(): assert obs.desde_validacion(FACTURA, [_resultado(True, [_item("1")])]) == [] def test_importe_distinto_muestra_ambos_importes(): (o,) = obs.desde_validacion(FACTURA, [_resultado(False, [_item("1"), _item("2")], sap_price=674.38)]) assert o.codigo == "precio_no_coincide" assert dict(o.filas) == {"Guías": "T001-1, T001-2", "Importe en su factura": "S/ 624.38", "Importe esperado": "S/ 674.38"} assert "Línea" not in dict(o.filas) # con una sola línea no hace falta numerarla def test_con_varias_lineas_se_indica_cual(): factura = {"lineas": [{"total_linea": 10}, {"total_linea": 20}]} resultados = [_resultado(True, [_item("1")]), _resultado(False, [_item("2")], sap_price=25.0)] (o,) = obs.desde_validacion(factura, resultados) assert dict(o.filas)["Línea"] == "2" def test_guia_sin_datos_en_sap_es_guia_no_encontrada_y_no_importe(): guias = [_item("1", 0.0, 0.0), _item("2", 0.0, 0.0)] observaciones = obs.desde_validacion(FACTURA, [_resultado(False, guias, 0.0, "Sin guías SAP")]) assert [o.codigo for o in observaciones] == ["guia_no_encontrada", "guia_no_encontrada"] assert [o.titulo for o in observaciones] == ["No encontramos la guía T001-1", "No encontramos la guía T001-2"] def test_una_guia_encontrada_y_otra_no_da_ambas_observaciones(): guias = [_item("1", 0.0, 0.0), _item("2", 40.0, 5.0)] observaciones = obs.desde_validacion(FACTURA, [_resultado(False, guias, sap_price=40.0)]) assert [o.codigo for o in observaciones] == ["guia_no_encontrada", "precio_no_coincide"] def test_una_guia_con_error_de_consulta_no_se_reporta_como_no_encontrada(): guias = [_item("1", 0.0, 0.0, error="timeout")] assert obs.desde_validacion(FACTURA, [_resultado(False, guias, 0.0)]) == [ obs.precio_no_coincide(1, 624.38, 0.0, ["T001-1"], False) ] def test_lineas_sin_guia_o_con_cantidad_cero(): factura = {"lineas": [{"descripcion": "Flete a Lima", "total_linea": 5}, {"total_linea": 7}]} resultados = [_resultado(False, [], label="Sin guía de referencia"), _resultado(False, [], label="Cantidad cero, sin validar")] sin_guia, cero = obs.desde_validacion(factura, resultados) assert sin_guia.codigo == "linea_sin_guia" and dict(sin_guia.filas) == {"Línea": "1", "Descripción": "Flete a Lima"} assert cero.codigo == "cantidad_cero" and dict(cero.filas) == {"Línea": "2"} def test_motivos_de_correo_sin_comprobante(): assert obs.sin_pdf_xml(2).filas == (("Archivos recibidos", "2"),) assert obs.falta_xml(1).codigo == "falta_xml" assert obs.sin_factura(["Guía de Remisión"]).filas == (("Documentos recibidos", "Guía de Remisión"),) assert obs.sin_factura([]).filas == (("Documentos recibidos", "ninguno reconocible"),) def test_todas_las_observaciones_tienen_titulo_y_como_corregir(): muestras = [ obs.precio_no_coincide(1, 1, 2, ["T001-1"], True), obs.guia_no_encontrada("T001-1"), obs.linea_sin_guia(1, ""), obs.cantidad_cero(1), obs.sin_pdf_xml(0), obs.falta_xml(1), obs.sin_factura([]), ] for o in muestras: assert o.titulo and o.como_corregir and o.codigo assert len({o.codigo for o in muestras}) == len(muestras) def test_a_dict_es_serializable(): d = obs.guia_no_encontrada("T001-9").a_dict() assert d == {"codigo": "guia_no_encontrada", "titulo": "No encontramos la guía T001-9", "filas": [{"etiqueta": "Guía", "valor": "T001-9"}], "como_corregir": d["como_corregir"]}