Balance sheet
 Ledger
 Cash Flow

along with urls
Balance sheet schema does not enforce multiple_of for amounts as multiple_of borks on random figures
This commit is contained in:
tanshu
2020-05-14 11:26:28 +05:30
parent 708a60baf7
commit a4b9fb7408
15 changed files with 120 additions and 138 deletions

View File

@ -268,7 +268,7 @@ class AccountBase(Base):
__mapper_args__ = {"polymorphic_on": account_type} __mapper_args__ = {"polymorphic_on": account_type}
journals = relationship("Journal", backref="account") journals = relationship("Journal", back_populates="account")
@property @property
def __name__(self): def __name__(self):

View File

@ -80,12 +80,7 @@ class Voucher(Base):
"User", primaryjoin="User.id==Voucher.poster_id", cascade=None "User", primaryjoin="User.id==Voucher.poster_id", cascade=None
) )
journals = relationship( journals = relationship("Journal", back_populates="voucher", cascade="delete, delete-orphan", cascade_backrefs=False,)
"Journal",
backref="voucher",
cascade="delete, delete-orphan",
cascade_backrefs=False,
)
inventories = relationship( inventories = relationship(
"Inventory", "Inventory",
backref="voucher", backref="voucher",
@ -167,6 +162,9 @@ class Journal(Base):
account_id = Column("account_id", GUID(), ForeignKey("accounts.id"), nullable=False) account_id = Column("account_id", GUID(), ForeignKey("accounts.id"), nullable=False)
cost_centre_id = Column("cost_centre_id", GUID(), ForeignKey("cost_centres.id"), nullable=False) cost_centre_id = Column("cost_centre_id", GUID(), ForeignKey("cost_centres.id"), nullable=False)
voucher = relationship("Voucher", back_populates="journals")
account = relationship("AccountBase", back_populates="journals")
@hybrid_property @hybrid_property
def signed_amount(self): def signed_amount(self):
return self.debit * self.amount return self.debit * self.amount

View File

@ -1,4 +1,4 @@
import datetime from datetime import datetime, date
from fastapi import APIRouter, Depends, Security, Request from fastapi import APIRouter, Depends, Security, Request
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@ -11,6 +11,7 @@ from brewman.models.master import AccountType, AccountBase
from brewman.models.voucher import Voucher, Journal, VoucherType from brewman.models.voucher import Voucher, Journal, VoucherType
from brewman.routers.reports.closing_stock import get_closing_stock from brewman.routers.reports.closing_stock import get_closing_stock
from brewman.routers.reports.profit_loss import get_accumulated_profit from brewman.routers.reports.profit_loss import get_accumulated_profit
import brewman.schemas.reports as schemas
from ...core.session import ( from ...core.session import (
set_period, set_period,
get_start_date, get_start_date,
@ -29,60 +30,58 @@ def get_db() -> Session:
db.close() db.close()
@router.get("") @router.get("", response_model=schemas.BalanceSheet)
def report_blank( def report_blank(
request: Request, request: Request,
user: UserToken = Security(get_user, scopes=["balance-sheet"]), user: UserToken = Security(get_user, scopes=["balance-sheet"]),
): ):
return {"date": get_finish_date(request.session), "body": [], "footer": []} return {"date": get_finish_date(request.session), "body": [], "footer": None}
@router.get("/{date}") @router.get("/{date_}", response_model=schemas.BalanceSheet)
def report_data( def report_data(
date: str, date_: str,
request: Request, request: Request,
db: Session = Depends(get_db), db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["balance-sheet"]), user: UserToken = Security(get_user, scopes=["balance-sheet"]),
): ):
body, footer = build_balance_sheet(date, db) body, footer = build_balance_sheet(datetime.strptime(date_, "%d-%b-%Y"), db)
set_period(get_start_date(request.session), date, request.session) set_period(get_start_date(request.session), date_, request.session)
return {"date": date, "body": body, "footer": footer} return {"date": date_, "body": body, "footer": footer}
def build_balance_sheet(finish_date, db): def build_balance_sheet(date_: date, db: Session):
if not isinstance(finish_date, datetime.datetime): type_list = [i.id for i in AccountType.list() if i.balance_sheet]
finish_date = datetime.datetime.strptime(finish_date, "%d-%b-%Y")
type_list = [i.id for i in AccountType.list() if i.balance_sheet == True]
report = [] report = []
groups = dict() groups = dict()
# Add Net Profit / Loss # Add Net Profit / Loss
closing_stock = get_closing_stock(finish_date, db) closing_stock = round(get_closing_stock(date_, db), 2)
net_profit = get_accumulated_profit(finish_date, db) - closing_stock net_profit = round(get_accumulated_profit(date_, db), 2) - closing_stock
total_amount = net_profit total_amount = net_profit
report.append( report.append(
{ {
"name": "Net Loss" if net_profit >= 0 else "Net Profit", "name": "Net Loss" if net_profit >= 0 else "Net Profit",
"subAmount": net_profit, "subAmount": round(net_profit, 2),
"order": 79, "order": 79000,
} }
) )
capital_group = AccountType.by_id(5) capital_group = AccountType.by_id(5)
groups[capital_group.id] = { groups[capital_group.id] = {
"group": capital_group.name, "group": capital_group.name,
"amount": total_amount, "amount": round(total_amount, 2),
"order": capital_group.order, "order": capital_group.order,
} }
total_amount += closing_stock total_amount += closing_stock
report.append( report.append(
{"name": "Closing Stock", "subAmount": closing_stock, "order": 20.001} {"name": "Closing Stock", "subAmount": round(closing_stock, 2), "order": 20001}
) )
asset_group = AccountType.by_id(4) asset_group = AccountType.by_id(4)
groups[asset_group.id] = { groups[asset_group.id] = {
"group": asset_group.name, "group": asset_group.name,
"amount": closing_stock, "amount": round(closing_stock, 2),
"order": asset_group.order, "order": asset_group.order,
} }
@ -91,7 +90,7 @@ def build_balance_sheet(finish_date, db):
db.query(AccountBase, amount_sum) db.query(AccountBase, amount_sum)
.join(Journal.voucher) .join(Journal.voucher)
.join(Journal.account) .join(Journal.account)
.filter(Voucher.date <= finish_date) .filter(Voucher.date <= date_)
.filter(Voucher.type != VoucherType.by_name("Issue").id) .filter(Voucher.type != VoucherType.by_name("Issue").id)
.filter(AccountBase.type.in_(type_list)) .filter(AccountBase.type.in_(type_list))
.group_by(AccountBase) .group_by(AccountBase)
@ -101,27 +100,25 @@ def build_balance_sheet(finish_date, db):
) )
counter = 0 counter = 0
sss = 0
for account, amount in query: for account, amount in query:
# Add Items # Add Items
account_type = AccountType.by_id(account.type) account_type = AccountType.by_id(account.type)
sss += amount
total_amount += amount total_amount += amount
if amount != 0: if amount != 0:
counter += 0.001 counter += 1
report.append( report.append(
{ {
"name": account.name, "name": account.name,
"subAmount": amount, "subAmount": round(amount, 2),
"order": account_type.order + counter, "order": account_type.order + counter,
} }
) )
if account_type.id in groups: if account_type.id in groups:
groups[account_type.id]["amount"] += amount groups[account_type.id]["amount"] = round(groups[account_type.id]["amount"] + amount, 2)
else: else:
groups[account_type.id] = { groups[account_type.id] = {
"group": account_type.name, "group": account_type.name,
"amount": amount, "amount": round(amount, 2),
"order": account_type.order, "order": account_type.order,
} }
@ -129,5 +126,5 @@ def build_balance_sheet(finish_date, db):
for item in groups.values(): for item in groups.values():
report.append(item) report.append(item)
footer = {"name": "Total", "amount": total_amount, "order": 100} footer = {"name": "Total", "amount": round(total_amount, 2), "order": 100000}
return sorted(report, key=lambda d: d["order"]), footer return sorted(report, key=lambda d: d["order"]), footer

View File

@ -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 AccountBase, AccountType from brewman.models.master import AccountBase, AccountType
from brewman.models.voucher import Voucher, Journal, VoucherType from brewman.models.voucher import Voucher, Journal, VoucherType
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,7 +29,7 @@ def get_db() -> Session:
db.close() db.close()
@router.get("") @router.get("", response_model=schemas.CashFlow)
def report_blank( def report_blank(
request: Request, request: Request,
user: UserToken = Security(get_user, scopes=["cash-flow"]), user: UserToken = Security(get_user, scopes=["cash-flow"]),
@ -36,8 +37,8 @@ def report_blank(
return { return {
"startDate": get_start_date(request.session), "startDate": get_start_date(request.session),
"finishDate": get_finish_date(request.session), "finishDate": get_finish_date(request.session),
"body": [], "body": {"operating": [], "investing": [], "financing": [], "details": []},
"footer": {}, "footer": None,
} }
@ -112,7 +113,7 @@ def build_report(start_date, finish_date, db):
cf[lt.cash_flow_classification.lower()].append( cf[lt.cash_flow_classification.lower()].append(
{ {
"name": lt.name, "name": lt.name,
"url": "", # request.route_url("cash_flow_id", id=str(lt.id), _query={"startDate": start_date, "finishDate": finish_date},), "url": ['/', 'cash-flow', str(lt.id)],
"amount": amount * -1, "amount": amount * -1,
} }
) )
@ -186,7 +187,7 @@ def build_report_id(account_type, start_date, finish_date, db):
details.append( details.append(
{ {
"name": account.name, "name": account.name,
"url": "", # request.route_url("ledger_id", id=account.id, _query={"startDate": start_date, "finishDate": finish_date},), "url": ['/', 'ledger', str(account.id)],
"amount": amount * -1, "amount": amount * -1,
} }
) )

View File

@ -104,6 +104,7 @@ def build_report(account_id, start_date, finish_date, db):
"id": voucher.id, "id": voucher.id,
"date": voucher.date.strftime("%d-%b-%Y"), "date": voucher.date.strftime("%d-%b-%Y"),
"name": name, "name": name,
"url": ['/', VoucherType.by_id(voucher.type).name.replace(" ", "-").lower(), str(voucher.id)],
"type": VoucherType.by_id(voucher.type).name, "type": VoucherType.by_id(voucher.type).name,
"narration": voucher.narration, "narration": voucher.narration,
"debit": debit, "debit": debit,
@ -135,6 +136,7 @@ def opening_balance(account_id, start_date, db):
"id": None, "id": None,
"name": "Opening Balance", "name": "Opening Balance",
"type": "Opening Balance", "type": "Opening Balance",
"url": [],
"narration": "", "narration": "",
"debit": debit, "debit": debit,
"credit": credit, "credit": credit,

View File

@ -61,7 +61,6 @@ def build_report(db: Session):
"date": voucher.date.strftime("%d-%b-%Y"), "date": voucher.date.strftime("%d-%b-%Y"),
"voucherType": VoucherType.by_id(voucher.type).name, "voucherType": VoucherType.by_id(voucher.type).name,
"narration": voucher.narration, "narration": voucher.narration,
"isPosted": voucher.posted,
"debitName": name_debit, "debitName": name_debit,
"debitAmount": debit, "debitAmount": debit,
"creditName": name_credit, "creditName": name_credit,

View File

@ -16,7 +16,7 @@ class LedgerItem(BaseModel):
id_: Optional[uuid.UUID] id_: Optional[uuid.UUID]
date_: date date_: date
name: str name: str
url: str url: List[str]
type_: str type_: str
narration: str narration: str
debit: Decimal = Field(multiple_of=0.01) debit: Decimal = Field(multiple_of=0.01)
@ -33,6 +33,9 @@ class LedgerItem(BaseModel):
class Config: class Config:
anystr_strip_whitespace = True anystr_strip_whitespace = True
alias_generator = to_camel alias_generator = to_camel
json_encoders = {
date: lambda v: v.strftime("%d-%b-%Y")
}
class Ledger(BaseModel): class Ledger(BaseModel):
@ -63,100 +66,84 @@ class Ledger(BaseModel):
).date() ).date()
class ClientIn(BaseModel): class BalanceSheetItem(BaseModel):
name: str name: Optional[str]
enabled: bool group: Optional[str]
otp: Optional[int] amount: Optional[Decimal]
sub_amount: Optional[Decimal]
order: int
class Client(ClientIn):
id_: uuid.UUID
code: int
creation_date: datetime
class LoginHistory(BaseModel):
id_: uuid.UUID
user_id: uuid.UUID
client_id: uuid.UUID
date: datetime
class Config: class Config:
fields = {"id_": "id"}
anystr_strip_whitespace = True anystr_strip_whitespace = True
alias_generator = to_camel alias_generator = to_camel
class PermissionItem(BaseModel): class BalanceSheet(BaseModel):
id_: uuid.UUID date_: date
name: str body: List[BalanceSheetItem]
enabled: bool footer: Optional[BalanceSheetItem]
class Config: class Config:
fields = {"id_": "id"}
class RoleIn(BaseModel):
name: str
permissions: List[PermissionItem]
class Config:
fields = {"id_": "id"}
anystr_strip_whitespace = True anystr_strip_whitespace = True
alias_generator = to_camel
json_encoders = {
date: lambda v: v.strftime("%d-%b-%Y")
}
@validator("date_", pre=True)
def parse_date(cls, value):
return datetime.strptime(
value,
"%d-%b-%Y"
).date()
class Role(RoleIn): class CashFlowItem(BaseModel):
id_: uuid.UUID
class RoleList(BaseModel):
id_: uuid.UUID
name: str name: str
permissions: List[str] url: Optional[List[str]]
amount: Decimal = Field(multiple_of=0.01)
class Config: class Config:
fields = {"id_": "id"}
anystr_strip_whitespace = True
class RoleItem(BaseModel):
id_: uuid.UUID
name: str
enabled: bool
class Config:
fields = {"id_": "id"}
class UserIn(BaseModel):
name: str
password: str
locked_out: bool
roles: List[RoleItem]
class Config:
fields = {"id_": "id"}
anystr_strip_whitespace = True anystr_strip_whitespace = True
alias_generator = to_camel alias_generator = to_camel
class User(UserIn): class CashFlowBody(BaseModel):
id_: uuid.UUID operating: List[CashFlowItem]
investing: List[CashFlowItem]
financing: List[CashFlowItem]
class UserList(BaseModel): details: List[CashFlowItem]
id_: uuid.UUID
name: str
roles: List[str]
class Config: class Config:
fields = {"id_": "id"}
anystr_strip_whitespace = True anystr_strip_whitespace = True
alias_generator = to_camel
class CashFlow(BaseModel):
start_date: date
finish_date: date
body: CashFlowBody
footer: Optional[List[CashFlowItem]]
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()
class UserToken(BaseModel):
id_: uuid.UUID
name: str
locked_out: bool = None
password: str
permissions: List[str]

View File

@ -21,21 +21,21 @@
<ng-container matColumnDef="group"> <ng-container matColumnDef="group">
<mat-header-cell *matHeaderCellDef mat-sort-header>Group</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Group</mat-header-cell>
<mat-cell *matCellDef="let row">{{row.group}}</mat-cell> <mat-cell *matCellDef="let row">{{row.group}}</mat-cell>
<mat-footer-cell *matFooterCellDef>{{info.footer.group}}</mat-footer-cell> <mat-footer-cell *matFooterCellDef>{{info.footer?.group}}</mat-footer-cell>
</ng-container> </ng-container>
<!-- Name Column --> <!-- Name Column -->
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
<mat-cell *matCellDef="let row">{{row.name}}</mat-cell> <mat-cell *matCellDef="let row">{{row.name}}</mat-cell>
<mat-footer-cell *matFooterCellDef>{{info.footer.name}}</mat-footer-cell> <mat-footer-cell *matFooterCellDef>{{info.footer?.name}}</mat-footer-cell>
</ng-container> </ng-container>
<!-- SubAmount Column --> <!-- SubAmount Column -->
<ng-container matColumnDef="subAmount"> <ng-container matColumnDef="subAmount">
<mat-header-cell *matHeaderCellDef mat-sort-header class="right">Amount</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header class="right">Amount</mat-header-cell>
<mat-cell *matCellDef="let row" class="right">{{row.subAmount | currency:'INR' | clear}}</mat-cell> <mat-cell *matCellDef="let row" class="right">{{row.subAmount | currency:'INR' | clear}}</mat-cell>
<mat-footer-cell *matFooterCellDef class="right">{{info.footer.subAmount | currency:'INR' | clear}} <mat-footer-cell *matFooterCellDef class="right">{{info.footer?.subAmount | currency:'INR' | clear}}
</mat-footer-cell> </mat-footer-cell>
</ng-container> </ng-container>
@ -43,7 +43,7 @@
<ng-container matColumnDef="total"> <ng-container matColumnDef="total">
<mat-header-cell *matHeaderCellDef mat-sort-header class="right">Total</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header class="right">Total</mat-header-cell>
<mat-cell *matCellDef="let row" class="right">{{row.amount | currency:'INR' | clear}}</mat-cell> <mat-cell *matCellDef="let row" class="right">{{row.amount | currency:'INR' | clear}}</mat-cell>
<mat-footer-cell *matFooterCellDef class="right">{{info.footer.amount | currency:'INR' | clear}} <mat-footer-cell *matFooterCellDef class="right">{{info.footer?.amount | currency:'INR' | clear}}
</mat-footer-cell> </mat-footer-cell>
</ng-container> </ng-container>

View File

@ -43,15 +43,12 @@ export class BalanceSheetComponent implements OnInit {
} }
show() { show() {
const info = this.getInfo(); const date = this.getDate();
this.router.navigate(['balance-sheet', info.date]); this.router.navigate(['balance-sheet', date]);
} }
getInfo(): BalanceSheet { getDate(): string {
const formModel = this.form.value; const formModel = this.form.value;
return moment(formModel.date).format('DD-MMM-YYYY');
return {
date: moment(formModel.date).format('DD-MMM-YYYY')
};
} }
} }

View File

@ -1,5 +1,12 @@
export class BalanceSheetItem {
name?: string;
group?: string;
amount?: number;
subAmount?: number;
order: number;
}
export class BalanceSheet { export class BalanceSheet {
date: string; date: string;
body?: any[]; body: BalanceSheetItem[];
footer?: any[]; footer?: BalanceSheetItem;
} }

View File

@ -26,7 +26,7 @@
<!-- Name Column --> <!-- Name Column -->
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
<mat-cell *matCellDef="let row"><a [href]="row.url">{{row.name}} </a></mat-cell> <mat-cell *matCellDef="let row"><a [routerLink]="row.url" [queryParams]="{startDate: info.startDate, finishDate: info.finishDate}">{{row.name}} </a></mat-cell>
</ng-container> </ng-container>
<!-- Amount Column --> <!-- Amount Column -->

View File

@ -1,6 +1,6 @@
export class CashFlowItem { export class CashFlowItem {
name: string; name: string;
url: string; url: [];
amount: number; amount: number;
constructor(name: string) { constructor(name: string) {

View File

@ -47,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>

View File

@ -56,12 +56,7 @@ export class LedgerComponent implements OnInit, AfterViewInit {
} }
ngOnInit() { ngOnInit() {
this.route.data.pipe(map( this.route.data.subscribe((data: { info: Ledger }) => {
(data: { info: Ledger }) => {
data.info.body = data.info.body.map(x => ({...x, url: x['type'].replace(/ /g, '-').toLowerCase()}));
return data;
}
)).subscribe((data: { info: Ledger }) => {
this.info = data.info; this.info = data.info;
this.calculateTotals(); this.calculateTotals();
this.form.setValue({ this.form.setValue({

View File

@ -3,7 +3,6 @@ export class Unposted {
date: string; date: string;
voucherType: string; voucherType: string;
narration: string; narration: string;
isPosted: boolean;
debitName: string; debitName: string;
debitAmount: number; debitAmount: number;
creditName: string; creditName: string;