diff --git a/.gitignore b/.gitignore index d693a8be..5e83d15b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ **/__pycache__/** -.vscode/settings.json +.vscode/** test/** .env 0.1.3.json @@ -7,4 +7,3 @@ ml_structure.json .DS_Store .venv venv -.vscode/extensions.json diff --git a/datastore/__init__.py b/datastore/__init__.py index 55cb89fd..92026f72 100644 --- a/datastore/__init__.py +++ b/datastore/__init__.py @@ -505,7 +505,7 @@ async def get_folder_pictures(self, cursor: Cursor, folder_id: UUID, user_id: UU - user_id (str): The UUID of the user. Returns: - - A list of the pictures of the folder. + - A list of the pictures (BLOB) of the folder. """ # Check if user exists if self.container_client is None or not self.container_client.exists(): diff --git a/fertiscan/__init__.py b/fertiscan/__init__.py index 7ac8140c..a8054f3f 100644 --- a/fertiscan/__init__.py +++ b/fertiscan/__init__.py @@ -4,12 +4,28 @@ from azure.storage.blob import ContainerClient from dotenv import load_dotenv from psycopg import Cursor +from datetime import datetime import datastore import datastore.db.queries.picture as picture import datastore.db.queries.user as user import fertiscan.db.metadata.inspection as data_inspection -import fertiscan.db.queries.inspection as inspection +from fertiscan.db.queries import ( + ingredient, + inspection, + label, + metric, + nutrients, + organization, + registration_number, + sub_label, + fertilizer, +) + +from datastore import ContainerController, ClientController, User + +from pydantic import BaseModel +from typing import Optional load_dotenv() @@ -26,112 +42,532 @@ print("Warning: FERTISCAN_STORAGE_URL not set") -async def register_analysis( - cursor: Cursor, - container_client: ContainerClient, - user_id, - hashed_pictures, - analysis_dict, -): +class InspectionController: + def __init__(self, inspection_model: data_inspection.Inspection): + self.model: data_inspection.Inspection = inspection_model + self.id: UUID = inspection_model.inspection_id + return + + def get_inspection_image_location_data(self, cursor: Cursor) -> tuple[UUID, UUID]: + """ + Retrieve the relevant information regarding where the Inspection Images are stored + This is usefull to perform Container_Controller.get_folder_pictures() + + Parameters: + - cursor (Cursor): Database cursor for executing queries. + + Returns: + - tuple (container_id: UUID, folder: UUID): The id necessary to locate the images + """ + if not inspection.is_a_inspection_id(cursor=cursor, inspection_id=self.id): + raise inspection.InspectionNotFoundError( + f"Inspection not found based on the given id: {self.id}" + ) + picture_set_id = inspection.get_inspection_fk(cursor, self.id)[2] + + container_id = picture.get_picture_set_container_id( + cursor=cursor, picture_set_id=picture_set_id + ) + return (container_id, picture_set_id) + + def update_inspection( + self, + cursor: Cursor, + user_id: UUID, + updated_data: dict | data_inspection.Inspection, + ) -> data_inspection.Inspection: + """ + Update an existing inspection record in the database. + + Parameters: + - cursor (Cursor): Database cursor for executing queries. + - inspection_id (str | UUID): UUID of the inspection to update. + - user_id (str | UUID): UUID of the user performing the update. + - updated_data (dict | data_inspection.Inspection): Dictionary or Inspection model containing updated inspection data. + + Returns: + - data_inspection.Inspection: Updated inspection data from the database. + + Raises: + - InspectionUpdateError: If an error occurs during the update. + """ + if not user.is_a_user_id(cursor=cursor, user_id=user_id): + raise user.UserNotFoundError( + f"User not found based on the given id: {user_id}" + ) + if not isinstance(updated_data, data_inspection.Inspection): + updated_data = data_inspection.Inspection.model_validate(updated_data) + # The inspection record must exist before updating it + if not inspection.is_a_inspection_id(cursor, str(updated_data.inspection_id)): + raise inspection.InspectionNotFoundError( + f"Inspection not found based on the given id: {self.model.inspection_id}" + ) + if ( + self.model.container_id != updated_data.container_id + or self.model.folder_id != updated_data.folder_id + ): + raise Warning( + "You should not update an Inspection picture location. This does not cause issues in the DB but could result in errors when attempting to fetch the pictures" + ) + if not datastore.container_db.is_a_container( + cursor=cursor, container_id=updated_data.container_id + ): + raise datastore.ContainerCreationError( + f"Container not found based on the given id: {updated_data.container_id}" + ) + # Make sure the user can verify this inspection + if not datastore.verify_user_can_write( + cursor=cursor, container_id=updated_data.container_id, user_id=user_id + ): + raise datastore.PermissionNotHighEnough( + f"The user {user_id} does not have the write permission or higher which mean he cannot upload an inspection in the container {self.id}" + ) + # A completed inspection cannot be updated + if inspection.is_inspection_verified( + cursor=cursor, inspection_id=updated_data.inspection_id + ): + raise inspection.InspectionUpdateError( + "The inspection you are trying to update is already verified" + ) + else: + updated_at = inspection.update_inspection( + cursor=cursor, + inspection_id=updated_data.inspection_id, + verified=updated_data.verified, + inspection_comment=updated_data.inspection_comment, + ) + updated_data.updated_at = updated_at + self.model.updated_at = updated_at # just in case + # -------- + label_to_update = updated_data.product + label_id = updated_data.product.label_id + # Label Information + label.update_label_info( + cursor=cursor, + label_id=label_to_update.label_id, + name=label_to_update.name, + lot_number=label_to_update.lot_number, + npk=label_to_update.npk, + n=label_to_update.n, + p=label_to_update.p, + k=label_to_update.k, + title_en=updated_data.guaranteed_analysis.title.en, + title_fr=updated_data.guaranteed_analysis.title.fr, + is_minimal=updated_data.guaranteed_analysis.is_minimal, + record_keeping=label_to_update.record_keeping, + ) + + # Organization Information + org_ids = [] + for org in updated_data.organizations: + if org.id is not None: + org_ids.append(org.id) + # delete any orgs that have been removed from the list + organization.delete_absent_organisation_information_from_label( + cursor=cursor, label_id=label_id, org_ids=org_ids + ) + for org in updated_data.organizations: + if org.id is not None: + organization.update_organization_info( + cursor=cursor, + information_id=org.id, + name=org.name, + website=org.website, + phone_number=org.phone_number, + address=org.address, + is_main_contact=org.is_main_contact, + ) + # An org was added by the user and not detected by the pipeline + else: + org.id = organization.new_organization_information( + cursor=cursor, + address=org.address, + name=org.name, + website=org.website, + phone_number=org.phone_number, + label_id=label_to_update.label_id, + edited=True, + is_main_contact=org.is_main_contact, + ) + + # Metrics (Weigth - Density - Volume) + metric.upsert_metric( + cursor=cursor, + label_id=label_id, + metrics=updated_data.product.metrics.model_dump(), + ) + + # Ingredient + ingredient.upsert_ingredient( + cursor=cursor, + label_id=label_id, + ingredients=updated_data.ingredients.model_dump(), + ) + + # GA + nutrients.upsert_guaranteed_analysis( + cursor=cursor, + label_id=label_id, + GA=updated_data.guaranteed_analysis.model_dump(), + ) + + # Sublabel + # This use the whole dict as parameter since we fetch and loop for all the Sub_label_types + sub_label.upsert_sub_label( + cursor=cursor, + label_id=label_id, + inspection_dict=updated_data.model_dump(), + ) + + # Reg numbers + reg_numbers = [] + for entry in updated_data.product.registration_numbers: + reg_numbers.append(entry.model_dump()) + registration_number.update_registration_number( + cursor=cursor, label_id=label_id, registration_numbers=reg_numbers + ) + + # Verified + if updated_data.verified: + fertilizer_name = updated_data.product.name + + # Check If there is a Reg Number for this fertilizer :(if there are only one) + registration_number_value = None + for reg_number in updated_data.product.registration_numbers: + if not reg_number.is_an_ingredient: + registration_number_value = reg_number.registration_number + + # Insert Organization + main_org = None + if len(updated_data.organizations) == 0: + raise Warning( + "at least one Organization information is required for a verified inspection" + ) + elif len(updated_data.organizations) == 1: + main_org = updated_data.organizations[0] + else: + for org in updated_data.organizations: + if org.is_main_contact: + main_org = org + if main_org is None: + raise Warning( + "'Main contact organization information is required and was not found'" + ) + else: + organization_id = organization.upsert_organization( + cursor=cursor, + name=main_org.name, + website=main_org.website, + phone_number=main_org.phone_number, + address=main_org.address, + ) + # Since it it verified, we upsert the Fertilizer inspected + fertilizer.upsert_fertilizer( + cursor=cursor, + name=fertilizer_name, + reg_number=registration_number_value, + org_owner_id=organization_id, + latest_inspection_id=updated_data.inspection_id, + ) + else: + updated_data.verified = False + # ------------ + updated_data.updated_at = updated_at + return data_inspection.Inspection.model_validate(updated_data) + + async def delete_inspection( + self, + cursor: Cursor, + user_id: UUID, + ) -> data_inspection.DBInspection: + """ + Delete an existing inspection record and its associated picture set from the database. + + Parameters: + - cursor (Cursor): Database cursor for executing queries. + - inspection_id (str | UUID): UUID of the inspection to delete. + - user_id (str | UUID): UUID of the user performing the deletion. + + Returns: + - data_inspection.Inspection: The deleted inspection data from the database. + + """ + if not user.is_a_user_id(cursor=cursor, user_id=user_id): + raise user.UserNotFoundError( + f"User not found based on the given id: {user_id}" + ) + if not datastore.container_db.is_a_container( + cursor=cursor, container_id=self.model.container_id + ): + raise datastore.ContainerCreationError( + f"Container not found based on the given id: {self.model.container_id}" + ) + if not datastore.verify_user_can_write( + cursor=cursor, container_id=self.model.container_id, user_id=user_id + ): + raise datastore.PermissionNotHighEnough( + f"The user {user_id} does not have the write permission or higher which mean he cannot upload an inspection in the container {self.id}" + ) + + # Delete the inspection and get the returned data + deleted_inspection = inspection.delete_inspection(cursor, self.id, user_id) + deleted_inspection = data_inspection.DBInspection.model_validate( + deleted_inspection + ) + + container_controller: ContainerController = ( + await datastore.get_container_controller( + cursor, self.model.container_id, FERTISCAN_STORAGE_URL, None + ) + ) + + await container_controller.delete_folder_permanently( + cursor, user_id, self.model.folder_id + ) + return deleted_inspection + + +def new_inspection( + cursor: Cursor, user_id: UUID, analysis_dict, container_id: UUID, folder_id: UUID +) -> InspectionController: """ Register an analysis in the database Parameters: - cursor: The cursor object to interact with the database. - container_client: The container client of the user. - - analysis_dict (dict): The analysis to register in a dict string. - - picture: The picture encoded to upload. + - analysis_dict (dict): The analysis to register in a dict string Returns: - The analysis_dict with the analysis_id added. """ if not user.is_a_user_id(cursor=cursor, user_id=user_id): raise user.UserNotFoundError(f"User not found based on the given id: {user_id}") - if not container_client.exists(): + if not datastore.container_db.is_a_container( + cursor=cursor, container_id=container_id + ): raise datastore.ContainerCreationError( - f"Container not found based on the given user_id: {user_id}" + f"Container not found based on the given id: {container_id}" + ) + if not datastore.verify_user_can_write( + cursor=cursor, container_id=container_id, user_id=user_id + ): + raise datastore.PermissionNotHighEnough( + f"The user {user_id} does not have the write permission or higher which mean he cannot upload an inspection in the container {container_id}" ) - picture_set_id = await datastore.create_picture_set( - cursor, container_client, len(hashed_pictures), user_id + formatted_analysis = data_inspection.build_inspection_import( + analysis_form=analysis_dict, + user_id=user_id, + folder_id=folder_id, + container_id=container_id, ) - - # Upload pictures to storage - await datastore.upload_pictures( + # ---------- + # Label info + label_info_id = label.new_label_information( cursor=cursor, - user_id=str(user_id), - container_client=container_client, - picture_set_id=str(picture_set_id), - hashed_pictures=hashed_pictures, + name=formatted_analysis.product.name, + lot_number=formatted_analysis.product.lot_number, + npk=formatted_analysis.product.npk, + n=formatted_analysis.product.n, + p=formatted_analysis.product.p, + k=formatted_analysis.product.k, + title_en=formatted_analysis.guaranteed_analysis.title.en, + title_fr=formatted_analysis.guaranteed_analysis.title.fr, + is_minimal=formatted_analysis.guaranteed_analysis.is_minimal, + record_keeping=formatted_analysis.product.record_keeping, ) - - # Register analysis in the database - formatted_analysis = data_inspection.build_inspection_import(analysis_dict,user_id) - - analysis_db = inspection.new_inspection_with_label_info( - cursor, user_id, picture_set_id, formatted_analysis + formatted_analysis.product.label_id = label_info_id + # Metrics + # Weight + for record in formatted_analysis.product.metrics.weight: + metric.new_metric( + cursor=cursor, + value=record.value, + read_unit=record.unit, + label_id=label_info_id, + metric_type="weight", + edited=False, + ) + # Density + metric.new_metric( + cursor=cursor, + value=formatted_analysis.product.metrics.density.value, + read_unit=formatted_analysis.product.metrics.density.unit, + label_id=label_info_id, + metric_type="density", + edited=False, + ) + # Volume + metric.new_metric( + cursor=cursor, + value=formatted_analysis.product.metrics.volume.value, + read_unit=formatted_analysis.product.metrics.volume.unit, + label_id=label_info_id, + metric_type="volume", + edited=False, ) - return analysis_db - -async def update_inspection( - cursor: Cursor, - inspection_id: str | UUID, - user_id: str | UUID, - updated_data: dict | data_inspection.Inspection, -): - """ - Update an existing inspection record in the database. + # Ingredients + for ingredient_en in formatted_analysis.ingredients.en: + ingredient.new_ingredient( + cursor=cursor, + name=ingredient_en.name, + value=ingredient_en.value, + read_unit=ingredient_en.unit, + label_id=label_info_id, + language="en", + organic=None, + active=None, + edited=False, + ) + for ingredient_fr in formatted_analysis.ingredients.fr: + ingredient.new_ingredient( + cursor=cursor, + name=ingredient_fr.name, + value=ingredient_fr.value, + read_unit=ingredient_fr.unit, + label_id=label_info_id, + language="fr", + organic=None, + active=None, + edited=False, + ) - Parameters: - - cursor (Cursor): Database cursor for executing queries. - - inspection_id (str | UUID): UUID of the inspection to update. - - user_id (str | UUID): UUID of the user performing the update. - - updated_data (dict | data_inspection.Inspection): Dictionary or Inspection model containing updated inspection data. + # Sub Label + instruction_id = sub_label.get_sub_type_id(cursor, "instructions") + caution_id = sub_label.get_sub_type_id(cursor, "cautions") + max_instruction = max( + len(formatted_analysis.instructions.en), len(formatted_analysis.instructions.fr) + ) + max_caution = max( + len(formatted_analysis.cautions.en), len(formatted_analysis.cautions.fr) + ) + for i in range(0, max_instruction): + if i >= len(formatted_analysis.instructions.fr): + fr = None + en = formatted_analysis.instructions.en[i] + elif i >= len(formatted_analysis.instructions.en): + fr = formatted_analysis.instructions.fr[i] + en = None + else: + fr = formatted_analysis.instructions.fr[i] + en = formatted_analysis.instructions.en[i] + sub_label.new_sub_label( + cursor=cursor, + text_fr=fr, + text_en=en, + label_id=label_info_id, + sub_type_id=str(instruction_id), + edited=False, + ) - Returns: - - data_inspection.Inspection: Updated inspection data from the database. + for i in range(0, max_caution): + if i >= len(formatted_analysis.cautions.fr): + fr = None + en = formatted_analysis.cautions.en[i] + elif i >= len(formatted_analysis.cautions.en): + fr = formatted_analysis.cautions.fr[i] + en = None + else: + fr = formatted_analysis.cautions.fr[i] + en = formatted_analysis.cautions.en[i] + sub_label.new_sub_label( + cursor=cursor, + text_fr=formatted_analysis.cautions.fr[i], + text_en=formatted_analysis.cautions.en[i], + label_id=label_info_id, + sub_type_id=str(caution_id), + edited=False, + ) - Raises: - - InspectionUpdateError: If an error occurs during the update. - """ - if isinstance(inspection_id, str): - inspection_id = UUID(inspection_id) - if isinstance(user_id, str): - user_id = UUID(user_id) - if not user.is_a_user_id(cursor, str(user_id)): - raise user.UserNotFoundError(f"User not found based on the given id: {user_id}") + # Guaranteed Analysis + for record in formatted_analysis.guaranteed_analysis.en: + nutrients.new_guaranteed_analysis( + cursor=cursor, + read_name=record.name, + value=record.value, + unit=record.unit, + label_id=label_info_id, + language="en", + element_id=None, + edited=False, + ) + for record in formatted_analysis.guaranteed_analysis.fr: + nutrients.new_guaranteed_analysis( + cursor=cursor, + read_name=record.name, + value=record.value, + unit=record.unit, + label_id=label_info_id, + language="fr", + element_id=None, + edited=False, + ) - if not isinstance(updated_data, data_inspection.Inspection): - updated_data = data_inspection.Inspection.model_validate(updated_data) + # Reg numbers + for record in formatted_analysis.product.registration_numbers: + registration_number.new_registration_number( + cursor=cursor, + registration_number=record.registration_number, + label_id=label_info_id, + is_an_ingredient=record.is_an_ingredient, + read_name=None, + edited=False, + ) - # The inspection record must exist before updating it - if not inspection.is_a_inspection_id(cursor, str(inspection_id)): - raise inspection.InspectionNotFoundError( - f"Inspection not found based on the given id: {inspection_id}" + # Organization + flag = True + for record in formatted_analysis.organizations: + record.id = organization.new_organization_information( + cursor=cursor, + address=record.address, + name=record.name, + website=record.website, + phone_number=record.phone_number, + label_id=label_info_id, + edited=False, + is_main_contact=flag, ) + if flag == True: + # We do this since we have no way of knowing who is the main contact + record.is_main_contact = True # We assume the first one is the main contact + flag = False - updated_result = inspection.update_inspection( - cursor, inspection_id, user_id, updated_data.model_dump() + # Inspection + inspection_id, upload_date = inspection.new_inspection( + cursor=cursor, + user_id=user_id, + picture_set_id=folder_id, + verified=False, + label_id=label_info_id, + container_id=container_id, + ) + formatted_analysis.inspection_id = inspection_id + formatted_analysis.upload_date = upload_date + formatted_analysis.updated_at = upload_date + analysis_db = data_inspection.Inspection.model_validate(formatted_analysis) + inspection.save_inspection_original_dataset( + cursor=cursor, + inspection_id=formatted_analysis.inspection_id, + og_data=analysis_db.model_dump_json(), ) - return data_inspection.Inspection.model_validate(updated_result) + # ------------ + inspection_controller = InspectionController(analysis_db) -async def get_full_inspection_json( + return inspection_controller + + +def get_inspection( cursor: Cursor, - inspection_id, - user_id=None, - picture_set_id=None, - label_info_id=None, - company_info_id=None, - manufacturer_info_id=None, -): + inspection_id: UUID, +) -> InspectionController: """ - Get the full inspection json from the database + Get the full inspection json from the database and return a controller for it Parameters: - cursor: The cursor object to interact with the database. - - inspection_id: The inspection id of the inspection. Returns: - The inspection json. @@ -141,82 +577,18 @@ async def get_full_inspection_json( f"Inspection not found based on the given id: {inspection_id}" ) - # Check Ids - if ( - ( - picture_set_id is None - or picture_set_id == "" - or not picture.is_a_picture_set_id( - cursor=cursor, picture_set_id=picture_set_id - ) - ) - or ( - user_id is None - or user_id == "" - or not user.is_a_user_id(cursor=cursor, user_id=user_id) - ) - or (label_info_id is None or label_info_id == "") - or (company_info_id is None or company_info_id == "") - or (manufacturer_info_id is None or manufacturer_info_id == "") - ): - ids = inspection.get_inspection_fk(cursor, inspection_id) - picture_set_id = ids[2] - label_info_id = ids[0] - company_info_id = ids[3] - manufacturer_info_id = ids[4] - user_id = ids[1] - else: - if not picture.is_a_picture_set_id( - cursor=cursor, picture_set_id=picture_set_id - ): - raise picture.PictureSetNotFoundError( - f"Picture set not found based on the given id: {picture_set_id}" - ) - if not user.is_a_user_id(cursor=cursor, user_id=user_id): - raise user.UserNotFoundError( - f"User not found based on the given id: {user_id}" - ) - ids = inspection.get_inspection_fk(cursor, inspection_id) - if not picture_set_id == ids[2]: - raise Warning( - "Picture set id does not match the picture_set_id in the inspection for the given inspection_id" - ) - if not label_info_id == ids[0]: - raise Warning( - "Label info id does not match the label_info_id in the inspection for the given inspection_id" - ) - if not company_info_id == ids[3]: - raise Warning( - "Company info id does not match the company_info_id in the inspection for the given inspection_id" - ) - if not manufacturer_info_id == ids[4]: - raise Warning( - "Manufacturer info id does not match the manufacturer_info_id in the inspection for the given inspection_id" - ) - if not user_id == ids[1]: - raise Warning( - "User id does not match the user_id in the inspection for the given inspection_id" - ) - - # Retrieve pictures - # pictures_ids = picture.get_picture_in_picture_set(cursor, picture_set_id) - # Retrieve label_info - inspection_metadata = data_inspection.build_inspection_export( - cursor, inspection_id - ) + inspection_metadata = data_inspection.build_inspection_export(cursor, inspection_id) + inspection_controller = InspectionController(inspection_model=inspection_metadata) + return inspection_controller - return inspection_metadata - -async def get_user_analysis_by_verified(cursor: Cursor, user_id, verified: bool): +def get_user_analysis_by_verified(cursor: Cursor, user_id: UUID, verified: bool): """ This function fetch all the inspection of a user - Parameters: - cursor: The cursor object to interact with the database. - user_id: The user id of the user. - Returns: - List of unverified analysis. [ @@ -227,46 +599,97 @@ async def get_user_analysis_by_verified(cursor: Cursor, user_id, verified: bool) inspection.picture_set_id, label_info.id as label_info_id, label_info.product_name, + label_info.company_info_id, + label_info.manufacturer_info_id company_info.id as company_info_id, - company_info.name as company_name, - inspection.verified + company_info.company_name ] """ - if not user.is_a_user_id(cursor=cursor, user_id=user_id): raise user.UserNotFoundError(f"User not found based on the given id: {user_id}") return inspection.get_all_user_inspection_filter_verified(cursor, user_id, verified) -async def delete_inspection( +def search_inspection( cursor: Cursor, - inspection_id: str | UUID, - user_id: str | UUID, - container_client: ContainerClient, -) -> data_inspection.DBInspection: + fertilizer_name: str, + reg_number: str, + lot_number: str, + inspector_name: str, + lower_bound_date: datetime, + upper_bound_date: datetime, + organization_name: str, + organization_address: str, + organization_phone: str, +): """ - Delete an existing inspection record and its associated picture set from the database. - + This function search all the verified inspection based on the given parameters Parameters: - - cursor (Cursor): Database cursor for executing queries. - - inspection_id (str | UUID): UUID of the inspection to delete. - - user_id (str | UUID): UUID of the user performing the deletion. + - cursor (Cursor): The cursor object to interact with the database. + - fertilizer_name (str): The name of the fertilizer. + - reg_number (str): The registration number of the fertilizer. + - lot_number (str): The lot number of the fertilizer. + - inspector_name (str): The name of the inspector. (Not used at the moment) + - lower_bound_date (str): The lower bound date of the inspection. + - upper_bound_date (str): The upper bound date of the inspection. + - organization_name (str): The name of the organization. + - organization_address (str): The address of the organization. + - organization_phone (str): The phone number of the organization. Returns: - - data_inspection.Inspection: The deleted inspection data from the database. - + - List of inspection tuple. + [ + inspection.id, + inspection.verified + inspection.upload_date, + inspection.updated_at, + inspection.inspector_id + inspection.label_info_id, + inspection.container_id, + inspection.folder_id, + inspection.inspection_comment, + inspection.verified_date, + label_info.product_name, + organization_info.id, (main_contact_id) + organization_info.name, + organization_info.phone_number, + organization_info.address, + label_info.is_minimal, + label_info.record_keeping, + registration_number.identifiers, (list of reg numbers) + ] """ - if isinstance(inspection_id, str): - inspection_id = UUID(inspection_id) - if isinstance(user_id, str): - user_id = UUID(user_id) - - # Delete the inspection and get the returned data - deleted_inspection = inspection.delete_inspection(cursor, inspection_id, user_id) - deleted_inspection = data_inspection.DBInspection.model_validate(deleted_inspection) - - await datastore.delete_picture_set_permanently( - cursor, str(user_id), str(deleted_inspection.picture_set_id), container_client + label_ids = [] + # search based on Organization info + if ( + organization_name is not None + or organization_address is not None + or organization_phone is not None + ): + orgs = organization.search_organization_information( + cursor=cursor, + name=organization_name, + address=organization_address, + phone_number=organization_phone, + website=None, + ) + if org is not None and len(orgs) > 0: + for org in orgs: + label_ids.append(org[1]) + # search based on registration number + if reg_number is not None and reg_number.strip() == "": + reg_result = registration_number.search_registration_number( + cursor=cursor, registration_number=reg_number + ) + if reg_result is not None and len(reg_result) > 0: + for reg in reg_result: + label_ids.append(reg[1]) + return inspection.search_inspection( + cursor=cursor, + fertilizer_name=fertilizer_name, + lower_bound_date=lower_bound_date, + upper_bound_date=upper_bound_date, + lot_number=lot_number, + label_ids=label_ids, + inspector_name=inspector_name ) - - return deleted_inspection diff --git a/fertiscan/db/bytebase/OLAP/guaranteed_triggers.sql b/fertiscan/db/bytebase/OLAP/guaranteed_triggers.sql index 90e3bd6c..08ae0d8c 100644 --- a/fertiscan/db/bytebase/OLAP/guaranteed_triggers.sql +++ b/fertiscan/db/bytebase/OLAP/guaranteed_triggers.sql @@ -1,11 +1,11 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_guaranteed_creation() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_guaranteed_creation() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'INSERT') THEN IF (NEW.id IS NOT NULL) AND (NEW.label_id IS NOT NULL) THEN -- Update the label_dimension table with the new guaranteed_analysis_id - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET guaranteed_ids = array_append(guaranteed_ids, NEW.id) WHERE label_dimension.label_id = NEW.label_id; ELSE @@ -17,19 +17,19 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS guaranteed_creation ON "fertiscan_0.0.18".guaranteed; +DROP TRIGGER IF EXISTS guaranteed_creation ON "fertiscan_0.1.1".guaranteed; CREATE TRIGGER guaranteed_creation -AFTER INSERT ON "fertiscan_0.0.18".guaranteed +AFTER INSERT ON "fertiscan_0.1.1".guaranteed FOR EACH ROW EXECUTE FUNCTION olap_guaranteed_creation(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_guaranteed_deletion() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_guaranteed_deletion() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'DELETE') THEN IF (OLD.id IS NOT NULL) AND (OLD.label_id IS NOT NULL) THEN -- Update the label_dimension table with the new guaranteed_analysis_id - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET guaranteed_ids = array_remove(guaranteed_ids, OLD.id) WHERE label_dimension.label_id = OLD.label_id; ELSE @@ -41,8 +41,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS guaranteed_deletion ON "fertiscan_0.0.18".guaranteed; +DROP TRIGGER IF EXISTS guaranteed_deletion ON "fertiscan_0.1.1".guaranteed; CREATE TRIGGER guaranteed_deletion -AFTER DELETE ON "fertiscan_0.0.18".guaranteed +AFTER DELETE ON "fertiscan_0.1.1".guaranteed FOR EACH ROW EXECUTE FUNCTION olap_guaranteed_deletion(); diff --git a/fertiscan/db/bytebase/OLAP/ingredient_trigger.sql b/fertiscan/db/bytebase/OLAP/ingredient_trigger.sql index 2078fef4..2a6e9ba0 100644 --- a/fertiscan/db/bytebase/OLAP/ingredient_trigger.sql +++ b/fertiscan/db/bytebase/OLAP/ingredient_trigger.sql @@ -1,11 +1,11 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_ingredient_creation() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_ingredient_creation() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'INSERT') THEN IF (NEW.id IS NOT NULL) AND (NEW.label_id IS NOT NULL) THEN -- Update the label_dimension table with the new ingredient_analysis_id - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET ingredient_ids = array_append(ingredient_ids, NEW.id) WHERE label_dimension.label_id = NEW.label_id; ELSE @@ -17,19 +17,19 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS ingredient_creation ON "fertiscan_0.0.18".ingredient; +DROP TRIGGER IF EXISTS ingredient_creation ON "fertiscan_0.1.1".ingredient; CREATE TRIGGER ingredient_creation -AFTER INSERT ON "fertiscan_0.0.18".ingredient +AFTER INSERT ON "fertiscan_0.1.1".ingredient FOR EACH ROW EXECUTE FUNCTION olap_ingredient_creation(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_ingredient_deletion() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_ingredient_deletion() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'DELETE') THEN IF (OLD.id IS NOT NULL) AND (OLD.label_id IS NOT NULL) THEN -- Update the label_dimension table with the new ingredient_analysis_id - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET ingredient_ids = array_remove(ingredient_ids, OLD.id) WHERE label_dimension.label_id = OLD.label_id; ELSE @@ -41,8 +41,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS ingredient_deletion ON "fertiscan_0.0.18".ingredient; +DROP TRIGGER IF EXISTS ingredient_deletion ON "fertiscan_0.1.1".ingredient; CREATE TRIGGER ingredient_deletion -AFTER DELETE ON "fertiscan_0.0.18".ingredient +AFTER DELETE ON "fertiscan_0.1.1".ingredient FOR EACH ROW EXECUTE FUNCTION olap_ingredient_deletion(); diff --git a/fertiscan/db/bytebase/OLAP/inspection_triggers.sql b/fertiscan/db/bytebase/OLAP/inspection_triggers.sql index 9519ac75..478d529e 100644 --- a/fertiscan/db/bytebase/OLAP/inspection_triggers.sql +++ b/fertiscan/db/bytebase/OLAP/inspection_triggers.sql @@ -1,5 +1,5 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_inspection_creation() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_inspection_creation() RETURNS TRIGGER AS $$ DECLARE time_id UUID; @@ -7,7 +7,7 @@ BEGIN IF (TG_OP = 'INSERT') THEN IF (NEW.id IS NOT NULL) AND (NEW.label_info_id IS NOT NULL) THEN -- Time Dimension - INSERT INTO "fertiscan_0.0.18".time_dimension ( + INSERT INTO "fertiscan_0.1.1".time_dimension ( date_value, year,month,day) VALUES ( CURRENT_DATE, @@ -16,7 +16,7 @@ BEGIN EXTRACT(DAY FROM CURRENT_DATE) ) RETURNING id INTO time_id; -- Create the Inspection_factual entry - INSERT INTO "fertiscan_0.0.18".inspection_factual ( + INSERT INTO "fertiscan_0.1.1".inspection_factual ( inspection_id, inspector_id, label_info_id, time_id, sample_id, picture_set_id, original_dataset ) VALUES ( NEW.id, @@ -36,19 +36,19 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS inspection_creation ON "fertiscan_0.0.18".inspection; +DROP TRIGGER IF EXISTS inspection_creation ON "fertiscan_0.1.1".inspection; CREATE TRIGGER inspection_creation -AFTER INSERT ON "fertiscan_0.0.18".inspection +AFTER INSERT ON "fertiscan_0.1.1".inspection FOR EACH ROW EXECUTE FUNCTION olap_inspection_creation(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_inspection_update() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_inspection_update() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'UPDATE') THEN IF (NEW.id IS NOT NULL) THEN IF (NEW.label_info_id != OLD.label_info_id) OR (NEW.inspector_id != OLD.inspector_id) OR (NEW.picture_set_id != OLD.picture_set_id) THEN - UPDATE "fertiscan_0.0.18".inspection_factual + UPDATE "fertiscan_0.1.1".inspection_factual SET inspector_id = NEW.inspector_id, label_info_id = NEW.label_info_id, picture_set_id = NEW.picture_set_id WHERE inspection_id = NEW.id; END IF; @@ -61,18 +61,18 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS inspection_update ON "fertiscan_0.0.18".inspection; +DROP TRIGGER IF EXISTS inspection_update ON "fertiscan_0.1.1".inspection; CREATE TRIGGER inspection_update -BEFORE UPDATE ON "fertiscan_0.0.18".inspection +BEFORE UPDATE ON "fertiscan_0.1.1".inspection FOR EACH ROW EXECUTE FUNCTION olap_inspection_update(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_inspection_deletion() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_inspection_deletion() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'DELETE') THEN IF (OLD.id IS NOT NULL) THEN - DELETE FROM "fertiscan_0.0.18".inspection_factual + DELETE FROM "fertiscan_0.1.1".inspection_factual WHERE inspection_id = OLD.id; ELSE -- Raise a warning if the condition is not met @@ -83,8 +83,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS inspection_deletion ON "fertiscan_0.0.18".inspection; +DROP TRIGGER IF EXISTS inspection_deletion ON "fertiscan_0.1.1".inspection; CREATE TRIGGER inspection_deletion -AFTER DELETE ON "fertiscan_0.0.18".inspection +AFTER DELETE ON "fertiscan_0.1.1".inspection FOR EACH ROW EXECUTE FUNCTION olap_inspection_deletion(); diff --git a/fertiscan/db/bytebase/OLAP/label_information_triggers.sql b/fertiscan/db/bytebase/OLAP/label_information_triggers.sql index ddca34c2..e436783d 100644 --- a/fertiscan/db/bytebase/OLAP/label_information_triggers.sql +++ b/fertiscan/db/bytebase/OLAP/label_information_triggers.sql @@ -1,10 +1,10 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_label_information_creation() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_label_information_creation() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'INSERT') THEN IF (NEW.id IS NOT NULL) THEN - INSERT INTO "fertiscan_0.0.18"."label_dimension" ( + INSERT INTO "fertiscan_0.1.1"."label_dimension" ( label_id ) VALUES ( NEW.id @@ -18,19 +18,19 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS label_information_creation ON "fertiscan_0.0.18".label_information; +DROP TRIGGER IF EXISTS label_information_creation ON "fertiscan_0.1.1".label_information; CREATE TRIGGER label_information_creation -AFTER INSERT ON "fertiscan_0.0.18".label_information +AFTER INSERT ON "fertiscan_0.1.1".label_information FOR EACH ROW EXECUTE FUNCTION olap_label_information_creation(); --- CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_label_information_update() +-- CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_label_information_update() -- RETURNS TRIGGER AS $$ -- BEGIN -- IF (TG_OP = 'UPDATE') THEN -- IF (NEW.id IS NOT NULL) THEN -- IF (NEW.company_info_id !=OLD.company_info_id) OR (NEW.manufacturer_info_id != OLD.manufacturer_info_id) THEN --- UPDATE "fertiscan_0.0.18"."label_dimension" +-- UPDATE "fertiscan_0.1.1"."label_dimension" -- SET company_info_id = NEW.company_info_id, manufacturer_info_id = NEW.manufacturer_info_id -- WHERE label_id = NEW.id; -- END IF; @@ -43,18 +43,18 @@ EXECUTE FUNCTION olap_label_information_creation(); -- END; -- $$ LANGUAGE plpgsql; --- DROP TRIGGER IF EXISTS label_information_update ON "fertiscan_0.0.18".label_information; +-- DROP TRIGGER IF EXISTS label_information_update ON "fertiscan_0.1.1".label_information; -- CREATE TRIGGER label_information_update --- BEFORE UPDATE ON "fertiscan_0.0.18".label_information +-- BEFORE UPDATE ON "fertiscan_0.1.1".label_information -- FOR EACH ROW -- EXECUTE FUNCTION olap_label_information_update(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_label_information_deletion() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_label_information_deletion() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'DELETE') THEN IF (OLD.id IS NOT NULL) THEN - DELETE FROM "fertiscan_0.0.18"."label_dimension" + DELETE FROM "fertiscan_0.1.1"."label_dimension" WHERE label_id = OLD.id; ELSE -- Raise a warning if the condition is not met @@ -65,8 +65,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS label_information_deletion ON "fertiscan_0.0.18".label_information; +DROP TRIGGER IF EXISTS label_information_deletion ON "fertiscan_0.1.1".label_information; CREATE TRIGGER label_information_deletion -AFTER DELETE ON "fertiscan_0.0.18".label_information +AFTER DELETE ON "fertiscan_0.1.1".label_information FOR EACH ROW EXECUTE FUNCTION olap_label_information_deletion(); diff --git a/fertiscan/db/bytebase/OLAP/metrics_triggers.sql b/fertiscan/db/bytebase/OLAP/metrics_triggers.sql index 104c2c0f..4a8c5106 100644 --- a/fertiscan/db/bytebase/OLAP/metrics_triggers.sql +++ b/fertiscan/db/bytebase/OLAP/metrics_triggers.sql @@ -1,5 +1,5 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_metrics_creation() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_metrics_creation() RETURNS TRIGGER AS $$ DECLARE metric_type TEXT; @@ -11,7 +11,7 @@ BEGIN IF (metric_type ILIKE 'test%') THEN RETURN NEW; END IF; - EXECUTE format('UPDATE "fertiscan_0.0.18"."label_dimension" + EXECUTE format('UPDATE "fertiscan_0.1.1"."label_dimension" SET %I = array_append(%I, %L) WHERE label_dimension.label_id = %L', metric_type, metric_type, NEW.id, NEW.label_id); @@ -24,13 +24,13 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS metrics_creation ON "fertiscan_0.0.18".metric; +DROP TRIGGER IF EXISTS metrics_creation ON "fertiscan_0.1.1".metric; CREATE TRIGGER metrics_creation -AFTER INSERT ON "fertiscan_0.0.18".metric +AFTER INSERT ON "fertiscan_0.1.1".metric FOR EACH ROW EXECUTE FUNCTION olap_metrics_creation(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_metrics_deletion() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_metrics_deletion() RETURNS TRIGGER AS $$ DECLARE metric_type TEXT; @@ -42,7 +42,7 @@ BEGIN IF (metric_type ILIKE 'test%') THEN RETURN OLD; END IF; - EXECUTE format('UPDATE "fertiscan_0.0.18"."label_dimension" + EXECUTE format('UPDATE "fertiscan_0.1.1"."label_dimension" SET %I = array_remove(%I, %L) WHERE label_dimension.label_id = %L', metric_type, metric_type, OLD.id, OLD.label_id); @@ -55,8 +55,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS metrics_deletion ON "fertiscan_0.0.18".metric; +DROP TRIGGER IF EXISTS metrics_deletion ON "fertiscan_0.1.1".metric; CREATE TRIGGER metrics_deletion -AFTER DELETE ON "fertiscan_0.0.18".metric +AFTER DELETE ON "fertiscan_0.1.1".metric FOR EACH ROW EXECUTE FUNCTION olap_metrics_deletion(); diff --git a/fertiscan/db/bytebase/OLAP/micronutrient_triggers.sql b/fertiscan/db/bytebase/OLAP/micronutrient_triggers.sql index f23fefa1..39a1cb88 100644 --- a/fertiscan/db/bytebase/OLAP/micronutrient_triggers.sql +++ b/fertiscan/db/bytebase/OLAP/micronutrient_triggers.sql @@ -1,11 +1,11 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_micronutrient_creation() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_micronutrient_creation() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'INSERT') THEN IF (NEW.id IS NOT NULL) AND (NEW.label_id IS NOT NULL) THEN -- Update the label_dimension table with the new micronutrient_analysis_id - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET micronutrient_ids = array_append(micronutrient_ids, NEW.id) WHERE label_dimension.label_id = NEW.label_id; ELSE @@ -17,19 +17,19 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS micronutrient_creation ON "fertiscan_0.0.18".micronutrient; +DROP TRIGGER IF EXISTS micronutrient_creation ON "fertiscan_0.1.1".micronutrient; CREATE TRIGGER micronutrient_creation -AFTER INSERT ON "fertiscan_0.0.18".micronutrient +AFTER INSERT ON "fertiscan_0.1.1".micronutrient FOR EACH ROW EXECUTE FUNCTION olap_micronutrient_creation(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_micronutrient_deletion() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_micronutrient_deletion() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'DELETE') THEN IF (OLD.id IS NOT NULL) AND (OLD.label_id IS NOT NULL) THEN -- Update the label_dimension table with the new micronutrient_analysis_id - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET micronutrient_ids = array_remove(micronutrient_ids, OLD.id) WHERE label_dimension.label_id = OLD.label_id; ELSE @@ -41,8 +41,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS micronutrient_deletion ON "fertiscan_0.0.18".micronutrient; +DROP TRIGGER IF EXISTS micronutrient_deletion ON "fertiscan_0.1.1".micronutrient; CREATE TRIGGER micronutrient_deletion -AFTER DELETE ON "fertiscan_0.0.18".micronutrient +AFTER DELETE ON "fertiscan_0.1.1".micronutrient FOR EACH ROW EXECUTE FUNCTION olap_micronutrient_deletion(); diff --git a/fertiscan/db/bytebase/OLAP/organization_triggers.sql b/fertiscan/db/bytebase/OLAP/organization_triggers.sql index 04c308be..57ec2d80 100644 --- a/fertiscan/db/bytebase/OLAP/organization_triggers.sql +++ b/fertiscan/db/bytebase/OLAP/organization_triggers.sql @@ -1,10 +1,10 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_organization_information_creation() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_organization_information_creation() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'INSERT') THEN IF (NEW.id IS NOT NULL) THEN - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET organization_info_ids = array_append(organization_info_ids, NEW.id) WHERE label_dimension.label_id = NEW.label_id; ELSE @@ -16,18 +16,18 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS organization_information_creation ON "fertiscan_0.0.18".organization_information; +DROP TRIGGER IF EXISTS organization_information_creation ON "fertiscan_0.1.1".organization_information; CREATE TRIGGER organization_information_creation -AFTER INSERT ON "fertiscan_0.0.18".organization_information +AFTER INSERT ON "fertiscan_0.1.1".organization_information FOR EACH ROW EXECUTE FUNCTION olap_organization_information_creation(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_organization_information_deletion() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_organization_information_deletion() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'DELETE') THEN IF (OLD.id IS NOT NULL) THEN - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET organization_info_ids = array_remove(organization_info_ids, OLD.id) WHERE label_dimension.label_id = OLD.label_id; ELSE diff --git a/fertiscan/db/bytebase/OLAP/registration_number_triggers.sql b/fertiscan/db/bytebase/OLAP/registration_number_triggers.sql index fcccc1ef..60c70e07 100644 --- a/fertiscan/db/bytebase/OLAP/registration_number_triggers.sql +++ b/fertiscan/db/bytebase/OLAP/registration_number_triggers.sql @@ -1,5 +1,5 @@ -- Trigger function -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_registration_number_creation() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_registration_number_creation() RETURNS TRIGGER AS $$ BEGIN -- Check if the operation is an INSERT @@ -7,7 +7,7 @@ BEGIN -- Check if the NEW.id is not NULL IF (NEW.id IS NOT NULL) AND (NEW.label_id IS NOT NULL) AND (NEW.identifier IS NOT NULL) THEN -- Update the label_dimension table with the new guaranteed_analysis_id - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET registration_number_ids = array_append(registration_number_ids, NEW.id) WHERE label_dimension.label_id = NEW.label_id; ELSE @@ -20,14 +20,14 @@ END; $$ LANGUAGE plpgsql; -- Trigger -DROP TRIGGER IF EXISTS registration_number_creation ON "fertiscan_0.0.18".registration_number_information; +DROP TRIGGER IF EXISTS registration_number_creation ON "fertiscan_0.1.1".registration_number_information; CREATE TRIGGER registration_number_creation -AFTER INSERT ON "fertiscan_0.0.18".registration_number_information +AFTER INSERT ON "fertiscan_0.1.1".registration_number_information FOR EACH ROW EXECUTE FUNCTION olap_registration_number_creation(); -- Trigger function -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_registration_number_deletion() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_registration_number_deletion() RETURNS TRIGGER AS $$ BEGIN -- Check if the operation is an INSERT @@ -35,7 +35,7 @@ BEGIN -- Check if the NEW.id is not NULL IF (OLD.id IS NOT NULL) AND (OLD.label_id IS NOT NULL) THEN -- Update the label_dimension table with the new guaranteed_analysis_id - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET registration_number_ids = array_remove(registration_number_ids, OLD.id) WHERE label_dimension.label_id = OLD.label_id; ELSE @@ -48,8 +48,8 @@ END; $$ LANGUAGE plpgsql; -- Trigger -DROP TRIGGER IF EXISTS registration_number_deletion ON "fertiscan_0.0.18".registration_number_information; +DROP TRIGGER IF EXISTS registration_number_deletion ON "fertiscan_0.1.1".registration_number_information; CREATE TRIGGER registration_number_deletion -AFTER INSERT ON "fertiscan_0.0.18".registration_number_information +AFTER INSERT ON "fertiscan_0.1.1".registration_number_information FOR EACH ROW EXECUTE FUNCTION olap_registration_number_deletion(); diff --git a/fertiscan/db/bytebase/OLAP/specification_triggers.sql b/fertiscan/db/bytebase/OLAP/specification_triggers.sql index 840e0678..e13a31f3 100644 --- a/fertiscan/db/bytebase/OLAP/specification_triggers.sql +++ b/fertiscan/db/bytebase/OLAP/specification_triggers.sql @@ -1,11 +1,11 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_specification_creation() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_specification_creation() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'INSERT') THEN IF (NEW.id IS NOT NULL) AND (NEW.label_id IS NOT NULL) THEN -- Update the label_dimension table with the new specification_analysis_id - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET specification_ids = array_append(specification_ids, NEW.id) WHERE label_dimension.label_id = NEW.label_id; ELSE @@ -17,19 +17,19 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS specification_creation ON "fertiscan_0.0.18".specification; +DROP TRIGGER IF EXISTS specification_creation ON "fertiscan_0.1.1".specification; CREATE TRIGGER specification_creation -AFTER INSERT ON "fertiscan_0.0.18".specification +AFTER INSERT ON "fertiscan_0.1.1".specification FOR EACH ROW EXECUTE FUNCTION olap_specification_creation(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_specification_deletion() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_specification_deletion() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'DELETE') THEN IF (OLD.id IS NOT NULL) AND (OLD.label_id IS NOT NULL) THEN -- Update the label_dimension table with the new specification_analysis_id - UPDATE "fertiscan_0.0.18"."label_dimension" + UPDATE "fertiscan_0.1.1"."label_dimension" SET specification_ids = array_remove(specification_ids, OLD.id) WHERE label_dimension.label_id = OLD.label_id; ELSE @@ -41,8 +41,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS specification_deletion ON "fertiscan_0.0.18".specification; +DROP TRIGGER IF EXISTS specification_deletion ON "fertiscan_0.1.1".specification; CREATE TRIGGER specification_deletion -AFTER DELETE ON "fertiscan_0.0.18".specification +AFTER DELETE ON "fertiscan_0.1.1".specification FOR EACH ROW EXECUTE FUNCTION olap_specification_deletion(); diff --git a/fertiscan/db/bytebase/OLAP/sub_label_triggers.sql b/fertiscan/db/bytebase/OLAP/sub_label_triggers.sql index e8ba9298..e525c79a 100644 --- a/fertiscan/db/bytebase/OLAP/sub_label_triggers.sql +++ b/fertiscan/db/bytebase/OLAP/sub_label_triggers.sql @@ -1,5 +1,5 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_sub_label_creation() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_sub_label_creation() RETURNS TRIGGER AS $$ DECLARE type_str TEXT; @@ -7,12 +7,12 @@ BEGIN IF (TG_OP = 'INSERT') THEN IF (NEW.id IS NOT NULL) AND (NEW.label_id IS NOT NULL) AND (NEW.sub_type_id IS NOT NULL) THEN -- FIND THE SUB_TYPE TO GET THE COLUMN IDENTIFIER - SELECT sub_type.type_en INTO type_str FROM "fertiscan_0.0.18".sub_type WHERE sub_type.id = NEW.sub_type_id; + SELECT sub_type.type_en INTO type_str FROM "fertiscan_0.1.1".sub_type WHERE sub_type.id = NEW.sub_type_id; IF (type_str ILIKE 'test%') THEN RETURN NEW; -- Do not update the OLAP dimension for test sub_labels END IF; type_str = type_str || '_ids'; - EXECUTE format('UPDATE "fertiscan_0.0.18".label_dimension + EXECUTE format('UPDATE "fertiscan_0.1.1".label_dimension SET %I = array_append(%I, %L) WHERE label_id = %L;', type_str, type_str, NEW.id, NEW.label_id); @@ -23,13 +23,13 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS sub_label_creation ON "fertiscan_0.0.18".sub_label; +DROP TRIGGER IF EXISTS sub_label_creation ON "fertiscan_0.1.1".sub_label; CREATE TRIGGER sub_label_creation -AFTER INSERT ON "fertiscan_0.0.18".sub_label +AFTER INSERT ON "fertiscan_0.1.1".sub_label FOR EACH ROW EXECUTE FUNCTION olap_sub_label_creation(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".olap_sub_label_deletion() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".olap_sub_label_deletion() RETURNS TRIGGER AS $$ DECLARE type_str TEXT; @@ -37,12 +37,12 @@ BEGIN IF (TG_OP = 'DELETE') THEN IF (OLD.id IS NOT NULL) AND (OLD.label_id IS NOT NULL) AND (OLD.sub_type_id IS NOT NULL) THEN -- FIND THE SUB_TYPE TO GET THE COLUMN IDENTIFIER - SELECT sub_type.type_en INTO type_str FROM "fertiscan_0.0.18".sub_type WHERE sub_type.id = OLD.sub_type_id; + SELECT sub_type.type_en INTO type_str FROM "fertiscan_0.1.1".sub_type WHERE sub_type.id = OLD.sub_type_id; IF (type_str ILIKE 'test%') THEN RETURN OLD; -- Do not update the OLAP dimension for test sub_labels END IF; type_str = type_str || '_ids'; - EXECUTE format('UPDATE "fertiscan_0.0.18".label_dimension + EXECUTE format('UPDATE "fertiscan_0.1.1".label_dimension SET %I = array_remove(%I, %L) WHERE label_id = %L;', type_str, type_str, OLD.id, OLD.label_id); @@ -55,8 +55,8 @@ BEGIN END; $$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS sub_label_deletion ON "fertiscan_0.0.18".sub_label; +DROP TRIGGER IF EXISTS sub_label_deletion ON "fertiscan_0.1.1".sub_label; CREATE TRIGGER sub_label_deletion -AFTER DELETE ON "fertiscan_0.0.18".sub_label +AFTER DELETE ON "fertiscan_0.1.1".sub_label FOR EACH ROW EXECUTE FUNCTION olap_sub_label_deletion(); diff --git a/fertiscan/db/bytebase/delete_inspection_function.sql b/fertiscan/db/bytebase/delete_inspection_function.sql index 0fdfefe1..be502500 100644 --- a/fertiscan/db/bytebase/delete_inspection_function.sql +++ b/fertiscan/db/bytebase/delete_inspection_function.sql @@ -1,17 +1,17 @@ -- To avoid potential schema drift issues -SET search_path TO "fertiscan_0.0.18"; +SET search_path TO "fertiscan_0.1.1"; -- Trigger function to handle after organization_information delete for location deletion -DROP TRIGGER IF EXISTS after_organization_delete_main_location ON "fertiscan_0.0.18".organization; -drop FUNCTION IF EXISTS "fertiscan_0.0.18".after_org_delete_location_trig(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".after_org_delete_location_trig() +DROP TRIGGER IF EXISTS after_organization_delete_main_location ON "fertiscan_0.1.1".organization; +drop FUNCTION IF EXISTS "fertiscan_0.1.1".after_org_delete_location_trig(); +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".after_org_delete_location_trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN IF OLD.main_location_id IS NOT NULL THEN BEGIN - DELETE FROM "fertiscan_0.0.18".location + DELETE FROM "fertiscan_0.1.1".location WHERE id = OLD.main_location_id; EXCEPTION WHEN foreign_key_violation THEN RAISE NOTICE 'Location % is still referenced by another record and cannot be deleted.', OLD.main_location_id; @@ -24,12 +24,12 @@ $$; -- Trigger definition on organization_information table for location deletion CREATE TRIGGER after_organization_delete_main_location -AFTER DELETE ON "fertiscan_0.0.18".organization +AFTER DELETE ON "fertiscan_0.1.1".organization FOR EACH ROW -EXECUTE FUNCTION "fertiscan_0.0.18".after_org_delete_location_trig(); +EXECUTE FUNCTION "fertiscan_0.1.1".after_org_delete_location_trig(); -- Function to delete an inspection and related data -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".delete_inspection( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".delete_inspection( p_inspection_id uuid, p_inspector_id uuid ) @@ -41,7 +41,7 @@ DECLARE BEGIN IF NOT EXISTS ( SELECT 1 - FROM "fertiscan_0.0.18".inspection + FROM "fertiscan_0.1.1".inspection WHERE id = p_inspection_id AND inspector_id = p_inspector_id ) THEN @@ -50,7 +50,7 @@ BEGIN -- Delete the inspection record and retrieve it WITH deleted_inspection AS ( - DELETE FROM "fertiscan_0.0.18".inspection + DELETE FROM "fertiscan_0.1.1".inspection WHERE id = p_inspection_id RETURNING * ) @@ -64,7 +64,7 @@ END; $$; -- Combined trigger function to handle after inspection delete for sample and label_information deletion -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".after_insp_delete_cleanup_trig() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".after_insp_delete_cleanup_trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ @@ -72,30 +72,30 @@ DECLARE newest_inspection_id uuid; BEGIN IF OLD.sample_id IS NOT NULL THEN - DELETE FROM "fertiscan_0.0.18".sample + DELETE FROM "fertiscan_0.1.1".sample WHERE id = OLD.sample_id; END IF; IF OLD.label_info_id IS NOT NULL THEN - DELETE FROM "fertiscan_0.0.18".label_information + DELETE FROM "fertiscan_0.1.1".label_information WHERE id = OLD.label_info_id; END IF; IF OLD.fertilizer_id IS NOT NULL THEN -- Select the newest inspection for the fertilizer.latest_inspection_id SELECT id INTO newest_inspection_id - from "fertiscan_0.0.18".inspection + from "fertiscan_0.1.1".inspection WHERE fertilizer_id = OLD.fertilizer_id AND verified_date IS NOT NULL AND id != OLD.id ORDER BY verified_date DESC LIMIT 1; if newest_inspection_id IS NOT NULL THEN - UPDATE "fertiscan_0.0.18".fertilizer + UPDATE "fertiscan_0.1.1".fertilizer SET latest_inspection_id = newest_inspection_id WHERE id = OLD.fertilizer_id; ELSE --This was the only inspection for the fertilizer so we delete it - DELETE FROM "fertiscan_0.0.18".fertilizer + DELETE FROM "fertiscan_0.1.1".fertilizer WHERE id = OLD.fertilizer_id; end if; END IF; @@ -105,8 +105,8 @@ END; $$; -- Trigger definition on inspection table for combined cleanup (sample and label_information deletion) -DROP TRIGGER IF EXISTS after_inspection_delete_cleanup ON "fertiscan_0.0.18".inspection; +DROP TRIGGER IF EXISTS after_inspection_delete_cleanup ON "fertiscan_0.1.1".inspection; CREATE TRIGGER after_inspection_delete_cleanup -AFTER DELETE ON "fertiscan_0.0.18".inspection +AFTER DELETE ON "fertiscan_0.1.1".inspection FOR EACH ROW -EXECUTE FUNCTION "fertiscan_0.0.18".after_insp_delete_cleanup_trig(); +EXECUTE FUNCTION "fertiscan_0.1.1".after_insp_delete_cleanup_trig(); diff --git a/fertiscan/db/bytebase/get_inspection/get_guaranteed_analysis.sql b/fertiscan/db/bytebase/get_inspection/get_guaranteed_analysis.sql index e652cfb7..911982b6 100644 --- a/fertiscan/db/bytebase/get_inspection/get_guaranteed_analysis.sql +++ b/fertiscan/db/bytebase/get_inspection/get_guaranteed_analysis.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".get_guaranteed_analysis_json( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".get_guaranteed_analysis_json( label_info_id uuid ) RETURNS jsonb @@ -28,7 +28,7 @@ BEGIN ) FILTER (WHERE guaranteed.language = 'fr'), '[]'::jsonb) ) INTO result_json - FROM "fertiscan_0.0.18".guaranteed + FROM "fertiscan_0.1.1".guaranteed WHERE guaranteed.label_id = label_info_id; -- build Guaranteed_analysis title json @@ -38,7 +38,7 @@ BEGIN 'is_minimal', title_is_minimal ) INTO result_json_title - FROM "fertiscan_0.0.18".label_information + FROM "fertiscan_0.1.1".label_information WHERE id = label_info_id; -- merge JSONs diff --git a/fertiscan/db/bytebase/get_inspection/get_ingredients_json.sql b/fertiscan/db/bytebase/get_inspection/get_ingredients_json.sql index 5df6576d..15750cc9 100644 --- a/fertiscan/db/bytebase/get_inspection/get_ingredients_json.sql +++ b/fertiscan/db/bytebase/get_inspection/get_ingredients_json.sql @@ -1,5 +1,5 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".get_ingredients_json( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".get_ingredients_json( label_info_id uuid) RETURNS jsonb LANGUAGE plpgsql diff --git a/fertiscan/db/bytebase/get_inspection/get_label_info_json.sql b/fertiscan/db/bytebase/get_inspection/get_label_info_json.sql index 011cd1d1..95760404 100644 --- a/fertiscan/db/bytebase/get_inspection/get_label_info_json.sql +++ b/fertiscan/db/bytebase/get_inspection/get_label_info_json.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".get_label_info_json( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".get_label_info_json( label_id uuid) RETURNS jsonb LANGUAGE plpgsql diff --git a/fertiscan/db/bytebase/get_inspection/get_metrics_json.sql b/fertiscan/db/bytebase/get_inspection/get_metrics_json.sql index 93c340ec..dd0caa36 100644 --- a/fertiscan/db/bytebase/get_inspection/get_metrics_json.sql +++ b/fertiscan/db/bytebase/get_inspection/get_metrics_json.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".get_metrics_json( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".get_metrics_json( label_info_id uuid ) RETURNS jsonb diff --git a/fertiscan/db/bytebase/get_inspection/get_micronutrients_json.sql b/fertiscan/db/bytebase/get_inspection/get_micronutrients_json.sql index 420f51d8..1eb7cfe0 100644 --- a/fertiscan/db/bytebase/get_inspection/get_micronutrients_json.sql +++ b/fertiscan/db/bytebase/get_inspection/get_micronutrients_json.sql @@ -1,5 +1,5 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".get_micronutrient_json( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".get_micronutrient_json( label_info_id uuid) RETURNS jsonb LANGUAGE plpgsql diff --git a/fertiscan/db/bytebase/get_inspection/get_organizations_json.sql b/fertiscan/db/bytebase/get_inspection/get_organizations_json.sql index 5ebd1500..afb7619b 100644 --- a/fertiscan/db/bytebase/get_inspection/get_organizations_json.sql +++ b/fertiscan/db/bytebase/get_inspection/get_organizations_json.sql @@ -1,7 +1,7 @@ --Unverified organization data -DROP FUNCTION IF EXISTS "fertiscan_0.0.18".get_organizations_information_json(label_id_value uuid); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".get_organizations_information_json( +DROP FUNCTION IF EXISTS "fertiscan_0.1.1".get_organizations_information_json(label_id_value uuid); +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".get_organizations_information_json( label_id_value uuid) RETURNS jsonb LANGUAGE plpgsql @@ -31,8 +31,8 @@ END; $function$; -- verified organization -DROP FUNCTION IF EXISTS "fertiscan_0.0.18".get_organizations_json(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".get_organizations_json() +DROP FUNCTION IF EXISTS "fertiscan_0.1.1".get_organizations_json(); +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".get_organizations_json() RETURNS jsonb LANGUAGE plpgsql AS $function$ diff --git a/fertiscan/db/bytebase/get_inspection/get_registration_numbers_json.sql b/fertiscan/db/bytebase/get_inspection/get_registration_numbers_json.sql index cf2e372a..c0fdbdfc 100644 --- a/fertiscan/db/bytebase/get_inspection/get_registration_numbers_json.sql +++ b/fertiscan/db/bytebase/get_inspection/get_registration_numbers_json.sql @@ -1,5 +1,5 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".get_registration_numbers_json( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".get_registration_numbers_json( label_info_id uuid) RETURNS jsonb LANGUAGE plpgsql diff --git a/fertiscan/db/bytebase/get_inspection/get_specification_json.sql b/fertiscan/db/bytebase/get_inspection/get_specification_json.sql index 5dc0cce9..135f8072 100644 --- a/fertiscan/db/bytebase/get_inspection/get_specification_json.sql +++ b/fertiscan/db/bytebase/get_inspection/get_specification_json.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".get_specification_json( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".get_specification_json( label_info_id uuid) RETURNS jsonb LANGUAGE plpgsql diff --git a/fertiscan/db/bytebase/get_inspection/get_sub_label.sql b/fertiscan/db/bytebase/get_inspection/get_sub_label.sql index 4393c15f..d1a38fbc 100644 --- a/fertiscan/db/bytebase/get_inspection/get_sub_label.sql +++ b/fertiscan/db/bytebase/get_inspection/get_sub_label.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".get_sub_label_json(label_info_id uuid) +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".get_sub_label_json(label_info_id uuid) RETURNS jsonb LANGUAGE plpgsql AS $function$ diff --git a/fertiscan/db/bytebase/new_inspection/new_guaranteed_analysis.sql b/fertiscan/db/bytebase/new_inspection/new_guaranteed_analysis.sql index 9f865123..34b46c69 100644 --- a/fertiscan/db/bytebase/new_inspection/new_guaranteed_analysis.sql +++ b/fertiscan/db/bytebase/new_inspection/new_guaranteed_analysis.sql @@ -1,9 +1,9 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".new_guaranteed_analysis( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".new_guaranteed_analysis( name TEXT, value FLOAT, unit TEXT, label_id UUID, -language "fertiscan_0.0.18".language, +language "fertiscan_0.1.1".language, edited BOOLEAN = FALSE, element_id int = NULL ) diff --git a/fertiscan/db/bytebase/new_inspection/new_ingredient.sql b/fertiscan/db/bytebase/new_inspection/new_ingredient.sql index 07438b89..ce3e9131 100644 --- a/fertiscan/db/bytebase/new_inspection/new_ingredient.sql +++ b/fertiscan/db/bytebase/new_inspection/new_ingredient.sql @@ -1,9 +1,9 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".new_ingredient( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".new_ingredient( name TEXT, value FLOAt, read_unit TEXT, label_id UUID, -language "fertiscan_0.0.18".language, +language "fertiscan_0.1.1".language, organic BOOLEAN, active BOOLEAN, edited BOOLEAN = FALSE diff --git a/fertiscan/db/bytebase/new_inspection/new_label_information.sql b/fertiscan/db/bytebase/new_inspection/new_label_information.sql index 004bfb02..d8db357b 100644 --- a/fertiscan/db/bytebase/new_inspection/new_label_information.sql +++ b/fertiscan/db/bytebase/new_inspection/new_label_information.sql @@ -1,5 +1,5 @@ -drOp FUNCTION IF EXISTS "fertiscan_0.0.18".new_label_information(TEXT, TEXT, TEXT, FLOAT, FLOAT, FLOAT, TEXT, TEXT, BOOLEAN, UUID, UUID); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".new_label_information( +drOp FUNCTION IF EXISTS "fertiscan_0.1.1".new_label_information(TEXT, TEXT, TEXT, FLOAT, FLOAT, FLOAT, TEXT, TEXT, BOOLEAN, UUID, UUID); +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".new_label_information( name TEXT, lot_number TEXT, npk TEXT, @@ -18,7 +18,7 @@ DECLARE label_id uuid; record RECORD; BEGIN - SET SEARCH_PATH TO "fertiscan_0.0.18"; + SET SEARCH_PATH TO "fertiscan_0.1.1"; -- LABEL INFORMATION INSERT INTO label_information ( product_name,lot_number, npk, n, p, k, guaranteed_title_en, guaranteed_title_fr, title_is_minimal, record_keeping diff --git a/fertiscan/db/bytebase/new_inspection/new_metric_unit.sql b/fertiscan/db/bytebase/new_inspection/new_metric_unit.sql index 0adc4bc4..ab8120f8 100644 --- a/fertiscan/db/bytebase/new_inspection/new_metric_unit.sql +++ b/fertiscan/db/bytebase/new_inspection/new_metric_unit.sql @@ -1,8 +1,8 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".new_metric_unit( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".new_metric_unit( value FLOAT, read_unit TEXT, label_id UUID, - metric_type "fertiscan_0.0.18".metric_type, + metric_type "fertiscan_0.1.1".metric_type, edited BOOLEAN = FALSE ) RETURNS UUID diff --git a/fertiscan/db/bytebase/new_inspection/new_micronutrient.sql b/fertiscan/db/bytebase/new_inspection/new_micronutrient.sql index 59c7a9f5..19c7fbd2 100644 --- a/fertiscan/db/bytebase/new_inspection/new_micronutrient.sql +++ b/fertiscan/db/bytebase/new_inspection/new_micronutrient.sql @@ -1,9 +1,9 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".new_micronutrient( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".new_micronutrient( name TEXT, value FLOAT, unit TEXT, label_id UUID, -language "fertiscan_0.0.18".language, +language "fertiscan_0.1.1".language, edited BOOLEAN = FALSE, element_id int = NULL ) diff --git a/fertiscan/db/bytebase/new_inspection/new_organization_information.sql b/fertiscan/db/bytebase/new_inspection/new_organization_information.sql index f1647203..a3cece3f 100644 --- a/fertiscan/db/bytebase/new_inspection/new_organization_information.sql +++ b/fertiscan/db/bytebase/new_inspection/new_organization_information.sql @@ -1,6 +1,6 @@ -DROP FUNCTION IF EXISTS "fertiscan_0.0.18".new_organization_located(TEXT, TEXT, TEXT, TEXT, BOOLEAN, UUID, BOOLEAN); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".new_organization_information( +DROP FUNCTION IF EXISTS "fertiscan_0.1.1".new_organization_located(TEXT, TEXT, TEXT, TEXT, BOOLEAN, UUID, BOOLEAN); +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".new_organization_information( name TEXT, address_str TEXT, website TEXT, diff --git a/fertiscan/db/bytebase/new_inspection/new_organization_located.sql b/fertiscan/db/bytebase/new_inspection/new_organization_located.sql index 0d92aae2..e40da031 100644 --- a/fertiscan/db/bytebase/new_inspection/new_organization_located.sql +++ b/fertiscan/db/bytebase/new_inspection/new_organization_located.sql @@ -1,6 +1,6 @@ -DROP FUNCTION IF EXISTS "fertiscan_0.0.18".new_organization_located(TEXT, TEXT, TEXT, TEXT, BOOLEAN, UUID, BOOLEAN); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".new_organization_info_located( +DROP FUNCTION IF EXISTS "fertiscan_0.1.1".new_organization_located(TEXT, TEXT, TEXT, TEXT, BOOLEAN, UUID, BOOLEAN); +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".new_organization_info_located( name TEXT, address_str TEXT, website TEXT, diff --git a/fertiscan/db/bytebase/new_inspection/new_registration_number.sql b/fertiscan/db/bytebase/new_inspection/new_registration_number.sql index e0e2eaab..191ceee8 100644 --- a/fertiscan/db/bytebase/new_inspection/new_registration_number.sql +++ b/fertiscan/db/bytebase/new_inspection/new_registration_number.sql @@ -1,6 +1,6 @@ -DROP FUNCTION IF EXISTS "fertiscan_0.0.18".new_registration_number(TEXT, UUID,BOOLEAN, TEXT, BOOLEAN); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".new_registration_number( +DROP FUNCTION IF EXISTS "fertiscan_0.1.1".new_registration_number(TEXT, UUID,BOOLEAN, TEXT, BOOLEAN); +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".new_registration_number( identifier TEXT, label_info_id UUID, is_an_ingredient_val BOOLEAN DEFAULT NULL, diff --git a/fertiscan/db/bytebase/new_inspection/new_specification.sql b/fertiscan/db/bytebase/new_inspection/new_specification.sql index de586aea..c91bbd5c 100644 --- a/fertiscan/db/bytebase/new_inspection/new_specification.sql +++ b/fertiscan/db/bytebase/new_inspection/new_specification.sql @@ -1,8 +1,8 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".new_specification( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".new_specification( humidity FLOAT, ph FLOAT, solubility FLOAT, -language "fertiscan_0.0.18".language, +language "fertiscan_0.1.1".language, label_id UUID, edited BOOLEAN = FALSE ) diff --git a/fertiscan/db/bytebase/new_inspection/new_sub_label.sql b/fertiscan/db/bytebase/new_inspection/new_sub_label.sql index 9f4cb8c1..364fcea8 100644 --- a/fertiscan/db/bytebase/new_inspection/new_sub_label.sql +++ b/fertiscan/db/bytebase/new_inspection/new_sub_label.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".new_sub_label( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".new_sub_label( content_fr text DEFAULT NULL::text, content_en text DEFAULT NULL::text, label_id uuid DEFAULT NULL::uuid, diff --git a/fertiscan/db/bytebase/new_inspection_function.sql b/fertiscan/db/bytebase/new_inspection_function.sql index 8eba9fd3..1b54d6ae 100644 --- a/fertiscan/db/bytebase/new_inspection_function.sql +++ b/fertiscan/db/bytebase/new_inspection_function.sql @@ -1,5 +1,5 @@ -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".new_inspection(user_id uuid, picture_set_id uuid, input_json jsonb) +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".new_inspection(user_id uuid, picture_set_id uuid, input_json jsonb) RETURNS jsonb LANGUAGE plpgsql AS $function$ @@ -56,7 +56,7 @@ BEGIN -- phone_number_string, -- '') <> '' -- THEN --- company_id := "fertiscan_0.0.18".new_organization_info_located( +-- company_id := "fertiscan_0.1.1".new_organization_info_located( -- input_json->'company'->>'name', -- input_json->'company'->>'address', -- input_json->'company'->>'website', @@ -81,7 +81,7 @@ BEGIN -- phone_number_string, -- '') <> '' -- THEN --- manufacturer_id := "fertiscan_0.0.18".new_organization_info_located( +-- manufacturer_id := "fertiscan_0.1.1".new_organization_info_located( -- input_json->'manufacturer'->>'name', -- input_json->'manufacturer'->>'address', -- input_json->'manufacturer'->>'website', @@ -94,7 +94,7 @@ BEGIN -- Manufacturer end -- LABEL INFORMATION - label_info_id := "fertiscan_0.0.18".new_label_information( + label_info_id := "fertiscan_0.1.1".new_label_information( input_json->'product'->>'name', input_json->'product'->>'lot_number', input_json->'product'->>'npk', @@ -124,11 +124,11 @@ BEGIN '') <> '' THEN -- Insert the new weight - weight_id = "fertiscan_0.0.18".new_metric_unit( + weight_id = "fertiscan_0.1.1".new_metric_unit( read_value::float, record->>'unit', label_info_id, - 'weight'::"fertiscan_0.0.18".metric_type, + 'weight'::"fertiscan_0.1.1".metric_type, FALSE ); END IF; @@ -145,11 +145,11 @@ BEGIN read_unit, '') <> '' THEN - density_id := "fertiscan_0.0.18".new_metric_unit( + density_id := "fertiscan_0.1.1".new_metric_unit( read_value::float, read_unit, label_info_id, - 'density'::"fertiscan_0.0.18".metric_type, + 'density'::"fertiscan_0.1.1".metric_type, FALSE ); END IF; @@ -168,11 +168,11 @@ BEGIN '') <> '' THEN -- Insert the new volume - volume_id := "fertiscan_0.0.18".new_metric_unit( + volume_id := "fertiscan_0.1.1".new_metric_unit( value_float, read_unit, label_info_id, - 'volume'::"fertiscan_0.0.18".metric_type, + 'volume'::"fertiscan_0.1.1".metric_type, FALSE ); END IF; @@ -191,11 +191,11 @@ BEGIN -- '') <> '' -- THEN -- -- Insert the new specification --- specification_id := "fertiscan_0.0.18".new_specification( +-- specification_id := "fertiscan_0.1.1".new_specification( -- (record->>'humidity')::float, -- (record->>'ph')::float, -- (record->>'solubility')::float, --- ingredient_language::"fertiscan_0.0.18".language, +-- ingredient_language::"fertiscan_0.1.1".language, -- label_info_id, -- FALSE -- ); @@ -221,12 +221,12 @@ BEGIN '') <> '' THEN -- Insert the new ingredient - ingredient_id := "fertiscan_0.0.18".new_ingredient( + ingredient_id := "fertiscan_0.1.1".new_ingredient( record->>'name', read_value::float, read_unit, label_info_id, - ingredient_language::"fertiscan_0.0.18".language, + ingredient_language::"fertiscan_0.1.1".language, NULL, --We cant tell atm NULL, --We cant tell atm FALSE --preset @@ -265,7 +265,7 @@ BEGIN en_value := en_values->>i; -- Insert sub-label without deleting existing data - sub_label_id := "fertiscan_0.0.18".new_sub_label( + sub_label_id := "fertiscan_0.1.1".new_sub_label( fr_value, en_value, label_info_id, @@ -289,12 +289,12 @@ BEGIN -- '') <> '' -- THEN -- -- Insert the new Micronutrient --- micronutrient_id := "fertiscan_0.0.18".new_micronutrient( +-- micronutrient_id := "fertiscan_0.1.1".new_micronutrient( -- record->> 'name', -- (record->> 'value')::float, -- record->> 'unit', -- label_info_id, --- micronutrient_language::"fertiscan_0.0.18".language +-- micronutrient_language::"fertiscan_0.1.1".language -- ); -- END IF; -- END LOOP; @@ -304,7 +304,7 @@ BEGIN -- GUARANTEED -- Loop through each language ('en' and 'fr') - FOR guaranteed_analysis_language IN SELECT unnest(enum_range(NULL::"fertiscan_0.0.18".LANGUAGE)) + FOR guaranteed_analysis_language IN SELECT unnest(enum_range(NULL::"fertiscan_0.1.1".LANGUAGE)) LOOP FOR record IN SELECT * FROM jsonb_array_elements(input_json->'guaranteed_analysis'->guaranteed_analysis_language) LOOP @@ -315,12 +315,12 @@ BEGIN '') <> '' THEN -- Insert the new guaranteed_analysis - guaranteed_analysis_id := "fertiscan_0.0.18".new_guaranteed_analysis( + guaranteed_analysis_id := "fertiscan_0.1.1".new_guaranteed_analysis( record->>'name', (record->>'value')::float, record->>'unit', label_info_id, - guaranteed_analysis_language::"fertiscan_0.0.18".language, + guaranteed_analysis_language::"fertiscan_0.1.1".language, FALSE, NULL -- We arent handeling element_id yet ); @@ -339,7 +339,7 @@ BEGIN '') <> '' THEN -- Insert the new registration number - registration_number_id := "fertiscan_0.0.18".new_registration_number( + registration_number_id := "fertiscan_0.1.1".new_registration_number( record->>'registration_number', label_info_id, (record->>'is_an_ingredient')::BOOLEAN, @@ -364,7 +364,7 @@ BEGIN '') <> '' THEN -- Insert the new organization info - organization_id := "fertiscan_0.0.18".new_organization_information( + organization_id := "fertiscan_0.1.1".new_organization_information( record->>'name', record->>'address', record->>'website', @@ -389,24 +389,25 @@ BEGIN -- ORGANIZATIONS INFO END -- INSPECTION - INSERT INTO "fertiscan_0.0.18".inspection ( - inspector_id, label_info_id, sample_id, picture_set_id, inspection_comment + INSERT INTO "fertiscan_0.1.1".inspection ( + inspector_id, label_info_id, sample_id, picture_set_id, inspection_comment, container_id ) VALUES ( user_id, -- Assuming inspector_id is handled separately label_info_id, NULL, -- NOT handled yet picture_set_id, -- Assuming picture_set_id is handled separately - NULL + NULL, + (input_json ->> 'container_id')::UUID ) RETURNING id INTO inspection_id_value; -- Update input_json with inspection data input_json := jsonb_set(input_json, '{inspection_id}', to_jsonb(inspection_id_value)); - input_json := jsonb_set(input_json, '{inspection_comment}', to_jsonb(''::text)); + -- input_json := jsonb_set(input_json, '{inspection_comment}', to_jsonb(''::text)); -- TODO: remove olap transactions from Operational transactions -- Update the Inspection_factual entry with the json - UPDATE "fertiscan_0.0.18".inspection_factual + UPDATE "fertiscan_0.1.1".inspection_factual SET original_dataset = input_json WHERE inspection_factual."inspection_id" = inspection_id_value; diff --git a/fertiscan/db/bytebase/schema_0.1.1.sql b/fertiscan/db/bytebase/schema_0.1.1.sql new file mode 100644 index 00000000..3241b7fd --- /dev/null +++ b/fertiscan/db/bytebase/schema_0.1.1.sql @@ -0,0 +1,437 @@ + + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + + CREATE TABLE "fertiscan_0.1.1".roles ( + "id" int PRIMARY KEY, + "name" text NOT NULL + ); + + CREATE TABLE "fertiscan_0.1.1".permission ( + "id" int PRIMARY KEY, + "name" text NOT NULL + ); + + INSERT INTO "fertiscan_0.1.1".roles (id, name) VALUES + (1, 'dev'), + (2, 'admin'), + (3, 'team leader'), + (4, 'inspector'); + + INSERT INTO "fertiscan_0.1.1".permission (id, name) VALUES + (1, 'read'), + (2, 'write'), + (3, 'owner'); + + + CREATE TABLE "fertiscan_0.1.1"."users" ( + "id" uuid PRIMARY KEY DEFAULT uuid_.uuid_generate_v4(), + "email" text NOT NULL UNIQUE, + "registration_date" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamp, + "role_id" INT NOT NULL DEFAULT 4 REFERENCES "fertiscan_0.1.1".roles(id) + ); + + Create table "fertiscan_0.1.1"."groups" ( + "id" uuid NOT NULL DEFAULT uuid_.uuid_generate_v4() PRIMARY KEY, + "name" text NOT NULL, + "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id) + ); + + Create table "fertiscan_0.1.1"."user_group" ( + "id" uuid NOT NULL DEFAULT uuid_.uuid_generate_v4() PRIMARY KEY, + "user_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id) ON DELETE CASCADE, + "group_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".groups(id) ON DELETE CASCADE, + "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "assigned_by_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id), + "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "permission_id" INT NOT NULL DEFAULT 1 REFERENCES "fertiscan_0.1.1".permission(id), + UNIQUE ("user_id", "group_id") + ); + + CREATE TABLE "fertiscan_0.1.1"."container" ( + "id" uuid NOT NULL DEFAULT uuid_.uuid_generate_v4() PRIMARY KEY, + "name" text, + "is_public" boolean NOT NULL DEFAULT false, + "storage_prefix" text default 'user', + "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id) ON DELETE SET NULL, + "last_updated_by_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id) + ); + + CREATE TABLE "fertiscan_0.1.1"."container_user" ( + "id" uuid NOT NULL DEFAULT uuid_.uuid_generate_v4() PRIMARY KEY, + "user_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id) ON DELETE cascade, + "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id) ON DELETE SET NULL, + "last_updated_by_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id) ON DELETE SET NULL, + "container_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".container(id) ON DELETE CASCADE, + "permission_id" INT NOT NULL DEFAULT 1 REFERENCES "fertiscan_0.1.1".permission(id), + UNIQUE ("user_id", "container_id") + ); + + CREATE TABLE "fertiscan_0.1.1"."container_group" ( + "id" uuid NOT NULL DEFAULT uuid_.uuid_generate_v4() PRIMARY KEY, + "group_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".groups(id) ON DELETE cascade, + "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id) ON DELETE SET NULL, + "last_updated_by_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id) ON DELETE SET NULL, + "container_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".container(id) ON DELETE CASCADE, + "permission_id" INT NOT NULL DEFAULT 1 REFERENCES "fertiscan_0.1.1".permission(id), + UNIQUE ("group_id", "container_id") + ); + + CREATE TABLE "fertiscan_0.1.1"."picture_set" ( + "id" uuid NOT NULL DEFAULT uuid_.uuid_generate_v4() PRIMARY KEY, + "picture_set" json NOT NULL, + "owner_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id), + "upload_date" date NOT NULL DEFAULT current_timestamp, + "name" text NOT NULL, + "container_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".container(id) ON DELETE CASCADE, + "parent_id" uuid REFERENCES "fertiscan_0.1.1".picture_set(id) ON DELETE CASCADE + ); + + CREATE TABLE "fertiscan_0.1.1"."picture" ( + "id" uuid NOT NULL DEFAULT uuid_.uuid_generate_v4() PRIMARY KEY, + "picture" json NOT NULL, + "picture_set_id" uuid NOT null, + "nb_obj" integer NOT NULL, + "verified" boolean NOT NULL DEFAULT false, + "upload_date" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ("picture_set_id") REFERENCES "fertiscan_0.1.1"."picture_set"(id) ON DELETE CASCADE + ); + + -- CREATE A TYPE FOR FRENCH/ENGLISH LANGUAGE + CREATE TYPE "fertiscan_0.1.1".LANGUAGE AS ENUM ('fr', 'en'); + + CREATE TABLE "fertiscan_0.1.1"."province" ( + "id" SERIAL PRIMARY KEY, + "name" text UNIQUE NOT NULL + ); + + CREATE TABLE "fertiscan_0.1.1"."region" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "province_id" int REFERENCES "fertiscan_0.1.1".province(id), + "name" text NOT NULL + ); + + CREATE TABLE "fertiscan_0.1.1"."unit" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "unit" text NOT NULL, + "to_si_unit" float + ); + + CREATE TABLE "fertiscan_0.1.1"."element_compound" ( + "id" SERIAL PRIMARY KEY, + "number" int NOT NULL, + "name_fr" text NOT NULL, + "name_en" text NOT NULL, + "symbol" text NOT NULL UNIQUE + ); + + CREATE TABLE "fertiscan_0.1.1"."label_information" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "product_name" text, + "lot_number" text, + "npk" text, + "n" float, + "p" float, + "k" float, + "guaranteed_title_en" text, + "guaranteed_title_fr" text, + "title_is_minimal" boolean, + "record_keeping" boolean + ); + + CREATE TABLE "fertiscan_0.1.1"."label_dimension" ( + "label_id" uuid PRIMARY KEY, + "organization_info_ids" uuid[] DEFAULT '{}', + "instructions_ids" uuid[] DEFAULT '{}', + "cautions_ids" uuid[] DEFAULT '{}', + "first_aid_ids" uuid[] DEFAULT '{}', + "warranties_ids" uuid[] DEFAULT '{}', + "specification_ids" uuid[] DEFAULT '{}', + "ingredient_ids" uuid[] DEFAULT '{}', + "micronutrient_ids" uuid[] DEFAULT '{}', + "guaranteed_ids" uuid[] DEFAULT '{}', + "registration_number_ids" uuid[] DEFAULT '{}', + "weight_ids" uuid[] DEFAULT '{}', + "volume_ids" uuid[] DEFAULT '{}', + "density_ids" uuid[] DEFAULT '{}' + ); + + CREATE TABLE "fertiscan_0.1.1"."time_dimension" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "date_value" date, + "year" int, + "month" int, + "day" int, + "month_name" text, + "day_name" text + ); + + CREATE TABLE "fertiscan_0.1.1"."inspection_factual" ( + "inspection_id" uuid PRIMARY KEY, + "inspector_id" uuid , + "label_info_id" uuid , + "time_id" uuid REFERENCES "fertiscan_0.1.1".time_dimension(id), + "sample_id" uuid, + "picture_set_id" uuid, + "inspection_date" timestamp DEFAULT CURRENT_TIMESTAMP, + "original_dataset" json + ); + + CREATE TABLE "fertiscan_0.1.1"."organization_information" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "name" text, + "website" text, + "phone_number" text, + "address" text, + "edited" boolean DEFAULT false, + "label_id" uuid REFERENCES "fertiscan_0.1.1".label_information(id) ON DELETE CASCADE, + "is_main_contact" boolean DEFAULT false NOT NULL, + CONSTRAINT check_not_all_null CHECK ( + (name IS NOT NULL)::integer + + (website IS NOT NULL)::integer + + (phone_number IS NOT NULL)::integer + + (address IS NOT NULL)::integer >= 1) + ); + + + CREATE TABLE "fertiscan_0.1.1"."location" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "name" text, + "address" text NOT NULL, + "address_number" text, + "city" text, + "postal_code" text, + "region_id" uuid REFERENCES "fertiscan_0.1.1".region(id) + ); + + CREATE TABLE "fertiscan_0.1.1"."organization" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "name" text UNIQUE NOT NULL, + "website" text, + "phone_number" text, + "address" text, + "main_location_id" uuid REFERENCES "fertiscan_0.1.1".location(id), + "upload_date" timestamp DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamp DEFAULT CURRENT_TIMESTAMP + ); + + Alter table "fertiscan_0.1.1".location ADD "organization_id" uuid REFERENCES "fertiscan_0.1.1".organization(id); + + CREATE TABLE "fertiscan_0.1.1"."sample" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "number" uuid, + "collection_date" date, + "location" uuid REFERENCES "fertiscan_0.1.1".location(id) + ); + + CREATE TYPE "fertiscan_0.1.1".metric_type as ENUM ('volume', 'weight','density'); + + CREATE TABLE "fertiscan_0.1.1"."metric" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "value" float, + "edited" boolean, + "unit_id" uuid REFERENCES "fertiscan_0.1.1".unit(id), + "metric_type" "fertiscan_0.1.1".metric_type, + "label_id" uuid REFERENCES "fertiscan_0.1.1".label_information(id) ON DELETE CASCADE, + CONSTRAINT check_not_all_null CHECK ( + (value IS NOT NULL)::integer + + (unit_id IS NOT NULL)::integer >= 1) + ); + + CREATE TABLE "fertiscan_0.1.1"."sub_type" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "type_fr" text Unique NOT NULL, + "type_en" text unique NOT NULL + ); + + CREATE TABLE "fertiscan_0.1.1"."registration_number_information" ( + "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + "identifier" text NOT NULL, + "name" text, + "is_an_ingredient" BOOLEAN, + "label_id" uuid REFERENCES "fertiscan_0.1.1".label_information(id) ON DELETE CASCADE, + "edited" BOOLEAN DEFAULT FALSE + ); + + CREATE TABLE "fertiscan_0.1.1"."specification" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "humidity" float, + "ph" float, + "solubility" float, + "edited" boolean, + "label_id" uuid REFERENCES "fertiscan_0.1.1".label_information(id) ON DELETE CASCADE, + "language" "fertiscan_0.1.1".LANGUAGE + ); + + CREATE TABLE "fertiscan_0.1.1"."sub_label" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "text_content_fr" text NOT NULL DEFAULT '', + "text_content_en" text NOT NULL DEFAULT '', + "label_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1"."label_information" ("id") ON DELETE CASCADE, + "edited" boolean, --this is because with the current upsert we can not determine if it was edited or not + "sub_type_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1"."sub_type" ("id"), + CONSTRAINT check_not_all_null CHECK ( + (COALESCE(sub_label.text_content_en, '') <> '') OR + (COALESCE(sub_label.text_content_fr, '') <> '') + ) + ); + + CREATE TABLE "fertiscan_0.1.1"."micronutrient" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "read_name" text, + "value" float, + "unit" text , + "element_id" int REFERENCES "fertiscan_0.1.1".element_compound(id), + "label_id" uuid REFERENCES "fertiscan_0.1.1".label_information(id) ON DELETE CASCADE, + "edited" boolean, + "language" "fertiscan_0.1.1".LANGUAGE + ); + + CREATE TABLE "fertiscan_0.1.1"."guaranteed" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "read_name" text, + "value" float , + "unit" text , + "language" "fertiscan_0.1.1".LANGUAGE, + "element_id" int REFERENCES "fertiscan_0.1.1".element_compound(id), + "label_id" uuid REFERENCES "fertiscan_0.1.1".label_information(id) ON DELETE CASCADE, + "edited" boolean, + CONSTRAINT check_not_all_null CHECK ( + (read_name IS NOT NULL)::integer + + (value IS NOT NULL)::integer + + (unit IS NOT NULL)::integer >= 1) + ); + + CREATE TABLE "fertiscan_0.1.1"."ingredient" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "organic" boolean, + "active" boolean, + "name" text, + "value" float, + "unit" text, + "edited" boolean, + "label_id" uuid REFERENCES "fertiscan_0.1.1".label_information(id) ON DELETE CASCADE, + "language" "fertiscan_0.1.1".LANGUAGE + ); + + CREATE TABLE "fertiscan_0.1.1"."inspection" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "verified" boolean DEFAULT false, + "upload_date" timestamp DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamp DEFAULT CURRENT_TIMESTAMP, + "inspector_id" uuid NOT NULL REFERENCES "fertiscan_0.1.1".users(id), + "label_info_id" uuid REFERENCES "fertiscan_0.1.1".label_information(id) ON DELETE CASCADE, + "sample_id" uuid REFERENCES "fertiscan_0.1.1".sample(id), + "container_id" uuid REFERENCES "fertiscan_0.1.1".container(id), + "picture_set_id" uuid REFERENCES "fertiscan_0.1.1".picture_set(id), + "inspection_comment" text, + "verified_date" timestamp default null + ); + + CREATE TABLE "fertiscan_0.1.1"."fertilizer" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "name" text UNIQUE NOT NULL, + "registration_number" text, + "upload_date" timestamp DEFAULT CURRENT_TIMESTAMP, + "update_at" timestamp DEFAULT CURRENT_TIMESTAMP, + "latest_inspection_id" uuid REFERENCES "fertiscan_0.1.1".inspection(id) ON DELETE SET NULL, + "main_contact_id" uuid REFERENCES "fertiscan_0.1.1".organization(id) ON DELETE SET NULL + );-- It should actually try to seek if there are any other organization that can be found under the latest_inspection organisation_information instead of setting it to null + + Alter table "fertiscan_0.1.1".inspection ADD "fertilizer_id" uuid REFERENCES "fertiscan_0.1.1".fertilizer(id); + + -- Trigger function for the `user` table + CREATE OR REPLACE FUNCTION update_user_timestamp() + RETURNS TRIGGER AS $$ + BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + -- Trigger for the `user` table + CREATE TRIGGER user_update_before + BEFORE UPDATE ON "fertiscan_0.1.1".users + FOR EACH ROW + EXECUTE FUNCTION update_user_timestamp(); + + -- Trigger function for the `analysis` table + CREATE OR REPLACE FUNCTION update_analysis_timestamp() + RETURNS TRIGGER AS $$ + BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + -- Trigger for the `analysis` table + CREATE TRIGGER analysis_update_before + BEFORE UPDATE ON "fertiscan_0.1.1".inspection + FOR EACH ROW + EXECUTE FUNCTION update_analysis_timestamp(); + + -- Trigger function for the `fertilizer` table + CREATE OR REPLACE FUNCTION update_fertilizer_timestamp() + RETURNS TRIGGER AS $$ + BEGIN + NEW.update_at = CURRENT_TIMESTAMP; + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + -- Trigger for the `fertilizer` table + CREATE TRIGGER fertilizer_update_before + BEFORE UPDATE ON "fertiscan_0.1.1".fertilizer + FOR EACH ROW + EXECUTE FUNCTION update_fertilizer_timestamp(); + + -- Trigger function for the `organization` table + CREATE OR REPLACE FUNCTION update_organization_timestamp() + RETURNS TRIGGER AS $$ + BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + -- Trigger for the `organization` table + CREATE TRIGGER organization_update_before + BEFORE UPDATE ON "fertiscan_0.1.1".organization + FOR EACH ROW + EXECUTE FUNCTION update_organization_timestamp(); + + -- Trigger function for the `inspection` table + CREATE OR REPLACE FUNCTION update_inspection_original_dataset_protection() + RETURNS TRIGGER AS $$ + BEGIN + IF (TG_OP = 'UPDATE') AND (OLD.original_dataset IS NULL) THEN + RETURN NEW; + ELSIF (TG_OP = 'UPDATE') AND (OLD.original_dataset IS NOT NULL) THEN + -- Protect the original dataset from being updated + NEW.original_dataset = OLD.original_dataset; + RETURN NEW; + END IF; + END; + $$ LANGUAGE plpgsql; + + -- Trigger for the `inspection` table + CREATE TRIGGER inspection_update_protect_original_dataset + BEFORE UPDATE ON "fertiscan_0.1.1".inspection_factual + FOR EACH ROW + EXECUTE FUNCTION update_inspection_original_dataset_protection(); + + -- Insert the default types : [instruction, caution,first_aid, warranty] + INSERT INTO "fertiscan_0.1.1".sub_type(type_fr,type_en) VALUES + ('instructions','instructions'), + ('mises_en_garde','cautions'); + -- ('premier_soin','first_aid'), -- We are not using this anymore + -- ('garanties','warranties'); -- we are not using this anymore diff --git a/fertiscan/db/bytebase/update_inspection/update_registration_number.sql b/fertiscan/db/bytebase/update_inspection/update_registration_number.sql index ff184794..2ef9ffc3 100644 --- a/fertiscan/db/bytebase/update_inspection/update_registration_number.sql +++ b/fertiscan/db/bytebase/update_inspection/update_registration_number.sql @@ -1,7 +1,7 @@ -drop FUNCTION IF EXISTS "fertiscan_0.0.18".update_registration_number; +drop FUNCTION IF EXISTS "fertiscan_0.1.1".update_registration_number; -- Function to update guaranteed analysis: delete old and insert new -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".update_registration_number( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".update_registration_number( p_label_id uuid, new_registration_numbers jsonb ) diff --git a/fertiscan/db/bytebase/update_inspection/update_sub_label.sql b/fertiscan/db/bytebase/update_inspection/update_sub_label.sql index 67606a0e..57b6db7a 100644 --- a/fertiscan/db/bytebase/update_inspection/update_sub_label.sql +++ b/fertiscan/db/bytebase/update_inspection/update_sub_label.sql @@ -1,7 +1,7 @@ -- Function to update sub labels: delete old and insert new -Drop FUNCTION IF EXISTS "fertiscan_0.0.18".update_sub_labels(uuid, jsonb); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".update_sub_labels( +Drop FUNCTION IF EXISTS "fertiscan_0.1.1".update_sub_labels(uuid, jsonb); +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".update_sub_labels( p_label_id uuid, new_sub_labels jsonb ) diff --git a/fertiscan/db/bytebase/update_inspection/upsert_organization.sql b/fertiscan/db/bytebase/update_inspection/upsert_organization.sql index 5c737fbc..884abf88 100644 --- a/fertiscan/db/bytebase/update_inspection/upsert_organization.sql +++ b/fertiscan/db/bytebase/update_inspection/upsert_organization.sql @@ -1,6 +1,6 @@ -DROP FUNCTION IF EXISTS "fertiscan_0.0.18".upsert_organization(); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".upsert_organization(org_info_id uuid) +DROP FUNCTION IF EXISTS "fertiscan_0.1.1".upsert_organization(); +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".upsert_organization(org_info_id uuid) RETURNS uuid LANGUAGE plpgsql AS $function$ diff --git a/fertiscan/db/bytebase/update_inspection/upsert_organization_info.sql b/fertiscan/db/bytebase/update_inspection/upsert_organization_info.sql index fb941dea..abcb68d6 100644 --- a/fertiscan/db/bytebase/update_inspection/upsert_organization_info.sql +++ b/fertiscan/db/bytebase/update_inspection/upsert_organization_info.sql @@ -1,6 +1,6 @@ -- Function to upsert organization information -DROP FUNCTION IF EXISTS "fertiscan_0.0.18".upsert_organization_info(jsonb, uuid); -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".upsert_organization_info(input_org_info jsonb, label_info_id uuid) +DROP FUNCTION IF EXISTS "fertiscan_0.1.1".upsert_organization_info(jsonb, uuid); +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".upsert_organization_info(input_org_info jsonb, label_info_id uuid) RETURNS jsonb AS $$ DECLARE record jsonb; diff --git a/fertiscan/db/bytebase/update_inspection_function.sql b/fertiscan/db/bytebase/update_inspection_function.sql index 786eaad4..cbaf134c 100644 --- a/fertiscan/db/bytebase/update_inspection_function.sql +++ b/fertiscan/db/bytebase/update_inspection_function.sql @@ -1,7 +1,7 @@ -SET search_path TO "fertiscan_0.0.18"; +SET search_path TO "fertiscan_0.1.1"; -- Function to upsert location information based on location_id and address -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".upsert_location(location_id uuid, address text) +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".upsert_location(location_id uuid, address text) RETURNS uuid AS $$ DECLARE new_location_id uuid; @@ -23,7 +23,7 @@ $$ LANGUAGE plpgsql; -- Function to update metrics: delete old and insert new -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".update_metrics( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".update_metrics( p_label_id uuid, metrics jsonb ) @@ -98,7 +98,7 @@ $$ LANGUAGE plpgsql; -- Function to update specifications: delete old and insert new -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".update_specifications( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".update_specifications( p_label_id uuid, new_specifications jsonb ) @@ -144,7 +144,7 @@ $$ LANGUAGE plpgsql; -- Function to update ingredients: delete old and insert new -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".update_ingredients( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".update_ingredients( p_label_id uuid, new_ingredients jsonb ) @@ -192,7 +192,7 @@ $$ LANGUAGE plpgsql; -- Function to update micronutrients: delete old and insert new -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".update_micronutrients( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".update_micronutrients( p_label_id uuid, new_micronutrients jsonb ) @@ -236,9 +236,9 @@ BEGIN END; $$ LANGUAGE plpgsql; -drop FUNCTION IF EXISTS "fertiscan_0.0.18".update_guaranteed; +drop FUNCTION IF EXISTS "fertiscan_0.1.1".update_guaranteed; -- Function to update guaranteed analysis: delete old and insert new -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".update_guaranteed( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".update_guaranteed( p_label_id uuid, new_guaranteed jsonb ) @@ -252,7 +252,7 @@ BEGIN DELETE FROM guaranteed WHERE label_id = p_label_id; -- Loop through each language ('en' and 'fr') - FOR guaranteed_analysis_language IN SELECT unnest(enum_range(NULL::"fertiscan_0.0.18".LANGUAGE)) + FOR guaranteed_analysis_language IN SELECT unnest(enum_range(NULL::"fertiscan_0.1.1".LANGUAGE)) LOOP FOR guaranteed_record IN SELECT * FROM jsonb_array_elements(new_guaranteed->guaranteed_analysis_language) LOOP @@ -285,7 +285,7 @@ $$ LANGUAGE plpgsql; -- Function to check if both text_content_fr and text_content_en are NULL or empty, and skip insertion if true -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".check_null_or_empty_sub_label() +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".check_null_or_empty_sub_label() RETURNS TRIGGER AS $$ BEGIN -- Check if both text_content_fr and text_content_en are NULL or empty @@ -312,15 +312,15 @@ $$ LANGUAGE plpgsql; -- Trigger to call check_null_or_empty_sub_label() before inserting into sub_label -DROP TRIGGER IF EXISTS before_insert_sub_label ON "fertiscan_0.0.18".sub_label; +DROP TRIGGER IF EXISTS before_insert_sub_label ON "fertiscan_0.1.1".sub_label; CREATE TRIGGER before_insert_sub_label -BEFORE INSERT ON "fertiscan_0.0.18".sub_label +BEFORE INSERT ON "fertiscan_0.1.1".sub_label FOR EACH ROW -EXECUTE FUNCTION "fertiscan_0.0.18".check_null_or_empty_sub_label(); +EXECUTE FUNCTION "fertiscan_0.1.1".check_null_or_empty_sub_label(); -Drop FUNCTION IF EXISTS "fertiscan_0.0.18".upsert_inspection; +Drop FUNCTION IF EXISTS "fertiscan_0.1.1".upsert_inspection; -- Function to upsert inspection information -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".upsert_inspection( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".upsert_inspection( p_inspection_id uuid, p_label_info_id uuid, p_inspector_id uuid, @@ -370,7 +370,7 @@ $$ LANGUAGE plpgsql; -- Function to upsert fertilizer information based on unique fertilizer name -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".upsert_fertilizer( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".upsert_fertilizer( p_name text, p_registration_number text, p_owner_id uuid, @@ -404,9 +404,9 @@ BEGIN END; $$ LANGUAGE plpgsql; -drop FUNCTION IF EXISTS "fertiscan_0.0.18".update_inspection; +drop FUNCTION IF EXISTS "fertiscan_0.1.1".update_inspection; -- Function to update inspection data and related entities, returning an updated JSON -CREATE OR REPLACE FUNCTION "fertiscan_0.0.18".update_inspection( +CREATE OR REPLACE FUNCTION "fertiscan_0.1.1".update_inspection( p_inspection_id uuid, p_inspector_id uuid, p_input_json jsonb @@ -536,7 +536,7 @@ BEGIN SELEct id into org_info_id from organization_information where label_id = label_info_id_value; else org_info_id := NULL; - for org_record in select * from "fertiscan_0.0.18".organization_information where label_id = label_info_id_value + for org_record in select * from "fertiscan_0.1.1".organization_information where label_id = label_info_id_value loop if org_record.is_main_contact then org_info_id := org_record.id; diff --git a/fertiscan/db/metadata/inspection/__init__.py b/fertiscan/db/metadata/inspection/__init__.py index 3b6dbccd..5b9a0cad 100644 --- a/fertiscan/db/metadata/inspection/__init__.py +++ b/fertiscan/db/metadata/inspection/__init__.py @@ -6,6 +6,7 @@ from datetime import datetime from typing import List, Optional +from uuid import UUID from pydantic import UUID4, BaseModel, ValidationError, model_validator @@ -37,7 +38,7 @@ def handle_none(cls, values): class OrganizationInformation(ValidatedModel): - id: Optional[str] = None + id: Optional[UUID4] = None name: Optional[str] = None address: Optional[str] = None website: Optional[str] = None @@ -95,7 +96,7 @@ class RegistrationNumber(ValidatedModel): class ProductInformation(ValidatedModel): name: str | None = None - label_id: str | None = None + label_id: UUID4 | None = None lot_number: str | None = None metrics: Metrics | None = Metrics() npk: str | None = None @@ -135,8 +136,8 @@ class DBInspection(ValidatedModel): class Inspection(ValidatedModel): - inspection_id: Optional[str] = None - inspector_id: Optional[str] = None + inspection_id: Optional[UUID4] = None + inspector_id: Optional[UUID4] = None inspection_comment: Optional[str] = None verified: Optional[bool] = False organizations: Optional[List[OrganizationInformation]] = [] @@ -145,9 +146,14 @@ class Inspection(ValidatedModel): instructions: SubLabel guaranteed_analysis: GuaranteedAnalysis ingredients: ValuesObjects + folder_id: UUID4 + container_id: UUID4 + upload_date: Optional[datetime] = None + updated_at: Optional[datetime] = None + -def build_inspection_import(analysis_form: dict, user_id) -> str: +def build_inspection_import(analysis_form: dict, user_id:UUID,folder_id:UUID,container_id:UUID) -> Inspection: """ This funtion build an inspection json object from the pipeline of digitalization analysis. This serves as the metadata for the inspection object in the database. @@ -349,9 +355,13 @@ def build_inspection_import(analysis_form: dict, user_id) -> str: guaranteed_analysis=guaranteed, registration_numbers=reg_numbers, ingredients=ingredients, + folder_id=folder_id, + container_id=container_id, + inspection_comment= None, + upload_date=None, + updated_at=None, ) - Inspection(**inspection_formatted.model_dump()) - return inspection_formatted.model_dump_json() + return inspection_formatted except MetadataError: raise except ValidationError as e: @@ -360,7 +370,7 @@ def build_inspection_import(analysis_form: dict, user_id) -> str: raise BuildInspectionImportError(f"Unexpected error: {e}") from e -def build_inspection_export(cursor, inspection_id) -> str: +def build_inspection_export(cursor, inspection_id) -> Inspection: """ This funtion build an inspection json object from the database. """ @@ -416,6 +426,12 @@ def build_inspection_export(cursor, inspection_id) -> str: # Get the inspection information db_inspection = inspection.get_inspection_dict(cursor, inspection_id) db_inspection = DBInspection.model_validate(db_inspection) + + inspection_fk = inspection.get_inspection_fk( + cursor=cursor, + inspection_id=inspection_id) + container_id = inspection_fk[3] + folder_id = inspection_fk[2] inspection_formatted = Inspection( inspection_id=str(inspection_id), @@ -428,9 +444,12 @@ def build_inspection_export(cursor, inspection_id) -> str: product=product_info, verified=db_inspection.verified, ingredients=ingredients, + folder_id=folder_id, + container_id=container_id, + upload_date=db_inspection.upload_date, + updated_at=db_inspection.updated_at, ) - - return inspection_formatted.model_dump_json() + return inspection_formatted except QueryError as e: raise BuildInspectionExportError(f"Error fetching data: {e}") from e except Exception as e: diff --git a/fertiscan/db/queries/errors.py b/fertiscan/db/queries/errors.py index 392438a0..1225ae4e 100644 --- a/fertiscan/db/queries/errors.py +++ b/fertiscan/db/queries/errors.py @@ -3,6 +3,7 @@ Exception |__QueryError + |__FertilizerQueryError |__InspectionQueryError |__LabelInformationQueryError |__LabelDimensionQueryError @@ -33,6 +34,16 @@ class QueryError(Exception): pass +class FertilizerQueryError(QueryError): + """Base exception for all fertilizer-related query errors.""" + + pass + +class FertilizerUpsertError(FertilizerQueryError): + """Raised when an error occurs during the upserting of a Fertilizer.""" + + pass + class InspectionQueryError(QueryError): """Base exception for all inspection-related query errors.""" @@ -605,6 +616,32 @@ class RegistrationNumberNotFoundError(RegistrationNumberQueryError): pass +class IngredientQueryError(QueryError): + """Base exception for all Ingredients related query error""" + + pass + +class IngredientCreationError(IngredientQueryError): + """Raised when an error occurs during the creation of an Ingredient.""" + + pass + +class IngredientRetrievalError(IngredientQueryError): + """Raised when an error occurs during the retrieval of an Ingredient.""" + + pass + +class IngredientNotFoundError(IngredientQueryError): + """Raised when an Ingredient was not found.""" + + pass + +class IngredientDeleteError(IngredientQueryError): + """Raised when an error occurs during the delete of an Ingredient""" + + pass + + def handle_query_errors(error_cls=QueryError): """Decorator for handling query errors.""" diff --git a/fertiscan/db/queries/fertilizer/__init__.py b/fertiscan/db/queries/fertilizer/__init__.py new file mode 100644 index 00000000..13e8f734 --- /dev/null +++ b/fertiscan/db/queries/fertilizer/__init__.py @@ -0,0 +1,178 @@ + + +from uuid import UUID +from psycopg import Cursor +from psycopg.rows import dict_row +from psycopg.sql import SQL + +from datetime import date as Date + +from fertiscan.db.queries.errors import ( + FertilizerUpsertError, + FertilizerQueryError, + handle_query_errors, +) + +handle_query_errors(FertilizerUpsertError) +def upsert_fertilizer(cursor: Cursor, name: str, reg_number:str, org_owner_id:UUID,latest_inspection_id: UUID): + """ + This function Inserts data for a Fertilizer based on its name. + If a Fertilizer is already named as such we update the latest inspection FK. + + Parameters: + cursor (Cursor): + name (str): + reg_number (str): + org_owner_id (UUID): + latest_inspection_id (UUID): + + Returns: + - Fertilizer id (uuid) + """ + query = """ + INSERT INTO + fertilizer ( + name, + registration_number, + upload_date, + update_at, + main_contact_id, + latest_inspection_id) + VALUES ( + %s, + %s, + CURRENT_TIMESTAMP, -- Set upload date to current timestamp + CURRENT_TIMESTAMP, -- Set update date to current timestamp + %s, + %s + ) + ON CONFLICT (name) DO UPDATE + SET + registration_number = EXCLUDED.registration_number, + update_at = CURRENT_TIMESTAMP, -- Update the update_at timestamp + main_contact_id = EXCLUDED.main_contact_id, + latest_inspection_id = EXCLUDED.latest_inspection_id + RETURNING id; + """ + cursor.execute(query,(name,reg_number,org_owner_id,latest_inspection_id,)) + return cursor.fetchone()[0] + + +def search_fertilizer(cursor: Cursor, fertilizer_name:str, registration_number: str, lower_bound_date: Date, upper_bound_date: Date, lot_number:str, org_ids:list[UUID]): + """ + Find all inspections where the organization is listed as main contact. + + Parameters: + - cursor (Cursor): Database cursor + - org_ids (list[UUID]): List of organization IDs to search for + + Returns: + - list: List of tuples containing inspection data + """ + query = """ + SELECT + i.id as inspection_id, + i.verified as verified, + i.upload_date as upload_date, + i.updated_at as last_updated_at, + i.inspector_id as inspector_id, + i.label_info_id as label_info_id, + i.container_id as container_id, + i.picture_set_id as foldeer_id, + i.inspection_comment as inspection_comment, + i.verified_date as verified_date, + f.id as fertilizer_id, + f.name as fertilizer_name, + f.registration_number as registration_number, + f.main_contact_id as organization, + o.name as organization_name, + o.phone_number as organization_phone_number, + o.address as organization_email, + l.lot_number as lot_number, + l.title_is_minimal as is_minimal_guaranteed_analysis, + l.record_keeping as is_record_keeping + FROM + fertilizer f + JOIN + inspection i ON f.latest_inspection_id = i.id + LEFT JOIN + organization o ON f.main_contact_id = o.id + LEFT JOIN + label_information l ON i.label_info_id = l.id + """ + first = True + # check if all parameters are not none + if ((org_ids is None or len(org_ids) < 1) and + (fertilizer_name is None or fertilizer_name.strip() == "") and + (registration_number is None or registration_number.strip() == "") and + (lower_bound_date is None) and + (upper_bound_date is None) and + (lot_number is None or lot_number.strip() == "") + ): + raise FertilizerQueryError("No search parameters provided, please provide at least one search parameter.") + if org_ids is not None and len(org_ids) > 0: + query += "WHERE o.id = ANY(%s)" + if first: + first = False + if fertilizer_name is not None and fertilizer_name.strip() != "": + if first: + query += "WHERE " + else: + query += "AND " + query += "f.name = %s" + first = False + if registration_number is not None and registration_number.strip() != "": + if first: + query += "WHERE " + else: + query += "AND " + query += "f.registration_number = %s" + first = False + if lower_bound_date is not None: + if first: + query += "WHERE " + else: + query += "AND " + query += "DATE(i.upload_date) >= DATE(%s)" + first = False + if upper_bound_date is not None: + if first: + query += "WHERE " + else: + query += "AND " + query += "DATE(i.upload_date) <= DATE(%s)" + first = False + if lot_number is not None and lot_number.strip() != "": + if first: + query += "WHERE " + else: + query += "AND " + query += "l.lot_number = %s" + first = False + + # Aggregate the Registration Numbers + query += """ + GROUP BY + i.id, + i.verified, + i.upload_date, + i.updated_at, + i.inspector_id, + i.label_info_id, + i.container_id, + i.picture_set_id, + i.inspection_comment, + i.verified_date, + l.product_name, + o.id, + o.name, + o.phone_number, + o.address, + l.lot_number, + l.title_is_minimal, + l.record_keeping + """ + query += ";" + + cursor.execute(query, (org_ids,)) + return cursor.fetchall() diff --git a/fertiscan/db/queries/ingredient/__init__.py b/fertiscan/db/queries/ingredient/__init__.py index 964cab4b..452b9a3a 100644 --- a/fertiscan/db/queries/ingredient/__init__.py +++ b/fertiscan/db/queries/ingredient/__init__.py @@ -1,22 +1,19 @@ from psycopg import Cursor, Error +from uuid import UUID """ This module represent the function for the Ingredient table """ +from fertiscan.db.queries.errors import ( + IngredientNotFoundError, + IngredientCreationError, + IngredientDeleteError, + IngredientQueryError, + handle_query_errors +) -class IngredientQueryError(Exception): - pass - - -class IngredientCreationError(IngredientQueryError): - pass - - -class IngredientRetrievalError(IngredientQueryError): - pass - - +handle_query_errors(IngredientCreationError) def new_ingredient( cursor: Cursor, name: str, @@ -31,41 +28,88 @@ def new_ingredient( """ This function creates a new ingredient in the database. """ - try: - if language not in ["en", "fr"]: - raise IngredientCreationError("Error: language must be either 'en' or 'fr'") - query = """ - SELECT new_ingredient(%s, %s, %s, %s, %s, %s, %s, %s); - """ - cursor.execute( - query, (name, value, read_unit, label_id, language, organic, active, edited) - ) - return cursor.fetchone()[0] - except IngredientCreationError: - raise - except Error as db_error: - raise IngredientCreationError(f"Database error: {db_error}") from db_error - except Exception as e: - raise IngredientCreationError(f"Unexpected error: {e}") from e - + if language not in ["en", "fr"]: + raise IngredientCreationError("Error: language must be either 'en' or 'fr'") + if name is None and value is None and read_unit is None: + raise IngredientCreationError("Error: All minimal inputs [name,value,read_unit] are Null") + query = """ + SELECT new_ingredient(%s, %s, %s, %s, %s, %s, %s, %s); + """ + cursor.execute( + query, (name, value, read_unit, label_id, language, organic, active, edited) + ) + return cursor.fetchone()[0] +handle_query_errors(IngredientNotFoundError) def get_ingredient_json(cursor: Cursor, label_id) -> dict: """ This function gets the ingredient JSON from the database. """ - try: - query = """ - SELECT get_ingredients_json(%s); - """ - cursor.execute(query, (label_id,)) - result = cursor.fetchone() - if result is None: - raise IngredientRetrievalError("Error: ingredient not found") - return result[0] + query = """ + SELECT get_ingredients_json(%s); + """ + cursor.execute(query, (label_id,)) + result = cursor.fetchone() + if result is None: + raise IngredientNotFoundError("Error: ingredient not found") + return result[0] + +handle_query_errors(IngredientNotFoundError) +def get_ingredient_label(cursor:Cursor,label_id:UUID): + """ + This function gets the ingredient JSON from the database. + """ + query = """ + SELECT + id, + name, + value, + unit, + edited, + organic, + active, + language + FROM + ingredient + WHERE + label_id = %s; + """ + cursor.execute(query, (label_id,)) + result = cursor.fetchall() + return result + +handle_query_errors(IngredientDeleteError) +def delete_ingredient_label(cursor:Cursor, label_id:UUID): + query = """ + DELETE FROM ingredient + WHERE label_id = %s + RETURNING id; + """ + cursor.execute(query, (label_id,)) + return cursor.rowcount - except IngredientRetrievalError: - raise - except Error as db_error: - raise IngredientCreationError(f"Database error: {db_error}") from db_error - except Exception as e: - raise IngredientCreationError(f"Unexpected error: {e}") from e +handle_query_errors(IngredientQueryError) +def upsert_ingredient(cursor: Cursor,label_id:UUID,ingredients:dict): + delete_ingredient_label(cursor=cursor,label_id=label_id) + for ingredient_en in ingredients["en"]: + new_ingredient( + cursor=cursor, + name=ingredient_en["name"], + value=ingredient_en["value"], + read_unit=ingredient_en["unit"], + label_id=label_id, + language="en", + organic=None, + active=None, + edited=True) + for ingredient_fr in ingredients["fr"]: + new_ingredient( + cursor=cursor, + name=ingredient_fr["name"], + value=ingredient_fr["value"], + read_unit=ingredient_fr["unit"], + label_id=label_id, + language="fr", + organic=None, + active=None, + edited=True) diff --git a/fertiscan/db/queries/inspection/__init__.py b/fertiscan/db/queries/inspection/__init__.py index 9527a723..6f400b61 100644 --- a/fertiscan/db/queries/inspection/__init__.py +++ b/fertiscan/db/queries/inspection/__init__.py @@ -5,6 +5,7 @@ import json from uuid import UUID +from datetime import datetime from psycopg import Cursor from psycopg.rows import dict_row @@ -22,7 +23,9 @@ @handle_query_errors(InspectionCreationError) -def new_inspection(cursor: Cursor, user_id, picture_set_id, verified=False): +def new_inspection( + cursor: Cursor, user_id, picture_set_id, label_id, container_id, verified=False +) -> tuple[UUID, datetime]: """ This function uploads a new inspection to the database. @@ -40,19 +43,33 @@ def new_inspection(cursor: Cursor, user_id, picture_set_id, verified=False): INSERT INTO inspection ( inspector_id, picture_set_id, - verified + verified, + label_info_id, + container_id ) VALUES - (%s, %s, %s) + (%s, %s, %s,%s,%s) RETURNING - id + id, upload_date; """ - cursor.execute(query, (user_id, picture_set_id, verified)) + cursor.execute(query, (user_id, picture_set_id, verified, label_id, container_id)) if result := cursor.fetchone(): - return result[0] + return result raise InspectionCreationError("Failed to create inspection. No data returned.") +def save_inspection_original_dataset(cursor: Cursor, inspection_id: UUID, og_data): + query = """ + UPDATE + "fertiscan_0.1.1".inspection_factual + SET + original_dataset = %s + WHERE + inspection_factual."inspection_id" = %s; + """ + cursor.execute(query, (og_data, inspection_id)) + + @handle_query_errors(InspectionCreationError) def new_inspection_with_label_info(cursor: Cursor, user_id, picture_set_id, label_json): """ @@ -234,8 +251,7 @@ def get_inspection_fk(cursor: Cursor, inspection_id): label_info_id, inspector_id, picture_set_id, - company_info_id, - manufacturer_info_id, + container_id, fertilizer_id, sample_id ] @@ -246,14 +262,11 @@ def get_inspection_fk(cursor: Cursor, inspection_id): inspection.label_info_id, inspection.inspector_id, inspection.picture_set_id, + inspection.container_id, inspection.fertilizer_id, inspection.sample_id FROM inspection - LEFT JOIN - label_information as label_info - ON - inspection.label_info_id = label_info.id WHERE inspection.id = %s """ @@ -369,12 +382,43 @@ def get_all_organization_inspection(cursor: Cursor, org_id): return cursor.fetchall() -@handle_query_errors(InspectionUpdateError) def update_inspection( - cursor: Cursor, - inspection_id: str | UUID, - user_id: str | UUID, - updated_data_dict: dict, + cursor: Cursor, inspection_id: str | UUID, verified: bool, inspection_comment: str +) -> datetime: + if verified: + query = """ + UPDATE + inspection + SET + verified = %s, + updated_at = CURRENT_TIMESTAMP, + inspection_comment = %s, + verified_date = CURRENT_TIMESTAMP + WHERE + id = %s + RETURNING + updated_at; + """ + else: + query = """ + UPDATE + inspection + SET + verified = %s, + updated_at = CURRENT_TIMESTAMP, + inspection_comment = %s + WHERE + id = %s + RETURNING + updated_at; + """ + cursor.execute(query, (verified, inspection_comment, inspection_id)) + return cursor.fetchone()[0] + + +@handle_query_errors(InspectionUpdateError) +def update_inspection_function( + cursor: Cursor, inspection_id: str | UUID, user_id: str | UUID, updated_data_dict ) -> dict: """ Update inspection data in the database. @@ -393,7 +437,7 @@ def update_inspection( """ # Prepare and execute the SQL function call query = SQL("SELECT update_inspection(%s, %s, %s)") - cursor.execute(query, (inspection_id, user_id, json.dumps(updated_data_dict))) + cursor.execute(query, (inspection_id, user_id, updated_data_dict)) if result := cursor.fetchone(): return result[0] @@ -459,3 +503,145 @@ def get_inspection_factual(cursor: Cursor, inspection_id): """ cursor.execute(query, (inspection_id,)) return cursor.fetchone() + + +def search_inspection( + cursor: Cursor, + fertilizer_name: str, + lower_bound_date: Date, + upper_bound_date: Date, + lot_number: str, + label_ids: list[UUID], +) -> list: + """ + Find all inspections where the organization is listed as main contact. + + Parameters: + - cursor (Cursor): Database cursor + - fertilizer_name (str): The name of the fertilizer + - lower_bound_date (Date): The lower bound date of the inspection + - upper_bound_date (Date): The upper bound date of the inspection + - lot_number (str): The lot number of the fertilizer + - label_ids (list[UUID]): The list of label IDs to search also search for regarless of other parameters + + Returns: + - list: List of tuples containing inspection data + """ + query = """ + SELECT + i.id as inspection_id, + i.verified as verified, + i.upload_date as upload_date, + i.updated_at as last_updated_at, + i.inspector_id as inspector_id, + i.label_info_id as label_info_id, + i.container_id as container_id, + i.picture_set_id as folder_id, + i.inspection_comment as inspection_comment, + i.verified_date as verified_date, + l.product_name as fertilizer_name, + o.id as organization_info_id, + o.name as organization_name, + o.phone_number as organization_phone_number, + o.address as organization_address, + l.lot_number as lot_number, + l.title_is_minimal as is_minimal_guaranteed_analysis, + l.record_keeping as is_record_keeping, + array_agg(r.identifier) as registration_numbers + FROM + inspection i + JOIN + label_information l ON i.label_info_id = l.id + LEFT JOIN + organization_information o ON l.id = o.label_id AND o.is_main_contact = TRUE + LEFT JOIN + registration_number_information r ON l.id = r.label_id + """ + first = True + params = () + # check if all parameters are not none + if ( + (fertilizer_name is None or fertilizer_name.strip() == "") + and (lower_bound_date is None) + and (upper_bound_date is None) + and (lot_number is None or lot_number.strip() == "") + and (label_ids is None or len(label_ids) < 1) + ): + raise InspectionQueryError( + "No search parameters provided, please provide at least one search parameter." + ) + # Check if the dates are valid + if lower_bound_date is not None and upper_bound_date is not None: + if lower_bound_date > upper_bound_date: + raise InspectionQueryError( + "The lower bound date is greater than the upper bound date." + ) + + if fertilizer_name is not None and fertilizer_name.strip() != "": + if first: + query += "WHERE " + else: + query += "AND " + query += "l.product_name = %s " + first = False + params += (fertilizer_name,) + if lower_bound_date is not None: + if first: + query += "WHERE " + else: + query += "AND " + query += "DATE(i.upload_date) >= DATE(%s) " + first = False + params += (lower_bound_date,) + if upper_bound_date is not None: + if first: + query += "WHERE " + else: + query += "AND " + query += "DATE(i.upload_date) <= DATE(%s) " + first = False + # Make sure upper_bound_date time is 23:59:59 to include the whole day + upper_bound_date = upper_bound_date.replace(hour=23, minute=59, second=59) + params += (upper_bound_date,) + if lot_number is not None and lot_number.strip() != "": + if first: + query += "WHERE " + else: + query += "AND " + query += "l.lot_number = %s " + first = False + params += (lot_number,) + if label_ids is not None and len(label_ids) > 0: + if first: + query += "WHERE " # This is a list for previous conditions that were met + else: + query += "OR " + query += "l.id = ANY(%s) " + first = False + params += (label_ids,) + + # Aggregate the Registration Numbers + query += """ + GROUP BY + i.id, + i.verified, + i.upload_date, + i.updated_at, + i.inspector_id, + i.label_info_id, + i.container_id, + i.picture_set_id, + i.inspection_comment, + i.verified_date, + l.product_name, + o.id, + o.name, + o.phone_number, + o.address, + l.lot_number, + l.title_is_minimal, + l.record_keeping + """ + query += ";" + cursor.execute(query, params) + return cursor.fetchall() diff --git a/fertiscan/db/queries/label/__init__.py b/fertiscan/db/queries/label/__init__.py index 8d872927..06234f2f 100644 --- a/fertiscan/db/queries/label/__init__.py +++ b/fertiscan/db/queries/label/__init__.py @@ -4,6 +4,8 @@ from psycopg import Cursor +from uuid import UUID + from fertiscan.db.queries.errors import ( LabelDimensionNotFoundError, LabelDimensionQueryError, @@ -47,7 +49,31 @@ def new_label_information( - str: The UUID of the label_information """ query = """ - SELECT new_label_information(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s); + INSERT INTO + label_information ( + product_name, + lot_number, + npk, + n, + p, + k, + guaranteed_title_en, + guaranteed_title_fr, + title_is_minimal, + record_keeping + ) VALUES ( + %s, + %s, + %s, + %s, + %s, + %s, + %s, + %s, + %s, + %s + ) + RETURNING id; """ cursor.execute( query, @@ -71,11 +97,61 @@ def new_label_information( ) -def new_label_information_complete( - cursor, lot_number, npk, registration_number, n, p, k, weight, density, volume +@handle_query_errors(LabelInformationCreationError) +def new_label_information_function( + cursor, + name: str, + lot_number: str, + npk: str, + n: float, + p: float, + k: float, + title_en: str, + title_fr: str, + is_minimal: bool, + record_keeping: bool, ): - ##TODO: Implement this function - return None + """ + This function create a new label_information in the database. + + Parameters: + - cursor (cursor): The cursor of the database. + - lot_number (str): The lot number of the label_information. + - npk (str): The npk of the label_information. + - n (float): The n of the label_information. + - p (float): The p of the label_information. + - k (float): The k of the label_information. + - title_en (str): The english title of the guaranteed analysis. + - title_fr (str): The french title of the guaranteed analysis. + - is_minimal (bool): if the tital is minimal for the guaranteed analysis. + - record_keeping (bool): if the label is a record keeping. + + Returns: + - str: The UUID of the label_information + """ + query = """ + SELECT new_label_information(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s); + """ + cursor.execute( + query, + ( + name, + lot_number, + npk, + n, + p, + k, + title_en, + title_fr, + is_minimal, + record_keeping, + ), + ) + if result := cursor.fetchone(): + return result[0] + raise LabelInformationCreationError( + "Failed to create label information. No data returned." + ) @handle_query_errors(LabelInformationRetrievalError) @@ -196,3 +272,75 @@ def delete_label_info(cursor: Cursor, label_id: str): """ cursor.execute(query, (label_id,)) return cursor.rowcount + + +@handle_query_errors(LabelInformationCreationError) +def update_label_info( + cursor: Cursor, + label_id: str, + name: str | None = None, + lot_number: str | None = None, + npk: str | None = None, + n: float | None = None, + p: float | None = None, + k: float | None = None, + title_en: str | None = None, + title_fr: str | None = None, + is_minimal: bool | None = None, + record_keeping: bool | None = None, +) -> dict: + """ + Updates an existing label_information record in the database. + + Parameters: + - cursor (Cursor): The database cursor + - label_id (str): UUID of the label to update + - name (str, optional): Product name + - lot_number (str, optional): Lot number + - npk (str, optional): NPK value + - n (float, optional): Nitrogen value + - p (float, optional): Phosphorus value + - k (float, optional): Potassium value + - title_en (str, optional): English title + - title_fr (str, optional): French title + - is_minimal (bool, optional): Minimal title flag + - record_keeping (bool, optional): Record keeping flag + + Returns: + - dict: Updated label information + + Raises: + - LabelInformationCreationError: If update fails + """ + query = """ + UPDATE + label_information + SET + product_name = %s, + lot_number = %s, + npk = %s, + n = %s, + p = %s, + k = %s, + guaranteed_title_en = %s, + guaranteed_title_fr = %s, + title_is_minimal = %s, + record_keeping = %s + WHERE id = %s; + """ + cursor.execute( + query, + ( + name, + lot_number, + npk, + n, + p, + k, + title_en, + title_fr, + is_minimal, + record_keeping, + label_id, + ), + ) diff --git a/fertiscan/db/queries/metric/__init__.py b/fertiscan/db/queries/metric/__init__.py index e951e0a6..112dcb1b 100644 --- a/fertiscan/db/queries/metric/__init__.py +++ b/fertiscan/db/queries/metric/__init__.py @@ -4,11 +4,14 @@ """ from psycopg import Cursor +from uuid import UUID from fertiscan.db.queries.errors import ( MetricCreationError, MetricQueryError, MetricRetrievalError, + MetricUpdateError, + MetricDeleteError, UnitCreationError, UnitQueryError, handle_query_errors, @@ -285,3 +288,82 @@ def get_unit_id(cursor: Cursor, unit): if result := cursor.fetchone(): return result[0] raise UnitQueryError("Failed to retrieve unit id. No data returned.") + +@handle_query_errors(MetricDeleteError) +def delete_metric_by_type(cursor:Cursor, label_id:UUID, metric_type:str)->int: + """ + This function deletes a metric from the database based on label_id and metric_type. + + Parameters: + - cursor (cursor): The cursor of the database. + - label_id (UUID): The UUID of the label. + - metric_type (str): The type of the metric. + + Returns: + - number of deleted rows. (int) + """ + query = """ + DELETE FROM + metric + WHERE + label_id = %s AND + metric_type = %s + RETURNING ID; + """ + cursor.execute(query, (label_id, metric_type)) + return cursor.rowcount + +@handle_query_errors(MetricDeleteError) +def delete_metric(cursor:Cursor, label_id:UUID)->int: + """ + This function deletes a metric from the database based on label_id and metric_type. + + Parameters: + - cursor (cursor): The cursor of the database. + - label_id (UUID): The UUID of the label. + + Returns: + - number of deleted rows. (int) + """ + rowcount=0 + for type in ["density", "weight", "volume"]: + rowcount += delete_metric_by_type( + cursor=cursor, + label_id=label_id, + metric_type=type + ) + return rowcount + +@handle_query_errors(MetricUpdateError) +def upsert_metric(cursor: Cursor, label_id:UUID,metrics:dict): + delete_metric(cursor=cursor,label_id=label_id) + + # Weight + for record in metrics["weight"]: + new_metric( + cursor=cursor, + value= record["value"], + read_unit=record["unit"], + label_id=label_id, + metric_type='weight', + edited=record["edited"] + ) + # Density + new_metric( + cursor=cursor, + value= metrics["density"]["value"], + read_unit=metrics["density"]["unit"], + label_id=label_id, + metric_type='density', + edited=metrics["density"]["edited"] + ) + # Volume + new_metric( + cursor=cursor, + value= metrics["volume"]["value"], + read_unit=metrics["volume"]["unit"], + label_id=label_id, + metric_type='volume', + edited=metrics["volume"]["edited"] + ) + diff --git a/fertiscan/db/queries/nutrients/__init__.py b/fertiscan/db/queries/nutrients/__init__.py index 723d6b84..64dea36a 100644 --- a/fertiscan/db/queries/nutrients/__init__.py +++ b/fertiscan/db/queries/nutrients/__init__.py @@ -4,6 +4,7 @@ """ from psycopg import Cursor +from uuid import UUID from fertiscan.db.queries.errors import ( ElementCompoundCreationError, @@ -11,6 +12,8 @@ ElementCompoundQueryError, GuaranteedAnalysisCreationError, GuaranteedAnalysisRetrievalError, + GuaranteedAnalysisDeleteError, + GuaranteedAnalysisUpdateError, MicronutrientCreationError, MicronutrientRetrievalError, handle_query_errors, @@ -288,9 +291,70 @@ def get_all_micronutrients(cursor: Cursor, label_id): cursor.execute(query, (label_id,)) return cursor.fetchall() +def new_guaranteed_analysis( + cursor: Cursor, + read_name, + value, + unit, + label_id, + language: str, + element_id: int = None, + edited: bool = False, +): + """ + This function add a new guaranteed in the database. + + Parameters: + - cursor (cursor): The cursor of the database. + - read_name (str): The name of the guaranteed. + - value (float): The value of the guaranteed. + - unit (str): The unit of the guaranteed. + - element_id (int): The element of the guaranteed. + - label_id (str): The label of the guaranteed. + + Returns: + - str: The UUID of the guaranteed. + """ + if language.lower() not in ["fr", "en"]: + raise GuaranteedAnalysisCreationError("Language not supported") + if ( + (read_name is None or read_name == "") + and (value is None or value == "") + and (unit is None or unit == "") + ): + raise GuaranteedAnalysisCreationError("Read name and value cannot be empty") + query = """ + INSERT INTO guaranteed ( + read_name, + value, + unit, + edited, + label_id, + element_id, + language) + VALUES ( + %s, + %s, + %s, + %s, + %s, + %s, + %s + ) + RETURNING id; + """ + cursor.execute( + query, (read_name, value, unit, edited,label_id, element_id, language) + ) + if result := cursor.fetchone(): + return result[0] + raise GuaranteedAnalysisCreationError( + "Failed to create guaranteed analysis. No data returned." + ) + @handle_query_errors(GuaranteedAnalysisCreationError) -def new_guaranteed_analysis( +def new_guaranteed_analysis_function( cursor: Cursor, read_name, value, @@ -392,6 +456,64 @@ def get_guaranteed_analysis_json(cursor: Cursor, label_id) -> dict: "Failed to retrieve guaranteed analysis json. No data returned for: " + str(label_id) ) +@handle_query_errors(GuaranteedAnalysisDeleteError) +def delete_guaranteed_analysis(cursor:Cursor, label_id:UUID): + """ + This function deletes guaranteed analysis records from the database. + + Parameters: + - cursor (cursor): The cursor of the database. + - label_id (UUID): The UUID of the label. + + Returns: + - int: The number of rows deleted. + """ + query = """ + DELETE FROM guaranteed + WHERE label_id = %s + RETURNING id; + """ + cursor.execute(query, (label_id,)) + return cursor.rowcount + +@handle_query_errors(GuaranteedAnalysisUpdateError) +def upsert_guaranteed_analysis(cursor:Cursor,label_id: UUID, GA:dict): + """ + Replaces all entries for a label by deleting existing ones and inserting new ones. + + Parameters: + - cursor: Database cursor + - label_id: UUID of the label to update + - GA: Dictionary containing the new values to insert + """ + delete_guaranteed_analysis( + cursor=cursor, + label_id= label_id, + ) + + for record in GA["en"]: + new_guaranteed_analysis( + cursor=cursor, + read_name=record["name"], + value=record["value"], + unit = record["unit"], + label_id=label_id, + language="en", + element_id=None, + edited= True + ) + for record in GA["fr"]: + new_guaranteed_analysis( + cursor=cursor, + read_name=record["name"], + value=record["value"], + unit = record["unit"], + label_id=label_id, + language="fr", + element_id=None, + edited= True + ) + @handle_query_errors(GuaranteedAnalysisRetrievalError) @@ -461,3 +583,32 @@ def get_all_guaranteeds(cursor: Cursor, label_id): """ cursor.execute(query, (label_id,)) return cursor.fetchall() + +@handle_query_errors(GuaranteedAnalysisRetrievalError) +def get_guaranteed_by_label(cursor:Cursor,label_id:UUID): + """ + This function get all the guaranteed in the database. + + Parameters: + - cursor (cursor): The cursor of the database. + - label_id (str): The UUID of the label. + + Returns: + - str: The UUID of the guaranteed. + """ + + query = """ + SELECT + g.id, + g.read_name, + g.value, + g.unit, + g.edited, + CONCAT(CAST(g.read_name AS TEXT),' ',g.value,' ', g.unit) AS reading + FROM + guaranteed g + WHERE + g.label_id = %s; + """ + cursor.execute(query, (label_id,)) + return cursor.fetchall() diff --git a/fertiscan/db/queries/organization/__init__.py b/fertiscan/db/queries/organization/__init__.py index 60b5f63a..af72501d 100644 --- a/fertiscan/db/queries/organization/__init__.py +++ b/fertiscan/db/queries/organization/__init__.py @@ -61,8 +61,126 @@ def new_organization(cursor: Cursor, name, website, phone_number, address): raise OrganizationCreationError("Failed to create Organization. No data returned.") +def upsert_organization(cursor: Cursor, name:str, website:str, phone_number: str, address:str): + """ + This function serves as an upsert when an organization is verified through an inspection + Therefor, we are looking for similar organizations with the same naming scheme to update their information or we insert a new Organization + + Parameters: + - cursor (Cursor): The database cursor to execute queries. + - name (str): The name of the organization. + - website (str): The website of the organization. + - phone_number (str): The phone number of the organization. + - address (str): The address of the organization. + Returns: + - None + + """ + query =""" + Select + "id" + FROM + organization + WHERE + name + ILIKE %s; + """ + cursor.execute(query, (name,)) + res = cursor.fetchone() + if res is None or res[0] is None: + id =new_organization( + cursor=cursor, + name=name, + website=website, + phone_number=phone_number, + address=address, + ) + else: + id = res[0] + query = """ + UPDATE + organization + SET + "website" = %s, + "phone_number" = %s, + "address" = %s, + "main_location_id" = Null + WHERE id = %s; + """ + cursor.execute(query,(website,phone_number,address,id)) + return id + @handle_query_errors(OrganizationInformationCreationError) def new_organization_information( + + cursor: Cursor, + address: str, + name: str, + website: str, + phone_number: str, + label_id: UUID, + edited: bool = False, + is_main_contact: bool = False, +): + """ + This function create a new organization information in the database using function. + + Parameters: + - cursor (cursor): The cursor of the database. + - name (str): The name of the organization. + - website (str): The website of the organization. + - phone_number (str): The phone number of the organization. + - label_id (str): The UUID of the label. + - edited (bool): The edited status of the organization information. + - is_main_contact (bool): The main contact status of the organization information. + + Returns: + - str: The UUID of the organization information + """ + if label_id is None: + raise OrganizationInformationCreationError( + "Label ID is required for organization information creation." + ) + query = """ + INSERT INTO + organization_information ( + "name", + "address", + "website", + "phone_number", + "edited", + "label_id", + "is_main_contact" + ) + VALUES ( + %s, + %s, + %s, + %s, + %s, + %s, + %s + ) + RETURNING id; + """ + cursor.execute( + query, + ( + name, + address, + website, + phone_number, + edited, + label_id, + is_main_contact, + ), + ) + if result := cursor.fetchone(): + return result[0] + raise OrganizationCreationError("Failed to create Organization. No data returned.") + +@handle_query_errors(OrganizationInformationCreationError) +def new_organization_information_function( cursor: Cursor, address: str, name: str, @@ -232,7 +350,13 @@ def get_organization_json(cursor: Cursor, fertilizer_id: UUID) -> dict: @handle_query_errors(OrganizationInformationUpdateError) def update_organization_info( - cursor: Cursor, information_id: UUID, name, website, phone_number + cursor: Cursor, + information_id: UUID, + name:str, + website:str, + phone_number:str, + address:str, + is_main_contact:bool ): """ This function update a organization information in the database. @@ -253,7 +377,9 @@ def update_organization_info( SET name = COALESCE(%s,name), website = COALESCE(%s,website), - phone_number = COALESCE(%s,phone_number) + phone_number = COALESCE(%s,phone_number), + is_main_contact = COALESCE(%s,is_main_contact), + address = COALESCE(%s,address) WHERE id = %s """ @@ -263,6 +389,8 @@ def update_organization_info( name, website, phone_number, + is_main_contact, + address, str(information_id), ), ) @@ -293,7 +421,7 @@ def upsert_organization_info(cursor: Cursor, organization_info, label_id: UUID): return cursor.fetchone()[0] -def upsert_organization(cursor: Cursor, organization_info_id: UUID): +def upsert_organization_function(cursor: Cursor, organization_info_id: UUID): """ This function upserts an organization information in the database. @@ -344,6 +472,30 @@ def get_organization(cursor: Cursor, organization_id: UUID): ) +def delete_absent_organisation_information_from_label(cursor:Cursor,label_id:UUID,org_ids:list[UUID]): + """ + Deletes organization information entries that have the specified label_id + but whose IDs are not in the provided org_ids list. + + Parameters: + - cursor (Cursor): The database cursor + - label_id (UUID): The label ID to match + - org_ids (list[UUID]): List of organization IDs to keep + + Returns: + - int: Number of rows deleted + """ + query = """ + DELETE FROM + organization_information + WHERE + label_id = %s + AND id != ALL(%s) + """ + cursor.execute(query, (label_id, org_ids)) + return cursor.rowcount + + @handle_query_errors(OrganizationRetrievalError) def get_full_organization(cursor: Cursor, org_id): """ @@ -394,6 +546,124 @@ def get_full_organization(cursor: Cursor, org_id): cursor.execute(query, (org_id,)) return cursor.fetchone() +def search_organization_information(cursor: Cursor, name:str,website:str,phone_number:str,address:str): + """ + This function search for an organization information in the database. + + Parameters: + - cursor (cursor): The cursor of the database. + - name (str): The name of the organization. + + Returns: + - dict: The organization information + """ + query = """ + SELECT + id, + label_id, + name, + website, + phone_number, + address + FROM + organization_information + WHERE + """ + first = True + # Make sure all parameters are not empty + if name is None and address is None and phone_number is None and website is None and name.strip() == "" and address.strip() == "" and phone_number.strip() == "" and website.strip() == "": + raise OrganizationRetrievalError("No search parameters provided. Please provide at least one search parameter.") + parameters = () + if name is not None and name.strip() != "": + first = False + query += "WHERE name ILIKE %s" + parameters += (name,) + if address is not None and address.strip() != "": + if not first: + query += " AND " + else: + query+="WHERE " + first = False + query += "address ILIKE %s" + parameters += (address,) + if phone_number is not None and phone_number.strip() != "": + if not first: + query += " AND " + else: + query+="WHERE " + first = False + query += "phone_number ILIKE %s" + parameters += (phone_number,) + if website is not None and website.strip() != "": + if not first: + query += " AND " + else: + query+="WHERE " + first = False + query += "website ILIKE %s" + parameters += (website,) + query += ";" + cursor.execute(query, parameters) + return cursor.fetchall() + +def search_organization(cursor: Cursor, name:str,address:str,phone_number:str,website:str): + """ + This function search for an organization in the database. + + Parameters: + - cursor (cursor): The cursor of the database. + - name (str): The name of the organization. + + Returns: + - dict: The organization + """ + query = """ + SELECT + id, + name, + website, + phone_number, + address + FROM + organization + """ + first = True + # Make sure all parameters are not empty + if name is None and address is None and phone_number is None and website is None and name.strip() == "" and address.strip() == "" and phone_number.strip() == "" and website.strip() == "": + raise OrganizationRetrievalError("No search parameters provided. Please provide at least one search parameter.") + parameters = () + if name is not None and name.strip() != "": + first = False + query += "WHERE name ILIKE %s" + parameters += (name,) + if address is not None and address.strip() != "": + if not first: + query += " AND " + else: + query+="WHERE " + first = False + query += "address ILIKE %s" + parameters += (address,) + if phone_number is not None and phone_number.strip() != "": + if not first: + query += " AND " + else: + query+="WHERE " + first = False + query += "phone_number ILIKE %s" + parameters += (phone_number,) + if website is not None and website.strip() != "": + if not first: + query += " AND " + else: + query+="WHERE " + first = False + query += "website ILIKE %s" + parameters += (website,) + query += ";" + cursor.execute(query, parameters) + return cursor.fetchall() + @handle_query_errors(LocationCreationError) def new_location(cursor: Cursor, name, address, region_id, org_id=None): diff --git a/fertiscan/db/queries/registration_number/__init__.py b/fertiscan/db/queries/registration_number/__init__.py index 28da77ae..e531f2e2 100644 --- a/fertiscan/db/queries/registration_number/__init__.py +++ b/fertiscan/db/queries/registration_number/__init__.py @@ -23,6 +23,54 @@ def new_registration_number( is_an_ingredient: bool, read_name: str = None, edited=False, +): + """ + This function creates a new registration_number in the database. + Parameters: + - cursor (cursor): The cursor of the database. + - registration_number (str): The registration number of the product. + - label_id (uuid): The UUID of the label_information. + - is_an_ingredient (bool): The status of the registration number. + - edited (bool): The edited status of the registration number. + Returns: + - The UUID of the new registration number. + """ + query = sql.SQL( + """ + INSERT INTO registration_number_information ( + "identifier", + "label_id", + "is_an_ingredient", + "name", + "edited" + ) + VALUES ( + %s, + %s, + %s, + %s, + %s + ) + RETURNING id; + """ + ) + cursor.execute( + query, (registration_number, label_id, is_an_ingredient, read_name, edited) + ) + if result := cursor.fetchone(): + return result[0] + raise RegistrationNumberCreationError( + "Failed to create Registration Number. No data returned." + ) + +@handle_query_errors(RegistrationNumberCreationError) +def new_registration_number_function( + cursor: Cursor, + registration_number, + label_id: UUID, + is_an_ingredient: bool, + read_name: str = None, + edited=False, ): """ This function creates a new registration_number in the database. @@ -71,10 +119,30 @@ def get_registration_numbers_json(cursor: Cursor, label_id: UUID): raise RegistrationNumberRetrievalError( "Failed to get Registration Numbers with the given label_id. No data returned." ) + +handle_query_errors(RegistrationNumberQueryError) +def update_registration_number( + cursor:Cursor, + label_id :UUID, + registration_numbers:list[dict] | None, + ): + delete_registration_numbers(cursor=cursor,label_id=label_id) + + if registration_numbers is not None: + for reg_number in registration_numbers: + new_registration_number( + cursor=cursor, + registration_number=reg_number["registration_number"], + is_an_ingredient=reg_number["is_an_ingredient"], + label_id=label_id, + read_name=None, + edited=True, + ) + @handle_query_errors(RegistrationNumberQueryError) -def update_registration_number( +def update_registration_number_function( cursor: Cursor, registration_numbers, label_id: UUID, @@ -130,9 +198,81 @@ def get_registration_numbers_from_label(cursor: Cursor, label_id: UUID): """ ) cursor.execute(query, (label_id,)) - result = cursor.fetchall() - if result: + return cursor.fetchall() + +def search_registration_number(cursor: Cursor, registration_number: str): + """ + This function searches for the registration numbers in the + database. + Parameters: + - cursor (cursor): The cursor of the database. + - registration_number (str): The registration number of the product. + + Returns: + - The registration numbers of the product. + """ + if registration_number is None or registration_number.strip() == "": + raise RegistrationNumberNotFoundError( + "No parameters provided for search. Please provide at least one search parameter." + ) + query = sql.SQL( + """ + SELECT + id, + identifier, + label_id, + is_an_ingredient, + name, + edited + FROM registration_number_information + WHERE identifier = %s; + """ + ) + cursor.execute(query, (registration_number,)) + if result := cursor.fetchall(): return result raise RegistrationNumberNotFoundError( - f"Failed to get Registration Numbers with the given label_id {label_id}. No data returned." + f"Failed to find Registration Number with the given registration number {registration_number}. No data returned." + ) + +def delete_registration_numbers(cursor: Cursor, label_id : UUID): + """ + This function deletes the registration numbers from the database. + Parameters: + - cursor (cursor): The cursor of the database. + - label_id (uuid): The UUID of the label_information. + """ + query = sql.SQL( + """ + DELETE FROM registration_number_information + WHERE label_id = %s; + """ ) + cursor.execute(query, (label_id,)) + if cursor.rowcount == 0: + raise RegistrationNumberQueryError( + f"Failed to delete Registration Numbers with the given label_id {label_id}. No rows affected." + ) + else: + return cursor.rowcount + +def upsert_registration_numbers(cursor: Cursor, label_id: UUID, reg_numbers:dict): + """ + Replaces all entries for a label by deleting existing ones and inserting new ones. + + Parameters: + - cursor: Database cursor + - label_id: UUID of the label to update + - reg_numbers: Dictionary containing the new values to insert + """ + delete_registration_numbers(cursor=cursor,label_id=label_id) + + for record in reg_numbers: + new_registration_number( + cursor=cursor, + registration_number=record.registration_number, + label_id=label_id, + is_an_ingredient=record.is_an_ingredient, + read_name=None, + edited=True + ) diff --git a/fertiscan/db/queries/sub_label/__init__.py b/fertiscan/db/queries/sub_label/__init__.py index 0e3ad385..7f309c3e 100644 --- a/fertiscan/db/queries/sub_label/__init__.py +++ b/fertiscan/db/queries/sub_label/__init__.py @@ -4,19 +4,20 @@ """ from psycopg import Cursor +from uuid import UUID from fertiscan.db.queries.errors import ( SubLabelCreationError, SubLabelNotFoundError, SubLabelQueryError, SubLabelRetrievalError, + SubLabelDeleteError, SubLabelUpdateError, SubTypeCreationError, SubTypeQueryError, handle_query_errors, ) - @handle_query_errors(SubLabelCreationError) def new_sub_label( cursor: Cursor, text_fr, text_en, label_id, sub_type_id, edited=False @@ -32,6 +33,47 @@ def new_sub_label( - sub_type_id (uuid): The UUID of the sub_type. - edited (bool): The edited status of the sub label. + Returns: + - The UUID of the new sub label. + """ + query = """ + INSERT INTO sub_label ( + text_content_fr, + text_content_en, + label_id, + edited, + sub_type_id + ) + VALUES ( + %s, + %s, + %s, + %s, + %s + ) + RETURNING id; + """ + cursor.execute(query, (text_fr, text_en, label_id, edited, sub_type_id)) + if result := cursor.fetchone(): + return result[0] + raise SubLabelCreationError("Failed to create SubLabel. No data returned.") + + +@handle_query_errors(SubLabelCreationError) +def new_sub_label_function( + cursor: Cursor, text_fr, text_en, label_id, sub_type_id, edited=False +): + """ + This function creates a new sub label in the database. + + Parameters: + - cursor (cursor): The cursor of the database. + - text_fr (str): The text in french. + - text_en (str): The text in english. + - label_id (uuid): The UUID of the label_information. + - sub_type_id (uuid): The UUID of the sub_type. + - edited (bool): The edited status of the sub label. + Returns: - The UUID of the new sub label. """ @@ -225,24 +267,70 @@ def update_sub_label(cursor: Cursor, sub_label_id, text_fr, text_en, edited=True """ cursor.execute(query, (text_fr, text_en, edited, sub_label_id)) -def update_sub_label_function(cursor: Cursor, sub_label_id, text_fr, text_en, edited=True): +handle_query_errors(SubLabelUpdateError) +def upsert_sub_label(cursor: Cursor, label_id: UUID, inspection_dict: dict): """ - This function updates the sub label in the database. + Replaces all entries for a label by deleting existing ones and inserting new ones. + + Parameters: + - cursor: Database cursor + - label_id: UUID of the label to update + - inspection_dict: Dictionary containing the new values to insert + """ + # get the all the active sub_types + sub_types = get_sub_types(cursor=cursor) + + delete_sub_label(cursor=cursor, label_id=label_id) + + for id, sub_type in sub_types: + if not inspection_dict.__contains__(sub_type): + continue + inspection_dict.__contains__(sub_type) + sub_label = inspection_dict[sub_type] + fr_list = sub_label["fr"] + en_list = sub_label["en"] + max_length = max(len(fr_list), len(en_list)) + for i in range(0, max_length): + if i >= len(fr_list): + fr = None + en = en_list[i] + elif i >= len(en_list): + fr = fr_list[i] + en = None + else: + fr = fr_list[i] + en = en_list[i] + new_sub_label( + cursor=cursor, + text_fr=fr, + text_en=en, + label_id=label_id, + sub_type_id=id, + edited=True, + ) + + +handle_query_errors(SubLabelDeleteError) +def delete_sub_label(cursor: Cursor, label_id: UUID): + """ + This function deletes all sub labels associated with a given label_id. Parameters: - cursor (cursor): The cursor of the database. - - sub_label_id (uuid): The UUID of the sub label. - - text_fr (str): The text in french. - - text_en (str): The text in english. - - edited (bool): The edited status of the sub label. + - label_id (uuid): The UUID of the label_information. Returns: - None """ query = """ - SELECT update_sub_label(%s, %s, %s, %s); + DELETE FROM + sub_label + WHERE + label_id = %s + RETURNING ID; """ - cursor.execute(query, (sub_label_id, text_fr, text_en, edited)) + cursor.execute(query, (label_id,)) + return cursor.rowcount @handle_query_errors(SubTypeCreationError) @@ -302,3 +390,23 @@ def get_sub_type_id(cursor: Cursor, type_name): if result := cursor.fetchone(): return result[0] raise SubTypeQueryError("Failed to get the sub type id. No data returned.") + + +def get_sub_types(cursor: Cursor) -> list: + """ + This function fetches all sub types names from the database. + + Parameters: + - cursor (cursor): The cursor of the database. + + Returns: + - A list of Tuple of sub type names and ids (id,name_en). + """ + query = """ + SELECT + id, type_en + FROM + sub_type + """ + cursor.execute(query) + return cursor.fetchall() diff --git a/fertiscan/doc/inspection-view.md b/fertiscan/doc/inspection-view.md index f4270994..97033595 100644 --- a/fertiscan/doc/inspection-view.md +++ b/fertiscan/doc/inspection-view.md @@ -8,6 +8,8 @@ object representing it. ```JSON { "inspection_id": "inspection uuid", + "container_id": "container_uuid", + "folder_id": "folder_uuid", "owner_id": "Organization uuid", "organizations": [ { diff --git a/fertiscan/doc/new-inspection.md b/fertiscan/doc/new-inspection.md index 412ccf3b..ed6c89e8 100644 --- a/fertiscan/doc/new-inspection.md +++ b/fertiscan/doc/new-inspection.md @@ -226,25 +226,20 @@ sequenceDiagram title FertiScan Submit Form actor C as Client participant FE as Frontend - participant BE as FertiScan - participant DS as DataStore + participant BE as Backend + participant FS as FertiScan.Inspection_Controller + participant DS as DataStore.Container_Controller participant DB as Database participant blob as BLOB Storage C ->> FE: Upload pictures FE ->> BE: Analysis label (user_id,[pictures]) BE ->> BE: Digitalize label(pictures) - BE ->> DS: register_analysis(cursor,user_id,pictures,form.json) + BE ->> DS: Save images + DS ->> blob: Upload images in new folder + DS --> BE: folder_id UUID - DS ->> DB: new_picture_set(user_id) - activate DS - DB --> DS: picture_set_id - DS ->>blob: new_folder(picture_set_id) - DS ->> DS: upload_pictures(user_id,pictures,picture_set_id,container_client) - DS ->> DB: register all pictures - DB --> DS: picture_ids - DS ->> blob: container_client.upload_pictures(pictures,picture_set_id) - deactivate DS + BE ->> FS: register analysis DS ->> DS: formatted_form = build_inspection_import(form) DS ->> DB: new_inspection(user_id,picture_set_id,formatted_form.json) DB --> DS: formatted_form_with_ids.json diff --git a/fertiscan/doc/search-inspection.md b/fertiscan/doc/search-inspection.md new file mode 100644 index 00000000..313c9dba --- /dev/null +++ b/fertiscan/doc/search-inspection.md @@ -0,0 +1,153 @@ +# Search Inspection Documentation + +## Context + +The User wants to be able to search within the datasets of existing inspection +based on the following parameters: + +- Fertilizer Name +- Registration number +- Lot number +- A timeframe (lower and upper bounds dates) +- Organization information (the entity responsable for the product) + - Name + - Phone number + - Address + +## Rules + +We need to establish rules regarding our search. + +### String matching + +- The search must allow for a case insensitive search, meaning the + capitalisation of words must not be considered. Meaning if you search "ABC in + the following dataset + +**Dataset** | ID | Name | |-----|------| | 1 | ABC | | 2 | abc | + +**Results** | ID | Name | |-----|------| | 1 | ABC | | 2 | abc | + +- The results must be an exact match to the parameter given, meaning if you + search "**ABC**" in the following dataset + +**Dataset** | ID | Name | |-----|------| | 1 | **ABC** | | 2 | **ABC**D | +| 3 | **AB** | | 4 | abc | + +**Results** | ID | Name | |-----|------| | 1 | **ABC** | | 4 | abc | + +### Multiple Parameters + +- For an entry with multiple parameters being evaluated, all parameters are + evaluated using a logical "**AND**" therefor they mist all be a match for the + entry to be returned as a result. Meaning if you search (**ABC**,**XYZ**) in + the following dataset + +**Dataset** | ID | first name | Last name | |-----|------|----| | 1 | +**ABC** | **XYZ** | | 2 | **ABC** | IJK | | 3 | DEF | **XYZ** | | 4 | +DEF | IJK | + +**Results** | ID | first name | Last name | |-----|------|----| | 1 | +**ABC** | **XYZ** | + +## Entity Used + +``` mermaid + +--- +title: FertiScan DB Structure +--- +%%{init: { + "theme": "default", + "themeCSS": [ + ".er.relationshipLabel { fill: black; }", + ".er.relationshipLabelBox { fill: white; }", + ".er.entityBox { fill: lightgray; }", + "[id^=entity-] .er.entityBox { fill: lightgreen;}", + "[id^=entity-timedimension] .er.entityBox { fill: pink;} ", + "[id^=entity-labeldimension] .er.entityBox { fill: pink;} ", + "[id^=entity-inspectionfactual] .er.entityBox { fill: pink;} " + ] +}}%% +erDiagram + inspection { + uuid id PK + boolean verified + TIMESTAMP upload_date + TIMESTAMP updated_at + uuid inspector_id FK + uuid label_info_id Fk + uuid fertilizer_id FK + uuid picture_set_id FK + } + organization_information{ + uuid id PK + string name + string website + string phone_number + string address + } + label_information{ + uuid id PK + string name + string lot_number + } + + registration_number_information{ + uuid id PK + string identifier + } + + inspection ||--|| label_information : defines + label_information ||--o| organization_information: company + label_information ||--o|registration_number_information: defines + +``` + +## Datastore Signature + +``` python + +def search_inspection( + cursor:Cursor, + fertilizer_name:str, + reg_number:str, + lot_number:str, + inspector_name:str, + date_of_inspection:str, + organization_name:str, + organization_address:str, + organization_phone:str, +): +``` + +## Sequence of searching + +```mermaid +sequenceDiagram + title FertiScan Submit Form + Actor User + participant FS as FertiScan-Datastore + participant Q as Query module + participant DB as Database + + User ->> FS: search_inspection() + FS ->> Q: search_organisation(
organization_name,
organization_address,
organization_phone) + Q --) Q: build query with parameters + Q->>DB: fetch query results + Q -->>FS: list of organisation information + FS --)FS: append list of label_ids to fetch + + FS ->> Q: search_registration_number(
registration_number) + Q --) Q: build query with parameters + Q->>DB: fetch query results + Q -->>FS: list of registration number information + FS --)FS: append list of label_ids to fetch + + FS ->> Q: search_inspection(
fertilizer_name,
lower_bound_date,
upper_bound_date,
lot_number,
label_ids) + Q --) Q: build query with parameters + Q->>DB: fetch query results + Q ->>FS: list of inspection resume + FS-->User: list of inspection resume + +``` diff --git a/tests/fertiscan/db/__init__.py b/tests/fertiscan/db/__init__.py index fba0404a..e69de29b 100644 --- a/tests/fertiscan/db/__init__.py +++ b/tests/fertiscan/db/__init__.py @@ -1,4 +0,0 @@ -import unittest - -if __name__ == "__main__": - unittest.main() diff --git a/tests/fertiscan/db/test_guaranteed_analysis.py b/tests/fertiscan/db/test_guaranteed_analysis.py index 20a310a4..e8b21100 100644 --- a/tests/fertiscan/db/test_guaranteed_analysis.py +++ b/tests/fertiscan/db/test_guaranteed_analysis.py @@ -316,3 +316,79 @@ def test_get_all_guaranteed_analysis(self): self.assertIn(guaranteed_analysis_id_2, guaranteed_analysis_dict) guaranteed_item = guaranteed_analysis_dict[guaranteed_analysis_id_2] self.assertEqual(guaranteed_item[1], other_name) + + def test_delete_guaranteed_analysis(self): + guaranteed_analysis_id = nutrients.new_guaranteed_analysis( + self.cursor, + self.guaranteed_analysis_name, + self.guaranteed_analysis_value, + self.guaranteed_analysis_unit, + self.label_information_id, + self.language, + self.element_id, + False, + ) + self.assertTrue(validator.is_valid_uuid(guaranteed_analysis_id)) + guaranteeds = nutrients.get_all_guaranteeds(cursor=self.cursor,label_id=self.label_information_id) + self.assertEqual(len(guaranteeds),1) + + nutrients.delete_guaranteed_analysis(cursor=self.cursor,label_id=self.label_information_id) + + guaranteeds = nutrients.get_all_guaranteeds(cursor=self.cursor,label_id=self.label_information_id) + self.assertEqual(len(guaranteeds),0) + + def test_upsert_guaranteed_analysis(self): + default_value = metadata.Value( + value=self.guaranteed_analysis_value, + unit=self.guaranteed_analysis_unit, + name=self.guaranteed_analysis_name, + edited=False + ) + + en = [default_value] + fr = [default_value] + og_model = metadata.GuaranteedAnalysis( + title=None, + is_minimal=None, + en = en, + fr = fr + ) + + guaranteed_analysis_id = nutrients.new_guaranteed_analysis( + self.cursor, + self.guaranteed_analysis_name, + self.guaranteed_analysis_value, + self.guaranteed_analysis_unit, + self.label_information_id, + "en", + self.element_id, + False, + ) + guaranteed_analysis_id = nutrients.new_guaranteed_analysis( + self.cursor, + self.guaranteed_analysis_name, + self.guaranteed_analysis_value, + self.guaranteed_analysis_unit, + self.label_information_id, + "fr", + self.element_id, + False, + ) + + get_ga = nutrients.get_all_guaranteeds(cursor=self.cursor,label_id=self.label_information_id) + self.assertEqual(len(get_ga),2) + + en.append(default_value) + fr.append(default_value) + + updated_ga = metadata.GuaranteedAnalysis( + title=None, + is_minimal=None, + en = en, + fr = fr + ) + + nutrients.upsert_guaranteed_analysis(self.cursor,label_id=self.label_information_id,GA=updated_ga.model_dump()) + updated_ga = nutrients.get_guaranteed_by_label(cursor=self.cursor,label_id=self.label_information_id) + self.assertEqual(len(updated_ga),len(en)+len(fr)) + self.assertNotEqual(len(get_ga),updated_ga) diff --git a/tests/fertiscan/db/test_ingredient.py b/tests/fertiscan/db/test_ingredient.py index 72202880..2b210c37 100644 --- a/tests/fertiscan/db/test_ingredient.py +++ b/tests/fertiscan/db/test_ingredient.py @@ -8,6 +8,7 @@ import datastore.db as db from datastore.db.metadata import validator +import fertiscan.db.metadata.inspection as metadata from fertiscan.db.queries import ingredient, label DB_CONNECTION_STRING = os.environ.get("FERTISCAN_DB_URL") @@ -170,3 +171,108 @@ def test_get_ingredient_json_record_keeping(self): self.assertEqual(len(ingredient_obj.get("ingredients").get("fr")),1) # make sure that the record keeping label does not display any ingredients self.assertEqual(len(ingredient_empty.get("ingredients").get("fr")),0) + + def test_get_ingredient_label(self): + ingredient_id = ingredient.new_ingredient( + self.cursor, + self.ingredient_name, + self.value, + self.unit, + self.label_id, + self.language, + False, + False, + False, + ) + self.assertTrue(validator.is_valid_uuid(ingredient_id)) + + get_ingredient = ingredient.get_ingredient_label(cursor=self.cursor,label_id=self.label_id) + self.assertEqual(len(get_ingredient),1) + self.assertEqual(get_ingredient[0][0],ingredient_id) + + def test_delete_ingredient_label(self): + ingredient_id = ingredient.new_ingredient( + self.cursor, + self.ingredient_name, + self.value, + self.unit, + self.label_id, + self.language, + False, + False, + False, + ) + self.assertTrue(validator.is_valid_uuid(ingredient_id)) + + og_ingredient = ingredient.get_ingredient_label( + cursor=self.cursor, + label_id=self.label_id, + ) + self.assertEqual(len(og_ingredient),1) + self.assertEqual(og_ingredient[0][0],ingredient_id) + + nb_row = ingredient.delete_ingredient_label( + cursor=self.cursor, + label_id=self.label_id + ) + self.assertEqual(nb_row,len(og_ingredient)) + deleted_ingredient = ingredient.get_ingredient_label( + cursor=self.cursor, + label_id=self.label_id, + ) + self.assertEqual(len(deleted_ingredient),0) + self.assertEqual(og_ingredient[0][0],ingredient_id) + + def test_upsert_ingredient_label(self): + ingredient_id1 = ingredient.new_ingredient( + self.cursor, + self.ingredient_name, + self.value, + self.unit, + self.label_id, + "fr", + False, + False, + False, + ) + ingredient_id2 = ingredient.new_ingredient( + self.cursor, + self.ingredient_name, + self.value, + self.unit, + self.label_id, + "en", + False, + False, + False, + ) + og_ingredient = ingredient.get_ingredient_label( + cursor=self.cursor, + label_id=self.label_id, + ) + self.assertEqual(len(og_ingredient),2) + + # Creating the structure for the upsert + default_ingredient = metadata.Value( + value=self.value, + unit=self.unit, + name=self.ingredient_name, + edited=True + ) + en = [default_ingredient,default_ingredient] + fr = [default_ingredient,default_ingredient] + ingredient_dict = metadata.ValuesObjects(en=en,fr=fr) + ingredient.upsert_ingredient( + cursor=self.cursor, + label_id=self.label_id, + ingredients=ingredient_dict.model_dump() + ) + # Validating the data has been correctly deleted and reinserted + updated_ingredients = ingredient.get_ingredient_label( + cursor=self.cursor, + label_id=self.label_id, + ) + self.assertEqual(len(updated_ingredients),len(en)+len(fr)) + for record in updated_ingredients: + self.assertNotEqual(record[0],ingredient_id1) + self.assertNotEqual(record[0],ingredient_id2) diff --git a/tests/fertiscan/db/test_inspection.py b/tests/fertiscan/db/test_inspection.py index d4bcbf7e..8bbe712e 100644 --- a/tests/fertiscan/db/test_inspection.py +++ b/tests/fertiscan/db/test_inspection.py @@ -1,15 +1,19 @@ """ This is a test script for the database packages. -It tests the functions in the inspection. +It tests the functions in the inspection module. """ import os import unittest +from datetime import datetime +from time import sleep + import datastore.db as db +from datastore import Role from datastore.db.metadata import picture_set, validator -from datastore.db.queries import picture, user -from fertiscan.db.queries import inspection +from datastore.db.queries import picture, user, container +from fertiscan.db.queries import inspection, label, organization DB_CONNECTION_STRING = os.environ.get("FERTISCAN_DB_URL") if DB_CONNECTION_STRING is None or DB_CONNECTION_STRING == "": @@ -27,11 +31,21 @@ def setUp(self): db.create_search_path(self.con, self.cursor, DB_SCHEMA) self.user_email = "testessr@email" - self.user_id = user.register_user(self.cursor, self.user_email) + self.user_id = user.register_user( + self.cursor, self.user_email, Role.INSPECTOR.value + ) self.folder_name = "test-folder" self.picture_set = picture_set.build_picture_set_metadata(self.user_id, 1) + self.container_id = container.create_container( + self.cursor, "test-container", self.user_id, False, "test-fertiscan-user" + ) self.picture_set_id = picture.new_picture_set( - self.cursor, self.picture_set, self.user_id, self.folder_name + self.cursor, + self.picture_set, + self.user_id, + self.folder_name, + container_id=self.container_id, + parent_id=None, ) def tearDown(self): @@ -39,25 +53,46 @@ def tearDown(self): db.end_query(self.con, self.cursor) def test_new_inspection(self): - inspection_id = inspection.new_inspection( - self.cursor, self.user_id, self.picture_set_id, False + inspection_id, upload_date = inspection.new_inspection( + cursor=self.cursor, + user_id=self.user_id, + picture_set_id=self.picture_set_id, + label_id=None, + container_id=self.container_id, + verified=False, ) self.assertTrue(validator.is_valid_uuid(inspection_id)) + self.assertIsInstance(upload_date, datetime) def test_is_inspection_verified(self): inspection_id = inspection.new_inspection( - self.cursor, self.user_id, self.picture_set_id, False - ) + cursor=self.cursor, + user_id=self.user_id, + picture_set_id=self.picture_set_id, + label_id=None, + container_id=self.container_id, + verified=False, + )[0] inspection_id2 = inspection.new_inspection( - self.cursor, self.user_id, self.picture_set_id, True - ) + cursor=self.cursor, + user_id=self.user_id, + picture_set_id=self.picture_set_id, + label_id=None, + container_id=self.container_id, + verified=True, + )[0] self.assertFalse(inspection.is_inspection_verified(self.cursor, inspection_id)) self.assertTrue(inspection.is_inspection_verified(self.cursor, inspection_id2)) def test_get_inspection(self): inspection_id = inspection.new_inspection( - self.cursor, self.user_id, self.picture_set_id, False - ) + cursor=self.cursor, + user_id=self.user_id, + picture_set_id=self.picture_set_id, + label_id=None, + container_id=self.container_id, + verified=False, + )[0] inspection_data = inspection.get_inspection(self.cursor, inspection_id) self.assertEqual(inspection_data[0], False) self.assertEqual(inspection_data[3], self.user_id) @@ -65,11 +100,21 @@ def test_get_inspection(self): def test_get_all_user_inspection(self): inspection_id = inspection.new_inspection( - self.cursor, self.user_id, self.picture_set_id, False - ) + cursor=self.cursor, + user_id=self.user_id, + picture_set_id=self.picture_set_id, + label_id=None, + container_id=self.container_id, + verified=False, + )[0] inspection_id2 = inspection.new_inspection( - self.cursor, self.user_id, self.picture_set_id, True - ) + cursor=self.cursor, + user_id=self.user_id, + picture_set_id=self.picture_set_id, + label_id=None, + container_id=self.container_id, + verified=False, + )[0] inspection_data = inspection.get_all_user_inspection(self.cursor, self.user_id) self.assertEqual(len(inspection_data), 2) self.assertEqual(inspection_data[0][0], inspection_id) @@ -77,11 +122,21 @@ def test_get_all_user_inspection(self): def test_get_all_user_inspection_filter_verified(self): inspection_id = inspection.new_inspection( - self.cursor, self.user_id, self.picture_set_id, False - ) + cursor=self.cursor, + user_id=self.user_id, + picture_set_id=self.picture_set_id, + label_id=None, + container_id=self.container_id, + verified=False, + )[0] inspection_id2 = inspection.new_inspection( - self.cursor, self.user_id, self.picture_set_id, True - ) + cursor=self.cursor, + user_id=self.user_id, + picture_set_id=self.picture_set_id, + label_id=None, + container_id=self.container_id, + verified=True, + )[0] inspection_data = inspection.get_all_user_inspection_filter_verified( self.cursor, self.user_id, True ) @@ -112,3 +167,31 @@ def test_get_all_user_inspection_filter_verified(self): # self.assertEqual(len(inspection_data), 2) # self.assertEqual(inspection_data[0][0], inspection_id) # self.assertEqual(inspection_data[1][0], inspection_id2) + + def test_update_inspection(self): + inspection_id, upload_date = inspection.new_inspection( + cursor=self.cursor, + user_id=self.user_id, + picture_set_id=self.picture_set_id, + label_id=None, + container_id=self.container_id, + verified=False, + ) + self.con.commit() # need to commit to get the time change + sleep(2) + updated_at = inspection.update_inspection( + cursor=self.cursor, + inspection_id=inspection_id, + verified=True, + inspection_comment="Test comment", + ) + inspection_data = inspection.get_inspection(self.cursor, inspection_id) + inspection.delete_inspection(self.cursor, inspection_id, self.user_id) + picture.delete_picture_set(self.cursor, self.picture_set_id) + container.delete_container(self.cursor, self.container_id) + user.delete_user(self.cursor, self.user_id) + self.con.commit() + + self.assertTrue(inspection_data[0]) + self.assertNotEqual(updated_at, upload_date) + self.assertEqual(updated_at, inspection_data[2]) diff --git a/tests/fertiscan/db/test_label.py b/tests/fertiscan/db/test_label.py index d92ef205..232941e7 100644 --- a/tests/fertiscan/db/test_label.py +++ b/tests/fertiscan/db/test_label.py @@ -31,6 +31,7 @@ def setUp(self): self.guaranteed_analysis_title_en = "guaranteed_analysis" self.guaranteed_analysis_title_fr = "analyse_garantie" self.guaranteed_is_minimal = False + self.record_keeping = False def tearDown(self): self.con.rollback() @@ -106,3 +107,54 @@ def test_get_label_information_json(self): def test_get_label_information_json_wrong_label_id(self): with self.assertRaises(label.LabelInformationNotFoundError): label.get_label_information_json(self.cursor, str(uuid.uuid4())) + + def test_update_sub_label(self): + label_information_id = label.new_label_information( + self.cursor, + self.product_name, + self.lot_number, + self.npk, + self.n, + self.p, + self.k, + self.guaranteed_analysis_title_en, + self.guaranteed_analysis_title_fr, + self.guaranteed_is_minimal, + self.record_keeping, + ) + self.assertTrue(validator.is_valid_uuid(label_information_id)) + label_data = label.get_label_information(self.cursor, label_information_id) + new_name = "new_name" + new_lot_number = "new lot number" + new_npk = "new npk" + new_npk_numerical_val = 17 + new_english_title = "new title" + + label.update_label_info( + cursor=self.cursor, + label_id=label_information_id, + name=new_name, + lot_number=new_lot_number, + npk=new_npk, + n=new_npk_numerical_val, + p=new_npk_numerical_val, + k=self.k, + title_en=new_english_title, + title_fr=self.guaranteed_analysis_title_fr, + is_minimal=not self.guaranteed_is_minimal, + record_keeping=not self.record_keeping, + ) + + updated_label = label.get_label_information(self.cursor, label_information_id) + + self.assertEqual(updated_label[0], label_data[0]) + self.assertEqual(updated_label[1], new_name) + self.assertEqual(updated_label[2], new_lot_number) + self.assertEqual(updated_label[3], new_npk) + self.assertEqual(updated_label[4], new_npk_numerical_val) + self.assertEqual(updated_label[5], new_npk_numerical_val) + self.assertEqual(updated_label[6], self.k) # We did not change this one + self.assertEqual(updated_label[7], new_english_title) + self.assertEqual(updated_label[8], self.guaranteed_analysis_title_fr) + self.assertEqual(updated_label[9], not self.guaranteed_is_minimal) + self.assertEqual(updated_label[10], not self.record_keeping) diff --git a/tests/fertiscan/db/test_metric.py b/tests/fertiscan/db/test_metric.py index 1707b0ee..9090b194 100644 --- a/tests/fertiscan/db/test_metric.py +++ b/tests/fertiscan/db/test_metric.py @@ -216,3 +216,100 @@ def test_get_full_metric(self): self.assertEqual(metric_data[3], self.unit_to_si_unit) self.assertEqual(metric_data[4], self.metric_edited) self.assertEqual(metric_data[5], self.metric_type) + + def test_delete_metric_by_type(self): + volume_unit = "ml" + weight_unit_imperial = "lb" + weight_unit_metric = "kg" + density_unit = "lb/ml" + metric.new_metric( + self.cursor, + self.metric_value, + volume_unit, + self.label_id, + "volume", + self.metric_edited, + ) + metric.new_metric( + self.cursor, + self.metric_value, + weight_unit_imperial, + self.label_id, + "weight", + self.metric_edited, + ) + metric.new_metric( + self.cursor, + self.metric_value, + weight_unit_metric, + self.label_id, + "weight", + self.metric_edited, + ) + metric.new_metric( + self.cursor, + self.metric_value, + density_unit, + self.label_id, + "density", + self.metric_edited, + ) + og_metrics = metric.get_metric_by_label(cursor=self.cursor,label_id=self.label_id) + + self.assertEqual(len(og_metrics),4) + # delete the 2 weights + metric.delete_metric_by_type( + cursor=self.cursor, + label_id=self.label_id, + metric_type="weight" + ) + deleted_metric = metric.get_metric_by_label(cursor=self.cursor,label_id=self.label_id) + self.assertEqual(len(deleted_metric),len(og_metrics)-2) + + def test_delete_metric(self): + volume_unit = "ml" + weight_unit_imperial = "lb" + weight_unit_metric = "kg" + density_unit = "lb/ml" + metric.new_metric( + self.cursor, + self.metric_value, + volume_unit, + self.label_id, + "volume", + self.metric_edited, + ) + metric.new_metric( + self.cursor, + self.metric_value, + weight_unit_imperial, + self.label_id, + "weight", + self.metric_edited, + ) + metric.new_metric( + self.cursor, + self.metric_value, + weight_unit_metric, + self.label_id, + "weight", + self.metric_edited, + ) + metric.new_metric( + self.cursor, + self.metric_value, + density_unit, + self.label_id, + "density", + self.metric_edited, + ) + og_metrics = metric.get_metric_by_label(cursor=self.cursor,label_id=self.label_id) + self.assertEqual(len(og_metrics),4) + + metric.delete_metric( + cursor=self.cursor, + label_id=self.label_id + ) + # Verify there are no metrics left + deleted_metrics = metric.get_metric_by_label(cursor=self.cursor,label_id=self.label_id) + self.assertEqual(len(deleted_metrics),0) diff --git a/tests/fertiscan/db/test_organization.py b/tests/fertiscan/db/test_organization.py index e113999e..33689191 100644 --- a/tests/fertiscan/db/test_organization.py +++ b/tests/fertiscan/db/test_organization.py @@ -290,6 +290,8 @@ def test_update_organization_info(self): new_name = "new-name" new_website = "www.new.com" new_phone = "987654321" + is_main_contact = False + updated_address = "1223 street" id = organization.new_organization_information( self.cursor, self.address, @@ -298,20 +300,29 @@ def test_update_organization_info(self): self.phone, self.label_information_id, False, - True, + is_main_contact, ) old_data = organization.get_organization_info(self.cursor, id) self.assertEqual(old_data[0], self.name) self.assertEqual(old_data[1], self.website) self.assertEqual(old_data[2], self.phone) self.assertEqual(old_data[3], self.address) + self.assertEqual(old_data[6], is_main_contact) organization.update_organization_info( - self.cursor, id, new_name, new_website, new_phone + cursor=self.cursor, + information_id=id, + name=new_name, + website=new_website, + phone_number=new_phone, + is_main_contact=not is_main_contact, + address=updated_address, ) data = organization.get_organization_info(self.cursor, id) self.assertEqual(data[0], new_name) self.assertEqual(data[1], new_website) self.assertEqual(data[2], new_phone) + self.assertEqual(data[3], updated_address) + self.assertEqual(data[6], not is_main_contact) def test_new_organization_information(self): id = organization.new_organization_information( @@ -526,12 +537,38 @@ def test_new_organization_no_location(self): ) self.assertTrue(validator.is_valid_uuid(organization_id)) - def test_upsert_organization(self): + def test_upsert_organization_function(self): organization_id = organization.new_organization( self.cursor, self.name, "wrong-website", "wrong-phone", "wrong-address" ) - update_id = organization.upsert_organization(self.cursor, str(self.org_info_id)) + update_id = organization.upsert_organization_function( + self.cursor, str(self.org_info_id) + ) + self.assertEqual(organization_id, update_id) + organization_data = organization.get_organization( + self.cursor, str(organization_id) + ) + self.assertEqual(organization_data[0], self.name) + self.assertEqual(organization_data[1], self.website) + self.assertEqual(organization_data[2], self.phone) + self.assertEqual(organization_data[3], self.address) + + def test_upsert_organization(self): + organization_id = organization.new_organization( + cursor=self.cursor, + name=self.name, + website="wrong-website", + phone_number="wrong-phone", + address="wrong-address", + ) + update_id = organization.upsert_organization( + cursor=self.cursor, + name=self.name, + website=self.website, + phone_number=self.phone, + address=self.address, + ) self.assertEqual(organization_id, update_id) organization_data = organization.get_organization( self.cursor, str(organization_id) @@ -554,3 +591,112 @@ def test_get_organization(self): def test_get_organization_not_found(self): with self.assertRaises(organization.OrganizationNotFoundError): organization.get_organization(self.cursor, str(uuid.uuid4())) + + def test_search_organization(self): + organization_id = organization.new_organization( + cursor=self.cursor, + name=self.name, + website=self.website, + phone_number=self.phone, + address=self.address, + ) + organization.new_organization( + cursor=self.cursor, + name="other-name", + website="other-website", + phone_number="other-phone", + address="other-address", + ) + organization.new_organization( + cursor=self.cursor, + name="partial-name", + website="partial-website", + phone_number="partial-phone", + address=self.address, + ) + # test search by name + data = organization.search_organization( + cursor=self.cursor, + name=self.name, + address=None, + phone_number=None, + website=None, + ) + self.assertEqual(len(data), 1) + self.assertEqual(data[0][0], organization_id) + self.assertEqual(data[0][1], self.name) + self.assertEqual(data[0][2], self.website) + self.assertEqual(data[0][3], self.phone) + self.assertEqual(data[0][4], self.address) + # test search Wrong name + data = organization.search_organization( + cursor=self.cursor, + name="wrong name", + address=None, + phone_number=None, + website=None, + ) + self.assertEqual(len(data), 0) + # test search by address + data = organization.search_organization( + cursor=self.cursor, + name=None, + address=self.address, + phone_number=None, + website=None, + ) + self.assertEqual(len(data), 2) + # test search by phone + data = organization.search_organization( + cursor=self.cursor, + name=None, + address=None, + phone_number=self.phone, + website=None, + ) + self.assertEqual(len(data), 1) + # test search by website + data = organization.search_organization( + cursor=self.cursor, + name=None, + address=None, + phone_number=None, + website=self.website, + ) + self.assertEqual(len(data), 1) + # test search by multiple fields + data = organization.search_organization( + cursor=self.cursor, + name=self.name, + address=self.address, + phone_number=self.phone, + website=self.website, + ) + self.assertEqual(len(data), 1) + self.assertEqual(data[0][0], organization_id) + # test search by multiple fields but one wrong + data = organization.search_organization( + cursor=self.cursor, + name=self.name, + address=self.address, + phone_number=self.phone, + website="wrong-website", + ) + self.assertEqual(len(data), 0) + # testing partial matching search (2 rows have the same address, but the rest is different) + data = organization.search_organization( + cursor=self.cursor, + name=None, + address=self.address, + phone_number=None, + website=None, + ) + self.assertEqual(len(data), 2) + data = organization.search_organization( + cursor=self.cursor, + name=self.name, + address=self.address, + phone_number=None, + website=None, + ) + self.assertEqual(len(data), 1) diff --git a/tests/fertiscan/db/test_registration_number.py b/tests/fertiscan/db/test_registration_number.py index 88ffc312..7d08a65f 100644 --- a/tests/fertiscan/db/test_registration_number.py +++ b/tests/fertiscan/db/test_registration_number.py @@ -92,7 +92,6 @@ def test_get_registration_numbers_json_empty(self): self.cursor, self.label_id ) RegistrationNumber.model_validate(registration_numbers) - print(registration_numbers) self.assertEqual(len(registration_numbers["registration_numbers"]), 0) def test_update_registration_number(self): @@ -107,27 +106,77 @@ def test_update_registration_number(self): old_data = registration_number.get_registration_numbers_json( self.cursor, self.label_id ) + reg_number_model = RegistrationNumber.model_validate(old_data["registration_numbers"][0]) self.assertEqual( - old_data["registration_numbers"][0]["registration_number"], + reg_number_model.registration_number, self.registration_number, ) new_reg_number = "654321" - new_dict = old_data["registration_numbers"] - new_dict[0]["registration_number"] = new_reg_number - + reg_number_model.registration_number = new_reg_number + reg_number_model.edited = True + reg_number_model.is_an_ingredient = not self.is_an_ingredient + + registration_number.update_registration_number( - self.cursor, - json.dumps(new_dict), - self.label_id, + cursor=self.cursor, + registration_numbers=[reg_number_model.model_dump()], + label_id=self.label_id, ) - new_data = registration_number.get_registration_numbers_json( + new_data = registration_number.get_registration_numbers_from_label( self.cursor, self.label_id + )[0] + + self.assertEqual(new_data[0],new_reg_number) + self.assertNotEqual(new_data[0],self.registration_number) + self.assertTrue(new_data[3]) + self.assertEqual(new_data[1],not self.is_an_ingredient) + + def test_delete_registration_numbers(self): + registration_number.new_registration_number( + self.cursor, + self.registration_number, + self.label_id, + self.is_an_ingredient, + self.read_name, + self.edited, ) + + og_data = registration_number.get_registration_numbers_from_label(self.cursor,self.label_id) - self.assertEqual( - new_data["registration_numbers"][0]["registration_number"], new_reg_number - ) - self.assertNotEqual( - new_data["registration_numbers"][0]["registration_number"], + self.assertEqual(len(og_data),1) + + affected_row = registration_number.delete_registration_numbers(cursor=self.cursor,label_id=self.label_id) + self.assertEqual(len(og_data),affected_row) + deleted_data = registration_number.get_registration_numbers_from_label(self.cursor,self.label_id) + + self.assertEqual(len(deleted_data),0) + + def test_search_registration_number(self): + reg_nb_id = registration_number.new_registration_number( + self.cursor, self.registration_number, + self.label_id, + self.is_an_ingredient, + self.read_name, + self.edited, + ) + other_reg_nb = "654321" + other_reg_nb_id = registration_number.new_registration_number( + self.cursor, + other_reg_nb, + self.label_id, + self.is_an_ingredient, + self.read_name, + self.edited, + ) + data = registration_number.search_registration_number( + cursor=self.cursor, + registration_number=self.registration_number, ) + self.assertEqual(len(data),1) + self.assertEqual(data[0][0],reg_nb_id) + self.assertEqual(data[0][1],self.registration_number) + self.assertEqual(data[0][2],self.label_id) + self.assertEqual(data[0][3],self.is_an_ingredient) + self.assertEqual(data[0][4],self.read_name) + self.assertEqual(data[0][5],self.edited) diff --git a/tests/fertiscan/db/test_sub_label.py b/tests/fertiscan/db/test_sub_label.py index 21958fae..cfa7220c 100644 --- a/tests/fertiscan/db/test_sub_label.py +++ b/tests/fertiscan/db/test_sub_label.py @@ -317,3 +317,44 @@ def test_sub_label_insertion_raises_exception_for_null_or_empty_texts(self): self.sub_type_id, False, ) + + def test_upsert_sub_label(self): + sub_label.new_sub_label( + self.cursor, + self.text_fr, + self.text_en, + self.label_id, + self.sub_type_id, + False, + ) + od_data = sub_label.get_all_sub_label(cursor=self.cursor,label_id=self.label_id) + self.assertEqual(len(od_data),1) + data = { + self.type_en: { + "en": [ + "1. Dissolve 50g in 10L of water.", + "2. Apply every 2 weeks.", + "3. Store in a cool, dry place." + ], + "fr": [ + "1. Dissoudre 50g dans 10L d'eau.", + "2. Appliquer toutes les 2 semaines.", + "3. Conserver dans un endroit frais et sec." + ] + }, + self.type_2_en: { + "en": [ + "Keep out of reach of children.", + "Avoid contact with skin and eyes." + ], + "fr": [ + "Tenir hors de port\u00e9e des enfants.", + "\u00c9viter le contact avec la peau et les yeux." + ] + }, + } + + sub_label.upsert_sub_label(self.cursor,self.label_id,data) + + updated = sub_label.get_all_sub_label(cursor=self.cursor,label_id=self.label_id) + self.assertEqual(len(updated),5) diff --git a/tests/fertiscan/test_datastore.py b/tests/fertiscan/test_datastore.py index bedf00c2..db79e8c1 100644 --- a/tests/fertiscan/test_datastore.py +++ b/tests/fertiscan/test_datastore.py @@ -8,14 +8,17 @@ import json import os import unittest +from uuid import UUID from PIL import Image +from datetime import datetime import datastore import datastore.db as db import datastore.db.metadata.validator as validator import fertiscan import fertiscan.db.metadata.inspection as metadata +from datastore.db.queries import container from datastore.db.queries import picture from fertiscan.db.queries import ( ingredient, @@ -25,6 +28,7 @@ nutrients, organization, sub_label, + errors, ) BLOB_CONNECTION_STRING = os.environ["FERTISCAN_STORAGE_URL"] @@ -60,6 +64,8 @@ def loop_into_empty_dict(dict_data): continue else: passing = False + elif isinstance(value, datetime): + continue elif isinstance(value, bool): if not value: continue @@ -86,20 +92,35 @@ def setUp(self): db.create_search_path(self.con, self.cursor, DB_SCHEMA) self.user_email = "testesss@email" self.tier = "test-user" + self.user_role = datastore.Role.INSPECTOR self.user = asyncio.run( datastore.new_user( - self.cursor, self.user_email, BLOB_CONNECTION_STRING, self.tier - ) - ) - self.container_client = asyncio.run( - datastore.get_user_container_client( - self.user.id, + self.cursor, + self.user_email, BLOB_CONNECTION_STRING, - BLOB_ACCOUNT, - BLOB_KEY, self.tier, + self.user_role, + ) + ) + self.container_model = next(iter(self.user.model.containers.values())) + self.container_controller = asyncio.run( + datastore.get_container_controller( + self.cursor, self.container_model.id, BLOB_CONNECTION_STRING, None + ) + ) + self.container_client = self.container_controller.container_client + # Assure the user Storage space exists + self.assertTrue(self.container_client.exists()) + self.folder: datastore.Folder = asyncio.run( + self.container_controller.create_folder( + cursor=self.cursor, + performed_by=self.user.id, + folder_name="test-folder-fertiscan", + nb_pictures=0, + parent_folder_id=None, ) ) + self.folder_id = self.folder.id self.image = Image.new("RGB", (1980, 1080), "blue") self.image_byte_array = io.BytesIO() @@ -159,27 +180,31 @@ def tearDown(self): except Exception as e: print(e) - def test_register_analysis(self): - # print(self.user.id) - self.assertTrue(self.container_client.exists()) - analysis = asyncio.run( - fertiscan.register_analysis( + def test_new_inspection(self): + + inspection_controller: fertiscan.InspectionController = ( + fertiscan.new_inspection( self.cursor, - self.container_client, self.user.id, - [self.pic_encoded, self.pic_encoded], self.analysis_json, + self.container_controller.id, + folder_id=self.folder_id, ) ) - self.assertIsNotNone(analysis) - self.assertTrue(validator.is_valid_uuid(analysis["inspection_id"])) - inspection_id = analysis["inspection_id"] - - self.assertTrue(validator.is_valid_uuid(analysis["product"]["label_id"])) - label_id = analysis["product"]["label_id"] + self.assertIsNotNone(inspection_controller) + self.assertIsInstance(inspection_controller, fertiscan.InspectionController) + analysis = inspection_controller.model + self.assertTrue(validator.is_valid_uuid(inspection_controller.id)) + self.assertIsInstance(analysis, fertiscan.data_inspection.Inspection) + analysis = fertiscan.data_inspection.Inspection.model_validate(analysis) + inspection_id = analysis.inspection_id + self.assertEqual(inspection_id, inspection_controller.id) + + self.assertTrue(validator.is_valid_uuid(analysis.product.label_id)) + label_id = analysis.product.label_id metrics = metric.get_metric_by_label( - self.cursor, str(analysis["product"]["label_id"]) + self.cursor, str(analysis.product.label_id) ) self.assertIsNotNone(metrics) @@ -188,11 +213,9 @@ def test_register_analysis(self): ) # There are 4 metrics in the analysis_json (1 volume, 1 density, 2 weight ) ingredients = ingredient.get_ingredient_json( - self.cursor, str(analysis["product"]["label_id"]) + self.cursor, str(analysis.product.label_id) ) self.assertIsNotNone(ingredients) - # print(ingredients) - # specifications = specification.get_all_specifications( # cursor=self.cursor, label_id=str(analysis["product"]["label_id"]) # ) @@ -210,7 +233,7 @@ def test_register_analysis(self): # len(nutrients_data), self.nb_micronutrients # ) # There are 2 nutrients in the analysis_json sub_labels = sub_label.get_sub_label_json( - self.cursor, str(analysis["product"]["label_id"]) + self.cursor, str(analysis.product.label_id) ) self.assertIsNotNone(sub_labels) @@ -222,6 +245,12 @@ def test_register_analysis(self): self.maxDiff = None # self.assertDictEqual(analysis, original_dataset) + inspection_fk = inspection.get_inspection_fk( + cursor=self.cursor, inspection_id=inspection_id + ) + self.assertEqual(self.user.id, inspection_fk[1]) + self.assertEqual(self.folder_id, inspection_fk[2]) + self.assertEqual(self.container_model.id, inspection_fk[3]) # Verify OLAP Layer query = "SELECT EXISTS (SELECT 1 FROM inspection_factual WHERE inspection_factual.inspection_id = %s)" @@ -238,12 +267,11 @@ def test_register_analysis(self): label_dimension = label.get_label_dimension(self.cursor, label_id) - self.assertEqual( - str(label_dimension[1][0]), str(analysis["organizations"][0]["id"]) - ) - self.assertEqual( - str(label_dimension[1][1]), str(analysis["organizations"][1]["id"]) - ) + company_info_id = str(label_dimension[1]) + manufacturer_info_id = str(label_dimension[3]) + + self.assertEqual(str(label_dimension[1][0]), str(analysis.organizations[0].id)) + self.assertEqual(str(label_dimension[1][1]), str(analysis.organizations[1].id)) self.assertEqual(len(label_dimension[2]), self.nb_instructions) @@ -258,7 +286,7 @@ def test_register_analysis(self): self.assertEqual(len(label_dimension[11]), 1) self.assertEqual(len(label_dimension[12]), 1) - def test_register_analysis_empty(self): + def test_new_inspection_empty(self): empty_analysis = { "organizations": [], "fertiliser_name": None, @@ -272,22 +300,33 @@ def test_register_analysis_empty(self): "instructions_en": [], "cautions_fr": [], "instructions_fr": [], - "guaranteed_analysis_en": {"title": None, "is_minimal": False, "nutrients": []}, - "guaranteed_analysis_fr": {"title": None, "is_minimal": False, "nutrients": []}, + "guaranteed_analysis_en": { + "title": None, + "is_minimal": False, + "nutrients": [], + }, + "guaranteed_analysis_fr": { + "title": None, + "is_minimal": False, + "nutrients": [], + }, } formatted_analysis = metadata.build_inspection_import( - empty_analysis, self.user.id - ) - picture_set_id = picture.new_picture_set( - self.cursor, json.dumps({}), self.user.id + empty_analysis, self.user.id, self.folder_id, self.container_model.id ) inspection_dict = inspection.new_inspection_with_label_info( - self.cursor, self.user.id, picture_set_id, formatted_analysis + self.cursor, + self.user.id, + self.folder_id, + formatted_analysis.model_dump_json(), + ) + inspection_model = fertiscan.data_inspection.Inspection.model_validate( + inspection_dict ) - inspection_id = inspection_dict["inspection_id"] - label_id = inspection_dict["product"]["label_id"] + inspection_id = inspection_model.inspection_id + label_id = inspection_model.product.label_id self.assertTrue(validator.is_valid_uuid(inspection_id)) # Verify the data @@ -297,152 +336,147 @@ def test_register_analysis_empty(self): # Verify getters inspection_data = metadata.build_inspection_export(self.cursor, inspection_id) - inspection_data = json.loads(inspection_data) - # TODO: investigate if this should pass and why it doesn't + inspection_data = metadata.Inspection.model_validate(inspection_data) # Make sure the inspection data is either a empty array or None - # self.assertTrue(loop_into_empty_dict(inspection_data)) + self.assertTrue(loop_into_empty_dict(inspection_data.model_dump())) def test_register_analysis_invalid_user(self): with self.assertRaises(Exception): - asyncio.run( - fertiscan.register_analysis( - self.cursor, - self.container_client, - "invalid_user_id", - [self.pic_encoded, self.pic_encoded], - self.analysis_json, - ) + fertiscan.new_inspection( + self.cursor, + "invalid_user_id", + self.analysis_json, + self.container_model.id, + self.folder_id, ) def test_register_analysy_missing_key(self): - self.analysis_json.pop("specification_en", None) + self.analysis_json.pop("instructions_en", None) with self.assertRaises(fertiscan.data_inspection.BuildInspectionImportError): - asyncio.run( - fertiscan.register_analysis( - self.cursor, - self.container_client, - self.user.id, - [self.pic_encoded, self.pic_encoded], - {}, - ) + fertiscan.new_inspection( + self.cursor, + self.user.id, + self.analysis_json, + self.container_model.id, + self.folder_id, ) - def test_get_full_inspection_json(self): - formatted_analysis = metadata.build_inspection_import( - self.analysis_json, self.user.id + def test_get_inspection(self): + inspection_controller = fertiscan.new_inspection( + self.cursor, + self.user.id, + self.analysis_json, + self.container_controller.id, + folder_id=self.folder_id, ) - picture_set_id = picture.new_picture_set( - self.cursor, json.dumps({}), self.user.id + inspection_dict = inspection_controller.model + inspection_model = fertiscan.data_inspection.Inspection.model_validate( + inspection_dict ) - inspection_dict = inspection.new_inspection_with_label_info( - self.cursor, self.user.id, picture_set_id, formatted_analysis + inspection_id = inspection_model.inspection_id + inspection_controller = fertiscan.get_inspection( + cursor=self.cursor, inspection_id=inspection_id + ) + get_inspection_model = metadata.Inspection.model_validate( + inspection_controller.model ) - inspection_id = inspection_dict["inspection_id"] - data = asyncio.run( - fertiscan.get_full_inspection_json(self.cursor, inspection_id) + self.assertEqual(str(get_inspection_model.inspection_id), str(inspection_id)) + self.assertEqual(str(get_inspection_model.inspector_id), str(self.user.id)) + self.maxDiff = None + self.assertDictEqual( + get_inspection_model.model_dump(), inspection_model.model_dump() ) - data = json.loads(data) - self.assertEqual(data["inspection_id"], str(inspection_id)) - self.assertEqual(data["inspector_id"], str(self.user.id)) def test_delete_inspection(self): # Create a new inspection to delete later - with open(TEST_INSPECTION_JSON_PATH, "r") as file: - input_json = json.load(file) - - picture_set_id = asyncio.run( - datastore.create_picture_set( - self.cursor, self.container_client, 0, self.user.id - ) + inspection_controller = fertiscan.new_inspection( + self.cursor, + self.user.id, + self.analysis_json, + self.container_model.id, + self.folder_id, ) - inspection_dict = inspection.new_inspection_with_label_info( - self.cursor, self.user.id, picture_set_id, json.dumps(input_json) - ) - inspection_id = inspection_dict["inspection_id"] + inspection_dict = inspection_controller.model + inspection_id = inspection_dict.inspection_id + self.assertEqual(inspection_controller.id, inspection_id) # Verify the inspection was created by directly querying the database - self.cursor.execute( - "SELECT id FROM inspection WHERE id = %s;", - (inspection_id,), - ) - fetched_inspection_id = self.cursor.fetchone() - self.assertIsNotNone( - fetched_inspection_id, "The inspection should exist before deletion." + self.assertTrue( + inspection.is_a_inspection_id( + cursor=self.cursor, inspection_id=inspection_id + ) ) # Perform the delete operation + self.assertTrue( + container.is_a_container( + self.cursor, inspection_controller.model.container_id + ) + ) deleted_inspection = asyncio.run( - fertiscan.delete_inspection( - self.cursor, inspection_id, self.user.id, self.container_client + inspection_controller.delete_inspection( + cursor=self.cursor, + user_id=self.user.id, ) ) # Verify that the inspection ID matches the one we deleted self.assertIsInstance(deleted_inspection, metadata.DBInspection) - self.assertEqual(str(deleted_inspection.id), inspection_id) + self.assertEqual(deleted_inspection.id, inspection_id) # Ensure that the inspection no longer exists in the database - self.cursor.execute( - "SELECT EXISTS(SELECT 1 FROM inspection WHERE id = %s);", - (inspection_id,), - ) - inspection_exists = self.cursor.fetchone()[0] - self.assertFalse( - inspection_exists, "The inspection should be deleted from the database." - ) + self.assertFalse(inspection.is_a_inspection_id(self.cursor, inspection_id)) # Verify that the picture set associated with the inspection was also deleted - self.cursor.execute( - "SELECT EXISTS(SELECT 1 FROM picture_set WHERE id = %s);", - (picture_set_id,), - ) - picture_set_exists = self.cursor.fetchone()[0] - self.assertFalse( - picture_set_exists, - "The picture set should be deleted from the database.", - ) + self.assertFalse(picture.is_a_picture_set_id(self.cursor, self.folder_id)) # Verify that no blobs associated with the picture set ID remain in the container blobs_after = [blob.name for blob in self.container_client.list_blobs()] self.assertFalse( - any(str(picture_set_id) in blob_name for blob_name in blobs_after), + any(str(self.folder_id) in blob_name for blob_name in blobs_after), "The folder associated with the picture set ID should be deleted from the container.", ) def test_update_inspection(self): - self.assertTrue(self.container_client.exists()) - analysis = asyncio.run( - fertiscan.register_analysis( - self.cursor, - self.container_client, - self.user.id, - [self.pic_encoded, self.pic_encoded], - self.analysis_json, - ) + inspection_controller = fertiscan.new_inspection( + self.cursor, + self.user.id, + self.analysis_json, + self.container_model.id, + self.folder_id, ) - self.assertIsNotNone(analysis) - inspection_id = analysis["inspection_id"] - label_id = analysis["product"]["label_id"] + self.assertIsInstance(inspection_controller, fertiscan.InspectionController) + inspection_id = inspection_controller.id + label_id = inspection_controller.model.product.label_id self.assertTrue(validator.is_valid_uuid(inspection_id)) + + og_inspection_data = inspection.get_inspection(self.cursor, inspection_id) + # Check the updated_at is the same as the the upload date + self.assertEqual(og_inspection_data[1], og_inspection_data[2]) + self.assertEqual( + inspection_controller.model.upload_date, + inspection_controller.model.updated_at, + ) + self.assertEqual(og_inspection_data[1], inspection_controller.model.updated_at) # new values new_product_name = "New Product Name" - untouched_weight = analysis["product"]["metrics"]["weight"][1]["value"] + untouched_weight = inspection_controller.model.product.metrics.weight[1].value new_weight = 1000.0 - untouched_volume = analysis["product"]["metrics"]["volume"]["value"] + untouched_volume = inspection_controller.model.product.metrics.volume.value new_density = 10.0 - old_npk = analysis["product"]["npk"] + old_npk = inspection_controller.model.product.npk new_npk = "10-10-10" new_instruction_en = ["3. of", "2. set"] new_instruction_fr = ["3. de", "2. ensemble"] new_instruction_nb = (len(new_instruction_en) + len(new_instruction_fr)) / 2 new_value = 100.0 - old_value = analysis["guaranteed_analysis"]["fr"][0]["value"] + old_value = inspection_controller.model.guaranteed_analysis.fr[0].value new_title = "Nouveau titre" - old_title = analysis["guaranteed_analysis"]["title"]["fr"] - old_name = analysis["guaranteed_analysis"]["fr"][0]["name"] + old_title = inspection_controller.model.guaranteed_analysis.title.fr + old_name = inspection_controller.model.guaranteed_analysis.fr[0].name new_name = "Nouveau nom" user_feedback = "This is a feedback" new_record_keeping = True @@ -487,40 +521,46 @@ def test_update_inspection(self): new_guaranteed_nb = 2 - old_organizations = analysis["organizations"] + old_organizations = inspection_controller.model.organizations new_organizations = [ { "name": "New Organization", "address": "New Address", - "phone": "New Phone", + "phone_number": "New Phone", "email": "New Email", "website": "New Website", + "is_main_contact": True, } ] # update the dataset - analysis["product"]["name"] = new_product_name - analysis["product"]["metrics"]["weight"][0]["value"] = new_weight - analysis["product"]["metrics"]["weight"][0]["edited"] = True - analysis["product"]["metrics"]["density"]["value"] = new_density - analysis["product"]["metrics"]["density"]["edited"] = True - analysis["product"]["record_keeping"] = new_record_keeping - analysis["product"]["npk"] = new_npk - analysis["instructions"]["en"] = new_instruction_en - analysis["instructions"]["fr"] = new_instruction_fr + to_update = inspection_controller.model.model_copy().model_dump() + to_update["product"]["name"] = new_product_name + to_update["product"]["metrics"]["weight"][0]["value"] = new_weight + to_update["product"]["metrics"]["weight"][0]["edited"] = True + to_update["product"]["metrics"]["density"]["value"] = new_density + to_update["product"]["metrics"]["density"]["edited"] = True + to_update["product"]["record_keeping"] = new_record_keeping + to_update["product"]["npk"] = new_npk + to_update["instructions"]["en"] = new_instruction_en + to_update["instructions"]["fr"] = new_instruction_fr # analysis["specifications"]["en"] = new_specification_en - analysis["cautions"]["en"] = new_cautions_en - analysis["cautions"]["fr"] = new_cautions_fr - analysis["guaranteed_analysis"] = new_guaranteed_analysis - analysis["organizations"] = new_organizations - analysis["inspection_comment"] = user_feedback + to_update["cautions"]["en"] = new_cautions_en + to_update["cautions"]["fr"] = new_cautions_fr + to_update["guaranteed_analysis"] = new_guaranteed_analysis + to_update["organizations"] = new_organizations + to_update["inspection_comment"] = user_feedback old_label_dimension = label.get_label_dimension(self.cursor, label_id) - asyncio.run( - fertiscan.update_inspection( - self.cursor, inspection_id, self.user.id, analysis - ) + inspection_to_update = metadata.Inspection.model_validate(to_update) + updated_inspection = inspection_controller.update_inspection( + self.cursor, + self.user.id, + inspection_to_update, ) + updated_inspection = metadata.Inspection.model_validate(updated_inspection) + # check if the updated_at is updated in the model + # self.assertNotEqual(updated_inspection.updated_at,updated_inspection.upload_date) # This is not passing since you need to commit the cursor to get a different timestamp # check if specifications are updated # specifications = specification.get_all_specifications(self.cursor, label_id) @@ -565,11 +605,14 @@ def test_update_inspection(self): inspection_data = inspection.get_inspection(self.cursor, inspection_id) self.assertEqual(inspection_data[8], user_feedback) + # Verify updated_at is updated in the db + # This is not passing since you need to commit the cursor to get a different timestamp + # self.assertNotEqual(inspection_data[1], og_inspection_data[1]) + # self.assertNotEqual(inspection_data[1], inspection_data[2]) + # Verify organizations are saved orgs = organization.get_organizations_info_label(self.cursor, label_id) - - self.assertEqual(len(orgs), 3) # 2 default + 1 new - self.assertEqual(len(orgs), len(old_organizations) + 1) + self.assertEqual(len(orgs), 1) # There is only 1 new # VERIFY OLAP new_label_dimension = label.get_label_dimension(self.cursor, label_id) @@ -646,11 +689,11 @@ def test_update_inspection(self): } old_guaranteed_nb = new_guaranteed_nb new_guaranteed_nb = 8 - analysis["guaranteed_analysis"] = new_guaranteed_analysis - asyncio.run( - fertiscan.update_inspection( - self.cursor, inspection_id, self.user.id, analysis - ) + to_update = updated_inspection.model_copy().model_dump() + to_update["guaranteed_analysis"] = new_guaranteed_analysis + inspection_to_update = metadata.Inspection.model_validate(to_update) + inspection_controller.update_inspection( + self.cursor, self.user.id, inspection_to_update ) new_label_dimension = label.get_label_dimension(self.cursor, label_id) @@ -659,3 +702,46 @@ def test_update_inspection(self): self.assertEqual(len(new_label_dimension[9]), new_guaranteed_nb) self.assertNotEqual(len(new_label_dimension[9]), len(old_label_dimension[12])) self.assertNotEqual(len(new_label_dimension[9]), old_guaranteed_nb) + + def test_update_inspection_already_verified(self): + inspection_controller: fertiscan.InspectionController = ( + fertiscan.new_inspection( + self.cursor, + self.user.id, + self.analysis_json, + self.container_controller.id, + folder_id=self.folder_id, + ) + ) + inspection_to_update = inspection_controller.model.model_copy() + self.assertFalse(inspection_to_update.verified) + inspection_to_update.verified = True + self.assertTrue(inspection_to_update.verified) + inspection_to_update.product.name = "verified_product" + updated_inspection = inspection_controller.update_inspection( + self.cursor, + self.user.id, + inspection_to_update, + ) + self.assertTrue(updated_inspection.verified) + updated_inspection.product.name = "blocked to be updated" + with self.assertRaises(inspection.InspectionUpdateError): + inspection_controller.update_inspection( + self.cursor, + self.user.id, + updated_inspection, + ), + inspection.InspectionUpdateError, + ) + + def test_search_inspection(self): + """ + The seach inspection function only uses the query modules + and doesn't modify the parameters nor the results from the query modules + + All the module function used to search have been tested individually. + Therefor, we decided there are no gains from testing the global search function + + We are still leaving this in case there are some things to be tested in the future. + """ + self.assertTrue(True)