v1.3 - Migration SQLite (au lieu de JSON)
This commit is contained in:
326
app.py
326
app.py
@@ -1,21 +1,43 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from flask import Flask, request, jsonify, redirect, send_file
|
from flask import Flask, request, jsonify, redirect, send_file
|
||||||
import json
|
import sqlite3
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CONFIG_FILE = '/home/h3r7/depenses_trello/config.json'
|
DB_FILE = '/home/h3r7/depenses_trello/depenses.db'
|
||||||
|
|
||||||
def load_config():
|
def init_db():
|
||||||
with open(CONFIG_FILE, 'r') as f:
|
conn = sqlite3.connect(DB_FILE)
|
||||||
return json.load(f)
|
c = conn.cursor()
|
||||||
|
c.execute('''CREATE TABLE IF NOT EXISTS depenses (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
prenom TEXT,
|
||||||
|
date TEXT,
|
||||||
|
libelle TEXT,
|
||||||
|
montant REAL,
|
||||||
|
status TEXT DEFAULT 'En attente',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)''')
|
||||||
|
c.execute('''CREATE TABLE IF NOT EXISTS config (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT
|
||||||
|
)''')
|
||||||
|
c.execute('''CREATE TABLE IF NOT EXISTS prenoms (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT UNIQUE
|
||||||
|
)''')
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
def save_config(data):
|
def get_db():
|
||||||
with open(CONFIG_FILE, 'w') as f:
|
conn = sqlite3.connect(DB_FILE)
|
||||||
json.dump(data, f, indent=2)
|
conn.row_factory = sqlite3.Row
|
||||||
|
return conn
|
||||||
|
|
||||||
|
init_db()
|
||||||
|
|
||||||
def parse_date(d):
|
def parse_date(d):
|
||||||
if not d: return ""
|
if not d: return ""
|
||||||
@@ -38,66 +60,90 @@ def dashboard():
|
|||||||
# API Config
|
# API Config
|
||||||
@app.route('/api/config')
|
@app.route('/api/config')
|
||||||
def get_config():
|
def get_config():
|
||||||
return jsonify(load_config())
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
|
||||||
|
# Get prenoms
|
||||||
|
c.execute('SELECT name FROM prenoms')
|
||||||
|
prenoms = [row[0] for row in c.fetchall()]
|
||||||
|
|
||||||
|
# Get format
|
||||||
|
c.execute("SELECT value FROM config WHERE key='format'")
|
||||||
|
format_row = c.fetchone()
|
||||||
|
format_val = format_row[0] if format_row else '{prenom} - {date} - {libelle} - {montant}€'
|
||||||
|
|
||||||
|
# Get trello config
|
||||||
|
c.execute("SELECT value FROM config WHERE key='trello'")
|
||||||
|
trello_row = c.fetchone()
|
||||||
|
trello = {}
|
||||||
|
if trello_row:
|
||||||
|
import json
|
||||||
|
trello = json.loads(trello_row[0])
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'format': format_val,
|
||||||
|
'prenoms': prenoms,
|
||||||
|
'trello': trello
|
||||||
|
})
|
||||||
|
|
||||||
# Add depense
|
# Add depense
|
||||||
@app.route('/api/add', methods=['POST'])
|
@app.route('/api/add', methods=['POST'])
|
||||||
def add_depense():
|
def add_depense():
|
||||||
config = load_config()
|
conn = get_db()
|
||||||
data = {
|
c = conn.cursor()
|
||||||
'id': len(config.get('depenses', [])) + 1,
|
c.execute('INSERT INTO depenses (prenom, date, libelle, montant, status) VALUES (?, ?, ?, ?, ?)',
|
||||||
'prenom': request.form.get('prenom'),
|
(request.form.get('prenom'), parse_date(request.form.get('date', '')),
|
||||||
'date': parse_date(request.form.get('date', '')),
|
request.form.get('libelle'), float(request.form.get('montant', 0)), 'En attente'))
|
||||||
'libelle': request.form.get('libelle'),
|
conn.commit()
|
||||||
'montant': float(request.form.get('montant', 0)),
|
conn.close()
|
||||||
'status': 'En attente'
|
|
||||||
}
|
|
||||||
if 'depenses' not in config:
|
|
||||||
config['depenses'] = []
|
|
||||||
config['depenses'].append(data)
|
|
||||||
save_config(config)
|
|
||||||
return redirect('/?page=saisie')
|
return redirect('/?page=saisie')
|
||||||
|
|
||||||
# Get depenses
|
# Get depenses
|
||||||
@app.route('/api/depenses')
|
@app.route('/api/depenses')
|
||||||
def get_depenses():
|
def get_depenses():
|
||||||
config = load_config()
|
conn = get_db()
|
||||||
return jsonify(config.get('depenses', []))
|
c = conn.cursor()
|
||||||
|
c.execute('SELECT id, prenom, date, libelle, montant, status FROM depenses ORDER BY date DESC')
|
||||||
|
rows = c.fetchall()
|
||||||
|
conn.close()
|
||||||
|
return jsonify([dict(row) for row in rows])
|
||||||
|
|
||||||
# Delete depense
|
# Delete depense
|
||||||
@app.route('/api/del/<int:id>')
|
@app.route('/api/del/<int:id>')
|
||||||
def delete_depense(id):
|
def delete_depense(id):
|
||||||
config = load_config()
|
conn = get_db()
|
||||||
config['depenses'] = [d for d in config.get('depenses', []) if d.get('id') != id]
|
c = conn.cursor()
|
||||||
save_config(config)
|
c.execute('DELETE FROM depenses WHERE id=?', (id,))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
return redirect('/?page=saisie')
|
return redirect('/?page=saisie')
|
||||||
|
|
||||||
# Clear all
|
# Clear all
|
||||||
@app.route('/api/clear', methods=['POST'])
|
@app.route('/api/clear', methods=['POST'])
|
||||||
def clear_depenses():
|
def clear_depenses():
|
||||||
config = load_config()
|
conn = get_db()
|
||||||
config['depenses'] = []
|
c = conn.cursor()
|
||||||
save_config(config)
|
c.execute('DELETE FROM depenses')
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
return jsonify({'success': True})
|
return jsonify({'success': True})
|
||||||
|
|
||||||
# Export CSV
|
# Export CSV
|
||||||
@app.route('/api/export')
|
@app.route('/api/export')
|
||||||
def export_csv():
|
def export_csv():
|
||||||
config = load_config()
|
conn = get_db()
|
||||||
depenses = config.get('depenses', [])
|
c = conn.cursor()
|
||||||
|
c.execute('SELECT prenom, date, libelle, montant, status FROM depenses ORDER BY date DESC')
|
||||||
|
rows = c.fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
writer = csv.writer(output)
|
writer = csv.writer(output)
|
||||||
writer.writerow(['prenom', 'date', 'libelle', 'montant', 'status'])
|
writer.writerow(['prenom', 'date', 'libelle', 'montant', 'status'])
|
||||||
|
for row in rows:
|
||||||
for d in depenses:
|
writer.writerow(row)
|
||||||
writer.writerow([
|
|
||||||
d.get('prenom', ''),
|
|
||||||
d.get('date', ''),
|
|
||||||
d.get('libelle', ''),
|
|
||||||
d.get('montant', 0),
|
|
||||||
d.get('status', '')
|
|
||||||
])
|
|
||||||
|
|
||||||
output.seek(0)
|
output.seek(0)
|
||||||
return send_file(
|
return send_file(
|
||||||
@@ -120,86 +166,68 @@ def import_csv():
|
|||||||
try:
|
try:
|
||||||
content = file.read().decode('utf-8')
|
content = file.read().decode('utf-8')
|
||||||
reader = csv.reader(content.splitlines())
|
reader = csv.reader(content.splitlines())
|
||||||
next(reader) # Skip header
|
next(reader)
|
||||||
|
|
||||||
config = load_config()
|
conn = get_db()
|
||||||
if 'depenses' not in config:
|
c = conn.cursor()
|
||||||
config['depenses'] = []
|
|
||||||
|
|
||||||
max_id = max([d.get('id', 0) for d in config.get('depenses', [])], default=0)
|
|
||||||
|
|
||||||
imported = 0
|
imported = 0
|
||||||
for row in reader:
|
for row in reader:
|
||||||
if len(row) >= 4:
|
if len(row) >= 4:
|
||||||
max_id += 1
|
c.execute('INSERT INTO depenses (prenom, date, libelle, montant, status) VALUES (?, ?, ?, ?, ?)',
|
||||||
config['depenses'].append({
|
(row[0].strip(), row[1].strip(), row[2].strip(),
|
||||||
'id': max_id,
|
float(row[3]) if row[3] else 0, row[4].strip() if len(row) > 4 else 'En attente'))
|
||||||
'prenom': row[0].strip(),
|
|
||||||
'date': row[1].strip(),
|
|
||||||
'libelle': row[2].strip(),
|
|
||||||
'montant': float(row[3]) if row[3] else 0,
|
|
||||||
'status': row[4].strip() if len(row) > 4 else 'En attente'
|
|
||||||
})
|
|
||||||
imported += 1
|
imported += 1
|
||||||
|
|
||||||
save_config(config)
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
return jsonify({'success': True, 'imported': imported})
|
return jsonify({'success': True, 'imported': imported})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 400
|
return jsonify({'error': str(e)}), 400
|
||||||
|
|
||||||
# Generate text for ONE expense
|
# Send ONE to Trello
|
||||||
@app.route('/api/generate_one/<int:id>', methods=['GET'])
|
|
||||||
def generate_one(id):
|
|
||||||
config = load_config()
|
|
||||||
d = None
|
|
||||||
for exp in config.get('depenses', []):
|
|
||||||
if exp.get('id') == id:
|
|
||||||
d = exp
|
|
||||||
break
|
|
||||||
if not d:
|
|
||||||
return jsonify({'text': ''})
|
|
||||||
|
|
||||||
template = config.get('format', '{prenom} - {date} - {libelle} - {montant}€')
|
|
||||||
line = template
|
|
||||||
ddate = d.get('date', '') or ''
|
|
||||||
if ddate:
|
|
||||||
ddate = '/'.join(ddate.split('-')[::-1])
|
|
||||||
line = line.replace('{prenom}', d.get('prenom', ''))
|
|
||||||
line = line.replace('{date}', ddate)
|
|
||||||
line = line.replace('{libelle}', d.get('libelle', ''))
|
|
||||||
line = line.replace('{montant}', str(d.get('montant', 0)))
|
|
||||||
return jsonify({'text': line})
|
|
||||||
|
|
||||||
# Send to Trello - ONE card per pending expense
|
|
||||||
@app.route('/api/trello/send_one/<int:id>', methods=['POST'])
|
@app.route('/api/trello/send_one/<int:id>', methods=['POST'])
|
||||||
def send_one(id):
|
def send_one(id):
|
||||||
config = load_config()
|
conn = get_db()
|
||||||
t = config.get('trello', {})
|
c = conn.cursor()
|
||||||
if not t.get('api_key') or not t.get('token') or not t.get('list_id'):
|
c.execute("SELECT value FROM config WHERE key='trello'")
|
||||||
|
trello_row = c.fetchone()
|
||||||
|
if not trello_row:
|
||||||
|
conn.close()
|
||||||
return jsonify({'error': 'Config Trello manquante'}), 400
|
return jsonify({'error': 'Config Trello manquante'}), 400
|
||||||
|
|
||||||
d = None
|
import json
|
||||||
for exp in config.get('depenses', []):
|
t = json.loads(trello_row[0])
|
||||||
if exp.get('id') == id:
|
if not t.get('api_key') or not t.get('token') or not t.get('list_id'):
|
||||||
d = exp
|
conn.close()
|
||||||
break
|
return jsonify({'error': 'Config Trello incomplète'}), 400
|
||||||
|
|
||||||
|
c.execute('SELECT * FROM depenses WHERE id=?', (id,))
|
||||||
|
d = c.fetchone()
|
||||||
if not d:
|
if not d:
|
||||||
|
conn.close()
|
||||||
return jsonify({'error': 'Dépense non trouvée'}), 404
|
return jsonify({'error': 'Dépense non trouvée'}), 404
|
||||||
|
|
||||||
if d.get('status') == 'Envoyé ✅':
|
if d['status'] == 'Envoyé ✅':
|
||||||
|
conn.close()
|
||||||
return jsonify({'error': 'Déjà envoyé'}), 400
|
return jsonify({'error': 'Déjà envoyé'}), 400
|
||||||
|
|
||||||
template = config.get('format', '{prenom} - {date} - {libelle} - {montant}€')
|
# Generate text
|
||||||
line = template
|
c.execute("SELECT value FROM config WHERE key='format'")
|
||||||
ddate = d.get('date', '') or ''
|
format_row = c.fetchone()
|
||||||
|
template = format_row[0] if format_row else '{prenom} - {date} - {libelle} - {montant}€'
|
||||||
|
|
||||||
|
ddate = d['date'] or ''
|
||||||
if ddate:
|
if ddate:
|
||||||
ddate = '/'.join(ddate.split('-')[::-1])
|
ddate = '/'.join(ddate.split('-')[::-1])
|
||||||
line = line.replace('{prenom}', d.get('prenom', ''))
|
|
||||||
line = line.replace('{date}', ddate)
|
|
||||||
line = line.replace('{libelle}', d.get('libelle', ''))
|
|
||||||
line = line.replace('{montant}', str(d.get('montant', 0)))
|
|
||||||
|
|
||||||
|
line = template
|
||||||
|
line = line.replace('{prenom}', d['prenom'] or '')
|
||||||
|
line = line.replace('{date}', ddate)
|
||||||
|
line = line.replace('{libelle}', d['libelle'] or '')
|
||||||
|
line = line.replace('{montant}', str(d['montant']))
|
||||||
|
|
||||||
|
# Send to Trello
|
||||||
url = 'https://api.trello.com/1/cards'
|
url = 'https://api.trello.com/1/cards'
|
||||||
params = {
|
params = {
|
||||||
'key': t.get('api_key'),
|
'key': t.get('api_key'),
|
||||||
@@ -208,64 +236,31 @@ def send_one(id):
|
|||||||
'name': line,
|
'name': line,
|
||||||
'desc': line
|
'desc': line
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.post(url, params=params)
|
r = requests.post(url, params=params)
|
||||||
if r.status_code == 200:
|
if r.status_code == 200:
|
||||||
for exp in config.get('depenses', []):
|
c.execute('UPDATE depenses SET status=? WHERE id=?', ('Envoyé ✅', id))
|
||||||
if exp.get('id') == id:
|
conn.commit()
|
||||||
exp['status'] = 'Envoyé ✅'
|
conn.close()
|
||||||
save_config(config)
|
|
||||||
return jsonify({'success': True})
|
return jsonify({'success': True})
|
||||||
|
conn.close()
|
||||||
return jsonify({'error': r.text}), r.status_code
|
return jsonify({'error': r.text}), r.status_code
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
conn.close()
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
# Legacy - send all (for compatibility)
|
|
||||||
@app.route('/api/trello/send', methods=['POST'])
|
|
||||||
def send_trello():
|
|
||||||
config = load_config()
|
|
||||||
t = config.get('trello', {})
|
|
||||||
if not t.get('api_key') or not t.get('token') or not t.get('list_id'):
|
|
||||||
return jsonify({'error': 'Config Trello manquante'}), 400
|
|
||||||
|
|
||||||
sent_count = 0
|
|
||||||
for d in config.get('depenses', []):
|
|
||||||
if d.get('status') == 'En attente':
|
|
||||||
template = config.get('format', '{prenom} - {date} - {libelle} - {montant}€')
|
|
||||||
line = template
|
|
||||||
ddate = d.get('date', '') or ''
|
|
||||||
if ddate:
|
|
||||||
ddate = '/'.join(ddate.split('-')[::-1])
|
|
||||||
line = line.replace('{prenom}', d.get('prenom', ''))
|
|
||||||
line = line.replace('{date}', ddate)
|
|
||||||
line = line.replace('{libelle}', d.get('libelle', ''))
|
|
||||||
line = line.replace('{montant}', str(d.get('montant', 0)))
|
|
||||||
|
|
||||||
url = 'https://api.trello.com/1/cards'
|
|
||||||
params = {
|
|
||||||
'key': t.get('api_key'),
|
|
||||||
'token': t.get('token'),
|
|
||||||
'idList': t.get('list_id'),
|
|
||||||
'name': line,
|
|
||||||
'desc': line
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
r = requests.post(url, params=params)
|
|
||||||
if r.status_code == 200:
|
|
||||||
d['status'] = 'Envoyé ✅'
|
|
||||||
sent_count += 1
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
save_config(config)
|
|
||||||
return jsonify({'success': True, 'sent': sent_count})
|
|
||||||
|
|
||||||
# Generate text (all)
|
# Generate text (all)
|
||||||
@app.route('/api/generate', methods=['POST'])
|
@app.route('/api/generate', methods=['POST'])
|
||||||
def generate_text():
|
def generate_text():
|
||||||
config = load_config()
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT value FROM config WHERE key='format'")
|
||||||
|
format_row = c.fetchone()
|
||||||
|
template = format_row[0] if format_row else '{prenom} - {date} - {libelle} - {montant}€'
|
||||||
|
conn.close()
|
||||||
|
|
||||||
data = request.json
|
data = request.json
|
||||||
template = config.get('format', '{prenom} - {date} - {libelle} - {montant}€')
|
|
||||||
lines = []
|
lines = []
|
||||||
for d in data.get('depenses', []):
|
for d in data.get('depenses', []):
|
||||||
line = template
|
line = template
|
||||||
@@ -282,42 +277,53 @@ def generate_text():
|
|||||||
# Add prenom
|
# Add prenom
|
||||||
@app.route('/api/prenom/add', methods=['POST'])
|
@app.route('/api/prenom/add', methods=['POST'])
|
||||||
def add_prenom():
|
def add_prenom():
|
||||||
config = load_config()
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
prenom = request.form.get('prenom', '').strip()
|
prenom = request.form.get('prenom', '').strip()
|
||||||
if prenom and prenom not in config.get('prenoms', []):
|
if prenom:
|
||||||
if 'prenoms' not in config:
|
try:
|
||||||
config['prenoms'] = []
|
c.execute('INSERT INTO prenoms (name) VALUES (?)', (prenom,))
|
||||||
config['prenoms'].append(prenom)
|
conn.commit()
|
||||||
save_config(config)
|
except:
|
||||||
|
pass
|
||||||
|
conn.close()
|
||||||
return redirect('/?page=config')
|
return redirect('/?page=config')
|
||||||
|
|
||||||
# Del prenom
|
# Del prenom
|
||||||
@app.route('/api/prenom/del/<int:idx>')
|
@app.route('/api/prenom/del/<int:idx>')
|
||||||
def del_prenom(idx):
|
def del_prenom(idx):
|
||||||
config = load_config()
|
conn = get_db()
|
||||||
if 0 <= idx < len(config.get('prenoms', [])):
|
c = conn.cursor()
|
||||||
config['prenoms'].pop(idx)
|
c.execute('DELETE FROM prenoms WHERE id=?', (idx+1,))
|
||||||
save_config(config)
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
return redirect('/?page=config')
|
return redirect('/?page=config')
|
||||||
|
|
||||||
# Save format
|
# Save format
|
||||||
@app.route('/api/format', methods=['POST'])
|
@app.route('/api/format', methods=['POST'])
|
||||||
def save_format():
|
def save_format():
|
||||||
config = load_config()
|
conn = get_db()
|
||||||
config['format'] = request.form.get('format', '{prenom} - {date} - {libelle} - {montant}€')
|
c = conn.cursor()
|
||||||
save_config(config)
|
format_val = request.form.get('format', '{prenom} - {date} - {libelle} - {montant}€')
|
||||||
|
c.execute('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)', ('format', format_val))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
return redirect('/?page=config')
|
return redirect('/?page=config')
|
||||||
|
|
||||||
# Save trello config
|
# Save trello config
|
||||||
@app.route('/api/trello/save', methods=['POST'])
|
@app.route('/api/trello/save', methods=['POST'])
|
||||||
def save_trello():
|
def save_trello():
|
||||||
config = load_config()
|
import json
|
||||||
config['trello'] = {
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
t = json.dumps({
|
||||||
'api_key': request.form.get('api_key', ''),
|
'api_key': request.form.get('api_key', ''),
|
||||||
'token': request.form.get('token', ''),
|
'token': request.form.get('token', ''),
|
||||||
'list_id': request.form.get('list_id', '')
|
'list_id': request.form.get('list_id', '')
|
||||||
}
|
})
|
||||||
save_config(config)
|
c.execute('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)', ('trello', t))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
return redirect('/?page=config')
|
return redirect('/?page=config')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
154
config.json
154
config.json
@@ -1,151 +1,11 @@
|
|||||||
{
|
{
|
||||||
"depenses": [
|
"prenoms": ["Papa", "Maman", "H3R7"],
|
||||||
{
|
"format": "{prenom} - {date} - {libelle} - {montant}€",
|
||||||
"id": 1,
|
"depenses": [],
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-06",
|
|
||||||
"libelle": "ALIEXPRESS - PISTOLET MASSAGE",
|
|
||||||
"montant": 13.67,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-10",
|
|
||||||
"libelle": "LUMINAIRE LAMPADAIRE",
|
|
||||||
"montant": 97.87,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-17",
|
|
||||||
"libelle": "ALIEXPRESS BOITIER AMPOULE REGLABLE",
|
|
||||||
"montant": 22.43,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-24",
|
|
||||||
"libelle": "LIDL GAUFFRIER",
|
|
||||||
"montant": 24.98,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-11",
|
|
||||||
"libelle": "LEETCHI FABIENNE",
|
|
||||||
"montant": 30.0,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-02",
|
|
||||||
"libelle": "CB LCL GAZOLE TOTAL LOOS",
|
|
||||||
"montant": 12.54,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 7,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-16",
|
|
||||||
"libelle": "BAR LA CLOCHE (soleil)",
|
|
||||||
"montant": 8.0,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-23",
|
|
||||||
"libelle": "COURSES CARREFOUR MOSELLE",
|
|
||||||
"montant": 44.47,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 9,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-24",
|
|
||||||
"libelle": "AMAZON CAFE FOLLIER",
|
|
||||||
"montant": 16.2,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-17",
|
|
||||||
"libelle": "CT CONTRE VISITE",
|
|
||||||
"montant": 25.0,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 11,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-16",
|
|
||||||
"libelle": "CONTROLE TECHNIQUE",
|
|
||||||
"montant": 54.0,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 12,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-16",
|
|
||||||
"libelle": "LOTO - EUROMILLION",
|
|
||||||
"montant": 9.0,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 13,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-14",
|
|
||||||
"libelle": "LUMIERE TOILETTES",
|
|
||||||
"montant": 14.78,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 14,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-09",
|
|
||||||
"libelle": "COMPTOIRE AFRIQ WAZEMME",
|
|
||||||
"montant": 9.4,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 15,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-07",
|
|
||||||
"libelle": "BOUCHERIE LILLE",
|
|
||||||
"montant": 35.02,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 16,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-06",
|
|
||||||
"libelle": "COURSES CARREFOUR",
|
|
||||||
"montant": 83.66,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 17,
|
|
||||||
"prenom": "HERY",
|
|
||||||
"date": "2026-02-02",
|
|
||||||
"libelle": "PARKING",
|
|
||||||
"montant": 2.0,
|
|
||||||
"status": "Envoy\u00e9 \u2705"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"format": "{prenom} - {date} - {libelle} - {montant}\u20ac",
|
|
||||||
"prenoms": [
|
|
||||||
"HERY",
|
|
||||||
"Papa",
|
|
||||||
"Maman"
|
|
||||||
],
|
|
||||||
"trello": {
|
"trello": {
|
||||||
"api_key": "0e86fa879fe9cc1bd5bff8a4b32bceff",
|
"api_key": "",
|
||||||
"token": "ATTA55718bc108ac05e2828a9406b9a0bf843dc47e958bd659d215210a7b0726bd6f4C4A53CE",
|
"token": "",
|
||||||
"list_id": "698499e98171f1383a04dbd6"
|
"board_id": "",
|
||||||
|
"list_id": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BIN
depenses.db
Normal file
BIN
depenses.db
Normal file
Binary file not shown.
Reference in New Issue
Block a user