diff --git a/.gitignore b/.gitignore index 7148036..b853d43 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ app/db/files/* app/redis/data/* app/.env specs +app/api/main/controller/prod_server_db_creds.md diff --git a/app/api/main/controller/execute.py b/app/api/main/controller/execute.py new file mode 100644 index 0000000..f57d57a --- /dev/null +++ b/app/api/main/controller/execute.py @@ -0,0 +1,28 @@ +from view.response import error_message +from model.connector import get_session + +def execute_query(custom_response, stm): + raw_data = [] + + # Connect to the database + try: + session = get_session() + except Exception: + error = error_message() + custom_response.insert_flash_message(error) + custom_response.set_status_code(500) + return custom_response, raw_data, False + + # Execute the query + try: + stream = session.execute(stm) + raw_data = stream.all() + except Exception: + error = error_message() + custom_response.insert_flash_message(error) + custom_response.set_status_code(400) + session.close() + return custom_response, raw_data, False + + session.close() + return custom_response, raw_data, True \ No newline at end of file diff --git a/app/api/main/controller/files.py b/app/api/main/controller/files.py deleted file mode 100644 index 927f6e3..0000000 --- a/app/api/main/controller/files.py +++ /dev/null @@ -1,240 +0,0 @@ -from flask import ( - current_app as app -) -import os -import pathlib -from werkzeug.utils import secure_filename -import hashlib -from sqlalchemy import select, insert, update, delete -import model as db - -def matchup_recursive(data, file_meta_key, file_meta_value, updates, finished): - """Recursivly search through request data for each saved file_data to matchup the file_data to the request data""" - if finished: - return data, updates, finished - if isinstance(data, dict): - for key, value in data.items(): - if key == "files": - continue - if key == "file_pointer" and value == file_meta_key: - data["file_pointer"] = file_meta_value["file_pointer"] - data["file_hash"] = file_meta_value["file_hash"] - data["id_code"] = file_meta_value["id_code"] - if "url_preview" in data.keys(): - data.pop("url_preview") - updates += 1 - return data, updates, True - else: - data[key], updates, finished = matchup_recursive(data[key], file_meta_key, file_meta_value, updates, finished) - elif isinstance(data, list): - for i, item in enumerate(data): - data[i], updates, finished = matchup_recursive(item, file_meta_key, file_meta_value, updates, finished) - return data, updates, finished - -def matchup_saved_file_to_data(data): - """Recursivly search through request data for each saved file_data to matchup the file_data to the request data""" - for file_meta_key, file_meta_value in data["doc"]["files"].items(): - updates = 0 - data, updates, finished = matchup_recursive(data, file_meta_key, file_meta_value, updates, False) - if updates < 1 or not finished: - raise NameError("Could not update file.") - if updates > 1: - raise NameError("Too many file updates. Should only have one, found: " + updates) - data["doc"].pop("files") - return data - -def save_files(data, session): - """Save files from form request while pairing file with database records. - - Args: - data: dictionary - session: database session - - Note: this function expects the following format for data: - TODO: Finish Docstring - { - : {} - files: { - - } - } - """ - if 'doc' in data.keys(): - if "remove_files" in data["doc"].keys() and data['doc']['remove_files']: - print(data['doc']['remove_files']) - for remove_hash in data['doc']['remove_files']: - remove_file(remove_hash, session) - if "remove_files" in data["doc"].keys(): - data['doc'].pop("remove_files") - if "files" in data["doc"].keys(): - if len(data["doc"]["files"]) > 0: - save = {} - for file in data["doc"]["files"].keys(): - compare = ["file_obj", "filename", "type", "page", "id_code"] - keys = data["doc"]["files"][file].keys() - values = list(data["doc"]["files"][file].values()) - if len(compare & keys) == 5 and all(values): - file_data = data["doc"]["files"][file] - file_hash, filename, pointer = save_file(file_data, session) - data["doc"]["files"][file].pop("file_obj") - save[file] = data["doc"]["files"][file].copy() - save[file]["filename"] = filename - save[file]["file_pointer"] = pointer - save[file]["file_hash"] = file_hash - else: - raise Exception("Missing File Information.") - data["doc"]["files"] = save.copy() - return matchup_saved_file_to_data(data) - return data - -def remove_file(file_hash, session): - """Remove file from server if no more references exist, decrement ref counter - Args: - file_hash (string): Hash of file to remove - session (Session): Database Session - """ - # Check if File Exists in DB and increment ref count, Save if not - stream = session.execute( - select(db.Files).where(db.Files.file_hash == file_hash) - ) - raw_data = stream.all() - - if len(raw_data) <= 0: - raise ValueError("Invalid File Hash to Remove: " + file_hash) - - if (raw_data[0][0].to_dict()["ref_count"] > 1): - stm = ( - update(db.Files) - .where(db.Files.file_hash == file_hash) - .values(ref_count=raw_data[0][0].to_dict()["ref_count"] - 1) - ) - session.execute(stm) - session.commit() - return - else: - pointer = os.path.join( - app.config['UPLOAD_FOLDER'], raw_data[0][0].to_dict()["file_pointer"] - ) - os.remove(pointer) - - stm = ( - delete(db.Files) - .where(db.Files.file_hash == file_hash) - ) - session.execute(stm) - session.commit() - return - -def save_file(file_data, session): - """Save file to server filesystem and to Files database - - Args: - file_data (Dict): _description_ - session (Session): _description_ - - Raises: - Exception: _description_ - Exception: _description_ - - Returns: - Tuple: (file_hash, file_name, file_pointer) - """ - - # Get File Hash - file_hash = md5_from_file(file_data["file_obj"]) - - # Check if File Exists in DB and increment ref count, Save if not - stream = session.execute( - select(db.Files).where(db.Files.file_hash == file_hash) - ) - raw_data = stream.all() - - # Save File to Filesystem - - # Create File Name - fn = secure_filename( - str(file_data["filename"]).replace(" ", "_").replace("/","-") - ) - id_code = secure_filename( - str(file_data["id_code"]).replace(" ", "_").replace("/","-") - ) - page = secure_filename( - str(file_data["page"]).replace(" ", "_").replace("/","-") - ) - - filename = f"║fn[{fn}]║id_code[{id_code}]║pg[{page}]║hash[{file_hash}]║" - - if file_data["file_obj"]["content_type"] == "application/pdf": - filename += ".pdf" - elif file_data["file_obj"]["content_type"] == "image/jpeg": - filename += ".jpeg" - elif file_data["file_obj"]["content_type"] == "image/png": - filename += ".png" - else: - raise Exception("Invalid File Type") - - if len(raw_data) > 0: - # Do not save the file to the filesystem if it already exists. - # Increment Ref Counter - stm = ( - update(db.Files) - .where(db.Files.file_hash == file_hash) - .values(ref_count=raw_data[0][0].to_dict()["ref_count"] + 1) - ) - session.execute(stm) - session.commit() - return raw_data[0][0].get_id(), raw_data[0][0].file_name, raw_data[0][0].file_pointer - - # Create Directory if it doesn't already exist - directory = os.path.join( - app.config['UPLOAD_FOLDER'], file_data["type"] - ) - - pointer = os.path.join(file_data["type"], filename) - - pathlib.Path( - directory - ).mkdir(exist_ok=True, parents=True) - - path = os.path.join( - directory, filename - ) - - # Save File - with open(path, 'wb') as file: - file.write(file_data["file_obj"]["content"]) - - # Save File Info in DB - stream = session.execute( - insert(db.Files).returning(db.Files), - { - "file_hash": file_hash, - "file_name": fn, - "file_type": file_data["type"], - "file_pointer": pointer, - "id_code": file_data["id_code"], - "pg": file_data["page"] - } - ) - raw_data = stream.all() - new_file_id = raw_data[0][0].get_id() - if new_file_id != file_hash: - session.rollback() - raise Exception("File Hash Mismatch") - else: - session.commit() - - return file_hash, filename, pointer - -def md5_from_file(file, block_size=2**14): - """Creates md5 Hash from file contents.""" - md5 = hashlib.md5() - content_copy = file["content"][:] - while True: - data = content_copy[:block_size] - if not data: - break - md5.update(data) - content_copy = content_copy[block_size:] - - return md5.hexdigest() \ No newline at end of file diff --git a/app/api/main/controller/inventory.py b/app/api/main/controller/inventory.py index 7acdacb..1fac2b3 100644 --- a/app/api/main/controller/inventory.py +++ b/app/api/main/controller/inventory.py @@ -1,22 +1,18 @@ ''' Handle Inventory Data ''' -import json -from sqlalchemy import select, insert, update, delete +from sqlalchemy import select -from view.response import ( - VariantType, - FlashMessage, - error_message -) +from view.response import CustomResponse import model as db -from model.connector import get_session -from .package import package_data -from .files import save_files +from .execute import execute_query + +import controller.organizations as org +from .orders import get_purchase_order_detail def get_components( custom_response, - product_ids, + component_ids, component_types, certifications, brand_ids, @@ -25,35 +21,12 @@ def get_components( ): # Build the query - tables = [db.Components, db.Component_Names] - - if 'purchase_order_detail' in populate: - tables.append(db.Purchase_Order_Detail) - if 'item_id' in populate or 'inventory' in populate: - tables.append(db.Item_id) - if 'inventory' in populate: - tables.append(db.Inventory) - if 'brand' in populate: - tables.append(db.Organizations) - tables.append(db.Organization_Names) - - stm = select(*tables) \ - .join(db.Component_Names, isouter=True) - - if 'purchase_order_detail' in populate: - stm = stm.join(db.Purchase_Order_Detail, db.Components.component_id == db.Purchase_Order_Detail.component_id, isouter=True) - if 'item_id' in populate or 'inventory' in populate: - stm = stm.join(db.Item_id, db.Components.component_id == db.Item_id.component_id, isouter=True) - if 'inventory' in populate: - stm = stm.join(db.Inventory, db.Item_id.item_id == db.Inventory.item_id, isouter=True) - if 'brand' in populate: - stm = stm.join(db.Organizations, db.Components.brand_id == db.Organizations.organization_id, isouter=True) - stm = stm.join(db.Organization_Names, db.Organizations.organization_id == db.Organization_Names.organization_id, isouter=True) + stm = select(db.Components, db.Item_id, db.Component_Names) + stm = stm.join(db.Item_id, db.Components.component_id == db.Item_id.component_id, isouter=True) + stm = stm.join(db.Component_Names, db.Components.primary_name_id == db.Component_Names.name_id, isouter=True) - # stm = stm.where(db.Component_Names.primary_name == True) - - if product_ids: - stm = stm.where(db.Components.component_id.in_(product_ids)) + if component_ids: + stm = stm.where(db.Components.component_id.in_(component_ids)) if brand_ids: stm = stm.where(db.Components.brand_id.in_(brand_ids)) @@ -87,170 +60,106 @@ def get_components( if 'fda' in certifications: stm = stm.where(db.Components.certified_fda == True) - # Connect to the database - try: - session = get_session() - except Exception: - error = error_message() - custom_response.insert_flash_message(error) - custom_response.set_status_code(500) - return custom_response - # Execute the query - try: - stream = session.execute(stm) - raw_data = stream.all() - except Exception: - error = error_message() - custom_response.insert_flash_message(error) - custom_response.set_status_code(400) - session.close() + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: return custom_response - session.close() # Process and Package the data - data, custom_response = package_data(raw_data, doc, custom_response) - custom_response.insert_data(data) + for row in raw_data: + pk = row[0].get_id() + component = row[0].to_dict() + + if row[1]: + item_id = row[1].get_id() + component['item_id'] = item_id + else: + component['item_id'] = None + + if row[2]: + component_primary_name = row[2].to_dict() + component['component_primary_name'] = component_primary_name['component_name'] + else: + component['component_primary_name'] = None + + component_names = {'component_names':[]} + purchase_order_detail = {'purchase_order_detail':[]} + inventory = {'inventory':[]} + brand = {'brand':[]} + + if 'component_names' in populate: + r = CustomResponse() + resp = get_component_names( r, [], [pk], False, False) + component_names = {'component_names':resp.get_data()} + custom_response.insert_flash_messages(r.get_flash_messages()) + + if 'purchase_order_detail' in populate: + r = CustomResponse() + resp = get_purchase_order_detail( r, [], [], False) + purchase_order_detail = {'purchase_order_detail':resp.get_data()} + custom_response.insert_flash_messages(r.get_flash_messages()) + + if 'inventory' in populate: + r = CustomResponse() + resp = get_inventory( r, [], [item_id], [], [], False) + inventory = {'inventory':resp.get_data()} + custom_response.insert_flash_messages(r.get_flash_messages()) + + if 'brand' in populate: + r = CustomResponse() + resp = org.get_organizations( r, [component.brand_id], [], [], False) + brand = {'brand':resp.get_data()} + custom_response.insert_flash_messages(r.get_flash_messages()) + + if not doc: + component.pop('doc') + + custom_response.insert_data({**component, **component_names, **purchase_order_detail, **inventory, **brand}) + return custom_response -def post_component( +def get_component_names( custom_response, - component + name_ids, + component_ids, + primary_name=False, + botanical_name=False ): - # Connect to the database - try: - session = get_session() - except Exception: - error = error_message() - custom_response.insert_flash_message(error) - custom_response.set_status_code(500) - return custom_response - - try: + # Build the query + stm = select(db.Component_Names) - # Save Files if Any - new_component = save_files(component, session) + if name_ids: + stm = stm.where(db.Component_Names.name_id.in_(name_ids)) - # Insert Component in Components Table - stream = session.execute( - insert(db.Components).returning(db.Components), - new_component - ) - raw_data = stream.all() - new_component_id = raw_data[0][0].get_id() - new_component["component_id"] = new_component_id + if component_ids: + stm = stm.where(db.Component_Names.component_id.in_(component_ids)) - # Insert new component_id in Items Table - stream = session.execute( - insert(db.Item_id).returning(db.Item_id), - {"component_id": new_component_id} - ) - raw_data = stream.all() - new_item_id = raw_data[0][0].get_id() + if primary_name: + stm = stm.where(db.Component_Names.primary_name == True) - # Insert Component_Names in Component_Names Table - for name in new_component["Component_Names"]: - name["component_id"] = new_component_id - stream = session.execute( - insert(db.Component_Names).returning(db.Component_Names), - name - ) - raw_data = stream.all() - name["name_id"] = raw_data[0][0].get_id() + if botanical_name: + stm = stm.where(db.Component_Names.botanical_name == True) - session.commit() - except Exception: - error = error_message() - custom_response.insert_flash_message(error) - custom_response.set_status_code(400) - session.rollback() - session.close() + # Execute the query + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: return custom_response - session.close() - # Process and Package the data - custom_response.insert_data(new_component) - custom_response.set_status_code(201) - flash_message = FlashMessage( - variant=VariantType.SUCCESS, - title="Success", - message="Component Added Successfully" - ) - custom_response.insert_flash_message(flash_message) + for row in raw_data: + component_name = row[0].to_dict() + + custom_response.insert_data({ **component_name }) + return custom_response -def put_component( +def get_inventory( custom_response, - component + inv_ids, + item_ids, + lot_numbers, + owners, + doc ): - # Connect to the database - try: - session = get_session() - except Exception: - error = error_message() - custom_response.insert_flash_message(error) - custom_response.set_status_code(500) - return custom_response - - try: - - # Save Files if Any - update_component = save_files(component, session) - - # Update Component Names - component_names = json.loads(update_component.pop("Component_Names")) - stream = session.execute( - select(db.Component_Names) - .where(db.Component_Names.component_id == update_component["component_id"]) - ) - raw_data = stream.all() - current_names = [] - for row in raw_data: - current_names.append(row[0].to_dict()) - - if component_names != current_names: - # Delete existing Component Names - session.execute( - delete(db.Component_Names) - .where(db.Component_Names.component_id == update_component["component_id"]) - ) - - # Add New Component Names - for name in component_names: - name["name_id"] = None - session.execute( - insert(db.Component_Names) - .values(component_names) - ) - - # Update Component Data - session.execute( - update(db.Components) - .where(db.Components.component_id == update_component["component_id"]) - .values(update_component) - ) - - - session.commit() - except Exception: - error = error_message() - custom_response.insert_flash_message(error) - custom_response.set_status_code(400) - session.rollback() - session.close() - return custom_response - - session.close() - - # Process and Package the data - custom_response.insert_data(update_component) - custom_response.set_status_code(201) - flash_message = FlashMessage( - variant=VariantType.SUCCESS, - title="Changes Saved!", - message="Component Updated Successfully" - ) - custom_response.insert_flash_message(flash_message) - return custom_response \ No newline at end of file + raise NotImplementedError \ No newline at end of file diff --git a/app/api/main/controller/order_66.py b/app/api/main/controller/order_66.py new file mode 100644 index 0000000..9d9749f --- /dev/null +++ b/app/api/main/controller/order_66.py @@ -0,0 +1,132 @@ +from view.response import CustomResponse, FlashMessage, error_message +import model as db +import sqlalchemy as sa +from flask import current_app as app + +from sqlalchemy import select, update + +def get_session(): + """ + Define the MariaDb engine using MariaDB Connector/Python. + """ + host = app.config['DB_HOST'] + port = app.config['DB_PORT'] + user = app.config['DB_USER'] + password = app.config['DB_PASSWORD'] + + engine = sa.create_engine( + f'mariadb+mariadbconnector://{user}:{password}@{host}:{port}',connect_args={'connect_timeout': 3} + ) + + Session = sa.orm.sessionmaker() + Session.configure(bind=engine) + Session.configure(autocommit=False) + Session = Session() + return Session + +def execute_query(custom_response, stm): + raw_data = [] + + # Connect to the database + try: + session = get_session() + except Exception: + error = error_message() + custom_response.insert_flash_message(error) + custom_response.set_status_code(500) + return custom_response, raw_data, False + + # Execute the query + try: + stream = session.execute(stm) + if isinstance(stm, sa.sql.selectable.Select): + raw_data = stream.all() + else: + session.commit() + except Exception: + error = error_message() + custom_response.insert_flash_message(error) + custom_response.set_status_code(400) + session.close() + return custom_response, raw_data, False + + session.close() + return custom_response, raw_data, True + +def order_66(request): + custom_response = CustomResponse() + + # custom_response = fix_primary_name_id_column_organizations(custom_response) + # custom_response = fix_primary_name_id_column_components(custom_response) + + if custom_response.status_code == 200: + fm = FlashMessage( + message='Success', + title='Order 66' + ) + print('SUCCESS') + custom_response.insert_flash_message(fm) + + return custom_response + +def fix_primary_name_id_column_components(custom_response): + stm = select(db.Components) + stm = stm.where(db.Components.primary_name_id == None) + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: + return custom_response + + for row in raw_data: + stm = select(db.Component_Names) + stm = stm.where(db.Component_Names.component_id == row[0].get_id()) + stm = stm.where(db.Component_Names.primary_name == True) + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: + return custom_response + + if raw_data: + primary_name_id = raw_data[0][0].get_id() + else: + print("No primary name found for component_id: ", row[0].get_id()) + continue + + stm = update(db.Components) \ + .values(primary_name_id=primary_name_id) \ + .where(db.Components.component_id == row[0].get_id()) + + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: + return custom_response + + return custom_response + +def fix_primary_name_id_column_organizations(custom_response): + stm = select(db.Organizations) + stm = stm.where(db.Organizations.primary_name_id == None) + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: + return custom_response + + for row in raw_data: + stm = select(db.Organization_Names) + stm = stm.where(db.Organization_Names.organization_id == row[0].get_id()) + stm = stm.where(db.Organization_Names.primary_name == True) + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: + return custom_response + + if raw_data: + primary_name_id = raw_data[0][0].get_id() + else: + print("No primary name found for organization_id: ", row[0].get_id()) + continue + + stm = update(db.Organizations) \ + .values(primary_name_id=primary_name_id) \ + .where(db.Organizations.organization_id == row[0].get_id()) + + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: + return custom_response + + return custom_response \ No newline at end of file diff --git a/app/api/main/controller/orders.py b/app/api/main/controller/orders.py new file mode 100644 index 0000000..a647c38 --- /dev/null +++ b/app/api/main/controller/orders.py @@ -0,0 +1,17 @@ +''' +Handle Orders Data +''' +from sqlalchemy import select + +from view.response import CustomResponse +import model as db +from .execute import execute_query + +def get_sales_orders(): + raise NotImplementedError + +def get_purchase_orders(): + raise NotImplementedError + +def get_purchase_order_detail(): + raise NotImplementedError \ No newline at end of file diff --git a/app/api/main/controller/organizations.py b/app/api/main/controller/organizations.py index 5b7dd14..4805bb6 100644 --- a/app/api/main/controller/organizations.py +++ b/app/api/main/controller/organizations.py @@ -1,73 +1,29 @@ ''' Handle Organizations Data ''' -import json -from sqlalchemy import select, text - -from view.response import ( - VariantType, - FlashMessage, - CustomResponse, - error_message -) +from sqlalchemy import select + +from view.response import CustomResponse import model as db -from model.connector import get_session -from .package import package_data - -def get_organizations(custom_response, org_ids, org_type, populate, doc): - """ - Fetch Organizaiton from the database. - Populate and filter Organizations if requested. - - Args: - custom_response (CustomResponse): CustomResponse object - org_ids (list): List of organization ids - org_type (list): Organization types (enum) - ('supplier', 'client', 'courier', 'lab') - Note: an empty list will return all organizations - populate (list): List of tables to populate (enum) - ('facilities','sales-orders', 'purchase-orders', 'people', 'components', 'products') - doc (bool): Whether or not to include the document column in the response - - Returns: - custom_response (CustomResponse): CustomResponse object containing the data and relivant error messages. - """ +from .execute import execute_query + +from .orders import get_sales_orders, get_purchase_orders +from .inventory import get_components +from .products import get_products + + +def get_organizations( + custom_response, + org_ids, + org_type, + populate, + doc + ): # Build the query - tables = [db.Organizations, db.Organization_Names] - - if 'facilities' in populate: - tables.append(db.Facilities) - if 'sales_orders' in populate: - tables.append(db.Sales_Orders) - if 'purchase_orders' in populate: - tables.append(db.Purchase_Orders) - if 'people' in populate: - tables.append(db.People) - if 'components' in populate: - tables.append(db.Components) - tables.append(db.Component_Names) - if 'products' in populate: - tables.append(db.Product_Master) - - stm = select(*tables) \ - .join(db.Organization_Names, isouter=True) - - if 'facilities' in populate: - stm = stm.join(db.Facilities, db.Organizations.organization_id == db.Facilities.organization_id, isouter=True) - if 'sales_orders' in populate: - stm = stm.join(db.Sales_Orders, db.Organizations.organization_id == db.Sales_Orders.organization_id, isouter=True) - if 'purchase_orders' in populate: - stm = stm.join(db.Purchase_Orders, db.Organizations.organization_id == db.Purchase_Orders.organization_id, isouter=True) - if 'people' in populate: - stm = stm.join(db.People, db.Organizations.organization_id == db.People.organization_id, isouter=True) - if 'components' in populate: - stm = stm.join(db.Components, db.Organizations.organization_id == db.Components.brand_id, isouter=True) - stm = stm.join(db.Component_Names, db.Components.component_id == db.Component_Names.component_id, isouter=True) - if 'products' in populate: - stm = stm.join(db.Product_Master, db.Organizations.organization_id == db.Product_Master.organization_id, isouter=True) - - stm = stm.where(db.Organization_Names.primary_name == True) + stm = select(db.Organizations, db.Organization_Names) + + stm = stm.join(db.Organization_Names, db.Organizations.primary_name_id == db.Organization_Names.name_id) if org_type: if 'client' in org_type: @@ -82,115 +38,299 @@ def get_organizations(custom_response, org_ids, org_type, populate, doc): if org_ids: stm = stm.where(db.Organizations.organization_id.in_(org_ids)) - # Connect to the database - try: - session = get_session() - except Exception: - error = error_message() - custom_response.insert_flash_message(error) - custom_response.set_status_code(500) + # Execute the query + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: return custom_response + # Process and Package the data + for row in raw_data: + pk = row[0].get_id() + organization = row[0].to_dict() + + if row[1]: + organization_primary_name = row[1].to_dict() + organization['organization_primary_name'] = organization_primary_name['organization_name'] + organization['organization_primary_initial'] = organization_primary_name['organization_initial'] + else: + organization['organization_primary_name'] = None + organization['organization_primary_initial'] = None + + organization_names = {'organization_names': []} + facilities = {'facilities':[]} + sales_orders = {'sales_orders': []} + purchase_orders = {'purchase_orders': []} + people = {'people': []} + components = {'components': []} + products = {'products': []} + + # Populate + if ('organization_names' in populate): + r = CustomResponse() + resp = get_organization_names(r, [], [pk], False) + organization_names = {'organization_names': resp.get_data()} + custom_response.insert_flash_messages(r.get_flash_messages()) + + if ('facilities' in populate): + r = CustomResponse() + resp = get_facilities( r, [], [pk], doc) + facilities_data = resp.get_data() + custom_response.insert_flash_messages(r.get_flash_messages()) + facilities = {'facilities': facilities_data} + + if ('sales_orders' in populate): + r = CustomResponse() + resp = get_sales_orders(r, [], [pk], doc) + sales_orders_data = resp.get_data() + custom_response.insert_flash_messages(r.get_flash_messages()) + sales_orders = {'sales_orders': sales_orders_data} + + if ('purchase_orders' in populate): + r = CustomResponse() + resp = get_purchase_orders(r, [], [pk], doc) + purchase_orders_data = resp.get_data() + custom_response.insert_flash_messages(r.get_flash_messages()) + purchase_orders = {'purchase_orders': purchase_orders_data} + + if ('people' in populate): + r = CustomResponse() + resp = get_people(r, [], [pk], [], doc) + people_data = resp.get_data() + custom_response.insert_flash_messages(r.get_flash_messages()) + people = {'people': people_data} + + if ('components' in populate): + r = CustomResponse() + resp = get_components(r, [], [], [], [pk], [], False) + components_data = resp.get_data() + custom_response.insert_flash_messages(r.get_flash_messages()) + components = {'components': components_data} + + if ('products' in populate): + r = CustomResponse() + resp = get_products(r, [], [], [pk], [], doc) + products_data = resp.get_data() + custom_response.insert_flash_messages(r.get_flash_messages()) + products = {'products': products_data} + + custom_response.insert_data({**organization, **organization_names, **facilities, **sales_orders, **purchase_orders, **people, **components, **products}) + + return custom_response + +def get_organization_names( + custom_response, + name_ids, + organization_ids, + primary_name=False + ): + + # Build the query + stm = select(db.Organization_Names) + + if name_ids: + stm = stm.where(db.Organization_Names.name_id.in_(name_ids)) + + if organization_ids: + stm = stm.where(db.Organization_Names.organization_id.in_(organization_ids)) + + if primary_name: + stm = stm.where(db.Organization_Names.primary_name == True) + # Execute the query - try: - stream = session.execute(stm) - raw_data = stream.all() - except Exception: - error = error_message() - custom_response.insert_flash_message(error) - custom_response.set_status_code(500) - session.close() + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: return custom_response - session.close() + # Process and Package the data + for row in raw_data: + organization_name = row[0].to_dict() + + custom_response.insert_data({ **organization_name }) + + return custom_response + +def get_facilities( + custom_response, + facility_ids, + organization_ids, + doc + ): + + # Build the query + stm = select(db.Facilities) + + if facility_ids: + stm = stm.where(db.Facilities.facility_id.in_(facility_ids)) + + if organization_ids: + stm = stm.where(db.Facilities.organization_id.in_(organization_ids)) + + # Execute the query + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: + return custom_response # Process and Package the data - data, custom_response = package_data(raw_data, doc, custom_response) - custom_response.insert_data(data) + for row in raw_data: + facility = row[0].to_dict() + + custom_response.insert_data({ **facility }) + return custom_response -def organization_exists(names, custom_response): - """ - Check if an organization already exists by organization name. - """ - # Execute Querys - primary_exists = False - levenshtein_results = [] - for name in names: - if name["primary_name"]: - primary_exists = True - results, levensthein_messages = check_org_exists_levenshtein( - name["organization_name"]) - if not results == -1: - levenshtein_results += results - custom_response.insert_flash_messages(levensthein_messages) - - # Handle Primary False - if not primary_exists: - e_message = FlashMessage( - message="Primary Name not selected!", - variant=VariantType.WARNING) - custom_response.insert_flash_message(e_message) - - # Insert the levenshtein_results into the response - custom_response.insert_data(levenshtein_results) +def get_people( + custom_response, + person_ids, + organization_ids, + populate, + doc + ): + + # Build the query + stm = select(db.People) + + if person_ids: + stm = stm.where(db.People.person_id.in_(person_ids)) + + if organization_ids: + stm = stm.where(db.People.organization_id.in_(organization_ids)) + + # Execute the query + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: + return custom_response + + # Process and Package the data + for row in raw_data: + pk = row[0].get_id() + person = row[0].to_dict() + + users = {'user': []} + + if 'user' in populate: + r = CustomResponse() + resp = get_users(r, [], [pk], doc) + user = {'user': resp.get_data()} + custom_response.insert_flash_messages(r.get_flash_messages()) + custom_response.insert_data({ **user }) + + custom_response.insert_data({ **person, **users }) + + return custom_response + +def get_users( + custom_response, + user_ids, + person_ids, + doc + ): + + # Build the query + stm = select(db.Users) + + if user_ids: + stm = stm.where(db.Users.user_id.in_(user_ids)) + + if person_ids: + stm = stm.where(db.Users.person_id.in_(person_ids)) + + # Execute the query + custom_response, raw_data, success = execute_query(custom_response, stm) + if not success: + return custom_response + + # Process and Package the data + for row in raw_data: + user = row[0].to_dict() + + custom_response.insert_data({ **user }) return custom_response +# from sqlalchemy import select, text + +# def organization_exists(names, custom_response): +# """ +# Check if an organization already exists by organization name. +# """ +# # Execute Querys +# primary_exists = False +# levenshtein_results = [] +# for name in names: +# if name["primary_name"]: +# primary_exists = True +# results, levensthein_messages = check_org_exists_levenshtein( +# name["organization_name"]) +# if not results == -1: +# levenshtein_results += results +# custom_response.insert_flash_messages(levensthein_messages) + +# # Handle Primary False +# if not primary_exists: +# e_message = FlashMessage( +# message="Primary Name not selected!", +# variant=VariantType.WARNING) +# custom_response.insert_flash_message(e_message) + +# # Insert the levenshtein_results into the response +# custom_response.insert_data(levenshtein_results) + +# return custom_response + + +# def check_org_exists_levenshtein(search_name): +# ''' +# Checks likelyhood if Organization Name already exists +# in the Database. +# ''' +# try: +# session = get_session() + +# # Build Query +# base_query = """ +# SELECT +# JSON_OBJECT( +# 'organization_id', `organization_id`, +# 'organization_name', `organization_name`, +# 'levenshtein_probability', CAST(sys.LEVENSHTEIN_RATIO(`organization_name`, :search_name) AS UNSIGNED INTEGER) +# ) AS levenshtein_results +# FROM `Organizations`.`Organization_Names` +# WHERE +# sys.LEVENSHTEIN_RATIO(`organization_name`, :search_name) > 50; +# """ + +# # Execute Query +# result = session.execute( +# text(base_query), +# {"search_name":search_name} +# ) + +# # Return Results +# levensthein_results = [] +# levensthein_messages = [] +# for row in result: +# # Save Data +# json_row = json.loads(row[0]) +# levensthein_results.append(json_row) + +# # Create Message Components +# message_alert_heading = "Possible Duplicate Organization." +# message = f"Possible duplicate organization between '{search_name}' and '{json_row['organization_name']}'." +# message_detail = f"'{search_name}' is a {json_row['levenshtein_probability']} percent match for '{json_row['organization_name']}'. Are they the same organization?" +# message_link = f"/api/organizations/?org-id={json_row['organization_id']}" + +# # Create Message Object using Components +# message_obj = FlashMessage( +# alert_heading=message_alert_heading, +# message=message, +# message_detail=message_detail, +# link=message_link, +# variant=VariantType.WARNING +# ) +# levensthein_messages.append(message_obj) + +# return levensthein_results, levensthein_messages -def check_org_exists_levenshtein(search_name): - ''' - Checks likelyhood if Organization Name already exists - in the Database. - ''' - try: - session = get_session() - - # Build Query - base_query = """ - SELECT - JSON_OBJECT( - 'organization_id', `organization_id`, - 'organization_name', `organization_name`, - 'levenshtein_probability', CAST(sys.LEVENSHTEIN_RATIO(`organization_name`, :search_name) AS UNSIGNED INTEGER) - ) AS levenshtein_results - FROM `Organizations`.`Organization_Names` - WHERE - sys.LEVENSHTEIN_RATIO(`organization_name`, :search_name) > 50; - """ - - # Execute Query - result = session.execute( - text(base_query), - {"search_name":search_name} - ) - - # Return Results - levensthein_results = [] - levensthein_messages = [] - for row in result: - # Save Data - json_row = json.loads(row[0]) - levensthein_results.append(json_row) - - # Create Message Components - message_alert_heading = "Possible Duplicate Organization." - message = f"Possible duplicate organization between '{search_name}' and '{json_row['organization_name']}'." - message_detail = f"'{search_name}' is a {json_row['levenshtein_probability']} percent match for '{json_row['organization_name']}'. Are they the same organization?" - message_link = f"/api/organizations/?org-id={json_row['organization_id']}" - - # Create Message Object using Components - message_obj = FlashMessage( - alert_heading=message_alert_heading, - message=message, - message_detail=message_detail, - link=message_link, - variant=VariantType.WARNING - ) - levensthein_messages.append(message_obj) - - return levensthein_results, levensthein_messages - - except Exception: - error = error_message() - return -1, [error] +# except Exception: +# error = error_message() +# return -1, [error] diff --git a/app/api/main/controller/package.py b/app/api/main/controller/package.py deleted file mode 100644 index 71cd0cc..0000000 --- a/app/api/main/controller/package.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -Package Data Gathered from Controlers through the Models. -""" -from view.response import ( - error_message -) - -def package_data(raw_data, doc, custom_response): - """ - Package data into json like format, populate child tabels - as object elements within a list. - - Args: - raw_data (list): List of tuples containing table objects - doc (bool): a boolean indicating whether or not to include the document column in the response - custom_response (CustomResponse): Collects any error or other messages. - - Returns: - data (dict): Dictionary containing the data in a json like format. - custom_response (CustomResponse): Collects any error or other messages. - """ - try: - data = {} - for i, row in enumerate(raw_data): - parent_key = row[0].get_id() - - if i == 0 or parent_key != raw_data[i - 1][0].get_id(): - d = row[0].to_dict() - if not doc and ("doc" in list(d.keys())): - d["doc"] = {} - data[parent_key] = d - - if len(row) > 1: - for j, table in enumerate(row, 0): - if j == 0: - continue - if table is None: - continue - - if table.__tablename__ not in data[parent_key]: - data[parent_key][table.__tablename__] = [] - - entered_keys = [] - for d in data[parent_key][table.__tablename__]: - if isinstance(table.get_id(), int): - entered_keys.append(d[table.get_id_name()]) - elif isinstance(table.get_id(), tuple): - entered_keys.append((d["source"], d["target"])) - else: - entered_keys.append(( - d["prefix"], - d["year"], - d["month"], - d["sec_number"], - d["suffix"] - )) - - if table.get_id() not in entered_keys: - d = table.to_dict() - if not doc and ("doc" in list(d.keys())): - d["doc"] = {} - data[parent_key][table.__tablename__].append(d) - except Exception: - error = error_message() - custom_response.insert_flash_message(error) - custom_response.set_status_code(500) - return data, custom_response \ No newline at end of file diff --git a/app/api/main/controller/products.py b/app/api/main/controller/products.py index a8f4514..e9d3c14 100644 --- a/app/api/main/controller/products.py +++ b/app/api/main/controller/products.py @@ -1,45 +1,11 @@ ''' Handle Product Data ''' -import json -from sqlalchemy import select, insert, update, delete - -from view.response import ( - VariantType, - FlashMessage, - error_message, - CustomResponse -) -import model as db -from model.connector import get_session -from .package import package_data -from .files import save_files - -def execute_query(custom_response, stm): - raw_data = [] - - # Connect to the database - try: - session = get_session() - except Exception: - error = error_message() - custom_response.insert_flash_message(error) - custom_response.set_status_code(500) - return custom_response, raw_data, False +from sqlalchemy import select - # Execute the query - try: - stream = session.execute(stm) - raw_data = stream.all() - except Exception: - error = error_message() - custom_response.insert_flash_message(error) - custom_response.set_status_code(400) - session.close() - return custom_response, raw_data, False - - session.close() - return custom_response, raw_data, True +from view.response import CustomResponse +import model as db +from .execute import execute_query def get_products( custom_response, diff --git a/app/api/main/controller/request.py b/app/api/main/controller/request.py index 6c9a391..6f47620 100644 --- a/app/api/main/controller/request.py +++ b/app/api/main/controller/request.py @@ -178,9 +178,11 @@ def _upsert_record(self, table_name, record): self.custom_response.set_status_code(400) return False - id, outcome = self._execute_query(stm) - if id and temp_key: - self.temp_key_lookup[temp_key] = id + new_id, outcome = self._execute_query(stm) + if new_id and temp_key: + self.temp_key_lookup[temp_key] = new_id + data = { 'table_name': table_name, 'new_id' : new_id, 'pk' : pk_col } + self.custom_response.insert_data(data) return outcome diff --git a/app/api/main/model/inventory.py b/app/api/main/model/inventory.py index 963cd23..52fecc0 100644 --- a/app/api/main/model/inventory.py +++ b/app/api/main/model/inventory.py @@ -55,6 +55,7 @@ class Components(Base): create_constraint=True, validate_strings=True, )) + primary_name_id: Mapped[int] = mapped_column(ForeignKey('Inventory.Component_Names.name_id')) doc = Column(JSON, default={}) @@ -86,7 +87,8 @@ def to_dict(self): "certified_vegan": self.certified_vegan, "date_entered": self.date_entered, "units": self.units, - "doc": self.doc + "doc": self.doc, + "primary_name_id": self.primary_name_id } def get_id(self): diff --git a/app/api/main/model/organizations.py b/app/api/main/model/organizations.py index 3dc7629..4081ffb 100644 --- a/app/api/main/model/organizations.py +++ b/app/api/main/model/organizations.py @@ -40,6 +40,8 @@ class Organizations(Base): lab: Mapped[bool] = mapped_column(default=False) courier: Mapped[bool] = mapped_column(default=False) other: Mapped[bool] = mapped_column(default=False) + notes: Mapped[str] = mapped_column(default=None) + primary_name_id: Mapped[int] = mapped_column(ForeignKey('Organization_Names.Organization_Names.name_id')) doc = Column(MutableDict.as_mutable(JSON)) @@ -66,7 +68,9 @@ def to_dict(self): 'lab': self.lab, 'courier': self.courier, 'other': self.other, - 'doc': self.doc + 'doc': self.doc, + 'notes': self.notes, + 'primary_name_id': self.primary_name_id } def get_id(self): diff --git a/app/api/main/view/catalogue.py b/app/api/main/view/catalogue.py index c146052..05c4606 100644 --- a/app/api/main/view/catalogue.py +++ b/app/api/main/view/catalogue.py @@ -1,15 +1,14 @@ ''' Handle Inventory Data ''' -import json from flask import ( Blueprint, request, jsonify ) from .auth import check_authenticated -from .helper import only_integers, check_type, collect_form_data -from .response import CustomResponse, FlashMessage +from .helper import only_integers, check_type +from .response import CustomResponse from controller import inventory as inv from controller import products as prod @@ -17,44 +16,8 @@ bp_comp = Blueprint('components', __name__, url_prefix='/components') bp_prod = Blueprint('products', __name__, url_prefix='/products') -@bp_comp.route('/', methods=['GET', 'POST', 'PUT']) +@bp_comp.route('/', methods=['GET']) @check_authenticated(authentication_required=True) -def handle_components(): - - if request.method == 'GET': - return handle_get_components() - elif request.method == 'POST': - return handle_post_components() - elif request.method == 'PUT': - return handle_put_components() - else: - r = CustomResponse() - r.set_status_code(404) - response = jsonify(r.to_json()) - response.status_code = r.get_status_code() - return response - -bp_cat.register_blueprint(bp_comp) - -@bp_prod.route('/', methods=['GET', 'POST', 'PUT']) -@check_authenticated(authentication_required=True) -def handle_products(): - - if request.method == 'GET': - return handle_get_products() - elif request.method == 'POST': - return handle_post_products() - elif request.method == 'PUT': - return handle_put_products() - else: - r = CustomResponse() - r.set_status_code(404) - response = jsonify(r.to_json()) - response.status_code = r.get_status_code() - return response - -bp_cat.register_blueprint(bp_prod) - def handle_get_components(): """ GET api/v/catalogue/components/ Endpoint @@ -63,6 +26,8 @@ def handle_get_components(): # Clean Request component_ids = list(only_integers(request.args.getlist('component-id'))) + process_component_ids = list(only_integers(request.args.getlist('process-component-id'))) + types_request = request.args.getlist('type') valid_types = [ 'powder', 'liquid', 'container', 'pouch','shrink_band', @@ -98,9 +63,9 @@ def handle_get_components(): populate_request = request.args.getlist('populate') valid_populate = [ 'purchase_order_detail', - 'item_id', 'inventory', - 'brand' + 'brand', + 'component_names' ] populate = check_type( valid_populate, @@ -131,126 +96,8 @@ def handle_get_components(): return response - -def handle_post_components(): - - # Clean the Request - custom_response = CustomResponse() - - component = collect_form_data(request) - - # Validate Request - required_keys = ["component_type", "brand_id", "units", "Component_Names"] - # brand_id is required, however it can be null. - flag = False - for key in required_keys: - if key not in component.keys() or not component.get(key): - if component.get('brand_id') == None: - continue - flag = True - title = "Invalid Request." - message = f"Missing {key} field." - custom_response.insert_flash_message( - FlashMessage( - title=title, - message=message - ) - ) - - if not flag: - # Validate Component_Names Entry - # [{"component_name": STRING, "primary_name": BOOL}] - # There must be one and only one selected primary name - try: - component["Component_Names"] = json.loads(component["Component_Names"]) - primary_name = False - primary_count = 0 - required_keys = ["component_name", "primary_name", "botanical_name", "name_id"] - for name in component["Component_Names"]: - if (not isinstance(name, dict)) or \ - (set(name.keys()) != set(required_keys)): - flag = True - title = "Invalid Component Name Data." - message = "Component name settings are in the wrong format." - custom_response.insert_flash_message( - FlashMessage( - title=title, - message=message - ) - ) - if name["component_name"] and \ - not isinstance(name["component_name"], str): - flag = True - title = "Invalid Component Name." - message = "The component name must be a string of text." - custom_response.insert_flash_message( - FlashMessage( - title=title, - message=message - ) - ) - if name["primary_name"] == 'true' or \ - name["primary_name"] == 'True' or \ - name["primary_name"] == True or \ - name["primary_name"] == 1: - primary_name = True - primary_count += 1 - if "name_id" in name.keys(): - name.pop("name_id") - if not primary_name or primary_count != 1: - flag = True - title="Invalid Primary Name." - message = "At least one and only one primary name must be selected." - custom_response.insert_flash_message( - FlashMessage( - title=title, - message=message - ) - ) - except: - flag = True - - if flag: - custom_response.set_status_code(400) - custom_response.insert_flash_message( - FlashMessage( - title="Proccessing Request Error.", - message="There was an error proccessing the component name." - ) - ) - response = jsonify(custom_response.to_json()) - response.status_code = 400 - return response - - # Post Components to Database - custom_response = inv.post_component( - custom_response, - component - ) - - response = jsonify(custom_response.to_json()) - response.status_code = custom_response.get_status_code() - - return response - -def handle_put_components(): - - # Clean the Request - component = collect_form_data(request) - - # Get Components from Database - custom_response = CustomResponse() - - custom_response = inv.put_component( - custom_response, - component - ) - - response = jsonify(custom_response.to_json()) - response.status_code = custom_response.get_status_code() - - return response - +@bp_prod.route('/', methods=['GET']) +@check_authenticated(authentication_required=True) def handle_get_products(): """ GET api/v/catalogue/products/ Endpoint @@ -319,8 +166,5 @@ def handle_get_products(): return response -def handle_post_products(): - raise NotImplementedError - -def handle_put_products(): - raise NotImplementedError +bp_cat.register_blueprint(bp_comp) +bp_cat.register_blueprint(bp_prod) diff --git a/app/api/main/view/order_66.py b/app/api/main/view/order_66.py new file mode 100644 index 0000000..ef02b97 --- /dev/null +++ b/app/api/main/view/order_66.py @@ -0,0 +1,20 @@ +''' +Handle all requests to interact with data in the database. +''' +from flask import ( + Blueprint, + request, + jsonify +) +from .auth import check_authenticated +from controller.order_66 import order_66 + +bp = Blueprint('order_66', __name__, url_prefix='/order_66') + +@bp.route('/', methods=['GET']) +@check_authenticated(authentication_required=True) +def handle_submit(): + res = order_66(request) + response = jsonify(res.to_json()) + response.status_code = res.get_status_code() + return response diff --git a/app/api/main/view/organizations.py b/app/api/main/view/organizations.py index e57eea5..0a7a407 100644 --- a/app/api/main/view/organizations.py +++ b/app/api/main/view/organizations.py @@ -36,11 +36,12 @@ def handle_get_organizations(): populate_request = request.args.getlist('populate') valid_populate = [ 'facilities', - 'sales-orders', - 'purchase-orders', + # 'sales_orders', + # 'purchase_orders', 'people', 'components', - 'products' + 'products', + 'organization_names' ] populate = check_type( valid_populate, @@ -67,20 +68,3 @@ def handle_get_organizations(): response.status_code = custom_response.get_status_code() return response - -@bp.route('/exists', methods=['GET']) -@check_authenticated(authentication_required=True) -def handle_org_exists(): - """ - Check if an organization already exists by organization name. - """ - custom_response = CustomResponse() # Create an instance of Response - - names = request.json['names'] - - custom_response = org.organization_exists(names, custom_response) - - response = jsonify(custom_response.to_json()) - response.status_code = custom_response.get_status_code() - - return response diff --git a/app/api/main/wsgi.py b/app/api/main/wsgi.py index bcf3514..bd18bfc 100644 --- a/app/api/main/wsgi.py +++ b/app/api/main/wsgi.py @@ -130,6 +130,9 @@ def create_app(): from view.submit_request import bp as submit_request_bp api_blueprint.register_blueprint(submit_request_bp) + from view.order_66 import bp as order_66_pb + api_blueprint.register_blueprint(order_66_pb) + # Sanity Check Routes @api_blueprint.route('/ping', methods=['GET']) def ping_pong(): diff --git a/app/client/src/components/ChooseIngredient.vue b/app/client/src/components/ChooseIngredient.vue index d7b90a4..2fd0374 100644 --- a/app/client/src/components/ChooseIngredient.vue +++ b/app/client/src/components/ChooseIngredient.vue @@ -121,7 +121,7 @@ export default { return this.ingredients.length > 0 }, filtered () { - return this.ingredients.filter((ing) => ing.component_name ? ing.component_name.includes(this.search) : false) + return this.ingredients.filter((ing) => ing.component_primary_name ? ing.component_primary_name?.includes(this.search) : false) }, paginated () { return this.filtered.slice(0, this.limit) diff --git a/app/client/src/components/ChooseOrg.vue b/app/client/src/components/ChooseOrg.vue index 721c1f7..9abfdb2 100644 --- a/app/client/src/components/ChooseOrg.vue +++ b/app/client/src/components/ChooseOrg.vue @@ -102,7 +102,7 @@ export default { return this.organizations.length > 0 }, filtered () { - return this.organizations.filter((org) => (org.organization_name.includes(this.search) || org.organization_initial.includes(this.search))) + return this.organizations.filter((org) => (org.organization_primary_name?.includes(this.search) || org.organization_primary_initial?.includes(this.search))) }, paginated () { return this.filtered.slice(0, this.limit) diff --git a/app/client/src/router/index.js b/app/client/src/router/index.js index 90360d0..9f80eca 100644 --- a/app/client/src/router/index.js +++ b/app/client/src/router/index.js @@ -75,44 +75,17 @@ const routes = [ { path: '/organizations', name: 'organizations', - component: () => import(/* webpackChunkName: "organizations" */ '../views/organizations/OrganizationsHome.vue'), - props: { - labs_filter_prop: false, - suppliers_filter_prop: false, - clients_filter_prop: false - }, - children: [ - { - path: '/clients', - name: 'clients', - component: () => import(/* webpackChunkName: "clients" */ '../views/organizations/OrganizationsHome.vue'), - props: { - labs_filter_prop: false, - suppliers_filter_prop: false, - clients_filter_prop: true - } - }, - { - path: '/suppliers', - name: 'suppliers', - component: () => import(/* webpackChunkName: "suppliers" */ '../views/organizations/OrganizationsHome.vue'), - props: { - labs_filter_prop: false, - suppliers_filter_prop: true, - clients_filter_prop: false - } - }, - { - path: '/labs', - name: 'labs', - component: () => import(/* webpackChunkName: "labs" */ '../views/organizations/OrganizationsHome.vue'), - props: { - labs_filter_prop: true, - suppliers_filter_prop: false, - clients_filter_prop: false - } - } - ] + component: () => import(/* webpackChunkName: "organizations" */ '../views/organizations/OrganizationsHome.vue') + }, + { + path: '/organizations/:id', + name: 'OrganizationsDetail', + component: () => import(/* webpackChunkName: "organizations" */ '../views/organizations/OrganizationDetail.vue') + }, + { + path: '/organizations/create', + name: 'NewOrganization', + component: () => import(/* webpackChunkName: "NewOrganization" */ '../views/organizations/NewOrganization.vue') }, { path: '/orders', diff --git a/app/client/src/views/catalogue/SpecificationsComponent.vue b/app/client/src/views/catalogue/SpecificationsComponent.vue index 14ea577..91863c7 100644 --- a/app/client/src/views/catalogue/SpecificationsComponent.vue +++ b/app/client/src/views/catalogue/SpecificationsComponent.vue @@ -17,15 +17,15 @@
- + - + - + - + - + @@ -356,6 +356,9 @@ export default { } }, methods: { + focus: function (elmId) { + document.getElementById(elmId).focus() + }, addSpec: function (specKey) { const test = this.newTest() test.type = this.spectype + '_specifications/' + specKey diff --git a/app/client/src/views/catalogue/components/ComponentDetail.vue b/app/client/src/views/catalogue/components/ComponentDetail.vue index a584039..f8604b8 100644 --- a/app/client/src/views/catalogue/components/ComponentDetail.vue +++ b/app/client/src/views/catalogue/components/ComponentDetail.vue @@ -33,7 +33,7 @@ s.charAt(0).toUpperCase() + s.substring(1)).join(' ') }, get_component_primary_name: function (component) { - if (component.Component_Names !== undefined && component.Component_Names.length > 0) { - for (let i = 0; i < component.Component_Names.length; i++) { - if (component.Component_Names[i].primary_name) { - return component.Component_Names[i].component_name + if (component.component_names !== undefined && component.component_names.length > 0) { + for (let i = 0; i < component.component_names.length; i++) { + if (component.component_names[i].primary_name) { + return component.component_names[i].component_name } } } return 'No Name' }, getComponentData: function () { - const fetchRequest = window.origin + '/api/v1/catalogue/components?component-id=' + this.id + '&populate=product_materials&populate=purchase_order_detail&populate=item_id&populate=inventory&populate=brand&doc=true' + const fetchRequest = window.origin + '/api/v1/catalogue/components?component-id=' + this.id + '&populate=component_names&doc=true' // eslint-disable-next-line console.log( 'GET ' + fetchRequest @@ -166,7 +166,7 @@ export default { }).then(response => { if (response.status === 200) { response.json().then(data => { - this.component_data = Object.values(data.data[0])[0] + this.component_data = data.data[0] if (this.component_data.doc === null) { this.component_data.doc = {} } diff --git a/app/client/src/views/catalogue/components/ComponentHome.vue b/app/client/src/views/catalogue/components/ComponentHome.vue index 4d0c760..9ccf276 100644 --- a/app/client/src/views/catalogue/components/ComponentHome.vue +++ b/app/client/src/views/catalogue/components/ComponentHome.vue @@ -66,12 +66,12 @@

- + -
{{ component.component_type.toLowerCase().split('_').map((s) => s.charAt(0).toUpperCase() + s.substring(1)).join(' ') }}
-
{{ getPrimaryName(component) }} {{ getBrandName(component) }}
+

{{ component.component_type?.toLowerCase().split('_').map((s) => s.charAt(0).toUpperCase() + s.substring(1)).join(' ') }}

+

{{ component.component_primary_name }} {{ getBrandName(component) }}

Incomplete Specs
@@ -81,10 +81,16 @@ -
+
+
+
+
+
+ +
- +
@@ -209,10 +215,10 @@ export default { this.search_query = this.search_query_buff }, getPrimaryName: function (component) { - if (component.Component_Names !== undefined && component.Component_Names.length > 0) { - for (let i = 0; i < component.Component_Names.length; i++) { - if (component.Component_Names[i].primary_name) { - return component.Component_Names[i].component_name + if (component.component_names !== undefined && component.component_names.length > 0) { + for (let i = 0; i < component.component_names.length; i++) { + if (component.component_names[i].primary_name) { + return component.component_names[i].component_name } } } @@ -228,12 +234,18 @@ export default { } return '' }, - populateComponent: function (componentId) { - const fetchRequest = window.origin + '/api/v1/catalogue/components?component-id=' + componentId + '&populate=product_materials&populate=purchase_order_detail&populate=label_formula_master&populate=ingredient_formula_master&populate=item_id&populate=inventory&populate=brand&doc=true' + getComponentData: function () { + let fetchRequest = window.origin + '/api/v1/catalogue/components?doc=true' + if (this.type_filter !== 'all') { + fetchRequest += '&type=' + this.type_filter + } else { + this.fetched_all_components = true + } // eslint-disable-next-line console.log( 'GET ' + fetchRequest ) + this.loaded = false fetch(fetchRequest, { method: 'GET', headers: { @@ -243,14 +255,19 @@ export default { }).then(response => { if (response.status === 200) { response.json().then(data => { - this.components_data[componentId] = Object.values(data.data[0])[0] + this.components_data = data.data // eslint-disable-next-line - console.log(this.components_data[componentId]) + console.log(this.components_data) + this.loaded = true }) } else if (response.status === 401) { this.$router.push({ name: 'login' }) + } else if (response.status === 404) { + this.$router.push({ + name: 'NotFound' + }) } else { // eslint-disable-next-line console.log('Looks like there was a problem. Status Code:' + response.status) @@ -259,18 +276,15 @@ export default { } }) }, - getComponentData: function () { - let fetchRequest = window.origin + '/api/v1/catalogue/components/?populate=brand&doc=true' - if (this.type_filter !== 'all') { - fetchRequest += '&type=' + this.type_filter - } else { - this.fetched_all_components = true + populateComponent: function (component) { + if (component.populated) { + return } + const fetchRequest = window.origin + '/api/v1/catalogue/components?doc=true&populate=component_names&component-id=' + component.component_id // eslint-disable-next-line console.log( 'GET ' + fetchRequest ) - this.loaded = false fetch(fetchRequest, { method: 'GET', headers: { @@ -280,10 +294,11 @@ export default { }).then(response => { if (response.status === 200) { response.json().then(data => { - this.components_data = data.data[0] - // eslint-disable-next-line - console.log(this.components_data) - this.loaded = true + const t = data.data[0] + t.populated = true + console.log(t) + const index = this.components_data.findIndex((c) => c.component_id === t.component_id) + this.components_data[index] = t }) } else if (response.status === 401) { this.$router.push({ @@ -311,8 +326,10 @@ export default { return 0 }, alphabetical: function (a, b) { - const aPrime = this.getPrimaryName(a) - const bPrime = this.getPrimaryName(b) + // const aPrime = this.getPrimaryName(a) + // const bPrime = this.getPrimaryName(b) + const aPrime = a.component_primary_name + const bPrime = b.component_primary_name if (aPrime < bPrime) { return -1 } @@ -325,14 +342,15 @@ export default { this.powder_cert_filter = checked ? this.powder_cert_options.map((obj) => obj.value).slice() : [] }, searchFilter: function (component) { - if (component.Component_Names !== undefined && component.Component_Names.length > 0) { - for (let i = 0; i < component.Component_Names.length; i++) { - if (component.Component_Names[i].component_name.toLowerCase().includes(this.search_query.toLowerCase())) { - return true - } - } - } - return false + // if (component.component_names !== undefined && component.component_names.length > 0) { + // for (let i = 0; i < component.component_names.length; i++) { + // if (component.component_names[i].component_name.toLowerCase().includes(this.search_query.toLowerCase())) { + // return true + // } + // } + // } + // return false + return component?.component_primary_name?.toLowerCase().includes(this.search_query.toLowerCase()) }, componentTypeFilter: function (component) { if (component.component_type === this.type_filter) { diff --git a/app/client/src/views/catalogue/components/NamesComponent.vue b/app/client/src/views/catalogue/components/NamesComponent.vue index 0080450..d382a53 100644 --- a/app/client/src/views/catalogue/components/NamesComponent.vue +++ b/app/client/src/views/catalogue/components/NamesComponent.vue @@ -96,8 +96,13 @@ export default { this.$emit('editNames', this.edit_names) } else { if (this.naming_type === 'component') { + let primary = null this.edit_names_buffer.forEach(name => { this.req.upsertRecord('Component_Names', name) + if (name.primary_name) { + primary = name.name_id + } + this.req.upsertRecord('Components', { component_id: this.id, primary_name_id: primary }) }) } else { throw new Error('Invalid naming_type: "' + this.naming_type + '". Only component is allowed') diff --git a/app/client/src/views/catalogue/components/NewComponent.vue b/app/client/src/views/catalogue/components/NewComponent.vue index e50e405..a36c0d7 100644 --- a/app/client/src/views/catalogue/components/NewComponent.vue +++ b/app/client/src/views/catalogue/components/NewComponent.vue @@ -10,77 +10,139 @@ New Name
-
+
- -
- Primary Name +
+ +
This is a required field.
-
- Botanical Name +
+
-
- Delete +
+ +
+
+ Delete
- + +
This is a required field.
- + +
This is a required field.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
- Submit + Submit
@@ -107,19 +169,18 @@ diff --git a/app/client/src/views/catalogue/products/ProductFormula.vue b/app/client/src/views/catalogue/products/ProductFormula.vue index f4b520d..55d3fe6 100644 --- a/app/client/src/views/catalogue/products/ProductFormula.vue +++ b/app/client/src/views/catalogue/products/ProductFormula.vue @@ -304,6 +304,7 @@ aria-describedby="max_grams_per_unit-live-feedback" :class="['form-control', (new_formula_buffer.max_grams_per_unit >= new_formula_buffer.total_grams_per_unit && new_formula_buffer.max_grams_per_unit !== null ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('total_grams_per_unit')" >
g @@ -326,6 +327,7 @@ aria-describedby="total_grams_per_unit-live-feedback" :class="['form-control', (new_formula_buffer.total_grams_per_unit >= 0 && new_formula_buffer.total_grams_per_unit !== '' && new_formula_buffer.total_grams_per_unit !== null ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('min_grams_per_unit')" >
g @@ -347,6 +349,7 @@ aria-describedby="min_grams_per_unit-live-feedback" :class="['form-control', (new_formula_buffer.min_grams_per_unit <= new_formula_buffer.total_grams_per_unit && new_formula_buffer.min_grams_per_unit !== null ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('max_milliliters_per_unit')" >
g @@ -373,6 +376,7 @@ aria-describedby="max_milliliters_per_unit-live-feedback" :class="['form-control', (new_formula_buffer.max_milliliters_per_unit >= new_formula_buffer.total_milliliters_per_unit && new_formula_buffer.max_milliliters_per_unit !== null ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('total_milliliters_per_unit')" >
ml @@ -394,6 +398,7 @@ aria-describedby="total_milliliters_per_unit-live-feedback" :class="['form-control', (new_formula_buffer.total_milliliters_per_unit >= 0 && new_formula_buffer.total_milliliters_per_unit !== '' && new_formula_buffer.total_milliliters_per_unit !== null ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('min_milliliters_per_unit')" >
ml @@ -415,6 +420,7 @@ aria-describedby="min_milliliters_per_unit-live-feedback" :class="['form-control', (new_formula_buffer.min_milliliters_per_unit <= new_formula_buffer.total_milliliters_per_unit && new_formula_buffer.min_milliliters_per_unit !== null ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('max_mg_per_capsule')" >
ml @@ -441,6 +447,7 @@ aria-describedby="max_mg_per_capsule-live-feedback" :class="['form-control', (new_formula_buffer.max_mg_per_capsule >= new_formula_buffer.total_mg_per_capsule && new_formula_buffer.max_mg_per_capsule !== '' && new_formula_buffer.max_mg_per_capsule !== null ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('total_mg_per_capsule')" >
mg @@ -462,6 +469,7 @@ aria-describedby="total_mg_per_capsule-live-feedback" :class="['form-control', (new_formula_buffer.total_mg_per_capsule >= 0 && new_formula_buffer.total_mg_per_capsule !== '' && new_formula_buffer.total_mg_per_capsule !== null ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('min_mg_per_capsule')" >
mg @@ -484,6 +492,7 @@ aria-describedby="min_mg_per_capsule-live-feedback" :class="['form-control', (new_formula_buffer.min_mg_per_capsule <= new_formula_buffer.total_mg_per_capsule && new_formula_buffer.min_mg_per_capsule !== null ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('total_capsules_per_unit')" >
mg @@ -506,6 +515,7 @@ aria-describedby="total_capsules_per_unit-live-feedback" :class="['form-control', (new_formula_buffer.total_capsules_per_unit >= 0 && new_formula_buffer.total_capsules_per_unit !== '' && new_formula_buffer.total_capsules_per_unit !== null ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('capsule_size')" >
ct @@ -523,6 +533,7 @@ aria-describedby="capsule_size-live-feedback" :class="['form-control', 'form-control-md', (new_formula_buffer.capsule_size !== '' ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('capsule_weight')" > @@ -547,6 +558,7 @@ aria-describedby="capsule_weight-live-feedback" :class="['form-control', (new_formula_buffer.capsule_weight >= 0 && new_formula_buffer.capsule_weight !== '' && new_formula_buffer.capsule_weight !== null ? '' : 'is-invalid')]" @input="update_formula(new_formula_buffer)" + v-on:keyup.enter="focus('max_grams_per_unit')" >
mg @@ -727,6 +739,9 @@ export default { } }, methods: { + focus: function (elmId) { + document.getElementById(elmId).focus() + }, set_default_formula_id: function (id) { this.$emit('toggleLoaded', false) this.req = new CustomRequest(this.$cookies.get('session')) @@ -1208,37 +1223,8 @@ export default { } this.versions.push({ value: 'NEW', text: 'New Formula' }) }, - get_component_primary_name: function (component) { - if (component.component_name !== undefined) { - return component.component_name - } - if (component.Component_Names !== undefined && component.Component_Names.length > 0) { - for (let i = 0; i < component.Component_Names.length; i++) { - if (component.Component_Names[i].primary_name) { - component.component_name = component.Component_Names[i].component_name - return component.Component_Names[i].component_name - } - } - } - return 'No Name' - }, - get_organization_primary_name: function (organization) { - if (organization.organization_name !== undefined) { - return organization.organization_name - } - if (organization.Organization_Names !== undefined && organization.Organization_Names.length > 0) { - for (let i = 0; i < organization.Organization_Names.length; i++) { - if (organization.Organization_Names[i].primary_name) { - organization.organization_name = organization.Organization_Names[i].organization_name - organization.organization_initial = organization.Organization_Names[i].organization_initial - return organization.Organization_Names[i].organization_name - } - } - } - return 'No Name' - }, get_organizations: function () { - const fetchRequest = window.origin + '/api/v1/organizations?org-type=supplier' + const fetchRequest = window.origin + '/api/v1/organizations' // eslint-disable-next-line console.log( 'GET ' + fetchRequest @@ -1252,8 +1238,8 @@ export default { }).then(response => { if (response.status === 200) { response.json().then(data => { - const orgs = Object.values(data.data[0]) - this.organization_options = orgs.sort((a, b) => (this.get_organization_primary_name(a) > this.get_organization_primary_name(b) ? 1 : -1)) + const orgs = Object.values(data.data) + this.organization_options = orgs.sort((a, b) => (a?.organization_primary_name > b?.organization_primary_name ? 1 : -1)) // eslint-disable-next-line console.log(this.organization_options) if (this.organization_options.doc === null) { @@ -1289,11 +1275,8 @@ export default { }).then(response => { if (response.status === 200) { response.json().then(data => { - const ings = Object.values(data.data[0]) - for (const i in ings) { - this.get_component_primary_name(ings[i]) - } - this.ingredient_options = ings.sort((a, b) => (a.component_name > b.component_name ? 1 : -1)) + const ings = Object.values(data.data) + this.ingredient_options = ings.sort((a, b) => (a.component_primary_name > b.component_name ? 1 : -1)) // eslint-disable-next-line console.log(this.ingredient_options) }) diff --git a/app/client/src/views/catalogue/products/ProductHome.vue b/app/client/src/views/catalogue/products/ProductHome.vue index 2d04a73..e016ef5 100644 --- a/app/client/src/views/catalogue/products/ProductHome.vue +++ b/app/client/src/views/catalogue/products/ProductHome.vue @@ -42,8 +42,8 @@ -
{{ product.product_name }}
-
{{ product.organization_name }}
+

{{ product.product_name }}

+

{{ product.organization_name }}

diff --git a/app/client/src/views/organizations/NewOrganization.vue b/app/client/src/views/organizations/NewOrganization.vue new file mode 100644 index 0000000..30c32aa --- /dev/null +++ b/app/client/src/views/organizations/NewOrganization.vue @@ -0,0 +1,277 @@ + + + + + diff --git a/app/client/src/views/organizations/OrganizationDetail.vue b/app/client/src/views/organizations/OrganizationDetail.vue new file mode 100644 index 0000000..15f65da --- /dev/null +++ b/app/client/src/views/organizations/OrganizationDetail.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/app/client/src/views/organizations/OrganizationsHome.vue b/app/client/src/views/organizations/OrganizationsHome.vue index 564a0f5..7bf1a50 100644 --- a/app/client/src/views/organizations/OrganizationsHome.vue +++ b/app/client/src/views/organizations/OrganizationsHome.vue @@ -42,7 +42,7 @@

Organizations

- @@ -62,54 +62,54 @@

- - - - + + + + +

{{ org.organization_primary_name }} - ({{ org.organization_primary_initial }})

+
+
+

-
-
-
+ +
+
-
+
-
+
Facilities
- +
-
+
Sales Orders
-
+
Purchase Orders
-
+
People
- +
-
+
Components
-
+
Products
+ + + +
-
+
@@ -156,7 +156,7 @@ export default { return } // TODO: Fix Component Names and add them to Populate - const fetchRequest = window.origin + '/api/v1/organizations/?org-id=' + orgId + '&populate=facilities&populate=sales-orders&populate=purchase-orders&populate=people&populate=products' + const fetchRequest = window.origin + '/api/v1/organizations/?org-id=' + orgId + '&populate=facilities&populate=people&populate=products&populate=organization_names' // eslint-disable-next-line console.log( 'GET ' + fetchRequest @@ -170,13 +170,17 @@ export default { }).then(response => { if (response.status === 200) { response.json().then(data => { - this.org_data[orgId] = Object.values(data.data[0])[0] - this.org_data[orgId].populated = true + const t = data.data[0] + t.populated = true + console.log(t) + const index = this.org_data.findIndex((o) => o.organization_id === t.organization_id) + this.org_data[index] = t }) } else if (response.status === 404) { - this.org_data[orgId].populated = true + const index = this.org_data.findIndex((o) => o.organization_id === orgId) + this.org_data[index].populated = true const errorToast = { - title: `'${this.org_data[orgId].Organization_Names[0].organization_name}' 404 Not Found.`, + title: `'${this.org_data[orgId].organization_primary_name}' 404 Not Found.`, message: 'Looks like there is no additional data to see here.', variant: 'info', visible: true, @@ -217,10 +221,7 @@ export default { }).then(response => { if (response.status === 200) { response.json().then(data => { - this.org_data = data.data[0] - for (let i = 0; i < this.org_data.length; i++) { - this.org_data[i].populated = false - } + this.org_data = data.data // eslint-disable-next-line console.log(this.org_data) this.loaded = true @@ -238,10 +239,10 @@ export default { }) }, compare: function (a, b) { - if (a.organization_name < b.organization_name) { + if (a.organization_primary_name < b.organization_primary_name) { return -1 } - if (a.organization_name > b.organization_name) { + if (a.organization_primary_name > b.organization_primary_name) { return 1 } return 0 @@ -259,8 +260,8 @@ export default { const list = Object.values(this.org_data) list.forEach(org => { if ( - org.Organization_Names[0].organization_name.toLowerCase().includes(this.search_query.toLowerCase()) || - org.Organization_Names[0].organization_initial.toLowerCase().includes(this.search_query.toLowerCase()) + org.organization_primary_name.toLowerCase().includes(this.search_query.toLowerCase()) || + org.organization_primary_initial.toLowerCase().includes(this.search_query.toLowerCase()) ) { searched.push(org) }