From 3d5f769b528d77714b78c184029d492952bd61bf Mon Sep 17 00:00:00 2001 From: Marius Robert Macamete Date: Wed, 10 Sep 2025 11:12:49 +0300 Subject: [PATCH] implement currency and report --- transportmanager/client/pages/archive_page.py | 9 + .../client/pages/dashboard_page.py | 4 +- .../client/pages/orders_edit_page.py | 66 +- .../client/pages/orders_in_page.py | 29 +- .../client/pages/orders_out_page.py | 70 ++- .../client/pages/report_order_in_page.py | 572 ++++++++++++++++++ ...eport_page.py => report_order_out_page.py} | 310 ++++++++-- transportmanager/client/pages/reports_page.py | 110 ++++ .../client/pages/view_orders_in_page.py | 29 +- transportmanager/server/app.py | 6 +- transportmanager/server/instance/dev.db | Bin 61440 -> 73728 bytes transportmanager/server/models/currency.py | 53 ++ transportmanager/server/models/order_in.py | 11 +- transportmanager/server/models/order_out.py | 14 +- transportmanager/server/models/user.py | 6 +- transportmanager/server/routes/currency.py | 36 ++ transportmanager/server/routes/orders_out.py | 4 + transportmanager/server/routes/ouders_in.py | 10 +- transportmanager/server/routes/profile.py | 3 +- .../routes/{report.py => report_order_out.py} | 4 +- transportmanager/server/schema_sqlite.sql | 36 +- transportmanager/server/utils/pdf.py | 8 +- 22 files changed, 1290 insertions(+), 100 deletions(-) create mode 100644 transportmanager/client/pages/report_order_in_page.py rename transportmanager/client/pages/{report_page.py => report_order_out_page.py} (54%) create mode 100644 transportmanager/client/pages/reports_page.py create mode 100644 transportmanager/server/models/currency.py create mode 100644 transportmanager/server/routes/currency.py rename transportmanager/server/routes/{report.py => report_order_out.py} (91%) 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 ec791dee270d5b79c1d5cd9a8eedecd2e1cfa72d..6656102a303223ab93bd8750a119343b74913f6d 100644 GIT binary patch delta 1781 zcmeHIPiz!b7@u!vXXnrUdAnQMEwzOyY1l5=EVfk=q7haYp@c%SZljXuGTlCKlFrO# z-Yim0Xf2gQqKVB*Jb6$L5H6->5)K^DgNa877n(+;Bwk238wnbHGrO}C+AB9^=6f^m z_ulut@Bb{U^9w&mzv|1v7aYfB8S#vO(b95pbQeQQm$wN%gdVezOQFZ{)AAkZf{=(@ z;{HZgmOnkuUyR4;1K2|^u59!2kqJ;f;}tQtb0>U0MKAC^!>FmHHD+HEsMFqLHM#jZwlM#{olL2hr;WT zyzR_jr)F3+t7%SSXM@Q47Gg9eDobxjWsvAU(hx}Apj-kF>7+6QqSsVD1c=c;;(ZYF z9>sNL!S<+_%jMv5#=D;Eij<3LzHXZh-I}X433l>@LQDS;G-lNC6IJbqR#6XYZ#gobTc zE>Ti3elXl)H(de!#r3o|v1xl(LI#$XzGM<%ye@OjF?ojRXjr;y&|eOy0}SeyNpqUi z9i!nIwnZ4C$N`Vy!TNuQMPu{RpF~$UQAlv;hr~Vf$+N}s+lykEt3Bspsq`dSsKn?# zF8Oi%p>j&vFQ$a|qWio|T8+NCsHVl-zyK_Dx%%`h4!cG8&K|00<*KGu%Wodn)DWfL zt&Gs0l>ClX)t}R6n^--3a?0N`SA zOx_~7Gf$tqiHCl>GAf09^vAn9=m)XbJPA8UOEcHeEy8+?%QCyO(&|H<^k%8tSqeH- zr=qrmS=Bs^JJ>&I07<&Ix*L#p bZS@gAg1)m-goJl{Z3f`E{YQ>$tknMjCO!>8 delta 1882 zcmds2O>7%Q6rS0Qv%CK3q)A*W4Z(3qN>XZj|7Le=K*a%sDq2dxMSuihz1~f%#&%+F z3I#!$G@w#(XthU##H9!XLWoT{Ko7Z891uc6LJbKBP!JNma6>7*FzX*05}*gfg_UMz zciuPOy!XAQx2KjnPJPdw+uaRr0{|d8+E4KOet8ewO`Yux&^?H*`<5N(dT1?pn7KL3kc-7) z@Jzq=bGV1;932ggjxz3xXrza2DiG!(jj$Pj7#if4JLVP=up1=7frX<@gplhy9L-N! zgZEaeQ*2*>l4Z%FjFTcs$~Z46SxmhYQ=%lRvNT(_YQctT>j14&)xJjVdOER7Kox*~ zML(hy^eOrXy^UP7h$_^cM~8X^HVGR!_me+9T`kdsbwqeJA>62h9*(`E1u9iq7$)4w z#@m=u1qIk)_u~Wm_I5Gs?LvLFK&32AJEdx+&OOVQhsyqG|9RBOMZQs0Z(IS%-#gr<^8A<)`uaeYMhKQK7uDS%!B^uR-=%F#l`%wXK z(~!2gFXNP?=t@Rvx_=R%iDX^XGXLExWE#KyoWxIJh{1hf;`$dKd-T;QP6#Ab-z0dd%ZVSk3#tWYNov3 zE}Kbr0di@cUPW`qp#X%@T&@q~>{6}njLYNYYQ>qXZ%H=Es