Files
JuridicBloc/client/pages/documents/ba.py

422 lines
18 KiB
Python

import flet as ft
import requests
from datetime import datetime
from helpers.document_status import DocumentsStatus
from helpers.emails import send_gmail
from dataclasses import dataclass, field
import asyncio
@dataclass
class State:
file_picker: ft.FilePicker | None = None
picked_files: list[ft.FilePickerFile] = field(default_factory=list)
state = State()
class Documents:
def __init__(self, page: ft.Page, home):
self.page = page
self.home = home
self.base_url = self.page.session.store.get('api_base_url')
self.token = self.page.session.store.get('token')
self.user = self.page.session.store.get('user')
self.user_id = self.user['id'] if self.user else None
self.documenet_title = None
self.all_requests = []
self.current_selected_request = None
# Elemente interfață: Căutare și Listă
self.search_bar = ft.TextField(
label="Căutare solicitări (Text solicitare sau comentariu)",
on_submit=self._on_search_change,
prefix_icon=ft.Icons.SEARCH,
expand=True
)
self.status_filter_dropdown = ft.Dropdown(
label="Filtrează după status",
options=[
ft.dropdown.Option("all", "Toate"),
ft.dropdown.Option(DocumentsStatus.NEW, DocumentsStatus.get_label(DocumentsStatus.NEW)),
ft.dropdown.Option(DocumentsStatus.ANALISE, DocumentsStatus.get_label(DocumentsStatus.ANALISE)),
ft.dropdown.Option(DocumentsStatus.IN_PROGRESS, DocumentsStatus.get_label(DocumentsStatus.IN_PROGRESS)),
ft.dropdown.Option(DocumentsStatus.WAITING_FOR_PAYMENT, DocumentsStatus.get_label(DocumentsStatus.WAITING_FOR_PAYMENT)),
ft.dropdown.Option(DocumentsStatus.COMPLETED, DocumentsStatus.get_label(DocumentsStatus.COMPLETED)),
ft.dropdown.Option(DocumentsStatus.CANCELED, DocumentsStatus.get_label(DocumentsStatus.CANCELED)),
],
value="all",
on_select=self._on_status_filter_change,
expand=True
)
self.requests_list_view = ft.ListView(
expand=True,
spacing=10,
)
# Elemente panou detalii
self.req_id_text = ft.Text("", size=18, weight=ft.FontWeight.BOLD)
self.req_text_display = ft.Text("", selectable=True)
self.price_field = ft.TextField(
label="Preț stabilit (Lei)",
width=200,
keyboard_type=ft.KeyboardType.NUMBER,
)
self.status_dropdown = ft.Dropdown(
label="Schimbă Status",
width=250,
options=[
ft.dropdown.Option(DocumentsStatus.NEW, DocumentsStatus.get_label(DocumentsStatus.NEW)),
ft.dropdown.Option(DocumentsStatus.ANALISE, DocumentsStatus.get_label(DocumentsStatus.ANALISE)),
ft.dropdown.Option(DocumentsStatus.IN_PROGRESS, DocumentsStatus.get_label(DocumentsStatus.IN_PROGRESS)),
ft.dropdown.Option(DocumentsStatus.WAITING_FOR_PAYMENT, DocumentsStatus.get_label(DocumentsStatus.WAITING_FOR_PAYMENT)),
ft.dropdown.Option(DocumentsStatus.COMPLETED, DocumentsStatus.get_label(DocumentsStatus.COMPLETED)),
ft.dropdown.Option(DocumentsStatus.CANCELED, DocumentsStatus.get_label(DocumentsStatus.CANCELED)),
]
)
self.comment_field = ft.TextField(
label="Adaugă răspuns/comentariu către client",
multiline=True,
min_lines=3,
expand=True
)
self.doc_id_info = ft.Text("Document Final: Niciunul", color=ft.Colors.GREY_700)
self.download_button = ft.IconButton(
icon=ft.Icons.DOWNLOAD,
on_click=self._on_download_button_click,
)
self.details_panel = ft.Column(
[
self.req_id_text,
ft.Divider(),
ft.Text("Descriere Solicitare Client:", weight=ft.FontWeight.BOLD),
ft.Container(content=self.req_text_display, padding=10, bgcolor=ft.Colors.GREY_50, border_radius=5),
ft.Divider(),
ft.Row([self.status_dropdown, self.price_field]),
ft.FilledButton(
"Salvează Modificări Status/Preț",
icon=ft.Icons.SAVE,
on_click=self._save_metadata
),
ft.Divider(),
ft.Text("Comunicare istoric:", weight=ft.FontWeight.BOLD),
ft.Row([
self.comment_field,
ft.IconButton(ft.Icons.SEND_ROUNDED, on_click=self._add_comment, tooltip="Trimite răspuns")
]),
ft.Divider(),
ft.Text("Finalizare și Încărcare Document:", weight=ft.FontWeight.BOLD),
ft.Row(
[
ft.Row([
ft.FilledButton(
"Încarcă Document Final",
icon=ft.Icons.UPLOAD_FILE,
on_click=self._handle_file_upload,
bgcolor=ft.Colors.GREEN_700
),
self.doc_id_info,
self.download_button
]),
ft.FilledButton("Salveaza si trimite", on_click=self._on_file_saved)
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
)
],
visible=False,
expand=True,
scroll=ft.ScrollMode.AUTO
)
self._load_requests()
def _load_requests(self):
"""Preia toate solicitările de documente custom de la server."""
try:
response = requests.get(
f"{self.base_url}/documents/customs/requests",
headers={'Authorization': f'Bearer {self.token}'}
)
if response.status_code == 200:
self.all_requests = response.json()[::-1]
self._populate_list(self.all_requests)
except Exception as e:
print(f"Error loading requests: {e}")
def _populate_list(self, items):
self.requests_list_view.controls = []
if not items:
self.requests_list_view.controls.append(ft.Text("Nicio solicitare găsită.", italic=True))
else:
for req in items:
self.requests_list_view.controls.append(
ft.Container(
content=ft.Column([
ft.Text(f"Solicitare #{req['id']}", weight=ft.FontWeight.BOLD),
ft.Text(f"Status: {DocumentsStatus.get_label(req['status'])}", size=12),
ft.Text(f"Client ID: {req['client_id']}", size=11, color=ft.Colors.GREY_600),
], spacing=2),
padding=15,
border_radius=10,
bgcolor=ft.Colors.WHITE,
border=ft.Border.all(1, ft.Colors.GREY_300),
ink=True,
on_click=lambda e, r=req: self._show_details(r)
)
)
self.page.update()
def _show_details(self, req_summary): # Renamed from _show_details to _show_details_from_summary
"""Preia datele proaspete de la server pentru solicitarea selectată."""
try:
response = requests.get(
f"{self.base_url}/documents/customs/requests/{req_summary['id']}",
headers={'Authorization': f'Bearer {self.token}'}
)
if response.status_code == 200:
req = response.json()
self.current_selected_request = req
self.req_id_text.value = f"Procesare Solicitare #{req['id']}"
self.req_text_display.value = req['request_text']
self.status_dropdown.value = req['status']
self.price_field.value = str(req['price']) if req['price'] is not None else ""
self.doc_id_info.value = f"ID Document asociat: {req['document_id']}" if req['document_id'] else "Document Final: Niciunul"
self.details_panel.visible = True
self.page.update()
except Exception as e:
print(f"Eroare la preluarea detaliilor solicitării: {e}")
def _apply_filters(self):
"""Aplică filtrele de căutare și status și actualizează lista."""
query = self.search_bar.value.lower().strip()
selected_status = self.status_filter_dropdown.value
filtered = []
for r in self.all_requests:
matches_query = query in r['request_text'].lower() or query in str(r['id']).lower()
matches_status = (selected_status == "all" or r['status'] == selected_status)
if matches_query and matches_status:
filtered.append(r)
self._populate_list(filtered)
def _on_search_change(self, e):
"""Declanșează filtrarea la schimbarea textului de căutare."""
self._apply_filters()
def _on_status_filter_change(self, e):
"""Declanșează filtrarea la schimbarea statusului selectat."""
self.current_selected_request = None # Clear details when filter changes
self.details_panel.visible = False
self._apply_filters()
def _save_metadata(self, e):
"""Salvează prețul și statusul solicitării."""
if not self.current_selected_request: return
price_val = self.price_field.value.strip() if self.price_field.value else ""
price = None
if price_val:
try:
price = float(price_val.replace(',', '.'))
except ValueError:
self.page.show_dialog(ft.SnackBar(ft.Text("Prețul trebuie să fie un număr!")))
return
payload = { # Removed from here
"status": self.status_dropdown.value,
"price": price,
"expert_id": self.user_id
}
# Actualizăm datele și, în caz de succes, trimitem notificarea
if self._update_request_api(payload):
try:
# Preluăm email-ul clientului pentru a-l notifica
client_id = self.current_selected_request.get('client_id')
user_resp = requests.get(
f"{self.base_url}/users/{client_id}",
headers={'Authorization': f'Bearer {self.token}'}
)
if user_resp.status_code == 200:
client_email = user_resp.json().get('email')
status_label = DocumentsStatus.get_label(payload['status'])
price_info = f"Preț stabilit: {price} Lei." if price is not None else "Prețul va fi stabilit după analiză."
subject = f"Actualizare status solicitare #{self.current_selected_request['id']}"
body = (
f"Bună ziua,\n\n"
f"Vă informăm că statusul solicitării dumneavoastră #{self.current_selected_request['id']} "
f"a fost actualizat la: {status_label}.\n"
f"{price_info}\n\n"
f"Vă mulțumim,\nEchipa JuridicBloc"
)
send_gmail(to_email=client_email, subject=subject, body=body)
except Exception as mail_err:
print(f"Eroare la trimiterea notificării email: {mail_err}")
def _add_comment(self, e):
"""Adaugă un comentariu în istoricul conversației solicitării."""
if not self.current_selected_request or not self.comment_field.value.strip(): return
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
comment = self.comment_field.value.strip()
updated_text = f"{self.current_selected_request['request_text']}\n\n--- Răspuns Expert ({timestamp}):\n{comment}"
payload = {
"request_text": updated_text,
"expert_id": self.user_id
}
self._update_request_api(payload)
self.comment_field.value = ""
def _update_request_api(self, payload):
try:
req_id = self.current_selected_request['id']
response = requests.put(
f"{self.base_url}/documents/customs/requests/update/{req_id}",
json=payload,
headers={'Authorization': f'Bearer {self.token}'}
)
if response.status_code == 200:
self.page.show_dialog(ft.SnackBar(ft.Text("Modificări salvate cu succes.")))
self.current_selected_request.update(payload)
self._load_requests()
self._show_details(self.current_selected_request)
client_id = self.current_selected_request.get('client_id')
user_resp = requests.get(
f"{self.base_url}/users/{client_id}",
headers={'Authorization': f'Bearer {self.token}'}
)
if user_resp.status_code == 200:
subject = f"Actualizare status solicitare #{self.current_selected_request['id']}"
client_email = user_resp.json().get('email')
body = (
f"Bună ziua,\n\n"
f"Vă informăm că ati primit un raspuns solicitării dumneavoastră #{self.current_selected_request['id']} "
f"Vă mulțumim,\nEchipa JuridicBloc"
)
send_gmail(to_email=client_email, subject=subject, body=body)
return True
except Exception as e:
print(f"Update failed: {e}")
return False
async def _handle_file_upload(self, e: ft.Event[ft.Button]):
print('File uploaded')
try:
state.file_picker = ft.FilePicker()
files = await state.file_picker.pick_files(allow_multiple=False)
print("Picked file:", files)
state.picked_files = files
uploaded_file_name = f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{state.picked_files[0].name}"
await state.file_picker.upload(
files=[
ft.FilePickerUploadFile(
name=file.name,
upload_url=self.page.get_upload_url(uploaded_file_name, 60),
)
for file in state.picked_files
]
)
self.documenet_title = uploaded_file_name
self.doc_id_info.value = f"ID Document asociat: {self.documenet_title}"
return f'{uploaded_file_name}'
except Exception as e:
print(e)
def _on_file_saved(self, e):
reg_resp = requests.post(
f"{self.base_url}/documents/customs/add",
json={"name": f"Document Final Solicitare #{self.current_selected_request['id']}", "path": self.documenet_title},
headers={'Authorization': f'Bearer {self.token}'}
)
if reg_resp.status_code == 201:
doc_id = reg_resp.json().get('id')
# 3. Legare document de solicitare și marcare ca finalizat
self._update_request_api({
"document_id": doc_id,
"status": DocumentsStatus.COMPLETED,
"expert_id": self.user_id,
})
async def _on_download_button_click(self, e):
"""Handles the download of the final document."""
if not self.current_selected_request or not self.current_selected_request.get('document_id'):
self.page.show_dialog(ft.SnackBar(ft.Text("Nu există un document final asociat acestei solicitări."), open=True))
self.page.update()
return
document_id = self.current_selected_request['document_id']
try:
# Fetch document details to get the path
response = requests.get(
f"{self.base_url}/documents/customs/{document_id}",
headers={'Authorization': f'Bearer {self.token}'}
)
if response.status_code == 200:
document_data = response.json()
document_path = document_data.get('path')
if document_path:
download_url = f"{self.base_url}/documents/download?path={document_path}&token={self.token}"
await self.page.launch_url(download_url)
else:
self.page.show_dialog(ft.SnackBar(ft.Text("Calea documentului nu a putut fi găsită."), open=True))
else:
self.page.show_dialog(ft.SnackBar(ft.Text(f"Eroare la preluarea detaliilor documentului: {response.status_code}"), open=True))
except requests.exceptions.RequestException as err:
self.page.show_dialog(ft.SnackBar(ft.Text(f"Eroare de rețea la descărcarea documentului: {err}"), open=True))
except Exception as ex:
self.page.show_dialog(ft.SnackBar(ft.Text(f"A apărut o eroare neașteptată: {ex}"), open=True))
self.page.update()
def build(self):
return ft.Container(
content=ft.Row(
[
ft.Column(
[
ft.Row(
[
#self.search_bar,
self.status_filter_dropdown,
]
),
self.requests_list_view
],
width=400
),
ft.VerticalDivider(width=1),
ft.Container(
content=self.details_panel,
expand=True,
padding=20,
bgcolor=ft.Colors.WHITE,
border_radius=10
)
],
expand=True
),
expand=True,
bgcolor=ft.Colors.GREY_100,
padding=10
)