DB Integrity checks for batch integrity and also updates quantities when it can.
This commit is contained in:
parent
6212eead20
commit
b1557bef88
@ -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
|
||||
|
44
overlord/src/app/settings/integrity-batch.ts
Normal file
44
overlord/src/app/settings/integrity-batch.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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) => {
|
||||
|
@ -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[];
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user