major claude changes

This commit is contained in:
2026-01-28 21:55:10 -05:00
parent 3f311980db
commit 2dbd3ea53f
41 changed files with 1235 additions and 278 deletions

View File

@@ -6,6 +6,8 @@ ENV PYTHONUNBUFFERED=1
ENV MODE="DEVELOPMENT" ENV MODE="DEVELOPMENT"
ENV FLASK_APP=app.py
RUN mkdir -p /app RUN mkdir -p /app
COPY requirements.txt /app COPY requirements.txt /app

View File

@@ -8,6 +8,8 @@ ENV TZ=America/New_York
ENV MODE="LOCAL" ENV MODE="LOCAL"
ENV FLASK_APP=app.py
RUN mkdir -p /app RUN mkdir -p /app
COPY requirements.txt /app COPY requirements.txt /app

View File

@@ -17,7 +17,8 @@ COPY . .
# Tell Docker that the container listens on port 80 # Tell Docker that the container listens on port 80
EXPOSE 80 EXPOSE 80
# Run the application using Gunicorn # Set Flask app for CLI commands
# This command runs the Flask app. 'app:app' means "in the file named app.py, run the variable named app". ENV FLASK_APP=app.py
# Adjust if your main file or Flask app variable is named differently.
CMD ["gunicorn", "--bind", "0.0.0.0:80", "app:app"] # Run database migrations and then the application
CMD flask db upgrade && gunicorn --bind 0.0.0.0:80 app:app

2
app.py
View File

@@ -6,7 +6,7 @@ HOST = '0.0.0.0'
if __name__ == '__main__': if __name__ == '__main__':
app.run( app.run(
debug=True, debug=app.config.get('DEBUG', False),
host=HOST, host=HOST,
port=PORT, port=PORT,
use_reloader=True use_reloader=True

View File

@@ -1,20 +1,56 @@
# coding=utf-8 # coding=utf-8
import logging
import sys
from flask import Flask, jsonify from flask import Flask, jsonify
from flask_bcrypt import Bcrypt from flask_bcrypt import Bcrypt
from flask_cors import CORS from flask_cors import CORS
from flask_marshmallow import Marshmallow from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_session import Session from flask_session import Session
from flask_login import LoginManager from flask_login import LoginManager
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from werkzeug.routing import BaseConverter from werkzeug.routing import BaseConverter
from flask_mail import Mail from flask_mail import Mail
from config import load_config from config import load_config
import re import re
from sqlalchemy import text
ApplicationConfig = load_config() ApplicationConfig = load_config()
# Configure logging
def setup_logging():
"""Configure structured logging for the application."""
log_level = logging.DEBUG if ApplicationConfig.CURRENT_SETTINGS != 'PRODUCTION' else logging.INFO
# Create formatter
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Configure root logger
root_logger = logging.getLogger()
root_logger.setLevel(log_level)
# Remove existing handlers to avoid duplicates
root_logger.handlers.clear()
# Console handler
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(log_level)
console_handler.setFormatter(formatter)
root_logger.addHandler(console_handler)
# Reduce noise from third-party libraries
logging.getLogger('werkzeug').setLevel(logging.WARNING)
logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING)
return logging.getLogger('eamco_office_api')
logger = setup_logging()
app = Flask(__name__, app = Flask(__name__,
static_url_path='', static_url_path='',
static_folder='static', static_folder='static',
@@ -55,6 +91,7 @@ app.config['SECRET_KEY'] = ApplicationConfig.SECRET_KEY
session.configure(bind=ApplicationConfig.SQLALCHEMY_DATABASE_URI) session.configure(bind=ApplicationConfig.SQLALCHEMY_DATABASE_URI)
db = SQLAlchemy(app) db = SQLAlchemy(app)
migrate = Migrate(app, db)
bcrypt = Bcrypt(app) bcrypt = Bcrypt(app)
app.config['SESSION_SQLALCHEMY'] = db app.config['SESSION_SQLALCHEMY'] = db
server_session = Session(app) server_session = Session(app)
@@ -93,15 +130,8 @@ def load_user_from_request(request):
# If no valid key is found in header or args, return None # If no valid key is found in header or args, return None
return None return None
# api_main = {
# "origins": [ApplicationConfig.ORIGIN_URL], cors = CORS(app,
# "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
# "allow_headers": ['Authorization', 'application/json', 'authorization', 'Content-Type',
# 'Access-Control-Allow-Headers', 'Origin,Accept',
# 'X-Requested-With', 'Content-Type', 'Access-Control-Request-cMethod',
# 'Access-Control-Request-Headers']
# }
cors = CORS(app,
supports_credentials=True, supports_credentials=True,
resources={r"/*": {"origins": ApplicationConfig.CORS_ALLOWED_ORIGINS} resources={r"/*": {"origins": ApplicationConfig.CORS_ALLOWED_ORIGINS}
}) })
@@ -217,8 +247,32 @@ from .service import service as service_blueprint
app.register_blueprint(service_blueprint, url_prefix='/service') app.register_blueprint(service_blueprint, url_prefix='/service')
def check_db_connection():
"""
Test database connectivity.
"""
try:
db.session.execute(text("SELECT 1"))
return True
except Exception:
return False
with app.app_context(): with app.app_context():
db.configure_mappers() db.configure_mappers()
db.create_all()
db.session.commit() db.session.commit()
# Startup logging
logger.info("🚀 eamco_office_api STARTING")
mode = ApplicationConfig.CURRENT_SETTINGS.upper()
if mode in ['DEVELOPMENT', 'DEV']:
logger.info("🤖🤖🤖🤖🤖 Mode: Development 🤖🤖🤖🤖🤖")
elif mode in ['PRODUCTION', 'PROD']:
logger.info("💀💀💀💀💀💀💀💀💀💀 ⚠️ WARNING PRODUCTION 💀💀💀💀💀💀💀💀💀💀")
logger.info(f"DB: {ApplicationConfig.SQLALCHEMY_DATABASE_URI[:30]}...")
logger.info(f"CORS: {len(ApplicationConfig.CORS_ALLOWED_ORIGINS)} origins configured")
# Test database connection
if check_db_connection():
logger.info("DB Connection: ✅ OK")
else:
logger.info("DB Connection: ❌ FAILED")

View File

@@ -1,3 +1,4 @@
import logging
from flask import request, jsonify from flask import request, jsonify
from flask_login import current_user, logout_user, login_user, login_required from flask_login import current_user, logout_user, login_user, login_required
from app.admin import admin from app.admin import admin
@@ -7,12 +8,17 @@ from app.classes.pricing import (
Pricing_Oil_Oil, Pricing_Oil_Oil,
Pricing_Oil_Oil_schema) Pricing_Oil_Oil_schema)
from app.classes.admin import Admin_Company, Admin_Company_schema, Call from app.classes.admin import Admin_Company, Admin_Company_schema, Call
from app.common.decorators import admin_required
logger = logging.getLogger(__name__)
@admin.route("/oil/create", methods=["POST"]) @admin.route("/oil/create", methods=["POST"])
@admin_required
def create_oil_price(): def create_oil_price():
""" """
Changes the price for oil deliveries Changes the price for oil deliveries
""" """
logger.info("POST /admin/oil/create - Creating new oil price")
now = datetime.utcnow() now = datetime.utcnow()
price_from_supplier = request.json["price_from_supplier"] price_from_supplier = request.json["price_from_supplier"]
price_for_customer = request.json["price_for_customer"] price_for_customer = request.json["price_for_customer"]
@@ -50,10 +56,12 @@ def create_oil_price():
@admin.route("/oil/get", methods=["GET"]) @admin.route("/oil/get", methods=["GET"])
@admin_required
def get_oil_price(): def get_oil_price():
""" """
gets oil prices gets oil prices
""" """
logger.info("GET /admin/oil/get - Fetching current oil prices")
get_oil_prices = (db.session get_oil_prices = (db.session
.query(Pricing_Oil_Oil) .query(Pricing_Oil_Oil)
.order_by(Pricing_Oil_Oil.date.desc()) .order_by(Pricing_Oil_Oil.date.desc())
@@ -63,7 +71,9 @@ def get_oil_price():
@admin.route("/company/<int:company_id>", methods=["GET"]) @admin.route("/company/<int:company_id>", methods=["GET"])
@admin_required
def get_company(company_id): def get_company(company_id):
logger.info(f"GET /admin/company/{company_id} - Fetching company data")
get_data_company = (db.session get_data_company = (db.session
.query(Admin_Company) .query(Admin_Company)
.first()) .first())
@@ -72,11 +82,12 @@ def get_company(company_id):
return jsonify(company_schema.dump(get_data_company)) return jsonify(company_schema.dump(get_data_company))
@admin.route("/voip_routing", methods=["GET"]) @admin.route("/voip_routing", methods=["GET"])
@admin_required
def get_voip_routing(): def get_voip_routing():
""" """
Gets the current VOIP routing (latest Call record's current_phone) Gets the current VOIP routing (latest Call record's current_phone)
""" """
logger.info("GET /admin/voip_routing - Fetching current VoIP routing")
latest_call = (db.session latest_call = (db.session
.query(Call) .query(Call)
.order_by(Call.created_at.desc()) .order_by(Call.created_at.desc())

View File

@@ -1,3 +1,4 @@
import logging
from flask import request, jsonify from flask import request, jsonify
from flask_login import current_user, logout_user, login_required from flask_login import current_user, logout_user, login_required
from app.auth import auth from app.auth import auth
@@ -5,9 +6,12 @@ from app import db, bcrypt
from datetime import datetime from datetime import datetime
from uuid import uuid4 from uuid import uuid4
from app.classes.auth import Auth_User from app.classes.auth import Auth_User
from app.classes.employee import Employee_Employee from app.classes.employee import Employee_Employee
from app.schemas import LoginSchema, RegisterSchema, ChangePasswordSchema, validate_request
import re import re
logger = logging.getLogger(__name__)
@auth.route("/whoami", methods=["GET"]) @auth.route("/whoami", methods=["GET"])
def check_session(): def check_session():
""" """
@@ -25,7 +29,7 @@ def check_session():
user = db.session.query(Auth_User).filter(Auth_User.api_key == api_key).first() user = db.session.query(Auth_User).filter(Auth_User.api_key == api_key).first()
if not user: if not user:
print("no user found with that api key") logger.warning("Authentication failed: no user found with provided API key")
return jsonify({"ok": False, "error": "Invalid token"}), 401 return jsonify({"ok": False, "error": "Invalid token"}), 401
# Now, build the complete response with both user and employee data. # Now, build the complete response with both user and employee data.
@@ -73,9 +77,11 @@ def logout():
@auth.route("/login", methods=["POST"]) @auth.route("/login", methods=["POST"])
@validate_request(LoginSchema)
def login(): def login():
username = request.json["username"] data = request.validated_data
password = request.json["password"] username = data["username"]
password = data["password"]
user = db.session.query(Auth_User).filter_by(username=username).first() user = db.session.query(Auth_User).filter_by(username=username).first()
@@ -103,15 +109,17 @@ def login():
}), 200 }), 200
@auth.route("/register", methods=["POST"]) @auth.route("/register", methods=["POST"])
@validate_request(RegisterSchema)
def register_user(): def register_user():
""" """
Main post function to register a user Main post function to register a user
""" """
data = request.validated_data
now = datetime.utcnow() now = datetime.utcnow()
username = request.json["username"] username = data["username"]
email = request.json["email"] email = data["email"]
password = request.json["password"] password = data["password"]
part_one_code = uuid4().hex part_one_code = uuid4().hex
part_two_code = uuid4().hex part_two_code = uuid4().hex
@@ -172,6 +180,7 @@ def register_user():
@auth.route('/change-password', methods=['POST']) @auth.route('/change-password', methods=['POST'])
@validate_request(ChangePasswordSchema)
def change_password(): def change_password():
auth_header = request.headers.get('Authorization') auth_header = request.headers.get('Authorization')
if not auth_header: if not auth_header:
@@ -184,8 +193,9 @@ def change_password():
if not user: if not user:
return jsonify({"error": "Invalid token"}), 401 return jsonify({"error": "Invalid token"}), 401
new_password = request.json["new_password"] data = request.validated_data
new_password_confirm = request.json["password_confirm"] new_password = data["new_password"]
new_password_confirm = data["password_confirm"]
if str(new_password) != str(new_password_confirm): if str(new_password) != str(new_password_confirm):
return jsonify({"error": "Error: Incorrect Passwords"}), 200 return jsonify({"error": "Error: Incorrect Passwords"}), 200
@@ -214,7 +224,7 @@ def admin_change_password():
if not user: if not user:
return jsonify({"error": "Invalid token"}), 401 return jsonify({"error": "Invalid token"}), 401
if user.admin_role != 0: if user.admin_role == 0:
return jsonify({"error": "Admin access required"}), 403 return jsonify({"error": "Admin access required"}), 403
employee_id = request.json.get("employee_id") employee_id = request.json.get("employee_id")

View File

@@ -1,5 +1,5 @@
from flask_login import current_user from flask_login import current_user
from flask import abort from flask import abort, jsonify
from functools import wraps from functools import wraps
@@ -14,3 +14,12 @@ def login_required(f):
return f(*args, **kwargs) return f(*args, **kwargs)
return decorated_function return decorated_function
def admin_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_authenticated or not current_user.admin_role:
return jsonify({"error": "Admin access required"}), 403
return f(*args, **kwargs)
return decorated_function

46
app/constants.py Normal file
View File

@@ -0,0 +1,46 @@
"""
EAMCO Office API Constants
This file contains all status code constants used throughout the application
to eliminate magic numbers and improve code maintainability.
"""
class DeliveryStatus:
"""Delivery status codes"""
WAITING = 0
CANCELLED = 1
OUT_FOR_DELIVERY = 2
TOMORROW = 3
PARTIAL_DELIVERY = 4
ISSUE = 5
UNKNOWN = 6
PENDING_PAYMENT = 9
FINALIZED = 10
DELIVERED = 11 # New: Replaces previous use of 1 for delivered
class PaymentStatus:
"""Payment status codes"""
UNPAID = 0
PRE_AUTHORIZED = 1
PROCESSING = 2
PAID = 3
FAILED = 4
class AutoStatus:
"""Automatic delivery status codes"""
DEFAULT = 0
WILL_CALL = 1
READY_FOR_FINALIZATION = 3
class TransactionStatus:
"""Transaction status codes"""
APPROVED = 0
DECLINED = 1
class CustomerAutomaticStatus:
"""Customer automatic delivery status"""
WILL_CALL = 0
AUTOMATIC = 1
# Additional constants can be added here as needed
# For example: ServiceStatus, UserRoles, etc.

View File

@@ -1,8 +1,12 @@
import logging
from flask import request, jsonify from flask import request, jsonify
from flask_login import login_required from flask_login import login_required
from geopy.geocoders import Nominatim from geopy.geocoders import Nominatim
from app.customer import customer from app.customer import customer
from app import db from app import db
from app.common.decorators import login_required as common_login_required
logger = logging.getLogger(__name__)
from datetime import datetime from datetime import datetime
from app.classes.cards import Card_Card from app.classes.cards import Card_Card
from app.classes.customer import \ from app.classes.customer import \
@@ -16,6 +20,7 @@ from app.classes.service import Service_Parts
from app.classes.admin import Admin_Company from app.classes.admin import Admin_Company
from app.classes.auto import Auto_Delivery,Auto_Delivery_schema from app.classes.auto import Auto_Delivery,Auto_Delivery_schema
from app.classes.stats_customer import Stats_Customer from app.classes.stats_customer import Stats_Customer
from app.schemas import CreateCustomerSchema, UpdateCustomerSchema, validate_request
import string import string
import random import random
@@ -33,8 +38,9 @@ def generate_random_number_string(length):
@customer.route("/all", methods=["GET"]) @customer.route("/all", methods=["GET"])
@common_login_required
def all_customers_around(): def all_customers_around():
logger.info("GET /customer/all - Fetching all customers")
customer_list = db.session \ customer_list = db.session \
.query(Customer_Customer) \ .query(Customer_Customer) \
.all() .all()
@@ -43,12 +49,12 @@ def all_customers_around():
@customer.route("/all/<int:page>", methods=["GET"]) @customer.route("/all/<int:page>", methods=["GET"])
@common_login_required
def all_customers(page): def all_customers(page):
""" """
pagination all customers pagination all customers
""" """
logger.info(f"GET /customer/all/{page} - Fetching customers page {page}")
per_page_amount = 100 per_page_amount = 100
if page is None: if page is None:
offset_limit = 0 offset_limit = 0
@@ -67,9 +73,11 @@ def all_customers(page):
@customer.route("/<int:customer_id>", methods=["GET"]) @customer.route("/<int:customer_id>", methods=["GET"])
@common_login_required
def get_a_customer(customer_id): def get_a_customer(customer_id):
""" """
""" """
logger.info(f"GET /customer/{customer_id} - Fetching customer")
get_customer = (db.session get_customer = (db.session
.query(Customer_Customer) .query(Customer_Customer)
.filter(Customer_Customer.id == customer_id) .filter(Customer_Customer.id == customer_id)
@@ -79,10 +87,12 @@ def get_a_customer(customer_id):
@customer.route("/description/<int:customer_id>", methods=["GET"]) @customer.route("/description/<int:customer_id>", methods=["GET"])
@common_login_required
def get_a_customer_description(customer_id): def get_a_customer_description(customer_id):
""" """
""" """
logger.info(f"GET /customer/description/{customer_id} - Fetching customer description")
get_customer_description = (db.session get_customer_description = (db.session
.query(Customer_Description) .query(Customer_Description)
.filter(Customer_Description.customer_id == customer_id) .filter(Customer_Description.customer_id == customer_id)
@@ -112,10 +122,12 @@ def get_a_customer_description(customer_id):
@customer.route("/tank/<int:customer_id>", methods=["GET"]) @customer.route("/tank/<int:customer_id>", methods=["GET"])
@common_login_required
def get_a_customer_tank(customer_id): def get_a_customer_tank(customer_id):
""" """
""" """
logger.info(f"GET /customer/tank/{customer_id} - Fetching customer tank info")
get_customer_tank = (db.session get_customer_tank = (db.session
.query(Customer_Tank_Inspection) .query(Customer_Tank_Inspection)
.filter(Customer_Tank_Inspection.customer_id == customer_id) .filter(Customer_Tank_Inspection.customer_id == customer_id)
@@ -142,56 +154,50 @@ def get_a_customer_tank(customer_id):
@customer.route("/create", methods=["POST"]) @customer.route("/create", methods=["POST"])
@validate_request(CreateCustomerSchema)
@common_login_required
def create_customer(): def create_customer():
""" """
Create a new customer with validated input data.
""" """
logger.info("POST /customer/create - Creating new customer")
# Get validated data from request
data = request.validated_data
now = datetime.utcnow() now = datetime.utcnow()
get_company = (db.session get_company = (db.session
.query(Admin_Company) .query(Admin_Company)
.filter(Admin_Company.id == 1) .filter(Admin_Company.id == 1)
.first()) .first())
get_company = (db.session
.query(Admin_Company)
.filter(Admin_Company.id == 1)
.first())
random_string = generate_random_number_string(6) random_string = generate_random_number_string(6)
made_account_number = str(get_company.account_prefix) + '-' + str(random_string) made_account_number = str(get_company.account_prefix) + '-' + str(random_string)
see_if_exists = (db.session.query(Customer_Customer).filter(Customer_Customer.account_number == made_account_number).first()) see_if_exists = (db.session.query(Customer_Customer).filter(Customer_Customer.account_number == made_account_number).first())
if see_if_exists is not None: if see_if_exists is not None:
random_string = generate_random_number_string(10) random_string = generate_random_number_string(10)
made_account_number = str(get_company.account_prefix) + '-' + str(random_string) made_account_number = str(get_company.account_prefix) + '-' + str(random_string)
see_if_exists = (db.session.query(Customer_Customer).filter(Customer_Customer.account_number == made_account_number).first()) see_if_exists = (db.session.query(Customer_Customer).filter(Customer_Customer.account_number == made_account_number).first())
if see_if_exists is not None: if see_if_exists is not None:
random_string = generate_random_number_string(10) random_string = generate_random_number_string(10)
made_account_number = str(get_company.account_prefix) + '-' + str(random_string) made_account_number = str(get_company.account_prefix) + '-' + str(random_string)
see_if_exists = (db.session.query(Customer_Customer).filter(Customer_Customer.account_number == made_account_number).first())
response_customer_last_name = request.json["customer_last_name"] # Use validated data instead of direct request.json access
response_customer_first_name = request.json["customer_first_name"] response_customer_last_name = data["customer_last_name"]
response_customer_town = request.json["customer_town"] response_customer_first_name = data["customer_first_name"]
response_customer_state = request.json["customer_state"] response_customer_town = data["customer_town"]
response_customer_zip = request.json["customer_zip"] response_customer_state = data["customer_state"]
response_customer_email = request.json["customer_email"] response_customer_zip = str(data["customer_zip"])
response_customer_home_type = request.json["customer_home_type"] response_customer_email = data.get("customer_email")
customer_phone_number = request.json["customer_phone_number"] response_customer_home_type = data["customer_home_type"]
customer_address = request.json["customer_address"] customer_phone_number = data.get("customer_phone_number")
customer_apt = request.json["customer_apt"] customer_address = data["customer_address"]
customer_description_msg = request.json["customer_description"] customer_apt = data.get("customer_apt")
customer_description_msg = data.get("customer_description")
int_customer_home_type = int(response_customer_home_type) int_customer_home_type = int(response_customer_home_type)
response_customer_zip = str(response_customer_zip)
response_customer_state = int(response_customer_state) response_customer_state = int(response_customer_state)
@@ -204,51 +210,18 @@ def create_customer():
else: else:
the_state = 'MA' the_state = 'MA'
# if response_customer_town == 0:
# the_town = 'Auburn'
# elif response_customer_town == 1:
# the_town = 'Charlton'
# elif response_customer_town == 2:
# the_town = 'Cherry Valley'
# elif response_customer_town == 3:
# the_town = 'Dudley'
# elif response_customer_town == 4:
# the_town = 'Grafton'
# elif response_customer_town == 5:
# the_town = 'Leicester'
# elif response_customer_town == 6:
# the_town = 'Millbury'
# elif response_customer_town == 7:
# the_town = 'N Oxford'
# elif response_customer_town == 8:
# the_town = 'Oxford'
# elif response_customer_town == 9:
# the_town = 'Rochdale'
# elif response_customer_town == 10:
# the_town = 'Shrewsbury'
# elif response_customer_town == 11:
# the_town = 'Southbridge'
# elif response_customer_town == 12:
# the_town = 'Spencer'
# elif response_customer_town == 13:
# the_town = 'Sturbridge'
# elif response_customer_town == 14:
# the_town = 'Webster'
# elif response_customer_town == 15:
# the_town = 'Worcester'
# else:
# the_town = 'NA'
geolocator = Nominatim(user_agent="auburnoil") geolocator = Nominatim(user_agent="auburnoil")
address_string = customer_address + ' ' + response_customer_town+ ' ' + the_state address_string = customer_address + ' ' + response_customer_town+ ' ' + the_state
try: try:
location = geolocator.geocode(address_string) location = geolocator.geocode(address_string)
user_lat =location.latitude user_lat = location.latitude
user_long = location.longitude user_long = location.longitude
cor_ad = True cor_ad = True
except: except Exception:
user_lat =None user_lat = None
user_long = None user_long = None
cor_ad = False cor_ad = False
@@ -323,70 +296,95 @@ def create_customer():
@customer.route("/edit/<int:customer_id>", methods=["PUT"]) @customer.route("/edit/<int:customer_id>", methods=["PUT"])
@login_required @login_required
@validate_request(UpdateCustomerSchema)
def edit_customer(customer_id): def edit_customer(customer_id):
""" """
""" """
logger.info(f"PUT /customer/edit/{customer_id} - Editing customer")
get_customer = (db.session get_customer = (db.session
.query(Customer_Customer) .query(Customer_Customer)
.filter(Customer_Customer.id == customer_id) .filter(Customer_Customer.id == customer_id)
.first()) .first())
if not get_customer:
return jsonify({"error": "Customer not found"}), 404
get_customer_description = (db.session get_customer_description = (db.session
.query(Customer_Description) .query(Customer_Description)
.filter(Customer_Description.customer_id == customer_id) .filter(Customer_Description.customer_id == customer_id)
.first()) .first())
response_customer_last_name = request.json["customer_last_name"]
response_customer_first_name = request.json["customer_first_name"]
response_customer_town = request.json["customer_town"]
response_customer_state = request.json["customer_state"]
response_customer_zip = request.json["customer_zip"]
response_customer_phone_number = request.json["customer_phone_number"]
response_customer_email = request.json["customer_email"]
response_customer_home_type = request.json["customer_home_type"]
response_customer_address = request.json["customer_address"]
response_customer_apt = request.json["customer_apt"]
response_customer_description = request.json["customer_description"]
response_customer_fill_location = request.json["customer_fill_location"] data = request.validated_data
response_customer_last_name = data.get("customer_last_name")
response_customer_first_name = data.get("customer_first_name")
response_customer_town = data.get("customer_town")
response_customer_state = data.get("customer_state")
response_customer_zip = data.get("customer_zip")
response_customer_phone_number = data.get("customer_phone_number")
response_customer_email = data.get("customer_email")
response_customer_home_type = data.get("customer_home_type")
response_customer_address = data.get("customer_address")
response_customer_apt = data.get("customer_apt")
response_customer_description = data.get("customer_description")
response_customer_fill_location = data.get("customer_fill_location")
# Update description if provided
if get_customer_description is not None: if get_customer_description is not None:
get_customer_description.description = response_customer_description if response_customer_description is not None:
get_customer_description.fill_location = response_customer_fill_location get_customer_description.description = response_customer_description
if response_customer_fill_location is not None:
get_customer_description.fill_location = response_customer_fill_location
db.session.add(get_customer_description) db.session.add(get_customer_description)
if response_customer_state == 0: # Only update fields that were provided in the request
the_state = 'MA' if response_customer_last_name is not None:
elif response_customer_state == 1: get_customer.customer_last_name = response_customer_last_name
the_state = 'RI' if response_customer_first_name is not None:
elif response_customer_state == 1: get_customer.customer_first_name = response_customer_first_name
the_state = 'NH' if response_customer_town is not None:
else: get_customer.customer_town = response_customer_town
the_state = 'MA' if response_customer_state is not None:
get_customer.customer_state = response_customer_state
if response_customer_zip is not None:
get_customer.customer_zip = response_customer_zip
if response_customer_phone_number is not None:
get_customer.customer_phone_number = response_customer_phone_number
if response_customer_email is not None:
get_customer.customer_email = response_customer_email
if response_customer_home_type is not None:
get_customer.customer_home_type = response_customer_home_type
if response_customer_apt is not None:
get_customer.customer_apt = response_customer_apt
geolocator = Nominatim(user_agent="auburnoil") # Re-geocode if address fields changed
address_string = response_customer_address + ' ' + response_customer_town+ ' ' + the_state if response_customer_address is not None or response_customer_town is not None or response_customer_state is not None:
try: get_customer.customer_address = response_customer_address if response_customer_address is not None else get_customer.customer_address
location = geolocator.geocode(address_string, timeout=10)
get_customer.customer_latitude = location.latitude
get_customer.customer_longitude = location.longitude
cor_ad = True
except:
get_customer.customer_latitude = None
get_customer.customer_longitude = None
cor_ad = False
get_customer.customer_address = response_customer_address state_code = response_customer_state if response_customer_state is not None else get_customer.customer_state
get_customer.customer_home_type = response_customer_home_type if state_code == 0:
get_customer.customer_phone_number = response_customer_phone_number the_state = 'MA'
get_customer.customer_last_name = response_customer_last_name elif state_code == 1:
get_customer.customer_first_name = response_customer_first_name the_state = 'RI'
get_customer.customer_town = response_customer_town elif state_code == 2:
get_customer.customer_state = response_customer_state the_state = 'NH'
get_customer.customer_zip = response_customer_zip else:
get_customer.customer_email = response_customer_email the_state = 'MA'
get_customer.customer_apt = response_customer_apt
get_customer.correct_address = cor_ad town = response_customer_town if response_customer_town is not None else get_customer.customer_town
address = get_customer.customer_address
geolocator = Nominatim(user_agent="auburnoil")
address_string = address + ' ' + town + ' ' + the_state
try:
location = geolocator.geocode(address_string, timeout=10)
get_customer.customer_latitude = location.latitude
get_customer.customer_longitude = location.longitude
get_customer.correct_address = True
except Exception:
get_customer.customer_latitude = None
get_customer.customer_longitude = None
get_customer.correct_address = False
db.session.add(get_customer) db.session.add(get_customer)
@@ -407,6 +405,7 @@ def edit_customer(customer_id):
def delete_customer(customer_id): def delete_customer(customer_id):
""" """
""" """
logger.info(f"DELETE /customer/delete/{customer_id} - Deleting customer")
get_customer = (db.session get_customer = (db.session
.query(Customer_Customer) .query(Customer_Customer)
.filter(Customer_Customer.id == customer_id) .filter(Customer_Customer.id == customer_id)
@@ -436,6 +435,7 @@ def delete_customer(customer_id):
def customer_count(): def customer_count():
""" """
""" """
logger.info("GET /customer/count - Getting customer count")
get_customer = (db.session get_customer = (db.session
.query(Customer_Customer) .query(Customer_Customer)
.count()) .count())
@@ -451,6 +451,7 @@ def customer_count():
def customer_automatic_status(customer_id): def customer_automatic_status(customer_id):
""" """
""" """
logger.info(f"GET /customer/automatic/status/{customer_id} - Checking auto delivery status")
get_customer = (db.session get_customer = (db.session
.query(Customer_Customer) .query(Customer_Customer)
.filter(Customer_Customer.id == customer_id) .filter(Customer_Customer.id == customer_id)
@@ -475,7 +476,7 @@ def get_all_automatic_deliveries():
""" """
Get all automatic deliveries for the table. Get all automatic deliveries for the table.
""" """
logger.info("GET /customer/automatic/deliveries - Fetching all auto deliveries")
try: try:
deliveries = Auto_Delivery.query.all() deliveries = Auto_Delivery.query.all()
schema = Auto_Delivery_schema(many=True) schema = Auto_Delivery_schema(many=True)
@@ -491,6 +492,7 @@ def get_all_automatic_deliveries():
def customer_automatic_assignment(customer_id): def customer_automatic_assignment(customer_id):
""" """
""" """
logger.info(f"GET /customer/automatic/assign/{customer_id} - Toggling auto delivery assignment")
get_customer = (db.session get_customer = (db.session
.query(Customer_Customer) .query(Customer_Customer)
.filter(Customer_Customer.id == customer_id) .filter(Customer_Customer.id == customer_id)
@@ -578,6 +580,7 @@ def edit_customer_tank(customer_id):
""" """
Safely edits or creates tank and description details for a customer. Safely edits or creates tank and description details for a customer.
""" """
logger.info(f"PUT /customer/edit/tank/{customer_id} - Editing customer tank info")
get_customer = db.session.query(Customer_Customer).filter(Customer_Customer.id == customer_id).one_or_none() get_customer = db.session.query(Customer_Customer).filter(Customer_Customer.id == customer_id).one_or_none()
if not get_customer: if not get_customer:
return jsonify({"ok": False, "error": "Customer not found"}), 404 return jsonify({"ok": False, "error": "Customer not found"}), 404

View File

@@ -1,8 +1,12 @@
import logging
from flask import request, jsonify from flask import request, jsonify
from flask_login import current_user from flask_login import current_user
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from app.delivery import delivery from app.delivery import delivery
from app import db from app import db
from app.common.decorators import login_required as common_login_required
logger = logging.getLogger(__name__)
from sqlalchemy import or_ from sqlalchemy import or_
from app.classes.customer import (Customer_Customer) from app.classes.customer import (Customer_Customer)
from app.classes.delivery import (Delivery_Delivery, from app.classes.delivery import (Delivery_Delivery,
@@ -20,6 +24,7 @@ from app.classes.auto import Tickets_Auto_Delivery, Tickets_Auto_Delivery_schema
# This endpoint is fine, but I've added some comments for clarity. # This endpoint is fine, but I've added some comments for clarity.
@delivery.route("/updatestatus", methods=["GET"]) @delivery.route("/updatestatus", methods=["GET"])
@common_login_required
def move_deliveries(): def move_deliveries():
""" """
Batch updates delivery statuses based on their expected delivery date relative to today. Batch updates delivery statuses based on their expected delivery date relative to today.
@@ -28,6 +33,7 @@ def move_deliveries():
- Future deliveries -> "Waiting" (0) - Future deliveries -> "Waiting" (0)
- Past-due deliveries -> "Pending" (9) - Past-due deliveries -> "Pending" (9)
""" """
logger.info("GET /delivery/updatestatus - Batch updating delivery statuses")
counter = 0 counter = 0
today = date.today() today = date.today()
tomorrow = today + timedelta(days=1) tomorrow = today + timedelta(days=1)
@@ -84,10 +90,12 @@ def move_deliveries():
@delivery.route("/<int:delivery_id>", methods=["GET"]) @delivery.route("/<int:delivery_id>", methods=["GET"])
@common_login_required
def get_a_delivery(delivery_id): def get_a_delivery(delivery_id):
""" """
Get a single delivery's details. Get a single delivery's details.
""" """
logger.info(f"GET /delivery/{delivery_id} - Fetching delivery")
get_delivery = db.session.query(Delivery_Delivery).filter(Delivery_Delivery.id == delivery_id).first() get_delivery = db.session.query(Delivery_Delivery).filter(Delivery_Delivery.id == delivery_id).first()
if not get_delivery: if not get_delivery:
return jsonify({"ok": False, "error": "Delivery not found"}), 404 return jsonify({"ok": False, "error": "Delivery not found"}), 404
@@ -101,8 +109,9 @@ def get_a_delivery(delivery_id):
@delivery.route("/past1/<int:customer_id>", methods=["GET"]) @delivery.route("/past1/<int:customer_id>", methods=["GET"])
@common_login_required
def get_customer_past_delivery1(customer_id): def get_customer_past_delivery1(customer_id):
logger.info(f"GET /delivery/past1/{customer_id} - Fetching customer past deliveries (first 5)")
get_customer_past_delivery = (db.session get_customer_past_delivery = (db.session
.query(Delivery_Delivery) .query(Delivery_Delivery)
.filter(Delivery_Delivery.customer_id == customer_id) .filter(Delivery_Delivery.customer_id == customer_id)
@@ -114,8 +123,9 @@ def get_customer_past_delivery1(customer_id):
@delivery.route("/past2/<int:customer_id>", methods=["GET"]) @delivery.route("/past2/<int:customer_id>", methods=["GET"])
@common_login_required
def get_customer_past_delivery2(customer_id): def get_customer_past_delivery2(customer_id):
logger.info(f"GET /delivery/past2/{customer_id} - Fetching customer past deliveries (next 5)")
get_customer_past_delivery = (db.session get_customer_past_delivery = (db.session
.query(Delivery_Delivery) .query(Delivery_Delivery)
.filter(Delivery_Delivery.customer_id == customer_id) .filter(Delivery_Delivery.customer_id == customer_id)
@@ -127,7 +137,9 @@ def get_customer_past_delivery2(customer_id):
return jsonify(delivery_schema.dump(get_customer_past_delivery)) return jsonify(delivery_schema.dump(get_customer_past_delivery))
@delivery.route("/auto/<int:customer_id>", methods=["GET"]) @delivery.route("/auto/<int:customer_id>", methods=["GET"])
@common_login_required
def get_customer_auto_delivery(customer_id): def get_customer_auto_delivery(customer_id):
logger.info(f"GET /delivery/auto/{customer_id} - Fetching customer auto deliveries")
get_customer_past_delivery = (db.session get_customer_past_delivery = (db.session
.query(Tickets_Auto_Delivery) .query(Tickets_Auto_Delivery)
.filter(Tickets_Auto_Delivery.customer_id == customer_id) .filter(Tickets_Auto_Delivery.customer_id == customer_id)
@@ -140,6 +152,7 @@ def get_customer_auto_delivery(customer_id):
@delivery.route("/order/<int:delivery_id>", methods=["GET"]) @delivery.route("/order/<int:delivery_id>", methods=["GET"])
@common_login_required
def get_a_specific_delivery(delivery_id): def get_a_specific_delivery(delivery_id):
""" """
Get a single delivery by its ID. Get a single delivery by its ID.
@@ -155,6 +168,7 @@ def get_a_specific_delivery(delivery_id):
@delivery.route("/cash/<int:delivery_id>/<int:type_of_payment>", methods=["PUT"]) @delivery.route("/cash/<int:delivery_id>/<int:type_of_payment>", methods=["PUT"])
@common_login_required
def update_a_delivery_payment(delivery_id, type_of_payment): def update_a_delivery_payment(delivery_id, type_of_payment):
""" """
This update a delivery for example if user updates to a fill This update a delivery for example if user updates to a fill
@@ -175,6 +189,7 @@ def update_a_delivery_payment(delivery_id, type_of_payment):
@delivery.route("/all/<int:page>", methods=["GET"]) @delivery.route("/all/<int:page>", methods=["GET"])
@common_login_required
def get_deliveries_all(page): def get_deliveries_all(page):
""" """
This will get deliveries not done This will get deliveries not done
@@ -200,6 +215,7 @@ def get_deliveries_all(page):
@delivery.route("/customer/<int:customer_id>/<int:page>", methods=["GET"]) @delivery.route("/customer/<int:customer_id>/<int:page>", methods=["GET"])
@common_login_required
def get_deliveries_from_customer(customer_id, page): def get_deliveries_from_customer(customer_id, page):
""" """
This will get deliveries not done This will get deliveries not done
@@ -225,6 +241,7 @@ def get_deliveries_from_customer(customer_id, page):
@delivery.route("/all/order/<int:page>", methods=["GET"]) @delivery.route("/all/order/<int:page>", methods=["GET"])
@common_login_required
def get_deliveries_not_delivered(page): def get_deliveries_not_delivered(page):
""" """
@@ -250,6 +267,7 @@ def get_deliveries_not_delivered(page):
@delivery.route("/waiting/<int:page>", methods=["GET"]) @delivery.route("/waiting/<int:page>", methods=["GET"])
@common_login_required
def get_deliveries_waiting(page): def get_deliveries_waiting(page):
""" """
This will get deliveries not done This will get deliveries not done
@@ -278,6 +296,7 @@ def get_deliveries_waiting(page):
@delivery.route("/pending/<int:page>", methods=["GET"]) @delivery.route("/pending/<int:page>", methods=["GET"])
@common_login_required
def get_deliveries_pending(page): def get_deliveries_pending(page):
""" """
""" """
@@ -299,6 +318,7 @@ def get_deliveries_pending(page):
return jsonify(customer_schema.dump(deliveries)) return jsonify(customer_schema.dump(deliveries))
@delivery.route("/outfordelivery/<int:page>", methods=["GET"]) @delivery.route("/outfordelivery/<int:page>", methods=["GET"])
@common_login_required
def get_deliveries_outfordelivery(page): def get_deliveries_outfordelivery(page):
""" """
""" """
@@ -322,6 +342,7 @@ def get_deliveries_outfordelivery(page):
@delivery.route("/tommorrow/<int:page>", methods=["GET"]) @delivery.route("/tommorrow/<int:page>", methods=["GET"])
@common_login_required
def get_deliveries_tommorrow(page): def get_deliveries_tommorrow(page):
""" """
This will get deliveries not done This will get deliveries not done
@@ -348,6 +369,7 @@ def get_deliveries_tommorrow(page):
@delivery.route("/finalized/<int:page>", methods=["GET"]) @delivery.route("/finalized/<int:page>", methods=["GET"])
@common_login_required
def get_deliveries_finalized(page): def get_deliveries_finalized(page):
""" """
This will get deliveries not done This will get deliveries not done
@@ -372,6 +394,7 @@ def get_deliveries_finalized(page):
@delivery.route("/cancelled/<int:page>", methods=["GET"]) @delivery.route("/cancelled/<int:page>", methods=["GET"])
@common_login_required
def get_deliveries_cancelled(page): def get_deliveries_cancelled(page):
""" """
This will get deliveries not done This will get deliveries not done
@@ -396,6 +419,7 @@ def get_deliveries_cancelled(page):
@delivery.route("/partialdelivery/<int:page>", methods=["GET"]) @delivery.route("/partialdelivery/<int:page>", methods=["GET"])
@common_login_required
def get_deliveries_partial(page): def get_deliveries_partial(page):
""" """
This will get deliveries not done This will get deliveries not done
@@ -420,6 +444,7 @@ def get_deliveries_partial(page):
@delivery.route("/issue/<int:page>", methods=["GET"]) @delivery.route("/issue/<int:page>", methods=["GET"])
@common_login_required
def get_deliveries_issue(page): def get_deliveries_issue(page):
""" """
This will get deliveries not done This will get deliveries not done
@@ -444,6 +469,7 @@ def get_deliveries_issue(page):
@delivery.route("/time/today", methods=["GET"]) @delivery.route("/time/today", methods=["GET"])
@common_login_required
def get_deliveries_today(): def get_deliveries_today():
""" """
This will get today's deliveries This will get today's deliveries
@@ -460,10 +486,12 @@ def get_deliveries_today():
@delivery.route("/edit/<int:delivery_id>", methods=["POST"]) @delivery.route("/edit/<int:delivery_id>", methods=["POST"])
@common_login_required
def edit_a_delivery(delivery_id): def edit_a_delivery(delivery_id):
""" """
This will edit a delivery using a delivery id. This will edit a delivery using a delivery id.
""" """
logger.info(f"POST /delivery/edit/{delivery_id} - Editing delivery")
data = request.json data = request.json
get_delivery = db.session.query(Delivery_Delivery).filter(Delivery_Delivery.id == delivery_id).first() get_delivery = db.session.query(Delivery_Delivery).filter(Delivery_Delivery.id == delivery_id).first()
@@ -561,10 +589,12 @@ def edit_a_delivery(delivery_id):
@delivery.route("/create/<int:user_id>", methods=["POST"]) @delivery.route("/create/<int:user_id>", methods=["POST"])
@common_login_required
def create_a_delivery(user_id): def create_a_delivery(user_id):
""" """
This will create a delivery using a customer id This will create a delivery using a customer id
""" """
logger.info(f"POST /delivery/create/{user_id} - Creating delivery for customer")
get_customer = db.session\ get_customer = db.session\
.query(Customer_Customer)\ .query(Customer_Customer)\
.filter(Customer_Customer.id == user_id)\ .filter(Customer_Customer.id == user_id)\
@@ -600,7 +630,7 @@ def create_a_delivery(user_id):
promo_id = request.json["promo_id"] promo_id = request.json["promo_id"]
else: else:
promo_id = None promo_id = None
except: except (KeyError, TypeError):
promo_id = None promo_id = None
if promo_id is not None: if promo_id is not None:
get_promo_data = (db.session get_promo_data = (db.session
@@ -651,7 +681,7 @@ def create_a_delivery(user_id):
card_payment_id = request.json["credit_card_id"] card_payment_id = request.json["credit_card_id"]
else: else:
card_payment_id = None card_payment_id = None
except: except (KeyError, TypeError):
card_payment_id = None card_payment_id = None
if card_payment_id is not None: if card_payment_id is not None:
@@ -777,10 +807,12 @@ def create_a_delivery(user_id):
@delivery.route("/cancel/<int:delivery_id>", methods=["POST"]) @delivery.route("/cancel/<int:delivery_id>", methods=["POST"])
@common_login_required
def cancel_a_delivery(delivery_id): def cancel_a_delivery(delivery_id):
""" """
This will cancel a delivery This will cancel a delivery
""" """
logger.info(f"POST /delivery/cancel/{delivery_id} - Cancelling delivery")
get_delivery = db.session\ get_delivery = db.session\
.query(Delivery_Delivery)\ .query(Delivery_Delivery)\
.filter(Delivery_Delivery.id == delivery_id)\ .filter(Delivery_Delivery.id == delivery_id)\
@@ -794,10 +826,12 @@ def cancel_a_delivery(delivery_id):
@delivery.route("/delivered/<int:delivery_id>", methods=["POST"]) @delivery.route("/delivered/<int:delivery_id>", methods=["POST"])
@common_login_required
def mark_as_delivered(delivery_id): def mark_as_delivered(delivery_id):
""" """
This will mark the delivery as delivered This will mark the delivery as delivered
""" """
logger.info(f"POST /delivery/delivered/{delivery_id} - Marking delivery as delivered")
# how many gallons delivered # how many gallons delivered
gallons_put_into_tank = request.json["gallons_put_into_tank"] gallons_put_into_tank = request.json["gallons_put_into_tank"]
# was the tank full or not # was the tank full or not
@@ -819,6 +853,7 @@ def mark_as_delivered(delivery_id):
@delivery.route("/partial/<int:delivery_id>", methods=["POST"]) @delivery.route("/partial/<int:delivery_id>", methods=["POST"])
@common_login_required
def partial_delivery(delivery_id): def partial_delivery(delivery_id):
""" """
This will mark the delivery as delivered This will mark the delivery as delivered
@@ -842,6 +877,7 @@ def partial_delivery(delivery_id):
@delivery.route("/note/technician/<int:delivery_id>", methods=["PUT"]) @delivery.route("/note/technician/<int:delivery_id>", methods=["PUT"])
@common_login_required
def delivery_note_driver(delivery_id): def delivery_note_driver(delivery_id):
""" """
@@ -876,10 +912,12 @@ def delivery_note_driver(delivery_id):
@delivery.route("/delete/<int:delivery_id>", methods=["DELETE"]) @delivery.route("/delete/<int:delivery_id>", methods=["DELETE"])
@common_login_required
def delete_call(delivery_id): def delete_call(delivery_id):
""" """
delete a delivery call delete a delivery call
""" """
logger.info(f"DELETE /delivery/delete/{delivery_id} - Deleting delivery")
get_call_to_delete = (db.session get_call_to_delete = (db.session
.query(Delivery_Delivery) .query(Delivery_Delivery)
.filter(Delivery_Delivery.id == delivery_id) .filter(Delivery_Delivery.id == delivery_id)
@@ -893,6 +931,7 @@ def delete_call(delivery_id):
@delivery.route("/total/<int:delivery_id>", methods=["GET"]) @delivery.route("/total/<int:delivery_id>", methods=["GET"])
@common_login_required
def calculate_total(delivery_id): def calculate_total(delivery_id):
""" """
This will get deliveries not done This will get deliveries not done

View File

@@ -1,3 +1,4 @@
import logging
from flask import request, jsonify from flask import request, jsonify
from datetime import datetime from datetime import datetime
from decimal import Decimal from decimal import Decimal
@@ -11,6 +12,8 @@ from app.classes.stats_employee import Stats_Employee_Oil
from app.classes.auto import Auto_Delivery from app.classes.auto import Auto_Delivery
from app.classes.stats_customer import Stats_Customer from app.classes.stats_customer import Stats_Customer
logger = logging.getLogger(__name__)
@delivery_data.route("/finalize/<int:delivery_id>", methods=["PUT"]) @delivery_data.route("/finalize/<int:delivery_id>", methods=["PUT"])
def office_finalize_delivery(delivery_id): def office_finalize_delivery(delivery_id):
""" """
@@ -20,6 +23,7 @@ def office_finalize_delivery(delivery_id):
""" """
Finalizes a delivery from office Finalizes a delivery from office
""" """
logger.info(f"PUT /deliverydata/finalize/{delivery_id} - Finalizing delivery from office")
now = datetime.utcnow() now = datetime.utcnow()
get_delivery = db.session \ get_delivery = db.session \
.query(Delivery_Delivery) \ .query(Delivery_Delivery) \

View File

@@ -1,3 +1,4 @@
import logging
from flask import jsonify from flask import jsonify
from datetime import date, timedelta from datetime import date, timedelta
from app.delivery_status import deliverystatus from app.delivery_status import deliverystatus
@@ -13,6 +14,8 @@ from app.classes.transactions import Transaction
from datetime import date, timedelta, datetime from datetime import date, timedelta, datetime
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
logger = logging.getLogger(__name__)
# --- NEW EFFICIENT ENDPOINT --- # --- NEW EFFICIENT ENDPOINT ---
@deliverystatus.route("/stats/sidebar-counts", methods=["GET"]) @deliverystatus.route("/stats/sidebar-counts", methods=["GET"])
@@ -21,6 +24,7 @@ def get_sidebar_counts():
Efficiently gets all counts needed for the navigation sidebar in a single request. Efficiently gets all counts needed for the navigation sidebar in a single request.
This combines logic from all the individual /count/* endpoints. This combines logic from all the individual /count/* endpoints.
""" """
logger.info("GET /deliverystatus/stats/sidebar-counts - Fetching sidebar counts")
try: try:
eastern = ZoneInfo("America/New_York") eastern = ZoneInfo("America/New_York")
now_local = datetime.now(eastern).replace(tzinfo=None) # naive local time now_local = datetime.now(eastern).replace(tzinfo=None) # naive local time
@@ -73,6 +77,7 @@ def get_tomorrow_totals():
""" """
Get total gallons by town for tomorrow's deliveries, including grand total. Get total gallons by town for tomorrow's deliveries, including grand total.
""" """
logger.info("GET /deliverystatus/tomorrow-totals - Fetching tomorrow delivery totals")
try: try:
deliveries = db.session.query( deliveries = db.session.query(
Delivery_Delivery.customer_town, Delivery_Delivery.customer_town,
@@ -101,6 +106,7 @@ def get_today_totals():
""" """
Get total gallons by town for today's deliveries, including grand total. Get total gallons by town for today's deliveries, including grand total.
""" """
logger.info("GET /deliverystatus/today-totals - Fetching today delivery totals")
try: try:
deliveries = db.session.query( deliveries = db.session.query(
Delivery_Delivery.customer_town, Delivery_Delivery.customer_town,
@@ -129,6 +135,7 @@ def get_waiting_totals():
""" """
Get total gallons by town for waiting deliveries, including grand total. Get total gallons by town for waiting deliveries, including grand total.
""" """
logger.info("GET /deliverystatus/waiting-totals - Fetching waiting delivery totals")
try: try:
deliveries = db.session.query( deliveries = db.session.query(
Delivery_Delivery.customer_town, Delivery_Delivery.customer_town,

View File

@@ -1,3 +1,4 @@
import logging
from flask import request, jsonify from flask import request, jsonify
from sqlalchemy import or_ from sqlalchemy import or_
@@ -9,9 +10,12 @@ from app.classes.employee import Employee_Employee, Employee_Employee_schema
from app.classes.auth import Auth_User from app.classes.auth import Auth_User
from app.classes.stats_employee import Stats_Employee_Oil, Stats_Employee_Office from app.classes.stats_employee import Stats_Employee_Oil, Stats_Employee_Office
logger = logging.getLogger(__name__)
@employees.route("/<int:userid>", methods=["GET"]) @employees.route("/<int:userid>", methods=["GET"])
@login_required @login_required
def get_specific_employee(userid): def get_specific_employee(userid):
logger.info(f"GET /employees/{userid} - Fetching employee by user ID")
employee = db.session \ employee = db.session \
.query(Employee_Employee) \ .query(Employee_Employee) \
.filter(Employee_Employee.user_id == userid) \ .filter(Employee_Employee.user_id == userid) \
@@ -31,6 +35,7 @@ def get_specific_employee(userid):
@employees.route("/byid/<int:employee_id>", methods=["GET"]) @employees.route("/byid/<int:employee_id>", methods=["GET"])
@login_required @login_required
def get_employee_by_id(employee_id): def get_employee_by_id(employee_id):
logger.info(f"GET /employees/byid/{employee_id} - Fetching employee by ID")
employee = db.session \ employee = db.session \
.query(Employee_Employee) \ .query(Employee_Employee) \
.filter(Employee_Employee.id == employee_id) \ .filter(Employee_Employee.id == employee_id) \
@@ -42,6 +47,7 @@ def get_employee_by_id(employee_id):
@employees.route("/userid/<int:userid>", methods=["GET"]) @employees.route("/userid/<int:userid>", methods=["GET"])
@login_required @login_required
def get_specific_employee_user_id(userid): def get_specific_employee_user_id(userid):
logger.info(f"GET /employees/userid/{userid} - Fetching employee by user ID")
employee = db.session \ employee = db.session \
.query(Employee_Employee) \ .query(Employee_Employee) \
.filter(Employee_Employee.user_id == userid) \ .filter(Employee_Employee.user_id == userid) \
@@ -56,6 +62,7 @@ def all_employees_paginated(page):
""" """
pagination all employees pagination all employees
""" """
logger.info(f"GET /employees/all/{page} - Fetching employees page {page}")
per_page_amount = 50 per_page_amount = 50
if page is None: if page is None:
offset_limit = 0 offset_limit = 0
@@ -75,6 +82,7 @@ def all_employees_paginated(page):
@employees.route("/all", methods=["GET"]) @employees.route("/all", methods=["GET"])
@login_required @login_required
def all_employees(): def all_employees():
logger.info("GET /employees/all - Fetching all employees")
employee_list = db.session \ employee_list = db.session \
.query(Employee_Employee) \ .query(Employee_Employee) \
.all() .all()
@@ -85,6 +93,7 @@ def all_employees():
@employees.route("/drivers", methods=["GET"]) @employees.route("/drivers", methods=["GET"])
@login_required @login_required
def all_employees_drivers(): def all_employees_drivers():
logger.info("GET /employees/drivers - Fetching all drivers")
employee_list = db.session \ employee_list = db.session \
.query(Employee_Employee) \ .query(Employee_Employee) \
.filter(or_(Employee_Employee.employee_type == 4, .filter(or_(Employee_Employee.employee_type == 4,
@@ -98,6 +107,7 @@ def all_employees_drivers():
@employees.route("/techs", methods=["GET"]) @employees.route("/techs", methods=["GET"])
@login_required @login_required
def all_employees_techs(): def all_employees_techs():
logger.info("GET /employees/techs - Fetching all technicians")
employee_list = db.session \ employee_list = db.session \
.query(Employee_Employee) \ .query(Employee_Employee) \
.filter(or_(Employee_Employee.employee_type == 0, .filter(or_(Employee_Employee.employee_type == 0,
@@ -116,6 +126,7 @@ def employee_create():
""" """
This will create an employee This will create an employee
""" """
logger.info("POST /employees/create - Creating new employee")
e_last_name = request.json["employee_last_name"] e_last_name = request.json["employee_last_name"]
e_first_name = request.json["employee_first_name"] e_first_name = request.json["employee_first_name"]
e_town = request.json["employee_town"] e_town = request.json["employee_town"]
@@ -180,6 +191,7 @@ def employee_edit(employee_id):
""" """
This will update an employee This will update an employee
""" """
logger.info(f"POST /employees/edit/{employee_id} - Editing employee")
e_last_name = request.json["employee_last_name"] e_last_name = request.json["employee_last_name"]
e_first_name = request.json["employee_first_name"] e_first_name = request.json["employee_first_name"]
e_town = request.json["employee_town"] e_town = request.json["employee_town"]

View File

@@ -1,3 +1,4 @@
import logging
from flask import jsonify from flask import jsonify
from decimal import Decimal from decimal import Decimal
from app.info import info from app.info import info
@@ -6,10 +7,15 @@ from app.classes.pricing import Pricing_Oil_Oil, Pricing_Oil_Oil_schema
from app.classes.admin import Admin_Company from app.classes.admin import Admin_Company
from app.classes.delivery import Delivery_Delivery from app.classes.delivery import Delivery_Delivery
from app.classes.service import Service_Service from app.classes.service import Service_Service
from flask_login import login_required
logger = logging.getLogger(__name__)
@info.route("/price/oil/tiers", methods=["GET"]) @info.route("/price/oil/tiers", methods=["GET"])
@login_required
def get_pricing_tiers(): def get_pricing_tiers():
logger.info("GET /info/price/oil/tiers - Fetching oil pricing tiers")
get_price_query = (db.session get_price_query = (db.session
.query(Pricing_Oil_Oil) .query(Pricing_Oil_Oil)
.order_by(Pricing_Oil_Oil.date.desc()) .order_by(Pricing_Oil_Oil.date.desc())
@@ -34,7 +40,9 @@ def get_pricing_tiers():
return jsonify(pricing_totals) return jsonify(pricing_totals)
@info.route("/price/oil", methods=["GET"]) @info.route("/price/oil", methods=["GET"])
@login_required
def get_oil_price_today(): def get_oil_price_today():
logger.info("GET /info/price/oil - Fetching current oil prices")
get_price_query = (db.session get_price_query = (db.session
.query(Pricing_Oil_Oil) .query(Pricing_Oil_Oil)
.order_by(Pricing_Oil_Oil.date.desc()) .order_by(Pricing_Oil_Oil.date.desc())
@@ -50,7 +58,9 @@ def get_oil_price_today():
@info.route("/price/oil/table", methods=["GET"]) @info.route("/price/oil/table", methods=["GET"])
@login_required
def get_pricing(): def get_pricing():
logger.info("GET /info/price/oil/table - Fetching oil pricing table")
get_price_query = (db.session get_price_query = (db.session
.query(Pricing_Oil_Oil) .query(Pricing_Oil_Oil)
.order_by(Pricing_Oil_Oil.date.desc()) .order_by(Pricing_Oil_Oil.date.desc())
@@ -63,7 +73,9 @@ def get_pricing():
@info.route("/company", methods=["GET"]) @info.route("/company", methods=["GET"])
@login_required
def get_company(): def get_company():
logger.info("GET /info/company - Fetching company information")
get_data_company = (db.session get_data_company = (db.session
.query(Admin_Company) .query(Admin_Company)
.first()) .first())

View File

@@ -1,14 +1,19 @@
import logging
from flask import jsonify, Response, url_for from flask import jsonify, Response, url_for
from app import app from app import app
logger = logging.getLogger(__name__)
@app.route("/favicon.ico") @app.route("/favicon.ico")
def favicon(): def favicon():
logger.info("GET /favicon.ico - Serving favicon")
return url_for('static', filename='data:,') return url_for('static', filename='data:,')
@app.route('/robots.txt') @app.route('/robots.txt')
@app.route('/sitemap.xml') @app.route('/sitemap.xml')
def static_from_root(): def static_from_root():
logger.info("GET /robots.txt or /sitemap.xml - Serving robots/sitemap")
def disallow(string): return 'Disallow: {0}'.format(string) def disallow(string): return 'Disallow: {0}'.format(string)
return Response("User-agent: *\n{0}\n".format("\n".join([ return Response("User-agent: *\n{0}\n".format("\n".join([
disallow('/bin/*'), disallow('/bin/*'),
@@ -19,5 +24,5 @@ def static_from_root():
@app.route('/index', methods=['GET']) @app.route('/index', methods=['GET'])
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
def index(): def index():
logger.info("GET / or /index - API health check")
return jsonify({"success": "Api is online"}), 200 return jsonify({"success": "Api is online"}), 200

View File

@@ -1,3 +1,4 @@
import logging
from flask import jsonify from flask import jsonify
from app.money import money from app.money import money
from app import db from app import db
@@ -6,6 +7,8 @@ from datetime import date
from app.classes.money import Money_delivery, Money_delivery_schema from app.classes.money import Money_delivery, Money_delivery_schema
from app.classes.delivery import Delivery_Delivery, Delivery_Delivery_schema from app.classes.delivery import Delivery_Delivery, Delivery_Delivery_schema
logger = logging.getLogger(__name__)
def get_monday_date(date_object): def get_monday_date(date_object):
"""Gets the date of the Monday for the given date.""" """Gets the date of the Monday for the given date."""
@@ -25,6 +28,7 @@ def get_monday_date(date_object):
@money.route("/profit/week", methods=["GET"]) @money.route("/profit/week", methods=["GET"])
def total_profit_week(): def total_profit_week():
logger.info("GET /money/profit/week - Calculating weekly profit")
# Get today's date # Get today's date
total_profit = 0 total_profit = 0
total_deliveries = 0 total_deliveries = 0
@@ -52,6 +56,7 @@ def total_profit_week():
@money.route("/profit/year", methods=["GET"]) @money.route("/profit/year", methods=["GET"])
def total_profit_year(): def total_profit_year():
logger.info("GET /money/profit/year - Calculating yearly profit")
# Get today's date # Get today's date
total_profit = 0 total_profit = 0
@@ -74,10 +79,11 @@ def total_profit_year():
def get_money_delivery(delivery_id): def get_money_delivery(delivery_id):
""" """
""" """
logger.info(f"GET /money/{delivery_id} - Fetching delivery profit data")
profit = (db.session profit = (db.session
.query(Money_delivery) .query(Money_delivery)
.filter(Money_delivery.delivery_id == delivery_id) .filter(Money_delivery.delivery_id == delivery_id)
.first()) .first())
money_schema = Money_delivery_schema(many=False) money_schema = Money_delivery_schema(many=False)
return jsonify(money_schema.dump(profit)) return jsonify(money_schema.dump(profit))

View File

@@ -1,3 +1,4 @@
import logging
from flask import jsonify, request from flask import jsonify, request
from app.payment import payment from app.payment import payment
from app import db from app import db
@@ -6,6 +7,9 @@ from app.classes.cards import Card_Card, Card_Card_schema
from app.classes.transactions import Transaction from app.classes.transactions import Transaction
from app.classes.delivery import Delivery_Delivery from app.classes.delivery import Delivery_Delivery
from app.classes.service import Service_Service, Service_Service_schema from app.classes.service import Service_Service, Service_Service_schema
from flask_login import login_required
logger = logging.getLogger(__name__)
@@ -52,10 +56,12 @@ def set_card_main(user_id, card_id):
@payment.route("/cards/<int:user_id>", methods=["GET"]) @payment.route("/cards/<int:user_id>", methods=["GET"])
@login_required
def get_user_cards(user_id): def get_user_cards(user_id):
""" """
gets all cards of a user gets all cards of a user
""" """
logger.info(f"GET /payment/cards/{user_id} - Fetching user cards")
get_u_cards = (db.session get_u_cards = (db.session
.query(Card_Card) .query(Card_Card)
.filter(Card_Card.user_id == user_id) .filter(Card_Card.user_id == user_id)
@@ -66,11 +72,12 @@ def get_user_cards(user_id):
@payment.route("/cards/onfile/<int:user_id>", methods=["GET"]) @payment.route("/cards/onfile/<int:user_id>", methods=["GET"])
@login_required
def get_user_cards_count(user_id): def get_user_cards_count(user_id):
""" """
gets all cards of a user gets all cards of a user
""" """
logger.info(f"GET /payment/cards/onfile/{user_id} - Getting card count")
get_u_cards = (db.session get_u_cards = (db.session
.query(Card_Card) .query(Card_Card)
.filter(Card_Card.user_id == user_id) .filter(Card_Card.user_id == user_id)
@@ -83,6 +90,7 @@ def get_user_cards_count(user_id):
@payment.route("/card/<int:card_id>", methods=["GET"]) @payment.route("/card/<int:card_id>", methods=["GET"])
@login_required
def get_user_specific_card(card_id): def get_user_specific_card(card_id):
""" """
gets a specific card of a user gets a specific card of a user
@@ -99,6 +107,7 @@ def get_user_specific_card(card_id):
@payment.route("/card/main/<int:card_id>/<int:user_id>", methods=["PUT"]) @payment.route("/card/main/<int:card_id>/<int:user_id>", methods=["PUT"])
@login_required
def set_main_card(user_id, card_id): def set_main_card(user_id, card_id):
""" """
updates a card of a user updates a card of a user
@@ -130,11 +139,12 @@ def set_main_card(user_id, card_id):
@payment.route("/card/remove/<int:card_id>", methods=["DELETE"]) @payment.route("/card/remove/<int:card_id>", methods=["DELETE"])
@login_required
def remove_user_card(card_id): def remove_user_card(card_id):
""" """
removes a card removes a card
""" """
logger.info(f"DELETE /payment/card/remove/{card_id} - Removing card")
get_card = (db.session get_card = (db.session
.query(Card_Card) .query(Card_Card)
.filter(Card_Card.id == card_id) .filter(Card_Card.id == card_id)
@@ -151,6 +161,7 @@ def remove_user_card(card_id):
# ... (your existing imports: jsonify, request, db, Customer_Customer, Card_Card) ... # ... (your existing imports: jsonify, request, db, Customer_Customer, Card_Card) ...
@payment.route("/card/create/<int:user_id>", methods=["POST"]) @payment.route("/card/create/<int:user_id>", methods=["POST"])
@login_required
def create_user_card(user_id): def create_user_card(user_id):
""" """
Adds a card for a user to the local database. This is its only job. Adds a card for a user to the local database. This is its only job.
@@ -196,17 +207,18 @@ def create_user_card(user_id):
set_card_main(user_id=get_customer.id, card_id=create_new_card.id) set_card_main(user_id=get_customer.id, card_id=create_new_card.id)
db.session.commit() db.session.commit()
print(f"SUCCESS: Card saved locally for user {user_id} with new ID {create_new_card.id}") logger.info(f"Card saved locally for user {user_id} with ID {create_new_card.id}")
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
print(f"DATABASE ERROR: Could not save card for user {user_id}. Error: {e}") logger.error(f"Database error saving card for user {user_id}: {e}")
return jsonify({"ok": False, "error": "Failed to save card information."}), 500 return jsonify({"ok": False, "error": "Failed to save card information."}), 500
# Return a success response with the card_id # Return a success response with the card_id
return jsonify({"ok": True, "card_id": create_new_card.id}), 200 return jsonify({"ok": True, "card_id": create_new_card.id}), 200
@payment.route("/card/update_payment_profile/<int:card_id>", methods=["PUT"]) @payment.route("/card/update_payment_profile/<int:card_id>", methods=["PUT"])
@login_required
def update_card_payment_profile(card_id): def update_card_payment_profile(card_id):
""" """
Updates the auth_net_payment_profile_id for a card Updates the auth_net_payment_profile_id for a card
@@ -230,6 +242,7 @@ def update_card_payment_profile(card_id):
@payment.route("/card/edit/<int:card_id>", methods=["PUT"]) @payment.route("/card/edit/<int:card_id>", methods=["PUT"])
@login_required
def update_user_card(card_id): def update_user_card(card_id):
""" """
edits a card edits a card
@@ -277,6 +290,7 @@ def update_user_card(card_id):
@payment.route("/transactions/authorize/<int:page>", methods=["GET"]) @payment.route("/transactions/authorize/<int:page>", methods=["GET"])
@login_required
def get_authorize_transactions(page): def get_authorize_transactions(page):
""" """
Gets transactions with transaction_type = 0 (charge), for the authorize page Gets transactions with transaction_type = 0 (charge), for the authorize page
@@ -320,6 +334,7 @@ def get_authorize_transactions(page):
@payment.route("/authorize/cleanup/<int:customer_id>", methods=["POST"]) @payment.route("/authorize/cleanup/<int:customer_id>", methods=["POST"])
@login_required
def cleanup_authorize_profile(customer_id): def cleanup_authorize_profile(customer_id):
""" """
Clean up Authorize.Net profile data in local database when API check fails. Clean up Authorize.Net profile data in local database when API check fails.
@@ -349,6 +364,7 @@ def cleanup_authorize_profile(customer_id):
@payment.route("/authorize/<int:delivery_id>", methods=["PUT"]) @payment.route("/authorize/<int:delivery_id>", methods=["PUT"])
@login_required
def update_delivery_payment_authorize(delivery_id): def update_delivery_payment_authorize(delivery_id):
""" """
Update a delivery's payment_type to 11 (CC - Authorize API) after successful preauthorization Update a delivery's payment_type to 11 (CC - Authorize API) after successful preauthorization
@@ -370,6 +386,7 @@ def update_delivery_payment_authorize(delivery_id):
@payment.route("/transaction/delivery/<int:delivery_id>", methods=["GET"]) @payment.route("/transaction/delivery/<int:delivery_id>", methods=["GET"])
@login_required
def get_transaction_by_delivery(delivery_id): def get_transaction_by_delivery(delivery_id):
""" """
Get a single transaction by delivery_id for Authorize.net payments Get a single transaction by delivery_id for Authorize.net payments
@@ -403,6 +420,7 @@ def get_transaction_by_delivery(delivery_id):
@payment.route("/transactions/customer/<int:customer_id>/<int:page>", methods=["GET"]) @payment.route("/transactions/customer/<int:customer_id>/<int:page>", methods=["GET"])
@login_required
def get_customer_transactions(customer_id, page): def get_customer_transactions(customer_id, page):
""" """
Gets transactions for a specific customer Gets transactions for a specific customer
@@ -445,6 +463,7 @@ def get_customer_transactions(customer_id, page):
@payment.route("/service/payment/<int:service_id>/<int:payment_type>", methods=["PUT"]) @payment.route("/service/payment/<int:service_id>/<int:payment_type>", methods=["PUT"])
@login_required
def process_service_payment_tiger(service_id, payment_type): def process_service_payment_tiger(service_id, payment_type):
service = db.session.query(Service_Service).filter(Service_Service.id == service_id).first() service = db.session.query(Service_Service).filter(Service_Service.id == service_id).first()
if not service: if not service:
@@ -464,6 +483,7 @@ def process_service_payment_tiger(service_id, payment_type):
@payment.route("/authorize/service/<int:service_id>", methods=["PUT"]) @payment.route("/authorize/service/<int:service_id>", methods=["PUT"])
@login_required
def update_service_payment_authorize(service_id): def update_service_payment_authorize(service_id):
service = db.session.query(Service_Service).filter(Service_Service.id == service_id).first() service = db.session.query(Service_Service).filter(Service_Service.id == service_id).first()
if not service: if not service:
@@ -487,6 +507,7 @@ def update_service_payment_authorize(service_id):
@payment.route("/capture/service/<int:service_id>", methods=["PUT"]) @payment.route("/capture/service/<int:service_id>", methods=["PUT"])
@login_required
def update_service_payment_capture(service_id): def update_service_payment_capture(service_id):
service = db.session.query(Service_Service).filter(Service_Service.id == service_id).first() service = db.session.query(Service_Service).filter(Service_Service.id == service_id).first()
if not service: if not service:
@@ -511,6 +532,7 @@ def update_service_payment_capture(service_id):
@payment.route("/transactions/service/<int:service_id>", methods=["GET"]) @payment.route("/transactions/service/<int:service_id>", methods=["GET"])
@login_required
def get_service_transactions(service_id): def get_service_transactions(service_id):
""" """
Gets all transactions for a specific service ID Gets all transactions for a specific service ID
@@ -544,5 +566,5 @@ def get_service_transactions(service_id):
return jsonify(transactions_data), 200 return jsonify(transactions_data), 200
except Exception as e: except Exception as e:
print(f"Error fetching transactions for service {service_id}: {str(e)}") logger.error(f"Error fetching transactions for service {service_id}: {e}")
return jsonify({"ok": False, "error": str(e)}), 500 return jsonify({"ok": False, "error": str(e)}), 500

View File

@@ -1,3 +1,4 @@
import logging
from flask import request, jsonify from flask import request, jsonify
import decimal import decimal
from datetime import datetime from datetime import datetime
@@ -11,6 +12,8 @@ from app.classes.delivery import (Delivery_Delivery,
Delivery_Notes_Driver, Delivery_Notes_Driver,
) )
logger = logging.getLogger(__name__)
def convert_to_decimal(text): def convert_to_decimal(text):
try: try:
number = float(text) number = float(text)
@@ -23,9 +26,10 @@ def convert_to_decimal(text):
def get_promo(promo_id): def get_promo(promo_id):
""" """
""" """
get_promo_data = (db.session logger.info(f"GET /promo/{promo_id} - Fetching promo")
.query(Promo_Promo) get_promo_data = (db.session
.filter(Promo_Promo.id == promo_id) .query(Promo_Promo)
.filter(Promo_Promo.id == promo_id)
.first()) .first())
query_schema = Promo_Promo_schema(many=False) query_schema = Promo_Promo_schema(many=False)
return jsonify(query_schema.dump(get_promo_data)) return jsonify(query_schema.dump(get_promo_data))
@@ -35,6 +39,7 @@ def get_promo(promo_id):
def get_promo_price(delivery_id): def get_promo_price(delivery_id):
""" """
""" """
logger.info(f"GET /promo/promoprice/{delivery_id} - Calculating promo price")
get_delivery = (db.session get_delivery = (db.session
.query(Delivery_Delivery) .query(Delivery_Delivery)
.filter(Delivery_Delivery.id == delivery_id) .filter(Delivery_Delivery.id == delivery_id)
@@ -53,8 +58,9 @@ def get_promo_price(delivery_id):
def get_all_promo(): def get_all_promo():
""" """
""" """
get_promo_data = (db.session logger.info("GET /promo/all - Fetching all promos")
.query(Promo_Promo) get_promo_data = (db.session
.query(Promo_Promo)
.all()) .all())
query_schema = Promo_Promo_schema(many=True) query_schema = Promo_Promo_schema(many=True)
return jsonify(query_schema.dump(get_promo_data)) return jsonify(query_schema.dump(get_promo_data))
@@ -64,11 +70,12 @@ def get_all_promo():
def delete_a_promo(promo_id): def delete_a_promo(promo_id):
""" """
""" """
get_promo_data = (db.session logger.info(f"DELETE /promo/delete/{promo_id} - Deleting promo")
.query(Promo_Promo) get_promo_data = (db.session
.filter(Promo_Promo.id == promo_id) .query(Promo_Promo)
.filter(Promo_Promo.id == promo_id)
.first()) .first())
db.session.delete(get_promo_data) db.session.delete(get_promo_data)
db.session.commit() db.session.commit()
@@ -81,6 +88,7 @@ def delete_a_promo(promo_id):
def create_promo(): def create_promo():
""" """
""" """
logger.info("POST /promo/create - Creating new promo")
date_created = datetime.utcnow() date_created = datetime.utcnow()
name_of_promotion = request.json["name_of_promotion"] name_of_promotion = request.json["name_of_promotion"]
money_off_delivery = request.json["money_off_delivery"] money_off_delivery = request.json["money_off_delivery"]
@@ -94,13 +102,13 @@ def create_promo():
name_of_promotion = name_of_promotion, name_of_promotion = name_of_promotion,
money_off_delivery = amount_off, money_off_delivery = amount_off,
description = description, description = description,
date_created = date_created, date_created = date_created,
text_on_ticket=text_on_ticket text_on_ticket=text_on_ticket
) )
db.session.add(new_promo) db.session.add(new_promo)
db.session.commit() db.session.commit()
return jsonify({ return jsonify({
"ok": True, "ok": True,
'promo_id':new_promo.id, 'promo_id':new_promo.id,
@@ -112,9 +120,10 @@ def edit_promo(promo_id):
""" """
""" """
get_promo_data = (db.session logger.info(f"PUT /promo/edit/{promo_id} - Editing promo")
.query(Promo_Promo) get_promo_data = (db.session
.filter(Promo_Promo.id == promo_id) .query(Promo_Promo)
.filter(Promo_Promo.id == promo_id)
.first()) .first())
text_on_ticket = request.json["text_on_ticket"] text_on_ticket = request.json["text_on_ticket"]
name_of_promotion = request.json["name_of_promotion"] name_of_promotion = request.json["name_of_promotion"]
@@ -144,13 +153,14 @@ def turn_on_promo(promo_id):
""" """
""" """
get_promo_data = (db.session logger.info(f"PATCH /promo/on/{promo_id} - Activating promo")
.query(Promo_Promo) get_promo_data = (db.session
.filter(Promo_Promo.id == promo_id) .query(Promo_Promo)
.filter(Promo_Promo.id == promo_id)
.first()) .first())
get_promo_data.active = True get_promo_data.active = True
db.session.add(get_promo_data) db.session.add(get_promo_data)
db.session.commit() db.session.commit()
@@ -165,13 +175,14 @@ def turn_off_promo(promo_id):
""" """
""" """
get_promo_data = (db.session logger.info(f"PATCH /promo/off/{promo_id} - Deactivating promo")
.query(Promo_Promo) get_promo_data = (db.session
.filter(Promo_Promo.id == promo_id) .query(Promo_Promo)
.filter(Promo_Promo.id == promo_id)
.first()) .first())
get_promo_data.active = False get_promo_data.active = False
db.session.add(get_promo_data) db.session.add(get_promo_data)
db.session.commit() db.session.commit()
@@ -179,4 +190,4 @@ def turn_off_promo(promo_id):
return jsonify({ return jsonify({
"ok": True, "ok": True,
'promo_id':get_promo_data.id, 'promo_id':get_promo_data.id,
}), 200 }), 200

View File

@@ -1,34 +1,42 @@
import logging
from flask import jsonify from flask import jsonify
from app.query import query from app.query import query
from app import db from app import db
from app.classes.admin import Admin_Company from app.classes.admin import Admin_Company
from app.classes.query import (Query_StateList, from app.classes.query import (Query_StateList,
Query_DeliveryStatusList, Query_DeliveryStatusList,
Query_DeliveryStatusList_Schema, Query_DeliveryStatusList_Schema,
Query_StateList_Schema, Query_StateList_Schema,
Query_CustomerTypeList, Query_CustomerTypeList,
Query_CustomerTypeList_Schema, Query_CustomerTypeList_Schema,
Query_EmployeeTypeList, Query_EmployeeTypeList,
Query_EmployeeTypeList_Schema) Query_EmployeeTypeList_Schema)
from flask_login import login_required
logger = logging.getLogger(__name__)
@query.route("/company/<int:company_id>", methods=["GET"]) @query.route("/company/<int:company_id>", methods=["GET"])
@login_required
def get_company(company_id): def get_company(company_id):
""" """
This will get the company from env variable This will get the company from env variable
""" """
logger.info(f"GET /query/company/{company_id} - Fetching company data")
query_data = (db.session query_data = (db.session
.query(Admin_Company) .query(Admin_Company)
.filter(Admin_Company.id == company_id) .filter(Admin_Company.id == company_id)
.first()) .first())
delivery_schema = Query_DeliveryStatusList_Schema(many=False) delivery_schema = Query_DeliveryStatusList_Schema(many=False)
return jsonify(delivery_schema.dump(query_data)) return jsonify(delivery_schema.dump(query_data))
@query.route("/states", methods=["GET"]) @query.route("/states", methods=["GET"])
@login_required
def get_state_list(): def get_state_list():
""" """
This will get states This will get states
""" """
logger.info("GET /query/states - Fetching state list")
query_data = db.session \ query_data = db.session \
.query(Query_StateList) \ .query(Query_StateList) \
@@ -39,10 +47,12 @@ def get_state_list():
@query.route("/customertype", methods=["GET"]) @query.route("/customertype", methods=["GET"])
@login_required
def get_customer_type_list(): def get_customer_type_list():
""" """
This will get types of customers This will get types of customers
""" """
logger.info("GET /query/customertype - Fetching customer type list")
query_data = db.session \ query_data = db.session \
.query(Query_CustomerTypeList) \ .query(Query_CustomerTypeList) \
@@ -54,10 +64,12 @@ def get_customer_type_list():
@query.route("/employeetype", methods=["GET"]) @query.route("/employeetype", methods=["GET"])
@login_required
def get_employee_type_list(): def get_employee_type_list():
""" """
This will get types of service This will get types of service
""" """
logger.info("GET /query/employeetype - Fetching employee type list")
query_data = db.session \ query_data = db.session \
.query(Query_EmployeeTypeList) \ .query(Query_EmployeeTypeList) \
@@ -67,16 +79,15 @@ def get_employee_type_list():
@query.route("/deliverystatus", methods=["GET"]) @query.route("/deliverystatus", methods=["GET"])
@login_required
def get_delivery_status_list(): def get_delivery_status_list():
""" """
This will get delivery status This will get delivery status
""" """
logger.info("GET /query/deliverystatus - Fetching delivery status list")
query_data = db.session \ query_data = db.session \
.query(Query_DeliveryStatusList) \ .query(Query_DeliveryStatusList) \
.all() .all()
delivery_schema = Query_DeliveryStatusList_Schema(many=True) delivery_schema = Query_DeliveryStatusList_Schema(many=True)
return jsonify(delivery_schema.dump(query_data)) return jsonify(delivery_schema.dump(query_data))

View File

@@ -1,3 +1,4 @@
import logging
from flask import jsonify from flask import jsonify
from sqlalchemy.sql import func from sqlalchemy.sql import func
from app.reports import reports from app.reports import reports
@@ -7,14 +8,17 @@ from app.classes.customer import Customer_Customer
from app.classes.delivery import Delivery_Delivery from app.classes.delivery import Delivery_Delivery
logger = logging.getLogger(__name__)
@reports.route("/oil/total", methods=["GET"]) @reports.route("/oil/total", methods=["GET"])
def oil_total_gallons(): def oil_total_gallons():
logger.info("GET /reports/oil/total - Calculating total oil delivered")
total_oil = db.session\ total_oil = db.session\
.query(func.sum(Delivery_Delivery.gallons_delivered))\ .query(func.sum(Delivery_Delivery.gallons_delivered))\
.group_by(Delivery_Delivery.id)\ .group_by(Delivery_Delivery.id)\
.all() .all()
return jsonify({"ok": True, "oil": total_oil }), 200 return jsonify({"ok": True, "oil": total_oil }), 200
@reports.route("/customers/list", methods=["GET"]) @reports.route("/customers/list", methods=["GET"])
@@ -24,6 +28,7 @@ def customer_list():
Returns account number, first name, last name, address, town, and phone number. Returns account number, first name, last name, address, town, and phone number.
Ordered by last name from A to Z. Ordered by last name from A to Z.
""" """
logger.info("GET /reports/customers/list - Fetching customer list for reports")
customers = db.session.query(Customer_Customer).order_by(Customer_Customer.customer_last_name.asc()).all() customers = db.session.query(Customer_Customer).order_by(Customer_Customer.customer_last_name.asc()).all()
customer_data = [ customer_data = [

4
app/schemas/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
# Validation schemas for API requests
from .customer import CreateCustomerSchema, UpdateCustomerSchema, UpdateDescriptionSchema
from .auth import LoginSchema, RegisterSchema, ChangePasswordSchema
from .utils import validate_request, validate_json

56
app/schemas/auth.py Normal file
View File

@@ -0,0 +1,56 @@
from marshmallow import Schema, fields, validate, EXCLUDE
class LoginSchema(Schema):
"""Validation schema for user login"""
class Meta:
unknown = EXCLUDE
username = fields.Str(
required=True,
validate=validate.Length(min=3, max=100),
error_messages={"required": "Username is required"}
)
password = fields.Str(
required=True,
validate=validate.Length(min=6, max=200),
error_messages={"required": "Password is required"}
)
class RegisterSchema(Schema):
"""Validation schema for user registration"""
class Meta:
unknown = EXCLUDE
username = fields.Str(
required=True,
validate=validate.Length(min=3, max=100),
error_messages={"required": "Username is required"}
)
password = fields.Str(
required=True,
validate=validate.Length(min=6, max=200),
error_messages={"required": "Password is required"}
)
email = fields.Email(
required=True,
error_messages={"required": "Email is required"}
)
class ChangePasswordSchema(Schema):
"""Validation schema for password change"""
class Meta:
unknown = EXCLUDE
new_password = fields.Str(
required=True,
validate=validate.Length(min=6, max=200),
error_messages={"required": "New password is required"}
)
password_confirm = fields.Str(
required=True,
validate=validate.Length(min=6, max=200),
error_messages={"required": "Password confirmation is required"}
)

126
app/schemas/customer.py Normal file
View File

@@ -0,0 +1,126 @@
from marshmallow import Schema, fields, validate, EXCLUDE
class CreateCustomerSchema(Schema):
"""Validation schema for creating a new customer"""
class Meta:
unknown = EXCLUDE
customer_last_name = fields.Str(
required=True,
validate=validate.Length(min=1, max=250),
error_messages={"required": "Last name is required"}
)
customer_first_name = fields.Str(
required=True,
validate=validate.Length(min=1, max=250),
error_messages={"required": "First name is required"}
)
customer_town = fields.Str(
required=True,
validate=validate.Length(min=1, max=140),
error_messages={"required": "Town is required"}
)
customer_state = fields.Int(
required=True,
validate=validate.Range(min=0, max=50),
error_messages={"required": "State is required"}
)
customer_zip = fields.Str(
required=True,
validate=validate.Length(min=5, max=10),
error_messages={"required": "Zip code is required"}
)
customer_email = fields.Email(
allow_none=True,
load_default=None
)
customer_home_type = fields.Int(
required=True,
validate=validate.Range(min=0, max=10),
error_messages={"required": "Home type is required"}
)
customer_phone_number = fields.Str(
allow_none=True,
validate=validate.Length(max=25),
load_default=None
)
customer_address = fields.Str(
required=True,
validate=validate.Length(min=1, max=1000),
error_messages={"required": "Address is required"}
)
customer_apt = fields.Str(
allow_none=True,
validate=validate.Length(max=140),
load_default=None
)
customer_description = fields.Str(
allow_none=True,
validate=validate.Length(max=2000),
load_default=None
)
class UpdateCustomerSchema(Schema):
"""Validation schema for updating an existing customer"""
class Meta:
unknown = EXCLUDE
customer_last_name = fields.Str(
validate=validate.Length(min=1, max=250)
)
customer_first_name = fields.Str(
validate=validate.Length(min=1, max=250)
)
customer_town = fields.Str(
validate=validate.Length(min=1, max=140)
)
customer_state = fields.Int(
validate=validate.Range(min=0, max=50)
)
customer_zip = fields.Str(
validate=validate.Length(min=5, max=10)
)
customer_email = fields.Email(
allow_none=True
)
customer_home_type = fields.Int(
validate=validate.Range(min=0, max=10)
)
customer_phone_number = fields.Str(
allow_none=True,
validate=validate.Length(max=25)
)
customer_address = fields.Str(
validate=validate.Length(min=1, max=1000)
)
customer_apt = fields.Str(
allow_none=True,
validate=validate.Length(max=140)
)
customer_automatic = fields.Int(
validate=validate.Range(min=0, max=10)
)
customer_description = fields.Str(
allow_none=True,
validate=validate.Length(max=2000)
)
customer_fill_location = fields.Int(
allow_none=True,
validate=validate.Range(min=0, max=10)
)
class UpdateDescriptionSchema(Schema):
"""Validation schema for updating customer description"""
class Meta:
unknown = EXCLUDE
description = fields.Str(
allow_none=True,
validate=validate.Length(max=2000)
)
fill_location = fields.Int(
allow_none=True
)

56
app/schemas/utils.py Normal file
View File

@@ -0,0 +1,56 @@
from flask import jsonify, request
from functools import wraps
from marshmallow import ValidationError
def validate_request(schema_class):
"""
Decorator to validate incoming JSON request data against a marshmallow schema.
Usage:
@customer.route("/create", methods=["POST"])
@validate_request(CreateCustomerSchema)
def create_customer():
data = request.validated_data # Access validated data
...
"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# Check if request has JSON data
if not request.is_json:
return jsonify({"error": "Request must be JSON"}), 400
json_data = request.get_json()
if json_data is None:
return jsonify({"error": "Invalid JSON data"}), 400
# Validate the data
schema = schema_class()
try:
validated_data = schema.load(json_data)
# Attach validated data to request object for easy access
request.validated_data = validated_data
except ValidationError as err:
return jsonify({"error": "Validation failed", "details": err.messages}), 400
return f(*args, **kwargs)
return decorated_function
return decorator
def validate_json(schema_class, data):
"""
Validate data against a schema and return (validated_data, errors).
Usage:
data, errors = validate_json(CreateCustomerSchema, request.get_json())
if errors:
return jsonify({"error": "Validation failed", "details": errors}), 400
"""
schema = schema_class()
try:
validated_data = schema.load(data or {})
return validated_data, None
except ValidationError as err:
return None, err.messages

View File

@@ -1,3 +1,4 @@
import logging
from flask import request, jsonify from flask import request, jsonify
from app.search import search from app.search import search
@@ -5,16 +6,27 @@ from app import db
from sqlalchemy import or_ from sqlalchemy import or_
from app.classes.customer import Customer_Customer, Customer_Customer_schema from app.classes.customer import Customer_Customer, Customer_Customer_schema
from app.classes.delivery import Delivery_Delivery, Delivery_Delivery_schema from app.classes.delivery import Delivery_Delivery, Delivery_Delivery_schema
from flask_login import login_required
logger = logging.getLogger(__name__)
def escape_like(value: str) -> str:
"""Escape special LIKE characters to prevent LIKE injection."""
if value is None:
return ""
return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
@search.route("/customer", methods=["GET"]) @search.route("/customer", methods=["GET"])
@login_required
def search_customers(): def search_customers():
""" """
""" """
keyword = request.args.get('q') keyword = request.args.get('q')
search = "%{}%".format(keyword) logger.info(f"GET /search/customer - Searching customers with keyword: {keyword}")
search = "%{}%".format(escape_like(keyword))
search_type = (search[1]) search_type = (search[1])
search = search.replace("!", "") search = search.replace("!", "")
search = search.replace("#", "") search = search.replace("#", "")
@@ -66,12 +78,14 @@ def search_customers():
@search.route("/delivery", methods=["GET"]) @search.route("/delivery", methods=["GET"])
@login_required
def search_delivery(): def search_delivery():
""" """
pagination all customers pagination all customers
""" """
keyword = request.args.get('q') keyword = request.args.get('q')
search = "%{}%".format(keyword) logger.info(f"GET /search/delivery - Searching deliveries with keyword: {keyword}")
search = "%{}%".format(escape_like(keyword))
search_type = (search[1]) search_type = (search[1])
delivery_ticket = (db.session delivery_ticket = (db.session

View File

@@ -1,3 +1,4 @@
import logging
from flask import request, jsonify from flask import request, jsonify
from app.service import service from app.service import service
from app import db from app import db
@@ -8,9 +9,14 @@ from app.classes.service import (Service_Service,
Service_Plans, Service_Plans_schema Service_Plans, Service_Plans_schema
) )
from app.classes.auto import Auto_Delivery from app.classes.auto import Auto_Delivery
from flask_login import login_required
logger = logging.getLogger(__name__)
@service.route("/all", methods=["GET"]) @service.route("/all", methods=["GET"])
@login_required
def get_all_service_calls(): def get_all_service_calls():
logger.info("GET /service/all - Fetching all service calls for calendar")
try: try:
all_services = Service_Service.query.all() all_services = Service_Service.query.all()
color_map = { color_map = {
@@ -49,18 +55,19 @@ def get_all_service_calls():
return jsonify(calendar_events), 200 return jsonify(calendar_events), 200
except Exception as e: except Exception as e:
# Add error logging to see what's happening logger.error(f"Error in /service/all: {e}")
print(f"An error occurred in /service/all: {e}")
return jsonify(error=str(e)), 500 return jsonify(error=str(e)), 500
# --- THIS IS THE FIX --- # --- THIS IS THE FIX ---
# The logic from /all has been copied here to ensure a consistent data structure. # The logic from /all has been copied here to ensure a consistent data structure.
@service.route("/upcoming", methods=["GET"]) @service.route("/upcoming", methods=["GET"])
@login_required
def get_upcoming_service_calls(): def get_upcoming_service_calls():
""" """
Fetches a list of all future service calls from today onwards. Fetches a list of all future service calls from today onwards.
""" """
logger.info("GET /service/upcoming - Fetching upcoming service calls")
now = datetime.now() now = datetime.now()
upcoming_services = ( upcoming_services = (
Service_Service.query Service_Service.query
@@ -78,6 +85,7 @@ def get_upcoming_service_calls():
@service.route("/past", methods=["GET"]) @service.route("/past", methods=["GET"])
@login_required
def get_past_service_calls(): def get_past_service_calls():
""" """
Fetches a list of all past service calls before today. Fetches a list of all past service calls before today.
@@ -97,6 +105,7 @@ def get_past_service_calls():
@service.route("/today", methods=["GET"]) @service.route("/today", methods=["GET"])
@login_required
def get_today_service_calls(): def get_today_service_calls():
""" """
Fetches a list of all service calls for today. Fetches a list of all service calls for today.
@@ -119,6 +128,7 @@ def get_today_service_calls():
@service.route("/upcoming/count", methods=["GET"]) @service.route("/upcoming/count", methods=["GET"])
@login_required
def get_upcoming_service_calls_count(): def get_upcoming_service_calls_count():
now = datetime.now() now = datetime.now()
try: try:
@@ -128,6 +138,7 @@ def get_upcoming_service_calls_count():
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@service.route("/for-customer/<int:customer_id>", methods=["GET"]) @service.route("/for-customer/<int:customer_id>", methods=["GET"])
@login_required
def get_service_calls_for_customer(customer_id): def get_service_calls_for_customer(customer_id):
service_records = (Service_Service.query.filter_by(customer_id=customer_id).order_by(Service_Service.scheduled_date.desc()).all()) service_records = (Service_Service.query.filter_by(customer_id=customer_id).order_by(Service_Service.scheduled_date.desc()).all())
service_schema = Service_Service_schema(many=True) service_schema = Service_Service_schema(many=True)
@@ -135,6 +146,7 @@ def get_service_calls_for_customer(customer_id):
return jsonify(result), 200 return jsonify(result), 200
@service.route("/create", methods=["POST"]) @service.route("/create", methods=["POST"])
@login_required
def create_service_call(): def create_service_call():
data = request.get_json() data = request.get_json()
if not data: return jsonify({"error": "No data provided"}), 400 if not data: return jsonify({"error": "No data provided"}), 400
@@ -155,6 +167,7 @@ def create_service_call():
return jsonify({ "ok": True, "id": new_service_call.id }), 201 return jsonify({ "ok": True, "id": new_service_call.id }), 201
@service.route("/update-cost/<int:id>", methods=["PUT"]) @service.route("/update-cost/<int:id>", methods=["PUT"])
@login_required
def update_service_cost(id): def update_service_cost(id):
""" """
Dedicated endpoint to update only the service cost for a service call. Dedicated endpoint to update only the service cost for a service call.
@@ -196,10 +209,11 @@ def update_service_cost(id):
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
print(f"Error updating service cost for service {id}: {str(e)}") logger.error(f"Error updating service cost for service {id}: {e}")
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@service.route("/update/<int:id>", methods=["PUT"]) @service.route("/update/<int:id>", methods=["PUT"])
@login_required
def update_service_call(id): def update_service_call(id):
service_record = Service_Service.query.get_or_404(id) service_record = Service_Service.query.get_or_404(id)
data = request.get_json() data = request.get_json()
@@ -222,6 +236,7 @@ def update_service_call(id):
# Service Plans CRUD endpoints # Service Plans CRUD endpoints
@service.route("/plans/active", methods=["GET"]) @service.route("/plans/active", methods=["GET"])
@login_required
def get_active_service_plans(): def get_active_service_plans():
""" """
Get all active service plans (contract_plan > 0) Get all active service plans (contract_plan > 0)
@@ -245,6 +260,7 @@ def get_active_service_plans():
@service.route("/plans/customer/<int:customer_id>", methods=["GET"]) @service.route("/plans/customer/<int:customer_id>", methods=["GET"])
@login_required
def get_customer_service_plan(customer_id): def get_customer_service_plan(customer_id):
""" """
Get service plan for a specific customer Get service plan for a specific customer
@@ -261,6 +277,7 @@ def get_customer_service_plan(customer_id):
@service.route("/plans/create", methods=["POST"]) @service.route("/plans/create", methods=["POST"])
@login_required
def create_service_plan(): def create_service_plan():
""" """
Create a new service plan for a customer Create a new service plan for a customer
@@ -287,6 +304,7 @@ def create_service_plan():
@service.route("/plans/update/<int:customer_id>", methods=["PUT"]) @service.route("/plans/update/<int:customer_id>", methods=["PUT"])
@login_required
def update_service_plan(customer_id): def update_service_plan(customer_id):
""" """
Update existing service plan for a customer Update existing service plan for a customer
@@ -317,6 +335,7 @@ def update_service_plan(customer_id):
@service.route("/plans/delete/<int:customer_id>", methods=["DELETE"]) @service.route("/plans/delete/<int:customer_id>", methods=["DELETE"])
@login_required
def delete_service_plan(customer_id): def delete_service_plan(customer_id):
""" """
Delete service plan for a customer Delete service plan for a customer
@@ -334,12 +353,14 @@ def delete_service_plan(customer_id):
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@service.route("/<int:id>", methods=["GET"]) @service.route("/<int:id>", methods=["GET"])
@login_required
def get_service_by_id(id): def get_service_by_id(id):
service_record = Service_Service.query.get_or_404(id) service_record = Service_Service.query.get_or_404(id)
service_schema = Service_Service_schema() service_schema = Service_Service_schema()
return jsonify({"ok": True, "service": service_schema.dump(service_record)}), 200 return jsonify({"ok": True, "service": service_schema.dump(service_record)}), 200
@service.route("/delete/<int:id>", methods=["DELETE"]) @service.route("/delete/<int:id>", methods=["DELETE"])
@login_required
def delete_service_call(id): def delete_service_call(id):
service_record = Service_Service.query.get_or_404(id) service_record = Service_Service.query.get_or_404(id)
try: try:
@@ -351,6 +372,7 @@ def delete_service_call(id):
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@service.route("/parts/customer/<int:customer_id>", methods=["GET"]) @service.route("/parts/customer/<int:customer_id>", methods=["GET"])
@login_required
def get_service_parts(customer_id): def get_service_parts(customer_id):
parts = Service_Parts.query.filter_by(customer_id=customer_id).first() parts = Service_Parts.query.filter_by(customer_id=customer_id).first()
if parts: if parts:
@@ -363,6 +385,7 @@ def get_service_parts(customer_id):
}), 200 }), 200
@service.route("/parts/update/<int:customer_id>", methods=["POST"]) @service.route("/parts/update/<int:customer_id>", methods=["POST"])
@login_required
def update_service_parts(customer_id): def update_service_parts(customer_id):
try: try:
data = request.get_json() data = request.get_json()
@@ -397,6 +420,7 @@ def update_service_parts(customer_id):
@service.route("/payment/<int:service_id>/<int:payment_type>", methods=["PUT"]) @service.route("/payment/<int:service_id>/<int:payment_type>", methods=["PUT"])
@login_required
def process_service_payment(service_id, payment_type): def process_service_payment(service_id, payment_type):
service = db.session.query(Service_Service).filter(Service_Service.id == service_id).first() service = db.session.query(Service_Service).filter(Service_Service.id == service_id).first()
if not service: if not service:

View File

@@ -1,14 +1,20 @@
import logging
from flask import jsonify, request from flask import jsonify, request
import datetime import datetime
from app.social import social from app.social import social
from app import db from app import db
from app.classes.customer_social import (Customer_Customer_Social_schema, from app.classes.customer_social import (Customer_Customer_Social_schema,
Customer_Customer_Social) Customer_Customer_Social)
from flask_login import login_required
logger = logging.getLogger(__name__)
@social.route("/posts/<int:customer_id>/<int:page>", methods=["GET"]) @social.route("/posts/<int:customer_id>/<int:page>", methods=["GET"])
@login_required
def get_customer_posts(customer_id, page): def get_customer_posts(customer_id, page):
logger.info(f"GET /social/posts/{customer_id}/{page} - Fetching customer posts page {page}")
per_page_amount = 50 per_page_amount = 50
if page is None: if page is None:
offset_limit = 0 offset_limit = 0
@@ -16,7 +22,7 @@ def get_customer_posts(customer_id, page):
offset_limit = 0 offset_limit = 0
else: else:
offset_limit = (per_page_amount * page) - per_page_amount offset_limit = (per_page_amount * page) - per_page_amount
customer_posts = (db.session customer_posts = (db.session
.query(Customer_Customer_Social) .query(Customer_Customer_Social)
.filter(Customer_Customer_Social.customer_id == customer_id) .filter(Customer_Customer_Social.customer_id == customer_id)
@@ -28,8 +34,10 @@ def get_customer_posts(customer_id, page):
@social.route("/create/<int:customer_id>", methods=["POST"]) @social.route("/create/<int:customer_id>", methods=["POST"])
@login_required
def create_post(customer_id): def create_post(customer_id):
logger.info(f"POST /social/create/{customer_id} - Creating new social post")
comment = request.json["comment"] comment = request.json["comment"]
poster_employee_id = request.json["poster_employee_id"] poster_employee_id = request.json["poster_employee_id"]
@@ -47,7 +55,9 @@ def create_post(customer_id):
@social.route("/posts/<int:post_id>", methods=["PATCH"]) @social.route("/posts/<int:post_id>", methods=["PATCH"])
@login_required
def edit_post(post_id): def edit_post(post_id):
logger.info(f"PATCH /social/posts/{post_id} - Editing social post")
customer_post = (db.session customer_post = (db.session
.query(Customer_Customer_Social) .query(Customer_Customer_Social)
@@ -55,7 +65,7 @@ def edit_post(post_id):
.first()) .first())
comment = request.json["comment"] comment = request.json["comment"]
customer_post.comment = comment customer_post.comment = comment
db.session.add(customer_post) db.session.add(customer_post)
db.session.commit() db.session.commit()
@@ -64,7 +74,9 @@ def edit_post(post_id):
@social.route("/delete/<int:post_id>", methods=["DELETE"]) @social.route("/delete/<int:post_id>", methods=["DELETE"])
@login_required
def delete_post(post_id): def delete_post(post_id):
logger.info(f"DELETE /social/delete/{post_id} - Deleting social post")
customer_post = (db.session customer_post = (db.session
.query(Customer_Customer_Social) .query(Customer_Customer_Social)

View File

@@ -1,3 +1,4 @@
import logging
from flask import jsonify from flask import jsonify
from datetime import date from datetime import date
from app.stats import stats from app.stats import stats
@@ -7,6 +8,8 @@ from app.classes.delivery import Delivery_Delivery
from app.classes.stats_company import Stats_Company, Stats_Company_schema from app.classes.stats_company import Stats_Company, Stats_Company_schema
from app.classes.stats_customer import Stats_Customer, Stats_Customer_schema from app.classes.stats_customer import Stats_Customer, Stats_Customer_schema
logger = logging.getLogger(__name__)
def get_monday_date(date_object): def get_monday_date(date_object):
"""Gets the date of the Monday for the given date.""" """Gets the date of the Monday for the given date."""
@@ -27,6 +30,7 @@ def get_monday_date(date_object):
@stats.route("/calls/add", methods=["PUT"]) @stats.route("/calls/add", methods=["PUT"])
def total_calls_post(): def total_calls_post():
logger.info("PUT /stats/calls/add - Incrementing call count")
total_calls_today = (db.session total_calls_today = (db.session
.query(Stats_Company) .query(Stats_Company)
.filter(Stats_Company.expected_delivery_date == date.today()) .filter(Stats_Company.expected_delivery_date == date.today())
@@ -38,17 +42,18 @@ def total_calls_post():
db.session.add(total_calls_today) db.session.add(total_calls_today)
db.session.commit() db.session.commit()
return jsonify({"ok": True,}), 200 return jsonify({"ok": True,}), 200
@stats.route("/calls/count/today", methods=["GET"]) @stats.route("/calls/count/today", methods=["GET"])
def total_calls_today(): def total_calls_today():
logger.info("GET /stats/calls/count/today - Getting today's call count")
total_calls_today = (db.session total_calls_today = (db.session
.query(Stats_Company) .query(Stats_Company)
.filter(Stats_Company.expected_delivery_date == date.today()) .filter(Stats_Company.expected_delivery_date == date.today())
.count()) .count())
return jsonify({"ok": True, return jsonify({"ok": True,
'data': total_calls_today, 'data': total_calls_today,
}), 200 }), 200
@@ -56,17 +61,18 @@ def total_calls_today():
@stats.route("/gallons/total/<int:driver_id>", methods=["GET"]) @stats.route("/gallons/total/<int:driver_id>", methods=["GET"])
def total_gallons_delivered_driver(driver_id): def total_gallons_delivered_driver(driver_id):
logger.info(f"GET /stats/gallons/total/{driver_id} - Calculating total gallons for driver")
gallons_list = [] gallons_list = []
total_gallons = db.session\ total_gallons = db.session\
.query(Delivery_Delivery)\ .query(Delivery_Delivery)\
.filter(Delivery_Delivery.driver_employee_id == driver_id)\ .filter(Delivery_Delivery.driver_employee_id == driver_id)\
.all() .all()
for f in total_gallons: for f in total_gallons:
gallons_list.append(f.gallons_delivered) gallons_list.append(f.gallons_delivered)
sum_of_gallons = (sum(gallons_list)) sum_of_gallons = (sum(gallons_list))
return jsonify({"ok": True, return jsonify({"ok": True,
'data': sum_of_gallons, 'data': sum_of_gallons,
}), 200 }), 200
@@ -74,6 +80,7 @@ def total_gallons_delivered_driver(driver_id):
@stats.route("/delivery/total/<int:driver_id>", methods=["GET"]) @stats.route("/delivery/total/<int:driver_id>", methods=["GET"])
def total_deliveries_driver(driver_id): def total_deliveries_driver(driver_id):
logger.info(f"GET /stats/delivery/total/{driver_id} - Counting total deliveries for driver")
total_stops = (db.session total_stops = (db.session
.query(Delivery_Delivery) .query(Delivery_Delivery)
.filter(Delivery_Delivery.driver_employee_id == driver_id) .filter(Delivery_Delivery.driver_employee_id == driver_id)
@@ -85,12 +92,13 @@ def total_deliveries_driver(driver_id):
@stats.route("/primes/total/<int:driver_id>", methods=["GET"]) @stats.route("/primes/total/<int:driver_id>", methods=["GET"])
def total_primes_driver(driver_id): def total_primes_driver(driver_id):
logger.info(f"GET /stats/primes/total/{driver_id} - Counting prime deliveries for driver")
total_stops = (db.session total_stops = (db.session
.query(Delivery_Delivery) .query(Delivery_Delivery)
.filter(Delivery_Delivery.driver_employee_id == driver_id) .filter(Delivery_Delivery.driver_employee_id == driver_id)
.filter(Delivery_Delivery.prime == 1) .filter(Delivery_Delivery.prime == 1)
.count()) .count())
return jsonify({"ok": True, return jsonify({"ok": True,
'data': total_stops, 'data': total_stops,
@@ -98,6 +106,7 @@ def total_primes_driver(driver_id):
@stats.route("/delivery/count/today", methods=["GET"]) @stats.route("/delivery/count/today", methods=["GET"])
def total_deliveries_today(): def total_deliveries_today():
logger.info("GET /stats/delivery/count/today - Counting today's deliveries")
total_stops = (db.session total_stops = (db.session
.query(Delivery_Delivery) .query(Delivery_Delivery)
.filter(Delivery_Delivery.expected_delivery_date == date.today()) .filter(Delivery_Delivery.expected_delivery_date == date.today())
@@ -109,12 +118,13 @@ def total_deliveries_today():
@stats.route("/delivery/count/delivered/today", methods=["GET"]) @stats.route("/delivery/count/delivered/today", methods=["GET"])
def total_deliveries_today_finished(): def total_deliveries_today_finished():
logger.info("GET /stats/delivery/count/delivered/today - Counting completed deliveries today")
total_stops = (db.session total_stops = (db.session
.query(Delivery_Delivery) .query(Delivery_Delivery)
.filter(Delivery_Delivery.expected_delivery_date == date.today()) .filter(Delivery_Delivery.expected_delivery_date == date.today())
.filter((Delivery_Delivery.delivery_status == 10)) .filter((Delivery_Delivery.delivery_status == 10))
.count()) .count())
return jsonify({"ok": True, return jsonify({"ok": True,
'data': total_stops, 'data': total_stops,
}), 200 }), 200
@@ -125,6 +135,7 @@ def get_user_stats(user_id):
""" """
gets stats of user gets stats of user
""" """
logger.info(f"GET /stats/user/{user_id} - Fetching user statistics")
get_user = db.session \ get_user = db.session \
.query(Stats_Customer) \ .query(Stats_Customer) \
.filter(Stats_Customer.customer_id == user_id) \ .filter(Stats_Customer.customer_id == user_id) \
@@ -157,6 +168,7 @@ def get_user_last_delivery(user_id):
""" """
gets users last delivery. used on profile page gets users last delivery. used on profile page
""" """
logger.info(f"GET /stats/user/lastdelivery/{user_id} - Fetching user's last delivery date")
get_delivery= db.session \ get_delivery= db.session \
.query(Delivery_Delivery) \ .query(Delivery_Delivery) \
.filter(Delivery_Delivery.customer_id == user_id) \ .filter(Delivery_Delivery.customer_id == user_id) \
@@ -174,6 +186,7 @@ def get_user_last_delivery(user_id):
@stats.route("/gallons/week", methods=["GET"]) @stats.route("/gallons/week", methods=["GET"])
def total_gallons_delivered_this_week(): def total_gallons_delivered_this_week():
logger.info("GET /stats/gallons/week - Calculating weekly gallons delivered")
# Get today's date # Get today's date
total_gallons = 0 total_gallons = 0
@@ -194,6 +207,7 @@ def total_gallons_delivered_this_week():
@stats.route("/gallons/check/total/<int:user_id>", methods=["GET"]) @stats.route("/gallons/check/total/<int:user_id>", methods=["GET"])
def calculate_gallons_user(user_id): def calculate_gallons_user(user_id):
logger.info(f"GET /stats/gallons/check/total/{user_id} - Recalculating user total gallons")
# Get today's date # Get today's date
total_gallons = 0 total_gallons = 0
@@ -215,4 +229,3 @@ def calculate_gallons_user(user_id):
db.session.commit() db.session.commit()
return jsonify({"ok": True, return jsonify({"ok": True,
}), 200 }), 200

View File

@@ -1,14 +1,16 @@
import logging
from flask import jsonify from flask import jsonify
from app.ticket import ticket from app.ticket import ticket
from app import db from app import db
from app.classes.delivery import Delivery_Delivery from app.classes.delivery import Delivery_Delivery
logger = logging.getLogger(__name__)
@ticket.route("/<int:ticket_id>", methods=["GET"]) @ticket.route("/<int:ticket_id>", methods=["GET"])
def get_ticket_printer_letter(ticket_id): def get_ticket_printer_letter(ticket_id):
logger.info(f"GET /ticket/{ticket_id} - Generating ticket printer letter")
return jsonify({"ok": True, return jsonify({"ok": True,
}), 200 }), 200

View File

@@ -3,7 +3,6 @@ import os
def load_config(mode=os.environ.get('MODE')): def load_config(mode=os.environ.get('MODE')):
try: try:
print(f"mode is {mode}")
if mode == 'PRODUCTION': if mode == 'PRODUCTION':
from settings_prod import ApplicationConfig from settings_prod import ApplicationConfig
return ApplicationConfig return ApplicationConfig
@@ -21,4 +20,4 @@ def load_config(mode=os.environ.get('MODE')):
except ImportError: except ImportError:
from settings_local import ApplicationConfig from settings_local import ApplicationConfig
return ApplicationConfig return ApplicationConfig

1
migrations/README Normal file
View File

@@ -0,0 +1 @@
Single-database configuration for Flask.

50
migrations/alembic.ini Normal file
View File

@@ -0,0 +1,50 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic,flask_migrate
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

113
migrations/env.py Normal file
View File

@@ -0,0 +1,113 @@
import logging
from logging.config import fileConfig
from flask import current_app
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
def get_engine():
try:
# this works with Flask-SQLAlchemy<3 and Alchemical
return current_app.extensions['migrate'].db.get_engine()
except (TypeError, AttributeError):
# this works with Flask-SQLAlchemy>=3
return current_app.extensions['migrate'].db.engine
def get_engine_url():
try:
return get_engine().url.render_as_string(hide_password=False).replace(
'%', '%%')
except AttributeError:
return str(get_engine().url).replace('%', '%%')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
config.set_main_option('sqlalchemy.url', get_engine_url())
target_db = current_app.extensions['migrate'].db
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def get_metadata():
if hasattr(target_db, 'metadatas'):
return target_db.metadatas[None]
return target_db.metadata
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=get_metadata(), literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
conf_args = current_app.extensions['migrate'].configure_args
if conf_args.get("process_revision_directives") is None:
conf_args["process_revision_directives"] = process_revision_directives
connectable = get_engine()
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=get_metadata(),
**conf_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
migrations/script.py.mako Normal file
View File

@@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@@ -0,0 +1,185 @@
"""Initial baseline
Revision ID: b43a39b1cf25
Revises:
Create Date: 2026-01-21 02:25:55.179218
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'b43a39b1cf25'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('printer_jobs')
op.drop_table('query_town_ist')
op.drop_table('taxes_pricing')
op.drop_table('pricing_service_general')
op.drop_table('portal_user')
op.drop_table('delivery_payment')
with op.batch_alter_table('street_reference', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_public_street_reference_osm_id'))
batch_op.drop_index(batch_op.f('ix_public_street_reference_street_name_normalized'))
batch_op.drop_index(batch_op.f('ix_street_ref_name_town'))
batch_op.drop_index(batch_op.f('ix_street_ref_town_state'))
op.drop_table('street_reference')
with op.batch_alter_table('auth_users', schema=None) as batch_op:
batch_op.create_unique_constraint(None, ['id'])
with op.batch_alter_table('auto_delivery', schema=None) as batch_op:
batch_op.alter_column('estimated_gallons_left',
existing_type=sa.INTEGER(),
type_=sa.DECIMAL(precision=6, scale=2),
existing_nullable=True)
batch_op.alter_column('estimated_gallons_left_prev_day',
existing_type=sa.INTEGER(),
type_=sa.DECIMAL(precision=6, scale=2),
existing_nullable=True)
with op.batch_alter_table('customer_customer', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_public_customer_customer_auth_net_profile_id'), ['auth_net_profile_id'], unique=True)
batch_op.drop_column('verified_at')
with op.batch_alter_table('service_service', schema=None) as batch_op:
batch_op.alter_column('when_ordered',
existing_type=sa.DATE(),
type_=sa.DATETIME(),
existing_nullable=True)
batch_op.alter_column('scheduled_date',
existing_type=postgresql.DOMAIN('time_stamp', TIMESTAMP()),
type_=sa.DATETIME(),
existing_nullable=True,
existing_server_default=sa.text('CURRENT_TIMESTAMP(2)'))
batch_op.alter_column('service_cost',
existing_type=sa.NUMERIC(precision=10, scale=2),
nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('service_service', schema=None) as batch_op:
batch_op.alter_column('service_cost',
existing_type=sa.NUMERIC(precision=10, scale=2),
nullable=True)
batch_op.alter_column('scheduled_date',
existing_type=sa.DATETIME(),
type_=postgresql.DOMAIN('time_stamp', TIMESTAMP()),
existing_nullable=True,
existing_server_default=sa.text('CURRENT_TIMESTAMP(2)'))
batch_op.alter_column('when_ordered',
existing_type=sa.DATETIME(),
type_=sa.DATE(),
existing_nullable=True)
with op.batch_alter_table('customer_customer', schema=None) as batch_op:
batch_op.add_column(sa.Column('verified_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True))
batch_op.drop_index(batch_op.f('ix_public_customer_customer_auth_net_profile_id'))
with op.batch_alter_table('auto_delivery', schema=None) as batch_op:
batch_op.alter_column('estimated_gallons_left_prev_day',
existing_type=sa.DECIMAL(precision=6, scale=2),
type_=sa.INTEGER(),
existing_nullable=True)
batch_op.alter_column('estimated_gallons_left',
existing_type=sa.DECIMAL(precision=6, scale=2),
type_=sa.INTEGER(),
existing_nullable=True)
with op.batch_alter_table('auth_users', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='unique')
op.create_table('street_reference',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('street_name', sa.VARCHAR(length=500), autoincrement=False, nullable=False),
sa.Column('street_name_normalized', sa.VARCHAR(length=500), autoincrement=False, nullable=False),
sa.Column('street_number_low', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('street_number_high', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('town', sa.VARCHAR(length=140), autoincrement=False, nullable=False),
sa.Column('town_normalized', sa.VARCHAR(length=140), autoincrement=False, nullable=False),
sa.Column('state', sa.VARCHAR(length=2), autoincrement=False, nullable=False),
sa.Column('zip_codes', sa.VARCHAR(length=100), autoincrement=False, nullable=True),
sa.Column('osm_id', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('street_reference_pkey'))
)
with op.batch_alter_table('street_reference', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_street_ref_town_state'), ['town_normalized', 'state'], unique=False)
batch_op.create_index(batch_op.f('ix_street_ref_name_town'), ['street_name_normalized', 'town_normalized'], unique=False)
batch_op.create_index(batch_op.f('ix_public_street_reference_street_name_normalized'), ['street_name_normalized'], unique=False)
batch_op.create_index(batch_op.f('ix_public_street_reference_osm_id'), ['osm_id'], unique=False)
op.create_table('delivery_payment',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('delivery_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('time_added', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.Column('total_amount_oil', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.Column('total_amount_emergency', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.Column('total_amount_prime', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.Column('total_amount_fee', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.Column('total_amount', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('delivery_payment_pkey'))
)
op.create_table('portal_user',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('username', sa.VARCHAR(length=50), autoincrement=False, nullable=True),
sa.Column('account_number', sa.VARCHAR(length=32), autoincrement=False, nullable=True),
sa.Column('house_number', sa.VARCHAR(length=32), autoincrement=False, nullable=True),
sa.Column('email', sa.VARCHAR(length=350), autoincrement=False, nullable=True),
sa.Column('password_hash', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('member_since', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
sa.Column('last_seen', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
sa.Column('admin', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('admin_role', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('confirmed', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('active', sa.INTEGER(), server_default=sa.text('1'), autoincrement=False, nullable=True),
sa.Column('password_reset_expires', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.Column('password_reset_token', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('confirmation_token', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('confirmation_sent_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.Column('confirmed_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('portal_user_pkey'))
)
op.create_table('pricing_service_general',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('price_service_hour', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.Column('price_emergency_service_hour', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.Column('price_emergency_call', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.Column('price_out_of_oil', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.Column('price_prime', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.Column('price_cleaning', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.Column('date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('pricing_service_general_pkey'))
)
op.create_table('taxes_pricing',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('state_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('taxes_oil', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.Column('taxes_other', sa.NUMERIC(precision=50, scale=2), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('taxes_pricing_pkey'))
)
op.create_table('query_town_ist',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('value', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('text', sa.VARCHAR(length=240), autoincrement=False, nullable=True)
)
op.create_table('printer_jobs',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('delivery_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('date_added', sa.DATE(), autoincrement=False, nullable=True),
sa.Column('date_completed', sa.DATE(), autoincrement=False, nullable=True),
sa.Column('employee_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('status', sa.INTEGER(), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('printer_jobs_pkey'))
)
# ### end Alembic commands ###

View File

@@ -7,6 +7,7 @@ Flask-Bcrypt==1.0.1
flask-cors==5.0.1 flask-cors==5.0.1
Flask-Login==0.6.3 Flask-Login==0.6.3
Flask-Mail==0.10.0 Flask-Mail==0.10.0
Flask-Migrate==4.0.7
flask-marshmallow==1.3.0 flask-marshmallow==1.3.0
Flask-Moment==1.0.6 Flask-Moment==1.0.6
Flask-Paranoid==0.3.0 Flask-Paranoid==0.3.0

View File

@@ -1,38 +1,42 @@
import os
class ApplicationConfig: class ApplicationConfig:
""" """
Basic Configuration for a generic User Development Configuration
""" """
CURRENT_SETTINGS = 'LOCAL' CURRENT_SETTINGS = 'DEVELOPMENT'
# databases info
POSTGRES_USERNAME = 'postgres' # Database credentials (defaults for local dev)
POSTGRES_PW = 'password' POSTGRES_USERNAME = os.environ.get('POSTGRES_USERNAME', 'postgres')
POSTGRES_SERVER = '192.168.1.204:5432' POSTGRES_PW = os.environ.get('POSTGRES_PW', 'password')
POSTGRES_DBNAME00 = 'eamco' POSTGRES_SERVER = os.environ.get('POSTGRES_SERVER', '192.168.1.204:5432')
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME, POSTGRES_DBNAME00 = os.environ.get('POSTGRES_DBNAME', 'eamco')
POSTGRES_PW,
POSTGRES_SERVER, SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(
POSTGRES_DBNAME00 POSTGRES_USERNAME,
) POSTGRES_PW,
POSTGRES_SERVER,
POSTGRES_DBNAME00
)
SQLALCHEMY_BINDS = {'eamco': SQLALCHEMY_DATABASE_URI} SQLALCHEMY_BINDS = {'eamco': SQLALCHEMY_DATABASE_URI}
# sqlalchemy config
# SQLAlchemy config
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
TRAP_HTTP_EXCEPTIONS = True TRAP_HTTP_EXCEPTIONS = True
PROPAGATE_EXCEPTIONS = True PROPAGATE_EXCEPTIONS = True
DEBUG = True DEBUG = True
UPLOADED_FILES_DEST_ITEM = '/data/item' UPLOADED_FILES_DEST_ITEM = '/data/item'
# file uploads # File uploads
UPLOADED_FILES_ALLOW = ['png', 'jpeg', 'jpg', 'png', 'gif'] UPLOADED_FILES_ALLOW = ['png', 'jpeg', 'jpg', 'png', 'gif']
MAX_CONTENT_LENGTH = 5 * 2500 * 2500 MAX_CONTENT_LENGTH = 5 * 2500 * 2500
ALLOWED_EXTENSIONS = ['png', 'jpeg', 'jpg', 'png', 'gif'] ALLOWED_EXTENSIONS = ['png', 'jpeg', 'jpg', 'png', 'gif']
# secret keys # Secret key (default for local dev only)
SECRET_KEY = "youwillneverguessthiskeycia" SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-key-not-for-production')
# sessions # Sessions
# Available SESSION_TYPE options: 'redis', 'sqlalchemy', 'mongodb', 'filesystem', 'memcached'
SESSION_TYPE = "sqlalchemy" SESSION_TYPE = "sqlalchemy"
SESSION_COOKIE_NAME = "eamco_session" SESSION_COOKIE_NAME = "eamco_session"
SESSION_COOKIE_SECURE = False SESSION_COOKIE_SECURE = False
@@ -43,13 +47,8 @@ class ApplicationConfig:
SESSION_USE_SIGNER = True SESSION_USE_SIGNER = True
# CORS # CORS
CORS_ALLOWED_ORIGINS = ["*"]
CORS_ALLOWED_ORIGINS = [
"*"
]
CORS_SEND_WILDCARD = False CORS_SEND_WILDCARD = False
CORS_SUPPORT_CREDENTIALS = True CORS_SUPPORT_CREDENTIALS = True
CORS_EXPOSE_HEADERS = None CORS_EXPOSE_HEADERS = None
CORS_ALLOW_HEADERS = "*" CORS_ALLOW_HEADERS = "*"

View File

@@ -1,38 +1,42 @@
import os
class ApplicationConfig: class ApplicationConfig:
""" """
Basic Configuration for a generic User Local Configuration (LAN deployment)
""" """
CURRENT_SETTINGS = 'LOCAL' CURRENT_SETTINGS = 'LOCAL'
# databases info
POSTGRES_USERNAME = 'postgres' # Database credentials from environment variables
POSTGRES_PW = 'password' POSTGRES_USERNAME = os.environ.get('POSTGRES_USERNAME', 'postgres')
POSTGRES_SERVER = '192.168.1.204:5432' POSTGRES_PW = os.environ.get('POSTGRES_PW')
POSTGRES_DBNAME00 = 'auburnoil' POSTGRES_SERVER = os.environ.get('POSTGRES_SERVER', '192.168.1.204:5432')
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME, POSTGRES_DBNAME00 = os.environ.get('POSTGRES_DBNAME', 'auburnoil')
POSTGRES_PW,
POSTGRES_SERVER, SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(
POSTGRES_DBNAME00 POSTGRES_USERNAME,
) POSTGRES_PW,
POSTGRES_SERVER,
POSTGRES_DBNAME00
)
SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI} SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI}
# sqlalchemy config
# SQLAlchemy config
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
TRAP_HTTP_EXCEPTIONS = True TRAP_HTTP_EXCEPTIONS = True
PROPAGATE_EXCEPTIONS = True PROPAGATE_EXCEPTIONS = True
DEBUG = True DEBUG = True
UPLOADED_FILES_DEST_ITEM = '/data/item' UPLOADED_FILES_DEST_ITEM = '/data/item'
# file uploads # File uploads
UPLOADED_FILES_ALLOW = ['png', 'jpeg', 'jpg', 'png', 'gif'] UPLOADED_FILES_ALLOW = ['png', 'jpeg', 'jpg', 'png', 'gif']
MAX_CONTENT_LENGTH = 5 * 2500 * 2500 MAX_CONTENT_LENGTH = 5 * 2500 * 2500
ALLOWED_EXTENSIONS = ['png', 'jpeg', 'jpg', 'png', 'gif'] ALLOWED_EXTENSIONS = ['png', 'jpeg', 'jpg', 'png', 'gif']
# secret keys # Secret key from environment variable
SECRET_KEY = "youwillneverguessthiskeycia" SECRET_KEY = os.environ.get('SECRET_KEY')
# sessions # Sessions
# Available SESSION_TYPE options: 'redis', 'sqlalchemy', 'mongodb', 'filesystem', 'memcached'
SESSION_TYPE = "sqlalchemy" SESSION_TYPE = "sqlalchemy"
SESSION_COOKIE_NAME = "eamco_session" SESSION_COOKIE_NAME = "eamco_session"
SESSION_COOKIE_SECURE = False SESSION_COOKIE_SECURE = False
@@ -43,7 +47,6 @@ class ApplicationConfig:
SESSION_USE_SIGNER = True SESSION_USE_SIGNER = True
# CORS # CORS
CORS_SEND_WILDCARD = False CORS_SEND_WILDCARD = False
CORS_SUPPORT_CREDENTIALS = True CORS_SUPPORT_CREDENTIALS = True
CORS_EXPOSE_HEADERS = None CORS_EXPOSE_HEADERS = None
@@ -54,5 +57,4 @@ class ApplicationConfig:
'http://192.168.1.204:9612', 'http://192.168.1.204:9612',
'http://192.168.1.204:9613', 'http://192.168.1.204:9613',
'http://192.168.1.204:9614', 'http://192.168.1.204:9614',
]
]

View File

@@ -1,38 +1,42 @@
import os
class ApplicationConfig: class ApplicationConfig:
""" """
Basic Configuration for a generic User Production Configuration
""" """
CURRENT_SETTINGS = 'PRODUCTION' CURRENT_SETTINGS = 'PRODUCTION'
# databases info
POSTGRES_USERNAME = 'postgres' # Database credentials from environment variables
POSTGRES_PW = 'password' POSTGRES_USERNAME = os.environ.get('POSTGRES_USERNAME', 'postgres')
POSTGRES_SERVER = '192.168.1.204:5432' POSTGRES_PW = os.environ.get('POSTGRES_PW')
POSTGRES_SERVER = os.environ.get('POSTGRES_SERVER', '192.168.1.204:5432')
POSTGRES_DBNAME00 = 'auburnoil' POSTGRES_DBNAME00 = os.environ.get('POSTGRES_DBNAME', 'auburnoil')
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME,
POSTGRES_PW, SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(
POSTGRES_SERVER, POSTGRES_USERNAME,
POSTGRES_DBNAME00 POSTGRES_PW,
) POSTGRES_SERVER,
POSTGRES_DBNAME00
)
SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI} SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI}
# sqlalchemy config
# SQLAlchemy config
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
TRAP_HTTP_EXCEPTIONS = True TRAP_HTTP_EXCEPTIONS = True
PROPAGATE_EXCEPTIONS = True PROPAGATE_EXCEPTIONS = True
DEBUG = False DEBUG = False
UPLOADED_FILES_DEST_ITEM = '/data/item' UPLOADED_FILES_DEST_ITEM = '/data/item'
# file uploads # File uploads
UPLOADED_FILES_ALLOW = ['png', 'jpeg', 'jpg', 'png', 'gif'] UPLOADED_FILES_ALLOW = ['png', 'jpeg', 'jpg', 'png', 'gif']
MAX_CONTENT_LENGTH = 5 * 2500 * 2500 MAX_CONTENT_LENGTH = 5 * 2500 * 2500
ALLOWED_EXTENSIONS = ['png', 'jpeg', 'jpg', 'png', 'gif'] ALLOWED_EXTENSIONS = ['png', 'jpeg', 'jpg', 'png', 'gif']
# secret keys # Secret key from environment variable
SECRET_KEY = "34dsfkjh43123cxzfvqwer23432dsf233214efdasf2134321" SECRET_KEY = os.environ.get('SECRET_KEY')
# sessions # Sessions
SESSION_TYPE = "sqlalchemy" SESSION_TYPE = "sqlalchemy"
SESSION_COOKIE_NAME = "eamco_session" SESSION_COOKIE_NAME = "eamco_session"
SESSION_COOKIE_SECURE = False SESSION_COOKIE_SECURE = False
@@ -43,9 +47,6 @@ class ApplicationConfig:
SESSION_USE_SIGNER = True SESSION_USE_SIGNER = True
# CORS # CORS
CORS_SEND_WILDCARD = False CORS_SEND_WILDCARD = False
CORS_SUPPORT_CREDENTIALS = True CORS_SUPPORT_CREDENTIALS = True
CORS_EXPOSE_HEADERS = None CORS_EXPOSE_HEADERS = None
@@ -53,4 +54,4 @@ class ApplicationConfig:
CORS_ALLOWED_ORIGINS = [ CORS_ALLOWED_ORIGINS = [
'https://oil.edwineames.com', 'https://oil.edwineames.com',
'https://edwineames.com' 'https://edwineames.com'
] ]

View File

@@ -1,6 +1,9 @@
#!/bin/bash #!/bin/bash
set -e set -e
# Run database migrations
flask db upgrade
# Start Gunicorn # Start Gunicorn
gunicorn --bind 127.0.0.1:8000 --workers 4 --timeout 120 app:app & gunicorn --bind 127.0.0.1:8000 --workers 4 --timeout 120 app:app &