init commit
This commit is contained in:
BIN
transportmanager/server/.DS_Store
vendored
Normal file
BIN
transportmanager/server/.DS_Store
vendored
Normal file
Binary file not shown.
0
transportmanager/server/admin/__init__.py
Normal file
0
transportmanager/server/admin/__init__.py
Normal file
0
transportmanager/server/admin/billing.py
Normal file
0
transportmanager/server/admin/billing.py
Normal file
53
transportmanager/server/admin/subscription.py
Normal file
53
transportmanager/server/admin/subscription.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
from flask_jwt_extended import jwt_required
|
||||
from models.subscription import Subscription
|
||||
|
||||
admin_subscription_bp = Blueprint("admin_subscription", __name__, url_prefix="/admin/subscriptions")
|
||||
|
||||
# List all subscriptions
|
||||
@admin_subscription_bp.route("", methods=["GET"])
|
||||
@jwt_required()
|
||||
def list_subscriptions():
|
||||
subscription_model = Subscription()
|
||||
|
||||
# Optional: if you want, you can add pagination here later!
|
||||
subscriptions = subscription_model.get_all()
|
||||
return jsonify(subscriptions), 200
|
||||
|
||||
# Update subscription status
|
||||
@admin_subscription_bp.route("/update_status", methods=["POST"])
|
||||
@jwt_required()
|
||||
def update_subscription_status():
|
||||
data = request.json
|
||||
subscription_id = data.get("subscription_id")
|
||||
new_status = data.get("status")
|
||||
|
||||
if not subscription_id or not new_status:
|
||||
return jsonify({"error": "Missing subscription_id or status"}), 400
|
||||
|
||||
subscription_model = Subscription()
|
||||
updated_rows = subscription_model.update_status(subscription_id, new_status)
|
||||
|
||||
if updated_rows == 0:
|
||||
return jsonify({"error": "Subscription not found"}), 404
|
||||
|
||||
return jsonify({"message": "Subscription status updated."}), 200
|
||||
|
||||
# Cancel subscription
|
||||
@admin_subscription_bp.route("/cancel", methods=["POST"])
|
||||
@jwt_required()
|
||||
def cancel_subscription():
|
||||
data = request.json
|
||||
subscription_id = data.get("subscription_id")
|
||||
|
||||
if not subscription_id:
|
||||
return jsonify({"error": "Missing subscription_id"}), 400
|
||||
|
||||
subscription_model = Subscription()
|
||||
updated_rows = subscription_model.update_status(subscription_id, "cancelled")
|
||||
|
||||
if updated_rows == 0:
|
||||
return jsonify({"error": "Subscription not found"}), 404
|
||||
|
||||
return jsonify({"message": "Subscription cancelled."}), 200
|
||||
|
||||
40
transportmanager/server/admin/tenants.py
Normal file
40
transportmanager/server/admin/tenants.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
from flask_jwt_extended import jwt_required
|
||||
from models.user import Users
|
||||
|
||||
admin_user_bp = Blueprint("admin_user", __name__, url_prefix="/admin/users")
|
||||
|
||||
# Get all users with role "user"
|
||||
@admin_user_bp.route("", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_all_users():
|
||||
users_model = Users()
|
||||
users = users_model.get_all_users_with_role("user")
|
||||
return jsonify(users), 200
|
||||
|
||||
# Get a single user by ID
|
||||
@admin_user_bp.route("/<int:user_id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_user(user_id):
|
||||
users_model = Users()
|
||||
user = users_model.get_user_by_id(user_id)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
return jsonify(user), 200
|
||||
|
||||
# Update a user
|
||||
@admin_user_bp.route("/update", methods=["POST"])
|
||||
@jwt_required()
|
||||
def update_user():
|
||||
if not request.is_json:
|
||||
print("Content-Type received:", request.content_type)
|
||||
return jsonify({"error": "Invalid content type, must be application/json"}), 415
|
||||
|
||||
data = request.get_json()
|
||||
if not data.get("user_id"):
|
||||
return jsonify({"error": "Missing user_id"}), 400
|
||||
|
||||
users_model = Users()
|
||||
users_model.update_user(data)
|
||||
|
||||
return jsonify({"message": "User updated successfully."}), 200
|
||||
93
transportmanager/server/app.py
Normal file
93
transportmanager/server/app.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import os
|
||||
from flask import Flask, jsonify
|
||||
from routes.auth import auth_bp
|
||||
from flask_jwt_extended import JWTManager
|
||||
from routes.profile import profile_bp
|
||||
from routes.clients import clients_bp
|
||||
from routes.transporters import transporters_bp
|
||||
from routes.destinations import destinations_bp
|
||||
from routes.orders_out import orders_bp
|
||||
from routes.ouders_in import orders_in_bp
|
||||
from routes.report import report_bp
|
||||
from admin.subscription import admin_subscription_bp
|
||||
from routes.subscription import subscription_bp
|
||||
from admin.tenants import admin_user_bp
|
||||
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from models.subscription import Subscription
|
||||
|
||||
from flask_cors import CORS
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(
|
||||
app,
|
||||
resources={r"/*": {"origins": [os.getenv("WEB_ORIGIN", "*")]}},
|
||||
allow_headers=["Authorization", "Content-Type"],
|
||||
expose_headers=["Content-Type"],
|
||||
)
|
||||
|
||||
@app.get("/db/check")
|
||||
def db_check():
|
||||
try:
|
||||
import psycopg # psycopg3 client
|
||||
except Exception as e:
|
||||
return {"ok": False, "error": f"psycopg not available: {e}"}, 500
|
||||
|
||||
dsn = os.getenv("DATABASE_URL")
|
||||
if not dsn:
|
||||
return {"ok": False, "error": "DATABASE_URL not set"}, 500
|
||||
|
||||
try:
|
||||
with psycopg.connect(dsn, connect_timeout=5) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SELECT version();")
|
||||
ver = cur.fetchone()[0]
|
||||
return {"ok": True, "version": ver}, 200
|
||||
except Exception as e:
|
||||
return {"ok": False, "error": str(e)}, 500
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"ok": True}, 200
|
||||
|
||||
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", "your-jwt-secret")
|
||||
app.config["JWT_TOKEN_LOCATION"] = ["headers", "query_string"]
|
||||
app.config["JWT_QUERY_STRING_NAME"] = "token"
|
||||
jwt = JWTManager(app)
|
||||
|
||||
env = os.environ.get("FLASK_ENV", "development")
|
||||
|
||||
# Register blueprints
|
||||
app.register_blueprint(auth_bp, url_prefix="/auth")
|
||||
app.register_blueprint(profile_bp, url_prefix="/profile")
|
||||
app.register_blueprint(clients_bp)
|
||||
app.register_blueprint(transporters_bp, url_prefix="/transporters")
|
||||
app.register_blueprint(destinations_bp, url_prefix="/destinations")
|
||||
app.register_blueprint(orders_bp, url_prefix="/orders")
|
||||
app.register_blueprint(orders_in_bp, url_prefix="/orders_in")
|
||||
app.register_blueprint(report_bp, url_prefix="/report")
|
||||
app.register_blueprint(admin_subscription_bp)
|
||||
app.register_blueprint(subscription_bp)
|
||||
app.register_blueprint(admin_user_bp)
|
||||
|
||||
|
||||
def update_subscription_statuses_job():
|
||||
print("[Scheduler] Running daily subscription status check...")
|
||||
subscription_model = Subscription()
|
||||
subscription_model.update_subscription_statuses()
|
||||
|
||||
RUN_SCHEDULER = os.getenv("RUN_SCHEDULER", "1") == "1"
|
||||
|
||||
if RUN_SCHEDULER:
|
||||
scheduler = BackgroundScheduler(daemon=True)
|
||||
scheduler.add_job(func=update_subscription_statuses_job, trigger="interval", days=1)
|
||||
scheduler.start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if env != "production":
|
||||
# Avoid running the scheduler twice in development mode
|
||||
import logging
|
||||
logging.getLogger("apscheduler").setLevel(logging.DEBUG)
|
||||
|
||||
app.run(debug=(env == "development"), use_reloader=False)
|
||||
70
transportmanager/server/database.py
Normal file
70
transportmanager/server/database.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import os
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import psycopg
|
||||
except ImportError:
|
||||
psycopg = None
|
||||
|
||||
DB_TYPE = 'sqlite3'
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
SCHEMA_PATH = BASE_DIR / "schema.sql" if DB_TYPE != 'sqlite3' else BASE_DIR / "schema_sqlite.sql"
|
||||
DATABASE_URL = os.getenv("DATABASE_URL")
|
||||
SQLITE_PATH = BASE_DIR / "instance" / "dev.db"
|
||||
|
||||
|
||||
def is_postgres():
|
||||
return DATABASE_URL and DATABASE_URL.lower().startswith("postgres")
|
||||
|
||||
|
||||
def get_connection():
|
||||
if is_postgres():
|
||||
if psycopg is None:
|
||||
raise RuntimeError("psycopg is required for PostgreSQL but not installed.")
|
||||
return psycopg.connect(DATABASE_URL, autocommit=True)
|
||||
else:
|
||||
SQLITE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(str(SQLITE_PATH))
|
||||
conn.execute("PRAGMA foreign_keys = ON;")
|
||||
return conn
|
||||
|
||||
|
||||
def _iter_sql_statements(script: str):
|
||||
buffer = []
|
||||
for line in script.splitlines():
|
||||
buffer.append(line)
|
||||
if line.strip().endswith(";"):
|
||||
stmt = "\n".join(buffer).strip()
|
||||
buffer = []
|
||||
if stmt:
|
||||
yield stmt
|
||||
tail = "\n".join(buffer).strip()
|
||||
if tail:
|
||||
yield tail
|
||||
|
||||
|
||||
def db_init():
|
||||
if not SCHEMA_PATH.is_file():
|
||||
raise FileNotFoundError(f"Schema file not found: {SCHEMA_PATH}")
|
||||
|
||||
with open(SCHEMA_PATH, "r", encoding="utf-8") as f:
|
||||
sql_script = f.read()
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
if is_postgres():
|
||||
with conn.cursor() as cur:
|
||||
for stmt in _iter_sql_statements(sql_script):
|
||||
cur.execute(stmt)
|
||||
else:
|
||||
conn.executescript(sql_script)
|
||||
conn.commit()
|
||||
print("Database initialized successfully.")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
db_init()
|
||||
BIN
transportmanager/server/generated_pdfs/.DS_Store
vendored
Normal file
BIN
transportmanager/server/generated_pdfs/.DS_Store
vendored
Normal file
Binary file not shown.
79
transportmanager/server/models/client.py
Normal file
79
transportmanager/server/models/client.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from datetime import datetime
|
||||
from database import get_connection, is_postgres
|
||||
|
||||
class Clients:
|
||||
def __init__(self):
|
||||
# Parameter style: Postgres uses %s, SQLite uses ?
|
||||
self.ph = "%s" if is_postgres() else "?"
|
||||
|
||||
def client_to_dict(self, row):
|
||||
return {
|
||||
"id": row[0],
|
||||
"user_id": row[1],
|
||||
"name": row[2],
|
||||
"address": row[3],
|
||||
"register_number": row[4],
|
||||
"contact_person": row[5],
|
||||
"phone": row[6],
|
||||
"email": row[7],
|
||||
"vat":row[8],
|
||||
"created_at": row[9],
|
||||
}
|
||||
|
||||
def create(self, user_id, name, address, register_number, contact_person, phone=None, email=None, vat=None):
|
||||
created_at = datetime.now().isoformat()
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"""
|
||||
INSERT INTO clients (user_id, name, address, register_number, contact_person, phone, email, vat, created_at)
|
||||
VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph})
|
||||
""",
|
||||
(user_id, name, address, register_number, contact_person, phone, email, vat, created_at),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def get_all_by_user(self, user_id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"SELECT * FROM clients WHERE user_id = {self.ph}",
|
||||
(user_id,),
|
||||
)
|
||||
return [self.client_to_dict(row) for row in cur.fetchall()]
|
||||
|
||||
def get_by_id(self, client_id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"SELECT * FROM clients WHERE id = {self.ph}",
|
||||
(client_id,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return self.client_to_dict(row) if row else None
|
||||
|
||||
def update(self, client_id, name, address, register_number, contact_person, phone=None, email=None, vat = None):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"""
|
||||
UPDATE clients
|
||||
SET name={self.ph}, address={self.ph}, register_number={self.ph}, contact_person={self.ph},
|
||||
phone={self.ph}, email={self.ph}, vat={self.ph}
|
||||
WHERE id={self.ph}
|
||||
""",
|
||||
(name, address, register_number, contact_person, phone, email, vat, client_id),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def delete(self, client_id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"DELETE FROM clients WHERE id={self.ph}",
|
||||
(client_id,),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
92
transportmanager/server/models/destinations.py
Normal file
92
transportmanager/server/models/destinations.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from datetime import datetime
|
||||
from database import get_connection, is_postgres
|
||||
|
||||
class Destinations:
|
||||
def __init__(self):
|
||||
# Parameter style: Postgres uses %s, SQLite uses ?
|
||||
self.ph = "%s" if is_postgres() else "?"
|
||||
|
||||
def destination_to_dict(self, row):
|
||||
destination = {
|
||||
"id": row[0],
|
||||
"user_id": row[1],
|
||||
"name": row[2],
|
||||
"address": row[3],
|
||||
"latitude": row[4],
|
||||
"longitude": row[5],
|
||||
"created_at": row[6]
|
||||
}
|
||||
return destination
|
||||
|
||||
def create(self, user_id, name, address):
|
||||
created_at = datetime.now().isoformat()
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"""
|
||||
INSERT INTO destinations (user_id, name, address, created_at)
|
||||
VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph})
|
||||
""",
|
||||
(user_id, name, address, created_at),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
return cur.lastrowid if hasattr(cur, "lastrowid") else None
|
||||
|
||||
def update(self, id, user_id, name, address):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"""
|
||||
UPDATE destinations
|
||||
SET user_id = {self.ph}, name = {self.ph}, address = {self.ph}
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(user_id, name, address, id),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def add_gps_coordinates(self, id, latitude, longitude):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"""
|
||||
UPDATE destinations
|
||||
SET latitude = {self.ph}, longitude = {self.ph}
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(latitude, longitude, id),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def delete(self, id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"DELETE FROM destinations WHERE id = {self.ph}",
|
||||
(id,),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def get_all_by_user(self, user_id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"SELECT * FROM destinations WHERE user_id = {self.ph} ORDER BY created_at DESC",
|
||||
(user_id,),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return [self.destination_to_dict(row) for row in rows]
|
||||
|
||||
def get_by_id(self, id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"SELECT * FROM destinations WHERE id = {self.ph}",
|
||||
(id,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return self.destination_to_dict(row) if row else None
|
||||
193
transportmanager/server/models/order_in.py
Normal file
193
transportmanager/server/models/order_in.py
Normal file
@@ -0,0 +1,193 @@
|
||||
from datetime import datetime
|
||||
from database import get_connection, is_postgres
|
||||
|
||||
class OrdersIn:
|
||||
def __init__(self):
|
||||
# Parameter placeholder per backend
|
||||
self.ph = "%s" if is_postgres() else "?"
|
||||
|
||||
def order_to_dict(self, row):
|
||||
return {
|
||||
"id": row[0],
|
||||
"order_number": row[1],
|
||||
"user_id": row[2],
|
||||
"client_id": row[3],
|
||||
"products_description": row[4],
|
||||
"ldb_quantity": row[5],
|
||||
"kg_quantity": row[6],
|
||||
"track_reg_number": row[7],
|
||||
"trailer_reg_number": row[8],
|
||||
"received_price": row[9],
|
||||
"created_at": row[10],
|
||||
}
|
||||
|
||||
def order_point_to_dict(self, row):
|
||||
return {
|
||||
"id": row[0],
|
||||
"order_id": row[1],
|
||||
"destination_id": row[2],
|
||||
"informatins": row[3],
|
||||
"point_data": row[4],
|
||||
"point_hour": row[5],
|
||||
'point_type': row[6],
|
||||
}
|
||||
|
||||
def create_order(self, data):
|
||||
created_at = datetime.now().isoformat()
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
returning = " RETURNING id" if is_postgres() else ""
|
||||
cur.execute(
|
||||
f"""
|
||||
INSERT INTO orders_in
|
||||
(user_id, client_id, products_description, received_price, order_number,
|
||||
ldb_quantity, kg_quantity, track_reg_number, trailer_reg_number, created_at)
|
||||
VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}){returning}
|
||||
""",
|
||||
(
|
||||
data["user_id"],
|
||||
data["client_id"],
|
||||
data['products_description'],
|
||||
data["received_price"],
|
||||
data["order_number"],
|
||||
data["ldb_quantity"],
|
||||
data["kg_quantity"],
|
||||
data["track_reg_number"],
|
||||
data["trailer_reg_number"],
|
||||
created_at,
|
||||
),
|
||||
)
|
||||
new_id = cur.fetchone()[0] if is_postgres() else getattr(cur, "lastrowid", None)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
return new_id
|
||||
|
||||
def update_order(self, data):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"""
|
||||
UPDATE orders_in SET
|
||||
user_id = {self.ph}, client_id = {self.ph}, products_description = {self.ph},
|
||||
received_price = {self.ph}, order_number = {self.ph},
|
||||
ldb_quantity = {self.ph}, kg_quantity = {self.ph}, track_reg_number = {self.ph},
|
||||
trailer_reg_number = {self.ph}
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(
|
||||
data["user_id"],
|
||||
data["client_id"],
|
||||
data["products_description"],
|
||||
data["received_price"],
|
||||
data["order_number"],
|
||||
data["ldb_quantity"],
|
||||
data["kg_quantity"],
|
||||
data["track_reg_number"],
|
||||
data["trailer_reg_number"],
|
||||
data["id"],
|
||||
),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def delete_order(self, order_id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(f"DELETE FROM orders_in WHERE id = {self.ph}", (order_id,))
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def get_order_by_id(self, order_id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(f"SELECT * FROM orders_in WHERE id = {self.ph}", (order_id,))
|
||||
row = cur.fetchone()
|
||||
return self.order_to_dict(row) if row else None
|
||||
|
||||
def get_orders_by_user(self, user_id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"SELECT * FROM orders_in WHERE user_id = {self.ph} ORDER BY created_at DESC",
|
||||
(user_id,),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return [self.order_to_dict(row) for row in rows]
|
||||
|
||||
def create_order_point(self, data):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
returning = " RETURNING id" if is_postgres() else ""
|
||||
cur.execute(
|
||||
f"""
|
||||
INSERT INTO order_in_points
|
||||
(order_id, destination_id, informatins, point_data, point_hour, point_type)
|
||||
VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}){returning}
|
||||
""",
|
||||
(
|
||||
data["order_id"],
|
||||
data["destination_id"],
|
||||
data["informatins"],
|
||||
data["point_data"],
|
||||
data["point_hour"],
|
||||
data['point_type'],
|
||||
),
|
||||
)
|
||||
new_id = cur.fetchone()[0] if is_postgres() else getattr(cur, "lastrowid", None)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
return new_id
|
||||
|
||||
def update_order_point(self, data):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"""
|
||||
UPDATE order_in_points SET
|
||||
order_id = {self.ph}, destination_id = {self.ph}, informatins = {self.ph},
|
||||
point_data = {self.ph}, point_hour = {self.ph}, point_type = {self.ph}
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(
|
||||
data["order_id"],
|
||||
data["destination_id"],
|
||||
data["informatins"],
|
||||
data["point_data"],
|
||||
data["point_hour"],
|
||||
data['point_type'],
|
||||
data["id"],
|
||||
),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def delete_order_point(self, point_id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(f"DELETE FROM order_in_points WHERE id = {self.ph}", (point_id,))
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def delete_points_by_order_id(self, order_id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(f"DELETE FROM order_in_points WHERE order_id = {self.ph}", (order_id,))
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def get_order_point_by_id(self, point_id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(f"SELECT * FROM order_in_points WHERE id = {self.ph}", (point_id,))
|
||||
row = cur.fetchone()
|
||||
return self.order_point_to_dict(row) if row else None
|
||||
|
||||
def get_order_points_by_order(self, order_id):
|
||||
with get_connection() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
f"SELECT * FROM order_in_points WHERE order_id = {self.ph} ORDER BY id",
|
||||
(order_id,),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return [self.order_point_to_dict(row) for row in rows]
|
||||
209
transportmanager/server/models/order_out.py
Normal file
209
transportmanager/server/models/order_out.py
Normal file
@@ -0,0 +1,209 @@
|
||||
from datetime import datetime
|
||||
from database import get_connection, is_postgres
|
||||
|
||||
class OrdersOut:
|
||||
def __init__(self):
|
||||
# Parameter placeholder per backend
|
||||
self.ph = "%s" if is_postgres() else "?"
|
||||
|
||||
def order_to_dict(self, row):
|
||||
return {
|
||||
"id": row[0],
|
||||
"order_number": row[1],
|
||||
"user_id": row[2],
|
||||
"client_id": row[3],
|
||||
"transporter_id": row[4],
|
||||
"products_description": row[5],
|
||||
"ldb_quantity": row[6],
|
||||
"kg_quantity": row[7],
|
||||
"track_reg_number": row[8],
|
||||
"trailer_reg_number": row[9],
|
||||
"received_price": row[10],
|
||||
"paid_price": row[11],
|
||||
"created_at": row[12],
|
||||
"status": row[13]
|
||||
}
|
||||
|
||||
def order_point_to_dict(self, row):
|
||||
return {
|
||||
"id": row[0],
|
||||
"order_id": row[1],
|
||||
"destination_id": row[2],
|
||||
"informatins": row[3],
|
||||
"point_data": row[4],
|
||||
"point_hour": row[5],
|
||||
'point_type': row[6],
|
||||
}
|
||||
|
||||
def create_order(self, data):
|
||||
created_at = datetime.now().isoformat()
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
returning = " RETURNING id" if is_postgres() else ""
|
||||
cursor.execute(
|
||||
f"""
|
||||
INSERT INTO orders_out
|
||||
(user_id, client_id, transporter_id, products_description, received_price, paid_price, order_number,
|
||||
ldb_quantity, kg_quantity, track_reg_number, trailer_reg_number, created_at)
|
||||
VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}){returning}
|
||||
""",
|
||||
(
|
||||
data["user_id"],
|
||||
data["client_id"],
|
||||
data["transporter_id"],
|
||||
data['products_description'],
|
||||
data["received_price"],
|
||||
data["paid_price"],
|
||||
data["order_number"],
|
||||
data["ldb_quantity"],
|
||||
data["kg_quantity"],
|
||||
data["track_reg_number"],
|
||||
data["trailer_reg_number"],
|
||||
created_at,
|
||||
),
|
||||
)
|
||||
new_id = cursor.fetchone()[0] if is_postgres() else getattr(cursor, "lastrowid", None)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
return new_id
|
||||
|
||||
def update_order(self, data):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE orders_out SET client_id = {self.ph}, transporter_id = {self.ph}, products_description = {self.ph},
|
||||
received_price = {self.ph}, paid_price = {self.ph}, order_number = {self.ph},
|
||||
ldb_quantity = {self.ph}, kg_quantity = {self.ph}, track_reg_number = {self.ph},
|
||||
trailer_reg_number = {self.ph}
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(
|
||||
data["client_id"],
|
||||
data["transporter_id"],
|
||||
data["products_description"],
|
||||
data["received_price"],
|
||||
data["paid_price"],
|
||||
data["order_number"],
|
||||
data["ldb_quantity"],
|
||||
data["kg_quantity"],
|
||||
data["track_reg_number"],
|
||||
data["trailer_reg_number"],
|
||||
data["id"],
|
||||
),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def cancel_order(self, order_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"UPDATE orders_out SET order_status = {self.ph} WHERE id = {self.ph}",
|
||||
('cancelled', order_id,),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def delete_order(self, order_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"DELETE FROM orders_out WHERE id = {self.ph}", (order_id,))
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def get_order_by_id(self, order_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT * FROM orders_out WHERE id = {self.ph}", (order_id,))
|
||||
row = cursor.fetchone()
|
||||
return self.order_to_dict(row) if row else None
|
||||
|
||||
def get_orders_by_user(self, user_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"SELECT * FROM orders_out WHERE user_id = {self.ph} ORDER BY created_at DESC",
|
||||
(user_id,),
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
return [self.order_to_dict(row) for row in rows]
|
||||
|
||||
|
||||
def create_order_point(self, data):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
returning = " RETURNING id" if is_postgres() else ""
|
||||
cursor.execute(
|
||||
f"""
|
||||
INSERT INTO order_out_points
|
||||
(order_id, destination_id, informatins, point_data, point_hour, point_type)
|
||||
VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}){returning}
|
||||
""",
|
||||
(
|
||||
data["order_id"],
|
||||
data["destination_id"],
|
||||
data["informatins"],
|
||||
data["point_data"],
|
||||
data["point_hour"],
|
||||
data['point_type']
|
||||
),
|
||||
)
|
||||
# keep behavior similar: no return expected, but commit consistently
|
||||
if is_postgres():
|
||||
_ = cursor.fetchone() # consume RETURNING if present
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def update_order_point(self, data):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE order_out_points SET
|
||||
order_id = {self.ph}, destination_id = {self.ph}, informatins = {self.ph}, point_data = {self.ph}, point_hour = {self.ph}, point_type = {self.ph}
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(
|
||||
data["order_id"],
|
||||
data["destination_id"],
|
||||
data["informatins"],
|
||||
data["point_data"],
|
||||
data["point_hour"],
|
||||
data['point_type'],
|
||||
data["id"],
|
||||
),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def delete_order_point(self, point_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"DELETE FROM order_out_points WHERE id = {self.ph}", (point_id,))
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def delete_points_by_order_id(self, order_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"DELETE FROM order_out_points WHERE order_id = {self.ph}", (order_id,))
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def get_order_point_by_id(self, point_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT * FROM order_out_points WHERE id = {self.ph}", (point_id,))
|
||||
row = cursor.fetchone()
|
||||
return self.order_point_to_dict(row) if row else None
|
||||
|
||||
def get_order_points_by_order(self, order_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"SELECT * FROM order_out_points WHERE order_id = {self.ph} ORDER BY id",
|
||||
(order_id,),
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
return [self.order_point_to_dict(row) for row in rows]
|
||||
130
transportmanager/server/models/subscription.py
Normal file
130
transportmanager/server/models/subscription.py
Normal file
@@ -0,0 +1,130 @@
|
||||
from datetime import datetime, timedelta
|
||||
from database import get_connection, is_postgres
|
||||
|
||||
class Subscription:
|
||||
def __init__(self):
|
||||
# Parameter placeholder per backend
|
||||
self.ph = "%s" if is_postgres() else "?"
|
||||
|
||||
def subscription_to_dict(self, row):
|
||||
return {
|
||||
"id": row[0],
|
||||
"user_id": row[1], #company id
|
||||
"plan": row[2],
|
||||
"start_date": row[3],
|
||||
"end_date": row[4],
|
||||
"status": row[5],
|
||||
"register_number": row[6],
|
||||
"created_at": row[7],
|
||||
}
|
||||
|
||||
def create(self, user_id, plan, start_date, end_date, register_number, status="active"):
|
||||
created_at = datetime.now().isoformat()
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
returning = " RETURNING id" if is_postgres() else ""
|
||||
cursor.execute(
|
||||
f"""
|
||||
INSERT INTO subscriptions (user_id, plan, start_date, end_date, status, register_number, created_at)
|
||||
VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}){returning}
|
||||
""",
|
||||
(user_id, plan, start_date, end_date, status, register_number, created_at),
|
||||
)
|
||||
new_id = cursor.fetchone()[0] if is_postgres() else getattr(cursor, "lastrowid", None)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
return new_id
|
||||
|
||||
def get_by_user_id(self, user_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
SELECT * FROM subscriptions
|
||||
WHERE user_id = {self.ph}
|
||||
ORDER BY start_date DESC
|
||||
""",
|
||||
(user_id,),
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
return [self.subscription_to_dict(row) for row in rows]
|
||||
|
||||
def get_by_id(self, subscription_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
SELECT * FROM subscriptions
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(subscription_id,),
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
return self.subscription_to_dict(row) if row else None
|
||||
|
||||
def update_status(self, subscription_id, new_status):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE subscriptions
|
||||
SET status = {self.ph}
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(new_status, subscription_id),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
return cursor.rowcount if hasattr(cursor, "rowcount") else 0
|
||||
|
||||
def delete(self, subscription_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
DELETE FROM subscriptions
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(subscription_id,),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
return cursor.rowcount if hasattr(cursor, "rowcount") else 0
|
||||
|
||||
def get_first_2_months_subscription_for_register_number(self, register_number):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
SELECT * FROM subscriptions
|
||||
WHERE register_number = {self.ph} AND plan = 'first_2_months' AND status = 'active'
|
||||
""",
|
||||
(register_number,),
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
return self.subscription_to_dict(row) if row else None
|
||||
|
||||
def get_all(self):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT * FROM subscriptions
|
||||
ORDER BY created_at DESC
|
||||
"""
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
return [self.subscription_to_dict(row) for row in rows]
|
||||
|
||||
def update_subscription_statuses(self):
|
||||
now = datetime.now()
|
||||
subscriptions = self.get_all()
|
||||
|
||||
for sub in subscriptions:
|
||||
end_date = datetime.fromisoformat(sub["end_date"])
|
||||
days_left = (end_date - now).days
|
||||
|
||||
if days_left < 0 and sub["status"] != "expired":
|
||||
self.update_status(sub["id"], "expired")
|
||||
elif 0 <= days_left <= 5 and sub["status"] != "less_than_5_days":
|
||||
self.update_status(sub["id"], "less_than_5_days")
|
||||
87
transportmanager/server/models/transporters.py
Normal file
87
transportmanager/server/models/transporters.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from datetime import datetime
|
||||
|
||||
from database import get_connection, is_postgres
|
||||
|
||||
class Transporters:
|
||||
def __init__(self):
|
||||
# Parameter placeholder depending on backend
|
||||
self.ph = "%s" if is_postgres() else "?"
|
||||
|
||||
def transporter_to_dict(self, row):
|
||||
return {
|
||||
"id": row[0],
|
||||
"user_id": row[1],
|
||||
"name": row[2],
|
||||
"address": row[3],
|
||||
"register_number": row[4],
|
||||
"contact_person": row[5],
|
||||
"phone": row[6],
|
||||
"email": row[7],
|
||||
"vat": row[8],
|
||||
"created_at": row[9]
|
||||
}
|
||||
|
||||
def create_transporter(self, name, address, register_number, contact_person, phone, email, vat, user_id):
|
||||
created_at = datetime.now().isoformat()
|
||||
returning = " RETURNING id" if is_postgres() else ""
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
INSERT INTO transporters
|
||||
(name, address, register_number, contact_person, phone, email, vat, created_at, user_id)
|
||||
VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph}){returning}
|
||||
""",
|
||||
(name, address, register_number, contact_person, phone, email, vat, created_at, user_id),
|
||||
)
|
||||
transporter_id = cursor.fetchone()[0] if is_postgres() else cursor.lastrowid
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
return transporter_id
|
||||
|
||||
def get_all_transporters_by_user(self, user_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"SELECT * FROM transporters WHERE user_id = {self.ph} ORDER BY created_at DESC",
|
||||
(user_id,),
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
return [self.transporter_to_dict(row) for row in rows]
|
||||
|
||||
def get_transporter_by_id(self, transporter_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"SELECT * FROM transporters WHERE id = {self.ph}",
|
||||
(transporter_id,),
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
return self.transporter_to_dict(row) if row else None
|
||||
|
||||
def update_transporter(self, transporter_id, name, address, register_number, contact_person, phone, email, vat):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE transporters SET
|
||||
name = {self.ph},
|
||||
address = {self.ph},
|
||||
register_number = {self.ph},
|
||||
contact_person = {self.ph},
|
||||
phone = {self.ph},
|
||||
email = {self.ph},
|
||||
vat = {self.ph}
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(name, address, register_number, contact_person, phone, email, vat, transporter_id),
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def delete_transporter(self, transporter_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"DELETE FROM transporters WHERE id = {self.ph}", (transporter_id,))
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
194
transportmanager/server/models/user.py
Normal file
194
transportmanager/server/models/user.py
Normal file
@@ -0,0 +1,194 @@
|
||||
from datetime import datetime
|
||||
from database import get_connection, is_postgres
|
||||
|
||||
class Users:
|
||||
def __init__(self):
|
||||
self.ph = "%s" if is_postgres() else "?"
|
||||
|
||||
def user_to_dict(self, row):
|
||||
user = {
|
||||
'id': row[0],
|
||||
'name': row[1],
|
||||
'contact_name': row[2],
|
||||
'email': row[3],
|
||||
'password_hash': row[4],
|
||||
'phone': row[5],
|
||||
'register_number': row[6],
|
||||
'vat':row[7],
|
||||
'address': row[8],
|
||||
'logo_filename': row[9],
|
||||
'terms': row[10],
|
||||
'first_order_number': row[11],
|
||||
'created_at': row[12],
|
||||
'otp_code': row[13],
|
||||
'otp_expiration': row[14],
|
||||
'user_role': row[15]
|
||||
}
|
||||
return user
|
||||
|
||||
def email_to_dict(self, row):
|
||||
email = {
|
||||
'id': row[0],
|
||||
'user_id': row[1],
|
||||
'smtp_host': row[2],
|
||||
'smtp_port': row[3],
|
||||
'smtp_user': row[4],
|
||||
'created_at': row[5]
|
||||
}
|
||||
return email
|
||||
|
||||
def get_user_by_email(self, email):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT * FROM users WHERE email = {self.ph}", (email,))
|
||||
row = cursor.fetchone()
|
||||
return self.user_to_dict(row) if row else None
|
||||
|
||||
def get_user_by_id(self, user_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT * FROM users WHERE id = {self.ph}", (user_id,))
|
||||
row = cursor.fetchone()
|
||||
return self.user_to_dict(row) if row else None
|
||||
|
||||
def insert_user(self, name, email, password_hash):
|
||||
created_at = datetime.now().isoformat()
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
returning = "RETURNING id" if is_postgres() else ""
|
||||
query = f"""
|
||||
INSERT INTO users (
|
||||
name, email, password_hash, created_at
|
||||
) VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}) {returning}
|
||||
"""
|
||||
cursor.execute(query, (name, email, password_hash, created_at))
|
||||
inserted_id = None
|
||||
if is_postgres():
|
||||
inserted_id = cursor.fetchone()[0]
|
||||
else:
|
||||
inserted_id = cursor.lastrowid
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
return inserted_id
|
||||
|
||||
def update_user_otp(self, user_id, otp_code, expiration):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE users
|
||||
SET otp_code = {self.ph}, otp_expiration = {self.ph}
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(otp_code, expiration.isoformat(), user_id)
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def clear_user_otp(self, user_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE users
|
||||
SET otp_code = NULL, otp_expiration = NULL
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(user_id,)
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def update_user_password(self, user_id, new_password_hash):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE users
|
||||
SET password_hash = {self.ph}
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(new_password_hash, user_id)
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def update_user(self, data):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE users
|
||||
SET name = {self.ph}, contact_name = {self.ph}, email = {self.ph}, phone = {self.ph}, register_number = {self.ph}, vat = {self.ph}, address = {self.ph}, logo_filename = {self.ph}, terms = {self.ph}, first_order_number = {self.ph}
|
||||
WHERE id = {self.ph}
|
||||
""",
|
||||
(
|
||||
data['name'],
|
||||
data['contact_name'],
|
||||
data['email'],
|
||||
data['phone'],
|
||||
data['register_number'],
|
||||
data['vat'],
|
||||
data['address'],
|
||||
data['logo_filename'],
|
||||
data['terms'],
|
||||
data['first_order_number'],
|
||||
data['user_id']
|
||||
)
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def update_user_logo(self, data):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE users SET logo_filename = {self.ph} WHERE id = {self.ph}
|
||||
""",
|
||||
(data['logo_filename'], data['user_id'])
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def get_all_users_with_role(self, role='user'):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT * FROM users WHERE user_role = {self.ph}", (role,))
|
||||
rows = cursor.fetchall()
|
||||
return [self.user_to_dict(row) for row in rows]
|
||||
|
||||
#--- email credentials ---
|
||||
def insert_email_credentials(self, user_id, smtp_host, smtp_port, smtp_user):
|
||||
created_at = datetime.now().isoformat()
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
INSERT INTO email (
|
||||
user_id, smtp_host, smtp_port, smtp_user, created_at
|
||||
) VALUES ({self.ph}, {self.ph}, {self.ph}, {self.ph}, {self.ph})
|
||||
""",
|
||||
(user_id, smtp_host, smtp_port, smtp_user, created_at)
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def get_email_credentials(self, user_id):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"SELECT * FROM email WHERE user_id = {self.ph}", (user_id,))
|
||||
row = cursor.fetchone()
|
||||
return self.email_to_dict(row) if row else None
|
||||
|
||||
def update_email_credentials(self, user_id, smtp_host, smtp_port, smtp_user):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE email SET smtp_host={self.ph}, smtp_port={self.ph}, smtp_user={self.ph} WHERE id = {self.ph}
|
||||
""",
|
||||
(smtp_host, smtp_port, smtp_user, user_id)
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
23
transportmanager/server/requirements.txt
Normal file
23
transportmanager/server/requirements.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
# --- Flask API (server) requirements ---
|
||||
# Web framework
|
||||
Flask==2.3.3
|
||||
Flask-Cors==4.0.0
|
||||
Flask-JWT-Extended==4.6.0
|
||||
|
||||
# WSGI server
|
||||
gunicorn==22.0.0
|
||||
|
||||
# Database (Postgres)
|
||||
psycopg[binary]==3.2.1
|
||||
|
||||
# Utilities
|
||||
python-dotenv==1.0.1
|
||||
requests==2.32.3
|
||||
|
||||
# Features used by your server
|
||||
geopy==2.4.1
|
||||
reportlab>=3.6.12
|
||||
PyPDF2==3.0.1
|
||||
|
||||
APScheduler==3.10.4
|
||||
tzlocal==5.2
|
||||
3
transportmanager/server/routes/__init__.py
Normal file
3
transportmanager/server/routes/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from flask import Blueprint
|
||||
|
||||
# Placeholder for blueprint registration if needed dynamically
|
||||
204
transportmanager/server/routes/auth.py
Normal file
204
transportmanager/server/routes/auth.py
Normal file
@@ -0,0 +1,204 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from utils.email import send_email, send_gmail
|
||||
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
|
||||
from flask_jwt_extended import decode_token
|
||||
import datetime
|
||||
import random
|
||||
import os
|
||||
from datetime import timezone
|
||||
|
||||
from models.user import Users
|
||||
|
||||
auth_bp = Blueprint("auth", __name__)
|
||||
|
||||
@auth_bp.route("/register", methods=["POST"])
|
||||
def register():
|
||||
users = Users()
|
||||
data = request.get_json()
|
||||
name = data.get("name")
|
||||
email = data.get("email")
|
||||
password = data.get("password")
|
||||
|
||||
if not name or not email or not password:
|
||||
return jsonify({"error": "Missing required fields"}), 400
|
||||
|
||||
existing_user = users.get_user_by_email(email)
|
||||
if existing_user:
|
||||
return jsonify({"error": "User already exists"}), 409
|
||||
|
||||
password_hash = generate_password_hash(password)
|
||||
users.insert_user(name, email, password_hash)
|
||||
|
||||
return jsonify({"message": "User registered successfully!"}), 201
|
||||
|
||||
|
||||
@auth_bp.route("/login", methods=["POST"])
|
||||
def login():
|
||||
users = Users()
|
||||
data = request.get_json()
|
||||
email = data.get("email", "").strip().lower()
|
||||
password = data.get("password", "")
|
||||
|
||||
if not email or not password:
|
||||
return jsonify({"error": "Missing email or password"}), 400
|
||||
|
||||
user = users.get_user_by_email(email)
|
||||
if not user or not check_password_hash(user["password_hash"], password):
|
||||
return jsonify({"error": "Invalid credentials"}), 401
|
||||
|
||||
otp_code = str(random.randint(100000, 999999))
|
||||
expiration = datetime.datetime.now(timezone.utc) + datetime.timedelta(minutes=10)
|
||||
users.update_user_otp(user["id"], otp_code, expiration)
|
||||
|
||||
send_gmail(
|
||||
to_email=user["email"],
|
||||
subject="Your Login Verification Code",
|
||||
body=f"Your login verification code is: {otp_code}"
|
||||
)
|
||||
|
||||
return jsonify({"message": "Verification code sent to your email."}), 200
|
||||
|
||||
|
||||
@auth_bp.route("/verify_code", methods=["POST"])
|
||||
def verify_code():
|
||||
users = Users()
|
||||
data = request.get_json()
|
||||
email = data.get("email", "").strip().lower()
|
||||
code = data.get("code", "")
|
||||
|
||||
if not email or not code:
|
||||
return jsonify({"error": "Missing email or verification code"}), 400
|
||||
|
||||
user = users.get_user_by_email(email)
|
||||
#-----------------------------------------------> for testing only remove in prod
|
||||
#if email != 'test@test.com':
|
||||
#-----------------------------------------------> for testing only remove in prod
|
||||
if not user or user.get("otp_code") != code:
|
||||
return jsonify({"error": "Invalid code"}), 401
|
||||
|
||||
exp = user.get("otp_expiration")
|
||||
# Normalize to aware UTC datetime for safe comparison across SQLite (string) and Postgres (datetime)
|
||||
now_utc = datetime.datetime.now(timezone.utc)
|
||||
if isinstance(exp, str):
|
||||
try:
|
||||
exp_dt = datetime.datetime.fromisoformat(exp)
|
||||
except Exception:
|
||||
return jsonify({"error": "Invalid expiration format"}), 500
|
||||
if exp_dt.tzinfo is None:
|
||||
exp_dt = exp_dt.replace(tzinfo=timezone.utc)
|
||||
else:
|
||||
# Assume a datetime object from DB driver
|
||||
exp_dt = exp
|
||||
if exp_dt is None:
|
||||
return jsonify({"error": "Missing expiration"}), 500
|
||||
if exp_dt.tzinfo is None:
|
||||
exp_dt = exp_dt.replace(tzinfo=timezone.utc)
|
||||
if now_utc > exp_dt:
|
||||
return jsonify({"error": "Verification code has expired"}), 403
|
||||
|
||||
users.clear_user_otp(user["id"])
|
||||
|
||||
access_token = create_access_token(
|
||||
identity=str(user["id"]),
|
||||
expires_delta=datetime.timedelta(hours=12)
|
||||
)
|
||||
return jsonify({
|
||||
"message": "Login successful",
|
||||
"access_token": access_token
|
||||
}), 200
|
||||
|
||||
|
||||
@auth_bp.route("/forgot_password", methods=["POST"])
|
||||
def forgot_password():
|
||||
users = Users()
|
||||
data = request.get_json()
|
||||
email = data.get("email", "").strip().lower()
|
||||
|
||||
if not email:
|
||||
return jsonify({"error": "Email is required"}), 400
|
||||
|
||||
user = users.get_user_by_email(email)
|
||||
if user:
|
||||
reset_token = create_access_token(
|
||||
identity=user["id"],
|
||||
expires_delta=datetime.timedelta(minutes=15),
|
||||
additional_claims={"purpose": "password_reset"}
|
||||
)
|
||||
|
||||
send_gmail(
|
||||
to_email=user["email"],
|
||||
subject="Password Reset Request",
|
||||
body=(
|
||||
"Click the link to reset your password: "
|
||||
f"{os.getenv('FRONTEND_BASE_URL', 'http://127.0.0.1:5100')}/reset_password?token={reset_token}"
|
||||
)
|
||||
)
|
||||
|
||||
return jsonify({"message": "If this email is registered, a reset link has been sent."}), 200
|
||||
|
||||
|
||||
@auth_bp.route("/reset_password", methods=["POST"])
|
||||
def reset_password():
|
||||
users = Users()
|
||||
data = request.get_json()
|
||||
token = data.get("token", "")
|
||||
new_password = data.get("new_password", "")
|
||||
|
||||
if not token or not new_password:
|
||||
return jsonify({"error": "Missing token or new password"}), 400
|
||||
|
||||
try:
|
||||
decoded_token = decode_token(token)
|
||||
if decoded_token.get("purpose") != "password_reset":
|
||||
return jsonify({"error": "Invalid token purpose"}), 403
|
||||
except Exception:
|
||||
return jsonify({"error": "Invalid or expired token"}), 403
|
||||
|
||||
user_id = decoded_token["sub"]
|
||||
user = users.get_user_by_id(user_id)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
password_hash = generate_password_hash(new_password)
|
||||
users.update_user_password(user_id, password_hash)
|
||||
|
||||
return jsonify({"message": "Password has been reset successfully."}), 200
|
||||
|
||||
|
||||
@auth_bp.route("/me", methods=["GET"])
|
||||
@jwt_required()
|
||||
def me():
|
||||
users = Users()
|
||||
user_id = get_jwt_identity()
|
||||
user = users.get_user_by_id(user_id)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
return jsonify({
|
||||
"id": user["id"],
|
||||
"name": user["name"],
|
||||
"contact_name": user['contact_name'],
|
||||
"email": user["email"],
|
||||
"phone": user["phone"],
|
||||
"register_number": user["register_number"],
|
||||
"vat":user["vat"],
|
||||
"address": user["address"],
|
||||
"logo_filename": user["logo_filename"],
|
||||
"terms": user["terms"],
|
||||
"first_order_number": user["first_order_number"],
|
||||
"created_at": user["created_at"],
|
||||
"user_role": user["user_role"]
|
||||
}), 200
|
||||
|
||||
|
||||
# Validate token endpoint
|
||||
@auth_bp.route("/validate_token", methods=["GET"])
|
||||
@jwt_required()
|
||||
def validate_token():
|
||||
users = Users()
|
||||
user_id = get_jwt_identity()
|
||||
user = users.get_user_by_id(user_id)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
return jsonify({"message": "Token is valid"}), 200
|
||||
65
transportmanager/server/routes/clients.py
Normal file
65
transportmanager/server/routes/clients.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from models.client import Clients
|
||||
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
|
||||
clients_bp = Blueprint("clients", __name__, url_prefix="/clients")
|
||||
|
||||
@clients_bp.route("/", methods=["GET"])
|
||||
@jwt_required()
|
||||
def list_clients():
|
||||
clients_db = Clients()
|
||||
user_id = get_jwt_identity()
|
||||
clients = clients_db.get_all_by_user(user_id)
|
||||
return jsonify(clients), 200
|
||||
|
||||
@clients_bp.route("/", methods=["POST"])
|
||||
@jwt_required()
|
||||
def create_client():
|
||||
clients_db = Clients()
|
||||
user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
client_id = clients_db.create(
|
||||
user_id=user_id,
|
||||
name=data["name"],
|
||||
address=data["address"],
|
||||
register_number=data["register_number"],
|
||||
contact_person=data["contact_person"],
|
||||
phone=data["phone"],
|
||||
email=data["email"],
|
||||
vat = data["vat"]
|
||||
)
|
||||
return jsonify({"message": "Client created", "id": client_id}), 201
|
||||
|
||||
@clients_bp.route("/<int:client_id>", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_client(client_id):
|
||||
clients_db = Clients()
|
||||
data = request.get_json()
|
||||
name=data["name"]
|
||||
address=data["address"]
|
||||
register_number=data["register_number"]
|
||||
contact_person=data["contact_person"]
|
||||
phone=data["phone"]
|
||||
email=data["email"]
|
||||
vat = data["vat"]
|
||||
clients_db.update(client_id, name, address, register_number, contact_person, phone, email, vat)
|
||||
return jsonify({"message": "Client updated"}), 200
|
||||
|
||||
@clients_bp.route("/<int:client_id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def delete_client(client_id):
|
||||
clients_db = Clients()
|
||||
success = clients_db.delete(client_id)
|
||||
if not success:
|
||||
return jsonify({"message": "Client not found or unauthorized"}), 404
|
||||
return jsonify({"message": "Client deleted"}), 200
|
||||
|
||||
@clients_bp.route("/<int:client_id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_client(client_id):
|
||||
clients_db = Clients()
|
||||
client = clients_db.get_by_id(client_id)
|
||||
if not client:
|
||||
return jsonify({"message": "Client not found"}), 404
|
||||
return jsonify(client), 200
|
||||
73
transportmanager/server/routes/destinations.py
Normal file
73
transportmanager/server/routes/destinations.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from models.destinations import Destinations
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from utils.maps import AdressCoordinates
|
||||
|
||||
destinations_bp = Blueprint("destinations", __name__, url_prefix="/destinations")
|
||||
|
||||
@destinations_bp.route("/", methods=["GET"])
|
||||
@jwt_required()
|
||||
def list_destinations():
|
||||
destinations_db = Destinations()
|
||||
user_id = get_jwt_identity()
|
||||
destinations = destinations_db.get_all_by_user(user_id)
|
||||
return jsonify([dict(d) for d in destinations]), 200
|
||||
|
||||
@destinations_bp.route("/", methods=["POST"])
|
||||
@jwt_required()
|
||||
def create_destination():
|
||||
destinations_db = Destinations()
|
||||
user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
destination_id = destinations_db.create(user_id, data.get("name"), data.get("address"))
|
||||
# coordinates = AdressCoordinates(data.get("address"))
|
||||
# lat_log = coordinates.open_Maps_by_address()
|
||||
# if lat_log:
|
||||
# latitude = lat_log['latitude']
|
||||
# longitude = lat_log['longitude']
|
||||
# destinations_db.add_gps_coordinates(destination_id, latitude, longitude)
|
||||
return jsonify({"id": destination_id, "message": "Destination created"}), 201
|
||||
|
||||
@destinations_bp.route("/<int:id>", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_destination(id):
|
||||
destinations_db = Destinations()
|
||||
user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
destinations_db.update(id, user_id, data.get("name"), data.get("address"))
|
||||
coordinates = AdressCoordinates(data.get("address"))
|
||||
lat_log = coordinates.open_Maps_by_address()
|
||||
if lat_log:
|
||||
latitude = lat_log['latitude']
|
||||
longitude = lat_log['longitude']
|
||||
destinations_db.add_gps_coordinates(id, latitude, longitude)
|
||||
|
||||
return jsonify({"message": "Destination updated"}), 200
|
||||
|
||||
@destinations_bp.route("/<int:id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def delete_destination(id):
|
||||
destinations_db = Destinations()
|
||||
success = destinations_db.delete(id)
|
||||
if not success:
|
||||
return jsonify({"message": "Destination not found or unauthorized"}), 404
|
||||
return "", 204
|
||||
|
||||
|
||||
# New route to update GPS coordinates of a destination
|
||||
@destinations_bp.route("/<int:id>/coordinates", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_coordinates(id):
|
||||
destinations_db = Destinations()
|
||||
data = request.get_json()
|
||||
latitude = data.get("latitude")
|
||||
longitude = data.get("longitude")
|
||||
|
||||
if latitude is None or longitude is None:
|
||||
return jsonify({"message": "Latitude and longitude are required"}), 400
|
||||
|
||||
success = destinations_db.add_gps_coordinates(id, latitude, longitude)
|
||||
if not success:
|
||||
return jsonify({"message": "Failed to update coordinates"}), 404
|
||||
|
||||
return jsonify({"message": "Coordinates updated"}), 200
|
||||
259
transportmanager/server/routes/orders_out.py
Normal file
259
transportmanager/server/routes/orders_out.py
Normal file
@@ -0,0 +1,259 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from models.order_out import OrdersOut
|
||||
from models.user import Users
|
||||
from models.transporters import Transporters
|
||||
from datetime import datetime
|
||||
from utils.pdf import generate_order_pdf
|
||||
from utils.cancel_order import cancel_order_pdf
|
||||
import os
|
||||
|
||||
from flask import send_from_directory
|
||||
from utils.email import send_gmail_with_attachment, send_custom_email_with_attachment
|
||||
|
||||
orders_bp = Blueprint("orders", __name__, url_prefix="/orders")
|
||||
|
||||
@orders_bp.route("/", methods=["POST"])
|
||||
@jwt_required()
|
||||
def create_order_route():
|
||||
user_id = get_jwt_identity()
|
||||
orders = OrdersOut()
|
||||
incoming_data = request.json
|
||||
#here we need to first implement the order pdf
|
||||
users = Users()
|
||||
user = users.get_user_by_id(user_id)
|
||||
logo_filename = user.get('logo_filename')
|
||||
logo_path = None
|
||||
if logo_filename:
|
||||
logo_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "..", "client", "assets", "images", logo_filename)
|
||||
)
|
||||
transporters = Transporters()
|
||||
transporter = transporters.get_transporter_by_id(incoming_data["transporter_id"])
|
||||
generate_order_pdf(order=incoming_data, user_data=user, transporter_data=transporter, logo_path=logo_path)
|
||||
#
|
||||
#try:
|
||||
order_data = {
|
||||
'user_id': user_id,
|
||||
'client_id': incoming_data["client_id"],
|
||||
'transporter_id': incoming_data["transporter_id"],
|
||||
'received_price': incoming_data["received_price"],
|
||||
'paid_price': incoming_data["paid_price"],
|
||||
'order_number': incoming_data["order_number"],
|
||||
'created_at': datetime.now(),
|
||||
'ldb_quantity': incoming_data["ldb_quantity"],
|
||||
'kg_quantity': incoming_data["kg_quantity"],
|
||||
'track_reg_number': incoming_data["track_reg_number"],
|
||||
'trailer_reg_number': incoming_data["trailer_reg_number"],
|
||||
'products_description': incoming_data["products_description"],
|
||||
}
|
||||
order_id = orders.create_order(order_data)
|
||||
|
||||
for address in incoming_data["loading_addresses"]:
|
||||
data = {
|
||||
"order_id": order_id,
|
||||
"destination_id": address['loading_address_id'],
|
||||
"informatins": address['loading_informatins'],
|
||||
"point_data": address['loading_date'],
|
||||
"point_hour": address['loading_hour'],
|
||||
"point_type": "loading"
|
||||
}
|
||||
orders.create_order_point(data)
|
||||
|
||||
for address in incoming_data["unloading_addresses"]:
|
||||
data = {
|
||||
"order_id": order_id,
|
||||
"destination_id": address['unloading_address_id'],
|
||||
"informatins": address['unloading_informatins'],
|
||||
"point_data": address['unloading_date'],
|
||||
"point_hour": address['unloading_hour'],
|
||||
"point_type": "unloading"
|
||||
}
|
||||
orders.create_order_point(data)
|
||||
|
||||
|
||||
|
||||
return jsonify({"message": "Order created", "order_id": order_id}), 201
|
||||
#except Exception as e:
|
||||
# return jsonify({"error": str(e)}), 400
|
||||
|
||||
@orders_bp.route("/<int:order_id>", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_order_route(order_id):
|
||||
orders = OrdersOut()
|
||||
data = request.json
|
||||
user_id = get_jwt_identity()
|
||||
order = orders.get_order_by_id(order_id)
|
||||
if not order:
|
||||
return jsonify({"error": "Order not found"}), 404
|
||||
if str(order["user_id"]) != str(user_id):
|
||||
return jsonify({"error": "Unauthorized"}), 403
|
||||
|
||||
try:
|
||||
orders.update_order({
|
||||
"id":data.get("id", order['id']),
|
||||
"client_id": data.get("client_id", order["client_id"]),
|
||||
"transporter_id": data.get("transporter_id", order["transporter_id"]),
|
||||
"received_price": data.get("received_price", order["received_price"]),
|
||||
"paid_price": data.get("paid_price", order["paid_price"]),
|
||||
"order_number": data.get("order_number", order["order_number"]),
|
||||
"ldb_quantity": data.get("ldb_quantity", order["ldb_quantity"]),
|
||||
"kg_quantity": data.get("kg_quantity", order["kg_quantity"]),
|
||||
"track_reg_number": data.get("track_reg_number", order["track_reg_number"]),
|
||||
"trailer_reg_number": data.get("trailer_reg_number", order["trailer_reg_number"]),
|
||||
"products_description": data.get("products_description", order["products_description"]),
|
||||
})
|
||||
|
||||
orders.delete_points_by_order_id(order_id)
|
||||
for address in data["loading_addresses"]:
|
||||
loading_data = {
|
||||
"order_id": order_id,
|
||||
"destination_id": address['loading_address_id'],
|
||||
"informatins": address['loading_informatins'],
|
||||
"point_data": address['loading_date'],
|
||||
"point_hour": address['loading_hour'],
|
||||
"point_type": "loading"
|
||||
}
|
||||
orders.create_order_point(loading_data)
|
||||
|
||||
for address in data["unloading_addresses"]:
|
||||
unloading_data = {
|
||||
"order_id": order_id,
|
||||
"destination_id": address['unloading_address_id'],
|
||||
"informatins": address['unloading_informatins'],
|
||||
"point_data": address['unloading_date'],
|
||||
"point_hour": address['unloading_hour'],
|
||||
"point_type": "unloading"
|
||||
}
|
||||
orders.create_order_point(unloading_data)
|
||||
|
||||
#regenerate pdf:
|
||||
incoming_data = data
|
||||
users = Users()
|
||||
user = users.get_user_by_id(user_id)
|
||||
transporters = Transporters()
|
||||
transporter = transporters.get_transporter_by_id(incoming_data["transporter_id"])
|
||||
logo_filename = user.get('logo_filename')
|
||||
logo_path = None
|
||||
if logo_filename:
|
||||
logo_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "..", "client", "assets", "images", logo_filename)
|
||||
)
|
||||
generate_order_pdf(order=incoming_data, user_data=user, transporter_data=transporter, logo_path=logo_path)
|
||||
|
||||
return jsonify({"message": "Order updated", "order_id": order_id}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
@orders_bp.route("/<int:order_id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def delete_order_route(order_id):
|
||||
orders = OrdersOut()
|
||||
user_id = get_jwt_identity()
|
||||
order = orders.get_order_by_id(order_id)
|
||||
if not order:
|
||||
return jsonify({"error": "Order not found"}), 404
|
||||
if order["user_id"] != user_id:
|
||||
return jsonify({"error": "Unauthorized"}), 403
|
||||
|
||||
try:
|
||||
orders.delete_points_by_order_id(order_id)
|
||||
orders.delete_order(order_id)
|
||||
return jsonify({"message": "Order deleted"}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
@orders_bp.route("/list", methods=["GET"])
|
||||
@jwt_required()
|
||||
def list_orders():
|
||||
orders = OrdersOut()
|
||||
user_id = get_jwt_identity()
|
||||
try:
|
||||
user_orders = orders.get_orders_by_user(user_id)
|
||||
#result = [{"id": order["id"], "order_number": order["order_number"]} for order in user_orders]
|
||||
return jsonify(user_orders), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
@orders_bp.route("/<int:order_id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_order(order_id):
|
||||
orders = OrdersOut()
|
||||
user_id = get_jwt_identity()
|
||||
order = orders.get_order_by_id(order_id)
|
||||
points = orders.get_order_points_by_order(order['id'])
|
||||
loading_points = []
|
||||
unloading_points = []
|
||||
for point in points:
|
||||
if point['point_type'] == 'loading':
|
||||
loading_points.append(point)
|
||||
else:
|
||||
unloading_points.append(point)
|
||||
order['loading_points'] = loading_points
|
||||
order['unloading_points'] = unloading_points
|
||||
if not order:
|
||||
return jsonify({"error": "Order not found"}), 404
|
||||
print(f'{order["user_id"]} {user_id}')
|
||||
print(f'{type(order["user_id"])} {type(user_id)}')
|
||||
if order["user_id"] != int(user_id):
|
||||
return jsonify({"error": "Unauthorized"}), 403
|
||||
return jsonify(order), 200
|
||||
|
||||
@orders_bp.route("/pdfs/<path:filename>", methods=["GET"])
|
||||
#@jwt_required()
|
||||
def serve_order_pdf(filename):
|
||||
pdf_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "generated_pdfs"))
|
||||
print(pdf_folder)
|
||||
print(filename)
|
||||
return send_from_directory(pdf_folder, filename, mimetype="application/pdf")
|
||||
|
||||
@orders_bp.route("/send-email/gmail", methods=["POST"])
|
||||
@jwt_required()
|
||||
def send_email_with_gmail():
|
||||
data = request.json
|
||||
try:
|
||||
to_email = data["to_email"]
|
||||
subject = data["subject"]
|
||||
body = data["body"]
|
||||
filename = data["filename"]
|
||||
attachment_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "uploads", filename))
|
||||
|
||||
send_gmail_with_attachment(to_email, subject, body, attachment_path)
|
||||
return jsonify({"message": "Email sent successfully using Gmail"}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
@orders_bp.route("/send-email/custom", methods=["POST"])
|
||||
@jwt_required()
|
||||
def send_email_with_custom_smtp():
|
||||
data = request.json
|
||||
try:
|
||||
to_email = data["to_email"]
|
||||
subject = data["subject"]
|
||||
body = data["body"]
|
||||
filename = data["filename"]
|
||||
smtp_host = data["smtp_host"]
|
||||
smtp_port = data["smtp_port"]
|
||||
smtp_user = data["smtp_user"]
|
||||
smtp_pass = data["smtp_pass"]
|
||||
|
||||
attachment_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "uploads", filename))
|
||||
|
||||
send_custom_email_with_attachment(to_email, subject, body, attachment_path, smtp_host, smtp_port, smtp_user, smtp_pass)
|
||||
return jsonify({"message": "Email sent successfully using custom SMTP"}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
@orders_bp.route("/cancel/<int:order_id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def cancel_order(order_id):
|
||||
try:
|
||||
orders = OrdersOut()
|
||||
order = orders.get_order_by_id(order_id)
|
||||
user_id = get_jwt_identity()
|
||||
pdf_name = f'order_{user_id}_{order['order_number']}.pdf'
|
||||
cancel_order_pdf(pdf_name)
|
||||
orders.cancel_order(order_id)
|
||||
return jsonify({"message": "The order was successfully canceled!"}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
163
transportmanager/server/routes/ouders_in.py
Normal file
163
transportmanager/server/routes/ouders_in.py
Normal file
@@ -0,0 +1,163 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from models.order_in import OrdersIn
|
||||
from models.transporters import Transporters
|
||||
from models.user import Users
|
||||
from datetime import datetime
|
||||
|
||||
orders_in_bp = Blueprint("orders_in", __name__, url_prefix="/orders_in")
|
||||
|
||||
@orders_in_bp.route("/", methods=["POST"])
|
||||
@jwt_required()
|
||||
def create_order_in_route():
|
||||
user_id = get_jwt_identity()
|
||||
orders = OrdersIn()
|
||||
incoming_data = request.json
|
||||
try:
|
||||
order_data = {
|
||||
'user_id': user_id,
|
||||
'client_id': incoming_data["client_id"],
|
||||
'received_price': incoming_data["received_price"],
|
||||
'order_number': incoming_data["order_number"],
|
||||
'created_at': datetime.now(),
|
||||
'ldb_quantity': incoming_data["ldb_quantity"],
|
||||
'kg_quantity': incoming_data["kg_quantity"],
|
||||
'track_reg_number': incoming_data["track_reg_number"],
|
||||
'trailer_reg_number': incoming_data["trailer_reg_number"],
|
||||
'products_description': incoming_data["products_description"],
|
||||
}
|
||||
order_id = orders.create_order(order_data)
|
||||
|
||||
for address in incoming_data["loading_addresses"]:
|
||||
data = {
|
||||
"order_id": order_id,
|
||||
"destination_id": address['loading_address_id'],
|
||||
"informatins": address['loading_informatins'],
|
||||
"point_data": address['loading_date'],
|
||||
"point_hour": address['loading_hour'],
|
||||
"point_type": "loading"
|
||||
}
|
||||
orders.create_order_point(data)
|
||||
|
||||
for address in incoming_data["unloading_addresses"]:
|
||||
data = {
|
||||
"order_id": order_id,
|
||||
"destination_id": address['unloading_address_id'],
|
||||
"informatins": address['unloading_informatins'],
|
||||
"point_data": address['unloading_date'],
|
||||
"point_hour": address['unloading_hour'],
|
||||
"point_type": "unloading"
|
||||
}
|
||||
orders.create_order_point(data)
|
||||
|
||||
return jsonify({"message": "Order in created", "order_id": order_id}), 201
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
@orders_in_bp.route("/<int:order_id>", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_order_route(order_id):
|
||||
orders = OrdersIn()
|
||||
data = request.json
|
||||
user_id = get_jwt_identity()
|
||||
order = orders.get_order_by_id(order_id)
|
||||
if not order:
|
||||
return jsonify({"error": "Order in not found"}), 404
|
||||
if str(order["user_id"]) != str(user_id):
|
||||
return jsonify({"error": "Unauthorized"}), 403
|
||||
|
||||
try:
|
||||
orders.update_order({
|
||||
"id":data.get("id", order['id']),
|
||||
"client_id": data.get("client_id", order["client_id"]),
|
||||
"received_price": data.get("received_price", order["received_price"]),
|
||||
"order_number": data.get("order_number", order["order_number"]),
|
||||
"ldb_quantity": data.get("ldb_quantity", order["ldb_quantity"]),
|
||||
"kg_quantity": data.get("kg_quantity", order["kg_quantity"]),
|
||||
"track_reg_number": data.get("track_reg_number", order["track_reg_number"]),
|
||||
"trailer_reg_number": data.get("trailer_reg_number", order["trailer_reg_number"]),
|
||||
"products_description": data.get("products_description", order["products_description"]),
|
||||
"user_id":user_id
|
||||
})
|
||||
|
||||
orders.delete_points_by_order_id(order_id)
|
||||
|
||||
for address in data["loading_addresses"]:
|
||||
loading_data = {
|
||||
"order_id": order_id,
|
||||
"destination_id": address['loading_address_id'],
|
||||
"informatins": address['loading_informatins'],
|
||||
"point_data": address['loading_date'],
|
||||
"point_hour": address['loading_hour'],
|
||||
"point_type": "loading"
|
||||
}
|
||||
orders.create_order_point(loading_data)
|
||||
|
||||
for address in data["unloading_addresses"]:
|
||||
unloading_data = {
|
||||
"order_id": order_id,
|
||||
"destination_id": address['unloading_address_id'],
|
||||
"informatins": address['unloading_informatins'],
|
||||
"point_data": address['unloading_date'],
|
||||
"point_hour": address['unloading_hour'],
|
||||
"point_type": "unloading"
|
||||
}
|
||||
orders.create_order_point(unloading_data)
|
||||
|
||||
return jsonify({"message": "Order updated"}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
@orders_in_bp.route("/<int:order_id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def delete_order_route(order_id):
|
||||
orders = OrdersIn()
|
||||
user_id = get_jwt_identity()
|
||||
order = orders.get_order_by_id(order_id)
|
||||
if not order:
|
||||
return jsonify({"error": "Order in not found"}), 404
|
||||
if str(order["user_id"]) != str(user_id):
|
||||
return jsonify({"error": "Unauthorized"}), 403
|
||||
|
||||
try:
|
||||
orders.delete_points_by_order_id(order_id)
|
||||
orders.delete_order(order_id)
|
||||
return jsonify({"message": "Order in deleted"}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
@orders_in_bp.route("/list", methods=["GET"])
|
||||
@jwt_required()
|
||||
def list_orders():
|
||||
orders = OrdersIn()
|
||||
user_id = get_jwt_identity()
|
||||
try:
|
||||
user_orders = orders.get_orders_by_user(user_id)
|
||||
#result = [{"id": order["id"], "order_number": order["order_number"]} for order in user_orders]
|
||||
return jsonify(user_orders), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
@orders_in_bp.route("/<int:order_id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_order(order_id):
|
||||
orders = OrdersIn()
|
||||
user_id = get_jwt_identity()
|
||||
order = orders.get_order_by_id(order_id)
|
||||
points = orders.get_order_points_by_order(order['id'])
|
||||
loading_points = []
|
||||
unloading_points = []
|
||||
for point in points:
|
||||
if point['point_type'] == 'loading':
|
||||
loading_points.append(point)
|
||||
else:
|
||||
unloading_points.append(point)
|
||||
order['loading_points'] = loading_points
|
||||
order['unloading_points'] = unloading_points
|
||||
if not order:
|
||||
return jsonify({"error": "Order not found"}), 404
|
||||
print(f'{order["user_id"]} {user_id}')
|
||||
print(f'{type(order["user_id"])} {type(user_id)}')
|
||||
if order["user_id"] != int(user_id):
|
||||
return jsonify({"error": "Unauthorized"}), 403
|
||||
return jsonify(order), 200
|
||||
144
transportmanager/server/routes/profile.py
Normal file
144
transportmanager/server/routes/profile.py
Normal file
@@ -0,0 +1,144 @@
|
||||
import os
|
||||
from werkzeug.utils import secure_filename
|
||||
from flask import current_app
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from models.user import Users
|
||||
|
||||
profile_bp = Blueprint("profile", __name__)
|
||||
|
||||
|
||||
@profile_bp.route("/", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_profile():
|
||||
user_id = get_jwt_identity()
|
||||
users = Users()
|
||||
user = users.get_user_by_id(user_id) # Plain SQL method returning dict or None
|
||||
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
return jsonify({
|
||||
"id": user["id"],
|
||||
"name": user["name"],
|
||||
"contact_name": user["contact_name"],
|
||||
"email": user["email"],
|
||||
"address": user["address"],
|
||||
"register_number": user["register_number"],
|
||||
"phone": user["phone"],
|
||||
"logo_filename": user["logo_filename"],
|
||||
"terms": user["terms"],
|
||||
"first_order_number": user["first_order_number"],
|
||||
"user_role": user["user_role"],
|
||||
"vat":user["vat"]
|
||||
})
|
||||
|
||||
|
||||
@profile_bp.route("/", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_profile():
|
||||
users = Users()
|
||||
user_id = get_jwt_identity()
|
||||
user = users.get_user_by_id(user_id)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
data = request.get_json()
|
||||
update_data = {
|
||||
"name": data.get("name", user["name"]),
|
||||
"contact_name": data.get("contact_name", user["contact_name"]),
|
||||
"email": data.get("email", user["email"]),
|
||||
"address": data.get("address", user["address"]),
|
||||
"register_number": data.get("register_number", user["register_number"]),
|
||||
"phone": data.get("phone", user["phone"]),
|
||||
"logo_filename": data.get("logo_filename", user["logo_filename"]),
|
||||
"terms": data.get("terms", user["terms"]),
|
||||
"first_order_number": data.get("first_order_number", user["first_order_number"]),
|
||||
"user_id": user_id,
|
||||
"vat":data.get("vat", user["vat"]),
|
||||
}
|
||||
users.update_user(update_data)
|
||||
|
||||
return jsonify({"message": "Profile updated successfully"})
|
||||
|
||||
|
||||
@profile_bp.route("/logo", methods=["POST"])
|
||||
@jwt_required()
|
||||
def upload_logo():
|
||||
users = Users()
|
||||
if 'logo' not in request.files:
|
||||
return jsonify({"error": "Logo file is required"}), 400
|
||||
|
||||
file = request.files['logo']
|
||||
if file.filename == '':
|
||||
return jsonify({"error": "No selected file"}), 400
|
||||
|
||||
filename = secure_filename(file.filename)
|
||||
upload_dir = os.path.join(current_app.root_path, '..', 'instance', 'logos')
|
||||
os.makedirs(upload_dir, exist_ok=True)
|
||||
|
||||
filepath = os.path.join(upload_dir, filename)
|
||||
file.save(filepath)
|
||||
|
||||
user_id = get_jwt_identity()
|
||||
user = users.get_user_by_id(user_id)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
# Update the logo filename in DB
|
||||
users.update_user_logo(user_id, {"logo_filename": filename})
|
||||
|
||||
return jsonify({"message": "Logo uploaded", "filename": filename}), 200
|
||||
|
||||
@profile_bp.route('/email')
|
||||
@jwt_required()
|
||||
def get_email_credentials():
|
||||
user_id = get_jwt_identity()
|
||||
users = Users()
|
||||
|
||||
credentials = users.get_email_credentials(user_id)
|
||||
if not credentials:
|
||||
return jsonify({"error": "Credentials not found"}), 404
|
||||
|
||||
return jsonify({
|
||||
'id': credentials['id'],
|
||||
'user_id': credentials['user_id'],
|
||||
'smtp_host': credentials['smtp_host'],
|
||||
'smtp_port': credentials['smtp_port'],
|
||||
'smtp_user': credentials['smtp_user'],
|
||||
'created_at': credentials['created_at']
|
||||
}), 200
|
||||
|
||||
@profile_bp.route('/email', methods=["POST"])
|
||||
@jwt_required()
|
||||
def insert_email_credentials():
|
||||
users = Users()
|
||||
user_id = get_jwt_identity()
|
||||
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"error": "Credentials not found"}), 404
|
||||
smtp_host = data['smtp_host']
|
||||
smtp_port = data['smtp_port']
|
||||
smtp_user = data['smtp_user']
|
||||
|
||||
users.insert_email_credentials(user_id, smtp_host, smtp_port, smtp_user)
|
||||
|
||||
return jsonify({"message": "Credentials inserted successfully"}), 200
|
||||
|
||||
@profile_bp.route('/email', methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_email_credentials():
|
||||
users = Users()
|
||||
user_id = get_jwt_identity()
|
||||
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"error": "Credentials not found"}), 404
|
||||
smtp_host = data['smtp_host']
|
||||
smtp_port = data['smtp_port']
|
||||
smtp_user = data['smtp_user']
|
||||
|
||||
users.update_email_credentials(user_id, smtp_host, smtp_port, smtp_user)
|
||||
|
||||
return jsonify({"message": "Credentials updated successfully"}), 200
|
||||
42
transportmanager/server/routes/report.py
Normal file
42
transportmanager/server/routes/report.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from models.order_out import OrdersOut # Your plain SQL model
|
||||
from datetime import datetime
|
||||
|
||||
report_bp = Blueprint("report", __name__, url_prefix="/report")
|
||||
|
||||
@report_bp.route("/profit", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_profit_report():
|
||||
try:
|
||||
user_id = get_jwt_identity()
|
||||
# Get filters from query params
|
||||
date_from = request.args.get("date_from")
|
||||
date_to = request.args.get("date_to")
|
||||
client_id = request.args.get("client_id")
|
||||
transporter_id = request.args.get("transporter_id")
|
||||
|
||||
# Use the plain SQL method that returns filtered orders list
|
||||
filters = {
|
||||
"user_id": user_id,
|
||||
"date_from": date_from,
|
||||
"date_to": date_to,
|
||||
"client_id": client_id,
|
||||
"transporter_id": transporter_id
|
||||
}
|
||||
|
||||
orders = OrdersOut.get_filtered_orders(filters) # Implement this method in your model
|
||||
|
||||
total_received = sum(float(o.get("price_received", 0) or 0) for o in orders)
|
||||
total_paid = sum(float(o.get("price_paid", 0) or 0) for o in orders)
|
||||
profit = total_received - total_paid
|
||||
|
||||
return jsonify({
|
||||
"total_received": total_received,
|
||||
"total_paid": total_paid,
|
||||
"profit": profit,
|
||||
"orders_count": len(orders)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
65
transportmanager/server/routes/subscription.py
Normal file
65
transportmanager/server/routes/subscription.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from datetime import datetime, timedelta
|
||||
from models.subscription import Subscription
|
||||
from models.user import Users
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
subscription_bp = Blueprint("subscription", __name__, url_prefix="/subscription")
|
||||
|
||||
@subscription_bp.route("/", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_subscription():
|
||||
user_id = get_jwt_identity()
|
||||
subscription_model = Subscription()
|
||||
subscriptions = subscription_model.get_by_user_id(user_id)
|
||||
return jsonify(subscriptions), 200
|
||||
|
||||
@subscription_bp.route("/first_2_months", methods=["POST"])
|
||||
@jwt_required()
|
||||
def first_2_months_subscription():
|
||||
user_id = get_jwt_identity()
|
||||
users_model = Users()
|
||||
user = users_model.get_user_by_id(user_id)
|
||||
|
||||
subscription_model = Subscription()
|
||||
existing_sub = subscription_model.get_first_2_months_subscription_for_register_number(user["register_number"])
|
||||
|
||||
if existing_sub:
|
||||
return jsonify({"error": "First 2 months subscription already used for this company."}), 400
|
||||
|
||||
start_date = datetime.now()
|
||||
end_date = start_date + timedelta(days=60)
|
||||
subscription_model.create(user_id, "first_2_months", start_date.isoformat(), end_date.isoformat(), user["register_number"])
|
||||
|
||||
return jsonify({"message": "First 2 months subscription created."}), 201
|
||||
|
||||
@subscription_bp.route("/one_month", methods=["POST"])
|
||||
@jwt_required()
|
||||
def one_month_subscription():
|
||||
user_id = get_jwt_identity()
|
||||
start_date = datetime.now()
|
||||
end_date = start_date + timedelta(days=30)
|
||||
|
||||
users_model = Users()
|
||||
user = users_model.get_user_by_id(user_id)
|
||||
|
||||
subscription_model = Subscription()
|
||||
subscription_model.create(user_id, "monthly", start_date.isoformat(), end_date.isoformat(), user["register_number"])
|
||||
|
||||
return jsonify({"message": "1 month subscription created."}), 201
|
||||
|
||||
@subscription_bp.route("/one_year", methods=["POST"])
|
||||
@jwt_required()
|
||||
def one_year_subscription():
|
||||
user_id = get_jwt_identity()
|
||||
start_date = datetime.now()
|
||||
end_date = start_date + timedelta(days=365)
|
||||
|
||||
users_model = Users()
|
||||
user = users_model.get_user_by_id(user_id)
|
||||
|
||||
subscription_model = Subscription()
|
||||
subscription_model.create(user_id, "yearly", start_date.isoformat(), end_date.isoformat(), user["register_number"])
|
||||
|
||||
return jsonify({"message": "1 year subscription created."}), 201
|
||||
74
transportmanager/server/routes/transporters.py
Normal file
74
transportmanager/server/routes/transporters.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from models.transporters import Transporters
|
||||
|
||||
transporters_bp = Blueprint("transporters", __name__, url_prefix="/transporters")
|
||||
|
||||
@transporters_bp.route("/", methods=["GET"])
|
||||
@jwt_required()
|
||||
def list_transporters():
|
||||
user_id = get_jwt_identity()
|
||||
transporters_db = Transporters()
|
||||
transporters = transporters_db.get_all_transporters_by_user(user_id)
|
||||
return jsonify(transporters), 200
|
||||
|
||||
@transporters_bp.route("/", methods=["POST"])
|
||||
@jwt_required()
|
||||
def create_transporter():
|
||||
transporters_db = Transporters()
|
||||
data = request.get_json()
|
||||
user_id = get_jwt_identity()
|
||||
transporter_id = transporters_db.create_transporter(
|
||||
user_id=user_id,
|
||||
name=data.get("name"),
|
||||
address=data.get("address"),
|
||||
register_number=data.get("register_number"),
|
||||
contact_person=data.get("contact_person"),
|
||||
phone=data.get("phone"),
|
||||
email=data.get("email"),
|
||||
vat = data.get("vat")
|
||||
)
|
||||
transporter = transporters_db.get_transporter_by_id(transporter_id)
|
||||
return jsonify(transporter), 201
|
||||
|
||||
@transporters_bp.route("/<int:transporter_id>", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_transporter(transporter_id):
|
||||
transporters_db = Transporters()
|
||||
user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
transporter = transporters_db.get_transporter_by_id(transporter_id)
|
||||
if not transporter:
|
||||
return jsonify({"error": "Transporter not found"}), 404
|
||||
transporters_db.update_transporter(
|
||||
transporter_id=transporter_id,
|
||||
name=data.get("name"),
|
||||
address=data.get("address"),
|
||||
register_number=data.get("register_number"),
|
||||
contact_person=data.get("contact_person"),
|
||||
phone=data.get("phone"),
|
||||
email=data.get("email"),
|
||||
vat=data.get("vat")
|
||||
)
|
||||
updated_transporter = transporters_db.get_transporter_by_id(transporter_id)
|
||||
return jsonify(updated_transporter), 200
|
||||
|
||||
@transporters_bp.route("/<int:transporter_id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def delete_transporter(transporter_id):
|
||||
transporters_db = Transporters()
|
||||
user_id = get_jwt_identity()
|
||||
transporter = transporters_db.get_transporter_by_id(transporter_id)
|
||||
if not transporter:
|
||||
return jsonify({"error": "Transporter not found"}), 404
|
||||
transporters_db.delete_transporter(transporter_id)
|
||||
return jsonify({"message": "Transporter deleted"}), 200
|
||||
|
||||
@transporters_bp.route("/<int:transporter_id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_transporter(transporter_id):
|
||||
transporters_db = Transporters()
|
||||
transporter = transporters_db.get_transporter_by_id(transporter_id)
|
||||
if not transporter:
|
||||
return jsonify({"error": "Transporter not found"}), 404
|
||||
return jsonify(transporter), 200
|
||||
146
transportmanager/server/schema.sql
Normal file
146
transportmanager/server/schema.sql
Normal file
@@ -0,0 +1,146 @@
|
||||
-- Users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
contact_name TEXT,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
phone TEXT,
|
||||
register_number TEXT,
|
||||
vat TEXT,
|
||||
address TEXT,
|
||||
logo_filename TEXT,
|
||||
terms TEXT,
|
||||
first_order_number INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
otp_code TEXT,
|
||||
otp_expiration TIMESTAMPTZ,
|
||||
user_role TEXT NOT NULL DEFAULT 'user' CHECK (user_role IN ('user', 'admin'))
|
||||
);
|
||||
|
||||
-- Clients table
|
||||
CREATE TABLE IF NOT EXISTS clients (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
register_number TEXT,
|
||||
contact_person TEXT,
|
||||
phone TEXT,
|
||||
email TEXT,
|
||||
vat TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Transporters table
|
||||
CREATE TABLE IF NOT EXISTS transporters (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
register_number TEXT,
|
||||
contact_person TEXT,
|
||||
phone TEXT,
|
||||
email TEXT,
|
||||
vat TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Destinations table
|
||||
CREATE TABLE IF NOT EXISTS destinations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
latitude TEXT,
|
||||
longitude TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Orders out table
|
||||
CREATE TABLE IF NOT EXISTS orders_out (
|
||||
id SERIAL PRIMARY KEY,
|
||||
order_number TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
client_id INTEGER NOT NULL,
|
||||
transporter_id INTEGER NOT NULL,
|
||||
products_description TEXT,
|
||||
ldb_quantity DOUBLE PRECISION,
|
||||
kg_quantity DOUBLE PRECISION,
|
||||
track_reg_number TEXT,
|
||||
trailer_reg_number TEXT,
|
||||
received_price DOUBLE PRECISION,
|
||||
paid_price DOUBLE PRECISION,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
order_status TEXT NOT NULL DEFAULT 'active' CHECK (order_status IN ('active', 'inactive', 'cancelled')),
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(client_id) REFERENCES clients(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(transporter_id) REFERENCES transporters(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Orders in table
|
||||
CREATE TABLE IF NOT EXISTS orders_in (
|
||||
id SERIAL PRIMARY KEY,
|
||||
order_number TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
client_id INTEGER NOT NULL,
|
||||
products_description TEXT,
|
||||
ldb_quantity DOUBLE PRECISION,
|
||||
kg_quantity DOUBLE PRECISION,
|
||||
track_reg_number TEXT,
|
||||
trailer_reg_number TEXT,
|
||||
received_price DOUBLE PRECISION,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(client_id) REFERENCES clients(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Order In Points (loading/unloading) table
|
||||
CREATE TABLE IF NOT EXISTS order_in_points (
|
||||
id SERIAL PRIMARY KEY,
|
||||
order_id INTEGER NOT NULL,
|
||||
destination_id INTEGER NOT NULL,
|
||||
informatins TEXT,
|
||||
point_data TEXT,
|
||||
point_hour TEXT,
|
||||
point_type TEXT NOT NULL CHECK (point_type IN ('loading', 'unloading')),
|
||||
FOREIGN KEY(order_id) REFERENCES orders_in(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Order In Points (loading/unloading) table
|
||||
CREATE TABLE IF NOT EXISTS order_out_points (
|
||||
id SERIAL PRIMARY KEY,
|
||||
order_id INTEGER NOT NULL,
|
||||
destination_id INTEGER NOT NULL,
|
||||
informatins TEXT,
|
||||
point_data TEXT,
|
||||
point_hour TEXT,
|
||||
point_type TEXT NOT NULL CHECK (point_type IN ('loading', 'unloading')),
|
||||
FOREIGN KEY(order_id) REFERENCES orders_out(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Subscriptions table
|
||||
CREATE TABLE IF NOT EXISTS subscriptions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
plan TEXT NOT NULL CHECK (plan IN ('first_2_months', 'monthly', 'yearly')),
|
||||
start_date TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
end_date TIMESTAMPTZ NOT NULL,
|
||||
status TEXT NOT NULL CHECK (status IN ('active', 'expired', 'cancelled')),
|
||||
register_number TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS email (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
smtp_host TEXT,
|
||||
smtp_port TEXT,
|
||||
smtp_user TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
146
transportmanager/server/schema_sqlite.sql
Normal file
146
transportmanager/server/schema_sqlite.sql
Normal file
@@ -0,0 +1,146 @@
|
||||
-- Users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
contact_name TEXT,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
phone TEXT,
|
||||
register_number TEXT,
|
||||
vat TEXT,
|
||||
address TEXT,
|
||||
logo_filename TEXT,
|
||||
terms TEXT,
|
||||
first_order_number INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
otp_code TEXT,
|
||||
otp_expiration TIMESTAMPTZ,
|
||||
user_role TEXT NOT NULL DEFAULT 'user' CHECK (user_role IN ('user', 'admin'))
|
||||
);
|
||||
|
||||
-- Clients table
|
||||
CREATE TABLE IF NOT EXISTS clients (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
register_number TEXT,
|
||||
contact_person TEXT,
|
||||
phone TEXT,
|
||||
email TEXT,
|
||||
vat TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Transporters table
|
||||
CREATE TABLE IF NOT EXISTS transporters (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
register_number TEXT,
|
||||
contact_person TEXT,
|
||||
phone TEXT,
|
||||
email TEXT,
|
||||
vat TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Destinations table
|
||||
CREATE TABLE IF NOT EXISTS destinations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
latitude TEXT,
|
||||
longitude TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Orders out table
|
||||
CREATE TABLE IF NOT EXISTS orders_out (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_number TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
client_id INTEGER NOT NULL,
|
||||
transporter_id INTEGER NOT NULL,
|
||||
products_description TEXT,
|
||||
ldb_quantity DOUBLE PRECISION,
|
||||
kg_quantity DOUBLE PRECISION,
|
||||
track_reg_number TEXT,
|
||||
trailer_reg_number TEXT,
|
||||
received_price DOUBLE PRECISION,
|
||||
paid_price DOUBLE PRECISION,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
order_status TEXT NOT NULL DEFAULT 'active' CHECK (order_status IN ('active', 'inactive', 'cancelled')),
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(client_id) REFERENCES clients(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(transporter_id) REFERENCES transporters(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Orders in table
|
||||
CREATE TABLE IF NOT EXISTS orders_in (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_number TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
client_id INTEGER NOT NULL,
|
||||
products_description TEXT,
|
||||
ldb_quantity DOUBLE PRECISION,
|
||||
kg_quantity DOUBLE PRECISION,
|
||||
track_reg_number TEXT,
|
||||
trailer_reg_number TEXT,
|
||||
received_price DOUBLE PRECISION,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(client_id) REFERENCES clients(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Order In Points (loading/unloading) table
|
||||
CREATE TABLE IF NOT EXISTS order_in_points (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_id INTEGER NOT NULL,
|
||||
destination_id INTEGER NOT NULL,
|
||||
informatins TEXT,
|
||||
point_data TEXT,
|
||||
point_hour TEXT,
|
||||
point_type TEXT NOT NULL CHECK (point_type IN ('loading', 'unloading')),
|
||||
FOREIGN KEY(order_id) REFERENCES orders_in(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Order In Points (loading/unloading) table
|
||||
CREATE TABLE IF NOT EXISTS order_out_points (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_id INTEGER NOT NULL,
|
||||
destination_id INTEGER NOT NULL,
|
||||
informatins TEXT,
|
||||
point_data TEXT,
|
||||
point_hour TEXT,
|
||||
point_type TEXT NOT NULL CHECK (point_type IN ('loading', 'unloading')),
|
||||
FOREIGN KEY(order_id) REFERENCES orders_out(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Subscriptions table
|
||||
CREATE TABLE IF NOT EXISTS subscriptions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
plan TEXT NOT NULL CHECK (plan IN ('first_2_months', 'monthly', 'yearly')),
|
||||
start_date TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
end_date TIMESTAMPTZ NOT NULL,
|
||||
status TEXT NOT NULL CHECK (status IN ('active', 'expired', 'cancelled')),
|
||||
register_number TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS email (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
smtp_host TEXT,
|
||||
smtp_port TEXT,
|
||||
smtp_user TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
62
transportmanager/server/utils/cancel_order.py
Normal file
62
transportmanager/server/utils/cancel_order.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import os
|
||||
from PyPDF2 import PdfReader, PdfWriter
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.pagesizes import A4
|
||||
|
||||
GENERATED_FOLDER = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "generated_pdfs")
|
||||
)
|
||||
|
||||
def create_watermark_pdf(watermark_path, text="CANCELLED"):
|
||||
c = canvas.Canvas(watermark_path, pagesize=A4)
|
||||
c.setFont("Helvetica-Bold", 80)
|
||||
c.setFillGray(0.6) # Light gray text
|
||||
c.saveState()
|
||||
c.translate(300, 400)
|
||||
c.rotate(45)
|
||||
c.drawCentredString(0, 0, text)
|
||||
c.restoreState()
|
||||
c.save()
|
||||
|
||||
def apply_watermark(input_pdf_path, output_pdf_path, watermark_pdf_path):
|
||||
reader = PdfReader(input_pdf_path)
|
||||
watermark = PdfReader(watermark_pdf_path).pages[0]
|
||||
writer = PdfWriter()
|
||||
|
||||
for page in reader.pages:
|
||||
page.merge_page(watermark)
|
||||
writer.add_page(page)
|
||||
|
||||
with open(output_pdf_path, "wb") as f:
|
||||
writer.write(f)
|
||||
|
||||
def cancel_order_pdf(order_filename):
|
||||
# File paths
|
||||
input_pdf_path = os.path.join(GENERATED_FOLDER, order_filename)
|
||||
output_pdf_path = input_pdf_path
|
||||
watermark_pdf_path = os.path.join(GENERATED_FOLDER, "temp_watermark.pdf")
|
||||
print(watermark_pdf_path)
|
||||
# Check if file exists
|
||||
if not os.path.isfile(input_pdf_path):
|
||||
raise FileNotFoundError(f"Original order PDF not found: {input_pdf_path}")
|
||||
|
||||
# Create watermark and apply it
|
||||
create_watermark_pdf(watermark_pdf_path, text="CANCELLED")
|
||||
apply_watermark(input_pdf_path, output_pdf_path, watermark_pdf_path)
|
||||
|
||||
# Optionally remove temp watermark
|
||||
os.remove(watermark_pdf_path)
|
||||
|
||||
return output_pdf_path
|
||||
|
||||
# Example usage:
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python cancel_order.py <order_filename>")
|
||||
else:
|
||||
try:
|
||||
result_path = cancel_order_pdf(sys.argv[1])
|
||||
print(f"Cancelled PDF created: {result_path}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
139
transportmanager/server/utils/email.py
Normal file
139
transportmanager/server/utils/email.py
Normal file
@@ -0,0 +1,139 @@
|
||||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
import os
|
||||
|
||||
def send_email(to_email, subject, body):
|
||||
smtp_host = os.environ.get("SMTP_HOST")
|
||||
smtp_port = int(os.environ.get("SMTP_PORT", 587))
|
||||
smtp_user = os.environ.get("SMTP_USER")
|
||||
smtp_pass = os.environ.get("SMTP_PASS")
|
||||
sender_email = os.environ.get("SMTP_FROM", smtp_user)
|
||||
|
||||
if not all([smtp_host, smtp_port, smtp_user, smtp_pass]):
|
||||
raise ValueError("SMTP config incomplete in environment variables.")
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = to_email
|
||||
msg.set_content(body)
|
||||
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(smtp_user, smtp_pass)
|
||||
server.send_message(msg)
|
||||
|
||||
|
||||
# Send email with attachment
|
||||
def send_email_with_attachment(to_email, subject, body, attachment_path):
|
||||
smtp_host = os.environ.get("SMTP_HOST")
|
||||
smtp_port = int(os.environ.get("SMTP_PORT", 587))
|
||||
smtp_user = os.environ.get("SMTP_USER")
|
||||
smtp_pass = os.environ.get("SMTP_PASS")
|
||||
sender_email = os.environ.get("SMTP_FROM", smtp_user)
|
||||
|
||||
if not all([smtp_host, smtp_port, smtp_user, smtp_pass]):
|
||||
raise ValueError("SMTP config incomplete in environment variables.")
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = to_email
|
||||
msg.set_content(body)
|
||||
|
||||
if attachment_path and os.path.isfile(attachment_path):
|
||||
with open(attachment_path, "rb") as f:
|
||||
file_data = f.read()
|
||||
file_name = os.path.basename(attachment_path)
|
||||
msg.add_attachment(file_data, maintype="application", subtype="pdf", filename=file_name)
|
||||
else:
|
||||
raise FileNotFoundError(f"Attachment file not found: {attachment_path}")
|
||||
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(smtp_user, smtp_pass)
|
||||
server.send_message(msg)
|
||||
|
||||
|
||||
# Send email using Gmail directly
|
||||
def send_gmail(to_email, subject, body):
|
||||
smtp_host = "smtp.gmail.com"
|
||||
smtp_port = 587
|
||||
smtp_user = 'macamete.robert@gmail.com'
|
||||
smtp_pass = 'advx yqlv jkaa czvr'
|
||||
sender_email = 'macamete.robert@gmail.com'
|
||||
|
||||
if not all([smtp_user, smtp_pass]):
|
||||
raise ValueError("GMAIL_USER and GMAIL_PASS must be set in environment variables.")
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = to_email
|
||||
msg.set_content(body)
|
||||
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(smtp_user, smtp_pass)
|
||||
server.send_message(msg)
|
||||
|
||||
|
||||
# Send email with attachment using Gmail directly
|
||||
def send_gmail_with_attachment(to_email, subject, body, attachment_path):
|
||||
smtp_host = "smtp.gmail.com"
|
||||
smtp_port = 587
|
||||
smtp_user = os.environ.get("GMAIL_USER")
|
||||
smtp_pass = os.environ.get("GMAIL_PASS")
|
||||
sender_email = smtp_user
|
||||
|
||||
if not all([smtp_user, smtp_pass]):
|
||||
raise ValueError("GMAIL_USER and GMAIL_PASS must be set in environment variables.")
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = to_email
|
||||
msg.set_content(body)
|
||||
|
||||
if attachment_path and os.path.isfile(attachment_path):
|
||||
with open(attachment_path, "rb") as f:
|
||||
file_data = f.read()
|
||||
file_name = os.path.basename(attachment_path)
|
||||
msg.add_attachment(file_data, maintype="application", subtype="pdf", filename=file_name)
|
||||
else:
|
||||
raise FileNotFoundError(f"Attachment file not found: {attachment_path}")
|
||||
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(smtp_user, smtp_pass)
|
||||
server.send_message(msg)
|
||||
|
||||
# Send email with attachment
|
||||
def send_custom_email_with_attachment(to_email, subject, body, attachment_path, SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS):
|
||||
smtp_host = SMTP_HOST
|
||||
smtp_port = int(SMTP_PORT)
|
||||
smtp_user = SMTP_USER
|
||||
smtp_pass = SMTP_PASS
|
||||
sender_email = smtp_user
|
||||
|
||||
if not all([smtp_host, smtp_port, smtp_user, smtp_pass]):
|
||||
raise ValueError("SMTP config incomplete in environment variables.")
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = to_email
|
||||
msg.set_content(body)
|
||||
|
||||
if attachment_path and os.path.isfile(attachment_path):
|
||||
with open(attachment_path, "rb") as f:
|
||||
file_data = f.read()
|
||||
file_name = os.path.basename(attachment_path)
|
||||
msg.add_attachment(file_data, maintype="application", subtype="pdf", filename=file_name)
|
||||
else:
|
||||
raise FileNotFoundError(f"Attachment file not found: {attachment_path}")
|
||||
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(smtp_user, smtp_pass)
|
||||
server.send_message(msg)
|
||||
28
transportmanager/server/utils/maps.py
Normal file
28
transportmanager/server/utils/maps.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from geopy.geocoders import Nominatim
|
||||
|
||||
class AdressCoordinates:
|
||||
def __init__(self, address):
|
||||
self.addess = address
|
||||
|
||||
def open_Maps_by_address(self):
|
||||
address = self.addess
|
||||
if not address:
|
||||
return
|
||||
|
||||
#try:
|
||||
geolocator = Nominatim(user_agent="flet_Maps_app")
|
||||
location = geolocator.geocode(address)
|
||||
print(location)
|
||||
if location:
|
||||
latitude = location.latitude
|
||||
longitude = location.longitude
|
||||
Maps_url = f"https://www.google.com/maps/search/?api=1&query={latitude},{longitude}"
|
||||
|
||||
return {
|
||||
'latitude' : latitude,
|
||||
'longitude' : longitude,
|
||||
'Maps_url': Maps_url
|
||||
}
|
||||
|
||||
#except Exception as ex:
|
||||
# print(ex)
|
||||
199
transportmanager/server/utils/pdf.py
Normal file
199
transportmanager/server/utils/pdf.py
Normal file
@@ -0,0 +1,199 @@
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.platypus import Table, TableStyle, Paragraph, SimpleDocTemplate, Spacer, Image
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
import io
|
||||
import os
|
||||
import logging
|
||||
|
||||
# --- helpers ---------------------------------------------------------------
|
||||
|
||||
def _resolve_logo_path(logo_path: str | None) -> str | None:
|
||||
"""Return a readable logo path or None. Tries multiple fallbacks.
|
||||
This avoids crashes when a caller passes a path that exists only in the
|
||||
client container (e.g., '/client/assets/...') while we're running in the
|
||||
server container on Fly.
|
||||
"""
|
||||
here = os.path.dirname(__file__)
|
||||
project_root = os.path.abspath(os.path.join(here, "..", ".."))
|
||||
|
||||
candidates = []
|
||||
|
||||
# 1) if caller provided a path, try it as-is
|
||||
if logo_path:
|
||||
candidates.append(logo_path)
|
||||
# also try without a leading slash inside our image tree
|
||||
stripped = logo_path.lstrip("/\\")
|
||||
candidates.append(os.path.join(project_root, stripped))
|
||||
|
||||
# 2) Known locations in this repo layout
|
||||
candidates.append(os.path.join(project_root, "client", "assets", "images", "truck_logo_black.png"))
|
||||
candidates.append(os.path.join(project_root, "assets", "images", "truck_logo_black.png"))
|
||||
|
||||
# 3) Allow override via env
|
||||
env_path = os.getenv("DEFAULT_LOGO_PATH")
|
||||
if env_path:
|
||||
candidates.insert(0, env_path)
|
||||
|
||||
for p in candidates:
|
||||
try:
|
||||
if p and os.path.isfile(p):
|
||||
return p
|
||||
except Exception:
|
||||
# some paths may be malformed on certain platforms
|
||||
continue
|
||||
return None
|
||||
|
||||
def generate_order_pdf(order, user_data, transporter_data, logo_path, save_to_disk=True):
|
||||
#print(order)
|
||||
#print(f'user data: {user_data}')
|
||||
if 'address' in user_data:
|
||||
address = user_data.get("address")
|
||||
if address:
|
||||
address = address.replace(" %",", ")
|
||||
else:
|
||||
address = ''
|
||||
else:
|
||||
address=''
|
||||
buffer = io.BytesIO()
|
||||
doc = SimpleDocTemplate(buffer, pagesize=A4)
|
||||
elements = []
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Resolve logo path robustly across local/dev and Fly
|
||||
logo_path = _resolve_logo_path(logo_path)
|
||||
|
||||
# Prepare texts
|
||||
user_text = f"""<b>{user_data.get("name", "")}</b><br/>
|
||||
{user_data.get("register_number", "")}<br/>
|
||||
{user_data.get("vat", "")}<br/>
|
||||
{address}<br/>
|
||||
{""}<br/>
|
||||
{user_data.get("contact_name", "")}<br/>
|
||||
{user_data.get("phone", "")}<br/>
|
||||
{user_data.get("email", "")}
|
||||
"""
|
||||
transporter_text = f"""<b>{transporter_data.get("name", "")}</b><br/>
|
||||
{transporter_data.get("contact_person", "")}<br/>
|
||||
{transporter_data.get("phone", "")}<br/>
|
||||
{transporter_data.get("email", "")}
|
||||
"""
|
||||
|
||||
# Logo (centered), tolerate missing file
|
||||
logo = None
|
||||
if logo_path:
|
||||
try:
|
||||
logo = Image(logo_path, width=120, mask='auto', height=60)
|
||||
logo.hAlign = 'CENTER'
|
||||
except Exception as e:
|
||||
logging.warning("PDF: failed to load logo at %s: %s", logo_path, e)
|
||||
logo = None
|
||||
|
||||
# Top section: transporter - logo - user
|
||||
top_section = Table([
|
||||
[
|
||||
Paragraph(transporter_text, styles['Normal']),
|
||||
logo,
|
||||
Paragraph(user_text, styles['Normal'])
|
||||
]
|
||||
], colWidths=[200, 150, 200])
|
||||
top_section.setStyle(TableStyle([
|
||||
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
||||
("ALIGN", (0, 0), (0, 0), "LEFT"),
|
||||
("ALIGN", (1, 0), (1, 0), "CENTER"),
|
||||
("ALIGN", (2, 0), (2, 0), "RIGHT"),
|
||||
("LEFTPADDING", (0, 0), (0, 0), 20),
|
||||
]))
|
||||
elements.append(top_section)
|
||||
elements.append(Spacer(1, 12))
|
||||
|
||||
# Order number and current date (centered, vertically stacked)
|
||||
from datetime import datetime
|
||||
header_info = Table([
|
||||
[Paragraph(f"<b>Loading Order</b>: {order['order_number']}", styles["Normal"])],
|
||||
[Paragraph(f"Date: {datetime.now().strftime('%d/%m/%Y')}", styles["Normal"])]
|
||||
], colWidths=[450])
|
||||
header_info.setStyle(TableStyle([
|
||||
("ALIGN", (0, 0), (-1, -1), "CENTER")
|
||||
]))
|
||||
elements.append(header_info)
|
||||
elements.append(Spacer(1, 12))
|
||||
|
||||
# Order Summary Table
|
||||
elements.append(Paragraph("Summary", styles['Heading3']))
|
||||
summary_data = [
|
||||
["Details", Paragraph("Values", styles["Normal"])],
|
||||
["Truck Reg. No.", Paragraph(str(order["track_reg_number"]), styles["Normal"])],
|
||||
["Trailer Reg. No.", Paragraph(str(order["trailer_reg_number"]), styles["Normal"])],
|
||||
["Product Description", Paragraph(str(order["products_description"]), styles["Normal"])],
|
||||
["LDM", Paragraph(str(order["ldb_quantity"]), styles["Normal"])],
|
||||
["KG", Paragraph(str(order["kg_quantity"]), styles["Normal"])],
|
||||
["Price", Paragraph(str(order["paid_price"]), styles["Normal"])],
|
||||
]
|
||||
summary_table = Table(summary_data, colWidths=[150, 350])
|
||||
summary_table.setStyle(TableStyle([
|
||||
("GRID", (0, 0), (-1, -1), 0.5, colors.black),
|
||||
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
|
||||
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
||||
("WORDWRAP", (0, 0), (-1, -1), "CJK"),
|
||||
("ALIGN", (0, 0), (-1, -1), "LEFT"),
|
||||
]))
|
||||
elements.append(summary_table)
|
||||
elements.append(Spacer(1, 24))
|
||||
|
||||
# Loading Points
|
||||
elements.append(Paragraph("Loading Sequence", styles['Heading3']))
|
||||
loading_data = [[Paragraph("Address", styles["Normal"]), Paragraph("Date & Hour", styles["Normal"]), Paragraph("Instructions", styles["Normal"])]]
|
||||
for l in order["loading_addresses"]:
|
||||
loading_data.append([
|
||||
Paragraph(f"{l["loading_address_name"]}: {l["loading_address"]}", styles["Normal"]),
|
||||
Paragraph(f"{l['loading_date']} {l['loading_hour']}", styles["Normal"]),
|
||||
Paragraph(str(l["loading_informatins"]), styles["Normal"])
|
||||
])
|
||||
loading_table = Table(loading_data, colWidths=[200, 100, 200])
|
||||
loading_table.setStyle(TableStyle([
|
||||
("GRID", (0, 0), (-1, -1), 0.5, colors.black),
|
||||
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
("WORDWRAP", (0, 0), (-1, -1), "CJK"),
|
||||
("ALIGN", (0, 0), (-1, -1), "LEFT"),
|
||||
]))
|
||||
elements.append(loading_table)
|
||||
elements.append(Spacer(1, 24))
|
||||
|
||||
# Unloading Points
|
||||
elements.append(Paragraph("Unloading Sequence", styles['Heading3']))
|
||||
unloading_data = [[Paragraph("Address", styles["Normal"]), Paragraph("Date & Hour", styles["Normal"]), Paragraph("Instructions", styles["Normal"])]]
|
||||
for u in order["unloading_addresses"]:
|
||||
unloading_data.append([
|
||||
Paragraph(f"{u["unloading_address_name"]}: {u["unloading_address"]}", styles["Normal"]),
|
||||
Paragraph(f"{u['unloading_date']} {u['unloading_hour']}", styles["Normal"]),
|
||||
Paragraph(str(u["unloading_informatins"]), styles["Normal"])
|
||||
])
|
||||
unloading_table = Table(unloading_data, colWidths=[200, 100, 200])
|
||||
unloading_table.setStyle(TableStyle([
|
||||
("GRID", (0, 0), (-1, -1), 0.5, colors.black),
|
||||
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
("WORDWRAP", (0, 0), (-1, -1), "CJK"),
|
||||
("ALIGN", (0, 0), (-1, -1), "LEFT"),
|
||||
]))
|
||||
elements.append(unloading_table)
|
||||
|
||||
elements.append(Spacer(1, 24))
|
||||
|
||||
elements.append(Paragraph("Terms and Conditions", styles["Heading3"]),)
|
||||
|
||||
elements.append(
|
||||
Paragraph(str(order["terms"]), styles["Normal"])
|
||||
)
|
||||
|
||||
doc.build(elements)
|
||||
buffer.seek(0)
|
||||
if save_to_disk:
|
||||
save_path=f"generated_pdfs/order_{order['user_id']}_{order['order_number']}.pdf"
|
||||
with open(save_path, "wb") as f:
|
||||
f.write(buffer.getvalue())
|
||||
|
||||
return buffer
|
||||
Reference in New Issue
Block a user