From b1557bef883ededbe38caac5c173267718c0f5f1 Mon Sep 17 00:00:00 2001 From: tanshu Date: Tue, 21 Sep 2021 09:54:11 +0530 Subject: [PATCH] DB Integrity checks for batch integrity and also updates quantities when it can. --- brewman/brewman/routers/db_integrity.py | 109 ++++++++++++++++-- overlord/src/app/settings/integrity-batch.ts | 44 +++++++ .../src/app/settings/settings.component.html | 47 +++++++- .../src/app/settings/settings.component.ts | 7 +- overlord/src/app/settings/settings.service.ts | 14 ++- 5 files changed, 206 insertions(+), 15 deletions(-) create mode 100644 overlord/src/app/settings/integrity-batch.ts diff --git a/brewman/brewman/routers/db_integrity.py b/brewman/brewman/routers/db_integrity.py index 09854e8b..fbc98ece 100644 --- a/brewman/brewman/routers/db_integrity.py +++ b/brewman/brewman/routers/db_integrity.py @@ -1,10 +1,18 @@ +import uuid + from fastapi import APIRouter, Security -from sqlalchemy import delete, desc, distinct, func, over, select +from sqlalchemy import delete, desc, distinct, func, over, select, update from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..db.session import SessionFuture from ..models.attendance import Attendance +from ..models.batch import Batch +from ..models.cost_centre import CostCentre +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 @@ -16,6 +24,9 @@ def post_check_db(user: UserToken = Security(get_user)): info = {} with SessionFuture() as db: + info["batches"] = batches(db) + info["batchDates"] = batch_dates(db) + #TODO: Also check the prices in the batches to errors duplicate_attendances = get_duplicate_attendances(db) if duplicate_attendances > 0: fix_duplicate_attendances(db) @@ -25,15 +36,13 @@ def post_check_db(user: UserToken = Security(get_user)): def get_duplicate_attendances(db: Session) -> int: - sub_query = ( - select( - over( - distinct(func.first_value(Attendance.id)), - partition_by=[Attendance.employee_id, Attendance.date], - ) + sub_query = select( + over( + distinct(func.first_value(Attendance.id)), + partition_by=[Attendance.employee_id, Attendance.date], ) - .where(Attendance.is_valid == True) # noqa: E712 - .subquery() + ).where( + Attendance.is_valid == True # noqa: E712 ) query = db.execute( select(func.count(Attendance.id)).where( @@ -56,3 +65,85 @@ def fix_duplicate_attendances(db: Session) -> None: .subquery() ) db.execute(delete(Attendance).where(~Attendance.id.in_(sub), Attendance.is_valid == True)) # noqa: E712 + + +def batches(db: Session): + quantities = db.execute( + select(Inventory.batch_id, func.sum(Inventory.quantity * Journal.debit)) + .join(Inventory.voucher) + .join(Voucher.journals) + .where( + Voucher.type.in_( + [ + VoucherType.by_name("Purchase").id, + VoucherType.by_name("Purchase Return").id, + VoucherType.by_name("Issue").id, + VoucherType.by_name("Opening Batches").id, + ] + ), + Journal.cost_centre_id == CostCentre.cost_centre_purchase(), + ) + .group_by(Inventory.batch_id) + ).all() + + issue = [] + for batch_id, quantity in quantities: + batch = db.execute(select(Batch).where(Batch.id == batch_id)).scalar_one() + if batch.quantity_remaining == quantity: + continue + if quantity >= 0: + db.execute(update(Batch).where(Batch.id == batch_id).values(quantity_remaining=quantity)) + else: + issue.append( + { + "product": batch.product.full_name, + "date": batch.name, + "showing": batch.quantity_remaining, + "actual": quantity, + "details": batch_details(batch_id, db), + } + ) + return issue + + +def batch_details(batch_id: uuid.UUID, db: Session): + details = db.execute( + select(Voucher.date, Voucher.type, Inventory.quantity) + .join(Inventory.voucher) + .join(Voucher.journals) + .where( + Inventory.batch_id == batch_id, + Voucher.type.in_( + [ + VoucherType.by_name("Purchase").id, + VoucherType.by_name("Purchase Return").id, + VoucherType.by_name("Issue").id, + VoucherType.by_name("Opening Batches").id, + ] + ), + Journal.cost_centre_id == CostCentre.cost_centre_purchase(), + ) + ).all() + return [{"date": x.date, "type": VoucherType.by_id(x.type).name, "quantity": x.quantity} for x in details] + + +def batch_dates(db: Session): + quantities = db.execute( + select(Batch, Voucher.date, Voucher.type) + .join(Batch.product) + .join(Batch.inventories) + .join(Inventory.voucher) + .where(Voucher.date < Batch.name) + ).all() + + issue = [] + for batch, date_, type_ in quantities: + issue.append( + { + "product": batch.product.full_name, + "batchDate": batch.name, + "voucherDate": date_, + "type": VoucherType.by_id(type_).name, + } + ) + return issue diff --git a/overlord/src/app/settings/integrity-batch.ts b/overlord/src/app/settings/integrity-batch.ts new file mode 100644 index 00000000..fd7ffefd --- /dev/null +++ b/overlord/src/app/settings/integrity-batch.ts @@ -0,0 +1,44 @@ +export class BatchDate { + product: string; + batchDate: string; + voucherDate: string; + type: string; + + public constructor(init?: Partial) { + this.product = ''; + this.batchDate = ''; + this.voucherDate = ''; + this.type = ''; + Object.assign(this, init); + } +} + +export class BatchDetail { + date: string; + type: string; + quantity: number; + + public constructor(init?: Partial) { + this.date = ''; + this.type = ''; + this.quantity = 0; + Object.assign(this, init); + } +} + +export class IntegrityBatch { + product: string; + date: string; + showing: number; + actual: number; + details: BatchDetail[]; + + public constructor(init?: Partial) { + this.product = ''; + this.date = ''; + this.showing = 0; + this.actual = 0; + this.details = []; + Object.assign(this, init); + } +} diff --git a/overlord/src/app/settings/settings.component.html b/overlord/src/app/settings/settings.component.html index c02720f3..e11bf8e7 100644 --- a/overlord/src/app/settings/settings.component.html +++ b/overlord/src/app/settings/settings.component.html @@ -303,9 +303,50 @@ - +
+ +
+
+
    +
  • + Batch of {{ batch.product }} dated {{ batch.date }} is showing + {{ batch.showing }} instead of {{ batch.actual }} +
      +
    • + {{ detail.type }} of {{ detail.quantity }} on {{ detail.date }} +
    • +
    +
  • +
+
+
+
    +
  • + {{ batch.type }} of {{ batch.product }} dated {{ batch.batchDate }} on + {{ batch.voucherDate }} +
  • +
+
diff --git a/overlord/src/app/settings/settings.component.ts b/overlord/src/app/settings/settings.component.ts index 82af0169..91e07ec8 100644 --- a/overlord/src/app/settings/settings.component.ts +++ b/overlord/src/app/settings/settings.component.ts @@ -15,6 +15,7 @@ import { ToasterService } from '../core/toaster.service'; import { ProductService } from '../product/product.service'; import { ConfirmDialogComponent } from '../shared/confirm-dialog/confirm-dialog.component'; +import { BatchDate, IntegrityBatch } from './integrity-batch'; import { LockDataSource } from './lock-datasource'; import { LockInfo } from './lock-info'; import { SettingsService } from './settings.service'; @@ -44,6 +45,8 @@ export class SettingsComponent implements OnInit { product: Product = new Product(); products: Observable; + batches: IntegrityBatch[] = []; + batchDates: BatchDate[] = []; maintenance: { enabled: boolean; user: string }; displayedColumns = ['index', 'validity', 'voucherTypes', 'accountTypes', 'lock', 'action']; @@ -290,7 +293,9 @@ export class SettingsComponent implements OnInit { checkIntegrity() { this.ser.checkDatabaseIntegrity().subscribe( - () => { + (x) => { + this.batches = x.batches; + this.batchDates = x.batchDates; this.toaster.show('Success', 'Database checked, it is fine!'); }, (error) => { diff --git a/overlord/src/app/settings/settings.service.ts b/overlord/src/app/settings/settings.service.ts index b90c5ca8..e349901d 100644 --- a/overlord/src/app/settings/settings.service.ts +++ b/overlord/src/app/settings/settings.service.ts @@ -6,6 +6,7 @@ import { catchError } from 'rxjs/operators'; import { ErrorLoggerService } from '../core/error-logger.service'; import { Product } from '../core/product'; +import { BatchDate, IntegrityBatch } from './integrity-batch'; import { LockInfo } from './lock-info'; const serviceName = 'SettingsService'; @@ -82,12 +83,21 @@ export class SettingsService { }>; } - checkDatabaseIntegrity(): Observable<{ attendanceCount: number }> { + checkDatabaseIntegrity(): Observable<{ + attendanceCount: number; + batches: IntegrityBatch[]; + batchDates: BatchDate[]; + }> { const url = '/api/db-integrity'; return this.http - .post<{ attendanceCount: number }>(url, {}) + .post<{ attendanceCount: number; batches: IntegrityBatch[]; batchDates: BatchDate[] }>( + url, + {}, + ) .pipe(catchError(this.log.handleError(serviceName, 'checkDatabaseIntegrity'))) as Observable<{ attendanceCount: number; + batches: IntegrityBatch[]; + batchDates: BatchDate[]; }>; } }