From 72af1ad5e9b6aa7e628449afce5006eaeb8e5aed Mon Sep 17 00:00:00 2001 From: Travis Semple Date: Tue, 8 Oct 2024 14:23:40 -0700 Subject: [PATCH] Fix pay-queue --- pay-queue/Makefile | 18 +- pay-queue/app.py | 8 +- pay-queue/gunicorn_config.py | 12 +- pay-queue/poetry.lock | 84 +- pay-queue/pyproject.toml | 123 ++ pay-queue/setup.py | 38 +- pay-queue/src/pay_queue/__init__.py | 14 +- pay-queue/src/pay_queue/config.py | 138 +- pay-queue/src/pay_queue/enums.py | 74 +- pay-queue/src/pay_queue/external/gcp_auth.py | 16 +- pay-queue/src/pay_queue/minio.py | 16 +- pay-queue/src/pay_queue/resources/__init__.py | 2 +- pay-queue/src/pay_queue/resources/worker.py | 18 +- .../pay_queue/services/cgi_reconciliations.py | 208 +- .../src/pay_queue/services/eft/eft_base.py | 4 +- .../src/pay_queue/services/eft/eft_enums.py | 12 +- .../src/pay_queue/services/eft/eft_errors.py | 26 +- .../src/pay_queue/services/eft/eft_header.py | 6 +- .../services/eft/eft_reconciliation.py | 257 ++- .../src/pay_queue/services/eft/eft_record.py | 27 +- .../src/pay_queue/services/email_service.py | 51 +- .../pay_queue/services/identifier_updater.py | 8 +- .../services/payment_reconciliations.py | 565 +++-- pay-queue/src/pay_queue/version.py | 2 +- pay-queue/tests/__init__.py | 3 +- pay-queue/tests/conftest.py | 68 +- pay-queue/tests/integration/__init__.py | 72 +- pay-queue/tests/integration/factory.py | 431 ++-- .../integration/test_cgi_reconciliations.py | 1858 +++++++++-------- .../integration/test_eft_reconciliation.py | 857 +++++--- .../test_payment_reconciliations.py | 912 +++++--- .../tests/integration/test_worker_queue.py | 15 +- pay-queue/tests/integration/utils.py | 110 +- pay-queue/tests/unit/test_eft_file_parser.py | 625 +++--- pay-queue/tests/utilities/factory_utils.py | 63 +- 35 files changed, 4067 insertions(+), 2674 deletions(-) diff --git a/pay-queue/Makefile b/pay-queue/Makefile index ff9f7a854..d39bcdb6a 100644 --- a/pay-queue/Makefile +++ b/pay-queue/Makefile @@ -44,15 +44,27 @@ install: clean ################################################################################# # COMMANDS - CI # ################################################################################# -ci: lint flake8 test ## CI flow +ci: isort-ci black-ci lint flake8 test ## CI flow + +isort: + poetry run isort . + +isort-ci: + poetry run isort --check . + +black: ## Linting with black + poetry run black . + +black-ci: + poetry run black --check . pylint: ## Linting with pylint - poetry run pylint --rcfile=setup.cfg src/$(PROJECT_NAME) + poetry run pylint src/$(PROJECT_NAME) flake8: ## Linting with flake8 poetry run flake8 src/$(PROJECT_NAME) tests -lint: pylint flake8 ## run all lint type scripts +lint: isort black pylint flake8 ## run all lint type scripts test: ## Unit testing poetry run pytest diff --git a/pay-queue/app.py b/pay-queue/app.py index a1f9fdbe5..eea5f3d2a 100755 --- a/pay-queue/app.py +++ b/pay-queue/app.py @@ -17,9 +17,11 @@ """Initialize Flask app.""" import os + from pay_queue import create_app + app = create_app() -if __name__ == '__main__': - server_port = os.environ.get('PORT', '5001') - app.run(debug=False, port=server_port, host='0.0.0.0') +if __name__ == "__main__": + server_port = os.environ.get("PORT", "5001") + app.run(debug=False, port=server_port, host="0.0.0.0") diff --git a/pay-queue/gunicorn_config.py b/pay-queue/gunicorn_config.py index d52a64e76..17c107427 100644 --- a/pay-queue/gunicorn_config.py +++ b/pay-queue/gunicorn_config.py @@ -18,10 +18,10 @@ import os # https://docs.gunicorn.org/en/stable/settings.html#workers -workers = int(os.environ.get('GUNICORN_PROCESSES', '1')) # gunicorn default - 1 -worker_class = os.environ.get('GUNICORN_WORKER_CLASS', 'sync') # gunicorn default - sync -worker_connections = int(os.environ.get('GUNICORN_WORKER_CONNECIONS', '1000')) # gunicorn default - 1000 -threads = int(os.environ.get('GUNICORN_THREADS', '4')) # gunicorn default - 1 -timeout = int(os.environ.get('GUNICORN_TIMEOUT', '100')) # gunicorn default - 30 -keepalive = int(os.environ.get('GUNICORN_KEEPALIVE', '2')) # gunicorn default - 2 +workers = int(os.environ.get("GUNICORN_PROCESSES", "1")) # gunicorn default - 1 +worker_class = os.environ.get("GUNICORN_WORKER_CLASS", "sync") # gunicorn default - sync +worker_connections = int(os.environ.get("GUNICORN_WORKER_CONNECIONS", "1000")) # gunicorn default - 1000 +threads = int(os.environ.get("GUNICORN_THREADS", "4")) # gunicorn default - 1 +timeout = int(os.environ.get("GUNICORN_TIMEOUT", "100")) # gunicorn default - 30 +keepalive = int(os.environ.get("GUNICORN_KEEPALIVE", "2")) # gunicorn default - 2 # WHEN MIGRATING TO GCP - GUNICORN_THREADS = 8, GUNICORN_TIMEOUT = 0, GUNICORN_PROCESSES = 1 diff --git a/pay-queue/poetry.lock b/pay-queue/poetry.lock index 5eb67f19e..9e5c1beee 100644 --- a/pay-queue/poetry.lock +++ b/pay-queue/poetry.lock @@ -252,6 +252,50 @@ files = [ [package.dependencies] pycodestyle = ">=2.12.0" +[[package]] +name = "black" +version = "24.10.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +files = [ + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "blinker" version = "1.7.0" @@ -807,6 +851,22 @@ isort = ">=5.0.0,<6" [package.extras] test = ["pytest"] +[[package]] +name = "flake8-pyproject" +version = "1.2.3" +description = "Flake8 plug-in loading the configuration from pyproject.toml" +optional = false +python-versions = ">= 3.6" +files = [ + {file = "flake8_pyproject-1.2.3-py3-none-any.whl", hash = "sha256:6249fe53545205af5e76837644dc80b4c10037e73a0e5db87ff562d75fb5bd4a"}, +] + +[package.dependencies] +Flake8 = ">=5" + +[package.extras] +dev = ["pyTest", "pyTest-cov"] + [[package]] name = "flake8-quotes" version = "3.4.0" @@ -1870,6 +1930,17 @@ files = [ {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "opentracing" version = "2.4.0" @@ -1894,6 +1965,17 @@ files = [ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + [[package]] name = "pay-api" version = "0.1.0" @@ -3104,4 +3186,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "04de57c69ecf9b5e5112cc584e3912723d6be73f05f3216ed09262f625bb1c5d" +content-hash = "6f6bbae22c7f261d99596df24c3e9dd621d97b8dd00956357dfdc3ec420cba59" diff --git a/pay-queue/pyproject.toml b/pay-queue/pyproject.toml index 8203b408c..74830de62 100644 --- a/pay-queue/pyproject.toml +++ b/pay-queue/pyproject.toml @@ -45,6 +45,129 @@ pydocstyle = "^6.3.0" isort = "^5.13.2" lovely-pytest-docker = "^0.3.1" pytest-asyncio = "0.18.3" +black = "^24.10.0" +flake8-pyproject = "^1.2.3" + +[tool.flake8] +ignore = ["F401","E402", "Q000", "E203", "W503"] +exclude = [ + ".venv", + "./venv", + ".git", + ".history", + "devops", + "*migrations*", +] +per-file-ignores = [ + "__init__.py:F401", + "*.py:B902" +] +max-line-length = 120 +docstring-min-length=10 +count = true + +[tool.zimports] +black-line-length = 120 +keep-unused-type-checking = true + +[tool.black] +target-version = ["py310", "py311", "py312"] +line-length = 120 +include = '\.pyi?$' +extend-exclude = ''' +/( + # The following are specific to Black, you probably don't want those. + migrations + | devops + | .history +)/ +''' + +[tool.isort] +atomic = true +profile = "black" +line_length = 120 +skip_gitignore = true +skip_glob = ["migrations", "devops"] + +[tool.pylint.main] +fail-under = 10 +max-line-length = 120 +ignore = [ "migrations", "devops", "tests"] +ignore-patterns = ["^\\.#"] +ignored-modules= ["flask_sqlalchemy", "sqlalchemy", "SQLAlchemy" , "alembic", "scoped_session"] +ignored-classes= "scoped_session" +ignore-long-lines = "^\\s*(# )??$" +extension-pkg-whitelist = "pydantic" +notes = ["FIXME","XXX","TODO"] +overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"] +confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"] +disable = "C0209,C0301,W0511,W0613,W0703,W1514,W1203,R0801,R0902,R0903,R0911,R0401,R1705,R1718,W3101" +argument-naming-style = "snake_case" +attr-naming-style = "snake_case" +class-attribute-naming-style = "any" +class-const-naming-style = "UPPER_CASE" +class-naming-style = "PascalCase" +const-naming-style = "UPPER_CASE" +function-naming-style = "snake_case" +inlinevar-naming-style = "any" +method-naming-style = "snake_case" +module-naming-style = "any" +variable-naming-style = "snake_case" +docstring-min-length = -1 +good-names = ["i", "j", "k", "ex", "Run", "_"] +bad-names = ["foo", "bar", "baz", "toto", "tutu", "tata"] +defining-attr-methods = ["__init__", "__new__", "setUp", "asyncSetUp", "__post_init__"] +exclude-protected = ["_asdict", "_fields", "_replace", "_source", "_make", "os._exit"] +valid-classmethod-first-arg = ["cls"] +valid-metaclass-classmethod-first-arg = ["mcs"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +minversion = "2.0" +testpaths = [ + "tests", +] +addopts = "--verbose --strict -p no:warnings --cov=src --cov-report html:htmlcov --cov-report xml:coverage.xml" +python_files = [ + "test*.py" +] +norecursedirs = [ + ".git", ".tox", "venv*", "requirements*", "build", +] +log_cli = true +log_cli_level = "1" +filterwarnings = [ + "ignore::UserWarning" +] +markers = [ + "slow", + "serial", +] + +[tool.coverage.run] +branch = true +source = [ + "src/auth_api", +] +omit = [ + "wsgi.py", + "gunicorn_config.py" +] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "from", + "import", + "def __repr__", + "if self.debug:", + "if settings.DEBUG", + "raise AssertionError", + "raise NotImplementedError", + "if 0:", + 'if __name__ == "__main__":', +] [build-system] requires = ["poetry-core"] diff --git a/pay-queue/setup.py b/pay-queue/setup.py index 9bc1ac212..0080379eb 100644 --- a/pay-queue/setup.py +++ b/pay-queue/setup.py @@ -20,12 +20,12 @@ from setuptools import find_packages, setup +_version_re = re.compile(r"__version__\s+=\s+(.*)") # pylint: disable=invalid-name -_version_re = re.compile(r'__version__\s+=\s+(.*)') # pylint: disable=invalid-name - -with open('src/pay_queue/version.py', 'rb') as f: - version = str(ast.literal_eval(_version_re.search( # pylint: disable=invalid-name - f.read().decode('utf-8')).group(1))) +with open("src/pay_queue/version.py", "rb") as f: + version = str( + ast.literal_eval(_version_re.search(f.read().decode("utf-8")).group(1)) # pylint: disable=invalid-name + ) def read_requirements(filename): @@ -34,9 +34,9 @@ def read_requirements(filename): the requirements.txt file. :return: Python requirements """ - with open(filename, 'r') as req: + with open(filename, "r") as req: requirements = req.readlines() - install_requires = [r.strip() for r in requirements if (r.find('git+') != 0 and r.find('-e git+') != 0)] + install_requires = [r.strip() for r in requirements if (r.find("git+") != 0 and r.find("-e git+") != 0)] return install_requires @@ -46,25 +46,29 @@ def read(filepath): :param str filepath: path to the file to be read :return: file contents """ - with open(filepath, 'r') as file_handle: + with open(filepath, "r") as file_handle: content = file_handle.read() return content -REQUIREMENTS = read_requirements('requirements.txt') +REQUIREMENTS = read_requirements("requirements.txt") setup( name="pay_queue", version=version, - author_email='', - packages=find_packages('src'), - package_dir={'': 'src'}, - py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')], + author_email="", + packages=find_packages("src"), + package_dir={"": "src"}, + py_modules=[splitext(basename(path))[0] for path in glob("src/*.py")], include_package_data=True, - license=read('LICENSE'), - long_description=read('README.md'), + license=read("LICENSE"), + long_description=read("README.md"), zip_safe=False, install_requires=REQUIREMENTS, - setup_requires=["pytest-runner", ], - tests_require=["pytest", ], + setup_requires=[ + "pytest-runner", + ], + tests_require=[ + "pytest", + ], ) diff --git a/pay-queue/src/pay_queue/__init__.py b/pay-queue/src/pay_queue/__init__.py index 1fe3ebdcc..4b9a4da46 100644 --- a/pay-queue/src/pay_queue/__init__.py +++ b/pay-queue/src/pay_queue/__init__.py @@ -34,22 +34,21 @@ from .resources import register_endpoints +setup_logging(os.path.join(os.path.abspath(os.path.dirname(__file__)), "logging.conf")) # important to do this first -setup_logging(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'logging.conf')) # important to do this first - -def create_app(run_mode=os.getenv('DEPLOYMENT_ENV', 'production')) -> Flask: +def create_app(run_mode=os.getenv("DEPLOYMENT_ENV", "production")) -> Flask: """Return a configured Flask App using the Factory method.""" app = Flask(__name__) app.env = run_mode app.config.from_object(config.CONFIGURATION[run_mode]) # Configure Sentry - if dsn := app.config.get('SENTRY_DSN', None): + if dsn := app.config.get("SENTRY_DSN", None): sentry_sdk.init( dsn=dsn, integrations=[FlaskIntegration()], - release=f'pay-queue@{get_run_version()}', + release=f"pay-queue@{get_run_version()}", send_default_pii=False, ) @@ -68,10 +67,11 @@ def build_cache(app): cache.init_app(app) with app.app_context(): cache.clear() - if not app.config.get('TESTING', False): + if not app.config.get("TESTING", False): try: from pay_api.services.code import Code as CodeService # pylint: disable=import-outside-toplevel + CodeService.build_all_codes_cache() except Exception as e: # NOQA pylint:disable=broad-except - app.logger.error('Error on caching ') + app.logger.error("Error on caching ") app.logger.error(e) diff --git a/pay-queue/src/pay_queue/config.py b/pay-queue/src/pay_queue/config.py index 2f05ba22e..d91fd6da9 100644 --- a/pay-queue/src/pay_queue/config.py +++ b/pay-queue/src/pay_queue/config.py @@ -23,98 +23,97 @@ from dotenv import find_dotenv, load_dotenv - # this will load all the envars from a .env file located in the project root (api) load_dotenv(find_dotenv()) CONFIGURATION = { - 'development': 'pay_queue.config.DevConfig', - 'testing': 'pay_queue.config.TestConfig', - 'production': 'pay_queue.config.ProdConfig', - 'default': 'pay_queue.config.ProdConfig' + "development": "pay_queue.config.DevConfig", + "testing": "pay_queue.config.TestConfig", + "production": "pay_queue.config.ProdConfig", + "default": "pay_queue.config.ProdConfig", } -def get_named_config(config_name: str = 'production'): +def get_named_config(config_name: str = "production"): """Return the configuration object based on the name. :raise: KeyError: if an unknown configuration is requested """ - if config_name in ['production', 'staging', 'default']: + if config_name in ["production", "staging", "default"]: app_config = ProdConfig() - elif config_name == 'testing': + elif config_name == "testing": app_config = TestConfig() - elif config_name == 'development': + elif config_name == "development": app_config = DevConfig() else: - raise KeyError(f'Unknown configuration: {config_name}') + raise KeyError(f"Unknown configuration: {config_name}") return app_config -class _Config(): # pylint: disable=too-few-public-methods,protected-access +class _Config: # pylint: disable=too-few-public-methods,protected-access """Base class configuration that should set reasonable defaults. Used as the base for all the other configurations. """ PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) - PAY_LD_SDK_KEY = os.getenv('PAY_LD_SDK_KEY', None) - LEGISLATIVE_TIMEZONE = os.getenv('LEGISLATIVE_TIMEZONE', 'America/Vancouver') + PAY_LD_SDK_KEY = os.getenv("PAY_LD_SDK_KEY", None) + LEGISLATIVE_TIMEZONE = os.getenv("LEGISLATIVE_TIMEZONE", "America/Vancouver") - SENTRY_ENABLE = os.getenv('SENTRY_ENABLE', 'False') - SENTRY_DSN = os.getenv('SENTRY_DSN', None) + SENTRY_ENABLE = os.getenv("SENTRY_ENABLE", "False") + SENTRY_DSN = os.getenv("SENTRY_DSN", None) SQLALCHEMY_TRACK_MODIFICATIONS = False # POSTGRESQL - DB_USER = os.getenv('DATABASE_USERNAME', '') - DB_PASSWORD = os.getenv('DATABASE_PASSWORD', '') - DB_NAME = os.getenv('DATABASE_NAME', '') - DB_HOST = os.getenv('DATABASE_HOST', '') - DB_PORT = os.getenv('DATABASE_PORT', '5432') - if DB_UNIX_SOCKET := os.getenv('DATABASE_UNIX_SOCKET', None): + DB_USER = os.getenv("DATABASE_USERNAME", "") + DB_PASSWORD = os.getenv("DATABASE_PASSWORD", "") + DB_NAME = os.getenv("DATABASE_NAME", "") + DB_HOST = os.getenv("DATABASE_HOST", "") + DB_PORT = os.getenv("DATABASE_PORT", "5432") + if DB_UNIX_SOCKET := os.getenv("DATABASE_UNIX_SOCKET", None): SQLALCHEMY_DATABASE_URI = ( - f'postgresql+pg8000://{DB_USER}:{DB_PASSWORD}@/{DB_NAME}?unix_sock={DB_UNIX_SOCKET}/.s.PGSQL.5432' + f"postgresql+pg8000://{DB_USER}:{DB_PASSWORD}@/{DB_NAME}?unix_sock={DB_UNIX_SOCKET}/.s.PGSQL.5432" ) else: - SQLALCHEMY_DATABASE_URI = f'postgresql+pg8000://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{int(DB_PORT)}/{DB_NAME}' + SQLALCHEMY_DATABASE_URI = f"postgresql+pg8000://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{int(DB_PORT)}/{DB_NAME}" # Minio configuration values - MINIO_ENDPOINT = os.getenv('MINIO_ENDPOINT') - MINIO_ACCESS_KEY = os.getenv('MINIO_ACCESS_KEY') - MINIO_ACCESS_SECRET = os.getenv('MINIO_ACCESS_SECRET') - MINIO_SECURE = os.getenv('MINIO_SECURE', 'True').lower() == 'true' + MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT") + MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY") + MINIO_ACCESS_SECRET = os.getenv("MINIO_ACCESS_SECRET") + MINIO_SECURE = os.getenv("MINIO_SECURE", "True").lower() == "true" # CFS API Settings - CFS_BASE_URL = os.getenv('CFS_BASE_URL') - CFS_CLIENT_ID = os.getenv('CFS_CLIENT_ID') - CFS_CLIENT_SECRET = os.getenv('CFS_CLIENT_SECRET') - CONNECT_TIMEOUT = int(os.getenv('CONNECT_TIMEOUT', '10')) + CFS_BASE_URL = os.getenv("CFS_BASE_URL") + CFS_CLIENT_ID = os.getenv("CFS_CLIENT_ID") + CFS_CLIENT_SECRET = os.getenv("CFS_CLIENT_SECRET") + CONNECT_TIMEOUT = int(os.getenv("CONNECT_TIMEOUT", "10")) # EFT Config - EFT_TDI17_LOCATION_ID = os.getenv('EFT_TDI17_LOCATION_ID') + EFT_TDI17_LOCATION_ID = os.getenv("EFT_TDI17_LOCATION_ID") # Secret key for encrypting bank account - ACCOUNT_SECRET_KEY = os.getenv('ACCOUNT_SECRET_KEY') + ACCOUNT_SECRET_KEY = os.getenv("ACCOUNT_SECRET_KEY") - KEYCLOAK_SERVICE_ACCOUNT_ID = os.getenv('SBC_AUTH_ADMIN_CLIENT_ID') - KEYCLOAK_SERVICE_ACCOUNT_SECRET = os.getenv('SBC_AUTH_ADMIN_CLIENT_SECRET') - JWT_OIDC_ISSUER = os.getenv('JWT_OIDC_ISSUER') - NOTIFY_API_URL = os.getenv('NOTIFY_API_URL', '') - NOTIFY_API_VERSION = os.getenv('NOTIFY_API_VERSION', '') - NOTIFY_API_ENDPOINT = f'{NOTIFY_API_URL + NOTIFY_API_VERSION}/' - IT_OPS_EMAIL = os.getenv('IT_OPS_EMAIL', 'SBC_ITOperationsSupport@gov.bc.ca') + KEYCLOAK_SERVICE_ACCOUNT_ID = os.getenv("SBC_AUTH_ADMIN_CLIENT_ID") + KEYCLOAK_SERVICE_ACCOUNT_SECRET = os.getenv("SBC_AUTH_ADMIN_CLIENT_SECRET") + JWT_OIDC_ISSUER = os.getenv("JWT_OIDC_ISSUER") + NOTIFY_API_URL = os.getenv("NOTIFY_API_URL", "") + NOTIFY_API_VERSION = os.getenv("NOTIFY_API_VERSION", "") + NOTIFY_API_ENDPOINT = f"{NOTIFY_API_URL + NOTIFY_API_VERSION}/" + IT_OPS_EMAIL = os.getenv("IT_OPS_EMAIL", "SBC_ITOperationsSupport@gov.bc.ca") - DISABLE_EJV_ERROR_EMAIL = os.getenv('DISABLE_EJV_ERROR_EMAIL', 'true').lower() == 'true' - DISABLE_CSV_ERROR_EMAIL = os.getenv('DISABLE_CSV_ERROR_EMAIL', 'true').lower() == 'true' + DISABLE_EJV_ERROR_EMAIL = os.getenv("DISABLE_EJV_ERROR_EMAIL", "true").lower() == "true" + DISABLE_CSV_ERROR_EMAIL = os.getenv("DISABLE_CSV_ERROR_EMAIL", "true").lower() == "true" # PUB/SUB - PUB: account-mailer-dev, auth-event-dev, SUB to ftp-poller-payment-reconciliation-dev, business-events - ACCOUNT_MAILER_TOPIC = os.getenv('ACCOUNT_MAILER_TOPIC', 'account-mailer-dev') - AUTH_EVENT_TOPIC = os.getenv('AUTH_EVENT_TOPIC', 'auth-event-dev') - GCP_AUTH_KEY = os.getenv('AUTHPAY_GCP_AUTH_KEY', None) + ACCOUNT_MAILER_TOPIC = os.getenv("ACCOUNT_MAILER_TOPIC", "account-mailer-dev") + AUTH_EVENT_TOPIC = os.getenv("AUTH_EVENT_TOPIC", "auth-event-dev") + GCP_AUTH_KEY = os.getenv("AUTHPAY_GCP_AUTH_KEY", None) # If blank in PUBSUB, this should match the https endpoint the subscription is pushing to. - PAY_AUDIENCE_SUB = os.getenv('PAY_AUDIENCE_SUB', None) - VERIFY_PUBSUB_EMAILS = f'{os.getenv("AUTHPAY_SERVICE_ACCOUNT")},{os.getenv("BUSINESS_SERVICE_ACCOUNT")}'.split(',') + PAY_AUDIENCE_SUB = os.getenv("PAY_AUDIENCE_SUB", None) + VERIFY_PUBSUB_EMAILS = f'{os.getenv("AUTHPAY_SERVICE_ACCOUNT")},{os.getenv("BUSINESS_SERVICE_ACCOUNT")}'.split(",") class DevConfig(_Config): # pylint: disable=too-few-public-methods @@ -133,38 +132,45 @@ class TestConfig(_Config): # pylint: disable=too-few-public-methods DEBUG = True TESTING = True # POSTGRESQL - DB_USER = os.getenv('DATABASE_TEST_USERNAME', '') - DB_PASSWORD = os.getenv('DATABASE_TEST_PASSWORD', '') - DB_NAME = os.getenv('DATABASE_TEST_NAME', '') - DB_HOST = os.getenv('DATABASE_TEST_HOST', '') - DB_PORT = os.getenv('DATABASE_TEST_PORT', '5432') + DB_USER = os.getenv("DATABASE_TEST_USERNAME", "") + DB_PASSWORD = os.getenv("DATABASE_TEST_PASSWORD", "") + DB_NAME = os.getenv("DATABASE_TEST_NAME", "") + DB_HOST = os.getenv("DATABASE_TEST_HOST", "") + DB_PORT = os.getenv("DATABASE_TEST_PORT", "5432") SQLALCHEMY_DATABASE_URI = os.getenv( - 'DATABASE_TEST_URL', - default=f'postgresql+pg8000://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{int(DB_PORT)}/{DB_NAME}' + "DATABASE_TEST_URL", + default=f"postgresql+pg8000://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{int(DB_PORT)}/{DB_NAME}", ) - USE_DOCKER_MOCK = os.getenv('USE_DOCKER_MOCK', None) + USE_DOCKER_MOCK = os.getenv("USE_DOCKER_MOCK", None) # Minio variables - MINIO_ENDPOINT = 'localhost:9000' - MINIO_ACCESS_KEY = 'minio' - MINIO_ACCESS_SECRET = 'minio123' - MINIO_BUCKET_NAME = 'payment-sftp' + MINIO_ENDPOINT = "localhost:9000" + MINIO_ACCESS_KEY = "minio" + MINIO_ACCESS_SECRET = "minio123" + MINIO_BUCKET_NAME = "payment-sftp" MINIO_SECURE = False - CFS_BASE_URL = 'http://localhost:8080/paybc-api' - CFS_CLIENT_ID = 'TEST' - CFS_CLIENT_SECRET = 'TEST' + CFS_BASE_URL = "http://localhost:8080/paybc-api" + CFS_CLIENT_ID = "TEST" + CFS_CLIENT_SECRET = "TEST" # Secret key for encrypting bank account - ACCOUNT_SECRET_KEY = os.getenv('ACCOUNT_SECRET_KEY', 'test') + ACCOUNT_SECRET_KEY = os.getenv("ACCOUNT_SECRET_KEY", "test") # Secrets for integration tests - TEST_GCP_PROJECT_NAME = 'pay-queue-dev' + TEST_GCP_PROJECT_NAME = "pay-queue-dev" # Needs to have ftp-poller-dev in it. - TEST_GCP_TOPICS = ['account-mailer-dev', 'ftp-poller-dev', 'business-identifier-update-pay-dev'] + TEST_GCP_TOPICS = [ + "account-mailer-dev", + "ftp-poller-dev", + "business-identifier-update-pay-dev", + ] TEST_PUSH_ENDPOINT_PORT = 5020 - TEST_PUSH_ENDPOINT = os.getenv('TEST_PUSH_ENDPOINT', f'http://host.docker.internal:{str(TEST_PUSH_ENDPOINT_PORT)}/') + TEST_PUSH_ENDPOINT = os.getenv( + "TEST_PUSH_ENDPOINT", + f"http://host.docker.internal:{str(TEST_PUSH_ENDPOINT_PORT)}/", + ) GCP_AUTH_KEY = None DISABLE_EJV_ERROR_EMAIL = False DISABLE_CSV_ERROR_EMAIL = False diff --git a/pay-queue/src/pay_queue/enums.py b/pay-queue/src/pay_queue/enums.py index f23c76288..6b2ae29b5 100644 --- a/pay-queue/src/pay_queue/enums.py +++ b/pay-queue/src/pay_queue/enums.py @@ -18,60 +18,60 @@ class SourceTransaction(Enum): """Source Transaction types.""" - PAD = 'BCR-PAD Daily' - ONLINE_BANKING = 'BCR Online Banking Payments' - CREDIT_MEMO = 'CM' - ADJUSTMENT = 'BCR-ADJ' - EFT_WIRE = 'BC REG EFT Wire Cheque' + PAD = "BCR-PAD Daily" + ONLINE_BANKING = "BCR Online Banking Payments" + CREDIT_MEMO = "CM" + ADJUSTMENT = "BCR-ADJ" + EFT_WIRE = "BC REG EFT Wire Cheque" class RecordType(Enum): """Record types.""" - PAD = 'PADP' - PAYR = 'PAYR' - BOLP = 'BOLP' - CMAP = 'CMAP' - ADJS = 'ADJS' - ONAC = 'ONAC' - ONAP = 'ONAP' - EFTP = 'EFTP' - PADR = 'PADR' - DRWP = 'DRWP' + PAD = "PADP" + PAYR = "PAYR" + BOLP = "BOLP" + CMAP = "CMAP" + ADJS = "ADJS" + ONAC = "ONAC" + ONAP = "ONAP" + EFTP = "EFTP" + PADR = "PADR" + DRWP = "DRWP" class Column(Enum): """Column Types.""" - RECORD_TYPE = 'Record type' - SOURCE_TXN = 'Source transaction type' - SOURCE_TXN_NO = 'Source Transaction Number' - APP_ID = 'Application Id' - APP_DATE = 'Application Date' - APP_AMOUNT = 'Application amount' - CUSTOMER_ACC = 'Customer Account' - TARGET_TXN = 'Target transaction type' - TARGET_TXN_NO = 'Target transaction Number' - TARGET_TXN_ORIGINAL = 'Target Transaction Original amount' - TARGET_TXN_OUTSTANDING = 'Target Transaction Outstanding Amount' - TARGET_TXN_STATUS = 'Target transaction status' - REVERSAL_REASON_CODE = 'Reversal Reason code' - REVERSAL_REASON_DESC = 'Reversal reason desc' + RECORD_TYPE = "Record type" + SOURCE_TXN = "Source transaction type" + SOURCE_TXN_NO = "Source Transaction Number" + APP_ID = "Application Id" + APP_DATE = "Application Date" + APP_AMOUNT = "Application amount" + CUSTOMER_ACC = "Customer Account" + TARGET_TXN = "Target transaction type" + TARGET_TXN_NO = "Target transaction Number" + TARGET_TXN_ORIGINAL = "Target Transaction Original amount" + TARGET_TXN_OUTSTANDING = "Target Transaction Outstanding Amount" + TARGET_TXN_STATUS = "Target transaction status" + REVERSAL_REASON_CODE = "Reversal Reason code" + REVERSAL_REASON_DESC = "Reversal reason desc" class Status(Enum): """Target Transaction Status.""" - PAID = 'Fully PAID' - NOT_PAID = 'Not PAID' - ON_ACC = 'On Account' - PARTIAL = 'Partially PAID' + PAID = "Fully PAID" + NOT_PAID = "Not PAID" + ON_ACC = "On Account" + PARTIAL = "Partially PAID" class TargetTransaction(Enum): """Target Transaction.""" - INV = 'INV' - DEBIT_MEMO = 'DM' - CREDIT_MEMO = 'CM' - RECEIPT = 'RECEIPT' + INV = "INV" + DEBIT_MEMO = "DM" + CREDIT_MEMO = "CM" + RECEIPT = "RECEIPT" diff --git a/pay-queue/src/pay_queue/external/gcp_auth.py b/pay-queue/src/pay_queue/external/gcp_auth.py index 398b9ca5f..eead6782b 100644 --- a/pay-queue/src/pay_queue/external/gcp_auth.py +++ b/pay-queue/src/pay_queue/external/gcp_auth.py @@ -15,28 +15,30 @@ def verify_jwt(session): """Check token is valid with the correct audience and email claims for configured email address.""" try: - jwt_token = request.headers.get('Authorization', '').split()[1] + jwt_token = request.headers.get("Authorization", "").split()[1] claims = id_token.verify_oauth2_token( jwt_token, Request(session=session), - audience=current_app.config.get('PAY_AUDIENCE_SUB') + audience=current_app.config.get("PAY_AUDIENCE_SUB"), ) - required_emails = current_app.config.get('VERIFY_PUBSUB_EMAILS') - if claims.get('email_verified') and claims.get('email') in required_emails: + required_emails = current_app.config.get("VERIFY_PUBSUB_EMAILS") + if claims.get("email_verified") and claims.get("email") in required_emails: return None else: - return 'Email not verified or does not match', 401 + return "Email not verified or does not match", 401 except Exception as e: - current_app.logger.info(f'Invalid token {e}') - return f'Invalid token: {e}', 400 + current_app.logger.info(f"Invalid token {e}") + return f"Invalid token: {e}", 400 def ensure_authorized_queue_user(f): """Ensures the user is authorized to use the queue.""" + @functools.wraps(f) def decorated_function(*args, **kwargs): # Use CacheControl to avoid re-fetching certificates for every request. if verify_jwt(CacheControl(Session())): abort(HTTPStatus.UNAUTHORIZED) return f(*args, **kwargs) + return decorated_function diff --git a/pay-queue/src/pay_queue/minio.py b/pay-queue/src/pay_queue/minio.py index d8e8f4eec..30e4aab76 100644 --- a/pay-queue/src/pay_queue/minio.py +++ b/pay-queue/src/pay_queue/minio.py @@ -20,12 +20,16 @@ def get_object(bucket_name: str, file_name: str) -> HTTPResponse: """Return a pre-signed URL for new doc upload.""" - current_app.logger.debug(f'Creating pre-signed URL for {file_name}') - minio_endpoint = current_app.config['MINIO_ENDPOINT'] - minio_key = current_app.config['MINIO_ACCESS_KEY'] - minio_secret = current_app.config['MINIO_ACCESS_SECRET'] + current_app.logger.debug(f"Creating pre-signed URL for {file_name}") + minio_endpoint = current_app.config["MINIO_ENDPOINT"] + minio_key = current_app.config["MINIO_ACCESS_KEY"] + minio_secret = current_app.config["MINIO_ACCESS_SECRET"] - minio_client: Minio = Minio(minio_endpoint, access_key=minio_key, secret_key=minio_secret, - secure=current_app.config['MINIO_SECURE']) + minio_client: Minio = Minio( + minio_endpoint, + access_key=minio_key, + secret_key=minio_secret, + secure=current_app.config["MINIO_SECURE"], + ) return minio_client.get_object(bucket_name, file_name) diff --git a/pay-queue/src/pay_queue/resources/__init__.py b/pay-queue/src/pay_queue/resources/__init__.py index 5fa222686..fca5f3457 100644 --- a/pay-queue/src/pay_queue/resources/__init__.py +++ b/pay-queue/src/pay_queue/resources/__init__.py @@ -24,7 +24,7 @@ def register_endpoints(app: Flask): app.url_map.strict_slashes = False app.register_blueprint( - url_prefix='/', + url_prefix="/", blueprint=worker_endpoint, ) app.register_blueprint(ops_bp) diff --git a/pay-queue/src/pay_queue/resources/worker.py b/pay-queue/src/pay_queue/resources/worker.py index 1d1b30e74..cbcb3092f 100644 --- a/pay-queue/src/pay_queue/resources/worker.py +++ b/pay-queue/src/pay_queue/resources/worker.py @@ -27,11 +27,10 @@ from pay_queue.services.eft.eft_reconciliation import reconcile_eft_payments from pay_queue.services.payment_reconciliations import reconcile_payments +bp = Blueprint("worker", __name__) -bp = Blueprint('worker', __name__) - -@bp.route('/', methods=('POST',)) +@bp.route("/", methods=("POST",)) @ensure_authorized_queue_user def worker(): """Worker to handle incoming queue pushes.""" @@ -40,7 +39,7 @@ def worker(): return {}, HTTPStatus.OK try: - current_app.logger.info('Event Message Received: %s ', json.dumps(dataclasses.asdict(ce))) + current_app.logger.info("Event Message Received: %s ", json.dumps(dataclasses.asdict(ce))) if ce.type == QueueMessageTypes.CAS_MESSAGE_TYPE.value: reconcile_payments(ce) elif ce.type == QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value: @@ -49,13 +48,16 @@ def worker(): reconcile_distributions(ce.data, is_feedback=True) elif ce.type == QueueMessageTypes.EFT_FILE_UPLOADED.value: reconcile_eft_payments(ce) - elif ce.type in [QueueMessageTypes.INCORPORATION.value, QueueMessageTypes.REGISTRATION.value]: + elif ce.type in [ + QueueMessageTypes.INCORPORATION.value, + QueueMessageTypes.REGISTRATION.value, + ]: update_temporary_identifier(ce.data) else: - current_app.logger.warning('Invalid queue message type: %s', ce.type) + current_app.logger.warning("Invalid queue message type: %s", ce.type) return {}, HTTPStatus.OK - except Exception: # NOQA # pylint: disable=broad-except + except Exception: # NOQA # pylint: disable=broad-except # Catch Exception so that any error is still caught and the message is removed from the queue - current_app.logger.error('Error processing event:', exc_info=True) + current_app.logger.error("Error processing event:", exc_info=True) return {}, HTTPStatus.OK diff --git a/pay-queue/src/pay_queue/services/cgi_reconciliations.py b/pay-queue/src/pay_queue/services/cgi_reconciliations.py index b625588c8..cea4a553f 100644 --- a/pay-queue/src/pay_queue/services/cgi_reconciliations.py +++ b/pay-queue/src/pay_queue/services/cgi_reconciliations.py @@ -35,16 +35,25 @@ from pay_api.services import gcp_queue_publisher from pay_api.services.gcp_queue_publisher import QueueMessage from pay_api.utils.enums import ( - DisbursementStatus, EFTShortnameRefundStatus, EjvFileType, EJVLinkType, InvoiceReferenceStatus, InvoiceStatus, - PaymentMethod, PaymentStatus, PaymentSystem, QueueSources, RoutingSlipStatus) + DisbursementStatus, + EFTShortnameRefundStatus, + EjvFileType, + EJVLinkType, + InvoiceReferenceStatus, + InvoiceStatus, + PaymentMethod, + PaymentStatus, + PaymentSystem, + QueueSources, + RoutingSlipStatus, +) from sbc_common_components.utils.enums import QueueMessageTypes from sentry_sdk import capture_message from pay_queue import config from pay_queue.minio import get_object - -APP_CONFIG = config.get_named_config(os.getenv('DEPLOYMENT_ENV', 'production')) +APP_CONFIG = config.get_named_config(os.getenv("DEPLOYMENT_ENV", "production")) def reconcile_distributions(msg: Dict[str, any], is_feedback: bool = False): @@ -61,27 +70,27 @@ def reconcile_distributions(msg: Dict[str, any], is_feedback: bool = False): def _update_acknowledgement(msg: Dict[str, any]): """Log the ack file, we don't know which batch it's for.""" - current_app.logger.info('Ack file received: %s', msg.get('fileName')) + current_app.logger.info("Ack file received: %s", msg.get("fileName")) def _update_feedback(msg: Dict[str, any]): # pylint:disable=too-many-locals, too-many-statements # Read the file and find records from the database, and update status. - file_name: str = msg.get('fileName') - minio_location: str = msg.get('location') + file_name: str = msg.get("fileName") + minio_location: str = msg.get("location") file = get_object(minio_location, file_name) - content = file.data.decode('utf-8-sig') + content = file.data.decode("utf-8-sig") group_batches: List[str] = _group_batches(content) - if _is_processed_or_processing(group_batches['EJV'], file_name): + if _is_processed_or_processing(group_batches["EJV"], file_name): return - has_errors = _process_ejv_feedback(group_batches['EJV']) - has_errors = _process_ap_feedback(group_batches['AP']) or has_errors + has_errors = _process_ejv_feedback(group_batches["EJV"]) + has_errors = _process_ap_feedback(group_batches["AP"]) or has_errors if has_errors and not APP_CONFIG.DISABLE_EJV_ERROR_EMAIL: _publish_mailer_events(file_name, minio_location) - current_app.logger.info('Feedback file processing completed.') + current_app.logger.info("Feedback file processing completed.") def _is_processed_or_processing(group_batches, file_name) -> bool: @@ -89,14 +98,16 @@ def _is_processed_or_processing(group_batches, file_name) -> bool: for group_batch in group_batches: ejv_file: Optional[EjvFileModel] = None for line in group_batch.splitlines(): - is_batch_group: bool = line[2:4] == 'BG' + is_batch_group: bool = line[2:4] == "BG" if is_batch_group: batch_number = int(line[15:24]) ejv_file = EjvFileModel.find_by_id(batch_number) if ejv_file.feedback_file_ref: current_app.logger.info( - 'EJV file id %s with feedback file %s is already processing or has been processed. Skipping.', - batch_number, file_name) + "EJV file id %s with feedback file %s is already processing or has been processed. Skipping.", + batch_number, + file_name, + ) return True ejv_file.feedback_file_ref = file_name ejv_file.save() @@ -111,10 +122,10 @@ def _process_ejv_feedback(group_batches) -> bool: # pylint:disable=too-many-loc receipt_number: Optional[str] = None for line in group_batch.splitlines(): # For all these indexes refer the sharepoint docs refer : https://github.com/bcgov/entity/issues/6226 - is_batch_group = line[2:4] == 'BG' - is_batch_header = line[2:4] == 'BH' - is_jv_header = line[2:4] == 'JH' - is_jv_detail = line[2:4] == 'JD' + is_batch_group = line[2:4] == "BG" + is_batch_header = line[2:4] == "BH" + is_jv_header = line[2:4] == "JH" + is_jv_detail = line[2:4] == "JD" if is_batch_group: batch_number = int(line[15:24]) ejv_file = EjvFileModel.find_by_id(batch_number) @@ -169,11 +180,11 @@ def _process_jv_details_feedback(ejv_file, has_errors, line, receipt_number) -> details = _build_jv_details(line, receipt_number) # If the JV process failed, then mark the GL code against the invoice to be stopped # for further JV process for the credit GL. - current_app.logger.info('Is Credit or Debit %s - %s', line[104:105], ejv_file.file_type) + current_app.logger.info("Is Credit or Debit %s - %s", line[104:105], ejv_file.file_type) credit_or_debit_line = details.line[104:105] - if credit_or_debit_line == 'C' and ejv_file.file_type == EjvFileType.DISBURSEMENT.value: + if credit_or_debit_line == "C" and ejv_file.file_type == EjvFileType.DISBURSEMENT.value: has_errors = _handle_jv_disbursement_feedback(details, has_errors) - elif credit_or_debit_line == 'D' and ejv_file.file_type == EjvFileType.PAYMENT.value: + elif credit_or_debit_line == "D" and ejv_file.file_type == EjvFileType.PAYMENT.value: has_errors = _handle_jv_payment_feedback(details, has_errors) return has_errors @@ -188,20 +199,23 @@ def _build_jv_details(line, receipt_number) -> JVDetailsFeedback: flowthrough=line[205:315].strip(), invoice_return_code=line[315:319], invoice_return_message=line[319:469], - receipt_number=receipt_number + receipt_number=receipt_number, ) - if '-' in details.flowthrough: - invoice_id = int(details.flowthrough.split('-')[0]) - partner_disbursement_id = int(details.flowthrough.split('-')[1]) + if "-" in details.flowthrough: + invoice_id = int(details.flowthrough.split("-")[0]) + partner_disbursement_id = int(details.flowthrough.split("-")[1]) details.partner_disbursement = PartnerDisbursementsModel.find_by_id(partner_disbursement_id) else: invoice_id = int(details.flowthrough) - current_app.logger.info('Invoice id - %s', invoice_id) + current_app.logger.info("Invoice id - %s", invoice_id) details.invoice = InvoiceModel.find_by_id(invoice_id) - details.invoice_link = db.session.query(EjvLinkModel).filter( - EjvLinkModel.ejv_header_id == details.ejv_header_model_id).filter( - EjvLinkModel.link_id == invoice_id).filter( - EjvLinkModel.link_type == EJVLinkType.INVOICE.value).one_or_none() + details.invoice_link = ( + db.session.query(EjvLinkModel) + .filter(EjvLinkModel.ejv_header_id == details.ejv_header_model_id) + .filter(EjvLinkModel.link_id == invoice_id) + .filter(EjvLinkModel.link_type == EJVLinkType.INVOICE.value) + .one_or_none() + ) return details @@ -209,7 +223,7 @@ def _handle_jv_disbursement_feedback(details: JVDetailsFeedback, has_errors: boo disbursement_status = _get_disbursement_status(details.invoice_return_code) details.invoice_link.disbursement_status_code = disbursement_status details.invoice_link.message = details.invoice_return_message.strip() - current_app.logger.info('disbursement_status %s', disbursement_status) + current_app.logger.info("disbursement_status %s", disbursement_status) if disbursement_status == DisbursementStatus.ERRORED.value: has_errors = True if details.partner_disbursement: @@ -219,13 +233,13 @@ def _handle_jv_disbursement_feedback(details: JVDetailsFeedback, has_errors: boo line_items: List[PaymentLineItemModel] = details.invoice.payment_line_items for line_item in line_items: # Line debit distribution - debit_distribution: DistributionCodeModel = DistributionCodeModel \ - .find_by_id(line_item.fee_distribution_id) - credit_distribution: DistributionCodeModel = DistributionCodeModel \ - .find_by_id(debit_distribution.disbursement_distribution_code_id) + debit_distribution: DistributionCodeModel = DistributionCodeModel.find_by_id(line_item.fee_distribution_id) + credit_distribution: DistributionCodeModel = DistributionCodeModel.find_by_id( + debit_distribution.disbursement_distribution_code_id + ) credit_distribution.stop_ejv = True else: - effective_date = datetime.strptime(details.line[22:30], '%Y%m%d') + effective_date = datetime.strptime(details.line[22:30], "%Y%m%d") _update_invoice_disbursement_status(details.invoice, effective_date, details.partner_disbursement) return has_errors @@ -234,25 +248,30 @@ def _handle_jv_payment_feedback(details: JVDetailsFeedback, has_errors: bool) -> # This is for gov account payment JV. details.invoice_link.disbursement_status_code = _get_disbursement_status(details.invoice_return_code) details.invoice_link.message = details.invoice_return_message.strip() - current_app.logger.info('Invoice ID %s', details.invoice.id) + current_app.logger.info("Invoice ID %s", details.invoice.id) inv_ref = InvoiceReferenceModel.find_by_invoice_id_and_status( - details.invoice.id, InvoiceReferenceStatus.ACTIVE.value) - current_app.logger.info('invoice_link.disbursement_status_code %s', details.invoice_link.disbursement_status_code) + details.invoice.id, InvoiceReferenceStatus.ACTIVE.value + ) + current_app.logger.info( + "invoice_link.disbursement_status_code %s", + details.invoice_link.disbursement_status_code, + ) if details.invoice_link.disbursement_status_code == DisbursementStatus.ERRORED.value: has_errors = True # Cancel the invoice reference. if inv_ref: inv_ref.status_code = InvoiceReferenceStatus.CANCELLED.value # Find the distribution code and set the stop_ejv flag to TRUE - dist_code = DistributionCodeModel.find_by_active_for_account( - details.invoice.payment_account_id) + dist_code = DistributionCodeModel.find_by_active_for_account(details.invoice.payment_account_id) dist_code.stop_ejv = True elif details.invoice_link.disbursement_status_code == DisbursementStatus.COMPLETED.value: # Set the invoice status as REFUNDED if it's a JV reversal, else mark as PAID - effective_date = datetime.strptime(details.line[22:30], '%Y%m%d') + effective_date = datetime.strptime(details.line[22:30], "%Y%m%d") # No need for credited here as these are just for EJV payments, which are never credited. is_reversal = details.invoice.invoice_status_code in ( - InvoiceStatus.REFUNDED.value, InvoiceStatus.REFUND_REQUESTED.value) + InvoiceStatus.REFUNDED.value, + InvoiceStatus.REFUND_REQUESTED.value, + ) _set_invoice_jv_reversal(details.invoice, effective_date, is_reversal) # Mark the invoice reference as COMPLETED, create a receipt @@ -260,14 +279,18 @@ def _handle_jv_payment_feedback(details: JVDetailsFeedback, has_errors: bool) -> inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value # Find receipt and add total to it, as single invoice can be multiple rows in the file if not is_reversal: - receipt = ReceiptModel.find_by_invoice_id_and_receipt_number(invoice_id=details.invoice.id, - receipt_number=details.receipt_number) + receipt = ReceiptModel.find_by_invoice_id_and_receipt_number( + invoice_id=details.invoice.id, receipt_number=details.receipt_number + ) if receipt: receipt.receipt_amount += float(details.line[89:104]) else: - ReceiptModel(invoice_id=details.invoice.id, receipt_number=details.receipt_number, - receipt_date=datetime.now(tz=timezone.utc), - receipt_amount=float(details.line[89:104])).flush() + ReceiptModel( + invoice_id=details.invoice.id, + receipt_number=details.receipt_number, + receipt_date=datetime.now(tz=timezone.utc), + receipt_amount=float(details.line[89:104]), + ).flush() return has_errors @@ -285,9 +308,9 @@ def _set_invoice_jv_reversal(invoice: InvoiceModel, effective_date: datetime, is def _fix_invoice_line(line): """Work around for CAS, they said fix the feedback files.""" # Check for zeros within 300->315 range. Bump them over with spaces. - if (zero_position := line[300:315].find('0')) > -1: + if (zero_position := line[300:315].find("0")) > -1: spaces_to_insert = 15 - zero_position - return line[:300 + zero_position] + (' ' * spaces_to_insert) + line[300 + zero_position:] + return line[: 300 + zero_position] + (" " * spaces_to_insert) + line[300 + zero_position :] return line @@ -300,13 +323,18 @@ def _update_partner_disbursement(partner_disbursement, status_code, effective_da partner_disbursement.feedback_on = effective_date -def _update_invoice_disbursement_status(invoice: InvoiceModel, - effective_date: datetime, - partner_disbursement: PartnerDisbursementsModel): +def _update_invoice_disbursement_status( + invoice: InvoiceModel, + effective_date: datetime, + partner_disbursement: PartnerDisbursementsModel, +): """Update status to reversed if its a refund, else to completed.""" # Look up partner disbursements table and update the status. - if invoice.invoice_status_code in (InvoiceStatus.REFUNDED.value, InvoiceStatus.REFUND_REQUESTED.value, - InvoiceStatus.CREDITED.value): + if invoice.invoice_status_code in ( + InvoiceStatus.REFUNDED.value, + InvoiceStatus.REFUND_REQUESTED.value, + InvoiceStatus.CREDITED.value, + ): _update_partner_disbursement(partner_disbursement, DisbursementStatus.REVERSED.value, effective_date) invoice.disbursement_status_code = DisbursementStatus.REVERSED.value invoice.disbursement_reversal_date = effective_date @@ -326,55 +354,57 @@ def _create_payment_record(amount, ejv_header, receipt_number): receipt_number=receipt_number, invoice_amount=amount, paid_amount=amount, - payment_date=datetime.now()).flush() + payment_date=datetime.now(), + ).flush() def _group_batches(content: str) -> Dict[str, List]: """Group batches based on the group and trailer.""" # A batch starts from BG to BT. - group_batches: Dict[str, List] = {'EJV': [], 'AP': []} - batch_content: str = '' + group_batches: Dict[str, List] = {"EJV": [], "AP": []} + batch_content: str = "" is_ejv = True for line in content.splitlines(): - if line[:4] in ('GABG', 'GIBG', 'APBG'): # batch starts from GIBG or GABG for JV - is_ejv = line[:4] in ('GABG', 'GIBG') + if line[:4] in ( + "GABG", + "GIBG", + "APBG", + ): # batch starts from GIBG or GABG for JV + is_ejv = line[:4] in ("GABG", "GIBG") batch_content = line else: batch_content = batch_content + os.linesep + line - if line[2:4] == 'BT': # batch ends with BT + if line[2:4] == "BT": # batch ends with BT if is_ejv: - group_batches['EJV'].append(batch_content) + group_batches["EJV"].append(batch_content) else: - group_batches['AP'].append(batch_content) + group_batches["AP"].append(batch_content) return group_batches def _get_disbursement_status(return_code: str) -> str: """Return disbursement status from return code.""" - if return_code == '0000': + if return_code == "0000": return DisbursementStatus.COMPLETED.value return DisbursementStatus.ERRORED.value def _publish_mailer_events(file_name: str, minio_location: str): """Publish payment message to the mailer queue.""" - payload = { - 'fileName': file_name, - 'minioLocation': minio_location - } + payload = {"fileName": file_name, "minioLocation": minio_location} try: gcp_queue_publisher.publish_to_queue( QueueMessage( source=QueueSources.PAY_QUEUE.value, message_type=QueueMessageTypes.EJV_FAILED.value, payload=payload, - topic=current_app.config.get('ACCOUNT_MAILER_TOPIC') + topic=current_app.config.get("ACCOUNT_MAILER_TOPIC"), ) ) except Exception as e: # NOQA pylint: disable=broad-except current_app.logger.error(e) - capture_message('EJV Failed message error', level='error') + capture_message("EJV Failed message error", level="error") def _process_ap_feedback(group_batches) -> bool: # pylint:disable=too-many-locals @@ -384,9 +414,9 @@ def _process_ap_feedback(group_batches) -> bool: # pylint:disable=too-many-loca ejv_file: Optional[EjvFileModel] = None for line in group_batch.splitlines(): # For all these indexes refer the sharepoint docs refer : https://github.com/bcgov/entity/issues/6226 - is_batch_group: bool = line[2:4] == 'BG' - is_batch_header: bool = line[2:4] == 'BH' - is_ap_header: bool = line[2:4] == 'IH' + is_batch_group: bool = line[2:4] == "BG" + is_batch_header: bool = line[2:4] == "BH" + is_ap_header: bool = line[2:4] == "IH" if is_batch_group: batch_number = int(line[15:24]) ejv_file = EjvFileModel.find_by_id(batch_number) @@ -424,8 +454,10 @@ def _process_ap_header_routing_slips(line) -> bool: if _get_disbursement_status(ap_header_return_code) == DisbursementStatus.ERRORED.value: has_errors = True routing_slip.status = RoutingSlipStatus.REFUND_REJECTED.value - capture_message(f'Refund failed for {routing_slip_number}, reason : {ap_header_error_message}', - level='error') + capture_message( + f"Refund failed for {routing_slip_number}, reason : {ap_header_error_message}", + level="error", + ) else: routing_slip.status = RoutingSlipStatus.REFUND_COMPLETED.value refund = RefundModel.find_by_routing_slip_id(routing_slip.id) @@ -444,7 +476,10 @@ def _process_ap_header_eft(line) -> bool: has_errors = True eft_refund.status = EFTShortnameRefundStatus.ERRORED.value eft_refund.disbursement_status_code = DisbursementStatus.ERRORED.value - capture_message(f'EFT Refund failed for {eft_refund_id}, reason : {ap_header_error_message}', level='error') + capture_message( + f"EFT Refund failed for {eft_refund_id}, reason : {ap_header_error_message}", + level="error", + ) else: eft_refund.status = EFTShortnameRefundStatus.COMPLETED.value eft_refund.disbursement_status_code = DisbursementStatus.COMPLETED.value @@ -460,19 +495,24 @@ def _process_ap_header_non_gov_disbursement(line, ejv_file: EjvFileModel) -> boo ap_header_return_code = line[414:418] ap_header_error_message = line[418:568] disbursement_status = _get_disbursement_status(ap_header_return_code) - invoice_link = db.session.query(EjvLinkModel)\ - .join(EjvHeaderModel).join(EjvFileModel)\ - .filter(EjvFileModel.id == ejv_file.id)\ - .filter(EjvLinkModel.link_id == invoice_id)\ - .filter(EjvLinkModel.link_type == EJVLinkType.INVOICE.value) \ + invoice_link = ( + db.session.query(EjvLinkModel) + .join(EjvHeaderModel) + .join(EjvFileModel) + .filter(EjvFileModel.id == ejv_file.id) + .filter(EjvLinkModel.link_id == invoice_id) + .filter(EjvLinkModel.link_type == EJVLinkType.INVOICE.value) .one_or_none() + ) invoice_link.disbursement_status_code = disbursement_status invoice_link.message = ap_header_error_message.strip() if disbursement_status == DisbursementStatus.ERRORED.value: invoice.disbursement_status_code = disbursement_status has_errors = True - capture_message(f'AP - NON-GOV - Disbursement failed for {invoice_id}, reason : {ap_header_error_message}', - level='error') + capture_message( + f"AP - NON-GOV - Disbursement failed for {invoice_id}, reason : {ap_header_error_message}", + level="error", + ) else: # TODO - Fix this on BC Assessment launch, so the effective date reads from the feedback. _update_invoice_disbursement_status(invoice, effective_date=datetime.now(), partner_disbursement=None) diff --git a/pay-queue/src/pay_queue/services/eft/eft_base.py b/pay-queue/src/pay_queue/services/eft/eft_base.py index 13f396db7..0026d7b4c 100644 --- a/pay-queue/src/pay_queue/services/eft/eft_base.py +++ b/pay-queue/src/pay_queue/services/eft/eft_base.py @@ -100,8 +100,8 @@ def parse_decimal(self, value: str, error: EFTError) -> decimal: """Try to parse decimal value from a string, return None if it fails and add an error.""" try: # ends with blank or minus sign, handle the minus sign situation - if value.endswith('-'): - value = '-' + value[:-1] + if value.endswith("-"): + value = "-" + value[:-1] result = decimal.Decimal(str(value)) except (ValueError, TypeError, decimal.InvalidOperation): diff --git a/pay-queue/src/pay_queue/services/eft/eft_enums.py b/pay-queue/src/pay_queue/services/eft/eft_enums.py index 86ae39f6e..e47be6a24 100644 --- a/pay-queue/src/pay_queue/services/eft/eft_enums.py +++ b/pay-queue/src/pay_queue/services/eft/eft_enums.py @@ -19,16 +19,16 @@ class EFTConstants(Enum): """EFT constants.""" # Currency - CURRENCY_CAD = 'CAD' + CURRENCY_CAD = "CAD" # Record Type - HEADER_RECORD_TYPE = '1' - TRANSACTION_RECORD_TYPE = '2' - TRAILER_RECORD_TYPE = '7' + HEADER_RECORD_TYPE = "1" + TRANSACTION_RECORD_TYPE = "2" + TRAILER_RECORD_TYPE = "7" # Formats - DATE_TIME_FORMAT = '%Y%m%d%H%M' - DATE_FORMAT = '%Y%m%d' + DATE_TIME_FORMAT = "%Y%m%d%H%M" + DATE_FORMAT = "%Y%m%d" # Lengths EXPECTED_LINE_LENGTH = 140 diff --git a/pay-queue/src/pay_queue/services/eft/eft_errors.py b/pay-queue/src/pay_queue/services/eft/eft_errors.py index 21b5906ad..7abf7566a 100644 --- a/pay-queue/src/pay_queue/services/eft/eft_errors.py +++ b/pay-queue/src/pay_queue/services/eft/eft_errors.py @@ -19,16 +19,16 @@ class EFTError(Enum): """EFT Error Enum.""" - INVALID_LINE_LENGTH = 'Invalid EFT file line length.' - INVALID_RECORD_TYPE = 'Invalid Record Type.' - INVALID_CREATION_DATETIME = 'Invalid header creation date time.' - INVALID_DEPOSIT_START_DATE = 'Invalid header deposit start date.' - INVALID_DEPOSIT_END_DATE = 'Invalid header deposit end date.' - INVALID_NUMBER_OF_DETAILS = 'Invalid trailer number of details value.' - INVALID_TOTAL_DEPOSIT_AMOUNT = 'Invalid trailer total deposit amount.' - INVALID_DEPOSIT_AMOUNT = 'Invalid transaction deposit amount.' - INVALID_EXCHANGE_ADJ_AMOUNT = 'Invalid transaction exchange adjustment amount.' - INVALID_DEPOSIT_AMOUNT_CAD = 'Invalid transaction deposit amount CAD.' - INVALID_TRANSACTION_DATE = 'Invalid transaction date.' - INVALID_DEPOSIT_DATETIME = 'Invalid transaction deposit date time' - ACCOUNT_SHORTNAME_REQUIRED = 'Account shortname is missing from the transaction description.' + INVALID_LINE_LENGTH = "Invalid EFT file line length." + INVALID_RECORD_TYPE = "Invalid Record Type." + INVALID_CREATION_DATETIME = "Invalid header creation date time." + INVALID_DEPOSIT_START_DATE = "Invalid header deposit start date." + INVALID_DEPOSIT_END_DATE = "Invalid header deposit end date." + INVALID_NUMBER_OF_DETAILS = "Invalid trailer number of details value." + INVALID_TOTAL_DEPOSIT_AMOUNT = "Invalid trailer total deposit amount." + INVALID_DEPOSIT_AMOUNT = "Invalid transaction deposit amount." + INVALID_EXCHANGE_ADJ_AMOUNT = "Invalid transaction exchange adjustment amount." + INVALID_DEPOSIT_AMOUNT_CAD = "Invalid transaction deposit amount CAD." + INVALID_TRANSACTION_DATE = "Invalid transaction date." + INVALID_DEPOSIT_DATETIME = "Invalid transaction deposit date time" + ACCOUNT_SHORTNAME_REQUIRED = "Account shortname is missing from the transaction description." diff --git a/pay-queue/src/pay_queue/services/eft/eft_header.py b/pay-queue/src/pay_queue/services/eft/eft_header.py index 9244eafa3..03aaef916 100644 --- a/pay-queue/src/pay_queue/services/eft/eft_header.py +++ b/pay-queue/src/pay_queue/services/eft/eft_header.py @@ -44,8 +44,10 @@ def _process(self): self.validate_record_type(EFTConstants.HEADER_RECORD_TYPE.value) # Confirm valid file creation datetime - self.creation_datetime = self.parse_datetime(self.extract_value(16, 24) + self.extract_value(41, 45), - EFTError.INVALID_CREATION_DATETIME) + self.creation_datetime = self.parse_datetime( + self.extract_value(16, 24) + self.extract_value(41, 45), + EFTError.INVALID_CREATION_DATETIME, + ) # Confirm valid deposit dates self.starting_deposit_date = self.parse_date(self.extract_value(69, 77), EFTError.INVALID_DEPOSIT_START_DATE) diff --git a/pay-queue/src/pay_queue/services/eft/eft_reconciliation.py b/pay-queue/src/pay_queue/services/eft/eft_reconciliation.py index 1795f4fbe..02f5bdf87 100644 --- a/pay-queue/src/pay_queue/services/eft/eft_reconciliation.py +++ b/pay-queue/src/pay_queue/services/eft/eft_reconciliation.py @@ -39,25 +39,24 @@ def __init__(self, ce): """:param ce: The cloud event object containing relevant data.""" self.ce = ce self.msg = ce.data - self.file_name: str = self.msg.get('fileName') - self.minio_location: str = self.msg.get('location') + self.file_name: str = self.msg.get("fileName") + self.minio_location: str = self.msg.get("location") self.error_messages: List[Dict[str, any]] = [] - def eft_error_handling(self, row, error_msg: str, - capture_error: bool = True, table_name: str = None): + def eft_error_handling(self, row, error_msg: str, capture_error: bool = True, table_name: str = None): """Handle EFT errors by logging, capturing messages, and optionally sending an email.""" if capture_error: current_app.logger.error(error_msg, exc_info=True) - capture_message(error_msg, level='error') - self.error_messages.append({'error': error_msg, 'row': row}) + capture_message(error_msg, level="error") + self.error_messages.append({"error": error_msg, "row": row}) if table_name is not None: email_service_params = EmailParams( - subject='EFT TDI17 Reconciliation Failure', + subject="EFT TDI17 Reconciliation Failure", file_name=self.file_name, minio_location=self.minio_location, error_messages=self.error_messages, ce=self.ce, - table_name=table_name + table_name=table_name, ) send_error_email(email_service_params) @@ -79,25 +78,28 @@ def reconcile_eft_payments(ce): # pylint: disable=too-many-locals context = EFTReconciliation(ce) # Used to filter transactions by location id to isolate EFT specific transactions from the TDI17 - eft_location_id = current_app.config.get('EFT_TDI17_LOCATION_ID') + eft_location_id = current_app.config.get("EFT_TDI17_LOCATION_ID") if eft_location_id is None: - context.eft_error_handling('N/A', 'Missing EFT_TDI17_LOCATION_ID configuration') + context.eft_error_handling("N/A", "Missing EFT_TDI17_LOCATION_ID configuration") return # Fetch EFT File file = get_object(context.minio_location, context.file_name) - file_content = file.data.decode('utf-8-sig') + file_content = file.data.decode("utf-8-sig") # Split into lines lines = file_content.splitlines() # Check if there is an existing EFT File record - eft_file_model: EFTFileModel = db.session.query(EFTFileModel).filter( - EFTFileModel.file_ref == context.file_name).one_or_none() - - if eft_file_model and eft_file_model.status_code in \ - [EFTProcessStatus.IN_PROGRESS.value, EFTProcessStatus.COMPLETED.value]: - current_app.logger.info('File: %s already %s.', context.file_name, str(eft_file_model.status_code)) + eft_file_model: EFTFileModel = ( + db.session.query(EFTFileModel).filter(EFTFileModel.file_ref == context.file_name).one_or_none() + ) + + if eft_file_model and eft_file_model.status_code in [ + EFTProcessStatus.IN_PROGRESS.value, + EFTProcessStatus.COMPLETED.value, + ]: + current_app.logger.info("File: %s already %s.", context.file_name, str(eft_file_model.status_code)) return # There is no existing EFT File record - instantiate one @@ -115,11 +117,10 @@ def reconcile_eft_payments(ce): # pylint: disable=too-many-locals # If header and/or trailer has errors do not proceed if not (eft_header_valid and eft_trailer_valid): - error_msg = f'Failed to process file {context.file_name} with an invalid header or trailer.' + error_msg = f"Failed to process file {context.file_name} with an invalid header or trailer." eft_file_model.status_code = EFTProcessStatus.FAILED.value eft_file_model.save() - context.eft_error_handling('N/A', error_msg, - table_name=eft_file_model.__tablename__) + context.eft_error_handling("N/A", error_msg, table_name=eft_file_model.__tablename__) return has_eft_transaction_errors = False @@ -131,19 +132,26 @@ def reconcile_eft_payments(ce): # pylint: disable=too-many-locals for eft_transaction in eft_transactions: if eft_transaction.has_errors(): # Flag any instance of an error - will indicate file is partially processed has_eft_transaction_errors = True - context.eft_error_handling(eft_transaction.index, eft_transaction.errors[0].message, capture_error=False) + context.eft_error_handling( + eft_transaction.index, + eft_transaction.errors[0].message, + capture_error=False, + ) _save_eft_transaction(eft_record=eft_transaction, eft_file_model=eft_file_model, is_error=True) else: # Save TDI17 transaction record - _save_eft_transaction(eft_record=eft_transaction, eft_file_model=eft_file_model, is_error=False) + _save_eft_transaction( + eft_record=eft_transaction, + eft_file_model=eft_file_model, + is_error=False, + ) # EFT Transactions have parsing errors - stop and FAIL transactions # We want a full file to be parseable as we want to get a full accurate balance before applying them to invoices if has_eft_transaction_errors: - error_msg = f'Failed to process file {context.file_name} has transaction parsing errors.' + error_msg = f"Failed to process file {context.file_name} has transaction parsing errors." _update_transactions_to_fail(eft_file_model) - context.eft_error_handling('N/A', error_msg, - table_name=eft_file_model.__tablename__) + context.eft_error_handling("N/A", error_msg, table_name=eft_file_model.__tablename__) return # Generate dictionary with shortnames and total deposits @@ -156,9 +164,8 @@ def reconcile_eft_payments(ce): # pylint: disable=too-many-locals if has_eft_transaction_errors or has_eft_credits_error: db.session.rollback() _update_transactions_to_fail(eft_file_model) - error_msg = f'Failed to process file {context.file_name} due to transaction errors.' - context.eft_error_handling('N/A', error_msg, - table_name=eft_file_model.__tablename__) + error_msg = f"Failed to process file {context.file_name} due to transaction errors." + context.eft_error_handling("N/A", error_msg, table_name=eft_file_model.__tablename__) return _finalize_process_state(eft_file_model) @@ -186,31 +193,32 @@ def _parse_tdi17_lines(eft_lines: List[str]): def _apply_eft_pending_payments(context: EFTReconciliation, shortname_balance): """Apply payments to short name links.""" for shortname in shortname_balance.keys(): - short_name_type = shortname_balance[shortname]['short_name_type'] + short_name_type = shortname_balance[shortname]["short_name_type"] eft_short_name = _get_shortname(shortname, short_name_type) eft_credit_balance = EFTCreditModel.get_eft_credit_balance(eft_short_name.id) - shortname_links = EFTShortnamesService.get_shortname_links(eft_short_name.id).get('items', []) + shortname_links = EFTShortnamesService.get_shortname_links(eft_short_name.id).get("items", []) for shortname_link in shortname_links: # We are expecting pending payments to have been cleared since this runs after the # eft task job. Something may have gone wrong, we will skip this link. - if shortname_link.get('has_pending_payment'): - error_msg = f'Unexpected pending payment on link: {shortname_link.id}' - context.eft_error_handling('N/A', error_msg, - table_name=eft_short_name.__tablename__) + if shortname_link.get("has_pending_payment"): + error_msg = f"Unexpected pending payment on link: {shortname_link.id}" + context.eft_error_handling("N/A", error_msg, table_name=eft_short_name.__tablename__) continue - amount_owing = shortname_link.get('amount_owing') - auth_account_id = shortname_link.get('account_id') + amount_owing = shortname_link.get("amount_owing") + auth_account_id = shortname_link.get("account_id") if 0 < amount_owing <= eft_credit_balance: try: - payload = {'action': EFTPaymentActions.APPLY_CREDITS.value, - 'accountId': auth_account_id} + payload = { + "action": EFTPaymentActions.APPLY_CREDITS.value, + "accountId": auth_account_id, + } EFTShortnamesService.process_payment_action(eft_short_name.id, payload) except Exception as exception: # NOQA # pylint: disable=broad-except # EFT Short name service handles commit and rollback when the action fails, we just need to make # sure we log the error here - error_msg = 'error in _apply_eft_pending_payments.' - context.eft_error_handling('N/A', error_msg, ex=exception) + error_msg = "error in _apply_eft_pending_payments." + context.eft_error_handling("N/A", error_msg, ex=exception) def _finalize_process_state(eft_file_model: EFTFileModel): @@ -226,7 +234,7 @@ def _finalize_process_state(eft_file_model: EFTFileModel): def _process_eft_header(eft_header: EFTHeader, eft_file_model: EFTFileModel) -> bool: """Process the EFT Header.""" if eft_header is None: - current_app.logger.error('Failed to process file %s with an invalid header.', eft_file_model.file_ref) + current_app.logger.error("Failed to process file %s with an invalid header.", eft_file_model.file_ref) return False # Populate header and trailer data on EFT File record - values will return None if parsing failed @@ -243,7 +251,10 @@ def _process_eft_header(eft_header: EFTHeader, eft_file_model: EFTFileModel) -> def _process_eft_trailer(eft_trailer: EFTTrailer, eft_file_model: EFTFileModel) -> bool: """Process the EFT Trailer.""" if eft_trailer is None: - current_app.logger.error('Failed to process file %s with an invalid trailer.', eft_file_model.file_ref) + current_app.logger.error( + "Failed to process file %s with an invalid trailer.", + eft_file_model.file_ref, + ) return False # Populate header and trailer data on EFT File record - values will return None if parsing failed @@ -262,23 +273,25 @@ def _process_eft_credits(shortname_balance, eft_file_id): has_credit_errors = False for shortname in shortname_balance.keys(): try: - short_name_type = shortname_balance[shortname]['short_name_type'] + short_name_type = shortname_balance[shortname]["short_name_type"] eft_short_name = _get_shortname(shortname, short_name_type) - eft_transactions = shortname_balance[shortname]['transactions'] + eft_transactions = shortname_balance[shortname]["transactions"] for eft_transaction in eft_transactions: # Check if there is an existing eft credit for this file and transaction - eft_credit_model = db.session.query(EFTCreditModel) \ - .filter(EFTCreditModel.eft_file_id == eft_file_id) \ - .filter(EFTCreditModel.short_name_id == eft_short_name.id) \ - .filter(EFTCreditModel.eft_transaction_id == eft_transaction['id']) \ + eft_credit_model = ( + db.session.query(EFTCreditModel) + .filter(EFTCreditModel.eft_file_id == eft_file_id) + .filter(EFTCreditModel.short_name_id == eft_short_name.id) + .filter(EFTCreditModel.eft_transaction_id == eft_transaction["id"]) .one_or_none() + ) if eft_credit_model is None: eft_credit_model = EFTCreditModel() # Skip if there is no deposit amount - deposit_amount = eft_transaction['deposit_amount'] + deposit_amount = eft_transaction["deposit_amount"] if not deposit_amount > 0: continue @@ -286,39 +299,45 @@ def _process_eft_credits(shortname_balance, eft_file_id): eft_credit_model.short_name_id = eft_short_name.id eft_credit_model.amount = deposit_amount eft_credit_model.remaining_amount = deposit_amount - eft_credit_model.eft_transaction_id = eft_transaction['id'] + eft_credit_model.eft_transaction_id = eft_transaction["id"] eft_credit_model.flush() credit_balance = EFTCreditModel.get_eft_credit_balance(eft_credit_model.short_name_id) - EFTHistoryService.create_funds_received(EFTHistory(short_name_id=eft_credit_model.short_name_id, - amount=deposit_amount, - credit_balance=credit_balance)).flush() + EFTHistoryService.create_funds_received( + EFTHistory( + short_name_id=eft_credit_model.short_name_id, + amount=deposit_amount, + credit_balance=credit_balance, + ) + ).flush() except Exception as e: # NOQA pylint: disable=broad-exception-caught has_credit_errors = True current_app.logger.error(e, exc_info=True) - capture_message('EFT Failed to set EFT balance.', level='error') + capture_message("EFT Failed to set EFT balance.", level="error") return has_credit_errors def _set_eft_header_on_file(eft_header: EFTHeader, eft_file_model: EFTFileModel): """Set EFT Header information on EFTFile model.""" - eft_file_model.file_creation_date = getattr(eft_header, 'creation_datetime', None) - eft_file_model.deposit_from_date = getattr(eft_header, 'starting_deposit_date', None) - eft_file_model.deposit_to_date = getattr(eft_header, 'ending_deposit_date', None) + eft_file_model.file_creation_date = getattr(eft_header, "creation_datetime", None) + eft_file_model.deposit_from_date = getattr(eft_header, "starting_deposit_date", None) + eft_file_model.deposit_to_date = getattr(eft_header, "ending_deposit_date", None) def _set_eft_trailer_on_file(eft_trailer: EFTTrailer, eft_file_model: EFTFileModel): """Set EFT Trailer information on EFTFile model.""" - eft_file_model.number_of_details = getattr(eft_trailer, 'number_of_details', None) - eft_file_model.total_deposit_cents = getattr(eft_trailer, 'total_deposit_amount', None) + eft_file_model.number_of_details = getattr(eft_trailer, "number_of_details", None) + eft_file_model.total_deposit_cents = getattr(eft_trailer, "total_deposit_amount", None) -def _set_eft_base_error(line_type: str, index: int, - eft_file_id: int, error_messages: [str]) -> EFTTransactionModel: +def _set_eft_base_error(line_type: str, index: int, eft_file_id: int, error_messages: [str]) -> EFTTransactionModel: """Instantiate EFT Transaction model error record.""" - eft_transaction_model = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_id) \ - .filter(EFTTransactionModel.line_type == line_type).one_or_none() + eft_transaction_model = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_id) + .filter(EFTTransactionModel.line_type == line_type) + .one_or_none() + ) if eft_transaction_model is None: eft_transaction_model = EFTTransactionModel() @@ -334,19 +353,23 @@ def _set_eft_base_error(line_type: str, index: int, def _save_eft_header_error(eft_header: EFTHeader, eft_file_model: EFTFileModel): """Save or update EFT Header error record.""" - eft_transaction_model = _set_eft_base_error(line_type=EFTFileLineType.HEADER.value, - index=eft_header.index, - eft_file_id=eft_file_model.id, - error_messages=eft_header.get_error_messages()) + eft_transaction_model = _set_eft_base_error( + line_type=EFTFileLineType.HEADER.value, + index=eft_header.index, + eft_file_id=eft_file_model.id, + error_messages=eft_header.get_error_messages(), + ) eft_transaction_model.save() def _save_eft_trailer_error(eft_trailer: EFTTrailer, eft_file_model: EFTFileModel): """Save or update EFT Trailer error record.""" - eft_transaction_model = _set_eft_base_error(line_type=EFTFileLineType.TRAILER.value, - index=eft_trailer.index, - eft_file_id=eft_file_model.id, - error_messages=eft_trailer.get_error_messages()) + eft_transaction_model = _set_eft_base_error( + line_type=EFTFileLineType.TRAILER.value, + index=eft_trailer.index, + eft_file_id=eft_file_model.id, + error_messages=eft_trailer.get_error_messages(), + ) eft_transaction_model.save() @@ -355,17 +378,21 @@ def _save_eft_transaction(eft_record: EFTRecord, eft_file_model: EFTFileModel, i line_type = EFTFileLineType.TRANSACTION.value status_code = EFTProcessStatus.FAILED.value if is_error else EFTProcessStatus.IN_PROGRESS.value - eft_transaction_model = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_number == eft_record.index) \ - .filter(EFTTransactionModel.line_type == line_type).one_or_none() + eft_transaction_model = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_number == eft_record.index) + .filter(EFTTransactionModel.line_type == line_type) + .one_or_none() + ) if eft_transaction_model is None: eft_transaction_model = EFTTransactionModel() if eft_record.transaction_description and eft_record.short_name_type: - eft_short_name: EFTShortnameModel = _get_shortname(eft_record.transaction_description, - eft_record.short_name_type) + eft_short_name: EFTShortnameModel = _get_shortname( + eft_record.transaction_description, eft_record.short_name_type + ) eft_transaction_model.short_name_id = eft_short_name.id eft_transaction_model.line_type = line_type @@ -373,13 +400,13 @@ def _save_eft_transaction(eft_record: EFTRecord, eft_file_model: EFTFileModel, i eft_transaction_model.file_id = eft_file_model.id eft_transaction_model.status_code = status_code eft_transaction_model.error_messages = eft_record.get_error_messages() - eft_transaction_model.batch_number = getattr(eft_record, 'batch_number', None) - eft_transaction_model.sequence_number = getattr(eft_record, 'transaction_sequence', None) - eft_transaction_model.jv_type = getattr(eft_record, 'jv_type', None) - eft_transaction_model.jv_number = getattr(eft_record, 'jv_number', None) - deposit_amount_cad = getattr(eft_record, 'deposit_amount_cad', None) - eft_transaction_model.deposit_date = getattr(eft_record, 'deposit_datetime') - eft_transaction_model.transaction_date = getattr(eft_record, 'transaction_date') + eft_transaction_model.batch_number = getattr(eft_record, "batch_number", None) + eft_transaction_model.sequence_number = getattr(eft_record, "transaction_sequence", None) + eft_transaction_model.jv_type = getattr(eft_record, "jv_type", None) + eft_transaction_model.jv_number = getattr(eft_record, "jv_number", None) + deposit_amount_cad = getattr(eft_record, "deposit_amount_cad", None) + eft_transaction_model.deposit_date = getattr(eft_record, "deposit_datetime") + eft_transaction_model.transaction_date = getattr(eft_record, "transaction_date") eft_transaction_model.deposit_amount_cents = deposit_amount_cad eft_transaction_model.save() @@ -388,10 +415,17 @@ def _save_eft_transaction(eft_record: EFTRecord, eft_file_model: EFTFileModel, i def _update_transactions_to_fail(eft_file_model: EFTFileModel) -> int: """Set EFT transactions to fail status.""" - result = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id, - EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value) \ - .update({EFTTransactionModel.status_code: EFTProcessStatus.FAILED.value}, synchronize_session='fetch') + result = ( + db.session.query(EFTTransactionModel) + .filter( + EFTTransactionModel.file_id == eft_file_model.id, + EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value, + ) + .update( + {EFTTransactionModel.status_code: EFTProcessStatus.FAILED.value}, + synchronize_session="fetch", + ) + ) eft_file_model.status_code = EFTProcessStatus.FAILED.value eft_file_model.save() @@ -401,10 +435,15 @@ def _update_transactions_to_fail(eft_file_model: EFTFileModel) -> int: def _update_transactions_to_complete(eft_file_model: EFTFileModel) -> int: """Set EFT transactions to complete status if they are currently in progress.""" - result = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.status_code == EFTProcessStatus.IN_PROGRESS.value) \ - .update({EFTTransactionModel.status_code: EFTProcessStatus.COMPLETED.value}, synchronize_session='fetch') + result = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.status_code == EFTProcessStatus.IN_PROGRESS.value) + .update( + {EFTTransactionModel.status_code: EFTProcessStatus.COMPLETED.value}, + synchronize_session="fetch", + ) + ) db.session.commit() return result @@ -412,10 +451,12 @@ def _update_transactions_to_complete(eft_file_model: EFTFileModel) -> int: def _get_shortname(short_name: str, short_name_type: str) -> EFTShortnameModel: """Save short name if it doesn't exist.""" - eft_short_name = db.session.query(EFTShortnameModel) \ - .filter(EFTShortnameModel.short_name == short_name) \ - .filter(EFTShortnameModel.type == short_name_type) \ + eft_short_name = ( + db.session.query(EFTShortnameModel) + .filter(EFTShortnameModel.short_name == short_name) + .filter(EFTShortnameModel.type == short_name_type) .one_or_none() + ) if eft_short_name is None: eft_short_name = EFTShortnameModel() @@ -438,12 +479,12 @@ def _shortname_balance_as_dict(eft_transactions: List[EFTRecord]) -> Dict: short_name = eft_transaction.transaction_description shortname_type = eft_transaction.short_name_type deposit_amount = eft_transaction.deposit_amount_cad / 100 - transaction = {'id': eft_transaction.id, 'deposit_amount': deposit_amount} + transaction = {"id": eft_transaction.id, "deposit_amount": deposit_amount} - shortname_balance.setdefault(short_name, {'balance': 0}) - shortname_balance[short_name]['short_name_type'] = shortname_type - shortname_balance[short_name]['balance'] += deposit_amount - shortname_balance[short_name].setdefault('transactions', []).append(transaction) + shortname_balance.setdefault(short_name, {"balance": 0}) + shortname_balance[short_name]["short_name_type"] = shortname_type + shortname_balance[short_name]["balance"] += deposit_amount + shortname_balance[short_name].setdefault("transactions", []).append(transaction) return shortname_balance @@ -451,10 +492,14 @@ def _shortname_balance_as_dict(eft_transactions: List[EFTRecord]) -> Dict: def _filter_eft_transactions(eft_transactions: List[EFTRecord], eft_location_id: str) -> List[EFTRecord]: """Filter down EFT Transactions.""" eft_transactions = [ - transaction for transaction in eft_transactions - if (transaction.has_errors() or (transaction.short_name_type in [ - EFTShortnameType.EFT.value, - EFTShortnameType.WIRE.value - ] and transaction.location_id == eft_location_id)) + transaction + for transaction in eft_transactions + if ( + transaction.has_errors() + or ( + transaction.short_name_type in [EFTShortnameType.EFT.value, EFTShortnameType.WIRE.value] + and transaction.location_id == eft_location_id + ) + ) ] return eft_transactions diff --git a/pay-queue/src/pay_queue/services/eft/eft_record.py b/pay-queue/src/pay_queue/services/eft/eft_record.py index 76a0e2765..4e0bd2ffb 100644 --- a/pay-queue/src/pay_queue/services/eft/eft_record.py +++ b/pay-queue/src/pay_queue/services/eft/eft_record.py @@ -26,9 +26,9 @@ class EFTRecord(EFTBase): """Defines the structure of the transaction record of a received EFT file.""" - PAD_DESCRIPTION_PATTERN = 'MISC PAYMENT BCONLINE' - EFT_DESCRIPTION_PATTERN = 'MISC PAYMENT' - WIRE_DESCRIPTION_PATTERN = 'FUNDS TRANSFER CR TT' + PAD_DESCRIPTION_PATTERN = "MISC PAYMENT BCONLINE" + EFT_DESCRIPTION_PATTERN = "MISC PAYMENT" + WIRE_DESCRIPTION_PATTERN = "FUNDS TRANSFER CR TT" ministry_code: str program_code: str @@ -75,10 +75,11 @@ def _process(self): self.program_code = self.extract_value(3, 7) deposit_time = self.extract_value(20, 24) - deposit_time = '0000' if len(deposit_time) == 0 else deposit_time # default to 0000 if time not provided + deposit_time = "0000" if len(deposit_time) == 0 else deposit_time # default to 0000 if time not provided - self.deposit_datetime = self.parse_datetime(self.extract_value(7, 15) + deposit_time, - EFTError.INVALID_DEPOSIT_DATETIME) + self.deposit_datetime = self.parse_datetime( + self.extract_value(7, 15) + deposit_time, EFTError.INVALID_DEPOSIT_DATETIME + ) self.location_id = self.extract_value(15, 20) self.transaction_sequence = self.extract_value(24, 27) @@ -99,8 +100,9 @@ def _process(self): # transaction date is optional - parse if there is a value transaction_date = self.extract_value(131, 139) - self.transaction_date = None if len(transaction_date) == 0 \ - else self.parse_date(transaction_date, EFTError.INVALID_TRANSACTION_DATE) + self.transaction_date = ( + None if len(transaction_date) == 0 else self.parse_date(transaction_date, EFTError.INVALID_TRANSACTION_DATE) + ) def parse_transaction_description(self): """Determine if the transaction is an EFT/Wire and parse it.""" @@ -109,11 +111,12 @@ def parse_transaction_description(self): if self.transaction_description.startswith(self.WIRE_DESCRIPTION_PATTERN): self.short_name_type = EFTShortnameType.WIRE.value - self.transaction_description = self.transaction_description[len(self.WIRE_DESCRIPTION_PATTERN):].strip() + self.transaction_description = self.transaction_description[len(self.WIRE_DESCRIPTION_PATTERN) :].strip() return # Check if this a PAD or EFT Transaction - if self.transaction_description.startswith(self.EFT_DESCRIPTION_PATTERN) \ - and not self.transaction_description.startswith(self.PAD_DESCRIPTION_PATTERN): + if self.transaction_description.startswith( + self.EFT_DESCRIPTION_PATTERN + ) and not self.transaction_description.startswith(self.PAD_DESCRIPTION_PATTERN): self.short_name_type = EFTShortnameType.EFT.value - self.transaction_description = self.transaction_description[len(self.EFT_DESCRIPTION_PATTERN):].strip() + self.transaction_description = self.transaction_description[len(self.EFT_DESCRIPTION_PATTERN) :].strip() diff --git a/pay-queue/src/pay_queue/services/email_service.py b/pay-queue/src/pay_queue/services/email_service.py index ce3132da2..dedae1aac 100644 --- a/pay-queue/src/pay_queue/services/email_service.py +++ b/pay-queue/src/pay_queue/services/email_service.py @@ -29,7 +29,7 @@ class EmailParams: """Params required to send error email.""" - subject: Optional[str] = '' + subject: Optional[str] = "" file_name: Optional[str] = None minio_location: Optional[str] = None error_messages: Optional[List[Dict[str, Any]]] = dataclasses.field(default_factory=list) @@ -39,58 +39,55 @@ class EmailParams: def send_error_email(params: EmailParams): """Send the email asynchronously, using the given details.""" - recipient = current_app.config.get('IT_OPS_EMAIL') + recipient = current_app.config.get("IT_OPS_EMAIL") current_dir = os.path.dirname(os.path.abspath(__file__)) project_root_dir = os.path.dirname(current_dir) - templates_dir = os.path.join(project_root_dir, 'templates') + templates_dir = os.path.join(project_root_dir, "templates") env = Environment(loader=FileSystemLoader(templates_dir), autoescape=True) - template = env.get_template('payment_reconciliation_failed_email.html') + template = env.get_template("payment_reconciliation_failed_email.html") email_params = { - 'fileName': params.file_name, - 'errorMessages': params.error_messages, - 'minioLocation': params.minio_location, - 'payload': json.dumps(dataclasses.asdict(params.ce)), - 'tableName': params.table_name + "fileName": params.file_name, + "errorMessages": params.error_messages, + "minioLocation": params.minio_location, + "payload": json.dumps(dataclasses.asdict(params.ce)), + "tableName": params.table_name, } html_body = template.render(email_params) - send_email_service( - recipients=[recipient], - subject=params.subject, - html_body=html_body - ) + send_email_service(recipients=[recipient], subject=params.subject, html_body=html_body) def send_email_service(recipients: list, subject: str, html_body: str): """Send the email notification.""" # Refactor this common code to PAY-API. token = get_service_account_token() - current_app.logger.info(f'>send_email to recipients: {recipients}') - notify_url = current_app.config.get('NOTIFY_API_ENDPOINT') + 'notify/' + current_app.logger.info(f">send_email to recipients: {recipients}") + notify_url = current_app.config.get("NOTIFY_API_ENDPOINT") + "notify/" success = False for recipient in recipients: notify_body = { - 'recipients': recipient, - 'content': { - 'subject': subject, - 'body': html_body - } + "recipients": recipient, + "content": {"subject": subject, "body": html_body}, } try: - notify_response = OAuthService.post(notify_url, token=token, - auth_header_type=AuthHeaderType.BEARER, - content_type=ContentType.JSON, data=notify_body) - current_app.logger.info(' PaymentModel: """Get the failed payment record for the invoice number.""" - payment: PaymentModel = db.session.query(PaymentModel) \ - .filter(PaymentModel.invoice_number == inv_number, - PaymentModel.payment_status_code == PaymentStatus.FAILED.value) \ - .order_by(PaymentModel.payment_date.desc()).first() + payment: PaymentModel = ( + db.session.query(PaymentModel) + .filter( + PaymentModel.invoice_number == inv_number, + PaymentModel.payment_status_code == PaymentStatus.FAILED.value, + ) + .order_by(PaymentModel.payment_date.desc()) + .first() + ) return payment @@ -178,10 +217,14 @@ def _get_payment_by_inv_number_and_status(inv_number: str, status: str) -> Payme # It's possible to look up null inv_number and return more than one. if inv_number is None: return None - payment: PaymentModel = db.session.query(PaymentModel) \ - .filter(PaymentModel.invoice_number == inv_number, - PaymentModel.payment_status_code == status) \ + payment: PaymentModel = ( + db.session.query(PaymentModel) + .filter( + PaymentModel.invoice_number == inv_number, + PaymentModel.payment_status_code == status, + ) .one_or_none() + ) return payment @@ -198,49 +241,61 @@ def reconcile_payments(ce): 4: If the transaction is On Account for Credit, apply the credit to the account. """ msg = ce.data - file_name: str = msg.get('fileName') - minio_location: str = msg.get('location') + file_name: str = msg.get("fileName") + minio_location: str = msg.get("location") - cas_settlement: CasSettlementModel = db.session.query(CasSettlementModel) \ - .filter(CasSettlementModel.file_name == file_name).one_or_none() + cas_settlement: CasSettlementModel = ( + db.session.query(CasSettlementModel).filter(CasSettlementModel.file_name == file_name).one_or_none() + ) if cas_settlement: - current_app.logger.info('File: %s has been processed or processing in progress. Skipping file. ' - 'Removing this row will allow processing to be restarted.', file_name) + current_app.logger.info( + "File: %s has been processed or processing in progress. Skipping file. " + "Removing this row will allow processing to be restarted.", + file_name, + ) return - current_app.logger.info('Creating cas_settlement record for file: %s', file_name) + current_app.logger.info("Creating cas_settlement record for file: %s", file_name) cas_settlement = _create_cas_settlement(file_name) file = get_object(minio_location, file_name) - content = file.data.decode('utf-8-sig') + content = file.data.decode("utf-8-sig") error_messages = [] has_errors, error_messages = _process_file_content(content, cas_settlement, msg, error_messages) - if has_errors and not current_app.config.get('DISABLE_CSV_ERROR_EMAIL'): + if has_errors and not current_app.config.get("DISABLE_CSV_ERROR_EMAIL"): email_service_params = EmailParams( - subject='Payment Reconciliation Failure', + subject="Payment Reconciliation Failure", file_name=file_name, minio_location=minio_location, error_messages=error_messages, ce=ce, - table_name=cas_settlement.__tablename__ + table_name=cas_settlement.__tablename__, ) send_error_email(email_service_params) -def _process_file_content(content: str, cas_settlement: CasSettlementModel, - msg: Dict[str, any], error_messages: List[Dict[str, any]]): +def _process_file_content( + content: str, + cas_settlement: CasSettlementModel, + msg: Dict[str, any], + error_messages: List[Dict[str, any]], +): """Process the content of the feedback file.""" has_errors = False # Iterate the rows and create key value pair for each row for row in csv.DictReader(content.splitlines()): # Convert lower case keys to avoid any key mismatch row = dict((k.lower(), v) for k, v in row.items()) - current_app.logger.debug('Processing %s', row) + current_app.logger.debug("Processing %s", row) # IF not PAD and application amount is zero, continue record_type = _get_row_value(row, Column.RECORD_TYPE) - pad_record_types: Tuple[str] = (RecordType.PAD.value, RecordType.PADR.value, RecordType.PAYR.value) + pad_record_types: Tuple[str] = ( + RecordType.PAD.value, + RecordType.PADR.value, + RecordType.PAYR.value, + ) if float(_get_row_value(row, Column.APP_AMOUNT)) == 0 and record_type not in pad_record_types: continue @@ -256,13 +311,17 @@ def _process_file_content(content: str, cas_settlement: CasSettlementModel, elif record_type in (RecordType.BOLP.value, RecordType.EFTP.value): # EFT, WIRE and Online Banking are one-to-one invoice. So handle them in same way. has_errors = _process_unconsolidated_invoices(row, error_messages) or has_errors - elif record_type in (RecordType.ONAC.value, RecordType.CMAP.value, RecordType.DRWP.value): + elif record_type in ( + RecordType.ONAC.value, + RecordType.CMAP.value, + RecordType.DRWP.value, + ): has_errors = _process_credit_on_invoices(row, error_messages) or has_errors elif record_type == RecordType.ADJS.value: - current_app.logger.info('Adjustment received for %s.', msg) + current_app.logger.info("Adjustment received for %s.", msg) else: # For any other transactions like DM log error and continue. - error_msg = f'Record Type is received as {record_type}, and cannot process {msg}.' + error_msg = f"Record Type is received as {record_type}, and cannot process {msg}." has_errors = True _csv_error_handling(row, error_msg, error_messages) # Continue processing @@ -273,26 +332,26 @@ def _process_file_content(content: str, cas_settlement: CasSettlementModel, # Create payment records for lines other than PAD try: _create_payment_records(content) - except Exception as e: # NOQA # pylint: disable=broad-except - error_msg = f'Error creating payment records: {str(e)}' + except Exception as e: # NOQA # pylint: disable=broad-except + error_msg = f"Error creating payment records: {str(e)}" has_errors = True - _csv_error_handling('N/A', error_msg, error_messages, e) + _csv_error_handling("N/A", error_msg, error_messages, e) return has_errors, error_messages try: _create_credit_records(content) - except Exception as e: # NOQA # pylint: disable=broad-except - error_msg = f'Error creating credit records: {str(e)}' + except Exception as e: # NOQA # pylint: disable=broad-except + error_msg = f"Error creating credit records: {str(e)}" has_errors = True - _csv_error_handling('N/A', error_msg, error_messages, e) + _csv_error_handling("N/A", error_msg, error_messages, e) return has_errors, error_messages try: _sync_credit_records_with_cfs() - except Exception as e: # NOQA # pylint: disable=broad-except - error_msg = f'Error syncing credit records: {str(e)}' + except Exception as e: # NOQA # pylint: disable=broad-except + error_msg = f"Error syncing credit records: {str(e)}" has_errors = True - _csv_error_handling('N/A', error_msg, error_messages, e) + _csv_error_handling("N/A", error_msg, error_messages, e) return has_errors, error_messages cas_settlement.processed_on = datetime.now() @@ -306,44 +365,48 @@ def _process_consolidated_invoices(row, error_messages: List[Dict[str, any]]) -> if (target_txn := _get_row_value(row, Column.TARGET_TXN)) == TargetTransaction.INV.value: inv_number = _get_row_value(row, Column.TARGET_TXN_NO) record_type = _get_row_value(row, Column.RECORD_TYPE) - current_app.logger.debug('Processing invoice : %s', inv_number) + current_app.logger.debug("Processing invoice : %s", inv_number) inv_references = _find_invoice_reference_by_number_and_status(inv_number, InvoiceReferenceStatus.ACTIVE.value) payment_account: PaymentAccountModel = _get_payment_account(row) if target_txn_status.lower() == Status.PAID.value.lower(): - current_app.logger.debug('Fully PAID payment.') + current_app.logger.debug("Fully PAID payment.") # if no inv reference is found, and if there are no COMPLETED inv ref, raise alert completed_inv_references = _find_invoice_reference_by_number_and_status( inv_number, InvoiceReferenceStatus.COMPLETED.value ) if not inv_references and not completed_inv_references: - error_msg = f'No invoice found for {inv_number} in the system, and cannot process {row}.' + error_msg = f"No invoice found for {inv_number} in the system, and cannot process {row}." has_errors = True _csv_error_handling(row, error_msg, error_messages) return has_errors _process_paid_invoices(inv_references, row) - elif target_txn_status.lower() == Status.NOT_PAID.value.lower() \ - or record_type in (RecordType.PADR.value, RecordType.PAYR.value): - current_app.logger.info('NOT PAID. NSF identified.') + elif target_txn_status.lower() == Status.NOT_PAID.value.lower() or record_type in ( + RecordType.PADR.value, + RecordType.PAYR.value, + ): + current_app.logger.info("NOT PAID. NSF identified.") # NSF Condition. Publish to account events for NSF. if _process_failed_payments(row): # Send mailer and account events to update status and send email notification _publish_account_events(QueueMessageTypes.NSF_LOCK_ACCOUNT.value, payment_account, row) else: - error_msg = f'Target Transaction Type is received as {target_txn} for PAD, and cannot process {row}.' + error_msg = f"Target Transaction Type is received as {target_txn} for PAD, and cannot process {row}." has_errors = True _csv_error_handling(row, error_msg, error_messages) return has_errors def _find_invoice_reference_by_number_and_status(inv_number: str, status: str): - inv_references: List[InvoiceReferenceModel] = db.session.query(InvoiceReferenceModel). \ - filter(InvoiceReferenceModel.status_code == status). \ - filter(InvoiceReferenceModel.invoice_number == inv_number). \ - all() + inv_references: List[InvoiceReferenceModel] = ( + db.session.query(InvoiceReferenceModel) + .filter(InvoiceReferenceModel.status_code == status) + .filter(InvoiceReferenceModel.invoice_number == inv_number) + .all() + ) return inv_references @@ -354,51 +417,60 @@ def _process_unconsolidated_invoices(row, error_messages: List[Dict[str, any]]) if (target_txn := _get_row_value(row, Column.TARGET_TXN)) == TargetTransaction.INV.value: inv_number = _get_row_value(row, Column.TARGET_TXN_NO) - inv_references: List[InvoiceReferenceModel] = db.session.query(InvoiceReferenceModel). \ - filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.ACTIVE.value). \ - filter(InvoiceReferenceModel.invoice_number == inv_number). \ - all() + inv_references: List[InvoiceReferenceModel] = ( + db.session.query(InvoiceReferenceModel) + .filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.ACTIVE.value) + .filter(InvoiceReferenceModel.invoice_number == inv_number) + .all() + ) if len(inv_references) != 1: # There could be case where same invoice can appear as PAID in 2 lines, especially when there are credits. # Make sure there is one invoice_reference with completed status, else raise error. - completed_inv_references: List[InvoiceReferenceModel] = db.session.query(InvoiceReferenceModel). \ - filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.COMPLETED.value). \ - filter(InvoiceReferenceModel.invoice_number == inv_number). \ - all() - current_app.logger.info('Found %s completed invoice references for invoice number %s', - len(completed_inv_references), inv_number) + completed_inv_references: List[InvoiceReferenceModel] = ( + db.session.query(InvoiceReferenceModel) + .filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.COMPLETED.value) + .filter(InvoiceReferenceModel.invoice_number == inv_number) + .all() + ) + current_app.logger.info( + "Found %s completed invoice references for invoice number %s", + len(completed_inv_references), + inv_number, + ) if len(completed_inv_references) != 1: - error_msg = (f'More than one or none invoice reference ' - f'received for invoice number {inv_number} for {record_type}') + error_msg = ( + f"More than one or none invoice reference " + f"received for invoice number {inv_number} for {record_type}" + ) has_errors = True _csv_error_handling(row, error_msg, error_messages) else: # Handle fully PAID and Partially Paid scenarios. if target_txn_status.lower() == Status.PAID.value.lower(): - current_app.logger.debug('Fully PAID payment.') + current_app.logger.debug("Fully PAID payment.") _process_paid_invoices(inv_references, row) elif target_txn_status.lower() == Status.PARTIAL.value.lower(): - current_app.logger.info('Partially PAID.') + current_app.logger.info("Partially PAID.") # As per validation above, get first and only inv ref _process_partial_paid_invoices(inv_references[0], row) else: - error_msg = (f'Target Transaction Type is received ' - f'as {target_txn} for {record_type}, and cannot process.') + error_msg = ( + f"Target Transaction Type is received " f"as {target_txn} for {record_type}, and cannot process." + ) has_errors = True _csv_error_handling(row, error_msg, error_messages) return has_errors -def _csv_error_handling(row, error_msg: str, error_messages: List[Dict[str, any]], - ex: Exception = None): +def _csv_error_handling(row, error_msg: str, error_messages: List[Dict[str, any]], ex: Exception = None): if ex: - formatted_traceback = ''.join(traceback.TracebackException.from_exception(ex).format()) - error_msg = f'{error_msg}\n{formatted_traceback}' + formatted_traceback = "".join(traceback.TracebackException.from_exception(ex).format()) + error_msg = f"{error_msg}\n{formatted_traceback}" current_app.logger.error(error_msg) - capture_message(error_msg, level='error') - error_messages.append({'error': error_msg, 'row': row}) + capture_message(error_msg, level="error") + error_messages.append({"error": error_msg, "row": row}) def _handle_credit_invoices_and_adjust_invoice_paid(row): @@ -406,10 +478,10 @@ def _handle_credit_invoices_and_adjust_invoice_paid(row): application_id = _get_row_value(row, Column.APP_ID) cfs_identifier = _get_row_value(row, Column.SOURCE_TXN_NO) if CfsCreditInvoices.find_by_application_id(application_id): - current_app.logger.warning(f'Credit invoices exists with application_id {application_id}.') + current_app.logger.warning(f"Credit invoices exists with application_id {application_id}.") return if not (credit := CreditModel.find_by_cfs_identifier(cfs_identifier=cfs_identifier, credit_memo=True)): - current_app.logger.warning(f'Credit with cfs_identifier {cfs_identifier} not found.') + current_app.logger.warning(f"Credit with cfs_identifier {cfs_identifier} not found.") return invoice_number = _get_row_value(row, Column.TARGET_TXN_NO) CfsCreditInvoices( @@ -418,21 +490,23 @@ def _handle_credit_invoices_and_adjust_invoice_paid(row): application_id=application_id, cfs_account=_get_row_value(row, Column.CUSTOMER_ACC), cfs_identifier=cfs_identifier, - created_on=datetime.strptime(_get_row_value(row, Column.APP_DATE), '%d-%b-%y'), + created_on=datetime.strptime(_get_row_value(row, Column.APP_DATE), "%d-%b-%y"), credit_id=credit.id, invoice_amount=Decimal(_get_row_value(row, Column.TARGET_TXN_ORIGINAL)), - invoice_number=invoice_number + invoice_number=invoice_number, ).save() amount = CfsCreditInvoices.credit_for_invoice_number(invoice_number) - invoices = db.session.query(InvoiceModel) \ - .join(InvoiceReferenceModel, InvoiceReferenceModel.invoice_id == InvoiceModel.id) \ - .filter(InvoiceReferenceModel.invoice_number == invoice_number) \ - .filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.COMPLETED.value) \ - .filter(InvoiceReferenceModel.is_consolidated.is_(False)) \ - .filter(InvoiceModel.invoice_status_code == InvoiceStatus.PAID.value) \ - .order_by(InvoiceModel.id.asc()) \ + invoices = ( + db.session.query(InvoiceModel) + .join(InvoiceReferenceModel, InvoiceReferenceModel.invoice_id == InvoiceModel.id) + .filter(InvoiceReferenceModel.invoice_number == invoice_number) + .filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.COMPLETED.value) + .filter(InvoiceReferenceModel.is_consolidated.is_(False)) + .filter(InvoiceModel.invoice_status_code == InvoiceStatus.PAID.value) + .order_by(InvoiceModel.id.asc()) .all() + ) for invoice in invoices: if amount <= 0: break @@ -442,7 +516,7 @@ def _handle_credit_invoices_and_adjust_invoice_paid(row): invoice.save() if amount >= 0: - current_app.logger.warning(f'Amount {amount} remaining after applying to invoices {invoice_number}.') + current_app.logger.warning(f"Amount {amount} remaining after applying to invoices {invoice_number}.") def _process_credit_on_invoices(row, error_messages: List[Dict[str, any]]) -> bool: @@ -451,21 +525,24 @@ def _process_credit_on_invoices(row, error_messages: List[Dict[str, any]]) -> bo target_txn_status = _get_row_value(row, Column.TARGET_TXN_STATUS) if _get_row_value(row, Column.TARGET_TXN) == TargetTransaction.INV.value: inv_number = _get_row_value(row, Column.TARGET_TXN_NO) - current_app.logger.debug('Processing invoice : %s', inv_number) + current_app.logger.debug("Processing invoice : %s", inv_number) - inv_references: List[InvoiceReferenceModel] = db.session.query(InvoiceReferenceModel). \ - filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.ACTIVE.value). \ - filter(InvoiceReferenceModel.invoice_number == inv_number). \ - all() + inv_references: List[InvoiceReferenceModel] = ( + db.session.query(InvoiceReferenceModel) + .filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.ACTIVE.value) + .filter(InvoiceReferenceModel.invoice_number == inv_number) + .all() + ) if target_txn_status.lower() == Status.PAID.value.lower(): - current_app.logger.debug('Fully PAID payment.') + current_app.logger.debug("Fully PAID payment.") _process_paid_invoices(inv_references, row) elif target_txn_status.lower() == Status.PARTIAL.value.lower(): - current_app.logger.info('Partially PAID using credit memo. ' - 'Ignoring as the credit memo payment is already captured.') + current_app.logger.info( + "Partially PAID using credit memo. Ignoring as the credit memo payment is already captured." + ) else: - error_msg = f'Target Transaction status is received as {target_txn_status} for CMAP, and cannot process.' + error_msg = f"Target Transaction status is received as {target_txn_status} for CMAP, and cannot process." has_errors = True _csv_error_handling(row, error_msg, error_messages) return has_errors @@ -482,18 +559,21 @@ def _process_paid_invoices(inv_references, row): for inv_ref in inv_references: invoice = InvoiceModel.find_by_id(inv_ref.invoice_id) if invoice.payment_method_code == PaymentMethod.CC.value: - current_app.logger.info('Cannot mark CC invoices as PAID.') + current_app.logger.info("Cannot mark CC invoices as PAID.") return - receipt_date = datetime.strptime(_get_row_value(row, Column.APP_DATE), '%d-%b-%y') + receipt_date = datetime.strptime(_get_row_value(row, Column.APP_DATE), "%d-%b-%y") receipt_number = _get_row_value(row, Column.SOURCE_TXN_NO) for inv_ref in inv_references: inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value # Find invoice, update status inv = InvoiceModel.find_by_id(inv_ref.invoice_id) _validate_account(inv, row) - current_app.logger.debug('PAID Invoice. Invoice Reference ID : %s, invoice ID : %s', - inv_ref.id, inv_ref.invoice_id) + current_app.logger.debug( + "PAID Invoice. Invoice Reference ID : %s, invoice ID : %s", + inv_ref.id, + inv_ref.invoice_id, + ) inv.invoice_status_code = InvoiceStatus.PAID.value inv.payment_date = receipt_date @@ -507,7 +587,7 @@ def _process_paid_invoices(inv_references, row): db.session.add(receipt) # Publish to the queue if it's an Online Banking payment if inv.payment_method_code == PaymentMethod.ONLINE_BANKING.value: - current_app.logger.debug('Publishing payment event for OB. Invoice : %s', inv.id) + current_app.logger.debug("Publishing payment event for OB. Invoice : %s", inv.id) _publish_payment_event(inv) @@ -518,13 +598,16 @@ def _process_partial_paid_invoices(inv_ref: InvoiceReferenceModel, row): Update Transaction is COMPLETED. Update Invoice as PARTIAL. """ - receipt_date: datetime = datetime.strptime(_get_row_value(row, Column.APP_DATE), '%d-%b-%y') + receipt_date: datetime = datetime.strptime(_get_row_value(row, Column.APP_DATE), "%d-%b-%y") receipt_number: str = _get_row_value(row, Column.APP_ID) inv: InvoiceModel = InvoiceModel.find_by_id(inv_ref.invoice_id) _validate_account(inv, row) - current_app.logger.debug('Partial Invoice. Invoice Reference ID : %s, invoice ID : %s', - inv_ref.id, inv_ref.invoice_id) + current_app.logger.debug( + "Partial Invoice. Invoice Reference ID : %s, invoice ID : %s", + inv_ref.id, + inv_ref.invoice_id, + ) inv.invoice_status_code = InvoiceStatus.PARTIAL.value inv.paid = inv.total - Decimal(_get_row_value(row, Column.TARGET_TXN_OUTSTANDING)) # Create Receipt records @@ -549,41 +632,45 @@ def _process_failed_payments(row): payment_account: PaymentAccountModel = _get_payment_account(row) # If there is a FAILED payment record for this; it means it's a duplicate event. Ignore it. - payment = PaymentModel.find_payment_by_invoice_number_and_status( - inv_number, PaymentStatus.FAILED.value - ) + payment = PaymentModel.find_payment_by_invoice_number_and_status(inv_number, PaymentStatus.FAILED.value) if payment: - current_app.logger.info('Ignoring duplicate NSF message for invoice : %s ', inv_number) + current_app.logger.info("Ignoring duplicate NSF message for invoice : %s ", inv_number) return False # If there is an NSF row, it means it's a duplicate NSF event. Ignore it. if NonSufficientFundsService.exists_for_invoice_number(inv_number): - current_app.logger.info('Ignoring duplicate NSF event for account: %s ', payment_account.auth_account_id) + current_app.logger.info( + "Ignoring duplicate NSF event for account: %s ", + payment_account.auth_account_id, + ) return False # Set CFS Account Status. cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, PaymentMethod.PAD.value) is_already_frozen = cfs_account.status == CfsAccountStatus.FREEZE.value - current_app.logger.info('setting payment account id : %s status as FREEZE', payment_account.id) + current_app.logger.info("setting payment account id : %s status as FREEZE", payment_account.id) cfs_account.status = CfsAccountStatus.FREEZE.value payment_account.has_nsf_invoices = datetime.now(tz=timezone.utc) # Call CFS to stop any further PAD transactions on this account. CFSService.update_site_receipt_method(cfs_account, receipt_method=RECEIPT_METHOD_PAD_STOP) if is_already_frozen: - current_app.logger.info('Ignoring NSF message for invoice : %s as the account is already FREEZE', inv_number) + current_app.logger.info( + "Ignoring NSF message for invoice : %s as the account is already FREEZE", + inv_number, + ) return False # Find the invoice_reference for this invoice and mark it as ACTIVE. - inv_references: List[InvoiceReferenceModel] = db.session.query(InvoiceReferenceModel). \ - filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.COMPLETED.value). \ - filter(InvoiceReferenceModel.invoice_number == inv_number). \ - all() + inv_references: List[InvoiceReferenceModel] = ( + db.session.query(InvoiceReferenceModel) + .filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.COMPLETED.value) + .filter(InvoiceReferenceModel.invoice_number == inv_number) + .all() + ) # Update status to ACTIVE, if it was marked COMPLETED for inv_reference in inv_references: inv_reference.status_code = InvoiceReferenceStatus.ACTIVE.value # Find receipt and delete it. - receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number( - invoice_id=inv_reference.invoice_id - ) + receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(invoice_id=inv_reference.invoice_id) if receipt: db.session.delete(receipt) # Find invoice and update the status to SETTLEMENT_SCHED @@ -615,7 +702,7 @@ def _create_credit_records(csv_content: str): is_credit_memo=False, amount=float(_get_row_value(row, Column.TARGET_TXN_ORIGINAL)), remaining_amount=float(_get_row_value(row, Column.TARGET_TXN_ORIGINAL)), - account_id=pay_account.id + account_id=pay_account.id, ).save() for row in csv.DictReader(csv_content.splitlines()): @@ -637,8 +724,8 @@ def _check_cfs_accounts_for_pad_and_ob(credit): has_online_banking = True if has_pad and has_online_banking: raise Exception( # pylint: disable=broad-exception-raised - 'Multiple payment methods for the same account for CREDITS' - ' credits has no link to CFS account.') + "Multiple payment methods for the same account for CREDITS credits has no link to CFS account." + ) def _sync_credit_records_with_cfs(): @@ -648,34 +735,37 @@ def _sync_credit_records_with_cfs(): # 3. If it's credit memo, call credit memo endpoint and calculate balance. # 4. Roll up the credits to credit field in payment_account. active_credits: List[CreditModel] = db.session.query(CreditModel).filter(CreditModel.remaining_amount > 0).all() - current_app.logger.info('Found %s credit records', len(active_credits)) + current_app.logger.info("Found %s credit records", len(active_credits)) account_ids: List[int] = [] for credit in active_credits: _check_cfs_accounts_for_pad_and_ob(credit) - cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(credit.account_id, - PaymentMethod.PAD.value) \ - or CfsAccountModel.find_effective_or_latest_by_payment_method(credit.account_id, - PaymentMethod.ONLINE_BANKING.value) + cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method( + credit.account_id, PaymentMethod.PAD.value + ) or CfsAccountModel.find_effective_or_latest_by_payment_method( + credit.account_id, PaymentMethod.ONLINE_BANKING.value + ) account_ids.append(credit.account_id) if credit.is_credit_memo: - credit_memo = CFSService.get_cms( - cfs_account=cfs_account, cms_number=credit.cfs_identifier) - credit.remaining_amount = abs(float(credit_memo.get('amount_due'))) + credit_memo = CFSService.get_cms(cfs_account=cfs_account, cms_number=credit.cfs_identifier) + credit.remaining_amount = abs(float(credit_memo.get("amount_due"))) else: - receipt = CFSService.get_receipt( - cfs_account=cfs_account, receipt_number=credit.cfs_identifier) - receipt_amount = float(receipt.get('receipt_amount')) + receipt = CFSService.get_receipt(cfs_account=cfs_account, receipt_number=credit.cfs_identifier) + receipt_amount = float(receipt.get("receipt_amount")) applied_amount: float = 0 - for invoice in receipt.get('invoices', []): - applied_amount += float(invoice.get('amount_applied')) + for invoice in receipt.get("invoices", []): + applied_amount += float(invoice.get("amount_applied")) credit.remaining_amount = receipt_amount - applied_amount credit.save() # Roll up the credits and add up to credit in payment_account. for account_id in set(account_ids): - account_credits: List[CreditModel] = db.session.query(CreditModel).filter( - CreditModel.remaining_amount > 0).filter(CreditModel.account_id == account_id).all() + account_credits: List[CreditModel] = ( + db.session.query(CreditModel) + .filter(CreditModel.remaining_amount > 0) + .filter(CreditModel.account_id == account_id) + .all() + ) credit_total: float = 0 for account_credit in account_credits: credit_total += account_credit.remaining_amount @@ -686,15 +776,23 @@ def _sync_credit_records_with_cfs(): def _get_payment_account(row) -> PaymentAccountModel: account_number: str = _get_row_value(row, Column.CUSTOMER_ACC) - payment_accounts: PaymentAccountModel = db.session.query(PaymentAccountModel) \ - .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \ - .filter(CfsAccountModel.cfs_account == account_number) \ + payment_accounts: PaymentAccountModel = ( + db.session.query(PaymentAccountModel) + .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) + .filter(CfsAccountModel.cfs_account == account_number) .filter( - CfsAccountModel.status.in_( - [CfsAccountStatus.ACTIVE.value, CfsAccountStatus.FREEZE.value, CfsAccountStatus.INACTIVE.value] - )).all() + CfsAccountModel.status.in_( + [ + CfsAccountStatus.ACTIVE.value, + CfsAccountStatus.FREEZE.value, + CfsAccountStatus.INACTIVE.value, + ] + ) + ) + .all() + ) if not all(payment_account.id == payment_accounts[0].id for payment_account in payment_accounts): - raise Exception('Multiple unique payment accounts for cfs_account.') # pylint: disable=broad-exception-raised + raise Exception("Multiple unique payment accounts for cfs_account.") # pylint: disable=broad-exception-raised return payment_accounts[0] if payment_accounts else None @@ -703,32 +801,38 @@ def _validate_account(inv: InvoiceModel, row: Dict[str, str]): # This should never happen, just in case cfs_account: CfsAccountModel = CfsAccountModel.find_by_id(inv.cfs_account_id) if (account_number := _get_row_value(row, Column.CUSTOMER_ACC)) != cfs_account.cfs_account: - current_app.logger.error('Customer Account received as %s, but expected %s.', - account_number, cfs_account.cfs_account) - capture_message(f'Customer Account received as {account_number}, but expected {cfs_account.cfs_account}.', - level='error') + current_app.logger.error( + "Customer Account received as %s, but expected %s.", + account_number, + cfs_account.cfs_account, + ) + capture_message( + f"Customer Account received as {account_number}, but expected {cfs_account.cfs_account}.", + level="error", + ) - raise Exception('Invalid Account Number') # pylint: disable=broad-exception-raised + raise Exception("Invalid Account Number") # pylint: disable=broad-exception-raised def _publish_payment_event(inv: InvoiceModel): """Publish payment message to the queue.""" - payload = PaymentTransactionService.create_event_payload(invoice=inv, - status_code=PaymentStatus.COMPLETED.value) + payload = PaymentTransactionService.create_event_payload(invoice=inv, status_code=PaymentStatus.COMPLETED.value) try: gcp_queue_publisher.publish_to_queue( QueueMessage( source=QueueSources.PAY_QUEUE.value, message_type=QueueMessageTypes.PAYMENT.value, payload=payload, - topic=get_topic_for_corp_type(inv.corp_type_code) + topic=get_topic_for_corp_type(inv.corp_type_code), ) ) except Exception as e: # NOQA pylint: disable=broad-except current_app.logger.error(e) - current_app.logger.warning('Notification to Queue failed for the Payment Event - %s', payload) - capture_message(f'Notification to Queue failed for the Payment Event {payload}.', - level='error') + current_app.logger.warning("Notification to Queue failed for the Payment Event - %s", payload) + capture_message( + f"Notification to Queue failed for the Payment Event {payload}.", + level="error", + ) def _publish_mailer_events(message_type: str, pay_account: PaymentAccountModel, row: Dict[str, str]): @@ -741,15 +845,22 @@ def _publish_mailer_events(message_type: str, pay_account: PaymentAccountModel, source=QueueSources.PAY_QUEUE.value, message_type=message_type, payload=payload, - topic=current_app.config.get('ACCOUNT_MAILER_TOPIC') + topic=current_app.config.get("ACCOUNT_MAILER_TOPIC"), ) ) except Exception as e: # NOQA pylint: disable=broad-except current_app.logger.error(e) - current_app.logger.warning('Notification to Queue failed for the Account Mailer %s - %s', - pay_account.auth_account_id, payload) - capture_message('Notification to Queue failed for the Account Mailer {auth_account_id}, {msg}.'.format( - auth_account_id=pay_account.auth_account_id, msg=payload), level='error') + current_app.logger.warning( + "Notification to Queue failed for the Account Mailer %s - %s", + pay_account.auth_account_id, + payload, + ) + capture_message( + "Notification to Queue failed for the Account Mailer {auth_account_id}, {msg}.".format( + auth_account_id=pay_account.auth_account_id, msg=payload + ), + level="error", + ) def _publish_online_banking_mailer_events(rows: List[Dict[str, str]], paid_amount: float): @@ -758,9 +869,17 @@ def _publish_online_banking_mailer_events(rows: List[Dict[str, str]], paid_amoun pay_account = _get_payment_account(rows[0]) # All rows are for same account. # Check for credit, or fully paid or under paid payment credit_rows = list( - filter(lambda r: (_get_row_value(r, Column.TARGET_TXN) == TargetTransaction.RECEIPT.value), rows)) + filter( + lambda r: (_get_row_value(r, Column.TARGET_TXN) == TargetTransaction.RECEIPT.value), + rows, + ) + ) under_pay_rows = list( - filter(lambda r: (_get_row_value(r, Column.TARGET_TXN_STATUS).lower() == Status.PARTIAL.value.lower()), rows)) + filter( + lambda r: (_get_row_value(r, Column.TARGET_TXN_STATUS).lower() == Status.PARTIAL.value.lower()), + rows, + ) + ) credit_amount: float = 0 if credit_rows: @@ -773,10 +892,10 @@ def _publish_online_banking_mailer_events(rows: List[Dict[str, str]], paid_amoun message_type = QueueMessageTypes.ONLINE_BANKING_PAYMENT.value payload = { - 'accountId': pay_account.auth_account_id, - 'paymentMethod': PaymentMethod.ONLINE_BANKING.value, - 'amount': '{:.2f}'.format(paid_amount), # pylint: disable = consider-using-f-string - 'creditAmount': '{:.2f}'.format(credit_amount) # pylint: disable = consider-using-f-string + "accountId": pay_account.auth_account_id, + "paymentMethod": PaymentMethod.ONLINE_BANKING.value, + "amount": "{:.2f}".format(paid_amount), # pylint: disable = consider-using-f-string + "creditAmount": "{:.2f}".format(credit_amount), # pylint: disable = consider-using-f-string } try: @@ -785,16 +904,21 @@ def _publish_online_banking_mailer_events(rows: List[Dict[str, str]], paid_amoun source=QueueSources.PAY_QUEUE.value, message_type=message_type, payload=payload, - topic=current_app.config.get('ACCOUNT_MAILER_TOPIC') + topic=current_app.config.get("ACCOUNT_MAILER_TOPIC"), ) ) except Exception as e: # NOQA pylint: disable=broad-except current_app.logger.error(e) - current_app.logger.warning('Notification to Queue failed for the Account Mailer %s - %s', - pay_account.auth_account_id, payload) - capture_message('Notification to Queue failed for the Account Mailer ' - '{auth_account_id}, {msg}.'.format(auth_account_id=pay_account.auth_account_id, msg=payload), - level='error') + current_app.logger.warning( + "Notification to Queue failed for the Account Mailer %s - %s", + pay_account.auth_account_id, + payload, + ) + capture_message( + "Notification to Queue failed for the Account Mailer " + "{auth_account_id}, {msg}.".format(auth_account_id=pay_account.auth_account_id, msg=payload), + level="error", + ) def _publish_account_events(message_type: str, pay_account: PaymentAccountModel, row: Dict[str, str]): @@ -807,24 +931,31 @@ def _publish_account_events(message_type: str, pay_account: PaymentAccountModel, source=QueueSources.PAY_QUEUE.value, message_type=message_type, payload=payload, - topic=current_app.config.get('AUTH_EVENT_TOPIC') + topic=current_app.config.get("AUTH_EVENT_TOPIC"), ) ) except Exception as e: # NOQA pylint: disable=broad-except current_app.logger.error(e) - current_app.logger.warning('Notification to Queue failed for the Account %s - %s', pay_account.auth_account_id, - pay_account.name) - capture_message('Notification to Queue failed for the Account {auth_account_id}, {msg}.'.format( - auth_account_id=pay_account.auth_account_id, msg=payload), level='error') + current_app.logger.warning( + "Notification to Queue failed for the Account %s - %s", + pay_account.auth_account_id, + pay_account.name, + ) + capture_message( + "Notification to Queue failed for the Account {auth_account_id}, {msg}.".format( + auth_account_id=pay_account.auth_account_id, msg=payload + ), + level="error", + ) def _create_event_payload(pay_account, row): return { - 'accountId': pay_account.auth_account_id, - 'paymentMethod': _convert_payment_method(_get_row_value(row, Column.SOURCE_TXN)), - 'outstandingAmount': _get_row_value(row, Column.TARGET_TXN_OUTSTANDING), - 'originalAmount': _get_row_value(row, Column.TARGET_TXN_ORIGINAL), - 'amount': _get_row_value(row, Column.APP_AMOUNT) + "accountId": pay_account.auth_account_id, + "paymentMethod": _convert_payment_method(_get_row_value(row, Column.SOURCE_TXN)), + "outstandingAmount": _get_row_value(row, Column.TARGET_TXN_OUTSTANDING), + "originalAmount": _get_row_value(row, Column.TARGET_TXN_ORIGINAL), + "amount": _get_row_value(row, Column.APP_AMOUNT), } @@ -845,11 +976,16 @@ def _get_row_value(row: Dict[str, str], key: Column) -> str: return row.get(key.value.lower()) -def _create_nsf_invoice(cfs_account: CfsAccountModel, inv_number: str, - payment_account: PaymentAccountModel, reason_description: str) -> InvoiceModel: +def _create_nsf_invoice( + cfs_account: CfsAccountModel, + inv_number: str, + payment_account: PaymentAccountModel, + reason_description: str, +) -> InvoiceModel: """Create Invoice, line item and invoice referwnce records.""" - fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type(corp_type_code='BCR', - filing_type_code='NSF') + fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type( + corp_type_code="BCR", filing_type_code="NSF" + ) invoice = InvoiceModel( bcol_account=payment_account.bcol_account, payment_account_id=payment_account.id, @@ -859,19 +995,22 @@ def _create_nsf_invoice(cfs_account: CfsAccountModel, inv_number: str, service_fees=0, paid=0, payment_method_code=PaymentMethod.CC.value, - corp_type_code='BCR', + corp_type_code="BCR", created_on=datetime.now(), - created_by='SYSTEM' + created_by="SYSTEM", ) invoice = invoice.save() - NonSufficientFundsService.save_non_sufficient_funds(invoice_id=invoice.id, - invoice_number=inv_number, - cfs_account=cfs_account.cfs_account, - description=reason_description) + NonSufficientFundsService.save_non_sufficient_funds( + invoice_id=invoice.id, + invoice_number=inv_number, + cfs_account=cfs_account.cfs_account, + description=reason_description, + ) distribution: DistributionCodeModel = DistributionCodeModel.find_by_active_for_fee_schedule( - fee_schedule.fee_schedule_id) + fee_schedule.fee_schedule_id + ) line_item = PaymentLineItemModel( invoice_id=invoice.id, @@ -885,15 +1024,17 @@ def _create_nsf_invoice(cfs_account: CfsAccountModel, inv_number: str, future_effective_fees=0, line_item_status_code=LineItemStatus.ACTIVE.value, service_fees=0, - fee_distribution_id=distribution.distribution_code_id if distribution else 1) + fee_distribution_id=distribution.distribution_code_id if distribution else 1, + ) line_item.save() inv_ref: InvoiceReferenceModel = InvoiceReferenceModel( invoice_id=invoice.id, invoice_number=inv_number, reference_number=InvoiceReferenceModel.find_any_active_reference_by_invoice_number( - invoice_number=inv_number).reference_number, - status_code=InvoiceReferenceStatus.ACTIVE.value + invoice_number=inv_number + ).reference_number, + status_code=InvoiceReferenceStatus.ACTIVE.value, ) inv_ref.save() @@ -904,9 +1045,13 @@ def _get_settlement_type(payment_lines) -> str: """Exclude ONAC, ADJS, PAYR, ONAP and return the record type.""" settlement_type: str = None for row in payment_lines: - if _get_row_value(row, Column.RECORD_TYPE) in \ - (RecordType.BOLP.value, RecordType.EFTP.value, RecordType.PAD.value, RecordType.PADR.value, - RecordType.PAYR.value): + if _get_row_value(row, Column.RECORD_TYPE) in ( + RecordType.BOLP.value, + RecordType.EFTP.value, + RecordType.PAD.value, + RecordType.PADR.value, + RecordType.PAYR.value, + ): settlement_type = _get_row_value(row, Column.RECORD_TYPE) break return settlement_type diff --git a/pay-queue/src/pay_queue/version.py b/pay-queue/src/pay_queue/version.py index c29b19b02..08f693c1e 100644 --- a/pay-queue/src/pay_queue/version.py +++ b/pay-queue/src/pay_queue/version.py @@ -22,4 +22,4 @@ Development release segment: .devN """ -__version__ = '2.0.1' # pylint: disable=invalid-name +__version__ = "2.0.1" # pylint: disable=invalid-name diff --git a/pay-queue/tests/__init__.py b/pay-queue/tests/__init__.py index 999702cfd..99b79aef3 100644 --- a/pay-queue/tests/__init__.py +++ b/pay-queue/tests/__init__.py @@ -15,10 +15,9 @@ import datetime import os - EPOCH_DATETIME = datetime.datetime.utcfromtimestamp(0) FROZEN_DATETIME = datetime.datetime(2001, 8, 5, 7, 7, 58, 272362) -os.environ['DEPLOYMENT_ENV'] = 'testing' +os.environ["DEPLOYMENT_ENV"] = "testing" def add_years(d, years): diff --git a/pay-queue/tests/conftest.py b/pay-queue/tests/conftest.py index c257f1432..0d188c627 100644 --- a/pay-queue/tests/conftest.py +++ b/pay-queue/tests/conftest.py @@ -27,21 +27,21 @@ from pay_queue import create_app -@pytest.fixture(scope='session', autouse=True) +@pytest.fixture(scope="session", autouse=True) def app(): """Return a session-wide application configured in TEST mode.""" - _app = create_app('testing') + _app = create_app("testing") return _app -@pytest.fixture(scope='function', autouse=True) +@pytest.fixture(scope="function", autouse=True) def gcp_queue(app, mocker): """Mock GcpQueue to avoid initializing the external connections.""" - mocker.patch.object(GcpQueue, 'init_app') + mocker.patch.object(GcpQueue, "init_app") return GcpQueue(app) -@pytest.fixture(scope='session', autouse=True) +@pytest.fixture(scope="session", autouse=True) def db(app): # pylint: disable=redefined-outer-name, invalid-name """Return a session-wide initialised database.""" with app.app_context(): @@ -49,8 +49,8 @@ def db(app): # pylint: disable=redefined-outer-name, invalid-name drop_database(_db.engine.url) create_database(_db.engine.url) _db.session().execute(text('SET TIME ZONE "UTC";')) - pay_api_dir = os.path.abspath('.').replace('pay-queue', 'pay-api') - pay_api_dir = os.path.join(pay_api_dir, 'migrations') + pay_api_dir = os.path.abspath(".").replace("pay-queue", "pay-api") + pay_api_dir = os.path.join(pay_api_dir, "migrations") Migrate(app, _db, directory=pay_api_dir) upgrade() return _db @@ -62,13 +62,13 @@ def config(app): return app.config -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def client(app): # pylint: disable=redefined-outer-name """Return a session-wide Flask test client.""" return app.test_client() -@pytest.fixture(scope='function', autouse=True) +@pytest.fixture(scope="function", autouse=True) def session(db, app): # pylint: disable=redefined-outer-name, invalid-name """Return a function-scoped session.""" with app.app_context(): @@ -82,7 +82,7 @@ def session(db, app): # pylint: disable=redefined-outer-name, invalid-name db.session.commit = nested.commit db.session.rollback = nested.rollback - @event.listens_for(sess, 'after_transaction_end') + @event.listens_for(sess, "after_transaction_end") def restart_savepoint(sess2, trans): # pylint: disable=unused-variable nonlocal nested if trans.nested: @@ -103,39 +103,42 @@ def restart_savepoint(sess2, trans): # pylint: disable=unused-variable finally: db.session.remove() transaction.rollback() - event.remove(sess, 'after_transaction_end', restart_savepoint) + event.remove(sess, "after_transaction_end", restart_savepoint) db.session = old_session -@pytest.fixture(scope='session', autouse=True) +@pytest.fixture(scope="session", autouse=True) def auto(docker_services, app): """Spin up docker containers.""" - if app.config['USE_DOCKER_MOCK']: - docker_services.start('minio') - docker_services.start('proxy') - docker_services.start('paybc') - docker_services.start('pubsub-emulator') + if app.config["USE_DOCKER_MOCK"]: + docker_services.start("minio") + docker_services.start("proxy") + docker_services.start("paybc") + docker_services.start("pubsub-emulator") @pytest.fixture() def mock_publish(monkeypatch): """Mock check_auth.""" - monkeypatch.setattr('pay_api.services.gcp_queue_publisher.publish_to_queue', lambda *args, **kwargs: None) + monkeypatch.setattr( + "pay_api.services.gcp_queue_publisher.publish_to_queue", + lambda *args, **kwargs: None, + ) @pytest.fixture(autouse=True) def mock_queue_auth(mocker): """Mock queue authorization.""" - mocker.patch('pay_queue.external.gcp_auth.verify_jwt', return_value='') + mocker.patch("pay_queue.external.gcp_auth.verify_jwt", return_value="") -@pytest.fixture(scope='session', autouse=True) +@pytest.fixture(scope="session", autouse=True) def initialize_pubsub(app): """Initialize pubsub emulator and respective publisher and subscribers.""" - os.environ['PUBSUB_EMULATOR_HOST'] = 'localhost:8085' - project = app.config.get('TEST_GCP_PROJECT_NAME') - topics = app.config.get('TEST_GCP_TOPICS') - push_config = pubsub.types.PushConfig(push_endpoint=app.config.get('TEST_PUSH_ENDPOINT')) + os.environ["PUBSUB_EMULATOR_HOST"] = "localhost:8085" + project = app.config.get("TEST_GCP_PROJECT_NAME") + topics = app.config.get("TEST_GCP_TOPICS") + push_config = pubsub.types.PushConfig(push_endpoint=app.config.get("TEST_PUSH_ENDPOINT")) publisher = pubsub.PublisherClient() subscriber = pubsub.SubscriberClient() with publisher, subscriber: @@ -146,16 +149,16 @@ def initialize_pubsub(app): except NotFound: pass publisher.create_topic(name=topic_path) - subscription_path = subscriber.subscription_path(project, f'{topic}_subscription') + subscription_path = subscriber.subscription_path(project, f"{topic}_subscription") try: subscriber.delete_subscription(subscription=subscription_path) except NotFound: pass subscriber.create_subscription( request={ - 'name': subscription_path, - 'topic': topic_path, - 'push_config': push_config, + "name": subscription_path, + "topic": topic_path, + "push_config": push_config, } ) @@ -163,6 +166,7 @@ def initialize_pubsub(app): @pytest.fixture(autouse=True) def mock_pub_sub_call(mocker): """Mock pub sub call.""" + class PublisherMock: """Publisher Mock.""" @@ -171,12 +175,12 @@ def __init__(self, *args, **kwargs): def publish(self, *args, **kwargs): """Publish mock.""" - raise CancelledError('This is a mock') + raise CancelledError("This is a mock") - mocker.patch('google.cloud.pubsub_v1.PublisherClient', PublisherMock) + mocker.patch("google.cloud.pubsub_v1.PublisherClient", PublisherMock) -@pytest.fixture(scope='session', autouse=True) +@pytest.fixture(scope="session", autouse=True) def set_eft_tdi17_location_id(app): """Set TDI17 Location ID for tests.""" - app.config['EFT_TDI17_LOCATION_ID'] = '85004' + app.config["EFT_TDI17_LOCATION_ID"] = "85004" diff --git a/pay-queue/tests/integration/__init__.py b/pay-queue/tests/integration/__init__.py index 4b0c4ea44..99d8fe9dc 100644 --- a/pay-queue/tests/integration/__init__.py +++ b/pay-queue/tests/integration/__init__.py @@ -19,21 +19,24 @@ from pay_api.utils.enums import InvoiceReferenceStatus, InvoiceStatus, PaymentMethod, PaymentStatus, PaymentSystem -def factory_payment_account(payment_system_code: str = 'PAYBC', payment_method_code: str = 'CC', account_number='4101', - bcol_user_id='test', - auth_account_id: str = '1234'): +def factory_payment_account( + payment_system_code: str = "PAYBC", + payment_method_code: str = "CC", + account_number="4101", + bcol_user_id="test", + auth_account_id: str = "1234", +): """Return Factory.""" # Create a payment account - account = PaymentAccount( - auth_account_id=auth_account_id, - bcol_user_id=bcol_user_id, - bcol_account='TEST' - ).save() + account = PaymentAccount(auth_account_id=auth_account_id, bcol_user_id=bcol_user_id, bcol_account="TEST").save() - CfsAccount(cfs_party='11111', - cfs_account=account_number, - cfs_site='29921', payment_account=account, - payment_method=payment_method_code).save() + CfsAccount( + cfs_party="11111", + cfs_account=account_number, + cfs_site="29921", + payment_account=account, + payment_method=payment_method_code, + ).save() if payment_system_code == PaymentSystem.BCOL.value: account.payment_method = PaymentMethod.DRAWDOWN.value @@ -44,46 +47,53 @@ def factory_payment_account(payment_system_code: str = 'PAYBC', payment_method_c def factory_payment( - payment_system_code: str = 'PAYBC', payment_method_code: str = 'CC', - payment_status_code: str = PaymentStatus.CREATED.value, - created_on: datetime = datetime.now(), - invoice_number: str = None + payment_system_code: str = "PAYBC", + payment_method_code: str = "CC", + payment_status_code: str = PaymentStatus.CREATED.value, + created_on: datetime = datetime.now(), + invoice_number: str = None, ): """Return Factory.""" return Payment( payment_system_code=payment_system_code, payment_method_code=payment_method_code, payment_status_code=payment_status_code, - invoice_number=invoice_number + invoice_number=invoice_number, ).save() -def factory_invoice(payment_account, status_code: str = InvoiceStatus.CREATED.value, - corp_type_code='CP', - business_identifier: str = 'CP0001234', - service_fees: float = 0.0, total=0, - payment_method_code: str = PaymentMethod.DIRECT_PAY.value, - created_on: datetime = datetime.now(), - disbursement_status_code=None): +def factory_invoice( + payment_account, + status_code: str = InvoiceStatus.CREATED.value, + corp_type_code="CP", + business_identifier: str = "CP0001234", + service_fees: float = 0.0, + total=0, + payment_method_code: str = PaymentMethod.DIRECT_PAY.value, + created_on: datetime = datetime.now(), + disbursement_status_code=None, +): """Return Factory.""" return Invoice( invoice_status_code=status_code, payment_account_id=payment_account.id, total=total, - created_by='test', + created_by="test", created_on=created_on, business_identifier=business_identifier, corp_type_code=corp_type_code, - folio_number='1234567890', + folio_number="1234567890", service_fees=service_fees, bcol_account=payment_account.bcol_account, payment_method_code=payment_method_code, - disbursement_status_code=disbursement_status_code + disbursement_status_code=disbursement_status_code, ).save() -def factory_invoice_reference(invoice_id: int, invoice_number: str = '10021'): +def factory_invoice_reference(invoice_id: int, invoice_number: str = "10021"): """Return Factory.""" - return InvoiceReference(invoice_id=invoice_id, - status_code=InvoiceReferenceStatus.ACTIVE.value, - invoice_number=invoice_number).save() + return InvoiceReference( + invoice_id=invoice_id, + status_code=InvoiceReferenceStatus.ACTIVE.value, + invoice_number=invoice_number, + ).save() diff --git a/pay-queue/tests/integration/factory.py b/pay-queue/tests/integration/factory.py index 2b9236cef..a779140cb 100644 --- a/pay-queue/tests/integration/factory.py +++ b/pay-queue/tests/integration/factory.py @@ -20,91 +20,130 @@ from datetime import datetime, timezone from pay_api.models import ( - CfsAccount, DistributionCode, EFTRefund, EFTShortnames, Invoice, InvoiceReference, Payment, PaymentAccount, - PaymentLineItem, PaymentTransaction, Receipt, Refund, RoutingSlip, Statement, StatementInvoices, StatementSettings) + CfsAccount, + DistributionCode, + EFTRefund, + EFTShortnames, + Invoice, + InvoiceReference, + Payment, + PaymentAccount, + PaymentLineItem, + PaymentTransaction, + Receipt, + Refund, + RoutingSlip, + Statement, + StatementInvoices, + StatementSettings, +) from pay_api.utils.enums import ( - CfsAccountStatus, DisbursementStatus, EFTShortnameRefundStatus, EFTShortnameType, InvoiceReferenceStatus, - InvoiceStatus, LineItemStatus, PaymentMethod, PaymentStatus, PaymentSystem, RoutingSlipStatus, TransactionStatus) - - -def factory_premium_payment_account(bcol_user_id='PB25020', bcol_account_id='1234567890', auth_account_id='1234'): + CfsAccountStatus, + DisbursementStatus, + EFTShortnameRefundStatus, + EFTShortnameType, + InvoiceReferenceStatus, + InvoiceStatus, + LineItemStatus, + PaymentMethod, + PaymentStatus, + PaymentSystem, + RoutingSlipStatus, + TransactionStatus, +) + + +def factory_premium_payment_account(bcol_user_id="PB25020", bcol_account_id="1234567890", auth_account_id="1234"): """Return Factory.""" - account = PaymentAccount(auth_account_id=auth_account_id, - bcol_user_id=bcol_user_id, - bcol_account=bcol_account_id, - ).save() + account = PaymentAccount( + auth_account_id=auth_account_id, + bcol_user_id=bcol_user_id, + bcol_account=bcol_account_id, + ).save() return account -def factory_statement_settings(pay_account_id: str, frequency='DAILY', from_date=datetime.now(), - to_date=None) -> StatementSettings: +def factory_statement_settings( + pay_account_id: str, frequency="DAILY", from_date=datetime.now(), to_date=None +) -> StatementSettings: """Return Factory.""" return StatementSettings( frequency=frequency, payment_account_id=pay_account_id, from_date=from_date, - to_date=to_date + to_date=to_date, ).save() def factory_statement( - frequency: str = 'WEEKLY', - payment_account_id: str = None, - from_date: datetime = datetime.now(tz=timezone.utc), - to_date: datetime = datetime.now(tz=timezone.utc), - statement_settings_id: str = None, - created_on: datetime = datetime.now(tz=timezone.utc), - payment_methods: str = PaymentMethod.EFT.value): - """Return Factory.""" - return Statement(frequency=frequency, - statement_settings_id=statement_settings_id, - payment_account_id=payment_account_id, - from_date=from_date, - to_date=to_date, - created_on=created_on, - payment_methods=payment_methods).save() - - -def factory_statement_invoices( - statement_id: str, - invoice_id: str): + frequency: str = "WEEKLY", + payment_account_id: str = None, + from_date: datetime = datetime.now(tz=timezone.utc), + to_date: datetime = datetime.now(tz=timezone.utc), + statement_settings_id: str = None, + created_on: datetime = datetime.now(tz=timezone.utc), + payment_methods: str = PaymentMethod.EFT.value, +): """Return Factory.""" - return StatementInvoices(statement_id=statement_id, - invoice_id=invoice_id).save() + return Statement( + frequency=frequency, + statement_settings_id=statement_settings_id, + payment_account_id=payment_account_id, + from_date=from_date, + to_date=to_date, + created_on=created_on, + payment_methods=payment_methods, + ).save() -def factory_invoice(payment_account: PaymentAccount, status_code: str = InvoiceStatus.CREATED.value, - corp_type_code='CP', - business_identifier: str = 'CP0001234', - service_fees: float = 0.0, total=0, - payment_method_code: str = PaymentMethod.DIRECT_PAY.value, - created_on: datetime = datetime.now(), - disbursement_status_code=None): +def factory_statement_invoices(statement_id: str, invoice_id: str): + """Return Factory.""" + return StatementInvoices(statement_id=statement_id, invoice_id=invoice_id).save() + + +def factory_invoice( + payment_account: PaymentAccount, + status_code: str = InvoiceStatus.CREATED.value, + corp_type_code="CP", + business_identifier: str = "CP0001234", + service_fees: float = 0.0, + total=0, + payment_method_code: str = PaymentMethod.DIRECT_PAY.value, + created_on: datetime = datetime.now(), + disbursement_status_code=None, +): """Return Factory.""" - cfs_account = CfsAccount.find_effective_by_payment_method(payment_account.id, - payment_method_code or payment_account.payment_method) + cfs_account = CfsAccount.find_effective_by_payment_method( + payment_account.id, payment_method_code or payment_account.payment_method + ) cfs_account_id = cfs_account.id if cfs_account else None return Invoice( invoice_status_code=status_code, payment_account_id=payment_account.id, total=total, paid=0, - created_by='test', + created_by="test", created_on=created_on, business_identifier=business_identifier, corp_type_code=corp_type_code, - folio_number='1234567890', + folio_number="1234567890", service_fees=service_fees, bcol_account=payment_account.bcol_account, cfs_account_id=cfs_account_id, payment_method_code=payment_method_code or payment_account.payment_method, - disbursement_status_code=disbursement_status_code + disbursement_status_code=disbursement_status_code, ).save() -def factory_payment_line_item(invoice_id: str, fee_schedule_id: int = 1, filing_fees: int = 10, total: int = 10, - service_fees: int = 0, status: str = LineItemStatus.ACTIVE.value, - fee_dist_id: int = None): +def factory_payment_line_item( + invoice_id: str, + fee_schedule_id: int = 1, + filing_fees: int = 10, + total: int = 10, + service_fees: int = 0, + status: str = LineItemStatus.ACTIVE.value, + fee_dist_id: int = None, +): """Return Factory.""" return PaymentLineItem( invoice_id=invoice_id, @@ -113,38 +152,51 @@ def factory_payment_line_item(invoice_id: str, fee_schedule_id: int = 1, filing_ total=total, service_fees=service_fees, line_item_status_code=status, - fee_distribution_id=fee_dist_id or DistributionCode.find_by_active_for_fee_schedule( - fee_schedule_id).distribution_code_id + fee_distribution_id=fee_dist_id + or DistributionCode.find_by_active_for_fee_schedule(fee_schedule_id).distribution_code_id, ).save() -def factory_invoice_reference(invoice_id: int, invoice_number: str = '10021', - status_code: str = InvoiceReferenceStatus.ACTIVE.value, - is_consolidated=False): +def factory_invoice_reference( + invoice_id: int, + invoice_number: str = "10021", + status_code: str = InvoiceReferenceStatus.ACTIVE.value, + is_consolidated=False, +): """Return Factory.""" - return InvoiceReference(invoice_id=invoice_id, - status_code=status_code, - invoice_number=invoice_number, - is_consolidated=is_consolidated).save() + return InvoiceReference( + invoice_id=invoice_id, + status_code=status_code, + invoice_number=invoice_number, + is_consolidated=is_consolidated, + ).save() -def factory_receipt(invoice_id: int, receipt_number: str = '10021'): +def factory_receipt(invoice_id: int, receipt_number: str = "10021"): """Return Factory.""" return Receipt(invoice_id=invoice_id, receipt_number=receipt_number).save() -def factory_payment(pay_account: PaymentAccount, - invoice_number: str = '10021', status=PaymentStatus.CREATED.value, - payment_method_code=PaymentMethod.ONLINE_BANKING.value, - invoice_amount: float = 100, paid_amount: float = 0, - receipt_number: str = ''): +def factory_payment( + pay_account: PaymentAccount, + invoice_number: str = "10021", + status=PaymentStatus.CREATED.value, + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + invoice_amount: float = 100, + paid_amount: float = 0, + receipt_number: str = "", +): """Return Factory.""" - return Payment(payment_status_code=status, payment_system_code=PaymentSystem.PAYBC.value, - payment_method_code=payment_method_code, payment_account_id=pay_account.id, - invoice_amount=invoice_amount, - invoice_number=invoice_number, - paid_amount=paid_amount, - receipt_number=receipt_number).save() + return Payment( + payment_status_code=status, + payment_system_code=PaymentSystem.PAYBC.value, + payment_method_code=payment_method_code, + payment_account_id=pay_account.id, + invoice_amount=invoice_amount, + invoice_number=invoice_number, + paid_amount=paid_amount, + receipt_number=receipt_number, + ).save() def factory_payment_transaction(payment_id: int): @@ -152,26 +204,24 @@ def factory_payment_transaction(payment_id: int): return PaymentTransaction( payment_id=payment_id, status_code=TransactionStatus.CREATED.value, - transaction_start_time=datetime.now()).save() + transaction_start_time=datetime.now(), + ).save() def factory_create_eft_shortname(short_name: str, short_name_type: str = EFTShortnameType.EFT.value): """Return Factory.""" - short_name = EFTShortnames( - short_name=short_name, - type=short_name_type - ).save() + short_name = EFTShortnames(short_name=short_name, type=short_name_type).save() return short_name def factory_create_eft_refund( - cas_supplier_number: str = '1234', - comment: str = 'Test Comment', + cas_supplier_number: str = "1234", + comment: str = "Test Comment", refund_amount: float = 100.0, - refund_email: str = 'test@email.com', + refund_email: str = "test@email.com", short_name_id: int = 1, status: str = EFTShortnameRefundStatus.APPROVED.value, - disbursement_status_code: str = DisbursementStatus.ACKNOWLEDGED.value + disbursement_status_code: str = DisbursementStatus.ACKNOWLEDGED.value, ): """Return Factory.""" eft_refund = EFTRefund( @@ -182,102 +232,139 @@ def factory_create_eft_refund( refund_email=refund_email, short_name_id=short_name_id, status=status, - created_on=datetime.now(tz=timezone.utc) + created_on=datetime.now(tz=timezone.utc), ).save() return eft_refund -def factory_create_eft_account(auth_account_id='1234', status=CfsAccountStatus.ACTIVE.value, - cfs_account='1234'): +def factory_create_eft_account(auth_account_id="1234", status=CfsAccountStatus.ACTIVE.value, cfs_account="1234"): """Return Factory.""" - account = PaymentAccount(auth_account_id=auth_account_id, - payment_method=PaymentMethod.EFT.value, - name=f'Test EFT {auth_account_id}').save() - CfsAccount(status=status, account_id=account.id, cfs_account=cfs_account, payment_method=PaymentMethod.EFT.value) \ - .save() + account = PaymentAccount( + auth_account_id=auth_account_id, + payment_method=PaymentMethod.EFT.value, + name=f"Test EFT {auth_account_id}", + ).save() + CfsAccount( + status=status, + account_id=account.id, + cfs_account=cfs_account, + payment_method=PaymentMethod.EFT.value, + ).save() return account -def factory_create_online_banking_account(auth_account_id='1234', status=CfsAccountStatus.PENDING.value, - cfs_account='1234'): +def factory_create_online_banking_account( + auth_account_id="1234", status=CfsAccountStatus.PENDING.value, cfs_account="1234" +): """Return Factory.""" - account = PaymentAccount(auth_account_id=auth_account_id, - payment_method=PaymentMethod.ONLINE_BANKING.value, - name=f'Test {auth_account_id}').save() - CfsAccount(status=status, account_id=account.id, cfs_account=cfs_account, - payment_method=PaymentMethod.ONLINE_BANKING.value).save() + account = PaymentAccount( + auth_account_id=auth_account_id, + payment_method=PaymentMethod.ONLINE_BANKING.value, + name=f"Test {auth_account_id}", + ).save() + CfsAccount( + status=status, + account_id=account.id, + cfs_account=cfs_account, + payment_method=PaymentMethod.ONLINE_BANKING.value, + ).save() return account -def factory_create_pad_account(auth_account_id='1234', bank_number='001', bank_branch='004', bank_account='1234567890', - status=CfsAccountStatus.PENDING.value, account_number='4101'): +def factory_create_pad_account( + auth_account_id="1234", + bank_number="001", + bank_branch="004", + bank_account="1234567890", + status=CfsAccountStatus.PENDING.value, + account_number="4101", +): """Return Factory.""" - account = PaymentAccount(auth_account_id=auth_account_id, - payment_method=PaymentMethod.PAD.value, - name=f'Test {auth_account_id}').save() - CfsAccount(status=status, account_id=account.id, bank_number=bank_number, - bank_branch_number=bank_branch, bank_account_number=bank_account, - cfs_party='11111', - cfs_account=account_number, - cfs_site='29921', - payment_method=PaymentMethod.PAD.value - ).save() + account = PaymentAccount( + auth_account_id=auth_account_id, + payment_method=PaymentMethod.PAD.value, + name=f"Test {auth_account_id}", + ).save() + CfsAccount( + status=status, + account_id=account.id, + bank_number=bank_number, + bank_branch_number=bank_branch, + bank_account_number=bank_account, + cfs_party="11111", + cfs_account=account_number, + cfs_site="29921", + payment_method=PaymentMethod.PAD.value, + ).save() return account -def factory_create_ejv_account(auth_account_id='1234', - client: str = '112', - resp_centre: str = '11111', - service_line: str = '11111', - stob: str = '1111', - project_code: str = '1111111'): +def factory_create_ejv_account( + auth_account_id="1234", + client: str = "112", + resp_centre: str = "11111", + service_line: str = "11111", + stob: str = "1111", + project_code: str = "1111111", +): """Return Factory.""" - account = PaymentAccount(auth_account_id=auth_account_id, - payment_method=PaymentMethod.EJV.value, - name=f'Test {auth_account_id}').save() - DistributionCode(name=account.name, - client=client, - responsibility_centre=resp_centre, - service_line=service_line, - stob=stob, - project_code=project_code, - account_id=account.id, - start_date=datetime.now(tz=timezone.utc).date(), - created_by='test').save() + account = PaymentAccount( + auth_account_id=auth_account_id, + payment_method=PaymentMethod.EJV.value, + name=f"Test {auth_account_id}", + ).save() + DistributionCode( + name=account.name, + client=client, + responsibility_centre=resp_centre, + service_line=service_line, + stob=stob, + project_code=project_code, + account_id=account.id, + start_date=datetime.now(tz=timezone.utc).date(), + created_by="test", + ).save() return account -def factory_distribution(name: str, client: str = '111', reps_centre: str = '22222', service_line: str = '33333', - stob: str = '4444', project_code: str = '5555555', service_fee_dist_id: int = None, - disbursement_dist_id: int = None): +def factory_distribution( + name: str, + client: str = "111", + reps_centre: str = "22222", + service_line: str = "33333", + stob: str = "4444", + project_code: str = "5555555", + service_fee_dist_id: int = None, + disbursement_dist_id: int = None, +): """Return Factory.""" - return DistributionCode(name=name, - client=client, - responsibility_centre=reps_centre, - service_line=service_line, - stob=stob, - project_code=project_code, - service_fee_distribution_code_id=service_fee_dist_id, - disbursement_distribution_code_id=disbursement_dist_id, - start_date=datetime.now(tz=timezone.utc).date(), - created_by='test').save() + return DistributionCode( + name=name, + client=client, + responsibility_centre=reps_centre, + service_line=service_line, + stob=stob, + project_code=project_code, + service_fee_distribution_code_id=service_fee_dist_id, + disbursement_distribution_code_id=disbursement_dist_id, + start_date=datetime.now(tz=timezone.utc).date(), + created_by="test", + ).save() def factory_routing_slip_account( - number: str = '1234', - status: str = CfsAccountStatus.PENDING.value, - total: int = 0, - remaining_amount: int = 0, - routing_slip_date=datetime.now(), - payment_method=PaymentMethod.CASH.value, - auth_account_id='1234', - routing_slip_status=RoutingSlipStatus.ACTIVE.value, - refund_amount=0 + number: str = "1234", + status: str = CfsAccountStatus.PENDING.value, + total: int = 0, + remaining_amount: int = 0, + routing_slip_date=datetime.now(), + payment_method=PaymentMethod.CASH.value, + auth_account_id="1234", + routing_slip_status=RoutingSlipStatus.ACTIVE.value, + refund_amount=0, ) -> PaymentAccount: """Create routing slip and return payment account with it.""" - payment_account = PaymentAccount( - payment_method=payment_method, - name=f'Test {auth_account_id}') + payment_account = PaymentAccount(payment_method=payment_method, name=f"Test {auth_account_id}") payment_account.save() rs = RoutingSlip( @@ -286,36 +373,38 @@ def factory_routing_slip_account( status=routing_slip_status, total=total, remaining_amount=remaining_amount, - created_by='test', + created_by="test", routing_slip_date=routing_slip_date, - refund_amount=refund_amount + refund_amount=refund_amount, ).save() - Payment(payment_system_code=PaymentSystem.FAS.value, - payment_account_id=payment_account.id, - payment_method_code=PaymentMethod.CASH.value, - payment_status_code=PaymentStatus.COMPLETED.value, - receipt_number=number, - is_routing_slip=True, - paid_amount=rs.total, - created_by='TEST') - - CfsAccount(status=status, account_id=payment_account.id, payment_method=PaymentMethod.INTERNAL.value).save() + Payment( + payment_system_code=PaymentSystem.FAS.value, + payment_account_id=payment_account.id, + payment_method_code=PaymentMethod.CASH.value, + payment_status_code=PaymentStatus.COMPLETED.value, + receipt_number=number, + is_routing_slip=True, + paid_amount=rs.total, + created_by="TEST", + ) + + CfsAccount( + status=status, + account_id=payment_account.id, + payment_method=PaymentMethod.INTERNAL.value, + ).save() return payment_account -def factory_refund( - routing_slip_id: int = None, - details={}, - invoice_id: int = None -): +def factory_refund(routing_slip_id: int = None, details={}, invoice_id: int = None): """Return Factory.""" return Refund( invoice_id=invoice_id, routing_slip_id=routing_slip_id, requested_date=datetime.now(), - reason='TEST', - requested_by='TEST', - details=details + reason="TEST", + requested_by="TEST", + details=details, ).save() diff --git a/pay-queue/tests/integration/test_cgi_reconciliations.py b/pay-queue/tests/integration/test_cgi_reconciliations.py index 6c24f760f..90f3f6b39 100644 --- a/pay-queue/tests/integration/test_cgi_reconciliations.py +++ b/pay-queue/tests/integration/test_cgi_reconciliations.py @@ -35,17 +35,35 @@ from pay_api.models import RoutingSlip as RoutingSlipModel from pay_api.models import db from pay_api.utils.enums import ( - CfsAccountStatus, DisbursementStatus, EFTShortnameRefundStatus, EFTShortnameType, EjvFileType, EJVLinkType, - InvoiceReferenceStatus, InvoiceStatus, PaymentMethod, PaymentStatus, RoutingSlipStatus) + CfsAccountStatus, + DisbursementStatus, + EFTShortnameRefundStatus, + EFTShortnameType, + EjvFileType, + EJVLinkType, + InvoiceReferenceStatus, + InvoiceStatus, + PaymentMethod, + PaymentStatus, + RoutingSlipStatus, +) from sbc_common_components.utils.enums import QueueMessageTypes from sqlalchemy import text from tests.integration.utils import add_file_event_to_queue_and_process from .factory import ( - factory_create_eft_refund, factory_create_eft_shortname, factory_create_ejv_account, factory_create_pad_account, - factory_distribution, factory_invoice, factory_invoice_reference, factory_payment_line_item, factory_refund, - factory_routing_slip_account) + factory_create_eft_refund, + factory_create_eft_shortname, + factory_create_ejv_account, + factory_create_pad_account, + factory_distribution, + factory_invoice, + factory_invoice_reference, + factory_payment_line_item, + factory_refund, + factory_routing_slip_account, +) from .utils import upload_to_minio @@ -55,40 +73,54 @@ def test_successful_partner_ejv_reconciliations(session, app, client): # 2. Create invoice and related records # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - partner_code = 'VS' + cfs_account_number = "1234" + partner_code = "VS" fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type( - corp_type_code=partner_code, filing_type_code='WILLSEARCH' + corp_type_code=partner_code, filing_type_code="WILLSEARCH" ) - pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, - account_number=cfs_account_number) - invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - corp_type_code='VS', - payment_method_code=PaymentMethod.ONLINE_BANKING.value, - status_code=InvoiceStatus.PAID.value) - eft_invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - corp_type_code='VS', - payment_method_code=PaymentMethod.EFT.value, - status_code=InvoiceStatus.PAID.value) + pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, account_number=cfs_account_number) + invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + corp_type_code="VS", + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + status_code=InvoiceStatus.PAID.value, + ) + eft_invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + corp_type_code="VS", + payment_method_code=PaymentMethod.EFT.value, + status_code=InvoiceStatus.PAID.value, + ) invoice_id = invoice.id line_item = factory_payment_line_item( - invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0, - fee_schedule_id=fee_schedule.fee_schedule_id + invoice_id=invoice.id, + filing_fees=90.0, + service_fees=10.0, + total=90.0, + fee_schedule_id=fee_schedule.fee_schedule_id, ) dist_code = DistributionCodeModel.find_by_id(line_item.fee_distribution_id) # Check if the disbursement distribution is present for this. if not dist_code.disbursement_distribution_code_id: - disbursement_distribution_code = factory_distribution(name='Disbursement') + disbursement_distribution_code = factory_distribution(name="Disbursement") dist_code.disbursement_distribution_code_id = disbursement_distribution_code.distribution_code_id dist_code.save() - invoice_number = '1234567890' + invoice_number = "1234567890" factory_invoice_reference( - invoice_id=invoice.id, invoice_number=invoice_number, status_code=InvoiceReferenceStatus.COMPLETED.value + invoice_id=invoice.id, + invoice_number=invoice_number, + status_code=InvoiceReferenceStatus.COMPLETED.value, ) factory_invoice_reference( - invoice_id=eft_invoice.id, invoice_number='1234567899', status_code=InvoiceReferenceStatus.COMPLETED.value + invoice_id=eft_invoice.id, + invoice_number="1234567899", + status_code=InvoiceReferenceStatus.COMPLETED.value, ) invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice = invoice.save() @@ -99,38 +131,43 @@ def test_successful_partner_ejv_reconciliations(session, app, client): partner_code=eft_invoice.corp_type_code, status_code=DisbursementStatus.WAITING_FOR_RECEIPT.value, target_id=eft_invoice.id, - target_type=EJVLinkType.INVOICE.value + target_type=EJVLinkType.INVOICE.value, ).save() - eft_flowthrough = f'{eft_invoice.id}-{partner_disbursement.id}' + eft_flowthrough = f"{eft_invoice.id}-{partner_disbursement.id}" - file_ref = f'INBOX.{datetime.now()}' - ejv_file = EjvFileModel(file_ref=file_ref, - disbursement_status_code=DisbursementStatus.UPLOADED.value).save() + file_ref = f"INBOX.{datetime.now()}" + ejv_file = EjvFileModel(file_ref=file_ref, disbursement_status_code=DisbursementStatus.UPLOADED.value).save() ejv_file_id = ejv_file.id - ejv_header = EjvHeaderModel(disbursement_status_code=DisbursementStatus.UPLOADED.value, - ejv_file_id=ejv_file.id, - partner_code=partner_code, - payment_account_id=pay_account.id).save() + ejv_header = EjvHeaderModel( + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ejv_file_id=ejv_file.id, + partner_code=partner_code, + payment_account_id=pay_account.id, + ).save() ejv_header_id = ejv_header.id EjvLinkModel( - link_id=invoice.id, link_type=EJVLinkType.INVOICE.value, - ejv_header_id=ejv_header.id, disbursement_status_code=DisbursementStatus.UPLOADED.value + link_id=invoice.id, + link_type=EJVLinkType.INVOICE.value, + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, ).save() EjvLinkModel( - link_id=eft_invoice.id, link_type=EJVLinkType.INVOICE.value, - ejv_header_id=ejv_header.id, disbursement_status_code=DisbursementStatus.UPLOADED.value + link_id=eft_invoice.id, + link_type=EJVLinkType.INVOICE.value, + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, ).save() - ack_file_name = f'ACK.{file_ref}' + ack_file_name = f"ACK.{file_ref}" - with open(ack_file_name, 'a+', encoding='utf-8') as jv_file: - jv_file.write('') + with open(ack_file_name, "a+", encoding="utf-8") as jv_file: + jv_file.write("") jv_file.close() - upload_to_minio(str.encode(''), ack_file_name) + upload_to_minio(str.encode(""), ack_file_name) add_file_event_to_queue_and_process(client, ack_file_name, QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value) @@ -139,50 +176,52 @@ def test_successful_partner_ejv_reconciliations(session, app, client): # Now upload a feedback file and check the status. # Just create feedback file to mock the real feedback file. # Has legacy and added in PartnerDisbursements rows. - feedback_content = f'GABG...........00000000{ejv_file_id}...\n' \ - f'..BH...0000.................................................................................' \ - f'.....................................................................CGI\n' \ - f'..JH...FI0000000{ejv_header_id}.........................000000000090.00.....................' \ - f'............................................................................................' \ - f'............................................................................................' \ - f'.........0000...............................................................................' \ - f'.......................................................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}0000120230529................................................' \ - f'...........000000000090.00D.................................................................' \ - f'..................................#{invoice_id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}0000220230529................................................' \ - f'...........000000000090.00C.................................................................' \ - f'..................................#{invoice_id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}0000120230529................................................' \ - f'...........000000000090.00D.................................................................' \ - f'..................................#{eft_flowthrough} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}0000220230529................................................' \ - f'...........000000000090.00C.................................................................' \ - f'..................................#{eft_flowthrough} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..BT.......FI0000000{ejv_header_id}000000000000002000000000180.000000.......................' \ - f'............................................................................................' \ - f'...................................CGI' - - feedback_file_name = f'FEEDBACK.{file_ref}' - - with open(feedback_file_name, 'a+', encoding='utf-8') as jv_file: + feedback_content = ( + f"GABG...........00000000{ejv_file_id}...\n" + f"..BH...0000................................................................................." + f".....................................................................CGI\n" + f"..JH...FI0000000{ejv_header_id}.........................000000000090.00....................." + f"............................................................................................" + f"............................................................................................" + f".........0000..............................................................................." + f".......................................................................CGI\n" + f"..JD...FI0000000{ejv_header_id}0000120230529................................................" + f"...........000000000090.00D................................................................." + f"..................................#{invoice_id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header_id}0000220230529................................................" + f"...........000000000090.00C................................................................." + f"..................................#{invoice_id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header_id}0000120230529................................................" + f"...........000000000090.00D................................................................." + f"..................................#{eft_flowthrough} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header_id}0000220230529................................................" + f"...........000000000090.00C................................................................." + f"..................................#{eft_flowthrough} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..BT.......FI0000000{ejv_header_id}000000000000002000000000180.000000......................." + f"............................................................................................" + f"...................................CGI" + ) + + feedback_file_name = f"FEEDBACK.{file_ref}" + + with open(feedback_file_name, "a+", encoding="utf-8") as jv_file: jv_file.write(feedback_content) jv_file.close() # Now upload the ACK file to minio and publish message. - with open(feedback_file_name, 'rb') as f: + with open(feedback_file_name, "rb") as f: upload_to_minio(f.read(), feedback_file_name) add_file_event_to_queue_and_process(client, feedback_file_name, QueueMessageTypes.CGI_FEEDBACK_MESSAGE_TYPE.value) @@ -203,41 +242,55 @@ def test_failed_partner_ejv_reconciliations(session, app, client): # 2. Create invoice and related records # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - partner_code = 'VS' + cfs_account_number = "1234" + partner_code = "VS" fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type( - corp_type_code=partner_code, filing_type_code='WILLSEARCH' + corp_type_code=partner_code, filing_type_code="WILLSEARCH" ) - pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, - account_number=cfs_account_number) - invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - corp_type_code='VS', - payment_method_code=PaymentMethod.ONLINE_BANKING.value, - status_code=InvoiceStatus.PAID.value) - eft_invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - corp_type_code='VS', - payment_method_code=PaymentMethod.EFT.value, - status_code=InvoiceStatus.PAID.value) + pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, account_number=cfs_account_number) + invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + corp_type_code="VS", + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + status_code=InvoiceStatus.PAID.value, + ) + eft_invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + corp_type_code="VS", + payment_method_code=PaymentMethod.EFT.value, + status_code=InvoiceStatus.PAID.value, + ) invoice_id = invoice.id line_item = factory_payment_line_item( - invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0, - fee_schedule_id=fee_schedule.fee_schedule_id + invoice_id=invoice.id, + filing_fees=90.0, + service_fees=10.0, + total=90.0, + fee_schedule_id=fee_schedule.fee_schedule_id, ) dist_code = DistributionCodeModel.find_by_id(line_item.fee_distribution_id) # Check if the disbursement distribution is present for this. if not dist_code.disbursement_distribution_code_id: - disbursement_distribution_code = factory_distribution(name='Disbursement') + disbursement_distribution_code = factory_distribution(name="Disbursement") dist_code.disbursement_distribution_code_id = disbursement_distribution_code.distribution_code_id dist_code.save() disbursement_distribution_code_id = dist_code.disbursement_distribution_code_id - invoice_number = '1234567890' + invoice_number = "1234567890" factory_invoice_reference( - invoice_id=invoice.id, invoice_number=invoice_number, status_code=InvoiceReferenceStatus.COMPLETED.value + invoice_id=invoice.id, + invoice_number=invoice_number, + status_code=InvoiceReferenceStatus.COMPLETED.value, ) factory_invoice_reference( - invoice_id=eft_invoice.id, invoice_number='1234567899', status_code=InvoiceReferenceStatus.COMPLETED.value + invoice_id=eft_invoice.id, + invoice_number="1234567899", + status_code=InvoiceReferenceStatus.COMPLETED.value, ) invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice = invoice.save() @@ -248,40 +301,44 @@ def test_failed_partner_ejv_reconciliations(session, app, client): partner_code=eft_invoice.corp_type_code, status_code=DisbursementStatus.WAITING_FOR_RECEIPT.value, target_id=eft_invoice.id, - target_type=EJVLinkType.INVOICE.value + target_type=EJVLinkType.INVOICE.value, ).save() - eft_flowthrough = f'{eft_invoice.id}-{partner_disbursement.id}' + eft_flowthrough = f"{eft_invoice.id}-{partner_disbursement.id}" - file_ref = f'INBOX{datetime.now()}' - ejv_file = EjvFileModel(file_ref=file_ref, - disbursement_status_code=DisbursementStatus.UPLOADED.value).save() + file_ref = f"INBOX{datetime.now()}" + ejv_file = EjvFileModel(file_ref=file_ref, disbursement_status_code=DisbursementStatus.UPLOADED.value).save() ejv_file_id = ejv_file.id - ejv_header = EjvHeaderModel(disbursement_status_code=DisbursementStatus.UPLOADED.value, - ejv_file_id=ejv_file.id, - partner_code=partner_code, - payment_account_id=pay_account.id).save() + ejv_header = EjvHeaderModel( + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ejv_file_id=ejv_file.id, + partner_code=partner_code, + payment_account_id=pay_account.id, + ).save() ejv_header_id = ejv_header.id EjvLinkModel( link_id=invoice.id, link_type=EJVLinkType.INVOICE.value, - ejv_header_id=ejv_header.id, disbursement_status_code=DisbursementStatus.UPLOADED.value + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, ).save() EjvLinkModel( - link_id=eft_invoice.id, link_type=EJVLinkType.INVOICE.value, - ejv_header_id=ejv_header.id, disbursement_status_code=DisbursementStatus.UPLOADED.value + link_id=eft_invoice.id, + link_type=EJVLinkType.INVOICE.value, + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, ).save() - ack_file_name = f'ACK.{file_ref}' + ack_file_name = f"ACK.{file_ref}" - with open(ack_file_name, 'a+', encoding='utf-8') as jv_file: - jv_file.write('') + with open(ack_file_name, "a+", encoding="utf-8") as jv_file: + jv_file.write("") jv_file.close() # Now upload the ACK file to minio and publish message. - upload_to_minio(str.encode(''), ack_file_name) + upload_to_minio(str.encode(""), ack_file_name) add_file_event_to_queue_and_process(client, ack_file_name, QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value) @@ -290,50 +347,52 @@ def test_failed_partner_ejv_reconciliations(session, app, client): # Now upload a feedback file and check the status. # Just create feedback file to mock the real feedback file. # Has legacy flow and PartnerDisbursements entries - feedback_content = f'GABG...........00000000{ejv_file_id}...\n' \ - f'..BH...1111TESTERRORMESSAGE................................................................' \ - f'......................................................................CGI\n' \ - f'..JH...FI0000000{ejv_header_id}.........................000000000090.00....................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'............1111TESTERRORMESSAGE...........................................................' \ - f'...........................................................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}00001.......................................................' \ - f'............000000000090.00D...............................................................' \ - f'....................................#{invoice_id} ' \ - f' 1111TESTERRORMESSAGE....' \ - f'...........................................................................................' \ - f'.......................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}00002.......................................................' \ - f'............000000000090.00C...............................................................' \ - f'....................................#{invoice_id} ' \ - f' 1111TESTERRORMESSAGE....' \ - f'...........................................................................................' \ - f'.......................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}00001.......................................................' \ - f'............000000000090.00D...............................................................' \ - f'....................................#{eft_flowthrough} ' \ - f' 1111TESTERRORMESSAGE.' \ - f'...........................................................................................' \ - f'..........................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}00002.......................................................' \ - f'............000000000090.00C...............................................................' \ - f'....................................#{eft_flowthrough} ' \ - f' 1111TESTERRORMESSAGE.' \ - f'...........................................................................................' \ - f'..........................................CGI\n' \ - f'..BT...........FI0000000{ejv_header_id}000000000000002000000000180.001111TESTERRORMESSAGE..' \ - f'...........................................................................................' \ - f'.........................................CGI\n' - - feedback_file_name = f'FEEDBACK.{file_ref}' - - with open(feedback_file_name, 'a+', encoding='utf-8') as jv_file: + feedback_content = ( + f"GABG...........00000000{ejv_file_id}...\n" + f"..BH...1111TESTERRORMESSAGE................................................................" + f"......................................................................CGI\n" + f"..JH...FI0000000{ejv_header_id}.........................000000000090.00...................." + f"..........................................................................................." + f"..........................................................................................." + f"............1111TESTERRORMESSAGE..........................................................." + f"...........................................................................CGI\n" + f"..JD...FI0000000{ejv_header_id}00001......................................................." + f"............000000000090.00D..............................................................." + f"....................................#{invoice_id} " + f" 1111TESTERRORMESSAGE...." + f"..........................................................................................." + f".......................................CGI\n" + f"..JD...FI0000000{ejv_header_id}00002......................................................." + f"............000000000090.00C..............................................................." + f"....................................#{invoice_id} " + f" 1111TESTERRORMESSAGE...." + f"..........................................................................................." + f".......................................CGI\n" + f"..JD...FI0000000{ejv_header_id}00001......................................................." + f"............000000000090.00D..............................................................." + f"....................................#{eft_flowthrough} " + f" 1111TESTERRORMESSAGE." + f"..........................................................................................." + f"..........................................CGI\n" + f"..JD...FI0000000{ejv_header_id}00002......................................................." + f"............000000000090.00C..............................................................." + f"....................................#{eft_flowthrough} " + f" 1111TESTERRORMESSAGE." + f"..........................................................................................." + f"..........................................CGI\n" + f"..BT...........FI0000000{ejv_header_id}000000000000002000000000180.001111TESTERRORMESSAGE.." + f"..........................................................................................." + f".........................................CGI\n" + ) + + feedback_file_name = f"FEEDBACK.{file_ref}" + + with open(feedback_file_name, "a+", encoding="utf-8") as jv_file: jv_file.write(feedback_content) jv_file.close() # Now upload the ACK file to minio and publish message. - with open(feedback_file_name, 'rb') as f: + with open(feedback_file_name, "rb") as f: upload_to_minio(f.read(), feedback_file_name) add_file_event_to_queue_and_process(client, feedback_file_name, QueueMessageTypes.CGI_FEEDBACK_MESSAGE_TYPE.value) @@ -356,40 +415,54 @@ def test_successful_partner_reversal_ejv_reconciliations(session, app, client): # 3. Create CFS Invoice records # 4. Mark the invoice as REFUNDED # 5. Assert that the payment to partner account is reversed. - cfs_account_number = '1234' - partner_code = 'VS' + cfs_account_number = "1234" + partner_code = "VS" fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type( - corp_type_code=partner_code, filing_type_code='WILLSEARCH' + corp_type_code=partner_code, filing_type_code="WILLSEARCH" ) - pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, - account_number=cfs_account_number) - invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - corp_type_code='VS', - payment_method_code=PaymentMethod.ONLINE_BANKING.value, - status_code=InvoiceStatus.PAID.value) - eft_invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - corp_type_code='VS', - payment_method_code=PaymentMethod.EFT.value, - status_code=InvoiceStatus.PAID.value) + pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, account_number=cfs_account_number) + invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + corp_type_code="VS", + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + status_code=InvoiceStatus.PAID.value, + ) + eft_invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + corp_type_code="VS", + payment_method_code=PaymentMethod.EFT.value, + status_code=InvoiceStatus.PAID.value, + ) invoice_id = invoice.id line_item = factory_payment_line_item( - invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0, - fee_schedule_id=fee_schedule.fee_schedule_id + invoice_id=invoice.id, + filing_fees=90.0, + service_fees=10.0, + total=90.0, + fee_schedule_id=fee_schedule.fee_schedule_id, ) dist_code = DistributionCodeModel.find_by_id(line_item.fee_distribution_id) # Check if the disbursement distribution is present for this. if not dist_code.disbursement_distribution_code_id: - disbursement_distribution_code = factory_distribution(name='Disbursement') + disbursement_distribution_code = factory_distribution(name="Disbursement") dist_code.disbursement_distribution_code_id = disbursement_distribution_code.distribution_code_id dist_code.save() - invoice_number = '1234567890' + invoice_number = "1234567890" factory_invoice_reference( - invoice_id=invoice.id, invoice_number=invoice_number, status_code=InvoiceReferenceStatus.COMPLETED.value + invoice_id=invoice.id, + invoice_number=invoice_number, + status_code=InvoiceReferenceStatus.COMPLETED.value, ) factory_invoice_reference( - invoice_id=eft_invoice.id, invoice_number='1234567899', status_code=InvoiceReferenceStatus.COMPLETED.value + invoice_id=eft_invoice.id, + invoice_number="1234567899", + status_code=InvoiceReferenceStatus.COMPLETED.value, ) invoice.invoice_status_code = InvoiceStatus.REFUND_REQUESTED.value invoice.disbursement_status_code = DisbursementStatus.COMPLETED.value @@ -405,39 +478,44 @@ def test_successful_partner_reversal_ejv_reconciliations(session, app, client): partner_code=eft_invoice.corp_type_code, status_code=DisbursementStatus.WAITING_FOR_RECEIPT.value, target_id=eft_invoice.id, - target_type=EJVLinkType.INVOICE.value + target_type=EJVLinkType.INVOICE.value, ).save() - eft_flowthrough = f'{eft_invoice.id}-{partner_disbursement.id}' + eft_flowthrough = f"{eft_invoice.id}-{partner_disbursement.id}" - file_ref = f'INBOX.{datetime.now()}' - ejv_file = EjvFileModel(file_ref=file_ref, - disbursement_status_code=DisbursementStatus.UPLOADED.value).save() + file_ref = f"INBOX.{datetime.now()}" + ejv_file = EjvFileModel(file_ref=file_ref, disbursement_status_code=DisbursementStatus.UPLOADED.value).save() ejv_file_id = ejv_file.id - ejv_header = EjvHeaderModel(disbursement_status_code=DisbursementStatus.UPLOADED.value, - ejv_file_id=ejv_file.id, - partner_code=partner_code, - payment_account_id=pay_account.id).save() + ejv_header = EjvHeaderModel( + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ejv_file_id=ejv_file.id, + partner_code=partner_code, + payment_account_id=pay_account.id, + ).save() ejv_header_id = ejv_header.id EjvLinkModel( - link_id=invoice.id, link_type=EJVLinkType.INVOICE.value, - ejv_header_id=ejv_header.id, disbursement_status_code=DisbursementStatus.UPLOADED.value + link_id=invoice.id, + link_type=EJVLinkType.INVOICE.value, + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, ).save() EjvLinkModel( - link_id=eft_invoice.id, link_type=EJVLinkType.INVOICE.value, - ejv_header_id=ejv_header.id, disbursement_status_code=DisbursementStatus.UPLOADED.value + link_id=eft_invoice.id, + link_type=EJVLinkType.INVOICE.value, + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, ).save() - ack_file_name = f'ACK.{file_ref}' + ack_file_name = f"ACK.{file_ref}" - with open(ack_file_name, 'a+', encoding='utf-8') as jv_file: - jv_file.write('') + with open(ack_file_name, "a+", encoding="utf-8") as jv_file: + jv_file.write("") jv_file.close() # Now upload the ACK file to minio and publish message. - upload_to_minio(str.encode(''), ack_file_name) + upload_to_minio(str.encode(""), ack_file_name) add_file_event_to_queue_and_process(client, ack_file_name, QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value) @@ -446,49 +524,51 @@ def test_successful_partner_reversal_ejv_reconciliations(session, app, client): # Now upload a feedback file and check the status. # Just create feedback file to mock the real feedback file. # Has legacy flow and PartnerDisbursements entries - feedback_content = f'GABG...........00000000{ejv_file_id}...\n' \ - f'..BH...0000.................................................................................' \ - f'.....................................................................CGI\n' \ - f'..JH...FI0000000{ejv_header_id}.........................000000000090.00.....................' \ - f'............................................................................................' \ - f'............................................................................................' \ - f'.........0000...............................................................................' \ - f'.......................................................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}0000120230529................................................' \ - f'...........000000000090.00C.................................................................' \ - f'..................................#{invoice_id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}0000220230529................................................' \ - f'...........000000000090.00D.................................................................' \ - f'..................................#{invoice_id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}0000120230529................................................' \ - f'...........000000000090.00C.................................................................' \ - f'...................................{eft_flowthrough} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header_id}0000220230529................................................' \ - f'...........000000000090.00D.................................................................' \ - f'...................................{eft_flowthrough} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..BT.......FI0000000{ejv_header_id}000000000000002000000000180.000000.......................' \ - f'............................................................................................' \ - f'...................................CGI' - - feedback_file_name = f'FEEDBACK.{file_ref}' - - with open(feedback_file_name, 'a+', encoding='utf-8') as jv_file: + feedback_content = ( + f"GABG...........00000000{ejv_file_id}...\n" + f"..BH...0000................................................................................." + f".....................................................................CGI\n" + f"..JH...FI0000000{ejv_header_id}.........................000000000090.00....................." + f"............................................................................................" + f"............................................................................................" + f".........0000..............................................................................." + f".......................................................................CGI\n" + f"..JD...FI0000000{ejv_header_id}0000120230529................................................" + f"...........000000000090.00C................................................................." + f"..................................#{invoice_id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header_id}0000220230529................................................" + f"...........000000000090.00D................................................................." + f"..................................#{invoice_id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header_id}0000120230529................................................" + f"...........000000000090.00C................................................................." + f"...................................{eft_flowthrough} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header_id}0000220230529................................................" + f"...........000000000090.00D................................................................." + f"...................................{eft_flowthrough} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..BT.......FI0000000{ejv_header_id}000000000000002000000000180.000000......................." + f"............................................................................................" + f"...................................CGI" + ) + + feedback_file_name = f"FEEDBACK.{file_ref}" + + with open(feedback_file_name, "a+", encoding="utf-8") as jv_file: jv_file.write(feedback_content) jv_file.close() - with open(feedback_file_name, 'rb') as f: + with open(feedback_file_name, "rb") as f: upload_to_minio(f.read(), feedback_file_name) add_file_event_to_queue_and_process(client, feedback_file_name, QueueMessageTypes.CGI_FEEDBACK_MESSAGE_TYPE.value) @@ -508,47 +588,55 @@ def test_successful_payment_ejv_reconciliations(session, app, client): # 1. Create EJV payment accounts # 2. Create invoice and related records # 3. Create a feedback file and assert status - corp_type = 'BEN' - filing_type = 'BCINC' + corp_type = "BEN" + filing_type = "BCINC" # Find fee schedule which have service fees. fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type(corp_type, filing_type) # Create a service fee distribution code - service_fee_dist_code = factory_distribution(name='service fee', client='112', reps_centre='99999', - service_line='99999', - stob='9999', project_code='9999998') + service_fee_dist_code = factory_distribution( + name="service fee", + client="112", + reps_centre="99999", + service_line="99999", + stob="9999", + project_code="9999998", + ) service_fee_dist_code.save() - dist_code = DistributionCodeModel.find_by_active_for_fee_schedule( - fee_schedule.fee_schedule_id) + dist_code = DistributionCodeModel.find_by_active_for_fee_schedule(fee_schedule.fee_schedule_id) # Update fee dist code to match the requirement. - dist_code.client = '112' - dist_code.responsibility_centre = '22222' - dist_code.service_line = '33333' - dist_code.stob = '4444' - dist_code.project_code = '5555559' + dist_code.client = "112" + dist_code.responsibility_centre = "22222" + dist_code.service_line = "33333" + dist_code.stob = "4444" + dist_code.project_code = "5555559" dist_code.service_fee_distribution_code_id = service_fee_dist_code.distribution_code_id dist_code.save() # GA - jv_account_1 = factory_create_ejv_account(auth_account_id='1') - jv_account_2 = factory_create_ejv_account(auth_account_id='2') + jv_account_1 = factory_create_ejv_account(auth_account_id="1") + jv_account_2 = factory_create_ejv_account(auth_account_id="2") # GI - jv_account_3 = factory_create_ejv_account(auth_account_id='3', client='111') - jv_account_4 = factory_create_ejv_account(auth_account_id='4', client='111') + jv_account_3 = factory_create_ejv_account(auth_account_id="3", client="111") + jv_account_4 = factory_create_ejv_account(auth_account_id="4", client="111") # Now create JV records. # Create EJV File model - file_ref = f'INBOX.{datetime.now()}' - ejv_file = EjvFileModel(file_ref=file_ref, - disbursement_status_code=DisbursementStatus.UPLOADED.value, - file_type=EjvFileType.PAYMENT.value).save() + file_ref = f"INBOX.{datetime.now()}" + ejv_file = EjvFileModel( + file_ref=file_ref, + disbursement_status_code=DisbursementStatus.UPLOADED.value, + file_type=EjvFileType.PAYMENT.value, + ).save() ejv_file_id = ejv_file.id - feedback_content = f'GABG...........00000000{ejv_file_id}...\n' \ - f'..BH...0000.................................................................................' \ - f'.....................................................................CGI\n' + feedback_content = ( + f"GABG...........00000000{ejv_file_id}...\n" + f"..BH...0000................................................................................." + f".....................................................................CGI\n" + ) jv_accounts = [jv_account_1, jv_account_2, jv_account_3, jv_account_4] inv_ids = [] @@ -556,80 +644,97 @@ def test_successful_payment_ejv_reconciliations(session, app, client): inv_total_amount = 101.5 for jv_acc in jv_accounts: jv_account_ids.append(jv_acc.id) - inv = factory_invoice(payment_account=jv_acc, corp_type_code=corp_type, total=inv_total_amount, - status_code=InvoiceStatus.APPROVED.value, payment_method_code=None) + inv = factory_invoice( + payment_account=jv_acc, + corp_type_code=corp_type, + total=inv_total_amount, + status_code=InvoiceStatus.APPROVED.value, + payment_method_code=None, + ) factory_invoice_reference(inv.id, status_code=InvoiceReferenceStatus.ACTIVE.value) - line = factory_payment_line_item(invoice_id=inv.id, - fee_schedule_id=fee_schedule.fee_schedule_id, - filing_fees=100, - total=100, - service_fees=1.5, - fee_dist_id=dist_code.distribution_code_id) + line = factory_payment_line_item( + invoice_id=inv.id, + fee_schedule_id=fee_schedule.fee_schedule_id, + filing_fees=100, + total=100, + service_fees=1.5, + fee_dist_id=dist_code.distribution_code_id, + ) inv_ids.append(inv.id) - ejv_header = EjvHeaderModel(disbursement_status_code=DisbursementStatus.UPLOADED.value, - ejv_file_id=ejv_file.id, payment_account_id=jv_acc.id).save() - - EjvLinkModel(link_id=inv.id, link_type=EJVLinkType.INVOICE.value, - ejv_header_id=ejv_header.id, disbursement_status_code=DisbursementStatus.UPLOADED.value - ).save() - inv_total = f'{inv.total:.2f}'.zfill(15) - pay_line_amount = f'{line.total:.2f}'.zfill(15) - service_fee_amount = f'{line.service_fees:.2f}'.zfill(15) + ejv_header = EjvHeaderModel( + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ejv_file_id=ejv_file.id, + payment_account_id=jv_acc.id, + ).save() + + EjvLinkModel( + link_id=inv.id, + link_type=EJVLinkType.INVOICE.value, + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ).save() + inv_total = f"{inv.total:.2f}".zfill(15) + pay_line_amount = f"{line.total:.2f}".zfill(15) + service_fee_amount = f"{line.service_fees:.2f}".zfill(15) # one JD has a shortened width (outside of spec). - jh_and_jd = f'..JH...FI0000000{ejv_header.id}.........................{inv_total}.....................' \ - f'............................................................................................' \ - f'............................................................................................' \ - f'.........0000...............................................................................' \ - f'.......................................................................CGI\n' \ - f'..JD...FI0000000{ejv_header.id}0000120230529................................................' \ - f'...........{pay_line_amount}D.................................................................' \ - f'...................................{inv.id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header.id}0000220230529................................................' \ - f'...........{pay_line_amount}C.................................................................' \ - f'...................................{inv.id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header.id}0000320230529...................................................' \ - f'........{service_fee_amount}D.................................................................' \ - f'...................................{inv.id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header.id}0000420230529................................................' \ - f'...........{service_fee_amount}C..............................................................' \ - f'......................................{inv.id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' + jh_and_jd = ( + f"..JH...FI0000000{ejv_header.id}.........................{inv_total}....................." + f"............................................................................................" + f"............................................................................................" + f".........0000..............................................................................." + f".......................................................................CGI\n" + f"..JD...FI0000000{ejv_header.id}0000120230529................................................" + f"...........{pay_line_amount}D................................................................." + f"...................................{inv.id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header.id}0000220230529................................................" + f"...........{pay_line_amount}C................................................................." + f"...................................{inv.id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header.id}0000320230529..................................................." + f"........{service_fee_amount}D................................................................." + f"...................................{inv.id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header.id}0000420230529................................................" + f"...........{service_fee_amount}C.............................................................." + f"......................................{inv.id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + ) feedback_content = feedback_content + jh_and_jd - feedback_content = feedback_content + f'..BT.......FI0000000{ejv_header.id}000000000000002{inv_total}0000.......' \ - f'.........................................................................' \ - f'......................................................................CGI' - ack_file_name = f'ACK.{file_ref}' + feedback_content = ( + feedback_content + f"..BT.......FI0000000{ejv_header.id}000000000000002{inv_total}0000......." + f"........................................................................." + f"......................................................................CGI" + ) + ack_file_name = f"ACK.{file_ref}" - with open(ack_file_name, 'a+', encoding='utf-8') as jv_file: - jv_file.write('') + with open(ack_file_name, "a+", encoding="utf-8") as jv_file: + jv_file.write("") jv_file.close() # Now upload the ACK file to minio and publish message. - upload_to_minio(str.encode(''), ack_file_name) + upload_to_minio(str.encode(""), ack_file_name) add_file_event_to_queue_and_process(client, ack_file_name, QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value) ejv_file = EjvFileModel.find_by_id(ejv_file_id) - feedback_file_name = f'FEEDBACK.{file_ref}' + feedback_file_name = f"FEEDBACK.{file_ref}" - with open(feedback_file_name, 'a+', encoding='utf-8') as jv_file: + with open(feedback_file_name, "a+", encoding="utf-8") as jv_file: jv_file.write(feedback_content) jv_file.close() # Now upload the ACK file to minio and publish message. - with open(feedback_file_name, 'rb') as f: + with open(feedback_file_name, "rb") as f: upload_to_minio(f.read(), feedback_file_name) add_file_event_to_queue_and_process(client, feedback_file_name, QueueMessageTypes.CGI_FEEDBACK_MESSAGE_TYPE.value) @@ -653,9 +758,12 @@ def test_successful_payment_ejv_reconciliations(session, app, client): # Assert payment records for jv_account_id in jv_account_ids: account = PaymentAccountModel.find_by_id(jv_account_id) - payment = PaymentModel.search_account_payments(auth_account_id=account.auth_account_id, - payment_status=PaymentStatus.COMPLETED.value, - page=1, limit=100)[0] + payment = PaymentModel.search_account_payments( + auth_account_id=account.auth_account_id, + payment_status=PaymentStatus.COMPLETED.value, + page=1, + limit=100, + )[0] assert len(payment) == 1 assert payment[0][0].paid_amount == inv_total_amount @@ -665,51 +773,59 @@ def test_successful_payment_reversal_ejv_reconciliations(session, app, client): # 1. Create EJV payment accounts # 2. Create invoice and related records # 3. Create a feedback file and assert status - corp_type = 'CP' - filing_type = 'OTFDR' + corp_type = "CP" + filing_type = "OTFDR" InvoiceModel.query.delete() # Reset the sequence, because the unit test is only dealing with 1 character for the invoice id. # This becomes more apparent when running unit tests in parallel. - db.session.execute(text('ALTER SEQUENCE invoices_id_seq RESTART WITH 1')) + db.session.execute(text("ALTER SEQUENCE invoices_id_seq RESTART WITH 1")) db.session.commit() # Find fee schedule which have service fees. fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type(corp_type, filing_type) # Create a service fee distribution code - service_fee_dist_code = factory_distribution(name='service fee', client='112', reps_centre='99999', - service_line='99999', - stob='9999', project_code='9999999') + service_fee_dist_code = factory_distribution( + name="service fee", + client="112", + reps_centre="99999", + service_line="99999", + stob="9999", + project_code="9999999", + ) service_fee_dist_code.save() - dist_code = DistributionCodeModel.find_by_active_for_fee_schedule( - fee_schedule.fee_schedule_id) + dist_code = DistributionCodeModel.find_by_active_for_fee_schedule(fee_schedule.fee_schedule_id) # Update fee dist code to match the requirement. - dist_code.client = '112' - dist_code.responsibility_centre = '22222' - dist_code.service_line = '33333' - dist_code.stob = '4444' - dist_code.project_code = '5555557' + dist_code.client = "112" + dist_code.responsibility_centre = "22222" + dist_code.service_line = "33333" + dist_code.stob = "4444" + dist_code.project_code = "5555557" dist_code.service_fee_distribution_code_id = service_fee_dist_code.distribution_code_id dist_code.save() # GA - jv_account_1 = factory_create_ejv_account(auth_account_id='1') + jv_account_1 = factory_create_ejv_account(auth_account_id="1") # GI - jv_account_3 = factory_create_ejv_account(auth_account_id='3', client='111') + jv_account_3 = factory_create_ejv_account(auth_account_id="3", client="111") # Now create JV records. # Create EJV File model - file_ref = f'INBOX.{datetime.now(tz=timezone.utc)}' - ejv_file = EjvFileModel(file_ref=file_ref, - disbursement_status_code=DisbursementStatus.UPLOADED.value, - file_type=EjvFileType.PAYMENT.value).save() + file_ref = f"INBOX.{datetime.now(tz=timezone.utc)}" + ejv_file = EjvFileModel( + file_ref=file_ref, + disbursement_status_code=DisbursementStatus.UPLOADED.value, + file_type=EjvFileType.PAYMENT.value, + ).save() ejv_file_id = ejv_file.id - feedback_content = f'GABG...........00000000{ejv_file_id}...\n' \ - f'..BH...0000.................................................................................' \ - f'.....................................................................CGI\n' + feedback_content = ( + f"GABG...........00000000{ejv_file_id}...\n" + f"..BH...0000................................................................................." + f".....................................................................CGI\n" + ) jv_accounts = [jv_account_1, jv_account_3] inv_ids = [] @@ -717,80 +833,95 @@ def test_successful_payment_reversal_ejv_reconciliations(session, app, client): inv_total_amount = 101.5 for jv_acc in jv_accounts: jv_account_ids.append(jv_acc.id) - inv = factory_invoice(payment_account=jv_acc, corp_type_code=corp_type, total=inv_total_amount, - status_code=InvoiceStatus.REFUND_REQUESTED.value, payment_method_code=None - ) + inv = factory_invoice( + payment_account=jv_acc, + corp_type_code=corp_type, + total=inv_total_amount, + status_code=InvoiceStatus.REFUND_REQUESTED.value, + payment_method_code=None, + ) factory_invoice_reference(inv.id, status_code=InvoiceReferenceStatus.ACTIVE.value) - line = factory_payment_line_item(invoice_id=inv.id, - fee_schedule_id=fee_schedule.fee_schedule_id, - filing_fees=100, - total=100, - service_fees=1.5, - fee_dist_id=dist_code.distribution_code_id) + line = factory_payment_line_item( + invoice_id=inv.id, + fee_schedule_id=fee_schedule.fee_schedule_id, + filing_fees=100, + total=100, + service_fees=1.5, + fee_dist_id=dist_code.distribution_code_id, + ) inv_ids.append(inv.id) - ejv_header = EjvHeaderModel(disbursement_status_code=DisbursementStatus.UPLOADED.value, - ejv_file_id=ejv_file.id, payment_account_id=jv_acc.id).save() + ejv_header = EjvHeaderModel( + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ejv_file_id=ejv_file.id, + payment_account_id=jv_acc.id, + ).save() EjvLinkModel( - link_id=inv.id, link_type=EJVLinkType.INVOICE.value, - ejv_header_id=ejv_header.id, disbursement_status_code=DisbursementStatus.UPLOADED.value + link_id=inv.id, + link_type=EJVLinkType.INVOICE.value, + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, ).save() - inv_total = f'{inv.total:.2f}'.zfill(15) - pay_line_amount = f'{line.total:.2f}'.zfill(15) - service_fee_amount = f'{line.service_fees:.2f}'.zfill(15) - jh_and_jd = f'..JH...FI0000000{ejv_header.id}.........................{inv_total}.....................' \ - f'............................................................................................' \ - f'............................................................................................' \ - f'.........0000...............................................................................' \ - f'.......................................................................CGI\n' \ - f'..JD...FI0000000{ejv_header.id}0000120230529................................................' \ - f'...........{pay_line_amount}C.................................................................' \ - f'...................................{inv.id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header.id}0000220230529................................................' \ - f'...........{pay_line_amount}D.................................................................' \ - f'...................................{inv.id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header.id}0000320230529...................................................' \ - f'........{service_fee_amount}C.................................................................' \ - f'...................................{inv.id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' \ - f'..JD...FI0000000{ejv_header.id}0000420230529................................................' \ - f'...........{service_fee_amount}D..............................................................' \ - f'......................................{inv.id} ' \ - f' 0000........................' \ - f'............................................................................................' \ - f'..................................CGI\n' + inv_total = f"{inv.total:.2f}".zfill(15) + pay_line_amount = f"{line.total:.2f}".zfill(15) + service_fee_amount = f"{line.service_fees:.2f}".zfill(15) + jh_and_jd = ( + f"..JH...FI0000000{ejv_header.id}.........................{inv_total}....................." + f"............................................................................................" + f"............................................................................................" + f".........0000..............................................................................." + f".......................................................................CGI\n" + f"..JD...FI0000000{ejv_header.id}0000120230529................................................" + f"...........{pay_line_amount}C................................................................." + f"...................................{inv.id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header.id}0000220230529................................................" + f"...........{pay_line_amount}D................................................................." + f"...................................{inv.id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header.id}0000320230529..................................................." + f"........{service_fee_amount}C................................................................." + f"...................................{inv.id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + f"..JD...FI0000000{ejv_header.id}0000420230529................................................" + f"...........{service_fee_amount}D.............................................................." + f"......................................{inv.id} " + f" 0000........................" + f"............................................................................................" + f"..................................CGI\n" + ) feedback_content = feedback_content + jh_and_jd - feedback_content = feedback_content + f'..BT.......FI0000000{ejv_header.id}000000000000002{inv_total}0000.......' \ - f'.........................................................................' \ - f'......................................................................CGI' - ack_file_name = f'ACK.{file_ref}' + feedback_content = ( + feedback_content + f"..BT.......FI0000000{ejv_header.id}000000000000002{inv_total}0000......." + f"........................................................................." + f"......................................................................CGI" + ) + ack_file_name = f"ACK.{file_ref}" - with open(ack_file_name, 'a+', encoding='utf-8') as jv_file: - jv_file.write('') + with open(ack_file_name, "a+", encoding="utf-8") as jv_file: + jv_file.write("") jv_file.close() - upload_to_minio(str.encode(''), ack_file_name) + upload_to_minio(str.encode(""), ack_file_name) add_file_event_to_queue_and_process(client, ack_file_name, QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value) ejv_file = EjvFileModel.find_by_id(ejv_file_id) - feedback_file_name = f'FEEDBACK.{file_ref}' + feedback_file_name = f"FEEDBACK.{file_ref}" - with open(feedback_file_name, 'a+', encoding='utf-8') as jv_file: + with open(feedback_file_name, "a+", encoding="utf-8") as jv_file: jv_file.write(feedback_content) jv_file.close() # Now upload the ACK file to minio and publish message. - with open(feedback_file_name, 'rb') as f: + with open(feedback_file_name, "rb") as f: upload_to_minio(f.read(), feedback_file_name) add_file_event_to_queue_and_process(client, feedback_file_name, QueueMessageTypes.CGI_FEEDBACK_MESSAGE_TYPE.value) @@ -812,9 +943,12 @@ def test_successful_payment_reversal_ejv_reconciliations(session, app, client): # Assert payment records for jv_account_id in jv_account_ids: account = PaymentAccountModel.find_by_id(jv_account_id) - payment = PaymentModel.search_account_payments(auth_account_id=account.auth_account_id, - payment_status=PaymentStatus.COMPLETED.value, - page=1, limit=100)[0] + payment = PaymentModel.search_account_payments( + auth_account_id=account.auth_account_id, + payment_status=PaymentStatus.COMPLETED.value, + page=1, + limit=100, + )[0] assert len(payment) == 1 assert payment[0][0].paid_amount == inv_total_amount @@ -825,7 +959,7 @@ def test_successful_refund_reconciliations(session, app, client): # 2. Mark the routing slip for refund. # 3. Create a AP reconciliation file. # 4. Assert the status. - rs_numbers = ('TEST00001', 'TEST00002') + rs_numbers = ("TEST00001", "TEST00002") for rs_number in rs_numbers: factory_routing_slip_account( number=rs_number, @@ -833,39 +967,44 @@ def test_successful_refund_reconciliations(session, app, client): total=100, remaining_amount=50, routing_slip_status=RoutingSlipStatus.REFUND_AUTHORIZED.value, - refund_amount=50) + refund_amount=50, + ) routing_slip = RoutingSlipModel.find_by_number(rs_number) - factory_refund(routing_slip_id=routing_slip.id, - details={ - 'name': 'TEST', - 'mailingAddress': { - 'city': 'Victoria', - 'region': 'BC', - 'street': '655 Douglas St', - 'country': 'CA', - 'postalCode': 'V8V 0B6', - 'streetAdditional': '' - } - }) + factory_refund( + routing_slip_id=routing_slip.id, + details={ + "name": "TEST", + "mailingAddress": { + "city": "Victoria", + "region": "BC", + "street": "655 Douglas St", + "country": "CA", + "postalCode": "V8V 0B6", + "streetAdditional": "", + }, + }, + ) routing_slip.status = RoutingSlipStatus.REFUND_UPLOADED.value # Now create AP records. # Create EJV File model - file_ref = f'INBOX.{datetime.now()}' - ejv_file = EjvFileModel(file_ref=file_ref, - file_type=EjvFileType.REFUND.value, - disbursement_status_code=DisbursementStatus.UPLOADED.value).save() + file_ref = f"INBOX.{datetime.now()}" + ejv_file = EjvFileModel( + file_ref=file_ref, + file_type=EjvFileType.REFUND.value, + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ).save() ejv_file_id = ejv_file.id # Upload an acknowledgement file - ack_file_name = f'ACK.{file_ref}' + ack_file_name = f"ACK.{file_ref}" - with open(ack_file_name, 'a+', encoding='utf-8') as jv_file: - jv_file.write('') + with open(ack_file_name, "a+", encoding="utf-8") as jv_file: + jv_file.write("") jv_file.close() # Now upload the ACK file to minio and publish message. - upload_to_minio(str.encode(''), ack_file_name) + upload_to_minio(str.encode(""), ack_file_name) add_file_event_to_queue_and_process(client, ack_file_name, QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value) @@ -873,71 +1012,72 @@ def test_successful_refund_reconciliations(session, app, client): # Now upload a feedback file and check the status. # Just create feedback file to mock the real feedback file. - feedback_content = f'APBG...........00000000{ejv_file_id}....\n' \ - f'APBH...0000.................................................................................' \ - f'.....................................................................CGI\n' \ - f'APIH...000000000...{rs_numbers[0]} ................' \ - f'...........................................................................................' \ - f'........................................................................................REF' \ - f'UND_FAS....................................................................................' \ - f'........................................................0000...............................' \ - f'...........................................................................................' \ - f'............................CGI\n' \ - f'APNA...............{rs_numbers[0]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................0000..............................................' \ - f'...........................................................................................' \ - f'.............CGI\n' \ - f'APIL...............{rs_numbers[0]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.....................................................................................0000..' \ - f'...........................................................................................' \ - f'.........................................................CGI\n' \ - f'APIC...............{rs_numbers[0]} ................' \ - f'............................0000...........................................................' \ - f'........................................................................................' \ - f'...CGI\n' \ - f'APIH...000000000...{rs_numbers[1]} ................' \ - f'...........................................................................................' \ - f'........................................................................................REF' \ - f'UND_FAS....................................................................................' \ - f'........................................................0000...............................' \ - f'...........................................................................................' \ - f'............................CGI\n' \ - f'APNA...............{rs_numbers[1]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................0000..............................................' \ - f'...........................................................................................' \ - f'.............CGI\n' \ - f'APIL...............{rs_numbers[1]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.....................................................................................0000..' \ - f'...........................................................................................' \ - f'.........................................................CGI\n' \ - f'APIC...............{rs_numbers[1]} ................' \ - f'............................0000...........................................................' \ - f'........................................................................................' \ - f'...CGI\n' \ - f'APBT...........00000000{ejv_file_id}..............................0000.....................' \ - f'...........................................................................................' \ - f'......................................CGI\n' \ - - feedback_file_name = f'FEEDBACK.{file_ref}' - - with open(feedback_file_name, 'a+', encoding='utf-8') as jv_file: + feedback_content = ( + f"APBG...........00000000{ejv_file_id}....\n" + f"APBH...0000................................................................................." + f".....................................................................CGI\n" + f"APIH...000000000...{rs_numbers[0]} ................" + f"..........................................................................................." + f"........................................................................................REF" + f"UND_FAS...................................................................................." + f"........................................................0000..............................." + f"..........................................................................................." + f"............................CGI\n" + f"APNA...............{rs_numbers[0]} ................" + f"..........................................................................................." + f"..........................................................................................." + f".........................................0000.............................................." + f"..........................................................................................." + f".............CGI\n" + f"APIL...............{rs_numbers[0]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f".....................................................................................0000.." + f"..........................................................................................." + f".........................................................CGI\n" + f"APIC...............{rs_numbers[0]} ................" + f"............................0000..........................................................." + f"........................................................................................" + f"...CGI\n" + f"APIH...000000000...{rs_numbers[1]} ................" + f"..........................................................................................." + f"........................................................................................REF" + f"UND_FAS...................................................................................." + f"........................................................0000..............................." + f"..........................................................................................." + f"............................CGI\n" + f"APNA...............{rs_numbers[1]} ................" + f"..........................................................................................." + f"..........................................................................................." + f".........................................0000.............................................." + f"..........................................................................................." + f".............CGI\n" + f"APIL...............{rs_numbers[1]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f".....................................................................................0000.." + f"..........................................................................................." + f".........................................................CGI\n" + f"APIC...............{rs_numbers[1]} ................" + f"............................0000..........................................................." + f"........................................................................................" + f"...CGI\n" + f"APBT...........00000000{ejv_file_id}..............................0000....................." + f"..........................................................................................." + f"......................................CGI\n" + ) + feedback_file_name = f"FEEDBACK.{file_ref}" + + with open(feedback_file_name, "a+", encoding="utf-8") as jv_file: jv_file.write(feedback_content) jv_file.close() # Now upload the ACK file to minio and publish message. - with open(feedback_file_name, 'rb') as f: + with open(feedback_file_name, "rb") as f: upload_to_minio(f.read(), feedback_file_name) add_file_event_to_queue_and_process(client, feedback_file_name, QueueMessageTypes.CGI_FEEDBACK_MESSAGE_TYPE.value) @@ -956,7 +1096,7 @@ def test_failed_refund_reconciliations(session, app, client): # 2. Mark the routing slip for refund. # 3. Create a AP reconciliation file. # 4. Assert the status. - rs_numbers = ('TEST00001', 'TEST00002') + rs_numbers = ("TEST00001", "TEST00002") for rs_number in rs_numbers: factory_routing_slip_account( number=rs_number, @@ -964,39 +1104,44 @@ def test_failed_refund_reconciliations(session, app, client): total=100, remaining_amount=50, routing_slip_status=RoutingSlipStatus.REFUND_AUTHORIZED.value, - refund_amount=50) + refund_amount=50, + ) routing_slip = RoutingSlipModel.find_by_number(rs_number) - factory_refund(routing_slip_id=routing_slip.id, - details={ - 'name': 'TEST', - 'mailingAddress': { - 'city': 'Victoria', - 'region': 'BC', - 'street': '655 Douglas St', - 'country': 'CA', - 'postalCode': 'V8V 0B6', - 'streetAdditional': '' - } - }) + factory_refund( + routing_slip_id=routing_slip.id, + details={ + "name": "TEST", + "mailingAddress": { + "city": "Victoria", + "region": "BC", + "street": "655 Douglas St", + "country": "CA", + "postalCode": "V8V 0B6", + "streetAdditional": "", + }, + }, + ) routing_slip.status = RoutingSlipStatus.REFUND_UPLOADED.value # Now create AP records. # Create EJV File model - file_ref = f'INBOX.{datetime.now()}' - ejv_file = EjvFileModel(file_ref=file_ref, - file_type=EjvFileType.REFUND.value, - disbursement_status_code=DisbursementStatus.UPLOADED.value).save() + file_ref = f"INBOX.{datetime.now()}" + ejv_file = EjvFileModel( + file_ref=file_ref, + file_type=EjvFileType.REFUND.value, + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ).save() ejv_file_id = ejv_file.id # Upload an acknowledgement file - ack_file_name = f'ACK.{file_ref}' + ack_file_name = f"ACK.{file_ref}" - with open(ack_file_name, 'a+', encoding='utf-8') as jv_file: - jv_file.write('') + with open(ack_file_name, "a+", encoding="utf-8") as jv_file: + jv_file.write("") jv_file.close() # Now upload the ACK file to minio and publish message. - upload_to_minio(str.encode(''), ack_file_name) + upload_to_minio(str.encode(""), ack_file_name) add_file_event_to_queue_and_process(client, ack_file_name, QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value) @@ -1005,71 +1150,72 @@ def test_failed_refund_reconciliations(session, app, client): # Now upload a feedback file and check the status. # Just create feedback file to mock the real feedback file. # Set first routing slip to be success and second to ve failed - feedback_content = f'APBG...........00000000{ejv_file_id}....\n' \ - f'APBH...0000.................................................................................' \ - f'.....................................................................CGI\n' \ - f'APIH...000000000...{rs_numbers[0]} ................' \ - f'...........................................................................................' \ - f'........................................................................................REF' \ - f'UND_FAS....................................................................................' \ - f'........................................................0000...............................' \ - f'...........................................................................................' \ - f'............................CGI\n' \ - f'APNA...............{rs_numbers[0]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................0000..............................................' \ - f'...........................................................................................' \ - f'.............CGI\n' \ - f'APIL...............{rs_numbers[0]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.....................................................................................0000..' \ - f'...........................................................................................' \ - f'.........................................................CGI\n' \ - f'APIC...............{rs_numbers[0]} ................' \ - f'............................0000...........................................................' \ - f'........................................................................................' \ - f'...CGI\n' \ - f'APIH...000000000...{rs_numbers[1]} ................' \ - f'...........................................................................................' \ - f'........................................................................................REF' \ - f'UND_FAS....................................................................................' \ - f'........................................................0001...............................' \ - f'...........................................................................................' \ - f'............................CGI\n' \ - f'APNA...............{rs_numbers[1]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................0001..............................................' \ - f'...........................................................................................' \ - f'.............CGI\n' \ - f'APIL...............{rs_numbers[1]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.....................................................................................0001..' \ - f'...........................................................................................' \ - f'.........................................................CGI\n' \ - f'APIC...............{rs_numbers[1]} ................' \ - f'............................0001...........................................................' \ - f'........................................................................................' \ - f'...CGI\n' \ - f'APBT...........00000000{ejv_file_id}..............................0000.....................' \ - f'...........................................................................................' \ - f'......................................CGI\n' \ - - feedback_file_name = f'FEEDBACK.{file_ref}' - - with open(feedback_file_name, 'a+', encoding='utf-8') as jv_file: + feedback_content = ( + f"APBG...........00000000{ejv_file_id}....\n" + f"APBH...0000................................................................................." + f".....................................................................CGI\n" + f"APIH...000000000...{rs_numbers[0]} ................" + f"..........................................................................................." + f"........................................................................................REF" + f"UND_FAS...................................................................................." + f"........................................................0000..............................." + f"..........................................................................................." + f"............................CGI\n" + f"APNA...............{rs_numbers[0]} ................" + f"..........................................................................................." + f"..........................................................................................." + f".........................................0000.............................................." + f"..........................................................................................." + f".............CGI\n" + f"APIL...............{rs_numbers[0]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f".....................................................................................0000.." + f"..........................................................................................." + f".........................................................CGI\n" + f"APIC...............{rs_numbers[0]} ................" + f"............................0000..........................................................." + f"........................................................................................" + f"...CGI\n" + f"APIH...000000000...{rs_numbers[1]} ................" + f"..........................................................................................." + f"........................................................................................REF" + f"UND_FAS...................................................................................." + f"........................................................0001..............................." + f"..........................................................................................." + f"............................CGI\n" + f"APNA...............{rs_numbers[1]} ................" + f"..........................................................................................." + f"..........................................................................................." + f".........................................0001.............................................." + f"..........................................................................................." + f".............CGI\n" + f"APIL...............{rs_numbers[1]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f".....................................................................................0001.." + f"..........................................................................................." + f".........................................................CGI\n" + f"APIC...............{rs_numbers[1]} ................" + f"............................0001..........................................................." + f"........................................................................................" + f"...CGI\n" + f"APBT...........00000000{ejv_file_id}..............................0000....................." + f"..........................................................................................." + f"......................................CGI\n" + ) + feedback_file_name = f"FEEDBACK.{file_ref}" + + with open(feedback_file_name, "a+", encoding="utf-8") as jv_file: jv_file.write(feedback_content) jv_file.close() # Now upload the ACK file to minio and publish message. - with open(feedback_file_name, 'rb') as f: + with open(feedback_file_name, "rb") as f: upload_to_minio(f.read(), feedback_file_name) add_file_event_to_queue_and_process(client, feedback_file_name, QueueMessageTypes.CGI_FEEDBACK_MESSAGE_TYPE.value) @@ -1089,7 +1235,7 @@ def test_successful_eft_refund_reconciliations(session, app, client): # 1. Create EFT refund. # 2. Create a AP reconciliation file. # 3. Assert the status. - eft_short_name_names = ('TEST00001', 'TEST00002') + eft_short_name_names = ("TEST00001", "TEST00002") eft_refund_ids = [] for eft_short_name_name in eft_short_name_names: factory_create_eft_shortname(short_name=eft_short_name_name) @@ -1097,88 +1243,93 @@ def test_successful_eft_refund_reconciliations(session, app, client): eft_refund = factory_create_eft_refund( disbursement_status_code=DisbursementStatus.ACKNOWLEDGED.value, refund_amount=100, - refund_email='test@test.com', + refund_email="test@test.com", short_name_id=eft_short_name.id, status=EFTShortnameRefundStatus.APPROVED.value, - comment=eft_short_name.short_name) + comment=eft_short_name.short_name, + ) eft_refund_ids.append(str(eft_refund.id).zfill(9)) # Now create AP records. # Create EJV File model - file_ref = f'INBOX.{datetime.now()}' - ejv_file = EjvFileModel(file_ref=file_ref, file_type=EjvFileType.EFT_REFUND.value, - disbursement_status_code=DisbursementStatus.UPLOADED.value).save() + file_ref = f"INBOX.{datetime.now()}" + ejv_file = EjvFileModel( + file_ref=file_ref, + file_type=EjvFileType.EFT_REFUND.value, + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ).save() ejv_file_id = ejv_file.id # Upload an acknowledgement file - ack_file_name = f'ACK.{file_ref}' + ack_file_name = f"ACK.{file_ref}" - with open(ack_file_name, 'a+') as jv_file: - jv_file.write('') + with open(ack_file_name, "a+") as jv_file: + jv_file.write("") jv_file.close() # Upload the ACK file to minio and publish message. - upload_to_minio(str.encode(''), ack_file_name) + upload_to_minio(str.encode(""), ack_file_name) add_file_event_to_queue_and_process(client, ack_file_name, QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value) ejv_file = EjvFileModel.find_by_id(ejv_file_id) # Create and upload a feedback file and check the status. - feedback_content = f'APBG...........00000000{ejv_file_id}....\n' \ - f'APBH...0000................................................................................' \ - f'......................................................................CGI\n' \ - f'APIH...000000000...{eft_refund_ids[0]} ............' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.REFUND_EFT................................................................................' \ - f'............................................................0000...........................' \ - f'...........................................................................................' \ - f'................................CGI\n' \ - f'APIL...............{eft_refund_ids[0]} ............' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................................................................' \ - f'0000.......................................................................................' \ - f'...............................................................CGI\n' \ - f'APIC...............{eft_short_name_names[0]} ......' \ - f'......................................0000.................................................' \ - f'...........................................................................................' \ - f'..........CGI\n' \ - f'APIH...000000000...{eft_refund_ids[1]} ............' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.REFUND_EFT................................................................................' \ - f'............................................................0000...........................' \ - f'...........................................................................................' \ - f'................................CGI\n' \ - f'APIL...............{eft_refund_ids[1]} ............' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................................................................' \ - f'0000.......................................................................................' \ - f'...............................................................CGI\n' \ - f'APIC...............{eft_short_name_names[1]} ......' \ - f'......................................0000.................................................' \ - f'...........................................................................................' \ - f'..........CGI\n' \ - f'APBT...........00000000{ejv_file_id}..............................0000.....................' \ - f'...........................................................................................' \ - f'......................................CGI\n' \ - - feedback_file_name = f'FEEDBACK.{file_ref}' - - with open(feedback_file_name, 'a+', encoding='utf-8') as jv_file: + feedback_content = ( + f"APBG...........00000000{ejv_file_id}....\n" + f"APBH...0000................................................................................" + f"......................................................................CGI\n" + f"APIH...000000000...{eft_refund_ids[0]} ............" + f"..........................................................................................." + f"..........................................................................................." + f".REFUND_EFT................................................................................" + f"............................................................0000..........................." + f"..........................................................................................." + f"................................CGI\n" + f"APIL...............{eft_refund_ids[0]} ............" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"........................................................................................." + f"0000......................................................................................." + f"...............................................................CGI\n" + f"APIC...............{eft_short_name_names[0]} ......" + f"......................................0000................................................." + f"..........................................................................................." + f"..........CGI\n" + f"APIH...000000000...{eft_refund_ids[1]} ............" + f"..........................................................................................." + f"..........................................................................................." + f".REFUND_EFT................................................................................" + f"............................................................0000..........................." + f"..........................................................................................." + f"................................CGI\n" + f"APIL...............{eft_refund_ids[1]} ............" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"........................................................................................." + f"0000......................................................................................." + f"...............................................................CGI\n" + f"APIC...............{eft_short_name_names[1]} ......" + f"......................................0000................................................." + f"..........................................................................................." + f"..........CGI\n" + f"APBT...........00000000{ejv_file_id}..............................0000....................." + f"..........................................................................................." + f"......................................CGI\n" + ) + feedback_file_name = f"FEEDBACK.{file_ref}" + + with open(feedback_file_name, "a+", encoding="utf-8") as jv_file: jv_file.write(feedback_content) jv_file.close() # Now upload the ACK file to minio and publish message. - with open(feedback_file_name, 'rb') as f: + with open(feedback_file_name, "rb") as f: upload_to_minio(f.read(), feedback_file_name) add_file_event_to_queue_and_process(client, feedback_file_name, QueueMessageTypes.CGI_FEEDBACK_MESSAGE_TYPE.value) @@ -1198,7 +1349,7 @@ def test_failed_eft_refund_reconciliations(session, app, client): # 1. Create EFT refund. # 2. Create a AP reconciliation file. # 3. Assert the status. - eft_short_name_names = ('TEST00001', 'TEST00002') + eft_short_name_names = ("TEST00001", "TEST00002") eft_refund_ids = [] for eft_short_name_name in eft_short_name_names: factory_create_eft_shortname(short_name=eft_short_name_name) @@ -1206,88 +1357,93 @@ def test_failed_eft_refund_reconciliations(session, app, client): eft_refund = factory_create_eft_refund( disbursement_status_code=DisbursementStatus.ACKNOWLEDGED.value, refund_amount=100, - refund_email='test@test.com', + refund_email="test@test.com", short_name_id=eft_short_name.id, status=EFTShortnameRefundStatus.APPROVED.value, - comment=eft_short_name.short_name) + comment=eft_short_name.short_name, + ) eft_refund_ids.append(str(eft_refund.id).zfill(9)) # Now create AP records. # Create EJV File model - file_ref = f'INBOX.{datetime.now()}' - ejv_file = EjvFileModel(file_ref=file_ref, file_type=EjvFileType.EFT_REFUND.value, - disbursement_status_code=DisbursementStatus.UPLOADED.value).save() + file_ref = f"INBOX.{datetime.now()}" + ejv_file = EjvFileModel( + file_ref=file_ref, + file_type=EjvFileType.EFT_REFUND.value, + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ).save() ejv_file_id = ejv_file.id # Upload an acknowledgement file - ack_file_name = f'ACK.{file_ref}' + ack_file_name = f"ACK.{file_ref}" - with open(ack_file_name, 'a+') as jv_file: - jv_file.write('') + with open(ack_file_name, "a+") as jv_file: + jv_file.write("") jv_file.close() # Now upload the ACK file to minio and publish message. - upload_to_minio(str.encode(''), ack_file_name) + upload_to_minio(str.encode(""), ack_file_name) add_file_event_to_queue_and_process(client, ack_file_name, QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value) ejv_file = EjvFileModel.find_by_id(ejv_file_id) # Create and upload a feedback file and check the status. - feedback_content = f'APBG...........00000000{ejv_file_id}....\n' \ - f'APBH...0000................................................................................' \ - f'......................................................................CGI\n' \ - f'APIH...000000000...{eft_refund_ids[0]} ............' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.REFUND_EFT................................................................................' \ - f'............................................................0000...........................' \ - f'...........................................................................................' \ - f'................................CGI\n' \ - f'APIL...............{eft_refund_ids[0]} ............' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................................................................' \ - f'0000.......................................................................................' \ - f'...............................................................CGI\n' \ - f'APIC...............{eft_short_name_names[0]} ......' \ - f'......................................0000.................................................' \ - f'...........................................................................................' \ - f'..........CGI\n' \ - f'APIH...000000000...{eft_refund_ids[1]} ............' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.REFUND_EFT................................................................................' \ - f'............................................................0001...........................' \ - f'...........................................................................................' \ - f'................................CGI\n' \ - f'APIL...............{eft_refund_ids[1]} ............' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................................................................' \ - f'0001.......................................................................................' \ - f'...............................................................CGI\n' \ - f'APIC...............{eft_short_name_names[1]} ......' \ - f'......................................0001.................................................' \ - f'...........................................................................................' \ - f'..........CGI\n' \ - f'APBT...........00000000{ejv_file_id}..............................0000.....................' \ - f'...........................................................................................' \ - f'......................................CGI\n' \ - - feedback_file_name = f'FEEDBACK.{file_ref}' - - with open(feedback_file_name, 'a+', encoding='utf-8') as jv_file: + feedback_content = ( + f"APBG...........00000000{ejv_file_id}....\n" + f"APBH...0000................................................................................" + f"......................................................................CGI\n" + f"APIH...000000000...{eft_refund_ids[0]} ............" + f"..........................................................................................." + f"..........................................................................................." + f".REFUND_EFT................................................................................" + f"............................................................0000..........................." + f"..........................................................................................." + f"................................CGI\n" + f"APIL...............{eft_refund_ids[0]} ............" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"........................................................................................." + f"0000......................................................................................." + f"...............................................................CGI\n" + f"APIC...............{eft_short_name_names[0]} ......" + f"......................................0000................................................." + f"..........................................................................................." + f"..........CGI\n" + f"APIH...000000000...{eft_refund_ids[1]} ............" + f"..........................................................................................." + f"..........................................................................................." + f".REFUND_EFT................................................................................" + f"............................................................0001..........................." + f"..........................................................................................." + f"................................CGI\n" + f"APIL...............{eft_refund_ids[1]} ............" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"........................................................................................." + f"0001......................................................................................." + f"...............................................................CGI\n" + f"APIC...............{eft_short_name_names[1]} ......" + f"......................................0001................................................." + f"..........................................................................................." + f"..........CGI\n" + f"APBT...........00000000{ejv_file_id}..............................0000....................." + f"..........................................................................................." + f"......................................CGI\n" + ) + feedback_file_name = f"FEEDBACK.{file_ref}" + + with open(feedback_file_name, "a+", encoding="utf-8") as jv_file: jv_file.write(feedback_content) jv_file.close() # Now upload the ACK file to minio and publish message. - with open(feedback_file_name, 'rb') as f: + with open(feedback_file_name, "rb") as f: upload_to_minio(f.read(), feedback_file_name) add_file_event_to_queue_and_process(client, feedback_file_name, QueueMessageTypes.CGI_FEEDBACK_MESSAGE_TYPE.value) @@ -1311,16 +1467,16 @@ def test_successful_ap_disbursement(session, app, client): # 2. Create a AP reconciliation file. # 3. Assert the status. invoice_ids = [] - account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.ACTIVE.value) + account = factory_create_pad_account(auth_account_id="1", status=CfsAccountStatus.ACTIVE.value) invoice = factory_invoice( payment_account=account, status_code=InvoiceStatus.PAID.value, total=10, - corp_type_code='BCA' + corp_type_code="BCA", ) invoice_ids.append(invoice.id) - fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type('BCA', 'OLAARTOQ') + fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type("BCA", "OLAARTOQ") line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() @@ -1329,7 +1485,7 @@ def test_successful_ap_disbursement(session, app, client): status_code=InvoiceStatus.REFUNDED.value, total=10, disbursement_status_code=DisbursementStatus.COMPLETED.value, - corp_type_code='BCA' + corp_type_code="BCA", ) invoice_ids.append(refund_invoice.id) @@ -1338,103 +1494,113 @@ def test_successful_ap_disbursement(session, app, client): factory_refund(invoice_id=refund_invoice.id) - file_ref = f'INBOX.{datetime.now()}' - ejv_file = EjvFileModel(file_ref=file_ref, - file_type=EjvFileType.NON_GOV_DISBURSEMENT.value, - disbursement_status_code=DisbursementStatus.UPLOADED.value).save() + file_ref = f"INBOX.{datetime.now()}" + ejv_file = EjvFileModel( + file_ref=file_ref, + file_type=EjvFileType.NON_GOV_DISBURSEMENT.value, + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ).save() ejv_file_id = ejv_file.id - ejv_header = EjvHeaderModel(disbursement_status_code=DisbursementStatus.UPLOADED.value, - ejv_file_id=ejv_file.id, payment_account_id=account.id).save() + ejv_header = EjvHeaderModel( + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ejv_file_id=ejv_file.id, + payment_account_id=account.id, + ).save() EjvLinkModel( - link_id=invoice.id, link_type=EJVLinkType.INVOICE.value, - ejv_header_id=ejv_header.id, disbursement_status_code=DisbursementStatus.UPLOADED.value + link_id=invoice.id, + link_type=EJVLinkType.INVOICE.value, + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, ).save() EjvLinkModel( - link_id=refund_invoice.id, link_type=EJVLinkType.INVOICE.value, ejv_header_id=ejv_header.id, - disbursement_status_code=DisbursementStatus.UPLOADED.value + link_id=refund_invoice.id, + link_type=EJVLinkType.INVOICE.value, + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, ).save() - ack_file_name = f'ACK.{file_ref}' + ack_file_name = f"ACK.{file_ref}" - with open(ack_file_name, 'a+', encoding='utf-8') as jv_file: - jv_file.write('') + with open(ack_file_name, "a+", encoding="utf-8") as jv_file: + jv_file.write("") jv_file.close() - upload_to_minio(str.encode(''), ack_file_name) + upload_to_minio(str.encode(""), ack_file_name) add_file_event_to_queue_and_process(client, ack_file_name, QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value) ejv_file = EjvFileModel.find_by_id(ejv_file_id) invoice_str = [str(invoice_id).zfill(9) for invoice_id in invoice_ids] - feedback_content = f'APBG...........{str(ejv_file_id).zfill(9)}....\n' \ - f'APBH...0000.................................................................................' \ - f'.....................................................................CGI\n' \ - f'APIH...000000000...{invoice_str[0]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'........................................................0000...............................' \ - f'...........................................................................................' \ - f'............................CGI\n' \ - f'APNA...............{invoice_str[0]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................0000..............................................' \ - f'...........................................................................................' \ - f'.............CGI\n' \ - f'APIL...............{invoice_str[0]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.....................................................................................0000..' \ - f'...........................................................................................' \ - f'.........................................................CGI\n' \ - f'APIC...............{invoice_str[0]} ................' \ - f'............................0000...........................................................' \ - f'........................................................................................' \ - f'...CGI\n' \ - f'APIH...000000000...{invoice_str[1]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'........................................................0000...............................' \ - f'...........................................................................................' \ - f'............................CGI\n' \ - f'APNA...............{invoice_str[1]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................0000..............................................' \ - f'...........................................................................................' \ - f'.............CGI\n' \ - f'APIL...............{invoice_str[1]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.....................................................................................0000..' \ - f'...........................................................................................' \ - f'.........................................................CGI\n' \ - f'APIC...............{invoice_str[1]} ................' \ - f'............................0000...........................................................' \ - f'........................................................................................' \ - f'...CGI\n' \ - f'APBT...........00000000{ejv_file_id}..............................0000.....................' \ - f'...........................................................................................' \ - f'......................................CGI\n' \ - - - feedback_file_name = f'FEEDBACK.{file_ref}' - - with open(feedback_file_name, 'a+', encoding='utf-8') as jv_file: + feedback_content = ( + f"APBG...........{str(ejv_file_id).zfill(9)}....\n" + f"APBH...0000................................................................................." + f".....................................................................CGI\n" + f"APIH...000000000...{invoice_str[0]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"........................................................0000..............................." + f"..........................................................................................." + f"............................CGI\n" + f"APNA...............{invoice_str[0]} ................" + f"..........................................................................................." + f"..........................................................................................." + f".........................................0000.............................................." + f"..........................................................................................." + f".............CGI\n" + f"APIL...............{invoice_str[0]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f".....................................................................................0000.." + f"..........................................................................................." + f".........................................................CGI\n" + f"APIC...............{invoice_str[0]} ................" + f"............................0000..........................................................." + f"........................................................................................" + f"...CGI\n" + f"APIH...000000000...{invoice_str[1]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"........................................................0000..............................." + f"..........................................................................................." + f"............................CGI\n" + f"APNA...............{invoice_str[1]} ................" + f"..........................................................................................." + f"..........................................................................................." + f".........................................0000.............................................." + f"..........................................................................................." + f".............CGI\n" + f"APIL...............{invoice_str[1]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f".....................................................................................0000.." + f"..........................................................................................." + f".........................................................CGI\n" + f"APIC...............{invoice_str[1]} ................" + f"............................0000..........................................................." + f"........................................................................................" + f"...CGI\n" + f"APBT...........00000000{ejv_file_id}..............................0000....................." + f"..........................................................................................." + f"......................................CGI\n" + ) + + feedback_file_name = f"FEEDBACK.{file_ref}" + + with open(feedback_file_name, "a+", encoding="utf-8") as jv_file: jv_file.write(feedback_content) jv_file.close() - with open(feedback_file_name, 'rb') as f: + with open(feedback_file_name, "rb") as f: upload_to_minio(f.read(), feedback_file_name) add_file_event_to_queue_and_process(client, feedback_file_name, QueueMessageTypes.CGI_FEEDBACK_MESSAGE_TYPE.value) @@ -1459,15 +1625,15 @@ def test_failure_ap_disbursement(session, app, client): # 2. Create a AP reconciliation file. # 3. Assert the status. invoice_ids = [] - account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.ACTIVE.value) + account = factory_create_pad_account(auth_account_id="1", status=CfsAccountStatus.ACTIVE.value) invoice = factory_invoice( payment_account=account, status_code=InvoiceStatus.PAID.value, total=10, - corp_type_code='BCA' + corp_type_code="BCA", ) invoice_ids.append(invoice.id) - fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type('BCA', 'OLAARTOQ') + fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type("BCA", "OLAARTOQ") line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() @@ -1476,7 +1642,7 @@ def test_failure_ap_disbursement(session, app, client): status_code=InvoiceStatus.REFUNDED.value, total=10, disbursement_status_code=DisbursementStatus.COMPLETED.value, - corp_type_code='BCA' + corp_type_code="BCA", ) invoice_ids.append(refund_invoice.id) line = factory_payment_line_item(refund_invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) @@ -1484,32 +1650,41 @@ def test_failure_ap_disbursement(session, app, client): factory_refund(invoice_id=refund_invoice.id) - file_ref = f'INBOX.{datetime.now()}' - ejv_file = EjvFileModel(file_ref=file_ref, - file_type=EjvFileType.NON_GOV_DISBURSEMENT.value, - disbursement_status_code=DisbursementStatus.UPLOADED.value).save() + file_ref = f"INBOX.{datetime.now()}" + ejv_file = EjvFileModel( + file_ref=file_ref, + file_type=EjvFileType.NON_GOV_DISBURSEMENT.value, + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ).save() ejv_file_id = ejv_file.id - ejv_header = EjvHeaderModel(disbursement_status_code=DisbursementStatus.UPLOADED.value, - ejv_file_id=ejv_file.id, payment_account_id=account.id).save() + ejv_header = EjvHeaderModel( + disbursement_status_code=DisbursementStatus.UPLOADED.value, + ejv_file_id=ejv_file.id, + payment_account_id=account.id, + ).save() EjvLinkModel( - link_id=invoice.id, link_type=EJVLinkType.INVOICE.value, - ejv_header_id=ejv_header.id, disbursement_status_code=DisbursementStatus.UPLOADED.value + link_id=invoice.id, + link_type=EJVLinkType.INVOICE.value, + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, ).save() EjvLinkModel( - link_id=refund_invoice.id, link_type=EJVLinkType.INVOICE.value, ejv_header_id=ejv_header.id, - disbursement_status_code=DisbursementStatus.UPLOADED.value + link_id=refund_invoice.id, + link_type=EJVLinkType.INVOICE.value, + ejv_header_id=ejv_header.id, + disbursement_status_code=DisbursementStatus.UPLOADED.value, ).save() - ack_file_name = f'ACK.{file_ref}' + ack_file_name = f"ACK.{file_ref}" - with open(ack_file_name, 'a+', encoding='utf-8') as jv_file: - jv_file.write('') + with open(ack_file_name, "a+", encoding="utf-8") as jv_file: + jv_file.write("") jv_file.close() - upload_to_minio(str.encode(''), ack_file_name) + upload_to_minio(str.encode(""), ack_file_name) add_file_event_to_queue_and_process(client, ack_file_name, QueueMessageTypes.CGI_ACK_MESSAGE_TYPE.value) @@ -1520,70 +1695,71 @@ def test_failure_ap_disbursement(session, app, client): # Now upload a feedback file and check the status. # Just create feedback file to mock the real feedback file. # Set first invoice to be success and second to be failed - feedback_content = f'APBG...........{str(ejv_file_id).zfill(9)}....\n' \ - f'APBH...0000.................................................................................' \ - f'.....................................................................CGI\n' \ - f'APIH...000000000...{invoice_str[0]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'........................................................0000...............................' \ - f'...........................................................................................' \ - f'............................CGI\n' \ - f'APNA...............{invoice_str[0]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................0000..............................................' \ - f'...........................................................................................' \ - f'.............CGI\n' \ - f'APIL...............{invoice_str[0]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.....................................................................................0000..' \ - f'...........................................................................................' \ - f'.........................................................CGI\n' \ - f'APIC...............{invoice_str[0]} ................' \ - f'............................0000...........................................................' \ - f'........................................................................................' \ - f'...CGI\n' \ - f'APIH...000000000...{invoice_str[1]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'........................................................0001...............................' \ - f'...........................................................................................' \ - f'............................CGI\n' \ - f'APNA...............{invoice_str[1]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.........................................0001..............................................' \ - f'...........................................................................................' \ - f'.............CGI\n' \ - f'APIL...............{invoice_str[1]} ................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'...........................................................................................' \ - f'.....................................................................................0001..' \ - f'...........................................................................................' \ - f'.........................................................CGI\n' \ - f'APIC...............{invoice_str[1]} ................' \ - f'............................0001...........................................................' \ - f'........................................................................................' \ - f'...CGI\n' \ - f'APBT...........00000000{ejv_file_id}..............................0000.....................' \ - f'...........................................................................................' \ - f'......................................CGI\n' \ - - feedback_file_name = f'FEEDBACK.{file_ref}' - - with open(feedback_file_name, 'a+', encoding='utf-8') as jv_file: + feedback_content = ( + f"APBG...........{str(ejv_file_id).zfill(9)}....\n" + f"APBH...0000................................................................................." + f".....................................................................CGI\n" + f"APIH...000000000...{invoice_str[0]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"........................................................0000..............................." + f"..........................................................................................." + f"............................CGI\n" + f"APNA...............{invoice_str[0]} ................" + f"..........................................................................................." + f"..........................................................................................." + f".........................................0000.............................................." + f"..........................................................................................." + f".............CGI\n" + f"APIL...............{invoice_str[0]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f".....................................................................................0000.." + f"..........................................................................................." + f".........................................................CGI\n" + f"APIC...............{invoice_str[0]} ................" + f"............................0000..........................................................." + f"........................................................................................" + f"...CGI\n" + f"APIH...000000000...{invoice_str[1]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"........................................................0001..............................." + f"..........................................................................................." + f"............................CGI\n" + f"APNA...............{invoice_str[1]} ................" + f"..........................................................................................." + f"..........................................................................................." + f".........................................0001.............................................." + f"..........................................................................................." + f".............CGI\n" + f"APIL...............{invoice_str[1]} ................" + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f"..........................................................................................." + f".....................................................................................0001.." + f"..........................................................................................." + f".........................................................CGI\n" + f"APIC...............{invoice_str[1]} ................" + f"............................0001..........................................................." + f"........................................................................................" + f"...CGI\n" + f"APBT...........00000000{ejv_file_id}..............................0000....................." + f"..........................................................................................." + f"......................................CGI\n" + ) + feedback_file_name = f"FEEDBACK.{file_ref}" + + with open(feedback_file_name, "a+", encoding="utf-8") as jv_file: jv_file.write(feedback_content) jv_file.close() - with open(feedback_file_name, 'rb') as f: + with open(feedback_file_name, "rb") as f: upload_to_minio(f.read(), feedback_file_name) add_file_event_to_queue_and_process(client, feedback_file_name, QueueMessageTypes.CGI_FEEDBACK_MESSAGE_TYPE.value) @@ -1593,14 +1769,10 @@ def test_failure_ap_disbursement(session, app, client): invoice_1 = InvoiceModel.find_by_id(invoice_ids[0]) assert invoice_1.disbursement_status_code == DisbursementStatus.COMPLETED.value assert invoice_1.disbursement_date is not None - invoice_link = db.session.query(EjvLinkModel)\ - .filter(EjvLinkModel.link_id == invoice_ids[0])\ - .one_or_none() + invoice_link = db.session.query(EjvLinkModel).filter(EjvLinkModel.link_id == invoice_ids[0]).one_or_none() assert invoice_link.disbursement_status_code == DisbursementStatus.COMPLETED.value invoice_2 = InvoiceModel.find_by_id(invoice_ids[1]) assert invoice_2.disbursement_status_code == DisbursementStatus.ERRORED.value - invoice_link = db.session.query(EjvLinkModel)\ - .filter(EjvLinkModel.link_id == invoice_ids[1])\ - .one_or_none() + invoice_link = db.session.query(EjvLinkModel).filter(EjvLinkModel.link_id == invoice_ids[1]).one_or_none() assert invoice_link.disbursement_status_code == DisbursementStatus.ERRORED.value diff --git a/pay-queue/tests/integration/test_eft_reconciliation.py b/pay-queue/tests/integration/test_eft_reconciliation.py index a76ae0b8f..c6f18e0f3 100644 --- a/pay-queue/tests/integration/test_eft_reconciliation.py +++ b/pay-queue/tests/integration/test_eft_reconciliation.py @@ -31,34 +31,52 @@ from pay_api.models import PaymentAccount as PaymentAccountModel from pay_api.services import EFTShortNamesService from pay_api.utils.enums import ( - EFTCreditInvoiceStatus, EFTFileLineType, EFTHistoricalTypes, EFTProcessStatus, EFTShortnameStatus, EFTShortnameType, - InvoiceStatus, PaymentMethod, StatementFrequency) + EFTCreditInvoiceStatus, + EFTFileLineType, + EFTHistoricalTypes, + EFTProcessStatus, + EFTShortnameStatus, + EFTShortnameType, + InvoiceStatus, + PaymentMethod, + StatementFrequency, +) from sbc_common_components.utils.enums import QueueMessageTypes from pay_queue.services.eft import EFTRecord from pay_queue.services.eft.eft_enums import EFTConstants from tests.integration.factory import ( - factory_create_eft_account, factory_invoice, factory_statement, factory_statement_invoices, - factory_statement_settings) + factory_create_eft_account, + factory_invoice, + factory_statement, + factory_statement_invoices, + factory_statement_settings, +) from tests.integration.utils import add_file_event_to_queue_and_process, create_and_upload_eft_file from tests.utilities.factory_utils import factory_eft_header, factory_eft_record, factory_eft_trailer def test_eft_tdi17_fail_header(session, app, client, mocker): """Test EFT Reconciliations properly fails for a bad EFT header.""" - mock_send_email = mocker.patch('pay_queue.services.eft.eft_reconciliation.send_error_email') + mock_send_email = mocker.patch("pay_queue.services.eft.eft_reconciliation.send_error_email") # Generate file with invalid header - file_name: str = 'test_eft_tdi17.txt' - header = factory_eft_header(record_type=EFTConstants.HEADER_RECORD_TYPE.value, file_creation_date='20230814', - file_creation_time='FAIL', deposit_start_date='20230810', deposit_end_date='20230810') + file_name: str = "test_eft_tdi17.txt" + header = factory_eft_header( + record_type=EFTConstants.HEADER_RECORD_TYPE.value, + file_creation_date="20230814", + file_creation_time="FAIL", + deposit_start_date="20230810", + deposit_end_date="20230810", + ) create_and_upload_eft_file(file_name, [header]) add_file_event_to_queue_and_process(client, file_name, QueueMessageTypes.EFT_FILE_UPLOADED.value) # Assert EFT File record was created - eft_file_model: EFTFileModel = db.session.query(EFTFileModel).filter( - EFTFileModel.file_ref == file_name).one_or_none() + eft_file_model: EFTFileModel = ( + db.session.query(EFTFileModel).filter(EFTFileModel.file_ref == file_name).one_or_none() + ) assert eft_file_model is not None assert eft_file_model.id is not None @@ -71,9 +89,12 @@ def test_eft_tdi17_fail_header(session, app, client, mocker): assert eft_file_model.number_of_details is None assert eft_file_model.total_deposit_cents is None - eft_header_transaction: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value).one_or_none() + eft_header_transaction: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value) + .one_or_none() + ) assert eft_header_transaction is not None assert eft_header_transaction.id is not None @@ -82,47 +103,63 @@ def test_eft_tdi17_fail_header(session, app, client, mocker): assert eft_header_transaction.status_code == EFTProcessStatus.FAILED.value assert eft_header_transaction.line_number == 0 assert len(eft_header_transaction.error_messages) == 1 - assert eft_header_transaction.error_messages[0] == 'Invalid header creation date time.' + assert eft_header_transaction.error_messages[0] == "Invalid header creation date time." - eft_trailer_transaction: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value).one_or_none() + eft_trailer_transaction: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value) + .one_or_none() + ) assert eft_trailer_transaction is None - eft_transactions: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value).all() + eft_transactions: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value) + .all() + ) assert not bool(eft_transactions) mock_send_email.assert_called_once() call_args = mock_send_email.call_args[0] - expected_error = 'Failed to process file test_eft_tdi17.txt with an invalid header or trailer.' - actual_error = call_args[0].error_messages[0]['error'] + expected_error = "Failed to process file test_eft_tdi17.txt with an invalid header or trailer." + actual_error = call_args[0].error_messages[0]["error"] assert expected_error == actual_error def test_eft_tdi17_fail_trailer(session, app, client, mocker): """Test EFT Reconciliations properly fails for a bad EFT trailer.""" - mock_send_email = mocker.patch( - 'pay_queue.services.eft.eft_reconciliation.send_error_email') + mock_send_email = mocker.patch("pay_queue.services.eft.eft_reconciliation.send_error_email") # Generate file with invalid trailer - file_name: str = 'test_eft_tdi17.txt' - header = factory_eft_header(record_type=EFTConstants.HEADER_RECORD_TYPE.value, file_creation_date='20230814', - file_creation_time='1601', deposit_start_date='20230810', deposit_end_date='20230810') - trailer = factory_eft_trailer(record_type=EFTConstants.TRAILER_RECORD_TYPE.value, number_of_details='A', - total_deposit_amount='3733750') + file_name: str = "test_eft_tdi17.txt" + header = factory_eft_header( + record_type=EFTConstants.HEADER_RECORD_TYPE.value, + file_creation_date="20230814", + file_creation_time="1601", + deposit_start_date="20230810", + deposit_end_date="20230810", + ) + trailer = factory_eft_trailer( + record_type=EFTConstants.TRAILER_RECORD_TYPE.value, + number_of_details="A", + total_deposit_amount="3733750", + ) create_and_upload_eft_file(file_name, [header, trailer]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value, + ) # Assert EFT File record was created - eft_file_model: EFTFileModel = db.session.query(EFTFileModel).filter( - EFTFileModel.file_ref == file_name).one_or_none() + eft_file_model: EFTFileModel = ( + db.session.query(EFTFileModel).filter(EFTFileModel.file_ref == file_name).one_or_none() + ) assert eft_file_model is not None assert eft_file_model.id is not None @@ -135,9 +172,12 @@ def test_eft_tdi17_fail_trailer(session, app, client, mocker): assert eft_file_model.number_of_details is None assert eft_file_model.total_deposit_cents == 3733750 - eft_trailer_transaction: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value).one_or_none() + eft_trailer_transaction: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value) + .one_or_none() + ) assert eft_trailer_transaction is not None assert eft_trailer_transaction.id is not None @@ -146,55 +186,83 @@ def test_eft_tdi17_fail_trailer(session, app, client, mocker): assert eft_trailer_transaction.status_code == EFTProcessStatus.FAILED.value assert eft_trailer_transaction.line_number == 1 assert len(eft_trailer_transaction.error_messages) == 1 - assert eft_trailer_transaction.error_messages[0] == 'Invalid trailer number of details value.' + assert eft_trailer_transaction.error_messages[0] == "Invalid trailer number of details value." - eft_header_transaction: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value).one_or_none() + eft_header_transaction: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value) + .one_or_none() + ) assert eft_header_transaction is None - eft_transactions: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value).all() + eft_transactions: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value) + .all() + ) assert not bool(eft_transactions) mock_send_email.assert_called_once() call_args = mock_send_email.call_args[0] - expected_error = 'Failed to process file test_eft_tdi17.txt with an invalid header or trailer.' - actual_error = call_args[0].error_messages[0]['error'] + expected_error = "Failed to process file test_eft_tdi17.txt with an invalid header or trailer." + actual_error = call_args[0].error_messages[0]["error"] assert expected_error == actual_error def test_eft_tdi17_fail_transactions(session, app, client, mocker): """Test EFT Reconciliations properly fails for a bad EFT trailer.""" - mock_send_email = mocker.patch( - 'pay_queue.services.eft.eft_reconciliation.send_error_email') + mock_send_email = mocker.patch("pay_queue.services.eft.eft_reconciliation.send_error_email") # Generate file with invalid trailer - file_name: str = 'test_eft_tdi17.txt' - header = factory_eft_header(record_type=EFTConstants.HEADER_RECORD_TYPE.value, file_creation_date='20230814', - file_creation_time='1601', deposit_start_date='20230810', deposit_end_date='20230810') - trailer = factory_eft_trailer(record_type=EFTConstants.TRAILER_RECORD_TYPE.value, number_of_details='1', - total_deposit_amount='3733750') - - transaction_1 = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, ministry_code='AT', - program_code='0146', deposit_date='20230810', deposit_time='0000', - location_id='85004', transaction_sequence='001', - transaction_description='ABC123', deposit_amount='13500', - currency='', exchange_adj_amount='0', deposit_amount_cad='FAIL', - destination_bank_number='0003', batch_number='002400986', jv_type='I', - jv_number='002425669', transaction_date='') + file_name: str = "test_eft_tdi17.txt" + header = factory_eft_header( + record_type=EFTConstants.HEADER_RECORD_TYPE.value, + file_creation_date="20230814", + file_creation_time="1601", + deposit_start_date="20230810", + deposit_end_date="20230810", + ) + trailer = factory_eft_trailer( + record_type=EFTConstants.TRAILER_RECORD_TYPE.value, + number_of_details="1", + total_deposit_amount="3733750", + ) + + transaction_1 = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="001", + transaction_description="ABC123", + deposit_amount="13500", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="FAIL", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) create_and_upload_eft_file(file_name, [header, transaction_1, trailer]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value, + ) # Assert EFT File record was created - eft_file_model: EFTFileModel = db.session.query(EFTFileModel).filter( - EFTFileModel.file_ref == file_name).one_or_none() + eft_file_model: EFTFileModel = ( + db.session.query(EFTFileModel).filter(EFTFileModel.file_ref == file_name).one_or_none() + ) assert eft_file_model is not None assert eft_file_model.id is not None @@ -207,44 +275,56 @@ def test_eft_tdi17_fail_transactions(session, app, client, mocker): assert eft_file_model.number_of_details == 1 assert eft_file_model.total_deposit_cents == 3733750 - eft_trailer_transaction: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value).one_or_none() + eft_trailer_transaction: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value) + .one_or_none() + ) assert eft_trailer_transaction is None - eft_header_transaction: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value).one_or_none() + eft_header_transaction: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value) + .one_or_none() + ) assert eft_header_transaction is None - eft_transactions: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value).all() + eft_transactions: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value) + .all() + ) assert eft_transactions is not None assert len(eft_transactions) == 1 - assert eft_transactions[0].error_messages[0] == 'Invalid transaction deposit amount CAD.' + assert eft_transactions[0].error_messages[0] == "Invalid transaction deposit amount CAD." mock_send_email.assert_called_once() call_args = mock_send_email.call_args[0] - assert 'Invalid transaction deposit amount CAD.' == call_args[0].error_messages[0]['error'] + assert "Invalid transaction deposit amount CAD." == call_args[0].error_messages[0]["error"] def test_eft_tdi17_basic_process(session, app, client): """Test EFT Reconciliations worker is able to create basic EFT processing records.""" # Generate happy path file - file_name: str = 'test_eft_tdi17.txt' + file_name: str = "test_eft_tdi17.txt" generate_basic_tdi17_file(file_name) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value, + ) # Assert EFT File record was created - eft_file_model: EFTFileModel = db.session.query(EFTFileModel).filter( - EFTFileModel.file_ref == file_name).one_or_none() + eft_file_model: EFTFileModel = ( + db.session.query(EFTFileModel).filter(EFTFileModel.file_ref == file_name).one_or_none() + ) assert eft_file_model is not None assert eft_file_model.id is not None @@ -260,22 +340,31 @@ def test_eft_tdi17_basic_process(session, app, client): assert eft_file_model.status_code == EFTProcessStatus.COMPLETED.value # Stored as part of the EFT File record - expecting none when no errors - eft_header_transaction = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value).one_or_none() + eft_header_transaction = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value) + .one_or_none() + ) assert eft_header_transaction is None # Stored as part of the EFT File record - expecting none when no errors - eft_trailer_transaction = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value).one_or_none() + eft_trailer_transaction = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value) + .one_or_none() + ) assert eft_trailer_transaction is None - eft_transactions: List[EFTTransactionModel] = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value).all() + eft_transactions: List[EFTTransactionModel] = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value) + .all() + ) assert eft_transactions is not None assert len(eft_transactions) == 2 @@ -291,9 +380,9 @@ def test_eft_tdi17_basic_process(session, app, client): short_name_link_2 = EFTShortnameLinksModel.find_by_short_name_id(eft_shortnames[1].id) assert not short_name_link_1 - assert eft_shortnames[0].short_name == 'ABC123' + assert eft_shortnames[0].short_name == "ABC123" assert not short_name_link_2 - assert eft_shortnames[1].short_name == 'DEF456' + assert eft_shortnames[1].short_name == "DEF456" eft_credits: List[EFTCreditModel] = db.session.query(EFTCreditModel).order_by(EFTCreditModel.id).all() assert eft_credits is not None @@ -318,8 +407,11 @@ def test_eft_tdi17_basic_process(session, app, client): assert_funds_received_history(eft_credits[1], history[1]) -def assert_funds_received_history(eft_credit: EFTCreditModel, eft_history: EFTHistoryModel, - assert_balance: bool = True): +def assert_funds_received_history( + eft_credit: EFTCreditModel, + eft_history: EFTHistoryModel, + assert_balance: bool = True, +): """Assert credit and history records match.""" assert eft_history.short_name_id == eft_credit.short_name_id assert eft_history.amount == eft_credit.amount @@ -339,16 +431,19 @@ def test_eft_tdi17_process(session, app, client): assert eft_shortname is not None assert invoice is not None # Generate happy path file - file_name: str = 'test_eft_tdi17.txt' + file_name: str = "test_eft_tdi17.txt" generate_tdi17_file(file_name) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value, + ) # Assert EFT File record was created - eft_file_model: EFTFileModel = db.session.query(EFTFileModel).filter( - EFTFileModel.file_ref == file_name).one_or_none() + eft_file_model: EFTFileModel = ( + db.session.query(EFTFileModel).filter(EFTFileModel.file_ref == file_name).one_or_none() + ) assert eft_file_model is not None assert eft_file_model.id is not None @@ -362,22 +457,31 @@ def test_eft_tdi17_process(session, app, client): assert eft_file_model.total_deposit_cents == 3733750 # Stored as part of the EFT File record - expecting none when no errors - eft_header_transaction = db.session.query(EFTTransactionModel)\ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value).one_or_none() + eft_header_transaction = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value) + .one_or_none() + ) assert eft_header_transaction is None # Stored as part of the EFT File record - expecting none when no errors - eft_trailer_transaction = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value).one_or_none() + eft_trailer_transaction = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value) + .one_or_none() + ) assert eft_trailer_transaction is None - eft_transactions = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value).all() + eft_transactions = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value) + .all() + ) assert eft_transactions is not None assert len(eft_transactions) == 3 @@ -393,9 +497,9 @@ def test_eft_tdi17_process(session, app, client): assert len(eft_shortnames) == 2 assert short_name_link_1 assert short_name_link_1.auth_account_id == payment_account.auth_account_id - assert eft_shortnames[0].short_name == 'TESTSHORTNAME' + assert eft_shortnames[0].short_name == "TESTSHORTNAME" assert not short_name_link_2 - assert eft_shortnames[1].short_name == 'ABC123' + assert eft_shortnames[1].short_name == "ABC123" eft_credits: List[EFTCreditModel] = db.session.query(EFTCreditModel).order_by(EFTCreditModel.id).all() history: List[EFTHistoryModel] = db.session.query(EFTHistoryModel).order_by(EFTHistoryModel.id).all() @@ -410,89 +514,145 @@ def test_eft_tdi17_rerun(session, app, client): payment_account, eft_shortname, invoice = create_test_data() # Generate file with invalid trailer - file_name: str = 'test_eft_tdi17.txt' - header = factory_eft_header(record_type=EFTConstants.HEADER_RECORD_TYPE.value, file_creation_date='20230814', - file_creation_time='1601', deposit_start_date='20230810', deposit_end_date='20230810') - trailer = factory_eft_trailer(record_type=EFTConstants.TRAILER_RECORD_TYPE.value, number_of_details='1', - total_deposit_amount='3733750') - - transaction_1 = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, ministry_code='AT', - program_code='0146', deposit_date='20230810', deposit_time='0000', - location_id='85004', transaction_sequence='001', - transaction_description='MISC PAYMENT TESTSHORTNAME', deposit_amount='13500', - currency='', exchange_adj_amount='0', deposit_amount_cad='FAIL', - destination_bank_number='0003', batch_number='002400986', jv_type='I', - jv_number='002425669', transaction_date='') + file_name: str = "test_eft_tdi17.txt" + header = factory_eft_header( + record_type=EFTConstants.HEADER_RECORD_TYPE.value, + file_creation_date="20230814", + file_creation_time="1601", + deposit_start_date="20230810", + deposit_end_date="20230810", + ) + trailer = factory_eft_trailer( + record_type=EFTConstants.TRAILER_RECORD_TYPE.value, + number_of_details="1", + total_deposit_amount="3733750", + ) + + transaction_1 = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="001", + transaction_description="MISC PAYMENT TESTSHORTNAME", + deposit_amount="13500", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="FAIL", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) create_and_upload_eft_file(file_name, [header, transaction_1, trailer]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value, + ) # Assert EFT File record was created - eft_file_model: EFTFileModel = db.session.query(EFTFileModel).filter( - EFTFileModel.file_ref == file_name).one_or_none() + eft_file_model: EFTFileModel = ( + db.session.query(EFTFileModel).filter(EFTFileModel.file_ref == file_name).one_or_none() + ) assert eft_file_model is not None assert eft_file_model.status_code == EFTProcessStatus.FAILED.value - eft_trailer_transaction: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value).one_or_none() + eft_trailer_transaction: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value) + .one_or_none() + ) assert eft_trailer_transaction is None - eft_header_transaction: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value).one_or_none() + eft_header_transaction: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value) + .one_or_none() + ) assert eft_header_transaction is None - eft_transactions: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value).all() + eft_transactions: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value) + .all() + ) assert eft_transactions is not None assert len(eft_transactions) == 1 - assert eft_transactions[0].error_messages[0] == 'Invalid transaction deposit amount CAD.' + assert eft_transactions[0].error_messages[0] == "Invalid transaction deposit amount CAD." # Correct transaction error and re-process - transaction_1 = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, ministry_code='AT', - program_code='0146', deposit_date='20230810', deposit_time='0000', - location_id='85004', transaction_sequence='001', - transaction_description='MISC PAYMENT TESTSHORTNAME', deposit_amount='13500', - currency='', exchange_adj_amount='0', deposit_amount_cad='13500', - destination_bank_number='0003', batch_number='002400986', jv_type='I', - jv_number='002425669', transaction_date='') + transaction_1 = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="001", + transaction_description="MISC PAYMENT TESTSHORTNAME", + deposit_amount="13500", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="13500", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) create_and_upload_eft_file(file_name, [header, transaction_1, trailer]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value, + ) # Check file is completed after correction - eft_file_model: EFTFileModel = db.session.query(EFTFileModel).filter( - EFTFileModel.file_ref == file_name).one_or_none() + eft_file_model: EFTFileModel = ( + db.session.query(EFTFileModel).filter(EFTFileModel.file_ref == file_name).one_or_none() + ) assert eft_file_model is not None assert eft_file_model.status_code == EFTProcessStatus.COMPLETED.value - eft_trailer_transaction: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value).one_or_none() + eft_trailer_transaction: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRAILER.value) + .one_or_none() + ) assert eft_trailer_transaction is None - eft_header_transaction: EFTTransactionModel = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value).one_or_none() + eft_header_transaction: EFTTransactionModel = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.HEADER.value) + .one_or_none() + ) assert eft_header_transaction is None - eft_transactions: List[EFTTransactionModel] = db.session.query(EFTTransactionModel) \ - .filter(EFTTransactionModel.file_id == eft_file_model.id) \ - .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value).all() + eft_transactions: List[EFTTransactionModel] = ( + db.session.query(EFTTransactionModel) + .filter(EFTTransactionModel.file_id == eft_file_model.id) + .filter(EFTTransactionModel.line_type == EFTFileLineType.TRANSACTION.value) + .all() + ) assert eft_transactions is not None assert len(eft_transactions) == 1 @@ -504,131 +664,241 @@ def test_eft_tdi17_rerun(session, app, client): def create_test_data(): """Create test seed data.""" payment_account = factory_create_eft_account() - eft_short_name = (EFTShortnameModel(short_name='TESTSHORTNAME', - type=EFTShortnameType.EFT.value).save()) + eft_short_name = EFTShortnameModel(short_name="TESTSHORTNAME", type=EFTShortnameType.EFT.value).save() EFTShortnameLinksModel( eft_short_name_id=eft_short_name.id, auth_account_id=payment_account.auth_account_id, status_code=EFTShortnameStatus.LINKED.value, - updated_by='IDIR/JSMITH', - updated_by_name='IDIR/JSMITH', - updated_on=datetime.now() + updated_by="IDIR/JSMITH", + updated_by_name="IDIR/JSMITH", + updated_on=datetime.now(), ).save() - invoice = factory_invoice(payment_account=payment_account, - status_code=InvoiceStatus.APPROVED.value, - total=150.50, - service_fees=1.50, - payment_method_code=PaymentMethod.EFT.value) + invoice = factory_invoice( + payment_account=payment_account, + status_code=InvoiceStatus.APPROVED.value, + total=150.50, + service_fees=1.50, + payment_method_code=PaymentMethod.EFT.value, + ) return payment_account, eft_short_name, invoice def generate_basic_tdi17_file(file_name: str): """Generate a complete TDI17 EFT file.""" - header = factory_eft_header(record_type=EFTConstants.HEADER_RECORD_TYPE.value, file_creation_date='20230814', - file_creation_time='1601', deposit_start_date='20230810', deposit_end_date='20230810') - - trailer = factory_eft_trailer(record_type=EFTConstants.TRAILER_RECORD_TYPE.value, number_of_details='5', - total_deposit_amount='3733750') - - transaction_1 = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, ministry_code='AT', - program_code='0146', deposit_date='20230810', deposit_time='0000', - location_id='85004', transaction_sequence='001', - transaction_description=f'{EFTRecord.EFT_DESCRIPTION_PATTERN} ABC123', - deposit_amount='13500', currency='', exchange_adj_amount='0', - deposit_amount_cad='13500', destination_bank_number='0003', - batch_number='002400986', jv_type='I', jv_number='002425669', - transaction_date='') - - transaction_2 = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, ministry_code='AT', - program_code='0146', deposit_date='20230810', deposit_time='', - location_id='85004', transaction_sequence='002', - transaction_description=f'{EFTRecord.WIRE_DESCRIPTION_PATTERN} DEF456', - deposit_amount='525000', currency='', exchange_adj_amount='0', - deposit_amount_cad='525000', destination_bank_number='0003', - batch_number='002400986', jv_type='I', jv_number='002425669', - transaction_date='') - - transaction_3 = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, ministry_code='AT', - program_code='0146', deposit_date='20230810', deposit_time='0000', - location_id='85004', transaction_sequence='003', - transaction_description='SHOULDIGNORE', - deposit_amount='525000', currency='', exchange_adj_amount='0', - deposit_amount_cad='525000', destination_bank_number='0003', - batch_number='002400986', jv_type='I', jv_number='002425669', - transaction_date='') - - transaction_4 = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, ministry_code='AT', - program_code='0146', deposit_date='20230810', deposit_time='0000', - location_id='85004', transaction_sequence='004', - transaction_description=f'{EFTRecord.PAD_DESCRIPTION_PATTERN} SHOULDIGNORE', - deposit_amount='525000', currency='', exchange_adj_amount='0', - deposit_amount_cad='525000', destination_bank_number='0003', - batch_number='002400986', jv_type='I', jv_number='002425669', - transaction_date='') - - create_and_upload_eft_file(file_name, [header, - transaction_1, transaction_2, transaction_3, transaction_4, - trailer]) + header = factory_eft_header( + record_type=EFTConstants.HEADER_RECORD_TYPE.value, + file_creation_date="20230814", + file_creation_time="1601", + deposit_start_date="20230810", + deposit_end_date="20230810", + ) + + trailer = factory_eft_trailer( + record_type=EFTConstants.TRAILER_RECORD_TYPE.value, + number_of_details="5", + total_deposit_amount="3733750", + ) + + transaction_1 = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="001", + transaction_description=f"{EFTRecord.EFT_DESCRIPTION_PATTERN} ABC123", + deposit_amount="13500", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="13500", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) + + transaction_2 = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="", + location_id="85004", + transaction_sequence="002", + transaction_description=f"{EFTRecord.WIRE_DESCRIPTION_PATTERN} DEF456", + deposit_amount="525000", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="525000", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) + + transaction_3 = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="003", + transaction_description="SHOULDIGNORE", + deposit_amount="525000", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="525000", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) + + transaction_4 = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="004", + transaction_description=f"{EFTRecord.PAD_DESCRIPTION_PATTERN} SHOULDIGNORE", + deposit_amount="525000", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="525000", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) + + create_and_upload_eft_file( + file_name, + [header, transaction_1, transaction_2, transaction_3, transaction_4, trailer], + ) def generate_tdi17_file(file_name: str): """Generate a complete TDI17 EFT file.""" - header = factory_eft_header(record_type=EFTConstants.HEADER_RECORD_TYPE.value, file_creation_date='20230814', - file_creation_time='1601', deposit_start_date='20230810', deposit_end_date='20230810') - - trailer = factory_eft_trailer(record_type=EFTConstants.TRAILER_RECORD_TYPE.value, number_of_details='5', - total_deposit_amount='3733750') - - transaction_1 = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, ministry_code='AT', - program_code='0146', deposit_date='20230810', deposit_time='0000', - location_id='85004', transaction_sequence='001', - transaction_description=f'{EFTRecord.EFT_DESCRIPTION_PATTERN} TESTSHORTNAME', - deposit_amount='10000', currency='', exchange_adj_amount='0', - deposit_amount_cad='10000', destination_bank_number='0003', - batch_number='002400986', jv_type='I', jv_number='002425669', - transaction_date='') - - transaction_2 = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, ministry_code='AT', - program_code='0146', deposit_date='20230810', deposit_time='', - location_id='85004', transaction_sequence='002', - transaction_description=f'{EFTRecord.EFT_DESCRIPTION_PATTERN} TESTSHORTNAME', - deposit_amount='5050', currency='', exchange_adj_amount='0', - deposit_amount_cad='5050', destination_bank_number='0003', - batch_number='002400986', jv_type='I', jv_number='002425669', - transaction_date='') - - transaction_3 = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, ministry_code='AT', - program_code='0146', deposit_date='20230810', deposit_time='0000', - location_id='85004', transaction_sequence='003', - transaction_description=f'{EFTRecord.WIRE_DESCRIPTION_PATTERN} ABC123', - deposit_amount='35150', currency='', exchange_adj_amount='0', - deposit_amount_cad='35150', destination_bank_number='0003', - batch_number='002400986', jv_type='I', jv_number='002425669', - transaction_date='') - - transaction_4 = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, ministry_code='AT', - program_code='0146', deposit_date='20230810', deposit_time='0000', - location_id='85004', transaction_sequence='004', - transaction_description=f'{EFTRecord.PAD_DESCRIPTION_PATTERN} SHOULDIGNORE', - deposit_amount='525000', currency='', exchange_adj_amount='0', - deposit_amount_cad='525000', destination_bank_number='0003', - batch_number='002400986', jv_type='I', jv_number='002425669', - transaction_date='') - - create_and_upload_eft_file(file_name, [header, - transaction_1, transaction_2, transaction_3, transaction_4, - trailer]) + header = factory_eft_header( + record_type=EFTConstants.HEADER_RECORD_TYPE.value, + file_creation_date="20230814", + file_creation_time="1601", + deposit_start_date="20230810", + deposit_end_date="20230810", + ) + + trailer = factory_eft_trailer( + record_type=EFTConstants.TRAILER_RECORD_TYPE.value, + number_of_details="5", + total_deposit_amount="3733750", + ) + + transaction_1 = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="001", + transaction_description=f"{EFTRecord.EFT_DESCRIPTION_PATTERN} TESTSHORTNAME", + deposit_amount="10000", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="10000", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) + + transaction_2 = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="", + location_id="85004", + transaction_sequence="002", + transaction_description=f"{EFTRecord.EFT_DESCRIPTION_PATTERN} TESTSHORTNAME", + deposit_amount="5050", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="5050", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) + + transaction_3 = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="003", + transaction_description=f"{EFTRecord.WIRE_DESCRIPTION_PATTERN} ABC123", + deposit_amount="35150", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="35150", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) + + transaction_4 = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="004", + transaction_description=f"{EFTRecord.PAD_DESCRIPTION_PATTERN} SHOULDIGNORE", + deposit_amount="525000", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="525000", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) + + create_and_upload_eft_file( + file_name, + [header, transaction_1, transaction_2, transaction_3, transaction_4, trailer], + ) def create_statement_from_invoices(account: PaymentAccountModel, invoices: List[InvoiceModel]): """Generate a statement from a list of invoices.""" - statement_settings = factory_statement_settings(pay_account_id=account.id, - frequency=StatementFrequency.MONTHLY.value) - statement = factory_statement(payment_account_id=account.id, - frequency=StatementFrequency.MONTHLY.value, - statement_settings_id=statement_settings.id) + statement_settings = factory_statement_settings( + pay_account_id=account.id, frequency=StatementFrequency.MONTHLY.value + ) + statement = factory_statement( + payment_account_id=account.id, + frequency=StatementFrequency.MONTHLY.value, + statement_settings_id=statement_settings.id, + ) for invoice in invoices: factory_statement_invoices(statement_id=statement.id, invoice_id=invoice.id) return statement @@ -638,33 +908,37 @@ def test_apply_pending_payments(session, app, client): """Test automatically applying a pending eft credit invoice link when there is a credit.""" payment_account, eft_short_name, invoice = create_test_data() create_statement_from_invoices(payment_account, [invoice]) - file_name: str = 'test_eft_tdi17.txt' + file_name: str = "test_eft_tdi17.txt" generate_tdi17_file(file_name) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value, + ) short_name_id = eft_short_name.id eft_credit_balance = EFTCreditModel.get_eft_credit_balance(short_name_id) assert eft_credit_balance == 0 short_name_links = EFTShortNamesService.get_shortname_links(short_name_id) - assert short_name_links['items'] - assert len(short_name_links['items']) == 1 + assert short_name_links["items"] + assert len(short_name_links["items"]) == 1 - short_name_link = short_name_links['items'][0] - assert short_name_link.get('has_pending_payment') is True - assert short_name_link.get('amount_owing') == 150.50 + short_name_link = short_name_links["items"][0] + assert short_name_link.get("has_pending_payment") is True + assert short_name_link.get("amount_owing") == 150.50 def test_skip_on_existing_pending_payments(session, app, client): """Test auto payment skipping payment when there exists a pending payment.""" payment_account, eft_short_name, invoice = create_test_data() - file_name: str = 'test_eft_tdi17.txt' + file_name: str = "test_eft_tdi17.txt" generate_tdi17_file(file_name) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value, + ) create_statement_from_invoices(payment_account, [invoice]) eft_credits = EFTCreditModel.get_eft_credits(eft_short_name.id) @@ -675,7 +949,8 @@ def test_skip_on_existing_pending_payments(session, app, client): status_code=EFTCreditInvoiceStatus.PENDING.value, invoice_id=invoice.id, amount=invoice.total, - link_group_id=1) + link_group_id=1, + ) short_name_id = eft_short_name.id eft_credit_balance = EFTCreditModel.get_eft_credit_balance(short_name_id) @@ -688,11 +963,13 @@ def test_skip_on_insufficient_balance(session, app, client): payment_account, eft_short_name, invoice = create_test_data() invoice.total = 99999 invoice.save() - file_name: str = 'test_eft_tdi17.txt' + file_name: str = "test_eft_tdi17.txt" generate_tdi17_file(file_name) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.EFT_FILE_UPLOADED.value, + ) create_statement_from_invoices(payment_account, [invoice]) @@ -701,9 +978,9 @@ def test_skip_on_insufficient_balance(session, app, client): assert eft_credit_balance == 150.50 short_name_links = EFTShortNamesService.get_shortname_links(short_name_id) - assert short_name_links['items'] - assert len(short_name_links['items']) == 1 + assert short_name_links["items"] + assert len(short_name_links["items"]) == 1 - short_name_link = short_name_links['items'][0] - assert short_name_link.get('has_pending_payment') is False - assert short_name_link.get('amount_owing') == 99999 + short_name_link = short_name_links["items"][0] + assert short_name_link.get("has_pending_payment") is False + assert short_name_link.get("amount_owing") == 99999 diff --git a/pay-queue/tests/integration/test_payment_reconciliations.py b/pay-queue/tests/integration/test_payment_reconciliations.py index 5fb7bdafa..4f475b779 100644 --- a/pay-queue/tests/integration/test_payment_reconciliations.py +++ b/pay-queue/tests/integration/test_payment_reconciliations.py @@ -33,8 +33,14 @@ from pay_queue.enums import RecordType, SourceTransaction, Status, TargetTransaction from .factory import ( - factory_create_online_banking_account, factory_create_pad_account, factory_invoice, factory_invoice_reference, - factory_payment, factory_payment_line_item, factory_receipt) + factory_create_online_banking_account, + factory_create_pad_account, + factory_invoice, + factory_invoice_reference, + factory_payment, + factory_payment_line_item, + factory_receipt, +) from .utils import add_file_event_to_queue_and_process, create_and_upload_settlement_file @@ -44,14 +50,18 @@ def test_online_banking_reconciliations(session, app, client): # 2. Create invoice and related records # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - pay_account = factory_create_online_banking_account(status=CfsAccountStatus.ACTIVE.value, - cfs_account=cfs_account_number) - invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.ONLINE_BANKING.value) - factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - invoice_number = '1234567890' + cfs_account_number = "1234" + pay_account = factory_create_online_banking_account( + status=CfsAccountStatus.ACTIVE.value, cfs_account=cfs_account_number + ) + invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + ) + factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0) + invoice_number = "1234567890" factory_invoice_reference(invoice_id=invoice.id, invoice_number=invoice_number) invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice = invoice.save() @@ -59,18 +69,30 @@ def test_online_banking_reconciliations(session, app, client): total = invoice.total # Create a settlement file and publish. - file_name: str = 'cas_settlement_file.csv' + file_name: str = "cas_settlement_file.csv" # Settlement row - date = datetime.now().strftime('%d-%b-%y') - receipt_number = '1234567890' - row = [RecordType.BOLP.value, SourceTransaction.ONLINE_BANKING.value, receipt_number, 100001, date, - total, cfs_account_number, - TargetTransaction.INV.value, invoice_number, - total, 0, Status.PAID.value] + date = datetime.now().strftime("%d-%b-%y") + receipt_number = "1234567890" + row = [ + RecordType.BOLP.value, + SourceTransaction.ONLINE_BANKING.value, + receipt_number, + 100001, + date, + total, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + total, + 0, + Status.PAID.value, + ] create_and_upload_settlement_file(file_name, [row]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) # The invoice should be in PAID status and Payment should be completed updated_invoice = InvoiceModel.find_by_id(invoice_id) @@ -90,14 +112,18 @@ def test_online_banking_reconciliations_over_payment(session, app, client): # 2. Create invoice and related records # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - pay_account = factory_create_online_banking_account(status=CfsAccountStatus.ACTIVE.value, - cfs_account=cfs_account_number) - invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.ONLINE_BANKING.value) - factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - invoice_number = '1234567890' + cfs_account_number = "1234" + pay_account = factory_create_online_banking_account( + status=CfsAccountStatus.ACTIVE.value, cfs_account=cfs_account_number + ) + invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + ) + factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0) + invoice_number = "1234567890" factory_invoice_reference(invoice_id=invoice.id, invoice_number=invoice_number) invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice = invoice.save() @@ -105,20 +131,45 @@ def test_online_banking_reconciliations_over_payment(session, app, client): total = invoice.total # Create a settlement file and publish. - file_name: str = 'cas_settlement_file.csv' + file_name: str = "cas_settlement_file.csv" # Settlement row - date = datetime.now().strftime('%d-%b-%y') - receipt_number = '1234567890' + date = datetime.now().strftime("%d-%b-%y") + receipt_number = "1234567890" over_payment_amount = 100 - inv_row = [RecordType.BOLP.value, SourceTransaction.ONLINE_BANKING.value, receipt_number, 100001, date, total, - cfs_account_number, TargetTransaction.INV.value, invoice_number, total, 0, Status.PAID.value] - credit_row = [RecordType.ONAC.value, SourceTransaction.ONLINE_BANKING.value, receipt_number, 100001, date, - over_payment_amount, cfs_account_number, TargetTransaction.INV.value, invoice_number, - over_payment_amount, 0, Status.ON_ACC.value] + inv_row = [ + RecordType.BOLP.value, + SourceTransaction.ONLINE_BANKING.value, + receipt_number, + 100001, + date, + total, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + total, + 0, + Status.PAID.value, + ] + credit_row = [ + RecordType.ONAC.value, + SourceTransaction.ONLINE_BANKING.value, + receipt_number, + 100001, + date, + over_payment_amount, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + over_payment_amount, + 0, + Status.ON_ACC.value, + ] create_and_upload_settlement_file(file_name, [inv_row, credit_row]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) # The invoice should be in PAID status and Payment should be completed updated_invoice = InvoiceModel.find_by_id(invoice_id) @@ -138,14 +189,18 @@ def test_online_banking_reconciliations_with_credit(session, app, client): # 2. Create invoice and related records # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - pay_account = factory_create_online_banking_account(status=CfsAccountStatus.ACTIVE.value, - cfs_account=cfs_account_number) - invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.ONLINE_BANKING.value) - factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - invoice_number = '1234567890' + cfs_account_number = "1234" + pay_account = factory_create_online_banking_account( + status=CfsAccountStatus.ACTIVE.value, cfs_account=cfs_account_number + ) + invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + ) + factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0) + invoice_number = "1234567890" factory_invoice_reference(invoice_id=invoice.id, invoice_number=invoice_number) invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice = invoice.save() @@ -153,20 +208,45 @@ def test_online_banking_reconciliations_with_credit(session, app, client): total = invoice.total # Create a settlement file and publish. - file_name: str = 'cas_settlement_file.csv' + file_name: str = "cas_settlement_file.csv" # Settlement row - date = datetime.now().strftime('%d-%b-%y') - receipt_number = '1234567890' + date = datetime.now().strftime("%d-%b-%y") + receipt_number = "1234567890" credit_amount = 10 - inv_row = [RecordType.BOLP.value, SourceTransaction.ONLINE_BANKING.value, receipt_number, 100001, date, - total - credit_amount, cfs_account_number, TargetTransaction.INV.value, invoice_number, total, 0, - Status.PAID.value] - credit_row = [RecordType.ONAC.value, SourceTransaction.EFT_WIRE.value, '555566677', 100001, date, credit_amount, - cfs_account_number, TargetTransaction.INV.value, invoice_number, total, 0, Status.PAID.value] + inv_row = [ + RecordType.BOLP.value, + SourceTransaction.ONLINE_BANKING.value, + receipt_number, + 100001, + date, + total - credit_amount, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + total, + 0, + Status.PAID.value, + ] + credit_row = [ + RecordType.ONAC.value, + SourceTransaction.EFT_WIRE.value, + "555566677", + 100001, + date, + credit_amount, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + total, + 0, + Status.PAID.value, + ] create_and_upload_settlement_file(file_name, [inv_row, credit_row]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) # The invoice should be in PAID status and Payment should be completed updated_invoice = InvoiceModel.find_by_id(invoice_id) @@ -186,14 +266,18 @@ def test_online_banking_reconciliations_overflows_credit(session, app, client): # 2. Create invoice and related records # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - pay_account = factory_create_online_banking_account(status=CfsAccountStatus.ACTIVE.value, - cfs_account=cfs_account_number) - invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.ONLINE_BANKING.value) - factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - invoice_number = '1234567890' + cfs_account_number = "1234" + pay_account = factory_create_online_banking_account( + status=CfsAccountStatus.ACTIVE.value, cfs_account=cfs_account_number + ) + invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + ) + factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0) + invoice_number = "1234567890" factory_invoice_reference(invoice_id=invoice.id, invoice_number=invoice_number) invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice = invoice.save() @@ -201,25 +285,61 @@ def test_online_banking_reconciliations_overflows_credit(session, app, client): total = invoice.total # Create a settlement file and publish. - file_name: str = 'cas_settlement_file.csv' + file_name: str = "cas_settlement_file.csv" # Settlement row - date = datetime.now().strftime('%d-%b-%y') - receipt_number = '1234567890' + date = datetime.now().strftime("%d-%b-%y") + receipt_number = "1234567890" credit_amount = 10 onac_amount = 100 - credit_row = [RecordType.ONAC.value, SourceTransaction.EFT_WIRE.value, '555566677', 100001, date, credit_amount, - cfs_account_number, TargetTransaction.INV.value, invoice_number, total, 0, Status.PAID.value] - inv_row = [RecordType.BOLP.value, SourceTransaction.ONLINE_BANKING.value, receipt_number, 100001, date, - total - credit_amount, cfs_account_number, TargetTransaction.INV.value, invoice_number, total, 0, - Status.PAID.value] - onac_row = [RecordType.ONAC.value, SourceTransaction.ONLINE_BANKING.value, receipt_number, 100001, date, - onac_amount, cfs_account_number, TargetTransaction.RECEIPT.value, receipt_number, total, 0, - Status.ON_ACC.value] + credit_row = [ + RecordType.ONAC.value, + SourceTransaction.EFT_WIRE.value, + "555566677", + 100001, + date, + credit_amount, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + total, + 0, + Status.PAID.value, + ] + inv_row = [ + RecordType.BOLP.value, + SourceTransaction.ONLINE_BANKING.value, + receipt_number, + 100001, + date, + total - credit_amount, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + total, + 0, + Status.PAID.value, + ] + onac_row = [ + RecordType.ONAC.value, + SourceTransaction.ONLINE_BANKING.value, + receipt_number, + 100001, + date, + onac_amount, + cfs_account_number, + TargetTransaction.RECEIPT.value, + receipt_number, + total, + 0, + Status.ON_ACC.value, + ] create_and_upload_settlement_file(file_name, [inv_row, credit_row, onac_row]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) # The invoice should be in PAID status and Payment should be completed updated_invoice = InvoiceModel.find_by_id(invoice_id) @@ -239,14 +359,18 @@ def test_online_banking_under_payment(session, app, client): # 2. Create invoice and related records # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - pay_account = factory_create_online_banking_account(status=CfsAccountStatus.ACTIVE.value, - cfs_account=cfs_account_number) - invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.ONLINE_BANKING.value) - factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - invoice_number = '1234567890' + cfs_account_number = "1234" + pay_account = factory_create_online_banking_account( + status=CfsAccountStatus.ACTIVE.value, cfs_account=cfs_account_number + ) + invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + ) + factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0) + invoice_number = "1234567890" factory_invoice_reference(invoice_id=invoice.id, invoice_number=invoice_number) invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice = invoice.save() @@ -254,20 +378,32 @@ def test_online_banking_under_payment(session, app, client): total = invoice.total # Create a settlement file and publish. - file_name: str = 'cas_settlement_file.csv' + file_name: str = "cas_settlement_file.csv" paid_amount = 10 # Settlement row - date = datetime.now().strftime('%d-%b-%y') - receipt_number = '1234567890' - - row = [RecordType.BOLP.value, SourceTransaction.ONLINE_BANKING.value, receipt_number, 100001, date, paid_amount, - cfs_account_number, - TargetTransaction.INV.value, invoice_number, - total, total - paid_amount, Status.PARTIAL.value] + date = datetime.now().strftime("%d-%b-%y") + receipt_number = "1234567890" + + row = [ + RecordType.BOLP.value, + SourceTransaction.ONLINE_BANKING.value, + receipt_number, + 100001, + date, + paid_amount, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + total, + total - paid_amount, + Status.PARTIAL.value, + ] create_and_upload_settlement_file(file_name, [row]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) # The invoice should be in PAID status and Payment should be completed updated_invoice = InvoiceModel.find_by_id(invoice_id) @@ -288,20 +424,25 @@ def test_pad_reconciliations(session, app, client): # 2. Create invoices and related records # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, - account_number=cfs_account_number) - invoice1 = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.PAD.value) - factory_payment_line_item(invoice_id=invoice1.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - - invoice2 = factory_invoice(payment_account=pay_account, total=200, service_fees=10.0, - payment_method_code=PaymentMethod.PAD.value) - factory_payment_line_item(invoice_id=invoice2.id, filing_fees=190.0, - service_fees=10.0, total=190.0) - - invoice_number = '1234567890' + cfs_account_number = "1234" + pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, account_number=cfs_account_number) + invoice1 = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.PAD.value, + ) + factory_payment_line_item(invoice_id=invoice1.id, filing_fees=90.0, service_fees=10.0, total=90.0) + + invoice2 = factory_invoice( + payment_account=pay_account, + total=200, + service_fees=10.0, + payment_method_code=PaymentMethod.PAD.value, + ) + factory_payment_line_item(invoice_id=invoice2.id, filing_fees=190.0, service_fees=10.0, total=190.0) + + invoice_number = "1234567890" factory_invoice_reference(invoice_id=invoice1.id, invoice_number=invoice_number) factory_invoice_reference(invoice_id=invoice2.id, invoice_number=invoice_number) @@ -315,18 +456,30 @@ def test_pad_reconciliations(session, app, client): total = invoice1.total + invoice2.total # Create a settlement file and publish. - file_name: str = 'cas_settlement_file.csv' + file_name: str = "cas_settlement_file.csv" # Settlement row - receipt_number = '1234567890' - date = datetime.now().strftime('%d-%b-%y') - row = [RecordType.PAD.value, SourceTransaction.PAD.value, receipt_number, 100001, date, total, - cfs_account_number, - 'INV', invoice_number, - total, 0, Status.PAID.value] + receipt_number = "1234567890" + date = datetime.now().strftime("%d-%b-%y") + row = [ + RecordType.PAD.value, + SourceTransaction.PAD.value, + receipt_number, + 100001, + date, + total, + cfs_account_number, + "INV", + invoice_number, + total, + 0, + Status.PAID.value, + ] create_and_upload_settlement_file(file_name, [row]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) # The invoice should be in PAID status and Payment should be completed updated_invoice1 = InvoiceModel.find_by_id(invoice1_id) @@ -355,20 +508,25 @@ def test_pad_reconciliations_with_credit_memo(session, app, client): # 3. Create CFS Invoice records # 4. Mimic some credits on the account # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, - account_number=cfs_account_number) - invoice1 = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.PAD.value) - factory_payment_line_item(invoice_id=invoice1.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - - invoice2 = factory_invoice(payment_account=pay_account, total=200, service_fees=10.0, - payment_method_code=PaymentMethod.PAD.value) - factory_payment_line_item(invoice_id=invoice2.id, filing_fees=190.0, - service_fees=10.0, total=190.0) - - invoice_number = '1234567890' + cfs_account_number = "1234" + pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, account_number=cfs_account_number) + invoice1 = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.PAD.value, + ) + factory_payment_line_item(invoice_id=invoice1.id, filing_fees=90.0, service_fees=10.0, total=90.0) + + invoice2 = factory_invoice( + payment_account=pay_account, + total=200, + service_fees=10.0, + payment_method_code=PaymentMethod.PAD.value, + ) + factory_payment_line_item(invoice_id=invoice2.id, filing_fees=190.0, service_fees=10.0, total=190.0) + + invoice_number = "1234567890" factory_invoice_reference(invoice_id=invoice1.id, invoice_number=invoice_number) factory_invoice_reference(invoice_id=invoice2.id, invoice_number=invoice_number) @@ -382,21 +540,47 @@ def test_pad_reconciliations_with_credit_memo(session, app, client): total = invoice1.total + invoice2.total # Create a settlement file and publish. - file_name: str = 'cas_settlement_file.csv' + file_name: str = "cas_settlement_file.csv" # Settlement row - receipt_number = '1234567890' - credit_memo_number = 'CM123' - date = datetime.now().strftime('%d-%b-%y') + receipt_number = "1234567890" + credit_memo_number = "CM123" + date = datetime.now().strftime("%d-%b-%y") credit_amount = 25 - credit_row = [RecordType.CMAP.value, SourceTransaction.CREDIT_MEMO.value, credit_memo_number, 100002, date, - credit_amount, cfs_account_number, 'INV', invoice_number, total, 0, Status.PAID.value] - pad_row = [RecordType.PAD.value, SourceTransaction.PAD.value, receipt_number, 100001, date, total - credit_amount, - cfs_account_number, 'INV', invoice_number, total, 0, Status.PAID.value] + credit_row = [ + RecordType.CMAP.value, + SourceTransaction.CREDIT_MEMO.value, + credit_memo_number, + 100002, + date, + credit_amount, + cfs_account_number, + "INV", + invoice_number, + total, + 0, + Status.PAID.value, + ] + pad_row = [ + RecordType.PAD.value, + SourceTransaction.PAD.value, + receipt_number, + 100001, + date, + total - credit_amount, + cfs_account_number, + "INV", + invoice_number, + total, + 0, + Status.PAID.value, + ] create_and_upload_settlement_file(file_name, [credit_row, pad_row]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) # The invoice should be in PAID status and Payment should be completed updated_invoice1 = InvoiceModel.find_by_id(invoice1_id) @@ -424,20 +608,25 @@ def test_pad_nsf_reconciliations(session, app, client): # 2. Create invoices and related records # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, - account_number=cfs_account_number) - invoice1 = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.PAD.value) - factory_payment_line_item(invoice_id=invoice1.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - - invoice2 = factory_invoice(payment_account=pay_account, total=200, service_fees=10.0, - payment_method_code=PaymentMethod.PAD.value) - factory_payment_line_item(invoice_id=invoice2.id, filing_fees=190.0, - service_fees=10.0, total=190.0) - - invoice_number = '1234567890' + cfs_account_number = "1234" + pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, account_number=cfs_account_number) + invoice1 = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.PAD.value, + ) + factory_payment_line_item(invoice_id=invoice1.id, filing_fees=90.0, service_fees=10.0, total=90.0) + + invoice2 = factory_invoice( + payment_account=pay_account, + total=200, + service_fees=10.0, + payment_method_code=PaymentMethod.PAD.value, + ) + factory_payment_line_item(invoice_id=invoice2.id, filing_fees=190.0, service_fees=10.0, total=190.0) + + invoice_number = "1234567890" factory_invoice_reference(invoice_id=invoice1.id, invoice_number=invoice_number) factory_invoice_reference(invoice_id=invoice2.id, invoice_number=invoice_number) @@ -453,17 +642,30 @@ def test_pad_nsf_reconciliations(session, app, client): total = invoice1.total + invoice2.total # Create a settlement file and publish. - file_name: str = 'cas_settlement_file.csv' + file_name: str = "cas_settlement_file.csv" # Settlement row - receipt_number = '1234567890' - date = datetime.now().strftime('%d-%b-%y') - row = [RecordType.PAD.value, SourceTransaction.PAD.value, receipt_number, 100001, date, 0, cfs_account_number, - 'INV', invoice_number, - total, total, Status.NOT_PAID.value] + receipt_number = "1234567890" + date = datetime.now().strftime("%d-%b-%y") + row = [ + RecordType.PAD.value, + SourceTransaction.PAD.value, + receipt_number, + 100001, + date, + 0, + cfs_account_number, + "INV", + invoice_number, + total, + total, + Status.NOT_PAID.value, + ] create_and_upload_settlement_file(file_name, [row]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) # The invoice should be in SETTLEMENT_SCHEDULED status and Payment should be FAILED updated_invoice1 = InvoiceModel.find_by_id(invoice1_id) @@ -478,8 +680,9 @@ def test_pad_nsf_reconciliations(session, app, client): assert payment.payment_method_code == PaymentMethod.PAD.value assert payment.invoice_number == invoice_number - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_payment_method(pay_account_id, - PaymentMethod.PAD.value) + cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_payment_method( + pay_account_id, PaymentMethod.PAD.value + ) assert cfs_account.status == CfsAccountStatus.FREEZE.value assert pay_account.has_nsf_invoices @@ -490,28 +693,39 @@ def test_pad_reversal_reconciliations(session, app, client): # 2. Create invoices and related records for a completed payment # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, - account_number=cfs_account_number) - invoice1 = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.PAD.value, - status_code=InvoiceStatus.PAID.value) - factory_payment_line_item(invoice_id=invoice1.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - - invoice2 = factory_invoice(payment_account=pay_account, total=200, service_fees=10.0, - payment_method_code=PaymentMethod.PAD.value, - status_code=InvoiceStatus.PAID.value) - factory_payment_line_item(invoice_id=invoice2.id, filing_fees=190.0, - service_fees=10.0, total=190.0) - - invoice_number = '1234567890' - receipt_number = '9999999999' - - factory_invoice_reference(invoice_id=invoice1.id, invoice_number=invoice_number, - status_code=InvoiceReferenceStatus.COMPLETED.value) - factory_invoice_reference(invoice_id=invoice2.id, invoice_number=invoice_number, - status_code=InvoiceReferenceStatus.COMPLETED.value) + cfs_account_number = "1234" + pay_account = factory_create_pad_account(status=CfsAccountStatus.ACTIVE.value, account_number=cfs_account_number) + invoice1 = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.PAD.value, + status_code=InvoiceStatus.PAID.value, + ) + factory_payment_line_item(invoice_id=invoice1.id, filing_fees=90.0, service_fees=10.0, total=90.0) + + invoice2 = factory_invoice( + payment_account=pay_account, + total=200, + service_fees=10.0, + payment_method_code=PaymentMethod.PAD.value, + status_code=InvoiceStatus.PAID.value, + ) + factory_payment_line_item(invoice_id=invoice2.id, filing_fees=190.0, service_fees=10.0, total=190.0) + + invoice_number = "1234567890" + receipt_number = "9999999999" + + factory_invoice_reference( + invoice_id=invoice1.id, + invoice_number=invoice_number, + status_code=InvoiceReferenceStatus.COMPLETED.value, + ) + factory_invoice_reference( + invoice_id=invoice2.id, + invoice_number=invoice_number, + status_code=InvoiceReferenceStatus.COMPLETED.value, + ) receipt_id1 = factory_receipt(invoice_id=invoice1.id, receipt_number=receipt_number).save().id receipt_id2 = factory_receipt(invoice_id=invoice2.id, receipt_number=receipt_number).save().id @@ -522,23 +736,41 @@ def test_pad_reversal_reconciliations(session, app, client): total = invoice1.total + invoice2.total - payment = factory_payment(pay_account=pay_account, paid_amount=total, invoice_amount=total, - invoice_number=invoice_number, - receipt_number=receipt_number, status=PaymentStatus.COMPLETED.value) + payment = factory_payment( + pay_account=pay_account, + paid_amount=total, + invoice_amount=total, + invoice_number=invoice_number, + receipt_number=receipt_number, + status=PaymentStatus.COMPLETED.value, + ) pay_id = payment.id # Now publish message saying payment has been reversed. # Create a settlement file and publish. - file_name: str = 'cas_settlement_file.csv' + file_name: str = "cas_settlement_file.csv" # Settlement row - date = datetime.now().strftime('%d-%b-%y') - row = [RecordType.PADR.value, SourceTransaction.PAD.value, receipt_number, 100001, date, 0, cfs_account_number, - 'INV', invoice_number, - total, total, Status.NOT_PAID.value] + date = datetime.now().strftime("%d-%b-%y") + row = [ + RecordType.PADR.value, + SourceTransaction.PAD.value, + receipt_number, + 100001, + date, + 0, + cfs_account_number, + "INV", + invoice_number, + total, + total, + Status.NOT_PAID.value, + ] create_and_upload_settlement_file(file_name, [row]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) # The invoice should be in SETTLEMENT_SCHEDULED status and Payment should be FAILED updated_invoice1 = InvoiceModel.find_by_id(invoice1_id) @@ -569,43 +801,63 @@ async def test_eft_wire_reconciliations(session, app, client): # 2. Create invoice and related records # 3. Create CFS Invoice records # 4. Create a CFS settlement file, and verify the records - cfs_account_number = '1234' - pay_account = factory_create_online_banking_account(status=CfsAccountStatus.ACTIVE.value, - cfs_account=cfs_account_number) - - invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.ONLINE_BANKING.value) - factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - invoice_number = '1234567890' + cfs_account_number = "1234" + pay_account = factory_create_online_banking_account( + status=CfsAccountStatus.ACTIVE.value, cfs_account=cfs_account_number + ) + + invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + ) + factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0) + invoice_number = "1234567890" factory_invoice_reference(invoice_id=invoice.id, invoice_number=invoice_number) invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice = invoice.save() invoice_id = invoice.id total = invoice.total - receipt = 'RCPT0012345' + receipt = "RCPT0012345" paid_amount = 100 - PaymentModel(payment_method_code=PaymentMethod.EFT.value, - payment_status_code=PaymentStatus.CREATED.value, - payment_system_code='PAYBC', - payment_account_id=pay_account.id, - payment_date=datetime.now(), - paid_amount=paid_amount, - receipt_number=receipt).save() + PaymentModel( + payment_method_code=PaymentMethod.EFT.value, + payment_status_code=PaymentStatus.CREATED.value, + payment_system_code="PAYBC", + payment_account_id=pay_account.id, + payment_date=datetime.now(), + paid_amount=paid_amount, + receipt_number=receipt, + ).save() # Create a settlement file and publish. - file_name: str = 'cas_settlement_file.csv' + file_name: str = "cas_settlement_file.csv" # Settlement row - date = datetime.now().strftime('%d-%b-%y') - - row = [RecordType.EFTP.value, SourceTransaction.EFT_WIRE.value, receipt, 100001, date, total, - cfs_account_number, TargetTransaction.INV.value, invoice_number, total, 0, Status.PAID.value] + date = datetime.now().strftime("%d-%b-%y") + + row = [ + RecordType.EFTP.value, + SourceTransaction.EFT_WIRE.value, + receipt, + 100001, + date, + total, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + total, + 0, + Status.PAID.value, + ] create_and_upload_settlement_file(file_name, [row]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) # The invoice should be in PAID status and Payment should be completed updated_invoice = InvoiceModel.find_by_id(invoice_id) @@ -625,73 +877,113 @@ def test_credits(session, app, client, monkeypatch): # 4. Publish credit in settlement file. # 5. Mock CFS Response for the receipt and credit memo. # 6. Confirm the credit matches the records. - cfs_account_number = '1234' - pay_account = factory_create_online_banking_account(status=CfsAccountStatus.ACTIVE.value, - cfs_account=cfs_account_number) + cfs_account_number = "1234" + pay_account = factory_create_online_banking_account( + status=CfsAccountStatus.ACTIVE.value, cfs_account=cfs_account_number + ) pay_account_id = pay_account.id - invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.ONLINE_BANKING.value) - factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - invoice_number = '1234567890' + invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + ) + factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0) + invoice_number = "1234567890" factory_invoice_reference(invoice_id=invoice.id, invoice_number=invoice_number) - receipt_number = 'RCPT0012345' + receipt_number = "RCPT0012345" onac_amount = 100 cm_identifier = 1000 cm_amount = 100 cm_used_amount = 50 - PaymentModel(payment_method_code=PaymentMethod.EFT.value, - payment_status_code=PaymentStatus.CREATED.value, - payment_system_code='PAYBC', - payment_account_id=pay_account.id, - payment_date=datetime.now(), - paid_amount=onac_amount, - receipt_number=receipt_number).save() - - credit = CreditModel(cfs_identifier=cm_identifier, - is_credit_memo=True, - amount=cm_amount, - remaining_amount=cm_amount, - account_id=pay_account_id).save() + PaymentModel( + payment_method_code=PaymentMethod.EFT.value, + payment_status_code=PaymentStatus.CREATED.value, + payment_system_code="PAYBC", + payment_account_id=pay_account.id, + payment_date=datetime.now(), + paid_amount=onac_amount, + receipt_number=receipt_number, + ).save() + + credit = CreditModel( + cfs_identifier=cm_identifier, + is_credit_memo=True, + amount=cm_amount, + remaining_amount=cm_amount, + account_id=pay_account_id, + ).save() credit_id = credit.id - def mock_receipt(cfs_account: CfsAccountModel, - receipt_number: str): # pylint: disable=unused-argument; mocks of library methods - return { - 'receipt_amount': onac_amount - } + def mock_receipt( + cfs_account: CfsAccountModel, receipt_number: str + ): # pylint: disable=unused-argument; mocks of library methods + return {"receipt_amount": onac_amount} - def mock_cms(cfs_account: CfsAccountModel, - cms_number: str): # pylint: disable=unused-argument; mocks of library methods - return { - 'amount_due': cm_amount - cm_used_amount - } + def mock_cms( + cfs_account: CfsAccountModel, cms_number: str + ): # pylint: disable=unused-argument; mocks of library methods + return {"amount_due": cm_amount - cm_used_amount} - monkeypatch.setattr('pay_api.services.cfs_service.CFSService.get_receipt', mock_receipt) - monkeypatch.setattr('pay_api.services.cfs_service.CFSService.get_cms', mock_cms) + monkeypatch.setattr("pay_api.services.cfs_service.CFSService.get_receipt", mock_receipt) + monkeypatch.setattr("pay_api.services.cfs_service.CFSService.get_cms", mock_cms) - file_name = 'cas_settlement_file.csv' + file_name = "cas_settlement_file.csv" date = datetime.now(tz=timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0).replace(tzinfo=None) - date_str = date.strftime('%d-%b-%y') - - row = [RecordType.ONAC.value, SourceTransaction.EFT_WIRE.value, receipt_number, 100001, date_str, onac_amount, - cfs_account_number, TargetTransaction.RECEIPT.value, receipt_number, onac_amount, 0, Status.ON_ACC.value] + date_str = date.strftime("%d-%b-%y") + + row = [ + RecordType.ONAC.value, + SourceTransaction.EFT_WIRE.value, + receipt_number, + 100001, + date_str, + onac_amount, + cfs_account_number, + TargetTransaction.RECEIPT.value, + receipt_number, + onac_amount, + 0, + Status.ON_ACC.value, + ] credit_invoices_row = [ - RecordType.CMAP.value, SourceTransaction.CREDIT_MEMO.value, cm_identifier, 100003, date_str, 2.5, - cfs_account_number, TargetTransaction.INV.value, invoice_number, 100, 0, Status.PAID.value + RecordType.CMAP.value, + SourceTransaction.CREDIT_MEMO.value, + cm_identifier, + 100003, + date_str, + 2.5, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + 100, + 0, + Status.PAID.value, ] credit_invoices_row2 = [ - RecordType.CMAP.value, SourceTransaction.CREDIT_MEMO.value, cm_identifier, 100004, date_str, 5.5, - cfs_account_number, TargetTransaction.INV.value, invoice_number, 100, 0, Status.PAID.value + RecordType.CMAP.value, + SourceTransaction.CREDIT_MEMO.value, + cm_identifier, + 100004, + date_str, + 5.5, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + 100, + 0, + Status.PAID.value, ] create_and_upload_settlement_file(file_name, [row, credit_invoices_row, credit_invoices_row2]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) # Look up credit file and make sure the credits are recorded. pay_account = PaymentAccountModel.find_by_id(pay_account_id) @@ -720,38 +1012,56 @@ def mock_cms(cfs_account: CfsAccountModel, def test_unconsolidated_invoices_errors(session, app, client, mocker): """Test error scenarios for unconsolidated invoices in the reconciliation worker.""" - cfs_account_number = '1234' - pay_account = factory_create_online_banking_account(status=CfsAccountStatus.ACTIVE.value, - cfs_account=cfs_account_number) - - invoice = factory_invoice(payment_account=pay_account, total=100, service_fees=10.0, - payment_method_code=PaymentMethod.ONLINE_BANKING.value) - factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, - service_fees=10.0, total=90.0) - invoice_number = '1234567890' + cfs_account_number = "1234" + pay_account = factory_create_online_banking_account( + status=CfsAccountStatus.ACTIVE.value, cfs_account=cfs_account_number + ) + + invoice = factory_invoice( + payment_account=pay_account, + total=100, + service_fees=10.0, + payment_method_code=PaymentMethod.ONLINE_BANKING.value, + ) + factory_payment_line_item(invoice_id=invoice.id, filing_fees=90.0, service_fees=10.0, total=90.0) + invoice_number = "1234567890" factory_invoice_reference(invoice_id=invoice.id, invoice_number=invoice_number) invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice = invoice.save() invoice_id = invoice.id total = invoice.total - error_messages = [{'error': 'Test error message', 'row': 'row 2'}] - mocker.patch('pay_queue.services.payment_reconciliations._process_file_content', - return_value=(True, error_messages)) - mock_send_error_email = mocker.patch('pay_queue.services.payment_reconciliations.send_error_email') + error_messages = [{"error": "Test error message", "row": "row 2"}] + mocker.patch( + "pay_queue.services.payment_reconciliations._process_file_content", + return_value=(True, error_messages), + ) + mock_send_error_email = mocker.patch("pay_queue.services.payment_reconciliations.send_error_email") - file_name: str = 'BCR_PAYMENT_APPL_20240619.csv' + file_name: str = "BCR_PAYMENT_APPL_20240619.csv" date = datetime.now(tz=timezone.utc).isoformat() - receipt_number = '1234567890' - row = [RecordType.BOLP.value, SourceTransaction.ONLINE_BANKING.value, receipt_number, 100001, date, - total, cfs_account_number, - TargetTransaction.INV.value, invoice_number, - total, 0, Status.PAID.value] + receipt_number = "1234567890" + row = [ + RecordType.BOLP.value, + SourceTransaction.ONLINE_BANKING.value, + receipt_number, + 100001, + date, + total, + cfs_account_number, + TargetTransaction.INV.value, + invoice_number, + total, + 0, + Status.PAID.value, + ] create_and_upload_settlement_file(file_name, [row]) - add_file_event_to_queue_and_process(client, - file_name=file_name, - message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value) + add_file_event_to_queue_and_process( + client, + file_name=file_name, + message_type=QueueMessageTypes.CAS_MESSAGE_TYPE.value, + ) updated_invoice = InvoiceModel.find_by_id(invoice_id) assert updated_invoice.invoice_status_code == InvoiceStatus.SETTLEMENT_SCHEDULED.value @@ -759,8 +1069,8 @@ def test_unconsolidated_invoices_errors(session, app, client, mocker): mock_send_error_email.assert_called_once() call_args = mock_send_error_email.call_args email_params = call_args[0][0] - assert email_params.subject == 'Payment Reconciliation Failure' + assert email_params.subject == "Payment Reconciliation Failure" assert email_params.file_name == file_name - assert email_params.minio_location == 'payment-sftp' + assert email_params.minio_location == "payment-sftp" assert email_params.error_messages == error_messages assert email_params.table_name == CasSettlementModel.__tablename__ diff --git a/pay-queue/tests/integration/test_worker_queue.py b/pay-queue/tests/integration/test_worker_queue.py index 79d1002e2..b511ebc2f 100644 --- a/pay-queue/tests/integration/test_worker_queue.py +++ b/pay-queue/tests/integration/test_worker_queue.py @@ -24,23 +24,24 @@ def test_update_payment(session, app, client): """Assert that the update internal payment records works.""" # vars - old_identifier = 'T000000000' - new_identifier = 'BC12345678' + old_identifier = "T000000000" + new_identifier = "BC12345678" # Create an Internal Payment payment_account = factory_payment_account(payment_system_code=PaymentSystem.BCOL.value).save() - invoice: Invoice = factory_invoice(payment_account=payment_account, - business_identifier=old_identifier, - payment_method_code=PaymentMethod.INTERNAL.value).save() + invoice: Invoice = factory_invoice( + payment_account=payment_account, + business_identifier=old_identifier, + payment_method_code=PaymentMethod.INTERNAL.value, + ).save() inv_ref = factory_invoice_reference(invoice_id=invoice.id) factory_payment(invoice_number=inv_ref.invoice_number) invoice_id = invoice.id - helper_add_identifier_event_to_queue(client, old_identifier=old_identifier, - new_identifier=new_identifier) + helper_add_identifier_event_to_queue(client, old_identifier=old_identifier, new_identifier=new_identifier) # Get the internal account and invoice and assert that the identifier is new identifier invoice = Invoice.find_by_id(invoice_id) diff --git a/pay-queue/tests/integration/utils.py b/pay-queue/tests/integration/utils.py index 07b9e7c7e..fa4e5cd97 100644 --- a/pay-queue/tests/integration/utils.py +++ b/pay-queue/tests/integration/utils.py @@ -34,69 +34,90 @@ def build_request_for_queue_push(message_type, payload): """Build request for queue message.""" - queue_message_bytes = to_queue_message(SimpleCloudEvent( - id=str(uuid.uuid4()), - source='pay-queue', - subject=None, - time=datetime.now(tz=timezone.utc).isoformat(), - type=message_type, - data=payload - )) + queue_message_bytes = to_queue_message( + SimpleCloudEvent( + id=str(uuid.uuid4()), + source="pay-queue", + subject=None, + time=datetime.now(tz=timezone.utc).isoformat(), + type=message_type, + data=payload, + ) + ) return { - 'message': { - 'data': base64.b64encode(queue_message_bytes).decode('utf-8') - }, - 'subscription': 'foobar' + "message": {"data": base64.b64encode(queue_message_bytes).decode("utf-8")}, + "subscription": "foobar", } def post_to_queue(client, request_payload): """Post request to worker using an http request on our wrapped flask instance.""" - response = client.post('/', data=json.dumps(request_payload), - headers={'Content-Type': 'application/json'}) + response = client.post( + "/", + data=json.dumps(request_payload), + headers={"Content-Type": "application/json"}, + ) assert response.status_code == 200 def create_and_upload_settlement_file(file_name: str, rows: List[List]): """Create settlement file, upload to minio and send event.""" - headers = ['Record type', 'Source Transaction Type', 'Source Transaction Number', - 'Application Id', 'Application Date', 'Application amount', 'Customer Account', - 'Target transaction type', - 'Target transaction Number', 'Target Transaction Original amount', - 'Target Transaction Outstanding Amount', - 'Target transaction status', 'Reversal Reason code', 'Reversal reason description'] - with open(file_name, mode='w', encoding='utf-8') as cas_file: + headers = [ + "Record type", + "Source Transaction Type", + "Source Transaction Number", + "Application Id", + "Application Date", + "Application amount", + "Customer Account", + "Target transaction type", + "Target transaction Number", + "Target Transaction Original amount", + "Target Transaction Outstanding Amount", + "Target transaction status", + "Reversal Reason code", + "Reversal reason description", + ] + with open(file_name, mode="w", encoding="utf-8") as cas_file: cas_writer = csv.writer(cas_file, quoting=csv.QUOTE_ALL) cas_writer.writerow(headers) for row in rows: cas_writer.writerow(row) - with open(file_name, 'rb') as f: + with open(file_name, "rb") as f: upload_to_minio(f.read(), file_name) def create_and_upload_eft_file(file_name: str, rows: List[List]): """Create eft file, upload to minio and send event.""" - with open(file_name, mode='w', encoding='utf-8') as eft_file: + with open(file_name, mode="w", encoding="utf-8") as eft_file: for row in rows: print(row, file=eft_file) - with open(file_name, 'rb') as f: + with open(file_name, "rb") as f: upload_to_minio(f.read(), file_name) def upload_to_minio(value_as_bytes, file_name: str): """Return a pre-signed URL for new doc upload.""" - minio_endpoint = current_app.config['MINIO_ENDPOINT'] - minio_key = current_app.config['MINIO_ACCESS_KEY'] - minio_secret = current_app.config['MINIO_ACCESS_SECRET'] - minio_client = Minio(minio_endpoint, access_key=minio_key, secret_key=minio_secret, - secure=current_app.config['MINIO_SECURE']) + minio_endpoint = current_app.config["MINIO_ENDPOINT"] + minio_key = current_app.config["MINIO_ACCESS_KEY"] + minio_secret = current_app.config["MINIO_ACCESS_SECRET"] + minio_client = Minio( + minio_endpoint, + access_key=minio_key, + secret_key=minio_secret, + secure=current_app.config["MINIO_SECURE"], + ) value_as_stream = io.BytesIO(value_as_bytes) - minio_client.put_object(current_app.config['MINIO_BUCKET_NAME'], file_name, value_as_stream, - os.stat(file_name).st_size) + minio_client.put_object( + current_app.config["MINIO_BUCKET_NAME"], + file_name, + value_as_stream, + os.stat(file_name).st_size, + ) def forward_incoming_message_to_test_instance(session, app, client): @@ -107,16 +128,16 @@ def forward_incoming_message_to_test_instance(session, app, client): with socket() as server_socket: server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) server_socket.settimeout(2) - server_socket.bind(('0.0.0.0', current_app.config.get('TEST_PUSH_ENDPOINT_PORT'))) + server_socket.bind(("0.0.0.0", current_app.config.get("TEST_PUSH_ENDPOINT_PORT"))) server_socket.listen(10) tries = 100 while tries > 0: client_socket, _ = server_socket.accept() if socket_data := client_socket.recv(4096): - body = socket_data.decode('utf8').split('\r\n')[-1] + body = socket_data.decode("utf8").split("\r\n")[-1] payload = json.loads(body) post_to_queue(client, payload) - client_socket.send('HTTP/1.1 200 OK\n\n'.encode('utf8')) + client_socket.send("HTTP/1.1 200 OK\n\n".encode("utf8")) break sleep(0.01) tries -= 1 @@ -126,8 +147,8 @@ def forward_incoming_message_to_test_instance(session, app, client): def add_file_event_to_queue_and_process(client, file_name: str, message_type: str, use_pubsub_emulator=False): """Add event to the Queue.""" queue_payload = { - 'fileName': file_name, - 'location': current_app.config['MINIO_BUCKET_NAME'] + "fileName": file_name, + "location": current_app.config["MINIO_BUCKET_NAME"], } if use_pubsub_emulator: gcp_queue_publisher.publish_to_queue( @@ -135,7 +156,7 @@ def add_file_event_to_queue_and_process(client, file_name: str, message_type: st source=QueueSources.FTP_POLLER.value, message_type=message_type, payload=queue_payload, - topic=f'projects/{current_app.config["TEST_GCP_PROJECT_NAME"]}/topics/ftp-poller-dev' + topic=f'projects/{current_app.config["TEST_GCP_PROJECT_NAME"]}/topics/ftp-poller-dev', ) ) forward_incoming_message_to_test_instance(client) @@ -144,17 +165,18 @@ def add_file_event_to_queue_and_process(client, file_name: str, message_type: st post_to_queue(client, payload) -def helper_add_identifier_event_to_queue(client, old_identifier: str = 'T1234567890', - new_identifier: str = 'BC1234567890'): +def helper_add_identifier_event_to_queue( + client, old_identifier: str = "T1234567890", new_identifier: str = "BC1234567890" +): """Add event to the Queue.""" message_type = QueueMessageTypes.INCORPORATION.value queue_payload = { - 'filing': { - 'header': {'filingId': '12345678'}, - 'business': {'identifier': 'BC1234567'} + "filing": { + "header": {"filingId": "12345678"}, + "business": {"identifier": "BC1234567"}, }, - 'identifier': new_identifier, - 'tempidentifier': old_identifier, + "identifier": new_identifier, + "tempidentifier": old_identifier, } request_payload = build_request_for_queue_push(message_type, queue_payload) post_to_queue(client, request_payload) diff --git a/pay-queue/tests/unit/test_eft_file_parser.py b/pay-queue/tests/unit/test_eft_file_parser.py index 33f0933c9..b033aa7c0 100644 --- a/pay-queue/tests/unit/test_eft_file_parser.py +++ b/pay-queue/tests/unit/test_eft_file_parser.py @@ -28,11 +28,13 @@ def test_eft_parse_header(): """Test EFT header parser.""" - content = factory_eft_header(record_type=EFTConstants.HEADER_RECORD_TYPE.value, - file_creation_date='20230814', - file_creation_time='1601', - deposit_start_date='20230810', - deposit_end_date='20230810') + content = factory_eft_header( + record_type=EFTConstants.HEADER_RECORD_TYPE.value, + file_creation_date="20230814", + file_creation_time="1601", + deposit_start_date="20230810", + deposit_end_date="20230810", + ) header: EFTHeader = EFTHeader(content, 0) @@ -41,7 +43,7 @@ def test_eft_parse_header(): deposit_date_end = datetime(2023, 8, 10) assert header.index == 0 - assert header.record_type == '1' + assert header.record_type == "1" assert header.creation_datetime == creation_datetime assert header.starting_deposit_date == deposit_date_start assert header.ending_deposit_date == deposit_date_end @@ -49,7 +51,7 @@ def test_eft_parse_header(): def test_eft_parse_header_invalid_length(): """Test EFT header parser invalid length.""" - content = ' ' + content = " " header: EFTHeader = EFTHeader(content, 0) assert header.errors @@ -61,11 +63,13 @@ def test_eft_parse_header_invalid_length(): def test_eft_parse_header_invalid_record_type(): """Test EFT header parser invalid record type.""" - content = factory_eft_header(record_type='X', - file_creation_date='20230814', - file_creation_time='1601', - deposit_start_date='20230810', - deposit_end_date='20230810') + content = factory_eft_header( + record_type="X", + file_creation_date="20230814", + file_creation_time="1601", + deposit_start_date="20230810", + deposit_end_date="20230810", + ) header: EFTHeader = EFTHeader(content, 0) @@ -78,11 +82,13 @@ def test_eft_parse_header_invalid_record_type(): def test_eft_parse_header_invalid_dates(): """Test EFT header parser invalid dates.""" - content = factory_eft_header(record_type=EFTConstants.HEADER_RECORD_TYPE.value, - file_creation_date='2023081_', - file_creation_time='160 ', - deposit_start_date='20230850', - deposit_end_date='202308AB') + content = factory_eft_header( + record_type=EFTConstants.HEADER_RECORD_TYPE.value, + file_creation_date="2023081_", + file_creation_time="160 ", + deposit_start_date="20230850", + deposit_end_date="202308AB", + ) header: EFTHeader = EFTHeader(content, 0) @@ -101,20 +107,22 @@ def test_eft_parse_header_invalid_dates(): def test_eft_parse_trailer(): """Test EFT trailer parser.""" - content = factory_eft_trailer(record_type=EFTConstants.TRAILER_RECORD_TYPE.value, - number_of_details='5', - total_deposit_amount='3733750') + content = factory_eft_trailer( + record_type=EFTConstants.TRAILER_RECORD_TYPE.value, + number_of_details="5", + total_deposit_amount="3733750", + ) trailer: EFTTrailer = EFTTrailer(content, 1) assert trailer.index == 1 - assert trailer.record_type == '7' + assert trailer.record_type == "7" assert trailer.number_of_details == 5 assert trailer.total_deposit_amount == 3733750 def test_eft_parse_trailer_invalid_length(): """Test EFT trailer parser invalid number types.""" - content = ' ' + content = " " trailer: EFTTrailer = EFTTrailer(content, 1) assert trailer.errors @@ -126,9 +134,7 @@ def test_eft_parse_trailer_invalid_length(): def test_eft_parse_trailer_invalid_record_type(): """Test EFT trailer parser invalid record_type.""" - content = factory_eft_trailer(record_type='X', - number_of_details='5', - total_deposit_amount='3733750') + content = factory_eft_trailer(record_type="X", number_of_details="5", total_deposit_amount="3733750") trailer: EFTTrailer = EFTTrailer(content, 1) assert trailer.errors @@ -140,9 +146,11 @@ def test_eft_parse_trailer_invalid_record_type(): def test_eft_parse_trailer_invalid_numbers(): """Test EFT trailer parser invalid number values.""" - content = factory_eft_trailer(record_type=EFTConstants.TRAILER_RECORD_TYPE.value, - number_of_details='B', - total_deposit_amount='3733A50') + content = factory_eft_trailer( + record_type=EFTConstants.TRAILER_RECORD_TYPE.value, + number_of_details="B", + total_deposit_amount="3733A50", + ) trailer: EFTTrailer = EFTTrailer(content, 1) assert trailer.errors @@ -157,209 +165,214 @@ def test_eft_parse_trailer_invalid_numbers(): def test_eft_parse_record(): """Test EFT record parser.""" - content = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, - ministry_code='AT', - program_code='0146', - deposit_date='20230810', - deposit_time='0000', - location_id='85004', - transaction_sequence='001', - transaction_description='DEPOSIT 26', - deposit_amount='13500', - currency='', - exchange_adj_amount='0', - deposit_amount_cad='13500', - destination_bank_number='0003', - batch_number='002400986', - jv_type='I', - jv_number='002425669', - transaction_date='' - ) + content = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="001", + transaction_description="DEPOSIT 26", + deposit_amount="13500", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="13500", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) record: EFTRecord = EFTRecord(content, 1) deposit_datetime = datetime(2023, 8, 10, 0, 0) transaction_date = None assert record.index == 1 - assert record.record_type == '2' - assert record.ministry_code == 'AT' - assert record.program_code == '0146' + assert record.record_type == "2" + assert record.ministry_code == "AT" + assert record.program_code == "0146" assert record.deposit_datetime == deposit_datetime - assert record.location_id == '85004' - assert record.transaction_sequence == '001' - assert record.transaction_description == 'DEPOSIT 26' + assert record.location_id == "85004" + assert record.transaction_sequence == "001" + assert record.transaction_description == "DEPOSIT 26" assert record.deposit_amount == 13500 assert record.currency == EFTConstants.CURRENCY_CAD.value assert record.exchange_adj_amount == 0 assert record.deposit_amount_cad == 13500 - assert record.dest_bank_number == '0003' - assert record.batch_number == '002400986' - assert record.jv_type == 'I' - assert record.jv_number == '002425669' + assert record.dest_bank_number == "0003" + assert record.batch_number == "002400986" + assert record.jv_type == "I" + assert record.jv_number == "002425669" assert record.transaction_date == transaction_date assert record.short_name_type is None - content = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, - ministry_code='AT', - program_code='0146', - deposit_date='20230810', - deposit_time='0000', - location_id='85004', - transaction_sequence='002', - transaction_description='FUNDS TRANSFER CR TT INTERBLOCK C', - deposit_amount='525000', - currency='', - exchange_adj_amount='0', - deposit_amount_cad='525000', - destination_bank_number='0003', - batch_number='002400986', - jv_type='I', - jv_number='002425669', - transaction_date='' - ) + content = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="002", + transaction_description="FUNDS TRANSFER CR TT INTERBLOCK C", + deposit_amount="525000", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="525000", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) record: EFTRecord = EFTRecord(content, 2) assert record.index == 2 - assert record.record_type == '2' - assert record.ministry_code == 'AT' - assert record.program_code == '0146' + assert record.record_type == "2" + assert record.ministry_code == "AT" + assert record.program_code == "0146" assert record.deposit_datetime == deposit_datetime - assert record.location_id == '85004' - assert record.transaction_sequence == '002' - assert record.transaction_description == 'INTERBLOCK C' + assert record.location_id == "85004" + assert record.transaction_sequence == "002" + assert record.transaction_description == "INTERBLOCK C" assert record.deposit_amount == 525000 assert record.currency == EFTConstants.CURRENCY_CAD.value assert record.exchange_adj_amount == 0 assert record.deposit_amount_cad == 525000 - assert record.dest_bank_number == '0003' - assert record.batch_number == '002400986' - assert record.jv_type == 'I' - assert record.jv_number == '002425669' + assert record.dest_bank_number == "0003" + assert record.batch_number == "002400986" + assert record.jv_type == "I" + assert record.jv_number == "002425669" assert record.transaction_date == transaction_date assert record.short_name_type == EFTShortnameType.WIRE.value - content = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, - ministry_code='AT', - program_code='0146', - deposit_date='20230810', - deposit_time='0000', - location_id='85004', - transaction_sequence='003', - transaction_description='MISC PAYMENT ABC1234567', - deposit_amount='951250', - currency='', - exchange_adj_amount='0', - deposit_amount_cad='951250', - destination_bank_number='0003', - batch_number='002400986', - jv_type='I', - jv_number='002425669', - transaction_date='' - ) + content = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="003", + transaction_description="MISC PAYMENT ABC1234567", + deposit_amount="951250", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="951250", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) record: EFTRecord = EFTRecord(content, 3) assert record.index == 3 - assert record.record_type == '2' - assert record.ministry_code == 'AT' - assert record.program_code == '0146' + assert record.record_type == "2" + assert record.ministry_code == "AT" + assert record.program_code == "0146" assert record.deposit_datetime == deposit_datetime - assert record.location_id == '85004' - assert record.transaction_sequence == '003' - assert record.transaction_description == 'ABC1234567' + assert record.location_id == "85004" + assert record.transaction_sequence == "003" + assert record.transaction_description == "ABC1234567" assert record.deposit_amount == 951250 assert record.currency == EFTConstants.CURRENCY_CAD.value assert record.exchange_adj_amount == 0 assert record.deposit_amount_cad == 951250 - assert record.dest_bank_number == '0003' - assert record.batch_number == '002400986' - assert record.jv_type == 'I' - assert record.jv_number == '002425669' + assert record.dest_bank_number == "0003" + assert record.batch_number == "002400986" + assert record.jv_type == "I" + assert record.jv_number == "002425669" assert record.transaction_date == transaction_date assert record.short_name_type == EFTShortnameType.EFT.value - content = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, - ministry_code='AT', - program_code='0146', - deposit_date='20230810', - deposit_time='0000', - location_id='85004', - transaction_sequence='004', - transaction_description='MISC PAYMENT BCONLINE INTERBLOCK C', - deposit_amount='2125000', - currency='', - exchange_adj_amount='0', - deposit_amount_cad='2125000', - destination_bank_number='0003', - batch_number='002400986', - jv_type='I', - jv_number='002425669', - transaction_date='' - ) + content = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="004", + transaction_description="MISC PAYMENT BCONLINE INTERBLOCK C", + deposit_amount="2125000", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="2125000", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) record: EFTRecord = EFTRecord(content, 4) assert record.index == 4 - assert record.record_type == '2' - assert record.ministry_code == 'AT' - assert record.program_code == '0146' + assert record.record_type == "2" + assert record.ministry_code == "AT" + assert record.program_code == "0146" assert record.deposit_datetime == deposit_datetime - assert record.location_id == '85004' - assert record.transaction_sequence == '004' - assert record.transaction_description == 'MISC PAYMENT BCONLINE INTERBLOCK C' + assert record.location_id == "85004" + assert record.transaction_sequence == "004" + assert record.transaction_description == "MISC PAYMENT BCONLINE INTERBLOCK C" assert record.deposit_amount == 2125000 assert record.currency == EFTConstants.CURRENCY_CAD.value assert record.exchange_adj_amount == 0 assert record.deposit_amount_cad == 2125000 - assert record.dest_bank_number == '0003' - assert record.batch_number == '002400986' - assert record.jv_type == 'I' - assert record.jv_number == '002425669' + assert record.dest_bank_number == "0003" + assert record.batch_number == "002400986" + assert record.jv_type == "I" + assert record.jv_number == "002425669" assert record.transaction_date == transaction_date assert record.short_name_type is None - content = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, - ministry_code='AT', - program_code='0146', - deposit_date='20230810', - deposit_time='1600', - location_id='85020', - transaction_sequence='001', - transaction_description='', - deposit_amount='119000', - currency='', - exchange_adj_amount='0', - deposit_amount_cad='119000', - destination_bank_number='0010', - batch_number='002400989', - jv_type='I', - jv_number='002425836', - transaction_date='20230810' - ) + content = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="1600", + location_id="85020", + transaction_sequence="001", + transaction_description="", + deposit_amount="119000", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="119000", + destination_bank_number="0010", + batch_number="002400989", + jv_type="I", + jv_number="002425836", + transaction_date="20230810", + ) record: EFTRecord = EFTRecord(content, 5) deposit_datetime = datetime(2023, 8, 10, 16, 0) transaction_date = datetime(2023, 8, 10) assert record.index == 5 - assert record.record_type == '2' - assert record.ministry_code == 'AT' - assert record.program_code == '0146' + assert record.record_type == "2" + assert record.ministry_code == "AT" + assert record.program_code == "0146" assert record.deposit_datetime == deposit_datetime - assert record.location_id == '85020' - assert record.transaction_sequence == '001' - assert record.transaction_description == '' + assert record.location_id == "85020" + assert record.transaction_sequence == "001" + assert record.transaction_description == "" assert record.deposit_amount == 119000 assert record.currency == EFTConstants.CURRENCY_CAD.value assert record.exchange_adj_amount == 0 assert record.deposit_amount_cad == 119000 - assert record.dest_bank_number == '0010' - assert record.batch_number == '002400989' - assert record.jv_type == 'I' - assert record.jv_number == '002425836' + assert record.dest_bank_number == "0010" + assert record.batch_number == "002400989" + assert record.jv_type == "I" + assert record.jv_number == "002425836" assert record.transaction_date == transaction_date def test_eft_parse_record_invalid_length(): """Test EFT record parser invalid length.""" - content = ' ' + content = " " record: EFTRecord = EFTRecord(content, 0) assert record.errors @@ -371,24 +384,25 @@ def test_eft_parse_record_invalid_length(): def test_eft_parse_record_invalid_record_type(): """Test EFT record parser invalid record_type.""" - content = factory_eft_record(record_type='X', - ministry_code='AT', - program_code='0146', - deposit_date='20230810', - deposit_time='0000', - location_id='85004', - transaction_sequence='001', - transaction_description='DEPOSIT 26', - deposit_amount='13500', - currency='', - exchange_adj_amount='0', - deposit_amount_cad='13500', - destination_bank_number='0003', - batch_number='002400986', - jv_type='I', - jv_number='002425669', - transaction_date='' - ) + content = factory_eft_record( + record_type="X", + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="001", + transaction_description="DEPOSIT 26", + deposit_amount="13500", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="13500", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) record: EFTRecord = EFTRecord(content, 0) assert record.errors @@ -400,24 +414,25 @@ def test_eft_parse_record_invalid_record_type(): def test_eft_parse_record_invalid_dates(): """Test EFT record parser for invalid dates.""" - content = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, - ministry_code='AT', - program_code='0146', - deposit_date='2023081 ', - deposit_time='A000', - location_id='85004', - transaction_sequence='001', - transaction_description='DEPOSIT 26', - deposit_amount='13500', - currency='', - exchange_adj_amount='0', - deposit_amount_cad='13500', - destination_bank_number='0003', - batch_number='002400986', - jv_type='I', - jv_number='002425669', - transaction_date='20233001' - ) + content = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="2023081 ", + deposit_time="A000", + location_id="85004", + transaction_sequence="001", + transaction_description="DEPOSIT 26", + deposit_amount="13500", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="13500", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="20233001", + ) record: EFTRecord = EFTRecord(content, 1) assert record.errors @@ -430,44 +445,45 @@ def test_eft_parse_record_invalid_dates(): assert record.errors[1].index == 1 assert record.index == 1 - assert record.record_type == '2' - assert record.ministry_code == 'AT' - assert record.program_code == '0146' + assert record.record_type == "2" + assert record.ministry_code == "AT" + assert record.program_code == "0146" assert record.deposit_datetime is None - assert record.location_id == '85004' - assert record.transaction_sequence == '001' - assert record.transaction_description == 'DEPOSIT 26' + assert record.location_id == "85004" + assert record.transaction_sequence == "001" + assert record.transaction_description == "DEPOSIT 26" assert record.deposit_amount == 13500 assert record.currency == EFTConstants.CURRENCY_CAD.value assert record.exchange_adj_amount == 0 assert record.deposit_amount_cad == 13500 - assert record.dest_bank_number == '0003' - assert record.batch_number == '002400986' - assert record.jv_type == 'I' - assert record.jv_number == '002425669' + assert record.dest_bank_number == "0003" + assert record.batch_number == "002400986" + assert record.jv_type == "I" + assert record.jv_number == "002425669" assert record.transaction_date is None def test_eft_parse_record_invalid_numbers(): """Test EFT record parser for invalid numbers.""" - content = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, - ministry_code='AT', - program_code='0146', - deposit_date='20230810', - deposit_time='0000', - location_id='85004', - transaction_sequence='001', - transaction_description='1234', - deposit_amount='1350A', - currency='', - exchange_adj_amount='ABC', - deposit_amount_cad='1350A', - destination_bank_number='0003', - batch_number='002400986', - jv_type='I', - jv_number='002425669', - transaction_date='' - ) + content = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="001", + transaction_description="1234", + deposit_amount="1350A", + currency="", + exchange_adj_amount="ABC", + deposit_amount_cad="1350A", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) record: EFTRecord = EFTRecord(content, 0) # We are expecting the transaction description as this is where we get the BCROS Account number @@ -486,24 +502,25 @@ def test_eft_parse_record_invalid_numbers(): def test_eft_parse_record_transaction_description_required(): """Test EFT record parser transaction description required.""" - content = factory_eft_record(record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, - ministry_code='AT', - program_code='0146', - deposit_date='20230810', - deposit_time='0000', - location_id='85004', - transaction_sequence='001', - transaction_description='', - deposit_amount='13500', - currency='', - exchange_adj_amount='0', - deposit_amount_cad='13500', - destination_bank_number='0003', - batch_number='002400986', - jv_type='I', - jv_number='002425669', - transaction_date='' - ) + content = factory_eft_record( + record_type=EFTConstants.TRANSACTION_RECORD_TYPE.value, + ministry_code="AT", + program_code="0146", + deposit_date="20230810", + deposit_time="0000", + location_id="85004", + transaction_sequence="001", + transaction_description="", + deposit_amount="13500", + currency="", + exchange_adj_amount="0", + deposit_amount_cad="13500", + destination_bank_number="0003", + batch_number="002400986", + jv_type="I", + jv_number="002425669", + transaction_date="", + ) record: EFTRecord = EFTRecord(content, 0) # We are expecting the transaction description as this is where we get the BCROS Account number @@ -516,7 +533,7 @@ def test_eft_parse_record_transaction_description_required(): def test_eft_parse_file(): """Test EFT parsing a file.""" - with open('tests/unit/test_data/tdi17_sample.txt', 'r') as f: + with open("tests/unit/test_data/tdi17_sample.txt", "r") as f: contents = f.read() lines = contents.splitlines() header_index = 0 @@ -534,107 +551,107 @@ def test_eft_parse_file(): assert len(eft_records) == 5 assert eft_header.index == 0 - assert eft_header.record_type == '1' + assert eft_header.record_type == "1" assert eft_header.creation_datetime == datetime(2023, 8, 14, 16, 1) assert eft_header.starting_deposit_date == datetime(2023, 8, 10) assert eft_header.ending_deposit_date == datetime(2023, 8, 10) assert eft_trailer.index == 6 - assert eft_trailer.record_type == '7' + assert eft_trailer.record_type == "7" assert eft_trailer.number_of_details == 5 assert eft_trailer.total_deposit_amount == 3733750 assert eft_records[0].index == 1 - assert eft_records[0].record_type == '2' - assert eft_records[0].ministry_code == 'AT' - assert eft_records[0].program_code == '0146' + assert eft_records[0].record_type == "2" + assert eft_records[0].ministry_code == "AT" + assert eft_records[0].program_code == "0146" assert eft_records[0].deposit_datetime == datetime(2023, 8, 10, 0, 0) - assert eft_records[0].location_id == '85004' - assert eft_records[0].transaction_sequence == '001' - assert eft_records[0].transaction_description == 'DEPOSIT 26' + assert eft_records[0].location_id == "85004" + assert eft_records[0].transaction_sequence == "001" + assert eft_records[0].transaction_description == "DEPOSIT 26" assert eft_records[0].deposit_amount == 13500 assert eft_records[0].currency == EFTConstants.CURRENCY_CAD.value assert eft_records[0].exchange_adj_amount == 0 assert eft_records[0].deposit_amount_cad == 13500 - assert eft_records[0].dest_bank_number == '0003' - assert eft_records[0].batch_number == '002400986' - assert eft_records[0].jv_type == 'I' - assert eft_records[0].jv_number == '002425669' + assert eft_records[0].dest_bank_number == "0003" + assert eft_records[0].batch_number == "002400986" + assert eft_records[0].jv_type == "I" + assert eft_records[0].jv_number == "002425669" assert eft_records[0].transaction_date is None assert eft_records[0].short_name_type is None assert eft_records[1].index == 2 - assert eft_records[1].record_type == '2' - assert eft_records[1].ministry_code == 'AT' - assert eft_records[1].program_code == '0146' + assert eft_records[1].record_type == "2" + assert eft_records[1].ministry_code == "AT" + assert eft_records[1].program_code == "0146" assert eft_records[1].deposit_datetime == datetime(2023, 8, 10, 0, 0) - assert eft_records[1].location_id == '85004' - assert eft_records[1].transaction_sequence == '002' - assert eft_records[1].transaction_description == 'HSIMPSON' + assert eft_records[1].location_id == "85004" + assert eft_records[1].transaction_sequence == "002" + assert eft_records[1].transaction_description == "HSIMPSON" assert eft_records[1].deposit_amount == 525000 assert eft_records[1].currency == EFTConstants.CURRENCY_CAD.value assert eft_records[1].exchange_adj_amount == 0 assert eft_records[1].deposit_amount_cad == 525000 - assert eft_records[1].dest_bank_number == '0003' - assert eft_records[1].batch_number == '002400986' - assert eft_records[1].jv_type == 'I' - assert eft_records[1].jv_number == '002425669' + assert eft_records[1].dest_bank_number == "0003" + assert eft_records[1].batch_number == "002400986" + assert eft_records[1].jv_type == "I" + assert eft_records[1].jv_number == "002425669" assert eft_records[1].transaction_date is None assert eft_records[1].short_name_type == EFTShortnameType.WIRE.value assert eft_records[2].index == 3 - assert eft_records[2].record_type == '2' - assert eft_records[2].ministry_code == 'AT' - assert eft_records[2].program_code == '0146' + assert eft_records[2].record_type == "2" + assert eft_records[2].ministry_code == "AT" + assert eft_records[2].program_code == "0146" assert eft_records[2].deposit_datetime == datetime(2023, 8, 10, 0, 0) - assert eft_records[2].location_id == '85004' - assert eft_records[2].transaction_sequence == '003' - assert eft_records[2].transaction_description == 'ABC1234567' + assert eft_records[2].location_id == "85004" + assert eft_records[2].transaction_sequence == "003" + assert eft_records[2].transaction_description == "ABC1234567" assert eft_records[2].deposit_amount == 951250 assert eft_records[2].currency == EFTConstants.CURRENCY_CAD.value assert eft_records[2].exchange_adj_amount == 0 assert eft_records[2].deposit_amount_cad == 951250 - assert eft_records[2].dest_bank_number == '0003' - assert eft_records[2].batch_number == '002400986' - assert eft_records[2].jv_type == 'I' - assert eft_records[2].jv_number == '002425669' + assert eft_records[2].dest_bank_number == "0003" + assert eft_records[2].batch_number == "002400986" + assert eft_records[2].jv_type == "I" + assert eft_records[2].jv_number == "002425669" assert eft_records[2].transaction_date is None assert eft_records[2].short_name_type == EFTShortnameType.EFT.value assert eft_records[3].index == 4 - assert eft_records[3].record_type == '2' - assert eft_records[3].ministry_code == 'AT' - assert eft_records[3].program_code == '0146' + assert eft_records[3].record_type == "2" + assert eft_records[3].ministry_code == "AT" + assert eft_records[3].program_code == "0146" assert eft_records[3].deposit_datetime == datetime(2023, 8, 10, 0, 0) - assert eft_records[3].location_id == '85004' - assert eft_records[3].transaction_sequence == '004' - assert eft_records[3].transaction_description == 'INTERBLOCK C' + assert eft_records[3].location_id == "85004" + assert eft_records[3].transaction_sequence == "004" + assert eft_records[3].transaction_description == "INTERBLOCK C" assert eft_records[3].deposit_amount == 2125000 assert eft_records[3].currency == EFTConstants.CURRENCY_CAD.value assert eft_records[3].exchange_adj_amount == 0 assert eft_records[3].deposit_amount_cad == 2125000 - assert eft_records[3].dest_bank_number == '0003' - assert eft_records[3].batch_number == '002400986' - assert eft_records[3].jv_type == 'I' - assert eft_records[3].jv_number == '002425669' + assert eft_records[3].dest_bank_number == "0003" + assert eft_records[3].batch_number == "002400986" + assert eft_records[3].jv_type == "I" + assert eft_records[3].jv_number == "002425669" assert eft_records[3].transaction_date is None assert eft_records[3].short_name_type == EFTShortnameType.WIRE.value assert eft_records[4].index == 5 - assert eft_records[4].record_type == '2' - assert eft_records[4].ministry_code == 'AT' - assert eft_records[4].program_code == '0146' + assert eft_records[4].record_type == "2" + assert eft_records[4].ministry_code == "AT" + assert eft_records[4].program_code == "0146" assert eft_records[4].deposit_datetime == datetime(2023, 8, 10, 16, 0) - assert eft_records[4].location_id == '85020' - assert eft_records[4].transaction_sequence == '001' - assert eft_records[4].transaction_description == '' + assert eft_records[4].location_id == "85020" + assert eft_records[4].transaction_sequence == "001" + assert eft_records[4].transaction_description == "" assert eft_records[4].deposit_amount == 119000 assert eft_records[4].currency == EFTConstants.CURRENCY_CAD.value assert eft_records[4].exchange_adj_amount == 0 assert eft_records[4].deposit_amount_cad == 119000 - assert eft_records[4].dest_bank_number == '0010' - assert eft_records[4].batch_number == '002400989' - assert eft_records[4].jv_type == 'I' - assert eft_records[4].jv_number == '002425836' + assert eft_records[4].dest_bank_number == "0010" + assert eft_records[4].batch_number == "002400989" + assert eft_records[4].jv_type == "I" + assert eft_records[4].jv_number == "002425836" assert eft_records[4].transaction_date is None assert eft_records[4].short_name_type is None diff --git a/pay-queue/tests/utilities/factory_utils.py b/pay-queue/tests/utilities/factory_utils.py index f493b3b88..faf9abdc8 100644 --- a/pay-queue/tests/utilities/factory_utils.py +++ b/pay-queue/tests/utilities/factory_utils.py @@ -18,11 +18,18 @@ from pay_queue.services.eft.eft_enums import EFTConstants -def factory_eft_header(record_type: str, file_creation_date: str, file_creation_time: str, - deposit_start_date: str, deposit_end_date) -> str: +def factory_eft_header( + record_type: str, + file_creation_date: str, + file_creation_time: str, + deposit_start_date: str, + deposit_end_date, +) -> str: """Produce eft header TDI17 formatted string.""" - result = f'{record_type}CREATION DATE: {file_creation_date}CREATION TIME: {file_creation_time}' \ - f'DEPOSIT DATE(S) FROM: {deposit_start_date} TO DATE : {deposit_end_date}' + result = ( + f"{record_type}CREATION DATE: {file_creation_date}CREATION TIME: {file_creation_time}" + f"DEPOSIT DATE(S) FROM: {deposit_start_date} TO DATE : {deposit_end_date}" + ) result = eft_pad_line_length(result) # Pad end of line length return result @@ -31,28 +38,44 @@ def factory_eft_header(record_type: str, file_creation_date: str, file_creation_ def factory_eft_trailer(record_type: str, number_of_details: str, total_deposit_amount: str) -> str: """Produce eft trailer TDI17 formatted string.""" total_deposit_amount = transform_money_string(total_deposit_amount) - result = f'{record_type}{left_pad_zero(number_of_details, 6)}{left_pad_zero(total_deposit_amount, 14)}' + result = f"{record_type}{left_pad_zero(number_of_details, 6)}{left_pad_zero(total_deposit_amount, 14)}" result = eft_pad_line_length(result) return result -def factory_eft_record(record_type: str, ministry_code: str, program_code: str, - deposit_date: str, deposit_time: str, location_id: str, transaction_sequence: str, - transaction_description: str, deposit_amount: str, currency: str, - exchange_adj_amount: str, deposit_amount_cad: str, destination_bank_number: str, - batch_number: str, jv_type: str, jv_number: str, transaction_date: str) -> str: +def factory_eft_record( + record_type: str, + ministry_code: str, + program_code: str, + deposit_date: str, + deposit_time: str, + location_id: str, + transaction_sequence: str, + transaction_description: str, + deposit_amount: str, + currency: str, + exchange_adj_amount: str, + deposit_amount_cad: str, + destination_bank_number: str, + batch_number: str, + jv_type: str, + jv_number: str, + transaction_date: str, +) -> str: """Produce eft transaction record TDI17 formatted string.""" deposit_amount = transform_money_string(deposit_amount) exchange_adj_amount = transform_money_string(exchange_adj_amount) deposit_amount_cad = transform_money_string(deposit_amount_cad) - result = f'{record_type}{ministry_code}{program_code}{deposit_date}{location_id}' \ - f'{right_pad_space(deposit_time, 4)}' \ - f'{transaction_sequence}{right_pad_space(transaction_description, 40)}' \ - f'{left_pad_zero(deposit_amount, 13)}{right_pad_space(currency, 2)}' \ - f'{left_pad_zero(exchange_adj_amount, 13)}{left_pad_zero(deposit_amount_cad, 13)}' \ - f'{destination_bank_number}{batch_number}{jv_type}{jv_number}{transaction_date}' + result = ( + f"{record_type}{ministry_code}{program_code}{deposit_date}{location_id}" + f"{right_pad_space(deposit_time, 4)}" + f"{transaction_sequence}{right_pad_space(transaction_description, 40)}" + f"{left_pad_zero(deposit_amount, 13)}{right_pad_space(currency, 2)}" + f"{left_pad_zero(exchange_adj_amount, 13)}{left_pad_zero(deposit_amount_cad, 13)}" + f"{destination_bank_number}{batch_number}{jv_type}{jv_number}{transaction_date}" + ) result = eft_pad_line_length(result) return result @@ -62,20 +85,20 @@ def transform_money_string(money_str: str) -> str: """Produce a properly formatted string for TDI17 money values.""" money_str = money_str.strip() - if not money_str.endswith('-'): # Ends with minus sign if it is a negative value - money_str = money_str + ' ' # Add a blank for positive value + if not money_str.endswith("-"): # Ends with minus sign if it is a negative value + money_str = money_str + " " # Add a blank for positive value return money_str def left_pad_zero(value: str, width: int) -> str: """Produce left padded zero string.""" - return '{:0>{}}'.format(value, width) + return "{:0>{}}".format(value, width) def right_pad_space(value: str, width: int) -> str: """Produce end padded white spaced string.""" - return '{:<{}}'.format(value, width) # Pad end of line length + return "{:<{}}".format(value, width) # Pad end of line length def eft_pad_line_length(value: str) -> str: