From 950eb2a826fbfb043234c2819a4088ba8d850c0b Mon Sep 17 00:00:00 2001 From: "ewerton.almeida" Date: Mon, 11 Aug 2025 13:14:54 -0300 Subject: [PATCH] =?UTF-8?q?Cria=C3=A7=C3=A3o=20da=20tela=20de=20clientes?= =?UTF-8?q?=20e=20relat=C3=B3rios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/main.py | 318 +++++++++++++++++++++++----------- app/models.py | 4 +- app/parametros.py | 11 +- app/processor.py | 46 ++++- app/templates/dashboard.html | 10 +- app/templates/relatorios.html | 238 ++++++++++++++++++++++--- app/templates/upload.html | 149 ++++++++++++---- 7 files changed, 595 insertions(+), 181 deletions(-) diff --git a/app/main.py b/app/main.py index 816c496..3f41161 100644 --- a/app/main.py +++ b/app/main.py @@ -1,6 +1,6 @@ import asyncio import uuid -from fastapi import FastAPI, HTTPException, Request, UploadFile, File +from fastapi import FastAPI, HTTPException, Request, UploadFile, File, Depends, Form from fastapi.templating import Jinja2Templates from fastapi.responses import HTMLResponse, JSONResponse from fastapi.staticfiles import StaticFiles @@ -25,6 +25,10 @@ from app.models import Fatura, SelicMensal, ParametrosFormula from datetime import date from app.utils import avaliar_formula from app.routes import clientes +from sqlalchemy.ext.asyncio import AsyncSession +from app.database import get_session +from fastapi import Query +from sqlalchemy import select as sqla_select app = FastAPI() @@ -32,7 +36,7 @@ templates = Jinja2Templates(directory="app/templates") app.state.templates = templates app.mount("/static", StaticFiles(directory="app/static"), name="static") -UPLOAD_DIR = "uploads/temp" +UPLOAD_DIR = os.path.join("app", "uploads", "temp") os.makedirs(UPLOAD_DIR, exist_ok=True) def _parse_referencia(ref: str): @@ -99,7 +103,27 @@ def _avaliar_formula(texto_formula: str | None, contexto: dict) -> float: # aceita vírgula como decimal vindo do banco if isinstance(v, str): v = v.replace(".", "").replace(",", ".") if re.search(r"[0-9],[0-9]", v) else v - expr = re.sub(rf'\b{re.escape(str(campo))}\b', str(v), expr) + # nome do campo escapado na regex + pat = rf'\b{re.escape(str(campo))}\b' + + # normaliza o valor para número; se não der, vira 0 + val = v + if val is None or val == "": + num = 0.0 + else: + if isinstance(val, str): + # troca vírgula decimal e remove separador de milhar simples + val_norm = val.replace(".", "").replace(",", ".") + else: + val_norm = val + try: + num = float(val_norm) + except Exception: + num = 0.0 + + # usa lambda para evitar interpretação de backslashes no replacement + expr = re.sub(pat, lambda m: str(num), expr) + try: return float(eval(expr, {"__builtins__": {}}, {})) @@ -113,10 +137,14 @@ async def dashboard(request: Request, cliente: str | None = None): async with AsyncSessionLocal() as session: print("DBG /: abrindo sessão", flush=True) - r = await session.execute(text( - "SELECT DISTINCT nome FROM faturas.faturas ORDER BY nome" - )) - clientes = [c for c, in r.fetchall()] + r = await session.execute(text(""" + SELECT id, nome_fantasia + FROM faturas.clientes + WHERE ativo = TRUE + ORDER BY nome_fantasia + """)) + clientes = [{"id": id_, "nome": nome} for id_, nome in r.fetchall()] + print(f"DBG /: clientes={len(clientes)}", flush=True) # Fórmulas @@ -135,7 +163,7 @@ async def dashboard(request: Request, cliente: str | None = None): sql = "SELECT * FROM faturas.faturas" params = {} if cliente: - sql += " WHERE nome = :cliente" + sql += " WHERE cliente_id = :cliente" params["cliente"] = cliente print("DBG /: SQL faturas ->", sql, params, flush=True) @@ -254,21 +282,58 @@ def upload_page(request: Request): }) @app.get("/relatorios", response_class=HTMLResponse) -def relatorios_page(request: Request): - return templates.TemplateResponse("relatorios.html", {"request": request}) +async def relatorios_page(request: Request, cliente: str | None = Query(None)): + async with AsyncSessionLocal() as session: + # Carregar clientes ativos para o combo + r_cli = await session.execute(text(""" + SELECT id, nome_fantasia + FROM faturas.clientes + WHERE ativo = TRUE + ORDER BY nome_fantasia + """)) + clientes = [{"id": str(row.id), "nome": row.nome_fantasia} for row in r_cli] + + # Carregar faturas (todas ou filtradas por cliente) + if cliente: + r_fat = await session.execute(text(""" + SELECT * + FROM faturas.faturas + WHERE cliente_id = :cid + ORDER BY data_processamento DESC + """), {"cid": cliente}) + else: + r_fat = await session.execute(text(""" + SELECT * + FROM faturas.faturas + ORDER BY data_processamento DESC + """)) + + faturas = r_fat.mappings().all() + + return templates.TemplateResponse("relatorios.html", { + "request": request, + "clientes": clientes, + "cliente_selecionado": cliente or "", + "faturas": faturas + }) @app.post("/upload-files") -async def upload_files(files: list[UploadFile] = File(...)): +async def upload_files( + cliente_id: str = Form(...), + files: list[UploadFile] = File(...) +): for file in files: temp_path = os.path.join(UPLOAD_DIR, f"{uuid.uuid4()}_{file.filename}") with open(temp_path, "wb") as f: shutil.copyfileobj(file.file, f) await fila_processamento.put({ "caminho_pdf": temp_path, - "nome_original": file.filename + "nome_original": file.filename, + "cliente_id": cliente_id }) return {"message": "Arquivos enviados para fila"} + @app.post("/process-queue") async def process_queue(): resultados = await processar_em_lote() @@ -307,117 +372,109 @@ async def clear_all(): return {"message": "Fila e arquivos limpos"} @app.get("/export-excel") -async def export_excel(): +async def export_excel( + tipo: str = Query("geral", regex="^(geral|exclusao_icms|aliquota_icms)$"), + cliente: str | None = Query(None) +): import pandas as pd + + # 1) Carregar faturas (com filtro por cliente, se houver) async with AsyncSessionLocal() as session: - # 1. Coletar faturas e tabela SELIC - faturas_result = await session.execute(select(Fatura)) + stmt = select(Fatura) + if cliente: + # filtra por cliente_id (UUID em string) + stmt = stmt.where(Fatura.cliente_id == cliente) + faturas_result = await session.execute(stmt) faturas = faturas_result.scalars().all() - selic_result = await session.execute(select(SelicMensal)) - selic_tabela = selic_result.scalars().all() - - # 2. Criar mapa {(ano, mes): percentual} - selic_map = {(s.ano, s.mes): float(s.percentual) for s in selic_tabela} - hoje = date.today() - - def calcular_fator_selic(ano_inicio, mes_inicio): - fator = 1.0 - ano, mes = ano_inicio, mes_inicio - while (ano < hoje.year) or (ano == hoje.year and mes <= hoje.month): - percentual = selic_map.get((ano, mes)) - if percentual: - fator *= (1 + percentual / 100) - mes += 1 - if mes > 12: - mes = 1 - ano += 1 - return fator - - # 3. Buscar fórmulas exatas por nome - formula_pis_result = await session.execute( - select(ParametrosFormula.formula).where( - ParametrosFormula.nome == "Cálculo PIS sobre ICMS", - ParametrosFormula.ativo == True - ).limit(1) - ) - formula_cofins_result = await session.execute( - select(ParametrosFormula.formula).where( - ParametrosFormula.nome == "Cálculo COFINS sobre ICMS", - ParametrosFormula.ativo == True - ).limit(1) - ) - - formula_pis = formula_pis_result.scalar_one_or_none() - formula_cofins = formula_cofins_result.scalar_one_or_none() - - # 4. Montar dados - mes_map = { - 'JAN': 1, 'FEV': 2, 'MAR': 3, 'ABR': 4, 'MAI': 5, 'JUN': 6, - 'JUL': 7, 'AGO': 8, 'SET': 9, 'OUT': 10, 'NOV': 11, 'DEZ': 12 - } - + # 2) Montar dados conforme 'tipo' dados = [] - for f in faturas: - try: - if "/" in f.referencia: - mes_str, ano_str = f.referencia.split("/") - mes = mes_map.get(mes_str.strip().upper()) - ano = int(ano_str) - if not mes or not ano: - raise ValueError("Mês ou ano inválido") - else: - ano = int(f.referencia[:4]) - mes = int(f.referencia[4:]) - fator = calcular_fator_selic(ano, mes) - periodo = f"{mes:02d}/{ano} à {hoje.month:02d}/{hoje.year}" - - contexto = f.__dict__ - valor_pis_icms = avaliar_formula(formula_pis, contexto) if formula_pis else None - valor_cofins_icms = avaliar_formula(formula_cofins, contexto) if formula_cofins else None + if tipo == "aliquota_icms": + # Campos: (Cliente, UC, Referência, Valor Total, ICMS (%), ICMS (R$), + # Base ICMS (R$), Consumo (kWh), Tarifa, Nota Fiscal) + for f in faturas: dados.append({ - "Nome": f.nome, + "Cliente": f.nome, + "UC": f.unidade_consumidora, + "Referência": f.referencia, + "Valor Total": f.valor_total, + "ICMS (%)": f.icms_aliq, + "ICMS (R$)": f.icms_valor, + "Base ICMS (R$)": f.icms_base, + "Consumo (kWh)": f.consumo, + "Tarifa": f.tarifa, + "Nota Fiscal": f.nota_fiscal, + }) + filename = "relatorio_aliquota_icms.xlsx" + + elif tipo == "exclusao_icms": + # Campos: (Cliente, UC, Referência, Valor Total, PIS (%), ICMS (%), COFINS (%), + # PIS (R$), ICMS (R$), COFINS (R$), Base PIS (R$), Base ICMS (R$), + # Base COFINS (R$), Consumo (kWh), Tarifa, Nota Fiscal) + for f in faturas: + dados.append({ + "Cliente": f.nome, + "UC": f.unidade_consumidora, + "Referência": f.referencia, + "Valor Total": f.valor_total, + "PIS (%)": f.pis_aliq, + "ICMS (%)": f.icms_aliq, + "COFINS (%)": f.cofins_aliq, + "PIS (R$)": f.pis_valor, + "ICMS (R$)": f.icms_valor, + "COFINS (R$)": f.cofins_valor, + "Base PIS (R$)": f.pis_base, + "Base ICMS (R$)": f.icms_base, + "Base COFINS (R$)": f.cofins_base, + "Consumo (kWh)": f.consumo, + "Tarifa": f.tarifa, + "Nota Fiscal": f.nota_fiscal, + }) + filename = "relatorio_exclusao_icms.xlsx" + + else: # "geral" (mantém seu relatório atual — sem fórmulas SELIC) + # Se quiser manter exatamente o que já tinha com SELIC e fórmulas, + # você pode copiar sua lógica anterior aqui. Abaixo deixo um "geral" + # simplificado com as colunas principais. + for f in faturas: + dados.append({ + "Cliente": f.nome, "UC": f.unidade_consumidora, "Referência": f.referencia, "Nota Fiscal": f.nota_fiscal, "Valor Total": f.valor_total, "ICMS (%)": f.icms_aliq, "ICMS (R$)": f.icms_valor, - "Base ICMS": f.icms_base, + "Base ICMS (R$)": f.icms_base, "PIS (%)": f.pis_aliq, "PIS (R$)": f.pis_valor, - "Base PIS": f.pis_base, + "Base PIS (R$)": f.pis_base, "COFINS (%)": f.cofins_aliq, "COFINS (R$)": f.cofins_valor, - "Base COFINS": f.cofins_base, + "Base COFINS (R$)": f.cofins_base, "Consumo (kWh)": f.consumo, "Tarifa": f.tarifa, - "Cidade": f.cidade, - "Estado": f.estado, "Distribuidora": f.distribuidora, "Data Processamento": f.data_processamento, - "Fator SELIC acumulado": fator, - "Período SELIC usado": periodo, - "PIS sobre ICMS": valor_pis_icms, - "Valor Corrigido PIS (ICMS)": valor_pis_icms * fator if valor_pis_icms else None, - "COFINS sobre ICMS": valor_cofins_icms, - "Valor Corrigido COFINS (ICMS)": valor_cofins_icms * fator if valor_cofins_icms else None, }) - except Exception as e: - print(f"Erro ao processar fatura {f.nota_fiscal}: {e}") - - df = pd.DataFrame(dados) + filename = "relatorio_geral.xlsx" + # 3) Gerar excel em memória + from io import BytesIO output = BytesIO() + df = pd.DataFrame(dados) with pd.ExcelWriter(output, engine="xlsxwriter") as writer: - df.to_excel(writer, index=False, sheet_name="Faturas Corrigidas") - + df.to_excel(writer, index=False, sheet_name="Relatório") output.seek(0) - return StreamingResponse(output, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={ - "Content-Disposition": "attachment; filename=faturas_corrigidas.xlsx" - }) - + + # 4) Responder + return StreamingResponse( + output, + media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + headers={"Content-Disposition": f'attachment; filename="{filename}"'} + ) + from app.parametros import router as parametros_router app.include_router(parametros_router) @@ -474,3 +531,70 @@ async def limpar_erros(): caminho = os.path.join(pasta, nome) if os.path.exists(caminho): os.remove(caminho) + +@app.get("/api/clientes") +async def listar_clientes(db: AsyncSession = Depends(get_session)): + sql = text(""" + SELECT id, nome_fantasia, cnpj, ativo + FROM faturas.clientes + WHERE ativo = TRUE + ORDER BY nome_fantasia + """) + res = await db.execute(sql) + rows = res.mappings().all() + return [ + { + "id": str(r["id"]), + "nome_fantasia": r["nome_fantasia"], + "cnpj": r["cnpj"], + "ativo": bool(r["ativo"]), + } + for r in rows + ] + +@app.get("/api/relatorios") +async def api_relatorios( + cliente: str | None = Query(None), + page: int = Query(1, ge=1), + page_size: int = Query(20, ge=5, le=200), + db: AsyncSession = Depends(get_session), +): + offset = (page - 1) * page_size + + where = "WHERE cliente_id = :cliente" if cliente else "" + params = {"limit": page_size, "offset": offset} + if cliente: + params["cliente"] = cliente + + sql = text(f""" + SELECT id, nome, unidade_consumidora, referencia, nota_fiscal, + valor_total, icms_aliq, icms_valor, pis_aliq, pis_valor, + cofins_aliq, cofins_valor, distribuidora, data_processamento + FROM faturas.faturas + {where} + ORDER BY data_processamento DESC + LIMIT :limit OFFSET :offset + """) + count_sql = text(f"SELECT COUNT(*) AS total FROM faturas.faturas {where}") + + rows = (await db.execute(sql, params)).mappings().all() + total = (await db.execute(count_sql, params)).scalar_one() + + items = [{ + "id": str(r["id"]), + "nome": r["nome"], + "unidade_consumidora": r["unidade_consumidora"], + "referencia": r["referencia"], + "nota_fiscal": r["nota_fiscal"], + "valor_total": float(r["valor_total"]) if r["valor_total"] is not None else None, + "icms_aliq": r["icms_aliq"], + "icms_valor": r["icms_valor"], + "pis_aliq": r["pis_aliq"], + "pis_valor": r["pis_valor"], + "cofins_aliq": r["cofins_aliq"], + "cofins_valor": r["cofins_valor"], + "distribuidora": r["distribuidora"], + "data_processamento": r["data_processamento"].isoformat() if r["data_processamento"] else None, + } for r in rows] + + return {"items": items, "total": total, "page": page, "page_size": page_size} \ No newline at end of file diff --git a/app/models.py b/app/models.py index 0092e17..98e2278 100755 --- a/app/models.py +++ b/app/models.py @@ -1,5 +1,5 @@ # 📄 models.py -from sqlalchemy import Column, String, Integer, Float, DateTime, Text +from sqlalchemy import Column, String, Integer, Float, DateTime, Text, ForeignKey from sqlalchemy.dialects.postgresql import UUID import uuid from datetime import datetime @@ -50,6 +50,8 @@ class Fatura(Base): estado = Column(String) distribuidora = Column(String) link_arquivo = Column("link_arquivo", String) + cliente_id = Column(UUID(as_uuid=True), ForeignKey("faturas.clientes.id"), nullable=False) + class LogProcessamento(Base): diff --git a/app/parametros.py b/app/parametros.py index 3f93418..37f5203 100644 --- a/app/parametros.py +++ b/app/parametros.py @@ -93,8 +93,9 @@ async def editar_parametro(param_id: int, request: Request): async with AsyncSessionLocal() as session: param = await session.get(ParametrosFormula, param_id) if param: - param.tipo = data.get("tipo", param.tipo) + param.nome = data.get("nome", param.nome) param.formula = data.get("formula", param.formula) + param.ativo = data.get("ativo", param.ativo) await session.commit() return {"success": True} return {"success": False, "error": "Não encontrado"} @@ -140,21 +141,21 @@ async def adicionar_aliquota(aliq: AliquotaUFSchema, db: AsyncSession = Depends( @router.get("/parametros/formulas", response_model=List[ParametrosFormulaSchema]) async def listar_formulas(db: AsyncSession = Depends(get_session)): - result = await db.execute(select(ParametrosFormula).order_by(ParametrosFormula.tipo)) + result = await db.execute(select(ParametrosFormula).order_by(ParametrosFormula.nome)) return result.scalars().all() @router.post("/parametros/formulas") async def salvar_formula(form: ParametrosFormulaSchema, db: AsyncSession = Depends(get_session)): result = await db.execute( - select(ParametrosFormula).filter_by(tipo=form.tipo) + select(ParametrosFormula).filter_by(nome=form.nome) ) existente = result.scalar_one_or_none() if existente: existente.formula = form.formula - existente.campos = form.campos + existente.ativo = form.ativo else: - novo = ParametrosFormula(**form.dict()) + novo = ParametrosFormula(nome=form.nome, formula=form.formula, ativo=form.ativo) db.add(novo) await db.commit() diff --git a/app/processor.py b/app/processor.py index 885350b..517868c 100644 --- a/app/processor.py +++ b/app/processor.py @@ -56,7 +56,7 @@ def salvar_em_uploads(caminho_pdf_temp, nome_original, nota_fiscal): logger.error(f"Erro ao salvar em uploads: {e}") return caminho_pdf_temp -async def process_single_file(caminho_pdf_temp: str, nome_original: str): +async def process_single_file(caminho_pdf_temp: str, nome_original: str, cliente_id: str | None = None): inicio = time.perf_counter() async with AsyncSessionLocal() as session: @@ -89,7 +89,10 @@ async def process_single_file(caminho_pdf_temp: str, nome_original: str): dados['link_arquivo'] = caminho_final # Salva fatura - fatura = Fatura(**dados) + dados['cliente_id'] = cliente_id + if cliente_id: + dados['cliente_id'] = cliente_id + fatura = Fatura(**dados) session.add(fatura) await session.commit() @@ -125,15 +128,36 @@ async def processar_em_lote(): while not fila_processamento.empty(): item = await fila_processamento.get() try: - resultado = await process_single_file(item['caminho_pdf'], item['nome_original']) + resultado = await process_single_file( + item['caminho_pdf'], + item['nome_original'], + item.get('cliente_id') + ) + # tentar tamanho/data do TEMP; se não existir mais, tenta do destino final; senão, 0/"" + temp_path = item['caminho_pdf'] + dest_path = (resultado.get("dados") or {}).get("link_arquivo", "") + + def _safe_size(p): + try: + return os.path.getsize(p) // 1024 + except Exception: + return 0 + + def _safe_mtime(p): + try: + return time.strftime("%d/%m/%Y", time.localtime(os.path.getmtime(p))) + except Exception: + return "" + status_arquivos[item['nome_original']] = { "status": resultado.get("status"), "mensagem": resultado.get("mensagem", ""), "tempo": resultado.get("tempo", "---"), - "tamanho": os.path.getsize(item['caminho_pdf']) // 1024, # tamanho em KB - "data": time.strftime("%d/%m/%Y", time.localtime(os.path.getmtime(item['caminho_pdf']))) + "tamanho": _safe_size(temp_path) or _safe_size(dest_path), + "data": _safe_mtime(temp_path) or _safe_mtime(dest_path), } + resultados.append(status_arquivos[item['nome_original']]) except Exception as e: status_arquivos[item['nome_original']] = { @@ -156,17 +180,21 @@ async def processar_em_lote(): erros_txt.append(f"{nome} - {status.get('mensagem', 'Erro desconhecido')}") if erros_txt: - with open(os.path.join(UPLOADS_DIR, "erros", "erros.txt"), "w", encoding="utf-8") as f: + erros_dir = os.path.join(UPLOADS_DIR, "erros") + os.makedirs(erros_dir, exist_ok=True) # <- GARANTE A PASTA + + with open(os.path.join(erros_dir, "erros.txt"), "w", encoding="utf-8") as f: f.write("\n".join(erros_txt)) # Compacta PDFs com erro - with ZipFile(os.path.join(UPLOADS_DIR, "erros", "faturas_erro.zip"), "w") as zipf: + with ZipFile(os.path.join(erros_dir, "faturas_erro.zip"), "w") as zipf: for nome in status_arquivos: if status_arquivos[nome]['status'] == 'Erro': caminho = os.path.join(UPLOADS_DIR, "temp", nome) if os.path.exists(caminho): zipf.write(caminho, arcname=nome) - return resultados + + return resultados def limpar_arquivos_processados(): status_arquivos.clear() @@ -192,6 +220,6 @@ async def garantir_selic_para_competencia(session, ano, mes): if dados: percentual = float(dados[0]["valor"].replace(",", ".")) - novo = SelicMensal(ano=ano, mes=mes, fator=percentual) + novo = SelicMensal(ano=ano, mes=mes, percentual=percentual) session.add(novo) await session.commit() \ No newline at end of file diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 0332d4f..16e32d3 100755 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -100,11 +100,11 @@
- + + {% for c in clientes %} + + {% endfor %}
diff --git a/app/templates/relatorios.html b/app/templates/relatorios.html index 1fe85c1..f027dc2 100755 --- a/app/templates/relatorios.html +++ b/app/templates/relatorios.html @@ -3,43 +3,227 @@ {% block content %}

📊 Relatórios

-
- - -
+
+
+ + +
-
- - 📥 Baixar Relatório Corrigido (Excel) - +
+ + +
+ +
+ + +
+ +
+ 📥 Baixar (Excel) +
- +
- - - + + + + + - - + + + + + + - + {% for f in faturas %} - - - - - - - + + + + + + + + + + + + + {% endfor %}
ClienteData
ClienteUCReferênciaNota Fiscal Valor TotalICMS na BaseStatusICMS (%)ICMS (R$)PIS (R$)COFINS (R$)DistribuidoraProcessado em
{{ f.nome }}{{ f.data_emissao }}R$ {{ '%.2f'|format(f.valor_total)|replace('.', ',') }}{{ 'Sim' if f.com_icms else 'Não' }}{{ f.status }}
{{ f.nome }}{{ f.unidade_consumidora }}{{ f.referencia }}{{ f.nota_fiscal }}R$ {{ '%.2f'|format((f.valor_total or 0.0))|replace('.', ',') }}{{ '%.2f'|format((f.icms_aliq or 0.0))|replace('.', ',') }}R$ {{ '%.2f'|format((f.icms_valor or 0.0))|replace('.', ',') }}R$ {{ '%.2f'|format((f.pis_valor or 0.0))|replace('.', ',') }}R$ {{ '%.2f'|format((f.cofins_valor or 0.0))|replace('.', ',') }}{{ f.distribuidora or '-' }}{{ f.data_processamento }}
+
+
Mostrando 0–0 de 0
+
+ + +
+
+ + + + {% endblock %} diff --git a/app/templates/upload.html b/app/templates/upload.html index 4e5f7e4..f516085 100755 --- a/app/templates/upload.html +++ b/app/templates/upload.html @@ -4,6 +4,16 @@

📤 Upload de Faturas

+ +
+ + + Selecione o cliente antes de anexar/ processar. +
+ +

Arraste faturas em PDF aqui ou clique para selecionar

Apenas PDFs textuais (não escaneados)

@@ -34,7 +44,6 @@
-ar