Create infra scripts
This commit is contained in:
138
uv_app/infrastructure/create_paslauga_kaina.py
Normal file
138
uv_app/infrastructure/create_paslauga_kaina.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from uv_app.core.mssql import connect_to_mssql
|
||||||
|
|
||||||
|
BASE_URL = "https://api.manorivile.lt/client/v2"
|
||||||
|
TIMEOUT = 30
|
||||||
|
|
||||||
|
DOTENV_PATH = Path(__file__).resolve().parents[2] / ".env"
|
||||||
|
load_dotenv(DOTENV_PATH, override=True)
|
||||||
|
|
||||||
|
SERVICE_CODE = "KAINA"
|
||||||
|
SERVICE_NAME = "Kaina"
|
||||||
|
SERVICE_UOM = "VNT"
|
||||||
|
|
||||||
|
|
||||||
|
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 _service_exists(api_key: str, code: str) -> bool:
|
||||||
|
payload = {
|
||||||
|
"method": "GET_N17_LIST",
|
||||||
|
"params": {
|
||||||
|
"list": "H",
|
||||||
|
"fil": f"n17_kodas_ps='{code}'",
|
||||||
|
"limit": 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data = _post(api_key, payload)
|
||||||
|
return bool(data.get("list") or data.get("N17"))
|
||||||
|
|
||||||
|
|
||||||
|
def _choose_service_ds() -> Optional[str]:
|
||||||
|
conn = connect_to_mssql()
|
||||||
|
if conn is None:
|
||||||
|
raise RuntimeError("Failed to connect to MSSQL.")
|
||||||
|
try:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT N17_KODAS_DS, COUNT(*) AS cnt
|
||||||
|
FROM dbo.N17_PROD
|
||||||
|
WHERE N17_TIPAS = 2
|
||||||
|
AND N17_KODAS_DS IS NOT NULL
|
||||||
|
AND LTRIM(RTRIM(N17_KODAS_DS)) <> ''
|
||||||
|
GROUP BY N17_KODAS_DS
|
||||||
|
ORDER BY cnt DESC, N17_KODAS_DS
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return str(rows[0][0]).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def create_service_kaina() -> dict:
|
||||||
|
api_key = _get_api_key()
|
||||||
|
|
||||||
|
service_ds = _choose_service_ds()
|
||||||
|
if not service_ds:
|
||||||
|
return {"status": "cancelled", "reason": "No N17_KODAS_DS selected"}
|
||||||
|
|
||||||
|
n17 = {
|
||||||
|
"N17_KODAS_PS": SERVICE_CODE,
|
||||||
|
"N17_TIPAS": "2",
|
||||||
|
"N17_PAV": SERVICE_NAME,
|
||||||
|
"N17_KODAS_US": SERVICE_UOM,
|
||||||
|
"N17_KODAS_DS": service_ds,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _service_exists(api_key, SERVICE_CODE):
|
||||||
|
payload = {
|
||||||
|
"method": "EDIT_N17",
|
||||||
|
"params": {
|
||||||
|
"oper": "U",
|
||||||
|
"user": api_key.split(".", 1)[0],
|
||||||
|
"fld": "N17_KODAS_PS",
|
||||||
|
"val": SERVICE_CODE,
|
||||||
|
},
|
||||||
|
"data": {"N17": n17},
|
||||||
|
}
|
||||||
|
data = _post(api_key, payload)
|
||||||
|
return {"status": "updated", "code": SERVICE_CODE, "response": data}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"method": "EDIT_N17",
|
||||||
|
"params": {"oper": "I"},
|
||||||
|
"data": {"N17": n17},
|
||||||
|
}
|
||||||
|
data = _post(api_key, payload)
|
||||||
|
return {"status": "created", "code": SERVICE_CODE, "response": data}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
result = create_service_kaina()
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
137
uv_app/infrastructure/create_paslauga_statusas.py
Normal file
137
uv_app/infrastructure/create_paslauga_statusas.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from uv_app.core.mssql import connect_to_mssql
|
||||||
|
|
||||||
|
BASE_URL = "https://api.manorivile.lt/client/v2"
|
||||||
|
TIMEOUT = 30
|
||||||
|
|
||||||
|
DOTENV_PATH = Path(__file__).resolve().parents[2] / ".env"
|
||||||
|
load_dotenv(DOTENV_PATH, override=True)
|
||||||
|
|
||||||
|
SERVICE_CODE = "STATUSAS"
|
||||||
|
SERVICE_NAME = "Statusas"
|
||||||
|
SERVICE_UOM = "VNT"
|
||||||
|
|
||||||
|
|
||||||
|
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 _service_exists(api_key: str, code: str) -> bool:
|
||||||
|
payload = {
|
||||||
|
"method": "GET_N17_LIST",
|
||||||
|
"params": {
|
||||||
|
"list": "H",
|
||||||
|
"fil": f"n17_kodas_ps='{code}'",
|
||||||
|
"limit": 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data = _post(api_key, payload)
|
||||||
|
return bool(data.get("list") or data.get("N17"))
|
||||||
|
|
||||||
|
|
||||||
|
def _choose_service_ds() -> Optional[str]:
|
||||||
|
conn = connect_to_mssql()
|
||||||
|
if conn is None:
|
||||||
|
raise RuntimeError("Failed to connect to MSSQL.")
|
||||||
|
try:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT N17_KODAS_DS, COUNT(*) AS cnt
|
||||||
|
FROM dbo.N17_PROD
|
||||||
|
WHERE N17_TIPAS = 2
|
||||||
|
AND N17_KODAS_DS IS NOT NULL
|
||||||
|
AND LTRIM(RTRIM(N17_KODAS_DS)) <> ''
|
||||||
|
GROUP BY N17_KODAS_DS
|
||||||
|
ORDER BY cnt DESC, N17_KODAS_DS
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return str(rows[0][0]).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def create_service_statusas() -> dict:
|
||||||
|
api_key = _get_api_key()
|
||||||
|
service_ds = _choose_service_ds()
|
||||||
|
if not service_ds:
|
||||||
|
return {"status": "cancelled", "reason": "No N17_KODAS_DS selected"}
|
||||||
|
|
||||||
|
n17 = {
|
||||||
|
"N17_KODAS_PS": SERVICE_CODE,
|
||||||
|
"N17_TIPAS": "2",
|
||||||
|
"N17_PAV": SERVICE_NAME,
|
||||||
|
"N17_KODAS_US": SERVICE_UOM,
|
||||||
|
"N17_KODAS_DS": service_ds,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _service_exists(api_key, SERVICE_CODE):
|
||||||
|
payload = {
|
||||||
|
"method": "EDIT_N17",
|
||||||
|
"params": {
|
||||||
|
"oper": "U",
|
||||||
|
"user": api_key.split(".", 1)[0],
|
||||||
|
"fld": "N17_KODAS_PS",
|
||||||
|
"val": SERVICE_CODE,
|
||||||
|
},
|
||||||
|
"data": {"N17": n17},
|
||||||
|
}
|
||||||
|
data = _post(api_key, payload)
|
||||||
|
return {"status": "updated", "code": SERVICE_CODE, "response": data}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"method": "EDIT_N17",
|
||||||
|
"params": {"oper": "I"},
|
||||||
|
"data": {"N17": n17},
|
||||||
|
}
|
||||||
|
data = _post(api_key, payload)
|
||||||
|
return {"status": "created", "code": SERVICE_CODE, "response": data}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
result = create_service_statusas()
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
111
uv_app/user/debug_client_101460.py
Normal file
111
uv_app/user/debug_client_101460.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
import psycopg2.extras
|
||||||
|
|
||||||
|
from uv_app.core.mssql import connect_to_mssql
|
||||||
|
from uv_app.core.pgsql import connect_to_pgsql
|
||||||
|
|
||||||
|
QUERY_PATH = Path(__file__).with_name("routeriai_query.sql")
|
||||||
|
CLIENT_CODE = "101460"
|
||||||
|
|
||||||
|
|
||||||
|
def _read_query() -> str:
|
||||||
|
return QUERY_PATH.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_text(value: object) -> Optional[str]:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
text = value.decode(errors="ignore") if isinstance(value, bytes) else str(value)
|
||||||
|
text = text.replace("\u00a0", " ")
|
||||||
|
text = re.sub(r"[\x00-\x1f\x7f\u2028\u2029\u0085]", " ", text)
|
||||||
|
return re.sub(r"\s+", " ", text).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _format_diff_value(value: str) -> str:
|
||||||
|
return (
|
||||||
|
value.replace("\r", "\\r")
|
||||||
|
.replace("\n", "\\n")
|
||||||
|
.replace("\t", "\\t")
|
||||||
|
.replace("\u2028", "\\u2028")
|
||||||
|
.replace("\u2029", "\\u2029")
|
||||||
|
.replace("\u0085", "\\u0085")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _dump_string(label: str, value: object) -> None:
|
||||||
|
if value is None:
|
||||||
|
print(f"{label}: None")
|
||||||
|
return
|
||||||
|
text = value.decode(errors="ignore") if isinstance(value, bytes) else str(value)
|
||||||
|
print(f"{label} raw repr: {text!r}")
|
||||||
|
print(f"{label} raw escaped: {_format_diff_value(text)}")
|
||||||
|
cleaned = _clean_text(text)
|
||||||
|
print(f"{label} cleaned repr: {cleaned!r}")
|
||||||
|
print(f"{label} cleaned escaped: {_format_diff_value(cleaned or '')}")
|
||||||
|
codepoints = [f"U+{ord(ch):04X}" for ch in text]
|
||||||
|
print(f"{label} codepoints: {' '.join(codepoints)}")
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_pg_client() -> Optional[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())
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
for row in rows:
|
||||||
|
if str(row.get("client_code") or "").strip() == CLIENT_CODE:
|
||||||
|
return row
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_mssql_client() -> Optional[Dict[str, object]]:
|
||||||
|
conn = connect_to_mssql()
|
||||||
|
if conn is None:
|
||||||
|
raise RuntimeError("Failed to connect to MSSQL.")
|
||||||
|
try:
|
||||||
|
query = """
|
||||||
|
SELECT
|
||||||
|
N08_KODAS_KS,
|
||||||
|
N08_PAV
|
||||||
|
FROM dbo.N08_KLIJ
|
||||||
|
WHERE N08_KODAS_KS = ?
|
||||||
|
"""
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute(query, (CLIENT_CODE,))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if not row:
|
||||||
|
return None
|
||||||
|
columns = [c[0] for c in cursor.description]
|
||||||
|
return dict(zip(columns, row))
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
pg_row = _fetch_pg_client()
|
||||||
|
if not pg_row:
|
||||||
|
print("Client not found in PostgreSQL.")
|
||||||
|
else:
|
||||||
|
print("PostgreSQL data:")
|
||||||
|
_dump_string("PG name", pg_row.get("name"))
|
||||||
|
|
||||||
|
mssql_row = _fetch_mssql_client()
|
||||||
|
if not mssql_row:
|
||||||
|
print("Client not found in MSSQL.")
|
||||||
|
else:
|
||||||
|
print("MSSQL data:")
|
||||||
|
_dump_string("MSSQL N08_PAV", mssql_row.get("N08_PAV"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Iterable, Optional, Tuple
|
from typing import Dict, Iterable, Optional, Tuple
|
||||||
@@ -108,6 +109,19 @@ def _normalize_person_type(value: Optional[object]) -> str:
|
|||||||
return "1"
|
return "1"
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_text(value: object) -> Optional[str]:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
text = value.decode(errors="ignore")
|
||||||
|
else:
|
||||||
|
text = str(value)
|
||||||
|
text = text.replace("\u00a0", " ")
|
||||||
|
# Drop control chars (including C1) and unicode line/paragraph separators.
|
||||||
|
text = re.sub(r"[\x00-\x1f\x7f-\x9f\u2028\u2029]", " ", text)
|
||||||
|
return re.sub(r"\s+", " ", text).strip()
|
||||||
|
|
||||||
|
|
||||||
def _build_n08_payload(row: Dict[str, object]) -> Dict[str, object]:
|
def _build_n08_payload(row: Dict[str, object]) -> Dict[str, object]:
|
||||||
mobile = (row.get("mobile_phone") or "").strip()
|
mobile = (row.get("mobile_phone") or "").strip()
|
||||||
phone = (row.get("phone") or "").strip()
|
phone = (row.get("phone") or "").strip()
|
||||||
@@ -129,9 +143,11 @@ def _build_n08_payload(row: Dict[str, object]) -> Dict[str, object]:
|
|||||||
if isinstance(creation_date, (date, datetime)):
|
if isinstance(creation_date, (date, datetime)):
|
||||||
creation_date = creation_date.isoformat()
|
creation_date = creation_date.isoformat()
|
||||||
|
|
||||||
|
name = _clean_text(row.get("name"))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"N08_KODAS_KS": row.get("client_code"),
|
"N08_KODAS_KS": row.get("client_code"),
|
||||||
"N08_PAV": row.get("name"),
|
"N08_PAV": name,
|
||||||
"N08_ADR": row.get("address"),
|
"N08_ADR": row.get("address"),
|
||||||
"N08_E_MAIL": row.get("email"),
|
"N08_E_MAIL": row.get("email"),
|
||||||
"N08_ADD_DATE": creation_date,
|
"N08_ADD_DATE": creation_date,
|
||||||
@@ -163,6 +179,17 @@ def _normalize_value(value: object) -> str:
|
|||||||
return str(value).strip()
|
return str(value).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _format_diff_value(value: str) -> str:
|
||||||
|
return (
|
||||||
|
value.replace("\r", "\\r")
|
||||||
|
.replace("\n", "\\n")
|
||||||
|
.replace("\t", "\\t")
|
||||||
|
.replace("\u2028", "\\u2028")
|
||||||
|
.replace("\u2029", "\\u2029")
|
||||||
|
.replace("\u0085", "\\u0085")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _diff_fields(
|
def _diff_fields(
|
||||||
existing: Optional[Dict[str, object]],
|
existing: Optional[Dict[str, object]],
|
||||||
desired: Dict[str, object],
|
desired: Dict[str, object],
|
||||||
@@ -207,7 +234,9 @@ def _upsert_client(
|
|||||||
return
|
return
|
||||||
print(f"Updating client: {client_code} ({index}/{total})")
|
print(f"Updating client: {client_code} ({index}/{total})")
|
||||||
for field, (old, new) in changes.items():
|
for field, (old, new) in changes.items():
|
||||||
print(f" {field}: '{old}' -> '{new}'")
|
old_fmt = _format_diff_value(old)
|
||||||
|
new_fmt = _format_diff_value(new)
|
||||||
|
print(f" {field}: '{old_fmt}' -> '{new_fmt}'")
|
||||||
payload = {
|
payload = {
|
||||||
"method": "EDIT_N08",
|
"method": "EDIT_N08",
|
||||||
"params": {
|
"params": {
|
||||||
@@ -221,7 +250,8 @@ def _upsert_client(
|
|||||||
else:
|
else:
|
||||||
print(f"Creating client: {client_code} ({index}/{total})")
|
print(f"Creating client: {client_code} ({index}/{total})")
|
||||||
for field, (_, new) in changes.items():
|
for field, (_, new) in changes.items():
|
||||||
print(f" {field}: '' -> '{new}'")
|
new_fmt = _format_diff_value(new)
|
||||||
|
print(f" {field}: '' -> '{new_fmt}'")
|
||||||
payload = {
|
payload = {
|
||||||
"method": "EDIT_N08_FULL",
|
"method": "EDIT_N08_FULL",
|
||||||
"params": {"oper": "I", "user": user},
|
"params": {"oper": "I", "user": user},
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
SELECT
|
SELECT
|
||||||
k.kodas AS client_code,
|
k.kodas AS client_code,
|
||||||
trim(concat_ws(' ', k.vardas, k.pavarde)) AS name,
|
regexp_replace(
|
||||||
|
trim(concat_ws(' ', k.vardas, k.pavarde)),
|
||||||
|
'\s+',
|
||||||
|
' ',
|
||||||
|
'g'
|
||||||
|
) AS name,
|
||||||
trim(concat_ws(', ',
|
trim(concat_ws(', ',
|
||||||
concat_ws(' ', g.pav, k.namas, NULLIF(NULLIF(k.butas::text, '0'), '')),
|
concat_ws(' ', g.pav, k.namas, NULLIF(NULLIF(k.butas::text, '0'), '')),
|
||||||
m.pav,
|
m.pav,
|
||||||
|
|||||||
Reference in New Issue
Block a user