diff --git a/application/__pycache__/database_payloads.cpython-313.pyc b/application/__pycache__/database_payloads.cpython-313.pyc index af1b7f0..278919b 100644 Binary files a/application/__pycache__/database_payloads.cpython-313.pyc and b/application/__pycache__/database_payloads.cpython-313.pyc differ diff --git a/application/administration/sql/CREATE/plan_events.sql b/application/administration/sql/CREATE/plan_events.sql index 53fbe22..ca65296 100644 --- a/application/administration/sql/CREATE/plan_events.sql +++ b/application/administration/sql/CREATE/plan_events.sql @@ -3,6 +3,7 @@ CREATE TABLE IF NOT EXISTS %%site_name%%_plan_events( event_uuid UUID DEFAULT gen_random_uuid(), plan_uuid UUID, recipe_uuid UUID, + receipt_uuid UUID DEFAULT NULL, event_shortname VARCHAR(32) NOT NULL, event_description TEXT, event_date_start TIMESTAMP NOT NULL, diff --git a/application/database_payloads.py b/application/database_payloads.py index 707322d..b3ed303 100644 --- a/application/database_payloads.py +++ b/application/database_payloads.py @@ -576,6 +576,7 @@ class PlanEventPayload: event_date_end: datetime.datetime created_by: int recipe_uuid: str + receipt_uuid: str event_type: str def payload(self): @@ -587,6 +588,7 @@ class PlanEventPayload: self.event_date_end, self.created_by, self.recipe_uuid, + self.receipt_uuid, self.event_type ) diff --git a/application/meal_planner/__pycache__/meal_planner_api.cpython-313.pyc b/application/meal_planner/__pycache__/meal_planner_api.cpython-313.pyc index 0edf40e..ea0ba63 100644 Binary files a/application/meal_planner/__pycache__/meal_planner_api.cpython-313.pyc and b/application/meal_planner/__pycache__/meal_planner_api.cpython-313.pyc differ diff --git a/application/meal_planner/__pycache__/meal_planner_database.cpython-313.pyc b/application/meal_planner/__pycache__/meal_planner_database.cpython-313.pyc index e1e4104..81b57f1 100644 Binary files a/application/meal_planner/__pycache__/meal_planner_database.cpython-313.pyc and b/application/meal_planner/__pycache__/meal_planner_database.cpython-313.pyc differ diff --git a/application/meal_planner/__pycache__/meal_planner_processes.cpython-313.pyc b/application/meal_planner/__pycache__/meal_planner_processes.cpython-313.pyc new file mode 100644 index 0000000..8bfe966 Binary files /dev/null and b/application/meal_planner/__pycache__/meal_planner_processes.cpython-313.pyc differ diff --git a/application/meal_planner/meal_planner_api.py b/application/meal_planner/meal_planner_api.py index e311b31..c5d6163 100644 --- a/application/meal_planner/meal_planner_api.py +++ b/application/meal_planner/meal_planner_api.py @@ -10,7 +10,7 @@ import datetime from config import config from application.access_module import access_api from application import postsqldb, database_payloads -from application.meal_planner import meal_planner_database +from application.meal_planner import meal_planner_database, meal_planner_processes meal_planner_api = Blueprint('meal_planner_api', __name__, template_folder="templates", static_folder="static") @@ -62,6 +62,24 @@ def getRecipes(): return jsonify(status=201, message="Recipes fetched Successfully!", recipes=recipes, end=math.ceil(count/limit)) return jsonify(status=405, message=f"{request.method} is not an allowed method on this endpoint!", recipes=recipes, end=math.ceil(count/limit)) + +@meal_planner_api.route('/api/getVendors', methods=["GET"]) +@access_api.login_required +def getVendors(): + if request.method == "GET": + site_name = session['selected_site'] + page = int(request.args.get('page', 1)) + limit = int(request.args.get('limit', 50)) + search_string = request.args.get('search_string', "") + + offset = (page - 1) * limit + vendors, count = [], 0 + vendors, count = meal_planner_database.paginateVendorsTuples(site_name, (limit, offset)) + + return jsonify(status=201, message="Recipes fetched Successfully!", vendors=vendors, end=math.ceil(count/limit)) + return jsonify(status=405, message=f"{request.method} is not an allowed method on this endpoint!", vendors=vendors, end=math.ceil(count/limit)) + + @meal_planner_api.route('/api/addEvent', methods=["POST"]) @access_api.login_required def addEvent(): @@ -78,6 +96,7 @@ def addEvent(): event_date_end=event_date_end, created_by=session['user_id'], recipe_uuid=request.get_json()['recipe_uuid'], + receipt_uuid=None, event_type=request.get_json()['event_type'] ) @@ -86,6 +105,20 @@ def addEvent(): return jsonify(status=201, message="Event added Successfully!") return jsonify(status=405, message=f"{request.method} is not an allowed method on this endpoint!") +@meal_planner_api.route('/api/addTOEvent', methods=["POST"]) +@access_api.login_required +def addTOEvent(): + if request.method == "POST": + site_name = session['selected_site'] + data= request.get_json() + user_id = session['user_id'] + + meal_planner_processes.addTakeOutEvent(site_name, data, user_id) + + return jsonify(status=201, message="Event added Successfully!") + return jsonify(status=405, message=f"{request.method} is not an allowed method on this endpoint!") + + @meal_planner_api.route('/api/saveEvent', methods=["POST"]) @access_api.login_required def saveEvent(): diff --git a/application/meal_planner/meal_planner_database.py b/application/meal_planner/meal_planner_database.py index a7037a0..6b2a9b1 100644 --- a/application/meal_planner/meal_planner_database.py +++ b/application/meal_planner/meal_planner_database.py @@ -3,6 +3,46 @@ import psycopg2 from application import postsqldb import config +def requestNextReceiptID(site_name, conn=None): + """gets the next id for receipts_id, currently returns a 8 digit number + + Args: + site (str): site to get the next id for + + Returns: + json: receipt_id, message, error keys + """ + next_receipt_id = None + self_conn = False + sql = f"SELECT receipt_id FROM {site_name}_receipts ORDER BY id DESC LIMIT 1;" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql) + next_receipt_id = cur.fetchone() + if next_receipt_id == None: + next_receipt_id = "00000001" + else: + next_receipt_id = next_receipt_id[0] + next_receipt_id = int(next_receipt_id.split("-")[1]) + 1 + y = str(next_receipt_id) + len_str = len(y) + x = "".join(["0" for _ in range(8 - len_str)]) + next_receipt_id = x + y + + if self_conn: + conn.commit() + conn.close() + + return next_receipt_id + except (Exception, psycopg2.DatabaseError) as error: + raise postsqldb.DatabaseError(error, payload=(), sql=sql) + def paginateRecipesTuples(site: str, payload: tuple, convert=True, conn=None): self_conn = False recipes = () @@ -34,6 +74,37 @@ def paginateRecipesTuples(site: str, payload: tuple, convert=True, conn=None): except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) +def paginateVendorsTuples(site: str, payload: tuple, convert=True, conn=None): + self_conn = False + recipes = () + count = 0 + sql = f"SELECT * FROM {site}_vendors ORDER BY vendor_name ASC LIMIT %s OFFSET %s;" + sql_count = f"SELECT COUNT(*) FROM {site}_vendors;" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchall() + if rows and convert: + recipes = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows] + if rows and not convert: + recipes = rows + + cur.execute(sql_count) + count = cur.fetchone()[0] + + if self_conn: + conn.close() + + return recipes, count + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + def selectPlanEventsByMonth(site: str, payload: tuple, convert=True, conn=None): """payload=(year, month)""" self_conn = False @@ -126,7 +197,63 @@ def insertPlanEventTuple(site: str, payload: tuple, convert=True, conn=None): return event_tuple except Exception as error: raise postsqldb.DatabaseError(error, payload, sql) - + +def insertReceiptItemsTuple(site, payload, convert=True, conn=None): + receipt_item = () + self_conn = False + with open(f"application/meal_planner/sql/insertReceiptItemsTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + receipt_item = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + receipt_item = rows + + if self_conn: + conn.commit() + conn.close() + + return receipt_item + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + +def insertReceiptsTuple(site, payload, convert=True, conn=None): + receipt = () + self_conn = False + with open(f"application/meal_planner/sql/insertReceiptsTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + receipt = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + receipt = rows + + if self_conn: + conn.commit() + conn.close() + + return receipt + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) + def updatePlanEventTuple(site:str, payload: dict, convert=True, conn=None): """ payload (dict): {'barcode': row_id, 'update': {... column_to_update: value_to_update_to...}} """ updated = () diff --git a/application/meal_planner/meal_planner_processes.py b/application/meal_planner/meal_planner_processes.py index e69de29..460614c 100644 --- a/application/meal_planner/meal_planner_processes.py +++ b/application/meal_planner/meal_planner_processes.py @@ -0,0 +1,66 @@ +import datetime +import psycopg2 + +from application.meal_planner import meal_planner_database +from application import postsqldb, database_payloads +import config + +def addTakeOutEvent(site, data, user_id, conn=None): + event_date_start = datetime.datetime.strptime(data['event_date_start'], "%Y-%m-%d") + event_date_end = datetime.datetime.strptime(data['event_date_end'], "%Y-%m-%d") + + vendor_id = data['vendor_id'] + + self_conn = False + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + + receipt_id = meal_planner_database.requestNextReceiptID(site, conn=conn) + + receipt_payload = database_payloads.ReceiptPayload( + receipt_id=f"TOR-{receipt_id}", + receipt_status="Unresolved", + submitted_by=user_id, + vendor_id=vendor_id + ) + + receipt = meal_planner_database.insertReceiptsTuple(site, receipt_payload.payload(), conn=conn) + + print(receipt) + + receipt_item = database_payloads.ReceiptItemPayload( + type = 'custom', + receipt_id=receipt['id'], + barcode="", + item_uuid=None, + name=data['event_shortname'], + qty=data['attendees'], + uom=1, + data={'cost': data['cost'], 'expires': False} + ) + + receipt_item = meal_planner_database.insertReceiptItemsTuple(site, receipt_item.payload(), conn=conn) + print(receipt_item) + event_payload = database_payloads.PlanEventPayload( + plan_uuid=None, + event_shortname=data['event_shortname'], + event_description=data['event_description'], + event_date_start=event_date_start, + event_date_end=event_date_end, + created_by=user_id, + recipe_uuid=data['recipe_uuid'], + receipt_uuid=receipt['receipt_uuid'], + event_type=data['event_type'] + ) + + event = meal_planner_database.insertPlanEventTuple(site, event_payload.payload(), conn=conn) + print(event) + if self_conn: + conn.commit() + conn.close() + return False + + return True \ No newline at end of file diff --git a/application/meal_planner/sql/insertPlanEvent.sql b/application/meal_planner/sql/insertPlanEvent.sql index 6902fa8..96dcd92 100644 --- a/application/meal_planner/sql/insertPlanEvent.sql +++ b/application/meal_planner/sql/insertPlanEvent.sql @@ -1,4 +1,4 @@ INSERT INTO %%site_name%%_plan_events -(plan_uuid, event_shortname, event_description, event_date_start, event_date_end, created_by, recipe_uuid, event_type) -VALUES (%s, %s, %s, %s, %s, %s, %s, %s) +(plan_uuid, event_shortname, event_description, event_date_start, event_date_end, created_by, recipe_uuid, receipt_uuid, event_type) +VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING *; \ No newline at end of file diff --git a/application/meal_planner/sql/insertReceiptItemsTuple.sql b/application/meal_planner/sql/insertReceiptItemsTuple.sql new file mode 100644 index 0000000..8f06d55 --- /dev/null +++ b/application/meal_planner/sql/insertReceiptItemsTuple.sql @@ -0,0 +1,4 @@ +INSERT INTO %%site_name%%_receipt_items +(type, receipt_id, barcode, item_uuid, name, qty, uom, data, status) +VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) +RETURNING *; \ No newline at end of file diff --git a/application/meal_planner/sql/insertReceiptsTuple.sql b/application/meal_planner/sql/insertReceiptsTuple.sql new file mode 100644 index 0000000..8ddaf60 --- /dev/null +++ b/application/meal_planner/sql/insertReceiptsTuple.sql @@ -0,0 +1,4 @@ +INSERT INTO %%site_name%%_receipts +(receipt_id, receipt_status, date_submitted, submitted_by, vendor_id, files) +VALUES (%s, %s, %s, %s, %s, %s) +RETURNING *; \ No newline at end of file diff --git a/application/meal_planner/static/css/planner.css b/application/meal_planner/static/css/planner.css index 576ac2b..258199a 100644 --- a/application/meal_planner/static/css/planner.css +++ b/application/meal_planner/static/css/planner.css @@ -133,6 +133,24 @@ background-color: rgb(255, 255, 255); } +.take-out-label { + background:rgb(250, 162, 238); + margin-bottom: 3px; + padding: 2px 5px; + border-radius: 1px; + font-size: 12px; + font-weight: bold; +} + +.take-out-label:hover{ + background-color: rgb(225, 255, 255); + cursor: pointer; +} + +.take-out-label:hover, .custom-label-selected { + background-color: rgb(255, 255, 255); +} + .my-list-item:hover { background-color: whitesmoke; } diff --git a/application/meal_planner/static/js/mealPlannerHandler.js b/application/meal_planner/static/js/mealPlannerHandler.js index dccf52f..25f1b2a 100644 --- a/application/meal_planner/static/js/mealPlannerHandler.js +++ b/application/meal_planner/static/js/mealPlannerHandler.js @@ -105,6 +105,7 @@ async function setupCalendarAndEvents(){ let recipeLabel = e.target.closest('.recipe-label'); let calendarCell = e.target.closest('.calendar-cell'); let customLabel = e.target.closest('.custom-label'); + let takeOutLabel = e.target.closest('.take-out-label') if (recipeLabel) { recipeLabel.classList.add('recipe-label-selected') let rect = recipeLabel.getBoundingClientRect(); @@ -121,6 +122,14 @@ async function setupCalendarAndEvents(){ let menuX = rect.left + scrollLeft; let menuY = rect.bottom + scrollTop; showContextMenuForEvent(customLabel, menuX, menuY); + } else if (takeOutLabel) { + takeOutLabel.classList.add('take-out-label-selected') + let rect = takeOutLabel.getBoundingClientRect(); + let scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; + let scrollTop = window.pageYOffset || document.documentElement.scrollTop; + let menuX = rect.left + scrollLeft; + let menuY = rect.bottom + scrollTop; + showContextMenuForTOEvent(takeOutLabel, menuX, menuY); } else if (calendarCell) { calendarCell.classList.add('calendar-cell-selected') let rect = calendarCell.getBoundingClientRect(); @@ -167,6 +176,8 @@ async function createCalender() { eventsHTML += `
| Name | +Operations | +
|---|