diff --git a/src/lob_hlpr/hlpr.py b/src/lob_hlpr/hlpr.py index 5db81fc..61bf828 100644 --- a/src/lob_hlpr/hlpr.py +++ b/src/lob_hlpr/hlpr.py @@ -12,6 +12,8 @@ class LobHlpr: """Helper functions for Lobaro tools.""" + LOB_PRINT_LOG_PATH = None + @staticmethod def sn_vid_pid_to_regex( sn: str | None = None, vid: str | None = None, pid: str | None = None @@ -55,7 +57,7 @@ def sn_vid_pid_to_regex( return f"VID:PID={vid or '.*'}:{pid or '.*'}.+SER={sn}" @staticmethod - def lob_print(log_path: str, *args, **kwargs): + def lob_print(log_path: str | None, *args, **kwargs): """Print to the console and log to a file. The log file is rotated when it reaches 256MB and the last two @@ -63,6 +65,8 @@ def lob_print(log_path: str, *args, **kwargs): only if the log handlers are set (i.e. basicConfig loglevel is Debug). """ print(*args, flush=True, **kwargs) + if log_path is None and LobHlpr.LOB_PRINT_LOG_PATH is None: + return # get the directory from the log_path log_dir = os.path.dirname(log_path) os.makedirs(log_dir, exist_ok=True) @@ -148,6 +152,100 @@ def parse_dmc(dmc): return erp_prod_number, batch_number, pcba_serial_number, article_number + @staticmethod + def verify_dmc_prefix(dmc: str, allowed_prefixes: list[str]) -> str: + """Verify if the DMC starts with an allowed prefix. + + If it does, cut any repeated scans + (e.g. MPP-OR019504_1-00781MPP-OR019504_1-00781) + to one DMC and return it, otherwise return an empty string. + + Args: + dmc (str): The scanned DMC, digital manufacturer code. + allowed_prefixes (list[str]): List of allowed DMC prefixes. + + Returns: + str: The (first) DMC if it starts with an allowed prefix, + otherwise an empty string. + """ + if not allowed_prefixes: + # No prefixes configured, so no check needed + LobHlpr.lob_print( + LobHlpr.LOB_PRINT_LOG_PATH, + "No DMC prefixes configured, skipping prefix check.", + ) + return dmc + for prefix in allowed_prefixes: + if dmc.startswith(prefix): + return dmc + LobHlpr.lob_print( + LobHlpr.LOB_PRINT_LOG_PATH, + f"DMC {dmc} does not start with any of the allowed prefixes: " + f"{allowed_prefixes}", + ) + return "" + + @staticmethod + def verify_dmc_customer(dmc: str, allowed_suffix: list[str]) -> bool: + """Verify if the DMC is allowed for the current customer config. + + This is done by checking if the article number ends with letter(s), + and if customer letter(s) are configured, it must match an allowed string. + + Args: + dmc (str): The scanned DMC, digital manufacturer code. + allowed_suffix (list[str]): List of allowed customer article suffixes. + + Returns: + bool: True if the DMC is valid for the customer, False otherwise. + """ + _, _, serial_no, article_no = LobHlpr.parse_dmc(dmc) + # serial no should be all digits, + # otherwise it may contain the nrf 2did as well + if not serial_no.isdigit(): + LobHlpr.lob_print( + LobHlpr.LOB_PRINT_LOG_PATH, + f"Serial number {serial_no} in DMC {dmc} is not all digits!", + ) + return False + if not allowed_suffix: + # No customer letters configured, so no check needed + LobHlpr.lob_print( + LobHlpr.LOB_PRINT_LOG_PATH, + "No customer letters configured, skipping customer check.", + ) + return True + if not article_no: + # No article number to check + LobHlpr.lob_print( + LobHlpr.LOB_PRINT_LOG_PATH, + "No article number found in DMC, skipping customer check.", + ) + return True + if not article_no[-1].isalpha(): + # No letters at the end, so no customer letters to check + LobHlpr.lob_print( + LobHlpr.LOB_PRINT_LOG_PATH, + f"No customer letters in article number {article_no}, " + f"skipping customer check.", + ) + return True + article_cust_letters = re.match(r".*([^0-9\W]+)$", article_no) + cust_letters = article_cust_letters.group(1) # type: ignore[union-attr] + if cust_letters not in allowed_suffix: + LobHlpr.lob_print( + LobHlpr.LOB_PRINT_LOG_PATH, + f"Customer letters {cust_letters} in article number {article_no} " + f"are not allowed, must be one of {allowed_suffix}.", + ) + return False + LobHlpr.lob_print( + LobHlpr.LOB_PRINT_LOG_PATH, + f"Customer letters {cust_letters} in article number {article_no} " + f"are allowed, continuing.", + ) + return True + @staticmethod def extract_identifier_from_hexfile(hex_str: str): """Extract the identifier from a hex file. diff --git a/tests/test_lob_hlpr.py b/tests/test_lob_hlpr.py index cdbb432..63b8658 100644 --- a/tests/test_lob_hlpr.py +++ b/tests/test_lob_hlpr.py @@ -28,6 +28,51 @@ def test_parse_dmc_fail(): hlp.parse_dmc("MPP-OR023282_1") +def test_verify_dmc_prefix_pass(): + """Test verify_dmc_prefix function passing cases.""" + assert ( + hlp.verify_dmc_prefix("MPP-OR019504_1-00781", ["MPP", "SOMETHING-ELSE"]) + == "MPP-OR019504_1-00781" + ) + assert ( + hlp.verify_dmc_prefix("MPP-M0011803L-OR024569_3-0187", ["MPP"]) + == "MPP-M0011803L-OR024569_3-0187" + ) + assert hlp.verify_dmc_prefix("MPP-OR019504_1-00781", []) == "MPP-OR019504_1-00781" + + +def test_verify_dmc_prefix_fail(): + """Test verify_dmc_prefix function failing cases.""" + assert hlp.verify_dmc_prefix("MPP-OR019504_1-00781", ["123", "456"]) == "" + + +def test_verify_dmc_customer_pass(): + """Test verify_dmc_customer function passing cases.""" + assert hlp.verify_dmc_customer("MPP-M0011803L-OR024569_3-0187", ["L"]) is True + assert hlp.verify_dmc_customer("MPP-M0011803L-OR024569_3-0187", ["B"]) is False + assert hlp.verify_dmc_customer("MPP-OR019504_1-00781", []) is True + + +def test_verify_dmc_customer_no_sn_digit(): + """Test verify_dmc_customer function with no digit at the end of article number.""" + assert hlp.verify_dmc_customer("MPP-M0011803L-OR024569_3-SNNOTDIGIT", []) is False + + +def test_verify_dmc_customer_no_article_number(): + """Test verify_dmc_customer function with no article number.""" + assert hlp.verify_dmc_customer("MPP-M0011803L_3-0187", ["L"]) is True + + +def test_verify_dmc_customer_article_num_is_not_alpha(): + """Test verify_dmc_customer function with article number not ending with letters.""" + assert hlp.verify_dmc_customer("MPP-M0011803-OR024569_3-0187", ["L"]) is True + + +def test_verify_dmc_customer_article_num_is_not_article_cust_letters(): + """Test verify_dmc_customer with article number not having customer letters.""" + assert hlp.verify_dmc_customer("MPP-M0011803L-OR024569_3-0187", ["X"]) is False + + # A bit of looking through a hex file and manual parsing allows me to provide # the following data for testing. HEX_STRINGS = [