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

This commit is contained in:
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 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 sqlalchemy.orm import Session
from ..core.security import get_current_active_user as get_user from ..core.security import get_current_active_user as get_user
from ..db.session import SessionFuture from ..db.session import SessionFuture
from ..models.attendance import Attendance 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 from ..schemas.user import UserToken
@ -16,6 +24,9 @@ def post_check_db(user: UserToken = Security(get_user)):
info = {} info = {}
with SessionFuture() as db: 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) duplicate_attendances = get_duplicate_attendances(db)
if duplicate_attendances > 0: if duplicate_attendances > 0:
fix_duplicate_attendances(db) fix_duplicate_attendances(db)
@ -25,15 +36,13 @@ def post_check_db(user: UserToken = Security(get_user)):
def get_duplicate_attendances(db: Session) -> int: def get_duplicate_attendances(db: Session) -> int:
sub_query = ( sub_query = select(
select( over(
over( distinct(func.first_value(Attendance.id)),
distinct(func.first_value(Attendance.id)), partition_by=[Attendance.employee_id, Attendance.date],
partition_by=[Attendance.employee_id, Attendance.date],
)
) )
.where(Attendance.is_valid == True) # noqa: E712 ).where(
.subquery() Attendance.is_valid == True # noqa: E712
) )
query = db.execute( query = db.execute(
select(func.count(Attendance.id)).where( select(func.count(Attendance.id)).where(
@ -56,3 +65,85 @@ def fix_duplicate_attendances(db: Session) -> None:
.subquery() .subquery()
) )
db.execute(delete(Attendance).where(~Attendance.id.in_(sub), Attendance.is_valid == True)) # noqa: E712 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-tab label="Integrity Check">
<mat-card> <mat-card>
<mat-card-content> <mat-card-content>
<button mat-raised-button color="warn" (click)="checkIntegrity()" fxFlex="100"> <div
Integrity Check fxLayout="row"
</button> 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-content>
</mat-card> </mat-card>
</mat-tab> </mat-tab>

View File

@ -15,6 +15,7 @@ import { ToasterService } from '../core/toaster.service';
import { ProductService } from '../product/product.service'; import { ProductService } from '../product/product.service';
import { ConfirmDialogComponent } from '../shared/confirm-dialog/confirm-dialog.component'; import { ConfirmDialogComponent } from '../shared/confirm-dialog/confirm-dialog.component';
import { BatchDate, IntegrityBatch } from './integrity-batch';
import { LockDataSource } from './lock-datasource'; import { LockDataSource } from './lock-datasource';
import { LockInfo } from './lock-info'; import { LockInfo } from './lock-info';
import { SettingsService } from './settings.service'; import { SettingsService } from './settings.service';
@ -44,6 +45,8 @@ export class SettingsComponent implements OnInit {
product: Product = new Product(); product: Product = new Product();
products: Observable<Product[]>; products: Observable<Product[]>;
batches: IntegrityBatch[] = [];
batchDates: BatchDate[] = [];
maintenance: { enabled: boolean; user: string }; maintenance: { enabled: boolean; user: string };
displayedColumns = ['index', 'validity', 'voucherTypes', 'accountTypes', 'lock', 'action']; displayedColumns = ['index', 'validity', 'voucherTypes', 'accountTypes', 'lock', 'action'];
@ -290,7 +293,9 @@ export class SettingsComponent implements OnInit {
checkIntegrity() { checkIntegrity() {
this.ser.checkDatabaseIntegrity().subscribe( this.ser.checkDatabaseIntegrity().subscribe(
() => { (x) => {
this.batches = x.batches;
this.batchDates = x.batchDates;
this.toaster.show('Success', 'Database checked, it is fine!'); this.toaster.show('Success', 'Database checked, it is fine!');
}, },
(error) => { (error) => {

View File

@ -6,6 +6,7 @@ import { catchError } from 'rxjs/operators';
import { ErrorLoggerService } from '../core/error-logger.service'; import { ErrorLoggerService } from '../core/error-logger.service';
import { Product } from '../core/product'; import { Product } from '../core/product';
import { BatchDate, IntegrityBatch } from './integrity-batch';
import { LockInfo } from './lock-info'; import { LockInfo } from './lock-info';
const serviceName = 'SettingsService'; 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'; const url = '/api/db-integrity';
return this.http return this.http
.post<{ attendanceCount: number }>(url, {}) .post<{ attendanceCount: number; batches: IntegrityBatch[]; batchDates: BatchDate[] }>(
url,
{},
)
.pipe(catchError(this.log.handleError(serviceName, 'checkDatabaseIntegrity'))) as Observable<{ .pipe(catchError(this.log.handleError(serviceName, 'checkDatabaseIntegrity'))) as Observable<{
attendanceCount: number; attendanceCount: number;
batches: IntegrityBatch[];
batchDates: BatchDate[];
}>; }>;
} }
} }