new file: .gitea/workflows/deploy.yml modified: compta/templates/compta/base.html modified: compta/templates/compta/gestion.html modified: compta/templates/compta/import_csv.html modified: compta/templates/compta/saisie_paiement.html modified: compta/views_gestion.py modified: compta/views_import.py modified: compta/views_saisie.py
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
name: Deploy via Portainer
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Trigger Portainer stack redeploy
|
||||
run: |
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "${{ secrets.PORTAINER_WEBHOOK }}")
|
||||
echo "HTTP: $HTTP_CODE"
|
||||
[ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "204" ] || exit 1
|
||||
@@ -5,83 +5,146 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>JB Compta — {% block titre %}Tableau de bord{% endblock %}</title>
|
||||
<style>
|
||||
:root { --bg:#0f172a; --card:#1e293b; --txt:#e2e8f0; --muted:#94a3b8;
|
||||
--accent:#38bdf8; --ok:#34d399; --warn:#fbbf24; --bad:#f87171; }
|
||||
:root { --bg:#0b1120; --panel:#0f172a; --card:#1e293b; --txt:#e2e8f0; --muted:#94a3b8;
|
||||
--accent:#38bdf8; --accent2:#0ea5e9; --ok:#34d399; --warn:#fbbf24; --bad:#f87171;
|
||||
--line:#334155; --sbw:236px; }
|
||||
* { box-sizing:border-box; }
|
||||
body { margin:0; font-family:system-ui,Segoe UI,Roboto,sans-serif;
|
||||
background:var(--bg); color:var(--txt); padding:24px; }
|
||||
h1 { margin:0 0 4px; font-size:22px; }
|
||||
h2 { font-size:15px; color:var(--muted); margin:24px 0 10px; font-weight:600; }
|
||||
html,body { margin:0; }
|
||||
body { font-family:system-ui,Segoe UI,Roboto,sans-serif; background:var(--bg); color:var(--txt); }
|
||||
a { color:var(--accent); text-decoration:none; }
|
||||
.sub { color:var(--muted); margin-bottom:16px; }
|
||||
.nav { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px; }
|
||||
.nav a { padding:7px 13px; border-radius:8px; border:1px solid #334155;
|
||||
background:var(--card); font-size:14px; }
|
||||
.nav a.on { background:var(--accent); color:#04263a; border-color:var(--accent); font-weight:600; }
|
||||
.nav a.saisie { margin-left:auto; }
|
||||
form.filters { display:flex; gap:12px; flex-wrap:wrap; margin-bottom:20px; }
|
||||
select, button, input { background:var(--card); color:var(--txt); border:1px solid #334155;
|
||||
border-radius:8px; padding:8px 12px; font-size:14px; }
|
||||
button { cursor:pointer; }
|
||||
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:16px; margin-bottom:8px; }
|
||||
.card { background:var(--card); border:1px solid #334155; border-radius:12px; padding:16px; }
|
||||
.card .label { color:var(--muted); font-size:13px; }
|
||||
.card .value { font-size:24px; font-weight:600; margin-top:6px; }
|
||||
h1 { margin:0 0 4px; font-size:22px; }
|
||||
h2 { font-size:15px; color:var(--muted); margin:22px 0 10px; font-weight:600; }
|
||||
.sub { color:var(--muted); margin-bottom:16px; font-size:14px; }
|
||||
.muted { color:var(--muted); }
|
||||
.app { display:flex; min-height:100vh; }
|
||||
.sidebar { width:var(--sbw); flex:0 0 var(--sbw); background:var(--panel);
|
||||
border-right:1px solid var(--line); position:fixed; inset:0 auto 0 0; height:100vh;
|
||||
display:flex; flex-direction:column; padding:18px 14px; z-index:60;
|
||||
transition:transform .22s ease; overflow-y:auto; }
|
||||
.brand { font-size:18px; font-weight:700; letter-spacing:.3px; padding:6px 10px 16px;
|
||||
display:flex; align-items:center; gap:10px; }
|
||||
.brand .dot { width:12px; height:12px; border-radius:4px;
|
||||
background:linear-gradient(135deg,var(--accent),var(--accent2)); }
|
||||
.sidebar nav { display:flex; flex-direction:column; gap:3px; }
|
||||
.sidebar nav a { display:flex; align-items:center; gap:11px; padding:10px 12px; border-radius:9px;
|
||||
color:var(--txt); font-size:14px; }
|
||||
.sidebar nav a:hover { background:#1e293b; }
|
||||
.sidebar nav a.on { background:linear-gradient(135deg,var(--accent),var(--accent2)); color:#04263a; font-weight:600; }
|
||||
.sidebar nav a svg { width:18px; height:18px; flex:0 0 18px; stroke:currentColor; fill:none; stroke-width:1.8; }
|
||||
.sidebar .sep { height:1px; background:var(--line); margin:12px 6px; }
|
||||
.sidebar .foot { margin-top:auto; padding-top:12px; }
|
||||
.sidebar .foot .who { font-size:12px; color:var(--muted); padding:0 12px 8px; }
|
||||
.sidebar .foot button { width:100%; background:var(--card); color:var(--txt); border:1px solid var(--line);
|
||||
border-radius:9px; padding:9px 12px; font-size:14px; cursor:pointer; }
|
||||
.sidebar .foot button:hover { border-color:var(--accent); }
|
||||
.main { flex:1 1 auto; margin-left:var(--sbw); padding:26px 30px; max-width:100%; min-width:0; }
|
||||
.topbar-mobile { display:none; }
|
||||
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:14px; margin-bottom:10px; }
|
||||
.card { background:var(--card); border:1px solid var(--line); border-radius:14px; padding:16px; }
|
||||
.card .label { color:var(--muted); font-size:12.5px; }
|
||||
.card .value { font-size:23px; font-weight:600; margin-top:5px; }
|
||||
.card .hint { color:var(--muted); font-size:12px; margin-top:4px; }
|
||||
.ok{color:var(--ok)} .warn{color:var(--warn)} .bad{color:var(--bad)}
|
||||
.bar { height:8px; background:#334155; border-radius:99px; overflow:hidden; margin-top:8px; }
|
||||
.pos{color:var(--ok)} .neg{color:var(--bad)}
|
||||
select, button, input, textarea { background:var(--card); color:var(--txt); border:1px solid var(--line);
|
||||
border-radius:9px; padding:9px 12px; font-size:14px; font-family:inherit; }
|
||||
button { cursor:pointer; }
|
||||
.btn-primary { background:linear-gradient(135deg,var(--accent),var(--accent2)); color:#04263a;
|
||||
border:none; font-weight:600; }
|
||||
.btn-danger { border-color:#7a2f2f; color:var(--bad); background:var(--card); }
|
||||
form.filters { display:flex; gap:12px; flex-wrap:wrap; margin-bottom:20px; align-items:end; }
|
||||
form.filters label { display:flex; flex-direction:column; gap:4px; font-size:12px; color:var(--muted); }
|
||||
.bar { height:8px; background:var(--line); border-radius:99px; overflow:hidden; margin-top:8px; }
|
||||
.bar > span { display:block; height:100%; background:var(--accent); }
|
||||
.row { display:grid; grid-template-columns:2fr 1fr; gap:16px; margin-top:8px; }
|
||||
@media(max-width:800px){ .row{grid-template-columns:1fr;} }
|
||||
@media(max-width:900px){ .row{grid-template-columns:1fr;} }
|
||||
canvas { max-width:100%; }
|
||||
.table-wrap { overflow-x:auto; border-radius:12px; }
|
||||
table { width:100%; border-collapse:collapse; font-size:14px; }
|
||||
td,th { padding:8px; border-bottom:1px solid #334155; text-align:right; }
|
||||
td,th { padding:9px 11px; border-bottom:1px solid var(--line); text-align:right; }
|
||||
th:first-child, td:first-child { text-align:left; }
|
||||
th { color:var(--muted); font-weight:600; font-size:12px; text-transform:uppercase; letter-spacing:.03em; }
|
||||
tr.total td { font-weight:600; border-top:2px solid #475569; }
|
||||
.exports a { display:inline-block; margin-right:12px; }
|
||||
.msg { padding:11px 15px; border-radius:10px; margin-bottom:12px; background:#14532d33; border:1px solid #2f6b52; }
|
||||
.msg.error, .msg.bad { background:#7f1d1d33; border-color:#7a2f2f; }
|
||||
@media(max-width:820px){
|
||||
.sidebar { transform:translateX(-100%); box-shadow:0 0 40px #000a; }
|
||||
body.sb-open .sidebar { transform:translateX(0); }
|
||||
.main { margin-left:0; padding:14px 16px 40px; }
|
||||
.topbar-mobile { display:flex; align-items:center; gap:12px; margin-bottom:14px;
|
||||
position:sticky; top:0; background:var(--bg); padding:8px 0; z-index:40; }
|
||||
.topbar-mobile .burger { background:var(--card); border:1px solid var(--line); border-radius:9px;
|
||||
padding:8px 12px; font-size:18px; line-height:1; cursor:pointer; color:var(--txt); }
|
||||
.topbar-mobile .t { font-weight:600; }
|
||||
.sb-backdrop { display:none; position:fixed; inset:0; background:#020617cc; z-index:55; }
|
||||
body.sb-open .sb-backdrop { display:block; }
|
||||
form.filters label { flex:1 1 44%; }
|
||||
}
|
||||
</style>
|
||||
{% block extrastyle %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% if not entreprise %}
|
||||
<h1>Aucune entreprise</h1>
|
||||
<p class="sub">Crée une entreprise dans <a href="/admin/">l'administration</a>,
|
||||
ou <a href="/saisie/">saisis une opération</a>.</p>
|
||||
{% else %}
|
||||
<div class="nav">
|
||||
<a class="{% if vue == 'global' %}on{% endif %}" href="/dashboard/?entreprise={{ entreprise.pk }}&annee={{ annee }}">Global</a>
|
||||
<a class="{% if vue == 'mensuel' %}on{% endif %}" href="/dashboard/mensuel/?entreprise={{ entreprise.pk }}&annee={{ annee }}">Mensuel</a>
|
||||
<a class="{% if vue == 'urssaf' %}on{% endif %}" href="/declaration/urssaf/?entreprise={{ entreprise.pk }}&annee={{ annee }}">Déclaration URSSAF</a>
|
||||
<a class="{% if vue == 'tva' %}on{% endif %}" href="/declaration/tva/?entreprise={{ entreprise.pk }}&annee={{ annee }}">Déclaration TVA</a>
|
||||
<a class="{% if vue == 'gestion' %}on{% endif %}" href="/gestion/?entreprise={{ entreprise.pk }}">Entrées / sorties</a>
|
||||
<a class="saisie" href="/saisie/">+ Saisir</a>
|
||||
<a href="/import/">Importer</a>
|
||||
{% if user.is_staff %}<a href="/admin/">Admin</a>{% endif %}
|
||||
<form method="post" action="/logout/" style="display:inline;margin:0;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" style="background:var(--card);color:var(--txt);border:1px solid #334155;border-radius:8px;padding:7px 13px;font-size:14px;cursor:pointer;">Déconnexion</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h1>{{ entreprise.nom }}</h1>
|
||||
<div class="sub">{{ entreprise.get_activite_display }} (principale) ·
|
||||
{% if entreprise.franchise_tva %}Franchise de TVA{% else %}Assujetti TVA{% endif %}
|
||||
{% if entreprise.versement_liberatoire %}· Versement libératoire{% endif %}
|
||||
· Exercice {{ annee }}</div>
|
||||
|
||||
<form class="filters" method="get">
|
||||
<select name="entreprise" onchange="this.form.submit()">
|
||||
{% for e in entreprises %}<option value="{{ e.pk }}" {% if e.pk == entreprise.pk %}selected{% endif %}>{{ e.nom }}</option>{% endfor %}
|
||||
</select>
|
||||
<select name="annee" onchange="this.form.submit()">
|
||||
{% for a in annees %}<option value="{{ a }}" {% if a == annee %}selected{% endif %}>{{ a }}</option>{% endfor %}
|
||||
</select>
|
||||
{% block extrafilters %}{% endblock %}
|
||||
</form>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
<div class="app">
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="brand"><span class="dot"></span> JB Compta</div>
|
||||
<nav>
|
||||
<a class="{% if vue == 'global' %}on{% endif %}" href="/dashboard/{% if entreprise %}?entreprise={{ entreprise.pk }}{% endif %}">
|
||||
<svg viewBox="0 0 24 24"><path d="M3 12h7V3H3zM14 21h7v-9h-7zM3 21h7v-6H3zM14 3v6h7V3z"/></svg> Tableau de bord</a>
|
||||
<a class="{% if vue == 'mensuel' %}on{% endif %}" href="/dashboard/mensuel/{% if entreprise %}?entreprise={{ entreprise.pk }}{% endif %}">
|
||||
<svg viewBox="0 0 24 24"><path d="M4 5h16v16H4zM4 9h16M8 3v4M16 3v4"/></svg> Mensuel</a>
|
||||
<a class="{% if vue == 'urssaf' %}on{% endif %}" href="/declaration/urssaf/{% if entreprise %}?entreprise={{ entreprise.pk }}{% endif %}">
|
||||
<svg viewBox="0 0 24 24"><path d="M6 3h9l5 5v13H6zM14 3v6h6M9 13h6M9 17h6"/></svg> Déclaration URSSAF</a>
|
||||
<a class="{% if vue == 'tva' %}on{% endif %}" href="/declaration/tva/{% if entreprise %}?entreprise={{ entreprise.pk }}{% endif %}">
|
||||
<svg viewBox="0 0 24 24"><path d="M6 3h9l5 5v13H6zM14 3v6h6M9 14l6 0M9 17l4 0"/></svg> Déclaration TVA</a>
|
||||
<a class="{% if vue == 'gestion' %}on{% endif %}" href="/gestion/{% if entreprise %}?entreprise={{ entreprise.pk }}{% endif %}">
|
||||
<svg viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18M7 4v3M17 14v3"/></svg> Entrées / sorties</a>
|
||||
<div class="sep"></div>
|
||||
<a class="{% if vue == 'saisie' %}on{% endif %}" href="/saisie/">
|
||||
<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg> Saisir</a>
|
||||
<a class="{% if vue == 'import' %}on{% endif %}" href="/import/">
|
||||
<svg viewBox="0 0 24 24"><path d="M12 3v12M8 11l4 4 4-4M5 21h14"/></svg> Importer</a>
|
||||
{% if user.is_staff %}<a href="/admin/">
|
||||
<svg viewBox="0 0 24 24"><path d="M12 3l7 4v5c0 5-3 7-7 9-4-2-7-4-7-9V7z"/></svg> Admin</a>{% endif %}
|
||||
</nav>
|
||||
<div class="foot">
|
||||
{% if user.is_authenticated %}<div class="who">Connecté : {{ user.username }}</div>{% endif %}
|
||||
<form method="post" action="/logout/">{% csrf_token %}<button type="submit">Déconnexion</button></form>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="sb-backdrop" onclick="document.body.classList.remove('sb-open')"></div>
|
||||
<main class="main">
|
||||
<div class="topbar-mobile">
|
||||
<button class="burger" onclick="document.body.classList.toggle('sb-open')" aria-label="Menu">☰</button>
|
||||
<span class="t">JB Compta</span>
|
||||
</div>
|
||||
{% block topbar %}
|
||||
{% if entreprise %}
|
||||
<h1>{{ entreprise.nom }}</h1>
|
||||
<div class="sub">{{ entreprise.get_activite_display }} (principale) ·
|
||||
{% if entreprise.franchise_tva %}Franchise de TVA{% else %}Assujetti TVA{% endif %}
|
||||
{% if entreprise.versement_liberatoire %}· Versement libératoire{% endif %}
|
||||
· Exercice {{ annee }}</div>
|
||||
<form class="filters" method="get">
|
||||
<label>Entreprise
|
||||
<select name="entreprise" onchange="this.form.submit()">
|
||||
{% for e in entreprises %}<option value="{{ e.pk }}" {% if e.pk == entreprise.pk %}selected{% endif %}>{{ e.nom }}</option>{% endfor %}
|
||||
</select></label>
|
||||
<label>Année
|
||||
<select name="annee" onchange="this.form.submit()">
|
||||
{% for a in annees %}<option value="{{ a }}" {% if a == annee %}selected{% endif %}>{{ a }}</option>{% endfor %}
|
||||
</select></label>
|
||||
{% block extrafilters %}{% endblock %}
|
||||
</form>
|
||||
{% else %}
|
||||
<h1>Aucune entreprise</h1>
|
||||
<p class="sub">Crée une entreprise dans <a href="/admin/">l'administration</a>,
|
||||
ou <a href="/saisie/">saisis une opération</a>.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,84 +1,47 @@
|
||||
{% load l10n %}<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>JB Compta — Entrées / sorties</title>
|
||||
{% extends "compta/base.html" %}
|
||||
{% load l10n %}
|
||||
{% block titre %}Entrées / sorties{% endblock %}
|
||||
|
||||
{% block extrastyle %}
|
||||
<style>
|
||||
:root { --bg:#0f172a; --card:#1e293b; --txt:#e2e8f0; --muted:#94a3b8;
|
||||
--accent:#38bdf8; --ok:#34d399; --warn:#fbbf24; --bad:#f87171; --line:#334155; }
|
||||
* { box-sizing:border-box; }
|
||||
body { margin:0; font-family:system-ui,Segoe UI,Roboto,sans-serif;
|
||||
background:var(--bg); color:var(--txt); padding:24px; }
|
||||
h1 { margin:0 0 4px; font-size:22px; }
|
||||
a { color:var(--accent); text-decoration:none; }
|
||||
.sub { color:var(--muted); margin-bottom:16px; }
|
||||
.nav { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px; }
|
||||
.nav a, .nav button { padding:7px 13px; border-radius:8px; border:1px solid var(--line);
|
||||
background:var(--card); font-size:14px; color:var(--txt); cursor:pointer; }
|
||||
.nav a.on { background:var(--accent); color:#04263a; border-color:var(--accent); font-weight:600; }
|
||||
select, button, input { background:var(--card); color:var(--txt); border:1px solid var(--line);
|
||||
border-radius:8px; padding:8px 10px; font-size:14px; }
|
||||
button { cursor:pointer; }
|
||||
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:12px; margin:8px 0 18px; }
|
||||
.card { background:var(--card); border:1px solid var(--line); border-radius:12px; padding:14px; }
|
||||
.card .label { color:var(--muted); font-size:12px; }
|
||||
.card .value { font-size:20px; font-weight:600; margin-top:4px; }
|
||||
.ok{color:var(--ok)} .warn{color:var(--warn)} .bad{color:var(--bad)}
|
||||
form.filters { display:flex; gap:10px; flex-wrap:wrap; margin-bottom:16px; align-items:end; }
|
||||
form.filters label { display:flex; flex-direction:column; gap:3px; font-size:12px; color:var(--muted); }
|
||||
table { width:100%; border-collapse:collapse; font-size:14px; }
|
||||
td,th { padding:8px 10px; border-bottom:1px solid var(--line); text-align:left; }
|
||||
th { color:var(--muted); font-weight:600; font-size:12px; text-transform:uppercase; letter-spacing:.03em; }
|
||||
td.num, th.num { text-align:right; font-variant-numeric:tabular-nums; }
|
||||
tr:hover td { background:#243247; }
|
||||
.tag { font-size:11px; padding:2px 7px; border-radius:99px; border:1px solid var(--line); }
|
||||
.tag { font-size:11px; padding:2px 8px; border-radius:99px; border:1px solid var(--line); white-space:nowrap; }
|
||||
.tag.recette { color:var(--ok); border-color:#2f6b52; }
|
||||
.tag.depense { color:var(--warn); border-color:#7a5f1f; }
|
||||
.tag.operation { color:var(--accent); border-color:#25566e; }
|
||||
.neutre-badge { font-size:10px; color:var(--muted); border:1px dashed var(--line); border-radius:6px; padding:1px 5px; margin-left:6px; }
|
||||
.btn-mini { padding:4px 9px; font-size:12px; }
|
||||
.pos{color:var(--ok)} .neg{color:var(--bad)}
|
||||
.msg { padding:10px 14px; border-radius:8px; margin-bottom:12px; background:#14532d33; border:1px solid #2f6b52; }
|
||||
.msg.error { background:#7f1d1d33; border-color:#7a2f2f; }
|
||||
.btn-mini { padding:5px 11px; font-size:12.5px; border-radius:8px; }
|
||||
#gtable td, #gtable th { border-bottom:1px solid var(--line); }
|
||||
#gtable tbody tr:hover td { background:#243247; }
|
||||
.chkcol { width:34px; text-align:center !important; }
|
||||
.chkcol input { width:16px; height:16px; accent-color:var(--accent); }
|
||||
.empty { color:var(--muted); padding:28px; text-align:center; }
|
||||
|
||||
.bulkbar { position:sticky; bottom:0; margin-top:14px; display:none; gap:12px; align-items:center;
|
||||
background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:12px 16px; }
|
||||
.bulkbar.on { display:flex; }
|
||||
.bulkbar .count { font-weight:600; }
|
||||
|
||||
/* modal */
|
||||
.overlay { position:fixed; inset:0; background:#020617cc; display:none; align-items:center;
|
||||
justify-content:center; padding:16px; z-index:50; }
|
||||
.overlay { position:fixed; inset:0; background:#020617d0; display:none; align-items:center;
|
||||
justify-content:center; padding:16px; z-index:100; }
|
||||
.overlay.on { display:flex; }
|
||||
.modal { background:var(--card); border:1px solid var(--line); border-radius:14px;
|
||||
width:min(560px,100%); max-height:90vh; overflow:auto; padding:22px; }
|
||||
.modal h3 { margin:0 0 14px; }
|
||||
.modal .field { display:flex; flex-direction:column; gap:4px; margin-bottom:12px; }
|
||||
.modal { background:var(--card); border:1px solid var(--line); border-radius:16px;
|
||||
width:min(560px,100%); max-height:92vh; overflow:auto; padding:22px; }
|
||||
.modal h3 { margin:0 0 16px; font-size:18px; }
|
||||
.modal .field { display:flex; flex-direction:column; gap:5px; margin-bottom:13px; }
|
||||
.modal .field label { font-size:13px; color:var(--muted); }
|
||||
.modal .field input, .modal .field select { width:100%; }
|
||||
.modal .actions { display:flex; gap:10px; justify-content:space-between; margin-top:18px; }
|
||||
.modal .actions { display:flex; gap:10px; justify-content:space-between; margin-top:20px; flex-wrap:wrap; }
|
||||
.explic { background:#0b1220; border:1px solid var(--line); border-radius:10px;
|
||||
padding:11px 13px; font-size:13px; color:var(--muted); margin-top:2px; }
|
||||
padding:11px 13px; font-size:13px; color:var(--muted); }
|
||||
.explic b { color:var(--txt); }
|
||||
.hidegroup { display:none; }
|
||||
.empty { color:var(--muted); padding:24px; text-align:center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% if not entreprise %}
|
||||
<h1>Aucune entreprise</h1>
|
||||
<p class="sub">Crée une entreprise dans <a href="/admin/">l'administration</a>.</p>
|
||||
{% else %}
|
||||
<div class="nav">
|
||||
<a href="/dashboard/?entreprise={{ entreprise.pk }}">Global</a>
|
||||
<a href="/dashboard/mensuel/?entreprise={{ entreprise.pk }}">Mensuel</a>
|
||||
<a href="/declaration/urssaf/?entreprise={{ entreprise.pk }}">Déclaration URSSAF</a>
|
||||
<a href="/declaration/tva/?entreprise={{ entreprise.pk }}">Déclaration TVA</a>
|
||||
<a class="on" href="/gestion/?entreprise={{ entreprise.pk }}">Entrées / sorties</a>
|
||||
<a href="/saisie/">+ Saisir</a>
|
||||
<a href="/import/">Importer</a>
|
||||
{% if user.is_staff %}<a href="/admin/">Admin</a>{% endif %}
|
||||
<form method="post" action="/logout/" style="margin:0 0 0 auto;">{% csrf_token %}<button type="submit">Déconnexion</button></form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block topbar %}
|
||||
<h1>Entrées / sorties</h1>
|
||||
<div class="sub">{{ entreprise.nom }} — vue unifiée de toutes les opérations importées</div>
|
||||
<div class="sub">{{ entreprise.nom }} — vue unifiée de toutes les opérations (recettes, dépenses, mouvements)</div>
|
||||
|
||||
{% for m in messages %}<div class="msg {{ m.tags }}">{{ m }}</div>{% endfor %}
|
||||
|
||||
@@ -90,7 +53,10 @@
|
||||
</div>
|
||||
|
||||
<form class="filters" method="get">
|
||||
<input type="hidden" name="entreprise" value="{{ entreprise.pk }}">
|
||||
<label>Entreprise
|
||||
<select name="entreprise" onchange="this.form.submit()">
|
||||
{% for e in entreprises %}<option value="{{ e.pk }}" {% if e.pk == entreprise.pk %}selected{% endif %}>{{ e.nom }}</option>{% endfor %}
|
||||
</select></label>
|
||||
<label>Recherche<input type="text" name="q" value="{{ filtres.q }}" placeholder="libellé, catégorie…"></label>
|
||||
<label>Type<select name="type">
|
||||
<option value="">Tous</option>
|
||||
@@ -117,26 +83,32 @@
|
||||
<option value="desc" {% if filtres.sens == 'desc' %}selected{% endif %}>↓ décroissant</option>
|
||||
<option value="asc" {% if filtres.sens == 'asc' %}selected{% endif %}>↑ croissant</option>
|
||||
</select></label>
|
||||
<button type="submit">Filtrer</button>
|
||||
<a class="btn-mini" href="/gestion/?entreprise={{ entreprise.pk }}" style="padding:8px 12px;border:1px solid var(--line);border-radius:8px;">Réinitialiser</a>
|
||||
<button type="submit" class="btn-primary">Filtrer</button>
|
||||
<a class="btn-mini" href="/gestion/?entreprise={{ entreprise.pk }}" style="padding:9px 12px;border:1px solid var(--line);border-radius:9px;">Réinitialiser</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if entreprise %}
|
||||
{% if rows %}
|
||||
<table>
|
||||
<div class="table-wrap">
|
||||
<table id="gtable">
|
||||
<thead><tr>
|
||||
<th class="chkcol"><input type="checkbox" id="chkall" title="Tout sélectionner"></th>
|
||||
<th>Date</th><th>Type</th><th>Libellé</th><th>Catégorie</th>
|
||||
<th class="num">Montant</th><th class="num">TVA</th><th></th>
|
||||
<th>Montant</th><th>TVA</th><th></th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td class="chkcol"><input type="checkbox" class="rowchk" name="sel" value="{{ r.type }}:{{ r.id }}" form="bulkform"></td>
|
||||
<td>{{ r.date|date:"d/m/Y" }}</td>
|
||||
<td><span class="tag {{ r.type }}">{{ r.type_label }}</span></td>
|
||||
<td>{{ r.libelle }}</td>
|
||||
<td>{{ r.categorie }}{% if r.neutre %}<span class="neutre-badge">neutre</span>{% endif %}</td>
|
||||
<td class="num {% if r.montant >= 0 %}pos{% else %}neg{% endif %}">{{ r.montant|floatformat:2|unlocalize }} €</td>
|
||||
<td class="num">{% if r.tva %}{{ r.tva|floatformat:2|unlocalize }}{% else %}—{% endif %}</td>
|
||||
<td class="num">
|
||||
<td class="{% if r.montant >= 0 %}pos{% else %}neg{% endif %}">{{ r.montant|floatformat:2|unlocalize }} €</td>
|
||||
<td>{% if r.tva %}{{ r.tva|floatformat:2|unlocalize }}{% else %}—{% endif %}</td>
|
||||
<td>
|
||||
<button type="button" class="btn-mini"
|
||||
data-edit
|
||||
data-type="{{ r.type }}" data-id="{{ r.id }}"
|
||||
@@ -158,6 +130,18 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<form class="bulkbar" id="bulkform" method="post" action="/gestion/"
|
||||
onsubmit="return confirm('Supprimer définitivement les opérations sélectionnées ?');">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="delete_bulk">
|
||||
<input type="hidden" name="entreprise" value="{{ entreprise.pk }}">
|
||||
<input type="hidden" name="retour_qs" id="bulk_retour">
|
||||
<span class="count"><span id="bulkcount">0</span> sélectionnée(s)</span>
|
||||
<button type="submit" class="btn-danger">Supprimer la sélection</button>
|
||||
<button type="button" onclick="clearSel()" style="background:var(--card);border:1px solid var(--line);">Annuler</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="empty">Aucune opération ne correspond à ces filtres.</div>
|
||||
{% endif %}
|
||||
@@ -234,10 +218,10 @@
|
||||
<div class="explic hidegroup" id="note_montant">Le montant est géré par les lignes de facture — modifiez-le dans l'admin.</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" onclick="supprimer()" style="border-color:#7a2f2f;color:var(--bad);">Supprimer</button>
|
||||
<button type="button" class="btn-danger" onclick="supprimer()">Supprimer</button>
|
||||
<div>
|
||||
<button type="button" onclick="fermer()" style="margin-right:6px;">Annuler</button>
|
||||
<button type="submit" style="background:var(--accent);color:#04263a;border-color:var(--accent);font-weight:600;">Enregistrer</button>
|
||||
<button type="submit" class="btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -251,9 +235,12 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ cats_json|json_script:"cats-data" }}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const CATS = {{ cats_json|safe }};
|
||||
const CATS = JSON.parse(document.getElementById('cats-data').textContent);
|
||||
const $ = id => document.getElementById(id);
|
||||
|
||||
function retourQs(){
|
||||
@@ -264,34 +251,31 @@ function retourQs(){
|
||||
function show(id, on){ $(id).classList.toggle('hidegroup', !on); }
|
||||
|
||||
function majExplic(){
|
||||
const id = $('f_cat').value;
|
||||
const c = CATS[id];
|
||||
const c = CATS[$('f_cat').value];
|
||||
const box = $('f_explic');
|
||||
if(!c){ box.innerHTML = 'Non catégorisé : le calcul fiscal peut être incomplet.'; return; }
|
||||
const badge = c.neutre ? ' <b>(neutre)</b>' : '';
|
||||
const compte = c.compte ? ` · compte ${c.compte}` : '';
|
||||
box.innerHTML = '<b>'+c.nom+'</b>'+badge+compte+'<br>'+c.explication;
|
||||
const compte = c.compte ? ' · compte ' + c.compte : '';
|
||||
box.innerHTML = '<b>' + c.nom + '</b>' + badge + compte + '<br>' + c.explication;
|
||||
}
|
||||
|
||||
function ouvrir(btn){
|
||||
const d = btn.dataset;
|
||||
const t = d.type;
|
||||
const d = btn.dataset, t = d.type;
|
||||
$('f_type').value = t; $('f_id').value = d.id;
|
||||
$('d_type').value = t; $('d_id').value = d.id;
|
||||
const qs = retourQs();
|
||||
$('f_retour').value = qs; $('d_retour').value = qs;
|
||||
$('f_date').value = d.date || '';
|
||||
$('f_libelle').value = d.libelle || '';
|
||||
$('f_montant').value = Math.abs(parseFloat(d.montant||'0')).toFixed(2);
|
||||
$('f_montant').value = Math.abs(parseFloat(d.montant || '0')).toFixed(2);
|
||||
$('f_cat').value = d.cat || '';
|
||||
$('f_activite').value = d.activite || '';
|
||||
$('f_regime').value = d.regime || 'nationale';
|
||||
$('f_taux').value = (parseFloat(d.taux||'0')||0).toString();
|
||||
$('f_taux').value = (parseFloat(d.taux || '0') || 0).toString();
|
||||
$('f_sens').value = d.sens || 'debit';
|
||||
$('f_statut').value = d.statut || '';
|
||||
$('f_encaissement').value = d.encaissement || '';
|
||||
|
||||
// Champs par type
|
||||
const dep = t === 'depense', ope = t === 'operation', rec = t === 'recette';
|
||||
show('g_cat', dep || ope);
|
||||
show('g_activite', dep);
|
||||
@@ -304,7 +288,7 @@ function ouvrir(btn){
|
||||
$('f_montant').readOnly = !editMontant;
|
||||
show('note_montant', !editMontant);
|
||||
$('lbl_montant').textContent = dep ? 'Montant payé — TTC (€)' : 'Montant (€)';
|
||||
$('mtitre').textContent = 'Modifier — ' + (rec?'recette':dep?'dépense':'opération bancaire');
|
||||
$('mtitre').textContent = 'Modifier — ' + (rec ? 'recette' : dep ? 'dépense' : 'opération bancaire');
|
||||
majExplic();
|
||||
$('ov').classList.add('on');
|
||||
}
|
||||
@@ -312,12 +296,37 @@ function fermer(){ $('ov').classList.remove('on'); }
|
||||
function supprimer(){
|
||||
if(confirm('Supprimer définitivement cette opération ?')) $('delform').submit();
|
||||
}
|
||||
|
||||
/* Sélection multiple */
|
||||
function majBulk(){
|
||||
const checked = document.querySelectorAll('.rowchk:checked').length;
|
||||
$('bulkcount').textContent = checked;
|
||||
document.getElementById('bulkform').classList.toggle('on', checked > 0);
|
||||
const all = $('chkall'); const total = document.querySelectorAll('.rowchk').length;
|
||||
if(all){ all.checked = checked === total && total > 0; all.indeterminate = checked > 0 && checked < total; }
|
||||
}
|
||||
function clearSel(){
|
||||
document.querySelectorAll('.rowchk').forEach(c => c.checked = false);
|
||||
if($('chkall')) $('chkall').checked = false;
|
||||
majBulk();
|
||||
}
|
||||
|
||||
document.addEventListener('click', e => {
|
||||
const b = e.target.closest('[data-edit]');
|
||||
if(b) ouvrir(b);
|
||||
});
|
||||
$('f_cat').addEventListener('change', majExplic);
|
||||
$('ov').addEventListener('click', e => { if(e.target === $('ov')) fermer(); });
|
||||
document.addEventListener('keydown', e => { if(e.key === 'Escape') fermer(); });
|
||||
|
||||
const chkall = $('chkall');
|
||||
if(chkall){
|
||||
chkall.addEventListener('change', () => {
|
||||
document.querySelectorAll('.rowchk').forEach(c => c.checked = chkall.checked);
|
||||
majBulk();
|
||||
});
|
||||
document.querySelectorAll('.rowchk').forEach(c => c.addEventListener('change', majBulk));
|
||||
const br = $('bulk_retour'); if(br) br.value = retourQs();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,49 +1,38 @@
|
||||
{% load l10n %}<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>JB Compta — Import</title>
|
||||
{% extends "compta/base.html" %}
|
||||
{% load l10n %}
|
||||
{% block titre %}Importer{% endblock %}
|
||||
|
||||
{% block extrastyle %}
|
||||
<style>
|
||||
:root { --bg:#0f172a; --card:#1e293b; --txt:#e2e8f0; --muted:#94a3b8;
|
||||
--accent:#38bdf8; --ok:#34d399; --bad:#f87171; }
|
||||
* { box-sizing:border-box; }
|
||||
body { margin:0; font-family:system-ui,Segoe UI,Roboto,sans-serif;
|
||||
background:var(--bg); color:var(--txt); padding:24px; }
|
||||
h1 { margin:0 0 4px; font-size:22px; } h2{font-size:15px;color:var(--muted);margin:0 0 8px;}
|
||||
a { color:var(--accent); }
|
||||
.sub { color:var(--muted); margin-bottom:14px; }
|
||||
.card { background:var(--card); border:1px solid #334155; border-radius:12px; padding:16px; margin-bottom:16px; }
|
||||
.card.indy { border-color:#0e7490; }
|
||||
label { display:block; font-size:12px; color:var(--muted); margin:8px 0 4px; }
|
||||
input, select, textarea { background:#0f172a; color:var(--txt); border:1px solid #334155;
|
||||
border-radius:8px; padding:8px 10px; font-size:14px; width:100%; }
|
||||
textarea { min-height:90px; font-family:ui-monospace,monospace; }
|
||||
.ic label { display:block; font-size:12px; color:var(--muted); margin:10px 0 4px; }
|
||||
.ic input, .ic select, .ic textarea { width:100%; background:var(--panel); }
|
||||
.ic textarea { min-height:90px; font-family:ui-monospace,monospace; }
|
||||
.ic .card { margin-bottom:16px; }
|
||||
.ic .card.indy { border-color:#0e7490; }
|
||||
.grid3 { display:grid; grid-template-columns:2fr 1fr 1fr; gap:12px; }
|
||||
.grid2 { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
||||
@media(max-width:700px){ .grid3,.grid2{grid-template-columns:1fr;} }
|
||||
button { cursor:pointer; border-radius:8px; border:1px solid #334155; background:var(--card);
|
||||
color:var(--txt); padding:9px 16px; font-size:14px; }
|
||||
button.primary { background:var(--accent); color:#04263a; border:none; font-weight:600; }
|
||||
.actions { display:flex; gap:12px; margin-top:12px; }
|
||||
table { width:100%; border-collapse:collapse; font-size:13px; }
|
||||
th,td { padding:4px 6px; border-bottom:1px solid #334155; text-align:left; }
|
||||
td input, td select { padding:5px 7px; }
|
||||
.num { text-align:right; }
|
||||
code { background:#0f172a; padding:1px 6px; border-radius:5px; }
|
||||
.msg { padding:10px 14px; border-radius:8px; margin-bottom:8px; }
|
||||
.msg.bad { background:#7f1d1d; } .msg.ok { background:#064e3b; }
|
||||
.ic .actions { display:flex; gap:12px; margin-top:14px; flex-wrap:wrap; }
|
||||
.ic table { font-size:13px; }
|
||||
.ic th, .ic td { padding:5px 7px; text-align:left; }
|
||||
.ic td input, .ic td select { padding:6px 8px; }
|
||||
.ic .num { text-align:right; }
|
||||
code { background:var(--panel); padding:1px 6px; border-radius:5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Importer un relevé</h1>
|
||||
<div class="sub"><a href="/dashboard/">← Tableau de bord</a> · <a href="/saisie/">Saisie manuelle</a></div>
|
||||
{% endblock %}
|
||||
|
||||
{% block topbar %}
|
||||
<h1>Importer un relevé</h1>
|
||||
<div class="sub">Synchronisation Indy, CSV, ou capture d'écran (OCR) — avec aperçu éditable et anti-doublon.</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="ic">
|
||||
{% if erreurs %}{% for e in erreurs %}<div class="msg bad">{{ e }}</div>{% endfor %}{% endif %}
|
||||
{% if resultat %}
|
||||
<div class="msg ok">Import terminé :
|
||||
<div class="msg">Import terminé :
|
||||
{% for k, v in resultat.items %}{{ v }} {{ k }}{% if not forloop.last %} · {% endif %}{% endfor %}.
|
||||
Voir <a href="/admin/compta/depense/">dépenses</a> · <a href="/admin/compta/facture/">factures</a>.</div>
|
||||
Voir <a href="/gestion/">Entrées / sorties</a>.</div>
|
||||
{% endif %}
|
||||
|
||||
{% if ocr_debug %}
|
||||
@@ -60,7 +49,7 @@
|
||||
{% csrf_token %}
|
||||
<h2>① Synchroniser depuis Indy (recommandé)</h2>
|
||||
<div class="sub">Récupère tes transactions directement depuis l'API Indy. Anti-doublon automatique.
|
||||
· <a href="/import/bookmarklet/">📌 Installer le favori « Indy → CSV »</a> (le plus simple, depuis ton navigateur)</div>
|
||||
· <a href="/import/bookmarklet/">Installer le favori « Indy → CSV »</a> (le plus simple, depuis ton navigateur)</div>
|
||||
<div class="grid3">
|
||||
<div>
|
||||
<label>Entreprise</label>
|
||||
@@ -74,7 +63,7 @@
|
||||
<label>Token Indy (Bearer, sans « Bearer ») — mémorisé sur l'entreprise, expire ~1 h</label>
|
||||
<textarea name="indy_token" placeholder="eyJ..."></textarea>
|
||||
<div class="actions">
|
||||
<button type="submit" name="action" value="indy" class="primary">Récupérer depuis Indy</button>
|
||||
<button type="submit" name="action" value="indy" class="btn-primary">Récupérer depuis Indy</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -98,10 +87,10 @@
|
||||
</div>
|
||||
<label>…ou fichier CSV</label>
|
||||
<input type="file" name="fichier" accept=".csv,.txt">
|
||||
<label>…ou colle des lignes CSV <span class="sub">(date ; libelle ; categorie ; activite ; taux_tva ; regime ; montant)</span></label>
|
||||
<label>…ou colle des lignes CSV <span class="muted">(date ; libelle ; categorie ; activite ; taux_tva ; regime ; montant)</span></label>
|
||||
<textarea name="texte" placeholder="2026-06-25;ALTISSIMO;Vente de produits finis;vente;20;;168.40">{{ texte }}</textarea>
|
||||
<div class="actions">
|
||||
<button type="submit" name="action" value="preview" class="primary">Analyser / Aperçu</button>
|
||||
<button type="submit" name="action" value="preview" class="btn-primary">Analyser / Aperçu</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
@@ -111,6 +100,7 @@
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="entreprise" value="{{ entreprise_id }}">
|
||||
<h2>Aperçu éditable — {{ lignes|length }} ligne(s){% if source == 'indy' %} (Indy){% elif source == 'ocr' %} (OCR){% endif %}</h2>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>Date</th><th>Libellé</th><th>Catégorie</th><th>Régime TVA</th><th>Taux %</th><th>Montant €</th><th>Type</th></tr></thead>
|
||||
<tbody>
|
||||
@@ -138,27 +128,30 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="submit" name="action" value="save" class="primary">Enregistrer ces opérations</button>
|
||||
<button type="submit" name="action" value="save" class="btn-primary">Enregistrer ces opérations</button>
|
||||
<a href="/import/"><button type="button">Recommencer</button></a>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
<script>
|
||||
window.addEventListener('paste', function(e){
|
||||
var input = document.getElementById('imgInput');
|
||||
if(!input || !e.clipboardData) return;
|
||||
for(var i=0; i<e.clipboardData.items.length; i++){
|
||||
var it = e.clipboardData.items[i];
|
||||
if(it.type && it.type.indexOf('image') === 0){
|
||||
var dt = new DataTransfer(); dt.items.add(it.getAsFile()); input.files = dt.files;
|
||||
var info = document.getElementById('pasteInfo');
|
||||
if(info){ info.textContent = 'Image collée — clique « Analyser ».'; info.style.color = '#34d399'; }
|
||||
e.preventDefault(); break;
|
||||
}
|
||||
{% block scripts %}
|
||||
<script>
|
||||
window.addEventListener('paste', function(e){
|
||||
var input = document.getElementById('imgInput');
|
||||
if(!input || !e.clipboardData) return;
|
||||
for(var i=0; i<e.clipboardData.items.length; i++){
|
||||
var it = e.clipboardData.items[i];
|
||||
if(it.type && it.type.indexOf('image') === 0){
|
||||
var dt = new DataTransfer(); dt.items.add(it.getAsFile()); input.files = dt.files;
|
||||
var info = document.getElementById('pasteInfo');
|
||||
if(info){ info.textContent = 'Image collée — clique « Analyser ».'; info.style.color = '#34d399'; }
|
||||
e.preventDefault(); break;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,56 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>JB Compta — Saisie</title>
|
||||
{% extends "compta/base.html" %}
|
||||
{% block titre %}Saisir{% endblock %}
|
||||
|
||||
{% block extrastyle %}
|
||||
<style>
|
||||
:root { --bg:#0f172a; --card:#1e293b; --txt:#e2e8f0; --muted:#94a3b8;
|
||||
--accent:#38bdf8; --ok:#34d399; --warn:#fbbf24; --bad:#f87171; }
|
||||
* { box-sizing:border-box; }
|
||||
body { margin:0; font-family:system-ui,Segoe UI,Roboto,sans-serif;
|
||||
background:var(--bg); color:var(--txt); padding:24px; }
|
||||
h1 { margin:0 0 4px; font-size:22px; }
|
||||
.sub { color:var(--muted); margin-bottom:20px; }
|
||||
a { color:var(--accent); }
|
||||
.card { background:var(--card); border:1px solid #334155; border-radius:12px;
|
||||
padding:16px; margin-bottom:16px; }
|
||||
label { display:block; font-size:12px; color:var(--muted); margin-bottom:4px; }
|
||||
input, select { background:#0f172a; color:var(--txt); border:1px solid #334155;
|
||||
border-radius:8px; padding:8px 10px; font-size:14px; width:100%; }
|
||||
.head { display:grid; grid-template-columns:1fr 1fr 2fr 1fr; gap:12px; }
|
||||
.sp label { display:block; font-size:12px; color:var(--muted); margin-bottom:4px; }
|
||||
.sp input, .sp select { width:100%; background:var(--panel); }
|
||||
.sp .card { margin-bottom:16px; }
|
||||
.sp .head { display:grid; grid-template-columns:1fr 1fr 2fr 1fr; gap:12px; }
|
||||
.grid2 { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
||||
@media(max-width:700px){ .head,.grid2{grid-template-columns:1fr;} }
|
||||
table { width:100%; border-collapse:collapse; font-size:13px; margin-top:8px; }
|
||||
th,td { padding:6px; border-bottom:1px solid #334155; text-align:left; vertical-align:top; }
|
||||
th { color:var(--muted); font-weight:600; }
|
||||
td input, td select { padding:6px 8px; }
|
||||
.num { text-align:right; }
|
||||
button { cursor:pointer; border-radius:8px; border:1px solid #334155;
|
||||
background:var(--card); color:var(--txt); padding:8px 14px; font-size:14px; }
|
||||
button.primary { background:var(--accent); color:#04263a; border:none; font-weight:600; }
|
||||
button.mini { padding:4px 8px; }
|
||||
@media(max-width:700px){ .sp .head,.grid2{grid-template-columns:1fr;} }
|
||||
.sp table { font-size:13px; margin-top:8px; }
|
||||
.sp th, .sp td { padding:6px; text-align:left; vertical-align:top; }
|
||||
.sp td input, .sp td select { padding:6px 8px; }
|
||||
.sp .num { text-align:right; }
|
||||
button.mini { padding:4px 10px; }
|
||||
.totaux { display:flex; gap:24px; align-items:center; margin-top:12px; flex-wrap:wrap; }
|
||||
.totaux b { font-size:18px; }
|
||||
.ok{color:var(--ok)} .bad{color:var(--bad)}
|
||||
.msg { padding:10px 14px; border-radius:8px; margin-bottom:10px; }
|
||||
.msg.error { background:#7f1d1d; }
|
||||
.msg.success { background:#064e3b; }
|
||||
.actions { display:flex; gap:12px; margin-top:16px; }
|
||||
.sp .actions { display:flex; gap:12px; margin-top:16px; }
|
||||
.badge { display:inline-block; padding:2px 10px; border-radius:99px; font-size:12px; }
|
||||
.badge.dep { background:#7f1d1d; }
|
||||
.badge.ven { background:#064e3b; }
|
||||
.badge.dep { background:#7f1d1d; } .badge.ven { background:#064e3b; }
|
||||
.hidden { display:none; }
|
||||
.hint { color:var(--muted); font-size:12px; margin-top:6px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% endblock %}
|
||||
|
||||
{% block topbar %}
|
||||
<h1>Saisie d'une opération</h1>
|
||||
<div class="sub">Montant <b>négatif</b> = dépense · <b>positif</b> = vente/encaissement.
|
||||
· <a href="/dashboard/">← Tableau de bord</a> · <a href="/import/">Importer un relevé</a></div>
|
||||
|
||||
<div class="sub">Montant <b>négatif</b> = dépense · <b>positif</b> = vente / encaissement.</div>
|
||||
{% if messages %}{% for m in messages %}<div class="msg {{ m.tags }}">{{ m }}</div>{% endfor %}{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="sp">
|
||||
<form method="post" id="frm">
|
||||
{% csrf_token %}
|
||||
<div class="card">
|
||||
@@ -77,9 +58,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== PANEL DÉPENSE (montant négatif) ===== -->
|
||||
<!-- PANEL DÉPENSE (montant négatif) -->
|
||||
<div class="card hidden" id="panel-depense">
|
||||
<div class="sub" style="margin:0 0 8px">Dépense — éclate en une ou plusieurs lignes.</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -94,6 +76,7 @@
|
||||
</thead>
|
||||
<tbody id="lignes"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="actions"><button type="button" onclick="addRow()">+ Ajouter une ligne</button></div>
|
||||
<div class="totaux">
|
||||
<span>Réparti : <b id="repartis">0.00</b> €</span>
|
||||
@@ -101,7 +84,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== PANEL VENTE (montant positif) ===== -->
|
||||
<!-- PANEL VENTE (montant positif) -->
|
||||
<div class="card hidden" id="panel-vente">
|
||||
<div class="sub" style="margin:0 0 8px">Vente — crée une facture encaissée (ou un mouvement neutre).</div>
|
||||
<div class="grid2">
|
||||
@@ -146,7 +129,7 @@
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="submit" class="primary">Enregistrer</button>
|
||||
<button type="submit" class="btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -180,35 +163,37 @@
|
||||
<td><button type="button" class="mini" onclick="this.closest('tr').remove();recalc()">✕</button></td>
|
||||
</tr>
|
||||
</template>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
<script>
|
||||
function addRow(){
|
||||
document.getElementById('lignes').appendChild(
|
||||
document.getElementById('rowtpl').content.cloneNode(true));
|
||||
}
|
||||
function recalc(){
|
||||
let s = 0;
|
||||
document.querySelectorAll('.montant').forEach(i => s += parseFloat(i.value||0));
|
||||
const total = Math.abs(parseFloat(document.getElementById('total').value||0));
|
||||
document.getElementById('repartis').textContent = s.toFixed(2);
|
||||
const reste = (total - s);
|
||||
const el = document.getElementById('reste');
|
||||
el.textContent = reste.toFixed(2);
|
||||
el.className = Math.abs(reste) < 0.01 ? 'ok' : 'bad';
|
||||
}
|
||||
function toggle(){
|
||||
const v = parseFloat(document.getElementById('total').value||0);
|
||||
const dep = document.getElementById('panel-depense');
|
||||
const ven = document.getElementById('panel-vente');
|
||||
const badge = document.getElementById('badge');
|
||||
if (v < 0){ dep.classList.remove('hidden'); ven.classList.add('hidden');
|
||||
badge.className='badge dep'; badge.textContent='Dépense';
|
||||
if(!document.querySelector('#lignes tr')){addRow();addRow();}
|
||||
recalc(); }
|
||||
else if (v > 0){ ven.classList.remove('hidden'); dep.classList.add('hidden');
|
||||
badge.className='badge ven'; badge.textContent='Vente'; }
|
||||
else { dep.classList.add('hidden'); ven.classList.add('hidden'); badge.textContent=''; }
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function addRow(){
|
||||
document.getElementById('lignes').appendChild(
|
||||
document.getElementById('rowtpl').content.cloneNode(true));
|
||||
}
|
||||
function recalc(){
|
||||
let s = 0;
|
||||
document.querySelectorAll('.montant').forEach(i => s += parseFloat(i.value||0));
|
||||
const total = Math.abs(parseFloat(document.getElementById('total').value||0));
|
||||
document.getElementById('repartis').textContent = s.toFixed(2);
|
||||
const reste = (total - s);
|
||||
const el = document.getElementById('reste');
|
||||
el.textContent = reste.toFixed(2);
|
||||
el.className = Math.abs(reste) < 0.01 ? 'ok' : 'bad';
|
||||
}
|
||||
function toggle(){
|
||||
const v = parseFloat(document.getElementById('total').value||0);
|
||||
const dep = document.getElementById('panel-depense');
|
||||
const ven = document.getElementById('panel-vente');
|
||||
const badge = document.getElementById('badge');
|
||||
if (v < 0){ dep.classList.remove('hidden'); ven.classList.add('hidden');
|
||||
badge.className='badge dep'; badge.textContent='Dépense';
|
||||
if(!document.querySelector('#lignes tr')){addRow();addRow();}
|
||||
recalc(); }
|
||||
else if (v > 0){ ven.classList.remove('hidden'); dep.classList.add('hidden');
|
||||
badge.className='badge ven'; badge.textContent='Vente'; }
|
||||
else { dep.classList.add('hidden'); ven.classList.add('hidden'); badge.textContent=''; }
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
+16
-2
@@ -204,10 +204,24 @@ def _traiter_post(request):
|
||||
if entreprise is None:
|
||||
return redirect(reverse("gestion"))
|
||||
action = request.POST.get("action")
|
||||
modeles = {"recette": Facture, "depense": Depense, "operation": Paiement}
|
||||
|
||||
if action == "delete_bulk":
|
||||
n = 0
|
||||
for token in request.POST.getlist("sel"):
|
||||
t, _, pk = token.partition(":")
|
||||
Mod = modeles.get(t)
|
||||
if not Mod or not pk:
|
||||
continue
|
||||
obj = Mod.objects.filter(entreprise=entreprise, pk=pk).first()
|
||||
if obj:
|
||||
obj.delete()
|
||||
n += 1
|
||||
messages.success(request, f"{n} opération(s) supprimée(s).")
|
||||
return _retour(request, entreprise)
|
||||
|
||||
typ = request.POST.get("type")
|
||||
oid = request.POST.get("id")
|
||||
|
||||
modeles = {"recette": Facture, "depense": Depense, "operation": Paiement}
|
||||
Modele = modeles.get(typ)
|
||||
if not Modele:
|
||||
return _retour(request, entreprise)
|
||||
|
||||
@@ -302,7 +302,7 @@ def import_csv(request):
|
||||
ctx = {"entreprises": entreprises,
|
||||
"categories": Categorie.objects.filter(actif=True),
|
||||
"activites": Activite.choices, "regimes": RegimeTva.choices,
|
||||
"annee_defaut": date_cls.today().year}
|
||||
"annee_defaut": date_cls.today().year, "vue": "import"}
|
||||
|
||||
if request.method != "POST":
|
||||
return render(request, "compta/import_csv.html", ctx)
|
||||
|
||||
@@ -45,6 +45,7 @@ def _context(entreprises, post=None):
|
||||
"categories_vente": Categorie.objects.filter(actif=True, usage__in=["vente", "neutre"]),
|
||||
"today": date.today().isoformat(),
|
||||
"post": post,
|
||||
"vue": "saisie",
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user