Feat: Kündigung blockt Erneuerungsentwurf; Upgrade storniert alte Rechnungen + legt neuen Entwurf an
This commit is contained in:
parent
b1dbde332f
commit
a9f7923716
2 changed files with 69 additions and 2 deletions
|
|
@ -1150,7 +1150,7 @@ async def list_upgrade_requests(user=Depends(require_admin)):
|
||||||
async def fulfill_upgrade_request(req_id: int, user=Depends(require_admin)):
|
async def fulfill_upgrade_request(req_id: int, user=Depends(require_admin)):
|
||||||
with db() as conn:
|
with db() as conn:
|
||||||
req = conn.execute(
|
req = conn.execute(
|
||||||
"SELECT r.*, u.name, u.email FROM upgrade_requests r JOIN users u ON u.id=r.user_id WHERE r.id=?",
|
"SELECT r.*, u.name, u.email, u.subscription_tier AS old_tier FROM upgrade_requests r JOIN users u ON u.id=r.user_id WHERE r.id=?",
|
||||||
(req_id,)
|
(req_id,)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if not req:
|
if not req:
|
||||||
|
|
@ -1259,9 +1259,70 @@ async def fulfill_upgrade_request(req_id: int, user=Depends(require_admin)):
|
||||||
import logging
|
import logging
|
||||||
logging.getLogger(__name__).warning(f"Bestätigungsmail fehlgeschlagen: {e}")
|
logging.getLogger(__name__).warning(f"Bestätigungsmail fehlgeschlagen: {e}")
|
||||||
|
|
||||||
|
# Offene Rechnungen (sent/draft) des alten Tiers stornieren + neuen Entwurf anlegen
|
||||||
|
try:
|
||||||
|
await _handle_upgrade_invoices(req, tier_label)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Upgrade-Rechnungslogik fehlgeschlagen für {req['name']}: {e}")
|
||||||
|
|
||||||
return {"ok": True, "tier": req["tier"], "user": req["name"]}
|
return {"ok": True, "tier": req["tier"], "user": req["name"]}
|
||||||
|
|
||||||
|
|
||||||
|
async def _handle_upgrade_invoices(req: dict, new_tier_label: str):
|
||||||
|
"""Storniert offene Rechnungen des alten Tiers und legt neuen Entwurf an."""
|
||||||
|
from routes.invoices import _next_invoice_number
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
with db() as conn:
|
||||||
|
# Offene Rechnungen (draft + sent) dieses Users finden
|
||||||
|
open_invoices = conn.execute(
|
||||||
|
"SELECT * FROM invoices WHERE user_id=? AND status IN ('draft','sent')",
|
||||||
|
(req["user_id"],)
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
for inv in open_invoices:
|
||||||
|
cancel_num = _next_invoice_number(conn, "ST")
|
||||||
|
conn.execute(
|
||||||
|
"""UPDATE invoices SET status='cancelled', cancelled_at=strftime('%Y-%m-%dT%H:%M:%SZ','now'),
|
||||||
|
cancellation_reason=?, cancellation_number=? WHERE id=?""",
|
||||||
|
(f"Tarif-Upgrade auf {new_tier_label}", cancel_num, inv["id"])
|
||||||
|
)
|
||||||
|
logger.info(f"Rechnung {inv['invoice_number']} storniert ({cancel_num}) — Upgrade auf {new_tier_label}")
|
||||||
|
|
||||||
|
# Neuen Entwurf für den neuen Tier anlegen
|
||||||
|
tier = req["tier"]
|
||||||
|
price = {"pro": 29.00, "breeder": 49.00}.get(tier, 29.00)
|
||||||
|
today = datetime.now(_TZ).date()
|
||||||
|
end_date = today.replace(year=today.year + 1) - timedelta(days=1)
|
||||||
|
period = f"{today.strftime('%d.%m.%Y')} – {end_date.strftime('%d.%m.%Y')}"
|
||||||
|
description = f"{new_tier_label} Jahresabo"
|
||||||
|
|
||||||
|
billing = conn.execute(
|
||||||
|
"SELECT billing_address FROM users WHERE id=?", (req["user_id"],)
|
||||||
|
).fetchone()
|
||||||
|
billing_address = billing["billing_address"] if billing else None
|
||||||
|
|
||||||
|
inv_number = _next_invoice_number(conn)
|
||||||
|
conn.execute("""
|
||||||
|
INSERT INTO invoices
|
||||||
|
(invoice_number, user_id, recipient_name, recipient_email, recipient_address,
|
||||||
|
description, service_period, amount_net, discount_pct, discount_amount,
|
||||||
|
amount_after_discount, tax_rate, tax_amount, amount_gross, notes)
|
||||||
|
VALUES (?,?,?,?,?,?,?,?,0,0,?,0,0,?,?)
|
||||||
|
""", (
|
||||||
|
inv_number, req["user_id"], req["name"], req["email"], billing_address,
|
||||||
|
description, period, price, price, price,
|
||||||
|
f"Automatisch bei Upgrade von {req.get('old_tier','Standard')} auf {new_tier_label}.",
|
||||||
|
))
|
||||||
|
invoice_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO invoice_items (invoice_id, description, quantity, unit_price, total) VALUES (?,?,1,?,?)",
|
||||||
|
(invoice_id, description, price, price)
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Neuer Rechnungsentwurf {inv_number} für {req['email']} nach Upgrade auf {new_tier_label}")
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Helpers: Quartalsdaten
|
# Helpers: Quartalsdaten
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,11 @@ async def _create_renewal_invoice_draft(user: dict, expires: date, tier_label: s
|
||||||
from mailer import send_email, email_html
|
from mailer import send_email, email_html
|
||||||
from routes.invoices import _next_invoice_number
|
from routes.invoices import _next_invoice_number
|
||||||
|
|
||||||
|
# Gekündigte Abos bekommen keine Erneuerungsrechnung
|
||||||
|
if user.get("subscription_cancelled_at"):
|
||||||
|
logger.info(f"Kein Erneuerungsentwurf für {user['email']} — Abo ist gekündigt.")
|
||||||
|
return
|
||||||
|
|
||||||
tier = user["subscription_tier"]
|
tier = user["subscription_tier"]
|
||||||
price = _TIER_PRICE.get(tier, 29.00)
|
price = _TIER_PRICE.get(tier, 29.00)
|
||||||
# Verlängerungszeitraum: Folgetag nach Ablauf bis +1 Jahr
|
# Verlängerungszeitraum: Folgetag nach Ablauf bis +1 Jahr
|
||||||
|
|
@ -330,7 +335,8 @@ async def _job_subscription_check():
|
||||||
|
|
||||||
with _db() as conn:
|
with _db() as conn:
|
||||||
users = conn.execute(
|
users = conn.execute(
|
||||||
"""SELECT id, name, email, subscription_tier, subscription_expires_at
|
"""SELECT id, name, email, subscription_tier, subscription_expires_at,
|
||||||
|
subscription_cancelled_at
|
||||||
FROM users
|
FROM users
|
||||||
WHERE subscription_tier IN ('pro','breeder')
|
WHERE subscription_tier IN ('pro','breeder')
|
||||||
AND subscription_expires_at IS NOT NULL"""
|
AND subscription_expires_at IS NOT NULL"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue