Product Ledger Done!!

This commit is contained in:
tanshu 2020-05-14 13:38:13 +05:30
parent 6ec4068ed0
commit 48d03ab832
6 changed files with 147 additions and 40 deletions

@ -1,4 +1,4 @@
import datetime from datetime import datetime, date
import uuid import uuid
from fastapi import APIRouter, Depends, Security, Request from fastapi import APIRouter, Depends, Security, Request
@ -10,6 +10,7 @@ from ...core.security import get_current_active_user as get_user
from ...db.session import SessionLocal from ...db.session import SessionLocal
from brewman.models.master import Product, CostCentre from brewman.models.master import Product, CostCentre
from brewman.models.voucher import Voucher, Journal, VoucherType, Inventory from brewman.models.voucher import Voucher, Journal, VoucherType, Inventory
import brewman.schemas.reports as schemas
from ...core.session import ( from ...core.session import (
set_period, set_period,
get_start_date, get_start_date,
@ -28,10 +29,9 @@ def get_db() -> Session:
db.close() db.close()
@router.get("") @router.get("", response_model=schemas.ProductLedger)
def show_blank( def show_blank(
request: Request, request: Request, user: UserToken = Security(get_user, scopes=["product-ledger"]),
user: UserToken = Security(get_user, scopes=["product-ledger"]),
): ):
return { return {
"startDate": get_start_date(request.session), "startDate": get_start_date(request.session),
@ -41,7 +41,7 @@ def show_blank(
} }
@router.get("/{id}") @router.get("/{id_}", response_model=schemas.ProductLedger)
def show_data( def show_data(
id_: uuid.UUID, id_: uuid.UUID,
request: Request, request: Request,
@ -53,7 +53,12 @@ def show_data(
product = db.query(Product).filter(Product.id == id_).first() product = db.query(Product).filter(Product.id == id_).first()
start_date = s if s is not None else get_start_date(request.session) start_date = s if s is not None else get_start_date(request.session)
finish_date = f if f is not None else get_finish_date(request.session) finish_date = f if f is not None else get_finish_date(request.session)
body = build_report(product.id, start_date, finish_date, db) body = build_report(
product.id,
datetime.strptime(start_date, "%d-%b-%Y"),
datetime.strptime(finish_date, "%d-%b-%Y"),
db,
)
set_period(start_date, finish_date, request.session) set_period(start_date, finish_date, request.session)
return { return {
"startDate": start_date, "startDate": start_date,
@ -63,7 +68,9 @@ def show_data(
} }
def build_report(product_id, start_date, finish_date, db): def build_report(
product_id: uuid.UUID, start_date: date, finish_date: date, db: Session
):
body = [] body = []
running_total_q, running_total_a, opening = opening_balance( running_total_q, running_total_a, opening = opening_balance(
product_id, start_date, db product_id, start_date, db
@ -80,8 +87,8 @@ def build_report(product_id, start_date, finish_date, db):
.filter(Voucher.id == Journal.voucher_id) .filter(Voucher.id == Journal.voucher_id)
.filter(Inventory.product_id == product_id) .filter(Inventory.product_id == product_id)
.filter(Journal.cost_centre_id != CostCentre.cost_centre_purchase()) .filter(Journal.cost_centre_id != CostCentre.cost_centre_purchase())
.filter(Voucher.date >= datetime.datetime.strptime(start_date, "%d-%b-%Y")) .filter(Voucher.date >= start_date)
.filter(Voucher.date <= datetime.datetime.strptime(finish_date, "%d-%b-%Y")) .filter(Voucher.date <= finish_date)
.order_by(Voucher.date) .order_by(Voucher.date)
.order_by(Voucher.last_edit_date) .order_by(Voucher.last_edit_date)
.all() .all()
@ -94,10 +101,10 @@ def build_report(product_id, start_date, finish_date, db):
if row.Voucher.type == VoucherType.by_name("Issue").id if row.Voucher.type == VoucherType.by_name("Issue").id
else row.Journal.account.name else row.Journal.account.name
) )
debit_q = row.Inventory.quantity if journal_debit == 1 else 0 debit_q = row.Inventory.quantity if journal_debit == 1 else None
debit_a = row.Inventory.amount if journal_debit == 1 else 0 debit_a = row.Inventory.amount if journal_debit == 1 else None
credit_q = row.Inventory.quantity if journal_debit != 1 else 0 credit_q = row.Inventory.quantity if journal_debit != 1 else None
credit_a = row.Inventory.amount if journal_debit != 1 else 0 credit_a = row.Inventory.amount if journal_debit != 1 else None
running_total_q += row.Inventory.quantity * journal_debit running_total_q += row.Inventory.quantity * journal_debit
running_total_a += row.Inventory.amount * journal_debit running_total_a += row.Inventory.amount * journal_debit
@ -107,6 +114,11 @@ def build_report(product_id, start_date, finish_date, db):
"id": row.Voucher.id, "id": row.Voucher.id,
"date": row.Voucher.date.strftime("%d-%b-%Y"), "date": row.Voucher.date.strftime("%d-%b-%Y"),
"name": name, "name": name,
"url": [
"/",
VoucherType.by_id(row.Voucher.type).name.replace(" ", "-").lower(),
str(row.Voucher.id),
],
"type": VoucherType.by_id(row.Voucher.type).name, "type": VoucherType.by_id(row.Voucher.type).name,
"narration": row.Voucher.narration, "narration": row.Voucher.narration,
"posted": row.Voucher.posted "posted": row.Voucher.posted
@ -123,7 +135,7 @@ def build_report(product_id, start_date, finish_date, db):
return body return body
def opening_balance(product_id, start_date, db): def opening_balance(product_id: uuid.UUID, start_date: date, db: Session):
quantity, amount = ( quantity, amount = (
db.query( db.query(
func.sum(Inventory.quantity * Journal.debit), func.sum(Inventory.quantity * Journal.debit),
@ -135,7 +147,7 @@ def opening_balance(product_id, start_date, db):
.filter(Voucher.id == Journal.voucher_id) .filter(Voucher.id == Journal.voucher_id)
.filter(Inventory.product_id == product_id) .filter(Inventory.product_id == product_id)
.filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase()) .filter(Journal.cost_centre_id == CostCentre.cost_centre_purchase())
.filter(Voucher.date < datetime.datetime.strptime(start_date, "%d-%b-%Y")) .filter(Voucher.date < start_date)
.one() .one()
) )
@ -143,8 +155,8 @@ def opening_balance(product_id, start_date, db):
debit_quantity = quantity debit_quantity = quantity
debit_amount = amount debit_amount = amount
else: else:
debit_quantity = "" debit_quantity = None
debit_amount = "" debit_amount = None
if quantity is None: if quantity is None:
quantity = 0 quantity = 0
@ -154,8 +166,10 @@ def opening_balance(product_id, start_date, db):
quantity, quantity,
amount, amount,
{ {
"date": start_date, "id": None,
"date": start_date.strftime("%d-%b-%Y"),
"name": "Opening Balance", "name": "Opening Balance",
"url": [],
"type": "Opening Balance", "type": "Opening Balance",
"narration": "", "narration": "",
"posted": True, "posted": True,

@ -40,6 +40,14 @@ class ProductGroupLink(BaseModel):
fields = {'id_': 'id'} fields = {'id_': 'id'}
class ProductLink(BaseModel):
id_: uuid.UUID = Field(...)
name: Optional[str]
class Config:
fields = {'id_': 'id'}
class ProductIn(BaseModel): class ProductIn(BaseModel):
name: str = Field(..., min_length=1) name: str = Field(..., min_length=1)
units: str units: str

@ -4,7 +4,7 @@ from typing import List, Optional
from datetime import datetime, date from datetime import datetime, date
from pydantic import BaseModel, Field, validator from pydantic import BaseModel, Field, validator
from brewman.schemas.master import AccountLink from brewman.schemas.master import AccountLink, ProductLink
def to_camel(string: str) -> str: def to_camel(string: str) -> str:
@ -270,3 +270,61 @@ class NetTransactions(BaseModel):
value, value,
"%d-%b-%Y" "%d-%b-%Y"
).date() ).date()
class ProductLedgerItem(BaseModel):
id_: Optional[uuid.UUID]
date_: date
name: str
url: List[str]
type_: str
narration: str
debit_quantity: Optional[Decimal] = Field(multiple_of=0.01)
debit_amount: Optional[Decimal] = Field(multiple_of=0.01)
credit_quantity: Optional[Decimal] = Field(multiple_of=0.01)
credit_amount: Optional[Decimal] = Field(multiple_of=0.01)
running_quantity: Decimal = Field(multiple_of=0.01)
running_amount: Decimal = Field(multiple_of=0.01)
posted: bool
@validator("date_", pre=True)
def parse_date(cls, value):
return datetime.strptime(
value,
"%d-%b-%Y"
).date()
class Config:
anystr_strip_whitespace = True
alias_generator = to_camel
json_encoders = {
date: lambda v: v.strftime("%d-%b-%Y")
}
class ProductLedger(BaseModel):
start_date: date
finish_date: date
product: Optional[ProductLink]
body: List[ProductLedgerItem]
class Config:
anystr_strip_whitespace = True
alias_generator = to_camel
json_encoders = {
date: lambda v: v.strftime("%d-%b-%Y")
}
@validator("start_date", pre=True)
def parse_start_date(cls, value):
return datetime.strptime(
value,
"%d-%b-%Y"
).date()
@validator("finish_date", pre=True)
def parse_finish_date(cls, value):
return datetime.strptime(
value,
"%d-%b-%Y"
).date()

@ -1,6 +1,9 @@
<mat-card> <mat-card>
<mat-card-title-group> <mat-card-title-group>
<mat-card-title>Product Ledger</mat-card-title> <mat-card-title>Product Ledger</mat-card-title>
<button mat-button mat-icon-button *ngIf="dataSource.data.length" (click)="exportCsv()">
<mat-icon>save_alt</mat-icon>
</button>
</mat-card-title-group> </mat-card-title-group>
<mat-card-content> <mat-card-content>
<form [formGroup]="form" fxLayout="column"> <form [formGroup]="form" fxLayout="column">
@ -22,8 +25,8 @@
<div fxLayout="row" fxLayout.lt-md="column" fxLayoutGap="20px" fxLayoutGap.lt-md="0px" <div fxLayout="row" fxLayout.lt-md="column" fxLayoutGap="20px" fxLayoutGap.lt-md="0px"
fxLayoutAlign="space-around start"> fxLayoutAlign="space-around start">
<mat-form-field fxFlex="80"> <mat-form-field fxFlex="80">
<input type="text" matInput placeholder="Product" [matAutocomplete]="auto" formControlName="product" <input type="text" matInput #productElement placeholder="Product" [matAutocomplete]="auto"
autocomplete="off"> formControlName="product" autocomplete="off">
<mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption [displayWith]="displayFn" <mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption [displayWith]="displayFn"
(optionSelected)="selected($event)"> (optionSelected)="selected($event)">
<mat-option *ngFor="let product of products | async" [value]="product">{{product.name}}</mat-option> <mat-option *ngFor="let product of products | async" [value]="product">{{product.name}}</mat-option>
@ -44,7 +47,7 @@
<!-- Particulars Column --> <!-- Particulars Column -->
<ng-container matColumnDef="particulars"> <ng-container matColumnDef="particulars">
<mat-header-cell *matHeaderCellDef mat-sort-header>Particulars</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Particulars</mat-header-cell>
<mat-cell *matCellDef="let row"><a [routerLink]="['/', row.url, row.id]">{{row.name}}</a></mat-cell> <mat-cell *matCellDef="let row"><a [routerLink]="row.url">{{row.name}}</a></mat-cell>
<mat-footer-cell *matFooterCellDef>Closing Balance</mat-footer-cell> <mat-footer-cell *matFooterCellDef>Closing Balance</mat-footer-cell>
</ng-container> </ng-container>

@ -1,4 +1,4 @@
import {Component, OnInit, ViewChild} from '@angular/core'; import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {FormBuilder, FormGroup} from '@angular/forms'; import {FormBuilder, FormGroup} from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
@ -12,13 +12,15 @@ import {ProductLedger} from './product-ledger';
import {ProductLedgerService} from './product-ledger.service'; import {ProductLedgerService} from './product-ledger.service';
import {Product} from '../core/product'; import {Product} from '../core/product';
import {ProductService} from '../product/product.service'; import {ProductService} from '../product/product.service';
import {ToCsvService} from '../shared/to-csv.service';
@Component({ @Component({
selector: 'app-product-ledger', selector: 'app-product-ledger',
templateUrl: './product-ledger.component.html', templateUrl: './product-ledger.component.html',
styleUrls: ['./product-ledger.component.css'] styleUrls: ['./product-ledger.component.css']
}) })
export class ProductLedgerComponent implements OnInit { export class ProductLedgerComponent implements OnInit, AfterViewInit {
@ViewChild('productElement', { static: true }) productElement: ElementRef;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild(MatSort, { static: true }) sort: MatSort; @ViewChild(MatSort, { static: true }) sort: MatSort;
dataSource: ProductLedgerDataSource; dataSource: ProductLedgerDataSource;
@ -45,6 +47,7 @@ export class ProductLedgerComponent implements OnInit {
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private fb: FormBuilder, private fb: FormBuilder,
private toCsv: ToCsvService,
private ser: ProductLedgerService, private ser: ProductLedgerService,
private productSer: ProductService private productSer: ProductService
) { ) {
@ -61,12 +64,7 @@ export class ProductLedgerComponent implements OnInit {
} }
ngOnInit() { ngOnInit() {
this.route.data.pipe(map( this.route.data.subscribe((data: { info: ProductLedger }) => {
(data: { info: ProductLedger }) => {
data.info.body = data.info.body.map(x => ({...x, url: x['type'].replace(/ /g, '-').toLowerCase()}));
return data;
}
)).subscribe((data: { info: ProductLedger }) => {
this.info = data.info; this.info = data.info;
this.calculateTotals(); this.calculateTotals();
this.form.setValue({ this.form.setValue({
@ -75,9 +73,16 @@ export class ProductLedgerComponent implements OnInit {
finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate() finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate()
}); });
this.dataSource = new ProductLedgerDataSource(this.paginator, this.sort, this.info.body); this.dataSource = new ProductLedgerDataSource(this.paginator, this.sort, this.info.body);
}); });
} }
ngAfterViewInit() {
setTimeout(() => {
this.productElement.nativeElement.focus();
}, 0);
}
displayFn(product?: Product): string | undefined { displayFn(product?: Product): string | undefined {
return product ? product.name : undefined; return product ? product.name : undefined;
} }
@ -111,11 +116,11 @@ export class ProductLedgerComponent implements OnInit {
} }
show() { show() {
const l = this.prepareSubmit(); const info = this.getInfo();
this.router.navigate(['product-ledger', l.product.id], { this.router.navigate(['product-ledger', info.product.id], {
queryParams: { queryParams: {
startDate: l.startDate, startDate: info.startDate,
finishDate: l.finishDate finishDate: info.finishDate
} }
}); });
} }
@ -128,14 +133,33 @@ export class ProductLedgerComponent implements OnInit {
}); });
} }
prepareSubmit(): ProductLedger { getInfo(): ProductLedger {
const formModel = this.form.value; const formModel = this.form.value;
return { return {
product: formModel.product, product: formModel.product,
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')
body: this.info.body
}; };
} }
exportCsv() {
const headers = {
Date: 'date',
Name: 'name',
Type: 'type',
Narration: 'narration',
Debit: 'debit',
Credit: 'credit',
Running: 'running',
Posted: 'posted'
};
const csvData = new Blob([this.toCsv.toCsv(headers, this.dataSource.data)], {type: 'text/csv;charset=utf-8;'});
const link = document.createElement('a');
link.href = window.URL.createObjectURL(csvData);
link.setAttribute('download', 'ledger.csv');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
} }

@ -13,12 +13,12 @@ export class ProductLedgerItem {
runningQuantity: number; runningQuantity: number;
runningAmount: number; runningAmount: number;
posted: boolean; posted: boolean;
url: string; url: string[];
} }
export class ProductLedger { export class ProductLedger {
startDate: string; startDate: string;
finishDate: string; finishDate: string;
product: Product; product?: Product;
body: ProductLedgerItem[]; body?: ProductLedgerItem[];
} }