feat: 5-tier pricing, market ticker integration, and delivery stats
Major update spanning pricing, market data, and analytics: - Pricing: Replace single-price service fees with 5-tier pricing for same-day, prime, and emergency deliveries across create/edit/finalize - Market: Add Ticker_Price and CompanyPrice models with endpoints for live commodity prices (HO, CL, RB) and competitor price tracking - Stats: Add daily/weekly/monthly gallons endpoints with multi-year comparison and YoY totals for the stats dashboard - Delivery: Add map and history endpoints, fix finalize null-driver crash - Schema: Change fill_location from INTEGER to VARCHAR(250), add pre_load normalization for customer updates, fix admin auth check Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
from datetime import date, datetime, timedelta
|
||||
@@ -24,6 +25,109 @@ from app.classes.auto import Tickets_Auto_Delivery, Tickets_Auto_Delivery_schema
|
||||
|
||||
|
||||
# This endpoint is fine, but I've added some comments for clarity.
|
||||
@delivery.route("/map", methods=["GET"])
|
||||
@common_login_required
|
||||
def get_deliveries_for_map():
|
||||
"""Get deliveries for map view by date."""
|
||||
date_str = request.args.get('date')
|
||||
if date_str:
|
||||
try:
|
||||
target_date = datetime.strptime(date_str, '%Y-%m-%d').date()
|
||||
except ValueError:
|
||||
return error_response("Invalid date format. Use YYYY-MM-DD", 400)
|
||||
else:
|
||||
target_date = date.today()
|
||||
|
||||
deliveries = (db.session
|
||||
.query(Delivery_Delivery, Customer_Customer)
|
||||
.join(Customer_Customer, Delivery_Delivery.customer_id == Customer_Customer.id)
|
||||
.filter(Delivery_Delivery.expected_delivery_date == target_date)
|
||||
.filter(Delivery_Delivery.delivery_status.notin_([1, 10]))
|
||||
.order_by(Delivery_Delivery.customer_town.asc())
|
||||
.all())
|
||||
|
||||
result = []
|
||||
for delivery_item, customer in deliveries:
|
||||
result.append({
|
||||
'id': delivery_item.id,
|
||||
'street': delivery_item.customer_address,
|
||||
'town': delivery_item.customer_town,
|
||||
'state': delivery_item.customer_state,
|
||||
'zipcode': delivery_item.customer_zip,
|
||||
'customerName': delivery_item.customer_name,
|
||||
'notes': delivery_item.dispatcher_notes or '',
|
||||
'latitude': customer.customer_latitude,
|
||||
'longitude': customer.customer_longitude,
|
||||
'gallonsOrdered': delivery_item.gallons_ordered,
|
||||
'isFill': delivery_item.customer_asked_for_fill == 1,
|
||||
'deliveryStatus': delivery_item.delivery_status,
|
||||
'customerId': delivery_item.customer_id,
|
||||
})
|
||||
|
||||
return success_response({'deliveries': result})
|
||||
|
||||
|
||||
@delivery.route("/history", methods=["GET"])
|
||||
@common_login_required
|
||||
def get_deliveries_history():
|
||||
"""Get completed deliveries (delivered or finalized) for a date range."""
|
||||
start_date_str = request.args.get('start_date')
|
||||
end_date_str = request.args.get('end_date')
|
||||
|
||||
try:
|
||||
start_date = datetime.strptime(start_date_str, '%Y-%m-%d').date() if start_date_str else date.today()
|
||||
end_date = datetime.strptime(end_date_str, '%Y-%m-%d').date() if end_date_str else date.today()
|
||||
except ValueError:
|
||||
return error_response("Invalid date format. Use YYYY-MM-DD", 400)
|
||||
|
||||
# Status 10 = finalized, 11 = delivered
|
||||
deliveries = (db.session
|
||||
.query(Delivery_Delivery)
|
||||
.filter(Delivery_Delivery.expected_delivery_date >= start_date)
|
||||
.filter(Delivery_Delivery.expected_delivery_date <= end_date)
|
||||
.filter(Delivery_Delivery.delivery_status.in_([10, 11]))
|
||||
.order_by(Delivery_Delivery.expected_delivery_date.desc(), Delivery_Delivery.customer_town.asc(), Delivery_Delivery.customer_name.asc())
|
||||
.all())
|
||||
|
||||
result = []
|
||||
total_gallons = 0
|
||||
prime_count = 0
|
||||
emergency_count = 0
|
||||
same_day_count = 0
|
||||
|
||||
for d in deliveries:
|
||||
gallons = float(d.gallons_delivered) if d.gallons_delivered and d.gallons_delivered > 0 else float(d.gallons_ordered or 0)
|
||||
total_gallons += gallons
|
||||
if d.prime == 1:
|
||||
prime_count += 1
|
||||
if d.emergency == 1:
|
||||
emergency_count += 1
|
||||
if d.same_day == 1:
|
||||
same_day_count += 1
|
||||
|
||||
result.append({
|
||||
'id': d.id,
|
||||
'customerId': d.customer_id,
|
||||
'customerName': d.customer_name,
|
||||
'town': d.customer_town,
|
||||
'address': f"{d.customer_address}, {d.customer_town}, {d.customer_state} {d.customer_zip}",
|
||||
'gallonsDelivered': gallons,
|
||||
'prime': d.prime == 1,
|
||||
'emergency': d.emergency == 1,
|
||||
'sameDay': d.same_day == 1,
|
||||
'deliveryStatus': d.delivery_status,
|
||||
})
|
||||
|
||||
return success_response({
|
||||
'deliveries': result,
|
||||
'totalGallons': total_gallons,
|
||||
'totalDeliveries': len(result),
|
||||
'primeCount': prime_count,
|
||||
'emergencyCount': emergency_count,
|
||||
'sameDayCount': same_day_count,
|
||||
})
|
||||
|
||||
|
||||
@delivery.route("/updatestatus", methods=["GET"])
|
||||
@common_login_required
|
||||
def move_deliveries():
|
||||
@@ -507,6 +611,11 @@ def edit_a_delivery(delivery_id):
|
||||
get_delivery.prime = 1 if data.get("prime") else 0
|
||||
get_delivery.same_day = 1 if data.get("same_day") else 0
|
||||
get_delivery.emergency = 1 if data.get("emergency") else 0
|
||||
|
||||
# Get tier selections (default to existing or 1)
|
||||
get_delivery.pricing_tier_same_day = data.get("pricing_tier_same_day", get_delivery.pricing_tier_same_day or 1)
|
||||
get_delivery.pricing_tier_prime = data.get("pricing_tier_prime", get_delivery.pricing_tier_prime or 1)
|
||||
get_delivery.pricing_tier_emergency = data.get("pricing_tier_emergency", get_delivery.pricing_tier_emergency or 1)
|
||||
|
||||
# --- Handle Driver Assignment ---
|
||||
driver_id = data.get("driver_employee_id")
|
||||
@@ -551,14 +660,20 @@ def edit_a_delivery(delivery_id):
|
||||
gallons_for_calc = 250 if customer_wants_fill else int(get_delivery.gallons_ordered)
|
||||
base_price = gallons_for_calc * get_today_price.price_for_customer
|
||||
|
||||
# Add fees
|
||||
# Add fees using tier pricing
|
||||
total_price = base_price
|
||||
if get_delivery.prime:
|
||||
total_price += get_today_price.price_prime
|
||||
tier_field = f"price_prime_tier{get_delivery.pricing_tier_prime}"
|
||||
prime_fee = Decimal(getattr(get_today_price, tier_field, get_today_price.price_prime))
|
||||
total_price += prime_fee
|
||||
if get_delivery.same_day:
|
||||
total_price += get_today_price.price_same_day
|
||||
tier_field = f"price_same_day_tier{get_delivery.pricing_tier_same_day}"
|
||||
same_day_fee = Decimal(getattr(get_today_price, tier_field, get_today_price.price_same_day))
|
||||
total_price += same_day_fee
|
||||
if get_delivery.emergency:
|
||||
total_price += get_today_price.price_emergency
|
||||
tier_field = f"price_emergency_tier{get_delivery.pricing_tier_emergency}"
|
||||
emergency_fee = Decimal(getattr(get_today_price, tier_field, get_today_price.price_emergency))
|
||||
total_price += emergency_fee
|
||||
|
||||
get_delivery.total_price = base_price # Price before fees
|
||||
get_delivery.pre_charge_amount = total_price # Price including fees
|
||||
@@ -603,6 +718,11 @@ def create_a_delivery(user_id):
|
||||
same_day_info = request.json["same_day"]
|
||||
emergency_info = request.json["emergency"]
|
||||
delivery_driver_id = request.json.get("driver_employee_id", 0)
|
||||
|
||||
# Get tier selections (default to tier 1)
|
||||
pricing_tier_same_day = request.json.get("pricing_tier_same_day", 1)
|
||||
pricing_tier_prime = request.json.get("pricing_tier_prime", 1)
|
||||
pricing_tier_emergency = request.json.get("pricing_tier_emergency", 1)
|
||||
|
||||
card_payment = request.json["credit"]
|
||||
cash_payment = request.json["cash"]
|
||||
@@ -718,18 +838,23 @@ def create_a_delivery(user_id):
|
||||
precharge_amount = int(gallons_ordered) * get_today_price.price_for_customer
|
||||
|
||||
|
||||
# if prime/emergency/sameday
|
||||
if same_day_asked == 1 and prime_asked == 0:
|
||||
total_precharge_amount = precharge_amount + get_today_price.price_same_day
|
||||
|
||||
elif prime_asked == 1 and same_day_asked == 0:
|
||||
total_precharge_amount = precharge_amount + get_today_price.price_prime
|
||||
|
||||
elif emergency_asked == 1:
|
||||
total_precharge_amount = precharge_amount + get_today_price.price_emergency
|
||||
|
||||
else:
|
||||
total_precharge_amount = precharge_amount + get_today_price.price_prime + get_today_price.price_same_day
|
||||
# Calculate fees using tier pricing
|
||||
total_precharge_amount = precharge_amount
|
||||
|
||||
if same_day_asked == 1:
|
||||
tier_field = f"price_same_day_tier{pricing_tier_same_day}"
|
||||
same_day_fee = Decimal(getattr(get_today_price, tier_field, get_today_price.price_same_day))
|
||||
total_precharge_amount += same_day_fee
|
||||
|
||||
if prime_asked == 1:
|
||||
tier_field = f"price_prime_tier{pricing_tier_prime}"
|
||||
prime_fee = Decimal(getattr(get_today_price, tier_field, get_today_price.price_prime))
|
||||
total_precharge_amount += prime_fee
|
||||
|
||||
if emergency_asked == 1:
|
||||
tier_field = f"price_emergency_tier{pricing_tier_emergency}"
|
||||
emergency_fee = Decimal(getattr(get_today_price, tier_field, get_today_price.price_emergency))
|
||||
total_precharge_amount += emergency_fee
|
||||
|
||||
|
||||
new_delivery = Delivery_Delivery(
|
||||
@@ -757,6 +882,9 @@ def create_a_delivery(user_id):
|
||||
prime=prime_asked,
|
||||
same_day=same_day_asked,
|
||||
emergency=emergency_asked,
|
||||
pricing_tier_same_day=pricing_tier_same_day,
|
||||
pricing_tier_prime=pricing_tier_prime,
|
||||
pricing_tier_emergency=pricing_tier_emergency,
|
||||
payment_type=delivery_payment_method,
|
||||
payment_card_id=card_id_from_customer,
|
||||
pre_charge_amount=total_precharge_amount,
|
||||
@@ -955,17 +1083,23 @@ def calculate_total(delivery_id):
|
||||
.first())
|
||||
|
||||
if get_delivery.prime == 1:
|
||||
priceprime = float(get_price_query.price_prime)
|
||||
tier = get_delivery.pricing_tier_prime or 1
|
||||
tier_field = f"price_prime_tier{tier}"
|
||||
priceprime = float(getattr(get_price_query, tier_field, get_price_query.price_prime))
|
||||
else:
|
||||
priceprime = 0
|
||||
|
||||
if get_delivery.emergency == 1:
|
||||
priceemergency = float(get_price_query.price_emergency)
|
||||
tier = get_delivery.pricing_tier_emergency or 1
|
||||
tier_field = f"price_emergency_tier{tier}"
|
||||
priceemergency = float(getattr(get_price_query, tier_field, get_price_query.price_emergency))
|
||||
else:
|
||||
priceemergency = 0
|
||||
|
||||
if get_delivery.same_day == 1:
|
||||
pricesameday = float(get_price_query.price_same_day)
|
||||
tier = get_delivery.pricing_tier_same_day or 1
|
||||
tier_field = f"price_same_day_tier{tier}"
|
||||
pricesameday = float(getattr(get_price_query, tier_field, get_price_query.price_same_day))
|
||||
else:
|
||||
pricesameday = 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user