brewman/brewman/brewman/routers/rebase.py

298 lines
10 KiB
Python

import uuid
from datetime import date, datetime, timedelta
from decimal import Decimal
from typing import List
from fastapi import APIRouter, Security
from sqlalchemy import and_, delete, distinct, func, select
from sqlalchemy.orm import Session, aliased
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionFuture
from ..models.account import Account
from ..models.account_base import AccountBase
from ..models.attendance import Attendance
from ..models.batch import Batch
from ..models.cost_centre import CostCentre
from ..models.db_image import DbImage
from ..models.employee import Employee
from ..models.employee_benefit import EmployeeBenefit
from ..models.fingerprint import Fingerprint
from ..models.incentive import Incentive
from ..models.inventory import Inventory
from ..models.journal import Journal
from ..models.voucher import Voucher
from ..models.voucher_type import VoucherType
from ..schemas.user import UserToken
router = APIRouter()
@router.post("/{date_}", response_model=None)
def rebase(
date_: str,
user: UserToken = Security(get_user, scopes=["rebase"]),
) -> None:
with SessionFuture() as db:
date_obj = datetime.strptime(date_, "%d-%b-%Y")
voucher_l = opening_accounts(date_obj, user.id_, db)
voucher_b = opening_batches(date_obj, user.id_, db)
starred_vouchers = save_starred(date_obj, db)
db.flush()
delete_data(date_obj, starred_vouchers, db)
db.add(voucher_l)
for j in voucher_l.journals:
db.add(j)
db.flush()
db.add(voucher_b)
for j in voucher_b.journals:
db.add(j)
for i in voucher_b.inventories:
db.add(i)
db.flush()
cleanup_lint(date_obj, db)
# request.dbsession.execute('RESET statement_timeout;')
db.commit()
def save_starred(date_: date, db: Session) -> List[Voucher]:
accounts = [
i.id for i in db.execute(select(AccountBase.id).where(AccountBase.is_starred == True)).all() # noqa: E712
]
vouchers = []
query = (
db.execute(
select(Voucher)
.join(Voucher.journals)
.join(Journal.account)
.where(Voucher.date < date_, Voucher.journals.any(Journal.account_id.in_(accounts)))
)
.unique()
.scalars()
.all()
)
for voucher in query:
vouchers.append(voucher.id)
others = [journal for journal in voucher.journals if journal.account_id not in accounts]
if len(others) == 0:
continue
amount = round(Decimal(sum(o.signed_amount for o in others)), 2)
if amount != 0:
journal = Journal(
amount=abs(amount),
debit=-1 if amount < 0 else 1,
account_id=AccountBase.suspense(),
cost_centre_id=CostCentre.cost_centre_overall(),
)
voucher.journals.append(journal)
db.add(journal)
for other in others:
if voucher.voucher_type != VoucherType.OPENING_ACCOUNTS:
voucher.narration += f"\nSuspense \u20B9{other.amount:,.2f} is {other.account.name}"
if other.employee_benefit:
db.delete(other.employee_benefit)
if other.incentive:
db.delete(other.incentive)
db.delete(other)
voucher.voucher_type = VoucherType.JOURNAL
if len(voucher.narration) >= 1000:
voucher.narration = voucher.narration[:1000]
return vouchers
def opening_accounts(date_: date, user_id: uuid.UUID, db: Session) -> Voucher:
running_total = 0
sum_func = func.sum(Journal.signed_amount)
query = db.execute(
select(AccountBase, sum_func)
.join(Journal, Voucher.journals)
.join(AccountBase, Journal.account)
.where(
AccountBase.is_starred == False, # noqa: E712
AccountBase.id != AccountBase.suspense(),
Voucher.date < date_,
Voucher.voucher_type != VoucherType.ISSUE,
)
.having(sum_func != 0)
.group_by(AccountBase)
).all()
dt = date_ - timedelta(days=1)
voucher = Voucher(
date=dt,
narration="Opening Accounts",
user_id=user_id,
posted=True,
voucher_type=VoucherType.OPENING_ACCOUNTS,
)
for account, amount in query:
amount = round(Decimal(amount), 2)
if account.type_.balance_sheet and amount != 0:
running_total += amount
journal = Journal(
amount=abs(amount),
debit=-1 if amount < 0 else 1,
account_id=account.id,
cost_centre_id=account.cost_centre_id,
)
voucher.journals.append(journal)
if running_total != 0:
journal = Journal(
amount=abs(amount),
debit=-1 if amount * -1 < 0 else 1,
account_id=AccountBase.suspense(),
cost_centre_id=CostCentre.cost_centre_overall(),
)
voucher.journals.append(journal)
return voucher
def opening_batches(date_: date, user_id: uuid.UUID, db: Session) -> Voucher:
total = Decimal(0)
sum_func = func.sum(Journal.debit * Inventory.quantity)
query = db.execute(
select(Batch, sum_func)
.join(Batch.inventories)
.join(Inventory.voucher)
.join(Voucher.journals)
.where(Voucher.date < date_, Journal.cost_centre_id == CostCentre.cost_centre_purchase())
.having(sum_func != 0)
.group_by(Batch)
).all()
dt = date_ - timedelta(days=1)
voucher = Voucher(
date=dt,
narration="Opening Batches",
user_id=user_id,
posted=True,
voucher_type=VoucherType.OPENING_BATCHES,
)
quantity: Decimal
for batch, quantity in query:
quantity = round(quantity, 2)
if quantity != 0:
total += quantity * batch.rate * (1 + batch.tax) * (1 - batch.discount)
inventory = Inventory(
batch=batch,
quantity=quantity,
rate=batch.rate,
tax=batch.tax,
discount=batch.discount,
)
voucher.inventories.append(inventory)
voucher.journals.append(
Journal(
amount=abs(total),
debit=-1,
account_id=AccountBase.all_purchases(),
cost_centre_id=CostCentre.cost_centre_overall(),
)
)
voucher.journals.append(
Journal(
amount=abs(total),
debit=1,
account_id=AccountBase.all_purchases(),
cost_centre_id=CostCentre.cost_centre_purchase(),
)
)
return voucher
def delete_data(date_: date, vouchers: List[Voucher], db: Session) -> None:
sub_voucher = aliased(Voucher)
sub_query = select(sub_voucher.id).where(sub_voucher.date < date_).subquery()
db.execute(
delete(Inventory).where(Inventory.voucher_id.in_(sub_query)).execution_options(synchronize_session=False)
)
db.execute(
delete(EmployeeBenefit)
.where(EmployeeBenefit.voucher_id.in_(sub_query))
.execution_options(synchronize_session=False)
)
db.execute(
delete(Incentive).where(Incentive.voucher_id.in_(sub_query)).execution_options(synchronize_session=False)
)
db.execute(
delete(Journal)
.where(and_(Journal.voucher_id.in_(sub_query), ~Journal.voucher_id.in_(vouchers)))
.execution_options(synchronize_session=False)
)
db.execute(
delete(DbImage)
.where(
and_(
DbImage.resource_type == "voucher",
DbImage.resource_id.in_(sub_query),
~DbImage.resource_id.in_(vouchers),
)
)
.execution_options(synchronize_session=False)
)
db.execute(
delete(Voucher)
.where(Voucher.date < date_, ~Voucher.id.in_(vouchers))
.execution_options(synchronize_session=False)
)
def cleanup_lint(date_: date, db: Session) -> None:
# Insert executes on the end so keep list of batches and journals
db.execute(
delete(Batch)
.where(~Batch.id.in_(select(distinct(Inventory.batch_id)).subquery()))
.execution_options(synchronize_session=False)
)
db.execute(delete(Fingerprint).where(Fingerprint.date < date_).execution_options(synchronize_session=False))
db.execute(delete(Attendance).where(Attendance.date < date_).execution_options(synchronize_session=False))
db.execute(
delete(Employee)
.where(
and_(
~Employee.id.in_(select(distinct(Journal.account_id)).subquery()),
~Employee.id.in_(select(distinct(Fingerprint.employee_id)).subquery()),
~Employee.id.in_(select(distinct(Attendance.employee_id)).subquery()),
Employee.id.in_(
select(AccountBase.id)
.where(
AccountBase.is_fixture == False, # noqa: E712
AccountBase.is_active == False, # noqa: E712
AccountBase.is_starred == False, # noqa: E712
)
.subquery()
),
Employee.leaving_date < date_,
)
)
.execution_options(synchronize_session=False)
)
db.execute(
delete(AccountBase)
.where(
and_(
~AccountBase.id.in_(select(Employee.id).subquery()),
AccountBase.account_type == Employee.__mapper_args__["polymorphic_identity"],
)
)
.execution_options(synchronize_session=False)
)
db.execute(
delete(Account)
.where(
and_(
~Account.id.in_(select(distinct(Journal.account_id)).subquery()),
Account.is_fixture == False, # noqa: E712
Account.is_starred == False, # noqa: E712
Account.account_type == Account.__mapper_args__["polymorphic_identity"],
)
)
.execution_options(synchronize_session=False)
)