DB Integrity checks for batch integrity and also updates quantities when it can.

This commit is contained in:
Amritanshu Agrawal 2021-09-21 09:54:11 +05:30
parent 6212eead20
commit b1557bef88
5 changed files with 206 additions and 15 deletions

View File

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

View File

@ -0,0 +1,44 @@
export class BatchDate {
product: string;
batchDate: string;
voucherDate: string;
type: string;
public constructor(init?: Partial<BatchDate>) {
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<BatchDetail>) {
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<IntegrityBatch>) {
this.product = '';
this.date = '';
this.showing = 0;
this.actual = 0;
this.details = [];
Object.assign(this, init);
}
}

View File

@ -303,9 +303,50 @@
<mat-tab label="Integrity Check">
<mat-card>
<mat-card-content>
<button mat-raised-button color="warn" (click)="checkIntegrity()" fxFlex="100">
Integrity Check
</button>
<div
fxLayout="row"
fxLayoutAlign="space-around start"
fxLayout.lt-md="column"
fxLayoutGap="20px"
fxLayoutGap.lt-md="0px"
>
<button mat-raised-button color="warn" (click)="checkIntegrity()" fxFlex="100">
Integrity Check
</button>
</div>
<div
fxLayout="row"
fxLayoutAlign="space-around start"
fxLayout.lt-md="column"
fxLayoutGap="20px"
fxLayoutGap.lt-md="0px"
>
<ul>
<li *ngFor="let batch of batches">
Batch of {{ batch.product }} dated {{ batch.date }} is showing
{{ batch.showing }} instead of {{ batch.actual }}
<ul>
<li *ngFor="let detail of batch.details">
{{ detail.type }} of {{ detail.quantity }} on {{ detail.date }}
</li>
</ul>
</li>
</ul>
</div>
<div
fxLayout="row"
fxLayoutAlign="space-around start"
fxLayout.lt-md="column"
fxLayoutGap="20px"
fxLayoutGap.lt-md="0px"
>
<ul>
<li *ngFor="let batch of batchDates">
{{ batch.type }} of {{ batch.product }} dated {{ batch.batchDate }} on
{{ batch.voucherDate }}
</li>
</ul>
</div>
</mat-card-content>
</mat-card>
</mat-tab>

View File

@ -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<Product[]>;
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) => {

View File

@ -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[];
}>;
}
}