Delete script draft - Klientas dalyvauja apskaitoje skolose

This commit is contained in:
2026-02-02 19:10:09 +02:00
parent 891ae221e0
commit f9a42d51ea
6 changed files with 346 additions and 13 deletions

View File

@@ -0,0 +1,238 @@
from __future__ import annotations
import argparse
import os
from datetime import date, datetime
from pathlib import Path
from typing import Dict, Iterable
import psycopg2
import psycopg2.extras
from dotenv import load_dotenv
import requests
from uv_app.core.mssql import connect_to_mssql
from uv_app.core.pgsql import connect_to_pgsql
DOTENV_PATH = Path(__file__).resolve().parents[2] / ".env"
load_dotenv(DOTENV_PATH, override=True)
QUERY_PATH = Path(__file__).with_name("multiple_contract_user_cleanup.sql")
BASE_URL = "https://api.manorivile.lt/client/v2"
TIMEOUT = 30
def _read_query() -> str:
return QUERY_PATH.read_text(encoding="utf-8")
def _fetch_rows() -> Iterable[Dict[str, object]]:
conn = connect_to_pgsql()
if conn is None:
raise RuntimeError("Failed to connect to PostgreSQL.")
try:
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cursor:
cursor.execute(_read_query())
return cursor.fetchall()
finally:
conn.close()
def _normalize_date(value: object) -> date:
if isinstance(value, date):
return value
if isinstance(value, datetime):
return value.date()
return date.min
def _pick_primary_sutartis(rows: list[Dict[str, object]]) -> Dict[str, object]:
rows_sorted = sorted(
rows,
key=lambda r: (
_normalize_date(r.get("data")),
str(r.get("sutartiesid") or ""),
),
)
return rows_sorted[0]
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument(
"--delete-rivile",
action="store_true",
help="Delete duplicate Rivile clients.",
)
return parser.parse_args()
def _get_api_key() -> str:
api_key = os.getenv("RIVILE_API_KEY", "").strip()
if not api_key:
raise RuntimeError("Missing RIVILE_API_KEY environment variable.")
return api_key
def _post(api_key: str, payload: dict) -> dict:
headers = {
"ApiKey": api_key,
"Content-Type": "application/json",
"Accept": "application/json",
}
response = requests.post(
BASE_URL,
json=payload,
headers=headers,
timeout=TIMEOUT,
)
if response.status_code != 200:
raise RuntimeError(f"Rivile HTTP {response.status_code}: {response.text}")
data = response.json()
if "errorMessage" in data:
raise RuntimeError(f"Rivile API error: {data}")
return data
def _delete_rivile_client(api_key: str, client_code: str) -> None:
payload = {
"method": "EDIT_N08",
"params": {"oper": "D", "user": api_key.split(".", 1)[0]},
"data": {"N08": {"N08_KODAS_KS": client_code}},
}
_post(api_key, payload)
def _update_rivile_client_code(api_key: str, old_code: str, new_code: str) -> None:
payload = {
"method": "EDIT_N08",
"params": {
"oper": "U",
"user": api_key.split(".", 1)[0],
"fld": "N08_KODAS_KS",
"val": old_code,
},
"data": {"N08": {"N08_KODAS_KS": new_code}},
}
_post(api_key, payload)
def main() -> None:
args = _parse_args()
rows = list(_fetch_rows())
if not rows:
print("No multi-contract clients found.")
return
by_client: dict[int, list[Dict[str, object]]] = {}
for row in rows:
by_client.setdefault(int(row["klientasid"]), []).append(row)
mssql_conn = connect_to_mssql()
if mssql_conn is None:
raise RuntimeError("Failed to connect to MSSQL.")
processed = 0
api_key = _get_api_key() if args.delete_rivile else ""
try:
for klientasid, items in by_client.items():
processed += 1
primary = _pick_primary_sutartis(items)
target_code = str(primary.get("sutartiesid") or "").strip()
current_code = str(primary.get("kodas") or "").strip()
sutarties_ids = [
str(item.get("sutartiesid") or "").strip() for item in items
]
sutarties_ids = [s for s in sutarties_ids if s]
print("=" * 80)
print(f"klientasid={klientasid}")
print(f" PG klientas.kodas={current_code}")
print(f" PG sutartiesid list={sutarties_ids}")
print(f" earliest sutartiesid={target_code}")
actions: list[str] = []
if not target_code:
print(" -> Skipping: missing sutartiesid.")
print(" -> Actions: skip (missing sutartiesid)")
continue
# Rivile (MSSQL) checks
cursor = mssql_conn.cursor()
placeholders = ",".join("?" for _ in sutarties_ids) or "?"
params = sutarties_ids or [target_code]
cursor.execute(
f"""
SELECT N08_KODAS_KS
FROM dbo.N08_KLIJ
WHERE N08_KODAS_KS IN ({placeholders})
""",
params,
)
rivile_clients = [str(row[0]).strip() for row in cursor.fetchall()]
print(f" Rivile clients found={rivile_clients}")
if len(rivile_clients) > 1:
print(" -> ERROR: multiple Rivile clients for one klientasid.")
elif not rivile_clients:
print(" -> WARNING: no Rivile client found for these codes.")
if target_code in rivile_clients:
print(" -> OK: earliest sutartiesid is present in Rivile.")
duplicates = [c for c in rivile_clients if c != target_code]
if duplicates:
print(f" -> Duplicate Rivile clients to delete: {duplicates}")
if args.delete_rivile:
for dup in duplicates:
_delete_rivile_client(api_key, dup)
print(f" Deleted Rivile client {dup}")
actions.append(f"delete {dup}")
else:
print(" (dry-run: not deleted)")
actions.append(f"would delete {duplicates}")
else:
print(" -> WARNING: earliest sutartiesid not found in Rivile.")
if args.delete_rivile:
if rivile_clients:
keep_code = rivile_clients[0]
print(
f" -> Renaming Rivile client {keep_code} -> {target_code}"
)
_update_rivile_client_code(api_key, keep_code, target_code)
actions.append(f"rename {keep_code} -> {target_code}")
duplicates = [c for c in rivile_clients if c != keep_code]
if duplicates:
print(
f" -> Deleting remaining duplicates: {duplicates}"
)
for dup in duplicates:
_delete_rivile_client(api_key, dup)
print(f" Deleted Rivile client {dup}")
actions.append(f"delete {dup}")
else:
print(" -> Cannot update: no Rivile client to rename.")
actions.append("no rivile client to rename")
elif rivile_clients:
print(
" -> (dry-run) Would rename one client to earliest and delete the rest."
)
actions.append("would rename one client to earliest and delete the rest")
if current_code == target_code:
print(f" -> PG klientas.kodas already matches earliest sutartiesid.")
else:
print(f" -> Needs update: {current_code} -> {target_code}")
actions.append(f"pg differs {current_code}->{target_code} (no pg write)")
if not actions:
actions = ["no changes"]
print(f" -> Actions: {', '.join(actions)}")
print(f"Processed klientasid with multiple contracts: {processed}")
finally:
mssql_conn.close()
print(f"Processed klientasid with multiple contracts: {processed}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,18 @@
SELECT
k.klientasid,
k.kodas,
k.vardas,
k.pavarde,
s.sutartisid,
s.sutartiesid,
s.data
FROM klientas k
JOIN sutartis s ON s.klientasid = k.klientasid
WHERE k.klientasid IN (
SELECT k2.klientasid
FROM klientas k2
JOIN sutartis s2 ON s2.klientasid = k2.klientasid
GROUP BY k2.klientasid
HAVING COUNT(*) > 1
)
ORDER BY k.klientasid;