brewman/brewman/brewman/routers/rebase.py

287 lines
9.2 KiB
Python

import uuid
from datetime import date, datetime, timedelta
from decimal import Decimal
from typing import List
from fastapi import APIRouter, Depends, Security
from sqlalchemy import and_, distinct, func
from sqlalchemy.orm import Session, aliased, joinedload_all
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal
from ..models import Batch, EmployeeBenefit, Inventory, Journal, Voucher, VoucherType
from ..models.master import Account, AccountBase, CostCentre, Employee
from ..models.voucher import Attendance, DbImage, Fingerprint, Incentive
from ..schemas.auth import UserToken
router = APIRouter()
# Dependency
def get_db() -> Session:
try:
db = SessionLocal()
yield db
finally:
db.close()
@router.post("/{date_}")
def rebase(
date_: str,
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["rebase"]),
):
# request.dbsession.execute('SET statement_timeout TO 300000;') # 5 minutes
date_ = datetime.strptime(date_, "%d-%b-%Y")
voucher_l = opening_accounts(date_, user.id_, db)
voucher_b = opening_batches(date_, user.id_, db)
starred_vouchers = save_starred(date_, db)
db.flush()
delete_data(date_, 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_, db)
# request.dbsession.execute('RESET statement_timeout;')
db.commit()
return {}
def save_starred(date_: date, db: Session):
accounts = [
i.id
for i in db.query(AccountBase.id).filter(AccountBase.is_starred == True).all()
]
vouchers = []
query = (
db.query(Voucher)
.options(joinedload_all(Voucher.journals, Journal.account, innerjoin=True))
.filter(Voucher.date < date_)
.filter(Voucher.journals.any(Journal.account_id.in_(accounts)))
.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.type != VoucherType.by_name("Opening Accounts").id:
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.type = VoucherType.by_name("Journal")
if len(voucher.narration) >= 1000:
voucher.narration = voucher.narration[:1000]
return vouchers
def opening_accounts(date_: date, user_id: uuid.UUID, db: Session):
running_total = 0
sum_func = func.sum(Journal.signed_amount)
query = (
db.query(AccountBase, sum_func)
.join(Journal, Voucher.journals)
.join(AccountBase, Journal.account)
.filter(AccountBase.is_starred == False)
.filter(AccountBase.id != AccountBase.suspense())
.filter(Voucher.date < date_)
.filter(Voucher.type != VoucherType.by_name("Issue").id)
.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,
type_=VoucherType.by_name("Opening Accounts"),
)
for account, amount in query:
amount = round(Decimal(amount), 2)
if account.type_object.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):
total = 0
sum_func = func.sum(Journal.debit * Inventory.quantity)
query = (
db.query(Batch, sum_func)
.join(Journal, Voucher.journals)
.join(Inventory, Voucher.inventories)
.join(Batch, Inventory.batch)
.filter(Voucher.date < date_)
.filter(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,
type_=VoucherType.by_name("Opening Batches"),
)
for batch, quantity in query:
quantity = round(Decimal(quantity), 2)
if quantity != 0:
total += quantity * batch.rate * (1 + batch.tax) * (1 - batch.discount)
inventory = Inventory(
product_id=batch.product_id,
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):
sub_voucher = aliased(Voucher)
sub_query = db.query(sub_voucher.id).filter(sub_voucher.date < date_).subquery()
db.execute(Inventory.__table__.delete(Inventory.voucher_id.in_(sub_query)))
db.execute(
EmployeeBenefit.__table__.delete(EmployeeBenefit.voucher_id.in_(sub_query))
)
db.execute(Incentive.__table__.delete(Incentive.voucher_id.in_(sub_query)))
db.execute(
Journal.__table__.delete(
and_(Journal.voucher_id.in_(sub_query), ~Journal.voucher_id.in_(vouchers))
)
)
db.execute(
DbImage.__table__.delete(
and_(
DbImage.resource_type == "voucher",
DbImage.resource_id.in_(sub_query),
~DbImage.resource_id.in_(vouchers),
)
)
)
db.execute(
Voucher.__table__.delete(and_(Voucher.date < date_, ~Voucher.id.in_(vouchers)))
)
def cleanup_lint(date_: date, db: Session):
# Insert executes on the end so keep list of batches and journals
db.execute(
Batch.__table__.delete(
~Batch.id.in_(db.query(distinct(Inventory.batch_id)).subquery())
)
)
db.execute(Fingerprint.__table__.delete(Fingerprint.date < date_))
db.execute(Attendance.__table__.delete(Attendance.date < date_))
db.execute(
Employee.__table__.delete(
and_(
~Employee.id.in_(db.query(distinct(Journal.account_id)).subquery()),
~Employee.id.in_(
db.query(distinct(Fingerprint.employee_id)).subquery()
),
~Employee.id.in_(db.query(distinct(Attendance.employee_id)).subquery()),
Employee.id.in_(
db.query(AccountBase.id)
.filter(AccountBase.is_fixture == False)
.filter(AccountBase.is_active == False)
.filter(AccountBase.is_starred == False)
.subquery()
),
Employee.leaving_date < date_,
)
)
)
db.execute(
AccountBase.__table__.delete(
and_(
~AccountBase.id.in_(db.query(Employee.id).subquery()),
AccountBase.account_type
== Employee.__mapper_args__["polymorphic_identity"],
)
)
)
db.execute(
Account.__table__.delete(
and_(
~Account.id.in_(db.query(distinct(Journal.account_id)).subquery()),
Account.is_fixture == False,
Account.is_starred == False,
Account.account_type == Account.__mapper_args__["polymorphic_identity"],
)
)
)