From 25de085191bb7a993ea3506beb01512addf7c5b1 Mon Sep 17 00:00:00 2001 From: HardMax71 Date: Wed, 14 Aug 2024 02:00:19 +0200 Subject: [PATCH] - removed date/datetime completely, from now only timestamps as ints - moved api classes and schemas into common, all-accessible package instead of being in /server - implemented inventory view/widget in GUI almost in full --- desktop_app/src/api/assets.py | 66 ------- desktop_app/src/api/audit.py | 47 ----- desktop_app/src/api/carriers.py | 21 --- desktop_app/src/api/customers.py | 26 --- desktop_app/src/api/inventory.py | 80 -------- desktop_app/src/api/locations.py | 23 --- desktop_app/src/api/orders.py | 57 ------ desktop_app/src/api/permissions.py | 21 --- desktop_app/src/api/pick_lists.py | 40 ---- desktop_app/src/api/po_items.py | 21 --- desktop_app/src/api/product_categories.py | 21 --- desktop_app/src/api/products.py | 32 ---- desktop_app/src/api/purchase_orders.py | 26 --- desktop_app/src/api/quality.py | 87 --------- desktop_app/src/api/reports.py | 20 -- desktop_app/src/api/shipments.py | 31 ---- desktop_app/src/api/suppliers.py | 25 --- desktop_app/src/api/users.py | 67 ------- desktop_app/src/api/warehouse.py | 26 --- desktop_app/src/main.py | 9 +- desktop_app/src/services/__init__.py | 1 - desktop_app/src/services/authentication.py | 17 +- desktop_app/src/ui/audit_log_view.py | 8 +- desktop_app/src/ui/components/__init__.py | 10 +- .../ui/components/inventory_widget_dialogs.py | 172 ++++++++++++++++++ desktop_app/src/ui/customer_view.py | 14 +- desktop_app/src/ui/dashboard.py | 16 +- desktop_app/src/ui/data_analysis.py | 4 +- desktop_app/src/ui/inventory_planning.py | 33 +++- desktop_app/src/ui/inventory_view.py | 159 ++++++++++++---- desktop_app/src/ui/login_dialog.py | 9 +- desktop_app/src/ui/main_window.py | 18 +- desktop_app/src/ui/notification_center.py | 5 +- desktop_app/src/ui/order_view.py | 20 +- desktop_app/src/ui/product_view.py | 18 +- desktop_app/src/ui/report_generator.py | 4 +- desktop_app/src/ui/search_filter.py | 2 +- .../src/ui/settings/general_settings.py | 9 +- desktop_app/src/ui/shipment_view.py | 18 +- desktop_app/src/ui/simulation_view.py | 3 +- desktop_app/src/ui/supplier_view.py | 14 +- desktop_app/src/ui/system_diagnostics.py | 4 +- desktop_app/src/ui/training_mode.py | 3 +- desktop_app/src/ui/user_management.py | 16 +- desktop_app/src/ui/warehouse_visualizer.py | 2 +- public_api/__init__.py | 0 .../src => public_api}/api/__init__.py | 0 public_api/api/assets.py | 103 +++++++++++ public_api/api/audit.py | 70 +++++++ public_api/api/carriers.py | 30 +++ {desktop_app/src => public_api}/api/client.py | 6 +- public_api/api/customers.py | 40 ++++ public_api/api/inventory.py | 134 ++++++++++++++ public_api/api/locations.py | 36 ++++ public_api/api/orders.py | 79 ++++++++ public_api/api/permissions.py | 30 +++ public_api/api/pick_lists.py | 57 ++++++ public_api/api/po_items.py | 30 +++ public_api/api/product_categories.py | 30 +++ public_api/api/products.py | 54 ++++++ public_api/api/purchase_orders.py | 42 +++++ public_api/api/quality.py | 129 +++++++++++++ public_api/api/reports.py | 27 +++ public_api/api/shipments.py | 50 +++++ public_api/api/suppliers.py | 42 +++++ public_api/api/users.py | 91 +++++++++ public_api/api/warehouse.py | 38 ++++ .../shared_schemas}/__init__.py | 26 +-- .../shared_schemas}/asset.py | 29 ++- .../shared_schemas}/audit.py | 13 +- .../shared_schemas}/inventory.py | 25 +-- .../shared_schemas}/order.py | 27 ++- .../shared_schemas}/quality.py | 25 ++- .../shared_schemas}/reports.py | 43 +++-- .../shared_schemas}/task.py | 21 +-- .../shared_schemas}/user.py | 15 +- .../shared_schemas}/warehouse.py | 47 +++-- .../shared_schemas}/yard.py | 23 ++- server/app/api/deps.py | 5 +- server/app/api/v1/endpoints/assets.py | 57 +++--- server/app/api/v1/endpoints/audit.py | 33 ++-- server/app/api/v1/endpoints/carriers.py | 17 +- server/app/api/v1/endpoints/customers.py | 21 ++- server/app/api/v1/endpoints/inventory.py | 130 ++++++++----- server/app/api/v1/endpoints/locations.py | 19 +- server/app/api/v1/endpoints/orders.py | 53 +++--- server/app/api/v1/endpoints/permissions.py | 27 +-- server/app/api/v1/endpoints/pick_lists.py | 33 ++-- server/app/api/v1/endpoints/po_items.py | 15 +- .../api/v1/endpoints/product_categories.py | 17 +- server/app/api/v1/endpoints/products.py | 34 ++-- .../app/api/v1/endpoints/purchase_orders.py | 23 +-- server/app/api/v1/endpoints/quality.py | 75 ++++---- server/app/api/v1/endpoints/receipts.py | 29 +-- server/app/api/v1/endpoints/reports.py | 21 ++- server/app/api/v1/endpoints/roles.py | 21 ++- server/app/api/v1/endpoints/search.py | 13 +- server/app/api/v1/endpoints/shipments.py | 25 +-- server/app/api/v1/endpoints/suppliers.py | 21 ++- server/app/api/v1/endpoints/tasks.py | 39 ++-- server/app/api/v1/endpoints/users.py | 49 ++--- server/app/api/v1/endpoints/warehouse.py | 24 +-- server/app/api/v1/endpoints/yard.py | 48 ++--- server/app/api/v1/endpoints/zones.py | 19 +- server/app/api/v1/router.py | 6 +- server/app/crud/asset.py | 2 +- server/app/crud/asset_maintenance.py | 2 +- server/app/crud/audit.py | 2 +- server/app/crud/base.py | 6 +- server/app/crud/customer.py | 2 +- server/app/crud/dock_appointment.py | 2 +- server/app/crud/inventory.py | 93 ++++++++-- server/app/crud/location.py | 2 +- server/app/crud/order.py | 6 +- server/app/crud/permission.py | 2 +- server/app/crud/pick_list.py | 6 +- server/app/crud/product.py | 13 +- server/app/crud/product_category.py | 2 +- server/app/crud/purchase_order.py | 4 +- server/app/crud/quality.py | 4 +- server/app/crud/receipt.py | 6 +- server/app/crud/reports.py | 2 +- server/app/crud/role.py | 4 +- server/app/crud/shipment.py | 6 +- server/app/crud/supplier.py | 2 +- server/app/crud/task.py | 7 +- server/app/crud/user.py | 4 +- server/app/crud/warehouse.py | 6 +- server/app/crud/yard.py | 2 +- server/app/crud/yard_location.py | 2 +- server/app/crud/zone.py | 2 +- server/app/models/asset.py | 10 +- server/app/models/audit_log.py | 6 +- server/app/models/dock_appointment.py | 8 +- server/app/models/inventory.py | 16 +- server/app/models/order.py | 11 +- server/app/models/pick_list.py | 8 +- server/app/models/quality.py | 9 +- server/app/models/receipt.py | 6 +- server/app/models/shipment.py | 4 +- server/app/models/task.py | 10 +- server/app/models/user.py | 10 +- server/nexusware.db | Bin 335872 -> 344064 bytes 143 files changed, 2359 insertions(+), 1541 deletions(-) delete mode 100644 desktop_app/src/api/assets.py delete mode 100644 desktop_app/src/api/audit.py delete mode 100644 desktop_app/src/api/carriers.py delete mode 100644 desktop_app/src/api/customers.py delete mode 100644 desktop_app/src/api/inventory.py delete mode 100644 desktop_app/src/api/locations.py delete mode 100644 desktop_app/src/api/orders.py delete mode 100644 desktop_app/src/api/permissions.py delete mode 100644 desktop_app/src/api/pick_lists.py delete mode 100644 desktop_app/src/api/po_items.py delete mode 100644 desktop_app/src/api/product_categories.py delete mode 100644 desktop_app/src/api/products.py delete mode 100644 desktop_app/src/api/purchase_orders.py delete mode 100644 desktop_app/src/api/quality.py delete mode 100644 desktop_app/src/api/reports.py delete mode 100644 desktop_app/src/api/shipments.py delete mode 100644 desktop_app/src/api/suppliers.py delete mode 100644 desktop_app/src/api/users.py delete mode 100644 desktop_app/src/api/warehouse.py create mode 100644 desktop_app/src/ui/components/inventory_widget_dialogs.py create mode 100644 public_api/__init__.py rename {desktop_app/src => public_api}/api/__init__.py (100%) create mode 100644 public_api/api/assets.py create mode 100644 public_api/api/audit.py create mode 100644 public_api/api/carriers.py rename {desktop_app/src => public_api}/api/client.py (91%) create mode 100644 public_api/api/customers.py create mode 100644 public_api/api/inventory.py create mode 100644 public_api/api/locations.py create mode 100644 public_api/api/orders.py create mode 100644 public_api/api/permissions.py create mode 100644 public_api/api/pick_lists.py create mode 100644 public_api/api/po_items.py create mode 100644 public_api/api/product_categories.py create mode 100644 public_api/api/products.py create mode 100644 public_api/api/purchase_orders.py create mode 100644 public_api/api/quality.py create mode 100644 public_api/api/reports.py create mode 100644 public_api/api/shipments.py create mode 100644 public_api/api/suppliers.py create mode 100644 public_api/api/users.py create mode 100644 public_api/api/warehouse.py rename {server/app/schemas => public_api/shared_schemas}/__init__.py (90%) rename {server/app/schemas => public_api/shared_schemas}/asset.py (74%) rename {server/app/schemas => public_api/shared_schemas}/audit.py (83%) rename {server/app/schemas => public_api/shared_schemas}/inventory.py (93%) rename {server/app/schemas => public_api/shared_schemas}/order.py (89%) rename {server/app/schemas => public_api/shared_schemas}/quality.py (86%) rename {server/app/schemas => public_api/shared_schemas}/reports.py (84%) rename {server/app/schemas => public_api/shared_schemas}/task.py (87%) rename {server/app/schemas => public_api/shared_schemas}/user.py (89%) rename {server/app/schemas => public_api/shared_schemas}/warehouse.py (87%) rename {server/app/schemas => public_api/shared_schemas}/yard.py (87%) diff --git a/desktop_app/src/api/assets.py b/desktop_app/src/api/assets.py deleted file mode 100644 index f376f6a..0000000 --- a/desktop_app/src/api/assets.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import Optional - -from .client import APIClient - - -class AssetsAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_asset(self, asset_data: dict): - return self.client.post("/assets/", json=asset_data) - - def get_assets(self, skip: int = 0, limit: int = 100, asset_filter: Optional[dict] = None): - return self.client.get("/assets/", params={"skip": skip, "limit": limit, **(asset_filter or {})}) - - def get_asset(self, asset_id: int): - return self.client.get(f"/assets/{asset_id}") - - def update_asset(self, asset_id: int, asset_data: dict): - return self.client.put(f"/assets/{asset_id}", json=asset_data) - - def delete_asset(self, asset_id: int): - return self.client.delete(f"/assets/{asset_id}") - - def get_asset_types(self): - return self.client.get("/assets/types") - - def get_asset_statuses(self): - return self.client.get("/assets/statuses") - - def create_asset_maintenance(self, maintenance_data: dict): - return self.client.post("/assets/maintenance", json=maintenance_data) - - def get_asset_maintenances(self, skip: int = 0, limit: int = 100, maintenance_filter: Optional[dict] = None): - return self.client.get("/assets/maintenance", - params={"skip": skip, "limit": limit, **(maintenance_filter or {})}) - - def get_asset_maintenance(self, maintenance_id: int): - return self.client.get(f"/assets/maintenance/{maintenance_id}") - - def update_asset_maintenance(self, maintenance_id: int, maintenance_data: dict): - return self.client.put(f"/assets/maintenance/{maintenance_id}", json=maintenance_data) - - def delete_asset_maintenance(self, maintenance_id: int): - return self.client.delete(f"/assets/maintenance/{maintenance_id}") - - def get_maintenance_types(self): - return self.client.get("/assets/maintenance/types") - - def get_asset_maintenance_history(self, asset_id: int, skip: int = 0, limit: int = 100): - return self.client.get(f"/assets/{asset_id}/maintenance_history", params={"skip": skip, "limit": limit}) - - def schedule_asset_maintenance(self, asset_id: int, maintenance_data: dict): - return self.client.post(f"/assets/{asset_id}/schedule_maintenance", json=maintenance_data) - - def complete_asset_maintenance(self, maintenance_id: int, completion_data: dict): - return self.client.put(f"/assets/maintenance/{maintenance_id}/complete", json=completion_data) - - def get_asset_current_location(self, asset_id: int): - return self.client.get(f"/assets/{asset_id}/current_location") - - def transfer_asset(self, asset_id: int, transfer_data: dict): - return self.client.post(f"/assets/{asset_id}/transfer", json=transfer_data) - - def get_assets_due_for_maintenance(self): - return self.client.get("/assets/due_for_maintenance") diff --git a/desktop_app/src/api/audit.py b/desktop_app/src/api/audit.py deleted file mode 100644 index 6187f9e..0000000 --- a/desktop_app/src/api/audit.py +++ /dev/null @@ -1,47 +0,0 @@ -from datetime import datetime -from typing import Optional -from .client import APIClient - -class AuditAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_audit_log(self, log_data: dict): - return self.client.post("/audit/logs", json=log_data) - - def get_audit_logs(self, skip: int = 0, limit: int = 100, filter_params: Optional[dict] = None): - return self.client.get("/audit/logs", params={"skip": skip, "limit": limit, **(filter_params or {})}) - - def get_audit_log(self, log_id: int): - return self.client.get(f"/audit/logs/{log_id}") - - def get_audit_summary(self, date_from: Optional[datetime] = None, date_to: Optional[datetime] = None): - params = {} - if date_from: - params["date_from"] = date_from.isoformat() - if date_to: - params["date_to"] = date_to.isoformat() - return self.client.get("/audit/logs/summary", params=params) - - def get_user_audit_logs(self, user_id: int, skip: int = 0, limit: int = 100): - return self.client.get(f"/audit/logs/user/{user_id}", params={"skip": skip, "limit": limit}) - - def get_table_audit_logs(self, table_name: str, skip: int = 0, limit: int = 100): - return self.client.get(f"/audit/logs/table/{table_name}", params={"skip": skip, "limit": limit}) - - def get_record_audit_logs(self, table_name: str, record_id: int, skip: int = 0, limit: int = 100): - return self.client.get(f"/audit/logs/record/{table_name}/{record_id}", params={"skip": skip, "limit": limit}) - - def export_audit_logs(self, date_from: Optional[datetime] = None, date_to: Optional[datetime] = None): - params = {} - if date_from: - params["date_from"] = date_from.isoformat() - if date_to: - params["date_to"] = date_to.isoformat() - return self.client.get("/audit/logs/export", params=params) - - def get_audit_log_actions(self): - return self.client.get("/audit/logs/actions") - - def get_audited_tables(self): - return self.client.get("/audit/logs/tables") \ No newline at end of file diff --git a/desktop_app/src/api/carriers.py b/desktop_app/src/api/carriers.py deleted file mode 100644 index 553fae6..0000000 --- a/desktop_app/src/api/carriers.py +++ /dev/null @@ -1,21 +0,0 @@ -from .client import APIClient - - -class CarriersAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_carrier(self, carrier_data: dict): - return self.client.post("/carriers/", json=carrier_data) - - def get_carriers(self, skip: int = 0, limit: int = 100): - return self.client.get("/carriers/", params={"skip": skip, "limit": limit}) - - def get_carrier(self, carrier_id: int): - return self.client.get(f"/carriers/{carrier_id}") - - def update_carrier(self, carrier_id: int, carrier_data: dict): - return self.client.put(f"/carriers/{carrier_id}", json=carrier_data) - - def delete_carrier(self, carrier_id: int): - return self.client.delete(f"/carriers/{carrier_id}") diff --git a/desktop_app/src/api/customers.py b/desktop_app/src/api/customers.py deleted file mode 100644 index 740bd8b..0000000 --- a/desktop_app/src/api/customers.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Optional - -from .client import APIClient - - -class CustomersAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_customer(self, customer_data: dict): - return self.client.post("/customers/", json=customer_data) - - def get_customers(self, skip: int = 0, limit: int = 100, customer_filter: Optional[dict] = None): - return self.client.get("/customers/", params={"skip": skip, "limit": limit, **(customer_filter or {})}) - - def get_customer(self, customer_id: int): - return self.client.get(f"/customers/{customer_id}") - - def update_customer(self, customer_id: int, customer_data: dict): - return self.client.put(f"/customers/{customer_id}", json=customer_data) - - def delete_customer(self, customer_id: int): - return self.client.delete(f"/customers/{customer_id}") - - def get_customer_orders(self, customer_id: int, skip: int = 0, limit: int = 100): - return self.client.get(f"/customers/{customer_id}/orders", params={"skip": skip, "limit": limit}) diff --git a/desktop_app/src/api/inventory.py b/desktop_app/src/api/inventory.py deleted file mode 100644 index df2cf76..0000000 --- a/desktop_app/src/api/inventory.py +++ /dev/null @@ -1,80 +0,0 @@ -from datetime import datetime -from typing import Optional, List - -from .client import APIClient - - -class InventoryAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_inventory(self, inventory_data: dict): - return self.client.post("/inventory/", json=inventory_data) - - def get_inventory(self, skip: int = 0, limit: int = 100, inventory_filter: Optional[dict] = None): - params = {"skip": skip, "limit": limit} - if inventory_filter: - params.update(inventory_filter) - return self.client.get("/inventory", params=params) - - def get_inventory_item(self, inventory_id: int): - return self.client.get(f"/inventory/{inventory_id}") - - def update_inventory(self, inventory_id: int, inventory_data: dict): - return self.client.put(f"/inventory/{inventory_id}", json=inventory_data) - - def adjust_inventory(self, inventory_id: int, adjustment_data: dict): - return self.client.post(f"/inventory/{inventory_id}/adjust", json=adjustment_data) - - def transfer_inventory(self, transfer_data: dict): - return self.client.post("/inventory/transfer", json=transfer_data) - - def get_inventory_report(self): - return self.client.get("/inventory/report") - - def perform_cycle_count(self, location_id: int, counted_items: List[dict]): - return self.client.post("/inventory/cycle_count", json={"location_id": location_id, "counted_items": counted_items}) - - def get_low_stock_items(self, threshold: int = 10): - return self.client.get("/inventory/low_stock", params={"threshold": threshold}) - - def get_out_of_stock_items(self): - return self.client.get("/inventory/out_of_stock") - - def create_reorder_list(self, threshold: int = 10): - return self.client.post("/inventory/reorder", params={"threshold": threshold}) - - def get_product_locations(self, product_id: int): - return self.client.get(f"/inventory/product_locations/{product_id}") - - def batch_update_inventory(self, updates: List[dict]): - return self.client.post("/inventory/batch_update", json=updates) - - def get_inventory_movement_history(self, product_id: int, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None): - params = {} - if start_date: - params["start_date"] = start_date.isoformat() - if end_date: - params["end_date"] = end_date.isoformat() - return self.client.get(f"/inventory/movement_history/{product_id}", params=params) - - def get_inventory_summary(self): - return self.client.get("/inventory/summary") - - def perform_stocktake(self, stocktake_data: dict): - return self.client.post("/inventory/stocktake", json=stocktake_data) - - def perform_abc_analysis(self): - return self.client.get("/inventory/abc_analysis") - - def optimize_inventory_locations(self): - return self.client.post("/inventory/optimize_locations") - - def get_expiring_soon_inventory(self, days: int = 30): - return self.client.get("/inventory/expiring_soon", params={"days": days}) - - def bulk_import_inventory(self, import_data: dict): - return self.client.post("/inventory/bulk_import", json=import_data) - - def get_storage_utilization(self): - return self.client.get("/inventory/storage_utilization") diff --git a/desktop_app/src/api/locations.py b/desktop_app/src/api/locations.py deleted file mode 100644 index 67d7a82..0000000 --- a/desktop_app/src/api/locations.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Optional - -from .client import APIClient - - -class LocationsAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_location(self, location_data: dict): - return self.client.post("/locations/", json=location_data) - - def get_locations(self, skip: int = 0, limit: int = 100, location_filter: Optional[dict] = None): - return self.client.get("/locations/", params={"skip": skip, "limit": limit, **(location_filter or {})}) - - def get_location(self, location_id: int): - return self.client.get(f"/locations/{location_id}") - - def update_location(self, location_id: int, location_data: dict): - return self.client.put(f"/locations/{location_id}", json=location_data) - - def delete_location(self, location_id: int): - return self.client.delete(f"/locations/{location_id}") diff --git a/desktop_app/src/api/orders.py b/desktop_app/src/api/orders.py deleted file mode 100644 index 5b41d3f..0000000 --- a/desktop_app/src/api/orders.py +++ /dev/null @@ -1,57 +0,0 @@ -from datetime import datetime -from typing import Optional - -from .client import APIClient - - -class OrdersAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_order(self, order_data: dict): - return self.client.post("/orders/", json=order_data) - - def get_orders(self, skip: int = 0, limit: int = 100, filter_params: Optional[dict] = None): - return self.client.get("/orders/", params={"skip": skip, "limit": limit, **(filter_params or {})}) - - def get_order(self, order_id: int): - return self.client.get(f"/orders/{order_id}") - - def update_order(self, order_id: int, order_data: dict): - return self.client.put(f"/orders/{order_id}", json=order_data) - - def delete_order(self, order_id: int): - return self.client.delete(f"/orders/{order_id}") - - def get_order_summary(self, date_from: Optional[datetime] = None, date_to: Optional[datetime] = None): - params = {} - if date_from: - params["date_from"] = date_from.isoformat() - if date_to: - params["date_to"] = date_to.isoformat() - return self.client.get("/orders/summary", params=params) - - def cancel_order(self, order_id: int): - return self.client.post(f"/orders/{order_id}/cancel") - - def ship_order(self, order_id: int, shipping_info: dict): - return self.client.post(f"/orders/{order_id}/ship", json=shipping_info) - - def cancel_order_item(self, order_id: int, item_id: int): - return self.client.post(f"/orders/{order_id}/cancel_item", json={"item_id": item_id}) - - def add_order_item(self, order_id: int, item_data: dict): - return self.client.post(f"/orders/{order_id}/add_item", json=item_data) - - def get_backorders(self): - return self.client.get("/orders/backorders") - - def bulk_import_orders(self, import_data: dict): - return self.client.post("/orders/bulk_import", json=import_data) - - def get_order_processing_times(self, start_date: datetime, end_date: datetime): - params = { - "start_date": start_date.isoformat(), - "end_date": end_date.isoformat() - } - return self.client.get("/orders/processing_times", params=params) diff --git a/desktop_app/src/api/permissions.py b/desktop_app/src/api/permissions.py deleted file mode 100644 index d9a3fc1..0000000 --- a/desktop_app/src/api/permissions.py +++ /dev/null @@ -1,21 +0,0 @@ -from .client import APIClient - - -class PermissionsAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_permission(self, permission_data: dict): - return self.client.post("/permissions/", json=permission_data) - - def get_permissions(self, skip: int = 0, limit: int = 100): - return self.client.get("/permissions/", params={"skip": skip, "limit": limit}) - - def get_permission(self, permission_id: int): - return self.client.get(f"/permissions/{permission_id}") - - def update_permission(self, permission_id: int, permission_data: dict): - return self.client.put(f"/permissions/{permission_id}", json=permission_data) - - def delete_permission(self, permission_id: int): - return self.client.delete(f"/permissions/{permission_id}") diff --git a/desktop_app/src/api/pick_lists.py b/desktop_app/src/api/pick_lists.py deleted file mode 100644 index 8018387..0000000 --- a/desktop_app/src/api/pick_lists.py +++ /dev/null @@ -1,40 +0,0 @@ -from datetime import datetime -from typing import Optional - -from .client import APIClient - - -class PickListsAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_pick_list(self, pick_list_data: dict): - return self.client.post("/pick_lists/", json=pick_list_data) - - def get_pick_lists(self, skip: int = 0, limit: int = 100, filter_params: Optional[dict] = None): - return self.client.get("/pick_lists/", params={"skip": skip, "limit": limit, **(filter_params or {})}) - - def get_pick_list(self, pick_list_id: int): - return self.client.get(f"/pick_lists/{pick_list_id}") - - def update_pick_list(self, pick_list_id: int, pick_list_data: dict): - return self.client.put(f"/pick_lists/{pick_list_id}", json=pick_list_data) - - def delete_pick_list(self, pick_list_id: int): - return self.client.delete(f"/pick_lists/{pick_list_id}") - - def optimize_picking_route(self, pick_list_id: int): - return self.client.get(f"/pick_lists/optimize_route?pick_list_id={pick_list_id}") - - def start_pick_list(self, pick_list_id: int): - return self.client.post(f"/pick_lists/{pick_list_id}/start") - - def complete_pick_list(self, pick_list_id: int): - return self.client.post(f"/pick_lists/{pick_list_id}/complete") - - def get_picking_performance(self, start_date: datetime, end_date: datetime): - params = { - "start_date": start_date.isoformat(), - "end_date": end_date.isoformat() - } - return self.client.get("/pick_lists/performance", params=params) diff --git a/desktop_app/src/api/po_items.py b/desktop_app/src/api/po_items.py deleted file mode 100644 index cfcb8b5..0000000 --- a/desktop_app/src/api/po_items.py +++ /dev/null @@ -1,21 +0,0 @@ -from .client import APIClient - - -class POItemsAPI: - def __init__(self, client: APIClient): - self.client = client - - def get_po_item(self, po_item_id: int): - return self.client.get(f"/po_items/{po_item_id}") - - def update_po_item(self, po_item_id: int, po_item_data: dict): - return self.client.put(f"/po_items/{po_item_id}", json=po_item_data) - - def get_po_items(self, skip: int = 0, limit: int = 100): - return self.client.get("/po_items", params={"skip": skip, "limit": limit}) - - def get_po_items_by_product(self, product_id: int, skip: int = 0, limit: int = 100): - return self.client.get(f"/po_items/by_product/{product_id}", params={"skip": skip, "limit": limit}) - - def get_pending_receipt_po_items(self, skip: int = 0, limit: int = 100): - return self.client.get("/po_items/pending_receipt", params={"skip": skip, "limit": limit}) diff --git a/desktop_app/src/api/product_categories.py b/desktop_app/src/api/product_categories.py deleted file mode 100644 index c9c107a..0000000 --- a/desktop_app/src/api/product_categories.py +++ /dev/null @@ -1,21 +0,0 @@ -from .client import APIClient - - -class ProductCategoriesAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_category(self, category_data: dict): - return self.client.post("/product_categories/", json=category_data) - - def get_categories(self, skip: int = 0, limit: int = 100): - return self.client.get("/product_categories/", params={"skip": skip, "limit": limit}) - - def get_category(self, category_id: int): - return self.client.get(f"/product_categories/{category_id}") - - def update_category(self, category_id: int, category_data: dict): - return self.client.put(f"/product_categories/{category_id}", json=category_data) - - def delete_category(self, category_id: int): - return self.client.delete(f"/product_categories/{category_id}") diff --git a/desktop_app/src/api/products.py b/desktop_app/src/api/products.py deleted file mode 100644 index ff9bac0..0000000 --- a/desktop_app/src/api/products.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Optional - -from .client import APIClient - - -class ProductsAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_product(self, product_data: dict): - return self.client.post("/products/", json=product_data) - - def get_products(self, skip: int = 0, limit: int = 100, product_filter: Optional[dict] = None): - return self.client.get("/products/", params={"skip": skip, "limit": limit, **(product_filter or {})}) - - def get_product(self, product_id: int): - return self.client.get(f"/products/{product_id}") - - def update_product(self, product_id: int, product_data: dict): - return self.client.put(f"/products/{product_id}", json=product_data) - - def delete_product(self, product_id: int): - return self.client.delete(f"/products/{product_id}") - - def get_product_by_barcode(self, barcode: str): - return self.client.post("/products/barcode", json={"barcode": barcode}) - - def get_product_substitutes(self, product_id: int): - return self.client.get(f"/products/{product_id}/substitutes") - - def add_product_substitute(self, product_id: int, substitute_id: int): - return self.client.post(f"/products/{product_id}/substitutes", json={"substitute_id": substitute_id}) diff --git a/desktop_app/src/api/purchase_orders.py b/desktop_app/src/api/purchase_orders.py deleted file mode 100644 index 775b873..0000000 --- a/desktop_app/src/api/purchase_orders.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Optional, List - -from .client import APIClient - - -class PurchaseOrdersAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_purchase_order(self, purchase_order_data: dict): - return self.client.post("/purchase_orders/", json=purchase_order_data) - - def get_purchase_orders(self, skip: int = 0, limit: int = 100, po_filter: Optional[dict] = None): - return self.client.get("/purchase_orders/", params={"skip": skip, "limit": limit, **(po_filter or {})}) - - def get_purchase_order(self, po_id: int): - return self.client.get(f"/purchase_orders/{po_id}") - - def update_purchase_order(self, po_id: int, po_data: dict): - return self.client.put(f"/purchase_orders/{po_id}", json=po_data) - - def delete_purchase_order(self, po_id: int): - return self.client.delete(f"/purchase_orders/{po_id}") - - def receive_purchase_order(self, po_id: int, received_items: List[dict]): - return self.client.post(f"/purchase_orders/{po_id}/receive", json={"received_items": received_items}) diff --git a/desktop_app/src/api/quality.py b/desktop_app/src/api/quality.py deleted file mode 100644 index 2ba8d2c..0000000 --- a/desktop_app/src/api/quality.py +++ /dev/null @@ -1,87 +0,0 @@ -from datetime import datetime -from typing import Optional, List - -from .client import APIClient - - -class QualityAPI: - def __init__(self, client: APIClient): - self.client = client - - def create_quality_check(self, check_data: dict): - return self.client.post("/quality/checks", json=check_data) - - def get_quality_checks(self, skip: int = 0, limit: int = 100, filter_params: Optional[dict] = None): - return self.client.get("/quality/checks", params={"skip": skip, "limit": limit, **(filter_params or {})}) - - def get_quality_check(self, check_id: int): - return self.client.get(f"/quality/checks/{check_id}") - - def update_quality_check(self, check_id: int, check_data: dict): - return self.client.put(f"/quality/checks/{check_id}", json=check_data) - - def delete_quality_check(self, check_id: int): - return self.client.delete(f"/quality/checks/{check_id}") - - def get_quality_metrics(self, date_from: Optional[datetime] = None, date_to: Optional[datetime] = None): - params = {} - if date_from: - params["date_from"] = date_from.isoformat() - if date_to: - params["date_to"] = date_to.isoformat() - return self.client.get("/quality/metrics", params=params) - - def create_quality_standard(self, standard_data: dict): - return self.client.post("/quality/standards", json=standard_data) - - def get_quality_standards(self, skip: int = 0, limit: int = 100): - return self.client.get("/quality/standards", params={"skip": skip, "limit": limit}) - - def get_quality_standard(self, standard_id: int): - return self.client.get(f"/quality/standards/{standard_id}") - - def update_quality_standard(self, standard_id: int, standard_data: dict): - return self.client.put(f"/quality/standards/{standard_id}", json=standard_data) - - def delete_quality_standard(self, standard_id: int): - return self.client.delete(f"/quality/standards/{standard_id}") - - def create_quality_alert(self, alert_data: dict): - return self.client.post("/quality/alerts", json=alert_data) - - def get_quality_alerts(self, skip: int = 0, limit: int = 100): - return self.client.get("/quality/alerts", params={"skip": skip, "limit": limit}) - - def resolve_quality_alert(self, alert_id: int, resolution_data: dict): - return self.client.put(f"/quality/alerts/{alert_id}/resolve", json=resolution_data) - - def get_product_quality_history(self, product_id: int, skip: int = 0, limit: int = 100): - return self.client.get(f"/quality/product/{product_id}/history", params={"skip": skip, "limit": limit}) - - def get_quality_check_summary(self, date_from: Optional[datetime] = None, date_to: Optional[datetime] = None): - params = {} - if date_from: - params["date_from"] = date_from.isoformat() - if date_to: - params["date_to"] = date_to.isoformat() - return self.client.get("/quality/checks/summary", params=params) - - def get_product_quality_standards(self, product_id: int): - return self.client.get(f"/quality/product/{product_id}/standards") - - def create_batch_quality_check(self, checks: List[dict]): - return self.client.post("/quality/batch_check", json=checks) - - def get_active_quality_alerts(self, skip: int = 0, limit: int = 100): - return self.client.get("/quality/alerts/active", params={"skip": skip, "limit": limit}) - - def add_comment_to_quality_check(self, check_id: int, comment_data: dict): - return self.client.post(f"/quality/checks/{check_id}/comment", json=comment_data) - - def get_product_defect_rates(self, date_from: Optional[datetime] = None, date_to: Optional[datetime] = None): - params = {} - if date_from: - params["date_from"] = date_from.isoformat() - if date_to: - params["date_to"] = date_to.isoformat() - return self.client.get("/quality/reports/defect_rate", params=params) diff --git a/desktop_app/src/api/reports.py b/desktop_app/src/api/reports.py deleted file mode 100644 index ec9cd59..0000000 --- a/desktop_app/src/api/reports.py +++ /dev/null @@ -1,20 +0,0 @@ -from .client import APIClient - - -class ReportsAPI: - def __init__(self, client: APIClient): - self.client = client - - def get_inventory_summary(self): - return self.client.get("/reports/inventory_summary") - - def get_order_summary(self, start_date, end_date): - params = {"start_date": start_date, "end_date": end_date} - return self.client.get("/reports/order_summary", params=params) - - def get_warehouse_performance(self, start_date, end_date): - params = {"start_date": start_date, "end_date": end_date} - return self.client.get("/reports/warehouse_performance", params=params) - - def get_kpi_dashboard(self): - return self.client.get("/reports/kpi_dashboard") diff --git a/desktop_app/src/api/shipments.py b/desktop_app/src/api/shipments.py deleted file mode 100644 index ab900e5..0000000 --- a/desktop_app/src/api/shipments.py +++ /dev/null @@ -1,31 +0,0 @@ -from .client import APIClient - - -class ShipmentsAPI: - def __init__(self, client: APIClient): - self.client = client - - def get_shipments(self, skip=0, limit=100, filter_params=None): - return self.client.get("/shipments/", params={"skip": skip, "limit": limit, **filter_params}) - - def get_shipment(self, shipment_id): - return self.client.get(f"/shipments/{shipment_id}") - - def create_shipment(self, shipment_data): - return self.client.post("/shipments/", json=shipment_data) - - def update_shipment(self, shipment_id, shipment_data): - return self.client.put(f"/shipments/{shipment_id}", json=shipment_data) - - def delete_shipment(self, shipment_id): - return self.client.delete(f"/shipments/{shipment_id}") - - def generate_shipping_label(self, shipment_id): - return self.client.post(f"/shipments/{shipment_id}/generate_label") - - def get_carrier_rates(self, weight, dimensions, destination_zip): - params = {"weight": weight, "dimensions": dimensions, "destination_zip": destination_zip} - return self.client.get("/shipments/carrier_rates", params=params) - - def track_shipment(self, shipment_id): - return self.client.post(f"/shipments/{shipment_id}/track") diff --git a/desktop_app/src/api/suppliers.py b/desktop_app/src/api/suppliers.py deleted file mode 100644 index b90dd77..0000000 --- a/desktop_app/src/api/suppliers.py +++ /dev/null @@ -1,25 +0,0 @@ -from .client import APIClient - -class SuppliersAPI: - def __init__(self, client: APIClient): - self.client = client - - def get_suppliers(self, skip=0, limit=100, filter_params=None): - if filter_params is None: - filter_params = {} - return self.client.get("/suppliers/", params={"skip": skip, "limit": limit, **filter_params}) - - def get_supplier(self, supplier_id): - return self.client.get(f"/suppliers/{supplier_id}") - - def create_supplier(self, supplier_data): - return self.client.post("/suppliers/", json=supplier_data) - - def update_supplier(self, supplier_id, supplier_data): - return self.client.put(f"/suppliers/{supplier_id}", json=supplier_data) - - def delete_supplier(self, supplier_id): - return self.client.delete(f"/suppliers/{supplier_id}") - - def get_supplier_purchase_orders(self, supplier_id, skip=0, limit=100): - return self.client.get(f"/suppliers/{supplier_id}/purchase_orders", params={"skip": skip, "limit": limit}) \ No newline at end of file diff --git a/desktop_app/src/api/users.py b/desktop_app/src/api/users.py deleted file mode 100644 index 1bdfdbb..0000000 --- a/desktop_app/src/api/users.py +++ /dev/null @@ -1,67 +0,0 @@ -from .client import APIClient - - -class UsersAPI: - def __init__(self, client: APIClient): - self.client = client - - def login(self, email, password): - data = { - "grant_type": "password", # This is required and should be "password" - "username": email, - "password": password, - } - headers = {"content-type": "application/x-www-form-urlencoded"} - - response = self.client.post("/users/login", data=data, headers=headers) - access_token = response.get("access_token") - - if access_token: - self.client.set_token(access_token) - - return response - - def register(self, user_data): - return self.client.post("/users/register", json=user_data) - - def reset_password(self, email): - return self.client.post("/users/reset_password", json={"email": email}) - - def get_current_user(self): - return self.client.get("/users/me") - - def update_current_user(self, user_data): - return self.client.put("/users/me", json=user_data) - - def get_users(self, skip=0, limit=100): - return self.client.get("/users/", params={"skip": skip, "limit": limit}) - - def create_user(self, user_data): - return self.client.post("/users/", json=user_data) - - def get_user(self, user_id): - return self.client.get(f"/users/{user_id}") - - def update_user(self, user_id, user_data): - return self.client.put(f"/users/{user_id}", json=user_data) - - def delete_user(self, user_id): - return self.client.delete(f"/users/{user_id}") - - def get_roles(self, skip=0, limit=100): - return self.client.get("/users/roles", params={"skip": skip, "limit": limit}) - - def get_role(self, role_id): - return self.client.get(f"/users/roles/{role_id}") - - def update_role(self, role_id, role_data): - return self.client.put(f"/users/roles/{role_id}", json=role_data) - - def delete_role(self, role_id): - return self.client.delete(f"/users/roles/{role_id}") - - def create_permission(self, permission_data): - return self.client.post("/users/permissions", json=permission_data) - - def get_permissions(self, skip=0, limit=100): - return self.client.get("/users/permissions", params={"skip": skip, "limit": limit}) diff --git a/desktop_app/src/api/warehouse.py b/desktop_app/src/api/warehouse.py deleted file mode 100644 index 3aabbef..0000000 --- a/desktop_app/src/api/warehouse.py +++ /dev/null @@ -1,26 +0,0 @@ -from datetime import datetime - -from .client import APIClient - - -class WarehouseAPI: - def __init__(self, client: APIClient): - self.client = client - - def get_warehouse_layout(self): - return self.client.get("/warehouse/layout") - - def get_warehouse_stats(self): - return self.client.get("/warehouse/stats") - - def get_location_inventory(self, location_id): - return self.client.get(f"/warehouse/inventory/{location_id}") - - def update_location_inventory(self, location_id, product_id, inventory_update): - return self.client.put(f"/warehouse/inventory/{location_id}/{product_id}", json=inventory_update) - - def move_inventory(self, movement_data): - return self.client.post("/warehouse/inventory/move", json=movement_data) - - def adjust_inventory(self, adjustment_data): - return self.client.post("/warehouse/inventory/adjust", json=adjustment_data) diff --git a/desktop_app/src/main.py b/desktop_app/src/main.py index 2d7d984..90d83ed 100644 --- a/desktop_app/src/main.py +++ b/desktop_app/src/main.py @@ -4,8 +4,9 @@ from PySide6.QtGui import QIcon from PySide6.QtWidgets import QApplication -from api.client import APIClient -from api.users import UsersAPI +from public_api.api import APIClient +from public_api.api import UsersAPI +from desktop_app.src.config import API_BASE_URL from services.authentication import AuthenticationService from services.offline_manager import OfflineManager from services.update_manager import UpdateManager @@ -41,7 +42,7 @@ def main(): config_manager = ConfigManager() # Initialize API client - api_client = APIClient() + api_client = APIClient(base_url=API_BASE_URL) # Initialize services users_api = UsersAPI(api_client) @@ -75,4 +76,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/desktop_app/src/services/__init__.py b/desktop_app/src/services/__init__.py index 34ca719..d654aa9 100644 --- a/desktop_app/src/services/__init__.py +++ b/desktop_app/src/services/__init__.py @@ -1,4 +1,3 @@ -from .authentication import AuthenticationService from .background_tasks import BackgroundTaskManager from .data_processor import DataProcessor from .offline_manager import OfflineManager diff --git a/desktop_app/src/services/authentication.py b/desktop_app/src/services/authentication.py index cba09c7..674cb3b 100644 --- a/desktop_app/src/services/authentication.py +++ b/desktop_app/src/services/authentication.py @@ -1,21 +1,24 @@ -from desktop_app.src.api import UsersAPI +from public_api.api import UsersAPI +from public_api.shared_schemas import ( + UserCreate, UserUpdate, UserSanitizedWithRole, Token, Message, User +) class AuthenticationService: def __init__(self, users_api: UsersAPI): self.users_api = users_api - def login(self, email, password): + def login(self, email: str, password: str) -> Token: return self.users_api.login(email, password) - def register(self, user_data): + def register(self, user_data: UserCreate) -> User: return self.users_api.register(user_data) - def reset_password(self, email): + def reset_password(self, email: str) -> Message: return self.users_api.reset_password(email) - def get_current_user(self): + def get_current_user(self) -> UserSanitizedWithRole: return self.users_api.get_current_user() - def update_current_user(self, user_data): - return self.users_api.update_current_user(user_data) \ No newline at end of file + def update_current_user(self, user_data: UserUpdate) -> User: + return self.users_api.update_current_user(user_data) diff --git a/desktop_app/src/ui/audit_log_view.py b/desktop_app/src/ui/audit_log_view.py index 3ec420a..978d354 100644 --- a/desktop_app/src/ui/audit_log_view.py +++ b/desktop_app/src/ui/audit_log_view.py @@ -1,7 +1,7 @@ from PySide6.QtCore import Qt, QDate from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QDateEdit, QLabel -from desktop_app.src.api import APIClient, AuditAPI +from public_api.api import APIClient, AuditAPI from desktop_app.src.ui.components import StyledButton @@ -39,10 +39,10 @@ def init_ui(self): def refresh_logs(self): start_date = self.start_date.date().toString(Qt.ISODate) end_date = self.end_date.date().toString(Qt.ISODate) - logs = self.audit_log_api.get_audit_summary(start_date, end_date) + audit_summary = self.audit_log_api.get_audit_summary(start_date, end_date) - self.log_table.setRowCount(len(logs)) - for row, log in enumerate(logs): + self.log_table.setRowCount(audit_summary.total_logs) + for row, log in enumerate(audit_summary): self.log_table.setItem(row, 0, QTableWidgetItem(log['timestamp'])) self.log_table.setItem(row, 1, QTableWidgetItem(log['username'])) self.log_table.setItem(row, 2, QTableWidgetItem(log['action'])) diff --git a/desktop_app/src/ui/components/__init__.py b/desktop_app/src/ui/components/__init__.py index 4b5a11b..a0f4a77 100644 --- a/desktop_app/src/ui/components/__init__.py +++ b/desktop_app/src/ui/components/__init__.py @@ -17,6 +17,10 @@ FileDialog ) from .error_dialog import ErrorDialog +from .inventory_widget_dialogs import ( + InventoryDialog, + AdjustmentDialog +) __all__ = [ "StyledButton", @@ -33,5 +37,7 @@ "ProgressDialog", "MessageBox", "FileDialog", - "ErrorDialog" -] \ No newline at end of file + "ErrorDialog", + "InventoryDialog", + "AdjustmentDialog" +] diff --git a/desktop_app/src/ui/components/inventory_widget_dialogs.py b/desktop_app/src/ui/components/inventory_widget_dialogs.py new file mode 100644 index 0000000..a40988b --- /dev/null +++ b/desktop_app/src/ui/components/inventory_widget_dialogs.py @@ -0,0 +1,172 @@ +from datetime import datetime + +from PySide6.QtWidgets import (QDialog, QFormLayout, QLineEdit, QSpinBox, QComboBox, + QPushButton, QHBoxLayout, QMessageBox, QDateEdit) + +from public_api.api import InventoryAPI, LocationsAPI, ProductsAPI +from public_api.shared_schemas import Inventory, InventoryUpdate, InventoryCreate, InventoryAdjustment + + +class InventoryDialog(QDialog): + def __init__(self, inventory_api: InventoryAPI, locations_api: LocationsAPI, + products_api: ProductsAPI, item_data: Inventory = None, + parent=None): + super().__init__(parent) + self.inventory_api = inventory_api + self.locations_api = locations_api + self.products_api = products_api + self.item_data = item_data + self.product_data = None + self.init_ui() + + def init_ui(self): + self.setWindowTitle("Add Inventory Item" if not self.item_data else "Edit Inventory Item") + layout = QFormLayout(self) + + self.product_input = QComboBox() + self.sku_input = QLineEdit() + self.sku_input.setReadOnly(True) + self.name_input = QLineEdit() + self.name_input.setReadOnly(True) + self.quantity_input = QSpinBox() + self.quantity_input.setRange(0, 1000000) + self.location_input = QComboBox() + self.expiration_date_input = QDateEdit() + self.expiration_date_input.setCalendarPopup(True) + + layout.addRow("Product:", self.product_input) + layout.addRow("SKU:", self.sku_input) + layout.addRow("Name:", self.name_input) + layout.addRow("Quantity:", self.quantity_input) + layout.addRow("Location:", self.location_input) + layout.addRow("Expiration Date:", self.expiration_date_input) + + self.load_products() + self.load_locations() + + if self.item_data: + self.set_existing_item_data() + else: + self.quantity_input.setValue(0) + self.expiration_date_input.setDate(datetime.now().date()) + + self.product_input.currentIndexChanged.connect(self.update_product_details) + + buttons = QHBoxLayout() + save_button = QPushButton("Save") + save_button.clicked.connect(self.save_item) + cancel_button = QPushButton("Cancel") + cancel_button.clicked.connect(self.reject) + buttons.addWidget(save_button) + buttons.addWidget(cancel_button) + layout.addRow(buttons) + + def load_products(self): + try: + products = self.products_api.get_products() + for product in products: + self.product_input.addItem(f"{product.sku} - {product.name}", product.product_id) + except Exception as e: + QMessageBox.warning(self, "Error", f"Failed to load products: {str(e)}") + + def load_locations(self): + try: + locations = self.locations_api.get_locations() + for location in locations: + self.location_input.addItem(location.name, location.location_id) + except Exception as e: + QMessageBox.warning(self, "Error", f"Failed to load locations: {str(e)}") + + def set_existing_item_data(self): + if self.item_data: + self.product_data = self.products_api.get_product(self.item_data.product_id) + index = self.product_input.findData(self.item_data.product_id) + if index >= 0: + self.product_input.setCurrentIndex(index) + self.quantity_input.setValue(self.item_data.quantity) + if self.item_data.location_id: + index = self.location_input.findData(self.item_data.location_id) + if index >= 0: + self.location_input.setCurrentIndex(index) + if self.item_data.expiration_date: + self.expiration_date_input.setDate(self.item_data.expiration_date.date()) + self.update_product_details() + + def update_product_details(self): + product_id = self.product_input.currentData() + if product_id: + product = self.products_api.get_product(product_id) + self.sku_input.setText(product.sku) + self.name_input.setText(product.name) + + def save_item(self): + try: + product_id = self.product_input.currentData() + expiration_date = self.expiration_date_input.date().toPython() + + if self.item_data: + # Update existing item + inventory_update = InventoryUpdate( + quantity=self.quantity_input.value(), + location_id=self.location_input.currentData(), + expiration_date=expiration_date + ) + response = self.inventory_api.update_inventory(self.item_data.id, inventory_update) + else: + # Create new item + inventory_create = InventoryCreate( + product_id=product_id, + quantity=self.quantity_input.value(), + location_id=self.location_input.currentData(), + expiration_date=expiration_date + ) + response = self.inventory_api.create_inventory(inventory_create) + + QMessageBox.information(self, "Success", "Item saved successfully!") + self.accept() + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to save item: {str(e)}") + + + +class AdjustmentDialog(QDialog): + def __init__(self, inventory_api: InventoryAPI, id: int, parent=None): + super().__init__(parent) + self.inventory_api = inventory_api + self.id = id + self.init_ui() + + def init_ui(self): + self.setWindowTitle("Adjust Inventory") + layout = QFormLayout(self) + + self.adjustment_input = QSpinBox() + self.adjustment_input.setRange(-1000000, 1000000) + self.reason_input = QLineEdit() + + layout.addRow("Adjustment:", self.adjustment_input) + layout.addRow("Reason:", self.reason_input) + + buttons = QHBoxLayout() + save_button = QPushButton("Save") + save_button.clicked.connect(self.save_adjustment) + cancel_button = QPushButton("Cancel") + cancel_button.clicked.connect(self.reject) + buttons.addWidget(save_button) + buttons.addWidget(cancel_button) + layout.addRow(buttons) + + def save_adjustment(self): + try: + adjustment_data = InventoryAdjustment( + product_id=self.id, + location_id=self.id, # Assuming `id` is the location ID, modify as needed. + quantity_change=self.adjustment_input.value(), + reason=self.reason_input.text(), + timestamp=datetime.now() + ) + response = self.inventory_api.adjust_inventory(self.id, adjustment_data) + QMessageBox.information(self, "Success", "Adjustment saved successfully!") + self.accept() + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to save adjustment: {str(e)}") \ No newline at end of file diff --git a/desktop_app/src/ui/customer_view.py b/desktop_app/src/ui/customer_view.py index e5f9085..fa53a75 100644 --- a/desktop_app/src/ui/customer_view.py +++ b/desktop_app/src/ui/customer_view.py @@ -1,11 +1,11 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem -from desktop_app.src.api import CustomersAPI from desktop_app.src.ui.components import StyledButton +from public_api.api import CustomersAPI, APIClient class CustomerView(QWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.customers_api = CustomersAPI(api_client) @@ -36,15 +36,15 @@ def refresh_customers(self): customers = self.customers_api.get_customers() self.customers_table.setRowCount(len(customers)) for row, customer in enumerate(customers): - self.customers_table.setItem(row, 0, QTableWidgetItem(customer['name'])) - self.customers_table.setItem(row, 1, QTableWidgetItem(customer['email'])) - self.customers_table.setItem(row, 2, QTableWidgetItem(customer['phone'])) - self.customers_table.setItem(row, 3, QTableWidgetItem(customer['address'])) + self.customers_table.setItem(row, 0, QTableWidgetItem(customer.name)) + self.customers_table.setItem(row, 1, QTableWidgetItem(customer.email)) + self.customers_table.setItem(row, 2, QTableWidgetItem(customer.phone)) + self.customers_table.setItem(row, 3, QTableWidgetItem(customer.address)) actions_widget = QWidget() actions_layout = QHBoxLayout(actions_widget) edit_button = StyledButton("Edit") - edit_button.clicked.connect(lambda _, cid=customer['customer_id']: self.edit_customer(cid)) + edit_button.clicked.connect(lambda _, cid=customer.customer_id: self.edit_customer(cid)) actions_layout.addWidget(edit_button) self.customers_table.setCellWidget(row, 4, actions_widget) diff --git a/desktop_app/src/ui/dashboard.py b/desktop_app/src/ui/dashboard.py index 51a2bb5..317ba0f 100644 --- a/desktop_app/src/ui/dashboard.py +++ b/desktop_app/src/ui/dashboard.py @@ -5,8 +5,8 @@ from PySide6.QtGui import QPainter from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel -from desktop_app.src.api import APIClient, ReportsAPI, InventoryAPI, PickListsAPI from desktop_app.src.ui.components import CardWidget +from public_api.api import APIClient, ReportsAPI, InventoryAPI, PickListsAPI class DashboardWidget(QWidget): @@ -24,8 +24,8 @@ def init_ui(self): # Add summary cards cards_layout = QHBoxLayout() kpi_data = self.reports_api.get_kpi_dashboard() - for metric in kpi_data.get("metrics", []): - cards_layout.addWidget(self.create_summary_card(metric["name"], metric["value"])) + for metric in kpi_data.metrics: + cards_layout.addWidget(self.create_summary_card(metric.name, metric.value)) layout.addLayout(cards_layout) # Add charts @@ -43,7 +43,7 @@ def create_summary_card(self, title, value): def create_inventory_chart(self): inventory_data = self.inventory_api.get_inventory_summary() series = QPieSeries() - for category, quantity in inventory_data.get("category_quantities", {}).items(): + for category, quantity in inventory_data.category_quantities.items(): series.append(category, quantity) chart = QChart() @@ -60,12 +60,12 @@ def create_inventory_chart(self): def create_performance_chart(self): start_date = datetime(2023, 1, 1) end_date = datetime(2024, 12, 31) - performance_data = self.pick_lists_api.get_picking_performance(start_date=start_date, end_date=end_date) + picking_performance = self.pick_lists_api.get_picking_performance(start_date=start_date, end_date=end_date) set0 = QBarSet("Average Picking Time") set1 = QBarSet("Items Picked Per Hour") - set0.append(performance_data.get("average_picking_time", 0)) - set1.append(performance_data.get("items_picked_per_hour", 0)) + set0.append(picking_performance.average_picking_time) + set1.append(picking_performance.items_picked_per_hour) series = QBarSeries() series.append(set0) @@ -92,4 +92,4 @@ def create_performance_chart(self): chart_view = QChartView(chart) chart_view.setRenderHint(QPainter.Antialiasing) - return chart_view \ No newline at end of file + return chart_view diff --git a/desktop_app/src/ui/data_analysis.py b/desktop_app/src/ui/data_analysis.py index 110b2e5..636c8bf 100644 --- a/desktop_app/src/ui/data_analysis.py +++ b/desktop_app/src/ui/data_analysis.py @@ -3,12 +3,12 @@ from PySide6.QtGui import QPainter from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QComboBox, QDateEdit -from desktop_app.src.api import ReportsAPI +from public_api.api import ReportsAPI, APIClient from desktop_app.src.ui.components import StyledButton class DataAnalysisWidget(QWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.reports_api = ReportsAPI(api_client) diff --git a/desktop_app/src/ui/inventory_planning.py b/desktop_app/src/ui/inventory_planning.py index a6abffe..a3fab01 100644 --- a/desktop_app/src/ui/inventory_planning.py +++ b/desktop_app/src/ui/inventory_planning.py @@ -1,16 +1,16 @@ -from PySide6.QtCharts import QChart, QChartView, QLineSeries -from PySide6.QtCore import QPointF -from PySide6.QtGui import QPainter +from PySide6.QtCharts import QChart, QChartView, QLineSeries, QValueAxis, QDateTimeAxis +from PySide6.QtCore import QDateTime +from PySide6.QtGui import QPainter, Qt from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QComboBox -from desktop_app.src.api import ProductsAPI, InventoryAPI +from public_api.api import ProductsAPI, InventoryAPI, APIClient from desktop_app.src.ui.components import StyledButton # TODO: Implement all missing inventory api methods class InventoryPlanningWidget(QWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.products_api = ProductsAPI(api_client) @@ -47,7 +47,7 @@ def load_products(self): products = self.products_api.get_products() self.product_combo.clear() for product in products: - self.product_combo.addItem(product['name'], product['product_id']) + self.product_combo.addItem(product.name, product.product_id) def update_forecast(self): product_id = self.product_combo.currentData() @@ -65,12 +65,29 @@ def generate_forecast(self): def update_chart(self, forecast_data): series = QLineSeries() for point in forecast_data['forecast']: - series.append(QPointF(point['date'], point['quantity'])) + date = QDateTime.fromString(point['date'], Qt.ISODate) + x = date.toMSecsSinceEpoch() # Convert date to milliseconds + y = float(point['quantity']) # Convert quantity to float + series.append(x, y) chart = QChart() chart.addSeries(series) chart.setTitle("Inventory Forecast") - chart.createDefaultAxes() + + # Create X axis + axis_x = QDateTimeAxis() + axis_x.setTickCount(5) + axis_x.setFormat("dd-MM-yyyy") + axis_x.setTitleText("Date") + chart.addAxis(axis_x, Qt.AlignBottom) + series.attachAxis(axis_x) + + # Create Y axis + axis_y = QValueAxis() + axis_y.setLabelFormat("%i") + axis_y.setTitleText("Quantity") + chart.addAxis(axis_y, Qt.AlignLeft) + series.attachAxis(axis_y) self.chart_view.setChart(chart) diff --git a/desktop_app/src/ui/inventory_view.py b/desktop_app/src/ui/inventory_view.py index f15438f..801fa1c 100644 --- a/desktop_app/src/ui/inventory_view.py +++ b/desktop_app/src/ui/inventory_view.py @@ -1,67 +1,154 @@ -from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem +from PySide6.QtCore import Signal +from PySide6.QtGui import QColor +from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, + QHeaderView, QDialog, QLineEdit, QStackedWidget, QMessageBox) -from desktop_app.src.api import InventoryAPI -from desktop_app.src.ui.components import StyledButton +from desktop_app.src.ui.components import StyledButton, AdjustmentDialog, InventoryDialog +from public_api.api import InventoryAPI, APIClient, LocationsAPI, ProductsAPI +from public_api.shared_schemas import InventoryWithDetails, Inventory +from .inventory_planning import InventoryPlanningWidget class InventoryView(QWidget): - def __init__(self, api_client): + inventory_updated = Signal() + + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.inventory_api = InventoryAPI(api_client) + self.locations_api = LocationsAPI(api_client) + self.products_api = ProductsAPI(api_client) self.init_ui() def init_ui(self): layout = QVBoxLayout(self) - # Buttons - button_layout = QHBoxLayout() - self.add_button = StyledButton("Add Item") - self.add_button.clicked.connect(self.add_item) + # Stacked Widget for main content and planning + self.stacked_widget = QStackedWidget() + layout.addWidget(self.stacked_widget) + + # Main Inventory View + main_widget = QWidget() + main_layout = QVBoxLayout(main_widget) + + # Controls + controls_layout = QHBoxLayout() + self.search_input = QLineEdit() + self.search_input.setPlaceholderText("Search inventory...") + self.search_input.textChanged.connect(self.filter_inventory) + controls_layout.addWidget(self.search_input) + self.refresh_button = StyledButton("Refresh") self.refresh_button.clicked.connect(self.refresh_inventory) - button_layout.addWidget(self.add_button) - button_layout.addWidget(self.refresh_button) - layout.addLayout(button_layout) + controls_layout.addWidget(self.refresh_button) + + main_layout.addLayout(controls_layout) # Table self.table = QTableWidget() - self.table.setColumnCount(5) - self.table.setHorizontalHeaderLabels(["SKU", "Name", "Quantity", "Location", "Last Updated"]) - layout.addWidget(self.table) + self.table.setColumnCount(7) # Added one more column for the delete button + self.table.setHorizontalHeaderLabels(["SKU", "Name", "Quantity", "Location", "Last Updated", "Actions", "Delete"]) + self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + main_layout.addWidget(self.table) + + self.stacked_widget.addWidget(main_widget) + + # Inventory Planning Widget + self.planning_widget = InventoryPlanningWidget(self.api_client) + self.stacked_widget.addWidget(self.planning_widget) + + # Floating Action Button for adding new items + self.fab = StyledButton("+") + self.fab.clicked.connect(self.add_item) + layout.addWidget(self.fab) + + # Planning toggle button + self.planning_button = StyledButton("Inventory Planning") + self.planning_button.clicked.connect(self.toggle_planning_view) + layout.addWidget(self.planning_button) self.refresh_inventory() def refresh_inventory(self): inventory_data = self.inventory_api.get_inventory() - self.table.setRowCount(len(inventory_data['items'])) - for row, item in enumerate(inventory_data['items']): - self.table.setItem(row, 0, QTableWidgetItem(item['product']['sku'])) - self.table.setItem(row, 1, QTableWidgetItem(item['product']['name'])) - self.table.setItem(row, 2, QTableWidgetItem(str(item['quantity']))) - self.table.setItem(row, 3, QTableWidgetItem(item['location']['name'])) - self.table.setItem(row, 4, QTableWidgetItem(str(item['last_updated']))) - - actions_layout = QHBoxLayout() + self.update_table(inventory_data.items) + + def update_table(self, items: list[InventoryWithDetails]): + self.table.setRowCount(len(items)) + for row, item in enumerate(items): + self.table.setItem(row, 0, QTableWidgetItem(item.product.sku)) + self.table.setItem(row, 1, QTableWidgetItem(item.product.name)) + self.table.setItem(row, 2, QTableWidgetItem(str(item.quantity))) + self.table.setItem(row, 3, QTableWidgetItem(item.location.name)) + self.table.setItem(row, 4, QTableWidgetItem(str(item.last_updated))) + + actions_widget = QWidget() + actions_layout = QHBoxLayout(actions_widget) edit_button = StyledButton("Edit") - edit_button.clicked.connect(lambda _, i=item['inventory_id']: self.edit_item(i)) + edit_button.clicked.connect(lambda _, i=item.id: self.edit_item(i)) adjust_button = StyledButton("Adjust") - adjust_button.clicked.connect(lambda _, i=item['inventory_id']: self.adjust_item(i)) + adjust_button.clicked.connect(lambda _, i=item.id: self.adjust_item(i)) actions_layout.addWidget(edit_button) actions_layout.addWidget(adjust_button) - - actions_widget = QWidget() - actions_widget.setLayout(actions_layout) + actions_layout.setContentsMargins(0, 0, 0, 0) self.table.setCellWidget(row, 5, actions_widget) + # Delete button + delete_button = StyledButton("Delete") + delete_button.clicked.connect(lambda _, i=item.id: self.delete_item(i)) + self.table.setCellWidget(row, 6, delete_button) + + # Color coding based on quantity + if item.quantity == 0: + self.table.item(row, 2).setBackground(QColor(255, 200, 200)) # Light red for out of stock + elif item.quantity < 10: + self.table.item(row, 2).setBackground(QColor(255, 255, 200)) # Light yellow for low stock + + def filter_inventory(self): + search_text = self.search_input.text().lower() + for row in range(self.table.rowCount()): + row_match = False + for col in range(self.table.columnCount()): + item = self.table.item(row, col) + if item and search_text in item.text().lower(): + row_match = True + break + self.table.setRowHidden(row, not row_match) + def add_item(self): - # Implement add item dialog - pass + dialog = InventoryDialog(self.inventory_api, locations_api=self.locations_api, products_api=self.products_api, + parent=self) + if dialog.exec_() == QDialog.Accepted: + self.refresh_inventory() + self.inventory_updated.emit() + + def edit_item(self, id): + item_data: Inventory = self.inventory_api.get_inventory_item(id) + dialog = InventoryDialog(self.inventory_api, self.locations_api, self.products_api, item_data, parent=self) + if dialog.exec_() == QDialog.Accepted: + self.refresh_inventory() + self.inventory_updated.emit() + + def adjust_item(self, id): + dialog = AdjustmentDialog(self.inventory_api, id, parent=self) + if dialog.exec_() == QDialog.Accepted: + self.refresh_inventory() + self.inventory_updated.emit() - def edit_item(self, inventory_id): - # Implement edit item dialog - pass + def delete_item(self, id): + confirm = QMessageBox.question(self, "Confirm Deletion", "Are you sure you want to delete this item?", + QMessageBox.Yes | QMessageBox.No) + if confirm == QMessageBox.Yes: + try: + self.inventory_api.delete_inventory_item(id) + self.refresh_inventory() + self.inventory_updated.emit() + QMessageBox.information(self, "Success", "Item deleted successfully.") + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to delete item: {str(e)}") - def adjust_item(self, inventory_id): - # Implement adjust item dialog - pass + def toggle_planning_view(self): + current_index = self.stacked_widget.currentIndex() + new_index = 1 if current_index == 0 else 0 + self.stacked_widget.setCurrentIndex(new_index) + self.planning_button.setText("Inventory List" if new_index == 1 else "Inventory Planning") \ No newline at end of file diff --git a/desktop_app/src/ui/login_dialog.py b/desktop_app/src/ui/login_dialog.py index 2d889c9..081f9ab 100644 --- a/desktop_app/src/ui/login_dialog.py +++ b/desktop_app/src/ui/login_dialog.py @@ -2,11 +2,12 @@ from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit from requests import HTTPError +from desktop_app.src.services.authentication import AuthenticationService from .components import StyledLineEdit, StyledButton, ErrorDialog class LoginDialog(QDialog): - def __init__(self, auth_service, parent=None): + def __init__(self, auth_service: AuthenticationService, parent=None): super().__init__(parent) self.auth_service = auth_service self.init_ui() @@ -45,8 +46,8 @@ def attempt_login(self): password = self.password_input.text() try: - response = self.auth_service.login(username, password) - if response.get("access_token"): + token = self.auth_service.login(username, password) + if token: self.accept() else: self.show_error_dialog("Invalid username or password") @@ -65,6 +66,6 @@ def keyPressEvent(self, event): else: super().keyPressEvent(event) - def show_error_dialog(self, message): + def show_error_dialog(self, message: str): error_dialog = ErrorDialog(message, self) error_dialog.exec_() diff --git a/desktop_app/src/ui/main_window.py b/desktop_app/src/ui/main_window.py index 66544fd..e92bf89 100644 --- a/desktop_app/src/ui/main_window.py +++ b/desktop_app/src/ui/main_window.py @@ -1,22 +1,24 @@ -from PySide6.QtWidgets import QMainWindow, QTabWidget, QVBoxLayout, QWidget, QStatusBar, QMessageBox -from PySide6.QtGui import QIcon from PySide6.QtCore import Qt +from PySide6.QtGui import QIcon +from PySide6.QtWidgets import QMainWindow, QTabWidget, QVBoxLayout, QWidget, QStatusBar, QMessageBox +from public_api.api import APIClient from .components.dialogs import UserManualDialog, AboutDialog +from .customer_view import CustomerView from .dashboard import DashboardWidget from .inventory_view import InventoryView +from .notification_center import NotificationCenter from .order_view import OrderView from .product_view import ProductView +from .report_generator import ReportGeneratorWidget from .settings.settings_dialog import SettingsDialog -from .supplier_view import SupplierView -from .customer_view import CustomerView from .shipment_view import ShipmentView -from .report_generator import ReportGeneratorWidget +from .supplier_view import SupplierView from .user_management import UserManagementWidget -from .notification_center import NotificationCenter + class MainWindow(QMainWindow): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.init_ui() @@ -86,4 +88,4 @@ def open_user_manual(self): def show_about_dialog(self): about_dialog = AboutDialog(self) - about_dialog.exec() \ No newline at end of file + about_dialog.exec() diff --git a/desktop_app/src/ui/notification_center.py b/desktop_app/src/ui/notification_center.py index cf95481..3d86de5 100644 --- a/desktop_app/src/ui/notification_center.py +++ b/desktop_app/src/ui/notification_center.py @@ -1,9 +1,11 @@ from PySide6.QtCore import Qt, QTimer from PySide6.QtWidgets import QDockWidget, QVBoxLayout, QWidget, QListWidget, QListWidgetItem, QLabel +from public_api.api import APIClient + class NotificationCenter(QDockWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__("Notifications") self.api_client = api_client self.init_ui() @@ -31,7 +33,6 @@ def init_ui(self): self.fetch_notifications() - # TODO: notifications api doesnt exist yet, so this is just a placeholder def fetch_notifications(self): self.notification_list.clear() diff --git a/desktop_app/src/ui/order_view.py b/desktop_app/src/ui/order_view.py index 6cd8879..f682dd7 100644 --- a/desktop_app/src/ui/order_view.py +++ b/desktop_app/src/ui/order_view.py @@ -1,13 +1,14 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QComboBox -from desktop_app.src.api import OrdersAPI +from public_api.api import OrdersAPI, APIClient from desktop_app.src.ui.components import StyledButton +from public_api.shared_schemas import OrderFilter # TODO: Implement missing functions class OrderView(QWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.orders_api = OrdersAPI(api_client) @@ -42,19 +43,20 @@ def refresh_orders(self): if status_filter == "All": status_filter = None - orders = self.orders_api.get_orders(filter_params={"status": status_filter}) + filter = OrderFilter(status=status_filter) + orders = self.orders_api.get_orders(filter_params=filter) self.orders_table.setRowCount(len(orders)) for row, order in enumerate(orders): - self.orders_table.setItem(row, 0, QTableWidgetItem(str(order['order_id']))) - self.orders_table.setItem(row, 1, QTableWidgetItem(order['customer']['name'])) - self.orders_table.setItem(row, 2, QTableWidgetItem(order['order_date'])) - self.orders_table.setItem(row, 3, QTableWidgetItem(f"${order['total_amount']:.2f}")) - self.orders_table.setItem(row, 4, QTableWidgetItem(order['status'])) + self.orders_table.setItem(row, 0, QTableWidgetItem(str(order.order_id))) + self.orders_table.setItem(row, 1, QTableWidgetItem(order.customer.name)) + self.orders_table.setItem(row, 2, QTableWidgetItem(order.order_date.strftime("%Y-%m-%d"))) + self.orders_table.setItem(row, 3, QTableWidgetItem(f"${order.total_amount:.2f}")) + self.orders_table.setItem(row, 4, QTableWidgetItem(order.status)) actions_widget = QWidget() actions_layout = QHBoxLayout(actions_widget) view_button = StyledButton("View") - view_button.clicked.connect(lambda _, oid=order['order_id']: self.view_order(oid)) + view_button.clicked.connect(lambda _, oid=order.order_id: self.view_order(oid)) actions_layout.addWidget(view_button) self.orders_table.setCellWidget(row, 5, actions_widget) diff --git a/desktop_app/src/ui/product_view.py b/desktop_app/src/ui/product_view.py index bb4dc37..15577dd 100644 --- a/desktop_app/src/ui/product_view.py +++ b/desktop_app/src/ui/product_view.py @@ -1,11 +1,11 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem -from desktop_app.src.api import ProductsAPI from desktop_app.src.ui.components import StyledButton +from public_api.api import ProductsAPI, APIClient class ProductView(QWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.products_api = ProductsAPI(api_client) @@ -36,19 +36,19 @@ def refresh_products(self): products = self.products_api.get_products() self.products_table.setRowCount(len(products)) for row, product in enumerate(products): - self.products_table.setItem(row, 0, QTableWidgetItem(product['sku'])) - self.products_table.setItem(row, 1, QTableWidgetItem(product['name'])) - self.products_table.setItem(row, 2, QTableWidgetItem(product['category']['name'])) - self.products_table.setItem(row, 3, QTableWidgetItem(f"${product['price']:.2f}")) + self.products_table.setItem(row, 0, QTableWidgetItem(product.sku)) + self.products_table.setItem(row, 1, QTableWidgetItem(product.name)) + self.products_table.setItem(row, 2, QTableWidgetItem(product.category.name)) + self.products_table.setItem(row, 3, QTableWidgetItem(f"${product.price:.2f}")) # Calculate total stock across all inventory items - total_stock = sum(item['quantity'] for item in product['inventory_items']) + total_stock = sum(item.quantity for item in product.inventory_items) self.products_table.setItem(row, 4, QTableWidgetItem(str(total_stock))) actions_widget = QWidget() actions_layout = QHBoxLayout(actions_widget) edit_button = StyledButton("Edit") - edit_button.clicked.connect(lambda _, pid=product['product_id']: self.edit_product(pid)) + edit_button.clicked.connect(lambda _, pid=product.product_id: self.edit_product(pid)) actions_layout.addWidget(edit_button) self.products_table.setCellWidget(row, 5, actions_widget) @@ -58,4 +58,4 @@ def add_product(self): def edit_product(self, product_id): # Implement edit product dialog - pass \ No newline at end of file + pass diff --git a/desktop_app/src/ui/report_generator.py b/desktop_app/src/ui/report_generator.py index d3066d0..e496b70 100644 --- a/desktop_app/src/ui/report_generator.py +++ b/desktop_app/src/ui/report_generator.py @@ -1,13 +1,13 @@ from PySide6.QtCore import Qt, QDate from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QComboBox, QDateEdit, QTextEdit -from desktop_app.src.api import ReportsAPI +from public_api.api import ReportsAPI, APIClient from desktop_app.src.ui.components import StyledButton # TODO: implement missing functions class ReportGeneratorWidget(QWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.reports_api = ReportsAPI(api_client) diff --git a/desktop_app/src/ui/search_filter.py b/desktop_app/src/ui/search_filter.py index 334c67b..d5fe942 100644 --- a/desktop_app/src/ui/search_filter.py +++ b/desktop_app/src/ui/search_filter.py @@ -1,6 +1,6 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QComboBox, QTableWidget, QTableWidgetItem -from desktop_app.src.api import APIClient +from public_api.api import APIClient from desktop_app.src.ui.components import StyledButton diff --git a/desktop_app/src/ui/settings/general_settings.py b/desktop_app/src/ui/settings/general_settings.py index 63b13d2..54a6977 100644 --- a/desktop_app/src/ui/settings/general_settings.py +++ b/desktop_app/src/ui/settings/general_settings.py @@ -48,10 +48,13 @@ def init_ui(self): layout.addStretch() def on_language_changed(self, language): - self.config_manager.set("language", language) + pass + # self.config_manager.set("language", language) def on_auto_update_toggled(self, state): - self.config_manager.set("auto_update", state) + pass + # self.config_manager.set("auto_update", state) def on_startup_toggled(self, state): - self.config_manager.set("start_on_startup", state) + pass + # self.config_manager.set("start_on_startup", state) diff --git a/desktop_app/src/ui/shipment_view.py b/desktop_app/src/ui/shipment_view.py index 4f1c3ee..7df1fdc 100644 --- a/desktop_app/src/ui/shipment_view.py +++ b/desktop_app/src/ui/shipment_view.py @@ -1,7 +1,8 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QComboBox -from desktop_app.src.api.shipments import ShipmentsAPI from desktop_app.src.ui.components import StyledButton +from public_api.api import ShipmentsAPI +from public_api.shared_schemas import ShipmentFilter # TODO: Implement shipment tracking dialog @@ -39,19 +40,20 @@ def refresh_shipments(self): if status_filter == "All": status_filter = None - shipments = self.shipments_api.get_shipments(filter_params={"status": status_filter}) + filter = ShipmentFilter(status=status_filter) + shipments = self.shipments_api.get_shipments(filter_params=filter) self.shipments_table.setRowCount(len(shipments)) for row, shipment in enumerate(shipments): - self.shipments_table.setItem(row, 0, QTableWidgetItem(str(shipment['shipment_id']))) - self.shipments_table.setItem(row, 1, QTableWidgetItem(str(shipment['order_id']))) - self.shipments_table.setItem(row, 2, QTableWidgetItem(shipment['label_id'])) - self.shipments_table.setItem(row, 3, QTableWidgetItem(shipment['status'])) - self.shipments_table.setItem(row, 4, QTableWidgetItem(shipment['tracking_number'])) + self.shipments_table.setItem(row, 0, QTableWidgetItem(str(shipment.shipment_id))) + self.shipments_table.setItem(row, 1, QTableWidgetItem(str(shipment.order_id))) + self.shipments_table.setItem(row, 2, QTableWidgetItem(shipment.label_id)) + self.shipments_table.setItem(row, 3, QTableWidgetItem(shipment.status)) + self.shipments_table.setItem(row, 4, QTableWidgetItem(shipment.tracking_number)) actions_widget = QWidget() actions_layout = QHBoxLayout(actions_widget) track_button = StyledButton("Track") - track_button.clicked.connect(lambda _, sid=shipment['shipment_id']: self.track_shipment(sid)) + track_button.clicked.connect(lambda _, sid=shipment.shipment_id: self.track_shipment(sid)) actions_layout.addWidget(track_button) self.shipments_table.setCellWidget(row, 5, actions_widget) diff --git a/desktop_app/src/ui/simulation_view.py b/desktop_app/src/ui/simulation_view.py index 05d25ea..94fb7c1 100644 --- a/desktop_app/src/ui/simulation_view.py +++ b/desktop_app/src/ui/simulation_view.py @@ -1,13 +1,14 @@ from PySide6.QtCore import QTimer from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QComboBox, QSpinBox +from public_api.api import APIClient from .components import StyledButton # TODO: Implement simulation stuff class SimulationView(QWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.init_ui() diff --git a/desktop_app/src/ui/supplier_view.py b/desktop_app/src/ui/supplier_view.py index d017ece..a63e8dd 100644 --- a/desktop_app/src/ui/supplier_view.py +++ b/desktop_app/src/ui/supplier_view.py @@ -1,13 +1,13 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem -from desktop_app.src.api import SuppliersAPI from desktop_app.src.ui.components import StyledButton +from public_api.api import SuppliersAPI, APIClient # TODO: Implement Supplier functions class SupplierView(QWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.suppliers_api = SuppliersAPI(api_client) @@ -38,15 +38,15 @@ def refresh_suppliers(self): suppliers = self.suppliers_api.get_suppliers() self.suppliers_table.setRowCount(len(suppliers)) for row, supplier in enumerate(suppliers): - self.suppliers_table.setItem(row, 0, QTableWidgetItem(supplier['name'])) - self.suppliers_table.setItem(row, 1, QTableWidgetItem(supplier['contact_person'])) - self.suppliers_table.setItem(row, 2, QTableWidgetItem(supplier['email'])) - self.suppliers_table.setItem(row, 3, QTableWidgetItem(supplier['phone'])) + self.suppliers_table.setItem(row, 0, QTableWidgetItem(supplier.name)) + self.suppliers_table.setItem(row, 1, QTableWidgetItem(supplier.contact_person)) + self.suppliers_table.setItem(row, 2, QTableWidgetItem(supplier.email)) + self.suppliers_table.setItem(row, 3, QTableWidgetItem(supplier.phone)) actions_widget = QWidget() actions_layout = QHBoxLayout(actions_widget) edit_button = StyledButton("Edit") - edit_button.clicked.connect(lambda _, sid=supplier['supplier_id']: self.edit_supplier(sid)) + edit_button.clicked.connect(lambda _, sid=supplier.supplier_id: self.edit_supplier(sid)) actions_layout.addWidget(edit_button) self.suppliers_table.setCellWidget(row, 4, actions_widget) diff --git a/desktop_app/src/ui/system_diagnostics.py b/desktop_app/src/ui/system_diagnostics.py index 32b03b5..7492854 100644 --- a/desktop_app/src/ui/system_diagnostics.py +++ b/desktop_app/src/ui/system_diagnostics.py @@ -1,11 +1,13 @@ from PySide6.QtCore import QTimer from PySide6.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QProgressBar +from public_api.api import APIClient from .components import StyledButton + # TODO: Implement in full class SystemDiagnosticsWidget(QWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.init_ui() diff --git a/desktop_app/src/ui/training_mode.py b/desktop_app/src/ui/training_mode.py index bf2aa25..e316f4e 100644 --- a/desktop_app/src/ui/training_mode.py +++ b/desktop_app/src/ui/training_mode.py @@ -1,11 +1,12 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QLabel +from public_api.api import APIClient from .components import StyledButton # TODO: IMplement in full class TrainingModeWidget(QWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.current_step = 0 diff --git a/desktop_app/src/ui/user_management.py b/desktop_app/src/ui/user_management.py index 8eb3f59..a14a68a 100644 --- a/desktop_app/src/ui/user_management.py +++ b/desktop_app/src/ui/user_management.py @@ -1,12 +1,12 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem -from desktop_app.src.api import UsersAPI from desktop_app.src.ui.components import StyledButton +from public_api.api import UsersAPI, APIClient # TODO: Implement missing functions class UserManagementWidget(QWidget): - def __init__(self, api_client): + def __init__(self, api_client: APIClient): super().__init__() self.api_client = api_client self.users_api = UsersAPI(api_client) @@ -37,17 +37,17 @@ def refresh_users(self): users = self.users_api.get_users() self.users_table.setRowCount(len(users)) for row, user in enumerate(users): - self.users_table.setItem(row, 0, QTableWidgetItem(user['username'])) - self.users_table.setItem(row, 1, QTableWidgetItem(user['email'])) - self.users_table.setItem(row, 2, QTableWidgetItem(user['role']['role_name'])) - self.users_table.setItem(row, 3, QTableWidgetItem("Active" if user['is_active'] else "Inactive")) + self.users_table.setItem(row, 0, QTableWidgetItem(user.username)) + self.users_table.setItem(row, 1, QTableWidgetItem(user.email)) + self.users_table.setItem(row, 2, QTableWidgetItem(user.role.role_name)) + self.users_table.setItem(row, 3, QTableWidgetItem("Active" if user.is_active else "Inactive")) actions_widget = QWidget() actions_layout = QHBoxLayout(actions_widget) edit_button = StyledButton("Edit") - edit_button.clicked.connect(lambda _, uid=user['user_id']: self.edit_user(uid)) + edit_button.clicked.connect(lambda _, uid=user.user_id: self.edit_user(uid)) delete_button = StyledButton("Delete") - delete_button.clicked.connect(lambda _, uid=user['user_id']: self.delete_user(uid)) + delete_button.clicked.connect(lambda _, uid=user.user_id: self.delete_user(uid)) actions_layout.addWidget(edit_button) actions_layout.addWidget(delete_button) self.users_table.setCellWidget(row, 4, actions_widget) diff --git a/desktop_app/src/ui/warehouse_visualizer.py b/desktop_app/src/ui/warehouse_visualizer.py index 6568063..7f819cc 100644 --- a/desktop_app/src/ui/warehouse_visualizer.py +++ b/desktop_app/src/ui/warehouse_visualizer.py @@ -2,7 +2,7 @@ from PySide6.QtGui import QPen, QBrush, QColor from PySide6.QtWidgets import QWidget, QVBoxLayout, QGraphicsView, QGraphicsScene -from desktop_app.src.api import WarehouseAPI, APIClient +from public_api.api import WarehouseAPI, APIClient class WarehouseVisualizerWidget(QWidget): diff --git a/public_api/__init__.py b/public_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/desktop_app/src/api/__init__.py b/public_api/api/__init__.py similarity index 100% rename from desktop_app/src/api/__init__.py rename to public_api/api/__init__.py diff --git a/public_api/api/assets.py b/public_api/api/assets.py new file mode 100644 index 0000000..826fe55 --- /dev/null +++ b/public_api/api/assets.py @@ -0,0 +1,103 @@ +from typing import Optional + +from public_api.shared_schemas import ( + AssetCreate, AssetUpdate, Asset, AssetWithMaintenance, AssetFilter, + AssetMaintenanceCreate, AssetMaintenanceUpdate, AssetMaintenance, + AssetMaintenanceFilter, AssetWithMaintenanceList, AssetTransfer, + Location +) +from .client import APIClient + + +class AssetsAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_asset(self, asset_data: AssetCreate) -> Asset: + response = self.client.post("/assets/", json=asset_data.model_dump(mode="json")) + return Asset.model_validate(response) + + def get_assets(self, skip: int = 0, limit: int = 100, + asset_filter: Optional[AssetFilter] = None) -> AssetWithMaintenanceList: + params = {"skip": skip, "limit": limit} + if asset_filter: + params.update(asset_filter.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/assets/", params=params) + return AssetWithMaintenanceList.model_validate(response) + + def get_asset(self, asset_id: int) -> AssetWithMaintenance: + response = self.client.get(f"/assets/{asset_id}") + return AssetWithMaintenance.model_validate(response) + + def update_asset(self, asset_id: int, asset_data: AssetUpdate) -> Asset: + response = self.client.put(f"/assets/{asset_id}", json=asset_data.model_dump(mode="json", exclude_unset=True)) + return Asset.model_validate(response) + + def delete_asset(self, asset_id: int) -> Asset: + response = self.client.delete(f"/assets/{asset_id}") + return Asset.model_validate(response) + + def get_asset_types(self) -> list[str]: + response = self.client.get("/assets/types") + return [str(item) for item in response] + + def get_asset_statuses(self) -> list[str]: + response = self.client.get("/assets/statuses") + return [str(item) for item in response] + + def create_asset_maintenance(self, maintenance_data: AssetMaintenanceCreate) -> AssetMaintenance: + response = self.client.post("/assets/maintenance", json=maintenance_data.model_dump(mode="json")) + return AssetMaintenance.model_validate(response) + + def get_asset_maintenances(self, skip: int = 0, limit: int = 100, + maintenance_filter: Optional[AssetMaintenanceFilter] = None) -> list[AssetMaintenance]: + params = {"skip": skip, "limit": limit} + if maintenance_filter: + params.update(maintenance_filter.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/assets/maintenance", params=params) + return [AssetMaintenance.model_validate(item) for item in response] + + def get_asset_maintenance(self, maintenance_id: int) -> AssetMaintenance: + response = self.client.get(f"/assets/maintenance/{maintenance_id}") + return AssetMaintenance.model_validate(response) + + def update_asset_maintenance(self, maintenance_id: int, + maintenance_data: AssetMaintenanceUpdate) -> AssetMaintenance: + response = self.client.put(f"/assets/maintenance/{maintenance_id}", + json=maintenance_data.model_dump(mode="json", exclude_unset=True)) + return AssetMaintenance.model_validate(response) + + def delete_asset_maintenance(self, maintenance_id: int) -> AssetMaintenance: + response = self.client.delete(f"/assets/maintenance/{maintenance_id}") + return AssetMaintenance.model_validate(response) + + def get_maintenance_types(self) -> list[str]: + response = self.client.get("/assets/maintenance/types") + return [str(item) for item in response] + + def get_asset_maintenance_history(self, asset_id: int, skip: int = 0, limit: int = 100) -> list[AssetMaintenance]: + response = self.client.get(f"/assets/{asset_id}/maintenance_history", params={"skip": skip, "limit": limit}) + return [AssetMaintenance.model_validate(item) for item in response] + + def schedule_asset_maintenance(self, asset_id: int, maintenance_data: AssetMaintenanceCreate) -> AssetMaintenance: + response = self.client.post(f"/assets/{asset_id}/schedule_maintenance", + json=maintenance_data.model_dump(mode="json")) + return AssetMaintenance.model_validate(response) + + def complete_asset_maintenance(self, maintenance_id: int, + completion_data: AssetMaintenanceUpdate) -> AssetMaintenance: + response = self.client.put(f"/assets/maintenance/{maintenance_id}/complete", + json=completion_data.model_dump(mode="json", exclude_unset=True)) + return AssetMaintenance.model_validate(response) + + def get_asset_current_location(self, asset_id: int) -> Location: + response = self.client.get(f"/assets/{asset_id}/current_location") + return Location.model_validate(response) + + def transfer_asset(self, asset_id: int, transfer_data: AssetTransfer) -> Asset: + response = self.client.post(f"/assets/{asset_id}/transfer", json=transfer_data.model_dump(mode="json")) + return Asset.model_validate(response) + + def get_assets_due_for_maintenance(self) -> list[AssetWithMaintenance]: + response = self.client.get("/assets/due_for_maintenance") + return [AssetWithMaintenance.model_validate(item) for item in response] diff --git a/public_api/api/audit.py b/public_api/api/audit.py new file mode 100644 index 0000000..e1d5276 --- /dev/null +++ b/public_api/api/audit.py @@ -0,0 +1,70 @@ +from typing import Optional, List + +from public_api.shared_schemas import ( + AuditLogCreate, AuditLog, AuditLogWithUser, AuditLogFilter, + AuditSummary, AuditLogExport +) +from .client import APIClient + + +class AuditAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_audit_log(self, log: AuditLogCreate) -> AuditLog: + response = self.client.post("/audit/logs", json=log.model_dump(mode="json")) + return AuditLog.model_validate(response) + + def get_audit_logs(self, skip: int = 0, limit: int = 100, + filter_params: Optional[AuditLogFilter] = None) -> List[AuditLogWithUser]: + params = {"skip": skip, "limit": limit} + if filter_params: + params.update(filter_params.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/audit/logs", params=params) + return [AuditLogWithUser.model_validate(item) for item in response] + + def get_audit_log(self, log_id: int) -> AuditLogWithUser: + response = self.client.get(f"/audit/logs/{log_id}") + return AuditLogWithUser.model_validate(response) + + def get_audit_summary(self, date_from: Optional[int] = None, + date_to: Optional[int] = None) -> AuditSummary: + params = {} + if date_from: + params["date_from"] = date_from + if date_to: + params["date_to"] = date_to + response = self.client.get("/audit/logs/summary", params=params) + return AuditSummary.model_validate(response) + + def get_user_audit_logs(self, user_id: int, skip: int = 0, limit: int = 100) -> List[AuditLog]: + response = self.client.get(f"/audit/logs/user/{user_id}", params={"skip": skip, "limit": limit}) + return [AuditLog.model_validate(item) for item in response] + + def get_table_audit_logs(self, table_name: str, skip: int = 0, limit: int = 100) -> List[AuditLog]: + response = self.client.get(f"/audit/logs/table/{table_name}", params={"skip": skip, "limit": limit}) + return [AuditLog.model_validate(item) for item in response] + + def get_record_audit_logs(self, table_name: str, record_id: int, + skip: int = 0, limit: int = 100) -> List[AuditLog]: + response = self.client.get(f"/audit/logs/record/{table_name}/{record_id}", + params={"skip": skip, "limit": limit}) + return [AuditLog.model_validate(item) for item in response] + + def export_audit_logs(self, date_from: Optional[int] = None, + date_to: Optional[int] = None) -> AuditLogExport: + params = {} + if date_from: + params["date_from"] = date_from + if date_to: + params["date_to"] = date_to + response = self.client.get("/audit/logs/export", params=params) + return AuditLogExport.model_validate(response) + + def get_audit_log_actions(self) -> List[str]: + response = self.client.get("/audit/logs/actions") + return [str(item) for item in response] + + def get_audited_tables(self) -> List[str]: + response = self.client.get("/audit/logs/tables") + return [str(item) for item in response] diff --git a/public_api/api/carriers.py b/public_api/api/carriers.py new file mode 100644 index 0000000..85b2b01 --- /dev/null +++ b/public_api/api/carriers.py @@ -0,0 +1,30 @@ +from typing import List + +from public_api.shared_schemas import CarrierCreate, CarrierUpdate, Carrier +from .client import APIClient + + +class CarriersAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_carrier(self, carrier_data: CarrierCreate) -> Carrier: + response = self.client.post("/carriers/", json=carrier_data.model_dump(mode="json")) + return Carrier.model_validate(response) + + def get_carriers(self, skip: int = 0, limit: int = 100) -> List[Carrier]: + response = self.client.get("/carriers/", params={"skip": skip, "limit": limit}) + return [Carrier.model_validate(item) for item in response] + + def get_carrier(self, carrier_id: int) -> Carrier: + response = self.client.get(f"/carriers/{carrier_id}") + return Carrier.model_validate(response) + + def update_carrier(self, carrier_id: int, carrier_data: CarrierUpdate) -> Carrier: + response = self.client.put(f"/carriers/{carrier_id}", + json=carrier_data.model_dump(mode="json", exclude_unset=True)) + return Carrier.model_validate(response) + + def delete_carrier(self, carrier_id: int) -> Carrier: + response = self.client.delete(f"/carriers/{carrier_id}") + return Carrier.model_validate(response) diff --git a/desktop_app/src/api/client.py b/public_api/api/client.py similarity index 91% rename from desktop_app/src/api/client.py rename to public_api/api/client.py index 83fc399..ff2f2db 100644 --- a/desktop_app/src/api/client.py +++ b/public_api/api/client.py @@ -1,11 +1,9 @@ import requests -from desktop_app.src.config import API_BASE_URL - class APIClient: - def __init__(self): - self.base_url = API_BASE_URL + def __init__(self, base_url: str): + self.base_url = base_url self.session = requests.Session() self.token = None diff --git a/public_api/api/customers.py b/public_api/api/customers.py new file mode 100644 index 0000000..26d7e69 --- /dev/null +++ b/public_api/api/customers.py @@ -0,0 +1,40 @@ +from typing import List, Optional + +from public_api.shared_schemas import ( + CustomerCreate, CustomerUpdate, Customer, CustomerFilter, Order +) +from .client import APIClient + + +class CustomersAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_customer(self, customer_data: CustomerCreate) -> Customer: + response = self.client.post("/customers/", json=customer_data.model_dump(mode="json")) + return Customer.model_validate(response) + + def get_customers(self, skip: int = 0, limit: int = 100, + customer_filter: Optional[CustomerFilter] = None) -> List[Customer]: + params = {"skip": skip, "limit": limit} + if customer_filter: + params.update(customer_filter.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/customers/", params=params) + return [Customer.model_validate(item) for item in response] + + def get_customer(self, customer_id: int) -> Customer: + response = self.client.get(f"/customers/{customer_id}") + return Customer.model_validate(response) + + def update_customer(self, customer_id: int, customer_data: CustomerUpdate) -> Customer: + response = self.client.put(f"/customers/{customer_id}", + json=customer_data.model_dump(mode="json", exclude_unset=True)) + return Customer.model_validate(response) + + def delete_customer(self, customer_id: int) -> Customer: + response = self.client.delete(f"/customers/{customer_id}") + return Customer.model_validate(response) + + def get_customer_orders(self, customer_id: int, skip: int = 0, limit: int = 100) -> List[Order]: + response = self.client.get(f"/customers/{customer_id}/orders", params={"skip": skip, "limit": limit}) + return [Order.model_validate(item) for item in response] diff --git a/public_api/api/inventory.py b/public_api/api/inventory.py new file mode 100644 index 0000000..a873cc3 --- /dev/null +++ b/public_api/api/inventory.py @@ -0,0 +1,134 @@ +from typing import Optional, List, Dict + +from public_api.shared_schemas import ( + InventoryCreate, InventoryUpdate, Inventory, InventoryList, InventoryFilter, + InventoryTransfer, InventoryReport, ProductWithInventory, Product, + LocationWithInventory, InventoryMovement, InventorySummary, StocktakeCreate, + StocktakeResult, ABCAnalysisResult, InventoryLocationSuggestion, + BulkImportData, BulkImportResult, StorageUtilization, InventoryAdjustment +) +from .client import APIClient + + +class InventoryAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_inventory(self, inventory_data: InventoryCreate) -> Inventory: + response = self.client.post("/inventory/", json=inventory_data.model_dump(mode="json")) + return Inventory.model_validate(response) + + def get_inventory(self, skip: int = 0, limit: int = 100, + inventory_filter: Optional[InventoryFilter] = None) -> InventoryList: + params = {"skip": skip, "limit": limit} + if inventory_filter: + params.update(inventory_filter.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/inventory", params=params) + return InventoryList.model_validate(response) + + def get_inventory_item(self, id: int) -> Inventory: + response = self.client.get(f"/inventory/{id}") + return Inventory.model_validate(response) + + def update_inventory(self, id: int, inventory_data: InventoryUpdate) -> Inventory: + response = self.client.put(f"/inventory/{id}", + json=inventory_data.model_dump(mode="json", exclude_unset=True)) + return Inventory.model_validate(response) + + def adjust_inventory(self, id: int, adjustment_data: InventoryAdjustment) -> Inventory: + response = self.client.post(f"/inventory/{id}/adjust", json=adjustment_data.model_dump(mode="json")) + return Inventory.model_validate(response) + + def transfer_inventory(self, transfer_data: InventoryTransfer) -> Inventory: + response = self.client.post("/inventory/transfer", json=transfer_data.model_dump(mode="json")) + return Inventory.model_validate(response) + + def get_inventory_report(self) -> InventoryReport: + response = self.client.get("/inventory/report") + return InventoryReport.model_validate(response) + + def perform_cycle_count(self, location_id: int, counted_items: List[InventoryUpdate]) -> List[Inventory]: + response = self.client.post("/inventory/cycle_count", json={ + "location_id": location_id, + "counted_items": [item.model_dump(mode="json") for item in counted_items] + }) + return [Inventory.model_validate(item) for item in response] + + def get_low_stock_items(self, threshold: int = 10) -> List[ProductWithInventory]: + response = self.client.get("/inventory/low_stock", params={"threshold": threshold}) + return [ProductWithInventory.model_validate(item) for item in response] + + def get_out_of_stock_items(self) -> List[Product]: + response = self.client.get("/inventory/out_of_stock") + return [Product.model_validate(item) for item in response] + + def create_reorder_list(self, threshold: int = 10) -> List[Product]: + response = self.client.post("/inventory/reorder", params={"threshold": threshold}) + return [Product.model_validate(item) for item in response] + + def get_product_locations(self, product_id: int) -> List[LocationWithInventory]: + response = self.client.get(f"/inventory/product_locations/{product_id}") + return [LocationWithInventory.model_validate(item) for item in response] + + def batch_update_inventory(self, updates: List[InventoryUpdate]) -> List[Inventory]: + response = self.client.post("/inventory/batch_update", + json=[update.model_dump(mode="json") for update in updates]) + return [Inventory.model_validate(item) for item in response] + + def get_inventory_movement_history(self, product_id: int, start_date: Optional[int] = None, + end_date: Optional[int] = None) -> List[InventoryMovement]: + params = {} + if start_date: + params["start_date"] = start_date + if end_date: + params["end_date"] = end_date + response = self.client.get(f"/inventory/movement_history/{product_id}", params=params) + return [InventoryMovement.model_validate(item) for item in response] + + def get_inventory_summary(self) -> InventorySummary: + response = self.client.get("/inventory/summary") + return InventorySummary.model_validate(response) + + def perform_stocktake(self, stocktake_data: StocktakeCreate) -> StocktakeResult: + response = self.client.post("/inventory/stocktake", json=stocktake_data.model_dump(mode="json")) + return StocktakeResult.model_validate(response) + + def perform_abc_analysis(self) -> ABCAnalysisResult: + response = self.client.get("/inventory/abc_analysis") + return ABCAnalysisResult.model_validate(response) + + def optimize_inventory_locations(self) -> List[InventoryLocationSuggestion]: + response = self.client.post("/inventory/optimize_locations") + return [InventoryLocationSuggestion.model_validate(item) for item in response] + + def get_expiring_soon_inventory(self, days: int = 30) -> List[ProductWithInventory]: + response = self.client.get("/inventory/expiring_soon", params={"days": days}) + return [ProductWithInventory.model_validate(item) for item in response] + + def bulk_import_inventory(self, import_data: BulkImportData) -> BulkImportResult: + response = self.client.post("/inventory/bulk_import", json=import_data.model_dump(mode="json")) + return BulkImportResult.model_validate(response) + + def get_storage_utilization(self) -> StorageUtilization: + response = self.client.get("/inventory/storage_utilization") + return StorageUtilization.model_validate(response) + + def get_inventory_forecast(self, product_id: int) -> Dict: + response = self.client.get(f"/inventory/forecast/{product_id}") + return response + + def generate_inventory_forecast(self, product_id: int) -> Dict: + response = self.client.post(f"/inventory/forecast/{product_id}") + return response + + def get_reorder_suggestions(self) -> List[Dict]: + response = self.client.get("/inventory/reorder_suggestions") + return response + + def delete_inventory_item(self, id: int) -> Inventory: + response = self.client.delete(f"/inventory/{id}") + if response.status_code == 404: + raise Exception("Inventory item not found") + elif response.status_code != 200: + raise Exception(f"Failed to delete inventory item: {response.text}") + return Inventory.model_validate(response.json()) diff --git a/public_api/api/locations.py b/public_api/api/locations.py new file mode 100644 index 0000000..3d93daf --- /dev/null +++ b/public_api/api/locations.py @@ -0,0 +1,36 @@ +from typing import List, Optional + +from public_api.shared_schemas.inventory import ( + LocationCreate, LocationUpdate, Location, LocationWithInventory, LocationFilter +) +from .client import APIClient + + +class LocationsAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_location(self, location_data: LocationCreate) -> Location: + response = self.client.post("/locations/", json=location_data.model_dump(mode="json")) + return Location.model_validate(response) + + def get_locations(self, skip: int = 0, limit: int = 100, + location_filter: Optional[LocationFilter] = None) -> List[LocationWithInventory]: + params = {"skip": skip, "limit": limit} + if location_filter: + params.update(location_filter.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/locations/", params=params) + return [LocationWithInventory.model_validate(item) for item in response] + + def get_location(self, location_id: int) -> LocationWithInventory: + response = self.client.get(f"/locations/{location_id}") + return LocationWithInventory.model_validate(response) + + def update_location(self, location_id: int, location_data: LocationUpdate) -> Location: + response = self.client.put(f"/locations/{location_id}", + json=location_data.model_dump(mode="json", exclude_unset=True)) + return Location.model_validate(response) + + def delete_location(self, location_id: int) -> Location: + response = self.client.delete(f"/locations/{location_id}") + return Location.model_validate(response) diff --git a/public_api/api/orders.py b/public_api/api/orders.py new file mode 100644 index 0000000..7f4618d --- /dev/null +++ b/public_api/api/orders.py @@ -0,0 +1,79 @@ +from typing import List, Optional + +from public_api.shared_schemas import ( + OrderCreate, OrderUpdate, Order, OrderWithDetails, OrderFilter, + OrderSummary, ShippingInfo, OrderItemCreate, BulkOrderImportData, + BulkOrderImportResult, OrderProcessingTimes +) +from .client import APIClient + + +class OrdersAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_order(self, order_data: OrderCreate) -> Order: + response = self.client.post("/orders/", json=order_data.model_dump(mode="json")) + return Order.model_validate(response) + + def get_orders(self, skip: int = 0, limit: int = 100, + filter_params: Optional[OrderFilter] = None) -> List[OrderWithDetails]: + params = {"skip": skip, "limit": limit} + if filter_params: + params.update(filter_params.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/orders/", params=params) + return [OrderWithDetails.model_validate(item) for item in response] + + def get_order(self, order_id: int) -> OrderWithDetails: + response = self.client.get(f"/orders/{order_id}") + return OrderWithDetails.model_validate(response) + + def update_order(self, order_id: int, order_data: OrderUpdate) -> Order: + response = self.client.put(f"/orders/{order_id}", json=order_data.model_dump(mode="json", exclude_unset=True)) + return Order.model_validate(response) + + def delete_order(self, order_id: int) -> Order: + response = self.client.delete(f"/orders/{order_id}") + return Order.model_validate(response) + + def get_order_summary(self, date_from: Optional[int] = None, + date_to: Optional[int] = None) -> OrderSummary: + params = {} + if date_from: + params["date_from"] = date_from + if date_to: + params["date_to"] = date_to + response = self.client.get("/orders/summary", params=params) + return OrderSummary.model_validate(response) + + def cancel_order(self, order_id: int) -> Order: + response = self.client.post(f"/orders/{order_id}/cancel") + return Order.model_validate(response) + + def ship_order(self, order_id: int, shipping_info: ShippingInfo) -> Order: + response = self.client.post(f"/orders/{order_id}/ship", json=shipping_info.model_dump(mode="json")) + return Order.model_validate(response) + + def cancel_order_item(self, order_id: int, item_id: int) -> Order: + response = self.client.post(f"/orders/{order_id}/cancel_item", json={"item_id": item_id}) + return Order.model_validate(response) + + def add_order_item(self, order_id: int, item_data: OrderItemCreate) -> Order: + response = self.client.post(f"/orders/{order_id}/add_item", json=item_data.model_dump(mode="json")) + return Order.model_validate(response) + + def get_backorders(self) -> List[Order]: + response = self.client.get("/orders/backorders") + return [Order.model_validate(item) for item in response] + + def bulk_import_orders(self, import_data: BulkOrderImportData) -> BulkOrderImportResult: + response = self.client.post("/orders/bulk_import", json=import_data.model_dump(mode="json")) + return BulkOrderImportResult.model_validate(response) + + def get_order_processing_times(self, start_date: int, end_date: int) -> OrderProcessingTimes: + params = { + "start_date": start_date, + "end_date": end_date + } + response = self.client.get("/orders/processing_times", params=params) + return OrderProcessingTimes.model_validate(response) diff --git a/public_api/api/permissions.py b/public_api/api/permissions.py new file mode 100644 index 0000000..dc5568b --- /dev/null +++ b/public_api/api/permissions.py @@ -0,0 +1,30 @@ +from typing import List + +from public_api.shared_schemas import PermissionCreate, PermissionUpdate, Permission +from .client import APIClient + + +class PermissionsAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_permission(self, permission_data: PermissionCreate) -> Permission: + response = self.client.post("/permissions/", json=permission_data.model_dump(mode="json")) + return Permission.model_validate(response) + + def get_permissions(self, skip: int = 0, limit: int = 100) -> List[Permission]: + response = self.client.get("/permissions/", params={"skip": skip, "limit": limit}) + return [Permission.model_validate(item) for item in response] + + def get_permission(self, permission_id: int) -> Permission: + response = self.client.get(f"/permissions/{permission_id}") + return Permission.model_validate(response) + + def update_permission(self, permission_id: int, permission_data: PermissionUpdate) -> Permission: + response = self.client.put(f"/permissions/{permission_id}", + json=permission_data.model_dump(mode="json", exclude_unset=True)) + return Permission.model_validate(response) + + def delete_permission(self, permission_id: int) -> Permission: + response = self.client.delete(f"/permissions/{permission_id}") + return Permission.model_validate(response) diff --git a/public_api/api/pick_lists.py b/public_api/api/pick_lists.py new file mode 100644 index 0000000..8e0a65f --- /dev/null +++ b/public_api/api/pick_lists.py @@ -0,0 +1,57 @@ +from typing import List, Optional + +from public_api.shared_schemas import ( + PickListCreate, PickListUpdate, PickList, PickListFilter, + OptimizedPickingRoute, PickingPerformance +) +from .client import APIClient + + +class PickListsAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_pick_list(self, pick_list_data: PickListCreate) -> PickList: + response = self.client.post("/pick_lists/", json=pick_list_data.model_dump(mode="json")) + return PickList.model_validate(response) + + def get_pick_lists(self, skip: int = 0, limit: int = 100, + filter_params: Optional[PickListFilter] = None) -> List[PickList]: + params = {"skip": skip, "limit": limit} + if filter_params: + params.update(filter_params.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/pick_lists/", params=params) + return [PickList.model_validate(item) for item in response] + + def get_pick_list(self, pick_list_id: int) -> PickList: + response = self.client.get(f"/pick_lists/{pick_list_id}") + return PickList.model_validate(response) + + def update_pick_list(self, pick_list_id: int, pick_list_data: PickListUpdate) -> PickList: + response = self.client.put(f"/pick_lists/{pick_list_id}", + json=pick_list_data.model_dump(mode="json", exclude_unset=True)) + return PickList.model_validate(response) + + def delete_pick_list(self, pick_list_id: int) -> PickList: + response = self.client.delete(f"/pick_lists/{pick_list_id}") + return PickList.model_validate(response) + + def optimize_picking_route(self, pick_list_id: int) -> OptimizedPickingRoute: + response = self.client.get(f"/pick_lists/optimize_route", params={"pick_list_id": pick_list_id}) + return OptimizedPickingRoute.model_validate(response) + + def start_pick_list(self, pick_list_id: int) -> PickList: + response = self.client.post(f"/pick_lists/{pick_list_id}/start") + return PickList.model_validate(response) + + def complete_pick_list(self, pick_list_id: int) -> PickList: + response = self.client.post(f"/pick_lists/{pick_list_id}/complete") + return PickList.model_validate(response) + + def get_picking_performance(self, start_date: int, end_date: int) -> PickingPerformance: + params = { + "start_date": start_date, + "end_date": end_date + } + response = self.client.get("/pick_lists/performance", params=params) + return PickingPerformance.model_validate(response) diff --git a/public_api/api/po_items.py b/public_api/api/po_items.py new file mode 100644 index 0000000..660736f --- /dev/null +++ b/public_api/api/po_items.py @@ -0,0 +1,30 @@ +from typing import List + +from public_api.shared_schemas import POItem, POItemUpdate +from .client import APIClient + + +class POItemsAPI: + def __init__(self, client: APIClient): + self.client = client + + def get_po_item(self, po_item_id: int) -> POItem: + response = self.client.get(f"/po_items/{po_item_id}") + return POItem.model_validate(response) + + def update_po_item(self, po_item_id: int, po_item_data: POItemUpdate) -> POItem: + response = self.client.put(f"/po_items/{po_item_id}", + json=po_item_data.model_dump(mode="json", exclude_unset=True)) + return POItem.model_validate(response) + + def get_po_items(self, skip: int = 0, limit: int = 100) -> List[POItem]: + response = self.client.get("/po_items", params={"skip": skip, "limit": limit}) + return [POItem.model_validate(item) for item in response] + + def get_po_items_by_product(self, product_id: int, skip: int = 0, limit: int = 100) -> List[POItem]: + response = self.client.get(f"/po_items/by_product/{product_id}", params={"skip": skip, "limit": limit}) + return [POItem.model_validate(item) for item in response] + + def get_pending_receipt_po_items(self, skip: int = 0, limit: int = 100) -> List[POItem]: + response = self.client.get("/po_items/pending_receipt", params={"skip": skip, "limit": limit}) + return [POItem.model_validate(item) for item in response] diff --git a/public_api/api/product_categories.py b/public_api/api/product_categories.py new file mode 100644 index 0000000..1a60540 --- /dev/null +++ b/public_api/api/product_categories.py @@ -0,0 +1,30 @@ +from typing import List + +from public_api.shared_schemas.inventory import ProductCategoryCreate, ProductCategoryUpdate, ProductCategory +from .client import APIClient + + +class ProductCategoriesAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_category(self, category_data: ProductCategoryCreate) -> ProductCategory: + response = self.client.post("/product_categories/", json=category_data.model_dump(mode="json")) + return ProductCategory.model_validate(response) + + def get_categories(self, skip: int = 0, limit: int = 100) -> List[ProductCategory]: + response = self.client.get("/product_categories/", params={"skip": skip, "limit": limit}) + return [ProductCategory.model_validate(item) for item in response] + + def get_category(self, category_id: int) -> ProductCategory: + response = self.client.get(f"/product_categories/{category_id}") + return ProductCategory.model_validate(response) + + def update_category(self, category_id: int, category_data: ProductCategoryUpdate) -> ProductCategory: + response = self.client.put(f"/product_categories/{category_id}", + json=category_data.model_dump(mode="json", exclude_unset=True)) + return ProductCategory.model_validate(response) + + def delete_category(self, category_id: int) -> ProductCategory: + response = self.client.delete(f"/product_categories/{category_id}") + return ProductCategory.model_validate(response) diff --git a/public_api/api/products.py b/public_api/api/products.py new file mode 100644 index 0000000..b73f244 --- /dev/null +++ b/public_api/api/products.py @@ -0,0 +1,54 @@ +from typing import List, Optional + +from public_api.shared_schemas.inventory import ( + ProductCreate, ProductUpdate, Product, ProductWithCategoryAndInventory, + ProductFilter, BarcodeData +) +from .client import APIClient + + +class ProductsAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_product(self, product_data: ProductCreate) -> Product: + response = self.client.post("/products/", json=product_data.model_dump(mode="json")) + return Product.model_validate(response) + + def get_products(self, skip: int = 0, limit: int = 100, + product_filter: Optional[ProductFilter] = None) -> List[ProductWithCategoryAndInventory]: + params = {"skip": skip, "limit": limit} + if product_filter: + params.update(product_filter.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/products/", params=params) + return [ProductWithCategoryAndInventory.model_validate(item) for item in response] + + def get_product(self, product_id: int) -> ProductWithCategoryAndInventory: + response = self.client.get(f"/products/{product_id}") + return ProductWithCategoryAndInventory.model_validate(response) + + def update_product(self, product_id: int, product_data: ProductUpdate) -> Product: + response = self.client.put(f"/products/{product_id}", + json=product_data.model_dump(mode="json", exclude_unset=True)) + return Product.model_validate(response) + + def delete_product(self, product_id: int) -> Product: + response = self.client.delete(f"/products/{product_id}") + return Product.model_validate(response) + + def get_product_by_barcode(self, barcode: str) -> Product: + barcode_data = BarcodeData(barcode=barcode) + response = self.client.post("/products/barcode", json=barcode_data.model_dump(mode="json")) + return Product.model_validate(response) + + def get_product_substitutes(self, product_id: int) -> List[Product]: + response = self.client.get(f"/products/{product_id}/substitutes") + return [Product.model_validate(item) for item in response] + + def add_product_substitute(self, product_id: int, substitute_id: int) -> Product: + response = self.client.post(f"/products/{product_id}/substitutes", json={"substitute_id": substitute_id}) + return Product.model_validate(response) + + def get_max_product_id(self) -> int: + response = self.client.get("/products/max_id") + return response diff --git a/public_api/api/purchase_orders.py b/public_api/api/purchase_orders.py new file mode 100644 index 0000000..d512d4c --- /dev/null +++ b/public_api/api/purchase_orders.py @@ -0,0 +1,42 @@ +from typing import List, Optional + +from public_api.shared_schemas import ( + PurchaseOrderCreate, PurchaseOrderUpdate, PurchaseOrder, + PurchaseOrderWithDetails, PurchaseOrderFilter, POItemReceive +) +from .client import APIClient + + +class PurchaseOrdersAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_purchase_order(self, purchase_order_data: PurchaseOrderCreate) -> PurchaseOrder: + response = self.client.post("/purchase_orders/", json=purchase_order_data.model_dump(mode="json")) + return PurchaseOrder.model_validate(response) + + def get_purchase_orders(self, skip: int = 0, limit: int = 100, + po_filter: Optional[PurchaseOrderFilter] = None) -> List[PurchaseOrderWithDetails]: + params = {"skip": skip, "limit": limit} + if po_filter: + params.update(po_filter.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/purchase_orders/", params=params) + return [PurchaseOrderWithDetails.model_validate(item) for item in response] + + def get_purchase_order(self, po_id: int) -> PurchaseOrderWithDetails: + response = self.client.get(f"/purchase_orders/{po_id}") + return PurchaseOrderWithDetails.model_validate(response) + + def update_purchase_order(self, po_id: int, po_data: PurchaseOrderUpdate) -> PurchaseOrder: + response = self.client.put(f"/purchase_orders/{po_id}", + json=po_data.model_dump(mode="json", exclude_unset=True)) + return PurchaseOrder.model_validate(response) + + def delete_purchase_order(self, po_id: int) -> PurchaseOrder: + response = self.client.delete(f"/purchase_orders/{po_id}") + return PurchaseOrder.model_validate(response) + + def receive_purchase_order(self, po_id: int, received_items: List[POItemReceive]) -> PurchaseOrder: + response = self.client.post(f"/purchase_orders/{po_id}/receive", + json={"received_items": [item.model_dump(mode="json") for item in received_items]}) + return PurchaseOrder.model_validate(response) diff --git a/public_api/api/quality.py b/public_api/api/quality.py new file mode 100644 index 0000000..2365f89 --- /dev/null +++ b/public_api/api/quality.py @@ -0,0 +1,129 @@ +from typing import Optional, List + +from public_api.shared_schemas.quality import ( + QualityCheckCreate, QualityCheckUpdate, QualityCheckWithProduct, QualityCheckFilter, + QualityMetrics, QualityStandardCreate, QualityStandardUpdate, QualityStandard, + QualityAlertCreate, QualityAlertUpdate, QualityAlert, ProductDefectRate, + QualityCheckComment, QualityCheckCommentCreate, QualityCheckSummary +) +from .client import APIClient + + +class QualityAPI: + def __init__(self, client: APIClient): + self.client = client + + def create_quality_check(self, check_data: QualityCheckCreate) -> QualityCheckWithProduct: + response = self.client.post("/quality/checks", json=check_data.model_dump(mode="json")) + return QualityCheckWithProduct.model_validate(response) + + def get_quality_checks(self, skip: int = 0, limit: int = 100, + filter_params: Optional[QualityCheckFilter] = None) -> List[QualityCheckWithProduct]: + params = {"skip": skip, "limit": limit} + if filter_params: + params.update(filter_params.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/quality/checks", params=params) + return [QualityCheckWithProduct.model_validate(item) for item in response] + + def get_quality_check(self, check_id: int) -> QualityCheckWithProduct: + response = self.client.get(f"/quality/checks/{check_id}") + return QualityCheckWithProduct.model_validate(response) + + def update_quality_check(self, check_id: int, check_data: QualityCheckUpdate) -> QualityCheckWithProduct: + response = self.client.put(f"/quality/checks/{check_id}", + json=check_data.model_dump(mode="json", exclude_unset=True)) + return QualityCheckWithProduct.model_validate(response) + + def delete_quality_check(self, check_id: int) -> QualityCheckWithProduct: + response = self.client.delete(f"/quality/checks/{check_id}") + return QualityCheckWithProduct.model_validate(response) + + def get_quality_metrics(self, date_from: Optional[int] = None, + date_to: Optional[int] = None) -> QualityMetrics: + params = {} + if date_from: + params["date_from"] = date_from + if date_to: + params["date_to"] = date_to + response = self.client.get("/quality/metrics", params=params) + return QualityMetrics.model_validate(response) + + def create_quality_standard(self, standard_data: QualityStandardCreate) -> QualityStandard: + response = self.client.post("/quality/standards", json=standard_data.model_dump(mode="json")) + return QualityStandard.model_validate(response) + + def get_quality_standards(self, skip: int = 0, limit: int = 100) -> List[QualityStandard]: + response = self.client.get("/quality/standards", params={"skip": skip, "limit": limit}) + return [QualityStandard.model_validate(item) for item in response] + + def get_quality_standard(self, standard_id: int) -> QualityStandard: + response = self.client.get(f"/quality/standards/{standard_id}") + return QualityStandard.model_validate(response) + + def update_quality_standard(self, standard_id: int, standard_data: QualityStandardUpdate) -> QualityStandard: + response = self.client.put(f"/quality/standards/{standard_id}", + json=standard_data.model_dump(mode="json", exclude_unset=True)) + return QualityStandard.model_validate(response) + + def delete_quality_standard(self, standard_id: int) -> QualityStandard: + response = self.client.delete(f"/quality/standards/{standard_id}") + return QualityStandard.model_validate(response) + + def create_quality_alert(self, alert_data: QualityAlertCreate) -> QualityAlert: + response = self.client.post("/quality/alerts", json=alert_data.model_dump(mode="json")) + return QualityAlert.model_validate(response) + + def get_quality_alerts(self, skip: int = 0, limit: int = 100) -> List[QualityAlert]: + response = self.client.get("/quality/alerts", params={"skip": skip, "limit": limit}) + return [QualityAlert.model_validate(item) for item in response] + + def resolve_quality_alert(self, alert_id: int, resolution_data: QualityAlertUpdate) -> QualityAlert: + response = self.client.put(f"/quality/alerts/{alert_id}/resolve", + json=resolution_data.model_dump(mode="json", exclude_unset=True)) + return QualityAlert.model_validate(response) + + def get_product_quality_history(self, product_id: int, + skip: int = 0, limit: int = 100) -> List[QualityCheckWithProduct]: + response = self.client.get(f"/quality/product/{product_id}/history", + params={"skip": skip, "limit": limit}) + return [QualityCheckWithProduct.model_validate(item) for item in response] + + def get_quality_check_summary(self, date_from: Optional[int] = None, + date_to: Optional[int] = None) -> QualityCheckSummary: + params = {} + if date_from: + params["date_from"] = date_from + if date_to: + params["date_to"] = date_to + response = self.client.get("/quality/checks/summary", params=params) + return QualityCheckSummary.model_validate(response) + + def get_product_quality_standards(self, product_id: int) -> List[QualityStandard]: + response = self.client.get(f"/quality/product/{product_id}/standards") + return [QualityStandard.model_validate(item) for item in response] + + def create_batch_quality_check(self, checks: List[QualityCheckCreate]) -> List[QualityCheckWithProduct]: + response = self.client.post("/quality/batch_check", + json=[check.model_dump(mode="json") for check in checks]) + return [QualityCheckWithProduct.model_validate(item) for item in response] + + def get_active_quality_alerts(self, skip: int = 0, limit: int = 100) -> List[QualityAlert]: + response = self.client.get("/quality/alerts/active", params={"skip": skip, "limit": limit}) + return [QualityAlert.model_validate(item) for item in response] + + def add_comment_to_quality_check(self, check_id: int, + comment_data: QualityCheckCommentCreate) -> QualityCheckComment: + response = self.client.post(f"/quality/checks/{check_id}/comment", + json=comment_data.model_dump(mode="json")) + return QualityCheckComment.model_validate(response) + + def get_product_defect_rates(self, + date_from: Optional[int] = None, + date_to: Optional[int] = None) -> List[ProductDefectRate]: + params = {} + if date_from: + params["date_from"] = date_from + if date_to: + params["date_to"] = date_to + response = self.client.get("/quality/reports/defect_rate", params=params) + return [ProductDefectRate.model_validate(item) for item in response] diff --git a/public_api/api/reports.py b/public_api/api/reports.py new file mode 100644 index 0000000..5d902bd --- /dev/null +++ b/public_api/api/reports.py @@ -0,0 +1,27 @@ +from public_api.shared_schemas.reports import ( + InventorySummaryReport, OrderSummaryReport, WarehousePerformanceReport, KPIDashboard +) +from .client import APIClient + + +class ReportsAPI: + def __init__(self, client: APIClient): + self.client = client + + def get_inventory_summary(self) -> InventorySummaryReport: + response = self.client.get("/reports/inventory_summary") + return InventorySummaryReport.model_validate(response) + + def get_order_summary(self, start_date: int, end_date: int) -> OrderSummaryReport: + params = {"start_date": start_date, "end_date": end_date} + response = self.client.get("/reports/order_summary", params=params) + return OrderSummaryReport.model_validate(response) + + def get_warehouse_performance(self, start_date: int, end_date: int) -> WarehousePerformanceReport: + params = {"start_date": start_date, "end_date": end_date} + response = self.client.get("/reports/warehouse_performance", params=params) + return WarehousePerformanceReport.model_validate(response) + + def get_kpi_dashboard(self) -> KPIDashboard: + response = self.client.get("/reports/kpi_dashboard") + return KPIDashboard.model_validate(response) diff --git a/public_api/api/shipments.py b/public_api/api/shipments.py new file mode 100644 index 0000000..bc32bed --- /dev/null +++ b/public_api/api/shipments.py @@ -0,0 +1,50 @@ +from typing import List, Optional + +from public_api.shared_schemas import ( + Shipment, ShipmentCreate, ShipmentUpdate, ShipmentFilter, + CarrierRate, ShippingLabel, ShipmentTracking +) +from .client import APIClient + + +class ShipmentsAPI: + def __init__(self, client: APIClient): + self.client = client + + def get_shipments(self, skip: int = 0, limit: int = 100, + filter_params: Optional[ShipmentFilter] = None) -> List[Shipment]: + params = {"skip": skip, "limit": limit} + if filter_params: + params.update(filter_params.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/shipments/", params=params) + return [Shipment.model_validate(item) for item in response] + + def get_shipment(self, shipment_id: int) -> Shipment: + response = self.client.get(f"/shipments/{shipment_id}") + return Shipment.model_validate(response) + + def create_shipment(self, shipment_data: ShipmentCreate) -> Shipment: + response = self.client.post("/shipments/", json=shipment_data.model_dump(mode="json")) + return Shipment.model_validate(response) + + def update_shipment(self, shipment_id: int, shipment_data: ShipmentUpdate) -> Shipment: + response = self.client.put(f"/shipments/{shipment_id}", + json=shipment_data.model_dump(mode="json", exclude_unset=True)) + return Shipment.model_validate(response) + + def delete_shipment(self, shipment_id: int) -> Shipment: + response = self.client.delete(f"/shipments/{shipment_id}") + return Shipment.model_validate(response) + + def generate_shipping_label(self, shipment_id: int) -> ShippingLabel: + response = self.client.post(f"/shipments/{shipment_id}/generate_label") + return ShippingLabel.model_validate(response) + + def get_carrier_rates(self, weight: float, dimensions: str, destination_zip: str) -> List[CarrierRate]: + params = {"weight": weight, "dimensions": dimensions, "destination_zip": destination_zip} + response = self.client.get("/shipments/carrier_rates", params=params) + return [CarrierRate.model_validate(item) for item in response] + + def track_shipment(self, shipment_id: int) -> ShipmentTracking: + response = self.client.post(f"/shipments/{shipment_id}/track") + return ShipmentTracking.model_validate(response) diff --git a/public_api/api/suppliers.py b/public_api/api/suppliers.py new file mode 100644 index 0000000..a48dc2d --- /dev/null +++ b/public_api/api/suppliers.py @@ -0,0 +1,42 @@ +from typing import List, Optional + +from public_api.shared_schemas import ( + Supplier, SupplierCreate, SupplierUpdate, SupplierFilter, PurchaseOrder +) +from .client import APIClient + + +class SuppliersAPI: + def __init__(self, client: APIClient): + self.client = client + + def get_suppliers(self, skip: int = 0, limit: int = 100, + filter_params: Optional[SupplierFilter] = None) -> List[Supplier]: + params = {"skip": skip, "limit": limit} + if filter_params: + params.update(filter_params.model_dump(mode="json", exclude_unset=True)) + response = self.client.get("/suppliers/", params=params) + return [Supplier.model_validate(item) for item in response] + + def get_supplier(self, supplier_id: int) -> Supplier: + response = self.client.get(f"/suppliers/{supplier_id}") + return Supplier.model_validate(response) + + def create_supplier(self, supplier_data: SupplierCreate) -> Supplier: + response = self.client.post("/suppliers/", json=supplier_data.model_dump(mode="json")) + return Supplier.model_validate(response) + + def update_supplier(self, supplier_id: int, supplier_data: SupplierUpdate) -> Supplier: + response = self.client.put(f"/suppliers/{supplier_id}", + json=supplier_data.model_dump(mode="json", exclude_unset=True)) + return Supplier.model_validate(response) + + def delete_supplier(self, supplier_id: int) -> Supplier: + response = self.client.delete(f"/suppliers/{supplier_id}") + return Supplier.model_validate(response) + + def get_supplier_purchase_orders(self, supplier_id: int, + skip: int = 0, limit: int = 100) -> List[PurchaseOrder]: + response = self.client.get(f"/suppliers/{supplier_id}/purchase_orders", + params={"skip": skip, "limit": limit}) + return [PurchaseOrder.model_validate(item) for item in response] diff --git a/public_api/api/users.py b/public_api/api/users.py new file mode 100644 index 0000000..d01da73 --- /dev/null +++ b/public_api/api/users.py @@ -0,0 +1,91 @@ +from typing import List + +from public_api.shared_schemas import ( + UserCreate, UserUpdate, User, UserSanitizedWithRole, Token, + Message, PasswordResetConfirm, UserFilter, + UserActivity, RoleWithUsers, UserPermissions, BulkUserCreate, + BulkUserCreateResult +) +from .client import APIClient + + +class UsersAPI: + def __init__(self, client: APIClient): + self.client = client + + def login(self, username: str, password: str) -> Token: + data = { + "grant_type": "password", + "username": username, + "password": password, + } + headers = {"content-type": "application/x-www-form-urlencoded"} + + response = self.client.post("/users/login", data=data, headers=headers) + access_token = response.get("access_token") + + if access_token: + self.client.set_token(access_token) + + return Token.model_validate(response) + + def register(self, user: UserCreate) -> User: + response = self.client.post("/users/register", json=user.model_dump(mode="json")) + return User.model_validate(response) + + def reset_password(self, email: str) -> Message: + response = self.client.post("/users/reset_password", json={"email": email}) + return Message.model_validate(response) + + def get_current_user(self) -> UserSanitizedWithRole: + response = self.client.get("/users/me") + return UserSanitizedWithRole.model_validate(response) + + def update_current_user(self, user_update: UserUpdate) -> User: + response = self.client.put("/users/me", json=user_update.model_dump(mode="json", exclude_unset=True)) + return User.model_validate(response) + + def get_users(self, skip: int = 0, limit: int = 100) -> List[UserSanitizedWithRole]: + response = self.client.get("/users/", params={"skip": skip, "limit": limit}) + return [UserSanitizedWithRole.model_validate(item) for item in response] + + def create_user(self, user: UserCreate) -> UserSanitizedWithRole: + response = self.client.post("/users/", json=user.model_dump(mode="json")) + return UserSanitizedWithRole.model_validate(response) + + def get_user(self, user_id: int) -> UserSanitizedWithRole: + response = self.client.get(f"/users/{user_id}") + return UserSanitizedWithRole.model_validate(response) + + def update_user(self, user_id: int, user_update: UserUpdate) -> User: + response = self.client.put(f"/users/{user_id}", json=user_update.model_dump(mode="json", exclude_unset=True)) + return User.model_validate(response) + + def delete_user(self, user_id: int) -> User: + response = self.client.delete(f"/users/{user_id}") + return User.model_validate(response) + + def confirm_password_reset(self, token: str, new_password: str) -> Message: + data = PasswordResetConfirm(token=token, new_password=new_password) + response = self.client.post("/users/reset_password_confirm", json=data.model_dump(mode="json")) + return Message.model_validate(response) + + def get_filtered_users(self, user_filter: UserFilter) -> List[UserSanitizedWithRole]: + response = self.client.get("/users/filter", params=user_filter.model_dump(mode="json", exclude_unset=True)) + return [UserSanitizedWithRole.model_validate(item) for item in response] + + def get_user_activity(self) -> List[UserActivity]: + response = self.client.get("/users/activity") + return [UserActivity.model_validate(item) for item in response] + + def get_role_with_users(self, role_id: int) -> RoleWithUsers: + response = self.client.get(f"/users/role/{role_id}") + return RoleWithUsers.model_validate(response) + + def get_user_permissions(self, user_id: int) -> UserPermissions: + response = self.client.get(f"/users/{user_id}/permissions") + return UserPermissions.model_validate(response) + + def bulk_create_users(self, users: BulkUserCreate) -> BulkUserCreateResult: + response = self.client.post("/users/bulk", json=users.model_dump(mode="json")) + return BulkUserCreateResult.model_validate(response) diff --git a/public_api/api/warehouse.py b/public_api/api/warehouse.py new file mode 100644 index 0000000..cb21ca8 --- /dev/null +++ b/public_api/api/warehouse.py @@ -0,0 +1,38 @@ +from typing import List + +from public_api.shared_schemas import WarehouseLayout, WarehouseStats, LocationInventory, LocationInventoryUpdate, \ + InventoryMovement, InventoryAdjustment +from .client import APIClient + + +class WarehouseAPI: + def __init__(self, client: APIClient): + self.client = client + + def get_warehouse_layout(self) -> WarehouseLayout: + response = self.client.get("/warehouse/layout") + return WarehouseLayout.model_validate(response) + + def get_warehouse_stats(self) -> WarehouseStats: + response = self.client.get("/warehouse/stats") + return WarehouseStats.model_validate(response) + + def get_location_inventory(self, location_id: int) -> List[LocationInventory]: + response = self.client.get(f"/warehouse/inventory/{location_id}") + return [LocationInventory.model_validate(item) for item in response] + + def update_location_inventory(self, location_id: int, product_id: int, + inventory_update: LocationInventoryUpdate) -> LocationInventory: + response = self.client.put(f"/warehouse/inventory/{location_id}/{product_id}", + json=inventory_update.model_dump(mode="json")) + return LocationInventory.model_validate(response) + + def move_inventory(self, movement_data: InventoryMovement) -> InventoryMovement: + response = self.client.post("/warehouse/inventory/move", + json=movement_data.model_dump(mode="json")) + return InventoryMovement.model_validate(response) + + def adjust_inventory(self, adjustment_data: InventoryAdjustment) -> InventoryAdjustment: + response = self.client.post("/warehouse/inventory/adjust", + json=adjustment_data.model_dump(mode="json")) + return InventoryAdjustment.model_validate(response) diff --git a/server/app/schemas/__init__.py b/public_api/shared_schemas/__init__.py similarity index 90% rename from server/app/schemas/__init__.py rename to public_api/shared_schemas/__init__.py index 669d01e..dd7e5d4 100644 --- a/server/app/schemas/__init__.py +++ b/public_api/shared_schemas/__init__.py @@ -1,6 +1,6 @@ -# /server/app/schemas/__init__.py +# /server/app/shared_schemas/__init__.py -# Asset schemas +# Asset shared_schemas from .asset import ( AssetBase, AssetCreate, AssetUpdate, Asset, AssetMaintenanceBase, AssetMaintenanceCreate, AssetMaintenanceUpdate, @@ -8,11 +8,11 @@ AssetFilter, AssetMaintenanceFilter, AssetWithMaintenanceList, AssetTransfer, AssetLocation ) -# Audit schemas +# Audit shared_schemas from .audit import (AuditLogBase, AuditLogCreate, AuditLog, AuditLogWithUser, AuditLogFilter, AuditSummary, UserActivitySummary, AuditLogExport) -# Inventory schemas +# Inventory shared_schemas from .inventory import ( ProductCategoryBase, ProductCategoryCreate, ProductCategoryUpdate, ProductCategory, ProductBase, ProductCreate, ProductUpdate, Product, @@ -28,7 +28,7 @@ StorageUtilization, LocationBase, LocationCreate, LocationUpdate, Location, LocationFilter, InventorySummary, InventoryList, InventoryWithDetails, ) -# Order schemas +# Order shared_schemas from .order import ( OrderItemBase, OrderItemCreate, OrderItemUpdate, OrderItem, OrderBase, OrderCreate, OrderUpdate, Order, @@ -42,7 +42,7 @@ ShippingInfo, POItemReceive, OrderProcessingTimes, BulkOrderImportData, BulkOrderImportResult ) -# Quality schemas +# Quality shared_schemas from .quality import (QualityCheckBase, QualityCheckCreate, QualityCheckUpdate, QualityCheck, QualityCheckWithProduct, QualityCheckFilter, @@ -51,24 +51,26 @@ QualityAlert, QualityAlertCreate, QualityAlertUpdate, QualityCheckComment, QualityCheckCommentCreate, ProductDefectRate) -# Report schemas +# Report shared_schemas from .reports import ( InventoryItem, InventorySummaryReport, OrderSummaryReport, WarehousePerformanceMetric, WarehousePerformanceReport, KPIMetric, KPIDashboard ) -# Task schemas +# Task shared_schemas from .task import (TaskBase, TaskCreate, TaskUpdate, Task, TaskWithAssignee, TaskFilter, TaskComment, TaskCommentCreate, TaskStatistics, UserTaskSummary) -# User schemas +# User shared_schemas from .user import ( PermissionBase, PermissionCreate, PermissionUpdate, Permission, RoleBase, RoleCreate, RoleUpdate, Role, UserBase, UserCreate, UserUpdate, User, UserInDB, Token, TokenData, - Message, UserSanitizedWithRole + Message, UserSanitizedWithRole, PasswordResetConfirm, UserFilter, + UserActivity, RoleWithUsers, UserPermissions, BulkUserCreate, + BulkUserCreateResult ) -# Warehouse schemas +# Warehouse shared_schemas from .warehouse import ( PickListItemBase, PickListItemCreate, PickListItemUpdate, PickListItem, PickListBase, PickListCreate, PickListUpdate, PickList, @@ -81,7 +83,7 @@ ReceiptDiscrepancy, ShippingLabel, CarrierRate, ShipmentTracking, InventoryMovementCreate, InventoryAdjustmentCreate ) -# Yard schemas +# Yard shared_schemas from .yard import ( YardLocationBase, YardLocationCreate, YardLocationUpdate, YardLocation, diff --git a/server/app/schemas/asset.py b/public_api/shared_schemas/asset.py similarity index 74% rename from server/app/schemas/asset.py rename to public_api/shared_schemas/asset.py index 1f86f03..dcdc6ce 100644 --- a/server/app/schemas/asset.py +++ b/public_api/shared_schemas/asset.py @@ -1,5 +1,4 @@ -# /server/app/schemas/asset.py -from datetime import date, datetime +# /server/app/shared_schemas/asset.py from typing import Optional, List from pydantic import BaseModel @@ -9,7 +8,7 @@ class AssetBase(BaseModel): asset_type: str asset_name: str serial_number: str - purchase_date: date + purchase_date: int status: str location_id: Optional[int] = None @@ -22,7 +21,7 @@ class AssetUpdate(BaseModel): asset_type: Optional[str] = None asset_name: Optional[str] = None serial_number: Optional[str] = None - purchase_date: Optional[date] = None + purchase_date: Optional[int] = None status: Optional[str] = None location_id: Optional[int] = None @@ -37,8 +36,8 @@ class Config: class AssetMaintenanceBase(BaseModel): asset_id: int maintenance_type: str - scheduled_date: date - completed_date: Optional[date] = None + scheduled_date: int + completed_date: Optional[int] = None performed_by: Optional[int] = None notes: Optional[str] = None @@ -49,8 +48,8 @@ class AssetMaintenanceCreate(AssetMaintenanceBase): class AssetMaintenanceUpdate(BaseModel): maintenance_type: Optional[str] = None - scheduled_date: Optional[date] = None - completed_date: Optional[date] = None + scheduled_date: Optional[int] = None + completed_date: Optional[int] = None performed_by: Optional[int] = None notes: Optional[str] = None @@ -72,18 +71,18 @@ class Config: class AssetFilter(BaseModel): asset_type: Optional[str] = None status: Optional[str] = None - purchase_date_from: Optional[date] = None - purchase_date_to: Optional[date] = None + purchase_date_from: Optional[int] = None + purchase_date_to: Optional[int] = None location_id: Optional[int] = None class AssetMaintenanceFilter(BaseModel): asset_id: Optional[int] = None maintenance_type: Optional[str] = None - scheduled_date_from: Optional[date] = None - scheduled_date_to: Optional[date] = None - completed_date_from: Optional[date] = None - completed_date_to: Optional[date] = None + scheduled_date_from: Optional[int] = None + scheduled_date_to: Optional[int] = None + completed_date_from: Optional[int] = None + completed_date_to: Optional[int] = None performed_by: Optional[int] = None @@ -99,4 +98,4 @@ class AssetTransfer(BaseModel): class AssetLocation(BaseModel): asset_id: int location_id: int - timestamp: datetime \ No newline at end of file + timestamp: int diff --git a/server/app/schemas/audit.py b/public_api/shared_schemas/audit.py similarity index 83% rename from server/app/schemas/audit.py rename to public_api/shared_schemas/audit.py index bb20ecc..f14d575 100644 --- a/server/app/schemas/audit.py +++ b/public_api/shared_schemas/audit.py @@ -1,5 +1,4 @@ -# /server/app/schemas/audit_log.py -from datetime import datetime +# /server/app/shared_schemas/audit_log.py from typing import Optional, List, Dict from pydantic import BaseModel @@ -22,7 +21,7 @@ class AuditLogCreate(AuditLogBase): class AuditLog(AuditLogBase): log_id: int - timestamp: datetime + timestamp: int class Config: from_attributes = True @@ -40,8 +39,8 @@ class AuditLogFilter(BaseModel): action_type: Optional[str] = None table_name: Optional[str] = None record_id: Optional[int] = None - date_from: Optional[datetime] = None - date_to: Optional[datetime] = None + date_from: Optional[int] = None + date_to: Optional[int] = None class UserActivitySummary(BaseModel): @@ -59,9 +58,9 @@ class AuditSummary(BaseModel): class AuditLogExport(BaseModel): logs: List[AuditLog] - export_timestamp: datetime + export_timestamp: int class AuditLogList(BaseModel): logs: List[AuditLog] - total: int \ No newline at end of file + total: int diff --git a/server/app/schemas/inventory.py b/public_api/shared_schemas/inventory.py similarity index 93% rename from server/app/schemas/inventory.py rename to public_api/shared_schemas/inventory.py index 0f6651d..3789d0e 100644 --- a/server/app/schemas/inventory.py +++ b/public_api/shared_schemas/inventory.py @@ -1,5 +1,4 @@ -# /server/app/schemas/inventory.py -from datetime import datetime +# /server/app/shared_schemas/inventory.py from typing import Optional, List, Dict from pydantic import BaseModel, constr, Field @@ -65,7 +64,7 @@ class InventoryBase(BaseModel): product_id: int location_id: int quantity: int - expiration_date: Optional[datetime] = None + expiration_date: Optional[int] = None class InventoryCreate(InventoryBase): @@ -73,16 +72,13 @@ class InventoryCreate(InventoryBase): class InventoryUpdate(BaseModel): - product_id: Optional[int] = None - location_id: Optional[int] = None quantity: Optional[int] = None - expiration_date: Optional[datetime] = None + expiration_date: Optional[int] = None class Inventory(InventoryBase): - inventory_id: int - last_updated: datetime - product: Product + id: int + last_updated: int class Config: from_attributes = True @@ -140,7 +136,6 @@ class Config: class ProductWithInventory(Product): inventory_items: List[Inventory] = [] - category: Optional[ProductCategory] = None class Config: from_attributes = True @@ -185,6 +180,7 @@ class InventoryTransfer(BaseModel): class ProductWithCategoryAndInventory(ProductWithInventory): + category: Optional[ProductCategory] = None pass @@ -205,7 +201,7 @@ class InventoryMovement(BaseModel): to_location_id: int quantity: int reason: str - timestamp: datetime + timestamp: int class InventoryAdjustment(BaseModel): @@ -213,7 +209,7 @@ class InventoryAdjustment(BaseModel): location_id: int quantity_change: int reason: str - timestamp: datetime + timestamp: int class StocktakeItem(BaseModel): @@ -287,17 +283,16 @@ class InventoryWithDetails(Inventory): product: Product location: Location - class Config: - from_attributes = True class InventoryList(BaseModel): items: List[InventoryWithDetails] total: int + class InventoryFilter(BaseModel): product_id: Optional[int] = None location_id: Optional[int] = None sku: Optional[str] = None name: Optional[str] = None quantity_min: Optional[int] = None - quantity_max: Optional[int] = None \ No newline at end of file + quantity_max: Optional[int] = None diff --git a/server/app/schemas/order.py b/public_api/shared_schemas/order.py similarity index 89% rename from server/app/schemas/order.py rename to public_api/shared_schemas/order.py index c5730bb..3377166 100644 --- a/server/app/schemas/order.py +++ b/public_api/shared_schemas/order.py @@ -1,5 +1,4 @@ -# /server/app/schemas/order.py -from datetime import datetime +# /server/app/shared_schemas/order.py from typing import Optional, List from pydantic import BaseModel @@ -40,7 +39,7 @@ class OrderBase(BaseModel): shipping_postal_code: Optional[str] = None shipping_country: Optional[str] = None shipping_phone: Optional[str] = None - ship_date: Optional[datetime] = None + ship_date: Optional[int] = None class OrderCreate(OrderBase): @@ -58,13 +57,13 @@ class OrderUpdate(BaseModel): shipping_postal_code: Optional[str] = None shipping_country: Optional[str] = None shipping_phone: Optional[str] = None - ship_date: Optional[datetime] = None + ship_date: Optional[int] = None items: Optional[List[OrderItemUpdate]] = None class Order(OrderBase): order_id: int - order_date: datetime + order_date: int order_items: List[OrderItem] = [] class Config: @@ -123,7 +122,7 @@ class Config: class PurchaseOrderBase(BaseModel): supplier_id: int status: str - expected_delivery_date: Optional[datetime] = None + expected_delivery_date: Optional[int] = None class PurchaseOrderCreate(PurchaseOrderBase): @@ -133,13 +132,13 @@ class PurchaseOrderCreate(PurchaseOrderBase): class PurchaseOrderUpdate(BaseModel): supplier_id: Optional[int] = None status: Optional[str] = None - expected_delivery_date: Optional[datetime] = None + expected_delivery_date: Optional[int] = None items: Optional[List[POItemUpdate]] = None class PurchaseOrder(PurchaseOrderBase): po_id: int - order_date: datetime + order_date: int po_items: List[POItem] = [] class Config: @@ -176,10 +175,10 @@ class Config: class OrderFilter(BaseModel): customer_id: Optional[int] = None status: Optional[str] = None - order_date_from: Optional[datetime] = None - order_date_to: Optional[datetime] = None - ship_date_from: Optional[datetime] = None - ship_date_to: Optional[datetime] = None + order_date_from: Optional[int] = None + order_date_to: Optional[int] = None + ship_date_from: Optional[int] = None + ship_date_to: Optional[int] = None class OrderSummary(BaseModel): @@ -196,8 +195,8 @@ class CustomerFilter(BaseModel): class PurchaseOrderFilter(BaseModel): supplier_id: Optional[int] = None status: Optional[str] = None - date_from: Optional[datetime] = None - date_to: Optional[datetime] = None + date_from: Optional[int] = None + date_to: Optional[int] = None class SupplierFilter(BaseModel): diff --git a/server/app/schemas/quality.py b/public_api/shared_schemas/quality.py similarity index 86% rename from server/app/schemas/quality.py rename to public_api/shared_schemas/quality.py index 34def73..f57261b 100644 --- a/server/app/schemas/quality.py +++ b/public_api/shared_schemas/quality.py @@ -1,10 +1,9 @@ -# /server/app/schemas/quality.py -from datetime import datetime +# /server/app/shared_schemas/quality.py from typing import Optional from pydantic import BaseModel -from server.app.schemas import Product +from public_api.shared_schemas import Product class QualityCheckBase(BaseModel): @@ -27,7 +26,7 @@ class QualityCheckUpdate(BaseModel): class QualityCheck(QualityCheckBase): check_id: int - check_date: datetime + check_date: int class Config: from_attributes = True @@ -41,8 +40,8 @@ class QualityCheckFilter(BaseModel): product_id: Optional[int] = None performed_by: Optional[int] = None result: Optional[str] = None - date_from: Optional[datetime] = None - date_to: Optional[datetime] = None + date_from: Optional[int] = None + date_to: Optional[int] = None class QualityMetrics(BaseModel): @@ -88,13 +87,13 @@ class QualityAlertUpdate(BaseModel): product_id: Optional[int] = None alert_type: Optional[str] = None description: Optional[str] = None - resolved_at: Optional[datetime] = None + resolved_at: Optional[int] = None class QualityAlert(QualityAlertBase): alert_id: int - created_at: datetime - resolved_at: Optional[datetime] = None + created_at: int + resolved_at: Optional[int] = None class Config: from_attributes = True @@ -105,7 +104,7 @@ class QualityCheckComment(BaseModel): check_id: int user_id: int comment: str - created_at: datetime + created_at: int class QualityCheckCommentCreate(BaseModel): @@ -130,8 +129,8 @@ class QualityAlertFilter(BaseModel): product_id: Optional[int] = None alert_type: Optional[str] = None resolved: Optional[bool] = None - date_from: Optional[datetime] = None - date_to: Optional[datetime] = None + date_from: Optional[int] = None + date_to: Optional[int] = None class QualityCheckSummary(BaseModel): @@ -142,7 +141,7 @@ class QualityCheckSummary(BaseModel): class QualityTrend(BaseModel): - date: datetime + date: int pass_rate: float diff --git a/server/app/schemas/reports.py b/public_api/shared_schemas/reports.py similarity index 84% rename from server/app/schemas/reports.py rename to public_api/shared_schemas/reports.py index 0a8fd9d..9eeeeff 100644 --- a/server/app/schemas/reports.py +++ b/public_api/shared_schemas/reports.py @@ -1,5 +1,4 @@ -# /server/app/schemas/reports.py -from datetime import date +# /server/app/shared_schemas/reports.py from typing import List, Optional from pydantic import BaseModel @@ -25,8 +24,8 @@ class OrderSummary(BaseModel): class OrderSummaryReport(BaseModel): - start_date: date - end_date: date + start_date: int + end_date: int summary: OrderSummary @@ -37,8 +36,8 @@ class WarehousePerformanceMetric(BaseModel): class WarehousePerformanceReport(BaseModel): - start_date: date - end_date: date + start_date: int + end_date: int metrics: List[WarehousePerformanceMetric] @@ -49,7 +48,7 @@ class KPIMetric(BaseModel): class KPIDashboard(BaseModel): - date: date + date: int metrics: List[KPIMetric] @@ -61,8 +60,8 @@ class ProductPerformance(BaseModel): class TopSellingProductsReport(BaseModel): - start_date: date - end_date: date + start_date: int + end_date: int products: List[ProductPerformance] @@ -75,8 +74,8 @@ class SupplierPerformance(BaseModel): class SupplierPerformanceReport(BaseModel): - start_date: date - end_date: date + start_date: int + end_date: int suppliers: List[SupplierPerformance] @@ -85,12 +84,12 @@ class StockMovement(BaseModel): product_name: str quantity_change: int movement_type: str - date: date + date: int class StockMovementReport(BaseModel): - start_date: date - end_date: date + start_date: int + end_date: int movements: List[StockMovement] @@ -103,8 +102,8 @@ class PickingEfficiency(BaseModel): class PickingEfficiencyReport(BaseModel): - start_date: date - end_date: date + start_date: int + end_date: int pickers: List[PickingEfficiency] @@ -117,7 +116,7 @@ class StorageUtilization(BaseModel): class StorageUtilizationReport(BaseModel): - date: date + date: int zones: List[StorageUtilization] @@ -128,8 +127,8 @@ class ReturnsAnalysis(BaseModel): class ReturnsReport(BaseModel): - start_date: date - end_date: date + start_date: int + end_date: int total_returns: int return_rate: float reasons: List[ReturnsAnalysis] @@ -140,12 +139,12 @@ class CustomReport(BaseModel): description: str query: str parameters: dict - created_at: date - last_run: Optional[date] + created_at: int + last_run: Optional[int] class ReportSchedule(BaseModel): report_id: int frequency: str # 'daily', 'weekly', 'monthly' - next_run: date + next_run: int recipients: List[str] diff --git a/server/app/schemas/task.py b/public_api/shared_schemas/task.py similarity index 87% rename from server/app/schemas/task.py rename to public_api/shared_schemas/task.py index 7c5a364..235ccf6 100644 --- a/server/app/schemas/task.py +++ b/public_api/shared_schemas/task.py @@ -1,5 +1,4 @@ -# /server/app/schemas/task.py -from datetime import datetime +# /server/app/shared_schemas/task.py from typing import Optional, List from pydantic import BaseModel @@ -11,7 +10,7 @@ class TaskBase(BaseModel): task_type: str description: str assigned_to: int - due_date: datetime + due_date: int priority: str status: str @@ -24,14 +23,14 @@ class TaskUpdate(BaseModel): task_type: Optional[str] = None description: Optional[str] = None assigned_to: Optional[int] = None - due_date: Optional[datetime] = None + due_date: Optional[int] = None priority: Optional[str] = None status: Optional[str] = None class Task(TaskBase): task_id: int - created_at: datetime + created_at: int class Config: from_attributes = True @@ -46,8 +45,8 @@ class TaskFilter(BaseModel): assigned_to: Optional[int] = None priority: Optional[str] = None status: Optional[str] = None - due_date_from: Optional[datetime] = None - due_date_to: Optional[datetime] = None + due_date_from: Optional[int] = None + due_date_to: Optional[int] = None class TaskCommentBase(BaseModel): @@ -62,7 +61,7 @@ class TaskCommentCreate(TaskCommentBase): class TaskComment(TaskCommentBase): comment_id: int - created_at: datetime + created_at: int class Config: from_attributes = True @@ -100,7 +99,7 @@ class TaskStatusUpdate(BaseModel): class TaskDueDateUpdate(BaseModel): - due_date: datetime + due_date: int class TaskProgressUpdate(BaseModel): @@ -120,8 +119,8 @@ class TaskTimeline(BaseModel): task_id: int task_type: str description: str - start_date: datetime - end_date: datetime + start_date: int + end_date: int class TaskTypeDistribution(BaseModel): diff --git a/server/app/schemas/user.py b/public_api/shared_schemas/user.py similarity index 89% rename from server/app/schemas/user.py rename to public_api/shared_schemas/user.py index 2cfb9ad..a3a92ca 100644 --- a/server/app/schemas/user.py +++ b/public_api/shared_schemas/user.py @@ -1,5 +1,4 @@ -# /server/app/schemas/user.py -from datetime import datetime +# /server/app/shared_schemas/user.py from typing import Optional, List from pydantic import BaseModel, EmailStr @@ -66,8 +65,8 @@ class UserUpdate(BaseModel): class UserSanitizedWithRole(UserBase): user_id: int - created_at: datetime - last_login: Optional[datetime] = None + created_at: int + last_login: Optional[int] = None role: Role class Config: @@ -76,11 +75,11 @@ class Config: class User(UserBase): user_id: int - created_at: datetime - last_login: Optional[datetime] = None + created_at: int + last_login: Optional[int] = None password_hash: str password_reset_token: Optional[str] = None - password_reset_expiration: Optional[datetime] = None + password_reset_expiration: Optional[int] = None class Config: from_attributes = True @@ -134,7 +133,7 @@ class UserFilter(BaseModel): class UserActivity(BaseModel): user_id: int username: str - last_login: Optional[datetime] + last_login: Optional[int] total_logins: int total_actions: int diff --git a/server/app/schemas/warehouse.py b/public_api/shared_schemas/warehouse.py similarity index 87% rename from server/app/schemas/warehouse.py rename to public_api/shared_schemas/warehouse.py index 09185e4..1675554 100644 --- a/server/app/schemas/warehouse.py +++ b/public_api/shared_schemas/warehouse.py @@ -1,5 +1,4 @@ -# /server/app/schemas/warehouse.py -from datetime import datetime +# /server/app/shared_schemas/warehouse.py from typing import Optional, List from pydantic import BaseModel @@ -48,8 +47,8 @@ class PickListUpdate(BaseModel): class PickList(PickListBase): pick_list_id: int - created_at: datetime - completed_at: Optional[datetime] = None + created_at: int + completed_at: Optional[int] = None items: List[PickListItem] = [] class Config: @@ -97,7 +96,7 @@ class ReceiptUpdate(BaseModel): class Receipt(ReceiptBase): receipt_id: int - received_date: datetime + received_date: int items: List[ReceiptItem] = [] class Config: @@ -122,14 +121,14 @@ class ShipmentUpdate(BaseModel): carrier_id: Optional[int] = None tracking_number: Optional[str] = None status: Optional[str] = None - ship_date: Optional[datetime] = None + ship_date: Optional[int] = None label_id: Optional[str] = None label_download_url: Optional[str] = None class Shipment(ShipmentBase): shipment_id: int - ship_date: Optional[datetime] = None + ship_date: Optional[int] = None class Config: from_attributes = True @@ -159,23 +158,23 @@ class Config: class PickListFilter(BaseModel): status: Optional[str] = None order_id: Optional[int] = None - date_from: Optional[datetime] = None - date_to: Optional[datetime] = None + date_from: Optional[int] = None + date_to: Optional[int] = None class ReceiptFilter(BaseModel): status: Optional[str] = None po_id: Optional[int] = None - date_from: Optional[datetime] = None - date_to: Optional[datetime] = None + date_from: Optional[int] = None + date_to: Optional[int] = None class ShipmentFilter(BaseModel): status: Optional[str] = None order_id: Optional[int] = None carrier_id: Optional[int] = None - date_from: Optional[datetime] = None - date_to: Optional[datetime] = None + date_from: Optional[int] = None + date_to: Optional[int] = None class WarehouseStats(BaseModel): @@ -239,7 +238,7 @@ class ShipmentTracking(BaseModel): shipment_id: int tracking_number: str current_status: str - estimated_delivery_date: Optional[datetime] + estimated_delivery_date: Optional[int] tracking_history: List[dict] @@ -257,7 +256,7 @@ class InventoryMovementCreate(InventoryMovementBase): class InventoryMovement(InventoryMovementBase): movement_id: int - timestamp: datetime + timestamp: int class Config: from_attributes = True @@ -276,7 +275,7 @@ class InventoryAdjustmentCreate(InventoryAdjustmentBase): class InventoryAdjustment(InventoryAdjustmentBase): adjustment_id: int - timestamp: datetime + timestamp: int class Config: from_attributes = True @@ -309,7 +308,7 @@ class Config: class DockAppointmentBase(BaseModel): yard_location_id: int - appointment_time: datetime + appointment_time: int carrier_id: int type: str status: str @@ -321,18 +320,18 @@ class DockAppointmentCreate(DockAppointmentBase): class DockAppointmentUpdate(BaseModel): yard_location_id: Optional[int] = None - appointment_time: Optional[datetime] = None + appointment_time: Optional[int] = None carrier_id: Optional[int] = None type: Optional[str] = None status: Optional[str] = None - actual_arrival_time: Optional[datetime] = None - actual_departure_time: Optional[datetime] = None + actual_arrival_time: Optional[int] = None + actual_departure_time: Optional[int] = None class DockAppointment(DockAppointmentBase): appointment_id: int - actual_arrival_time: Optional[datetime] = None - actual_departure_time: Optional[datetime] = None + actual_arrival_time: Optional[int] = None + actual_departure_time: Optional[int] = None class Config: from_attributes = True @@ -348,8 +347,8 @@ class DockAppointmentFilter(BaseModel): carrier_id: Optional[int] = None type: Optional[str] = None status: Optional[str] = None - date_from: Optional[datetime] = None - date_to: Optional[datetime] = None + date_from: Optional[int] = None + date_to: Optional[int] = None class YardManagementStats(BaseModel): diff --git a/server/app/schemas/yard.py b/public_api/shared_schemas/yard.py similarity index 87% rename from server/app/schemas/yard.py rename to public_api/shared_schemas/yard.py index 56c882d..1cb5527 100644 --- a/server/app/schemas/yard.py +++ b/public_api/shared_schemas/yard.py @@ -1,5 +1,4 @@ -# /server/app/schemas/yard.py -from datetime import datetime +# /server/app/shared_schemas/yard.py from typing import Optional, List from pydantic import BaseModel @@ -32,12 +31,12 @@ class Config: class DockAppointmentBase(BaseModel): yard_location_id: int - appointment_time: datetime + appointment_time: int carrier_id: int type: str status: str - actual_arrival_time: Optional[datetime] = None - actual_departure_time: Optional[datetime] = None + actual_arrival_time: Optional[int] = None + actual_departure_time: Optional[int] = None class DockAppointmentCreate(DockAppointmentBase): @@ -46,12 +45,12 @@ class DockAppointmentCreate(DockAppointmentBase): class DockAppointmentUpdate(BaseModel): yard_location_id: Optional[int] = None - appointment_time: Optional[datetime] = None + appointment_time: Optional[int] = None carrier_id: Optional[int] = None type: Optional[str] = None status: Optional[str] = None - actual_arrival_time: Optional[datetime] = None - actual_departure_time: Optional[datetime] = None + actual_arrival_time: Optional[int] = None + actual_departure_time: Optional[int] = None class DockAppointment(DockAppointmentBase): @@ -76,8 +75,8 @@ class DockAppointmentFilter(BaseModel): carrier_id: Optional[int] = None type: Optional[str] = None status: Optional[str] = None - date_from: Optional[datetime] = None - date_to: Optional[datetime] = None + date_from: Optional[int] = None + date_to: Optional[int] = None class YardStats(BaseModel): @@ -100,7 +99,7 @@ class YardLocationCapacity(BaseModel): class YardUtilizationReport(BaseModel): - date: datetime + date: int total_capacity: int total_utilization: int utilization_percentage: float @@ -134,7 +133,7 @@ class YardOverview(BaseModel): class AppointmentScheduleConflict(BaseModel): conflicting_appointments: List[DockAppointment] - suggested_time_slots: List[datetime] + suggested_time_slots: List[int] class CarrierSchedule(BaseModel): diff --git a/server/app/api/deps.py b/server/app/api/deps.py index 01e8539..9ba3c45 100644 --- a/server/app/api/deps.py +++ b/server/app/api/deps.py @@ -5,7 +5,8 @@ from pydantic import ValidationError from sqlalchemy.orm import Session -from server.app import crud, models, schemas +from server.app import crud, models +from public_api import shared_schemas from server.app.core.config import settings from server.app.db.database import get_db @@ -20,7 +21,7 @@ def get_current_user( payload = jwt.decode( token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM] ) - token_data = schemas.TokenData(**payload) + token_data = shared_schemas.TokenData(**payload) except (jwt.JWTError, ValidationError): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, diff --git a/server/app/api/v1/endpoints/assets.py b/server/app/api/v1/endpoints/assets.py index 7e4a049..feb4508 100644 --- a/server/app/api/v1/endpoints/assets.py +++ b/server/app/api/v1/endpoints/assets.py @@ -1,31 +1,32 @@ # /server/app/api/v1/endpoints/assets.py -from datetime import date +from datetime import datetime from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from .... import crud, models, schemas +from public_api import shared_schemas +from .... import crud, models from ....api import deps router = APIRouter() # Asset routes -@router.post("/", response_model=schemas.Asset) +@router.post("/", response_model=shared_schemas.Asset) def create_asset( - asset: schemas.AssetCreate, + asset: shared_schemas.AssetCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.asset.create(db=db, obj_in=asset) -@router.get("/", response_model=schemas.AssetWithMaintenanceList) +@router.get("/", response_model=shared_schemas.AssetWithMaintenanceList) def read_assets( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - asset_filter: schemas.AssetFilter = Depends(), + asset_filter: shared_schemas.AssetFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): assets = crud.asset.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=asset_filter) @@ -50,27 +51,27 @@ def read_asset_statuses( # Asset Maintenance routes -@router.post("/maintenance", response_model=schemas.AssetMaintenance) +@router.post("/maintenance", response_model=shared_schemas.AssetMaintenance) def create_asset_maintenance( - maintenance: schemas.AssetMaintenanceCreate, + maintenance: shared_schemas.AssetMaintenanceCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.asset_maintenance.create(db=db, obj_in=maintenance) -@router.get("/maintenance", response_model=list[schemas.AssetMaintenance]) +@router.get("/maintenance", response_model=list[shared_schemas.AssetMaintenance]) def read_asset_maintenances( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - maintenance_filter: schemas.AssetMaintenanceFilter = Depends(), + maintenance_filter: shared_schemas.AssetMaintenanceFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.asset_maintenance.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=maintenance_filter) -@router.get("/maintenance/{maintenance_id}", response_model=schemas.AssetMaintenance) +@router.get("/maintenance/{maintenance_id}", response_model=shared_schemas.AssetMaintenance) def read_asset_maintenance( maintenance_id: int, db: Session = Depends(deps.get_db), @@ -82,10 +83,10 @@ def read_asset_maintenance( return maintenance -@router.put("/maintenance/{maintenance_id}", response_model=schemas.AssetMaintenance) +@router.put("/maintenance/{maintenance_id}", response_model=shared_schemas.AssetMaintenance) def update_asset_maintenance( maintenance_id: int, - maintenance_in: schemas.AssetMaintenanceUpdate, + maintenance_in: shared_schemas.AssetMaintenanceUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -95,7 +96,7 @@ def update_asset_maintenance( return crud.asset_maintenance.update(db, db_obj=maintenance, obj_in=maintenance_in) -@router.delete("/maintenance/{maintenance_id}", response_model=schemas.AssetMaintenance) +@router.delete("/maintenance/{maintenance_id}", response_model=shared_schemas.AssetMaintenance) def delete_asset_maintenance( maintenance_id: int, db: Session = Depends(deps.get_db), @@ -115,7 +116,7 @@ def read_maintenance_types( return crud.asset_maintenance.get_all_types(db) -@router.get("/{asset_id}/maintenance_history", response_model=list[schemas.AssetMaintenance]) +@router.get("/{asset_id}/maintenance_history", response_model=list[shared_schemas.AssetMaintenance]) def read_asset_maintenance_history( asset_id: int, db: Session = Depends(deps.get_db), @@ -126,10 +127,10 @@ def read_asset_maintenance_history( return crud.asset_maintenance.get_multi_by_asset(db, asset_id=asset_id, skip=skip, limit=limit) -@router.post("/{asset_id}/schedule_maintenance", response_model=schemas.AssetMaintenance) +@router.post("/{asset_id}/schedule_maintenance", response_model=shared_schemas.AssetMaintenance) def schedule_asset_maintenance( asset_id: int, - maintenance: schemas.AssetMaintenanceCreate, + maintenance: shared_schemas.AssetMaintenanceCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -140,22 +141,22 @@ def schedule_asset_maintenance( return crud.asset_maintenance.create(db=db, obj_in=maintenance) -@router.put("/maintenance/{maintenance_id}/complete", response_model=schemas.AssetMaintenance) +@router.put("/maintenance/{maintenance_id}/complete", response_model=shared_schemas.AssetMaintenance) def complete_asset_maintenance( maintenance_id: int, - completion_data: schemas.AssetMaintenanceUpdate, + completion_data: shared_schemas.AssetMaintenanceUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): maintenance = crud.asset_maintenance.get(db, id=maintenance_id) if maintenance is None: raise HTTPException(status_code=404, detail="Asset maintenance record not found") - completion_data.completed_date = date.today() + completion_data.completed_date = int(datetime.now().timestamp()) completion_data.performed_by = current_user.user_id return crud.asset_maintenance.update(db, db_obj=maintenance, obj_in=completion_data) -@router.get("/due_for_maintenance", response_model=list[schemas.AssetWithMaintenance]) +@router.get("/due_for_maintenance", response_model=list[shared_schemas.AssetWithMaintenance]) def get_assets_due_for_maintenance( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -163,7 +164,7 @@ def get_assets_due_for_maintenance( return crud.asset.get_due_for_maintenance(db) -@router.get("/{asset_id}", response_model=schemas.AssetWithMaintenance) +@router.get("/{asset_id}", response_model=shared_schemas.AssetWithMaintenance) def read_asset( asset_id: int, db: Session = Depends(deps.get_db), @@ -175,10 +176,10 @@ def read_asset( return asset -@router.put("/{asset_id}", response_model=schemas.Asset) +@router.put("/{asset_id}", response_model=shared_schemas.Asset) def update_asset( asset_id: int, - asset_in: schemas.AssetUpdate, + asset_in: shared_schemas.AssetUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -188,7 +189,7 @@ def update_asset( return crud.asset.update(db, db_obj=asset, obj_in=asset_in) -@router.delete("/{asset_id}", response_model=schemas.Asset) +@router.delete("/{asset_id}", response_model=shared_schemas.Asset) def delete_asset( asset_id: int, db: Session = Depends(deps.get_db), @@ -200,7 +201,7 @@ def delete_asset( return crud.asset.remove(db, id=asset_id) -@router.get("/{asset_id}/current_location", response_model=schemas.Location) +@router.get("/{asset_id}/current_location", response_model=shared_schemas.Location) def get_asset_current_location( asset_id: int, db: Session = Depends(deps.get_db), @@ -212,10 +213,10 @@ def get_asset_current_location( return location -@router.post("/{asset_id}/transfer", response_model=schemas.Asset) +@router.post("/{asset_id}/transfer", response_model=shared_schemas.Asset) def transfer_asset( asset_id: int, - transfer: schemas.AssetTransfer, + transfer: shared_schemas.AssetTransfer, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): diff --git a/server/app/api/v1/endpoints/audit.py b/server/app/api/v1/endpoints/audit.py index bf649bd..ea80f7d 100644 --- a/server/app/api/v1/endpoints/audit.py +++ b/server/app/api/v1/endpoints/audit.py @@ -5,33 +5,34 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Path from sqlalchemy.orm import Session -from .... import crud, models, schemas +from public_api import shared_schemas +from .... import crud, models from ....api import deps router = APIRouter() -@router.post("/logs", response_model=schemas.AuditLog) +@router.post("/logs", response_model=shared_schemas.AuditLog) def create_audit_log( - log: schemas.AuditLogCreate, + log: shared_schemas.AuditLogCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.audit_log.create(db=db, obj_in=log) -@router.get("/logs", response_model=List[schemas.AuditLogWithUser]) +@router.get("/logs", response_model=List[shared_schemas.AuditLogWithUser]) def read_audit_logs( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - filter_params: schemas.AuditLogFilter = Depends(), + filter_params: shared_schemas.AuditLogFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.audit_log.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=filter_params) -@router.get("/logs/{log_id:int}", response_model=schemas.AuditLogWithUser) +@router.get("/logs/{log_id:int}", response_model=shared_schemas.AuditLogWithUser) def read_audit_log( log_id: int = Path(..., title="The ID of the audit log to get"), db: Session = Depends(deps.get_db), @@ -43,17 +44,17 @@ def read_audit_log( return log -@router.get("/logs/summary", response_model=schemas.AuditSummary) +@router.get("/logs/summary", response_model=shared_schemas.AuditSummary) def get_audit_summary( db: Session = Depends(deps.get_db), - date_from: datetime = Query(None), - date_to: datetime = Query(None), + date_from: int = Query(None), + date_to: int = Query(None), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.audit_log.get_summary(db, date_from=date_from, date_to=date_to) -@router.get("/logs/user/{user_id}", response_model=List[schemas.AuditLog]) +@router.get("/logs/user/{user_id}", response_model=List[shared_schemas.AuditLog]) def get_user_audit_logs( user_id: int = Path(..., title="The ID of the user to get audit logs for"), db: Session = Depends(deps.get_db), @@ -64,7 +65,7 @@ def get_user_audit_logs( return crud.audit_log.get_by_user(db, user_id=user_id, skip=skip, limit=limit) -@router.get("/logs/table/{table_name}", response_model=List[schemas.AuditLog]) +@router.get("/logs/table/{table_name}", response_model=List[shared_schemas.AuditLog]) def get_table_audit_logs( table_name: str = Path(..., title="The name of the table to get audit logs for"), db: Session = Depends(deps.get_db), @@ -75,7 +76,7 @@ def get_table_audit_logs( return crud.audit_log.get_by_table(db, table_name=table_name, skip=skip, limit=limit) -@router.get("/logs/record/{table_name}/{record_id}", response_model=List[schemas.AuditLog]) +@router.get("/logs/record/{table_name}/{record_id}", response_model=List[shared_schemas.AuditLog]) def get_record_audit_logs( table_name: str = Path(..., title="The name of the table"), record_id: int = Path(..., title="The ID of the record to get audit logs for"), @@ -87,15 +88,15 @@ def get_record_audit_logs( return crud.audit_log.get_by_record(db, table_name=table_name, record_id=record_id, skip=skip, limit=limit) -@router.get("/logs/export", response_model=schemas.AuditLogExport) +@router.get("/logs/export", response_model=shared_schemas.AuditLogExport) def export_audit_logs( db: Session = Depends(deps.get_db), - date_from: datetime = Query(None), - date_to: datetime = Query(None), + date_from: int = Query(None), + date_to: int = Query(None), current_user: models.User = Depends(deps.get_current_admin) ): logs = crud.audit_log.get_for_export(db, date_from=date_from, date_to=date_to) - return schemas.AuditLogExport(logs=logs, export_timestamp=datetime.utcnow()) + return shared_schemas.AuditLogExport(logs=logs, export_timestamp=int(datetime.now().timestamp())) @router.get("/logs/actions", response_model=List[str]) diff --git a/server/app/api/v1/endpoints/carriers.py b/server/app/api/v1/endpoints/carriers.py index 269e012..67855da 100644 --- a/server/app/api/v1/endpoints/carriers.py +++ b/server/app/api/v1/endpoints/carriers.py @@ -4,22 +4,23 @@ from fastapi import APIRouter, Depends, HTTPException, Path, Body from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Carrier) +@router.post("/", response_model=shared_schemas.Carrier) def create_carrier( - carrier: schemas.CarrierCreate, + carrier: shared_schemas.CarrierCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.carrier.create(db=db, obj_in=carrier) -@router.get("/", response_model=List[schemas.Carrier]) +@router.get("/", response_model=List[shared_schemas.Carrier]) def read_carriers( db: Session = Depends(deps.get_db), skip: int = 0, @@ -29,7 +30,7 @@ def read_carriers( return crud.carrier.get_multi(db, skip=skip, limit=limit) -@router.get("/{carrier_id}", response_model=schemas.Carrier) +@router.get("/{carrier_id}", response_model=shared_schemas.Carrier) def read_carrier( carrier_id: int = Path(..., title="The ID of the carrier to get"), db: Session = Depends(deps.get_db), @@ -41,10 +42,10 @@ def read_carrier( return carrier -@router.put("/{carrier_id}", response_model=schemas.Carrier) +@router.put("/{carrier_id}", response_model=shared_schemas.Carrier) def update_carrier( carrier_id: int = Path(..., title="The ID of the carrier to update"), - carrier_in: schemas.CarrierUpdate = Body(..., title="Carrier update data"), + carrier_in: shared_schemas.CarrierUpdate = Body(..., title="Carrier update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -54,7 +55,7 @@ def update_carrier( return crud.carrier.update(db, db_obj=carrier, obj_in=carrier_in) -@router.delete("/{carrier_id}", response_model=schemas.Carrier) +@router.delete("/{carrier_id}", response_model=shared_schemas.Carrier) def delete_carrier( carrier_id: int = Path(..., title="The ID of the carrier to delete"), db: Session = Depends(deps.get_db), diff --git a/server/app/api/v1/endpoints/customers.py b/server/app/api/v1/endpoints/customers.py index 24759ca..0dd135c 100644 --- a/server/app/api/v1/endpoints/customers.py +++ b/server/app/api/v1/endpoints/customers.py @@ -4,33 +4,34 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Customer) +@router.post("/", response_model=shared_schemas.Customer) def create_customer( - customer: schemas.CustomerCreate, + customer: shared_schemas.CustomerCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.customer.create(db=db, obj_in=customer) -@router.get("/", response_model=List[schemas.Customer]) +@router.get("/", response_model=List[shared_schemas.Customer]) def read_customers( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - customer_filter: schemas.CustomerFilter = Depends(), + customer_filter: shared_schemas.CustomerFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.customer.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=customer_filter) -@router.get("/{customer_id}", response_model=schemas.Customer) +@router.get("/{customer_id}", response_model=shared_schemas.Customer) def read_customer( customer_id: int, db: Session = Depends(deps.get_db), @@ -42,10 +43,10 @@ def read_customer( return customer -@router.put("/{customer_id}", response_model=schemas.Customer) +@router.put("/{customer_id}", response_model=shared_schemas.Customer) def update_customer( customer_id: int, - customer_in: schemas.CustomerUpdate, + customer_in: shared_schemas.CustomerUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -55,7 +56,7 @@ def update_customer( return crud.customer.update(db, db_obj=customer, obj_in=customer_in) -@router.delete("/{customer_id}", response_model=schemas.Customer) +@router.delete("/{customer_id}", response_model=shared_schemas.Customer) def delete_customer( customer_id: int, db: Session = Depends(deps.get_db), @@ -67,7 +68,7 @@ def delete_customer( return crud.customer.remove(db, id=customer_id) -@router.get("/{customer_id}/orders", response_model=List[schemas.Order]) +@router.get("/{customer_id}/orders", response_model=List[shared_schemas.Order]) def read_customer_orders( customer_id: int, db: Session = Depends(deps.get_db), diff --git a/server/app/api/v1/endpoints/inventory.py b/server/app/api/v1/endpoints/inventory.py index 21a029c..23b277d 100644 --- a/server/app/api/v1/endpoints/inventory.py +++ b/server/app/api/v1/endpoints/inventory.py @@ -1,48 +1,49 @@ # /server/app/api/v1/endpoints/inventory.py -from datetime import datetime -from typing import List + +from typing import List, Dict from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session -from .... import crud, models, schemas +from public_api import shared_schemas +from .... import crud, models from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Inventory) +@router.post("/", response_model=shared_schemas.Inventory) def create_inventory( - inventory: schemas.InventoryCreate, + inventory: shared_schemas.InventoryCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.inventory.create(db=db, obj_in=inventory) -@router.get("/", response_model=schemas.InventoryList) +@router.get("/", response_model=shared_schemas.InventoryList) def read_inventory( - db: Session = Depends(deps.get_db), - skip: int = 0, - limit: int = 100, - inventory_filter: schemas.InventoryFilter = Depends(), - current_user: models.User = Depends(deps.get_current_active_user) + db: Session = Depends(deps.get_db), + skip: int = 0, + limit: int = 100, + inventory_filter: shared_schemas.InventoryFilter = Depends(), + current_user: models.User = Depends(deps.get_current_active_user) ): items = crud.inventory.get_multi_with_products(db, skip=skip, limit=limit, filter_params=inventory_filter) total = len(items) return {"items": items, "total": total} -@router.post("/transfer", response_model=schemas.Inventory) +@router.post("/transfer", response_model=shared_schemas.Inventory) def transfer_inventory( - transfer: schemas.InventoryTransfer, + transfer: shared_schemas.InventoryTransfer, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.inventory.transfer(db, transfer=transfer) -@router.get("/report", response_model=schemas.InventoryReport) +@router.get("/report", response_model=shared_schemas.InventoryReport) def get_inventory_report( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -50,17 +51,17 @@ def get_inventory_report( return crud.inventory.get_inventory_report(db) -@router.post("/cycle_count", response_model=List[schemas.Inventory]) +@router.post("/cycle_count", response_model=List[shared_schemas.Inventory]) def perform_cycle_count( location_id: int, - counted_items: List[schemas.InventoryUpdate], + counted_items: List[shared_schemas.InventoryUpdate], db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.inventory.perform_cycle_count(db, location_id=location_id, counted_items=counted_items) -@router.get("/low_stock", response_model=List[schemas.ProductWithInventory]) +@router.get("/low_stock", response_model=List[shared_schemas.ProductWithInventory]) def get_low_stock_items( threshold: int = Query(10, ge=0), db: Session = Depends(deps.get_db), @@ -69,7 +70,7 @@ def get_low_stock_items( return crud.inventory.get_low_stock_items(db, threshold=threshold) -@router.get("/out_of_stock", response_model=List[schemas.Product]) +@router.get("/out_of_stock", response_model=List[shared_schemas.Product]) def get_out_of_stock_items( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -77,7 +78,7 @@ def get_out_of_stock_items( return crud.inventory.get_out_of_stock_items(db) -@router.post("/reorder", response_model=List[schemas.Product]) +@router.post("/reorder", response_model=List[shared_schemas.Product]) def create_reorder_list( threshold: int = Query(10, ge=0), db: Session = Depends(deps.get_db), @@ -86,7 +87,7 @@ def create_reorder_list( return crud.inventory.create_reorder_list(db, threshold=threshold) -@router.get("/product_locations/{product_id}", response_model=List[schemas.LocationWithInventory]) +@router.get("/product_locations/{product_id}", response_model=List[shared_schemas.LocationWithInventory]) def get_product_locations( product_id: int, db: Session = Depends(deps.get_db), @@ -95,27 +96,27 @@ def get_product_locations( return crud.inventory.get_product_locations(db, product_id=product_id) -@router.post("/batch_update", response_model=List[schemas.Inventory]) +@router.post("/batch_update", response_model=List[shared_schemas.Inventory]) def batch_update_inventory( - updates: List[schemas.InventoryUpdate], + updates: List[shared_schemas.InventoryUpdate], db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.inventory.batch_update(db, updates=updates) -@router.get("/movement_history/{product_id}", response_model=List[schemas.InventoryMovement]) +@router.get("/movement_history/{product_id}", response_model=List[shared_schemas.InventoryMovement]) def get_inventory_movement_history( product_id: int, - start_date: datetime = Query(None), - end_date: datetime = Query(None), + start_date: int = Query(None), + end_date: int = Query(None), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.inventory.get_movement_history(db, product_id=product_id, start_date=start_date, end_date=end_date) -@router.get("/summary", response_model=schemas.InventorySummary) +@router.get("/summary", response_model=shared_schemas.InventorySummary) def get_inventory_summary( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -123,16 +124,16 @@ def get_inventory_summary( return crud.inventory.get_inventory_summary(db) -@router.post("/stocktake", response_model=schemas.StocktakeResult) +@router.post("/stocktake", response_model=shared_schemas.StocktakeResult) def perform_stocktake( - stocktake: schemas.StocktakeCreate, + stocktake: shared_schemas.StocktakeCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.inventory.perform_stocktake(db, stocktake=stocktake) -@router.get("/abc_analysis", response_model=schemas.ABCAnalysisResult) +@router.get("/abc_analysis", response_model=shared_schemas.ABCAnalysisResult) def perform_abc_analysis( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -140,7 +141,7 @@ def perform_abc_analysis( return crud.inventory.perform_abc_analysis(db) -@router.post("/optimize_locations", response_model=List[schemas.InventoryLocationSuggestion]) +@router.post("/optimize_locations", response_model=List[shared_schemas.InventoryLocationSuggestion]) def optimize_inventory_locations( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -148,7 +149,7 @@ def optimize_inventory_locations( return crud.inventory.optimize_locations(db) -@router.get("/expiring_soon", response_model=List[schemas.ProductWithInventory]) +@router.get("/expiring_soon", response_model=List[shared_schemas.ProductWithInventory]) def get_expiring_soon_inventory( days: int = Query(30, description="Number of days to consider for expiration"), db: Session = Depends(deps.get_db), @@ -157,16 +158,16 @@ def get_expiring_soon_inventory( return crud.inventory.get_expiring_soon(db, days=days) -@router.post("/bulk_import", response_model=schemas.BulkImportResult) +@router.post("/bulk_import", response_model=shared_schemas.BulkImportResult) def bulk_import_inventory( - import_data: schemas.BulkImportData, + import_data: shared_schemas.BulkImportData, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin) ): return crud.inventory.bulk_import(db, import_data=import_data) -@router.get("/storage_utilization", response_model=schemas.StorageUtilization) +@router.get("/storage_utilization", response_model=shared_schemas.StorageUtilization) def get_storage_utilization( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -174,36 +175,75 @@ def get_storage_utilization( return crud.inventory.get_storage_utilization(db) -@router.get("/{inventory_id}", response_model=schemas.Inventory) +@router.get("/{id}", response_model=shared_schemas.Inventory) def read_inventory_item( - inventory_id: int, + id: int, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): - inventory = crud.inventory.get(db, id=inventory_id) + inventory = crud.inventory.get(db, id=id) if inventory is None: raise HTTPException(status_code=404, detail="Inventory item not found") return inventory -@router.put("/{inventory_id}", response_model=schemas.Inventory) +@router.put("/{id}", response_model=shared_schemas.Inventory) def update_inventory( - inventory_id: int, - inventory_in: schemas.InventoryUpdate, + id: int, + inventory_in: shared_schemas.InventoryUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): - inventory = crud.inventory.get(db, id=inventory_id) + inventory = crud.inventory.get(db, id=id) if inventory is None: raise HTTPException(status_code=404, detail="Inventory item not found") return crud.inventory.update(db, db_obj=inventory, obj_in=inventory_in) -@router.post("/{inventory_id}/adjust", response_model=schemas.Inventory) +@router.post("/{id}/adjust", response_model=shared_schemas.Inventory) def adjust_inventory( - inventory_id: int, - adjustment: schemas.InventoryAdjustment, + id: int, + adjustment: shared_schemas.InventoryAdjustment, + db: Session = Depends(deps.get_db), + current_user: models.User = Depends(deps.get_current_active_user) +): + return crud.inventory.adjust_quantity(db, id=id, adjustment=adjustment) + + +@router.get("/forecast/{product_id}", response_model=Dict) +def get_inventory_forecast( + product_id: int, + db: Session = Depends(deps.get_db), + current_user: models.User = Depends(deps.get_current_active_user) +): + return crud.inventory.get_inventory_forecast(db, product_id=product_id) + + +@router.post("/forecast/{product_id}", response_model=Dict) +def generate_inventory_forecast( + product_id: int, + db: Session = Depends(deps.get_db), + current_user: models.User = Depends(deps.get_current_active_user) +): + return crud.inventory.generate_inventory_forecast(db, product_id=product_id) + + +@router.get("/reorder_suggestions", response_model=List[Dict]) +def get_reorder_suggestions( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): - return crud.inventory.adjust_quantity(db, inventory_id=inventory_id, adjustment=adjustment) + return crud.inventory.get_reorder_suggestions(db) + + +@router.delete("/{id}", response_model=shared_schemas.Inventory) +def delete_inventory_item( + id: int, + db: Session = Depends(deps.get_db), + current_user: models.User = Depends(deps.get_current_active_user) +): + inventory_item = crud.inventory.get(db, id=id) + if not inventory_item: + raise HTTPException(status_code=404, detail="Inventory item not found") + inventory_item = crud.inventory.remove(db, id=id) + return inventory_item diff --git a/server/app/api/v1/endpoints/locations.py b/server/app/api/v1/endpoints/locations.py index 6fd2990..d553345 100644 --- a/server/app/api/v1/endpoints/locations.py +++ b/server/app/api/v1/endpoints/locations.py @@ -4,33 +4,34 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Location) +@router.post("/", response_model=shared_schemas.Location) def create_location( - location: schemas.LocationCreate, + location: shared_schemas.LocationCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.location.create(db=db, obj_in=location) -@router.get("/", response_model=List[schemas.LocationWithInventory]) +@router.get("/", response_model=List[shared_schemas.LocationWithInventory]) def read_locations( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - location_filter: schemas.LocationFilter = Depends(), + location_filter: shared_schemas.LocationFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.location.get_multi_with_inventory(db, skip=skip, limit=limit, filter_params=location_filter) -@router.get("/{location_id}", response_model=schemas.LocationWithInventory) +@router.get("/{location_id}", response_model=shared_schemas.LocationWithInventory) def read_location( location_id: int, db: Session = Depends(deps.get_db), @@ -42,10 +43,10 @@ def read_location( return location -@router.put("/{location_id}", response_model=schemas.Location) +@router.put("/{location_id}", response_model=shared_schemas.Location) def update_location( location_id: int, - location_in: schemas.LocationUpdate, + location_in: shared_schemas.LocationUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -55,7 +56,7 @@ def update_location( return crud.location.update(db, db_obj=location, obj_in=location_in) -@router.delete("/{location_id}", response_model=schemas.Location) +@router.delete("/{location_id}", response_model=shared_schemas.Location) def delete_location( location_id: int, db: Session = Depends(deps.get_db), diff --git a/server/app/api/v1/endpoints/orders.py b/server/app/api/v1/endpoints/orders.py index e1ea5f2..de99f58 100644 --- a/server/app/api/v1/endpoints/orders.py +++ b/server/app/api/v1/endpoints/orders.py @@ -1,47 +1,48 @@ # /server/app/api/v1/endpoints/orders.py -from datetime import datetime + from typing import List from fastapi import APIRouter, Depends, HTTPException, Query, Body from sqlalchemy.orm import Session -from .... import crud, models, schemas +from public_api import shared_schemas +from .... import crud, models from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Order) +@router.post("/", response_model=shared_schemas.Order) def create_order( - order: schemas.OrderCreate, + order: shared_schemas.OrderCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.order.create(db=db, obj_in=order) -@router.get("/", response_model=List[schemas.OrderWithDetails]) +@router.get("/", response_model=List[shared_schemas.OrderWithDetails]) def read_orders( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - filter_params: schemas.OrderFilter = Depends(), + filter_params: shared_schemas.OrderFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.order.get_multi_with_details(db, skip=skip, limit=limit, filter_params=filter_params) -@router.get("/summary", response_model=schemas.OrderSummary) +@router.get("/summary", response_model=shared_schemas.OrderSummary) def get_order_summary( db: Session = Depends(deps.get_db), - date_from: datetime = Query(None), - date_to: datetime = Query(None), + date_from: int = Query(None), + date_to: int = Query(None), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.order.get_summary(db, date_from=date_from, date_to=date_to) -@router.get("/backorders", response_model=List[schemas.Order]) +@router.get("/backorders", response_model=List[shared_schemas.Order]) def get_backorders( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -49,26 +50,26 @@ def get_backorders( return crud.order.get_backorders(db) -@router.post("/bulk_import", response_model=schemas.BulkImportResult) +@router.post("/bulk_import", response_model=shared_schemas.BulkImportResult) def bulk_import_orders( - import_data: schemas.BulkImportData, + import_data: shared_schemas.BulkImportData, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin) ): return crud.order.bulk_import(db, import_data=import_data) -@router.get("/processing_times", response_model=schemas.OrderProcessingTimes) +@router.get("/processing_times", response_model=shared_schemas.OrderProcessingTimes) def get_order_processing_times( - start_date: datetime = Query(...), - end_date: datetime = Query(...), + start_date: int = Query(...), + end_date: int = Query(...), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.order.get_processing_times(db, start_date=start_date, end_date=end_date) -@router.get("/{order_id}", response_model=schemas.OrderWithDetails) +@router.get("/{order_id}", response_model=shared_schemas.OrderWithDetails) def read_order( order_id: int, db: Session = Depends(deps.get_db), @@ -80,10 +81,10 @@ def read_order( return order -@router.put("/{order_id}", response_model=schemas.Order) +@router.put("/{order_id}", response_model=shared_schemas.Order) def update_order( order_id: int, - order_in: schemas.OrderUpdate, + order_in: shared_schemas.OrderUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -93,7 +94,7 @@ def update_order( return crud.order.update(db, db_obj=order, obj_in=order_in) -@router.delete("/{order_id}", response_model=schemas.Order) +@router.delete("/{order_id}", response_model=shared_schemas.Order) def delete_order( order_id: int, db: Session = Depends(deps.get_db), @@ -105,7 +106,7 @@ def delete_order( return crud.order.remove(db, id=order_id) -@router.post("/{order_id}/cancel", response_model=schemas.Order) +@router.post("/{order_id}/cancel", response_model=shared_schemas.Order) def cancel_order( order_id: int, db: Session = Depends(deps.get_db), @@ -117,10 +118,10 @@ def cancel_order( return crud.order.cancel(db, db_obj=order) -@router.post("/{order_id}/ship", response_model=schemas.Order) +@router.post("/{order_id}/ship", response_model=shared_schemas.Order) def ship_order( order_id: int, - shipping_info: schemas.ShippingInfo, + shipping_info: shared_schemas.ShippingInfo, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -130,7 +131,7 @@ def ship_order( return crud.order.ship(db, db_obj=order, shipping_info=shipping_info) -@router.post("/{order_id}/cancel_item", response_model=schemas.Order) +@router.post("/{order_id}/cancel_item", response_model=shared_schemas.Order) def cancel_order_item( order_id: int, item_id: int = Body(..., embed=True), @@ -140,11 +141,11 @@ def cancel_order_item( return crud.order.cancel_item(db, order_id=order_id, item_id=item_id) -@router.post("/{order_id}/add_item", response_model=schemas.Order) +@router.post("/{order_id}/add_item", response_model=shared_schemas.Order) def add_order_item( order_id: int, - item: schemas.OrderItemCreate, + item: shared_schemas.OrderItemCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): - return crud.order.add_item(db, order_id=order_id, item=item) \ No newline at end of file + return crud.order.add_item(db, order_id=order_id, item=item) diff --git a/server/app/api/v1/endpoints/permissions.py b/server/app/api/v1/endpoints/permissions.py index de4c086..44e9014 100644 --- a/server/app/api/v1/endpoints/permissions.py +++ b/server/app/api/v1/endpoints/permissions.py @@ -4,23 +4,24 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Permission) +@router.post("/", response_model=shared_schemas.Permission) def create_permission( - permission: schemas.PermissionCreate, + permission: shared_schemas.PermissionCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin) ): new_permission = crud.permission.create(db=db, obj_in=permission) - return schemas.Permission.model_validate(new_permission) + return shared_schemas.Permission.model_validate(new_permission) -@router.get("/", response_model=List[schemas.Permission]) +@router.get("/", response_model=List[shared_schemas.Permission]) def read_permissions( skip: int = 0, limit: int = 100, @@ -28,10 +29,10 @@ def read_permissions( current_user: models.User = Depends(deps.get_current_active_user) ): permissions = crud.permission.get_multi(db, skip=skip, limit=limit) - return [schemas.Permission.model_validate(perm) for perm in permissions] + return [shared_schemas.Permission.model_validate(perm) for perm in permissions] -@router.get("/{permission_id}", response_model=schemas.Permission) +@router.get("/{permission_id}", response_model=shared_schemas.Permission) def read_permission( permission_id: int, db: Session = Depends(deps.get_db), @@ -40,13 +41,13 @@ def read_permission( permission = crud.permission.get(db, id=permission_id) if not permission: raise HTTPException(status_code=404, detail="Permission not found") - return schemas.Permission.model_validate(permission) + return shared_schemas.Permission.model_validate(permission) -@router.put("/{permission_id}", response_model=schemas.Permission) +@router.put("/{permission_id}", response_model=shared_schemas.Permission) def update_permission( permission_id: int, - permission_in: schemas.PermissionUpdate, + permission_in: shared_schemas.PermissionUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin) ): @@ -54,10 +55,10 @@ def update_permission( if not permission: raise HTTPException(status_code=404, detail="Permission not found") updated_permission = crud.permission.update(db, db_obj=permission, obj_in=permission_in) - return schemas.Permission.model_validate(updated_permission) + return shared_schemas.Permission.model_validate(updated_permission) -@router.delete("/{permission_id}", response_model=schemas.Permission) +@router.delete("/{permission_id}", response_model=shared_schemas.Permission) def delete_permission( permission_id: int, db: Session = Depends(deps.get_db), @@ -67,4 +68,4 @@ def delete_permission( if not permission: raise HTTPException(status_code=404, detail="Permission not found") deleted_permission = crud.permission.remove(db, id=permission_id) - return schemas.Permission.model_validate(deleted_permission) + return shared_schemas.Permission.model_validate(deleted_permission) diff --git a/server/app/api/v1/endpoints/pick_lists.py b/server/app/api/v1/endpoints/pick_lists.py index bd185f9..e04e0cc 100644 --- a/server/app/api/v1/endpoints/pick_lists.py +++ b/server/app/api/v1/endpoints/pick_lists.py @@ -1,37 +1,38 @@ # /server/app/api/v1/endpoints/pick_lists.py -from datetime import datetime + from typing import List from fastapi import APIRouter, Depends, HTTPException, Path, Body, Query from sqlalchemy.orm import Session -from .... import crud, models, schemas +from public_api import shared_schemas +from .... import crud, models from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.PickList) +@router.post("/", response_model=shared_schemas.PickList) def create_pick_list( - pick_list: schemas.PickListCreate, + pick_list: shared_schemas.PickListCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.pick_list.create_with_items(db=db, obj_in=pick_list) -@router.get("/", response_model=List[schemas.PickList]) +@router.get("/", response_model=List[shared_schemas.PickList]) def read_pick_lists( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - filter_params: schemas.PickListFilter = Depends(), + filter_params: shared_schemas.PickListFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.pick_list.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=filter_params) -@router.get("/optimize_route", response_model=schemas.OptimizedPickingRoute) +@router.get("/optimize_route", response_model=shared_schemas.OptimizedPickingRoute) def optimize_picking_route( pick_list_id: int, db: Session = Depends(deps.get_db), @@ -40,17 +41,17 @@ def optimize_picking_route( return crud.pick_list.optimize_route(db, pick_list_id=pick_list_id) -@router.get("/performance", response_model=schemas.PickingPerformance) +@router.get("/performance", response_model=shared_schemas.PickingPerformance) def get_picking_performance( - start_date: datetime = Query(...), - end_date: datetime = Query(...), + start_date: int = Query(...), + end_date: int = Query(...), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.pick_list.get_performance(db, start_date=start_date, end_date=end_date) -@router.get("/{pick_list_id}", response_model=schemas.PickList) +@router.get("/{pick_list_id}", response_model=shared_schemas.PickList) def read_pick_list( pick_list_id: int = Path(..., title="The ID of the pick list to get"), db: Session = Depends(deps.get_db), @@ -62,10 +63,10 @@ def read_pick_list( return pick_list -@router.put("/{pick_list_id}", response_model=schemas.PickList) +@router.put("/{pick_list_id}", response_model=shared_schemas.PickList) def update_pick_list( pick_list_id: int = Path(..., title="The ID of the pick list to update"), - pick_list_in: schemas.PickListUpdate = Body(..., title="Pick list update data"), + pick_list_in: shared_schemas.PickListUpdate = Body(..., title="Pick list update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -75,7 +76,7 @@ def update_pick_list( return crud.pick_list.update_with_items(db, db_obj=pick_list, obj_in=pick_list_in) -@router.delete("/{pick_list_id}", response_model=schemas.PickList) +@router.delete("/{pick_list_id}", response_model=shared_schemas.PickList) def delete_pick_list( pick_list_id: int = Path(..., title="The ID of the pick list to delete"), db: Session = Depends(deps.get_db), @@ -87,7 +88,7 @@ def delete_pick_list( return crud.pick_list.remove(db, id=pick_list_id) -@router.post("/{pick_list_id}/start", response_model=schemas.PickList) +@router.post("/{pick_list_id}/start", response_model=shared_schemas.PickList) def start_pick_list( pick_list_id: int, db: Session = Depends(deps.get_db), @@ -96,7 +97,7 @@ def start_pick_list( return crud.pick_list.start(db, pick_list_id=pick_list_id, user_id=current_user.user_id) -@router.post("/{pick_list_id}/complete", response_model=schemas.PickList) +@router.post("/{pick_list_id}/complete", response_model=shared_schemas.PickList) def complete_pick_list( pick_list_id: int, db: Session = Depends(deps.get_db), diff --git a/server/app/api/v1/endpoints/po_items.py b/server/app/api/v1/endpoints/po_items.py index b7b7c4f..95e2ddd 100644 --- a/server/app/api/v1/endpoints/po_items.py +++ b/server/app/api/v1/endpoints/po_items.py @@ -4,13 +4,14 @@ from fastapi import APIRouter, Depends, HTTPException, Path, Body from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.get("/", response_model=List[schemas.POItem]) +@router.get("/", response_model=List[shared_schemas.POItem]) def read_po_items( skip: int = 0, limit: int = 100, @@ -21,7 +22,7 @@ def read_po_items( return po_items -@router.get("/by_product/{product_id}", response_model=List[schemas.POItem]) +@router.get("/by_product/{product_id}", response_model=List[shared_schemas.POItem]) def read_po_items_by_product( product_id: int = Path(..., title="The ID of the product to filter by"), skip: int = 0, @@ -33,7 +34,7 @@ def read_po_items_by_product( return po_items -@router.get("/pending_receipt", response_model=List[schemas.POItem]) +@router.get("/pending_receipt", response_model=List[shared_schemas.POItem]) def read_pending_receipt_po_items( skip: int = 0, limit: int = 100, @@ -44,7 +45,7 @@ def read_pending_receipt_po_items( return po_items -@router.get("/{po_item_id}", response_model=schemas.POItem) +@router.get("/{po_item_id}", response_model=shared_schemas.POItem) def read_po_item( po_item_id: int = Path(..., title="The ID of the PO item to get"), db: Session = Depends(deps.get_db), @@ -56,10 +57,10 @@ def read_po_item( return po_item -@router.put("/{po_item_id}", response_model=schemas.POItem) +@router.put("/{po_item_id}", response_model=shared_schemas.POItem) def update_po_item( po_item_id: int = Path(..., title="The ID of the PO item to update"), - po_item_in: schemas.POItemUpdate = Body(..., title="PO Item update data"), + po_item_in: shared_schemas.POItemUpdate = Body(..., title="PO Item update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): diff --git a/server/app/api/v1/endpoints/product_categories.py b/server/app/api/v1/endpoints/product_categories.py index 515b57b..1a17da2 100644 --- a/server/app/api/v1/endpoints/product_categories.py +++ b/server/app/api/v1/endpoints/product_categories.py @@ -4,22 +4,23 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.ProductCategory) +@router.post("/", response_model=shared_schemas.ProductCategory) def create_category( - category: schemas.ProductCategoryCreate, + category: shared_schemas.ProductCategoryCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.product_category.create(db=db, obj_in=category) -@router.get("/", response_model=List[schemas.ProductCategory]) +@router.get("/", response_model=List[shared_schemas.ProductCategory]) def read_categories( db: Session = Depends(deps.get_db), skip: int = 0, @@ -29,7 +30,7 @@ def read_categories( return crud.product_category.get_multi(db, skip=skip, limit=limit) -@router.get("/{category_id}", response_model=schemas.ProductCategory) +@router.get("/{category_id}", response_model=shared_schemas.ProductCategory) def read_category( category_id: int, db: Session = Depends(deps.get_db), @@ -41,10 +42,10 @@ def read_category( return category -@router.put("/{category_id}", response_model=schemas.ProductCategory) +@router.put("/{category_id}", response_model=shared_schemas.ProductCategory) def update_category( category_id: int, - category_in: schemas.ProductCategoryUpdate, + category_in: shared_schemas.ProductCategoryUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -54,7 +55,7 @@ def update_category( return crud.product_category.update(db, db_obj=category, obj_in=category_in) -@router.delete("/{category_id}", response_model=schemas.ProductCategory) +@router.delete("/{category_id}", response_model=shared_schemas.ProductCategory) def delete_category( category_id: int, db: Session = Depends(deps.get_db), diff --git a/server/app/api/v1/endpoints/products.py b/server/app/api/v1/endpoints/products.py index 3c90cf3..e6209e9 100644 --- a/server/app/api/v1/endpoints/products.py +++ b/server/app/api/v1/endpoints/products.py @@ -4,34 +4,42 @@ from fastapi import APIRouter, Depends, HTTPException, Body from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Product) +@router.post("/", response_model=shared_schemas.Product) def create_product( - product: schemas.ProductCreate, + product: shared_schemas.ProductCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.product.create(db=db, obj_in=product) -@router.get("/", response_model=List[schemas.ProductWithCategoryAndInventory]) +@router.get("/", response_model=List[shared_schemas.ProductWithCategoryAndInventory]) def read_products( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - product_filter: schemas.ProductFilter = Depends(), + product_filter: shared_schemas.ProductFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.product.get_multi_with_category_and_inventory(db, skip=skip, limit=limit, filter_params=product_filter) -@router.post("/barcode", response_model=schemas.Product) +@router.get("/max_id", response_model=int) +def get_max_product_id( + db: Session = Depends(deps.get_db), + current_user: models.User = Depends(deps.get_current_active_user) +): + return crud.product.get_max_id(db) + +@router.post("/barcode", response_model=shared_schemas.Product) def get_product_by_barcode( - barcode_data: schemas.BarcodeData, + barcode_data: shared_schemas.BarcodeData, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -40,7 +48,7 @@ def get_product_by_barcode( raise HTTPException(status_code=404, detail="Product not found") return product -@router.get("/{product_id}", response_model=schemas.ProductWithCategoryAndInventory) +@router.get("/{product_id}", response_model=shared_schemas.ProductWithCategoryAndInventory) def read_product( product_id: int, db: Session = Depends(deps.get_db), @@ -52,10 +60,10 @@ def read_product( return product -@router.put("/{product_id}", response_model=schemas.Product) +@router.put("/{product_id}", response_model=shared_schemas.Product) def update_product( product_id: int, - product_in: schemas.ProductUpdate, + product_in: shared_schemas.ProductUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -65,7 +73,7 @@ def update_product( return crud.product.update(db, db_obj=product, obj_in=product_in) -@router.delete("/{product_id}", response_model=schemas.Product) +@router.delete("/{product_id}", response_model=shared_schemas.Product) def delete_product( product_id: int, db: Session = Depends(deps.get_db), @@ -78,7 +86,7 @@ def delete_product( -@router.get("/{product_id}/substitutes", response_model=List[schemas.Product]) +@router.get("/{product_id}/substitutes", response_model=List[shared_schemas.Product]) def get_product_substitutes( product_id: int, db: Session = Depends(deps.get_db), @@ -87,7 +95,7 @@ def get_product_substitutes( return crud.product.get_substitutes(db, product_id=product_id) -@router.post("/{product_id}/substitutes", response_model=schemas.Product) +@router.post("/{product_id}/substitutes", response_model=shared_schemas.Product) def add_product_substitute( product_id: int, substitute_id: int = Body(..., embed=True), diff --git a/server/app/api/v1/endpoints/purchase_orders.py b/server/app/api/v1/endpoints/purchase_orders.py index a125b0a..384e473 100644 --- a/server/app/api/v1/endpoints/purchase_orders.py +++ b/server/app/api/v1/endpoints/purchase_orders.py @@ -4,33 +4,34 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.PurchaseOrder) +@router.post("/", response_model=shared_schemas.PurchaseOrder) def create_purchase_order( - purchase_order: schemas.PurchaseOrderCreate, + purchase_order: shared_schemas.PurchaseOrderCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.purchase_order.create(db=db, obj_in=purchase_order) -@router.get("/", response_model=List[schemas.PurchaseOrderWithDetails]) +@router.get("/", response_model=List[shared_schemas.PurchaseOrderWithDetails]) def read_purchase_orders( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - po_filter: schemas.PurchaseOrderFilter = Depends(), + po_filter: shared_schemas.PurchaseOrderFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.purchase_order.get_multi_with_products(db, skip=skip, limit=limit, filter_params=po_filter) -@router.get("/{po_id}", response_model=schemas.PurchaseOrderWithDetails) +@router.get("/{po_id}", response_model=shared_schemas.PurchaseOrderWithDetails) def read_purchase_order( po_id: int, db: Session = Depends(deps.get_db), @@ -42,10 +43,10 @@ def read_purchase_order( return po -@router.put("/{po_id}", response_model=schemas.PurchaseOrder) +@router.put("/{po_id}", response_model=shared_schemas.PurchaseOrder) def update_purchase_order( po_id: int, - po_in: schemas.PurchaseOrderUpdate, + po_in: shared_schemas.PurchaseOrderUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -55,7 +56,7 @@ def update_purchase_order( return crud.purchase_order.update(db, db_obj=po, obj_in=po_in) -@router.delete("/{po_id}", response_model=schemas.PurchaseOrder) +@router.delete("/{po_id}", response_model=shared_schemas.PurchaseOrder) def delete_purchase_order( po_id: int, db: Session = Depends(deps.get_db), @@ -67,10 +68,10 @@ def delete_purchase_order( return crud.purchase_order.remove(db, id=po_id) -@router.post("/{po_id}/receive", response_model=schemas.PurchaseOrder) +@router.post("/{po_id}/receive", response_model=shared_schemas.PurchaseOrder) def receive_purchase_order( po_id: int, - received_items: List[schemas.POItemReceive], + received_items: List[shared_schemas.POItemReceive], db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): diff --git a/server/app/api/v1/endpoints/quality.py b/server/app/api/v1/endpoints/quality.py index e6d3606..4c68bde 100644 --- a/server/app/api/v1/endpoints/quality.py +++ b/server/app/api/v1/endpoints/quality.py @@ -1,37 +1,38 @@ # /server/app/api/v1/endpoints/quality.py -from datetime import datetime + from typing import List from fastapi import APIRouter, Depends, HTTPException, Query, Path, Body from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/checks", response_model=schemas.QualityCheck) +@router.post("/checks", response_model=shared_schemas.QualityCheck) def create_quality_check( - check: schemas.QualityCheckCreate, + check: shared_schemas.QualityCheckCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.quality_check.create(db=db, obj_in=check) -@router.get("/checks", response_model=List[schemas.QualityCheckWithProduct]) +@router.get("/checks", response_model=List[shared_schemas.QualityCheckWithProduct]) def read_quality_checks( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - filter_params: schemas.QualityCheckFilter = Depends(), + filter_params: shared_schemas.QualityCheckFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.quality_check.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=filter_params) -@router.get("/checks/{check_id}", response_model=schemas.QualityCheckWithProduct) +@router.get("/checks/{check_id}", response_model=shared_schemas.QualityCheckWithProduct) def read_quality_check( check_id: int = Path(..., title="The ID of the quality check to get"), db: Session = Depends(deps.get_db), @@ -43,10 +44,10 @@ def read_quality_check( return check -@router.put("/checks/{check_id}", response_model=schemas.QualityCheck) +@router.put("/checks/{check_id}", response_model=shared_schemas.QualityCheck) def update_quality_check( check_id: int = Path(..., title="The ID of the quality check to update"), - check_in: schemas.QualityCheckUpdate = Body(..., title="Quality check update data"), + check_in: shared_schemas.QualityCheckUpdate = Body(..., title="Quality check update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -56,7 +57,7 @@ def update_quality_check( return crud.quality_check.update(db, db_obj=check, obj_in=check_in) -@router.delete("/checks/{check_id}", response_model=schemas.QualityCheck) +@router.delete("/checks/{check_id}", response_model=shared_schemas.QualityCheck) def delete_quality_check( check_id: int = Path(..., title="The ID of the quality check to delete"), db: Session = Depends(deps.get_db), @@ -68,26 +69,26 @@ def delete_quality_check( return crud.quality_check.remove(db, id=check_id) -@router.get("/metrics", response_model=schemas.QualityMetrics) +@router.get("/metrics", response_model=shared_schemas.QualityMetrics) def get_quality_metrics( db: Session = Depends(deps.get_db), - date_from: datetime = Query(None), - date_to: datetime = Query(None), + date_from: int = Query(None), + date_to: int = Query(None), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.quality_check.get_metrics(db, date_from=date_from, date_to=date_to) -@router.post("/standards", response_model=schemas.QualityStandard) +@router.post("/standards", response_model=shared_schemas.QualityStandard) def create_quality_standard( - standard: schemas.QualityStandardCreate, + standard: shared_schemas.QualityStandardCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.quality_standard.create(db=db, obj_in=standard) -@router.get("/standards", response_model=List[schemas.QualityStandard]) +@router.get("/standards", response_model=List[shared_schemas.QualityStandard]) def read_quality_standards( db: Session = Depends(deps.get_db), skip: int = 0, @@ -97,7 +98,7 @@ def read_quality_standards( return crud.quality_standard.get_multi(db, skip=skip, limit=limit) -@router.get("/standards/{standard_id}", response_model=schemas.QualityStandard) +@router.get("/standards/{standard_id}", response_model=shared_schemas.QualityStandard) def read_quality_standard( standard_id: int = Path(..., title="The ID of the quality standard to get"), db: Session = Depends(deps.get_db), @@ -109,10 +110,10 @@ def read_quality_standard( return standard -@router.put("/standards/{standard_id}", response_model=schemas.QualityStandard) +@router.put("/standards/{standard_id}", response_model=shared_schemas.QualityStandard) def update_quality_standard( standard_id: int = Path(..., title="The ID of the quality standard to update"), - standard_in: schemas.QualityStandardUpdate = Body(..., title="Quality standard update data"), + standard_in: shared_schemas.QualityStandardUpdate = Body(..., title="Quality standard update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -122,7 +123,7 @@ def update_quality_standard( return crud.quality_standard.update(db, db_obj=standard, obj_in=standard_in) -@router.delete("/standards/{standard_id}", response_model=schemas.QualityStandard) +@router.delete("/standards/{standard_id}", response_model=shared_schemas.QualityStandard) def delete_quality_standard( standard_id: int = Path(..., title="The ID of the quality standard to delete"), db: Session = Depends(deps.get_db), @@ -134,16 +135,16 @@ def delete_quality_standard( return crud.quality_standard.remove(db, id=standard_id) -@router.post("/alerts", response_model=schemas.QualityAlert) +@router.post("/alerts", response_model=shared_schemas.QualityAlert) def create_quality_alert( - alert: schemas.QualityAlertCreate, + alert: shared_schemas.QualityAlertCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.quality_alert.create(db=db, obj_in=alert) -@router.get("/alerts", response_model=List[schemas.QualityAlert]) +@router.get("/alerts", response_model=List[shared_schemas.QualityAlert]) def read_quality_alerts( db: Session = Depends(deps.get_db), skip: int = 0, @@ -153,10 +154,10 @@ def read_quality_alerts( return crud.quality_alert.get_multi(db, skip=skip, limit=limit) -@router.put("/alerts/{alert_id}/resolve", response_model=schemas.QualityAlert) +@router.put("/alerts/{alert_id}/resolve", response_model=shared_schemas.QualityAlert) def resolve_quality_alert( alert_id: int = Path(..., title="The ID of the quality alert to resolve"), - alert_in: schemas.QualityAlertUpdate = Body(..., title="Quality alert update data"), + alert_in: shared_schemas.QualityAlertUpdate = Body(..., title="Quality alert update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -166,7 +167,7 @@ def resolve_quality_alert( return crud.quality_alert.update(db, db_obj=alert, obj_in=alert_in) -@router.get("/product/{product_id}/history", response_model=List[schemas.QualityCheckWithProduct]) +@router.get("/product/{product_id}/history", response_model=List[shared_schemas.QualityCheckWithProduct]) def get_product_quality_history( product_id: int = Path(..., title="The ID of the product to get quality history for"), db: Session = Depends(deps.get_db), @@ -180,14 +181,14 @@ def get_product_quality_history( @router.get("/checks/summary", response_model=dict[str, int]) def get_quality_check_summary( db: Session = Depends(deps.get_db), - date_from: datetime = Query(None), - date_to: datetime = Query(None), + date_from: int = Query(None), + date_to: int = Query(None), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.quality_check.get_summary(db, date_from=date_from, date_to=date_to) -@router.get("/product/{product_id}/standards", response_model=List[schemas.QualityStandard]) +@router.get("/product/{product_id}/standards", response_model=List[shared_schemas.QualityStandard]) def get_product_quality_standards( product_id: int = Path(..., title="The ID of the product to get quality standards for"), db: Session = Depends(deps.get_db), @@ -196,16 +197,16 @@ def get_product_quality_standards( return crud.quality_standard.get_by_product(db, product_id=product_id) -@router.post("/batch_check", response_model=List[schemas.QualityCheck]) +@router.post("/batch_check", response_model=List[shared_schemas.QualityCheck]) def create_batch_quality_check( - checks: List[schemas.QualityCheckCreate], + checks: List[shared_schemas.QualityCheckCreate], db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.quality_check.create_batch(db=db, obj_in_list=checks) -@router.get("/alerts/active", response_model=List[schemas.QualityAlert]) +@router.get("/alerts/active", response_model=List[shared_schemas.QualityAlert]) def get_active_quality_alerts( db: Session = Depends(deps.get_db), skip: int = 0, @@ -215,21 +216,21 @@ def get_active_quality_alerts( return crud.quality_alert.get_active(db, skip=skip, limit=limit) -@router.post("/checks/{check_id}/comment", response_model=schemas.QualityCheckComment) +@router.post("/checks/{check_id}/comment", response_model=shared_schemas.QualityCheckComment) def add_comment_to_quality_check( check_id: int = Path(..., title="The ID of the quality check to comment on"), - comment: schemas.QualityCheckCommentCreate = Body(..., title="Quality check comment data"), + comment: shared_schemas.QualityCheckCommentCreate = Body(..., title="Quality check comment data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.quality_check.add_comment(db, check_id=check_id, comment=comment, user_id=current_user.user_id) -@router.get("/reports/defect_rate", response_model=List[schemas.ProductDefectRate]) +@router.get("/reports/defect_rate", response_model=List[shared_schemas.ProductDefectRate]) def get_product_defect_rates( db: Session = Depends(deps.get_db), - date_from: datetime = Query(None), - date_to: datetime = Query(None), + date_from: int = Query(None), + date_to: int = Query(None), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.quality_check.get_product_defect_rates(db, date_from=date_from, date_to=date_to) diff --git a/server/app/api/v1/endpoints/receipts.py b/server/app/api/v1/endpoints/receipts.py index 9fbe0a5..94a7996 100644 --- a/server/app/api/v1/endpoints/receipts.py +++ b/server/app/api/v1/endpoints/receipts.py @@ -4,33 +4,34 @@ from fastapi import APIRouter, Depends, HTTPException, Path, Body from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Receipt) +@router.post("/", response_model=shared_schemas.Receipt) def create_receipt( - receipt: schemas.ReceiptCreate, + receipt: shared_schemas.ReceiptCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.receipt.create_with_items(db=db, obj_in=receipt) -@router.get("/", response_model=List[schemas.Receipt]) +@router.get("/", response_model=List[shared_schemas.Receipt]) def read_receipts( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - filter_params: schemas.ReceiptFilter = Depends(), + filter_params: shared_schemas.ReceiptFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.receipt.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=filter_params) -@router.get("/expected_today", response_model=List[schemas.Receipt]) +@router.get("/expected_today", response_model=List[shared_schemas.Receipt]) def get_expected_receipts_today( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -38,7 +39,7 @@ def get_expected_receipts_today( return crud.receipt.get_expected_today(db) -@router.get("/{receipt_id}", response_model=schemas.Receipt) +@router.get("/{receipt_id}", response_model=shared_schemas.Receipt) def read_receipt( receipt_id: int = Path(..., title="The ID of the receipt to get"), db: Session = Depends(deps.get_db), @@ -50,10 +51,10 @@ def read_receipt( return receipt -@router.put("/{receipt_id}", response_model=schemas.Receipt) +@router.put("/{receipt_id}", response_model=shared_schemas.Receipt) def update_receipt( receipt_id: int = Path(..., title="The ID of the receipt to update"), - receipt_in: schemas.ReceiptUpdate = Body(..., title="Receipt update data"), + receipt_in: shared_schemas.ReceiptUpdate = Body(..., title="Receipt update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -63,7 +64,7 @@ def update_receipt( return crud.receipt.update_with_items(db, db_obj=receipt, obj_in=receipt_in) -@router.delete("/{receipt_id}", response_model=schemas.Receipt) +@router.delete("/{receipt_id}", response_model=shared_schemas.Receipt) def delete_receipt( receipt_id: int = Path(..., title="The ID of the receipt to delete"), db: Session = Depends(deps.get_db), @@ -75,20 +76,20 @@ def delete_receipt( return crud.receipt.remove(db, id=receipt_id) -@router.post("/{receipt_id}/quality_check", response_model=schemas.Receipt) +@router.post("/{receipt_id}/quality_check", response_model=shared_schemas.Receipt) def perform_receipt_quality_check( receipt_id: int, - quality_check: schemas.QualityCheckCreate, + quality_check: shared_schemas.QualityCheckCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.receipt.perform_quality_check(db, receipt_id=receipt_id, quality_check=quality_check) -@router.post("/{receipt_id}/discrepancy", response_model=schemas.Receipt) +@router.post("/{receipt_id}/discrepancy", response_model=shared_schemas.Receipt) def report_receipt_discrepancy( receipt_id: int, - discrepancy: schemas.ReceiptDiscrepancy, + discrepancy: shared_schemas.ReceiptDiscrepancy, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): diff --git a/server/app/api/v1/endpoints/reports.py b/server/app/api/v1/endpoints/reports.py index 535604f..4f959f5 100644 --- a/server/app/api/v1/endpoints/reports.py +++ b/server/app/api/v1/endpoints/reports.py @@ -1,16 +1,17 @@ # /server/app/api/v1/endpoints/reports.py -from datetime import date + from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.get("/inventory_summary", response_model=schemas.InventorySummaryReport) +@router.get("/inventory_summary", response_model=shared_schemas.InventorySummaryReport) def get_inventory_summary( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -18,27 +19,27 @@ def get_inventory_summary( return crud.reports.get_inventory_summary(db) -@router.get("/order_summary", response_model=schemas.OrderSummaryReport) +@router.get("/order_summary", response_model=shared_schemas.OrderSummaryReport) def get_order_summary( - start_date: date = Query(...), - end_date: date = Query(...), + start_date: int = Query(...), + end_date: int = Query(...), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.reports.get_order_summary(db, start_date, end_date) -@router.get("/warehouse_performance", response_model=schemas.WarehousePerformanceReport) +@router.get("/warehouse_performance", response_model=shared_schemas.WarehousePerformanceReport) def get_warehouse_performance( - start_date: date = Query(...), - end_date: date = Query(...), + start_date: int = Query(...), + end_date: int = Query(...), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.reports.get_warehouse_performance(db, start_date, end_date) -@router.get("/kpi_dashboard", response_model=schemas.KPIDashboard) +@router.get("/kpi_dashboard", response_model=shared_schemas.KPIDashboard) def get_kpi_dashboard( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) diff --git a/server/app/api/v1/endpoints/roles.py b/server/app/api/v1/endpoints/roles.py index 6096c92..abe603e 100644 --- a/server/app/api/v1/endpoints/roles.py +++ b/server/app/api/v1/endpoints/roles.py @@ -4,13 +4,14 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.get("/", response_model=List[schemas.Role]) +@router.get("/", response_model=List[shared_schemas.Role]) def read_roles( skip: int = 0, limit: int = 100, @@ -18,10 +19,10 @@ def read_roles( current_user: models.User = Depends(deps.get_current_active_user) ): roles = crud.role.get_multi(db, skip=skip, limit=limit) - return [schemas.Role.model_validate(role) for role in roles] + return [shared_schemas.Role.model_validate(role) for role in roles] -@router.get("/{role_id}", response_model=schemas.Role) +@router.get("/{role_id}", response_model=shared_schemas.Role) def read_role( role_id: int, db: Session = Depends(deps.get_db), @@ -30,13 +31,13 @@ def read_role( role = crud.role.get(db, id=role_id) if not role: raise HTTPException(status_code=404, detail="Role not found") - return schemas.Role.model_validate(role) + return shared_schemas.Role.model_validate(role) -@router.put("/{role_id}", response_model=schemas.Role) +@router.put("/{role_id}", response_model=shared_schemas.Role) def update_role( role_id: int, - role_in: schemas.RoleUpdate, + role_in: shared_schemas.RoleUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin) ): @@ -44,10 +45,10 @@ def update_role( if not role: raise HTTPException(status_code=404, detail="Role not found") updated_role = crud.role.update(db, db_obj=role, obj_in=role_in) - return schemas.Role.model_validate(updated_role) + return shared_schemas.Role.model_validate(updated_role) -@router.delete("/{role_id}", response_model=schemas.Role) +@router.delete("/{role_id}", response_model=shared_schemas.Role) def delete_role( role_id: int, db: Session = Depends(deps.get_db), @@ -57,4 +58,4 @@ def delete_role( if not role: raise HTTPException(status_code=404, detail="Role not found") deleted_role = crud.role.remove(db, id=role_id) - return schemas.Role.model_validate(deleted_role) + return shared_schemas.Role.model_validate(deleted_role) diff --git a/server/app/api/v1/endpoints/search.py b/server/app/api/v1/endpoints/search.py index ff2f06e..8684aba 100644 --- a/server/app/api/v1/endpoints/search.py +++ b/server/app/api/v1/endpoints/search.py @@ -1,17 +1,18 @@ # /server/app/api/v1/endpoints/search.py -from datetime import date + from typing import List, Optional from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session -from .... import crud, models, schemas +from public_api import shared_schemas +from .... import crud, models from ....api import deps router = APIRouter() -@router.get("/products", response_model=List[schemas.Product]) +@router.get("/products", response_model=List[shared_schemas.Product]) def search_products( q: Optional[str] = Query(None, description="Search query string"), category_id: Optional[int] = Query(None), @@ -30,14 +31,14 @@ def search_products( ) -@router.get("/orders", response_model=List[schemas.Order]) +@router.get("/orders", response_model=List[shared_schemas.Order]) def search_orders( q: Optional[str] = Query(None, description="Search query string"), status: Optional[str] = Query(None), min_total: Optional[float] = Query(None), max_total: Optional[float] = Query(None), - start_date: Optional[date] = Query(None), - end_date: Optional[date] = Query(None), + start_date: Optional[int] = Query(None), + end_date: Optional[int] = Query(None), sort_by: Optional[str] = Query(None), sort_order: Optional[str] = Query("asc"), db: Session = Depends(deps.get_db), diff --git a/server/app/api/v1/endpoints/shipments.py b/server/app/api/v1/endpoints/shipments.py index 35e104c..0ab3ee8 100644 --- a/server/app/api/v1/endpoints/shipments.py +++ b/server/app/api/v1/endpoints/shipments.py @@ -4,33 +4,34 @@ from fastapi import APIRouter, Depends, HTTPException, Path, Body, Query from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Shipment) +@router.post("/", response_model=shared_schemas.Shipment) def create_shipment( - shipment: schemas.ShipmentCreate, + shipment: shared_schemas.ShipmentCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.shipment.create(db=db, obj_in=shipment) -@router.get("/", response_model=List[schemas.Shipment]) +@router.get("/", response_model=List[shared_schemas.Shipment]) def read_shipments( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - filter_params: schemas.ShipmentFilter = Depends(), + filter_params: shared_schemas.ShipmentFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.shipment.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=filter_params) -@router.get("/carrier_rates", response_model=List[schemas.CarrierRate]) +@router.get("/carrier_rates", response_model=List[shared_schemas.CarrierRate]) def get_carrier_rates( weight: float = Query(...), dimensions: str = Query(...), @@ -41,7 +42,7 @@ def get_carrier_rates( return crud.shipment.get_carrier_rates(db, weight=weight, dimensions=dimensions, destination_zip=destination_zip) -@router.get("/{shipment_id}", response_model=schemas.Shipment) +@router.get("/{shipment_id}", response_model=shared_schemas.Shipment) def read_shipment( shipment_id: int = Path(..., title="The ID of the shipment to get"), db: Session = Depends(deps.get_db), @@ -53,10 +54,10 @@ def read_shipment( return shipment -@router.put("/{shipment_id}", response_model=schemas.Shipment) +@router.put("/{shipment_id}", response_model=shared_schemas.Shipment) def update_shipment( shipment_id: int = Path(..., title="The ID of the shipment to update"), - shipment_in: schemas.ShipmentUpdate = Body(..., title="Shipment update data"), + shipment_in: shared_schemas.ShipmentUpdate = Body(..., title="Shipment update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -66,7 +67,7 @@ def update_shipment( return crud.shipment.update(db, db_obj=shipment, obj_in=shipment_in) -@router.delete("/{shipment_id}", response_model=schemas.Shipment) +@router.delete("/{shipment_id}", response_model=shared_schemas.Shipment) def delete_shipment( shipment_id: int = Path(..., title="The ID of the shipment to delete"), db: Session = Depends(deps.get_db), @@ -78,7 +79,7 @@ def delete_shipment( return crud.shipment.remove(db, id=shipment_id) -@router.post("/{shipment_id}/generate_label", response_model=schemas.ShippingLabel) +@router.post("/{shipment_id}/generate_label", response_model=shared_schemas.ShippingLabel) def generate_shipping_label( shipment_id: int, db: Session = Depends(deps.get_db), @@ -87,7 +88,7 @@ def generate_shipping_label( return crud.shipment.generate_label(db, shipment_id=shipment_id) -@router.post("/{shipment_id}/track", response_model=schemas.ShipmentTracking) +@router.post("/{shipment_id}/track", response_model=shared_schemas.ShipmentTracking) def track_shipment( shipment_id: int, db: Session = Depends(deps.get_db), diff --git a/server/app/api/v1/endpoints/suppliers.py b/server/app/api/v1/endpoints/suppliers.py index aeb2f2c..0393b73 100644 --- a/server/app/api/v1/endpoints/suppliers.py +++ b/server/app/api/v1/endpoints/suppliers.py @@ -4,33 +4,34 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Supplier) +@router.post("/", response_model=shared_schemas.Supplier) def create_supplier( - supplier: schemas.SupplierCreate, + supplier: shared_schemas.SupplierCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.supplier.create(db=db, obj_in=supplier) -@router.get("/", response_model=List[schemas.Supplier]) +@router.get("/", response_model=List[shared_schemas.Supplier]) def read_suppliers( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - supplier_filter: schemas.SupplierFilter = Depends(), + supplier_filter: shared_schemas.SupplierFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.supplier.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=supplier_filter) -@router.get("/{supplier_id}", response_model=schemas.Supplier) +@router.get("/{supplier_id}", response_model=shared_schemas.Supplier) def read_supplier( supplier_id: int, db: Session = Depends(deps.get_db), @@ -42,10 +43,10 @@ def read_supplier( return supplier -@router.put("/{supplier_id}", response_model=schemas.Supplier) +@router.put("/{supplier_id}", response_model=shared_schemas.Supplier) def update_supplier( supplier_id: int, - supplier_in: schemas.SupplierUpdate, + supplier_in: shared_schemas.SupplierUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -55,7 +56,7 @@ def update_supplier( return crud.supplier.update(db, db_obj=supplier, obj_in=supplier_in) -@router.delete("/{supplier_id}", response_model=schemas.Supplier) +@router.delete("/{supplier_id}", response_model=shared_schemas.Supplier) def delete_supplier( supplier_id: int, db: Session = Depends(deps.get_db), @@ -69,7 +70,7 @@ def delete_supplier( -@router.get("/{supplier_id}/purchase_orders", response_model=List[schemas.PurchaseOrder]) +@router.get("/{supplier_id}/purchase_orders", response_model=List[shared_schemas.PurchaseOrder]) def read_supplier_purchase_orders( supplier_id: int, db: Session = Depends(deps.get_db), diff --git a/server/app/api/v1/endpoints/tasks.py b/server/app/api/v1/endpoints/tasks.py index c604be3..b0bfc3e 100644 --- a/server/app/api/v1/endpoints/tasks.py +++ b/server/app/api/v1/endpoints/tasks.py @@ -4,33 +4,34 @@ from fastapi import APIRouter, Depends, HTTPException, Path, Body from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Task) +@router.post("/", response_model=shared_schemas.Task) def create_task( - task: schemas.TaskCreate, + task: shared_schemas.TaskCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.task.create(db=db, obj_in=task) -@router.get("/", response_model=List[schemas.TaskWithAssignee]) +@router.get("/", response_model=List[shared_schemas.TaskWithAssignee]) def read_tasks( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - filter_params: schemas.TaskFilter = Depends(), + filter_params: shared_schemas.TaskFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.task.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=filter_params) -@router.get("/statistics", response_model=schemas.TaskStatistics) +@router.get("/statistics", response_model=shared_schemas.TaskStatistics) def get_task_statistics( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -38,7 +39,7 @@ def get_task_statistics( return crud.task.get_statistics(db) -@router.get("/user_summary", response_model=List[schemas.UserTaskSummary]) +@router.get("/user_summary", response_model=List[shared_schemas.UserTaskSummary]) def get_user_task_summary( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -46,7 +47,7 @@ def get_user_task_summary( return crud.task.get_user_summary(db) -@router.get("/overdue", response_model=List[schemas.TaskWithAssignee]) +@router.get("/overdue", response_model=List[shared_schemas.TaskWithAssignee]) def get_overdue_tasks( db: Session = Depends(deps.get_db), skip: int = 0, @@ -56,16 +57,16 @@ def get_overdue_tasks( return crud.task.get_overdue(db, skip=skip, limit=limit) -@router.post("/batch_create", response_model=List[schemas.Task]) +@router.post("/batch_create", response_model=List[shared_schemas.Task]) def create_batch_tasks( - tasks: List[schemas.TaskCreate], + tasks: List[shared_schemas.TaskCreate], db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.task.create_batch(db=db, obj_in_list=tasks) -@router.get("/my_tasks", response_model=List[schemas.Task]) +@router.get("/my_tasks", response_model=List[shared_schemas.Task]) def get_my_tasks( db: Session = Depends(deps.get_db), skip: int = 0, @@ -75,7 +76,7 @@ def get_my_tasks( return crud.task.get_user_tasks(db, user_id=current_user.user_id, skip=skip, limit=limit) -@router.get("/{task_id}", response_model=schemas.TaskWithAssignee) +@router.get("/{task_id}", response_model=shared_schemas.TaskWithAssignee) def read_task( task_id: int = Path(..., title="The ID of the task to get"), db: Session = Depends(deps.get_db), @@ -87,10 +88,10 @@ def read_task( return task -@router.put("/{task_id}", response_model=schemas.Task) +@router.put("/{task_id}", response_model=shared_schemas.Task) def update_task( task_id: int = Path(..., title="The ID of the task to update"), - task_in: schemas.TaskUpdate = Body(..., title="Task update data"), + task_in: shared_schemas.TaskUpdate = Body(..., title="Task update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -100,7 +101,7 @@ def update_task( return crud.task.update(db, db_obj=task, obj_in=task_in) -@router.delete("/{task_id}", response_model=schemas.Task) +@router.delete("/{task_id}", response_model=shared_schemas.Task) def delete_task( task_id: int = Path(..., title="The ID of the task to delete"), db: Session = Depends(deps.get_db), @@ -112,7 +113,7 @@ def delete_task( return crud.task.remove(db, id=task_id) -@router.post("/{task_id}/complete", response_model=schemas.Task) +@router.post("/{task_id}/complete", response_model=shared_schemas.Task) def complete_task( task_id: int = Path(..., title="The ID of the task to complete"), db: Session = Depends(deps.get_db), @@ -124,17 +125,17 @@ def complete_task( return crud.task.complete(db, db_obj=task) -@router.post("/{task_id}/comment", response_model=schemas.TaskComment) +@router.post("/{task_id}/comment", response_model=shared_schemas.TaskComment) def add_task_comment( task_id: int = Path(..., title="The ID of the task to comment on"), - comment: schemas.TaskCommentCreate = Body(..., title="Task comment data"), + comment: shared_schemas.TaskCommentCreate = Body(..., title="Task comment data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.task.add_comment(db, task_id=task_id, comment=comment, user_id=current_user.user_id) -@router.get("/{task_id}/comments", response_model=List[schemas.TaskComment]) +@router.get("/{task_id}/comments", response_model=List[shared_schemas.TaskComment]) def get_task_comments( task_id: int = Path(..., title="The ID of the task to get comments for"), db: Session = Depends(deps.get_db), diff --git a/server/app/api/v1/endpoints/users.py b/server/app/api/v1/endpoints/users.py index c3758a2..2977ea6 100644 --- a/server/app/api/v1/endpoints/users.py +++ b/server/app/api/v1/endpoints/users.py @@ -5,7 +5,8 @@ from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.orm import Session, joinedload -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps from ....core import security from ....core.email import send_reset_password_email @@ -13,7 +14,7 @@ router = APIRouter() -@router.post("/login", response_model=schemas.Token) +@router.post("/login", response_model=shared_schemas.Token) def login( db: Session = Depends(deps.get_db), form_data: OAuth2PasswordRequestForm = Depends() @@ -27,19 +28,19 @@ def login( return {"access_token": access_token, "token_type": "bearer"} -@router.post("/register", response_model=schemas.User) +@router.post("/register", response_model=shared_schemas.User) def register_user( - user: schemas.UserCreate, + user: shared_schemas.UserCreate, db: Session = Depends(deps.get_db) ): db_user = crud.user.get_by_email(db, email=user.email) if db_user: raise HTTPException(status_code=400, detail="Email already registered") new_user = crud.user.create(db=db, obj_in=user) - return schemas.User.model_validate(new_user) + return shared_schemas.User.model_validate(new_user) -@router.post("/reset_password", response_model=schemas.Message) +@router.post("/reset_password", response_model=shared_schemas.Message) def reset_password( email: str = Body(..., embed=True), db: Session = Depends(deps.get_db) @@ -58,7 +59,7 @@ def reset_password( return {"message": result} -@router.get("/me", response_model=schemas.UserSanitizedWithRole) +@router.get("/me", response_model=shared_schemas.UserSanitizedWithRole) def read_users_me( current_user: models.User = Depends(deps.get_current_active_user), db: Session = Depends(deps.get_db) @@ -67,20 +68,20 @@ def read_users_me( .options(joinedload(models.User.role)) .filter(models.User.id == current_user.id) .first()) - return schemas.UserSanitizedWithRole.model_validate(user) + return shared_schemas.UserSanitizedWithRole.model_validate(user) -@router.put("/me", response_model=schemas.User) +@router.put("/me", response_model=shared_schemas.User) def update_user_me( - user_in: schemas.UserUpdate, + user_in: shared_schemas.UserUpdate, current_user: models.User = Depends(deps.get_current_active_user), db: Session = Depends(deps.get_db) ): user = crud.user.update(db, db_obj=current_user, obj_in=user_in) - return schemas.User.model_validate(user) + return shared_schemas.User.model_validate(user) -@router.get("/", response_model=List[schemas.UserSanitizedWithRole]) +@router.get("/", response_model=List[shared_schemas.UserSanitizedWithRole]) def read_users( skip: int = 0, limit: int = 100, @@ -88,12 +89,12 @@ def read_users( current_user: models.User = Depends(deps.get_current_admin) ): users = crud.user.get_multi_with_role(db, skip=skip, limit=limit) - return [schemas.UserSanitizedWithRole.model_validate(user) for user in users] + return [shared_schemas.UserSanitizedWithRole.model_validate(user) for user in users] -@router.post("/", response_model=schemas.UserSanitizedWithRole) +@router.post("/", response_model=shared_schemas.UserSanitizedWithRole) def create_user( - user: schemas.UserCreate, + user: shared_schemas.UserCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin) ): @@ -101,10 +102,10 @@ def create_user( if db_user: raise HTTPException(status_code=400, detail="Email already registered") new_user = crud.user.create(db=db, obj_in=user) - return schemas.UserSanitizedWithRole.model_validate(new_user) + return shared_schemas.UserSanitizedWithRole.model_validate(new_user) -@router.get("/{user_id}", response_model=schemas.UserSanitizedWithRole) +@router.get("/{user_id}", response_model=shared_schemas.UserSanitizedWithRole) def read_user( user_id: int, db: Session = Depends(deps.get_db), @@ -114,16 +115,16 @@ def read_user( if not user: raise HTTPException(status_code=404, detail="User not found") if user.id == current_user.user_id: - return schemas.User.model_validate(user) + return shared_schemas.User.model_validate(user) if not crud.user.is_admin(current_user): raise HTTPException(status_code=400, detail="Not enough permissions") - return schemas.UserSanitizedWithRole.model_validate(user) + return shared_schemas.UserSanitizedWithRole.model_validate(user) -@router.put("/{user_id}", response_model=schemas.User) +@router.put("/{user_id}", response_model=shared_schemas.User) def update_user( user_id: int, - user_in: schemas.UserUpdate, + user_in: shared_schemas.UserUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin) ): @@ -131,10 +132,10 @@ def update_user( if not user: raise HTTPException(status_code=404, detail="User not found") updated_user = crud.user.update(db, db_obj=user, obj_in=user_in) - return schemas.User.model_validate(updated_user) + return shared_schemas.User.model_validate(updated_user) -@router.delete("/{user_id}", response_model=schemas.User) +@router.delete("/{user_id}", response_model=shared_schemas.User) def delete_user( user_id: int, db: Session = Depends(deps.get_db), @@ -144,7 +145,7 @@ def delete_user( if not user: raise HTTPException(status_code=404, detail="User not found") user = crud.user.remove(db, id=user_id) - return schemas.User.model_validate(user) + return shared_schemas.User.model_validate(user) diff --git a/server/app/api/v1/endpoints/warehouse.py b/server/app/api/v1/endpoints/warehouse.py index 8ab4116..3ff25e6 100644 --- a/server/app/api/v1/endpoints/warehouse.py +++ b/server/app/api/v1/endpoints/warehouse.py @@ -1,24 +1,24 @@ # /server/app/api/v1/endpoints/warehouse.py -from datetime import datetime from typing import List -from fastapi import APIRouter, Depends, HTTPException, Path, Body, Query +from fastapi import APIRouter, Depends, Path, Body from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.get("/layout", response_model=schemas.WarehouseLayout) +@router.get("/layout", response_model=shared_schemas.WarehouseLayout) def get_warehouse_layout( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.zone.get_warehouse_layout(db) -@router.get("/stats", response_model=schemas.WarehouseStats) +@router.get("/stats", response_model=shared_schemas.WarehouseStats) def get_warehouse_stats( db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) @@ -26,7 +26,7 @@ def get_warehouse_stats( return crud.whole_warehouse.get_stats(db) -@router.get("/inventory/{location_id}", response_model=List[schemas.LocationInventory]) +@router.get("/inventory/{location_id}", response_model=List[shared_schemas.LocationInventory]) def get_location_inventory( location_id: int = Path(..., title="The ID of the location to get inventory for"), db: Session = Depends(deps.get_db), @@ -35,11 +35,11 @@ def get_location_inventory( return crud.whole_warehouse.get_location_inventory(db, location_id=location_id) -@router.put("/inventory/{location_id}/{product_id}", response_model=schemas.LocationInventory) +@router.put("/inventory/{location_id}/{product_id}", response_model=shared_schemas.LocationInventory) def update_location_inventory( location_id: int = Path(..., title="The ID of the location"), product_id: int = Path(..., title="The ID of the product"), - inventory_update: schemas.LocationInventoryUpdate = Body(..., title="Inventory update data"), + inventory_update: shared_schemas.LocationInventoryUpdate = Body(..., title="Inventory update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -47,18 +47,18 @@ def update_location_inventory( quantity=inventory_update.quantity) -@router.post("/inventory/move", response_model=schemas.InventoryMovement) +@router.post("/inventory/move", response_model=shared_schemas.InventoryMovement) def move_inventory( - movement: schemas.InventoryMovement, + movement: shared_schemas.InventoryMovement, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.whole_warehouse.move_inventory(db, movement=movement) -@router.post("/inventory/adjust", response_model=schemas.InventoryAdjustment) +@router.post("/inventory/adjust", response_model=shared_schemas.InventoryAdjustment) def adjust_inventory( - adjustment: schemas.InventoryAdjustment, + adjustment: shared_schemas.InventoryAdjustment, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): diff --git a/server/app/api/v1/endpoints/yard.py b/server/app/api/v1/endpoints/yard.py index 72bdc78..690eb7b 100644 --- a/server/app/api/v1/endpoints/yard.py +++ b/server/app/api/v1/endpoints/yard.py @@ -1,38 +1,38 @@ # /server/app/api/v1/endpoints/yard.py -from datetime import datetime from typing import List from fastapi import APIRouter, Depends, HTTPException, Query, Path, Body from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() # Yard Location routes -@router.post("/locations", response_model=schemas.YardLocation) +@router.post("/locations", response_model=shared_schemas.YardLocation) def create_yard_location( - location: schemas.YardLocationCreate, + location: shared_schemas.YardLocationCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.yard_location.create(db=db, obj_in=location) -@router.get("/locations", response_model=List[schemas.YardLocation]) +@router.get("/locations", response_model=List[shared_schemas.YardLocation]) def read_yard_locations( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - filter_params: schemas.YardLocationFilter = Depends(), + filter_params: shared_schemas.YardLocationFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.yard_location.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=filter_params) -@router.get("/locations/{location_id}", response_model=schemas.YardLocationWithAppointments) +@router.get("/locations/{location_id}", response_model=shared_schemas.YardLocationWithAppointments) def read_yard_location( location_id: int = Path(..., title="The ID of the yard location to get"), db: Session = Depends(deps.get_db), @@ -44,10 +44,10 @@ def read_yard_location( return location -@router.put("/locations/{location_id}", response_model=schemas.YardLocation) +@router.put("/locations/{location_id}", response_model=shared_schemas.YardLocation) def update_yard_location( location_id: int = Path(..., title="The ID of the yard location to update"), - location_in: schemas.YardLocationUpdate = Body(..., title="Yard location update data"), + location_in: shared_schemas.YardLocationUpdate = Body(..., title="Yard location update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -57,7 +57,7 @@ def update_yard_location( return crud.yard_location.update(db, db_obj=location, obj_in=location_in) -@router.delete("/locations/{location_id}", response_model=schemas.YardLocation) +@router.delete("/locations/{location_id}", response_model=shared_schemas.YardLocation) def delete_yard_location( location_id: int = Path(..., title="The ID of the yard location to delete"), db: Session = Depends(deps.get_db), @@ -70,9 +70,9 @@ def delete_yard_location( # Dock Appointment routes -@router.post("/appointments", response_model=schemas.DockAppointment) +@router.post("/appointments", response_model=shared_schemas.DockAppointment) def create_dock_appointment( - appointment: schemas.DockAppointmentCreate, + appointment: shared_schemas.DockAppointmentCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -83,18 +83,18 @@ def create_dock_appointment( return crud.dock_appointment.create(db=db, obj_in=appointment) -@router.get("/appointments", response_model=List[schemas.DockAppointment]) +@router.get("/appointments", response_model=List[shared_schemas.DockAppointment]) def read_dock_appointments( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - filter_params: schemas.DockAppointmentFilter = Depends(), + filter_params: shared_schemas.DockAppointmentFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.dock_appointment.get_multi_with_filter(db, skip=skip, limit=limit, filter_params=filter_params) -@router.get("/appointments/{appointment_id}", response_model=schemas.DockAppointment) +@router.get("/appointments/{appointment_id}", response_model=shared_schemas.DockAppointment) def read_dock_appointment( appointment_id: int = Path(..., title="The ID of the dock appointment to get"), db: Session = Depends(deps.get_db), @@ -106,10 +106,10 @@ def read_dock_appointment( return appointment -@router.put("/appointments/{appointment_id}", response_model=schemas.DockAppointment) +@router.put("/appointments/{appointment_id}", response_model=shared_schemas.DockAppointment) def update_dock_appointment( appointment_id: int = Path(..., title="The ID of the dock appointment to update"), - appointment_in: schemas.DockAppointmentUpdate = Body(..., title="Dock appointment update data"), + appointment_in: shared_schemas.DockAppointmentUpdate = Body(..., title="Dock appointment update data"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -123,7 +123,7 @@ def update_dock_appointment( return crud.dock_appointment.update(db, db_obj=appointment, obj_in=appointment_in) -@router.delete("/appointments/{appointment_id}", response_model=schemas.DockAppointment) +@router.delete("/appointments/{appointment_id}", response_model=shared_schemas.DockAppointment) def delete_dock_appointment( appointment_id: int = Path(..., title="The ID of the dock appointment to delete"), db: Session = Depends(deps.get_db), @@ -135,16 +135,16 @@ def delete_dock_appointment( return crud.dock_appointment.remove(db, id=appointment_id) -@router.get("/utilization", response_model=schemas.YardUtilizationReport) +@router.get("/utilization", response_model=shared_schemas.YardUtilizationReport) def get_yard_utilization( - date: datetime = Query(None), + date: int = Query(None), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): - return crud.yard.get_utilization_report(db, date=date or datetime.now()) + return crud.yard.get_utilization_report(db, date=date) -@router.get("/carrier_performance", response_model=List[schemas.CarrierPerformance]) +@router.get("/carrier_performance", response_model=List[shared_schemas.CarrierPerformance]) def get_carrier_performance( start_date: datetime = Query(...), end_date: datetime = Query(...), @@ -154,9 +154,9 @@ def get_carrier_performance( return crud.yard.get_carrier_performance(db, start_date=start_date, end_date=end_date) -@router.post("/check_availability", response_model=List[schemas.AppointmentConflict]) +@router.post("/check_availability", response_model=List[shared_schemas.AppointmentConflict]) def check_appointment_availability( - appointment: schemas.DockAppointmentCreate, + appointment: shared_schemas.DockAppointmentCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): diff --git a/server/app/api/v1/endpoints/zones.py b/server/app/api/v1/endpoints/zones.py index 28383b4..55619cf 100644 --- a/server/app/api/v1/endpoints/zones.py +++ b/server/app/api/v1/endpoints/zones.py @@ -4,33 +4,34 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from .... import crud, models, schemas +from .... import crud, models +from public_api import shared_schemas from ....api import deps router = APIRouter() -@router.post("/", response_model=schemas.Zone) +@router.post("/", response_model=shared_schemas.Zone) def create_zone( - zone: schemas.ZoneCreate, + zone: shared_schemas.ZoneCreate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.zone.create(db=db, obj_in=zone) -@router.get("/", response_model=List[schemas.ZoneWithLocations]) +@router.get("/", response_model=List[shared_schemas.ZoneWithLocations]) def read_zones( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, - zone_filter: schemas.ZoneFilter = Depends(), + zone_filter: shared_schemas.ZoneFilter = Depends(), current_user: models.User = Depends(deps.get_current_active_user) ): return crud.zone.get_multi_with_locations(db, skip=skip, limit=limit, filter_params=zone_filter) -@router.get("/{zone_id}", response_model=schemas.ZoneWithLocations) +@router.get("/{zone_id}", response_model=shared_schemas.ZoneWithLocations) def read_zone( zone_id: int, db: Session = Depends(deps.get_db), @@ -42,10 +43,10 @@ def read_zone( return zone -@router.put("/{zone_id}", response_model=schemas.Zone) +@router.put("/{zone_id}", response_model=shared_schemas.Zone) def update_zone( zone_id: int, - zone_in: schemas.ZoneUpdate, + zone_in: shared_schemas.ZoneUpdate, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user) ): @@ -55,7 +56,7 @@ def update_zone( return crud.zone.update(db, db_obj=zone, obj_in=zone_in) -@router.delete("/{zone_id}", response_model=schemas.Zone) +@router.delete("/{zone_id}", response_model=shared_schemas.Zone) def delete_zone( zone_id: int, db: Session = Depends(deps.get_db), diff --git a/server/app/api/v1/router.py b/server/app/api/v1/router.py index 08a079b..9593f4e 100644 --- a/server/app/api/v1/router.py +++ b/server/app/api/v1/router.py @@ -1,9 +1,9 @@ # /server/app/api/v1/router.py from fastapi import APIRouter -from .endpoints import users, inventory, orders, warehouse, yard, assets, quality, tasks, audit, reports, search, \ - products, customers, purchase_orders, suppliers, po_items, locations, zones, product_categories, roles, permissions, \ - pick_lists, receipts, shipments, carriers +from .endpoints import (users, inventory, orders, warehouse, yard, assets, quality, tasks, audit, reports, search, \ + products, customers, purchase_orders, suppliers, po_items, locations, zones, product_categories, roles, + permissions, pick_lists, receipts, shipments, carriers) api_router = APIRouter() diff --git a/server/app/crud/asset.py b/server/app/crud/asset.py index 0031b30..2481a1f 100644 --- a/server/app/crud/asset.py +++ b/server/app/crud/asset.py @@ -5,7 +5,7 @@ from sqlalchemy.orm import Session, joinedload from server.app.models import Asset, AssetMaintenance, Location -from server.app.schemas import ( +from public_api.shared_schemas import ( Asset as AssetSchema, Location as LocationSchema, AssetWithMaintenance as AssetWithMaintenanceSchema, diff --git a/server/app/crud/asset_maintenance.py b/server/app/crud/asset_maintenance.py index b684de2..5b6371b 100644 --- a/server/app/crud/asset_maintenance.py +++ b/server/app/crud/asset_maintenance.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import Session from server.app.models import AssetMaintenance -from server.app.schemas import ( +from public_api.shared_schemas import ( AssetMaintenance as AssetMaintenanceSchema, AssetMaintenanceCreate, AssetMaintenanceUpdate, diff --git a/server/app/crud/audit.py b/server/app/crud/audit.py index fca8270..4172c83 100644 --- a/server/app/crud/audit.py +++ b/server/app/crud/audit.py @@ -6,7 +6,7 @@ from sqlalchemy.orm import Session from server.app.models import AuditLog, User -from server.app.schemas import AuditLog as AuditLogSchema, AuditLogCreate, AuditLogFilter, AuditSummary, \ +from public_api.shared_schemas import AuditLog as AuditLogSchema, AuditLogCreate, AuditLogFilter, AuditSummary, \ UserActivitySummary from .base import CRUDBase diff --git a/server/app/crud/base.py b/server/app/crud/base.py index 88a576e..3726e17 100644 --- a/server/app/crud/base.py +++ b/server/app/crud/base.py @@ -26,7 +26,7 @@ def get_multi(self, db: Session, *, skip: int = 0, limit: int = 100) -> List[Mod return db.query(self.model).offset(skip).limit(limit).all() def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType: - obj_in_data = jsonable_encoder(obj_in) + obj_in_data = obj_in.model_dump() db_obj = self.model(**obj_in_data) db.add(db_obj) db.commit() @@ -38,11 +38,13 @@ def update(self, *, db_obj: ModelType, obj_in: Union[UpdateSchemaType, Dict[str, Any]]) -> ModelType: + # WARNING: if date/datetime column in db_obj - encoding will change it to str, and will subsequently raise + # an error when trying to commit the changes. obj_data = jsonable_encoder(db_obj) if isinstance(obj_in, dict): update_data = obj_in else: - update_data = obj_in.dict(exclude_unset=True) + update_data = obj_in.model_dump(exclude_unset=True) for field in obj_data: if field in update_data: setattr(db_obj, field, update_data[field]) diff --git a/server/app/crud/customer.py b/server/app/crud/customer.py index b2b0a7e..02c1ed3 100644 --- a/server/app/crud/customer.py +++ b/server/app/crud/customer.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import Session from server.app.models import Customer -from server.app.schemas import ( +from public_api.shared_schemas import ( Customer as CustomerSchema, CustomerCreate, CustomerUpdate, CustomerFilter diff --git a/server/app/crud/dock_appointment.py b/server/app/crud/dock_appointment.py index 8c71946..ec3dbb4 100644 --- a/server/app/crud/dock_appointment.py +++ b/server/app/crud/dock_appointment.py @@ -5,7 +5,7 @@ from sqlalchemy.orm import Session from server.app.models import DockAppointment -from server.app.schemas import ( +from public_api.shared_schemas import ( DockAppointment as DockAppointmentSchema, DockAppointmentCreate, DockAppointmentUpdate, DockAppointmentFilter, diff --git a/server/app/crud/inventory.py b/server/app/crud/inventory.py index 794f536..1d9bcb3 100644 --- a/server/app/crud/inventory.py +++ b/server/app/crud/inventory.py @@ -1,29 +1,32 @@ # /server/app/crud/inventory.py from collections import defaultdict from datetime import datetime, timedelta -from typing import Optional, List +from typing import Optional, List, Dict +import numpy as np from fastapi import HTTPException +from scipy import stats from sqlalchemy import func, or_ from sqlalchemy.orm import Session, joinedload -from server.app.models import ( - Product, Inventory, Location, Zone, ProductCategory -) -from server.app.schemas import ( +from public_api.shared_schemas import ( Product as ProductSchema, ProductWithInventory as ProductWithInventorySchema, Inventory as InventorySchema, - InventoryCreate, InventoryUpdate, InventoryAdjustment, InventoryTransfer, - InventoryReport, LocationWithInventory as LocationWithInventorySchema, InventoryMovement, + InventoryCreate, InventoryUpdate, + InventoryAdjustment as InventoryAdjustmentSchema, + InventoryTransfer, + InventoryReport, LocationWithInventory as LocationWithInventorySchema, InventoryMovement as InventoryMovementSchema, StocktakeCreate, StocktakeResult, ABCAnalysisResult, InventoryLocationSuggestion, StocktakeDiscrepancy, ABCCategory, StorageUtilization, BulkImportData, BulkImportResult, InventoryFilter, InventoryWithDetails, InventorySummary ) +from server.app.models import ( + Product, Inventory, Location, Zone, ProductCategory, InventoryMovement, InventoryAdjustment +) from .base import CRUDBase - class CRUDInventory(CRUDBase[Inventory, InventoryCreate, InventoryUpdate]): def get_multi_with_products( @@ -70,15 +73,26 @@ def get_multi_with_filter(self, db: Session, *, skip: int = 0, limit: int = 100, query = query.filter(Inventory.quantity >= filter_params.quantity_min) return [InventorySchema.model_validate(x) for x in query.offset(skip).limit(limit).all()] - def adjust_quantity(self, db: Session, inventory_id: int, adjustment: InventoryAdjustment) -> InventorySchema: - current_inventory = self.get(db, id=inventory_id) + def adjust_quantity(self, db: Session, id: int, adjustment: InventoryAdjustmentSchema) -> InventorySchema: + current_inventory = self.get(db, id=id) if not current_inventory: raise HTTPException(status_code=404, detail="Inventory item not found") - current_inventory.quantity += adjustment.quantity + current_inventory.quantity += adjustment.quantity_change current_inventory.last_updated = datetime.utcnow() + + new_adjustment = InventoryAdjustment( + product_id=current_inventory.product_id, + location_id=current_inventory.location_id, + quantity_change=adjustment.quantity_change, + reason=adjustment.reason, + timestamp=adjustment.timestamp or datetime.utcnow() + ) + db.add(current_inventory) + db.add(new_adjustment) db.commit() db.refresh(current_inventory) + return InventorySchema.model_validate(current_inventory) def transfer(self, db: Session, transfer: InventoryTransfer) -> InventorySchema: @@ -164,7 +178,7 @@ def batch_update(self, db: Session, updates: list[InventoryUpdate]) -> list[Inve Inventory.location_id == update.location_id ).first() if current_inventory: - for key, value in update.dict(exclude_unset=True).items(): + for key, value in update.model_dump(exclude_unset=True).items(): setattr(current_inventory, key, value) current_inventory.last_updated = datetime.utcnow() db.add(current_inventory) @@ -173,13 +187,15 @@ def batch_update(self, db: Session, updates: list[InventoryUpdate]) -> list[Inve return [InventorySchema.model_validate(inventory) for inventory in updated_items] def get_movement_history(self, db: Session, product_id: int, start_date: Optional[datetime], - end_date: Optional[datetime]) -> list[InventoryMovement]: + end_date: Optional[datetime]) -> list[InventoryMovementSchema]: query = db.query(InventoryMovement).filter(InventoryMovement.product_id == product_id) if start_date: query = query.filter(InventoryMovement.timestamp >= start_date) if end_date: query = query.filter(InventoryMovement.timestamp <= end_date) - return query.order_by(InventoryMovement.timestamp.desc()).all() + + result = query.order_by(InventoryMovement.timestamp.desc()).all() + return [InventoryMovementSchema.model_validate(movement) for movement in result] def get_inventory_summary(self, db: Session) -> InventorySummary: categories = db.query(ProductCategory).all() @@ -439,5 +455,54 @@ def advanced_search( products = query.all() return [ProductSchema.model_validate(product) for product in products] + def get_inventory_forecast(self, db: Session, product_id: int) -> Dict: + # Get historical inventory data + history = db.query(InventoryMovement).filter(InventoryMovement.product_id == product_id).order_by( + InventoryMovement.timestamp).all() + + if not history: + return {"forecast": []} + + # Prepare data for forecasting + dates = [h.timestamp for h in history] + quantities = [h.quantity for h in history] + + # Simple linear regression for forecasting + # TODO: Implement more advanced methods + x = np.array([(d - dates[0]).days for d in dates]) + y = np.array(quantities) + slope, intercept, r_value, p_value, std_err = stats.linregress(x, y) + + # Generate forecast for next 30 days + forecast = [] + for i in range(30): + forecast_date = dates[-1] + timedelta(days=i + 1) + forecast_quantity = slope * (len(x) + i) + intercept + forecast.append({"date": forecast_date.isoformat(), "quantity": max(0, round(forecast_quantity))}) + + return {"forecast": forecast} + + def generate_inventory_forecast(self, db: Session, product_id: int) -> Dict: + # This could involve more complex forecasting methods + # For now, we'll use the same method as get_inventory_forecast + return self.get_inventory_forecast(db, product_id) + + def get_reorder_suggestions(self, db: Session) -> List[Dict]: + products = db.query(Product).all() + suggestions = [] + for product in products: + current_stock = sum(inv.quantity for inv in product.inventory_items) + # Simple reorder point calculation based on price + # TODO: Implement more advanced methods + reorder_point = product.price + if current_stock <= reorder_point: + suggestions.append({ + "sku": product.sku, + "name": product.name, + "current_stock": current_stock, + "suggested_reorder": reorder_point - current_stock + }) + return suggestions + inventory = CRUDInventory(Inventory) diff --git a/server/app/crud/location.py b/server/app/crud/location.py index 930fb87..6e79c85 100644 --- a/server/app/crud/location.py +++ b/server/app/crud/location.py @@ -6,7 +6,7 @@ from server.app.models import ( Location ) -from server.app.schemas import ( +from public_api.shared_schemas import ( LocationCreate, LocationUpdate, LocationWithInventory as LocationWithInventorySchema, LocationFilter ) diff --git a/server/app/crud/order.py b/server/app/crud/order.py index 915bd99..d217762 100644 --- a/server/app/crud/order.py +++ b/server/app/crud/order.py @@ -7,7 +7,7 @@ from sqlalchemy.orm import Session, joinedload from server.app.models import Order, OrderItem -from server.app.schemas import ( +from public_api.shared_schemas import ( Order as OrderSchema, OrderWithDetails as OrderWithDetailsSchema, OrderCreate, OrderUpdate, OrderItemCreate, OrderItemUpdate, @@ -19,7 +19,7 @@ class CRUDOrder(CRUDBase[Order, OrderCreate, OrderUpdate]): def create(self, db: Session, *, obj_in: OrderCreate) -> OrderSchema: - obj_in_data = jsonable_encoder(obj_in) + obj_in_data = obj_in.model_dump() items = obj_in_data.pop("items") db_obj = self.model(**obj_in_data) for item in items: @@ -122,7 +122,7 @@ def add_item(self, db: Session, *, order_id: int, item: OrderItemCreate) -> Orde if not order: raise ValueError("Order not found") - new_item = OrderItem(**item.dict(), order_id=order_id) + new_item = OrderItem(**item.model_dump(), order_id=order_id) order.order_items.append(new_item) order.total_amount += item.quantity * item.unit_price diff --git a/server/app/crud/permission.py b/server/app/crud/permission.py index f9522e2..1487cac 100644 --- a/server/app/crud/permission.py +++ b/server/app/crud/permission.py @@ -4,7 +4,7 @@ from sqlalchemy.orm import Session from server.app.models import Permission -from server.app.schemas import PermissionCreate, PermissionUpdate, \ +from public_api.shared_schemas import PermissionCreate, PermissionUpdate, \ Permission as PermissionSchema from .base import CRUDBase diff --git a/server/app/crud/pick_list.py b/server/app/crud/pick_list.py index a9eebb4..3d6d8c2 100644 --- a/server/app/crud/pick_list.py +++ b/server/app/crud/pick_list.py @@ -7,7 +7,7 @@ from server.app.models import ( PickList, PickListItem ) -from server.app.schemas import ( +from public_api.shared_schemas import ( PickList as PickListSchema, PickListCreate, PickListUpdate, PickListItem as PickListItemSchema, PickListItemCreate, PickListItemUpdate, PickListFilter, PickingPerformance, OptimizedPickingRoute @@ -24,14 +24,14 @@ def create_with_items(self, db: Session, *, obj_in: PickListCreate) -> PickListS db.add(db_obj) db.flush() for item in obj_in.items: - db_item = PickListItem(**item.dict(), pick_list_id=db_obj.pick_list_id) + db_item = PickListItem(**item.model_dump(), pick_list_id=db_obj.pick_list_id) db.add(db_item) db.commit() db.refresh(db_obj) return PickListSchema.model_validate(db_obj) def update_with_items(self, db: Session, *, db_obj: PickList, obj_in: PickListUpdate) -> PickListSchema: - update_data = obj_in.dict(exclude_unset=True) + update_data = obj_in.model_dump(exclude_unset=True) if "items" in update_data: items = update_data.pop("items") for item in db_obj.pick_list_items: diff --git a/server/app/crud/product.py b/server/app/crud/product.py index 9f437be..087f0d9 100644 --- a/server/app/crud/product.py +++ b/server/app/crud/product.py @@ -1,17 +1,18 @@ # /server/app/crud/product.py from typing import Optional +from sqlalchemy import func from sqlalchemy.orm import Session, joinedload -from server.app.models import ( - Product -) -from server.app.schemas import ( +from public_api.shared_schemas import ( Product as ProductSchema, ProductWithInventory as ProductWithInventorySchema, ProductCreate, ProductUpdate, ProductFilter, ProductWithCategoryAndInventory ) +from server.app.models import ( + Product +) from .base import CRUDBase @@ -49,5 +50,9 @@ def get_by_barcode(self, db: Session, barcode: str) -> Optional[ProductSchema]: current_product = db.query(Product).filter(Product.barcode == barcode).first() return ProductSchema.model_validate(current_product) if current_product else None + def get_max_id(self, db: Session) -> int: + max_id = db.query(func.max(Product.product_id)).scalar() + return max_id if max_id is not None else 0 + product = CRUDProduct(Product) diff --git a/server/app/crud/product_category.py b/server/app/crud/product_category.py index 7e753ce..662cf6c 100644 --- a/server/app/crud/product_category.py +++ b/server/app/crud/product_category.py @@ -3,7 +3,7 @@ from server.app.models import ( ProductCategory ) -from server.app.schemas import ( +from public_api.shared_schemas import ( ProductCategoryCreate, ProductCategoryUpdate ) from .base import CRUDBase diff --git a/server/app/crud/purchase_order.py b/server/app/crud/purchase_order.py index ab7ff87..5f87ce1 100644 --- a/server/app/crud/purchase_order.py +++ b/server/app/crud/purchase_order.py @@ -5,7 +5,7 @@ from sqlalchemy.orm import Session from server.app.models import PurchaseOrder, POItem, Supplier -from server.app.schemas import ( +from public_api.shared_schemas import ( PurchaseOrder as PurchaseOrderSchema, PurchaseOrderWithDetails as PurchaseOrderWithDetailsSchema, POItem as POItemSchema, @@ -17,7 +17,7 @@ class CRUDPurchaseOrder(CRUDBase[PurchaseOrder, PurchaseOrderCreate, PurchaseOrderUpdate]): def create(self, db: Session, *, obj_in: PurchaseOrderCreate) -> PurchaseOrderSchema: - obj_in_data = jsonable_encoder(obj_in) + obj_in_data = obj_in.model_dump() items = obj_in_data.pop("items") db_obj = self.model(**obj_in_data) for item in items: diff --git a/server/app/crud/quality.py b/server/app/crud/quality.py index 87b4067..9e8cadc 100644 --- a/server/app/crud/quality.py +++ b/server/app/crud/quality.py @@ -6,7 +6,7 @@ from sqlalchemy.orm import Session from server.app.models import QualityCheck, QualityStandard, QualityAlert, Product -from server.app.schemas import ( +from public_api.shared_schemas import ( QualityCheck as QualityCheckSchema, QualityCheckWithProduct as QualityCheckWithProductSchema, QualityCheckCreate, QualityCheckUpdate, QualityCheckFilter, @@ -88,7 +88,7 @@ def get_summary(self, db: Session, date_from: Optional[datetime], date_to: Optio } def create_batch(self, db: Session, *, obj_in_list: list[QualityCheckCreate]) -> list[QualityCheckSchema]: - db_objs = [QualityCheck(**obj_in.dict()) for obj_in in obj_in_list] + db_objs = [QualityCheck(**obj_in.model_dump()) for obj_in in obj_in_list] db.add_all(db_objs) db.commit() for obj in db_objs: diff --git a/server/app/crud/receipt.py b/server/app/crud/receipt.py index 79c9bce..d0a544e 100644 --- a/server/app/crud/receipt.py +++ b/server/app/crud/receipt.py @@ -6,7 +6,7 @@ from server.app.models import ( Receipt, ReceiptItem ) -from server.app.schemas import ( +from public_api.shared_schemas import ( Receipt as ReceiptSchema, ReceiptCreate, ReceiptUpdate, ReceiptItem as ReceiptItemSchema, ReceiptItemCreate, ReceiptItemUpdate, ReceiptFilter, QualityCheckCreate @@ -23,14 +23,14 @@ def create_with_items(self, db: Session, *, obj_in: ReceiptCreate) -> ReceiptSch db.add(db_obj) db.flush() for item in obj_in.items: - db_item = ReceiptItem(**item.dict(), receipt_id=db_obj.receipt_id) + db_item = ReceiptItem(**item.model_dump(), receipt_id=db_obj.receipt_id) db.add(db_item) db.commit() db.refresh(db_obj) return ReceiptSchema.model_validate(db_obj) def update_with_items(self, db: Session, *, db_obj: Receipt, obj_in: ReceiptUpdate) -> ReceiptSchema: - update_data = obj_in.dict(exclude_unset=True) + update_data = obj_in.model_dump(exclude_unset=True) if "items" in update_data: items = update_data.pop("items") for item in db_obj.receipt_items: diff --git a/server/app/crud/reports.py b/server/app/crud/reports.py index b8bdda2..3418897 100644 --- a/server/app/crud/reports.py +++ b/server/app/crud/reports.py @@ -5,7 +5,7 @@ from sqlalchemy.orm import Session from server.app.models import Product, Inventory, Order, OrderItem, Task -from server.app.schemas import InventorySummaryReport, InventoryItem, OrderSummaryReport, OrderSummary, \ +from public_api.shared_schemas import InventorySummaryReport, InventoryItem, OrderSummaryReport, OrderSummary, \ WarehousePerformanceReport, WarehousePerformanceMetric, KPIDashboard, KPIMetric diff --git a/server/app/crud/role.py b/server/app/crud/role.py index db6d116..bc7785f 100644 --- a/server/app/crud/role.py +++ b/server/app/crud/role.py @@ -3,8 +3,8 @@ from sqlalchemy.orm import Session +from public_api.shared_schemas import RoleCreate, RoleUpdate, Role as RoleSchema from server.app.models import Role, RolePermission -from server.app.schemas import RoleCreate, RoleUpdate, Role as RoleSchema from .base import CRUDBase @@ -25,7 +25,7 @@ def update(self, db: Session, *, db_obj: Role, obj_in: Union[RoleUpdate, Dict[st if isinstance(obj_in, dict): update_data = obj_in else: - update_data = obj_in.dict(exclude_unset=True) + update_data = obj_in.model_dump(exclude_unset=True) if "permissions" in update_data: db.query(RolePermission).filter(RolePermission.role_id == db_obj.role_id).delete() diff --git a/server/app/crud/shipment.py b/server/app/crud/shipment.py index 49a14a9..cab9dc4 100644 --- a/server/app/crud/shipment.py +++ b/server/app/crud/shipment.py @@ -5,9 +5,9 @@ from sqlalchemy.orm import Session from server.app.models import Order, Shipment, Carrier -from server.app.schemas import CarrierCreate, CarrierUpdate -from server.app.schemas import (Shipment as ShipmentSchema, ShipmentCreate, ShipmentUpdate, - ShipmentFilter, ShipmentTracking, CarrierRate, ShippingLabel) +from public_api.shared_schemas import CarrierCreate, CarrierUpdate +from public_api.shared_schemas import (Shipment as ShipmentSchema, ShipmentCreate, ShipmentUpdate, + ShipmentFilter, ShipmentTracking, CarrierRate, ShippingLabel) from .base import CRUDBase from ..utils.generate_label import shipengine_api_call diff --git a/server/app/crud/supplier.py b/server/app/crud/supplier.py index b20a426..71e9d5d 100644 --- a/server/app/crud/supplier.py +++ b/server/app/crud/supplier.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import Session from server.app.models import Supplier -from server.app.schemas import ( +from public_api.shared_schemas import ( Supplier as SupplierSchema, SupplierCreate, SupplierUpdate, SupplierFilter diff --git a/server/app/crud/task.py b/server/app/crud/task.py index f39be6f..1df3958 100644 --- a/server/app/crud/task.py +++ b/server/app/crud/task.py @@ -5,9 +5,10 @@ from sqlalchemy import func, case from sqlalchemy.orm import Session -from server.app.models import Task, User, TaskComment -from server.app.schemas import TaskCreate, TaskUpdate, TaskFilter, TaskCommentCreate, TaskStatistics, UserTaskSummary, \ +from public_api.shared_schemas import TaskCreate, TaskUpdate, TaskFilter, TaskCommentCreate, TaskStatistics, \ + UserTaskSummary, \ Task as TaskSchema, TaskWithAssignee, TaskComment as TaskCommentSchema +from server.app.models import Task, User, TaskComment from .base import CRUDBase @@ -101,7 +102,7 @@ def get_overdue(self, db: Session, *, skip: int = 0, limit: int = 100) -> list[T return [TaskSchema.model_validate(task) for task in overdue_tasks] def create_batch(self, db: Session, *, obj_in_list: list[TaskCreate]) -> list[TaskSchema]: - db_objs = [Task(**obj_in.dict()) for obj_in in obj_in_list] + db_objs = [Task(**obj_in.model_dump()) for obj_in in obj_in_list] db.add_all(db_objs) db.commit() for obj in db_objs: diff --git a/server/app/crud/user.py b/server/app/crud/user.py index ff22efb..e024bac 100644 --- a/server/app/crud/user.py +++ b/server/app/crud/user.py @@ -4,9 +4,9 @@ from sqlalchemy.orm import Session, joinedload +from public_api.shared_schemas import UserCreate, UserUpdate from server.app.core.security import get_password_hash, verify_password from server.app.models import User -from server.app.schemas import UserCreate, UserUpdate from .base import CRUDBase @@ -40,7 +40,7 @@ def update(self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[st if isinstance(obj_in, dict): update_data = obj_in else: - update_data = obj_in.dict(exclude_unset=True) + update_data = obj_in.model_dump(exclude_unset=True) if update_data.get("password"): hashed_password = get_password_hash(update_data["password"]) del update_data["password"] diff --git a/server/app/crud/warehouse.py b/server/app/crud/warehouse.py index 1fd8b9e..c93f395 100644 --- a/server/app/crud/warehouse.py +++ b/server/app/crud/warehouse.py @@ -7,7 +7,7 @@ from server.app.models import ( PickList, Receipt, Shipment, LocationInventory, InventoryMovement, InventoryAdjustment ) -from server.app.schemas import ( +from public_api.shared_schemas import ( InventoryMovement as InventoryMovementSchema, InventoryAdjustment as InventoryAdjustmentSchema, InventoryAdjustmentCreate, @@ -79,7 +79,7 @@ def move_inventory(self, db: Session, *, movement: InventoryMovementCreate) -> I ) db.add(to_location_inventory) - db_movement = InventoryMovement(**movement.dict()) + db_movement = InventoryMovement(**movement.model_dump()) db.add(db_movement) db.commit() db.refresh(db_movement) @@ -101,7 +101,7 @@ def adjust_inventory(self, db: Session, *, adjustment: InventoryAdjustmentCreate ) db.add(location_inventory) - db_adjustment = InventoryAdjustment(**adjustment.dict()) + db_adjustment = InventoryAdjustment(**adjustment.model_dump()) db.add(db_adjustment) db.commit() db.refresh(db_adjustment) diff --git a/server/app/crud/yard.py b/server/app/crud/yard.py index 195d765..1e17a95 100644 --- a/server/app/crud/yard.py +++ b/server/app/crud/yard.py @@ -6,7 +6,7 @@ from sqlalchemy.orm import Session from server.app.models import YardLocation, DockAppointment, Carrier -from server.app.schemas import ( +from public_api.shared_schemas import ( YardStats, YardUtilizationReport, CarrierPerformance, YardLocationCapacity ) diff --git a/server/app/crud/yard_location.py b/server/app/crud/yard_location.py index 1e7f0b2..e76ccaa 100644 --- a/server/app/crud/yard_location.py +++ b/server/app/crud/yard_location.py @@ -4,7 +4,7 @@ from sqlalchemy.orm import Session, selectinload from server.app.models import YardLocation -from server.app.schemas import ( +from public_api.shared_schemas import ( YardLocation as YardLocationSchema, YardLocationCreate, YardLocationUpdate, YardLocationFilter, YardLocationWithAppointments diff --git a/server/app/crud/zone.py b/server/app/crud/zone.py index df2426e..1af87dd 100644 --- a/server/app/crud/zone.py +++ b/server/app/crud/zone.py @@ -6,7 +6,7 @@ from server.app.models import ( Zone ) -from server.app.schemas import ( +from public_api.shared_schemas import ( ZoneCreate, ZoneUpdate, LocationFilter, ZoneWithLocations ) from .base import CRUDBase diff --git a/server/app/models/asset.py b/server/app/models/asset.py index 065a84d..f25ff92 100644 --- a/server/app/models/asset.py +++ b/server/app/models/asset.py @@ -1,5 +1,5 @@ # /server/app/models/asset.py -from sqlalchemy import Column, Integer, String, Date, ForeignKey, Text +from sqlalchemy import Column, Integer, String, ForeignKey, Text from sqlalchemy.orm import relationship from .base import Base @@ -12,7 +12,7 @@ class Asset(Base): asset_type = Column(String(50)) asset_name = Column(String(100)) serial_number = Column(String(50)) - purchase_date = Column(Date) + purchase_date = Column(Integer) status = Column(String(20)) location_id = Column(Integer, ForeignKey("locations.location_id")) @@ -26,10 +26,10 @@ class AssetMaintenance(Base): maintenance_id = Column(Integer, primary_key=True, index=True) asset_id = Column(Integer, ForeignKey("assets.asset_id")) maintenance_type = Column(String(50)) - scheduled_date = Column(Date) - completed_date = Column(Date) + scheduled_date = Column(Integer) + completed_date = Column(Integer) performed_by = Column(Integer, ForeignKey("users.user_id")) notes = Column(Text) asset = relationship("Asset", back_populates="maintenance_records") - user = relationship("User") \ No newline at end of file + user = relationship("User") diff --git a/server/app/models/audit_log.py b/server/app/models/audit_log.py index 90dcd25..de032cd 100644 --- a/server/app/models/audit_log.py +++ b/server/app/models/audit_log.py @@ -1,5 +1,7 @@ # /server/app/models/audit_log.py -from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Text, func +import time + +from sqlalchemy import Column, Integer, String, ForeignKey, Text from sqlalchemy.orm import relationship from .base import Base @@ -15,6 +17,6 @@ class AuditLog(Base): record_id = Column(Integer) old_value = Column(Text) new_value = Column(Text) - timestamp = Column(DateTime, server_default=func.now()) + timestamp = Column(Integer, default=lambda: int(time.time())) user = relationship("User", back_populates="audit_logs") diff --git a/server/app/models/dock_appointment.py b/server/app/models/dock_appointment.py index 51293e9..83dca66 100644 --- a/server/app/models/dock_appointment.py +++ b/server/app/models/dock_appointment.py @@ -1,5 +1,5 @@ # /server/app/models/dock_appointment.py -from sqlalchemy import Column, Integer, String, DateTime, ForeignKey +from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from .base import Base @@ -10,12 +10,12 @@ class DockAppointment(Base): appointment_id = Column(Integer, primary_key=True, index=True) yard_location_id = Column(Integer, ForeignKey("yard_locations.yard_location_id")) - appointment_time = Column(DateTime) + appointment_time = Column(Integer) carrier_id = Column(Integer, ForeignKey("carriers.carrier_id")) type = Column(String(20)) status = Column(String(20)) - actual_arrival_time = Column(DateTime) - actual_departure_time = Column(DateTime) + actual_arrival_time = Column(Integer) + actual_departure_time = Column(Integer) yard_location = relationship("YardLocation", back_populates="appointments") carrier = relationship("Carrier") diff --git a/server/app/models/inventory.py b/server/app/models/inventory.py index b3361af..fb756e0 100644 --- a/server/app/models/inventory.py +++ b/server/app/models/inventory.py @@ -1,6 +1,8 @@ # /server/app/models/inventory.py +import time + from sqlalchemy import (Column, Integer, String, - ForeignKey, DateTime, func) + ForeignKey) from sqlalchemy.orm import relationship from .base import Base @@ -9,12 +11,12 @@ class Inventory(Base): __tablename__ = "inventory" - inventory_id = Column(Integer, primary_key=True, index=True) + id = Column(Integer, primary_key=True, index=True) product_id = Column(Integer, ForeignKey("products.product_id")) location_id = Column(Integer, ForeignKey("locations.location_id")) - expiration_date = Column(DateTime, server_default=func.now()) + expiration_date = Column(Integer, default=lambda: int(time.time())) quantity = Column(Integer, nullable=False) - last_updated = Column(DateTime, server_default=func.now()) + last_updated = Column(Integer, default=lambda: int(time.time()), onupdate=lambda: int(time.time())) product = relationship("Product", back_populates="inventory_items") location = relationship("Location", back_populates="inventory_items") @@ -40,7 +42,7 @@ class InventoryMovement(Base): to_location_id = Column(Integer, ForeignKey("locations.location_id")) quantity = Column(Integer, nullable=False) reason = Column(String(255)) - timestamp = Column(DateTime, server_default=func.now()) + timestamp = Column(Integer, default=lambda: int(time.time())) product = relationship("Product") from_location = relationship("Location", foreign_keys=[from_location_id]) @@ -50,12 +52,12 @@ class InventoryMovement(Base): class InventoryAdjustment(Base): __tablename__ = "inventory_adjustments" - adjustment_id = Column(Integer, primary_key=True, index=True) + id = Column(Integer, primary_key=True, index=True) product_id = Column(Integer, ForeignKey("products.product_id")) location_id = Column(Integer, ForeignKey("locations.location_id")) quantity_change = Column(Integer, nullable=False) reason = Column(String(255)) - timestamp = Column(DateTime, server_default=func.now()) + timestamp = Column(Integer, default=lambda: int(time.time())) product = relationship("Product") location = relationship("Location") diff --git a/server/app/models/order.py b/server/app/models/order.py index 7b222af..f78dff8 100644 --- a/server/app/models/order.py +++ b/server/app/models/order.py @@ -1,7 +1,8 @@ # /server/app/models/order.py from sqlalchemy import (Column, Integer, String, ForeignKey, - Numeric, DateTime, func) + Numeric) from sqlalchemy.orm import relationship +import time from .base import Base @@ -11,8 +12,8 @@ class Order(Base): order_id = Column(Integer, primary_key=True, index=True) customer_id = Column(Integer, ForeignKey("customers.customer_id")) - order_date = Column(DateTime, server_default=func.now()) - ship_date = Column(DateTime) + order_date = Column(Integer, default=lambda: int(time.time())) + ship_date = Column(Integer) status = Column(String(20)) total_amount = Column(Numeric(10, 2)) shipping_name = Column(String(100)) @@ -45,9 +46,9 @@ class PurchaseOrder(Base): po_id = Column(Integer, primary_key=True, index=True) supplier_id = Column(Integer, ForeignKey("suppliers.supplier_id")) - order_date = Column(DateTime, server_default=func.now()) + order_date = Column(Integer, default=lambda: int(time.time())) status = Column(String(20)) - expected_delivery_date = Column(DateTime) + expected_delivery_date = Column(Integer) supplier = relationship("Supplier", back_populates="purchase_orders") po_items = relationship("POItem", back_populates="purchase_order") diff --git a/server/app/models/pick_list.py b/server/app/models/pick_list.py index ec123dd..f8fefe4 100644 --- a/server/app/models/pick_list.py +++ b/server/app/models/pick_list.py @@ -1,5 +1,7 @@ # /server/app/models/pick_list.py -from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, func +import time + +from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from .base import Base @@ -11,8 +13,8 @@ class PickList(Base): pick_list_id = Column(Integer, primary_key=True, index=True) order_id = Column(Integer, ForeignKey("orders.order_id")) status = Column(String(20)) - created_at = Column(DateTime, server_default=func.now()) - completed_at = Column(DateTime) + created_at = Column(Integer, default=lambda: int(time.time())) + completed_at = Column(Integer) order = relationship("Order") pick_list_items = relationship("PickListItem", back_populates="pick_list") diff --git a/server/app/models/quality.py b/server/app/models/quality.py index 5925cfd..00dbc0f 100644 --- a/server/app/models/quality.py +++ b/server/app/models/quality.py @@ -1,6 +1,7 @@ # /server/app/models/quality.py -from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Text, func +from sqlalchemy import Column, Integer, String, ForeignKey, Text from sqlalchemy.orm import relationship +import time from .base import Base @@ -10,7 +11,7 @@ class QualityCheck(Base): check_id = Column(Integer, primary_key=True, index=True) product_id = Column(Integer, ForeignKey("products.product_id")) - check_date = Column(DateTime, server_default=func.now()) + check_date = Column(Integer, default=lambda: int(time.time())) performed_by = Column(Integer, ForeignKey("users.user_id")) result = Column(String(20)) notes = Column(Text) @@ -37,7 +38,7 @@ class QualityAlert(Base): product_id = Column(Integer, ForeignKey("products.product_id")) alert_type = Column(String(50), nullable=False) description = Column(Text, nullable=False) - created_at = Column(DateTime, server_default=func.now()) - resolved_at = Column(DateTime, nullable=True) + created_at = Column(Integer, default=lambda: int(time.time())) + resolved_at = Column(Integer, nullable=True) product = relationship("Product") diff --git a/server/app/models/receipt.py b/server/app/models/receipt.py index 999f5b8..01412a2 100644 --- a/server/app/models/receipt.py +++ b/server/app/models/receipt.py @@ -1,5 +1,7 @@ # /server/app/models/receipt.py -from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, func +import time + +from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from .base import Base @@ -10,7 +12,7 @@ class Receipt(Base): receipt_id = Column(Integer, primary_key=True, index=True) po_id = Column(Integer, ForeignKey("purchase_orders.po_id")) - received_date = Column(DateTime, server_default=func.now()) + received_date = Column(Integer, default=lambda: int(time.time())) status = Column(String(20)) purchase_order = relationship("PurchaseOrder") diff --git a/server/app/models/shipment.py b/server/app/models/shipment.py index 96b555c..864fdaf 100644 --- a/server/app/models/shipment.py +++ b/server/app/models/shipment.py @@ -1,5 +1,5 @@ # /server/app/models/shipment.py -from sqlalchemy import Column, Integer, String, ForeignKey, DateTime +from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from .base import Base @@ -12,7 +12,7 @@ class Shipment(Base): order_id = Column(Integer, ForeignKey("orders.order_id")) carrier_id = Column(Integer, ForeignKey("carriers.carrier_id")) tracking_number = Column(String(50)) - ship_date = Column(DateTime) + ship_date = Column(Integer) status = Column(String(20)) label_id = Column(String(100)) label_download_url = Column(String(255)) diff --git a/server/app/models/task.py b/server/app/models/task.py index 7c9bc38..305a020 100644 --- a/server/app/models/task.py +++ b/server/app/models/task.py @@ -1,5 +1,7 @@ # /server/app/models/task.py -from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Text, func +import time + +from sqlalchemy import Column, Integer, String, ForeignKey, Text from sqlalchemy.orm import relationship from .base import Base @@ -12,10 +14,10 @@ class Task(Base): task_type = Column(String(50)) description = Column(Text) assigned_to = Column(Integer, ForeignKey("users.user_id")) - due_date = Column(DateTime) + due_date = Column(Integer) priority = Column(String(20)) status = Column(String(20)) - created_at = Column(DateTime, server_default=func.now()) + created_at = Column(Integer, default=lambda: int(time.time())) assigned_user = relationship("User") comments = relationship("TaskComment", back_populates="task") @@ -28,7 +30,7 @@ class TaskComment(Base): task_id = Column(Integer, ForeignKey("tasks.task_id")) user_id = Column(Integer, ForeignKey("users.user_id")) comment = Column(Text) - created_at = Column(DateTime, server_default=func.now()) + created_at = Column(Integer, default=lambda: int(time.time())) task = relationship("Task", back_populates="comments") user = relationship("User", back_populates="task_comments") diff --git a/server/app/models/user.py b/server/app/models/user.py index bff6fdf..b283046 100644 --- a/server/app/models/user.py +++ b/server/app/models/user.py @@ -1,5 +1,7 @@ # /server/app/models/user.py -from sqlalchemy import Boolean, Column, Integer, String, ForeignKey, DateTime, func +import time + +from sqlalchemy import Boolean, Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from .base import Base @@ -14,10 +16,10 @@ class User(Base): password_hash = Column(String(255), nullable=False) is_active = Column(Boolean, default=True) role_id = Column(Integer, ForeignKey("roles.role_id")) - created_at = Column(DateTime, server_default=func.now()) - last_login = Column(DateTime) + created_at = Column(Integer, default=lambda: int(time.time())) + last_login = Column(Integer) password_reset_token = Column(String(255)) - password_reset_expiration = Column(DateTime) + password_reset_expiration = Column(Integer) role = relationship("Role", back_populates="users") assigned_tasks = relationship("Task", back_populates="assigned_user") diff --git a/server/nexusware.db b/server/nexusware.db index af71577b148b7cd1d3a38b74b2a820debe8e2636..ad0a7b963217dbb40c82bf702e3f1d2872bf4081 100644 GIT binary patch delta 1842 zcmb_cZ%i9y7{B*@uiScVxxxkrYw0nE&)S;adMWj2|0 z<~L?_XBT%Y|e9=jJnAH<26(XRxVJBK&2Mp21C&}YGvM5sF%T`A62j! z=8d%!sa#8eII1=;D-?&Er1`#i8ON@e2O6s>0vJI>2yXE_&+(pC&fn_xu$(){2|-bC z`2}9|dm5{XuLuz9UuJ}o$ZvFVUn zJ9-QVN~aw^2};I}ny|TIM@O*v!j5@@vTny&yb4>7Azmd?k7HitY(3_Am3Iry@3FJa z+v?_6w?F9d1cd-INmu}0ZGu6xDi#N7#Av~RI_mLR>a|FNfrNBRL>fsA#pFaP3FeR8 zuKhv1>OgU;u3t4Xsp;2>HF~QzXt*G!{au{CjQdM5f#roD z=Lxz!?N*%#LVFOaAK(=R(Srvu&;cmI2tolEGGma~%Hh@rSpBU#FD4dtCvf924)R_X zFS} zHzKp$&qX5Pf#{&-JP{4`KTA7L^;0!ZI>59ck&H{@iSfZi^0btcQ<;fD<@!BX1zI&F z>a=@vUOzLgp@h*DnK|1>)i@mBe6OxZDOkEW@poZL{)g2j|38cKu-fDcZI;H5h-2wrU&;t{#hHe~;>4#$R zBs26>GMP#o>FPG(dcpu@bNQaoG|3P~lun!EiV(6G!?d*CkIfhT!bRo>w*ejZ9rkW| zet9lPPa3w!H0cyq#bGR=33!!SMf`U#9=KWUWH9ab7H)Ff%n2Lht ztYWDfDhk)V$BugIq?=Ahdk`sgN^h`E@tF@CuM$LBowppcc~G-wL^XTvWzDV|(`;VV zNxwO%60$pbL&z}46%^r9A#~k8nGr;LK z+F^Z30HlFrkk6#xx+bpS6+8&P!mKan{U{e?pZkpT61)fLVowWCucKQM9%?yD_}n&{ X5lXu3;;To+W;B}HK|HsE3vK@a)Yq&T