diff --git a/transportmanager/client/pages/archive_page.py b/transportmanager/client/pages/archive_page.py index 1554263..3117f5f 100644 --- a/transportmanager/client/pages/archive_page.py +++ b/transportmanager/client/pages/archive_page.py @@ -66,6 +66,9 @@ class ArchivePage: def view_order(self, order): #print(order) user_id = self.page.session.get("user_id") + user = self.get_user() + if user['user_role'] == 'company_user': + user_id = user['company_id'] pdf_name = f'order_{user_id}_{order['order_number']}.pdf' #print(pdf_name) view_page = ViewPage(self.page, pdf_name, self.order_page, self.dashboard, order['id']) @@ -118,6 +121,12 @@ class ArchivePage: id = self.page.session.get("user_id") response = requests.get(f"{API_BASE_URL}/company_user/access/{id}", headers=headers) return True if response.json()['orders_out'] == 1 else False + + def get_user(self): + token = self.page.client_storage.get("token") + headers = {"Authorization": f"Bearer {token}"} + response = requests.get(f"{API_BASE_URL}/profile/", headers=headers, timeout=10) + return response.json() def build(self): self.refresh() diff --git a/transportmanager/client/pages/dashboard_page.py b/transportmanager/client/pages/dashboard_page.py index 6494d1b..410e442 100644 --- a/transportmanager/client/pages/dashboard_page.py +++ b/transportmanager/client/pages/dashboard_page.py @@ -5,7 +5,7 @@ from pages.clients_page import ClientsPage from pages.transporters_page import TransportersPage from pages.destinations_page import DestinationsPage from pages.orders_page import OrdersPage -from pages.report_page import ReportPage +from pages.reports_page import ReportsPage from pages.profile_page import ProfilePage from pages.subscription_page import Subscription from datetime import datetime @@ -356,7 +356,7 @@ class DashboardPage: orders = OrdersPage(self.page, self) self.placeholder.content = orders.build() elif index == 5: - reports = ReportPage(self.page, self) + reports = ReportsPage(self.page, self) self.placeholder.content = reports.build() elif index == 6: profile = ProfilePage(self.page, self) diff --git a/transportmanager/client/pages/orders_edit_page.py b/transportmanager/client/pages/orders_edit_page.py index 2ed1c97..316ce45 100644 --- a/transportmanager/client/pages/orders_edit_page.py +++ b/transportmanager/client/pages/orders_edit_page.py @@ -234,6 +234,10 @@ class OrdersEditPage: ), value = self.order['order_number'] ) + self.order_in_number = ft.TextField( + label="Order number", + value=self.order['order_in_number'] + ) self.error_message = ft.Text(color = ft.Colors.RED) @@ -326,6 +330,31 @@ class OrdersEditPage: self.unloading_query = addresses self.unloading.controls = self.create_unloading_list(addresses, self.on_delete_unloading_address_btn_click) + self.currency = ft.Dropdown( + editable=True, + label="Currency", + options=self.get_currency(), + value=self.order['currency'], + ) + + def get_currency(self): + token = self.page.client_storage.get("token") + headers = {"Authorization": f"Bearer {token}"} + response = requests.get(f"{API_BASE_URL}/currency/", headers=headers) + currency_list = response.json() if response.status_code == 200 else [] + + options = [] + for currency in currency_list: + options.append( + ft.DropdownOption( + key=currency['name'], + content=ft.Text( + value=currency['name'], + ), + ) + ) + return options + def get_order(self): try: token = self.page.client_storage.get("token") @@ -883,6 +912,7 @@ class OrdersEditPage: saved_data = { 'order_number': self.order_number.value, + 'order_in_number': self.order_in_number.value, 'client_id': self.selected_client_id, 'transporter_id': self.selected_transporter_id, 'products_description': self.product_description.value, @@ -893,7 +923,8 @@ class OrdersEditPage: 'received_price': self.received_price.value, 'paid_price': self.paid_price.value, 'loading_addresses': loading_addresses, - 'unloading_addresses': unloading_addresses + 'unloading_addresses': unloading_addresses, + 'currency': self.currency.value } #print(saved_data) if self.order_number.value == None or len(self.order_number.value)==0: @@ -1030,23 +1061,29 @@ class OrdersEditPage: [ ft.Row( [ - ft.Column( - [ - ft.Text('Edit Order', size=24, weight=ft.FontWeight.BOLD), - ft.Row( - [ - ft.Text("Number", size=18, weight=ft.FontWeight.BOLD), - self.order_number - ] - ) - ], - alignment=ft.MainAxisAlignment.START - ), + ft.Text('Edit Order', size=24, weight=ft.FontWeight.BOLD), ft.ElevatedButton("Archive", on_click=self.on_archive_btn_click, width=150) ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, vertical_alignment=ft.CrossAxisAlignment.START ), + ft.Row( + [ + ft.Row( + [ + ft.Text("Order Out Number", size=18, weight=ft.FontWeight.BOLD), + self.order_number + ] + ), + ft.Row( + [ + ft.Text("Order In Number", size=18, weight=ft.FontWeight.BOLD), + self.order_in_number + ] + ) + ], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN, + ), ft.Row( [ ft.Column( @@ -1151,7 +1188,8 @@ class OrdersEditPage: [ ft.Row( [ - ft.Text("Price", size=18, weight=ft.FontWeight.BOLD) + ft.Text("Price", size=18, weight=ft.FontWeight.BOLD), + self.currency ], alignment=ft.MainAxisAlignment.START ), diff --git a/transportmanager/client/pages/orders_in_page.py b/transportmanager/client/pages/orders_in_page.py index 70a85d0..ae2ffc2 100644 --- a/transportmanager/client/pages/orders_in_page.py +++ b/transportmanager/client/pages/orders_in_page.py @@ -260,6 +260,31 @@ class OrdersInPage: ) self.filename = ft.Text() + + self.currency = ft.Dropdown( + editable=True, + label="Currency", + options=self.get_currency(), + value="EURO", + ) + + def get_currency(self): + token = self.page.client_storage.get("token") + headers = {"Authorization": f"Bearer {token}"} + response = requests.get(f"{API_BASE_URL}/currency/", headers=headers) + currency_list = response.json() if response.status_code == 200 else [] + + options = [] + for currency in currency_list: + options.append( + ft.DropdownOption( + key=currency['name'], + content=ft.Text( + value=currency['name'], + ), + ) + ) + return options def _open_date_picker(self, picker): # Works on both newer and older Flet @@ -817,6 +842,7 @@ class OrdersInPage: 'unloading_addresses': unloading_addresses, 'file':self.filename.value, 'expenses': self.expenses.value, + 'currency': self.currency.value } #print(saved_data) if self.order_number.value == None or len(self.order_number.value)==0: @@ -1028,7 +1054,8 @@ class OrdersInPage: [ ft.Row( [ - ft.Text("Price / Expenses", size=18, weight=ft.FontWeight.BOLD) + ft.Text("Price / Expenses", size=18, weight=ft.FontWeight.BOLD), + self.currency ], alignment=ft.MainAxisAlignment.START ), diff --git a/transportmanager/client/pages/orders_out_page.py b/transportmanager/client/pages/orders_out_page.py index 2a9a103..fdfe845 100644 --- a/transportmanager/client/pages/orders_out_page.py +++ b/transportmanager/client/pages/orders_out_page.py @@ -213,6 +213,9 @@ class OrdersOutPage: ), value=number ) + self.order_in_number = ft.TextField( + label="Order number" + ) self.error_message = ft.Text(color = ft.Colors.RED) @@ -233,6 +236,30 @@ class OrdersOutPage: replacement_string="" ), ) + self.currency = ft.Dropdown( + editable=True, + label="Currency", + options=self.get_currency(), + value="EURO", + ) + + def get_currency(self): + token = self.page.client_storage.get("token") + headers = {"Authorization": f"Bearer {token}"} + response = requests.get(f"{API_BASE_URL}/currency/", headers=headers) + currency_list = response.json() if response.status_code == 200 else [] + + options = [] + for currency in currency_list: + options.append( + ft.DropdownOption( + key=currency['name'], + content=ft.Text( + value=currency['name'], + ), + ) + ) + return options def load_orders(self): try: @@ -739,6 +766,7 @@ class OrdersOutPage: saved_data = { 'order_number': self.order_number.value, + 'order_in_number': self.order_in_number.value, 'client_id': self.selected_client_id, 'transporter_id': self.selected_transporter_id, 'products_description': self.product_description.value, @@ -749,7 +777,8 @@ class OrdersOutPage: 'received_price': self.received_price.value, 'paid_price': self.paid_price.value, 'loading_addresses': loading_addresses, - 'unloading_addresses': unloading_addresses + 'unloading_addresses': unloading_addresses, + 'currency':self.currency.value } #print(saved_data) if self.order_number.value == None or len(str(self.order_number.value))==0: @@ -897,23 +926,29 @@ class OrdersOutPage: [ ft.Row( [ - ft.Column( - [ - ft.Text('Create Order Out', size=24, weight=ft.FontWeight.BOLD), - ft.Row( - [ - ft.Text("Number", size=18, weight=ft.FontWeight.BOLD), - self.order_number - ] - ) - ], - alignment=ft.MainAxisAlignment.START - ), + ft.Text('Create Order Out', size=24, weight=ft.FontWeight.BOLD), ft.ElevatedButton("Archive", on_click=self.on_archive_btn_click, width=150) ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, vertical_alignment=ft.CrossAxisAlignment.START ), + ft.Row( + [ + ft.Row( + [ + ft.Text("Order Out Number", size=18, weight=ft.FontWeight.BOLD), + self.order_number + ] + ), + ft.Row( + [ + ft.Text("Order In Number", size=18, weight=ft.FontWeight.BOLD), + self.order_in_number + ] + ) + ], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN, + ), ft.Row( [ ft.Column( @@ -1018,14 +1053,17 @@ class OrdersOutPage: [ ft.Row( [ - ft.Text("Price", size=18, weight=ft.FontWeight.BOLD) + ft.Text("Price", size=18, weight=ft.FontWeight.BOLD), + self.currency ], - alignment=ft.MainAxisAlignment.START + expand = True, + alignment=ft.MainAxisAlignment.SPACE_BETWEEN ), self.received_price, self.paid_price ], - expand=2.5 + expand=2.5, + alignment=ft.MainAxisAlignment.END ) ], expand=True, diff --git a/transportmanager/client/pages/report_order_in_page.py b/transportmanager/client/pages/report_order_in_page.py new file mode 100644 index 0000000..85a70c7 --- /dev/null +++ b/transportmanager/client/pages/report_order_in_page.py @@ -0,0 +1,572 @@ +ISO = {"EURO": "EUR", "USD": "USD", "CHF": "CHF", "GBP": "GBP"} + +def fetch_rates_for_base(base_ui: str) -> dict: + """ + Returns dict like {"EURO": 1.0, "USD": 1.0712, "CHF": 0.9573, "GBP": 0.8451} + meaning: 1 BASE = X other currency. + """ + base_iso = ISO[base_ui] + symbols = [c for c in ["EUR","USD","CHF","GBP"] if c != base_iso] + url = "https://api.frankfurter.app/latest" + params = {"from": base_iso, "to": ",".join(symbols)} + r = requests.get(url, params=params, timeout=6) + r.raise_for_status() + data = r.json() + rates = {k: float(v) for k, v in data["rates"].items()} + rates[base_iso] = 1.0 + return {ui: rates[iso] for ui, iso in ISO.items()} + +import flet as ft +import requests +from datetime import datetime +from config import API_BASE_URL + +class ReportOrderInPage: + def __init__(self, page: ft.Page, dashboard): + self.page = page + self.dashboard = dashboard + self.start_date = ft.Text() + self.end_date = ft.Text() + # self.client_filter = ft.TextField(label="Client", expand=True) + # self.transporter_filter = ft.TextField(label="Transporter", expand=True) + self.status_text = ft.Text("") + self.results_text = ft.Text("") + self.rows = [] + self.rows_copy = [] + self.total = ft.Text("Total: ", weight=ft.FontWeight.BOLD) + self.currency_list = [] + self.convert_currency_placeholder = ft.Container() + self.convert_currency = ft.Button("Convert Currency", on_click=self.on_convert_curency_btn_click) + self.data_table = ft.DataTable( + columns=[ + ft.DataColumn(label=ft.Text("Order #")), + ft.DataColumn(label=ft.Text("Client")), + ft.DataColumn(label=ft.Text("Date")), + ft.DataColumn(label=ft.Text("Expenses")), + ft.DataColumn(label=ft.Text("Received")), + ft.DataColumn(label=ft.Text("Profit")), + ft.DataColumn(label=ft.Text("Currency")), + ], + rows=[], + border=ft.border.all(1, ft.Colors.GREY_300), + expand=True + ) + + self.all_clients = [] + self.all_transporters = [] + self.create_table_rows_data() + + self.clients_filter = ft.Dropdown( + options=[ + ft.dropdown.Option(text = client['name'], key=client['name']) for client in self.all_clients + ], + width=250, + label="Clients", + hint_text= "Select client", + on_change= self.filter_by_client + ) + self.clients_filter_placeholder = ft.Container(content=self.clients_filter) + + self.convert_courrency_dialog_placeholder = ft.Column() + self.convert_currency_choice = ft.RadioGroup( + content=ft.Row( + [ + ft.Radio(value="USD", label="USD"), + ft.Radio(value="EURO", label="EURO"), + ft.Radio(value="CHF", label="CHF"), + ft.Radio(value="GBP", label="GBP"), + ] + ), + on_change=self.radiogroup_changed, + ) + self.convert_courency_dialog = ft.AlertDialog( + title=ft.Text("Select Currency"), + content=ft.Column( + [ + self.convert_currency_choice, + self.convert_courrency_dialog_placeholder + ], + width=400, + height=300 + ), + actions=[ + ft.TextButton("Cancel", on_click=self.on_cancel_btn_click), + ft.Button("Confirm", on_click=self.on_confirm_btn_click) + ] + ) + self.euro = ft.TextField( + label="EURO", + input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="") + ) + self.usd = ft.TextField( + label="USD", + input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="") + ) + self.chf = ft.TextField( + label="CHF", + input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="") + ) + self.gbp = ft.TextField( + label="GBP", + input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="") + ) + + def radiogroup_changed(self, e): + self.convert_courrency_dialog_placeholder.controls.clear() + self.convert_courrency_dialog_placeholder.controls.append( + ft.Text(f"Sets the currency exchange rates in relation to {self.convert_currency_choice.value}"), + ) + if self.convert_currency_choice.value == 'USD': + self.convert_courrency_dialog_placeholder.controls.append( + self.euro, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.chf, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.gbp, + ) + if self.convert_currency_choice.value == 'EURO': + self.convert_courrency_dialog_placeholder.controls.append( + self.usd, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.chf, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.gbp, + ) + if self.convert_currency_choice.value == 'CHF': + self.convert_courrency_dialog_placeholder.controls.append( + self.usd, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.euro, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.gbp, + ) + if self.convert_currency_choice.value == 'GBP': + self.convert_courrency_dialog_placeholder.controls.append( + self.usd, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.euro, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.chf, + ) + try: + fx = fetch_rates_for_base(self.convert_currency_choice.value) + self.euro.value = f"{fx['EURO']:.4f}" + self.usd.value = f"{fx['USD']:.4f}" + self.chf.value = f"{fx['CHF']:.4f}" + self.gbp.value = f"{fx['GBP']:.4f}" + except Exception: + pass + self.convert_courrency_dialog_placeholder.update() + + def on_convert_curency_btn_click(self, e): + self.page.open(self.convert_courency_dialog) + base = self.convert_currency_choice.value or "EURO" + try: + fx = fetch_rates_for_base(base) + self.euro.value = f"{fx['EURO']:.4f}" + self.usd.value = f"{fx['USD']:.4f}" + self.chf.value = f"{fx['CHF']:.4f}" + self.gbp.value = f"{fx['GBP']:.4f}" + self.convert_courrency_dialog_placeholder.update() + except Exception as ex: + self.page.snack_bar = ft.SnackBar(ft.Text(f"FX fetch failed: {ex}")) + self.page.snack_bar.open = True + self.page.update() + + def on_cancel_btn_click(self, e): + self.page.close(self.convert_courency_dialog) + + def on_confirm_btn_click(self, e): + self.page.close(self.convert_courency_dialog) + # Build rates relative to the selected base currency. + # Example: if base is EURO, then rates['USD'] should be "1 EURO = X USD". + # To convert an amount from USD -> EURO we divide by rates['USD']. + base = self.convert_currency_choice.value + # Normalize and validate rate inputs + def _to_float(v): + try: + return float(v) + except Exception: + return None + + rates = { + 'EURO': _to_float(self.euro.value), + 'USD': _to_float(self.usd.value), + 'CHF': _to_float(self.chf.value), + 'GBP': _to_float(self.gbp.value), + } + + # Helper to convert any amount to the selected base currency + def to_base(amount, currency): + try: + amt = float(amount) + except Exception: + return 0.0 + if currency == base: + return amt + rate = rates.get(currency) + # If the rate is missing or invalid, keep original (safer than crashing) + if not rate or rate == 0: + return amt + # Convert from currency -> base by dividing when rates are "1 base = rate[currency] currency" + return amt / rate + + # Rebuild table in base currency + self.rows_copy = list(self.rows) + total = 0.0 + self.data_table.rows.clear() + + for r in self.rows_copy: + # r structure: [order_number, client_name, transporter_name, order_date, paid, received, profit, currency] + original_currency = r[6] + + paid_base = to_base(r[3], original_currency) + received_base = to_base(r[4], original_currency) + profit_base = round(float(received_base) - float(paid_base), 2) + + # Update a display row (do not mutate self.rows source amounts) + row = ft.DataRow( + cells=[ + ft.DataCell(ft.Text(r[0])), + ft.DataCell(ft.Text(r[1])), + ft.DataCell(ft.Text(r[2])), + ft.DataCell(ft.Text(f"{paid_base:.2f}")), + ft.DataCell(ft.Text(f"{received_base:.2f}")), + ft.DataCell(ft.Text(f"{profit_base:.2f}")), + ft.DataCell(ft.Text(base)), + ], + ) + self.data_table.rows.append(row) + total += profit_base + + self.data_table.update() + self.total.value = f"Total: {total:.2f} {base}" + self.total.update() + + def get_orders(self): + try: + token = self.page.client_storage.get("token") + headers = {"Authorization": f"Bearer {token}"} + response = requests.get(f"{API_BASE_URL}/orders_in/list", headers=headers) + #print(response.text) + return response.json() if response.status_code == 200 else [] + except Exception as e: + print("Error loading orders:", e) + + def get_client(self, id): + try: + token = self.page.client_storage.get("token") + headers = {"Authorization": f"Bearer {token}"} + response = requests.get(f"{API_BASE_URL}/clients/{id}", headers=headers) + if response.json() not in self.all_clients: + self.all_clients.append(response.json()) + return response.json() if response.status_code == 200 else None + except Exception as e: + print("Error loading clients:", e) + + def fetch_report(self, e): + total = 0 + data = self.search_input.value + self.rows_copy = self.rows + self.data_table.rows.clear() + buffer = [] + for r in self.rows_copy: + if data in r[1]: + row = ft.DataRow( + cells=[ + ft.DataCell(ft.Text(r[0])), + ft.DataCell(ft.Text(r[1])), + ft.DataCell(ft.Text(r[2])), + ft.DataCell(ft.Text(r[3])), + ft.DataCell(ft.Text(r[4])), + ft.DataCell(ft.Text(r[5])), + ft.DataCell(ft.Text(r[6])), + ], + ) + self.data_table.rows.append(row) + buffer.append(r) + total += r[5] + self.rows_copy = buffer + self.data_table.update() + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" + self.total.update() + + def create_table_rows_data(self): + all_orders = self.get_orders() + print(all_orders) + total = 0 + for order in all_orders: + if order['currency'] not in self.currency_list: + self.currency_list.append(order['currency']) + if len(self.currency_list) > 1: + self.convert_currency_placeholder.content=self.convert_currency + order_number = order['order_number'] + client_name = self.get_client(order['client_id'])['name'] + order_date = order['created_at'].split("T")[0] + paid = order['expenses'] + received = order['received_price'] + currency = order['currency'] + try: + profit = round(float(received) - float(paid), 2) + except: + profit = 0.00 + + row = ft.DataRow( + cells=[ + ft.DataCell(ft.Text(order_number)), + ft.DataCell(ft.Text(client_name)), + ft.DataCell(ft.Text(order_date)), + ft.DataCell(ft.Text(paid)), + ft.DataCell(ft.Text(received)), + ft.DataCell(ft.Text(profit)), + ft.DataCell(ft.Text(currency)), + ], + ) + row_data = [order_number, client_name, order_date, paid, received, profit, currency] + self.rows.append(row_data) + self.data_table.rows.append(row) + total += profit + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" + self.rows_copy = self.rows + + def on_reset_btn_click(self, e): + # --- Recreate Clients dropdown to avoid sticky selection text on Flet 0.28.3 --- + try: + client_options = list(self.clients_filter.options) + except Exception: + client_options = [] + new_clients_dd = ft.Dropdown( + options=client_options, + width=250, + label="Clients", + hint_text="Select client", + on_change=self.filter_by_client, + value=None, + ) + self.clients_filter = new_clients_dd + self.clients_filter_placeholder.content = self.clients_filter + self.clients_filter_placeholder.update() + + self.page.update() + self.rows_copy = list(self.rows) + total = 0 + self.data_table.rows.clear() + for r in self.rows_copy: + row = ft.DataRow( + cells=[ + ft.DataCell(ft.Text(r[0])), + ft.DataCell(ft.Text(r[1])), + ft.DataCell(ft.Text(r[2])), + ft.DataCell(ft.Text(r[3])), + ft.DataCell(ft.Text(r[4])), + ft.DataCell(ft.Text(r[5])), + ft.DataCell(ft.Text(r[6])), + ], + ) + self.data_table.rows.append(row) + total += r[5] + self.data_table.update() + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" + self.total.update() + self.start_date.value = "" + self.start_date.update() + self.end_date.value = "" + self.end_date.update() + + def on_start_date_click(self, e): + self.start_date.value = e.control.value.strftime('%Y-%m-%d') + self.start_date.update() + total = 0 + self.data_table.rows.clear() + buffer = [] + data = datetime.strptime(self.start_date.value, '%Y-%m-%d') + for r in self.rows_copy: + obj_date = datetime.strptime(r[3], '%Y-%m-%d') + if data <= obj_date: + row = ft.DataRow( + cells=[ + ft.DataCell(ft.Text(r[0])), + ft.DataCell(ft.Text(r[1])), + ft.DataCell(ft.Text(r[2])), + ft.DataCell(ft.Text(r[3])), + ft.DataCell(ft.Text(r[4])), + ft.DataCell(ft.Text(r[5])), + ft.DataCell(ft.Text(r[6])), + ], + ) + self.data_table.rows.append(row) + buffer.append(r) + total += r[5] + self.rows_copy = buffer + self.data_table.update() + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" + self.total.update() + + def on_end_date_click(self, e): + self.end_date.value = e.control.value.strftime('%Y-%m-%d') + self.end_date.update() + total = 0 + self.data_table.rows.clear() + buffer = [] + data = datetime.strptime(self.end_date.value, '%Y-%m-%d') + for r in self.rows_copy: + obj_date = datetime.strptime(r[3], '%Y-%m-%d') + if data >= obj_date: + row = ft.DataRow( + cells=[ + ft.DataCell(ft.Text(r[0])), + ft.DataCell(ft.Text(r[1])), + ft.DataCell(ft.Text(r[2])), + ft.DataCell(ft.Text(r[3])), + ft.DataCell(ft.Text(r[4])), + ft.DataCell(ft.Text(r[5])), + ft.DataCell(ft.Text(r[6])), + ], + ) + self.data_table.rows.append(row) + buffer.append(r) + total += r[5] + self.rows_copy = buffer + self.data_table.update() + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" + self.total.update() + + def filter_by_client(self, e): + total = 0 + self.data_table.rows.clear() + buffer = [] + for r in self.rows_copy: + #print(r[1]) + #print(self.clients_filter.value) + if r[1] == self.clients_filter.value: + row = ft.DataRow( + cells=[ + ft.DataCell(ft.Text(r[0])), + ft.DataCell(ft.Text(r[1])), + ft.DataCell(ft.Text(r[2])), + ft.DataCell(ft.Text(r[3])), + ft.DataCell(ft.Text(r[4])), + ft.DataCell(ft.Text(r[5])), + ft.DataCell(ft.Text(r[6])), + ], + ) + self.data_table.rows.append(row) + buffer.append(r) + total += r[5] + self.rows_copy = buffer + self.data_table.update() + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" + self.total.update() + + def get_client_access(self): + token = self.page.client_storage.get("token") + headers = {"Authorization": f"Bearer {token}"} + response = requests.get(f"{API_BASE_URL}/profile/", headers=headers, timeout=10) + user = response.json() + if user['user_role'] == 'user': + return True + else: + id = self.page.session.get("user_id") + response = requests.get(f"{API_BASE_URL}/company_user/access/{id}", headers=headers) + return True if response.json()['report'] == 1 else False + + def build(self): + return ft.Container( + content=ft.Column( + [ + ft.Row( + [ + ft.Text("Report Orders In", size=24, weight=ft.FontWeight.BOLD), + self.total + ], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN + ), + ft.Row( + [ + ft.Row( + [ + self.start_date, + ft.ElevatedButton( + "Start Date", + on_click=lambda _: self.page.open(ft.DatePicker( + first_date=datetime(year=2000, month=10, day=1), + last_date=datetime(year=2025, month=10, day=1), + on_change=self.on_start_date_click, + )), + width=120, + icon=ft.Icons.CALENDAR_MONTH + ), + self.end_date, + ft.ElevatedButton( + "End Date", + on_click=lambda _: self.page.open(ft.DatePicker( + first_date=datetime(year=2000, month=10, day=1), + last_date=datetime(year=2025, month=10, day=1), + on_change=self.on_end_date_click, + )), + width=120, + icon=ft.Icons.CALENDAR_MONTH + ), + ft.Text(), + self.clients_filter_placeholder, + ft.ElevatedButton("Reset", on_click=self.on_reset_btn_click, width=120), + ] + ), + self.convert_currency_placeholder + ], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN + ), + ft.Column( + [ + ft.Row( + [ + self.data_table, + self.status_text, + ], + expand=True, + ) + ], + alignment=ft.MainAxisAlignment.START, + scroll=ft.ScrollMode.ADAPTIVE, + expand=True, + ) + ], + expand=True, + alignment=ft.MainAxisAlignment.START, + ), + expand=True + ) if self.get_client_access() else ft.Container( + content=ft.Column( + [ + ft.Row( + [ + ft.Text("Reports Order In", size=24, weight=ft.FontWeight.BOLD), + ], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN + ), + ft.Row( + [ + ft.Text( + "You do not have access to this page content", + size=24, + weight=ft.FontWeight.BOLD, + color=ft.Colors.RED + ) + ], + alignment=ft.MainAxisAlignment.CENTER + ), + ft.Text("") + ], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN, + expand=True + ), + expand=True + ) \ No newline at end of file diff --git a/transportmanager/client/pages/report_page.py b/transportmanager/client/pages/report_order_out_page.py similarity index 54% rename from transportmanager/client/pages/report_page.py rename to transportmanager/client/pages/report_order_out_page.py index fa2d583..18df5f0 100644 --- a/transportmanager/client/pages/report_page.py +++ b/transportmanager/client/pages/report_order_out_page.py @@ -1,9 +1,27 @@ +ISO = {"EURO": "EUR", "USD": "USD", "CHF": "CHF", "GBP": "GBP"} + +def fetch_rates_for_base(base_ui: str) -> dict: + """ + Returns dict like {"EURO": 1.0, "USD": 1.0712, "CHF": 0.9573, "GBP": 0.8451} + meaning: 1 BASE = X other currency. + """ + base_iso = ISO[base_ui] + symbols = [c for c in ["EUR","USD","CHF","GBP"] if c != base_iso] + url = "https://api.frankfurter.app/latest" + params = {"from": base_iso, "to": ",".join(symbols)} + r = requests.get(url, params=params, timeout=6) + r.raise_for_status() + data = r.json() + rates = {k: float(v) for k, v in data["rates"].items()} + rates[base_iso] = 1.0 + return {ui: rates[iso] for ui, iso in ISO.items()} + import flet as ft import requests from datetime import datetime from config import API_BASE_URL -class ReportPage: +class ReportOrderOutPage: def __init__(self, page: ft.Page, dashboard): self.page = page self.dashboard = dashboard @@ -16,16 +34,19 @@ class ReportPage: self.rows = [] self.rows_copy = [] self.total = ft.Text("Total: ", weight=ft.FontWeight.BOLD) - + self.currency_list = [] + self.convert_currency_placeholder = ft.Container() + self.convert_currency = ft.Button("Convert Currency", on_click=self.on_convert_curency_btn_click) self.data_table = ft.DataTable( columns=[ ft.DataColumn(label=ft.Text("Order #")), ft.DataColumn(label=ft.Text("Client")), ft.DataColumn(label=ft.Text("Transporter")), ft.DataColumn(label=ft.Text("Date")), - ft.DataColumn(label=ft.Text("Paid (€)")), - ft.DataColumn(label=ft.Text("Received (€)")), - ft.DataColumn(label=ft.Text("Profit (€)")), + ft.DataColumn(label=ft.Text("Paid")), + ft.DataColumn(label=ft.Text("Received")), + ft.DataColumn(label=ft.Text("Profit")), + ft.DataColumn(label=ft.Text("Currency")), ], rows=[], border=ft.border.all(1, ft.Colors.GREY_300), @@ -58,6 +79,191 @@ class ReportPage: ) self.transporters_filter_placeholder = ft.Container(content=self.transporters_filter) + self.convert_courrency_dialog_placeholder = ft.Column() + self.convert_currency_choice = ft.RadioGroup( + content=ft.Row( + [ + ft.Radio(value="USD", label="USD"), + ft.Radio(value="EURO", label="EURO"), + ft.Radio(value="CHF", label="CHF"), + ft.Radio(value="GBP", label="GBP"), + ] + ), + on_change=self.radiogroup_changed, + ) + self.convert_courency_dialog = ft.AlertDialog( + title=ft.Text("Select Currency"), + content=ft.Column( + [ + self.convert_currency_choice, + self.convert_courrency_dialog_placeholder + ], + width=400, + height=300 + ), + actions=[ + ft.TextButton("Cancel", on_click=self.on_cancel_btn_click), + ft.Button("Confirm", on_click=self.on_confirm_btn_click) + ] + ) + self.euro = ft.TextField( + label="EURO", + input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="") + ) + self.usd = ft.TextField( + label="USD", + input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="") + ) + self.chf = ft.TextField( + label="CHF", + input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="") + ) + self.gbp = ft.TextField( + label="GBP", + input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="") + ) + + def radiogroup_changed(self, e): + self.convert_courrency_dialog_placeholder.controls.clear() + self.convert_courrency_dialog_placeholder.controls.append( + ft.Text(f"Sets the currency exchange rates in relation to {self.convert_currency_choice.value}"), + ) + if self.convert_currency_choice.value == 'USD': + self.convert_courrency_dialog_placeholder.controls.append( + self.euro, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.chf, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.gbp, + ) + if self.convert_currency_choice.value == 'EURO': + self.convert_courrency_dialog_placeholder.controls.append( + self.usd, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.chf, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.gbp, + ) + if self.convert_currency_choice.value == 'CHF': + self.convert_courrency_dialog_placeholder.controls.append( + self.usd, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.euro, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.gbp, + ) + if self.convert_currency_choice.value == 'GBP': + self.convert_courrency_dialog_placeholder.controls.append( + self.usd, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.euro, + ) + self.convert_courrency_dialog_placeholder.controls.append( + self.chf, + ) + try: + fx = fetch_rates_for_base(self.convert_currency_choice.value) + self.euro.value = f"{fx['EURO']:.4f}" + self.usd.value = f"{fx['USD']:.4f}" + self.chf.value = f"{fx['CHF']:.4f}" + self.gbp.value = f"{fx['GBP']:.4f}" + except Exception: + pass + self.convert_courrency_dialog_placeholder.update() + + def on_convert_curency_btn_click(self, e): + self.page.open(self.convert_courency_dialog) + base = self.convert_currency_choice.value or "EURO" + try: + fx = fetch_rates_for_base(base) + self.euro.value = f"{fx['EURO']:.4f}" + self.usd.value = f"{fx['USD']:.4f}" + self.chf.value = f"{fx['CHF']:.4f}" + self.gbp.value = f"{fx['GBP']:.4f}" + self.convert_courrency_dialog_placeholder.update() + except Exception as ex: + self.page.snack_bar = ft.SnackBar(ft.Text(f"FX fetch failed: {ex}")) + self.page.snack_bar.open = True + self.page.update() + + def on_cancel_btn_click(self, e): + self.page.close(self.convert_courency_dialog) + + def on_confirm_btn_click(self, e): + self.page.close(self.convert_courency_dialog) + # Build rates relative to the selected base currency. + # Example: if base is EURO, then rates['USD'] should be "1 EURO = X USD". + # To convert an amount from USD -> EURO we divide by rates['USD']. + base = self.convert_currency_choice.value + # Normalize and validate rate inputs + def _to_float(v): + try: + return float(v) + except Exception: + return None + + rates = { + 'EURO': _to_float(self.euro.value), + 'USD': _to_float(self.usd.value), + 'CHF': _to_float(self.chf.value), + 'GBP': _to_float(self.gbp.value), + } + + # Helper to convert any amount to the selected base currency + def to_base(amount, currency): + try: + amt = float(amount) + except Exception: + return 0.0 + if currency == base: + return amt + rate = rates.get(currency) + # If the rate is missing or invalid, keep original (safer than crashing) + if not rate or rate == 0: + return amt + # Convert from currency -> base by dividing when rates are "1 base = rate[currency] currency" + return amt / rate + + # Rebuild table in base currency + self.rows_copy = list(self.rows) + total = 0.0 + self.data_table.rows.clear() + + for r in self.rows_copy: + # r structure: [order_number, client_name, transporter_name, order_date, paid, received, profit, currency] + original_currency = r[7] + + paid_base = to_base(r[4], original_currency) + received_base = to_base(r[5], original_currency) + profit_base = round(float(received_base) - float(paid_base), 2) + + # Update a display row (do not mutate self.rows source amounts) + row = ft.DataRow( + cells=[ + ft.DataCell(ft.Text(r[0])), + ft.DataCell(ft.Text(r[1])), + ft.DataCell(ft.Text(r[2])), + ft.DataCell(ft.Text(r[3])), + ft.DataCell(ft.Text(f"{paid_base:.2f}")), + ft.DataCell(ft.Text(f"{received_base:.2f}")), + ft.DataCell(ft.Text(f"{profit_base:.2f}")), + ft.DataCell(ft.Text(base)), + ], + ) + self.data_table.rows.append(row) + total += profit_base + + self.data_table.update() + self.total.value = f"Total: {total:.2f} {base}" + self.total.update() + def get_orders(self): try: token = self.page.client_storage.get("token") @@ -106,6 +312,7 @@ class ReportPage: ft.DataCell(ft.Text(r[4])), ft.DataCell(ft.Text(r[5])), ft.DataCell(ft.Text(r[6])), + ft.DataCell(ft.Text(r[7])), ], ) self.data_table.rows.append(row) @@ -113,13 +320,17 @@ class ReportPage: total += r[6] self.rows_copy = buffer self.data_table.update() - self.total.value = f"Total: {total}" + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" self.total.update() def create_table_rows_data(self): all_orders = self.get_orders() total = 0 for order in all_orders: + if order['currency'] not in self.currency_list: + self.currency_list.append(order['currency']) + if len(self.currency_list) > 1: + self.convert_currency_placeholder.content=self.convert_currency # Skip non-active orders from reports #print(order.get('status')) if order.get('status') != 'active': @@ -130,6 +341,7 @@ class ReportPage: order_date = order['created_at'].split("T")[0] paid = order['paid_price'] received = order['received_price'] + currency = order['currency'] try: profit = round(float(received) - float(paid), 2) except: @@ -144,13 +356,14 @@ class ReportPage: ft.DataCell(ft.Text(paid)), ft.DataCell(ft.Text(received)), ft.DataCell(ft.Text(profit)), + ft.DataCell(ft.Text(currency)), ], ) - row_data = [order_number, client_name, transporter_name, order_date, paid, received, profit] + row_data = [order_number, client_name, transporter_name, order_date, paid, received, profit, currency] self.rows.append(row_data) self.data_table.rows.append(row) total += profit - self.total.value = f"Total: {total}" + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" self.rows_copy = self.rows def on_reset_btn_click(self, e): @@ -202,12 +415,13 @@ class ReportPage: ft.DataCell(ft.Text(r[4])), ft.DataCell(ft.Text(r[5])), ft.DataCell(ft.Text(r[6])), + ft.DataCell(ft.Text(r[7])), ], ) self.data_table.rows.append(row) total += r[6] self.data_table.update() - self.total.value = f"Total: {total}" + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" self.total.update() self.start_date.value = "" self.start_date.update() @@ -233,6 +447,7 @@ class ReportPage: ft.DataCell(ft.Text(r[4])), ft.DataCell(ft.Text(r[5])), ft.DataCell(ft.Text(r[6])), + ft.DataCell(ft.Text(r[7])), ], ) self.data_table.rows.append(row) @@ -240,7 +455,7 @@ class ReportPage: total += r[6] self.rows_copy = buffer self.data_table.update() - self.total.value = f"Total: {total}" + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" self.total.update() def on_end_date_click(self, e): @@ -262,6 +477,7 @@ class ReportPage: ft.DataCell(ft.Text(r[4])), ft.DataCell(ft.Text(r[5])), ft.DataCell(ft.Text(r[6])), + ft.DataCell(ft.Text(r[7])), ], ) self.data_table.rows.append(row) @@ -269,7 +485,7 @@ class ReportPage: total += r[6] self.rows_copy = buffer self.data_table.update() - self.total.value = f"Total: {total}" + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" self.total.update() def filter_by_client(self, e): @@ -289,6 +505,7 @@ class ReportPage: ft.DataCell(ft.Text(r[4])), ft.DataCell(ft.Text(r[5])), ft.DataCell(ft.Text(r[6])), + ft.DataCell(ft.Text(r[7])), ], ) self.data_table.rows.append(row) @@ -296,7 +513,7 @@ class ReportPage: total += r[6] self.rows_copy = buffer self.data_table.update() - self.total.value = f"Total: {total}" + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" self.total.update() def filter_by_transporter(self, e): @@ -316,6 +533,7 @@ class ReportPage: ft.DataCell(ft.Text(r[4])), ft.DataCell(ft.Text(r[5])), ft.DataCell(ft.Text(r[6])), + ft.DataCell(ft.Text(r[7])), ], ) self.data_table.rows.append(row) @@ -323,7 +541,7 @@ class ReportPage: total += r[6] self.rows_copy = buffer self.data_table.update() - self.total.value = f"Total: {total}" + self.total.value = f"Total: {total}" if len(self.currency_list)==1 else "" self.total.update() def get_client_access(self): @@ -344,41 +562,47 @@ class ReportPage: [ ft.Row( [ - ft.Text("Reports", size=24, weight=ft.FontWeight.BOLD), + ft.Text("Report Orders Out", size=24, weight=ft.FontWeight.BOLD), self.total ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN ), ft.Row( [ - self.start_date, - ft.ElevatedButton( - "Start Date", - on_click=lambda _: self.page.open(ft.DatePicker( - first_date=datetime(year=2000, month=10, day=1), - last_date=datetime(year=2025, month=10, day=1), - on_change=self.on_start_date_click, - )), - width=120, - icon=ft.Icons.CALENDAR_MONTH - ), - self.end_date, - ft.ElevatedButton( - "End Date", - on_click=lambda _: self.page.open(ft.DatePicker( - first_date=datetime(year=2000, month=10, day=1), - last_date=datetime(year=2025, month=10, day=1), - on_change=self.on_end_date_click, - )), - width=120, - icon=ft.Icons.CALENDAR_MONTH - ), - ft.Text(), - self.clients_filter_placeholder, - self.transporters_filter_placeholder, - ft.ElevatedButton("Reset", on_click=self.on_reset_btn_click, width=120), - ] - ), + ft.Row( + [ + self.start_date, + ft.ElevatedButton( + "Start Date", + on_click=lambda _: self.page.open(ft.DatePicker( + first_date=datetime(year=2000, month=10, day=1), + last_date=datetime(year=2025, month=10, day=1), + on_change=self.on_start_date_click, + )), + width=120, + icon=ft.Icons.CALENDAR_MONTH + ), + self.end_date, + ft.ElevatedButton( + "End Date", + on_click=lambda _: self.page.open(ft.DatePicker( + first_date=datetime(year=2000, month=10, day=1), + last_date=datetime(year=2025, month=10, day=1), + on_change=self.on_end_date_click, + )), + width=120, + icon=ft.Icons.CALENDAR_MONTH + ), + ft.Text(), + self.clients_filter_placeholder, + self.transporters_filter_placeholder, + ft.ElevatedButton("Reset", on_click=self.on_reset_btn_click, width=120), + ] + ), + self.convert_currency_placeholder + ], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN + ), ft.Column( [ ft.Row( @@ -403,7 +627,7 @@ class ReportPage: [ ft.Row( [ - ft.Text("Reports", size=24, weight=ft.FontWeight.BOLD), + ft.Text("Reports Order Out", size=24, weight=ft.FontWeight.BOLD), ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN ), diff --git a/transportmanager/client/pages/reports_page.py b/transportmanager/client/pages/reports_page.py new file mode 100644 index 0000000..179f9e1 --- /dev/null +++ b/transportmanager/client/pages/reports_page.py @@ -0,0 +1,110 @@ +import flet as ft +from pages.report_order_out_page import ReportOrderOutPage +from pages.report_order_in_page import ReportOrderInPage + +class ReportsPage: + def __init__(self, page: ft.Page, dashboard): + self.page = page + self.dashboard = dashboard + + def on_report_orders_in_btn_click(self, e): + order_in_page = ReportOrderInPage(self.page, self.dashboard) + self.dashboard.placeholder.content = order_in_page.build() + self.dashboard.placeholder.update() + + def on_report_orders_out_btn_click(self, e): + orders_out_page = ReportOrderOutPage(self.page, self.dashboard) + self.dashboard.placeholder.content = orders_out_page.build() + self.dashboard.placeholder.update() + + def build(self): + return ft.Container( + content=ft.Column( + [ + ft.Text("Reports", size=24, weight=ft.FontWeight.BOLD), + ft.Row( + [ + ft.Container( + content=ft.Column( + [ + ft.Icon(ft.Icons.ASSESSMENT, size=150), + ft.Container( + ft.Row( + [ + ft.Text("Report for Orders In", size=20) + ], + alignment=ft.MainAxisAlignment.CENTER + ), + bgcolor=ft.Colors.BLUE_200, + width=250, + height=80 + ), + ft.Row( + [ + ft.FilledButton( + "Report", + on_click=self.on_report_orders_in_btn_click, + width=100 + ) + ], + alignment=ft.MainAxisAlignment.SPACE_EVENLY + ) + + ], + alignment=ft.MainAxisAlignment.CENTER, + horizontal_alignment=ft.CrossAxisAlignment.CENTER + ), + border=ft.border.all(1, ft.Colors.GREY_300), + bgcolor=ft.Colors.BLUE_50, + padding = ft.padding.symmetric(vertical=20), + width=250, + height=350, + border_radius=20 + ), + ft.Container( + content=ft.Column( + [ + ft.Icon(ft.Icons.ASSESSMENT, size=150), + ft.Container( + ft.Row( + [ + ft.Text("Report for Orders Out", size=20) + ], + alignment=ft.MainAxisAlignment.CENTER + ), + bgcolor=ft.Colors.BLUE_200, + width=250, + height=80 + ), + ft.Row( + [ + ft.FilledButton( + "Report", + on_click=self.on_report_orders_out_btn_click, + width=100 + ) + ], + alignment=ft.MainAxisAlignment.SPACE_EVENLY + ) + ], + alignment=ft.MainAxisAlignment.CENTER, + horizontal_alignment=ft.CrossAxisAlignment.CENTER + ), + border=ft.border.all(1, ft.Colors.GREY_300), + bgcolor=ft.Colors.BLUE_50, + padding = ft.padding.symmetric(vertical=20), + width=250, + height=350, + border_radius=20 + ) + ], + alignment=ft.MainAxisAlignment.CENTER, + spacing=50 + ), + ft.Text(" ") + ], + expand=True, + alignment=ft.MainAxisAlignment.SPACE_BETWEEN, + ), + expand=True + ) \ No newline at end of file diff --git a/transportmanager/client/pages/view_orders_in_page.py b/transportmanager/client/pages/view_orders_in_page.py index 991d86c..26870a9 100644 --- a/transportmanager/client/pages/view_orders_in_page.py +++ b/transportmanager/client/pages/view_orders_in_page.py @@ -298,6 +298,31 @@ class ViewOrdersIn: self.filename = ft.Text(value=self.order['file']) + self.currency = ft.Dropdown( + editable=True, + label="Currency", + options=self.get_currency(), + value=self.order['currency'], + ) + + def get_currency(self): + token = self.page.client_storage.get("token") + headers = {"Authorization": f"Bearer {token}"} + response = requests.get(f"{API_BASE_URL}/currency/", headers=headers) + currency_list = response.json() if response.status_code == 200 else [] + + options = [] + for currency in currency_list: + options.append( + ft.DropdownOption( + key=currency['name'], + content=ft.Text( + value=currency['name'], + ), + ) + ) + return options + def on_go_back_btn_click(self, e): self.dashboard.placeholder.content = self.archive.build() self.dashboard.placeholder.update() @@ -826,6 +851,7 @@ class ViewOrdersIn: 'unloading_addresses': unloading_addresses, 'file':self.filename.value, 'expenses': self.expenses.value, + 'currency': self.currency.value } #print(saved_data) if self.order_number.value == None or len(self.order_number.value)==0: @@ -1038,7 +1064,8 @@ class ViewOrdersIn: [ ft.Row( [ - ft.Text("Price", size=18, weight=ft.FontWeight.BOLD) + ft.Text("Price / Expenses", size=18, weight=ft.FontWeight.BOLD), + self.currency ], alignment=ft.MainAxisAlignment.START ), diff --git a/transportmanager/server/app.py b/transportmanager/server/app.py index 3c6ca11..05e8b58 100644 --- a/transportmanager/server/app.py +++ b/transportmanager/server/app.py @@ -8,11 +8,12 @@ from routes.transporters import transporters_bp from routes.destinations import destinations_bp from routes.orders_out import orders_bp from routes.ouders_in import orders_in_bp -from routes.report import report_bp +from routes.report_order_out import report_order_out_bp from admin.subscription import admin_subscription_bp from routes.subscription import subscription_bp from admin.tenants import admin_user_bp from routes.company_user import company_user_bp +from routes.currency import currency_bp from apscheduler.schedulers.background import BackgroundScheduler from models.subscription import Subscription @@ -66,11 +67,12 @@ app.register_blueprint(transporters_bp, url_prefix="/transporters") app.register_blueprint(destinations_bp, url_prefix="/destinations") app.register_blueprint(orders_bp, url_prefix="/orders") app.register_blueprint(orders_in_bp, url_prefix="/orders_in") -app.register_blueprint(report_bp, url_prefix="/report") +app.register_blueprint(report_order_out_bp) app.register_blueprint(admin_subscription_bp) app.register_blueprint(subscription_bp) app.register_blueprint(admin_user_bp) app.register_blueprint(company_user_bp) +app.register_blueprint(currency_bp) def update_subscription_statuses_job(): print("[Scheduler] Running daily subscription status check...") diff --git a/transportmanager/server/instance/dev.db b/transportmanager/server/instance/dev.db index ec791de..6656102 100644 Binary files a/transportmanager/server/instance/dev.db and b/transportmanager/server/instance/dev.db differ diff --git a/transportmanager/server/models/currency.py b/transportmanager/server/models/currency.py new file mode 100644 index 0000000..4209653 --- /dev/null +++ b/transportmanager/server/models/currency.py @@ -0,0 +1,53 @@ +from datetime import datetime +from database import get_connection, is_postgres + +class Currency: + def __init__(self): + self.ph = "%s" if is_postgres() else "?" + + def currency_to_dict(self, row): + currency = { + 'id': row[0], + 'user_id': row[1], + 'name': row[2], + 'value': row[3], + 'created_at': row[4] + } + return currency + + def insert_currency(self, access_data): + created_at = datetime.now().isoformat() + with get_connection() as conn: + cursor = conn.cursor() + returning = "RETURNING id" if is_postgres() else "" + query = f""" + INSERT INTO currency ( + user_id, name, value, created_at + ) VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}) {returning} + """ + cursor.execute( + query, + ( + access_data['user_id'], + access_data['name'], + access_data['value'], + created_at + ) + ) + inserted_id = None + if is_postgres(): + inserted_id = cursor.fetchone()[0] + else: + inserted_id = cursor.lastrowid + if hasattr(conn, "commit"): + conn.commit() + return inserted_id + + def get_currency_user_id(self, user_id): + with get_connection() as conn: + cursor = conn.cursor() + cursor.execute(f"SELECT * FROM currency WHERE user_id = {self.ph}", (user_id,)) + row = cursor.fetchone() + return self.currency_to_dict(row) if row else None + + \ No newline at end of file diff --git a/transportmanager/server/models/order_in.py b/transportmanager/server/models/order_in.py index 9bf933a..a8f5f7d 100644 --- a/transportmanager/server/models/order_in.py +++ b/transportmanager/server/models/order_in.py @@ -21,6 +21,7 @@ class OrdersIn: "created_at": row[10], "file":row[11], "expenses": row[12], + "currency": row[13] } def order_point_to_dict(self, row): @@ -43,8 +44,8 @@ class OrdersIn: f""" INSERT INTO orders_in (user_id, client_id, products_description, received_price, order_number, - ldb_quantity, kg_quantity, track_reg_number, trailer_reg_number, created_at, file_name, expenses) - VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}){returning} + ldb_quantity, kg_quantity, track_reg_number, trailer_reg_number, created_at, file_name, expenses, currency) + VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}){returning} """, ( data["user_id"], @@ -58,7 +59,8 @@ class OrdersIn: data["trailer_reg_number"], created_at, data["file"], - data["expenses"] + data["expenses"], + data['currency'] ), ) new_id = cur.fetchone()[0] if is_postgres() else getattr(cur, "lastrowid", None) @@ -75,7 +77,7 @@ class OrdersIn: user_id = {self.ph}, client_id = {self.ph}, products_description = {self.ph}, received_price = {self.ph}, order_number = {self.ph}, ldb_quantity = {self.ph}, kg_quantity = {self.ph}, track_reg_number = {self.ph}, - trailer_reg_number = {self.ph}, file_name = {self.ph}, expenses = {self.ph} + trailer_reg_number = {self.ph}, file_name = {self.ph}, expenses = {self.ph}, currency = {self.ph} WHERE id = {self.ph} """, ( @@ -90,6 +92,7 @@ class OrdersIn: data["trailer_reg_number"], data['file'], data['expenses'], + data['currency'], data["id"], ), ) diff --git a/transportmanager/server/models/order_out.py b/transportmanager/server/models/order_out.py index 38131ae..3bee2d6 100644 --- a/transportmanager/server/models/order_out.py +++ b/transportmanager/server/models/order_out.py @@ -21,7 +21,9 @@ class OrdersOut: "received_price": row[10], "paid_price": row[11], "created_at": row[12], - "status": row[13] + "status": row[13], + "order_in_number": row[14], + "currency":row[15] } def order_point_to_dict(self, row): @@ -44,8 +46,8 @@ class OrdersOut: f""" INSERT INTO orders_out (user_id, client_id, transporter_id, products_description, received_price, paid_price, order_number, - ldb_quantity, kg_quantity, track_reg_number, trailer_reg_number, created_at) - VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}){returning} + ldb_quantity, kg_quantity, track_reg_number, trailer_reg_number, created_at, order_in_number, currency) + VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}){returning} """, ( data["user_id"], @@ -60,6 +62,8 @@ class OrdersOut: data["track_reg_number"], data["trailer_reg_number"], created_at, + data['order_in_number'], + data['currency'] ), ) new_id = cursor.fetchone()[0] if is_postgres() else getattr(cursor, "lastrowid", None) @@ -75,7 +79,7 @@ class OrdersOut: UPDATE orders_out SET client_id = {self.ph}, transporter_id = {self.ph}, products_description = {self.ph}, received_price = {self.ph}, paid_price = {self.ph}, order_number = {self.ph}, ldb_quantity = {self.ph}, kg_quantity = {self.ph}, track_reg_number = {self.ph}, - trailer_reg_number = {self.ph} + trailer_reg_number = {self.ph}, order_in_number = {self.ph}, currency = {self.ph} WHERE id = {self.ph} """, ( @@ -89,6 +93,8 @@ class OrdersOut: data["kg_quantity"], data["track_reg_number"], data["trailer_reg_number"], + data['order_in_number'], + data['currency'], data["id"], ), ) diff --git a/transportmanager/server/models/user.py b/transportmanager/server/models/user.py index f0b9ff5..c0949bc 100644 --- a/transportmanager/server/models/user.py +++ b/transportmanager/server/models/user.py @@ -83,10 +83,10 @@ class Users: returning = "RETURNING id" if is_postgres() else "" query = f""" INSERT INTO users ( - name, email, password_hash, created_at, user_role, company_id - ) VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}) {returning} + name, email, password_hash, created_at, user_role, company_id, temporary_password + ) VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}) {returning} """ - cursor.execute(query, (name, email, password_hash, created_at, user_role, company_id)) + cursor.execute(query, (name, email, password_hash, created_at, user_role, company_id, 1)) inserted_id = None if is_postgres(): inserted_id = cursor.fetchone()[0] diff --git a/transportmanager/server/routes/currency.py b/transportmanager/server/routes/currency.py new file mode 100644 index 0000000..34b0f49 --- /dev/null +++ b/transportmanager/server/routes/currency.py @@ -0,0 +1,36 @@ +from flask import Blueprint, request, jsonify +from models.currency import Currency +from models.user import Users + +from flask_jwt_extended import jwt_required, get_jwt_identity + +currency_bp = Blueprint("currency", __name__, url_prefix="/currency") + +@currency_bp.route("/", methods=["GET"]) +@jwt_required() +def list_currency(): + #currency = Currency() + currency = [ + { + 'id':1, + 'name':'USD', + 'value':'', + }, + { + 'id':2, + 'name':'EURO', + 'value':'', + }, + { + 'id':3, + 'name':'CHF', + 'value':'', + }, + { + 'id':4, + 'name':'GBP', + 'value':'', + } + ] + + return jsonify(currency), 200 \ No newline at end of file diff --git a/transportmanager/server/routes/orders_out.py b/transportmanager/server/routes/orders_out.py index 12f084f..174631e 100644 --- a/transportmanager/server/routes/orders_out.py +++ b/transportmanager/server/routes/orders_out.py @@ -50,6 +50,8 @@ def create_order_route(): 'track_reg_number': incoming_data["track_reg_number"], 'trailer_reg_number': incoming_data["trailer_reg_number"], 'products_description': incoming_data["products_description"], + 'order_in_number': incoming_data["order_in_number"], + 'currency': incoming_data["currency"] } order_id = orders.create_order(order_data) @@ -110,6 +112,8 @@ def update_order_route(order_id): "track_reg_number": data.get("track_reg_number", order["track_reg_number"]), "trailer_reg_number": data.get("trailer_reg_number", order["trailer_reg_number"]), "products_description": data.get("products_description", order["products_description"]), + "order_in_number": data.get("order_in_number", order["order_in_number"]), + "currency":data.get("currency", order["currency"]), }) orders.delete_points_by_order_id(order_id) diff --git a/transportmanager/server/routes/ouders_in.py b/transportmanager/server/routes/ouders_in.py index 859d204..114fc08 100644 --- a/transportmanager/server/routes/ouders_in.py +++ b/transportmanager/server/routes/ouders_in.py @@ -33,7 +33,8 @@ def create_order_in_route(): 'trailer_reg_number': incoming_data["trailer_reg_number"], 'products_description': incoming_data["products_description"], 'file': incoming_data['file'], - 'expenses': incoming_data['expenses'] + 'expenses': incoming_data['expenses'], + 'currency': incoming_data['currency'] } #print(order_data) order_id = orders.create_order(order_data) @@ -91,9 +92,10 @@ def update_order_route(order_id): "track_reg_number": data.get("track_reg_number", order["track_reg_number"]), "trailer_reg_number": data.get("trailer_reg_number", order["trailer_reg_number"]), "products_description": data.get("products_description", order["products_description"]), - 'file': data.get("file", order["file"]), - 'expenses': data.get("expenses", order["expenses"]), - "user_id":user_id + "file": data.get("file", order["file"]), + "expenses": data.get("expenses", order["expenses"]), + "currency": data.get("currency", order["currency"]), + "user_id":user_id, }) orders.delete_points_by_order_id(order_id) diff --git a/transportmanager/server/routes/profile.py b/transportmanager/server/routes/profile.py index fbe44bb..ecea0d7 100644 --- a/transportmanager/server/routes/profile.py +++ b/transportmanager/server/routes/profile.py @@ -30,7 +30,8 @@ def get_profile(): "terms": user["terms"], "first_order_number": user["first_order_number"], "user_role": user["user_role"], - "vat":user["vat"] + "vat":user["vat"], + "company_id":user['company_id'] }) diff --git a/transportmanager/server/routes/report.py b/transportmanager/server/routes/report_order_out.py similarity index 91% rename from transportmanager/server/routes/report.py rename to transportmanager/server/routes/report_order_out.py index 6d1a07b..08412e5 100644 --- a/transportmanager/server/routes/report.py +++ b/transportmanager/server/routes/report_order_out.py @@ -4,9 +4,9 @@ from models.order_out import OrdersOut # Your plain SQL model from datetime import datetime from models.user import Users -report_bp = Blueprint("report", __name__, url_prefix="/report") +report_order_out_bp = Blueprint("report_order_out", __name__, url_prefix="/report_order_out") -@report_bp.route("/profit", methods=["GET"]) +@report_order_out_bp.route("/profit", methods=["GET"]) @jwt_required() def get_profit_report(): try: diff --git a/transportmanager/server/schema_sqlite.sql b/transportmanager/server/schema_sqlite.sql index 016683e..fd5a7e3 100644 --- a/transportmanager/server/schema_sqlite.sql +++ b/transportmanager/server/schema_sqlite.sql @@ -1,4 +1,26 @@ +-- Reset schema: drop tables first (children → parents), then recreate. +BEGIN TRANSACTION; +PRAGMA foreign_keys=OFF; + +-- Drop child tables first to avoid FK constraints +DROP TABLE IF EXISTS order_in_points; +DROP TABLE IF EXISTS order_out_points; +DROP TABLE IF EXISTS orders_in; +DROP TABLE IF EXISTS orders_out; +DROP TABLE IF EXISTS email; +DROP TABLE IF EXISTS subscriptions; +DROP TABLE IF EXISTS company_user_access; +DROP TABLE IF EXISTS destinations; +DROP TABLE IF EXISTS transporters; +DROP TABLE IF EXISTS clients; +DROP TABLE IF EXISTS currency; +DROP TABLE IF EXISTS users; + +PRAGMA foreign_keys=ON; +COMMIT; + -- Users table + CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, @@ -18,7 +40,7 @@ CREATE TABLE IF NOT EXISTS users ( user_role TEXT NOT NULL DEFAULT 'user' CHECK (user_role IN ('user', 'admin', 'company_user')), company_id INTEGER DEFAULT 0, active INTEGER DEFAULT 1, - temporary_passwrd INTEGER DEFAULT 0 + temporary_password INTEGER DEFAULT 0 ); -- Clients table @@ -79,6 +101,8 @@ CREATE TABLE IF NOT EXISTS orders_out ( paid_price DOUBLE PRECISION, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, order_status TEXT NOT NULL DEFAULT 'active' CHECK (order_status IN ('active', 'inactive', 'cancelled')), + order_in_number TEXT, + currency TEXT, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(client_id) REFERENCES clients(id) ON DELETE CASCADE, FOREIGN KEY(transporter_id) REFERENCES transporters(id) ON DELETE CASCADE @@ -99,6 +123,7 @@ CREATE TABLE IF NOT EXISTS orders_in ( created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, file_name TEXT, expenses DOUBLE PRECISION, + currency TEXT, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(client_id) REFERENCES clients(id) ON DELETE CASCADE ); @@ -162,3 +187,12 @@ CREATE TABLE IF NOT EXISTS company_user_access ( created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(company_user_id) REFERENCES users(id) ON DELETE CASCADE ); + +CREATE TABLE IF NOT EXISTS currency ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + name TEXT, + value DOUBLE PRECISION, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/transportmanager/server/utils/pdf.py b/transportmanager/server/utils/pdf.py index 84f3c46..eb954ed 100644 --- a/transportmanager/server/utils/pdf.py +++ b/transportmanager/server/utils/pdf.py @@ -191,9 +191,13 @@ def generate_order_pdf(order, user_data, transporter_data, logo_path, save_to_di doc.build(elements) buffer.seek(0) + user_id = order['user_id'] + if user_data['user_role'] == 'company_user': + user_id = user_data['company_id'] if save_to_disk: - save_path=f"generated_pdfs/order_{order['user_id']}_{order['order_number']}.pdf" + save_path=f"generated_pdfs/order_{user_id}_{order['order_number']}.pdf" with open(save_path, "wb") as f: f.write(buffer.getvalue()) - return buffer \ No newline at end of file + return buffer +