DB Integrity checks for batch integrity and also updates quantities when it can.
This commit is contained in:
@ -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
|
||||||
|
|||||||
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-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>
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
@ -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[];
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user