implement currency and report

This commit is contained in:
2025-09-10 11:12:49 +03:00
parent 106045d72a
commit 3d5f769b52
22 changed files with 1290 additions and 100 deletions

View File

@@ -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()

View File

@@ -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)

View File

@@ -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
),

View File

@@ -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
),

View File

@@ -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,

View File

@@ -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
)

View File

@@ -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
),

View File

@@ -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
)

View File

@@ -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
),