intrgrating suggestions after betta 1

This commit is contained in:
2025-09-08 18:06:00 +03:00
parent eb262451ad
commit 106045d72a
34 changed files with 1549 additions and 146 deletions

View File

@@ -58,6 +58,11 @@ class ArchiveInPage:
self.selected_delete_order = order self.selected_delete_order = order
self.page.open(self.delete_dialog) self.page.open(self.delete_dialog)
def download_order_file(self, order):
file = order['file']
url=f"{API_BASE_URL}/orders_in/files/{file}"
self.page.launch_url(url)
def load_orders(self): def load_orders(self):
try: try:
token = self.page.client_storage.get("token") token = self.page.client_storage.get("token")
@@ -90,6 +95,11 @@ class ArchiveInPage:
self.orders_list.controls.clear() self.orders_list.controls.clear()
for order in self.orders: for order in self.orders:
client = self.get_client(order['client_id']) client = self.get_client(order['client_id'])
buttons = []
if order.get("file"):
buttons.append(ft.Button("Download", icon=ft.Icons.DOWNLOAD, on_click=lambda e, o=order: self.download_order_file(o)))
buttons.append(ft.Button("View", icon=ft.Icons.PICTURE_AS_PDF, on_click=lambda e, o=order: self.view_order(o)))
buttons.append(ft.Button("Delete", icon=ft.Icons.CANCEL, on_click=lambda e, o=order: self.cancel_order(o)))
self.orders_list.controls.append( self.orders_list.controls.append(
ft.Container( ft.Container(
content=ft.Row([ content=ft.Row([
@@ -97,8 +107,7 @@ class ArchiveInPage:
ft.Text(f"{client}", size=16, weight=ft.FontWeight.BOLD), ft.Text(f"{client}", size=16, weight=ft.FontWeight.BOLD),
ft.Text(f"Order Number: {order['order_number']}", size=14), ft.Text(f"Order Number: {order['order_number']}", size=14),
], expand=True), ], expand=True),
ft.Button("View",icon=ft.Icons.PICTURE_AS_PDF, on_click=lambda e, o=order: self.view_order(o)), *buttons
ft.Button("Delete", icon=ft.Icons.CANCEL, on_click=lambda e, o=order: self.cancel_order(o))
]), ]),
padding=10, padding=10,
border=ft.border.all(1, ft.Colors.GREY_300), border=ft.border.all(1, ft.Colors.GREY_300),
@@ -108,6 +117,18 @@ class ArchiveInPage:
) )
self.page.update() self.page.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()['orders_in'] == 1 else False
def build(self): def build(self):
self.refresh() self.refresh()
return ft.Container( return ft.Container(
@@ -115,7 +136,7 @@ class ArchiveInPage:
[ [
ft.Row( ft.Row(
[ [
ft.Text("Archive", size=24, weight=ft.FontWeight.BOLD), ft.Text("Archive Orders In", size=24, weight=ft.FontWeight.BOLD),
ft.Button("Back", icon=ft.Icons.ARROW_BACK_IOS_NEW, on_click=self.on_go_back_btn_click) ft.Button("Back", icon=ft.Icons.ARROW_BACK_IOS_NEW, on_click=self.on_go_back_btn_click)
], ],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN alignment=ft.MainAxisAlignment.SPACE_BETWEEN
@@ -123,4 +144,30 @@ class ArchiveInPage:
self.orders_list self.orders_list
] ]
) )
) if self.get_client_access() else ft.Container(
content=ft.Column(
[
ft.Row(
[
ft.Text("Archive Orders 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

@@ -107,6 +107,18 @@ class ArchivePage:
self.dashboard.placeholder.content = self.order_page.build() self.dashboard.placeholder.content = self.order_page.build()
self.dashboard.placeholder.update() self.dashboard.placeholder.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()['orders_out'] == 1 else False
def build(self): def build(self):
self.refresh() self.refresh()
return ft.Container( return ft.Container(
@@ -114,7 +126,7 @@ class ArchivePage:
[ [
ft.Row( ft.Row(
[ [
ft.Text("Archive", size=24, weight=ft.FontWeight.BOLD), ft.Text("Archive Orders Out", size=24, weight=ft.FontWeight.BOLD),
ft.Button("Back", icon=ft.Icons.ARROW_BACK_IOS_NEW, on_click=self.on_go_back_btn_click) ft.Button("Back", icon=ft.Icons.ARROW_BACK_IOS_NEW, on_click=self.on_go_back_btn_click)
], ],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN alignment=ft.MainAxisAlignment.SPACE_BETWEEN
@@ -122,4 +134,30 @@ class ArchivePage:
self.orders_list self.orders_list
] ]
) )
) if self.get_client_access() else ft.Container(
content=ft.Column(
[
ft.Row(
[
ft.Text("Archive Orders Out", 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

@@ -265,6 +265,18 @@ class ClientsPage:
except Exception as e: except Exception as e:
print("Error loading subscription:", e) print("Error loading subscription:", e)
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()['clients'] == 1 else False
def build(self): def build(self):
self.load_clients() self.load_clients()
self.subscription = self.get_current_subscription_plan() self.subscription = self.get_current_subscription_plan()
@@ -291,4 +303,30 @@ class ClientsPage:
expand=True expand=True
), ),
expand=True, expand=True,
) if self.get_client_access() else ft.Container(
content=ft.Column(
[
ft.Row(
[
ft.Text("Clients", 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

@@ -208,6 +208,18 @@ class DestinationsPage:
except Exception as e: except Exception as e:
print("Error loading subscription:", e) print("Error loading subscription:", e)
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()['destinations'] == 1 else False
def build(self): def build(self):
self.refresh() self.refresh()
self.add_destination_btn = ft.ElevatedButton("Add Destination", icon=ft.Icons.ADD, on_click=lambda e: self.open_dialog()) self.add_destination_btn = ft.ElevatedButton("Add Destination", icon=ft.Icons.ADD, on_click=lambda e: self.open_dialog())
@@ -230,4 +242,30 @@ class DestinationsPage:
self.destinations_column self.destinations_column
], ],
expand=True, expand=True,
) if self.get_client_access() else ft.Container(
content=ft.Column(
[
ft.Row(
[
ft.Text("Adderess", 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

@@ -1196,7 +1196,7 @@ class OrdersEditPage:
[ [
self.loading_date, self.loading_date,
ft.ElevatedButton( ft.ElevatedButton(
"Pick date", "Select date",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.DatePicker( ft.DatePicker(
@@ -1213,7 +1213,7 @@ class OrdersEditPage:
[ [
self.loading_hour, self.loading_hour,
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - Start", "Select hour - Start",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(
@@ -1226,7 +1226,7 @@ class OrdersEditPage:
), ),
), ),
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - End", "Select hour - End",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(
@@ -1289,7 +1289,7 @@ class OrdersEditPage:
[ [
self.unloading_date, self.unloading_date,
ft.ElevatedButton( ft.ElevatedButton(
"Pick date", "Select date",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.DatePicker( ft.DatePicker(
@@ -1306,7 +1306,7 @@ class OrdersEditPage:
[ [
self.unloading_hour, self.unloading_hour,
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - Start", "Select hour - Start",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(
@@ -1319,7 +1319,7 @@ class OrdersEditPage:
), ),
), ),
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - End", "Select hour - End",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(

View File

@@ -129,6 +129,7 @@ class OrdersInPage:
) )
self.ldm_quantity = ft.TextField( self.ldm_quantity = ft.TextField(
value='13.6',
expand=True, expand=True,
keyboard_type=ft.KeyboardType.NUMBER, keyboard_type=ft.KeyboardType.NUMBER,
input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="") input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="")
@@ -159,7 +160,12 @@ class OrdersInPage:
) )
self.received_price = ft.TextField( self.received_price = ft.TextField(
label="Price Received - visible only to you!", label="Price Received",
keyboard_type=ft.KeyboardType.NUMBER,
input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="")
)
self.expenses = ft.TextField(
label="Expenses",
keyboard_type=ft.KeyboardType.NUMBER, keyboard_type=ft.KeyboardType.NUMBER,
input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="") input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="")
) )
@@ -193,6 +199,130 @@ class OrdersInPage:
), ),
) )
self.file_picker = ft.FilePicker(on_result=self.on_file_result)
self.page.overlay.append(self.file_picker)
# --- Persistent date & time pickers (must be on page.overlay) ---
self.loading_date_picker = ft.DatePicker(
first_date=datetime.datetime(year=2000, month=10, day=1),
last_date=datetime.datetime(year=2025, month=10, day=1),
on_change=self.on_loading_date_click,
)
self.unloading_date_picker = ft.DatePicker(
first_date=datetime.datetime(year=2000, month=10, day=1),
last_date=datetime.datetime(year=2025, month=10, day=1),
on_change=self.on_unloading_date_click,
)
self.loading_time_picker_start = ft.TimePicker(
confirm_text="Confirm",
error_invalid_text="Time out of range",
help_text="Pick your time slot",
on_change=self.on_loading_hour_click,
time_picker_entry_mode=ft.TimePickerEntryMode.INPUT_ONLY,
)
self.loading_time_picker_end = ft.TimePicker(
confirm_text="Confirm",
error_invalid_text="Time out of range",
help_text="Pick your time slot",
on_change=self.on_loading_hour_click,
time_picker_entry_mode=ft.TimePickerEntryMode.INPUT_ONLY,
)
self.unloading_time_picker_start = ft.TimePicker(
confirm_text="Confirm",
error_invalid_text="Time out of range",
help_text="Pick your time slot",
on_change=self.on_unloading_hour_click,
time_picker_entry_mode=ft.TimePickerEntryMode.INPUT_ONLY,
)
self.unloading_time_picker_end = ft.TimePicker(
confirm_text="Confirm",
error_invalid_text="Time out of range",
help_text="Pick your time slot",
on_change=self.on_unloading_hour_click,
time_picker_entry_mode=ft.TimePickerEntryMode.INPUT_ONLY,
)
# Attach to overlay
self.page.overlay.extend([
self.loading_date_picker,
self.unloading_date_picker,
self.loading_time_picker_start,
self.loading_time_picker_end,
self.unloading_time_picker_start,
self.unloading_time_picker_end,
])
#self.page.update()
self.upload_order_btn = ft.Button(
"Upload",
icon=ft.Icons.UPLOAD_FILE,
width=150,
on_click=self.on_upload_document_btn_click
)
self.filename = ft.Text()
def _open_date_picker(self, picker):
# Works on both newer and older Flet
if hasattr(picker, "pick_date"):
picker.pick_date()
else:
picker.open = True
self.page.update()
def _open_time_picker(self, picker):
# Works on both newer and older Flet
if hasattr(picker, "pick_time"):
picker.pick_time()
else:
picker.open = True
self.page.update()
def on_upload_document_btn_click(self, e):
# Ensure FilePicker is attached to the page (Portainer/Flet sometimes reuses page objects)
if self.file_picker not in getattr(self.page.overlay, "controls", []):
self.page.overlay.append(self.file_picker)
self.page.update()
self.file_picker.pick_files(
allow_multiple=False,
allowed_extensions=["png", "jpg", "jpeg", "pdf"]
)
def on_file_result(self, e: ft.FilePickerResultEvent):
if not e.files:
return
file = e.files[0]
# Build a safe filename: order_in_YYYYmmdd_HHMMSS_userid_originalname
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
user_id = self.page.session.get("user_id")
new_filename = f"order_in_{timestamp}_{user_id}_{file.name}"
upload_url = self.page.get_upload_url(new_filename, 1000)
self.file_picker.upload([ft.FilePickerUploadFile(file.name, upload_url=upload_url)])
self.filename.value = new_filename
self.filename.update()
#import os
#import shutil
#source_path = os.path.join("uploads", new_filename)
# destination_path = os.path.join("assets/images", new_filename)
# try:
# time.sleep(2)
# shutil.move(source_path, destination_path)
# self.logo.src = f"images/{new_filename}"
# self.logo.update()
# self.logo_field.value = new_filename
# self.page.update()
# self.page.client_storage.set("logo_filename", new_filename)
# self.dashboard.logo.src = f"images/{new_filename}"
# self.dashboard.logo.update()
# except Exception as err:
# self.message.value = f"Upload error: {err}"
# self.message.color = ft.Colors.RED
# self.page.update()
def on_location_btn_click(self, destination): def on_location_btn_click(self, destination):
query = destination["address"].replace(", ", "+") query = destination["address"].replace(", ", "+")
maps_url = f"https://www.google.com/maps/search/?api=1&query={query}" maps_url = f"https://www.google.com/maps/search/?api=1&query={query}"
@@ -266,7 +396,7 @@ class OrdersInPage:
#print(self.loading_date.value) #print(self.loading_date.value)
def on_loading_hour_click(self, e): def on_loading_hour_click(self, e):
if len(self.loading_hour.value) != None and len(self.loading_hour.value)==0: if not self.loading_hour.value:
self.loading_hour.value = str(e.control.value) self.loading_hour.value = str(e.control.value)
else: else:
self.loading_hour.value += f' - {e.control.value}' self.loading_hour.value += f' - {e.control.value}'
@@ -322,10 +452,10 @@ class OrdersInPage:
self.loading_error_message.value = "Please select loading point!" self.loading_error_message.value = "Please select loading point!"
self.loading_error_message.update() self.loading_error_message.update()
return return
if self.loading_informations.value == None or len(self.loading_informations.value) == 0: # if self.loading_informations.value == None or len(self.loading_informations.value) == 0:
self.loading_error_message.value = "Add loading informations!" # self.loading_error_message.value = "Add loading informations!"
self.loading_error_message.update() # self.loading_error_message.update()
return # return
if self.loading_date.value == None or len(str(self.loading_date.value)) == 0: if self.loading_date.value == None or len(str(self.loading_date.value)) == 0:
self.loading_error_message.value = "Add loading date!" self.loading_error_message.value = "Add loading date!"
self.loading_error_message.update() self.loading_error_message.update()
@@ -391,7 +521,7 @@ class OrdersInPage:
self.unloading_date.update() self.unloading_date.update()
def on_unloading_hour_click(self, e): def on_unloading_hour_click(self, e):
if len(self.unloading_hour.value) != None and len(self.unloading_hour.value)==0: if not self.unloading_hour.value:
self.unloading_hour.value = str(e.control.value) self.unloading_hour.value = str(e.control.value)
else: else:
self.unloading_hour.value += f' - {e.control.value}' self.unloading_hour.value += f' - {e.control.value}'
@@ -440,10 +570,10 @@ class OrdersInPage:
self.unloading_error_message.value = "Please select unloading point!" self.unloading_error_message.value = "Please select unloading point!"
self.unloading_error_message.update() self.unloading_error_message.update()
return return
if self.unloading_informations.value == None or len(self.unloading_informations.value) == 0: # if self.unloading_informations.value == None or len(self.unloading_informations.value) == 0:
self.unloading_error_message.value = "Add unloading informations!" # self.unloading_error_message.value = "Add unloading informations!"
self.unloading_error_message.update() # self.unloading_error_message.update()
return # return
if self.unloading_date.value == None or len(str(self.unloading_date.value)) == 0: if self.unloading_date.value == None or len(str(self.unloading_date.value)) == 0:
self.unloading_error_message.value = "Add unloading date!" self.unloading_error_message.value = "Add unloading date!"
self.unloading_error_message.update() self.unloading_error_message.update()
@@ -684,7 +814,9 @@ class OrdersInPage:
'trailer_reg_number': self.trailer_reg_number.value, 'trailer_reg_number': self.trailer_reg_number.value,
'received_price': self.received_price.value, 'received_price': self.received_price.value,
'loading_addresses': loading_addresses, 'loading_addresses': loading_addresses,
'unloading_addresses': unloading_addresses 'unloading_addresses': unloading_addresses,
'file':self.filename.value,
'expenses': self.expenses.value,
} }
#print(saved_data) #print(saved_data)
if self.order_number.value == None or len(self.order_number.value)==0: if self.order_number.value == None or len(self.order_number.value)==0:
@@ -763,6 +895,18 @@ class OrdersInPage:
self.error_message.value = f"Error: {str(ex)}" self.error_message.value = f"Error: {str(ex)}"
self.error_message.update() self.error_message.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()['orders_in'] == 1 else False
def build(self): def build(self):
self.save_btn = ft.FilledButton( self.save_btn = ft.FilledButton(
"Save Order", "Save Order",
@@ -785,7 +929,9 @@ class OrdersInPage:
ft.Row( ft.Row(
[ [
ft.Text("Number", size=18, weight=ft.FontWeight.BOLD), ft.Text("Number", size=18, weight=ft.FontWeight.BOLD),
self.order_number self.order_number,
self.upload_order_btn,
self.filename
] ]
) )
], ],
@@ -882,11 +1028,12 @@ class OrdersInPage:
[ [
ft.Row( ft.Row(
[ [
ft.Text("Price", size=18, weight=ft.FontWeight.BOLD) ft.Text("Price / Expenses", size=18, weight=ft.FontWeight.BOLD)
], ],
alignment=ft.MainAxisAlignment.START alignment=ft.MainAxisAlignment.START
), ),
self.received_price, self.received_price,
self.expenses
], ],
expand=2.5 expand=2.5
) )
@@ -926,15 +1073,9 @@ class OrdersInPage:
[ [
self.loading_date, self.loading_date,
ft.ElevatedButton( ft.ElevatedButton(
"Pick date", "Select date",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self._open_date_picker(self.loading_date_picker),
ft.DatePicker(
first_date=datetime.datetime(year=2000, month=10, day=1),
last_date=datetime.datetime(year=2025, month=10, day=1),
on_change=self.on_loading_date_click,
)
),
) )
], ],
expand=True expand=True
@@ -943,30 +1084,14 @@ class OrdersInPage:
[ [
self.loading_hour, self.loading_hour,
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - Start", "Select hour - Start",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self._open_time_picker(self.loading_time_picker_start),
ft.TimePicker(
confirm_text="Confirm",
error_invalid_text="Time out of range",
help_text="Pick your time slot",
on_change=self.on_loading_hour_click,
time_picker_entry_mode = ft.TimePickerEntryMode.INPUT_ONLY
)
),
), ),
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - End", "Select hour - End",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self._open_time_picker(self.loading_time_picker_end),
ft.TimePicker(
confirm_text="Confirm",
error_invalid_text="Time out of range",
help_text="Pick your time slot",
on_change=self.on_loading_hour_click,
time_picker_entry_mode = ft.TimePickerEntryMode.INPUT_ONLY
)
),
), ),
ft.ElevatedButton( ft.ElevatedButton(
"Reset", "Reset",
@@ -1019,15 +1144,9 @@ class OrdersInPage:
[ [
self.unloading_date, self.unloading_date,
ft.ElevatedButton( ft.ElevatedButton(
"Pick date", "Select date",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self._open_date_picker(self.unloading_date_picker),
ft.DatePicker(
first_date=datetime.datetime(year=2000, month=10, day=1),
last_date=datetime.datetime(year=2025, month=10, day=1),
on_change=self.on_unloading_date_click,
)
),
) )
], ],
expand=True expand=True
@@ -1036,30 +1155,14 @@ class OrdersInPage:
[ [
self.unloading_hour, self.unloading_hour,
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - Start", "Select hour - Start",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self._open_time_picker(self.unloading_time_picker_start),
ft.TimePicker(
confirm_text="Confirm",
error_invalid_text="Time out of range",
help_text="Pick your time slot",
on_change=self.on_unloading_hour_click,
time_picker_entry_mode = ft.TimePickerEntryMode.INPUT_ONLY
)
),
), ),
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - End", "Select hour - End",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self._open_time_picker(self.unloading_time_picker_end),
ft.TimePicker(
confirm_text="Confirm",
error_invalid_text="Time out of range",
help_text="Pick your time slot",
on_change=self.on_unloading_hour_click,
time_picker_entry_mode = ft.TimePickerEntryMode.INPUT_ONLY
)
),
), ),
ft.ElevatedButton( ft.ElevatedButton(
"Reset", "Reset",
@@ -1101,5 +1204,31 @@ class OrdersInPage:
scroll=ft.ScrollMode.ADAPTIVE, scroll=ft.ScrollMode.ADAPTIVE,
spacing=20 spacing=20
) )
) if self.get_client_access() else ft.Container(
content=ft.Column(
[
ft.Row(
[
ft.Text("Create 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

@@ -156,7 +156,8 @@ class OrdersOutPage:
self.ldm_quantity = ft.TextField( self.ldm_quantity = ft.TextField(
expand=True, expand=True,
keyboard_type=ft.KeyboardType.NUMBER, keyboard_type=ft.KeyboardType.NUMBER,
input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string="") input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string=""),
value=13.6
) )
self.kg_quantity = ft.TextField( self.kg_quantity = ft.TextField(
@@ -402,10 +403,10 @@ class OrdersOutPage:
self.loading_error_message.value = "Please select loading point!" self.loading_error_message.value = "Please select loading point!"
self.loading_error_message.update() self.loading_error_message.update()
return return
if self.loading_informations.value == None or len(self.loading_informations.value) == 0: # if self.loading_informations.value == None or len(self.loading_informations.value) == 0:
self.loading_error_message.value = "Add loading informations!" # self.loading_error_message.value = "Add loading informations!"
self.loading_error_message.update() # self.loading_error_message.update()
return # return
if self.loading_date.value == None or len(str(self.loading_date.value)) == 0: if self.loading_date.value == None or len(str(self.loading_date.value)) == 0:
self.loading_error_message.value = "Add loading date!" self.loading_error_message.value = "Add loading date!"
self.loading_error_message.update() self.loading_error_message.update()
@@ -520,10 +521,10 @@ class OrdersOutPage:
self.unloading_error_message.value = "Please select unloading point!" self.unloading_error_message.value = "Please select unloading point!"
self.unloading_error_message.update() self.unloading_error_message.update()
return return
if self.unloading_informations.value == None or len(self.unloading_informations.value) == 0: # if self.unloading_informations.value == None or len(self.unloading_informations.value) == 0:
self.unloading_error_message.value = "Add unloading informations!" # self.unloading_error_message.value = "Add unloading informations!"
self.unloading_error_message.update() # self.unloading_error_message.update()
return # return
if self.unloading_date.value == None or len(str(self.unloading_date.value)) == 0: if self.unloading_date.value == None or len(str(self.unloading_date.value)) == 0:
self.unloading_error_message.value = "Add unloading date!" self.unloading_error_message.value = "Add unloading date!"
self.unloading_error_message.update() self.unloading_error_message.update()
@@ -868,6 +869,18 @@ class OrdersOutPage:
print("Error loading subscription:", e) print("Error loading subscription:", e)
return None return None
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()['orders_out'] == 1 else False
def build(self): def build(self):
self.save_btn = ft.FilledButton( self.save_btn = ft.FilledButton(
"Save and Generate", "Save and Generate",
@@ -1050,7 +1063,7 @@ class OrdersOutPage:
[ [
self.loading_date, self.loading_date,
ft.ElevatedButton( ft.ElevatedButton(
"Pick date", "Select date",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.DatePicker( ft.DatePicker(
@@ -1067,7 +1080,7 @@ class OrdersOutPage:
[ [
self.loading_hour, self.loading_hour,
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - Start", "Select hour - Start",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(
@@ -1080,7 +1093,7 @@ class OrdersOutPage:
), ),
), ),
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - End", "Select hour - End",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(
@@ -1143,7 +1156,7 @@ class OrdersOutPage:
[ [
self.unloading_date, self.unloading_date,
ft.ElevatedButton( ft.ElevatedButton(
"Pick date", "Select date",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.DatePicker( ft.DatePicker(
@@ -1160,7 +1173,7 @@ class OrdersOutPage:
[ [
self.unloading_hour, self.unloading_hour,
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - Start", "Select hour - Start",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(
@@ -1173,7 +1186,7 @@ class OrdersOutPage:
), ),
), ),
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - End", "Select hour - End",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(
@@ -1225,5 +1238,31 @@ class OrdersOutPage:
scroll=ft.ScrollMode.ADAPTIVE, scroll=ft.ScrollMode.ADAPTIVE,
spacing=20 spacing=20
) )
) if self.get_client_access() else ft.Container(
content=ft.Column(
[
ft.Row(
[
ft.Text("Create Order Out", 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,6 +1,8 @@
import flet as ft import flet as ft
from pages.orders_in_page import OrdersInPage from pages.orders_in_page import OrdersInPage
from pages.orders_out_page import OrdersOutPage from pages.orders_out_page import OrdersOutPage
from pages.archive_in_page import ArchiveInPage
from pages.archive_page import ArchivePage
class OrdersPage: class OrdersPage:
def __init__(self, page: ft.Page, dashboard): def __init__(self, page: ft.Page, dashboard):
@@ -17,6 +19,16 @@ class OrdersPage:
self.dashboard.placeholder.content = orders_out_page.build() self.dashboard.placeholder.content = orders_out_page.build()
self.dashboard.placeholder.update() self.dashboard.placeholder.update()
def on_archive_order_out_btn_click(self, e):
archive = ArchivePage(self.page, self.dashboard, self)
self.dashboard.placeholder.content = archive.build()
self.dashboard.placeholder.update()
def on_archive_order_in_btn_click(self, e):
archive = ArchiveInPage(self.page, self.dashboard, self)
self.dashboard.placeholder.content = archive.build()
self.dashboard.placeholder.update()
def build(self): def build(self):
return ft.Container( return ft.Container(
content=ft.Column( content=ft.Column(
@@ -31,7 +43,7 @@ class OrdersPage:
ft.Container( ft.Container(
ft.Row( ft.Row(
[ [
ft.Text("Incoming orders", size=20) ft.Text("Orders In", size=20)
], ],
alignment=ft.MainAxisAlignment.CENTER alignment=ft.MainAxisAlignment.CENTER
), ),
@@ -39,11 +51,22 @@ class OrdersPage:
width=250, width=250,
height=80 height=80
), ),
ft.FilledButton( ft.Row(
"Orders In", [
on_click=self.on_orders_in_btn_click, ft.FilledButton(
width=150 "Create",
on_click=self.on_orders_in_btn_click,
width=100
),
ft.FilledButton(
"Archive",
on_click=self.on_archive_order_in_btn_click,
width=100
)
],
alignment=ft.MainAxisAlignment.SPACE_EVENLY
) )
], ],
alignment=ft.MainAxisAlignment.CENTER, alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER horizontal_alignment=ft.CrossAxisAlignment.CENTER
@@ -62,7 +85,7 @@ class OrdersPage:
ft.Container( ft.Container(
ft.Row( ft.Row(
[ [
ft.Text("Outcoming orders", size=20) ft.Text("Orders Out", size=20)
], ],
alignment=ft.MainAxisAlignment.CENTER alignment=ft.MainAxisAlignment.CENTER
), ),
@@ -70,10 +93,20 @@ class OrdersPage:
width=250, width=250,
height=80 height=80
), ),
ft.FilledButton( ft.Row(
"Orders Out", [
on_click=self.on_orders_out_btn_click, ft.FilledButton(
width=150 "Create",
on_click=self.on_orders_out_btn_click,
width=100
),
ft.FilledButton(
"Archive",
on_click=self.on_archive_order_out_btn_click,
width=100
)
],
alignment=ft.MainAxisAlignment.SPACE_EVENLY
) )
], ],
alignment=ft.MainAxisAlignment.CENTER, alignment=ft.MainAxisAlignment.CENTER,

View File

@@ -2,6 +2,7 @@ import flet as ft
import requests import requests
import time import time
from config import API_BASE_URL from config import API_BASE_URL
from pages.users_page import Users
class ProfilePage: class ProfilePage:
def __init__(self, page: ft.Page, dashboard): def __init__(self, page: ft.Page, dashboard):
@@ -138,9 +139,9 @@ class ProfilePage:
), ),
) )
self.created_at_text = ft.Text(value="Created At: TBD") # Set dynamically later self.created_at_text = ft.Text(value="Created At: TBD") # Set dynamically later
self.edit_button = ft.ElevatedButton(text="Edit Profile", on_click=self.on_edit_click) self.edit_button = ft.ElevatedButton(text="Edit Profile", on_click=self.on_edit_click, width=100)
self.save_button = ft.ElevatedButton(text="Save Changes", visible=False, on_click=self.on_save_click) self.save_button = ft.ElevatedButton(text="Save Changes", visible=False, on_click=self.on_save_click, width=130)
self.cancel_button = ft.TextButton(text="Cancel", visible=False, on_click=self.on_cancel_click) self.cancel_button = ft.ElevatedButton(text="Cancel", visible=False, on_click=self.on_cancel_click, width=100)
self.upload_logo_btn = ft.ElevatedButton("Upload Company Logo", icon=ft.Icons.UPLOAD, on_click=self.on_upload_click, disabled=True) self.upload_logo_btn = ft.ElevatedButton("Upload Company Logo", icon=ft.Icons.UPLOAD, on_click=self.on_upload_click, disabled=True)
self.message = ft.Text() self.message = ft.Text()
self.logo = ft.Image(src="images/image_placeholder.png", width=250) self.logo = ft.Image(src="images/image_placeholder.png", width=250)
@@ -177,6 +178,18 @@ class ProfilePage:
), ),
) )
self.add_users = ft.Button(
"Add Users",
icon=ft.Icons.MANAGE_ACCOUNTS_ROUNDED,
width=250,
on_click=self.on_add_users_btn_click
)
def on_add_users_btn_click(self, e):
users = Users(self.page, self.dashboard)
self.dashboard.placeholder.content = users.build()
self.dashboard.placeholder.update()
def _auth_headers(self): def _auth_headers(self):
"""Build Authorization header from client storage robustly (web/desktop).""" """Build Authorization header from client storage robustly (web/desktop)."""
t = self.page.client_storage.get("token") t = self.page.client_storage.get("token")
@@ -408,12 +421,22 @@ class ProfilePage:
self.message.color = ft.Colors.RED self.message.color = ft.Colors.RED
self.page.update() self.page.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:
return False
def build(self): def build(self):
self.populate_user_data() self.populate_user_data()
return ft.Container( return ft.Container(
content=ft.Column( content=ft.Column(
[ [
ft.Text("User Profile", size=24, weight=ft.FontWeight.BOLD), ft.Text("Company Profile", size=24, weight=ft.FontWeight.BOLD),
ft.Row( ft.Row(
[ [
ft.Column( ft.Column(
@@ -425,7 +448,10 @@ class ProfilePage:
self.smtp_user, self.smtp_user,
self.smtp_host, self.smtp_host,
self.smtp_port, self.smtp_port,
ft.Row([self.edit_button, self.save_button, self.cancel_button]) ft.Text(),
ft.Row([self.edit_button, self.save_button, self.cancel_button]),
ft.Divider(),
self.add_users,ft.Text()
], ],
horizontal_alignment=ft.CrossAxisAlignment.CENTER, horizontal_alignment=ft.CrossAxisAlignment.CENTER,
width=250 width=250
@@ -455,4 +481,30 @@ class ProfilePage:
], ],
scroll=ft.ScrollMode.ADAPTIVE scroll=ft.ScrollMode.ADAPTIVE
), ),
) if self.get_client_access() else ft.Container(
content=ft.Column(
[
ft.Row(
[
ft.Text("Company Profile", 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

@@ -326,6 +326,18 @@ class ReportPage:
self.total.value = f"Total: {total}" self.total.value = f"Total: {total}"
self.total.update() 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): def build(self):
return ft.Container( return ft.Container(
content=ft.Column( content=ft.Column(
@@ -386,4 +398,30 @@ class ReportPage:
alignment=ft.MainAxisAlignment.START, alignment=ft.MainAxisAlignment.START,
), ),
expand=True expand=True
) if self.get_client_access() else ft.Container(
content=ft.Column(
[
ft.Row(
[
ft.Text("Reports", 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

@@ -93,6 +93,16 @@ class Subscription:
self.current_subscription_status.value = self.status[status] self.current_subscription_status.value = self.status[status]
self.current_subscription_status.update() self.current_subscription_status.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:
return False
def build(self): def build(self):
return ft.Container( return ft.Container(
content=ft.Column( content=ft.Column(
@@ -244,4 +254,30 @@ class Subscription:
spacing=50 spacing=50
), ),
expand=True expand=True
) if self.get_client_access() else ft.Container(
content=ft.Column(
[
ft.Row(
[
ft.Text("Subscription", 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

@@ -0,0 +1,60 @@
import flet as ft
import requests
import time
from config import API_BASE_URL
import re
from pages.dashboard_page import DashboardPage
class TemporaryPassword:
def __init__(self, page: ft.Page):
self.page = page
self.password = ft.TextField(label="Password", password=True, can_reveal_password=True, width=300)
self.confirm_password = ft.TextField(label="Confirm Password", password=True, can_reveal_password=True, width=300)
self.message = ft.Text("", color=ft.Colors.RED, text_align=ft.TextAlign.CENTER)
def on_save_btn_click(self, e):
# Password strength validation
if len(self.password.value) < 8 or not re.search(r"[A-Z]", self.password.value) or not re.search(r"[0-9]", self.password.value):
self.message.value = "Password must be at least 8 characters long and include a number and a capital letter"
self.message.update()
return
# Placeholder for register logic
if self.password.value != self.confirm_password.value:
self.message.value = "Passwords do not match"
self.message.update()
return
try:
token = self.page.client_storage.get("token")
headers = {"Authorization": f"Bearer {token}"}
response = requests.post(f"{API_BASE_URL}/auth/temporary_password", headers=headers, json={
"password": self.password.value
})
if response.status_code == 200:
self.message.color = ft.Colors.GREEN
self.message.value = "Password updated!\nYou will be redirected to Dashboard"
self.message.update()
time.sleep(3)
# Proceed to Dashboard
self.page.go("/dashboard")
else:
self.message.color = ft.Colors.RED
self.message.value = response.json().get("message", "Password update fail!")
self.message.update()
except Exception as e:
print(e)
def build(self):
return ft.Container(
content=ft.Column(
[
ft.Text("This is your first login,\nplease change your password!", weight=ft.FontWeight.BOLD, size=20, text_align=ft.TextAlign.CENTER),
self.password,
self.confirm_password,
self.message,
ft.Button("Save", width=150, on_click=self.on_save_btn_click)
],
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
alignment=ft.MainAxisAlignment.CENTER,
spacing=20,
)
)

View File

@@ -245,6 +245,18 @@ class TransportersPage:
except Exception as e: except Exception as e:
print("Error loading subscription:", e) print("Error loading subscription:", e)
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()['transporters'] == 1 else False
def build(self): def build(self):
self.transporter_list = ft.Column(spacing=10, expand=True, scroll=ft.ScrollMode.ADAPTIVE,) self.transporter_list = ft.Column(spacing=10, expand=True, scroll=ft.ScrollMode.ADAPTIVE,)
self.refresh() self.refresh()
@@ -270,4 +282,30 @@ class TransportersPage:
], ],
alignment=ft.MainAxisAlignment.START, alignment=ft.MainAxisAlignment.START,
) if self.get_client_access() else ft.Container(
content=ft.Column(
[
ft.Row(
[
ft.Text("Transporters", 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,6 +1,7 @@
import flet as ft import flet as ft
import requests import requests
from config import API_BASE_URL from config import API_BASE_URL
from pages.temporary_password_page import TemporaryPassword
class TwoFactorAuth: class TwoFactorAuth:
def __init__(self, page: ft.Page, email: str, login, auth): def __init__(self, page: ft.Page, email: str, login, auth):
@@ -35,10 +36,30 @@ class TwoFactorAuth:
print('Admin Logged In') print('Admin Logged In')
self.page.go("/admin") self.page.go("/admin")
else: else:
self.success_text.value = "Verification successful. You are now logged in." ui = requests.get(
self.error_text.value = "" f"{API_BASE_URL}/auth/me",
self.page.update() headers={"Authorization": f"Bearer {token}"},
self.page.go("/dashboard") # Change this to your main page timeout=10,
)
if ui.status_code == 200:
uj = ui.json()
try:
logo_filename = uj.get("logo_filename", "")
if logo_filename:
self.page.client_storage.set("custom_logo", logo_filename)
except Exception:
pass
if uj['temporary_password'] == 1:
self.auth.placeholder.content.clean()
temp_passwd_page = TemporaryPassword(self.page)
self.auth.placeholder.content = temp_passwd_page.build()
self.auth.placeholder.update()
else:
self.success_text.value = "Verification successful. You are now logged in."
self.error_text.value = ""
self.page.update()
self.page.go("/dashboard")
else: else:
self.error_text.value = "Invalid or expired code." self.error_text.value = "Invalid or expired code."
self.success_text.value = "" self.success_text.value = ""

View File

@@ -0,0 +1,274 @@
import flet as ft
import requests
from config import API_BASE_URL
class Users:
def __init__(self, page: ft.Page, dashboard):
self.page = page
self.dashboard = dashboard
self.users = self.get_users()
self.selected_user = None
self.users_list = ft.ListView(
controls=self.create_users_list(self.users, self.on_edit_btn_click, self.on_delete_btn_click),
spacing=10,
expand=True
)
self.add_user_dialog = ft.AlertDialog(
title="Add Company User",
content=ft.Column(
[
ft.TextField(label="Name"),
ft.TextField(label="Email"),
ft.TextField(label="Temporary Password", password=True, can_reveal_password=True),
ft.Text("Give user access to add and modify:"),
ft.Checkbox(label="Orders In"),
ft.Checkbox(label="Order Out"),
ft.Checkbox(label="Addresses"),
ft.Checkbox(label="Clients"),
ft.Checkbox(label="Transporters"),
ft.Checkbox(label="Report"),
ft.Text("", color=ft.Colors.RED)
],
width=600,
height=500
),
actions=[
ft.TextButton(
"Cancel",
width=100,
on_click=self.on_cancel_btn_click
),
ft.ElevatedButton(
"Save",
width=100,
on_click=self.on_save_btn_click
)
]
)
self.delete_dialog = ft.AlertDialog(
title=ft.Text("Delete User?"),
actions=[
ft.TextButton("Cancel", on_click=self.on_cancel_delete_btn_click, width=100),
ft.Button("Yes", on_click=self.on_confirm_delete_btn_click, width=100),
]
)
self.edit_user_dialog = ft.AlertDialog(
title=ft.Text(f"Edit User"),
content=ft.Column(
[
ft.Checkbox(label="Orders In"),
ft.Checkbox(label="Order Out"),
ft.Checkbox(label="Addresses"),
ft.Checkbox(label="Clients"),
ft.Checkbox(label="Transporters"),
ft.Checkbox(label="Report"),
],
width=400,
height=350
),
actions=[
ft.TextButton(
"Cancel",
width=100,
on_click=self.on_edit_cancel_btn_click
),
ft.ElevatedButton(
"Save",
width=100,
on_click=self.on_edit_save_btn_click
)
]
)
def on_edit_cancel_btn_click(self, e):
self.page.close(self.edit_user_dialog)
def on_edit_save_btn_click(self, e):
token = self.page.client_storage.get("token")
headers = {"Authorization": f"Bearer {token}"}
access_payload = {
'company_user_id':self.selected_user['id'],
'orders_in':1 if self.edit_user_dialog.content.controls[0].value else 0,
'orders_out':1 if self.edit_user_dialog.content.controls[1].value else 0,
'addresses':1 if self.edit_user_dialog.content.controls[2].value else 0,
'clients':1 if self.edit_user_dialog.content.controls[3].value else 0,
'transporters':1 if self.edit_user_dialog.content.controls[4].value else 0,
'report':1 if self.edit_user_dialog.content.controls[5].value else 0
}
response = requests.put(f"{API_BASE_URL}/company_user/update_access", headers=headers, json=access_payload)
#set to default
self.selected_user = None
self.page.close(self.edit_user_dialog)
self.edit_user_dialog.content.controls[0].value = False
self.edit_user_dialog.content.controls[1].value = False
self.edit_user_dialog.content.controls[2].value = False
self.edit_user_dialog.content.controls[3].value = False
self.edit_user_dialog.content.controls[4].value = False
self.edit_user_dialog.content.controls[5].value = False
self.edit_user_dialog.update()
def on_confirm_delete_btn_click(self, e):
self.page.close(self.delete_dialog)
try:
token = self.page.client_storage.get("token")
headers = {"Authorization": f"Bearer {token}"}
delete_payload = {
'user_id':self.selected_user['id']
}
response = requests.post(f"{API_BASE_URL}/admin/users/deactivate", headers=headers, json=delete_payload)
except Exception as e:
print("Error deleting company users:", e)
users = self.get_users()
self.users_list.controls.clear()
self.users_list.controls = self.create_users_list(users, self.on_edit_btn_click, self.on_delete_btn_click)
self.users_list.update()
self.selected_user = None
def on_cancel_delete_btn_click(self, e):
self.page.close(self.delete_dialog)
def add_new_user(self, e):
self.page.open(self.add_user_dialog)
def on_edit_btn_click(self, item):
self.selected_user = item
access = self.get_user_access(item['id'])
self.edit_user_dialog.content.controls[0].value = True if access['orders_in']==1 else False
self.edit_user_dialog.content.controls[1].value = True if access['orders_out']==1 else False
self.edit_user_dialog.content.controls[2].value = True if access['destinations']==1 else False
self.edit_user_dialog.content.controls[3].value = True if access['clients']==1 else False
self.edit_user_dialog.content.controls[4].value = True if access['transporters']==1 else False
self.edit_user_dialog.content.controls[5].value = True if access['report']==1 else False
self.page.open(self.edit_user_dialog)
def get_user_access(self, id):
try:
token = self.page.client_storage.get("token")
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{API_BASE_URL}/company_user/access/{id}", headers=headers)
return response.json()
except Exception as e:
print("Error loading company user access:", e)
def on_delete_btn_click(self, item):
self.selected_user = item
self.page.open(self.delete_dialog)
def on_save_btn_click(self, e):
self.page.close(self.add_user_dialog)
try:
token = self.page.client_storage.get("token")
headers = {"Authorization": f"Bearer {token}"}
create_payload = {
'name':self.add_user_dialog.content.controls[0].value,
'email':self.add_user_dialog.content.controls[1].value,
'company_id':self.page.session.get("user_id"),
'password':self.add_user_dialog.content.controls[2].value
}
response = requests.post(f"{API_BASE_URL}/company_user/register_company_user", headers=headers, json=create_payload)
company_user_id = response.json()['company_user_id'] if response.status_code == 201 else self.show_error_mesage
if company_user_id:
access_payload = {
'company_user_id':company_user_id,
'orders_in':1 if self.add_user_dialog.content.controls[4].value else 0,
'orders_out':1 if self.add_user_dialog.content.controls[5].value else 0,
'addresses':1 if self.add_user_dialog.content.controls[6].value else 0,
'clients':1 if self.add_user_dialog.content.controls[7].value else 0,
'transporters':1 if self.add_user_dialog.content.controls[8].value else 0,
'report':1 if self.add_user_dialog.content.controls[9].value else 0
}
response = requests.post(f"{API_BASE_URL}/company_user/access", headers=headers, json=access_payload)
except Exception as e:
print("Error loading company users:", e)
users = self.get_users()
self.users_list.controls.clear()
self.users_list.controls = self.create_users_list(users, self.on_edit_btn_click, self.on_delete_btn_click)
self.users_list.update()
#set dialog to default valuse:
self.add_user_dialog.content.controls[0].value = ''
self.add_user_dialog.content.controls[1].value = ''
self.add_user_dialog.content.controls[2].value = ''
self.add_user_dialog.content.controls[4].value = False
self.add_user_dialog.content.controls[5].value = False
self.add_user_dialog.content.controls[6].value = False
self.add_user_dialog.content.controls[7].value = False
self.add_user_dialog.content.controls[8].value = False
self.add_user_dialog.content.controls[9].value = False
self.add_user_dialog.content.controls[10].value = ""
self.add_user_dialog.update()
def show_error_mesage(self):
self.add_user_dialog.content.controls[10].value = "A user with this email has been already created, to reactivate it send a request at office@ordergo.eu"
self.add_user_dialog.update()
def on_cancel_btn_click(self, e):
self.page.close(self.add_user_dialog)
def get_users(self):
try:
user_id = self.page.session.get("user_id")
users = []
token = self.page.client_storage.get("token")
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{API_BASE_URL}/admin/users/company_users", headers=headers)
if response.status_code == 200:
all_users=response.json()
for user in all_users:
if user['company_id'] == user_id and user['active'] == 1:
users.append(user)
return users
else:
return []
except Exception as e:
print("Error loading company users:", e)
def create_users_list(self, items, on_click_handler, on_click_handler2):
return [
ft.Container(
content=ft.Row(
[
ft.Column(
[
ft.Text(item['name'], expand=True, weight=ft.FontWeight.BOLD),
ft.Text(item['email'], size=12)
]
),
ft.Row(
[
ft.Button("Edit", on_click=lambda e, id=item: on_click_handler(id), width=100),
ft.FilledButton("Delete", on_click=lambda e, id=item: on_click_handler2(id), width=100, bgcolor=ft.Colors.RED)
]
)
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
),
width=300,
bgcolor=ft.Colors.BLUE_50,
padding=10,
border_radius=8,
border=ft.border.all(1, ft.Colors.GREY_300),
)
for item in items
]
def build(self):
return ft.Container(
content= ft.Column(
[
ft.Row(
[
ft.Text("Users", size=24, weight=ft.FontWeight.BOLD),
ft.Button("Add User", icon=ft.Icons.ADD, on_click=self.add_new_user)
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
),
self.users_list
],
expand=True
),
expand=True
)

View File

@@ -188,6 +188,12 @@ class ViewOrdersIn:
input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string=""), input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string=""),
value=self.order['received_price'] value=self.order['received_price']
) )
self.expenses = ft.TextField(
label="Expenses",
keyboard_type=ft.KeyboardType.NUMBER,
input_filter=ft.InputFilter(allow=True, regex_string=r"^[0-9]*\.?[0-9]*$", replacement_string=""),
value=self.order['expenses']
)
self.error_message = ft.Text(color = ft.Colors.RED) self.error_message = ft.Text(color = ft.Colors.RED)
@@ -280,6 +286,18 @@ class ViewOrdersIn:
self.unloading_query = addresses self.unloading_query = addresses
self.unloading.controls = self.create_unloading_list(addresses, self.on_delete_unloading_address_btn_click) self.unloading.controls = self.create_unloading_list(addresses, self.on_delete_unloading_address_btn_click)
self.file_picker = ft.FilePicker(on_result=self.on_file_result)
self.page.overlay.append(self.file_picker)
self.upload_order_btn = ft.Button(
"Upload",
icon=ft.Icons.UPLOAD_FILE,
width=150,
on_click=self.on_upload_document_btn_click
)
self.filename = ft.Text(value=self.order['file'])
def on_go_back_btn_click(self, e): def on_go_back_btn_click(self, e):
self.dashboard.placeholder.content = self.archive.build() self.dashboard.placeholder.content = self.archive.build()
self.dashboard.placeholder.update() self.dashboard.placeholder.update()
@@ -447,10 +465,10 @@ class ViewOrdersIn:
self.loading_error_message.value = "Please select loading point!" self.loading_error_message.value = "Please select loading point!"
self.loading_error_message.update() self.loading_error_message.update()
return return
if self.loading_informations.value == None or len(self.loading_informations.value) == 0: # if self.loading_informations.value == None or len(self.loading_informations.value) == 0:
self.loading_error_message.value = "Add loading informations!" # self.loading_error_message.value = "Add loading informations!"
self.loading_error_message.update() # self.loading_error_message.update()
return # return
if self.loading_date.value == None or len(str(self.loading_date.value)) == 0: if self.loading_date.value == None or len(str(self.loading_date.value)) == 0:
self.loading_error_message.value = "Add loading date!" self.loading_error_message.value = "Add loading date!"
self.loading_error_message.update() self.loading_error_message.update()
@@ -480,6 +498,29 @@ class ViewOrdersIn:
self.loading_error_message.value = "All fields of the loading address are required." self.loading_error_message.value = "All fields of the loading address are required."
self.loading_error_message.update() self.loading_error_message.update()
def on_upload_document_btn_click(self, e):
# Ensure FilePicker is attached to the page (Portainer/Flet sometimes reuses page objects)
if self.file_picker not in getattr(self.page.overlay, "controls", []):
self.page.overlay.append(self.file_picker)
self.page.update()
self.file_picker.pick_files(
allow_multiple=False,
allowed_extensions=["png", "jpg", "jpeg", "pdf"]
)
def on_file_result(self, e: ft.FilePickerResultEvent):
if not e.files:
return
file = e.files[0]
# Build a safe filename: order_in_YYYYmmdd_HHMMSS_userid_originalname
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
user_id = self.page.session.get("user_id")
new_filename = f"order_in_{timestamp}_{user_id}_{file.name}"
upload_url = self.page.get_upload_url(new_filename, 1000)
self.file_picker.upload([ft.FilePickerUploadFile(file.name, upload_url=upload_url)])
self.filename.value = new_filename
self.filename.update()
def on_searching_unloading_address(self, e): def on_searching_unloading_address(self, e):
query = e.control.value.lower() query = e.control.value.lower()
self.filtered_addresses_ul = [a for a in self.all_addresses if query in a["name"].lower()] self.filtered_addresses_ul = [a for a in self.all_addresses if query in a["name"].lower()]
@@ -565,10 +606,10 @@ class ViewOrdersIn:
self.unloading_error_message.value = "Please select unloading point!" self.unloading_error_message.value = "Please select unloading point!"
self.unloading_error_message.update() self.unloading_error_message.update()
return return
if self.unloading_informations.value == None or len(self.unloading_informations.value) == 0: # if self.unloading_informations.value == None or len(self.unloading_informations.value) == 0:
self.unloading_error_message.value = "Add unloading informations!" # self.unloading_error_message.value = "Add unloading informations!"
self.unloading_error_message.update() # self.unloading_error_message.update()
return # return
if self.unloading_date.value == None or len(str(self.unloading_date.value)) == 0: if self.unloading_date.value == None or len(str(self.unloading_date.value)) == 0:
self.unloading_error_message.value = "Add unloading date!" self.unloading_error_message.value = "Add unloading date!"
self.unloading_error_message.update() self.unloading_error_message.update()
@@ -782,7 +823,9 @@ class ViewOrdersIn:
'trailer_reg_number': self.trailer_reg_number.value, 'trailer_reg_number': self.trailer_reg_number.value,
'received_price': self.received_price.value, 'received_price': self.received_price.value,
'loading_addresses': loading_addresses, 'loading_addresses': loading_addresses,
'unloading_addresses': unloading_addresses 'unloading_addresses': unloading_addresses,
'file':self.filename.value,
'expenses': self.expenses.value,
} }
#print(saved_data) #print(saved_data)
if self.order_number.value == None or len(self.order_number.value)==0: if self.order_number.value == None or len(self.order_number.value)==0:
@@ -904,7 +947,9 @@ class ViewOrdersIn:
ft.Row( ft.Row(
[ [
ft.Text("Number", size=18, weight=ft.FontWeight.BOLD), ft.Text("Number", size=18, weight=ft.FontWeight.BOLD),
self.order_number self.order_number,
self.upload_order_btn,
self.filename
] ]
), ),
ft.Row( ft.Row(
@@ -998,6 +1043,7 @@ class ViewOrdersIn:
alignment=ft.MainAxisAlignment.START alignment=ft.MainAxisAlignment.START
), ),
self.received_price, self.received_price,
self.expenses,
], ],
expand=2.5 expand=2.5
) )
@@ -1037,7 +1083,7 @@ class ViewOrdersIn:
[ [
self.loading_date, self.loading_date,
ft.ElevatedButton( ft.ElevatedButton(
"Pick date", "Select date",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.DatePicker( ft.DatePicker(
@@ -1054,7 +1100,7 @@ class ViewOrdersIn:
[ [
self.loading_hour, self.loading_hour,
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - Start", "Select hour - Start",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(
@@ -1067,7 +1113,7 @@ class ViewOrdersIn:
), ),
), ),
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - End", "Select hour - End",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(
@@ -1130,7 +1176,7 @@ class ViewOrdersIn:
[ [
self.unloading_date, self.unloading_date,
ft.ElevatedButton( ft.ElevatedButton(
"Pick date", "Select date",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.DatePicker( ft.DatePicker(
@@ -1147,7 +1193,7 @@ class ViewOrdersIn:
[ [
self.unloading_hour, self.unloading_hour,
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - Start", "Select hour - Start",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(
@@ -1160,7 +1206,7 @@ class ViewOrdersIn:
), ),
), ),
ft.ElevatedButton( ft.ElevatedButton(
"Pick hour - End", "Select hour - End",
icon=ft.Icons.CALENDAR_MONTH, icon=ft.Icons.CALENDAR_MONTH,
on_click=lambda e: self.page.open( on_click=lambda e: self.page.open(
ft.TimePicker( ft.TimePicker(

View File

@@ -5,13 +5,21 @@ from models.user import Users
admin_user_bp = Blueprint("admin_user", __name__, url_prefix="/admin/users") admin_user_bp = Blueprint("admin_user", __name__, url_prefix="/admin/users")
# Get all users with role "user" # Get all users with role "user"
@admin_user_bp.route("", methods=["GET"]) @admin_user_bp.route("/", methods=["GET"])
@jwt_required() @jwt_required()
def get_all_users(): def get_all_users():
users_model = Users() users_model = Users()
users = users_model.get_all_users_with_role("user") users = users_model.get_all_users_with_role("user")
return jsonify(users), 200 return jsonify(users), 200
# Get all users with role "user"
@admin_user_bp.route("/company_users", methods=["GET"])
@jwt_required()
def get_all_company_users():
users_model = Users()
users = users_model.get_all_users_with_role("company_user")
return jsonify(users), 200
# Get a single user by ID # Get a single user by ID
@admin_user_bp.route("/<int:user_id>", methods=["GET"]) @admin_user_bp.route("/<int:user_id>", methods=["GET"])
@jwt_required() @jwt_required()
@@ -38,3 +46,21 @@ def update_user():
users_model.update_user(data) users_model.update_user(data)
return jsonify({"message": "User updated successfully."}), 200 return jsonify({"message": "User updated successfully."}), 200
# Deactivate
@admin_user_bp.route("/deactivate", methods=["POST"])
@jwt_required()
def deactivate_user():
if not request.is_json:
print("Content-Type received:", request.content_type)
return jsonify({"error": "Invalid content type, must be application/json"}), 415
data = request.get_json()
user_id = data.get("user_id")
if not user_id:
return jsonify({"error": "Missing user_id"}), 400
users_model = Users()
users_model.deactivate_user(user_id)
return jsonify({"message": "User updated successfully."}), 200

View File

@@ -12,6 +12,7 @@ from routes.report import report_bp
from admin.subscription import admin_subscription_bp from admin.subscription import admin_subscription_bp
from routes.subscription import subscription_bp from routes.subscription import subscription_bp
from admin.tenants import admin_user_bp from admin.tenants import admin_user_bp
from routes.company_user import company_user_bp
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from models.subscription import Subscription from models.subscription import Subscription
@@ -69,7 +70,7 @@ app.register_blueprint(report_bp, url_prefix="/report")
app.register_blueprint(admin_subscription_bp) app.register_blueprint(admin_subscription_bp)
app.register_blueprint(subscription_bp) app.register_blueprint(subscription_bp)
app.register_blueprint(admin_user_bp) app.register_blueprint(admin_user_bp)
app.register_blueprint(company_user_bp)
def update_subscription_statuses_job(): def update_subscription_statuses_job():
print("[Scheduler] Running daily subscription status check...") print("[Scheduler] Running daily subscription status check...")

Binary file not shown.

View File

@@ -0,0 +1,81 @@
from datetime import datetime
from database import get_connection, is_postgres
class CompanyUsers:
def __init__(self):
self.ph = "%s" if is_postgres() else "?"
def access_to_dict(self, row):
access = {
'id': row[0],
'company_user_id': row[1],
'clients': row[2],
'transporters': row[3],
'destinations': row[4],
'orders_in': row[5],
'orders_out': row[6],
'report':row[7],
'created_at': row[8]
}
return access
def insert_company_user_access(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 company_user_access (
company_user_id, clients, transporters, destinations, orders_in, orders_out, report, created_at
) VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}) {returning}
"""
cursor.execute(
query,
(
access_data['company_user_id'],
access_data['clients'],
access_data['transporters'],
access_data['destinations'],
access_data['orders_in'],
access_data['orders_out'],
access_data['report'],
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_access_by_company_user_id(self, company_user_id):
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute(f"SELECT * FROM company_user_access WHERE company_user_id = {self.ph}", (company_user_id,))
row = cursor.fetchone()
return self.access_to_dict(row) if row else None
def update_company_user_access(self, data):
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
f"""
UPDATE company_user_access
SET clients = {self.ph}, transporters = {self.ph}, destinations = {self.ph}, orders_in = {self.ph}, orders_out = {self.ph}, report = {self.ph}
WHERE company_user_id = {self.ph}
""",
(
data['clients'],
data['transporters'],
data['destinations'],
data['orders_in'],
data['orders_out'],
data['report'],
data['company_user_id']
)
)
if hasattr(conn, "commit"):
conn.commit()

View File

@@ -19,6 +19,8 @@ class OrdersIn:
"trailer_reg_number": row[8], "trailer_reg_number": row[8],
"received_price": row[9], "received_price": row[9],
"created_at": row[10], "created_at": row[10],
"file":row[11],
"expenses": row[12],
} }
def order_point_to_dict(self, row): def order_point_to_dict(self, row):
@@ -41,8 +43,8 @@ class OrdersIn:
f""" f"""
INSERT INTO orders_in INSERT INTO orders_in
(user_id, client_id, products_description, received_price, order_number, (user_id, client_id, products_description, received_price, order_number,
ldb_quantity, kg_quantity, track_reg_number, trailer_reg_number, created_at) 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}){returning} 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}
""", """,
( (
data["user_id"], data["user_id"],
@@ -55,6 +57,8 @@ class OrdersIn:
data["track_reg_number"], data["track_reg_number"],
data["trailer_reg_number"], data["trailer_reg_number"],
created_at, created_at,
data["file"],
data["expenses"]
), ),
) )
new_id = cur.fetchone()[0] if is_postgres() else getattr(cur, "lastrowid", None) new_id = cur.fetchone()[0] if is_postgres() else getattr(cur, "lastrowid", None)
@@ -71,7 +75,7 @@ class OrdersIn:
user_id = {self.ph}, client_id = {self.ph}, products_description = {self.ph}, user_id = {self.ph}, client_id = {self.ph}, products_description = {self.ph},
received_price = {self.ph}, order_number = {self.ph}, received_price = {self.ph}, order_number = {self.ph},
ldb_quantity = {self.ph}, kg_quantity = {self.ph}, track_reg_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}, file_name = {self.ph}, expenses = {self.ph}
WHERE id = {self.ph} WHERE id = {self.ph}
""", """,
( (
@@ -84,6 +88,8 @@ class OrdersIn:
data["kg_quantity"], data["kg_quantity"],
data["track_reg_number"], data["track_reg_number"],
data["trailer_reg_number"], data["trailer_reg_number"],
data['file'],
data['expenses'],
data["id"], data["id"],
), ),
) )

View File

@@ -22,7 +22,10 @@ class Users:
'created_at': row[12], 'created_at': row[12],
'otp_code': row[13], 'otp_code': row[13],
'otp_expiration': row[14], 'otp_expiration': row[14],
'user_role': row[15] 'user_role': row[15],
'company_id': row[16],
'active': row[17],
'temporary_password': row[18]
} }
return user return user
@@ -71,6 +74,28 @@ class Users:
conn.commit() conn.commit()
return inserted_id return inserted_id
def insert_company_user(self, name, email, password_hash, company_id):
created_at = datetime.now().isoformat()
company_id = company_id
user_role = 'company_user'
with get_connection() as conn:
cursor = conn.cursor()
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}
"""
cursor.execute(query, (name, email, password_hash, created_at, user_role, company_id))
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 update_user_otp(self, user_id, otp_code, expiration): def update_user_otp(self, user_id, otp_code, expiration):
with get_connection() as conn: with get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
@@ -192,3 +217,31 @@ class Users:
) )
if hasattr(conn, "commit"): if hasattr(conn, "commit"):
conn.commit() conn.commit()
def deactivate_user(self, user_id):
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
f"""
UPDATE users
SET active = {self.ph}
WHERE id = {self.ph}
""",
(0, user_id)
)
if hasattr(conn, "commit"):
conn.commit()
def update_temp_pass(self, user_id):
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
f"""
UPDATE users
SET temporary_passwrd = {self.ph}
WHERE id = {self.ph}
""",
(0, user_id)
)
if hasattr(conn, "commit"):
conn.commit()

View File

@@ -9,6 +9,7 @@ import os
from datetime import timezone from datetime import timezone
from models.user import Users from models.user import Users
from utils.welcome_email import WelcomeMessage
auth_bp = Blueprint("auth", __name__) auth_bp = Blueprint("auth", __name__)
@@ -30,8 +31,10 @@ def register():
password_hash = generate_password_hash(password) password_hash = generate_password_hash(password)
users.insert_user(name, email, password_hash) users.insert_user(name, email, password_hash)
return jsonify({"message": "User registered successfully!"}), 201 welcome_message = WelcomeMessage(name, email)
welcome_message.send_email()
return jsonify({"message": "User registered successfully!"}), 201
@auth_bp.route("/login", methods=["POST"]) @auth_bp.route("/login", methods=["POST"])
def login(): def login():
@@ -47,6 +50,9 @@ def login():
if not user or not check_password_hash(user["password_hash"], password): if not user or not check_password_hash(user["password_hash"], password):
return jsonify({"error": "Invalid credentials"}), 401 return jsonify({"error": "Invalid credentials"}), 401
if user["active"] != 1:
return jsonify({"error": "Inactive user"}), 401
otp_code = str(random.randint(100000, 999999)) otp_code = str(random.randint(100000, 999999))
expiration = datetime.datetime.now(timezone.utc) + datetime.timedelta(minutes=10) expiration = datetime.datetime.now(timezone.utc) + datetime.timedelta(minutes=10)
users.update_user_otp(user["id"], otp_code, expiration) users.update_user_otp(user["id"], otp_code, expiration)
@@ -188,7 +194,8 @@ def me():
"terms": user["terms"], "terms": user["terms"],
"first_order_number": user["first_order_number"], "first_order_number": user["first_order_number"],
"created_at": user["created_at"], "created_at": user["created_at"],
"user_role": user["user_role"] "user_role": user["user_role"],
"temporary_password": user["temporary_password"]
}), 200 }), 200
@@ -202,3 +209,16 @@ def validate_token():
if not user: if not user:
return jsonify({"error": "User not found"}), 404 return jsonify({"error": "User not found"}), 404
return jsonify({"message": "Token is valid"}), 200 return jsonify({"message": "Token is valid"}), 200
@auth_bp.route("/temporary_password", methods=["POST"])
@jwt_required()
def change_passwd():
data = request.get_json()
if not data:
return jsonify({"error": "Password not found"}), 404
users = Users()
user_id = get_jwt_identity()
new_password_hash = generate_password_hash(data['password'])
users.update_user_password(user_id, new_password_hash)
users.update_temp_pass(user_id)
return jsonify({"message": "Password has been updated successfully."}), 200

View File

@@ -1,5 +1,6 @@
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
from models.client import Clients from models.client import Clients
from models.user import Users
from flask_jwt_extended import jwt_required, get_jwt_identity from flask_jwt_extended import jwt_required, get_jwt_identity
@@ -10,6 +11,10 @@ clients_bp = Blueprint("clients", __name__, url_prefix="/clients")
def list_clients(): def list_clients():
clients_db = Clients() clients_db = Clients()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
clients = clients_db.get_all_by_user(user_id) clients = clients_db.get_all_by_user(user_id)
return jsonify(clients), 200 return jsonify(clients), 200
@@ -19,6 +24,10 @@ def create_client():
clients_db = Clients() clients_db = Clients()
user_id = get_jwt_identity() user_id = get_jwt_identity()
data = request.get_json() data = request.get_json()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
client_id = clients_db.create( client_id = clients_db.create(
user_id=user_id, user_id=user_id,
name=data["name"], name=data["name"],

View File

@@ -0,0 +1,118 @@
from flask import Blueprint, request, jsonify
from models.user import Users
from utils.welcome_email import WelcomeMessage
from werkzeug.security import generate_password_hash, check_password_hash
from flask_jwt_extended import jwt_required, get_jwt_identity
from models.company_users import CompanyUsers
company_user_bp = Blueprint("company_user", __name__, url_prefix="/company_user")
@company_user_bp.route('/access/<int:id>', methods=["GET"])
@jwt_required()
def get_access(id):
company_user_id = id
if not company_user_id:
return jsonify({"error":"Missing required fields"}), 400
company_users = CompanyUsers()
access = company_users.get_access_by_company_user_id(company_user_id)
return jsonify(access)
@company_user_bp.route("/register_company_user", methods=["POST"])
@jwt_required()
def register_company_user():
users = Users()
data = request.get_json()
name = data.get("name")
email = data.get("email")
company_id = data.get("company_id")
password = data.get("password")
if not name or not email or not password or not company_id:
return jsonify({"error": "Missing required fields"}), 400
existing_user = users.get_user_by_email(email)
if existing_user:
return jsonify({"error": "User already exists"}), 409
password_hash = generate_password_hash(password)
company_user_id = users.insert_company_user(name, email, password_hash, company_id)
welcome_message = WelcomeMessage(name, email)
welcome_message.send_email()
return jsonify({"message": "User registered successfully!", "company_user_id":company_user_id}), 201
@company_user_bp.route('/access', methods=["POST"])
@jwt_required()
def create_access():
data = request.get_json()
company_user_id = data.get('company_user_id')
orders_in = data.get("orders_in")
orders_out = data.get("orders_out")
addresses = data.get("addresses")
clients = data.get("clients")
transporters = data.get("transporters")
report = data.get("report")
company_users = CompanyUsers()
# Use explicit None checks so that False/0 values are allowed
if (
company_user_id is None or
orders_in is None or
orders_out is None or
addresses is None or
clients is None or
transporters is None or
report is None
):
return jsonify({"error": "Missing required fields"}), 400
access_data = {
'company_user_id': company_user_id,
'clients': clients,
'transporters': transporters,
'destinations': addresses,
'orders_in': orders_in,
'orders_out': orders_out,
'report': report
}
company_users.insert_company_user_access(access_data)
return jsonify({"message": "Company User access inserted!"})
@company_user_bp.route('/update_access', methods=["PUT"])
@jwt_required()
def update_access():
data = request.get_json()
company_user_id = data.get('company_user_id')
orders_in = data.get("orders_in")
orders_out = data.get("orders_out")
addresses = data.get("addresses")
clients = data.get("clients")
transporters = data.get("transporters")
report = data.get("report")
company_users = CompanyUsers()
# Use explicit None checks so that False/0 values are allowed
if (
company_user_id is None or
orders_in is None or
orders_out is None or
addresses is None or
clients is None or
transporters is None or
report is None
):
return jsonify({"error": "Missing required fields"}), 400
access_data = {
'company_user_id': company_user_id,
'clients': clients,
'transporters': transporters,
'destinations': addresses,
'orders_in': orders_in,
'orders_out': orders_out,
'report': report
}
company_users.update_company_user_access(access_data)
return jsonify({"message": "Company User access updated!"})

View File

@@ -2,6 +2,7 @@ from flask import Blueprint, request, jsonify
from models.destinations import Destinations from models.destinations import Destinations
from flask_jwt_extended import jwt_required, get_jwt_identity from flask_jwt_extended import jwt_required, get_jwt_identity
from utils.maps import AdressCoordinates from utils.maps import AdressCoordinates
from models.user import Users
destinations_bp = Blueprint("destinations", __name__, url_prefix="/destinations") destinations_bp = Blueprint("destinations", __name__, url_prefix="/destinations")
@@ -10,6 +11,10 @@ destinations_bp = Blueprint("destinations", __name__, url_prefix="/destinations"
def list_destinations(): def list_destinations():
destinations_db = Destinations() destinations_db = Destinations()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
destinations = destinations_db.get_all_by_user(user_id) destinations = destinations_db.get_all_by_user(user_id)
return jsonify([dict(d) for d in destinations]), 200 return jsonify([dict(d) for d in destinations]), 200
@@ -18,6 +23,10 @@ def list_destinations():
def create_destination(): def create_destination():
destinations_db = Destinations() destinations_db = Destinations()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
data = request.get_json() data = request.get_json()
destination_id = destinations_db.create(user_id, data.get("name"), data.get("address")) destination_id = destinations_db.create(user_id, data.get("name"), data.get("address"))
# coordinates = AdressCoordinates(data.get("address")) # coordinates = AdressCoordinates(data.get("address"))
@@ -33,6 +42,10 @@ def create_destination():
def update_destination(id): def update_destination(id):
destinations_db = Destinations() destinations_db = Destinations()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
data = request.get_json() data = request.get_json()
destinations_db.update(id, user_id, data.get("name"), data.get("address")) destinations_db.update(id, user_id, data.get("name"), data.get("address"))
coordinates = AdressCoordinates(data.get("address")) coordinates = AdressCoordinates(data.get("address"))

View File

@@ -17,6 +17,10 @@ orders_bp = Blueprint("orders", __name__, url_prefix="/orders")
@jwt_required() @jwt_required()
def create_order_route(): def create_order_route():
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
orders = OrdersOut() orders = OrdersOut()
incoming_data = request.json incoming_data = request.json
#here we need to first implement the order pdf #here we need to first implement the order pdf
@@ -83,6 +87,10 @@ def update_order_route(order_id):
orders = OrdersOut() orders = OrdersOut()
data = request.json data = request.json
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
order = orders.get_order_by_id(order_id) order = orders.get_order_by_id(order_id)
if not order: if not order:
return jsonify({"error": "Order not found"}), 404 return jsonify({"error": "Order not found"}), 404
@@ -150,6 +158,10 @@ def update_order_route(order_id):
def delete_order_route(order_id): def delete_order_route(order_id):
orders = OrdersOut() orders = OrdersOut()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
order = orders.get_order_by_id(order_id) order = orders.get_order_by_id(order_id)
if not order: if not order:
return jsonify({"error": "Order not found"}), 404 return jsonify({"error": "Order not found"}), 404
@@ -168,6 +180,10 @@ def delete_order_route(order_id):
def list_orders(): def list_orders():
orders = OrdersOut() orders = OrdersOut()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
try: try:
user_orders = orders.get_orders_by_user(user_id) user_orders = orders.get_orders_by_user(user_id)
#result = [{"id": order["id"], "order_number": order["order_number"]} for order in user_orders] #result = [{"id": order["id"], "order_number": order["order_number"]} for order in user_orders]
@@ -180,6 +196,10 @@ def list_orders():
def get_order(order_id): def get_order(order_id):
orders = OrdersOut() orders = OrdersOut()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
order = orders.get_order_by_id(order_id) order = orders.get_order_by_id(order_id)
points = orders.get_order_points_by_order(order['id']) points = orders.get_order_points_by_order(order['id'])
loading_points = [] loading_points = []
@@ -251,6 +271,10 @@ def cancel_order(order_id):
orders = OrdersOut() orders = OrdersOut()
order = orders.get_order_by_id(order_id) order = orders.get_order_by_id(order_id)
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
pdf_name = f'order_{user_id}_{order['order_number']}.pdf' pdf_name = f'order_{user_id}_{order['order_number']}.pdf'
cancel_order_pdf(pdf_name) cancel_order_pdf(pdf_name)
orders.cancel_order(order_id) orders.cancel_order(order_id)

View File

@@ -1,9 +1,12 @@
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify, abort
from flask_jwt_extended import jwt_required, get_jwt_identity from flask_jwt_extended import jwt_required, get_jwt_identity
from models.order_in import OrdersIn from models.order_in import OrdersIn
from models.transporters import Transporters from models.transporters import Transporters
from models.user import Users from models.user import Users
from datetime import datetime from datetime import datetime
import os
from flask import send_from_directory
import mimetypes
orders_in_bp = Blueprint("orders_in", __name__, url_prefix="/orders_in") orders_in_bp = Blueprint("orders_in", __name__, url_prefix="/orders_in")
@@ -11,6 +14,10 @@ orders_in_bp = Blueprint("orders_in", __name__, url_prefix="/orders_in")
@jwt_required() @jwt_required()
def create_order_in_route(): def create_order_in_route():
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
orders = OrdersIn() orders = OrdersIn()
incoming_data = request.json incoming_data = request.json
try: try:
@@ -25,7 +32,10 @@ def create_order_in_route():
'track_reg_number': incoming_data["track_reg_number"], 'track_reg_number': incoming_data["track_reg_number"],
'trailer_reg_number': incoming_data["trailer_reg_number"], 'trailer_reg_number': incoming_data["trailer_reg_number"],
'products_description': incoming_data["products_description"], 'products_description': incoming_data["products_description"],
'file': incoming_data['file'],
'expenses': incoming_data['expenses']
} }
#print(order_data)
order_id = orders.create_order(order_data) order_id = orders.create_order(order_data)
for address in incoming_data["loading_addresses"]: for address in incoming_data["loading_addresses"]:
@@ -60,6 +70,10 @@ def update_order_route(order_id):
orders = OrdersIn() orders = OrdersIn()
data = request.json data = request.json
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
order = orders.get_order_by_id(order_id) order = orders.get_order_by_id(order_id)
if not order: if not order:
return jsonify({"error": "Order in not found"}), 404 return jsonify({"error": "Order in not found"}), 404
@@ -77,6 +91,8 @@ def update_order_route(order_id):
"track_reg_number": data.get("track_reg_number", order["track_reg_number"]), "track_reg_number": data.get("track_reg_number", order["track_reg_number"]),
"trailer_reg_number": data.get("trailer_reg_number", order["trailer_reg_number"]), "trailer_reg_number": data.get("trailer_reg_number", order["trailer_reg_number"]),
"products_description": data.get("products_description", order["products_description"]), "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 "user_id":user_id
}) })
@@ -113,6 +129,10 @@ def update_order_route(order_id):
def delete_order_route(order_id): def delete_order_route(order_id):
orders = OrdersIn() orders = OrdersIn()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
order = orders.get_order_by_id(order_id) order = orders.get_order_by_id(order_id)
if not order: if not order:
return jsonify({"error": "Order in not found"}), 404 return jsonify({"error": "Order in not found"}), 404
@@ -131,6 +151,10 @@ def delete_order_route(order_id):
def list_orders(): def list_orders():
orders = OrdersIn() orders = OrdersIn()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
try: try:
user_orders = orders.get_orders_by_user(user_id) user_orders = orders.get_orders_by_user(user_id)
#result = [{"id": order["id"], "order_number": order["order_number"]} for order in user_orders] #result = [{"id": order["id"], "order_number": order["order_number"]} for order in user_orders]
@@ -143,6 +167,10 @@ def list_orders():
def get_order(order_id): def get_order(order_id):
orders = OrdersIn() orders = OrdersIn()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
order = orders.get_order_by_id(order_id) order = orders.get_order_by_id(order_id)
points = orders.get_order_points_by_order(order['id']) points = orders.get_order_points_by_order(order['id'])
loading_points = [] loading_points = []
@@ -161,3 +189,25 @@ def get_order(order_id):
if order["user_id"] != int(user_id): if order["user_id"] != int(user_id):
return jsonify({"error": "Unauthorized"}), 403 return jsonify({"error": "Unauthorized"}), 403
return jsonify(order), 200 return jsonify(order), 200
@orders_in_bp.route("/files/<path:filename>", methods=["GET"])
#@jwt_required()
def serve_order_pdf(filename):
try:
# Directory containing uploaded client files
uploads_dir = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "..", "client", "uploads")
)
# Security: prevent path traversal and ensure file exists
abs_file_path = os.path.abspath(os.path.join(uploads_dir, filename))
if not abs_file_path.startswith(uploads_dir + os.sep):
abort(404)
if not os.path.isfile(abs_file_path):
abort(404)
guessed_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
# send_from_directory expects the directory and the filename relative to it
return send_from_directory(uploads_dir, filename, mimetype=guessed_type, as_attachment=False)
except Exception as e:
print(e)
return jsonify({"error": "File not found"}), 404

View File

@@ -2,6 +2,7 @@ from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity from flask_jwt_extended import jwt_required, get_jwt_identity
from models.order_out import OrdersOut # Your plain SQL model from models.order_out import OrdersOut # Your plain SQL model
from datetime import datetime from datetime import datetime
from models.user import Users
report_bp = Blueprint("report", __name__, url_prefix="/report") report_bp = Blueprint("report", __name__, url_prefix="/report")
@@ -10,6 +11,10 @@ report_bp = Blueprint("report", __name__, url_prefix="/report")
def get_profit_report(): def get_profit_report():
try: try:
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
# Get filters from query params # Get filters from query params
date_from = request.args.get("date_from") date_from = request.args.get("date_from")
date_to = request.args.get("date_to") date_to = request.args.get("date_to")

View File

@@ -11,6 +11,10 @@ subscription_bp = Blueprint("subscription", __name__, url_prefix="/subscription"
@jwt_required() @jwt_required()
def get_subscription(): def get_subscription():
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
subscription_model = Subscription() subscription_model = Subscription()
subscriptions = subscription_model.get_by_user_id(user_id) subscriptions = subscription_model.get_by_user_id(user_id)
return jsonify(subscriptions), 200 return jsonify(subscriptions), 200

View File

@@ -1,6 +1,7 @@
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity from flask_jwt_extended import jwt_required, get_jwt_identity
from models.transporters import Transporters from models.transporters import Transporters
from models.user import Users
transporters_bp = Blueprint("transporters", __name__, url_prefix="/transporters") transporters_bp = Blueprint("transporters", __name__, url_prefix="/transporters")
@@ -8,6 +9,10 @@ transporters_bp = Blueprint("transporters", __name__, url_prefix="/transporters"
@jwt_required() @jwt_required()
def list_transporters(): def list_transporters():
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
transporters_db = Transporters() transporters_db = Transporters()
transporters = transporters_db.get_all_transporters_by_user(user_id) transporters = transporters_db.get_all_transporters_by_user(user_id)
return jsonify(transporters), 200 return jsonify(transporters), 200
@@ -18,6 +23,10 @@ def create_transporter():
transporters_db = Transporters() transporters_db = Transporters()
data = request.get_json() data = request.get_json()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
transporter_id = transporters_db.create_transporter( transporter_id = transporters_db.create_transporter(
user_id=user_id, user_id=user_id,
name=data.get("name"), name=data.get("name"),
@@ -36,6 +45,10 @@ def create_transporter():
def update_transporter(transporter_id): def update_transporter(transporter_id):
transporters_db = Transporters() transporters_db = Transporters()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
data = request.get_json() data = request.get_json()
transporter = transporters_db.get_transporter_by_id(transporter_id) transporter = transporters_db.get_transporter_by_id(transporter_id)
if not transporter: if not transporter:
@@ -58,6 +71,10 @@ def update_transporter(transporter_id):
def delete_transporter(transporter_id): def delete_transporter(transporter_id):
transporters_db = Transporters() transporters_db = Transporters()
user_id = get_jwt_identity() user_id = get_jwt_identity()
users = Users()
user = users.get_user_by_id(user_id)
if user['user_role'] == 'company_user':
user_id = user['company_id']
transporter = transporters_db.get_transporter_by_id(transporter_id) transporter = transporters_db.get_transporter_by_id(transporter_id)
if not transporter: if not transporter:
return jsonify({"error": "Transporter not found"}), 404 return jsonify({"error": "Transporter not found"}), 404

View File

@@ -15,7 +15,10 @@ CREATE TABLE IF NOT EXISTS users (
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
otp_code TEXT, otp_code TEXT,
otp_expiration TIMESTAMPTZ, otp_expiration TIMESTAMPTZ,
user_role TEXT NOT NULL DEFAULT 'user' CHECK (user_role IN ('user', 'admin')) 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
); );
-- Clients table -- Clients table
@@ -94,6 +97,8 @@ CREATE TABLE IF NOT EXISTS orders_in (
trailer_reg_number TEXT, trailer_reg_number TEXT,
received_price DOUBLE PRECISION, received_price DOUBLE PRECISION,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
file_name TEXT,
expenses DOUBLE PRECISION,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(client_id) REFERENCES clients(id) ON DELETE CASCADE FOREIGN KEY(client_id) REFERENCES clients(id) ON DELETE CASCADE
); );
@@ -144,3 +149,16 @@ CREATE TABLE IF NOT EXISTS email (
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
); );
CREATE TABLE IF NOT EXISTS company_user_access (
id INTEGER PRIMARY KEY AUTOINCREMENT,
company_user_id INTEGER NOT NULL,
clients INTEGER,
transporters INTEGER,
destinations INTEGER,
orders_in INTEGER,
orders_out INTEGER,
report INTEGER,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(company_user_id) REFERENCES users(id) ON DELETE CASCADE
);

View File

@@ -82,9 +82,9 @@ def send_gmail(to_email, subject, body):
def send_gmail_with_attachment(to_email, subject, body, attachment_path): def send_gmail_with_attachment(to_email, subject, body, attachment_path):
smtp_host = "smtp.gmail.com" smtp_host = "smtp.gmail.com"
smtp_port = 587 smtp_port = 587
smtp_user = os.environ.get("GMAIL_USER") smtp_user = 'macamete.robert@gmail.com'
smtp_pass = os.environ.get("GMAIL_PASS") smtp_pass = 'advx yqlv jkaa czvr'
sender_email = smtp_user sender_email = 'macamete.robert@gmail.com'
if not all([smtp_user, smtp_pass]): if not all([smtp_user, smtp_pass]):
raise ValueError("GMAIL_USER and GMAIL_PASS must be set in environment variables.") raise ValueError("GMAIL_USER and GMAIL_PASS must be set in environment variables.")

View File

@@ -0,0 +1,31 @@
import os
from utils.email import send_gmail_with_attachment
class WelcomeMessage:
def __init__(self, name, email):
self.name = name
self.email = email
self.subject = 'Welcome to Order Go - TMS - Your Account is Ready'
self.assets_folder = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "assets")
)
self.manual = os.path.join(self.assets_folder, "manual.pdf")
self.body = f'''
Dear {self.name},
We are pleased to welcome you to Order Go - TMS. Thank you for choosing our platform to support your business needs.
To assist you in getting started, we have attached the User Manual to this email. It provides step-by-step instructions on account setup, feature overview, and best practices for using Order Go - TMS efficiently.
We recommend reviewing the manual at your convenience to become familiar with the system's capabilities. Should you require any further assistance, our support team is available at support@ordergotms.com.
We look forward to supporting your success and building a long-term partnership.
Sincerely,
The Order Go - TMS Team
'''
def send_email(self):
send_gmail_with_attachment(to_email=self.email, subject=self.subject, body=self.body, attachment_path=self.manual)