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) )