Product sale report can now be section wise

This commit is contained in:
2024-12-17 08:46:29 +05:30
parent bbb7be8070
commit 55d5017254
9 changed files with 98 additions and 83 deletions

View File

@ -1,15 +1,16 @@
import uuid import uuid
from datetime import date, datetime, time, timedelta from datetime import date, datetime, time, timedelta
from typing import Any from typing import Annotated, Any
from fastapi import APIRouter, Cookie, Depends, Security from fastapi import APIRouter, Cookie, Depends, Query, Security
from sqlalchemy import func, or_, select from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from ...core.config import settings from ...core.config import settings
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.food_table import FoodTable
from ...models.inventory import Inventory from ...models.inventory import Inventory
from ...models.kot import Kot from ...models.kot import Kot
from ...models.menu_category import MenuCategory from ...models.menu_category import MenuCategory
@ -31,6 +32,7 @@ router = APIRouter()
def product_sale_report_view( def product_sale_report_view(
start_date: date = Depends(report_start_date), start_date: date = Depends(report_start_date),
finish_date: date = Depends(report_finish_date), finish_date: date = Depends(report_finish_date),
section: Annotated[uuid.UUID | None, Query()] = None,
user: UserToken = Security(get_user, scopes=["product-sale-report"]), user: UserToken = Security(get_user, scopes=["product-sale-report"]),
): ):
check_audit_permission(start_date, user.permissions) check_audit_permission(start_date, user.permissions)
@ -38,11 +40,12 @@ def product_sale_report_view(
return { return {
"startDate": start_date.strftime("%d-%b-%Y"), "startDate": start_date.strftime("%d-%b-%Y"),
"finishDate": finish_date.strftime("%d-%b-%Y"), "finishDate": finish_date.strftime("%d-%b-%Y"),
"amounts": product_sale_report(start_date, finish_date, db), "sectionId": str(section) if section is not None else None,
"amounts": product_sale_report(start_date, finish_date, section, db),
} }
def product_sale_report(s: date, f: date, db: Session): def product_sale_report(s: date, f: date, id_: uuid.UUID, db: Session):
start_date = datetime.combine(s, time()) + timedelta( start_date = datetime.combine(s, time()) + timedelta(
minutes=settings.NEW_DAY_OFFSET_MINUTES - settings.TIMEZONE_OFFSET_MINUTES minutes=settings.NEW_DAY_OFFSET_MINUTES - settings.TIMEZONE_OFFSET_MINUTES
) )
@ -51,7 +54,7 @@ def product_sale_report(s: date, f: date, db: Session):
) )
day = func.date_trunc("day", Voucher.date - timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES)).label("day") day = func.date_trunc("day", Voucher.date - timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES)).label("day")
list_ = db.execute( query = (
select( select(
ProductVersion.id, ProductVersion.id,
ProductVersion.full_name, ProductVersion.full_name,
@ -65,6 +68,7 @@ def product_sale_report(s: date, f: date, db: Session):
.join(Product.versions) .join(Product.versions)
.join(ProductVersion.sale_category) .join(ProductVersion.sale_category)
.join(ProductVersion.menu_category) .join(ProductVersion.menu_category)
.join(Voucher.food_table)
.where( .where(
Voucher.date >= start_date, Voucher.date >= start_date,
Voucher.date <= finish_date, Voucher.date <= finish_date,
@ -77,16 +81,18 @@ def product_sale_report(s: date, f: date, db: Session):
ProductVersion.valid_till >= day, ProductVersion.valid_till >= day,
), ),
) )
.group_by( )
SaleCategory.name, if id_:
MenuCategory.name, query = query.where(FoodTable.section_id == id_)
ProductVersion.id, query = query.group_by(
ProductVersion.full_name, SaleCategory.name,
Voucher.voucher_type, MenuCategory.name,
Inventory.is_happy_hour, ProductVersion.id,
) ProductVersion.full_name,
.order_by(SaleCategory.name, MenuCategory.name, ProductVersion.full_name) Voucher.voucher_type,
).all() Inventory.is_happy_hour,
).order_by(SaleCategory.name, MenuCategory.name, ProductVersion.full_name)
list_ = db.execute(query).all()
info: list[Any] = [] info: list[Any] = []
for id_, name, v_type, hh, quantity in list_: for id_, name, v_type, hh, quantity in list_:
type_ = to_camel(VoucherType(v_type).name) type_ = to_camel(VoucherType(v_type).name)
@ -111,6 +117,7 @@ def product_sale_report(s: date, f: date, db: Session):
def print_report( def print_report(
start_date: date = Depends(report_start_date), start_date: date = Depends(report_start_date),
finish_date: date = Depends(report_finish_date), finish_date: date = Depends(report_finish_date),
section: Annotated[uuid.UUID | None, Query()] = None,
device_id: uuid.UUID = Cookie(None), device_id: uuid.UUID = Cookie(None),
user: UserToken = Security(get_user, scopes=["product-sale-report"]), user: UserToken = Security(get_user, scopes=["product-sale-report"]),
) -> bool: ) -> bool:
@ -120,6 +127,7 @@ def print_report(
"userName": user.name, "userName": user.name,
"startDate": start_date.strftime("%d-%b-%Y"), "startDate": start_date.strftime("%d-%b-%Y"),
"finishDate": finish_date.strftime("%d-%b-%Y"), "finishDate": finish_date.strftime("%d-%b-%Y"),
"sectionId": str(section) if section is not None else None,
"amounts": product_sale_report(start_date, finish_date, db), "amounts": product_sale_report(start_date, finish_date, db),
} }
print_product_sale_report(report, device_id, db) print_product_sale_report(report, device_id, db)

View File

@ -137,6 +137,7 @@ def print_report(
report = SaleReport( report = SaleReport(
start_date=start_date, start_date=start_date,
finish_date=finish_date, finish_date=finish_date,
section_id=section,
amounts=( amounts=(
get_sale(start_date, finish_date, section, db) get_sale(start_date, finish_date, section, db)
+ [SaleReportItem(name="--", amount=Decimal(0))] + [SaleReportItem(name="--", amount=Decimal(0))]

View File

@ -14,7 +14,7 @@
<mat-card-content> <mat-card-content>
<form [formGroup]="form" class="flex flex-col"> <form [formGroup]="form" class="flex flex-col">
<div class="flex flex-row justify-around content-start items-start sm:max-lg:flex-col"> <div class="flex flex-row justify-around content-start items-start sm:max-lg:flex-col">
<mat-form-field class="flex-auto basis-2/5 mr-5"> <mat-form-field class="flex-auto basis-1/3 mr-5">
<mat-label>Start Date</mat-label> <mat-label>Start Date</mat-label>
<input <input
matInput matInput
@ -26,7 +26,7 @@
<mat-datepicker-toggle matSuffix [for]="startDate"></mat-datepicker-toggle> <mat-datepicker-toggle matSuffix [for]="startDate"></mat-datepicker-toggle>
<mat-datepicker #startDate></mat-datepicker> <mat-datepicker #startDate></mat-datepicker>
</mat-form-field> </mat-form-field>
<mat-form-field class="flex-auto basis-2/5 mr-5"> <mat-form-field class="flex-auto basis-1/3 mr-5">
<mat-label>Finish Date</mat-label> <mat-label>Finish Date</mat-label>
<input <input
matInput matInput
@ -38,7 +38,18 @@
<mat-datepicker-toggle matSuffix [for]="finishDate"></mat-datepicker-toggle> <mat-datepicker-toggle matSuffix [for]="finishDate"></mat-datepicker-toggle>
<mat-datepicker #finishDate></mat-datepicker> <mat-datepicker #finishDate></mat-datepicker>
</mat-form-field> </mat-form-field>
<button mat-raised-button class="flex-auto basis-1/5" color="primary" (click)="show()">Show</button> <mat-form-field class="flex-auto basis-1/6 mr-5">
<mat-label>Section</mat-label>
<mat-select formControlName="section" name="section">
<mat-option>-- All --</mat-option>
@for (p of sections; track p.id) {
<mat-option [value]="p.id">
{{ p.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<button mat-raised-button class="flex-auto basis-1/6" color="primary" (click)="show()">Show</button>
</div> </div>
</form> </form>
<mat-table #table [dataSource]="dataSource" aria-label="Elements"> <mat-table #table [dataSource]="dataSource" aria-label="Elements">

View File

@ -1,27 +1,18 @@
import { DecimalPipe } from '@angular/common'; import { DecimalPipe } from '@angular/common';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MatIconButton, MatButton } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCard, MatCardHeader, MatCardTitleGroup, MatCardTitle, MatCardContent } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatDatepickerInput, MatDatepickerToggle, MatDatepicker } from '@angular/material/datepicker'; import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormField, MatLabel, MatSuffix } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIcon } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInput } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { import { MatSelectModule } from '@angular/material/select';
MatTable, import { MatTableModule } from '@angular/material/table';
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow,
} from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import moment from 'moment'; import moment from 'moment';
import { Section } from '../core/section';
import { ToasterService } from '../core/toaster.service'; import { ToasterService } from '../core/toaster.service';
import { ToCsvService } from '../shared/to-csv.service'; import { ToCsvService } from '../shared/to-csv.service';
@ -34,41 +25,26 @@ import { ProductSaleReportService } from './product-sale-report.service';
templateUrl: './product-sale-report.component.html', templateUrl: './product-sale-report.component.html',
styleUrls: ['./product-sale-report.component.css'], styleUrls: ['./product-sale-report.component.css'],
imports: [ imports: [
MatCard, MatCardModule,
MatCardHeader, MatButtonModule,
MatCardTitleGroup,
MatCardTitle,
MatIconButton,
MatIcon,
MatCardContent,
ReactiveFormsModule, ReactiveFormsModule,
MatFormField, MatFormFieldModule,
MatLabel, MatInputModule,
MatInput, MatIconModule,
MatDatepickerInput, MatDatepickerModule,
MatDatepickerToggle, MatTableModule,
MatSuffix, MatSelectModule,
MatDatepicker,
MatButton,
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow,
DecimalPipe, DecimalPipe,
], ],
}) })
export class ProductSaleReportComponent implements OnInit { export class ProductSaleReportComponent implements OnInit {
sections: Section[] = [];
info: ProductSaleReport = new ProductSaleReport(); info: ProductSaleReport = new ProductSaleReport();
dataSource: ProductSaleReportDataSource = new ProductSaleReportDataSource(this.info.amounts); dataSource: ProductSaleReportDataSource = new ProductSaleReportDataSource(this.info.amounts);
form: FormGroup<{ form: FormGroup<{
startDate: FormControl<Date>; startDate: FormControl<Date>;
finishDate: FormControl<Date>; finishDate: FormControl<Date>;
section: FormControl<string | null>;
}>; }>;
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
@ -85,16 +61,19 @@ export class ProductSaleReportComponent implements OnInit {
this.form = new FormGroup({ this.form = new FormGroup({
startDate: new FormControl(new Date(), { nonNullable: true }), startDate: new FormControl(new Date(), { nonNullable: true }),
finishDate: new FormControl(new Date(), { nonNullable: true }), finishDate: new FormControl(new Date(), { nonNullable: true }),
section: new FormControl<string | null>(null),
}); });
} }
ngOnInit() { ngOnInit() {
this.route.data.subscribe((value) => { this.route.data.subscribe((value) => {
const data = value as { info: ProductSaleReport }; const data = value as { sections: Section[]; info: ProductSaleReport };
this.sections = data.sections;
this.info = data.info; this.info = data.info;
this.form.setValue({ this.form.setValue({
startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(),
finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(),
section: this.info.sectionId || null,
}); });
this.dataSource = new ProductSaleReportDataSource(this.info.amounts); this.dataSource = new ProductSaleReportDataSource(this.info.amounts);
}); });
@ -102,25 +81,33 @@ export class ProductSaleReportComponent implements OnInit {
show() { show() {
const info = this.getInfo(); const info = this.getInfo();
const params = {
startDate: info.startDate,
finishDate: info.finishDate,
} as { startDate: string; finishDate: string; section?: string };
if (info.sectionId) {
params['section'] = info.sectionId;
}
this.router.navigate(['product-sale-report'], { this.router.navigate(['product-sale-report'], {
queryParams: { queryParams: params,
startDate: info.startDate,
finishDate: info.finishDate,
},
}); });
} }
getInfo(): ProductSaleReport { getInfo(): ProductSaleReport {
const formModel = this.form.value; const formModel = this.form.value;
return new ProductSaleReport({ const data = new ProductSaleReport({
startDate: moment(formModel.startDate).format('DD-MMM-YYYY'), startDate: moment(formModel.startDate).format('DD-MMM-YYYY'),
finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'), finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'),
}); });
if (formModel.section) {
data.sectionId = formModel.section;
}
return data;
} }
print() { print() {
this.ser.print(this.info.startDate, this.info.finishDate).subscribe({ this.ser.print(this.info.startDate, this.info.finishDate, this.info.sectionId).subscribe({
next: () => { next: () => {
this.toaster.show('', 'Successfully Printed'); this.toaster.show('', 'Successfully Printed');
}, },

View File

@ -7,5 +7,6 @@ import { ProductSaleReportService } from './product-sale-report.service';
export const productSaleReportResolver: ResolveFn<ProductSaleReport> = (route) => { export const productSaleReportResolver: ResolveFn<ProductSaleReport> = (route) => {
const startDate = route.queryParamMap.get('startDate') ?? null; const startDate = route.queryParamMap.get('startDate') ?? null;
const finishDate = route.queryParamMap.get('finishDate') ?? null; const finishDate = route.queryParamMap.get('finishDate') ?? null;
return inject(ProductSaleReportService).get(startDate, finishDate); const section = route.queryParamMap.get('section') ?? null;
return inject(ProductSaleReportService).get(startDate, finishDate, section);
}; };

View File

@ -1,6 +1,7 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { authGuard } from '../auth/auth-guard.service'; import { authGuard } from '../auth/auth-guard.service';
import { sectionListResolver } from '../sections/section-list.resolver';
import { ProductSaleReportComponent } from './product-sale-report.component'; import { ProductSaleReportComponent } from './product-sale-report.component';
import { productSaleReportResolver } from './product-sale-report.resolver'; import { productSaleReportResolver } from './product-sale-report.resolver';
@ -14,6 +15,7 @@ export const routes: Routes = [
permission: 'Product Sale Report', permission: 'Product Sale Report',
}, },
resolve: { resolve: {
sections: sectionListResolver,
info: productSaleReportResolver, info: productSaleReportResolver,
}, },
runGuardsAndResolvers: 'always', runGuardsAndResolvers: 'always',

View File

@ -19,7 +19,7 @@ export class ProductSaleReportService {
private log: ErrorLoggerService, private log: ErrorLoggerService,
) {} ) {}
get(startDate: string | null, finishDate: string | null): Observable<ProductSaleReport> { get(startDate: string | null, finishDate: string | null, section: string | null): Observable<ProductSaleReport> {
const options = { params: new HttpParams() }; const options = { params: new HttpParams() };
if (startDate !== null) { if (startDate !== null) {
options.params = options.params.set('s', startDate); options.params = options.params.set('s', startDate);
@ -27,12 +27,15 @@ export class ProductSaleReportService {
if (finishDate !== null) { if (finishDate !== null) {
options.params = options.params.set('f', finishDate); options.params = options.params.set('f', finishDate);
} }
if (section !== null) {
options.params = options.params.set('section', section);
}
return this.http return this.http
.get<ProductSaleReport>(url, options) .get<ProductSaleReport>(url, options)
.pipe(catchError(this.log.handleError(serviceName, 'get'))) as Observable<ProductSaleReport>; .pipe(catchError(this.log.handleError(serviceName, 'get'))) as Observable<ProductSaleReport>;
} }
print(startDate: string | null, finishDate: string | null): Observable<boolean> { print(startDate: string | null, finishDate: string | null, section: string | null): Observable<boolean> {
const printUrl = `${url}/print`; const printUrl = `${url}/print`;
const options = { params: new HttpParams() }; const options = { params: new HttpParams() };
if (startDate !== null) { if (startDate !== null) {
@ -41,6 +44,9 @@ export class ProductSaleReportService {
if (finishDate !== null) { if (finishDate !== null) {
options.params = options.params.set('f', finishDate); options.params = options.params.set('f', finishDate);
} }
if (section !== null) {
options.params = options.params.set('section', section);
}
return this.http return this.http
.get<boolean>(printUrl, options) .get<boolean>(printUrl, options)
.pipe(catchError(this.log.handleError(serviceName, 'print'))) as Observable<boolean>; .pipe(catchError(this.log.handleError(serviceName, 'print'))) as Observable<boolean>;

View File

@ -3,11 +3,13 @@ import { ProductSaleReportItem } from './product-sale-report-item';
export class ProductSaleReport { export class ProductSaleReport {
startDate: string; startDate: string;
finishDate: string; finishDate: string;
sectionId: string | null;
amounts: ProductSaleReportItem[]; amounts: ProductSaleReportItem[];
public constructor(init?: Partial<ProductSaleReport>) { public constructor(init?: Partial<ProductSaleReport>) {
this.startDate = ''; this.startDate = '';
this.finishDate = ''; this.finishDate = '';
this.sectionId = null;
this.amounts = []; this.amounts = [];
Object.assign(this, init); Object.assign(this, init);
} }

View File

@ -1,13 +1,13 @@
import { CurrencyPipe } from '@angular/common'; import { CurrencyPipe } from '@angular/common';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MatIconButton, MatButton } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatOptionModule } from '@angular/material/core'; import { MatOptionModule } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormField, MatLabel, MatSuffix } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIcon } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInput } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
@ -27,19 +27,16 @@ import { SaleReportService } from './sale-report.service';
styleUrls: ['./sale-report.component.css'], styleUrls: ['./sale-report.component.css'],
imports: [ imports: [
MatCardModule, MatCardModule,
MatIconButton, MatButtonModule,
MatIcon,
ReactiveFormsModule, ReactiveFormsModule,
MatFormField, MatFormFieldModule,
MatLabel, MatIconModule,
MatInput, MatInputModule,
MatDatepickerModule, MatDatepickerModule,
MatSuffix,
MatButton,
MatTableModule, MatTableModule,
CurrencyPipe,
MatOptionModule, MatOptionModule,
MatSelectModule, MatSelectModule,
CurrencyPipe,
], ],
}) })
export class SaleReportComponent implements OnInit { export class SaleReportComponent implements OnInit {