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"], ) ) )