Add bid tracker

This commit is contained in:
2026-04-24 07:15:48 -04:00
parent 262c71e93b
commit 6ec439bafe
7 changed files with 2273 additions and 205 deletions
+420
View File
@@ -1693,3 +1693,423 @@ def get_ai_analysis_detail(analysis_id: int):
finally:
if conn:
conn.close()
# ─── Bid Tracker ──────────────────────────────────────────────────────────────
def get_all_bids(status_filter=None):
"""
Return all bids ordered by updated_at DESC.
status_filter: optional string to filter by status, e.g. 'open'.
Includes creator username and latest update timestamp.
"""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
where = ""
params = []
if status_filter and status_filter != "all":
where = "WHERE b.status = %s"
params.append(status_filter)
cur.execute(
f"""
SELECT b.*,
COALESCE(u.full_name, u.username) AS creator_name,
u.username AS creator_username,
(SELECT COUNT(*) FROM bid_updates bu WHERE bu.bid_id = b.id)
AS update_count,
(SELECT MAX(bu2.created_at) FROM bid_updates bu2
WHERE bu2.bid_id = b.id) AS last_update_at
FROM bid_tracker b
LEFT JOIN users u ON u.id = b.created_by
{where}
ORDER BY b.updated_at DESC
""",
params
)
rows = cur.fetchall()
cur.close()
return rows
finally:
if conn:
conn.close()
def get_bid_by_id(bid_id: int):
"""Return a single bid row with creator info."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute(
"""
SELECT b.*,
COALESCE(u.full_name, u.username) AS creator_name,
u.username AS creator_username
FROM bid_tracker b
LEFT JOIN users u ON u.id = b.created_by
WHERE b.id = %s
""",
(bid_id,)
)
row = cur.fetchone()
cur.close()
return row
finally:
if conn:
conn.close()
def create_bid(user_id: int, title: str, url: str,
source: str, notes: str, status: str) -> int:
"""Insert a new bid. Returns the new row id."""
conn = None
try:
conn = get_connection()
cur = conn.cursor()
cur.execute(
"""
INSERT INTO bid_tracker (title, url, source, notes, status, created_by)
VALUES (%s, %s, %s, %s, %s, %s)
""",
(title, url, source or None, notes or None, status, user_id)
)
conn.commit()
new_id = cur.lastrowid
cur.close()
log_action(user_id, "CREATE_BID", "bid_tracker", new_id,
f"Created bid '{title}' status={status} url={url[:80]}")
logger.info(f"Bid id={new_id} '{title}' created by user_id={user_id}.")
return new_id
finally:
if conn:
conn.close()
def update_bid(user_id: int, bid_id: int, title: str, url: str,
source: str, notes: str, status: str):
"""Update an existing bid record."""
conn = None
try:
conn = get_connection()
cur = conn.cursor()
cur.execute(
"""
UPDATE bid_tracker
SET title=%s, url=%s, source=%s, notes=%s, status=%s
WHERE id=%s
""",
(title, url, source or None, notes or None, status, bid_id)
)
conn.commit()
cur.close()
log_action(user_id, "UPDATE_BID", "bid_tracker", bid_id,
f"Updated bid id={bid_id} '{title}' status={status}")
logger.info(f"Bid id={bid_id} updated by user_id={user_id}.")
finally:
if conn:
conn.close()
def delete_bid(user_id: int, bid_id: int):
"""Hard-delete a bid and all its updates (CASCADE)."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute("SELECT title FROM bid_tracker WHERE id=%s", (bid_id,))
row = cur.fetchone()
title = row["title"] if row else str(bid_id)
cur.execute("DELETE FROM bid_tracker WHERE id=%s", (bid_id,))
conn.commit()
cur.close()
log_action(user_id, "DELETE_BID", "bid_tracker", bid_id,
f"Deleted bid id={bid_id} '{title}'")
logger.info(f"Bid id={bid_id} '{title}' deleted by user_id={user_id}.")
finally:
if conn:
conn.close()
def get_bid_updates(bid_id: int):
"""Return all updates for a bid, newest first."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute(
"""
SELECT bu.*,
COALESCE(u.full_name, u.username) AS author_name,
u.username AS author_username
FROM bid_updates bu
LEFT JOIN users u ON u.id = bu.user_id
WHERE bu.bid_id = %s
ORDER BY bu.created_at DESC
""",
(bid_id,)
)
rows = cur.fetchall()
cur.close()
return rows
finally:
if conn:
conn.close()
def add_bid_update(user_id: int, bid_id: int, content: str) -> int:
"""Add an update entry to a bid. Also touches bid_tracker.updated_at."""
conn = None
try:
conn = get_connection()
cur = conn.cursor()
cur.execute(
"INSERT INTO bid_updates (bid_id, user_id, content) VALUES (%s, %s, %s)",
(bid_id, user_id, content)
)
# Touch bid updated_at so it sorts to top of list
cur.execute(
"UPDATE bid_tracker SET updated_at=NOW() WHERE id=%s", (bid_id,))
conn.commit()
new_id = cur.lastrowid
cur.close()
log_action(user_id, "ADD_BID_UPDATE", "bid_updates", new_id,
f"Added update to bid id={bid_id}: {content[:100]}")
logger.info(f"Bid update id={new_id} added to bid id={bid_id} "
f"by user_id={user_id}.")
return new_id
finally:
if conn:
conn.close()
def delete_bid_update(user_id: int, update_id: int):
"""Delete a single bid update entry."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute("SELECT bid_id, content FROM bid_updates WHERE id=%s",
(update_id,))
row = cur.fetchone()
bid_id = row["bid_id"] if row else 0
snippet = (row["content"][:60] if row else "") if row else ""
cur.execute("DELETE FROM bid_updates WHERE id=%s", (update_id,))
conn.commit()
cur.close()
log_action(user_id, "DELETE_BID_UPDATE", "bid_updates", update_id,
f"Deleted update id={update_id} from bid id={bid_id}: {snippet}")
logger.info(f"Bid update id={update_id} deleted by user_id={user_id}.")
finally:
if conn:
conn.close()
# ─── Bid Tracker CRUD ─────────────────────────────────────────────────────────
BID_STATUSES = ("open", "monitoring", "awarded", "no_bid", "cancelled")
def get_all_bids(status_filter: str = "") -> list:
"""
Return all bids ordered by due_date (nulls last), then created_at desc.
When status_filter is given, only bids with that status are returned.
Includes the adder's username and the count of updates per bid.
"""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
where = "WHERE b.status = %s" if status_filter else ""
params = (status_filter,) if status_filter else ()
cur.execute(
f"""
SELECT b.*,
u.username AS added_by_username,
COUNT(bu.id) AS update_count
FROM bid_tracker b
LEFT JOIN users u ON u.id = b.added_by
LEFT JOIN bid_updates bu ON bu.bid_id = b.id
{where}
GROUP BY b.id
ORDER BY b.due_date IS NULL, b.due_date ASC, b.created_at DESC
""",
params,
)
rows = cur.fetchall()
cur.close()
return rows
finally:
if conn:
conn.close()
def get_bid(bid_id: int) -> dict | None:
"""Return a single bid row with adder username."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute(
"""
SELECT b.*, u.username AS added_by_username
FROM bid_tracker b
LEFT JOIN users u ON u.id = b.added_by
WHERE b.id = %s
""",
(bid_id,),
)
row = cur.fetchone()
cur.close()
return row
finally:
if conn:
conn.close()
def create_bid(user_id: int, title: str, url: str, source: str,
solicitation_number: str, status: str,
due_date, notes: str) -> int:
"""Insert a new bid. Returns new row id."""
conn = None
try:
conn = get_connection()
cur = conn.cursor()
cur.execute(
"""
INSERT INTO bid_tracker
(title, url, source, solicitation_number, status, due_date, notes, added_by)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""",
(title, url,
source or None, solicitation_number or None,
status, due_date or None, notes or None,
user_id),
)
conn.commit()
new_id = cur.lastrowid
cur.close()
log_action(user_id, "CREATE_BID", "bid_tracker", new_id,
f"Created bid '{title}' status={status}.")
logger.info(f"Bid id={new_id} '{title}' created by user_id={user_id}.")
return new_id
finally:
if conn:
conn.close()
def update_bid(user_id: int, bid_id: int, title: str, url: str,
source: str, solicitation_number: str, status: str,
due_date, notes: str):
"""Update an existing bid."""
conn = None
try:
conn = get_connection()
cur = conn.cursor()
cur.execute(
"""
UPDATE bid_tracker
SET title=%s, url=%s, source=%s, solicitation_number=%s,
status=%s, due_date=%s, notes=%s
WHERE id=%s
""",
(title, url,
source or None, solicitation_number or None,
status, due_date or None, notes or None,
bid_id),
)
conn.commit()
cur.close()
log_action(user_id, "UPDATE_BID", "bid_tracker", bid_id,
f"Updated bid '{title}' status={status}.")
logger.info(f"Bid id={bid_id} updated by user_id={user_id}.")
finally:
if conn:
conn.close()
def delete_bid(user_id: int, bid_id: int):
"""Hard-delete a bid and all its updates (CASCADE)."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute("SELECT title FROM bid_tracker WHERE id=%s", (bid_id,))
row = cur.fetchone()
title = row["title"] if row else str(bid_id)
cur.execute("DELETE FROM bid_tracker WHERE id=%s", (bid_id,))
conn.commit()
cur.close()
log_action(user_id, "DELETE_BID", "bid_tracker", bid_id,
f"Deleted bid '{title}'.")
logger.info(f"Bid id={bid_id} '{title}' deleted by user_id={user_id}.")
finally:
if conn:
conn.close()
# ─── Bid Updates CRUD ─────────────────────────────────────────────────────────
def get_bid_updates(bid_id: int) -> list:
"""Return all updates for a bid, newest first."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute(
"""
SELECT bu.*, u.username AS posted_by_username,
COALESCE(u.full_name, u.username) AS posted_by_full_name
FROM bid_updates bu
LEFT JOIN users u ON u.id = bu.user_id
WHERE bu.bid_id = %s
ORDER BY bu.created_at DESC
""",
(bid_id,),
)
rows = cur.fetchall()
cur.close()
return rows
finally:
if conn:
conn.close()
def add_bid_update(user_id: int, bid_id: int, content: str) -> int:
"""Post a new update on a bid. Returns new row id."""
conn = None
try:
conn = get_connection()
cur = conn.cursor()
cur.execute(
"INSERT INTO bid_updates (bid_id, user_id, content) VALUES (%s, %s, %s)",
(bid_id, user_id, content),
)
conn.commit()
new_id = cur.lastrowid
cur.close()
log_action(user_id, "ADD_BID_UPDATE", "bid_updates", new_id,
f"Posted update on bid_id={bid_id}.")
logger.info(f"Bid update id={new_id} posted on bid_id={bid_id} by user_id={user_id}.")
return new_id
finally:
if conn:
conn.close()
def delete_bid_update(user_id: int, update_id: int):
"""Delete a single bid update. Any user can delete their own; admin can delete any."""
conn = None
try:
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM bid_updates WHERE id=%s", (update_id,))
conn.commit()
cur.close()
log_action(user_id, "DELETE_BID_UPDATE", "bid_updates", update_id,
f"Deleted bid update id={update_id}.")
logger.info(f"Bid update id={update_id} deleted by user_id={user_id}.")
finally:
if conn:
conn.close()