updated Payment service
This commit is contained in:
93
ServicesBank/Finder/.dockerignore
Normal file
93
ServicesBank/Finder/.dockerignore
Normal file
@@ -0,0 +1,93 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
|
||||
# CI
|
||||
.codeclimate.yml
|
||||
.travis.yml
|
||||
.taskcluster.yml
|
||||
|
||||
# Docker
|
||||
docker-compose.yml
|
||||
service_app/Dockerfile
|
||||
.docker
|
||||
.dockerignore
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
**/__pycache__/
|
||||
**/*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
service_app/env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Virtual environment
|
||||
service_app/.env
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
|
||||
# Python mode for VIM
|
||||
.ropeproject
|
||||
**/.ropeproject
|
||||
|
||||
# Vim swap files
|
||||
**/*.swp
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
test_application/
|
||||
|
||||
|
||||
93
ServicesBank/Finder/BuildFromIban/.dockerignore
Normal file
93
ServicesBank/Finder/BuildFromIban/.dockerignore
Normal file
@@ -0,0 +1,93 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
|
||||
# CI
|
||||
.codeclimate.yml
|
||||
.travis.yml
|
||||
.taskcluster.yml
|
||||
|
||||
# Docker
|
||||
docker-compose.yml
|
||||
service_app/Dockerfile
|
||||
.docker
|
||||
.dockerignore
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
**/__pycache__/
|
||||
**/*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
service_app/env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Virtual environment
|
||||
service_app/.env
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
|
||||
# Python mode for VIM
|
||||
.ropeproject
|
||||
**/.ropeproject
|
||||
|
||||
# Vim swap files
|
||||
**/*.swp
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
test_application/
|
||||
|
||||
|
||||
33
ServicesBank/Finder/BuildFromIban/Dockerfile
Normal file
33
ServicesBank/Finder/BuildFromIban/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Set Python path to include app directory
|
||||
ENV PYTHONPATH=/ PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Install system dependencies and Poetry
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/* && pip install --no-cache-dir poetry
|
||||
|
||||
# Copy Poetry configuration
|
||||
COPY /pyproject.toml ./pyproject.toml
|
||||
|
||||
# Configure Poetry and install dependencies with optimizations
|
||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi --no-root --only main && pip cache purge && rm -rf ~/.cache/pypoetry
|
||||
|
||||
# Install cron for scheduling tasks
|
||||
RUN apt-get update && apt-get install -y cron
|
||||
|
||||
# Copy application code
|
||||
COPY /ServicesBank/Finder/BuildFromIban /
|
||||
COPY /ServicesApi/Schemas /Schemas
|
||||
COPY /ServicesApi/Controllers /Controllers
|
||||
|
||||
# Create log file to grab cron logs
|
||||
RUN touch /var/log/cron.log
|
||||
|
||||
# Make entrypoint script executable
|
||||
RUN chmod +x /entrypoint.sh
|
||||
RUN chmod +x /run_app.sh
|
||||
|
||||
# Use entrypoint script to update run_app.sh with environment variables and start cron
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
3
ServicesBank/Finder/BuildFromIban/README.md
Normal file
3
ServicesBank/Finder/BuildFromIban/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Docs of Finder
|
||||
|
||||
Finds people, living spaces, companies from AccountRecords
|
||||
30
ServicesBank/Finder/BuildFromIban/entrypoint.sh
Normal file
30
ServicesBank/Finder/BuildFromIban/entrypoint.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Create environment file that will be available to cron jobs
|
||||
echo "POSTGRES_USER=\"$POSTGRES_USER\"" >> /env.sh
|
||||
echo "POSTGRES_PASSWORD=\"$POSTGRES_PASSWORD\"" >> /env.sh
|
||||
echo "POSTGRES_DB=\"$POSTGRES_DB\"" >> /env.sh
|
||||
echo "POSTGRES_HOST=\"$POSTGRES_HOST\"" >> /env.sh
|
||||
echo "POSTGRES_PORT=$POSTGRES_PORT" >> /env.sh
|
||||
echo "POSTGRES_ENGINE=\"$POSTGRES_ENGINE\"" >> /env.sh
|
||||
echo "POSTGRES_POOL_PRE_PING=\"$POSTGRES_POOL_PRE_PING\"" >> /env.sh
|
||||
echo "POSTGRES_POOL_SIZE=$POSTGRES_POOL_SIZE" >> /env.sh
|
||||
echo "POSTGRES_MAX_OVERFLOW=$POSTGRES_MAX_OVERFLOW" >> /env.sh
|
||||
echo "POSTGRES_POOL_RECYCLE=$POSTGRES_POOL_RECYCLE" >> /env.sh
|
||||
echo "POSTGRES_POOL_TIMEOUT=$POSTGRES_POOL_TIMEOUT" >> /env.sh
|
||||
echo "POSTGRES_ECHO=\"$POSTGRES_ECHO\"" >> /env.sh
|
||||
|
||||
# Add Python environment variables
|
||||
echo "PYTHONPATH=/" >> /env.sh
|
||||
echo "PYTHONUNBUFFERED=1" >> /env.sh
|
||||
echo "PYTHONDONTWRITEBYTECODE=1" >> /env.sh
|
||||
|
||||
# Make the environment file available to cron
|
||||
echo "*/5 * * * * /run_app.sh >> /var/log/cron.log 2>&1" > /tmp/crontab_list
|
||||
crontab /tmp/crontab_list
|
||||
|
||||
# Start cron
|
||||
cron
|
||||
|
||||
# Tail the log file
|
||||
tail -f /var/log/cron.log
|
||||
26
ServicesBank/Finder/BuildFromIban/run_app.sh
Normal file
26
ServicesBank/Finder/BuildFromIban/run_app.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Source the environment file directly
|
||||
. /env.sh
|
||||
|
||||
# Re-export all variables to ensure they're available to the Python script
|
||||
export POSTGRES_USER
|
||||
export POSTGRES_PASSWORD
|
||||
export POSTGRES_DB
|
||||
export POSTGRES_HOST
|
||||
export POSTGRES_PORT
|
||||
export POSTGRES_ENGINE
|
||||
export POSTGRES_POOL_PRE_PING
|
||||
export POSTGRES_POOL_SIZE
|
||||
export POSTGRES_MAX_OVERFLOW
|
||||
export POSTGRES_POOL_RECYCLE
|
||||
export POSTGRES_POOL_TIMEOUT
|
||||
export POSTGRES_ECHO
|
||||
|
||||
# Python environment variables
|
||||
export PYTHONPATH
|
||||
export PYTHONUNBUFFERED
|
||||
export PYTHONDONTWRITEBYTECODE
|
||||
|
||||
# env >> /var/log/cron.log
|
||||
/usr/local/bin/python /runner.py
|
||||
26
ServicesBank/Finder/BuildFromIban/runner.py
Normal file
26
ServicesBank/Finder/BuildFromIban/runner.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import arrow
|
||||
|
||||
from Schemas import AccountRecords, BuildIbans
|
||||
|
||||
|
||||
def account_find_build_from_iban(session):
|
||||
AccountRecords.set_session(session)
|
||||
BuildIbans.set_session(session)
|
||||
|
||||
account_records_ibans = AccountRecords.query.filter(AccountRecords.build_id == None, AccountRecords.approved_record == False).distinct(AccountRecords.iban).all()
|
||||
for account_records_iban in account_records_ibans:
|
||||
found_iban: BuildIbans = BuildIbans.query.filter(BuildIbans.iban == account_records_iban.iban).first()
|
||||
if not found_iban:
|
||||
create_build_ibans = BuildIbans.create(iban=account_records_iban.iban, start_date=str(arrow.now().shift(days=-1)))
|
||||
create_build_ibans.save()
|
||||
else:
|
||||
update_dict = {"build_id": found_iban.build_id, "build_uu_id": str(found_iban.build_uu_id)}
|
||||
session.query(AccountRecords).filter(AccountRecords.iban == account_records_iban.iban).update(update_dict, synchronize_session=False)
|
||||
session.commit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Account Records Service is running...")
|
||||
with AccountRecords.new_session() as session:
|
||||
account_find_build_from_iban(session=session)
|
||||
print("Account Records Service is finished...")
|
||||
93
ServicesBank/Finder/BuildLivingSpace/.dockerignore
Normal file
93
ServicesBank/Finder/BuildLivingSpace/.dockerignore
Normal file
@@ -0,0 +1,93 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
|
||||
# CI
|
||||
.codeclimate.yml
|
||||
.travis.yml
|
||||
.taskcluster.yml
|
||||
|
||||
# Docker
|
||||
docker-compose.yml
|
||||
service_app/Dockerfile
|
||||
.docker
|
||||
.dockerignore
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
**/__pycache__/
|
||||
**/*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
service_app/env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Virtual environment
|
||||
service_app/.env
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
|
||||
# Python mode for VIM
|
||||
.ropeproject
|
||||
**/.ropeproject
|
||||
|
||||
# Vim swap files
|
||||
**/*.swp
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
test_application/
|
||||
|
||||
|
||||
33
ServicesBank/Finder/BuildLivingSpace/Dockerfile
Normal file
33
ServicesBank/Finder/BuildLivingSpace/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Set Python path to include app directory
|
||||
ENV PYTHONPATH=/ PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Install system dependencies and Poetry
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/* && pip install --no-cache-dir poetry
|
||||
|
||||
# Copy Poetry configuration
|
||||
COPY /pyproject.toml ./pyproject.toml
|
||||
|
||||
# Configure Poetry and install dependencies with optimizations
|
||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi --no-root --only main && pip cache purge && rm -rf ~/.cache/pypoetry
|
||||
|
||||
# Install cron for scheduling tasks
|
||||
RUN apt-get update && apt-get install -y cron
|
||||
|
||||
# Copy application code
|
||||
COPY /ServicesBank/Finder/BuildLivingSpace /
|
||||
COPY /ServicesApi/Schemas /Schemas
|
||||
COPY /ServicesApi/Controllers /Controllers
|
||||
|
||||
# Create log file to grab cron logs
|
||||
RUN touch /var/log/cron.log
|
||||
|
||||
# Make entrypoint script executable
|
||||
RUN chmod +x /entrypoint.sh
|
||||
RUN chmod +x /run_app.sh
|
||||
|
||||
# Use entrypoint script to update run_app.sh with environment variables and start cron
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
3
ServicesBank/Finder/BuildLivingSpace/README.md
Normal file
3
ServicesBank/Finder/BuildLivingSpace/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Docs of Finder
|
||||
|
||||
Finds people, living spaces, companies from AccountRecords
|
||||
8
ServicesBank/Finder/BuildLivingSpace/configs.py
Normal file
8
ServicesBank/Finder/BuildLivingSpace/configs.py
Normal file
@@ -0,0 +1,8 @@
|
||||
class AccountConfig:
|
||||
BEFORE_DAY = 30
|
||||
CATEGORIES = {
|
||||
"DAIRE": ["daire", "dagire", "daare", "nolu daire", "no", "nolu dairenin"],
|
||||
"APARTMAN": ["apartman", "aparman", "aprmn"],
|
||||
"VILLA": ["villa", "vlla"],
|
||||
"BINA": ["bina", "binna"],
|
||||
}
|
||||
30
ServicesBank/Finder/BuildLivingSpace/entrypoint.sh
Normal file
30
ServicesBank/Finder/BuildLivingSpace/entrypoint.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Create environment file that will be available to cron jobs
|
||||
echo "POSTGRES_USER=\"$POSTGRES_USER\"" >> /env.sh
|
||||
echo "POSTGRES_PASSWORD=\"$POSTGRES_PASSWORD\"" >> /env.sh
|
||||
echo "POSTGRES_DB=\"$POSTGRES_DB\"" >> /env.sh
|
||||
echo "POSTGRES_HOST=\"$POSTGRES_HOST\"" >> /env.sh
|
||||
echo "POSTGRES_PORT=$POSTGRES_PORT" >> /env.sh
|
||||
echo "POSTGRES_ENGINE=\"$POSTGRES_ENGINE\"" >> /env.sh
|
||||
echo "POSTGRES_POOL_PRE_PING=\"$POSTGRES_POOL_PRE_PING\"" >> /env.sh
|
||||
echo "POSTGRES_POOL_SIZE=$POSTGRES_POOL_SIZE" >> /env.sh
|
||||
echo "POSTGRES_MAX_OVERFLOW=$POSTGRES_MAX_OVERFLOW" >> /env.sh
|
||||
echo "POSTGRES_POOL_RECYCLE=$POSTGRES_POOL_RECYCLE" >> /env.sh
|
||||
echo "POSTGRES_POOL_TIMEOUT=$POSTGRES_POOL_TIMEOUT" >> /env.sh
|
||||
echo "POSTGRES_ECHO=\"$POSTGRES_ECHO\"" >> /env.sh
|
||||
|
||||
# Add Python environment variables
|
||||
echo "PYTHONPATH=/" >> /env.sh
|
||||
echo "PYTHONUNBUFFERED=1" >> /env.sh
|
||||
echo "PYTHONDONTWRITEBYTECODE=1" >> /env.sh
|
||||
|
||||
# Make the environment file available to cron
|
||||
echo "*/15 * * * * /run_app.sh >> /var/log/cron.log 2>&1" > /tmp/crontab_list
|
||||
crontab /tmp/crontab_list
|
||||
|
||||
# Start cron
|
||||
cron
|
||||
|
||||
# Tail the log file
|
||||
tail -f /var/log/cron.log
|
||||
319
ServicesBank/Finder/BuildLivingSpace/parser.py
Normal file
319
ServicesBank/Finder/BuildLivingSpace/parser.py
Normal file
@@ -0,0 +1,319 @@
|
||||
import re
|
||||
import textdistance
|
||||
|
||||
from unidecode import unidecode
|
||||
from gc import garbage
|
||||
from Schemas import AccountRecords, People, Build, Companies, BuildIbanDescription
|
||||
from regex_func import category_finder
|
||||
from validations import Similarity
|
||||
|
||||
|
||||
def parse_comment_to_split_with_star(account_record):
|
||||
# Handle both ORM objects and dictionaries
|
||||
try:
|
||||
# Check if account_record is a dictionary or an ORM object
|
||||
if isinstance(account_record, dict):
|
||||
process_comment = str(account_record.get('process_comment', ''))
|
||||
else:
|
||||
process_comment = str(account_record.process_comment)
|
||||
|
||||
if "*" in process_comment:
|
||||
process_comment_cleaned = process_comment.replace("**", "*")
|
||||
process_comments = process_comment_cleaned.split("*")
|
||||
return len(process_comments), *process_comments
|
||||
return 1, process_comment
|
||||
except Exception as e:
|
||||
# print(f"Error in parse_comment_to_split_with_star: {e}")
|
||||
# Return a safe default if there's an error
|
||||
return 1, ""
|
||||
|
||||
|
||||
def remove_garbage_words(comment: str, garbage_word: str):
|
||||
cleaned_comment = remove_spaces_from_string(comment.replace("*", " "))
|
||||
if garbage_word:
|
||||
garbage_word = remove_spaces_from_string(garbage_word.replace("*", " "))
|
||||
for letter in garbage_word.split(" "):
|
||||
cleaned_comment = unidecode(remove_spaces_from_string(cleaned_comment))
|
||||
cleaned_comment = cleaned_comment.replace(remove_spaces_from_string(letter), "")
|
||||
return str(remove_spaces_from_string(cleaned_comment)).upper()
|
||||
|
||||
|
||||
def remove_spaces_from_string(remove_string: str):
|
||||
letter_list = []
|
||||
for letter in remove_string.split(" "):
|
||||
if letter_ := "".join(i for i in letter if not i == " "):
|
||||
letter_list.append(letter_)
|
||||
return " ".join(letter_list).upper()
|
||||
|
||||
|
||||
def get_garbage_words(comment: str, search_word: str):
|
||||
garbage_words = unidecode(remove_spaces_from_string(comment))
|
||||
search_word = unidecode(remove_spaces_from_string(search_word))
|
||||
for word in search_word.split(" "):
|
||||
garbage_words = garbage_words.replace(remove_spaces_from_string(unidecode(word)), "")
|
||||
if cleaned_from_spaces := remove_spaces_from_string(garbage_words):
|
||||
return str(unidecode(cleaned_from_spaces)).upper()
|
||||
return None
|
||||
|
||||
|
||||
def parse_comment_with_name_iban_description(account_record):
|
||||
# Extract necessary data from account_record to avoid session detachment
|
||||
if isinstance(account_record, dict):
|
||||
iban = account_record.get('iban', '')
|
||||
process_comment = account_record.get('process_comment', '')
|
||||
else:
|
||||
try:
|
||||
iban = account_record.iban
|
||||
process_comment = account_record.process_comment
|
||||
except Exception as e:
|
||||
# print(f"Error accessing account_record attributes: {e}")
|
||||
return Similarity(similarity=0.0, garbage="", cleaned="")
|
||||
|
||||
# Process the comment locally without depending on the account_record object
|
||||
if "*" in process_comment:
|
||||
process_comment_cleaned = str(process_comment.replace("**", "*"))
|
||||
process_comments = process_comment_cleaned.split("*")
|
||||
comments_list, comments_length = process_comments, len(process_comments)
|
||||
else:
|
||||
comments_list, comments_length = [process_comment], 1
|
||||
|
||||
# print("comments_list", comments_list, "comments_length", comments_length)
|
||||
|
||||
with BuildIbanDescription.new_session() as session:
|
||||
BuildIbanDescription.set_session(session)
|
||||
Companies.set_session(session)
|
||||
|
||||
iban_results = BuildIbanDescription.query.filter(BuildIbanDescription.iban == iban).all()
|
||||
best_similarity = Similarity(similarity=0.0, garbage="", cleaned="")
|
||||
|
||||
for comment in comments_list:
|
||||
for iban_result in iban_results:
|
||||
search_word = unidecode(iban_result.search_word)
|
||||
garbage_words = get_garbage_words(comment, search_word)
|
||||
cleaned_comment = remove_garbage_words(comment, garbage_words)
|
||||
similarity_ratio = textdistance.jaro_winkler(cleaned_comment, search_word)
|
||||
company = Companies.query.filter_by(id=iban_result.company_id).first()
|
||||
|
||||
if float(similarity_ratio) > float(best_similarity.similarity):
|
||||
best_similarity = Similarity(similarity=similarity_ratio, garbage=garbage_words, cleaned=cleaned_comment)
|
||||
best_similarity.set_company(company)
|
||||
best_similarity.set_found_from("Customer Public Name Description")
|
||||
return best_similarity
|
||||
|
||||
|
||||
def parse_comment_for_build_parts(comment: str, max_build_part: int = 200, parse: str = "DAIRE"):
|
||||
results, results_list = category_finder(comment), []
|
||||
# print("results[parse]", results[parse])
|
||||
for result in results[parse] or []:
|
||||
if digits := "".join([letter for letter in str(result) if letter.isdigit()]):
|
||||
# print("digits", digits)
|
||||
if int(digits) <= int(max_build_part):
|
||||
results_list.append(int(digits))
|
||||
return results_list or None
|
||||
|
||||
|
||||
def parse_comment_with_name(account_record, living_space_dict: dict = None):
|
||||
# Extract necessary data from account_record to avoid session detachment
|
||||
if isinstance(account_record, dict):
|
||||
iban = account_record.get('iban', '')
|
||||
process_comment = account_record.get('process_comment', '')
|
||||
try:
|
||||
currency_value = int(account_record.get('currency_value', 0))
|
||||
except (ValueError, TypeError):
|
||||
currency_value = 0
|
||||
else:
|
||||
try:
|
||||
iban = account_record.iban
|
||||
process_comment = account_record.process_comment
|
||||
currency_value = int(account_record.currency_value)
|
||||
except Exception as e:
|
||||
# print(f"Error accessing account_record attributes: {e}")
|
||||
return Similarity(similarity=0.0, garbage="", cleaned="")
|
||||
|
||||
# Process the comment locally without depending on the account_record object
|
||||
if "*" in process_comment:
|
||||
process_comment_cleaned = str(process_comment.replace("**", "*"))
|
||||
process_comments = process_comment_cleaned.split("*")
|
||||
comments_list, comments_length = process_comments, len(process_comments)
|
||||
else:
|
||||
comments_list, comments_length = [process_comment], 1
|
||||
|
||||
# print("comments_list", comments_list, "comments_length", comments_length)
|
||||
best_similarity = Similarity(similarity=0.0, garbage="", cleaned="")
|
||||
|
||||
if currency_value > 0: # Build receive money from living space people
|
||||
living_space_matches = dict(living_space_dict=living_space_dict, iban=iban, whole_comment=process_comment)
|
||||
if comments_length == 1:
|
||||
best_similarity = parse_comment_for_living_space(iban=iban, comment=comments_list[0], living_space_dict=living_space_dict)
|
||||
best_similarity.set_send_person_id(best_similarity.customer_id)
|
||||
living_space_matches["best_similarity"] = best_similarity
|
||||
# if 0.5 < float(best_similarity['similarity']) < 0.8
|
||||
best_similarity = check_build_living_space_matches_with_build_parts(**living_space_matches)
|
||||
return best_similarity
|
||||
for comment in comments_list:
|
||||
similarity_result = parse_comment_for_living_space(iban=iban, comment=comment, living_space_dict=living_space_dict)
|
||||
if float(similarity_result.similarity) > float(best_similarity.similarity):
|
||||
best_similarity = similarity_result
|
||||
living_space_matches["best_similarity"] = best_similarity
|
||||
# if 0.5 < float(best_similarity['similarity']) < 0.8:
|
||||
best_similarity = check_build_living_space_matches_with_build_parts(**living_space_matches)
|
||||
# print("last best_similarity", best_similarity)
|
||||
return best_similarity
|
||||
else: # Build pays money for service taken from company or individual
|
||||
if not comments_length > 1:
|
||||
best_similarity = parse_comment_for_company_or_individual(comment=comments_list[0])
|
||||
best_similarity.set_send_person_id(best_similarity.customer_id)
|
||||
return best_similarity
|
||||
for comment in comments_list:
|
||||
similarity_result = parse_comment_for_company_or_individual(comment=comment)
|
||||
if float(similarity_result.similarity) > float(best_similarity.similarity):
|
||||
best_similarity = similarity_result
|
||||
return best_similarity
|
||||
|
||||
|
||||
def check_build_living_space_matches_with_build_parts(living_space_dict: dict, best_similarity: Similarity, iban: str, whole_comment: str):
|
||||
if 0.6 < float(best_similarity.similarity) < 0.8:
|
||||
build_parts_data = living_space_dict[iban]["build_parts"]
|
||||
# Check if we have living space ID in the similarity object
|
||||
living_space_id = getattr(best_similarity, 'living_space_id', None)
|
||||
if living_space_id:
|
||||
# Find the corresponding living space data
|
||||
living_space_data = None
|
||||
for ls in living_space_dict[iban]["living_space"]:
|
||||
if ls.get('id') == living_space_id:
|
||||
living_space_data = ls
|
||||
break
|
||||
|
||||
if living_space_data:
|
||||
build_parts_id = living_space_data.get('build_parts_id')
|
||||
parser_dict = dict(comment=str(whole_comment), max_build_part=len(build_parts_data))
|
||||
# print("build parts similarity", best_similarity, "parser_dict", parser_dict)
|
||||
results_list = parse_comment_for_build_parts(**parser_dict)
|
||||
# print("results_list", results_list)
|
||||
if not results_list:
|
||||
return best_similarity
|
||||
|
||||
for build_part_data in build_parts_data:
|
||||
# Get part_no directly if it exists in the dictionary
|
||||
part_no = build_part_data.get('part_no')
|
||||
|
||||
# If part_no doesn't exist, try to extract it from other attributes
|
||||
if part_no is None:
|
||||
# Try to get it from a name attribute if it exists
|
||||
name = build_part_data.get('name', '')
|
||||
if name and isinstance(name, str) and 'part' in name.lower():
|
||||
try:
|
||||
part_no = int(name.lower().replace('part', '').strip())
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# If we have a part_no, proceed with the comparison
|
||||
if part_no is not None:
|
||||
# print("part_no", part_no, " | ", results_list)
|
||||
# print("build_part", build_part_data.get('id'), build_parts_id)
|
||||
# print("cond", build_part_data.get('id') == build_parts_id)
|
||||
# print("cond2", part_no in results_list)
|
||||
|
||||
if build_part_data.get('id') == build_parts_id and part_no in results_list:
|
||||
similarity = float(best_similarity.similarity)
|
||||
best_similarity.set_similarity((1 - similarity) / 2 + similarity)
|
||||
# print("similarity", best_similarity.similarity)
|
||||
break
|
||||
return best_similarity
|
||||
|
||||
|
||||
def parse_comment_for_company_or_individual(comment: str):
|
||||
# Extract all necessary data from Companies within the session
|
||||
companies_data = []
|
||||
with Companies.new_session() as session:
|
||||
Companies.set_session(session)
|
||||
companies_list = Companies.query.filter(Companies.commercial_type != "Commercial").all()
|
||||
|
||||
# Extract all needed data from companies while session is active
|
||||
for company in companies_list:
|
||||
company_data = {
|
||||
'id': company.id,
|
||||
'public_name': unidecode(company.public_name)
|
||||
}
|
||||
# Add any other needed attributes
|
||||
if hasattr(company, 'commercial_type'):
|
||||
company_data['commercial_type'] = company.commercial_type
|
||||
companies_data.append(company_data)
|
||||
|
||||
# Process the data outside the session
|
||||
comment = unidecode(comment)
|
||||
best_similarity = Similarity(similarity=0.0, garbage="", cleaned="")
|
||||
|
||||
for company_data in companies_data:
|
||||
search_word = company_data['public_name']
|
||||
garbage_words = get_garbage_words(comment, search_word)
|
||||
cleaned_comment = remove_garbage_words(comment, garbage_words)
|
||||
similarity_ratio = textdistance.jaro_winkler(cleaned_comment, search_word)
|
||||
|
||||
if similarity_ratio > float(best_similarity.similarity):
|
||||
best_similarity = Similarity(similarity=similarity_ratio, garbage=garbage_words, cleaned=cleaned_comment)
|
||||
# Store company ID instead of the ORM object
|
||||
best_similarity.set_company_id(company_data['id'])
|
||||
best_similarity.set_found_from("Customer Public Name")
|
||||
# print('cleaned_comment', cleaned_comment, '\n', 'search_word', search_word, '\n', 'best_similarity', best_similarity, '\n',
|
||||
# 'company name', company_data['public_name'], '\n', 'similarity_ratio', similarity_ratio, '\n', 'garbage_words', garbage_words)
|
||||
|
||||
return best_similarity
|
||||
|
||||
|
||||
def parse_comment_for_living_space(iban: str, comment: str, living_space_dict: dict = None) -> Similarity:
|
||||
comment = unidecode(comment)
|
||||
best_similarity = Similarity(similarity=0.0, garbage="", cleaned="")
|
||||
|
||||
if not iban in living_space_dict:
|
||||
return best_similarity
|
||||
|
||||
for person_data in living_space_dict[iban]["people"]:
|
||||
# Extract name components from dictionary
|
||||
first_name = unidecode(person_data.get('name', '')).upper()
|
||||
last_name = unidecode(person_data.get('surname', '')).upper()
|
||||
search_word_list = [
|
||||
remove_spaces_from_string("".join([f"{first_name} {last_name}"])),
|
||||
remove_spaces_from_string("".join([f"{last_name} {first_name}"])),
|
||||
]
|
||||
# We don't have middle_name in our dictionary, so skip that part
|
||||
|
||||
cleaned_comment = unidecode(comment).upper()
|
||||
for search_word in search_word_list:
|
||||
if garbage_words := get_garbage_words(comment, unidecode(search_word)):
|
||||
garbage_words = unidecode(garbage_words).upper()
|
||||
cleaned_comment = unidecode(remove_garbage_words(comment, garbage_words)).upper()
|
||||
similarity_ratio = textdistance.jaro_winkler(cleaned_comment, str(search_word).upper())
|
||||
if len(cleaned_comment) < len(f"{first_name}{last_name}"):
|
||||
continue
|
||||
if cleaned_comment and 0.9 < similarity_ratio <= 1:
|
||||
pass
|
||||
# print("cleaned comment dict", dict(
|
||||
# garbage=garbage_words, cleaned=cleaned_comment, similarity=similarity_ratio,
|
||||
# search_word=search_word, comment=comment, last_similarity=float(best_similarity.similarity))
|
||||
# )
|
||||
|
||||
if similarity_ratio > float(best_similarity.similarity):
|
||||
# Use person_id from the dictionary data
|
||||
person_id = person_data['id']
|
||||
for living_space_data in living_space_dict[iban]["living_space"]:
|
||||
if living_space_data.get('person_id') == person_id:
|
||||
# Create a dictionary with living space data
|
||||
living_space_info = {
|
||||
'id': living_space_data.get('id'),
|
||||
'build_parts_id': living_space_data.get('build_parts_id'),
|
||||
'name': living_space_data.get('name')
|
||||
}
|
||||
best_similarity.set_living_space_id(living_space_data.get('id'))
|
||||
best_similarity.set_found_from("Person Name")
|
||||
best_similarity.set_similarity(similarity_ratio)
|
||||
best_similarity.set_garbage(garbage_words)
|
||||
best_similarity.set_cleaned(cleaned_comment)
|
||||
best_similarity.set_customer_id(person_data['id'])
|
||||
# Find matching build part
|
||||
build_parts_id = living_space_data.get('build_parts_id')
|
||||
for build_part_data in living_space_dict[iban]["build_parts"]:
|
||||
if build_part_data.get('id') == build_parts_id:
|
||||
best_similarity.set_build_part_id(build_part_data.get('id'))
|
||||
break
|
||||
return best_similarity
|
||||
23
ServicesBank/Finder/BuildLivingSpace/regex_func.py
Normal file
23
ServicesBank/Finder/BuildLivingSpace/regex_func.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import re
|
||||
|
||||
from difflib import get_close_matches
|
||||
from configs import AccountConfig
|
||||
|
||||
|
||||
def word_straighten(word, ref_list, threshold=0.8):
|
||||
matches = get_close_matches(word, ref_list, n=1, cutoff=threshold)
|
||||
return matches[0] if matches else word
|
||||
|
||||
|
||||
def category_finder(text, output_template="{kategori} {numara}"):
|
||||
categories = AccountConfig.CATEGORIES
|
||||
result = {category: [] for category in categories}
|
||||
for category, patterns in categories.items():
|
||||
words = re.split(r"\W+", text)
|
||||
straighten_words = [word_straighten(word, patterns) for word in words]
|
||||
straighten_text = " ".join(straighten_words)
|
||||
pattern = r"(?:\b|\s|^)(?:" + "|".join(map(re.escape, patterns)) + r")(?:\s*|:|\-|\#)*(\d+)(?:\b|$)"
|
||||
if founds_list := re.findall(pattern, straighten_text, re.IGNORECASE):
|
||||
list_of_output = [output_template.format(kategori=category, numara=num) for num in founds_list]
|
||||
result[category].extend([i for i in list_of_output if str(i).replace(" ", "")])
|
||||
return result
|
||||
26
ServicesBank/Finder/BuildLivingSpace/run_app.sh
Normal file
26
ServicesBank/Finder/BuildLivingSpace/run_app.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Source the environment file directly
|
||||
. /env.sh
|
||||
|
||||
# Re-export all variables to ensure they're available to the Python script
|
||||
export POSTGRES_USER
|
||||
export POSTGRES_PASSWORD
|
||||
export POSTGRES_DB
|
||||
export POSTGRES_HOST
|
||||
export POSTGRES_PORT
|
||||
export POSTGRES_ENGINE
|
||||
export POSTGRES_POOL_PRE_PING
|
||||
export POSTGRES_POOL_SIZE
|
||||
export POSTGRES_MAX_OVERFLOW
|
||||
export POSTGRES_POOL_RECYCLE
|
||||
export POSTGRES_POOL_TIMEOUT
|
||||
export POSTGRES_ECHO
|
||||
|
||||
# Python environment variables
|
||||
export PYTHONPATH
|
||||
export PYTHONUNBUFFERED
|
||||
export PYTHONDONTWRITEBYTECODE
|
||||
|
||||
# Run the Python script
|
||||
/usr/local/bin/python /runner.py
|
||||
187
ServicesBank/Finder/BuildLivingSpace/runner.py
Normal file
187
ServicesBank/Finder/BuildLivingSpace/runner.py
Normal file
@@ -0,0 +1,187 @@
|
||||
from Schemas import AccountRecords, BuildIbans, BuildDecisionBook, Build, BuildLivingSpace, People, OccupantTypes, BuildParts, BuildDecisionBookPayments, ApiEnumDropdown
|
||||
from Controllers.Postgres.engine import get_session_factory
|
||||
from parser import parse_comment_with_name, parse_comment_with_name_iban_description
|
||||
from validations import Similarity
|
||||
import re
|
||||
import time
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
def account_save_search_result(account_record_main_session: AccountRecords, similarity_result: Similarity):
|
||||
|
||||
with AccountRecords.new_session() as session:
|
||||
AccountRecords.set_session(session)
|
||||
BuildParts.set_session(session)
|
||||
Build.set_session(session)
|
||||
BuildLivingSpace.set_session(session)
|
||||
People.set_session(session)
|
||||
|
||||
account_record = AccountRecords.query.filter_by(id=account_record_main_session.id).first()
|
||||
if not account_record:
|
||||
# print(f"Could not find account record with ID {account_record_main_session.id}")
|
||||
return
|
||||
|
||||
company_id = getattr(similarity_result, 'company_id', None)
|
||||
living_space_id = getattr(similarity_result, 'living_space_id', None)
|
||||
build_part_id = getattr(similarity_result, 'build_part_id', None)
|
||||
customer_id = getattr(similarity_result, 'customer_id', None)
|
||||
|
||||
part, build, found_customer = None, None, None
|
||||
|
||||
if living_space_id:
|
||||
found_customer = BuildLivingSpace.query.get(living_space_id)
|
||||
if build_part_id:
|
||||
part = BuildParts.query.get(build_part_id)
|
||||
elif found_customer and hasattr(found_customer, 'build_parts_id'):
|
||||
part = BuildParts.query.filter_by(id=found_customer.build_parts_id, human_livable=True).first()
|
||||
|
||||
if part:
|
||||
build = Build.query.filter_by(id=part.build_id).first()
|
||||
|
||||
account_record.similarity = similarity_result.similarity
|
||||
account_record.found_from = similarity_result.found_from
|
||||
account_record.company_id = company_id
|
||||
if company_id:
|
||||
company = People.query.get(company_id)
|
||||
account_record.company_uu_id = getattr(company, "uu_id", None) if company else None
|
||||
|
||||
account_record.build_parts_id = getattr(part, "id", None)
|
||||
account_record.build_parts_uu_id = getattr(part, "uu_id", None) if part else None
|
||||
|
||||
if not account_record.build_id and build:
|
||||
account_record.build_id = getattr(build, "id", None)
|
||||
account_record.build_uu_id = getattr(build, "uu_id", None)
|
||||
|
||||
account_record.living_space_id = living_space_id
|
||||
if found_customer:
|
||||
account_record.living_space_uu_id = getattr(found_customer, "uu_id", None)
|
||||
if customer_id:
|
||||
account_record.send_person_id = customer_id
|
||||
customer = People.query.get(customer_id)
|
||||
if customer:
|
||||
account_record.send_person_uu_id = getattr(customer, "uu_id", None)
|
||||
account_record.save()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Start timer
|
||||
start_time = time.time()
|
||||
print("Build Living Space Service is running...")
|
||||
|
||||
new_session = get_session_factory()
|
||||
flat_id_list = []
|
||||
build_living_space_dict = {}
|
||||
found_list = []
|
||||
account_records_ibans = []
|
||||
|
||||
with OccupantTypes.new_session() as occupant_types_session:
|
||||
OccupantTypes.set_session(occupant_types_session)
|
||||
flat_resident = OccupantTypes.query.filter_by(occupant_category_type="FL", occupant_code="FL-RES").first()
|
||||
flat_owner = OccupantTypes.query.filter_by(occupant_category_type="FL", occupant_code="FL-OWN").first()
|
||||
flat_tenant = OccupantTypes.query.filter_by(occupant_category_type="FL", occupant_code="FL-TEN").first()
|
||||
flat_represent = OccupantTypes.query.filter_by(occupant_category_type="FL", occupant_code="FL-REP").first()
|
||||
flat_id_list = [flat_resident.id, flat_owner.id, flat_tenant.id, flat_represent.id]
|
||||
|
||||
AccountRecords.set_session(new_session)
|
||||
BuildLivingSpace.set_session(new_session)
|
||||
BuildParts.set_session(new_session)
|
||||
People.set_session(new_session)
|
||||
account_records_ibans = AccountRecords.query.filter(AccountRecords.build_decision_book_id != None).distinct(AccountRecords.iban).all()
|
||||
|
||||
for account_records_iban in account_records_ibans:
|
||||
if account_records_iban.iban not in build_living_space_dict:
|
||||
build_parts = BuildParts.query.filter_by(build_id=account_records_iban.build_id, human_livable=True).all()
|
||||
build_parts_data = []
|
||||
for bp in build_parts:
|
||||
bp_dict = {'id': bp.id, 'build_id': bp.build_id, 'human_livable': bp.human_livable}
|
||||
if hasattr(bp, 'part_no'):
|
||||
bp_dict['part_no'] = bp.part_no
|
||||
build_parts_data.append(bp_dict)
|
||||
|
||||
living_spaces = BuildLivingSpace.query.filter(
|
||||
BuildLivingSpace.build_parts_id.in_([bp.id for bp in build_parts]), BuildLivingSpace.occupant_type_id.in_(flat_id_list),
|
||||
).all()
|
||||
living_spaces_data = []
|
||||
for ls in living_spaces:
|
||||
ls_dict = {'id': ls.id, 'build_parts_id': ls.build_parts_id, 'occupant_type_id': ls.occupant_type_id, 'person_id': ls.person_id}
|
||||
if hasattr(ls, 'name'):
|
||||
ls_dict['name'] = ls.name
|
||||
living_spaces_data.append(ls_dict)
|
||||
|
||||
living_spaces_people = [ls.person_id for ls in living_spaces if ls.person_id]
|
||||
people_list = People.query.filter(People.id.in_(living_spaces_people)).all()
|
||||
people_data = []
|
||||
for p in people_list:
|
||||
p_dict = {'id': p.id, 'name': p.firstname, 'surname': p.surname, 'middle_name': p.middle_name}
|
||||
p_dict['full_name'] = f"{p.firstname} {p.surname}".strip()
|
||||
people_data.append(p_dict)
|
||||
build_living_space_dict[str(account_records_iban.iban)] = {"people": people_data, "living_space": living_spaces_data, "build_parts": build_parts_data}
|
||||
|
||||
with AccountRecords.new_session() as query_session:
|
||||
AccountRecords.set_session(query_session)
|
||||
account_record_ids = [record.id for record in AccountRecords.query.filter(AccountRecords.build_decision_book_id != None).order_by(AccountRecords.bank_date.desc()).all()]
|
||||
|
||||
for account_id in account_record_ids:
|
||||
with AccountRecords.new_session() as record_session:
|
||||
AccountRecords.set_session(record_session)
|
||||
account_record = AccountRecords.query.filter_by(id=account_id).first()
|
||||
if not account_record:
|
||||
continue
|
||||
account_iban = account_record.iban
|
||||
account_process_comment = account_record.process_comment
|
||||
account_currency_value = account_record.currency_value
|
||||
account_similarity_value = float(account_record.similarity or 0.0)
|
||||
account_build_id = account_record.build_id
|
||||
|
||||
account_data = {"id": account_id, "iban": account_iban, "process_comment": account_process_comment, "currency_value": account_currency_value,
|
||||
"similarity": account_similarity_value, "build_id": account_build_id}
|
||||
|
||||
try:
|
||||
similarity_result = parse_comment_with_name(account_record=account_data, living_space_dict=build_living_space_dict)
|
||||
fs = float(similarity_result.similarity)
|
||||
|
||||
if fs >= 0.8 and fs >= account_similarity_value:
|
||||
found_list.append(similarity_result)
|
||||
with AccountRecords.new_session() as save_session:
|
||||
AccountRecords.set_session(save_session)
|
||||
fresh_account = AccountRecords.query.filter_by(id=account_id).first()
|
||||
if fresh_account:
|
||||
account_save_search_result(account_record_main_session=fresh_account, similarity_result=similarity_result)
|
||||
print("POSITIVE SIMILARITY RESULT:", {
|
||||
'similarity': similarity_result.similarity, 'found_from': similarity_result.found_from, 'garbage': similarity_result.garbage,
|
||||
'cleaned': similarity_result.cleaned, 'company_id': getattr(similarity_result, 'company_id', None),
|
||||
'living_space_id': getattr(similarity_result, 'living_space_id', None), 'build_part_id': getattr(similarity_result, 'build_part_id', None),
|
||||
'customer_id': getattr(similarity_result, 'customer_id', None)
|
||||
})
|
||||
else:
|
||||
similarity_result = parse_comment_with_name_iban_description(account_record=account_data)
|
||||
fs = float(similarity_result.similarity)
|
||||
|
||||
if fs >= 0.8 and fs > account_similarity_value:
|
||||
found_list.append(similarity_result)
|
||||
with AccountRecords.new_session() as save_session:
|
||||
AccountRecords.set_session(save_session)
|
||||
fresh_account = AccountRecords.query.filter_by(id=account_id).first()
|
||||
if fresh_account:
|
||||
account_save_search_result(account_record_main_session=fresh_account, similarity_result=similarity_result)
|
||||
print("NEGATIVE SIMILARITY RESULT:", {
|
||||
'similarity': similarity_result.similarity, 'found_from': similarity_result.found_from,
|
||||
'garbage': similarity_result.garbage, 'cleaned': similarity_result.cleaned,
|
||||
'company_id': getattr(similarity_result, 'company_id', None), 'living_space_id': getattr(similarity_result, 'living_space_id', None),
|
||||
'build_part_id': getattr(similarity_result, 'build_part_id', None), 'customer_id': getattr(similarity_result, 'customer_id', None)
|
||||
})
|
||||
except Exception as e:
|
||||
# print(f"Error processing account {account_id}: {e}")
|
||||
continue
|
||||
|
||||
# Calculate elapsed time
|
||||
end_time = time.time()
|
||||
elapsed_time = end_time - start_time
|
||||
elapsed_formatted = str(timedelta(seconds=int(elapsed_time)))
|
||||
|
||||
print("Account Records Search : ", len(found_list), "/", len(account_record_ids))
|
||||
print(f"Total runtime: {elapsed_formatted} (HH:MM:SS)")
|
||||
print(f"Total seconds: {elapsed_time:.2f}")
|
||||
|
||||
new_session.close()
|
||||
print("Build Living Space Service is finished...")
|
||||
49
ServicesBank/Finder/BuildLivingSpace/validations.py
Normal file
49
ServicesBank/Finder/BuildLivingSpace/validations.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from Schemas import BuildLivingSpace, People
|
||||
|
||||
class Similarity:
|
||||
|
||||
def __init__(self, similarity: float, garbage: str, cleaned: str):
|
||||
self.similarity = similarity
|
||||
self.garbage = garbage
|
||||
self.cleaned = cleaned
|
||||
self.living_space = None
|
||||
self.living_space_id = None
|
||||
self.build_part_id = None
|
||||
self.company = None
|
||||
self.company_id = None
|
||||
self.found_from = None
|
||||
self.send_person_id = None
|
||||
self.customer_id = None
|
||||
|
||||
def set_customer_id(self, customer_id: int):
|
||||
self.customer_id = customer_id
|
||||
|
||||
def set_living_space(self, living_space: BuildLivingSpace):
|
||||
self.living_space = living_space
|
||||
|
||||
def set_company(self, company: People):
|
||||
self.company = company
|
||||
|
||||
def set_found_from(self, found_from: str):
|
||||
self.found_from = found_from
|
||||
|
||||
def set_send_person_id(self, send_person_id: int):
|
||||
self.send_person_id = send_person_id
|
||||
|
||||
def set_similarity(self, similarity: float):
|
||||
self.similarity = similarity
|
||||
|
||||
def set_garbage(self, garbage: str):
|
||||
self.garbage = garbage
|
||||
|
||||
def set_cleaned(self, cleaned: str):
|
||||
self.cleaned = cleaned
|
||||
|
||||
def set_living_space_id(self, living_space_id: int):
|
||||
self.living_space_id = living_space_id
|
||||
|
||||
def set_build_part_id(self, build_part_id: int):
|
||||
self.build_part_id = build_part_id
|
||||
|
||||
def set_company_id(self, company_id: int):
|
||||
self.company_id = company_id
|
||||
93
ServicesBank/Finder/DecisionBook/.dockerignore
Normal file
93
ServicesBank/Finder/DecisionBook/.dockerignore
Normal file
@@ -0,0 +1,93 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
|
||||
# CI
|
||||
.codeclimate.yml
|
||||
.travis.yml
|
||||
.taskcluster.yml
|
||||
|
||||
# Docker
|
||||
docker-compose.yml
|
||||
service_app/Dockerfile
|
||||
.docker
|
||||
.dockerignore
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
**/__pycache__/
|
||||
**/*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
service_app/env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Virtual environment
|
||||
service_app/.env
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
|
||||
# Python mode for VIM
|
||||
.ropeproject
|
||||
**/.ropeproject
|
||||
|
||||
# Vim swap files
|
||||
**/*.swp
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
test_application/
|
||||
|
||||
|
||||
33
ServicesBank/Finder/DecisionBook/Dockerfile
Normal file
33
ServicesBank/Finder/DecisionBook/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Set Python path to include app directory
|
||||
ENV PYTHONPATH=/ PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Install system dependencies and Poetry
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/* && pip install --no-cache-dir poetry
|
||||
|
||||
# Copy Poetry configuration
|
||||
COPY /pyproject.toml ./pyproject.toml
|
||||
|
||||
# Configure Poetry and install dependencies with optimizations
|
||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi --no-root --only main && pip cache purge && rm -rf ~/.cache/pypoetry
|
||||
|
||||
# Install cron for scheduling tasks
|
||||
RUN apt-get update && apt-get install -y cron
|
||||
|
||||
# Copy application code
|
||||
COPY /ServicesBank/Finder/DecisionBook /
|
||||
COPY /ServicesApi/Schemas /Schemas
|
||||
COPY /ServicesApi/Controllers /Controllers
|
||||
|
||||
# Create log file to grab cron logs
|
||||
RUN touch /var/log/cron.log
|
||||
|
||||
# Make entrypoint script executable
|
||||
RUN chmod +x /entrypoint.sh
|
||||
RUN chmod +x /run_app.sh
|
||||
|
||||
# Use entrypoint script to update run_app.sh with environment variables and start cron
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
3
ServicesBank/Finder/DecisionBook/README.md
Normal file
3
ServicesBank/Finder/DecisionBook/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Docs of Finder
|
||||
|
||||
Finds people, living spaces, companies from AccountRecords
|
||||
30
ServicesBank/Finder/DecisionBook/entrypoint.sh
Normal file
30
ServicesBank/Finder/DecisionBook/entrypoint.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Create environment file that will be available to cron jobs
|
||||
echo "POSTGRES_USER=\"$POSTGRES_USER\"" >> /env.sh
|
||||
echo "POSTGRES_PASSWORD=\"$POSTGRES_PASSWORD\"" >> /env.sh
|
||||
echo "POSTGRES_DB=\"$POSTGRES_DB\"" >> /env.sh
|
||||
echo "POSTGRES_HOST=\"$POSTGRES_HOST\"" >> /env.sh
|
||||
echo "POSTGRES_PORT=$POSTGRES_PORT" >> /env.sh
|
||||
echo "POSTGRES_ENGINE=\"$POSTGRES_ENGINE\"" >> /env.sh
|
||||
echo "POSTGRES_POOL_PRE_PING=\"$POSTGRES_POOL_PRE_PING\"" >> /env.sh
|
||||
echo "POSTGRES_POOL_SIZE=$POSTGRES_POOL_SIZE" >> /env.sh
|
||||
echo "POSTGRES_MAX_OVERFLOW=$POSTGRES_MAX_OVERFLOW" >> /env.sh
|
||||
echo "POSTGRES_POOL_RECYCLE=$POSTGRES_POOL_RECYCLE" >> /env.sh
|
||||
echo "POSTGRES_POOL_TIMEOUT=$POSTGRES_POOL_TIMEOUT" >> /env.sh
|
||||
echo "POSTGRES_ECHO=\"$POSTGRES_ECHO\"" >> /env.sh
|
||||
|
||||
# Add Python environment variables
|
||||
echo "PYTHONPATH=/" >> /env.sh
|
||||
echo "PYTHONUNBUFFERED=1" >> /env.sh
|
||||
echo "PYTHONDONTWRITEBYTECODE=1" >> /env.sh
|
||||
|
||||
# Make the environment file available to cron
|
||||
echo "*/15 * * * * /run_app.sh >> /var/log/cron.log 2>&1" > /tmp/crontab_list
|
||||
crontab /tmp/crontab_list
|
||||
|
||||
# Start cron
|
||||
cron
|
||||
|
||||
# Tail the log file
|
||||
tail -f /var/log/cron.log
|
||||
26
ServicesBank/Finder/DecisionBook/run_app.sh
Normal file
26
ServicesBank/Finder/DecisionBook/run_app.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Source the environment file directly
|
||||
. /env.sh
|
||||
|
||||
# Re-export all variables to ensure they're available to the Python script
|
||||
export POSTGRES_USER
|
||||
export POSTGRES_PASSWORD
|
||||
export POSTGRES_DB
|
||||
export POSTGRES_HOST
|
||||
export POSTGRES_PORT
|
||||
export POSTGRES_ENGINE
|
||||
export POSTGRES_POOL_PRE_PING
|
||||
export POSTGRES_POOL_SIZE
|
||||
export POSTGRES_MAX_OVERFLOW
|
||||
export POSTGRES_POOL_RECYCLE
|
||||
export POSTGRES_POOL_TIMEOUT
|
||||
export POSTGRES_ECHO
|
||||
|
||||
# Python environment variables
|
||||
export PYTHONPATH
|
||||
export PYTHONUNBUFFERED
|
||||
export PYTHONDONTWRITEBYTECODE
|
||||
|
||||
# env >> /var/log/cron.log
|
||||
/usr/local/bin/python /runner.py
|
||||
29
ServicesBank/Finder/DecisionBook/runner.py
Normal file
29
ServicesBank/Finder/DecisionBook/runner.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from sqlalchemy import cast, Date
|
||||
from Schemas import AccountRecords, BuildIbans, BuildDecisionBook
|
||||
|
||||
|
||||
def account_records_find_decision_book(session):
|
||||
AccountRecords.set_session(session)
|
||||
BuildIbans.set_session(session)
|
||||
BuildDecisionBook.set_session(session)
|
||||
|
||||
created_ibans, iban_build_dict = [], {}
|
||||
filter_account_records = AccountRecords.build_id != None, AccountRecords.build_decision_book_id == None
|
||||
account_records_list: list[AccountRecords] = AccountRecords.query.filter(*filter_account_records).order_by(AccountRecords.bank_date.desc()).all()
|
||||
for account_record in account_records_list:
|
||||
if found_iban := BuildIbans.query.filter(BuildIbans.iban == account_record.iban).first():
|
||||
if found_decision_book := BuildDecisionBook.query.filter(
|
||||
BuildDecisionBook.build_id == found_iban.build_id,
|
||||
cast(BuildDecisionBook.expiry_starts, Date) <= cast(account_record.bank_date, Date),
|
||||
cast(BuildDecisionBook.expiry_ends, Date) >= cast(account_record.bank_date, Date),
|
||||
).first():
|
||||
account_record.build_decision_book_id = found_decision_book.id
|
||||
account_record.build_decision_book_uu_id = str(found_decision_book.uu_id)
|
||||
account_record.save()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("DecisionBook Service is running...")
|
||||
with AccountRecords.new_session() as session:
|
||||
account_records_find_decision_book(session)
|
||||
print("DecisionBook Service is finished...")
|
||||
32
ServicesBank/Finder/Dockerfile
Normal file
32
ServicesBank/Finder/Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Set Python path to include app directory
|
||||
ENV PYTHONPATH=/ PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Install system dependencies and Poetry
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/* && pip install --no-cache-dir poetry
|
||||
|
||||
# Copy Poetry configuration
|
||||
COPY /pyproject.toml ./pyproject.toml
|
||||
|
||||
# Configure Poetry and install dependencies with optimizations
|
||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi --no-root --only main && pip cache purge && rm -rf ~/.cache/pypoetry
|
||||
|
||||
# Install cron for scheduling tasks
|
||||
RUN apt-get update && apt-get install -y cron
|
||||
|
||||
# Copy application code
|
||||
COPY /ServicesBank/Finder /
|
||||
COPY /ServicesApi/Schemas /Schemas
|
||||
COPY /ServicesApi/Controllers /Controllers
|
||||
|
||||
# Create log file to grab cron logs
|
||||
RUN touch /var/log/cron.log
|
||||
|
||||
# Make entrypoint script executable
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# Use entrypoint script to update run_app.sh with environment variables and start cron
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
93
ServicesBank/Finder/Payment/.dockerignore
Normal file
93
ServicesBank/Finder/Payment/.dockerignore
Normal file
@@ -0,0 +1,93 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
|
||||
# CI
|
||||
.codeclimate.yml
|
||||
.travis.yml
|
||||
.taskcluster.yml
|
||||
|
||||
# Docker
|
||||
docker-compose.yml
|
||||
service_app/Dockerfile
|
||||
.docker
|
||||
.dockerignore
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
**/__pycache__/
|
||||
**/*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
service_app/env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Virtual environment
|
||||
service_app/.env
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
|
||||
# Python mode for VIM
|
||||
.ropeproject
|
||||
**/.ropeproject
|
||||
|
||||
# Vim swap files
|
||||
**/*.swp
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
test_application/
|
||||
|
||||
|
||||
33
ServicesBank/Finder/Payment/Dockerfile
Normal file
33
ServicesBank/Finder/Payment/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Set Python path to include app directory
|
||||
ENV PYTHONPATH=/ PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Install system dependencies and Poetry
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/* && pip install --no-cache-dir poetry
|
||||
|
||||
# Copy Poetry configuration
|
||||
COPY /pyproject.toml ./pyproject.toml
|
||||
|
||||
# Configure Poetry and install dependencies with optimizations
|
||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi --no-root --only main && pip cache purge && rm -rf ~/.cache/pypoetry
|
||||
|
||||
# Install cron for scheduling tasks
|
||||
RUN apt-get update && apt-get install -y cron
|
||||
|
||||
# Copy application code
|
||||
COPY /ServicesBank/Finder/Payment /
|
||||
COPY /ServicesApi/Schemas /Schemas
|
||||
COPY /ServicesApi/Controllers /Controllers
|
||||
|
||||
# Create log file to grab cron logs
|
||||
RUN touch /var/log/cron.log
|
||||
|
||||
# Make entrypoint script executable
|
||||
RUN chmod +x /entrypoint.sh
|
||||
RUN chmod +x /run_app.sh
|
||||
|
||||
# Use entrypoint script to update run_app.sh with environment variables and start cron
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
44
ServicesBank/Finder/Payment/README.md
Normal file
44
ServicesBank/Finder/Payment/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Docs of Finder
|
||||
|
||||
Finds people, living spaces, companies from AccountRecords
|
||||
|
||||
start_time = perf_counter()
|
||||
end_time = perf_counter()
|
||||
elapsed = end_time - start_time
|
||||
print(f'{elapsed:.3f} : seconds')
|
||||
print('shallow_copy_list', len(shallow_copy_list))
|
||||
"""
|
||||
"""
|
||||
1. Stage (Incoming Money)
|
||||
# BuildDecisionBookPayments are reverse records of AccountRecords
|
||||
AccountRecords.approved_record == True
|
||||
AccountRecords.living_space_id is not None
|
||||
# AccountRecords.receive_debit = Credit Receiver (Incoming money from client) / Debit Sender (Debt to be paid by system)
|
||||
|
||||
1.1
|
||||
AccountRecords.currency_value > 0 Received Money Transaction +
|
||||
AccountRecords.currency_value > AccountRecords.remainder_balance () You have extra money in system account
|
||||
Money consumed => AccountRecords.currency_value != abs(AccountRecords.remainder_balance) singluar iban
|
||||
Some payment done but money not yet all money is consumed => AccountRecords.currency_value + AccountRecords.remainder_balance != 0
|
||||
|
||||
|
||||
AccountRecords.currency_value = AccountRecords.remainder_balance (There is no money that individual has in system)
|
||||
AccountRecords.bank_date (Date money arrived)
|
||||
AccountRecords.process_type (Type of bank transaction)
|
||||
|
||||
1.2
|
||||
AccountRecords.currency_value < 0 Sent Money Transaction -
|
||||
|
||||
|
||||
2. Stage (Payment Match Process)
|
||||
Parse : BuildDecisionBookPayments.process_date (Year / Month / Day / Time)
|
||||
BuildDecisionBookPayments.account_records_id == None ( Payment is not assigned to any account record)
|
||||
BuildDecisionBookPayments.payment_types_id == debit_enum.id (Payment type is debit)
|
||||
|
||||
2.1 Check current month has any payment to due Payment Month == Money Arrived Month
|
||||
2.2 Check previous months has any payment to due Payment Month < Money Arrived Month
|
||||
|
||||
3. Stage (Payment Assignment Process)
|
||||
Do payment set left money to account record as AccountRecords.remainder_balance
|
||||
|
||||
|
||||
774
ServicesBank/Finder/Payment/draft/completed.py
Normal file
774
ServicesBank/Finder/Payment/draft/completed.py
Normal file
@@ -0,0 +1,774 @@
|
||||
import arrow
|
||||
import time
|
||||
|
||||
from decimal import Decimal
|
||||
from datetime import datetime, timedelta
|
||||
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown
|
||||
|
||||
from time import perf_counter
|
||||
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_
|
||||
from Controllers.Postgres.engine import get_session_factory
|
||||
#from ServicesApi.Schemas.account.account import AccountRecords
|
||||
#from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments
|
||||
|
||||
|
||||
class BuildDuesTypes:
|
||||
|
||||
def __init__(self):
|
||||
self.debit: ApiEnumDropdownShallowCopy = None
|
||||
self.add_debit: ApiEnumDropdownShallowCopy = None
|
||||
self.renovation: ApiEnumDropdownShallowCopy = None
|
||||
self.lawyer_expence: ApiEnumDropdownShallowCopy = None
|
||||
self.service_fee: ApiEnumDropdownShallowCopy = None
|
||||
self.information: ApiEnumDropdownShallowCopy = None
|
||||
|
||||
|
||||
class ApiEnumDropdownShallowCopy:
|
||||
id: int
|
||||
uuid: str
|
||||
enum_class: str
|
||||
key: str
|
||||
value: str
|
||||
|
||||
def __init__(self, id: int, uuid: str, enum_class: str, key: str, value: str):
|
||||
self.id = id
|
||||
self.uuid = uuid
|
||||
self.enum_class = enum_class
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
|
||||
def get_enums_from_database():
|
||||
build_dues_types = BuildDuesTypes()
|
||||
with ApiEnumDropdown.new_session() as session:
|
||||
|
||||
ApiEnumDropdown.set_session(session)
|
||||
|
||||
debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-D").first() # Debit
|
||||
add_debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-A").first() # Add Debit
|
||||
renovation_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-R").first() # Renovation
|
||||
late_payment_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-L").first() # Lawyer expence
|
||||
service_fee_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-S").first() # Service fee
|
||||
information_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-I").first() # Information
|
||||
|
||||
build_dues_types.debit = ApiEnumDropdownShallowCopy(
|
||||
debit_enum_shallow.id, str(debit_enum_shallow.uu_id), debit_enum_shallow.enum_class, debit_enum_shallow.key, debit_enum_shallow.value
|
||||
)
|
||||
build_dues_types.add_debit = ApiEnumDropdownShallowCopy(
|
||||
add_debit_enum_shallow.id, str(add_debit_enum_shallow.uu_id), add_debit_enum_shallow.enum_class, add_debit_enum_shallow.key, add_debit_enum_shallow.value
|
||||
)
|
||||
build_dues_types.renovation = ApiEnumDropdownShallowCopy(
|
||||
renovation_enum_shallow.id, str(renovation_enum_shallow.uu_id), renovation_enum_shallow.enum_class, renovation_enum_shallow.key, renovation_enum_shallow.value
|
||||
)
|
||||
build_dues_types.lawyer_expence = ApiEnumDropdownShallowCopy(
|
||||
late_payment_enum_shallow.id, str(late_payment_enum_shallow.uu_id), late_payment_enum_shallow.enum_class, late_payment_enum_shallow.key, late_payment_enum_shallow.value
|
||||
)
|
||||
build_dues_types.service_fee = ApiEnumDropdownShallowCopy(
|
||||
service_fee_enum_shallow.id, str(service_fee_enum_shallow.uu_id), service_fee_enum_shallow.enum_class, service_fee_enum_shallow.key, service_fee_enum_shallow.value
|
||||
)
|
||||
build_dues_types.information = ApiEnumDropdownShallowCopy(
|
||||
information_enum_shallow.id, str(information_enum_shallow.uu_id), information_enum_shallow.enum_class, information_enum_shallow.key, information_enum_shallow.value
|
||||
)
|
||||
return [build_dues_types.debit, build_dues_types.lawyer_expence, build_dues_types.add_debit, build_dues_types.renovation, build_dues_types.service_fee, build_dues_types.information]
|
||||
|
||||
|
||||
def generate_total_paid_amount_for_spesific_build_part_id(build_parts_id: int, session):
|
||||
"""
|
||||
Calculate the total amount paid for a specific build part ID.
|
||||
|
||||
Args:
|
||||
build_parts_id: The build part ID to calculate payments for
|
||||
session: Database session
|
||||
|
||||
Returns:
|
||||
float: The total amount paid (absolute value)
|
||||
"""
|
||||
payment_query = session.query(func.sum(BuildDecisionBookPayments.payment_amount)).filter(
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
BuildDecisionBookPayments.account_is_debit == False,
|
||||
cast(BuildDecisionBookPayments.process_date, Date) >= '2022-01-01'
|
||||
).scalar()
|
||||
return payment_query if payment_query is not None else 0
|
||||
|
||||
|
||||
def generate_total_debt_amount_for_spesific_build_part_id(build_parts_id: int, session):
|
||||
|
||||
# Use SQLAlchemy's func.sum to calculate the total debts
|
||||
# For total debt, we want to include ALL debts, both processed and unprocessed
|
||||
result = session.query(
|
||||
func.sum(BuildDecisionBookPayments.payment_amount)
|
||||
).filter(
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
BuildDecisionBookPayments.account_is_debit == True,
|
||||
cast(BuildDecisionBookPayments.process_date, Date) >= '2022-01-01'
|
||||
).scalar()
|
||||
|
||||
# Return 0 if no debts found, otherwise return the absolute value of the sum
|
||||
return abs(result) if result is not None else 0
|
||||
|
||||
|
||||
def generate_total_amount_that_user_has_in_account(account_record: AccountRecords, session):
|
||||
# Get total amount that user has in account
|
||||
result = session.query(
|
||||
func.sum(AccountRecords.currency_value)
|
||||
).filter(
|
||||
AccountRecords.build_parts_id == account_record.build_parts_id,
|
||||
AccountRecords.currency_value > 0,
|
||||
cast(AccountRecords.bank_date, Date) >= '2022-01-01'
|
||||
).scalar()
|
||||
|
||||
# Return 0 if no payments found, otherwise return the absolute value of the sum
|
||||
return abs(result)
|
||||
|
||||
|
||||
def get_unpaid_debts(build_parts_id: int, session, debit_type, date_query: tuple):
|
||||
"""Find BuildDecisionBookPayments entries where the debt has NOT been fully paid for a specific debit type.
|
||||
|
||||
This function identifies payments where the sum of payments is less than the debit amount,
|
||||
meaning the debt has not been fully closed.
|
||||
|
||||
Args:
|
||||
build_parts_id: The build part ID to check
|
||||
session: Database session
|
||||
debit_type: The specific debit type to check
|
||||
date_query: Tuple of date filters to apply
|
||||
|
||||
Returns:
|
||||
list: List of unpaid debt entries (full BuildDecisionBookPayments objects)
|
||||
|
||||
query:
|
||||
SELECT
|
||||
bpf.ref_id,
|
||||
bpf.process_date,
|
||||
ABS(COALESCE(SUM(bpf.payment_amount), 0)) AS total_payments
|
||||
FROM public.build_decision_book_payments AS bpf
|
||||
GROUP BY
|
||||
bpf.ref_id,
|
||||
bpf.process_date
|
||||
HAVING ABS(COALESCE(SUM(bpf.payment_amount), 0)) > 0
|
||||
order by bpf.process_date
|
||||
"""
|
||||
# Create a subquery for the payment sums without executing it separately
|
||||
payment_sums_subquery = select(
|
||||
BuildDecisionBookPayments.ref_id,
|
||||
BuildDecisionBookPayments.process_date,
|
||||
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)).label("total_payments")
|
||||
).filter(
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
||||
*date_query
|
||||
).group_by(
|
||||
BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date
|
||||
).having(
|
||||
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)) > 0
|
||||
).order_by(BuildDecisionBookPayments.process_date.desc())
|
||||
|
||||
# Use the subquery directly in the main query
|
||||
# query_results = session.query(BuildDecisionBookPayments).filter(
|
||||
# BuildDecisionBookPayments.ref_id.in_(payment_sums_subquery),
|
||||
# BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
# BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
||||
# ).order_by(BuildDecisionBookPayments.process_date.desc())
|
||||
payment_sums = session.execute(payment_sums_subquery).all()
|
||||
payment_sums_list = []
|
||||
for item in payment_sums:
|
||||
payment_sums_list.append({"ref_id": item[0], "process_date": item[1], "total_payments": item[2]})
|
||||
return payment_sums
|
||||
|
||||
|
||||
def _print_debt_details(debt, session):
|
||||
"""Helper function to print detailed information about an unpaid debt.
|
||||
|
||||
Args:
|
||||
debt: The BuildDecisionBookPayments object representing the debt
|
||||
session: Database session
|
||||
"""
|
||||
# Get the sum of payments for this debt
|
||||
payments_sum = session.query(
|
||||
func.sum(BuildDecisionBookPayments.payment_amount)
|
||||
).filter(
|
||||
BuildDecisionBookPayments.ref_id == debt.ref_id,
|
||||
BuildDecisionBookPayments.account_is_debit == False
|
||||
).scalar() or 0
|
||||
|
||||
# Calculate remaining amount
|
||||
debit_amount = abs(debt.payment_amount)
|
||||
remaining = debit_amount - abs(payments_sum)
|
||||
payment_percentage = (abs(payments_sum) / debit_amount) * 100 if debit_amount > 0 else 0
|
||||
|
||||
# Format the date for display
|
||||
date_str = debt.process_date.strftime('%Y-%m-%d') if debt.process_date else 'Unknown date'
|
||||
|
||||
|
||||
def analyze_payment_function():
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
# Set session for all models
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
order_pay = get_enums_from_database()
|
||||
# Get distinct build_parts_id values from account records with positive currency_value
|
||||
# This avoids redundant processing of the same build_parts_id
|
||||
distinct_build_parts = session.query(
|
||||
distinct(AccountRecords.build_parts_id)
|
||||
).filter(
|
||||
AccountRecords.build_parts_id.isnot(None),
|
||||
AccountRecords.currency_value > 0,
|
||||
AccountRecords.bank_date >= '2022-01-01'
|
||||
).order_by(AccountRecords.build_parts_id.desc()).all()
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
for build_part_id_tuple in distinct_build_parts:
|
||||
build_part_id = build_part_id_tuple[0] # Extract the ID from the tuple
|
||||
process_date = datetime.now()
|
||||
last_date_of_process_date = datetime(process_date.year, process_date.month, 1) - timedelta(days=1)
|
||||
first_date_of_process_date = datetime(process_date.year, process_date.month, 1)
|
||||
|
||||
print(f"\n{'=' * 50}")
|
||||
print(f"ACCOUNT ANALYSIS FOR BUILD PART ID: {build_part_id}")
|
||||
print(f"{'=' * 50}")
|
||||
|
||||
# Calculate total paid amount for this build_part_id
|
||||
total_amount_paid = generate_total_paid_amount_for_spesific_build_part_id(build_part_id, session)
|
||||
|
||||
# Calculate total debt amount for this build_part_id
|
||||
total_debt_amount = generate_total_debt_amount_for_spesific_build_part_id(build_part_id, session)
|
||||
|
||||
# Get total amount in account for this build_part_id
|
||||
account_record = AccountRecords()
|
||||
account_record.build_parts_id = build_part_id
|
||||
total_amount_in_account = generate_total_amount_that_user_has_in_account(account_record, session)
|
||||
|
||||
# Calculate remaining amount to be paid
|
||||
amount_need_to_paid = total_debt_amount - total_amount_paid
|
||||
total_amount_that_user_need_to_transfer = abs(amount_need_to_paid) - abs(total_amount_in_account)
|
||||
|
||||
# Print summary with clear descriptions
|
||||
print(f"PAYMENT SUMMARY:")
|
||||
print(f" • Total debt amount: {total_debt_amount:,.2f} TL")
|
||||
print(f" • Amount already paid: {total_amount_paid:,.2f} TL")
|
||||
print(f" • Remaining debt to be collected: {amount_need_to_paid:,.2f} TL")
|
||||
print(f" • Current account balance: {total_amount_in_account:,.2f} TL")
|
||||
|
||||
if total_amount_that_user_need_to_transfer > 0:
|
||||
print(f" • Additional funds needed: {total_amount_that_user_need_to_transfer:,.2f} TL")
|
||||
elif amount_need_to_paid <= 0:
|
||||
print(f" • Account is fully paid with no outstanding debt")
|
||||
else:
|
||||
print(f" • Sufficient funds available to close all debt")
|
||||
# Show debt coverage percentage
|
||||
if total_debt_amount > 0:
|
||||
# Calculate current coverage (already paid)
|
||||
current_coverage_percentage = (total_amount_paid / total_debt_amount) * 100
|
||||
|
||||
# Calculate potential coverage (including available funds)
|
||||
potential_coverage = min(100, ((total_amount_paid + total_amount_in_account) / total_debt_amount) * 100)
|
||||
|
||||
# Display both percentages
|
||||
print(f" • Current debt coverage: {current_coverage_percentage:.2f}%")
|
||||
print(f" • Potential debt coverage with available funds: {potential_coverage:.2f}%")
|
||||
|
||||
# Analyze unpaid debts for each payment type
|
||||
print("\nUNPAID DEBTS ANALYSIS BY PAYMENT TYPE:")
|
||||
for payment_type in order_pay:
|
||||
# Get unpaid debts for current month
|
||||
date_query_current = (
|
||||
BuildDecisionBookPayments.process_date >= first_date_of_process_date,
|
||||
BuildDecisionBookPayments.process_date <= process_date
|
||||
)
|
||||
date_query_previous = (
|
||||
BuildDecisionBookPayments.process_date < first_date_of_process_date,
|
||||
)
|
||||
|
||||
current_unpaid_debts = get_unpaid_debts(build_parts_id=build_part_id, session=session, debit_type=payment_type, date_query=date_query_current)
|
||||
|
||||
# Get unpaid debts from previous months
|
||||
previous_unpaid_debts = get_unpaid_debts(build_parts_id=build_part_id, session=session, debit_type=payment_type, date_query=date_query_previous)
|
||||
|
||||
# Calculate totals
|
||||
current_total = sum(abs(debt[2]) for debt in current_unpaid_debts)
|
||||
previous_total = sum(abs(debt[2]) for debt in previous_unpaid_debts)
|
||||
grand_total = current_total + previous_total
|
||||
|
||||
# Print summary for this payment type
|
||||
if current_unpaid_debts or previous_unpaid_debts:
|
||||
print(f" • {payment_type.key}: Total unpaid: {grand_total:,.2f} TL")
|
||||
|
||||
# Current month details
|
||||
if current_unpaid_debts:
|
||||
print(f" - Current month: {len(current_unpaid_debts)} debts, {current_total:,.2f} TL")
|
||||
# Show details of each unpaid debt if there aren't too many
|
||||
# if len(current_unpaid_debts) <= 3:
|
||||
# for debt in current_unpaid_debts:
|
||||
# _print_debt_details(debt, session)
|
||||
|
||||
# Previous months details
|
||||
if previous_unpaid_debts:
|
||||
print(f" - Previous months: {len(previous_unpaid_debts)} debts, {previous_total:,.2f} TL")
|
||||
# Show details of each unpaid debt if there aren't too many
|
||||
# if len(previous_unpaid_debts) <= 3:
|
||||
# for debt in previous_unpaid_debts:
|
||||
# _print_debt_details(debt, session)
|
||||
else:
|
||||
print(f" • {payment_type.key}: All debts paid")
|
||||
print(f"{'=' * 50}\n")
|
||||
|
||||
|
||||
def close_payment_book(payment_row_book, account_record, value, session):
|
||||
"""Create a credit entry in BuildDecisionBookPayments to close a debt.
|
||||
|
||||
Args:
|
||||
payment_row_book: The debit entry to be paid
|
||||
account_record: The account record containing the funds
|
||||
value: The amount to pay
|
||||
session: Database session
|
||||
|
||||
Returns:
|
||||
The newly created payment record
|
||||
"""
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
# Create a new credit entry (payment)
|
||||
new_row = BuildDecisionBookPayments.create(
|
||||
ref_id=str(payment_row_book.uu_id),
|
||||
payment_plan_time_periods=payment_row_book.payment_plan_time_periods,
|
||||
period_time=payment_row_book.period_time,
|
||||
currency=payment_row_book.currency,
|
||||
account_records_id=account_record.id,
|
||||
account_records_uu_id=str(account_record.uu_id),
|
||||
build_parts_id=payment_row_book.build_parts_id,
|
||||
build_parts_uu_id=str(payment_row_book.build_parts_uu_id),
|
||||
payment_amount=abs(value), # Negative for credit entries
|
||||
payment_types_id=payment_row_book.payment_types_id,
|
||||
payment_types_uu_id=str(payment_row_book.payment_types_uu_id),
|
||||
process_date_m=payment_row_book.process_date.month,
|
||||
process_date_y=payment_row_book.process_date.year,
|
||||
process_date=payment_row_book.process_date,
|
||||
build_decision_book_item_id=payment_row_book.build_decision_book_item_id if payment_row_book.build_decision_book_item_id else None,
|
||||
build_decision_book_item_uu_id=str(payment_row_book.build_decision_book_item_uu_id) if payment_row_book.build_decision_book_item_uu_id else None,
|
||||
decision_book_project_id=payment_row_book.decision_book_project_id if payment_row_book.decision_book_project_id else None,
|
||||
decision_book_project_uu_id=str(payment_row_book.decision_book_project_uu_id) if payment_row_book.decision_book_project_uu_id else None,
|
||||
is_confirmed=True,
|
||||
account_is_debit=False,
|
||||
)
|
||||
|
||||
# Save the new payment record
|
||||
saved_row = new_row.save()
|
||||
# Update the original debt record to mark it as processed
|
||||
# payment_row_book.account_records_id = account_record.id
|
||||
# payment_row_book.account_records_uu_id = str(account_record.uu_id)
|
||||
# payment_row_book.save()
|
||||
# # Flush to ensure both records are saved to the database
|
||||
# session.flush()
|
||||
return saved_row
|
||||
|
||||
|
||||
def update_account_remainder_if_spent(account_record, ref_id: str, session):
|
||||
"""Update the remainder_balance of an account after spending money.
|
||||
|
||||
Args:
|
||||
account_record: The account record to update
|
||||
amount_spent: The amount spent in this transaction
|
||||
session: Database session
|
||||
|
||||
Returns:
|
||||
bool: True if all money is spent, False otherwise
|
||||
"""
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
sum_of_paid = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||
BuildDecisionBookPayments.account_records_id == account_record.id,
|
||||
BuildDecisionBookPayments.account_is_debit == False
|
||||
).scalar()
|
||||
|
||||
debit_row = BuildDecisionBookPayments.query.filter_by(ref_id=ref_id).first()
|
||||
account_record_to_update = AccountRecords.query.filter_by(id=account_record.id).first()
|
||||
account_record_to_update.remainder_balance = sum_of_paid
|
||||
account_record_to_update.save()
|
||||
# Get the current remainder balance
|
||||
if abs(sum_of_paid) == abs(account_record.currency_value):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def update_all_spent_accounts(session):
|
||||
"""Update remainder_balance for all accounts with payments.
|
||||
|
||||
This function finds account records in BuildDecisionBookPayments and updates
|
||||
their remainder_balance based on the sum of payments made, regardless of whether
|
||||
all funds have been spent or not.
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
"""
|
||||
with AccountRecords.new_session() as session:
|
||||
# Set sessions for models
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
# Get distinct account_records_id values from BuildDecisionBookPayments
|
||||
distinct_account_ids = session.query(BuildDecisionBookPayments.account_records_id).filter(
|
||||
BuildDecisionBookPayments.account_records_id.isnot(None),
|
||||
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments)
|
||||
).distinct().all()
|
||||
|
||||
updated_count = 0
|
||||
|
||||
for account_id_tuple in distinct_account_ids:
|
||||
account_id = account_id_tuple[0]
|
||||
|
||||
# Get the account record
|
||||
account = AccountRecords.query.filter_by(id=account_id).first()
|
||||
if not account or not account.build_parts_id or account.currency_value <= 0:
|
||||
continue
|
||||
|
||||
# Calculate the sum of payments made using this account
|
||||
# Note: payment_amount is negative for credit entries, so we need to use abs() to get the positive amount
|
||||
payment_query = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||
BuildDecisionBookPayments.account_records_id == account_id,
|
||||
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments)
|
||||
)
|
||||
|
||||
payment_sum = payment_query.scalar() or 0
|
||||
|
||||
# Update remainder_balance for ALL accounts, regardless of payment_sum value
|
||||
threshold = Decimal('0.01')
|
||||
fully_spent = payment_sum >= abs(account.currency_value) - threshold
|
||||
status = "All funds spent" if fully_spent else "Partial payment"
|
||||
|
||||
# Store the positive value in remainder_balance
|
||||
account.remainder_balance = payment_sum
|
||||
account.save()
|
||||
updated_count += 1
|
||||
|
||||
print(f"\nTotal accounts updated: {updated_count}")
|
||||
|
||||
|
||||
# def find_amount_to_pay_by_ref_id(ref_id, session):
|
||||
# """Calculate the remaining amount to pay for a specific debt reference ID.
|
||||
|
||||
# Args:
|
||||
# ref_id: The reference ID of the debt (this is the uu_id of the debt record)
|
||||
# session: Database session
|
||||
|
||||
# Returns:
|
||||
# float: The remaining amount to pay
|
||||
# """
|
||||
# # Get the original debt amount - the debt is identified by its uu_id which is passed as ref_id
|
||||
# debit = BuildDecisionBookPayments.query.filter(
|
||||
# BuildDecisionBookPayments.uu_id == ref_id,
|
||||
# BuildDecisionBookPayments.account_is_debit == True
|
||||
# ).first()
|
||||
# if not debit:
|
||||
# return 0 # No debit found, nothing to pay
|
||||
|
||||
# debit_amount = abs(debit.payment_amount) # Ensure positive value for debit amount
|
||||
|
||||
# # Get the sum of payments already made for this debt
|
||||
# # The ref_id in credit records points to the uu_id of the original debit
|
||||
# # Note: payment_amount is negative for credit entries, so we use abs() to get positive values
|
||||
# credit_amount = session.query(
|
||||
# func.sum(func.abs(BuildDecisionBookPayments.payment_amount))
|
||||
# ).filter(
|
||||
# BuildDecisionBookPayments.ref_id == str(ref_id),
|
||||
# BuildDecisionBookPayments.account_is_debit == False
|
||||
# ).scalar() or 0
|
||||
# # Calculate remaining amount to pay
|
||||
# remaining = abs(debit_amount) - abs(credit_amount)
|
||||
# # Ensure we don't return negative values
|
||||
# if remaining < 0:
|
||||
# return 0
|
||||
|
||||
# return remaining
|
||||
|
||||
|
||||
def do_payments_of_this_month():
|
||||
"""Process payments for the current month's unpaid debts.
|
||||
This function retrieves account records with available funds and processes
|
||||
payments for current month's unpaid debts in order of payment type priority.
|
||||
"""
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
|
||||
# Set session for all models
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
# Get payment types in priority order
|
||||
payment_type_list = get_enums_from_database()
|
||||
|
||||
# Get account records with positive currency_value and available funds
|
||||
account_records = AccountRecords.query.filter(
|
||||
AccountRecords.build_parts_id.isnot(None),
|
||||
AccountRecords.currency_value > 0,
|
||||
or_(
|
||||
AccountRecords.remainder_balance.is_(None),
|
||||
AccountRecords.remainder_balance < AccountRecords.currency_value
|
||||
),
|
||||
AccountRecords.bank_date >= '2022-01-01'
|
||||
).order_by(AccountRecords.build_parts_id.desc()).all()
|
||||
|
||||
payments_made = 0
|
||||
total_amount_paid = 0
|
||||
process_date = datetime.now()
|
||||
first_date_of_process_date = datetime(process_date.year, process_date.month, 1)
|
||||
last_date_of_process_date_ = datetime(process_date.year, process_date.month, 1) + timedelta(days=31)
|
||||
last_date_of_process_date = datetime(last_date_of_process_date_.year, last_date_of_process_date_.month, 1) - timedelta(days=1)
|
||||
|
||||
# Current month date filter
|
||||
date_query_tuple = (
|
||||
BuildDecisionBookPayments.process_date >= first_date_of_process_date,
|
||||
BuildDecisionBookPayments.process_date <= last_date_of_process_date
|
||||
)
|
||||
|
||||
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
||||
# Update all accounts with spent funds but zero remainder_balance
|
||||
update_all_spent_accounts(session)
|
||||
|
||||
for account_record in account_records:
|
||||
# Initialize variables for this account
|
||||
money_in_account = abs(account_record.currency_value) - abs(account_record.remainder_balance)
|
||||
money_paid = 0
|
||||
build_parts_id = account_record.build_parts_id
|
||||
# Process each payment type in priority order
|
||||
for payment_type in payment_type_list:
|
||||
# Check if all money is spent and update remainder balance if needed
|
||||
if fund_finished(money_paid, money_in_account):
|
||||
break
|
||||
# Get unpaid debts for this payment type
|
||||
unpaid_debts = get_unpaid_debts(build_parts_id=build_parts_id, session=session, debit_type=payment_type, date_query=date_query_tuple)
|
||||
# Process each unpaid debt
|
||||
for debt in unpaid_debts:
|
||||
if not money_in_account > 0:
|
||||
update_account_remainder_if_spent(account_record, debt[0], session)
|
||||
break
|
||||
# Check if all money is spent and update remainder balance if needed
|
||||
# if fund_finished(money_paid, money_in_account):
|
||||
# break
|
||||
# Calculate remaining amount to pay
|
||||
# Use debt.uu_id as the ref_id, not debt.ref_id
|
||||
debt_to_pay = debt[2]
|
||||
# Skip if nothing to pay
|
||||
if debt_to_pay <= 0:
|
||||
continue
|
||||
# Determine amount to pay based on available funds
|
||||
payment_amount = min(debt_to_pay, money_in_account)
|
||||
# Make payment
|
||||
debt_to_copy = BuildDecisionBookPayments.query.filter_by(ref_id=debt[0]).first()
|
||||
close_payment_book(debt_to_copy, account_record, payment_amount, session)
|
||||
# Update counters
|
||||
money_in_account -= payment_amount
|
||||
money_paid += payment_amount
|
||||
payments_made += 1
|
||||
total_amount_paid += payment_amount
|
||||
if not money_in_account > 0:
|
||||
update_account_remainder_if_spent(account_record, debt[0], session)
|
||||
break
|
||||
|
||||
# if money_paid > 0:
|
||||
# update_account_remainder_if_spent(account_record, money_paid, session)
|
||||
|
||||
# Update all accounts with spent funds but zero remainder_balance
|
||||
update_all_spent_accounts(session)
|
||||
|
||||
# Commit all changes to the database
|
||||
session.commit()
|
||||
|
||||
# Print summary
|
||||
print("\nCURRENT MONTH PAYMENT SUMMARY:")
|
||||
print(f"Total payments made: {payments_made}")
|
||||
print(f"Total amount paid: {total_amount_paid:,.2f} TL")
|
||||
|
||||
|
||||
def do_payments_of_previos_months():
|
||||
"""Process payments for previous months' unpaid debts.
|
||||
|
||||
This function retrieves account records with available funds and processes
|
||||
payments for previous months' unpaid debts in order of payment type priority.
|
||||
"""
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
|
||||
# Set session for all models
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
# Get payment types in priority order
|
||||
payment_type_list = get_enums_from_database()
|
||||
|
||||
# Get account records with positive currency_value and available funds
|
||||
account_query = AccountRecords.query.filter(
|
||||
AccountRecords.build_parts_id.isnot(None),
|
||||
AccountRecords.currency_value > 0,
|
||||
AccountRecords.remainder_balance < AccountRecords.currency_value,
|
||||
AccountRecords.bank_date >= '2022-01-01'
|
||||
).order_by(AccountRecords.bank_date.desc())
|
||||
|
||||
account_records = account_query.all()
|
||||
|
||||
payments_made = 0
|
||||
total_amount_paid = 0
|
||||
# process_date = datetime.now()
|
||||
# first_date_of_process_date = datetime(process_date.year, process_date.month, 1)
|
||||
|
||||
# Previous months date filter
|
||||
# date_query_tuple = (BuildDecisionBookPayments.process_date < first_date_of_process_date, )
|
||||
fund_finished = lambda money_spend, money_in_account: money_spend >= money_in_account
|
||||
|
||||
# Update all accounts with spent funds but zero remainder_balance
|
||||
update_all_spent_accounts(session)
|
||||
|
||||
for account_record in account_records:
|
||||
# Initialize variables for this account
|
||||
process_date_begins = datetime(account_record.bank_date.year, account_record.bank_date.month, 1)
|
||||
process_date_ends_ = datetime(account_record.bank_date.year, account_record.bank_date.month, 1)+ timedelta(days=31)
|
||||
process_date_ends = datetime(process_date_ends_.year, process_date_ends_.month, 1)- timedelta(days=1)
|
||||
|
||||
date_query_tuple = (BuildDecisionBookPayments.process_date >= process_date_begins, BuildDecisionBookPayments.process_date <= process_date_ends)
|
||||
|
||||
money_in_account = abs(account_record.currency_value) - abs(account_record.remainder_balance)
|
||||
money_paid = 0
|
||||
build_parts_id = account_record.build_parts_id
|
||||
# Process each payment type in priority order
|
||||
for payment_type in payment_type_list:
|
||||
# Check if all money is spent and update remainder balance if needed
|
||||
if fund_finished(money_paid, money_in_account):
|
||||
break
|
||||
# Get unpaid debts for this payment type
|
||||
unpaid_debts = get_unpaid_debts(build_parts_id=build_parts_id, session=session, debit_type=payment_type, date_query=date_query_tuple)
|
||||
if not len(unpaid_debts) > 0:
|
||||
process_date = datetime.now()
|
||||
process_date_ends = datetime(process_date.year, process_date.month, 1)
|
||||
date_query_tuple = (BuildDecisionBookPayments.process_date < process_date_ends, )
|
||||
unpaid_debts = get_unpaid_debts(build_parts_id=build_parts_id, session=session, debit_type=payment_type, date_query=date_query_tuple)
|
||||
|
||||
# Process each unpaid debt
|
||||
for debt in unpaid_debts:
|
||||
if not money_in_account > 0:
|
||||
update_account_remainder_if_spent(account_record, debt[0], session)
|
||||
break
|
||||
# Check if all money is spent and update remainder balance if needed
|
||||
if fund_finished(money_paid, money_in_account):
|
||||
break
|
||||
# Calculate remaining amount to pay
|
||||
debt_to_pay = debt[2]
|
||||
# Skip if nothing to pay
|
||||
if debt_to_pay <= 0:
|
||||
continue
|
||||
# Determine amount to pay based on available funds
|
||||
payment_amount = min(debt_to_pay, money_in_account)
|
||||
# Make payment
|
||||
try:
|
||||
# Create payment record and update original debt
|
||||
debt_to_copy = BuildDecisionBookPayments.query.filter_by(ref_id=debt[0]).first()
|
||||
new_payment = close_payment_book(debt_to_copy, account_record, payment_amount, session)
|
||||
# Verify the payment was created
|
||||
if new_payment and new_payment.id:
|
||||
# Update counters
|
||||
money_in_account -= payment_amount
|
||||
money_paid += payment_amount
|
||||
payments_made += 1
|
||||
total_amount_paid += payment_amount
|
||||
# Flush changes to ensure they are visible in subsequent queries
|
||||
session.flush()
|
||||
else:
|
||||
session.rollback()
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Debt : {debt[0]} -> Exception: {e}")
|
||||
session.rollback()
|
||||
continue
|
||||
if not money_in_account > 0:
|
||||
update_account_remainder_if_spent(account_record, debt[0], session)
|
||||
break
|
||||
# Update all accounts with spent funds but zero remainder_balance
|
||||
update_all_spent_accounts(session)
|
||||
|
||||
# Commit all changes to the database
|
||||
session.commit()
|
||||
|
||||
# Print summary
|
||||
print("\nPREVIOUS MONTHS PAYMENT SUMMARY:")
|
||||
print(f"Total payments made: {payments_made}")
|
||||
print(f"Total amount paid: {total_amount_paid:,.2f} TL")
|
||||
|
||||
|
||||
# /draft/draft-first-work.py
|
||||
if __name__ == "__main__":
|
||||
start_time = perf_counter()
|
||||
|
||||
print("\n===== PROCESSING PAYMENTS =====\n")
|
||||
print("Starting payment processing at:", datetime.now())
|
||||
|
||||
# Process payments for current month first
|
||||
print("\n1. Processing current month payments...")
|
||||
do_payments_of_this_month()
|
||||
|
||||
# Then process payments for previous months with remaining funds
|
||||
print("\n2. Processing previous months payments...")
|
||||
attempt = 4
|
||||
while True:
|
||||
total_debit = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.account_is_debit == True).count()
|
||||
total_credit = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.account_is_debit == False).count()
|
||||
if total_debit != total_credit:
|
||||
do_payments_of_previos_months()
|
||||
attempt -= 1
|
||||
if attempt == 0:
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
print("\n===== PAYMENT PROCESSING COMPLETE =====\n")
|
||||
print("Payment processing completed at:", datetime.now())
|
||||
|
||||
# Analyze the payment situation after processing payments
|
||||
print("\n===== ANALYZING PAYMENT SITUATION AFTER PROCESSING =====\n")
|
||||
analyze_payment_function()
|
||||
|
||||
end_time = perf_counter()
|
||||
print(f"\n{end_time - start_time:.3f} : seconds")
|
||||
|
||||
|
||||
# # Create a subquery to get the sum of payments for each debit's uu_id
|
||||
# # For credit entries, ref_id points to the original debit's uu_id
|
||||
# payment_sums = session.query(
|
||||
# BuildDecisionBookPayments.ref_id.label('original_debt_id'),
|
||||
# func.sum(func.abs(BuildDecisionBookPayments.payment_amount)).label('payment_sum')
|
||||
# ).filter(
|
||||
# BuildDecisionBookPayments.account_is_debit == False # Credit entries only
|
||||
# ).group_by(BuildDecisionBookPayments.ref_id).subquery()
|
||||
|
||||
# # Main query to find debits with their payment sums
|
||||
# query = session.query(BuildDecisionBookPayments)
|
||||
|
||||
# # Join with payment sums - cast uu_id to string to match ref_id type
|
||||
# query = query.outerjoin(
|
||||
# payment_sums,
|
||||
# func.cast(BuildDecisionBookPayments.uu_id, String) == payment_sums.c.original_debt_id
|
||||
# )
|
||||
|
||||
# # Filter for debits of the specified build part and payment type
|
||||
# query = query.filter(
|
||||
# BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
# BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
||||
# BuildDecisionBookPayments.account_is_debit == True, # Debit entries only
|
||||
# )
|
||||
|
||||
# # Apply date filters if provided
|
||||
# if date_query:
|
||||
# for date_filter in date_query:
|
||||
# query = query.filter(date_filter)
|
||||
|
||||
# # Filter for debits that are not fully paid
|
||||
# # (payment_sum < debit_amount or payment_sum is NULL)
|
||||
# query = query.filter(
|
||||
# or_(
|
||||
# payment_sums.c.payment_sum.is_(None),
|
||||
# func.coalesce(payment_sums.c.payment_sum, 0) < func.abs(BuildDecisionBookPayments.payment_amount)
|
||||
# )
|
||||
# )
|
||||
|
||||
# # Execute the query and return the results
|
||||
# results = query.order_by(BuildDecisionBookPayments.process_date).all()s
|
||||
188
ServicesBank/Finder/Payment/draft/debug-payment.py
Normal file
188
ServicesBank/Finder/Payment/draft/debug-payment.py
Normal file
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
# Debug script to test payment processing functions directly
|
||||
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
from time import perf_counter
|
||||
from sqlalchemy import func
|
||||
|
||||
# Import directly from the draft-first-work.py file in the same directory
|
||||
sys.path.insert(0, '/draft')
|
||||
|
||||
# Import the necessary functions and classes directly from the file
|
||||
from draft_first_work import * # Import everything for simplicity
|
||||
|
||||
def debug_find_unpaid_debts(build_parts_id, session):
|
||||
"""Find unpaid debts directly using raw SQL for debugging."""
|
||||
print(f"\nDEBUG: Finding unpaid debts for build part ID: {build_parts_id}")
|
||||
|
||||
# Set session for the model
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
# Get payment types
|
||||
payment_types = get_enums_from_database()
|
||||
|
||||
for payment_type in payment_types:
|
||||
print(f"\nChecking payment type: {payment_type.key}")
|
||||
|
||||
# Find debits that don't have account_records_id set (unpaid)
|
||||
query = session.query(BuildDecisionBookPayments).filter(
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
BuildDecisionBookPayments.payment_types_id == payment_type.id,
|
||||
BuildDecisionBookPayments.account_is_debit == True,
|
||||
BuildDecisionBookPayments.account_records_id.is_(None)
|
||||
)
|
||||
|
||||
# Print the SQL query
|
||||
print(f"SQL Query: {query}")
|
||||
|
||||
# Execute the query
|
||||
unpaid_debts = query.all()
|
||||
print(f"Found {len(unpaid_debts)} unpaid debts")
|
||||
|
||||
# Print details of each unpaid debt
|
||||
for i, debt in enumerate(unpaid_debts[:5]): # Limit to first 5 for brevity
|
||||
print(f" Debt {i+1}:")
|
||||
print(f" ID: {debt.id}")
|
||||
print(f" UUID: {debt.uu_id}")
|
||||
print(f" Amount: {abs(debt.payment_amount):,.2f} TL")
|
||||
print(f" Process Date: {debt.process_date}")
|
||||
print(f" Account Records ID: {debt.account_records_id}")
|
||||
|
||||
# Check if any payments have been made for this debt
|
||||
credit_query = session.query(
|
||||
func.sum(func.abs(BuildDecisionBookPayments.payment_amount))
|
||||
).filter(
|
||||
BuildDecisionBookPayments.ref_id == str(debt.uu_id),
|
||||
BuildDecisionBookPayments.account_is_debit == False
|
||||
)
|
||||
|
||||
credit_amount = credit_query.scalar() or 0
|
||||
print(f" Payments made: {credit_amount:,.2f} TL")
|
||||
print(f" Remaining to pay: {abs(debt.payment_amount) - credit_amount:,.2f} TL")
|
||||
|
||||
def debug_process_payment(build_parts_id, session):
|
||||
"""Process a single payment for debugging purposes."""
|
||||
print(f"\nDEBUG: Processing payment for build part ID: {build_parts_id}")
|
||||
|
||||
# Set session for all models
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
# Get payment types in priority order
|
||||
payment_type_list = get_enums_from_database()
|
||||
|
||||
# Get account records with positive currency_value
|
||||
account_query = AccountRecords.query.filter(
|
||||
AccountRecords.build_parts_id == build_parts_id,
|
||||
AccountRecords.currency_value > 0,
|
||||
AccountRecords.bank_date >= '2022-01-01'
|
||||
).order_by(AccountRecords.id.desc()).limit(5)
|
||||
|
||||
print(f"Account query: {account_query}")
|
||||
account_records = account_query.all()
|
||||
print(f"Found {len(account_records)} account records with funds")
|
||||
|
||||
# Print account details
|
||||
for i, account in enumerate(account_records):
|
||||
available_funds = abs(account.currency_value) - abs(account.remainder_balance)
|
||||
print(f" Account {i+1}: ID: {account.id}, Build Part ID: {account.build_parts_id}, Value: {account.currency_value:,.2f} TL, Available: {available_funds:,.2f} TL")
|
||||
|
||||
if not account_records:
|
||||
print("No account records found with funds. Cannot process payments.")
|
||||
return
|
||||
|
||||
# Use the first account with funds
|
||||
account_record = account_records[0]
|
||||
money_in_account = abs(account_record.currency_value) - abs(account_record.remainder_balance)
|
||||
|
||||
print(f"\nUsing account ID: {account_record.id} with available funds: {money_in_account:,.2f} TL")
|
||||
|
||||
# Get the first payment type
|
||||
payment_type = payment_type_list[0]
|
||||
print(f"Using payment type: {payment_type.key}")
|
||||
|
||||
# Find unpaid debts for this payment type
|
||||
query = session.query(BuildDecisionBookPayments).filter(
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
BuildDecisionBookPayments.payment_types_id == payment_type.id,
|
||||
BuildDecisionBookPayments.account_is_debit == True,
|
||||
BuildDecisionBookPayments.account_records_id.is_(None)
|
||||
).limit(1)
|
||||
|
||||
print(f"Unpaid debt query: {query}")
|
||||
unpaid_debt = query.first()
|
||||
|
||||
if not unpaid_debt:
|
||||
print(f"No unpaid debts found for payment type {payment_type.key}")
|
||||
return
|
||||
|
||||
print(f"\nFound unpaid debt:")
|
||||
print(f" ID: {unpaid_debt.id}")
|
||||
print(f" UUID: {unpaid_debt.uu_id}")
|
||||
print(f" Amount: {abs(unpaid_debt.payment_amount):,.2f} TL")
|
||||
|
||||
# Calculate amount to pay
|
||||
debt_amount = abs(unpaid_debt.payment_amount)
|
||||
payment_amount = min(debt_amount, money_in_account)
|
||||
|
||||
print(f"\nProcessing payment:")
|
||||
print(f" Debt amount: {debt_amount:,.2f} TL")
|
||||
print(f" Available funds: {money_in_account:,.2f} TL")
|
||||
print(f" Will pay: {payment_amount:,.2f} TL")
|
||||
|
||||
# Make payment
|
||||
try:
|
||||
print("Creating payment record...")
|
||||
before_state = session.query(BuildDecisionBookPayments).filter(
|
||||
BuildDecisionBookPayments.uu_id == unpaid_debt.uu_id
|
||||
).first()
|
||||
print(f"Before payment - account_records_id: {before_state.account_records_id}")
|
||||
|
||||
new_payment = close_payment_book(unpaid_debt, account_record, payment_amount, session)
|
||||
|
||||
# Verify the payment was created
|
||||
after_state = session.query(BuildDecisionBookPayments).filter(
|
||||
BuildDecisionBookPayments.uu_id == unpaid_debt.uu_id
|
||||
).first()
|
||||
print(f"After payment - account_records_id: {after_state.account_records_id}")
|
||||
|
||||
# Check if the new payment record was created
|
||||
if new_payment:
|
||||
print(f"New payment record created with ID: {new_payment.id}")
|
||||
print(f"New payment amount: {abs(new_payment.payment_amount):,.2f} TL")
|
||||
print(f"New payment ref_id: {new_payment.ref_id}")
|
||||
print(f"New payment account_is_debit: {new_payment.account_is_debit}")
|
||||
else:
|
||||
print("Failed to create new payment record")
|
||||
|
||||
# Commit the transaction
|
||||
session.commit()
|
||||
print("Transaction committed successfully")
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
print(f"Error making payment: {str(e)}")
|
||||
print("Transaction rolled back")
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_time = perf_counter()
|
||||
|
||||
print("\n===== PAYMENT PROCESSING DEBUG =====\n")
|
||||
|
||||
# Create a session
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
|
||||
# Set the build part ID to debug
|
||||
build_part_id = 14 # Change this to the build part ID you want to debug
|
||||
|
||||
# Find unpaid debts
|
||||
debug_find_unpaid_debts(build_part_id, session)
|
||||
|
||||
# Process a single payment
|
||||
debug_process_payment(build_part_id, session)
|
||||
|
||||
end_time = perf_counter()
|
||||
print(f"\nDebug completed in {end_time - start_time:.3f} seconds")
|
||||
104
ServicesBank/Finder/Payment/draft/debug_remainder.py
Normal file
104
ServicesBank/Finder/Payment/draft/debug_remainder.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from Schemas import AccountRecords, BuildDecisionBookPayments
|
||||
from Controllers.Postgres.engine import get_session_factory
|
||||
from sqlalchemy import func
|
||||
from decimal import Decimal
|
||||
|
||||
def debug_remainder_balance():
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
|
||||
# Set sessions for models
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("DEBUGGING REMAINDER BALANCE ISSUES")
|
||||
print("=" * 50)
|
||||
|
||||
# Get counts of accounts
|
||||
total_accounts = session.query(AccountRecords).filter(
|
||||
AccountRecords.currency_value > 0
|
||||
).count()
|
||||
|
||||
zero_remainder = session.query(AccountRecords).filter(
|
||||
AccountRecords.currency_value > 0,
|
||||
AccountRecords.remainder_balance == 0
|
||||
).count()
|
||||
|
||||
nonzero_remainder = session.query(AccountRecords).filter(
|
||||
AccountRecords.currency_value > 0,
|
||||
AccountRecords.remainder_balance != 0
|
||||
).count()
|
||||
|
||||
print(f"Total accounts with positive currency_value: {total_accounts}")
|
||||
print(f"Accounts with zero remainder_balance: {zero_remainder}")
|
||||
print(f"Accounts with non-zero remainder_balance: {nonzero_remainder}")
|
||||
|
||||
# Get distinct account IDs with payments
|
||||
distinct_account_ids = session.query(BuildDecisionBookPayments.account_records_id).filter(
|
||||
BuildDecisionBookPayments.account_records_id.isnot(None),
|
||||
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments)
|
||||
).distinct().all()
|
||||
|
||||
print(f"\nDistinct account IDs with payments: {len(distinct_account_ids)}")
|
||||
|
||||
# Sample some accounts with zero remainder_balance but have payments
|
||||
print("\nSampling accounts with zero remainder_balance:")
|
||||
sample_count = 0
|
||||
|
||||
for account_id_tuple in distinct_account_ids[:10]: # Check first 10 accounts with payments
|
||||
account_id = account_id_tuple[0]
|
||||
|
||||
# Get the account record
|
||||
account = AccountRecords.query.get(account_id)
|
||||
if not account or account.remainder_balance != 0:
|
||||
continue
|
||||
|
||||
# Calculate the sum of payments made using this account
|
||||
payment_sum = session.query(
|
||||
func.sum(BuildDecisionBookPayments.payment_amount)
|
||||
).filter(
|
||||
BuildDecisionBookPayments.account_records_id == account_id,
|
||||
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments)
|
||||
).scalar() or 0
|
||||
|
||||
print(f" Account {account_id}: Currency Value={abs(account.currency_value):,.2f} TL, Payments={abs(payment_sum):,.2f} TL, Remainder={account.remainder_balance}")
|
||||
sample_count += 1
|
||||
|
||||
if sample_count == 0:
|
||||
print(" No accounts found with zero remainder_balance that have payments")
|
||||
|
||||
# Now let's fix a sample of accounts
|
||||
print("\nFixing sample accounts with zero remainder_balance:")
|
||||
fixed_count = 0
|
||||
|
||||
for account_id_tuple in distinct_account_ids[:5]: # Fix first 5 accounts with payments
|
||||
account_id = account_id_tuple[0]
|
||||
|
||||
# Get the account record
|
||||
account = AccountRecords.query.get(account_id)
|
||||
if not account or not account.build_parts_id or account.currency_value <= 0:
|
||||
continue
|
||||
|
||||
# Calculate the sum of payments made using this account
|
||||
payment_sum = session.query(
|
||||
func.sum(BuildDecisionBookPayments.payment_amount)
|
||||
).filter(
|
||||
BuildDecisionBookPayments.account_records_id == account_id,
|
||||
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments)
|
||||
).scalar() or 0
|
||||
|
||||
old_remainder = account.remainder_balance
|
||||
|
||||
# Update remainder_balance for this account
|
||||
account.remainder_balance = payment_sum
|
||||
account.save()
|
||||
fixed_count += 1
|
||||
|
||||
print(f" Fixed Account {account_id}: Old remainder={old_remainder}, New remainder={account.remainder_balance:,.2f} TL")
|
||||
|
||||
print(f"\nTotal accounts fixed in this run: {fixed_count}")
|
||||
print("=" * 50)
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_remainder_balance()
|
||||
346
ServicesBank/Finder/Payment/draft/draft-first-work.py
Normal file
346
ServicesBank/Finder/Payment/draft/draft-first-work.py
Normal file
@@ -0,0 +1,346 @@
|
||||
import arrow
|
||||
from decimal import Decimal
|
||||
from datetime import datetime, timedelta
|
||||
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown
|
||||
|
||||
from time import perf_counter
|
||||
import time
|
||||
from sqlalchemy import cast, Date, String
|
||||
from Controllers.Postgres.engine import get_session_factory
|
||||
# from ServicesApi.Schemas.account.account import AccountRecords
|
||||
# from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments
|
||||
|
||||
# Helper function to calculate available funds
|
||||
def df_fund(account_income, total_paid):
|
||||
return abs(account_income) - abs(total_paid)
|
||||
|
||||
class BuildDuesTypes:
|
||||
|
||||
def __init__(self):
|
||||
self.debit: ApiEnumDropdownShallowCopy = None
|
||||
self.add_debit: ApiEnumDropdownShallowCopy = None
|
||||
self.renovation: ApiEnumDropdownShallowCopy = None
|
||||
self.lawyer_expence: ApiEnumDropdownShallowCopy = None
|
||||
self.service_fee: ApiEnumDropdownShallowCopy = None
|
||||
self.information: ApiEnumDropdownShallowCopy = None
|
||||
|
||||
|
||||
class ApiEnumDropdownShallowCopy:
|
||||
id: int
|
||||
uuid: str
|
||||
enum_class: str
|
||||
key: str
|
||||
value: str
|
||||
|
||||
def __init__(self, id: int, uuid: str, enum_class: str, key: str, value: str):
|
||||
self.id = id
|
||||
self.uuid = uuid
|
||||
self.enum_class = enum_class
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
|
||||
def find_master_payment_value(build_parts_id: int, process_date: datetime, session, debit_type):
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
book_payments_query = (
|
||||
BuildDecisionBookPayments.process_date_m == process_date.month, BuildDecisionBookPayments.process_date_y == process_date.year,
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id, BuildDecisionBookPayments.account_records_id.is_(None),
|
||||
BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
||||
)
|
||||
debit_row = BuildDecisionBookPayments.query.filter(*book_payments_query).order_by(BuildDecisionBookPayments.process_date.desc()).first()
|
||||
if not debit_row:
|
||||
return 0, None, None
|
||||
return abs(debit_row.payment_amount), debit_row, str(debit_row.ref_id)
|
||||
|
||||
|
||||
def calculate_paid_amount_for_master(ref_id: str, session, debit_amount):
|
||||
"""Calculate how much has been paid for a given payment reference.
|
||||
|
||||
Args:
|
||||
ref_id: The reference ID to check payments for
|
||||
session: Database session
|
||||
debit_amount: Original debit amount
|
||||
|
||||
Returns:
|
||||
float: Remaining amount to pay (debit_amount - total_paid)
|
||||
"""
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
paid_rows = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.ref_id == ref_id,
|
||||
BuildDecisionBookPayments.account_records_id.isnot(None),
|
||||
BuildDecisionBookPayments.account_is_debit == False
|
||||
).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
|
||||
if not paid_rows:
|
||||
return debit_amount
|
||||
|
||||
total_paid = sum([abs(paid_row.payment_amount) for paid_row in paid_rows])
|
||||
remaining = abs(debit_amount) - abs(total_paid)
|
||||
return remaining
|
||||
|
||||
|
||||
def find_master_payment_value_previous(build_parts_id: int, process_date: datetime, session, debit_type):
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
parse_process_date = datetime(process_date.year, process_date.month, 1) - timedelta(days=1)
|
||||
book_payments_query = (
|
||||
BuildDecisionBookPayments.process_date < parse_process_date,
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id, BuildDecisionBookPayments.account_records_id.is_(None),
|
||||
BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
||||
)
|
||||
debit_rows = BuildDecisionBookPayments.query.filter(*book_payments_query).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
return debit_rows
|
||||
|
||||
|
||||
def find_amount_to_pay(build_parts_id: int, process_date: datetime, session, debit_type):
|
||||
# debit -negative value that need to be pay
|
||||
debit, debit_row, debit_row_ref_id = find_master_payment_value(build_parts_id=build_parts_id, process_date=process_date, session=session, debit_type=debit_type)
|
||||
# Is there any payment done for this ref_id ?
|
||||
return calculate_paid_amount_for_master(ref_id=debit_row_ref_id, session=session, debit_amount=debit), debit_row
|
||||
|
||||
|
||||
def calculate_total_debt_for_account(build_parts_id: int, session):
|
||||
"""Calculate the total debt and total paid amount for an account regardless of process date."""
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
# Get all debits for this account
|
||||
all_debits = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
BuildDecisionBookPayments.account_records_id.is_(None),
|
||||
BuildDecisionBookPayments.account_is_debit == True
|
||||
).all()
|
||||
|
||||
total_debt = sum([abs(debit.payment_amount) for debit in all_debits])
|
||||
|
||||
# Get all payments for this account's debits
|
||||
total_paid = 0
|
||||
for debit in all_debits:
|
||||
payments = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.ref_id == debit.ref_id,
|
||||
BuildDecisionBookPayments.account_is_debit == False
|
||||
).all()
|
||||
|
||||
if payments:
|
||||
total_paid += sum([abs(payment.payment_amount) for payment in payments])
|
||||
|
||||
return total_debt, total_paid
|
||||
|
||||
|
||||
def refresh_book_payment(account_record: AccountRecords):
|
||||
"""Update the remainder_balance of an account record based on attached payments.
|
||||
|
||||
This function calculates the total of all payments attached to an account record
|
||||
and updates the remainder_balance field accordingly. The remainder_balance represents
|
||||
funds that have been received but not yet allocated to specific debits.
|
||||
|
||||
Args:
|
||||
account_record: The account record to update
|
||||
|
||||
Returns:
|
||||
float: The total payment amount
|
||||
"""
|
||||
total_payment = 0
|
||||
all_payment_attached = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.account_records_id == account_record.id,
|
||||
BuildDecisionBookPayments.account_is_debit == False,
|
||||
).all()
|
||||
|
||||
if all_payment_attached:
|
||||
total_payment = sum([abs(row.payment_amount) for row in all_payment_attached])
|
||||
|
||||
# Always update the remainder_balance, even if no payments are attached
|
||||
# This ensures we track unallocated funds properly
|
||||
old_balance = account_record.remainder_balance
|
||||
account_record.update(remainder_balance=total_payment)
|
||||
account_record.save()
|
||||
|
||||
return total_payment
|
||||
|
||||
|
||||
def close_payment_book(payment_row_book, account_record, value, session):
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
new_row = BuildDecisionBookPayments.create(
|
||||
ref_id=str(payment_row_book.uu_id),
|
||||
payment_plan_time_periods=payment_row_book.payment_plan_time_periods,
|
||||
period_time=payment_row_book.period_time,
|
||||
currency=payment_row_book.currency,
|
||||
account_records_id=account_record.id,
|
||||
account_records_uu_id=str(account_record.uu_id),
|
||||
build_parts_id=payment_row_book.build_parts_id,
|
||||
build_parts_uu_id=str(payment_row_book.build_parts_uu_id),
|
||||
payment_amount=value,
|
||||
payment_types_id=payment_row_book.payment_types_id,
|
||||
payment_types_uu_id=str(payment_row_book.payment_types_uu_id),
|
||||
process_date_m=payment_row_book.process_date.month,
|
||||
process_date_y=payment_row_book.process_date.year,
|
||||
process_date=payment_row_book.process_date,
|
||||
build_decision_book_item_id=payment_row_book.build_decision_book_item_id,
|
||||
build_decision_book_item_uu_id=str(payment_row_book.build_decision_book_item_uu_id),
|
||||
decision_book_project_id=payment_row_book.decision_book_project_id,
|
||||
decision_book_project_uu_id=str(payment_row_book.decision_book_project_uu_id),
|
||||
is_confirmed=True,
|
||||
account_is_debit=False,
|
||||
)
|
||||
return new_row.save()
|
||||
|
||||
|
||||
def get_enums_from_database():
|
||||
build_dues_types = BuildDuesTypes()
|
||||
with ApiEnumDropdown.new_session() as session:
|
||||
|
||||
ApiEnumDropdown.set_session(session)
|
||||
|
||||
debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-D").first() # Debit
|
||||
add_debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-A").first() # Add Debit
|
||||
renovation_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-R").first() # Renovation
|
||||
late_payment_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-L").first() # Lawyer expence
|
||||
service_fee_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-S").first() # Service fee
|
||||
information_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-I").first() # Information
|
||||
|
||||
build_dues_types.debit = ApiEnumDropdownShallowCopy(
|
||||
debit_enum_shallow.id, str(debit_enum_shallow.uu_id), debit_enum_shallow.enum_class, debit_enum_shallow.key, debit_enum_shallow.value
|
||||
)
|
||||
build_dues_types.add_debit = ApiEnumDropdownShallowCopy(
|
||||
add_debit_enum_shallow.id, str(add_debit_enum_shallow.uu_id), add_debit_enum_shallow.enum_class, add_debit_enum_shallow.key, add_debit_enum_shallow.value
|
||||
)
|
||||
build_dues_types.renovation = ApiEnumDropdownShallowCopy(
|
||||
renovation_enum_shallow.id, str(renovation_enum_shallow.uu_id), renovation_enum_shallow.enum_class, renovation_enum_shallow.key, renovation_enum_shallow.value
|
||||
)
|
||||
build_dues_types.lawyer_expence = ApiEnumDropdownShallowCopy(
|
||||
late_payment_enum_shallow.id, str(late_payment_enum_shallow.uu_id), late_payment_enum_shallow.enum_class, late_payment_enum_shallow.key, late_payment_enum_shallow.value
|
||||
)
|
||||
build_dues_types.service_fee = ApiEnumDropdownShallowCopy(
|
||||
service_fee_enum_shallow.id, str(service_fee_enum_shallow.uu_id), service_fee_enum_shallow.enum_class, service_fee_enum_shallow.key, service_fee_enum_shallow.value
|
||||
)
|
||||
build_dues_types.information = ApiEnumDropdownShallowCopy(
|
||||
information_enum_shallow.id, str(information_enum_shallow.uu_id), information_enum_shallow.enum_class, information_enum_shallow.key, information_enum_shallow.value
|
||||
)
|
||||
return [build_dues_types.debit, build_dues_types.lawyer_expence, build_dues_types.add_debit, build_dues_types.renovation, build_dues_types.service_fee, build_dues_types.information]
|
||||
|
||||
|
||||
def payment_function():
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
# Set session for all models
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
order_pay = get_enums_from_database()
|
||||
|
||||
# Get account records with positive currency_value regardless of remainder_balance
|
||||
# This ensures accounts with unallocated funds are processed
|
||||
account_records = AccountRecords.query.filter(
|
||||
AccountRecords.build_parts_id.isnot(None),
|
||||
AccountRecords.currency_value > 0,
|
||||
AccountRecords.bank_date >= '2022-01-01'
|
||||
).order_by(AccountRecords.build_parts_id.desc()).all()
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
for account_record in account_records:
|
||||
incoming_total_money = abs(account_record.currency_value)
|
||||
total_paid = refresh_book_payment(account_record)
|
||||
available_fund = df_fund(incoming_total_money, total_paid)
|
||||
|
||||
|
||||
# Calculate total debt and payment status for this account
|
||||
total_debt, already_paid = calculate_total_debt_for_account(account_record.build_parts_id, session)
|
||||
remaining_debt = total_debt - already_paid
|
||||
|
||||
# Skip accounts with no debt and zero remainder balance
|
||||
if remaining_debt <= 0 and account_record.remainder_balance == 0:
|
||||
continue
|
||||
|
||||
# Skip accounts with no available funds
|
||||
if not available_fund > 0.0:
|
||||
continue
|
||||
|
||||
|
||||
process_date = datetime.now()
|
||||
|
||||
# Try to pay current month first
|
||||
for debit_type in order_pay:
|
||||
amount_to_pay, debit_row = find_amount_to_pay(
|
||||
build_parts_id=account_record.build_parts_id,
|
||||
process_date=process_date,
|
||||
session=session,
|
||||
debit_type=debit_type
|
||||
)
|
||||
|
||||
if amount_to_pay > 0 and debit_row:
|
||||
if amount_to_pay >= available_fund:
|
||||
close_payment_book(
|
||||
payment_row_book=debit_row,
|
||||
account_record=account_record,
|
||||
value=available_fund,
|
||||
session=session
|
||||
)
|
||||
total_paid = refresh_book_payment(account_record)
|
||||
available_fund = df_fund(incoming_total_money, total_paid)
|
||||
else:
|
||||
close_payment_book(
|
||||
payment_row_book=debit_row,
|
||||
account_record=account_record,
|
||||
value=amount_to_pay,
|
||||
session=session
|
||||
)
|
||||
total_paid = refresh_book_payment(account_record)
|
||||
available_fund = df_fund(incoming_total_money, total_paid)
|
||||
|
||||
if not available_fund > 0.0:
|
||||
continue
|
||||
|
||||
# Try to pay previous unpaid debts
|
||||
should_continue = False
|
||||
for debit_type in order_pay:
|
||||
debit_rows = find_master_payment_value_previous(
|
||||
build_parts_id=account_record.build_parts_id,
|
||||
process_date=process_date,
|
||||
session=session,
|
||||
debit_type=debit_type
|
||||
)
|
||||
|
||||
if not debit_rows:
|
||||
continue
|
||||
|
||||
for debit_row in debit_rows:
|
||||
amount_to_pay = calculate_paid_amount_for_master(
|
||||
ref_id=debit_row.ref_id,
|
||||
session=session,
|
||||
debit_amount=debit_row.payment_amount
|
||||
)
|
||||
|
||||
# Skip if already fully paid
|
||||
if not amount_to_pay > 0:
|
||||
continue
|
||||
|
||||
if amount_to_pay >= available_fund:
|
||||
close_payment_book(
|
||||
payment_row_book=debit_row,
|
||||
account_record=account_record,
|
||||
value=available_fund,
|
||||
session=session
|
||||
)
|
||||
total_paid = refresh_book_payment(account_record)
|
||||
available_fund = df_fund(incoming_total_money, total_paid)
|
||||
should_continue = True
|
||||
break
|
||||
else:
|
||||
close_payment_book(
|
||||
payment_row_book=debit_row,
|
||||
account_record=account_record,
|
||||
value=amount_to_pay,
|
||||
session=session
|
||||
)
|
||||
total_paid = refresh_book_payment(account_record)
|
||||
available_fund = df_fund(incoming_total_money, total_paid)
|
||||
if should_continue or not available_fund > 0.0:
|
||||
break
|
||||
if not available_fund > 0.0:
|
||||
continue # Changed from break to continue to process next account record
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_time = perf_counter()
|
||||
payment_function()
|
||||
end_time = perf_counter()
|
||||
elapsed = end_time - start_time
|
||||
print(f'{elapsed:.3f} : seconds')
|
||||
493
ServicesBank/Finder/Payment/draft/draft.py
Normal file
493
ServicesBank/Finder/Payment/draft/draft.py
Normal file
@@ -0,0 +1,493 @@
|
||||
|
||||
class AccountRecordsShallowCopy:
|
||||
id: int
|
||||
uuid: str
|
||||
iban: str
|
||||
currency_value: Decimal
|
||||
remainder_balance: Decimal
|
||||
bank_date: datetime
|
||||
process_type: int
|
||||
receive_debit: int
|
||||
receive_debit_uuid: str
|
||||
living_space_id: int
|
||||
living_space_uuid: str
|
||||
build_id: int
|
||||
build_uuid: str
|
||||
build_parts_id: int
|
||||
build_parts_uuid: str
|
||||
|
||||
|
||||
class BuildDecisionBookPaymentsShallowCopy:
|
||||
|
||||
def __init__(self):
|
||||
self.id: int
|
||||
self.uuid: str
|
||||
self.payment_plan_time_periods: str
|
||||
self.process_date: datetime
|
||||
self.payment_amount: float
|
||||
self.currency: str
|
||||
self.payment_types_id: int
|
||||
self.payment_types_uu_id: str
|
||||
self.period_time: str
|
||||
self.process_date_y: int
|
||||
self.process_date_m: int
|
||||
self.build_decision_book_item_id: int
|
||||
self.build_decision_book_item_uu_id: str
|
||||
self.build_parts_id: int
|
||||
self.build_parts_uu_id: str
|
||||
self.decision_book_project_id: int
|
||||
self.decision_book_project_uu_id: str
|
||||
self.account_records_id: int
|
||||
self.account_records_uu_id: str
|
||||
|
||||
@classmethod
|
||||
def convert_row_to_shallow_copy(cls, row: BuildDecisionBookPayments):
|
||||
shallow_copy = cls()
|
||||
shallow_copy.id = row.id
|
||||
shallow_copy.uuid = str(row.uu_id)
|
||||
shallow_copy.ref_id = str(row.ref_id)
|
||||
shallow_copy.payment_plan_time_periods = row.payment_plan_time_periods
|
||||
shallow_copy.process_date = row.process_date
|
||||
shallow_copy.payment_amount = row.payment_amount
|
||||
shallow_copy.currency = row.currency
|
||||
shallow_copy.payment_types_id = row.payment_types_id
|
||||
shallow_copy.payment_types_uu_id = str(row.payment_types_uu_id)
|
||||
shallow_copy.period_time = row.period_time
|
||||
shallow_copy.process_date_y = row.process_date_y
|
||||
shallow_copy.process_date_m = row.process_date_m
|
||||
shallow_copy.build_decision_book_item_id = row.build_decision_book_item_id
|
||||
shallow_copy.build_decision_book_item_uu_id = str(row.build_decision_book_item_uu_id)
|
||||
shallow_copy.build_parts_id = row.build_parts_id
|
||||
shallow_copy.build_parts_uu_id = str(row.build_parts_uu_id)
|
||||
shallow_copy.decision_book_project_id = row.decision_book_project_id
|
||||
shallow_copy.decision_book_project_uu_id = str(row.decision_book_project_uu_id)
|
||||
shallow_copy.account_records_id = row.account_records_id
|
||||
shallow_copy.account_records_uu_id = str(row.account_records_uu_id)
|
||||
return shallow_copy
|
||||
|
||||
|
||||
class ApiEnumDropdownShallowCopy:
|
||||
id: int
|
||||
uuid: str
|
||||
enum_class: str
|
||||
key: str
|
||||
value: str
|
||||
|
||||
def __init__(self, id: int, uuid: str, enum_class: str, key: str, value: str):
|
||||
self.id = id
|
||||
self.uuid = uuid
|
||||
self.enum_class = enum_class
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
|
||||
class BuildDuesTypes:
|
||||
|
||||
def __init__(self):
|
||||
self.debit: ApiEnumDropdownShallowCopy = None
|
||||
self.add_debit: ApiEnumDropdownShallowCopy = None
|
||||
self.renovation: ApiEnumDropdownShallowCopy = None
|
||||
self.lawyer_expence: ApiEnumDropdownShallowCopy = None
|
||||
self.service_fee: ApiEnumDropdownShallowCopy = None
|
||||
self.information: ApiEnumDropdownShallowCopy = None
|
||||
|
||||
|
||||
class PaymentsRows:
|
||||
|
||||
def __init__(self):
|
||||
self.debit: list[BuildDecisionBookPaymentsShallowCopy] = []
|
||||
self.add_debit: list[BuildDecisionBookPaymentsShallowCopy] = []
|
||||
self.renovation: list[BuildDecisionBookPaymentsShallowCopy] = []
|
||||
self.lawyer_expence: list[BuildDecisionBookPaymentsShallowCopy] = []
|
||||
self.service_fee: list[BuildDecisionBookPaymentsShallowCopy] = []
|
||||
self.information: list[BuildDecisionBookPaymentsShallowCopy] = []
|
||||
|
||||
|
||||
class PaidRow:
|
||||
|
||||
def __init__(self, uuid: str, amount: Decimal, closed: bool, left_payment: Decimal | None = None):
|
||||
self.uuid: str = uuid
|
||||
self.amount: Decimal = amount
|
||||
self.closed: bool = closed
|
||||
self.left_payment: Decimal = Decimal(0)
|
||||
if not self.closed:
|
||||
self.left_payment = left_payment
|
||||
if not self.check_transaction_is_valid():
|
||||
raise ValueError(f"Record uuid: {self.uuid} tries to pay more than its debt in records.")
|
||||
|
||||
def check_transaction_is_valid(self):
|
||||
with BuildDecisionBookPayments.new_session() as session:
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
payment_row = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.ref_id == self.uuid,
|
||||
cast(BuildDecisionBookPayments.uu_id, String) == cast(BuildDecisionBookPayments.ref_id, String),
|
||||
BuildDecisionBookPayments.account_records_id.is_(None),
|
||||
).first()
|
||||
if not payment_row:
|
||||
return False
|
||||
already_paid = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.ref_id == self.uuid,
|
||||
cast(BuildDecisionBookPayments.uu_id, String) != cast(BuildDecisionBookPayments.ref_id, String),
|
||||
BuildDecisionBookPayments.account_records_id.isnot(None),
|
||||
).all()
|
||||
already_paid = sum([abs(row.payment_amount) for row in already_paid])
|
||||
left_amount = abs(payment_row.payment_amount) - abs(already_paid)
|
||||
if left_amount < self.amount:
|
||||
print(f"left_amount: {left_amount}, self.amount: {self.amount}. Record uuid: {self.uuid} tries to pay more than its debt in records.")
|
||||
return False
|
||||
return True
|
||||
def get_dict(self):
|
||||
return {
|
||||
"uuid": self.uuid,
|
||||
"amount": self.amount,
|
||||
"closed": self.closed,
|
||||
"left_payment": self.left_payment,
|
||||
}
|
||||
def __str__(self):
|
||||
return f"{self.uuid} = Paid: {self.amount} Left: {self.left_payment}"
|
||||
|
||||
|
||||
class PaymentActions:
|
||||
|
||||
def __init__(self, initial_money: Decimal):
|
||||
self.initial_money: Decimal = initial_money
|
||||
self.consumed_money: Decimal = Decimal(0)
|
||||
self.remaining_money: Decimal = self.initial_money
|
||||
self.paid_list: list[PaidRow] = []
|
||||
|
||||
def is_money_consumed(self):
|
||||
return self.consumed_money == self.initial_money
|
||||
|
||||
def consume(self, payment_due: Decimal, payment_uuid: str):
|
||||
left_payment = Decimal(0)
|
||||
if self.remaining_money >= payment_due:
|
||||
self.consumed_money += abs(payment_due)
|
||||
self.remaining_money = abs(self.remaining_money) - abs(payment_due)
|
||||
paid_row = PaidRow(payment_uuid, payment_due, True)
|
||||
self.paid_list.append(paid_row)
|
||||
elif self.remaining_money < payment_due:
|
||||
self.consumed_money = self.remaining_money
|
||||
self.remaining_money = Decimal(0)
|
||||
left_payment = abs(payment_due) - abs(self.consumed_money)
|
||||
paid_row = PaidRow(payment_uuid, self.consumed_money, False, left_payment)
|
||||
self.paid_list.append(paid_row)
|
||||
return left_payment
|
||||
|
||||
|
||||
def row_iteration_account_records():
|
||||
shallow_copy_list = []
|
||||
build_dues_types = BuildDuesTypes()
|
||||
|
||||
with ApiEnumDropdown.new_session() as session:
|
||||
|
||||
ApiEnumDropdown.set_session(session)
|
||||
|
||||
debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-D").first() # Debit
|
||||
add_debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-A").first() # Add Debit
|
||||
renovation_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-R").first() # Renovation
|
||||
late_payment_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-L").first() # Lawyer expence
|
||||
service_fee_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-S").first() # Service fee
|
||||
information_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-I").first() # Information
|
||||
|
||||
build_dues_types.debit = ApiEnumDropdownShallowCopy(
|
||||
debit_enum_shallow.id, str(debit_enum_shallow.uu_id), debit_enum_shallow.enum_class, debit_enum_shallow.key, debit_enum_shallow.value
|
||||
)
|
||||
build_dues_types.add_debit = ApiEnumDropdownShallowCopy(
|
||||
add_debit_enum_shallow.id, str(add_debit_enum_shallow.uu_id), add_debit_enum_shallow.enum_class, add_debit_enum_shallow.key, add_debit_enum_shallow.value
|
||||
)
|
||||
build_dues_types.renovation = ApiEnumDropdownShallowCopy(
|
||||
renovation_enum_shallow.id, str(renovation_enum_shallow.uu_id), renovation_enum_shallow.enum_class, renovation_enum_shallow.key, renovation_enum_shallow.value
|
||||
)
|
||||
build_dues_types.lawyer_expence = ApiEnumDropdownShallowCopy(
|
||||
late_payment_enum_shallow.id, str(late_payment_enum_shallow.uu_id), late_payment_enum_shallow.enum_class, late_payment_enum_shallow.key, late_payment_enum_shallow.value
|
||||
)
|
||||
build_dues_types.service_fee = ApiEnumDropdownShallowCopy(
|
||||
service_fee_enum_shallow.id, str(service_fee_enum_shallow.uu_id), service_fee_enum_shallow.enum_class, service_fee_enum_shallow.key, service_fee_enum_shallow.value
|
||||
)
|
||||
build_dues_types.information = ApiEnumDropdownShallowCopy(
|
||||
information_enum_shallow.id, str(information_enum_shallow.uu_id), information_enum_shallow.enum_class, information_enum_shallow.key, information_enum_shallow.value
|
||||
)
|
||||
|
||||
with AccountRecords.new_session() as session:
|
||||
|
||||
AccountRecords.set_session(session)
|
||||
account_records: list[AccountRecords] = AccountRecords.query.filter(
|
||||
AccountRecords.approved_record == True, AccountRecords.living_space_id.isnot(None),
|
||||
(AccountRecords.remainder_balance + AccountRecords.currency_value) > 0,
|
||||
cast(AccountRecords.bank_date, Date) > cast("2022-01-01", Date),
|
||||
# AccountRecords.currency_value > 0,
|
||||
).all()
|
||||
|
||||
for account_record in account_records:
|
||||
shallow_copy = AccountRecordsShallowCopy()
|
||||
shallow_copy.id = account_record.id
|
||||
shallow_copy.uuid = str(account_record.uu_id)
|
||||
shallow_copy.iban = account_record.iban
|
||||
shallow_copy.currency_value = account_record.currency_value
|
||||
shallow_copy.remainder_balance = account_record.remainder_balance
|
||||
shallow_copy.bank_date = arrow.get(account_record.bank_date).datetime
|
||||
shallow_copy.process_type = account_record.process_type
|
||||
shallow_copy.receive_debit = account_record.receive_debit
|
||||
shallow_copy.receive_debit_uuid = str(account_record.receive_debit_uu_id)
|
||||
shallow_copy.living_space_id = account_record.living_space_id
|
||||
shallow_copy.living_space_uuid = str(account_record.living_space_uu_id)
|
||||
shallow_copy.build_id = account_record.build_id
|
||||
shallow_copy.build_uuid = str(account_record.build_uu_id)
|
||||
shallow_copy.build_parts_id = account_record.build_parts_id
|
||||
shallow_copy.build_parts_uuid = str(account_record.build_parts_uu_id)
|
||||
shallow_copy_list.append(shallow_copy)
|
||||
|
||||
return shallow_copy_list, build_dues_types
|
||||
|
||||
|
||||
def check_payment_stage_debit(account_shallow_copy: AccountRecordsShallowCopy, build_dues_types: BuildDuesTypes, payments_rows: PaymentsRows, payment_actions: PaymentActions):
|
||||
account_records_bank_date = account_shallow_copy.bank_date
|
||||
for payment_row in payments_rows.debit:
|
||||
payment_actions.consume(payment_row.payment_amount, payment_row.uuid)
|
||||
if payment_actions.is_money_consumed():
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
def check_payment_stage_add_debit(account_shallow_copy: AccountRecordsShallowCopy, build_dues_types: BuildDuesTypes, payments_rows: PaymentsRows, payment_actions: PaymentActions):
|
||||
account_records_bank_date = account_shallow_copy.bank_date
|
||||
for payment_row in payments_rows.add_debit:
|
||||
payment_actions.consume(payment_row.payment_amount, payment_row.uuid)
|
||||
if payment_actions.is_money_consumed():
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
def check_payment_stage_renovation(account_shallow_copy: AccountRecordsShallowCopy, build_dues_types: BuildDuesTypes, payments_rows: PaymentsRows, payment_actions: PaymentActions):
|
||||
account_records_bank_date = account_shallow_copy.bank_date
|
||||
for payment_row in payments_rows.renovation:
|
||||
payment_actions.consume(payment_row.payment_amount, payment_row.uuid)
|
||||
if payment_actions.is_money_consumed():
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
def check_payment_stage_lawyer_expence(account_shallow_copy: AccountRecordsShallowCopy, build_dues_types: BuildDuesTypes, payments_rows: PaymentsRows, payment_actions: PaymentActions):
|
||||
account_records_bank_date = account_shallow_copy.bank_date
|
||||
for payment_row in payments_rows.lawyer_expence:
|
||||
payment_actions.consume(payment_row.payment_amount, payment_row.uuid)
|
||||
if payment_actions.is_money_consumed():
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
def check_payment_stage_service_fee(account_shallow_copy: AccountRecordsShallowCopy, build_dues_types: BuildDuesTypes, payments_rows: PaymentsRows, payment_actions: PaymentActions):
|
||||
account_records_bank_date = account_shallow_copy.bank_date
|
||||
for payment_row in payments_rows.service_fee:
|
||||
payment_actions.consume(payment_row.payment_amount, payment_row.uuid)
|
||||
if payment_actions.is_money_consumed():
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
def check_payment_stage_information(account_shallow_copy: AccountRecordsShallowCopy, build_dues_types: BuildDuesTypes, payments_rows: PaymentsRows, payment_actions: PaymentActions):
|
||||
account_records_bank_date = account_shallow_copy.bank_date
|
||||
for payment_row in payments_rows.information:
|
||||
payment_actions.consume(payment_row.payment_amount, payment_row.uuid)
|
||||
if payment_actions.is_money_consumed():
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
def close_account_records(account_shallow_copy: AccountRecordsShallowCopy, payment_actions: PaymentActions, records_to_close: int):
|
||||
if payment_actions.is_money_consumed():
|
||||
print(f'payment_actions.is_money_consumed() : {payment_actions.is_money_consumed()}')
|
||||
for paid_row in payment_actions.paid_list:
|
||||
print(f'paid_row item : {paid_row.get_dict()}')
|
||||
print(f'payment_actions.consumed_money : {payment_actions.consumed_money}')
|
||||
print(f'payment_actions.initial_money : {payment_actions.initial_money}')
|
||||
print(f'payment_actions.remaining_money : {payment_actions.remaining_money}')
|
||||
|
||||
with BuildDecisionBookPayments.new_session() as session:
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
for payment_row in payment_actions.paid_list:
|
||||
print(f'payment_row : {payment_row}')
|
||||
payment_row_book = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.uu_id == payment_row.uuid,
|
||||
cast(BuildDecisionBookPayments.ref_id, String) == cast(BuildDecisionBookPayments.uu_id, String),
|
||||
BuildDecisionBookPayments.account_records_id.is_(None),
|
||||
).first()
|
||||
if not payment_row_book:
|
||||
raise ValueError(f"Payment row not found for uuid: {payment_row.uuid}")
|
||||
new_row = BuildDecisionBookPayments.create(
|
||||
ref_id=str(payment_row_book.uu_id),
|
||||
payment_plan_time_periods=payment_row_book.payment_plan_time_periods,
|
||||
period_time=payment_row_book.period_time,
|
||||
currency=payment_row_book.currency,
|
||||
account_records_id=account_shallow_copy.id,
|
||||
account_records_uu_id=str(account_shallow_copy.uuid),
|
||||
build_parts_id=payment_row_book.build_parts_id,
|
||||
build_parts_uu_id=str(payment_row_book.build_parts_uu_id),
|
||||
payment_amount=abs(payment_row.amount),
|
||||
payment_types_id=payment_row_book.payment_types_id,
|
||||
payment_types_uu_id=str(payment_row_book.payment_types_uu_id),
|
||||
process_date_m=payment_row_book.process_date.month,
|
||||
process_date_y=payment_row_book.process_date.year,
|
||||
process_date=payment_row_book.process_date,
|
||||
build_decision_book_item_id=payment_row_book.build_decision_book_item_id,
|
||||
build_decision_book_item_uu_id=str(payment_row_book.build_decision_book_item_uu_id),
|
||||
decision_book_project_id=payment_row_book.decision_book_project_id,
|
||||
decision_book_project_uu_id=str(payment_row_book.decision_book_project_uu_id),
|
||||
is_confirmed=True,
|
||||
)
|
||||
new_row.save()
|
||||
account_record = AccountRecords.query.filter_by(id=account_shallow_copy.id).first()
|
||||
account_record.remainder_balance = - abs(account_record.remainder_balance) - abs(payment_actions.consumed_money)
|
||||
account_record.save()
|
||||
records_to_close += 1
|
||||
return records_to_close, True
|
||||
return records_to_close, False
|
||||
|
||||
|
||||
def any_function(shallow_copy_list, build_dues_types):
|
||||
error_records, not_closed_records, records_to_close = 0, 0, 0
|
||||
shallow_copy_list, build_dues_types = row_iteration_account_records()
|
||||
for index, shallow_copy in enumerate(shallow_copy_list):
|
||||
initial_amount = abs(shallow_copy.currency_value) - abs(shallow_copy.remainder_balance)
|
||||
if initial_amount == 0:
|
||||
# print(f'AC: {shallow_copy.uuid} initial_amount : {initial_amount} must be greater than 0 to spend money on payments')
|
||||
not_closed_records += 1
|
||||
continue
|
||||
if initial_amount < 0:
|
||||
print(f'AC: {shallow_copy.uuid} initial_amount : {initial_amount} wrong calculation is saved on account records Remainder Balance : {shallow_copy.remainder_balance} Currency Value : {shallow_copy.currency_value}')
|
||||
error_records += 1
|
||||
continue
|
||||
|
||||
payment_actions = PaymentActions(initial_amount)
|
||||
# print(f'initial_amount : {initial_amount}')
|
||||
payments_rows = PaymentsRows()
|
||||
with BuildDecisionBookPayments.new_session() as session:
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
book_payments_query = (
|
||||
BuildDecisionBookPayments.process_date_m == shallow_copy.bank_date.month, BuildDecisionBookPayments.process_date_y == shallow_copy.bank_date.year,
|
||||
BuildDecisionBookPayments.build_parts_id == shallow_copy.build_parts_id, BuildDecisionBookPayments.account_records_id.is_(None),
|
||||
cast(BuildDecisionBookPayments.ref_id, String) == cast(BuildDecisionBookPayments.uu_id, String),
|
||||
)
|
||||
query_db = BuildDecisionBookPayments.query.filter(
|
||||
*book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.debit.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
payments_rows.debit: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
query_db = BuildDecisionBookPayments.query.filter(
|
||||
*book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.add_debit.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
payments_rows.add_debit: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
query_db = BuildDecisionBookPayments.query.filter(
|
||||
*book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.renovation.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
payments_rows.renovation: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
query_db = BuildDecisionBookPayments.query.filter(
|
||||
*book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.lawyer_expence.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
payments_rows.lawyer_expence: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
query_db = BuildDecisionBookPayments.query.filter(
|
||||
*book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.service_fee.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
payments_rows.service_fee: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
query_db = BuildDecisionBookPayments.query.filter(
|
||||
*book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.information.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
payments_rows.information: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
|
||||
if len(payments_rows.debit) > 0:
|
||||
# print(f'{shallow_copy.uuid} debit', len(payments_rows.debit))
|
||||
records_to_close += 1
|
||||
if len(payments_rows.add_debit) > 0:
|
||||
# print(f'{shallow_copy.uuid} add_debit', len(payments_rows.add_debit))
|
||||
records_to_close += 1
|
||||
if len(payments_rows.renovation) > 0:
|
||||
# print(f'{shallow_copy.uuid} renovation', len(payments_rows.renovation))
|
||||
records_to_close += 1
|
||||
if len(payments_rows.lawyer_expence) > 0:
|
||||
# print(f'{shallow_copy.uuid} lawyer_expence', len(payments_rows.lawyer_expence))
|
||||
records_to_close += 1
|
||||
if len(payments_rows.service_fee) > 0:
|
||||
# print(f'{shallow_copy.uuid} service_fee', len(payments_rows.service_fee))
|
||||
records_to_close += 1
|
||||
if len(payments_rows.information) > 0:
|
||||
# print(f'{shallow_copy.uuid} information', len(payments_rows.information))
|
||||
records_to_close += 1
|
||||
|
||||
# continue
|
||||
check_payment_stage_debit(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
if is_money_consumed:
|
||||
continue
|
||||
check_payment_stage_add_debit(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
if is_money_consumed:
|
||||
continue
|
||||
check_payment_stage_renovation(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
if is_money_consumed:
|
||||
continue
|
||||
check_payment_stage_lawyer_expence(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
if is_money_consumed:
|
||||
continue
|
||||
check_payment_stage_service_fee(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
if is_money_consumed:
|
||||
continue
|
||||
check_payment_stage_information(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
if is_money_consumed:
|
||||
continue
|
||||
|
||||
# with BuildDecisionBookPayments.new_session() as session:
|
||||
# BuildDecisionBookPayments.set_session(session)
|
||||
# book_payments_query = (
|
||||
# BuildDecisionBookPayments.process_date_m < shallow_copy.bank_date.month, BuildDecisionBookPayments.process_date_y == shallow_copy.bank_date.year,
|
||||
# BuildDecisionBookPayments.build_parts_id == shallow_copy.build_parts_id, BuildDecisionBookPayments.account_records_id.is_(None),
|
||||
# BuildDecisionBookPayments.ref_id == BuildDecisionBookPayments.uu_id,
|
||||
# )
|
||||
# query_db = BuildDecisionBookPayments.query.filter(
|
||||
# *book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.debit.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
# payments_rows.debit: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
# query_db = BuildDecisionBookPayments.query.filter(
|
||||
# *book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.add_debit.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
# payments_rows.add_debit: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
# query_db = BuildDecisionBookPayments.query.filter(
|
||||
# *book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.renovation.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
# payments_rows.renovation: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
# query_db = BuildDecisionBookPayments.query.filter(
|
||||
# *book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.lawyer_expence.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
# payments_rows.lawyer_expence: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
# query_db = BuildDecisionBookPayments.query.filter(
|
||||
# *book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.service_fee.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
# payments_rows.service_fee: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
# query_db = BuildDecisionBookPayments.query.filter(
|
||||
# *book_payments_query, BuildDecisionBookPayments.payment_types_id == build_dues_types.information.id).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
# payments_rows.information: list[BuildDecisionBookPaymentsShallowCopy] = [BuildDecisionBookPaymentsShallowCopy.convert_row_to_shallow_copy(row) for row in query_db]
|
||||
|
||||
# check_payment_stage_debit(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
# records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
# if is_money_consumed:
|
||||
# continue
|
||||
# check_payment_stage_add_debit(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
# records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
# if is_money_consumed:
|
||||
# continue
|
||||
# check_payment_stage_renovation(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
# records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
# if is_money_consumed:
|
||||
# continue
|
||||
# check_payment_stage_lawyer_expence(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
# records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
# if is_money_consumed:
|
||||
# continue
|
||||
# check_payment_stage_service_fee(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
# records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
# if is_money_consumed:
|
||||
# continue
|
||||
# check_payment_stage_information(account_shallow_copy=shallow_copy, build_dues_types=build_dues_types, payments_rows=payments_rows, payment_actions=payment_actions)
|
||||
# records_to_close, is_money_consumed = close_account_records(account_shallow_copy=shallow_copy, payment_actions=payment_actions, records_to_close=records_to_close)
|
||||
# if is_money_consumed:
|
||||
# continue
|
||||
|
||||
"""
|
||||
build_decision_book_item_id, type=null, pos=10
|
||||
build_parts_id, type=null, pos=12
|
||||
payment_plan_time_periods, type=null, pos=1
|
||||
process_date, type=null, pos=2
|
||||
payment_types_id, type=null, pos=5
|
||||
account_records_id, type=null, pos=16
|
||||
"""
|
||||
348
ServicesBank/Finder/Payment/draft/second.py
Normal file
348
ServicesBank/Finder/Payment/draft/second.py
Normal file
@@ -0,0 +1,348 @@
|
||||
import arrow
|
||||
from decimal import Decimal
|
||||
from datetime import datetime, timedelta
|
||||
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown
|
||||
|
||||
from time import perf_counter
|
||||
from sqlalchemy import cast, Date, String
|
||||
from Controllers.Postgres.engine import get_session_factory
|
||||
# from ServicesApi.Schemas.account.account import AccountRecords
|
||||
# from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments
|
||||
|
||||
class BuildDuesTypes:
|
||||
|
||||
def __init__(self):
|
||||
self.debit: ApiEnumDropdownShallowCopy = None
|
||||
self.add_debit: ApiEnumDropdownShallowCopy = None
|
||||
self.renovation: ApiEnumDropdownShallowCopy = None
|
||||
self.lawyer_expence: ApiEnumDropdownShallowCopy = None
|
||||
self.service_fee: ApiEnumDropdownShallowCopy = None
|
||||
self.information: ApiEnumDropdownShallowCopy = None
|
||||
|
||||
|
||||
class ApiEnumDropdownShallowCopy:
|
||||
id: int
|
||||
uuid: str
|
||||
enum_class: str
|
||||
key: str
|
||||
value: str
|
||||
|
||||
def __init__(self, id: int, uuid: str, enum_class: str, key: str, value: str):
|
||||
self.id = id
|
||||
self.uuid = uuid
|
||||
self.enum_class = enum_class
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
|
||||
def find_master_payment_value(build_parts_id: int, process_date: datetime, session, debit_type):
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
book_payments_query = (
|
||||
BuildDecisionBookPayments.process_date_m == process_date.month, BuildDecisionBookPayments.process_date_y == process_date.year,
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id, BuildDecisionBookPayments.account_records_id.is_(None),
|
||||
BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
||||
)
|
||||
debit_row = BuildDecisionBookPayments.query.filter(*book_payments_query).order_by(BuildDecisionBookPayments.process_date.desc()).first()
|
||||
if not debit_row:
|
||||
print(f'No record of master payment is found for :{process_date.strftime("%Y-%m-%d %H:%M:%S")} ')
|
||||
return 0, None, None
|
||||
return abs(debit_row.payment_amount), debit_row, str(debit_row.ref_id)
|
||||
|
||||
|
||||
def calculate_paid_amount_for_master(ref_id: str, session, debit_amount):
|
||||
"""Calculate how much has been paid for a given payment reference.
|
||||
|
||||
Args:
|
||||
ref_id: The reference ID to check payments for
|
||||
session: Database session
|
||||
debit_amount: Original debit amount
|
||||
|
||||
Returns:
|
||||
float: Remaining amount to pay (debit_amount - total_paid)
|
||||
"""
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
paid_rows = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.ref_id == ref_id,
|
||||
BuildDecisionBookPayments.account_records_id.isnot(None),
|
||||
BuildDecisionBookPayments.account_is_debit == False
|
||||
).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
|
||||
if not paid_rows:
|
||||
return debit_amount
|
||||
|
||||
total_paid = sum([abs(paid_row.payment_amount) for paid_row in paid_rows])
|
||||
remaining = abs(debit_amount) - abs(total_paid)
|
||||
return remaining
|
||||
|
||||
|
||||
def find_master_payment_value_previous(build_parts_id: int, process_date: datetime, session, debit_type):
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
parse_process_date = datetime(process_date.year, process_date.month, 1) - timedelta(days=1)
|
||||
book_payments_query = (
|
||||
BuildDecisionBookPayments.process_date < parse_process_date,
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id, BuildDecisionBookPayments.account_records_id.is_(None),
|
||||
BuildDecisionBookPayments.account_is_debit == True, BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
||||
)
|
||||
debit_rows = BuildDecisionBookPayments.query.filter(*book_payments_query).order_by(BuildDecisionBookPayments.process_date.desc()).all()
|
||||
return debit_rows
|
||||
|
||||
|
||||
def find_amount_to_pay(build_parts_id: int, process_date: datetime, session, debit_type):
|
||||
# debit -negative value that need to be pay
|
||||
debit, debit_row, debit_row_ref_id = find_master_payment_value(build_parts_id=build_parts_id, process_date=process_date, session=session, debit_type=debit_type)
|
||||
# Is there any payment done for this ref_id ?
|
||||
return calculate_paid_amount_for_master(ref_id=debit_row_ref_id, session=session, debit_amount=debit), debit_row
|
||||
|
||||
|
||||
def calculate_total_debt_for_account(build_parts_id: int, session):
|
||||
"""Calculate the total debt and total paid amount for an account regardless of process date."""
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
# Get all debits for this account
|
||||
all_debits = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
BuildDecisionBookPayments.account_records_id.is_(None),
|
||||
BuildDecisionBookPayments.account_is_debit == True
|
||||
).all()
|
||||
|
||||
total_debt = sum([abs(debit.payment_amount) for debit in all_debits])
|
||||
|
||||
# Get all payments for this account's debits
|
||||
total_paid = 0
|
||||
for debit in all_debits:
|
||||
payments = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.ref_id == debit.ref_id,
|
||||
BuildDecisionBookPayments.account_is_debit == False
|
||||
).all()
|
||||
|
||||
if payments:
|
||||
total_paid += sum([abs(payment.payment_amount) for payment in payments])
|
||||
|
||||
return total_debt, total_paid
|
||||
|
||||
|
||||
def refresh_book_payment(account_record: AccountRecords):
|
||||
"""Update the remainder_balance of an account record based on attached payments.
|
||||
|
||||
This function calculates the total of all payments attached to an account record
|
||||
and updates the remainder_balance field accordingly. The remainder_balance represents
|
||||
funds that have been received but not yet allocated to specific debits.
|
||||
|
||||
Args:
|
||||
account_record: The account record to update
|
||||
|
||||
Returns:
|
||||
float: The total payment amount
|
||||
"""
|
||||
total_payment = 0
|
||||
all_payment_attached = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.account_records_id == account_record.id,
|
||||
BuildDecisionBookPayments.account_is_debit == False,
|
||||
).all()
|
||||
|
||||
if all_payment_attached:
|
||||
total_payment = sum([abs(row.payment_amount) for row in all_payment_attached])
|
||||
|
||||
old_balance = account_record.remainder_balance
|
||||
account_record.update(remainder_balance=total_payment)
|
||||
account_record.save()
|
||||
|
||||
# Only print if there's a change in balance
|
||||
if old_balance != total_payment:
|
||||
print(f"Account {account_record.id}: Updated remainder_balance {old_balance} → {total_payment}")
|
||||
|
||||
return total_payment
|
||||
|
||||
|
||||
def close_payment_book(payment_row_book, account_record, value, session):
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
new_row = BuildDecisionBookPayments.create(
|
||||
ref_id=str(payment_row_book.uu_id),
|
||||
payment_plan_time_periods=payment_row_book.payment_plan_time_periods,
|
||||
period_time=payment_row_book.period_time,
|
||||
currency=payment_row_book.currency,
|
||||
account_records_id=account_record.id,
|
||||
account_records_uu_id=str(account_record.uu_id),
|
||||
build_parts_id=payment_row_book.build_parts_id,
|
||||
build_parts_uu_id=str(payment_row_book.build_parts_uu_id),
|
||||
payment_amount=value,
|
||||
payment_types_id=payment_row_book.payment_types_id,
|
||||
payment_types_uu_id=str(payment_row_book.payment_types_uu_id),
|
||||
process_date_m=payment_row_book.process_date.month,
|
||||
process_date_y=payment_row_book.process_date.year,
|
||||
process_date=payment_row_book.process_date,
|
||||
build_decision_book_item_id=payment_row_book.build_decision_book_item_id,
|
||||
build_decision_book_item_uu_id=str(payment_row_book.build_decision_book_item_uu_id),
|
||||
decision_book_project_id=payment_row_book.decision_book_project_id,
|
||||
decision_book_project_uu_id=str(payment_row_book.decision_book_project_uu_id),
|
||||
is_confirmed=True,
|
||||
account_is_debit=False,
|
||||
)
|
||||
return new_row.save()
|
||||
|
||||
|
||||
def get_enums_from_database():
|
||||
build_dues_types = BuildDuesTypes()
|
||||
with ApiEnumDropdown.new_session() as session:
|
||||
|
||||
ApiEnumDropdown.set_session(session)
|
||||
|
||||
debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-D").first() # Debit
|
||||
add_debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-A").first() # Add Debit
|
||||
renovation_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-R").first() # Renovation
|
||||
late_payment_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-L").first() # Lawyer expence
|
||||
service_fee_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-S").first() # Service fee
|
||||
information_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-I").first() # Information
|
||||
|
||||
build_dues_types.debit = ApiEnumDropdownShallowCopy(
|
||||
debit_enum_shallow.id, str(debit_enum_shallow.uu_id), debit_enum_shallow.enum_class, debit_enum_shallow.key, debit_enum_shallow.value
|
||||
)
|
||||
build_dues_types.add_debit = ApiEnumDropdownShallowCopy(
|
||||
add_debit_enum_shallow.id, str(add_debit_enum_shallow.uu_id), add_debit_enum_shallow.enum_class, add_debit_enum_shallow.key, add_debit_enum_shallow.value
|
||||
)
|
||||
build_dues_types.renovation = ApiEnumDropdownShallowCopy(
|
||||
renovation_enum_shallow.id, str(renovation_enum_shallow.uu_id), renovation_enum_shallow.enum_class, renovation_enum_shallow.key, renovation_enum_shallow.value
|
||||
)
|
||||
build_dues_types.lawyer_expence = ApiEnumDropdownShallowCopy(
|
||||
late_payment_enum_shallow.id, str(late_payment_enum_shallow.uu_id), late_payment_enum_shallow.enum_class, late_payment_enum_shallow.key, late_payment_enum_shallow.value
|
||||
)
|
||||
build_dues_types.service_fee = ApiEnumDropdownShallowCopy(
|
||||
service_fee_enum_shallow.id, str(service_fee_enum_shallow.uu_id), service_fee_enum_shallow.enum_class, service_fee_enum_shallow.key, service_fee_enum_shallow.value
|
||||
)
|
||||
build_dues_types.information = ApiEnumDropdownShallowCopy(
|
||||
information_enum_shallow.id, str(information_enum_shallow.uu_id), information_enum_shallow.enum_class, information_enum_shallow.key, information_enum_shallow.value
|
||||
)
|
||||
return [build_dues_types.debit, build_dues_types.lawyer_expence, build_dues_types.add_debit, build_dues_types.renovation, build_dues_types.service_fee, build_dues_types.information]
|
||||
|
||||
|
||||
def payment_function():
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
ApiEnumDropdown.set_session(session)
|
||||
order_pay = ApiEnumDropdown.query.filter(ApiEnumDropdown.enum_type == 'order_pay').order_by(ApiEnumDropdown.id.asc()).all()
|
||||
|
||||
# Get account records with positive currency_value regardless of remainder_balance
|
||||
# This ensures accounts with unallocated funds are processed
|
||||
account_records = AccountRecords.query.filter(
|
||||
AccountRecords.build_parts_id.isnot(None),
|
||||
AccountRecords.currency_value > 0,
|
||||
AccountRecords.bank_date >= '2022-01-01'
|
||||
).order_by(AccountRecords.build_parts_id.desc()).all()
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
for account_record in account_records:
|
||||
incoming_total_money = abs(account_record.currency_value)
|
||||
total_paid = refresh_book_payment(account_record)
|
||||
available_fund = df_fund(incoming_total_money, total_paid)
|
||||
|
||||
# Calculate total debt and payment status for this account
|
||||
total_debt, already_paid = calculate_total_debt_for_account(account_record.build_parts_id, session)
|
||||
remaining_debt = total_debt - already_paid
|
||||
|
||||
# Log account status with debt information
|
||||
if remaining_debt <= 0 and account_record.remainder_balance == 0:
|
||||
# Skip accounts with no debt and zero remainder balance
|
||||
continue
|
||||
|
||||
if not available_fund > 0.0:
|
||||
# Skip accounts with no available funds
|
||||
continue
|
||||
|
||||
process_date = datetime.now()
|
||||
|
||||
# Try to pay current month first
|
||||
for debit_type in order_pay:
|
||||
amount_to_pay, debit_row = find_amount_to_pay(
|
||||
build_parts_id=account_record.build_parts_id,
|
||||
process_date=process_date,
|
||||
session=session,
|
||||
debit_type=debit_type
|
||||
)
|
||||
|
||||
if amount_to_pay > 0 and debit_row:
|
||||
if amount_to_pay >= available_fund:
|
||||
close_payment_book(
|
||||
payment_row_book=debit_row,
|
||||
account_record=account_record,
|
||||
value=available_fund,
|
||||
session=session
|
||||
)
|
||||
total_paid = refresh_book_payment(account_record)
|
||||
available_fund = df_fund(incoming_total_money, total_paid)
|
||||
else:
|
||||
close_payment_book(
|
||||
payment_row_book=debit_row,
|
||||
account_record=account_record,
|
||||
value=amount_to_pay,
|
||||
session=session
|
||||
)
|
||||
total_paid = refresh_book_payment(account_record)
|
||||
available_fund = df_fund(incoming_total_money, total_paid)
|
||||
|
||||
if not available_fund > 0.0:
|
||||
continue
|
||||
|
||||
# Try to pay previous unpaid debts
|
||||
should_continue = False
|
||||
for debit_type in order_pay:
|
||||
debit_rows = find_master_payment_value_previous(
|
||||
build_parts_id=account_record.build_parts_id,
|
||||
process_date=process_date,
|
||||
session=session,
|
||||
debit_type=debit_type
|
||||
)
|
||||
|
||||
if not debit_rows:
|
||||
continue
|
||||
|
||||
for debit_row in debit_rows:
|
||||
amount_to_pay = calculate_paid_amount_for_master(
|
||||
ref_id=debit_row.ref_id,
|
||||
session=session,
|
||||
debit_amount=debit_row.payment_amount
|
||||
)
|
||||
|
||||
# Skip if already fully paid
|
||||
if not amount_to_pay > 0:
|
||||
continue
|
||||
|
||||
if amount_to_pay >= available_fund:
|
||||
close_payment_book(
|
||||
payment_row_book=debit_row,
|
||||
account_record=account_record,
|
||||
value=available_fund,
|
||||
session=session
|
||||
)
|
||||
total_paid = refresh_book_payment(account_record)
|
||||
available_fund = df_fund(incoming_total_money, total_paid)
|
||||
should_continue = True
|
||||
break
|
||||
else:
|
||||
close_payment_book(
|
||||
payment_row_book=debit_row,
|
||||
account_record=account_record,
|
||||
value=amount_to_pay,
|
||||
session=session
|
||||
)
|
||||
total_paid = refresh_book_payment(account_record)
|
||||
available_fund = df_fund(incoming_total_money, total_paid)
|
||||
|
||||
print(f"Account {account_record.id}: {available_fund} funds remaining after payment")
|
||||
if should_continue or not available_fund > 0.0:
|
||||
break
|
||||
if not available_fund > 0.0:
|
||||
continue # Changed from break to continue to process next account record
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
start_time = perf_counter()
|
||||
payment_function()
|
||||
end_time = perf_counter()
|
||||
elapsed = end_time - start_time
|
||||
print(f'{elapsed:.3f} : seconds')
|
||||
# print(f'not_closed_records : {not_closed_records}')
|
||||
# print(f'error_records : {error_records}')
|
||||
# print(f'records_to_close : {records_to_close}')
|
||||
# print(f"Total: {not_closed_records + error_records + records_to_close} / {len(shallow_copy_list)}")
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from sqlalchemy import create_engine, func
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
import os
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
# Get database connection details from environment variables
|
||||
DB_HOST = os.environ.get('DB_HOST', 'postgres')
|
||||
DB_PORT = os.environ.get('DB_PORT', '5432')
|
||||
DB_NAME = os.environ.get('DB_NAME', 'evyos')
|
||||
DB_USER = os.environ.get('DB_USER', 'evyos')
|
||||
DB_PASS = os.environ.get('DB_PASS', 'evyos')
|
||||
|
||||
# Create SQLAlchemy engine and session
|
||||
engine = create_engine(f'postgresql://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}')
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("UPDATING ACCOUNTS WITH ZERO REMAINDER_BALANCE")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
# First, get the count of accounts that need updating
|
||||
count_query = """SELECT COUNT(*) FROM account_records
|
||||
WHERE build_parts_id IS NOT NULL
|
||||
AND currency_value > 0
|
||||
AND (remainder_balance = 0 OR remainder_balance IS NULL)
|
||||
AND bank_date >= '2022-01-01'"""
|
||||
count_result = session.execute(count_query).scalar()
|
||||
print(f"Found {count_result} accounts with zero remainder_balance")
|
||||
|
||||
# Then update all those accounts
|
||||
update_query = """UPDATE account_records
|
||||
SET remainder_balance = currency_value
|
||||
WHERE build_parts_id IS NOT NULL
|
||||
AND currency_value > 0
|
||||
AND (remainder_balance = 0 OR remainder_balance IS NULL)
|
||||
AND bank_date >= '2022-01-01'"""
|
||||
result = session.execute(update_query)
|
||||
session.commit()
|
||||
print(f"Updated {result.rowcount} accounts with zero remainder_balance")
|
||||
|
||||
# Verify the update
|
||||
verify_query = """SELECT COUNT(*) FROM account_records
|
||||
WHERE build_parts_id IS NOT NULL
|
||||
AND currency_value > 0
|
||||
AND (remainder_balance = 0 OR remainder_balance IS NULL)
|
||||
AND bank_date >= '2022-01-01'"""
|
||||
verify_result = session.execute(verify_query).scalar()
|
||||
print(f"Remaining accounts with zero remainder_balance: {verify_result}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error updating accounts: {str(e)}")
|
||||
session.rollback()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
print("=" * 50)
|
||||
30
ServicesBank/Finder/Payment/entrypoint.sh
Normal file
30
ServicesBank/Finder/Payment/entrypoint.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Create environment file that will be available to cron jobs
|
||||
echo "POSTGRES_USER=\"$POSTGRES_USER\"" >> /env.sh
|
||||
echo "POSTGRES_PASSWORD=\"$POSTGRES_PASSWORD\"" >> /env.sh
|
||||
echo "POSTGRES_DB=\"$POSTGRES_DB\"" >> /env.sh
|
||||
echo "POSTGRES_HOST=\"$POSTGRES_HOST\"" >> /env.sh
|
||||
echo "POSTGRES_PORT=$POSTGRES_PORT" >> /env.sh
|
||||
echo "POSTGRES_ENGINE=\"$POSTGRES_ENGINE\"" >> /env.sh
|
||||
echo "POSTGRES_POOL_PRE_PING=\"$POSTGRES_POOL_PRE_PING\"" >> /env.sh
|
||||
echo "POSTGRES_POOL_SIZE=$POSTGRES_POOL_SIZE" >> /env.sh
|
||||
echo "POSTGRES_MAX_OVERFLOW=$POSTGRES_MAX_OVERFLOW" >> /env.sh
|
||||
echo "POSTGRES_POOL_RECYCLE=$POSTGRES_POOL_RECYCLE" >> /env.sh
|
||||
echo "POSTGRES_POOL_TIMEOUT=$POSTGRES_POOL_TIMEOUT" >> /env.sh
|
||||
echo "POSTGRES_ECHO=\"$POSTGRES_ECHO\"" >> /env.sh
|
||||
|
||||
# Add Python environment variables
|
||||
echo "PYTHONPATH=/" >> /env.sh
|
||||
echo "PYTHONUNBUFFERED=1" >> /env.sh
|
||||
echo "PYTHONDONTWRITEBYTECODE=1" >> /env.sh
|
||||
|
||||
# Make the environment file available to cron
|
||||
echo "*/30 * * * * /run_app.sh >> /var/log/cron.log 2>&1" > /tmp/crontab_list
|
||||
crontab /tmp/crontab_list
|
||||
|
||||
# Start cron
|
||||
cron
|
||||
|
||||
# Tail the log file
|
||||
tail -f /var/log/cron.log
|
||||
326
ServicesBank/Finder/Payment/old_runner.py
Normal file
326
ServicesBank/Finder/Payment/old_runner.py
Normal file
@@ -0,0 +1,326 @@
|
||||
import sys
|
||||
import arrow
|
||||
from decimal import Decimal
|
||||
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown
|
||||
|
||||
# Counters for tracking conditions
|
||||
counter_current_currency_not_positive = 0 # Track when current_currency_value <= 0
|
||||
counter_net_amount_not_positive = 0 # Track when net_amount <= 0
|
||||
counter_account_records_updated = 0 # Track number of account records updated
|
||||
counter_payments_found = 0 # Track how many payments were found
|
||||
counter_found_payment_skips = 0 # Track how many times we skip due to found_payment
|
||||
counter_payment_exceptions = 0 # Track exceptions during payment processing
|
||||
counter_missing_build_parts_id = 0 # Track accounts with missing build_parts_id
|
||||
counter_null_payment_types = 0 # Track payments with null payment_types_id
|
||||
|
||||
|
||||
def pay_the_registration(account_record, receive_enum, debit_enum, is_old_record: bool = False, session=None):
|
||||
# If no session is provided, create a new one
|
||||
if session is None:
|
||||
with AccountRecords.new_session() as new_session:
|
||||
AccountRecords.set_session(new_session)
|
||||
BuildDecisionBookPayments.set_session(new_session)
|
||||
return _process_payment(account_record, receive_enum, debit_enum, is_old_record, new_session)
|
||||
else:
|
||||
# Use the provided session
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
return _process_payment(account_record, receive_enum, debit_enum, is_old_record, session)
|
||||
|
||||
|
||||
def _process_payment(account_record, receive_enum, debit_enum, is_old_record, session):
|
||||
"""Internal function to process payments with a given session"""
|
||||
current_currency_value = float(Decimal(account_record.currency_value)) - float(Decimal(account_record.remainder_balance))
|
||||
if not current_currency_value > 0:
|
||||
global counter_current_currency_not_positive
|
||||
counter_current_currency_not_positive += 1
|
||||
return current_currency_value
|
||||
|
||||
# Check if account_record has build_parts_id
|
||||
if account_record.build_parts_id is None:
|
||||
global counter_missing_build_parts_id
|
||||
counter_missing_build_parts_id += 1
|
||||
return current_currency_value
|
||||
|
||||
process_date = arrow.get(account_record.bank_date)
|
||||
account_bank_date_year, account_bank_date_month = (process_date.date().year, process_date.date().month)
|
||||
|
||||
# First, try to find payments with null payment_types_id
|
||||
payment_arguments_debit = [
|
||||
BuildDecisionBookPayments.build_parts_id == account_record.build_parts_id,
|
||||
BuildDecisionBookPayments.account_records_id == None,
|
||||
]
|
||||
|
||||
# Add date filters if not processing old records
|
||||
if not is_old_record:
|
||||
payment_arguments_debit.extend([BuildDecisionBookPayments.process_date_y == int(account_bank_date_year), BuildDecisionBookPayments.process_date_m == int(account_bank_date_month)])
|
||||
|
||||
# First try with debit_enum.id
|
||||
payments = BuildDecisionBookPayments.query.filter(*payment_arguments_debit, BuildDecisionBookPayments.payment_types_id == debit_enum.id).order_by(BuildDecisionBookPayments.process_date.asc()).all()
|
||||
|
||||
# If no payments found, try with null payment_types_id
|
||||
if len(payments) == 0:
|
||||
payments = BuildDecisionBookPayments.query.filter(*payment_arguments_debit, BuildDecisionBookPayments.payment_types_id == None).order_by(BuildDecisionBookPayments.process_date.asc()).all()
|
||||
if len(payments) > 0:
|
||||
global counter_null_payment_types
|
||||
counter_null_payment_types += len(payments)
|
||||
|
||||
global counter_payments_found
|
||||
counter_payments_found += len(payments)
|
||||
|
||||
# Debug: Print info about the first few payments found (if any)
|
||||
if len(payments) > 0 and account_record.id % 100 == 0: # Only print for every 100th record to avoid too much output
|
||||
print(f"DEBUG: Found {len(payments)} payments for account_record {account_record.id}")
|
||||
if len(payments) > 0:
|
||||
sample_payment = payments[0]
|
||||
print(f" Sample payment: ID={getattr(sample_payment, 'id', 'N/A')}, amount={getattr(sample_payment, 'payment_amount', 'N/A')}")
|
||||
|
||||
if len(payments) == 0:
|
||||
# No payments found for this account record
|
||||
return current_currency_value
|
||||
for payment in payments:
|
||||
if not current_currency_value > 0:
|
||||
return current_currency_value
|
||||
payment_arguments_receive = [
|
||||
BuildDecisionBookPayments.build_parts_id == account_record.build_parts_id,
|
||||
BuildDecisionBookPayments.payment_plan_time_periods == payment.payment_plan_time_periods,
|
||||
BuildDecisionBookPayments.payment_types_id == receive_enum.id,
|
||||
BuildDecisionBookPayments.build_decision_book_item_id == payment.build_decision_book_item_id,
|
||||
BuildDecisionBookPayments.decision_book_project_id == payment.decision_book_project_id,
|
||||
BuildDecisionBookPayments.process_date == payment.process_date,
|
||||
]
|
||||
if not is_old_record:
|
||||
payment_arguments_receive.extend([BuildDecisionBookPayments.process_date_y == int(account_bank_date_year), BuildDecisionBookPayments.process_date_m == int(account_bank_date_month)])
|
||||
|
||||
payment_received = BuildDecisionBookPayments.query.filter(*payment_arguments_receive).all()
|
||||
sum_of_payment_received = sum([abs(payment.payment_amount) for payment in payment_received])
|
||||
net_amount = float(abs(Decimal(payment.payment_amount))) - float(abs(Decimal(sum_of_payment_received)))
|
||||
if not net_amount > 0:
|
||||
global counter_net_amount_not_positive
|
||||
counter_net_amount_not_positive += 1
|
||||
continue
|
||||
if float(abs(current_currency_value)) < float(abs(net_amount)):
|
||||
net_amount = float(current_currency_value)
|
||||
process_date = arrow.get(payment.process_date)
|
||||
try:
|
||||
found_payment = BuildDecisionBookPayments.query.filter_by(
|
||||
build_parts_id=payment.build_parts_id, payment_plan_time_periods=payment.payment_plan_time_periods,
|
||||
payment_types_id=receive_enum.id, build_decision_book_item_id=payment.build_decision_book_item_id,
|
||||
decision_book_project_id=payment.decision_book_project_id, process_date=str(process_date),
|
||||
).first()
|
||||
if found_payment:
|
||||
global counter_found_payment_skips
|
||||
counter_found_payment_skips += 1
|
||||
continue
|
||||
created_book_payment = BuildDecisionBookPayments.create(
|
||||
payment_plan_time_periods=payment.payment_plan_time_periods, payment_amount=float(abs(net_amount)),
|
||||
payment_types_id=receive_enum.id, payment_types_uu_id=str(receive_enum.uu_id),
|
||||
process_date=str(process_date), process_date_m=process_date.date().month, process_date_y=process_date.date().year,
|
||||
period_time=f"{process_date.year}-{str(process_date.month).zfill(2)}", build_parts_id=payment.build_parts_id,
|
||||
build_parts_uu_id=str(payment.build_parts_uu_id), account_records_id=account_record.id,
|
||||
account_records_uu_id=str(account_record.uu_id), build_decision_book_item_id=payment.build_decision_book_item_id,
|
||||
build_decision_book_item_uu_id=str(payment.build_decision_book_item_uu_id), decision_book_project_id=payment.decision_book_project_id,
|
||||
decision_book_project_uu_id=str(payment.decision_book_project_uu_id),
|
||||
)
|
||||
created_book_payment.save()
|
||||
created_payment_amount = float(Decimal(created_book_payment.payment_amount))
|
||||
remainder_balance = float(Decimal(account_record.remainder_balance)) + float(abs(created_payment_amount))
|
||||
account_record.update(remainder_balance=remainder_balance)
|
||||
account_record.save()
|
||||
|
||||
global counter_account_records_updated
|
||||
counter_account_records_updated += 1
|
||||
if current_currency_value >= abs(net_amount):
|
||||
current_currency_value -= abs(net_amount)
|
||||
except Exception as e:
|
||||
print("Exception of decision payment:", e)
|
||||
global counter_payment_exceptions
|
||||
counter_payment_exceptions += 1
|
||||
return current_currency_value
|
||||
|
||||
|
||||
def create_direct_payment(account_record, receive_enum, session):
|
||||
"""
|
||||
Create a direct payment record for an account record without relying on matching BuildDecisionBookPayments
|
||||
"""
|
||||
try:
|
||||
# Calculate the amount to process
|
||||
payment_amount = float(Decimal(account_record.currency_value)) - float(Decimal(account_record.remainder_balance))
|
||||
if payment_amount <= 0:
|
||||
return False
|
||||
|
||||
# Get process date information
|
||||
process_date = arrow.get(account_record.bank_date)
|
||||
process_date_y = process_date.date().year
|
||||
process_date_m = process_date.date().month
|
||||
period_time = f"{process_date_y}-{str(process_date_m).zfill(2)}"
|
||||
|
||||
# Check if a payment already exists for this account record
|
||||
existing_payment = BuildDecisionBookPayments.query.filter_by(account_records_id=account_record.id, payment_types_id=receive_enum.id).first()
|
||||
if existing_payment: # Skip if payment already exists
|
||||
return False
|
||||
|
||||
# Create a new payment record directly
|
||||
created_book_payment = BuildDecisionBookPayments.create(
|
||||
payment_plan_time_periods=1, # Default value
|
||||
payment_types_id=receive_enum.id, payment_types_uu_id=str(receive_enum.uu_id), payment_amount=payment_amount, process_date=str(process_date), process_date_y=process_date_y,
|
||||
process_date_m=process_date_m, period_time=period_time, account_records_id=account_record.id, account_records_uu_id=str(account_record.uu_id),
|
||||
build_parts_id=account_record.build_parts_id, build_parts_uu_id=str(account_record.build_parts_uu_id) if hasattr(account_record, 'build_parts_uu_id') and account_record.build_parts_uu_id else None,
|
||||
decision_book_project_id=getattr(account_record, 'decision_book_project_id', None),
|
||||
decision_book_project_uu_id=str(account_record.decision_book_project_uu_id) if hasattr(account_record, 'decision_book_project_uu_id') and account_record.decision_book_project_uu_id else None,
|
||||
)
|
||||
created_book_payment.save()
|
||||
|
||||
# Update the account record
|
||||
remainder_balance = float(Decimal(account_record.remainder_balance)) + float(abs(payment_amount))
|
||||
account_record.update(remainder_balance=remainder_balance)
|
||||
account_record.save()
|
||||
|
||||
global counter_account_records_updated
|
||||
counter_account_records_updated += 1
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Exception in create_direct_payment for account {account_record.id}: {e}")
|
||||
global counter_payment_exceptions
|
||||
counter_payment_exceptions += 1
|
||||
return False
|
||||
|
||||
|
||||
def send_accounts_to_decision_payment():
|
||||
with ApiEnumDropdown.new_session() as session:
|
||||
# Set the session for all models that will be used
|
||||
ApiEnumDropdown.set_session(session)
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
try:
|
||||
# Get required enum values
|
||||
receive_enum = ApiEnumDropdown.query.filter_by(enum_class="DebitTypes", key="DT-R").first()
|
||||
debit_enum = ApiEnumDropdown.query.filter_by(enum_class="DebitTypes", key="DT-D").first()
|
||||
if not receive_enum or not debit_enum:
|
||||
print("Error: Could not find required enum values")
|
||||
return
|
||||
|
||||
# Check if there are any BuildDecisionBookPayments records at all
|
||||
total_payments = BuildDecisionBookPayments.query.count()
|
||||
print(f"\n--- DEBUG: Database Statistics ---")
|
||||
print(f"Total BuildDecisionBookPayments records in database: {total_payments}")
|
||||
|
||||
# Check how many have payment_types_id = debit_enum.id
|
||||
debit_payments = BuildDecisionBookPayments.query.filter_by(payment_types_id=debit_enum.id).count()
|
||||
print(f"BuildDecisionBookPayments with payment_types_id={debit_enum.id} (DT-D): {debit_payments}")
|
||||
|
||||
# Check how many have account_records_id = None
|
||||
null_account_payments = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.account_records_id == None).count()
|
||||
print(f"BuildDecisionBookPayments with account_records_id=None: {null_account_payments}")
|
||||
|
||||
# Check a sample payment record
|
||||
sample_payment = BuildDecisionBookPayments.query.first()
|
||||
if sample_payment:
|
||||
print("\n--- Sample BuildDecisionBookPayment ---")
|
||||
print(f"ID: {getattr(sample_payment, 'id', 'N/A')}")
|
||||
print(f"payment_types_id: {getattr(sample_payment, 'payment_types_id', 'N/A')}")
|
||||
print(f"build_parts_id: {getattr(sample_payment, 'build_parts_id', 'N/A')}")
|
||||
print(f"account_records_id: {getattr(sample_payment, 'account_records_id', 'N/A')}")
|
||||
print(f"process_date_y: {getattr(sample_payment, 'process_date_y', 'N/A')}")
|
||||
print(f"process_date_m: {getattr(sample_payment, 'process_date_m', 'N/A')}")
|
||||
else:
|
||||
print("No BuildDecisionBookPayment records found in the database!")
|
||||
|
||||
# Check a sample account record
|
||||
sample_account = AccountRecords.query.filter(AccountRecords.remainder_balance < AccountRecords.currency_value, AccountRecords.receive_debit == receive_enum.id).first()
|
||||
if sample_account:
|
||||
print("\n--- Sample AccountRecord ---")
|
||||
print(f"ID: {getattr(sample_account, 'id', 'N/A')}")
|
||||
print(f"build_parts_id: {getattr(sample_account, 'build_parts_id', 'N/A')}")
|
||||
print(f"bank_date: {getattr(sample_account, 'bank_date', 'N/A')}")
|
||||
|
||||
# Try to find payments for this specific account record
|
||||
if sample_account.bank_date:
|
||||
process_date = arrow.get(sample_account.bank_date)
|
||||
account_bank_date_year, account_bank_date_month = (process_date.date().year, process_date.date().month)
|
||||
|
||||
print("\n--- Checking for payments for sample account ---")
|
||||
print(f"Looking for payments with build_parts_id={sample_account.build_parts_id}, payment_types_id={debit_enum.id}, account_records_id=None")
|
||||
|
||||
# Try without date filters first
|
||||
basic_payments = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.build_parts_id == sample_account.build_parts_id, BuildDecisionBookPayments.payment_types_id == debit_enum.id,
|
||||
BuildDecisionBookPayments.account_records_id == None
|
||||
).count()
|
||||
print(f"Found {basic_payments} payments without date filters")
|
||||
|
||||
# Now try with date filters
|
||||
dated_payments = BuildDecisionBookPayments.query.filter(
|
||||
BuildDecisionBookPayments.build_parts_id == sample_account.build_parts_id, BuildDecisionBookPayments.payment_types_id == debit_enum.id,
|
||||
BuildDecisionBookPayments.account_records_id == None, BuildDecisionBookPayments.process_date_y == int(account_bank_date_year),
|
||||
BuildDecisionBookPayments.process_date_m == int(account_bank_date_month)
|
||||
).count()
|
||||
print(f"Found {dated_payments} payments with date filters (year={account_bank_date_year}, month={account_bank_date_month})")
|
||||
else:
|
||||
print("No matching AccountRecord found for debugging!")
|
||||
|
||||
# Query for account records that need payment processing
|
||||
# Note: We removed the approved_record condition as it was too restrictive
|
||||
account_records_list = AccountRecords.query.filter(AccountRecords.remainder_balance < AccountRecords.currency_value, AccountRecords.receive_debit == receive_enum.id
|
||||
).order_by(AccountRecords.bank_date.desc()).all()
|
||||
|
||||
print(f"\nProcessing {len(account_records_list)} account records")
|
||||
|
||||
# Track how many records were processed with each method
|
||||
traditional_method_count = 0
|
||||
direct_method_count = 0
|
||||
|
||||
for account_record in account_records_list:
|
||||
# Try the traditional method first
|
||||
current_currency_value = pay_the_registration(account_record, receive_enum, debit_enum, False, session)
|
||||
|
||||
if current_currency_value > 0:
|
||||
# If first pass found payments, try with old records too
|
||||
pay_the_registration(account_record, receive_enum, debit_enum, True, session)
|
||||
traditional_method_count += 1
|
||||
else:
|
||||
# If traditional method didn't work, try direct payment for all records
|
||||
# This will handle records with missing build_parts_id
|
||||
if create_direct_payment(account_record, receive_enum, session):
|
||||
direct_method_count += 1
|
||||
if direct_method_count % 10 == 0: # Only print every 10th record to avoid too much output
|
||||
print(f"Direct payment created for account_record {account_record.id}")
|
||||
|
||||
# Refresh the account record to get updated values
|
||||
session.refresh(account_record)
|
||||
|
||||
# Update status if the remainder balance equals the currency value
|
||||
if abs(float(Decimal(account_record.remainder_balance))) == abs(float(Decimal(account_record.currency_value))):
|
||||
account_record.update(status_id=97)
|
||||
account_record.save()
|
||||
|
||||
print(f"\nProcessed with traditional method: {traditional_method_count} records")
|
||||
print(f"Processed with direct payment method: {direct_method_count} records")
|
||||
|
||||
print("Payment processing completed successfully")
|
||||
except Exception as e:
|
||||
print(f"Error in send_accounts_to_decision_payment: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
# Rollback the session in case of error
|
||||
session.rollback()
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Payment Service is running...")
|
||||
try:
|
||||
send_accounts_to_decision_payment()
|
||||
|
||||
# Print counter statistics
|
||||
print("\n--- Processing Statistics ---")
|
||||
print(f"Records where current_currency_value <= 0: {counter_current_currency_not_positive}")
|
||||
print(f"Records where net_amount <= 0: {counter_net_amount_not_positive}")
|
||||
print(f"Account records updated: {counter_account_records_updated}")
|
||||
print(f"Total payments found: {counter_payments_found}")
|
||||
print(f"Skips due to found_payment: {counter_found_payment_skips}")
|
||||
print(f"Payment exceptions: {counter_payment_exceptions}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
print("Payment Service is finished...")
|
||||
26
ServicesBank/Finder/Payment/run_app.sh
Normal file
26
ServicesBank/Finder/Payment/run_app.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Source the environment file directly
|
||||
. /env.sh
|
||||
|
||||
# Re-export all variables to ensure they're available to the Python script
|
||||
export POSTGRES_USER
|
||||
export POSTGRES_PASSWORD
|
||||
export POSTGRES_DB
|
||||
export POSTGRES_HOST
|
||||
export POSTGRES_PORT
|
||||
export POSTGRES_ENGINE
|
||||
export POSTGRES_POOL_PRE_PING
|
||||
export POSTGRES_POOL_SIZE
|
||||
export POSTGRES_MAX_OVERFLOW
|
||||
export POSTGRES_POOL_RECYCLE
|
||||
export POSTGRES_POOL_TIMEOUT
|
||||
export POSTGRES_ECHO
|
||||
|
||||
# Python environment variables
|
||||
export PYTHONPATH
|
||||
export PYTHONUNBUFFERED
|
||||
export PYTHONDONTWRITEBYTECODE
|
||||
|
||||
# env >> /var/log/cron.log
|
||||
/usr/local/bin/python /runner.py
|
||||
854
ServicesBank/Finder/Payment/runner.py
Normal file
854
ServicesBank/Finder/Payment/runner.py
Normal file
@@ -0,0 +1,854 @@
|
||||
import arrow
|
||||
import calendar
|
||||
import time
|
||||
|
||||
from decimal import Decimal
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from Schemas import BuildDecisionBookPayments, AccountRecords, ApiEnumDropdown, Build, BuildDecisionBook
|
||||
from time import perf_counter
|
||||
from sqlalchemy import select, func, distinct, cast, Date, String, literal, desc, and_, or_
|
||||
from Controllers.Postgres.engine import get_session_factory
|
||||
#from ServicesApi.Schemas.account.account import AccountRecords
|
||||
#from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments
|
||||
|
||||
|
||||
def find_last_day_of_month(date_value):
|
||||
today = date_value.date()
|
||||
_, last_day = calendar.monthrange(today.year, today.month)
|
||||
return datetime(today.year, today.month, last_day, 23, 59, 59)
|
||||
|
||||
def find_first_day_of_month(date_value):
|
||||
today = date_value.date()
|
||||
return datetime(today.year, today.month, 1)
|
||||
|
||||
|
||||
class BuildDuesTypes:
|
||||
|
||||
def __init__(self):
|
||||
self.debit: ApiEnumDropdownShallowCopy = None
|
||||
self.add_debit: ApiEnumDropdownShallowCopy = None
|
||||
self.renovation: ApiEnumDropdownShallowCopy = None
|
||||
self.lawyer_expence: ApiEnumDropdownShallowCopy = None
|
||||
self.service_fee: ApiEnumDropdownShallowCopy = None
|
||||
self.information: ApiEnumDropdownShallowCopy = None
|
||||
|
||||
|
||||
class ApiEnumDropdownShallowCopy:
|
||||
id: int
|
||||
uuid: str
|
||||
enum_class: str
|
||||
key: str
|
||||
value: str
|
||||
|
||||
def __init__(self, id: int, uuid: str, enum_class: str, key: str, value: str):
|
||||
self.id = id
|
||||
self.uuid = uuid
|
||||
self.enum_class = enum_class
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
|
||||
def get_enums_from_database():
|
||||
build_dues_types = BuildDuesTypes()
|
||||
with ApiEnumDropdown.new_session() as session:
|
||||
|
||||
ApiEnumDropdown.set_session(session)
|
||||
|
||||
debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-D").first() # Debit
|
||||
add_debit_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-A").first() # Add Debit
|
||||
renovation_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-R").first() # Renovation
|
||||
late_payment_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-L").first() # Lawyer expence
|
||||
service_fee_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-S").first() # Service fee
|
||||
information_enum_shallow = ApiEnumDropdown.query.filter_by(enum_class="BuildDuesTypes", key="BDT-I").first() # Information
|
||||
|
||||
build_dues_types.debit = ApiEnumDropdownShallowCopy(
|
||||
debit_enum_shallow.id, str(debit_enum_shallow.uu_id), debit_enum_shallow.enum_class, debit_enum_shallow.key, debit_enum_shallow.value
|
||||
)
|
||||
build_dues_types.add_debit = ApiEnumDropdownShallowCopy(
|
||||
add_debit_enum_shallow.id, str(add_debit_enum_shallow.uu_id), add_debit_enum_shallow.enum_class, add_debit_enum_shallow.key, add_debit_enum_shallow.value
|
||||
)
|
||||
build_dues_types.renovation = ApiEnumDropdownShallowCopy(
|
||||
renovation_enum_shallow.id, str(renovation_enum_shallow.uu_id), renovation_enum_shallow.enum_class, renovation_enum_shallow.key, renovation_enum_shallow.value
|
||||
)
|
||||
build_dues_types.lawyer_expence = ApiEnumDropdownShallowCopy(
|
||||
late_payment_enum_shallow.id, str(late_payment_enum_shallow.uu_id), late_payment_enum_shallow.enum_class, late_payment_enum_shallow.key, late_payment_enum_shallow.value
|
||||
)
|
||||
build_dues_types.service_fee = ApiEnumDropdownShallowCopy(
|
||||
service_fee_enum_shallow.id, str(service_fee_enum_shallow.uu_id), service_fee_enum_shallow.enum_class, service_fee_enum_shallow.key, service_fee_enum_shallow.value
|
||||
)
|
||||
build_dues_types.information = ApiEnumDropdownShallowCopy(
|
||||
information_enum_shallow.id, str(information_enum_shallow.uu_id), information_enum_shallow.enum_class, information_enum_shallow.key, information_enum_shallow.value
|
||||
)
|
||||
return [build_dues_types.debit, build_dues_types.lawyer_expence, build_dues_types.add_debit, build_dues_types.renovation, build_dues_types.service_fee, build_dues_types.information]
|
||||
|
||||
|
||||
def generate_total_paid_amount_for_spesific_build_part_id(build_parts_id: int, session):
|
||||
"""
|
||||
Calculate the total amount paid for a specific build part ID.
|
||||
|
||||
Args:
|
||||
build_parts_id: The build part ID to calculate payments for
|
||||
session: Database session
|
||||
|
||||
Returns:
|
||||
float: The total amount paid (absolute value)
|
||||
"""
|
||||
payment_query = session.query(func.sum(BuildDecisionBookPayments.payment_amount)).filter(
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
BuildDecisionBookPayments.account_is_debit == False,
|
||||
cast(BuildDecisionBookPayments.process_date, Date) >= '2022-01-01'
|
||||
).scalar()
|
||||
return payment_query if payment_query is not None else 0
|
||||
|
||||
|
||||
def generate_total_debt_amount_for_spesific_build_part_id(build_parts_id: int, session):
|
||||
|
||||
# Use SQLAlchemy's func.sum to calculate the total debts
|
||||
# For total debt, we want to include ALL debts, both processed and unprocessed
|
||||
result = session.query(
|
||||
func.sum(BuildDecisionBookPayments.payment_amount)
|
||||
).filter(
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
BuildDecisionBookPayments.account_is_debit == True,
|
||||
cast(BuildDecisionBookPayments.process_date, Date) >= '2022-01-01'
|
||||
).scalar()
|
||||
|
||||
# Return 0 if no debts found, otherwise return the absolute value of the sum
|
||||
return abs(result) if result is not None else 0
|
||||
|
||||
|
||||
def generate_total_amount_that_user_has_in_account(account_record: AccountRecords, session):
|
||||
# Get total amount that user has in account
|
||||
result = session.query(
|
||||
func.sum(AccountRecords.currency_value)
|
||||
).filter(
|
||||
AccountRecords.build_parts_id == account_record.build_parts_id,
|
||||
AccountRecords.currency_value > 0,
|
||||
cast(AccountRecords.bank_date, Date) >= '2022-01-01'
|
||||
).scalar()
|
||||
|
||||
# Return 0 if no payments found, otherwise return the absolute value of the sum
|
||||
return abs(result)
|
||||
|
||||
|
||||
|
||||
def _print_debt_details(debt, session):
|
||||
"""Helper function to print detailed information about an unpaid debt.
|
||||
|
||||
Args:
|
||||
debt: The BuildDecisionBookPayments object representing the debt
|
||||
session: Database session
|
||||
"""
|
||||
# Get the sum of payments for this debt
|
||||
payments_sum = session.query(
|
||||
func.sum(BuildDecisionBookPayments.payment_amount)
|
||||
).filter(
|
||||
BuildDecisionBookPayments.ref_id == debt.ref_id,
|
||||
BuildDecisionBookPayments.account_is_debit == False
|
||||
).scalar() or 0
|
||||
|
||||
# Calculate remaining amount
|
||||
debit_amount = abs(debt.payment_amount)
|
||||
remaining = debit_amount - abs(payments_sum)
|
||||
payment_percentage = (abs(payments_sum) / debit_amount) * 100 if debit_amount > 0 else 0
|
||||
|
||||
# Format the date for display
|
||||
date_str = debt.process_date.strftime('%Y-%m-%d') if debt.process_date else 'Unknown date'
|
||||
|
||||
|
||||
def analyze_payment_function():
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
# Set session for all models
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
order_pay = get_enums_from_database()
|
||||
# Get distinct build_parts_id values from account records with positive currency_value
|
||||
# This avoids redundant processing of the same build_parts_id
|
||||
distinct_build_parts = session.query(
|
||||
distinct(AccountRecords.build_parts_id)
|
||||
).filter(
|
||||
AccountRecords.build_parts_id.isnot(None),
|
||||
AccountRecords.currency_value > 0,
|
||||
AccountRecords.bank_date >= '2022-01-01'
|
||||
).order_by(AccountRecords.build_parts_id.desc()).all()
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
for build_part_id_tuple in distinct_build_parts:
|
||||
build_part_id = build_part_id_tuple[0] # Extract the ID from the tuple
|
||||
process_date = datetime.now()
|
||||
last_date_of_process_date = datetime(process_date.year, process_date.month, 1) - timedelta(days=1)
|
||||
first_date_of_process_date = datetime(process_date.year, process_date.month, 1)
|
||||
|
||||
print(f"\n{'=' * 50}")
|
||||
print(f"ACCOUNT ANALYSIS FOR BUILD PART ID: {build_part_id}")
|
||||
print(f"{'=' * 50}")
|
||||
|
||||
# Calculate total paid amount for this build_part_id
|
||||
total_amount_paid = generate_total_paid_amount_for_spesific_build_part_id(build_part_id, session)
|
||||
|
||||
# Calculate total debt amount for this build_part_id
|
||||
total_debt_amount = generate_total_debt_amount_for_spesific_build_part_id(build_part_id, session)
|
||||
|
||||
# Get total amount in account for this build_part_id
|
||||
account_record = AccountRecords()
|
||||
account_record.build_parts_id = build_part_id
|
||||
total_amount_in_account = generate_total_amount_that_user_has_in_account(account_record, session)
|
||||
|
||||
# Calculate remaining amount to be paid
|
||||
amount_need_to_paid = total_debt_amount - total_amount_paid
|
||||
total_amount_that_user_need_to_transfer = abs(amount_need_to_paid) - abs(total_amount_in_account)
|
||||
|
||||
# Print summary with clear descriptions
|
||||
print(f"PAYMENT SUMMARY:")
|
||||
print(f" • Total debt amount: {total_debt_amount:,.2f} TL")
|
||||
print(f" • Amount already paid: {total_amount_paid:,.2f} TL")
|
||||
print(f" • Remaining debt to be collected: {amount_need_to_paid:,.2f} TL")
|
||||
print(f" • Current account balance: {total_amount_in_account:,.2f} TL")
|
||||
|
||||
if total_amount_that_user_need_to_transfer > 0:
|
||||
print(f" • Additional funds needed: {total_amount_that_user_need_to_transfer:,.2f} TL")
|
||||
elif amount_need_to_paid <= 0:
|
||||
print(f" • Account is fully paid with no outstanding debt")
|
||||
else:
|
||||
print(f" • Sufficient funds available to close all debt")
|
||||
# Show debt coverage percentage
|
||||
if total_debt_amount > 0:
|
||||
# Calculate current coverage (already paid)
|
||||
current_coverage_percentage = (total_amount_paid / total_debt_amount) * 100
|
||||
|
||||
# Calculate potential coverage (including available funds)
|
||||
potential_coverage = min(100, ((total_amount_paid + total_amount_in_account) / total_debt_amount) * 100)
|
||||
|
||||
# Display both percentages
|
||||
print(f" • Current debt coverage: {current_coverage_percentage:.2f}%")
|
||||
print(f" • Potential debt coverage with available funds: {potential_coverage:.2f}%")
|
||||
|
||||
# Analyze unpaid debts for each payment type
|
||||
print("\nUNPAID DEBTS ANALYSIS BY PAYMENT TYPE:")
|
||||
for payment_type in order_pay:
|
||||
# Get unpaid debts for current month
|
||||
date_query_current = (
|
||||
BuildDecisionBookPayments.process_date >= first_date_of_process_date,
|
||||
BuildDecisionBookPayments.process_date <= process_date
|
||||
)
|
||||
date_query_previous = (
|
||||
BuildDecisionBookPayments.process_date < first_date_of_process_date,
|
||||
)
|
||||
|
||||
current_unpaid_debts = get_unpaid_debts(build_parts_id=build_part_id, session=session, debit_type=payment_type, date_query=date_query_current)
|
||||
|
||||
# Get unpaid debts from previous months
|
||||
previous_unpaid_debts = get_unpaid_debts(build_parts_id=build_part_id, session=session, debit_type=payment_type, date_query=date_query_previous)
|
||||
|
||||
# Calculate totals
|
||||
current_total = sum(abs(debt[2]) for debt in current_unpaid_debts)
|
||||
previous_total = sum(abs(debt[2]) for debt in previous_unpaid_debts)
|
||||
grand_total = current_total + previous_total
|
||||
|
||||
# Print summary for this payment type
|
||||
if current_unpaid_debts or previous_unpaid_debts:
|
||||
print(f" • {payment_type.key}: Total unpaid: {grand_total:,.2f} TL")
|
||||
|
||||
# Current month details
|
||||
if current_unpaid_debts:
|
||||
print(f" - Current month: {len(current_unpaid_debts)} debts, {current_total:,.2f} TL")
|
||||
# Show details of each unpaid debt if there aren't too many
|
||||
# if len(current_unpaid_debts) <= 3:
|
||||
# for debt in current_unpaid_debts:
|
||||
# _print_debt_details(debt, session)
|
||||
|
||||
# Previous months details
|
||||
if previous_unpaid_debts:
|
||||
print(f" - Previous months: {len(previous_unpaid_debts)} debts, {previous_total:,.2f} TL")
|
||||
# Show details of each unpaid debt if there aren't too many
|
||||
# if len(previous_unpaid_debts) <= 3:
|
||||
# for debt in previous_unpaid_debts:
|
||||
# _print_debt_details(debt, session)
|
||||
else:
|
||||
print(f" • {payment_type.key}: All debts paid")
|
||||
print(f"{'=' * 50}\n")
|
||||
|
||||
|
||||
def close_payment_book(payment_row_book, account_record, value, session):
|
||||
"""Create a credit entry in BuildDecisionBookPayments to close a debt.
|
||||
|
||||
Args:
|
||||
payment_row_book: The debit entry to be paid
|
||||
account_record: The account record containing the funds
|
||||
value: The amount to pay
|
||||
session: Database session
|
||||
|
||||
Returns:
|
||||
The newly created payment record
|
||||
"""
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
# Create a new credit entry (payment)
|
||||
new_row = BuildDecisionBookPayments.create(
|
||||
ref_id=str(payment_row_book.uu_id),
|
||||
payment_plan_time_periods=payment_row_book.payment_plan_time_periods,
|
||||
period_time=payment_row_book.period_time,
|
||||
currency=payment_row_book.currency,
|
||||
account_records_id=account_record.id,
|
||||
account_records_uu_id=str(account_record.uu_id),
|
||||
build_parts_id=payment_row_book.build_parts_id,
|
||||
build_parts_uu_id=str(payment_row_book.build_parts_uu_id),
|
||||
payment_amount=abs(value), # Negative for credit entries
|
||||
payment_types_id=payment_row_book.payment_types_id,
|
||||
payment_types_uu_id=str(payment_row_book.payment_types_uu_id),
|
||||
process_date_m=payment_row_book.process_date.month,
|
||||
process_date_y=payment_row_book.process_date.year,
|
||||
process_date=payment_row_book.process_date,
|
||||
build_decision_book_item_id=payment_row_book.build_decision_book_item_id if payment_row_book.build_decision_book_item_id else None,
|
||||
build_decision_book_item_uu_id=str(payment_row_book.build_decision_book_item_uu_id) if payment_row_book.build_decision_book_item_uu_id else None,
|
||||
decision_book_project_id=payment_row_book.decision_book_project_id if payment_row_book.decision_book_project_id else None,
|
||||
decision_book_project_uu_id=str(payment_row_book.decision_book_project_uu_id) if payment_row_book.decision_book_project_uu_id else None,
|
||||
build_decision_book_id=payment_row_book.build_decision_book_id if payment_row_book.build_decision_book_id else None,
|
||||
build_decision_book_uu_id=str(payment_row_book.build_decision_book_uu_id) if payment_row_book.build_decision_book_uu_id else None,
|
||||
is_confirmed=True,
|
||||
account_is_debit=False,
|
||||
)
|
||||
|
||||
# Save the new payment record
|
||||
saved_row = new_row.save()
|
||||
session.commit()
|
||||
session.refresh(saved_row)
|
||||
session.flush()
|
||||
return saved_row
|
||||
|
||||
|
||||
def update_account_remainder_if_spent(account_record, ref_id: str, session):
|
||||
"""Update the remainder_balance of an account after spending money.
|
||||
|
||||
Args:
|
||||
account_record: The account record to update
|
||||
amount_spent: The amount spent in this transaction
|
||||
session: Database session
|
||||
|
||||
Returns:
|
||||
bool: True if all money is spent, False otherwise
|
||||
"""
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
session.commit()
|
||||
sum_of_paid = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||
BuildDecisionBookPayments.account_records_id == account_record.id,
|
||||
BuildDecisionBookPayments.account_is_debit == False
|
||||
).scalar()
|
||||
if not sum_of_paid:
|
||||
return False
|
||||
debit_row = BuildDecisionBookPayments.query.filter_by(ref_id=ref_id).first()
|
||||
account_record_to_update = AccountRecords.query.filter_by(id=account_record.id).first()
|
||||
account_record_to_update.remainder_balance = (-1 * abs(sum_of_paid)) or 0
|
||||
account_record_to_update.save()
|
||||
session.commit()
|
||||
session.refresh(account_record_to_update)
|
||||
# Get the current remainder balance
|
||||
if abs(sum_of_paid) == abs(account_record_to_update.currency_value):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def update_all_spent_accounts(session):
|
||||
"""Update remainder_balance for all accounts with payments.
|
||||
|
||||
This function finds account records in BuildDecisionBookPayments and updates
|
||||
their remainder_balance based on the sum of payments made, regardless of whether
|
||||
all funds have been spent or not.
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
"""
|
||||
with AccountRecords.new_session() as session:
|
||||
# Set sessions for models
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
# Get distinct account_records_id values from BuildDecisionBookPayments
|
||||
distinct_account_ids = session.query(BuildDecisionBookPayments.account_records_id).filter(
|
||||
BuildDecisionBookPayments.account_records_id.isnot(None),
|
||||
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments)
|
||||
).distinct().all()
|
||||
|
||||
updated_count = 0
|
||||
|
||||
for account_id_tuple in distinct_account_ids:
|
||||
account_id = account_id_tuple[0]
|
||||
|
||||
# Get the account record
|
||||
account = AccountRecords.query.filter_by(id=account_id).first()
|
||||
if not account or not account.build_parts_id or account.currency_value <= 0:
|
||||
continue
|
||||
|
||||
# Calculate the sum of payments made using this account
|
||||
# Note: payment_amount is negative for credit entries, so we need to use abs() to get the positive amount
|
||||
payment_query = session.query(func.sum(func.abs(BuildDecisionBookPayments.payment_amount))).filter(
|
||||
BuildDecisionBookPayments.account_records_id == account_id,
|
||||
BuildDecisionBookPayments.account_is_debit == False # Credit entries (payments)
|
||||
)
|
||||
|
||||
payment_sum = payment_query.scalar() or 0
|
||||
|
||||
# Update remainder_balance for ALL accounts, regardless of payment_sum value
|
||||
threshold = Decimal('0.01')
|
||||
fully_spent = abs(payment_sum) >= abs(account.currency_value) - threshold
|
||||
status = "All funds spent" if fully_spent else "Partial payment"
|
||||
|
||||
# Store the positive value in remainder_balance
|
||||
account.remainder_balance = -1 * abs(payment_sum)
|
||||
account.save()
|
||||
updated_count += 1
|
||||
session.commit()
|
||||
print(f"\nTotal accounts updated: {updated_count}")
|
||||
|
||||
|
||||
# def find_amount_to_pay_by_ref_id(ref_id, session):
|
||||
# """Calculate the remaining amount to pay for a specific debt reference ID.
|
||||
|
||||
# Args:
|
||||
# ref_id: The reference ID of the debt (this is the uu_id of the debt record)
|
||||
# session: Database session
|
||||
|
||||
# Returns:
|
||||
# float: The remaining amount to pay
|
||||
# """
|
||||
# # Get the original debt amount - the debt is identified by its uu_id which is passed as ref_id
|
||||
# debit = BuildDecisionBookPayments.query.filter(
|
||||
# BuildDecisionBookPayments.uu_id == ref_id,
|
||||
# BuildDecisionBookPayments.account_is_debit == True
|
||||
# ).first()
|
||||
# if not debit:
|
||||
# return 0 # No debit found, nothing to pay
|
||||
|
||||
# debit_amount = abs(debit.payment_amount) # Ensure positive value for debit amount
|
||||
|
||||
# # Get the sum of payments already made for this debt
|
||||
# # The ref_id in credit records points to the uu_id of the original debit
|
||||
# # Note: payment_amount is negative for credit entries, so we use abs() to get positive values
|
||||
# credit_amount = session.query(
|
||||
# func.sum(func.abs(BuildDecisionBookPayments.payment_amount))
|
||||
# ).filter(
|
||||
# BuildDecisionBookPayments.ref_id == str(ref_id),
|
||||
# BuildDecisionBookPayments.account_is_debit == False
|
||||
# ).scalar() or 0
|
||||
# # Calculate remaining amount to pay
|
||||
# remaining = abs(debit_amount) - abs(credit_amount)
|
||||
# # Ensure we don't return negative values
|
||||
# if remaining < 0:
|
||||
# return 0
|
||||
|
||||
# return remaining
|
||||
|
||||
def get_unpaid_debts_via_build_parts(build_parts_id: int, session, debit_type, date_query: tuple):
|
||||
"""Find BuildDecisionBookPayments entries where the debt has NOT been fully paid for a specific debit type.
|
||||
|
||||
This function identifies payments where the sum of payments is less than the debit amount,
|
||||
meaning the debt has not been fully closed.
|
||||
|
||||
Args:
|
||||
build_parts_id: The build part ID to check
|
||||
session: Database session
|
||||
debit_type: The specific debit type to check
|
||||
date_query: Tuple of date filters to apply
|
||||
|
||||
Returns:
|
||||
list: List of unpaid debt entries (full BuildDecisionBookPayments objects)
|
||||
|
||||
query:
|
||||
SELECT
|
||||
bpf.ref_id,
|
||||
bpf.process_date,
|
||||
ABS(COALESCE(SUM(bpf.payment_amount), 0)) AS total_payments
|
||||
FROM public.build_decision_book_payments AS bpf
|
||||
GROUP BY
|
||||
bpf.ref_id,
|
||||
bpf.process_date
|
||||
HAVING ABS(COALESCE(SUM(bpf.payment_amount), 0)) > 0
|
||||
order by bpf.process_date
|
||||
"""
|
||||
# Create a subquery for the payment sums without executing it separately
|
||||
payment_sums_subquery = select(
|
||||
BuildDecisionBookPayments.ref_id,
|
||||
BuildDecisionBookPayments.process_date,
|
||||
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)).label("total_payments")
|
||||
).filter(
|
||||
BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
||||
*date_query
|
||||
).group_by(
|
||||
BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date
|
||||
).having(
|
||||
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)) > 0
|
||||
).order_by(BuildDecisionBookPayments.process_date.desc())
|
||||
|
||||
# Use the subquery directly in the main query
|
||||
payment_sums = session.execute(payment_sums_subquery).all()
|
||||
payment_sums_list = []
|
||||
for item in payment_sums:
|
||||
payment_sums_list.append({"ref_id": item[0], "process_date": item[1], "total_payments": item[2]})
|
||||
return payment_sums
|
||||
|
||||
|
||||
def get_unpaid_debts(session, debit_type, date_query: tuple):
|
||||
"""Find BuildDecisionBookPayments entries where the debt has NOT been fully paid for a specific debit type.
|
||||
|
||||
This function identifies payments where the sum of payments is less than the debit amount,
|
||||
meaning the debt has not been fully closed.
|
||||
|
||||
Args:
|
||||
build_parts_id: The build part ID to check
|
||||
session: Database session
|
||||
debit_type: The specific debit type to check
|
||||
date_query: Tuple of date filters to apply
|
||||
|
||||
Returns:
|
||||
list: List of unpaid debt entries (full BuildDecisionBookPayments objects)
|
||||
|
||||
query:
|
||||
SELECT
|
||||
bpf.ref_id,
|
||||
bpf.process_date,
|
||||
ABS(COALESCE(SUM(bpf.payment_amount), 0)) AS total_payments
|
||||
FROM public.build_decision_book_payments AS bpf
|
||||
GROUP BY
|
||||
bpf.ref_id,
|
||||
bpf.process_date
|
||||
HAVING ABS(COALESCE(SUM(bpf.payment_amount), 0)) > 0
|
||||
order by bpf.process_date
|
||||
"""
|
||||
# Create a subquery for the payment sums without executing it separately
|
||||
do_payments_set = lambda ref, date, total: {"ref_id": ref, "process_date": date, "total_payments": total}
|
||||
payment_sums_subquery = select(BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date,
|
||||
func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)).label("total_payments")
|
||||
).filter(BuildDecisionBookPayments.payment_types_id == debit_type.id, *date_query
|
||||
).group_by(BuildDecisionBookPayments.ref_id, BuildDecisionBookPayments.process_date
|
||||
).having(func.abs(func.coalesce(func.sum(BuildDecisionBookPayments.payment_amount), 0)) > 0
|
||||
).order_by(BuildDecisionBookPayments.process_date.asc())
|
||||
|
||||
payment_sums = session.execute(payment_sums_subquery).all()
|
||||
return [do_payments_set(item[0], item[1], item[2]) for item in payment_sums]
|
||||
|
||||
|
||||
def do_payments_of_this_month(build_id: int = 1):
|
||||
"""Process payments for the current month's unpaid debts.
|
||||
This function retrieves account records with available funds and processes
|
||||
payments for current month's unpaid debts in order of payment type priority.
|
||||
"""
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
|
||||
# Set session for all models
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
Build.set_session(session)
|
||||
BuildDecisionBook.set_session(session)
|
||||
|
||||
# Get payment types in priority order
|
||||
payment_type_list = get_enums_from_database()
|
||||
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
||||
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
||||
target_build = Build.query.filter(Build.id == build_id).first()
|
||||
if not target_build:
|
||||
raise ValueError(f"Build with id {build_id} not found")
|
||||
|
||||
now = datetime.now()
|
||||
decision_book = BuildDecisionBook.query.filter(
|
||||
BuildDecisionBook.build_id == build_id, cast(BuildDecisionBook.expiry_starts, Date) <= now.date(),
|
||||
cast(BuildDecisionBook.expiry_ends, Date) >= now.date(), BuildDecisionBook.decision_type == "RBM"
|
||||
).first()
|
||||
if not decision_book:
|
||||
raise ValueError(f"Decision book not found for build with id {build_id}")
|
||||
|
||||
period_date_start = decision_book.expiry_starts
|
||||
period_date_end = decision_book.expiry_ends
|
||||
period_id = decision_book.id
|
||||
|
||||
first_date_of_process_date = find_first_day_of_month(now)
|
||||
last_date_of_process_date = find_last_day_of_month(now)
|
||||
|
||||
# Current month date filter
|
||||
date_query_tuple = (
|
||||
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
||||
)
|
||||
|
||||
update_all_spent_accounts(session)
|
||||
|
||||
for payment_type in payment_type_list:
|
||||
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
|
||||
for unpaid_debt in unpaid_debts:
|
||||
amount_to_pay = unpaid_debt["total_payments"]
|
||||
ref_id = unpaid_debt["ref_id"]
|
||||
process_date = unpaid_debt["process_date"]
|
||||
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
|
||||
build_parts_id = debit_row.build_parts_id
|
||||
money_to_pay_rows = AccountRecords.query.filter(
|
||||
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
||||
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
||||
).order_by(AccountRecords.bank_date.asc()).all()
|
||||
for money_to_pay_row in money_to_pay_rows:
|
||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||
money_to_pay_row = AccountRecords.query.filter(AccountRecords.id == money_to_pay_row.id).first()
|
||||
available_money = abs(money_to_pay_row.currency_value) - abs(money_to_pay_row.remainder_balance)
|
||||
if available_money > amount_to_pay:
|
||||
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
|
||||
total_amount_paid += amount_to_pay
|
||||
paid_count += 1
|
||||
payments_made += 1
|
||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||
break
|
||||
elif available_money <= amount_to_pay:
|
||||
print('All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||
close_payment_book(debit_row, money_to_pay_row, available_money, session)
|
||||
total_amount_paid += available_money
|
||||
paid_count += 1
|
||||
payments_made += 1
|
||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||
continue
|
||||
else:
|
||||
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
|
||||
|
||||
update_all_spent_accounts(session)
|
||||
|
||||
print('payments_made', payments_made)
|
||||
print('total_amount_paid', total_amount_paid)
|
||||
print('paid_count', paid_count)
|
||||
|
||||
|
||||
def do_payments_of_previos_months(build_id: int = 1):
|
||||
"""Process payments for previous months' unpaid debts.
|
||||
|
||||
This function retrieves account records with available funds and processes
|
||||
payments for previous months' unpaid debts in order of payment type priority.
|
||||
"""
|
||||
"""Process payments for the current month's unpaid debts.
|
||||
This function retrieves account records with available funds and processes
|
||||
payments for current month's unpaid debts in order of payment type priority.
|
||||
"""
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
|
||||
# Set session for all models
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
Build.set_session(session)
|
||||
BuildDecisionBook.set_session(session)
|
||||
|
||||
# Get payment types in priority order
|
||||
payment_type_list = get_enums_from_database()
|
||||
fund_finished = lambda money_spend, money_in_account: money_spend == money_in_account
|
||||
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
||||
target_build = Build.query.filter(Build.id == build_id).first()
|
||||
if not target_build:
|
||||
raise ValueError(f"Build with id {build_id} not found")
|
||||
|
||||
now = datetime.now()
|
||||
decision_book = BuildDecisionBook.query.filter(
|
||||
BuildDecisionBook.build_id == build_id,
|
||||
cast(BuildDecisionBook.expiry_starts, Date) <= now.date(),
|
||||
cast(BuildDecisionBook.expiry_ends, Date) >= now.date(),
|
||||
BuildDecisionBook.decision_type == "RBM"
|
||||
).first()
|
||||
if not decision_book:
|
||||
raise ValueError(f"Decision book not found for build with id {build_id}")
|
||||
early_date = datetime(now.year - 1, now.month, now.day)
|
||||
early_decision_book = BuildDecisionBook.query.filter(
|
||||
BuildDecisionBook.build_id == build_id,
|
||||
cast(BuildDecisionBook.expiry_starts, Date) <= early_date.date(),
|
||||
cast(BuildDecisionBook.expiry_ends, Date) >= early_date.date(),
|
||||
BuildDecisionBook.decision_type == "RBM"
|
||||
).first()
|
||||
|
||||
period_date_start = decision_book.expiry_starts
|
||||
period_date_end = decision_book.expiry_ends
|
||||
period_id = decision_book.id
|
||||
early_period_date_start = early_decision_book.expiry_starts
|
||||
early_period_date_end = early_decision_book.expiry_ends
|
||||
early_period_id = early_decision_book.id
|
||||
|
||||
first_date_of_process_date = arrow.get(period_date_start).datetime
|
||||
last_date_of_process_date = now
|
||||
|
||||
# Current month date filter
|
||||
date_query_tuple = (
|
||||
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(),
|
||||
cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
||||
)
|
||||
|
||||
update_all_spent_accounts(session)
|
||||
|
||||
for payment_type in payment_type_list:
|
||||
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
|
||||
print('length unpaid debts: ', len(unpaid_debts))
|
||||
for unpaid_debt in unpaid_debts:
|
||||
amount_to_pay = unpaid_debt["total_payments"]
|
||||
ref_id = unpaid_debt["ref_id"]
|
||||
process_date = unpaid_debt["process_date"]
|
||||
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
|
||||
build_parts_id = debit_row.build_parts_id
|
||||
money_to_pay_rows = AccountRecords.query.filter(
|
||||
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
||||
cast(AccountRecords.bank_date, Date) >= find_first_day_of_month(process_date).date(), cast(AccountRecords.bank_date, Date) <= find_last_day_of_month(process_date).date(),
|
||||
).order_by(AccountRecords.bank_date.asc()).all()
|
||||
if not money_to_pay_rows:
|
||||
money_to_pay_rows = AccountRecords.query.filter(
|
||||
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
||||
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
||||
).order_by(AccountRecords.bank_date.asc()).all()
|
||||
for money_to_pay_row in money_to_pay_rows:
|
||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||
money_to_pay_row = AccountRecords.query.filter(AccountRecords.id == money_to_pay_row.id).first()
|
||||
available_money = abs(money_to_pay_row.currency_value) - abs(money_to_pay_row.remainder_balance)
|
||||
if available_money > amount_to_pay:
|
||||
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
|
||||
total_amount_paid += amount_to_pay
|
||||
paid_count += 1
|
||||
payments_made += 1
|
||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||
break
|
||||
elif available_money <= amount_to_pay:
|
||||
print('All money is spent amount_to_pay:', available_money, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||
close_payment_book(debit_row, money_to_pay_row, available_money, session)
|
||||
total_amount_paid += available_money
|
||||
paid_count += 1
|
||||
payments_made += 1
|
||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||
continue
|
||||
else:
|
||||
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
|
||||
|
||||
print('This years decision book payments')
|
||||
print('payments_made', payments_made)
|
||||
print('total_amount_paid', total_amount_paid)
|
||||
print('paid_count', paid_count)
|
||||
payments_made, total_amount_paid, paid_count = 0, 0, 0
|
||||
update_all_spent_accounts(session)
|
||||
first_date_of_process_date = arrow.get(early_period_date_start).datetime
|
||||
last_date_of_process_date = arrow.get(early_period_date_end).datetime
|
||||
|
||||
# Early month date filter
|
||||
date_query_tuple = (
|
||||
cast(BuildDecisionBookPayments.process_date, Date) >= first_date_of_process_date.date(), cast(BuildDecisionBookPayments.process_date, Date) <= last_date_of_process_date.date()
|
||||
)
|
||||
|
||||
for payment_type in payment_type_list:
|
||||
unpaid_debts = get_unpaid_debts(session, payment_type, date_query_tuple)
|
||||
print('length unpaid debts: ', len(unpaid_debts))
|
||||
for unpaid_debt in unpaid_debts:
|
||||
amount_to_pay = unpaid_debt["total_payments"]
|
||||
ref_id = unpaid_debt["ref_id"]
|
||||
process_date = unpaid_debt["process_date"]
|
||||
debit_row = BuildDecisionBookPayments.query.filter(BuildDecisionBookPayments.uu_id == ref_id).first()
|
||||
build_parts_id = debit_row.build_parts_id
|
||||
first_date_of_process_date = find_first_day_of_month(process_date)
|
||||
last_date_of_process_date = find_last_day_of_month(process_date)
|
||||
money_to_pay_rows = AccountRecords.query.filter(
|
||||
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
||||
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
||||
).order_by(AccountRecords.bank_date.asc()).all()
|
||||
if not money_to_pay_rows:
|
||||
money_to_pay_rows = AccountRecords.query.filter(
|
||||
AccountRecords.build_parts_id == build_parts_id, AccountRecords.currency_value > 0, AccountRecords.currency_value > func.abs(AccountRecords.remainder_balance),
|
||||
cast(AccountRecords.bank_date, Date) >= first_date_of_process_date.date(), cast(AccountRecords.bank_date, Date) <= last_date_of_process_date.date(),
|
||||
).order_by(AccountRecords.bank_date.asc()).all()
|
||||
for money_to_pay_row in money_to_pay_rows:
|
||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||
money_to_pay_row = AccountRecords.query.filter(AccountRecords.id == money_to_pay_row.id).first()
|
||||
available_money = abs(money_to_pay_row.currency_value) - abs(money_to_pay_row.remainder_balance)
|
||||
if available_money > amount_to_pay:
|
||||
print('NOT All money is spent amount_to_pay:', amount_to_pay, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||
close_payment_book(debit_row, money_to_pay_row, amount_to_pay, session)
|
||||
total_amount_paid += amount_to_pay
|
||||
paid_count += 1
|
||||
payments_made += 1
|
||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||
break
|
||||
elif available_money <= amount_to_pay:
|
||||
print('All money is spent amount_to_pay:', available_money, 'debit_row payment_amount:', debit_row.payment_amount)
|
||||
close_payment_book(debit_row, money_to_pay_row, available_money, session)
|
||||
total_amount_paid += available_money
|
||||
paid_count += 1
|
||||
payments_made += 1
|
||||
update_account_remainder_if_spent(account_record=money_to_pay_row, ref_id=debit_row.ref_id, session=session)
|
||||
continue
|
||||
else:
|
||||
print(f"Something else happened available_money: {available_money}, amount_to_pay: {amount_to_pay}")
|
||||
|
||||
update_all_spent_accounts(session)
|
||||
print('Early years decision book payments')
|
||||
print('payments_made', payments_made)
|
||||
print('total_amount_paid', total_amount_paid)
|
||||
print('paid_count', paid_count)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_time = perf_counter()
|
||||
|
||||
print("\n===== PROCESSING PAYMENTS =====\n")
|
||||
print("Starting payment processing at:", datetime.now())
|
||||
|
||||
# Process payments for current month first
|
||||
print("\n1. Processing current month payments...")
|
||||
do_payments_of_this_month()
|
||||
|
||||
# Process payments for previous months
|
||||
print("\n2. Processing previous months payments...")
|
||||
do_payments_of_previos_months()
|
||||
|
||||
print("\n===== PAYMENT PROCESSING COMPLETE =====\n")
|
||||
print("Payment processing completed at:", datetime.now())
|
||||
|
||||
# Analyze the payment situation after processing payments
|
||||
print("\n===== ANALYZING PAYMENT SITUATION AFTER PROCESSING =====\n")
|
||||
# analyze_payment_function()
|
||||
|
||||
end_time = perf_counter()
|
||||
print(f"\n{end_time - start_time:.3f} : seconds")
|
||||
|
||||
|
||||
# # Create a subquery to get the sum of payments for each debit's uu_id
|
||||
# # For credit entries, ref_id points to the original debit's uu_id
|
||||
# payment_sums = session.query(
|
||||
# BuildDecisionBookPayments.ref_id.label('original_debt_id'),
|
||||
# func.sum(func.abs(BuildDecisionBookPayments.payment_amount)).label('payment_sum')
|
||||
# ).filter(
|
||||
# BuildDecisionBookPayments.account_is_debit == False # Credit entries only
|
||||
# ).group_by(BuildDecisionBookPayments.ref_id).subquery()
|
||||
|
||||
# # Main query to find debits with their payment sums
|
||||
# query = session.query(BuildDecisionBookPayments)
|
||||
|
||||
# # Join with payment sums - cast uu_id to string to match ref_id type
|
||||
# query = query.outerjoin(
|
||||
# payment_sums,
|
||||
# func.cast(BuildDecisionBookPayments.uu_id, String) == payment_sums.c.original_debt_id
|
||||
# )
|
||||
|
||||
# # Filter for debits of the specified build part and payment type
|
||||
# query = query.filter(
|
||||
# BuildDecisionBookPayments.build_parts_id == build_parts_id,
|
||||
# BuildDecisionBookPayments.payment_types_id == debit_type.id,
|
||||
# BuildDecisionBookPayments.account_is_debit == True, # Debit entries only
|
||||
# )
|
||||
|
||||
# # Apply date filters if provided
|
||||
# if date_query:
|
||||
# for date_filter in date_query:
|
||||
# query = query.filter(date_filter)
|
||||
|
||||
# # Filter for debits that are not fully paid
|
||||
# # (payment_sum < debit_amount or payment_sum is NULL)
|
||||
# query = query.filter(
|
||||
# or_(
|
||||
# payment_sums.c.payment_sum.is_(None),
|
||||
# func.coalesce(payment_sums.c.payment_sum, 0) < func.abs(BuildDecisionBookPayments.payment_amount)
|
||||
# )
|
||||
# )
|
||||
|
||||
# # Execute the query and return the results
|
||||
# results = query.order_by(BuildDecisionBookPayments.process_date).all()
|
||||
3
ServicesBank/Finder/README.md
Normal file
3
ServicesBank/Finder/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Docs of Finder
|
||||
|
||||
Finds people, living spaces, companies from AccountRecords
|
||||
452
ServicesBank/Finder/account_record_parser.py
Normal file
452
ServicesBank/Finder/account_record_parser.py
Normal file
@@ -0,0 +1,452 @@
|
||||
import re
|
||||
import textdistance
|
||||
|
||||
from unidecode import unidecode
|
||||
from datetime import datetime
|
||||
from Schemas import BuildIbanDescription, BuildIbans, BuildDecisionBook, BuildLivingSpace, AccountRecords, Companies, People
|
||||
from gc import garbage
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
from regex_func import category_finder
|
||||
|
||||
|
||||
class InsertBudgetRecord(BaseModel):
|
||||
iban: str
|
||||
bank_date: str = datetime.now().__str__()
|
||||
receive_debit: str = "debit"
|
||||
budget_type: str = "B"
|
||||
currency_value: float = 0
|
||||
balance: float = 0
|
||||
bank_reference_code: str = ""
|
||||
currency: str = "TL"
|
||||
channel_branch: str = ""
|
||||
process_name: str = ""
|
||||
process_type: str = ""
|
||||
process_comment: str = ""
|
||||
|
||||
add_xcomment: Optional[str] = None
|
||||
project_no: Optional[str] = None
|
||||
company_id: Optional[str] = None
|
||||
customer_id: Optional[str] = None
|
||||
|
||||
send_person_id: Optional[int] = None
|
||||
send_company_id: Optional[int] = None
|
||||
build_id: Optional[int] = None
|
||||
build_decision_book_id: Optional[int] = None
|
||||
build_parts_id: Optional[int] = None
|
||||
build_db_item_id: Optional[int] = None
|
||||
dues_type: Optional[str] = "D"
|
||||
period_time: Optional[str] = ""
|
||||
approving_accounting_record: Optional[bool] = False
|
||||
approving_accounting_person: Optional[str] = None
|
||||
accounting_receipt_date: Optional[str] = "1900-01-01 00:00:00"
|
||||
accounting_receipt_number: Optional[int] = 0
|
||||
|
||||
|
||||
def strip_time_date(date_str):
|
||||
return datetime.strptime(date_str, "%Y-%m-%d")
|
||||
|
||||
|
||||
def strip_date_to_valid(date_str):
|
||||
return datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def find_iban_in_comment(iban: str, comment: str, living_space_dict: dict = None):
|
||||
with BuildIbanDescription.new_session() as session:
|
||||
BuildIbanDescription.set_session(session)
|
||||
iban_results = BuildIbanDescription.query.filter(BuildIbanDescription.iban == iban).all()
|
||||
sm_dict_extended, sm_dict_digit = {}, {}
|
||||
for iban_result in iban_results or []:
|
||||
extended_candidate_parts, digit_part, candidate_parts = [], [], comment.split(" ")
|
||||
for part in candidate_parts:
|
||||
if part.lower() not in ["no", "daire", "nolu"]:
|
||||
extended_candidate_parts.append(part)
|
||||
# if part.isdigit():
|
||||
# digit_part.append(part)
|
||||
if extended_candidate_parts:
|
||||
if all(candidate_part.lower() in comment.lower() for candidate_part in extended_candidate_parts):
|
||||
similarity_ratio = textdistance.jaro_winkler(unidecode(str(iban_result.search_word)), comment)
|
||||
found = False
|
||||
name_list = (unidecode(str(iban_result.search_word)).replace(".", " ").split(" "))
|
||||
for name in name_list:
|
||||
if len(name) > 3 and name.lower() in comment.lower():
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
similarity_ratio = 0.1
|
||||
sm_dict_extended[f"{iban_result.id}"] = similarity_ratio
|
||||
if sm_dict_extended:
|
||||
result = sorted(sm_dict_extended.items(), key=lambda item: item[1], reverse=True)[0]
|
||||
if float(result[1]) >= 0.5:
|
||||
iban_result = BuildIbanDescription.query.filter(BuildIbanDescription.id == int(result[0]), system=True).first()
|
||||
return {"company_id": iban_result.company_id, "customer_id": iban_result.customer_id, "found_from": "Name", "similarity": result[1]}
|
||||
return {"company_id": None, "customer_id": None, "found_from": None, "similarity": 0.0}
|
||||
|
||||
|
||||
def remove_spaces_from_string(remove_string: str):
|
||||
letter_list = []
|
||||
for letter in remove_string.split(" "):
|
||||
if letter_ := "".join(i for i in letter if not i == " "):
|
||||
letter_list.append(letter_)
|
||||
return " ".join(letter_list).upper()
|
||||
|
||||
|
||||
def get_garbage_words(comment: str, search_word: str):
|
||||
garbage_words = unidecode(remove_spaces_from_string(comment))
|
||||
search_word = unidecode(remove_spaces_from_string(search_word))
|
||||
for word in search_word.split(" "):
|
||||
garbage_words = garbage_words.replace(remove_spaces_from_string(unidecode(word)), "")
|
||||
if cleaned_from_spaces := remove_spaces_from_string(garbage_words):
|
||||
return str(unidecode(cleaned_from_spaces)).upper()
|
||||
return None
|
||||
|
||||
|
||||
def remove_garbage_words(comment: str, garbage_word: str):
|
||||
cleaned_comment = remove_spaces_from_string(comment.replace("*", " "))
|
||||
if garbage_word:
|
||||
garbage_word = remove_spaces_from_string(garbage_word.replace("*", " "))
|
||||
for letter in garbage_word.split(" "):
|
||||
cleaned_comment = unidecode(remove_spaces_from_string(cleaned_comment))
|
||||
cleaned_comment = cleaned_comment.replace(remove_spaces_from_string(letter), "")
|
||||
return str(remove_spaces_from_string(cleaned_comment)).upper()
|
||||
|
||||
|
||||
def check_is_comment_is_build(comment: str):
|
||||
has_build_words = False
|
||||
candidate_parts = remove_spaces_from_string(comment.replace("*", " ")).split(" ")
|
||||
for candidate_part in candidate_parts:
|
||||
candidate_part = remove_spaces_from_string(candidate_part).replace(":", "")
|
||||
for build_word in ["no", "daire", "apt", "apartman"]:
|
||||
if unidecode(candidate_part).upper() in unidecode(build_word).upper():
|
||||
has_build_words = True
|
||||
break
|
||||
return has_build_words
|
||||
|
||||
|
||||
def get_list_of_build_words(comment: str):
|
||||
build_words = []
|
||||
candidate_parts = remove_spaces_from_string(comment.replace("*", " "))
|
||||
for build_word in ["no", "nolu", "daire", "apt", "apartman"]:
|
||||
if unidecode(build_word).upper() in unidecode(candidate_parts).upper():
|
||||
st = unidecode(candidate_parts).upper().index(unidecode(build_word).upper())
|
||||
et = st + len(build_word)
|
||||
st = st - 5 if st > 5 else 0
|
||||
et = et + 5 if et + 5 <= len(candidate_parts) else len(candidate_parts)
|
||||
number_digit = "".join(letter for letter in str(candidate_parts[st:et]) if letter.isdigit())
|
||||
if number_digit:
|
||||
rt_dict = {"garbage": candidate_parts[st:et],"number": int(number_digit) if number_digit else None }
|
||||
build_words.append(rt_dict)
|
||||
return build_words
|
||||
|
||||
|
||||
def generate_pattern(word):
|
||||
if len(word) < 1:
|
||||
raise ValueError("The word must have at least 1 character.")
|
||||
add_string, add_match = "\d{1,3}$\s?$", f"{{1, {len(word)}}}"
|
||||
adda_string = "d{1,3}$\s?\^["
|
||||
return adda_string + f"{word}]" + add_match + rf"{word}(?:e|é|ı|i|ğr)?" + add_string
|
||||
|
||||
|
||||
def test_pattern(word, test_cases): # Generate the pattern
|
||||
pattern = generate_pattern(word)
|
||||
for test in test_cases: # Test the regex pattern on each input and print results
|
||||
if re.match(pattern, test, re.IGNORECASE):
|
||||
print(f"'{test}' matches the pattern.", "*" * 60)
|
||||
else:
|
||||
print(f"'{test}' does NOT match the pattern.")
|
||||
|
||||
|
||||
def parse_comment_for_living_space(iban: str, comment: str, living_space_dict: dict = None):
|
||||
comment = unidecode(comment)
|
||||
best_similarity = dict(company=None, living_space=None, found_from=None, similarity=0.0, garbage="", cleaned="",)
|
||||
if not iban in living_space_dict:
|
||||
return best_similarity
|
||||
for person in living_space_dict[iban]["people"]:
|
||||
firstname = person.get("firstname")
|
||||
surname = person.get("surname")
|
||||
middle_name = person.get("middle_name")
|
||||
|
||||
first_name = unidecode(firstname).upper()
|
||||
last_name = unidecode(surname).upper()
|
||||
search_word_list = [
|
||||
remove_spaces_from_string("".join([f"{first_name} {last_name}"])),
|
||||
remove_spaces_from_string("".join([f"{last_name} {first_name}"])),
|
||||
]
|
||||
if middle_name := unidecode(middle_name).upper():
|
||||
search_word_list.append(remove_spaces_from_string(f"{first_name} {middle_name} {last_name}"))
|
||||
search_word_list.append(remove_spaces_from_string(f"{last_name} {middle_name} {first_name}"))
|
||||
|
||||
cleaned_comment = unidecode(comment).upper()
|
||||
for search_word in search_word_list:
|
||||
garbage_words = get_garbage_words(comment, unidecode(search_word))
|
||||
if garbage_words:
|
||||
garbage_words = unidecode(garbage_words).upper()
|
||||
cleaned_comment = unidecode(remove_garbage_words(comment, garbage_words)).upper()
|
||||
similarity_ratio = textdistance.jaro_winkler(cleaned_comment, str(search_word).upper())
|
||||
if len(cleaned_comment) < len(f"{first_name}{last_name}"):
|
||||
continue
|
||||
if cleaned_comment and 0.9 < similarity_ratio <= 1:
|
||||
print("cleaned comment dict", dict(garbage=garbage_words, cleaned=cleaned_comment, similarity=similarity_ratio, search_word=search_word, comment=comment, last_similarity=float(best_similarity["similarity"])))
|
||||
if similarity_ratio > float(best_similarity["similarity"]):
|
||||
for living_space in living_space_dict[iban]["living_space"]:
|
||||
if living_space.get("person_id") == person.get("id"):
|
||||
best_similarity = {"company": None,"living_space": living_space,"found_from": "Person Name","similarity": similarity_ratio,"garbage": garbage_words,"cleaned": cleaned_comment,}
|
||||
return best_similarity
|
||||
|
||||
|
||||
def parse_comment_for_build_parts(comment: str, max_build_part: int = 200, parse: str = "DAIRE"):
|
||||
results, results_list = category_finder(comment), []
|
||||
print("results[parse]", results[parse])
|
||||
for result in results[parse] or []:
|
||||
if digits := "".join([letter for letter in str(result) if letter.isdigit()]):
|
||||
print("digits", digits)
|
||||
if int(digits) <= int(max_build_part):
|
||||
results_list.append(int(digits))
|
||||
return results_list or None
|
||||
|
||||
|
||||
def parse_comment_for_company_or_individual(comment: str):
|
||||
with Companies.new_session() as session:
|
||||
Companies.set_session(session)
|
||||
companies_list = Companies.query.filter(Companies.commercial_type != "Commercial").all()
|
||||
comment = unidecode(comment)
|
||||
best_similarity = dict(company=None,living_space=None,found_from=None,similarity=0.0,garbage="",cleaned="",)
|
||||
for company in companies_list:
|
||||
search_word = unidecode(company.public_name)
|
||||
garbage_words = get_garbage_words(comment, search_word)
|
||||
cleaned_comment = remove_garbage_words(comment, garbage_words)
|
||||
similarity_ratio = textdistance.jaro_winkler(cleaned_comment, search_word)
|
||||
if similarity_ratio > float(best_similarity["similarity"]):
|
||||
best_similarity = {"company": company,"living_space": None,"found_from": "Customer Public Name","similarity": similarity_ratio,"garbage": garbage_words,"cleaned": cleaned_comment,}
|
||||
# print(
|
||||
# 'cleaned_comment', cleaned_comment, '\n'
|
||||
# 'search_word', search_word, '\n'
|
||||
# 'best_similarity', best_similarity, '\n'
|
||||
# 'company name', company.public_name, '\n'
|
||||
# 'similarity_ratio', similarity_ratio, '\n'
|
||||
# 'garbage_words', garbage_words
|
||||
# )
|
||||
return best_similarity
|
||||
|
||||
|
||||
def parse_comment_to_split_with_star(account_record: dict):
|
||||
if "*" in account_record.get("process_comment", ""):
|
||||
process_comment = str(account_record.get("process_comment", "").replace("**", "*"))
|
||||
process_comments = process_comment.split("*")
|
||||
return len(process_comments), *process_comments
|
||||
return 1, account_record.get("process_comment", "")
|
||||
|
||||
|
||||
def check_build_living_space_matches_with_build_parts(living_space_dict: dict, best_similarity: dict, iban: str, whole_comment: str):
|
||||
if 0.6 < float(best_similarity["similarity"]) < 0.8:
|
||||
build_parts = living_space_dict[iban]["build_parts"]
|
||||
if best_similarity["living_space"]:
|
||||
build_parts_id = best_similarity["living_space"].build_parts_id
|
||||
parser_dict = dict(comment=str(whole_comment), max_build_part=len(build_parts))
|
||||
print("build parts similarity", best_similarity, "parser_dict", parser_dict)
|
||||
results_list = parse_comment_for_build_parts(**parser_dict)
|
||||
print("results_list", results_list)
|
||||
if not results_list:
|
||||
return best_similarity
|
||||
for build_part in build_parts:
|
||||
print("part_no", int(build_part.part_no), " | ", results_list)
|
||||
print("build_part", int(build_part.id), int(build_parts_id))
|
||||
print("cond", int(build_part.id) == int(build_parts_id))
|
||||
print("cond2", int(build_part.part_no) in results_list)
|
||||
if (int(build_part.id) == int(build_parts_id) and int(build_part.part_no) in results_list):
|
||||
similarity = float(best_similarity["similarity"])
|
||||
best_similarity["similarity"] = (1 - similarity) / 2 + similarity
|
||||
print("similarity", best_similarity["similarity"])
|
||||
break
|
||||
return best_similarity
|
||||
|
||||
|
||||
def parse_comment_with_name(account_record: dict, living_space_dict: dict = None):
|
||||
|
||||
comments = parse_comment_to_split_with_star(account_record=account_record)
|
||||
best_similarity = {"similarity": 0.0}
|
||||
comments_list, comments_length = comments[1:], int(comments[0])
|
||||
|
||||
if int(account_record.get("currency_value", 0)) > 0: # Build receive money from living space people
|
||||
living_space_matches = dict(living_space_dict=living_space_dict, iban=account_record.get("iban", None), whole_comment=account_record.get("process_comment", None))
|
||||
if comments_length == 1:
|
||||
best_similarity = parse_comment_for_living_space(iban=account_record.get("iban", None), comment=comments_list[0], living_space_dict=living_space_dict)
|
||||
best_similarity["send_person_id"] = best_similarity.get("customer_id", None)
|
||||
living_space_matches["best_similarity"] = best_similarity
|
||||
# if 0.5 < float(best_similarity['similarity']) < 0.8
|
||||
best_similarity = check_build_living_space_matches_with_build_parts(**living_space_matches)
|
||||
return best_similarity
|
||||
for comment in comments_list:
|
||||
similarity_result = parse_comment_for_living_space(iban=account_record.get("iban", None), comment=comment, living_space_dict=living_space_dict)
|
||||
if float(similarity_result["similarity"]) > float(best_similarity["similarity"]):
|
||||
best_similarity = similarity_result
|
||||
living_space_matches["best_similarity"] = best_similarity
|
||||
# if 0.5 < float(best_similarity['similarity']) < 0.8:
|
||||
best_similarity = check_build_living_space_matches_with_build_parts(**living_space_matches)
|
||||
print("last best_similarity", best_similarity)
|
||||
return best_similarity
|
||||
else: # Build pays money for service taken from company or individual
|
||||
if not comments_length > 1:
|
||||
best_similarity = parse_comment_for_company_or_individual(comment=comments_list[0])
|
||||
best_similarity["send_person_id"] = best_similarity.get("customer_id", None)
|
||||
return best_similarity
|
||||
for comment in comments_list:
|
||||
similarity_result = parse_comment_for_company_or_individual(comment=comment)
|
||||
if float(similarity_result["similarity"]) > float(best_similarity["similarity"]):
|
||||
best_similarity = similarity_result
|
||||
return best_similarity
|
||||
|
||||
|
||||
def parse_comment_with_name_iban_description(account_record: dict):
|
||||
with AccountRecords.new_session() as session:
|
||||
BuildIbanDescription.set_session(session)
|
||||
Companies.set_session(session)
|
||||
|
||||
comments = parse_comment_to_split_with_star(account_record=account_record)
|
||||
comments_list, comments_length = comments[1:], int(comments[0])
|
||||
iban_results = BuildIbanDescription.query.filter(BuildIbanDescription.iban == account_record.get("iban", "")).all()
|
||||
best_similarity = dict(company=None,living_space=None,found_from=None,similarity=0.0,garbage="",cleaned="",)
|
||||
for comment in comments_list:
|
||||
for iban_result in iban_results:
|
||||
search_word = unidecode(iban_result.search_word)
|
||||
garbage_words = get_garbage_words(comment, search_word)
|
||||
cleaned_comment = remove_garbage_words(comment, garbage_words)
|
||||
similarity_ratio = textdistance.jaro_winkler(cleaned_comment, search_word)
|
||||
company = Companies.query.filter_by(id=iban_result.company_id).first()
|
||||
if float(similarity_ratio) > float(best_similarity["similarity"]):
|
||||
best_similarity = {"company": company,"living_space": None,"found_from": "Customer Public Name Description","similarity": similarity_ratio,"garbage": garbage_words,"cleaned": cleaned_comment,}
|
||||
return best_similarity
|
||||
|
||||
# "decision_book_project_id": None,
|
||||
# "build_parts_id": None,
|
||||
# "decision_book_project_id": iban_result.decision_book_project_id,
|
||||
# "build_parts_id": iban_result.build_parts_id,
|
||||
|
||||
# print('account_record.process_comment', account_record.process_comment)
|
||||
# test_pattern(
|
||||
# word=unidecode("no"),
|
||||
# test_cases=[account_record.process_comment]
|
||||
# )
|
||||
# test_pattern(word="daire", test_cases=comments_list)
|
||||
|
||||
# sm_dict_extended, sm_dict_digit = {}, {}
|
||||
# iban_results = BuildIbanDescription.filter_all(
|
||||
# BuildIbanDescription.iban == iban, system=True
|
||||
# ).data
|
||||
# for iban_result in iban_results or []:
|
||||
# candidate_parts = comment.split(" ")
|
||||
# extended_candidate_parts, digit_part = [], []
|
||||
# for part in candidate_parts:
|
||||
# if part.lower() not in ["no", "daire", "nolu"]:
|
||||
# extended_candidate_parts.append(part)
|
||||
# if extended_candidate_parts:
|
||||
# if all(
|
||||
# candidate_part.lower() in comment.lower()
|
||||
# for candidate_part in extended_candidate_parts
|
||||
# ):
|
||||
# similarity_ratio = textdistance.jaro_winkler(
|
||||
# unidecode(str(iban_result.search_word)), comment
|
||||
# )
|
||||
# found = False
|
||||
# name_list = (
|
||||
# unidecode(str(iban_result.search_word)).replace(".", " ").split(" ")
|
||||
# )
|
||||
# for name in name_list:
|
||||
# if len(name) > 3 and name.lower() in comment.lower():
|
||||
# found = True
|
||||
# break
|
||||
#
|
||||
# if not found:
|
||||
# similarity_ratio = 0.1
|
||||
# sm_dict_extended[f"{iban_result.id}"] = similarity_ratio
|
||||
# if sm_dict_extended:
|
||||
# result = sorted(
|
||||
# sm_dict_extended.items(), key=lambda item: item[1], reverse=True
|
||||
# )[0]
|
||||
# if float(result[1]) >= 0.5:
|
||||
# iban_result = BuildIbanDescription.filter_one(
|
||||
# BuildIbanDescription.id == int(result[0]), system=True
|
||||
# ).data
|
||||
# return {
|
||||
# "company_id": iban_result.company_id,
|
||||
# "customer_id": iban_result.customer_id,
|
||||
# "found_from": "Name",
|
||||
# "similarity": result[1],
|
||||
# }
|
||||
# return {
|
||||
# "company_id": None,
|
||||
# "customer_id": None,
|
||||
# "found_from": None,
|
||||
# "similarity": 0.0,
|
||||
# }
|
||||
|
||||
|
||||
#
|
||||
# def wag_insert_budget_record(data):
|
||||
# similarity_result = parse_comment_with_name(data["iban"], data["process_comment"])
|
||||
# build_iban = BuildIbans.find_one(iban=data["iban"])
|
||||
#
|
||||
# if payload := InsertBudgetRecord(**data):
|
||||
# payload_dict = payload.model_dump(exclude_unset=True, exclude_none=True)
|
||||
# decision_books = BuildDecisionBook.select_only(
|
||||
# BuildDecisionBook.period_start_date
|
||||
# < strip_date_to_valid(payload_dict["bank_date"]),
|
||||
# BuildDecisionBook.period_stop_date
|
||||
# > strip_date_to_valid(payload_dict["bank_date"]),
|
||||
# select_args=[BuildDecisionBook.id],
|
||||
# order_by=[BuildDecisionBook.expiry_ends.desc()],
|
||||
# )
|
||||
# payload_dict["build_id"] = getattr(
|
||||
# BuildIbans.find_one(iban=data["iban"]), "build_id", None
|
||||
# )
|
||||
# living_space, count = BuildLivingSpace.find_living_from_customer_id(
|
||||
# similarity_result.get("customer_id", None),
|
||||
# strip_date_to_valid(payload_dict["bank_date"]),
|
||||
# )
|
||||
# # living_space, count = BuildLivingSpace.filter(
|
||||
# # or_(
|
||||
# # BuildLivingSpace.owner_person_id
|
||||
# # == similarity_result.get("customer_id", None),
|
||||
# # BuildLivingSpace.life_person_id
|
||||
# # == similarity_result.get("customer_id", None),
|
||||
# # ),
|
||||
# # BuildLivingSpace.start_date
|
||||
# # < strip_date_to_valid(payload_dict["bank_date"]) - timedelta(days=30),
|
||||
# # BuildLivingSpace.stop_date
|
||||
# # > strip_date_to_valid(payload_dict["bank_date"]) + timedelta(days=30),
|
||||
# # BuildLivingSpace.active == True,
|
||||
# # BuildLivingSpace.deleted == False,
|
||||
# # )
|
||||
# payload_dict["build_decision_book_id"] = (
|
||||
# decision_books[0][0].id if decision_books else None
|
||||
# )
|
||||
# payload_dict["company_id"] = similarity_result.get("company_id", None)
|
||||
# payload_dict["customer_id"] = similarity_result.get("customer_id", None)
|
||||
# payload_dict["send_person_id"] = similarity_result.get("send_person_id", None)
|
||||
#
|
||||
# payload_dict["build_parts_id"] = (
|
||||
# living_space[0].build_parts_id if living_space else None
|
||||
# )
|
||||
#
|
||||
# payload_dict["bank_date_y"] = strip_date_to_valid(
|
||||
# payload_dict["bank_date"]
|
||||
# ).year
|
||||
# payload_dict["bank_date_m"] = strip_date_to_valid(
|
||||
# payload_dict["bank_date"]
|
||||
# ).month
|
||||
# payload_dict["bank_date_d"] = strip_date_to_valid(payload_dict["bank_date"]).day
|
||||
# payload_dict["bank_date_w"] = strip_date_to_valid(
|
||||
# payload_dict["bank_date"]
|
||||
# ).isocalendar()[2]
|
||||
# payload_dict["build_id"] = build_iban.build_id if build_iban else None
|
||||
# payload_dict["replication_id"] = 55
|
||||
# payload_dict["receive_debit"] = (
|
||||
# "R" if payload_dict["currency_value"] < 0 else "D"
|
||||
# )
|
||||
# data, found = AccountRecords.find_or_create(
|
||||
# **payload_dict,
|
||||
# found_from=similarity_result.get("found_from", None),
|
||||
# similarity=similarity_result.get("similarity", 0.0),
|
||||
# )
|
||||
# data.payment_budget_record_close()
|
||||
# return data, found
|
||||
266
ServicesBank/Finder/app_accounts.py
Normal file
266
ServicesBank/Finder/app_accounts.py
Normal file
@@ -0,0 +1,266 @@
|
||||
import sys
|
||||
import arrow
|
||||
|
||||
if "/service_account_records" not in list(sys.path):
|
||||
sys.path.append("/service_account_records")
|
||||
|
||||
from decimal import Decimal
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Union
|
||||
from sqlalchemy import func, cast, Date
|
||||
from Schemas import AccountRecords, BuildIbans, BuildDecisionBook, Build, BuildLivingSpace, People, OccupantTypes, BuildParts, BuildDecisionBookPayments, ApiEnumDropdown
|
||||
from account_record_parser import parse_comment_with_name, parse_comment_with_name_iban_description
|
||||
# from ServicesApi.Schemas.account.account import AccountRecords
|
||||
# from ServicesApi.Schemas.building.build import BuildIbans, BuildDecisionBook, BuildParts, BuildLivingSpace, Build
|
||||
# from ServicesApi.Schemas.identity.identity import People, OccupantTypes
|
||||
# from ServicesApi.Schemas.others.enums import ApiEnumDropdown
|
||||
# from ServicesApi.Schemas.building.decision_book import BuildDecisionBookPayments
|
||||
|
||||
|
||||
# AccountRecords.approved_record = False
|
||||
|
||||
def account_find_build_from_iban(session):
|
||||
created_ibans = []
|
||||
AccountRecords.set_session(session)
|
||||
BuildIbans.set_session(session)
|
||||
|
||||
account_records_ibans = AccountRecords.query.filter(AccountRecords.build_id == None, AccountRecords.approved_record == False).distinct(AccountRecords.iban).all()
|
||||
for account_records_iban in account_records_ibans:
|
||||
found_iban: BuildIbans = BuildIbans.query.filter(BuildIbans.iban == account_records_iban.iban).first()
|
||||
if not found_iban:
|
||||
create_build_ibans = BuildIbans.create(iban=account_records_iban.iban, start_date=str(arrow.now().shift(days=-1)))
|
||||
create_build_ibans.save()
|
||||
created_ibans.append(account_records_iban.iban)
|
||||
else:
|
||||
update_dict = {"build_id": found_iban.build_id, "build_uu_id": str(found_iban.build_uu_id)}
|
||||
session.query(AccountRecords).filter(AccountRecords.iban == account_records_iban.iban).update(update_dict, synchronize_session=False)
|
||||
session.commit()
|
||||
|
||||
|
||||
def account_records_find_decision_book(session):
|
||||
AccountRecords.set_session(session)
|
||||
BuildIbans.set_session(session)
|
||||
BuildDecisionBook.set_session(session)
|
||||
|
||||
created_ibans, iban_build_dict = [], {}
|
||||
account_records_list: list[AccountRecords] = AccountRecords.query.filter(AccountRecords.build_id != None, AccountRecords.build_decision_book_id == None).order_by(AccountRecords.bank_date.desc()).all()
|
||||
for account_record in account_records_list:
|
||||
if found_iban := BuildIbans.query.filter(BuildIbans.iban == account_record.iban).first():
|
||||
if found_decision_book := BuildDecisionBook.query.filter(
|
||||
BuildDecisionBook.build_id == found_iban.build_id,
|
||||
cast(BuildDecisionBook.expiry_starts, Date) <= cast(account_record.bank_date, Date),
|
||||
cast(BuildDecisionBook.expiry_ends, Date) >= cast(account_record.bank_date, Date),
|
||||
).first():
|
||||
account_record.build_decision_book_id = found_decision_book.id
|
||||
account_record.build_decision_book_uu_id = str(found_decision_book.uu_id)
|
||||
account_record.save()
|
||||
|
||||
|
||||
def account_get_people_and_living_space_info_via_iban() -> dict:
|
||||
build_living_space_dict = {}
|
||||
AccountRecords.set_session(session)
|
||||
OccupantTypes.set_session(session)
|
||||
BuildParts.set_session(session)
|
||||
BuildLivingSpace.set_session(session)
|
||||
People.set_session(session)
|
||||
|
||||
account_records_ibans = AccountRecords.query.filter(AccountRecords.build_decision_book_id != None).distinct(AccountRecords.iban).all()
|
||||
|
||||
flat_resident = OccupantTypes.query.filter_by(occupant_category_type="FL", occupant_code="FL-RES").first()
|
||||
flat_owner = OccupantTypes.query.filter_by(occupant_category_type="FL", occupant_code="FL-OWN").first()
|
||||
flat_tenant = OccupantTypes.query.filter_by(occupant_category_type="FL", occupant_code="FL-TEN").first()
|
||||
flat_represent = OccupantTypes.query.filter_by(occupant_category_type="FL", occupant_code="FL-REP").first()
|
||||
|
||||
for account_records_iban in account_records_ibans:
|
||||
if account_records_iban.iban not in build_living_space_dict:
|
||||
build_parts = BuildParts.query.filter_by(build_id=account_records_iban.build_id, human_livable=True).all()
|
||||
living_spaces = BuildLivingSpace.query.filter(
|
||||
BuildLivingSpace.build_parts_id.in_([build_parts.id for build_parts in build_parts]),
|
||||
BuildLivingSpace.occupant_type_id.in_([flat_resident.id, flat_owner.id, flat_tenant.id, flat_represent.id]),
|
||||
).all()
|
||||
living_spaces_people = [living_space.person_id for living_space in living_spaces if living_space.person_id]
|
||||
people_list = People.query.filter(People.id.in_(living_spaces_people)).all()
|
||||
people_list_dict = []
|
||||
for people in people_list:
|
||||
people_list_dict.append({"id": people.id, "uu_id": str(people.uu_id), "firstname": people.firstname, "surname": people.surname, "middle_name": people.middle_name})
|
||||
living_spaces_dict = []
|
||||
for living_space in living_spaces:
|
||||
living_spaces_dict.append({"id": living_space.id, "uu_id": str(living_space.uu_id), "person_id": living_space.person_id, "person_uu_id": str(living_space.person_uu_id)})
|
||||
build_parts_dict = []
|
||||
for build_parts in build_parts:
|
||||
build_parts_dict.append({"id": build_parts.id, "uu_id": str(build_parts.uu_id)})
|
||||
build_living_space_dict[str(account_records_iban.iban)] = {"people": list(people_list_dict), "living_space": list(living_spaces_dict), "build_parts": list(build_parts_dict)}
|
||||
return build_living_space_dict
|
||||
|
||||
|
||||
def account_records_search():
|
||||
build_living_space_dict = account_get_people_and_living_space_info_via_iban()
|
||||
found_list = []
|
||||
with AccountRecords.new_session() as session:
|
||||
AccountRecords.set_session(session)
|
||||
|
||||
account_records_list: list[AccountRecords] = AccountRecords.query.filter(AccountRecords.build_decision_book_id != None).all()
|
||||
for account_record in account_records_list:
|
||||
account_record_dict = account_record.get_dict()
|
||||
similarity_result = parse_comment_with_name(account_record=account_record_dict, living_space_dict=build_living_space_dict)
|
||||
fs, ac = float(similarity_result.get("similarity", 0)), float(account_record_dict.get("similarity", 0))
|
||||
if fs >= 0.8 and fs >= ac:
|
||||
print("similarity_result positive", similarity_result)
|
||||
found_list.append(similarity_result)
|
||||
account_save_search_result(account_record=account_record_dict, similarity_result=similarity_result)
|
||||
else:
|
||||
similarity_result = parse_comment_with_name_iban_description(account_record=account_record_dict)
|
||||
fs, ac = float(similarity_result.get("similarity", 0)), float(account_record_dict.get("similarity", 0))
|
||||
if fs >= 0.8 and fs > ac:
|
||||
print("similarity_result negative", similarity_result)
|
||||
found_list.append(similarity_result)
|
||||
account_save_search_result(account_record=account_record_dict, similarity_result=similarity_result)
|
||||
print("Account Records Search : ", len(found_list), "/", len(account_records_list))
|
||||
return
|
||||
|
||||
|
||||
def account_save_search_result(account_record, similarity_result):
|
||||
BuildParts.set_session(session)
|
||||
Build.set_session(session)
|
||||
BuildLivingSpace.set_session(session)
|
||||
|
||||
found_company = similarity_result.get("company", None)
|
||||
found_customer, part, build = (similarity_result.get("living_space", None), None, None)
|
||||
if found_customer:
|
||||
found_living_space = BuildLivingSpace.query.filter_by(id=int(found_customer.get("id"))).first()
|
||||
part = BuildParts.query.filter_by(id=found_living_space.build_parts_id, human_livable=True).first()
|
||||
if part:
|
||||
build = Build.query.filter_by(id=part.build_id).first()
|
||||
|
||||
account_record_dict = {
|
||||
"similarity": similarity_result.get("similarity", 0.00),
|
||||
"found_from": similarity_result.get("found_from", None),
|
||||
"company_id": getattr(found_company, "id", None),
|
||||
"company_uu_id": str(getattr(found_company, "uu_id", None)) if getattr(found_company, "uu_id", None) else None,
|
||||
"build_parts_id": getattr(part, "id", None) if getattr(part, "id", None) else None,
|
||||
"build_parts_uu_id": str(getattr(part, "uu_id", None)) if getattr(part, "uu_id", None) else None,
|
||||
"living_space_id": getattr(found_customer, "id", None),
|
||||
"living_space_uu_id": str(getattr(found_customer, "uu_id", None)) if getattr(found_customer, "uu_id", None) else None,
|
||||
}
|
||||
if not account_record.get("build_id") and build:
|
||||
account_record_dict.update({"build_id": getattr(build, "id", None), "build_uu_id": str(getattr(build, "uu_id", None)) if getattr(build, "uu_id", None) else None})
|
||||
AccountRecords.query.filter_by(uu_id=str(account_record.get("uu_id"))).update(account_record_dict)
|
||||
session.commit()
|
||||
|
||||
|
||||
def pay_the_registration(account_record, receive_enum, debit_enum, is_old_record: bool = False):
|
||||
with AccountRecords.new_session() as session:
|
||||
AccountRecords.set_session(session)
|
||||
BuildDecisionBookPayments.set_session(session)
|
||||
|
||||
current_currency_value = float(Decimal(account_record.currency_value)) - float(Decimal(account_record.remainder_balance))
|
||||
if not current_currency_value > 0:
|
||||
return current_currency_value
|
||||
|
||||
process_date = arrow.get(account_record.bank_date)
|
||||
account_bank_date_year, account_bank_date_month = (process_date.date().year, process_date.date().month)
|
||||
payment_arguments_debit = [
|
||||
BuildDecisionBookPayments.build_parts_id == account_record.build_parts_id,
|
||||
BuildDecisionBookPayments.payment_types_id == debit_enum.id,
|
||||
BuildDecisionBookPayments.account_records_id == None,
|
||||
]
|
||||
if not is_old_record:
|
||||
payment_arguments_debit.extend([BuildDecisionBookPayments.process_date_y == int(account_bank_date_year), BuildDecisionBookPayments.process_date_m == int(account_bank_date_month)])
|
||||
payments = BuildDecisionBookPayments.query.filter(*payment_arguments_debit).order_by(BuildDecisionBookPayments.process_date.asc()).all()
|
||||
for payment in payments:
|
||||
if not current_currency_value > 0:
|
||||
return current_currency_value
|
||||
payment_arguments_receive = [
|
||||
BuildDecisionBookPayments.build_parts_id == account_record.build_parts_id,
|
||||
BuildDecisionBookPayments.payment_plan_time_periods == payment.payment_plan_time_periods,
|
||||
BuildDecisionBookPayments.payment_types_id == receive_enum.id,
|
||||
BuildDecisionBookPayments.build_decision_book_item_id == payment.build_decision_book_item_id,
|
||||
BuildDecisionBookPayments.decision_book_project_id == payment.decision_book_project_id,
|
||||
BuildDecisionBookPayments.process_date == payment.process_date,
|
||||
]
|
||||
if not is_old_record:
|
||||
payment_arguments_receive.extend([BuildDecisionBookPayments.process_date_y == int(account_bank_date_year), BuildDecisionBookPayments.process_date_m == int(account_bank_date_month)])
|
||||
|
||||
payment_received = BuildDecisionBookPayments.query.filter(*payment_arguments_receive).all()
|
||||
sum_of_payment_received = sum([abs(payment.payment_amount) for payment in payment_received])
|
||||
net_amount = float(abs(Decimal(payment.payment_amount))) - float(abs(Decimal(sum_of_payment_received)))
|
||||
if not net_amount > 0:
|
||||
continue
|
||||
if float(abs(current_currency_value)) < float(abs(net_amount)):
|
||||
net_amount = float(current_currency_value)
|
||||
process_date = arrow.get(payment.process_date)
|
||||
try:
|
||||
found_payment = BuildDecisionBookPayments.query.filter_by(
|
||||
build_parts_id=payment.build_parts_id,
|
||||
payment_plan_time_periods=payment.payment_plan_time_periods,
|
||||
payment_types_id=receive_enum.id,
|
||||
build_decision_book_item_id=payment.build_decision_book_item_id,
|
||||
decision_book_project_id=payment.decision_book_project_id,
|
||||
process_date=str(process_date),
|
||||
).first()
|
||||
if found_payment:
|
||||
continue
|
||||
created_book_payment = BuildDecisionBookPayments.create(
|
||||
payment_plan_time_periods=payment.payment_plan_time_periods,
|
||||
payment_amount=float(abs(net_amount)),
|
||||
payment_types_id=receive_enum.id,
|
||||
payment_types_uu_id=str(receive_enum.uu_id),
|
||||
process_date=str(process_date),
|
||||
process_date_m=process_date.date().month,
|
||||
process_date_y=process_date.date().year,
|
||||
period_time=f"{process_date.year}-{str(process_date.month).zfill(2)}",
|
||||
build_parts_id=payment.build_parts_id,
|
||||
build_parts_uu_id=str(payment.build_parts_uu_id),
|
||||
account_records_id=account_record.id,
|
||||
account_records_uu_id=str(account_record.uu_id),
|
||||
build_decision_book_item_id=payment.build_decision_book_item_id,
|
||||
build_decision_book_item_uu_id=str(payment.build_decision_book_item_uu_id),
|
||||
decision_book_project_id=payment.decision_book_project_id,
|
||||
decision_book_project_uu_id=str(payment.decision_book_project_uu_id),
|
||||
)
|
||||
created_book_payment.save_and_confirm()
|
||||
created_payment_amount = float(Decimal(created_book_payment.payment_amount))
|
||||
remainder_balance = float(Decimal(account_record.remainder_balance)) + float(abs(created_payment_amount))
|
||||
account_record.update(remainder_balance=remainder_balance)
|
||||
account_record.save()
|
||||
if current_currency_value >= abs(net_amount):
|
||||
current_currency_value -= abs(net_amount)
|
||||
except Exception as e:
|
||||
print("Exception of decision payment ln:300", e)
|
||||
return current_currency_value
|
||||
|
||||
|
||||
def send_accounts_to_decision_payment():
|
||||
with ApiEnumDropdown.new_session() as session:
|
||||
ApiEnumDropdown.set_session(session)
|
||||
AccountRecords.set_session(session)
|
||||
|
||||
receive_enum = ApiEnumDropdown.query.filter_by(enum_class="DebitTypes", key="DT-R").first()
|
||||
debit_enum = ApiEnumDropdown.query.filter_by(enum_class="DebitTypes", key="DT-D").first()
|
||||
account_records_list: list[AccountRecords] = AccountRecords.query.filter(
|
||||
AccountRecords.remainder_balance < AccountRecords.currency_value,
|
||||
AccountRecords.approved_record == True,
|
||||
AccountRecords.receive_debit == receive_enum.id,
|
||||
).order_by(AccountRecords.bank_date.desc()).limit(1000).offset(0).all()
|
||||
for account_record in account_records_list:
|
||||
current_currency_value = pay_the_registration(account_record, receive_enum, debit_enum)
|
||||
if current_currency_value > 0:
|
||||
pay_the_registration(account_record, receive_enum, debit_enum, True)
|
||||
if abs(float(Decimal(account_record.remainder_balance))) == abs(float(Decimal(account_record.currency_value))):
|
||||
account_record.update(status_id=97)
|
||||
account_record.save()
|
||||
# # # todo If the payment is more than the amount, then create a new account record with the remaining amount
|
||||
return
|
||||
|
||||
|
||||
def account_records_service() -> None:
|
||||
print("Account Records Service is running...")
|
||||
account_find_build_from_iban(session=session)
|
||||
account_records_find_decision_book(session=session)
|
||||
account_records_search(session=session)
|
||||
send_accounts_to_decision_payment(session=session)
|
||||
print("Account Records Service is finished...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
account_records_service()
|
||||
8
ServicesBank/Finder/configs.py
Normal file
8
ServicesBank/Finder/configs.py
Normal file
@@ -0,0 +1,8 @@
|
||||
class AccountConfig:
|
||||
BEFORE_DAY = 30
|
||||
CATEGORIES = {
|
||||
"DAIRE": ["daire", "dagire", "daare", "nolu daire", "no", "nolu dairenin"],
|
||||
"APARTMAN": ["apartman", "aparman", "aprmn"],
|
||||
"VILLA": ["villa", "vlla"],
|
||||
"BINA": ["bina", "binna"],
|
||||
}
|
||||
29
ServicesBank/Finder/entrypoint.sh
Normal file
29
ServicesBank/Finder/entrypoint.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Create environment file that will be available to cron jobs
|
||||
echo "# Environment variables for cron jobs" > /env.sh
|
||||
echo "EMAIL_HOST=\"$EMAIL_HOST\"" >> /env.sh
|
||||
echo "EMAIL_USERNAME=\"$EMAIL_USERNAME\"" >> /env.sh
|
||||
echo "EMAIL_PASSWORD=\"$EMAIL_PASSWORD\"" >> /env.sh
|
||||
echo "EMAIL_PORT=$EMAIL_PORT" >> /env.sh
|
||||
echo "EMAIL_SEND=$EMAIL_SEND" >> /env.sh
|
||||
echo "DB_HOST=\"$DB_HOST\"" >> /env.sh
|
||||
echo "DB_USER=\"$DB_USER\"" >> /env.sh
|
||||
echo "DB_PASSWORD=\"$DB_PASSWORD\"" >> /env.sh
|
||||
echo "DB_PORT=$DB_PORT" >> /env.sh
|
||||
echo "DB_NAME=\"$DB_NAME\"" >> /env.sh
|
||||
|
||||
# Add Python environment variables
|
||||
echo "PYTHONPATH=/" >> /env.sh
|
||||
echo "PYTHONUNBUFFERED=1" >> /env.sh
|
||||
echo "PYTHONDONTWRITEBYTECODE=1" >> /env.sh
|
||||
|
||||
# Make the environment file available to cron
|
||||
echo "0 8 * * * /run_app.sh >> /var/log/cron.log 2>&1" > /tmp/crontab_list
|
||||
crontab /tmp/crontab_list
|
||||
|
||||
# Start cron
|
||||
cron
|
||||
|
||||
# Tail the log file
|
||||
tail -f /var/log/cron.log
|
||||
28
ServicesBank/Finder/regex_func.py
Normal file
28
ServicesBank/Finder/regex_func.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import sys
|
||||
|
||||
if "/service_account_records" not in list(sys.path):
|
||||
sys.path.append("/service_account_records")
|
||||
|
||||
import re
|
||||
|
||||
from difflib import get_close_matches
|
||||
from configs import AccountConfig
|
||||
|
||||
|
||||
def word_straighten(word, ref_list, threshold=0.8):
|
||||
matches = get_close_matches(word, ref_list, n=1, cutoff=threshold)
|
||||
return matches[0] if matches else word
|
||||
|
||||
|
||||
def category_finder(text, output_template="{kategori} {numara}"):
|
||||
categories = AccountConfig.CATEGORIES
|
||||
result = {category: [] for category in categories}
|
||||
for category, patterns in categories.items():
|
||||
words = re.split(r"\W+", text)
|
||||
straighten_words = [word_straighten(word, patterns) for word in words]
|
||||
straighten_text = " ".join(straighten_words)
|
||||
pattern = (r"(?:\b|\s|^)(?:" + "|".join(map(re.escape, patterns)) + r")(?:\s*|:|\-|\#)*(\d+)(?:\b|$)")
|
||||
if founds_list := re.findall(pattern, straighten_text, re.IGNORECASE):
|
||||
list_of_output = [output_template.format(kategori=category, numara=num) for num in founds_list]
|
||||
result[category].extend([i for i in list_of_output if str(i).replace(" ", "")])
|
||||
return result
|
||||
Reference in New Issue
Block a user