diff --git a/uv_app/contracts/port_bukle_to_rivile.py b/uv_app/contracts/port_bukle_to_rivile.py index b76807e..3365f18 100644 --- a/uv_app/contracts/port_bukle_to_rivile.py +++ b/uv_app/contracts/port_bukle_to_rivile.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import argparse from datetime import date, timedelta from pathlib import Path from typing import Dict, Iterable, Optional @@ -327,8 +328,23 @@ def _build_periods(rows: list[dict[str, object]]) -> list[dict[str, object]]: return periods +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--user-id", dest="user_id") + parser.add_argument("--sutartis-id", dest="sutartis_id") + return parser.parse_args() + + +def _get_sutarties_kodas(args: argparse.Namespace) -> str: + sutarties_kodas = (args.sutartis_id or "").strip() + if sutarties_kodas: + return sutarties_kodas + return input("Sutarties kodas: ").strip() + + def main() -> None: - sutarties_kodas = input("Sutarties kodas: ").strip() + args = _parse_args() + sutarties_kodas = _get_sutarties_kodas(args) if not sutarties_kodas: print("Missing sutarties kodas.") return diff --git a/uv_app/contracts/port_menesine_kaina_to_rivile.py b/uv_app/contracts/port_menesine_kaina_to_rivile.py index 124c89d..1f471df 100644 --- a/uv_app/contracts/port_menesine_kaina_to_rivile.py +++ b/uv_app/contracts/port_menesine_kaina_to_rivile.py @@ -1,5 +1,6 @@ from __future__ import annotations +import argparse import os from datetime import date from pathlib import Path @@ -377,8 +378,23 @@ def _build_periods(rows: list[dict[str, object]]) -> list[dict[str, object]]: return periods +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--user-id", dest="user_id") + parser.add_argument("--sutartis-id", dest="sutartis_id") + return parser.parse_args() + + +def _get_sutarties_kodas(args: argparse.Namespace) -> str: + sutarties_kodas = (args.sutartis_id or "").strip() + if sutarties_kodas: + return sutarties_kodas + return input("Sutarties kodas: ").strip() + + def main() -> None: - sutarties_kodas = input("Sutarties kodas: ").strip() + args = _parse_args() + sutarties_kodas = _get_sutarties_kodas(args) if not sutarties_kodas: print("Missing sutarties kodas.") return diff --git a/uv_app/contracts/port_sutartis_full.py b/uv_app/contracts/port_sutartis_full.py index a0b5048..1c49f11 100644 --- a/uv_app/contracts/port_sutartis_full.py +++ b/uv_app/contracts/port_sutartis_full.py @@ -1,5 +1,6 @@ from __future__ import annotations +import argparse import subprocess import sys from pathlib import Path @@ -11,10 +12,17 @@ SCRIPTS = [ ] -def _run_script(script_path: Path, sutarties_kodas: str) -> None: +def _run_script(script_path: Path, user_id: str, sutarties_kodas: str) -> None: result = subprocess.run( - [sys.executable, str(script_path)], - input=f"{sutarties_kodas}\n", + [ + sys.executable, + str(script_path), + "--user-id", + user_id, + "--sutartis-id", + sutarties_kodas, + ], + input="", text=True, check=True, ) @@ -22,16 +30,33 @@ def _run_script(script_path: Path, sutarties_kodas: str) -> None: def main() -> None: - sutarties_kodas = input("Sutarties kodas: ").strip() - if not sutarties_kodas: + parser = argparse.ArgumentParser() + parser.add_argument("--user-id", dest="user_id") + parser.add_argument("--sutartis-id", dest="sutartis_id", nargs="*") + args = parser.parse_args() + + user_id = (args.user_id or "").strip() + if not user_id: + user_id = input("User ID: ").strip() + if not user_id: + print("Missing user id.") + return + + sutarties_kodai = [s.strip() for s in (args.sutartis_id or []) if s.strip()] + if not sutarties_kodai: + raw = input("Sutarties kodas (comma-separated for multiple): ").strip() + if raw: + sutarties_kodai = [s.strip() for s in raw.split(",") if s.strip()] + if not sutarties_kodai: print("Missing sutarties kodas.") return base_dir = Path(__file__).resolve().parent - for script_name in SCRIPTS: - script_path = base_dir / script_name - print(f"Running {script_name}...") - _run_script(script_path, sutarties_kodas) + for sutarties_kodas in sutarties_kodai: + for script_name in SCRIPTS: + script_path = base_dir / script_name + print(f"Running {script_name} for {sutarties_kodas}...") + _run_script(script_path, user_id, sutarties_kodas) if __name__ == "__main__": diff --git a/uv_app/contracts/port_sutartis_to_rivile.py b/uv_app/contracts/port_sutartis_to_rivile.py index 064afd7..aa7381d 100644 --- a/uv_app/contracts/port_sutartis_to_rivile.py +++ b/uv_app/contracts/port_sutartis_to_rivile.py @@ -1,5 +1,6 @@ from __future__ import annotations +import argparse import os from datetime import date, datetime from pathlib import Path @@ -248,8 +249,27 @@ def _update_contract( _post(api_key, payload) +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--user-id", dest="user_id") + parser.add_argument("--sutartis-id", dest="sutartis_id") + return parser.parse_args() + + +def _get_sutarties_kodas(args: argparse.Namespace) -> str: + sutarties_kodas = (args.sutartis_id or "").strip() + if sutarties_kodas: + return sutarties_kodas + return input("Sutarties kodas: ").strip() + + +def _get_user_id(args: argparse.Namespace) -> str: + return (args.user_id or "").strip() + + def main() -> None: - sutarties_kodas = input("Sutarties kodas: ").strip() + args = _parse_args() + sutarties_kodas = _get_sutarties_kodas(args) if not sutarties_kodas: print("Missing sutarties kodas.") return @@ -260,7 +280,7 @@ def main() -> None: return row = rows[0] - client_code = str(row.get("client_code") or "").strip() + client_code = _get_user_id(args) or str(row.get("client_code") or "").strip() if not client_code: raise RuntimeError("Missing client code for sutartis.") diff --git a/uv_app/user/multiple_contract_user_cleanup.py b/uv_app/user/multiple_contract_user_cleanup.py new file mode 100644 index 0000000..5b5cc60 --- /dev/null +++ b/uv_app/user/multiple_contract_user_cleanup.py @@ -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() diff --git a/uv_app/user/multiple_contract_user_cleanup.sql b/uv_app/user/multiple_contract_user_cleanup.sql new file mode 100644 index 0000000..ce8df32 --- /dev/null +++ b/uv_app/user/multiple_contract_user_cleanup.sql @@ -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;