Feature: Changed the unposted report to entries report with paging, sorting, etc.
This commit is contained in:
@ -57,7 +57,7 @@ from .routers.reports import (
|
||||
reconcile,
|
||||
stock_movement,
|
||||
trial_balance,
|
||||
unposted,
|
||||
entries,
|
||||
)
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ app.include_router(raw_material_cost.router, prefix="/api/raw-material-cost", ta
|
||||
app.include_router(reconcile.router, prefix="/api/reconcile", tags=["reports"])
|
||||
app.include_router(stock_movement.router, prefix="/api/stock-movement", tags=["reports"])
|
||||
app.include_router(trial_balance.router, prefix="/api/trial-balance", tags=["reports"])
|
||||
app.include_router(unposted.router, prefix="/api/unposted", tags=["reports"])
|
||||
app.include_router(entries.router, prefix="/api/entries", tags=["reports"])
|
||||
|
||||
app.include_router(issue_grid.router, prefix="/api/issue-grid", tags=["vouchers"])
|
||||
app.include_router(batch.router, prefix="/api/batch", tags=["vouchers"])
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
from datetime import date, datetime
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def report_start_date(s: str = None) -> Optional[date]:
|
||||
return None if s is None else datetime.strptime(s, "%d-%b-%Y").date()
|
||||
|
||||
|
||||
def report_finish_date(f: str = None) -> Optional[date]:
|
||||
return None if f is None else datetime.strptime(f, "%d-%b-%Y").date()
|
||||
|
||||
117
brewman/brewman/routers/reports/entries.py
Normal file
117
brewman/brewman/routers/reports/entries.py
Normal file
@ -0,0 +1,117 @@
|
||||
from datetime import date
|
||||
from typing import List, Optional
|
||||
|
||||
import brewman.schemas.entries as schemas
|
||||
|
||||
from fastapi import APIRouter, Depends, Security
|
||||
from sqlalchemy import desc, or_, select
|
||||
from sqlalchemy.orm import Session, contains_eager, joinedload
|
||||
from sqlalchemy.sql.functions import count
|
||||
|
||||
from ...core.security import get_current_active_user as get_user
|
||||
from ...db.session import SessionFuture
|
||||
from ...models.journal import Journal
|
||||
from ...models.voucher import Voucher
|
||||
from ...models.voucher_type import VoucherType
|
||||
from ...schemas.user import UserToken
|
||||
from ...schemas.user_link import UserLink
|
||||
from . import report_finish_date, report_start_date
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def build_report(
|
||||
start_date: Optional[date],
|
||||
finish_date: date,
|
||||
posted: Optional[bool],
|
||||
issue: bool,
|
||||
page_size: int,
|
||||
page_index: int,
|
||||
active_sort: str,
|
||||
sort_direction: str,
|
||||
db: Session,
|
||||
) -> (int, List[Voucher]):
|
||||
query = (
|
||||
select(Voucher)
|
||||
.join(Voucher.user)
|
||||
.options(
|
||||
joinedload(Voucher.user, innerjoin=True),
|
||||
joinedload(Voucher.journals, innerjoin=True).joinedload(Journal.account, innerjoin=True),
|
||||
contains_eager(Voucher.user),
|
||||
contains_eager(Voucher.journals, Journal.account),
|
||||
)
|
||||
)
|
||||
sq = select(Voucher.id)
|
||||
counts = select(count(Voucher.id))
|
||||
if start_date is not None:
|
||||
sq = sq.where(or_(Voucher.creation_date >= start_date, Voucher.last_edit_date >= start_date))
|
||||
counts = counts.where(or_(Voucher.creation_date >= start_date, Voucher.last_edit_date >= start_date))
|
||||
if finish_date is not None:
|
||||
sq = sq.where(or_(Voucher.creation_date <= finish_date, Voucher.last_edit_date <= finish_date))
|
||||
counts = counts.where(or_(Voucher.creation_date <= finish_date, Voucher.last_edit_date <= finish_date))
|
||||
if posted is not None:
|
||||
sq = sq.where(Voucher.posted == posted)
|
||||
counts = counts.where(Voucher.posted == posted)
|
||||
if issue is False:
|
||||
sq = sq.where(Voucher.type != VoucherType.by_name("Issue").id)
|
||||
counts = counts.where(Voucher.type != VoucherType.by_name("Issue").id)
|
||||
if active_sort == "date":
|
||||
if sort_direction == "desc":
|
||||
sq = sq.order_by(desc(Voucher.date), desc(Voucher.last_edit_date))
|
||||
query = query.order_by(desc(Voucher.date), desc(Voucher.last_edit_date))
|
||||
else:
|
||||
sq = sq.order_by(Voucher.date, Voucher.last_edit_date)
|
||||
query = query.order_by(Voucher.date, Voucher.last_edit_date)
|
||||
if active_sort == "user":
|
||||
if sort_direction == "desc":
|
||||
sq = sq.order_by(desc(Voucher.last_edit_date))
|
||||
query = query.order_by(desc(Voucher.last_edit_date))
|
||||
else:
|
||||
sq = sq.order_by(Voucher.last_edit_date)
|
||||
query = query.order_by(Voucher.last_edit_date)
|
||||
if page_size:
|
||||
sq = sq.limit(page_size)
|
||||
if page_index:
|
||||
sq = sq.offset(page_size * page_index)
|
||||
return db.execute(counts).scalar_one(), db.execute(query.where(Voucher.id.in_(sq))).unique().scalars().all()
|
||||
|
||||
|
||||
@router.get("", response_model=schemas.Report)
|
||||
def report_data(
|
||||
start_date: Optional[date] = Depends(report_start_date),
|
||||
finish_date: Optional[date] = Depends(report_finish_date),
|
||||
p: Optional[bool] = None, # Posted
|
||||
ps: Optional[int] = 50, # Page Size
|
||||
pi: Optional[int] = 0, # Page Index
|
||||
a: Optional[str] = "date", # Active Sort
|
||||
d: Optional[str] = "desc", # Sort Direction
|
||||
i: Optional[bool] = False, # Show issue vouchers
|
||||
user: UserToken = Security(get_user, scopes=["ledger"]),
|
||||
) -> schemas.Report:
|
||||
with SessionFuture() as db:
|
||||
counts, report = build_report(start_date, finish_date, p, i, ps, pi, a, d, db)
|
||||
return schemas.Report(
|
||||
counts=counts,
|
||||
report=[
|
||||
schemas.Entries(
|
||||
id=voucher.id,
|
||||
date=voucher.date,
|
||||
url=[
|
||||
"/",
|
||||
VoucherType.by_id(voucher.type).name.replace(" ", "-").lower(),
|
||||
str(voucher.id),
|
||||
],
|
||||
type=VoucherType.by_id(voucher.type).name,
|
||||
posted=voucher.posted,
|
||||
narration=voucher.narration,
|
||||
debitNames=[x.account.name for x in voucher.journals if x.debit == 1],
|
||||
creditNames=[x.account.name for x in voucher.journals if x.debit != 1],
|
||||
amount=sum(x.amount for x in voucher.journals if x.debit != 1),
|
||||
creationDate=voucher.creation_date,
|
||||
lastEditDate=voucher.last_edit_date,
|
||||
user=UserLink(id=voucher.user.id, name=voucher.user.name),
|
||||
)
|
||||
for voucher in report
|
||||
],
|
||||
)
|
||||
@ -1,77 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
import brewman.schemas.unposted as schemas
|
||||
|
||||
from fastapi import APIRouter, Security
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ...core.security import get_current_active_user as get_user
|
||||
from ...db.session import SessionFuture
|
||||
from ...models.journal import Journal
|
||||
from ...models.voucher import Voucher
|
||||
from ...models.voucher_type import VoucherType
|
||||
from ...schemas.user import UserToken
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", response_model=List[schemas.Unposted])
|
||||
def report_data(
|
||||
user: UserToken = Security(get_user, scopes=["post-vouchers"]),
|
||||
) -> List[schemas.Unposted]:
|
||||
with SessionFuture() as db:
|
||||
return build_report(db)
|
||||
|
||||
|
||||
def build_report(db: Session) -> List[schemas.Unposted]:
|
||||
body = []
|
||||
|
||||
query = (
|
||||
db.execute(
|
||||
select(Voucher)
|
||||
.join(Voucher.journals)
|
||||
.join(Journal.account)
|
||||
.where(Voucher.posted == False, Voucher.type != VoucherType.by_name("Issue").id) # noqa: E712
|
||||
.order_by(Voucher.date, Voucher.last_edit_date)
|
||||
)
|
||||
.unique()
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
|
||||
for voucher in query:
|
||||
debit = 0
|
||||
credit = 0
|
||||
name_debit = ""
|
||||
name_credit = ""
|
||||
for journal in voucher.journals:
|
||||
if journal.debit == 1:
|
||||
debit += journal.amount
|
||||
name_debit += "{0} / ".format(journal.account.name)
|
||||
else:
|
||||
credit += journal.amount
|
||||
name_credit += "{0} / ".format(journal.account.name)
|
||||
name_debit = name_debit[:-3]
|
||||
name_credit = name_credit[:-3]
|
||||
|
||||
body.append(
|
||||
schemas.Unposted(
|
||||
id=voucher.id,
|
||||
date=voucher.date.strftime("%d-%b-%Y"),
|
||||
url=[
|
||||
"/",
|
||||
VoucherType.by_id(voucher.type).name.replace(" ", "-").lower(),
|
||||
str(voucher.id),
|
||||
],
|
||||
type=VoucherType.by_id(voucher.type).name,
|
||||
narration=voucher.narration,
|
||||
debitName=name_debit,
|
||||
debitAmount=debit,
|
||||
creditName=name_credit,
|
||||
creditAmount=credit,
|
||||
)
|
||||
)
|
||||
|
||||
return body
|
||||
64
brewman/brewman/schemas/entries.py
Normal file
64
brewman/brewman/schemas/entries.py
Normal file
@ -0,0 +1,64 @@
|
||||
import uuid
|
||||
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
from . import to_camel
|
||||
from .user_link import UserLink
|
||||
|
||||
|
||||
class Entries(BaseModel):
|
||||
id_: uuid.UUID
|
||||
date_: date
|
||||
url: List[str]
|
||||
type_: str
|
||||
posted: bool
|
||||
narration: str
|
||||
debit_names: List[str]
|
||||
credit_names: List[str]
|
||||
amount: Decimal
|
||||
creation_date: datetime
|
||||
last_edit_date: datetime
|
||||
user: UserLink
|
||||
|
||||
class Config:
|
||||
anystr_strip_whitespace = True
|
||||
alias_generator = to_camel
|
||||
json_encoders = {
|
||||
date: lambda v: v.strftime("%d-%b-%Y"),
|
||||
datetime: lambda v: v.strftime("%d-%b-%Y %H:%M"),
|
||||
}
|
||||
|
||||
@validator("date_", pre=True)
|
||||
def parse_date(cls, value):
|
||||
if isinstance(value, date):
|
||||
return value
|
||||
return datetime.strptime(value, "%d-%b-%Y").date()
|
||||
|
||||
@validator("creation_date", pre=True)
|
||||
def parse_creation_date(cls, value):
|
||||
if isinstance(value, datetime):
|
||||
return value
|
||||
return datetime.strptime(value, "%d-%b-%Y %H:%M")
|
||||
|
||||
@validator("last_edit_date", pre=True)
|
||||
def parse_last_edit_date(cls, value):
|
||||
if isinstance(value, datetime):
|
||||
return value
|
||||
return datetime.strptime(value, "%d-%b-%Y %H:%M")
|
||||
|
||||
|
||||
class Report(BaseModel):
|
||||
counts: int
|
||||
report: List[Entries]
|
||||
|
||||
class Config:
|
||||
anystr_strip_whitespace = True
|
||||
alias_generator = to_camel
|
||||
json_encoders = {
|
||||
date: lambda v: v.strftime("%d-%b-%Y"),
|
||||
datetime: lambda v: v.strftime("%d-%b-%Y %H:%M"),
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import uuid
|
||||
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
from . import to_camel
|
||||
|
||||
|
||||
class Unposted(BaseModel):
|
||||
id_: uuid.UUID
|
||||
date_: date
|
||||
url: List[str]
|
||||
type_: str
|
||||
narration: str
|
||||
debit_name: str
|
||||
debit_amount: Decimal
|
||||
credit_name: str
|
||||
credit_amount: Decimal
|
||||
|
||||
@validator("date_", pre=True)
|
||||
def parse_date(cls, value):
|
||||
if isinstance(value, date):
|
||||
return 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")}
|
||||
@ -165,8 +165,8 @@ const appRoutes: Routes = [
|
||||
import('./trial-balance/trial-balance.module').then((mod) => mod.TrialBalanceModule),
|
||||
},
|
||||
{
|
||||
path: 'unposted',
|
||||
loadChildren: () => import('./unposted/unposted.module').then((mod) => mod.UnpostedModule),
|
||||
path: 'entries',
|
||||
loadChildren: () => import('./entries/entries.module').then((mod) => mod.EntriesModule),
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
<mat-datepicker-toggle matSuffix [for]="finishDate"></mat-datepicker-toggle>
|
||||
<mat-datepicker #finishDate></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" (click)="show()" fxFlex="20c">Show</button>
|
||||
<button mat-raised-button color="primary" (click)="show()" fxFlex="20">Show</button>
|
||||
</div>
|
||||
</form>
|
||||
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
|
||||
|
||||
@ -19,7 +19,9 @@
|
||||
<a mat-menu-item routerLink="/profit-loss">Profit and Loss</a>
|
||||
<a mat-menu-item routerLink="/balance-sheet">Balance Sheet</a>
|
||||
<a mat-menu-item routerLink="/net-transactions">Net Transactions</a>
|
||||
<a mat-menu-item routerLink="/unposted">UnPosted Entries</a>
|
||||
<a mat-menu-item routerLink="/entries" [queryParams]="{ p: 'false', a: 'date', d: 'desc' }"
|
||||
>All Entries</a
|
||||
>
|
||||
</mat-menu>
|
||||
<button mat-button [matMenuTriggerFor]="reportMenu">Reports</button>
|
||||
|
||||
|
||||
45
overlord/src/app/entries/entries-datasource.ts
Normal file
45
overlord/src/app/entries/entries-datasource.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { DataSource } from '@angular/cdk/collections';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { SortDirection } from '@angular/material/sort/sort-direction';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
|
||||
import { Entries } from './entries';
|
||||
|
||||
export class EntriesDatasource extends DataSource<Entries> {
|
||||
constructor(
|
||||
public data: Entries[],
|
||||
public length: number,
|
||||
private paginator?: MatPaginator,
|
||||
private sort?: MatSort,
|
||||
private pageSize?: number,
|
||||
private pageIndex?: number,
|
||||
private activeSort?: string,
|
||||
private sortDirection?: SortDirection,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
connect(): Observable<Entries[]> {
|
||||
if (this.paginator) {
|
||||
this.paginator.length = this.length;
|
||||
if (this.pageSize !== undefined) {
|
||||
this.paginator.pageSize = this.pageSize;
|
||||
}
|
||||
if (this.pageIndex !== undefined) {
|
||||
this.paginator.pageIndex = this.pageIndex;
|
||||
}
|
||||
}
|
||||
if (this.sort) {
|
||||
if (this.activeSort !== undefined) {
|
||||
this.sort.active = this.activeSort;
|
||||
}
|
||||
if (this.sortDirection !== undefined) {
|
||||
this.sort.direction = this.sortDirection;
|
||||
}
|
||||
}
|
||||
return observableOf(this.data);
|
||||
}
|
||||
|
||||
disconnect() {}
|
||||
}
|
||||
@ -1,17 +1,17 @@
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UnpostedResolver } from './unposted-resolver.service';
|
||||
import { EntriesResolver } from './entries-resolver.service';
|
||||
|
||||
describe('UnpostedResolver', () => {
|
||||
describe('EntriesResolver', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientModule],
|
||||
providers: [UnpostedResolver],
|
||||
providers: [EntriesResolver],
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([UnpostedResolver], (service: UnpostedResolver) => {
|
||||
it('should be created', inject([EntriesResolver], (service: EntriesResolver) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
||||
38
overlord/src/app/entries/entries-resolver.service.ts
Normal file
38
overlord/src/app/entries/entries-resolver.service.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
|
||||
import { EntriesService } from './entries.service';
|
||||
import { Report } from './report';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class EntriesResolver implements Resolve<Report> {
|
||||
constructor(private ser: EntriesService) {}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot): Observable<Report> {
|
||||
const start_date = route.queryParamMap.get('s');
|
||||
const finish_date = route.queryParamMap.get('f');
|
||||
const posted =
|
||||
route.queryParamMap.get('p') === null ? null : route.queryParamMap.get('p') === 'true';
|
||||
const page_size =
|
||||
route.queryParamMap.get('ps') === null ? 50 : +(route.queryParamMap.get('ps') as string);
|
||||
const page_index =
|
||||
route.queryParamMap.get('pi') === null ? 0 : +(route.queryParamMap.get('pi') as string);
|
||||
const sort = route.queryParamMap.get('a') ?? 'date';
|
||||
const direction = route.queryParamMap.get('d') ?? 'desc';
|
||||
const issue_vouchers =
|
||||
route.queryParamMap.get('i') === null ? false : route.queryParamMap.get('i') === 'true';
|
||||
return this.ser.list(
|
||||
start_date,
|
||||
finish_date,
|
||||
posted,
|
||||
page_size,
|
||||
page_index,
|
||||
sort,
|
||||
direction,
|
||||
issue_vouchers,
|
||||
);
|
||||
}
|
||||
}
|
||||
13
overlord/src/app/entries/entries-routing.module.spec.ts
Normal file
13
overlord/src/app/entries/entries-routing.module.spec.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { EntriesRoutingModule } from './entries-routing.module';
|
||||
|
||||
describe('EntriesRoutingModule', () => {
|
||||
let entriesRoutingModule: EntriesRoutingModule;
|
||||
|
||||
beforeEach(() => {
|
||||
entriesRoutingModule = new EntriesRoutingModule();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(entriesRoutingModule).toBeTruthy();
|
||||
});
|
||||
});
|
||||
30
overlord/src/app/entries/entries-routing.module.ts
Normal file
30
overlord/src/app/entries/entries-routing.module.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { AuthGuard } from '../auth/auth-guard.service';
|
||||
|
||||
import { EntriesResolver } from './entries-resolver.service';
|
||||
import { EntriesComponent } from './entries.component';
|
||||
|
||||
const entriesRoutes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: EntriesComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: {
|
||||
permission: 'Ledger',
|
||||
},
|
||||
resolve: {
|
||||
info: EntriesResolver,
|
||||
},
|
||||
runGuardsAndResolvers: 'always',
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterModule.forChild(entriesRoutes)],
|
||||
exports: [RouterModule],
|
||||
providers: [EntriesResolver],
|
||||
})
|
||||
export class EntriesRoutingModule {}
|
||||
19
overlord/src/app/entries/entries.component.css
Normal file
19
overlord/src/app/entries/entries.component.css
Normal file
@ -0,0 +1,19 @@
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.my-margin {
|
||||
margin: 0 12px;
|
||||
}
|
||||
.selected {
|
||||
background: #fff3cd;
|
||||
}
|
||||
|
||||
.unposted {
|
||||
background: #f8d7da;
|
||||
}
|
||||
|
||||
.mat-column-date,
|
||||
.mat-column-voucherType {
|
||||
max-width: 100px;
|
||||
}
|
||||
138
overlord/src/app/entries/entries.component.html
Normal file
138
overlord/src/app/entries/entries.component.html
Normal file
@ -0,0 +1,138 @@
|
||||
<mat-card>
|
||||
<mat-card-title-group>
|
||||
<mat-card-title>Entries</mat-card-title>
|
||||
</mat-card-title-group>
|
||||
<form [formGroup]="form" fxLayout="column">
|
||||
<div
|
||||
fxLayout="row"
|
||||
fxLayout.lt-md="column"
|
||||
fxLayoutGap="20px"
|
||||
fxLayoutGap.lt-md="0px"
|
||||
fxLayoutAlign="space-around start"
|
||||
>
|
||||
<mat-form-field fxFlex="40%">
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="startDate"
|
||||
placeholder="Create / Last Edit Date From"
|
||||
formControlName="startDate"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="startDate"></mat-datepicker-toggle>
|
||||
<mat-datepicker #startDate></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="40%">
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="finishDate"
|
||||
placeholder="Create / Last Edit Date Till"
|
||||
formControlName="finishDate"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="finishDate"></mat-datepicker-toggle>
|
||||
<mat-datepicker #finishDate></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button (click)="refresh()" fxFlex="20%">
|
||||
<mat-icon>refresh</mat-icon>Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
fxLayout="row"
|
||||
fxLayout.lt-md="column"
|
||||
fxLayoutGap="20px"
|
||||
fxLayoutGap.lt-md="0px"
|
||||
fxLayoutAlign="space-around start"
|
||||
>
|
||||
<mat-radio-group fxFlex="40%" formControlName="posted" (change)="togglePosted($event)">
|
||||
<mat-radio-button class="my-margin" value="true"> Posted </mat-radio-button>
|
||||
<mat-radio-button class="my-margin" value="false"> Not Posted </mat-radio-button>
|
||||
<mat-radio-button class="my-margin" value="null"> All </mat-radio-button>
|
||||
</mat-radio-group>
|
||||
<mat-checkbox
|
||||
fxFlex="40%"
|
||||
formControlName="issue"
|
||||
class="my-margin"
|
||||
[checked]="issue"
|
||||
(change)="toggleIssue()"
|
||||
>
|
||||
Include Issue Entries
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</form>
|
||||
<mat-card-content>
|
||||
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
|
||||
<!-- Date Column -->
|
||||
<ng-container matColumnDef="date">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Date</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"
|
||||
><a [routerLink]="row.url">{{ row.date }}</a></mat-cell
|
||||
>
|
||||
</ng-container>
|
||||
|
||||
<!-- Type Column -->
|
||||
<ng-container matColumnDef="voucherType">
|
||||
<mat-header-cell *matHeaderCellDef>Type</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{ row.type }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Narration Column -->
|
||||
<ng-container matColumnDef="narration">
|
||||
<mat-header-cell *matHeaderCellDef>Narration</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{ row.narration }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- DebitName Column -->
|
||||
<ng-container matColumnDef="debitNames">
|
||||
<mat-header-cell *matHeaderCellDef>Debit</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<ul>
|
||||
<li *ngFor="let item of row.debitNames">{{ item }}</li>
|
||||
</ul>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- CreditName Column -->
|
||||
<ng-container matColumnDef="creditNames">
|
||||
<mat-header-cell *matHeaderCellDef>Credit</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<ul>
|
||||
<li *ngFor="let item of row.creditNames">{{ item }}</li>
|
||||
</ul>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Amount Column -->
|
||||
<ng-container matColumnDef="amount">
|
||||
<mat-header-cell *matHeaderCellDef class="right">Amount</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" class="right">{{ row.amount | currency: 'INR' }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- User Column -->
|
||||
<ng-container matColumnDef="user">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="right">User</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<ul>
|
||||
<li>{{ row.creationDate | localTime }}</li>
|
||||
<li>{{ row.lastEditDate | localTime }}</li>
|
||||
<li>{{ row.user.name }}</li>
|
||||
</ul>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
[class.unposted]="!row.posted && posted !== false && row.type !== 'Issue'"
|
||||
></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator
|
||||
#paginator
|
||||
[length]="dataSource.data.length"
|
||||
[pageIndex]="0"
|
||||
[pageSize]="50"
|
||||
[pageSizeOptions]="[25, 50, 100, 250, 300]"
|
||||
>
|
||||
</mat-paginator>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@ -2,23 +2,23 @@ import { HttpClientModule } from '@angular/common/http';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { UnpostedComponent } from './unposted.component';
|
||||
import { EntriesComponent } from './entries.component';
|
||||
|
||||
describe('UnpostedComponent', () => {
|
||||
let component: UnpostedComponent;
|
||||
let fixture: ComponentFixture<UnpostedComponent>;
|
||||
describe('EntriesComponent', () => {
|
||||
let component: EntriesComponent;
|
||||
let fixture: ComponentFixture<EntriesComponent>;
|
||||
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientModule, RouterTestingModule],
|
||||
declarations: [UnpostedComponent],
|
||||
declarations: [EntriesComponent],
|
||||
}).compileComponents();
|
||||
}),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(UnpostedComponent);
|
||||
fixture = TestBed.createComponent(EntriesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
143
overlord/src/app/entries/entries.component.ts
Normal file
143
overlord/src/app/entries/entries.component.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatRadioChange } from '@angular/material/radio';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { SortDirection } from '@angular/material/sort/sort-direction';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import * as moment from 'moment';
|
||||
|
||||
import { EntriesDatasource } from './entries-datasource';
|
||||
import { Report } from './report';
|
||||
|
||||
@Component({
|
||||
selector: 'app-entries',
|
||||
templateUrl: './entries.component.html',
|
||||
styleUrls: ['./entries.component.css'],
|
||||
})
|
||||
export class EntriesComponent implements OnInit {
|
||||
@ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator;
|
||||
@ViewChild(MatSort, { static: true }) sort?: MatSort;
|
||||
info: Report = new Report();
|
||||
dataSource: EntriesDatasource = new EntriesDatasource(this.info.report, 0);
|
||||
form: FormGroup;
|
||||
displayedColumns = [
|
||||
'date',
|
||||
'voucherType',
|
||||
'narration',
|
||||
'debitNames',
|
||||
'creditNames',
|
||||
'amount',
|
||||
'user',
|
||||
];
|
||||
|
||||
posted: boolean | null = null;
|
||||
issue: boolean = false;
|
||||
constructor(private route: ActivatedRoute, private router: Router, private fb: FormBuilder) {
|
||||
this.form = this.fb.group({
|
||||
startDate: '',
|
||||
finishDate: '',
|
||||
posted: '',
|
||||
issue: '',
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.data.subscribe((value) => {
|
||||
const data = value as { info: Report };
|
||||
this.info = data.info;
|
||||
const queryMap = this.route.snapshot.queryParamMap;
|
||||
const startDateO = queryMap.get('s');
|
||||
const finishDateO = queryMap.get('f');
|
||||
const startDate = startDateO !== null ? moment(startDateO, 'DD-MMM-YYYY').toDate() : '';
|
||||
const finishDate = finishDateO !== null ? moment(finishDateO, 'DD-MMM-YYYY').toDate() : '';
|
||||
const postedO = queryMap.get('p');
|
||||
this.posted = postedO !== null ? postedO === 'true' : null;
|
||||
const pageSizeO = queryMap.get('ps');
|
||||
const pageSize = pageSizeO !== null ? +pageSizeO : 50;
|
||||
const pageIndexO = queryMap.get('pi');
|
||||
const pageIndex = pageIndexO !== null ? +pageIndexO : 0;
|
||||
const activeSortO = queryMap.get('a');
|
||||
const activeSort = activeSortO !== null ? (activeSortO as string) : 'date';
|
||||
const sortDirectionO = queryMap.get('d');
|
||||
const sortDirection = sortDirectionO !== null ? (sortDirectionO as SortDirection) : 'desc';
|
||||
const issueO = queryMap.get('i');
|
||||
this.issue = issueO !== null ? issueO === 'true' : false;
|
||||
this.form.setValue({
|
||||
startDate: startDate,
|
||||
finishDate: finishDate,
|
||||
posted: postedO ?? 'null',
|
||||
issue: this.issue,
|
||||
});
|
||||
if (this.posted === null || this.posted) {
|
||||
(this.form.get('issue') as FormControl).enable();
|
||||
} else {
|
||||
(this.form.get('issue') as FormControl).disable();
|
||||
}
|
||||
this.dataSource = new EntriesDatasource(
|
||||
this.info.report,
|
||||
this.info.counts,
|
||||
this.paginator,
|
||||
this.sort,
|
||||
pageSize,
|
||||
pageIndex,
|
||||
activeSort,
|
||||
sortDirection,
|
||||
);
|
||||
});
|
||||
this.sort?.sortChange.subscribe((x) =>
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { a: this.sort?.active ?? 'date', d: this.sort?.direction },
|
||||
queryParamsHandling: 'merge',
|
||||
}),
|
||||
);
|
||||
this.paginator?.page.subscribe((x) =>
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { ps: this.paginator?.pageSize, pi: this.paginator?.pageIndex },
|
||||
queryParamsHandling: 'merge',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
const formModel = this.form.value;
|
||||
const startDate = formModel.startDate
|
||||
? moment(formModel.startDate).format('DD-MMM-YYYY')
|
||||
: null;
|
||||
const finishDate = formModel.finishDate
|
||||
? moment(formModel.finishDate).format('DD-MMM-YYYY')
|
||||
: null;
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { s: startDate, f: finishDate },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
togglePosted($event: MatRadioChange) {
|
||||
if ($event.value === 'true') {
|
||||
this.posted = true;
|
||||
} else if ($event.value === 'false') {
|
||||
this.posted = false;
|
||||
this.issue = false;
|
||||
} else {
|
||||
this.posted = null;
|
||||
}
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { p: this.posted, i: this.issue || null },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
toggleIssue() {
|
||||
this.issue = !this.issue;
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { i: this.issue || null },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
}
|
||||
13
overlord/src/app/entries/entries.module.spec.ts
Normal file
13
overlord/src/app/entries/entries.module.spec.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { EntriesModule } from './entries.module';
|
||||
|
||||
describe('EntriesModule', () => {
|
||||
let entriesModule: EntriesModule;
|
||||
|
||||
beforeEach(() => {
|
||||
entriesModule = new EntriesModule();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(entriesModule).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -2,22 +2,32 @@ import { A11yModule } from '@angular/cdk/a11y';
|
||||
import { CdkTableModule } from '@angular/cdk/table';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatNativeDateModule } from '@angular/material/core';
|
||||
import {
|
||||
DateAdapter,
|
||||
MAT_DATE_FORMATS,
|
||||
MAT_DATE_LOCALE,
|
||||
MatNativeDateModule,
|
||||
} from '@angular/material/core';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatRadioModule } from '@angular/material/radio';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { UnpostedRoutingModule } from './unposted-routing.module';
|
||||
import { UnpostedComponent } from './unposted.component';
|
||||
import { EntriesRoutingModule } from './entries-routing.module';
|
||||
import { EntriesComponent } from './entries.component';
|
||||
|
||||
export const MY_FORMATS = {
|
||||
parse: {
|
||||
@ -44,12 +54,20 @@ export const MY_FORMATS = {
|
||||
MatNativeDateModule,
|
||||
MatPaginatorModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatRadioModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
SharedModule,
|
||||
UnpostedRoutingModule,
|
||||
EntriesRoutingModule,
|
||||
MatExpansionModule,
|
||||
MatDatepickerModule,
|
||||
ReactiveFormsModule,
|
||||
FlexLayoutModule,
|
||||
],
|
||||
declarations: [EntriesComponent],
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
|
||||
{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
|
||||
],
|
||||
declarations: [UnpostedComponent],
|
||||
})
|
||||
export class UnpostedModule {}
|
||||
export class EntriesModule {}
|
||||
@ -1,17 +1,17 @@
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UnpostedService } from './unposted.service';
|
||||
import { EntriesService } from './entries.service';
|
||||
|
||||
describe('UnpostedService', () => {
|
||||
describe('EntriesService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientModule],
|
||||
providers: [UnpostedService],
|
||||
providers: [EntriesService],
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([UnpostedService], (service: UnpostedService) => {
|
||||
it('should be created', inject([EntriesService], (service: EntriesService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
||||
44
overlord/src/app/entries/entries.service.ts
Normal file
44
overlord/src/app/entries/entries.service.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
import { ErrorLoggerService } from '../core/error-logger.service';
|
||||
|
||||
import { Report } from './report';
|
||||
|
||||
const url = '/api/entries';
|
||||
const serviceName = 'EntriesService';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class EntriesService {
|
||||
constructor(private http: HttpClient, private log: ErrorLoggerService) {}
|
||||
|
||||
list(
|
||||
start_date: string | null,
|
||||
finish_date: string | null,
|
||||
posted: boolean | null,
|
||||
page_size: number,
|
||||
page_index: number,
|
||||
sort: string,
|
||||
direction: string,
|
||||
issue: boolean,
|
||||
): Observable<Report> {
|
||||
let params = new HttpParams();
|
||||
if (start_date !== null) params = params.set('s', start_date);
|
||||
if (finish_date !== null) params = params.set('f', finish_date);
|
||||
if (posted !== null) params = params.set('p', posted.toString());
|
||||
params = params
|
||||
.set('ps', page_size)
|
||||
.set('pi', page_index)
|
||||
.set('a', sort)
|
||||
.set('d', direction)
|
||||
.set('i', issue.toString());
|
||||
|
||||
return this.http
|
||||
.get<Report>(url, { params })
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<Report>;
|
||||
}
|
||||
}
|
||||
32
overlord/src/app/entries/entries.ts
Normal file
32
overlord/src/app/entries/entries.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { User } from '../core/user';
|
||||
|
||||
export class Entries {
|
||||
id: string;
|
||||
date: string;
|
||||
url: string[];
|
||||
type: string;
|
||||
posted: boolean;
|
||||
narration: string;
|
||||
debitNames: string[];
|
||||
creditNames: string[];
|
||||
amount: number;
|
||||
creationDate: string;
|
||||
lastEditDate: string;
|
||||
user: User;
|
||||
|
||||
public constructor(init?: Partial<Entries>) {
|
||||
this.id = '';
|
||||
this.date = '';
|
||||
this.url = [];
|
||||
this.type = '';
|
||||
this.posted = false;
|
||||
this.narration = '';
|
||||
this.debitNames = [];
|
||||
this.creditNames = [];
|
||||
this.amount = 0;
|
||||
this.creationDate = '';
|
||||
this.lastEditDate = '';
|
||||
this.user = new User();
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
||||
12
overlord/src/app/entries/report.ts
Normal file
12
overlord/src/app/entries/report.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Entries } from './entries';
|
||||
|
||||
export class Report {
|
||||
counts: number;
|
||||
report: Entries[];
|
||||
|
||||
public constructor(init?: Partial<Report>) {
|
||||
this.counts = 0;
|
||||
this.report = [];
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
||||
@ -364,20 +364,21 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
productSelected(event: MatAutocompleteSelectedEvent): void {
|
||||
const product: Product = event.option.value;
|
||||
const addRowForm: FormControl = this.form.get('addRow') as FormControl;
|
||||
this.product = product;
|
||||
((this.form.get('addRow') as FormControl).get('price') as FormControl).setValue(product.price);
|
||||
(addRowForm.get('price') as FormControl).setValue(product.price);
|
||||
if (product.isRateContracted) {
|
||||
((this.form.get('addRow') as FormControl).get('price') as FormControl).disable();
|
||||
((this.form.get('addRow') as FormControl).get('tax') as FormControl).disable();
|
||||
((this.form.get('addRow') as FormControl).get('discount') as FormControl).disable();
|
||||
((this.form.get('addRow') as FormControl).get('tax') as FormControl).setValue('RC');
|
||||
((this.form.get('addRow') as FormControl).get('discount') as FormControl).setValue('RC');
|
||||
(addRowForm.get('price') as FormControl).disable();
|
||||
(addRowForm.get('tax') as FormControl).disable();
|
||||
(addRowForm.get('discount') as FormControl).disable();
|
||||
(addRowForm.get('tax') as FormControl).setValue('RC');
|
||||
(addRowForm.get('discount') as FormControl).setValue('RC');
|
||||
} else {
|
||||
((this.form.get('addRow') as FormControl).get('price') as FormControl).enable();
|
||||
((this.form.get('addRow') as FormControl).get('tax') as FormControl).enable();
|
||||
((this.form.get('addRow') as FormControl).get('discount') as FormControl).enable();
|
||||
((this.form.get('addRow') as FormControl).get('tax') as FormControl).setValue('');
|
||||
((this.form.get('addRow') as FormControl).get('discount') as FormControl).setValue('');
|
||||
(addRowForm.get('price') as FormControl).enable();
|
||||
(addRowForm.get('tax') as FormControl).enable();
|
||||
(addRowForm.get('discount') as FormControl).enable();
|
||||
(addRowForm.get('tax') as FormControl).setValue('');
|
||||
(addRowForm.get('discount') as FormControl).setValue('');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
import { DataSource } from '@angular/cdk/collections';
|
||||
import { EventEmitter } from '@angular/core';
|
||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||
import { MatSort, Sort } from '@angular/material/sort';
|
||||
import { merge, Observable, of as observableOf } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { Unposted } from './unposted';
|
||||
|
||||
/** Simple sort comparator for example ID/Name columns (for client-side sorting). */
|
||||
const compare = (a: string | number, b: string | number, isAsc: boolean) =>
|
||||
(a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||
|
||||
export class UnpostedDataSource extends DataSource<Unposted> {
|
||||
constructor(public data: Unposted[], private paginator?: MatPaginator, private sort?: MatSort) {
|
||||
super();
|
||||
}
|
||||
|
||||
connect(): Observable<Unposted[]> {
|
||||
const dataMutations: (Observable<Unposted[]> | EventEmitter<PageEvent> | EventEmitter<Sort>)[] =
|
||||
[observableOf(this.data)];
|
||||
if (this.paginator) {
|
||||
dataMutations.push((this.paginator as MatPaginator).page);
|
||||
}
|
||||
if (this.sort) {
|
||||
dataMutations.push((this.sort as MatSort).sortChange);
|
||||
}
|
||||
|
||||
// Set the paginators length
|
||||
if (this.paginator) {
|
||||
this.paginator.length = this.data.length;
|
||||
}
|
||||
|
||||
return merge(...dataMutations).pipe(
|
||||
map(() => this.getPagedData(this.getSortedData([...this.data]))),
|
||||
);
|
||||
}
|
||||
|
||||
disconnect() {}
|
||||
|
||||
private getPagedData(data: Unposted[]) {
|
||||
if (this.paginator === undefined) {
|
||||
return data;
|
||||
}
|
||||
const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
|
||||
return data.splice(startIndex, this.paginator.pageSize);
|
||||
}
|
||||
|
||||
private getSortedData(data: Unposted[]) {
|
||||
if (this.sort === undefined) {
|
||||
return data;
|
||||
}
|
||||
if (!this.sort.active || this.sort.direction === '') {
|
||||
return data;
|
||||
}
|
||||
const sort = this.sort as MatSort;
|
||||
|
||||
return data.sort((a: Unposted, b: Unposted) => {
|
||||
const isAsc = sort.direction === 'asc';
|
||||
switch (sort.active) {
|
||||
case 'date':
|
||||
return compare(a.date, b.date, isAsc);
|
||||
case 'type':
|
||||
return compare(a.type, b.type, isAsc);
|
||||
case 'debitAmount':
|
||||
return compare(+a.debitAmount, +b.debitAmount, isAsc);
|
||||
case 'creditAmount':
|
||||
return compare(+a.creditAmount, +b.creditAmount, isAsc);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Resolve } from '@angular/router';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
|
||||
import { Unposted } from './unposted';
|
||||
import { UnpostedService } from './unposted.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class UnpostedResolver implements Resolve<Unposted[]> {
|
||||
constructor(private ser: UnpostedService) {}
|
||||
|
||||
resolve(): Observable<Unposted[]> {
|
||||
return this.ser.list();
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
import { UnpostedRoutingModule } from './unposted-routing.module';
|
||||
|
||||
describe('UnpostedRoutingModule', () => {
|
||||
let unpostedRoutingModule: UnpostedRoutingModule;
|
||||
|
||||
beforeEach(() => {
|
||||
unpostedRoutingModule = new UnpostedRoutingModule();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(unpostedRoutingModule).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -1,29 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { AuthGuard } from '../auth/auth-guard.service';
|
||||
|
||||
import { UnpostedResolver } from './unposted-resolver.service';
|
||||
import { UnpostedComponent } from './unposted.component';
|
||||
|
||||
const unpostedRoutes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: UnpostedComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: {
|
||||
permission: 'post-vouchers',
|
||||
},
|
||||
resolve: {
|
||||
info: UnpostedResolver,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterModule.forChild(unpostedRoutes)],
|
||||
exports: [RouterModule],
|
||||
providers: [UnpostedResolver],
|
||||
})
|
||||
export class UnpostedRoutingModule {}
|
||||
@ -1,7 +0,0 @@
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.my-margin {
|
||||
margin: 0 12px;
|
||||
}
|
||||
@ -1,108 +0,0 @@
|
||||
<mat-card>
|
||||
<mat-card-title-group>
|
||||
<mat-card-title>Unposted</mat-card-title>
|
||||
<a mat-button (click)="refresh()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
Refresh
|
||||
</a>
|
||||
</mat-card-title-group>
|
||||
<mat-expansion-panel (opened)="panelOpenState = true" (closed)="panelOpenState = false">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> Self aware panel </mat-panel-title>
|
||||
<mat-panel-description>
|
||||
Currently I am {{ panelOpenState ? 'open' : 'closed' }}
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<mat-checkbox
|
||||
class="my-margin"
|
||||
[checked]="!!unposted"
|
||||
[indeterminate]="unposted === undefined"
|
||||
(change)="toggleUnposted()"
|
||||
>
|
||||
Unposted
|
||||
</mat-checkbox>
|
||||
<p></p>
|
||||
<mat-checkbox
|
||||
class="my-margin"
|
||||
[checked]="!!unposted"
|
||||
[indeterminate]="unposted === undefined"
|
||||
(change)="toggleUnposted()"
|
||||
>
|
||||
All userscv
|
||||
</mat-checkbox>
|
||||
<mat-checkbox
|
||||
class="my-margin"
|
||||
[checked]="!!unposted"
|
||||
[indeterminate]="unposted === undefined"
|
||||
(change)="toggleUnposted()"
|
||||
>
|
||||
Unposted
|
||||
</mat-checkbox>
|
||||
</mat-expansion-panel>
|
||||
<mat-card-content>
|
||||
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
|
||||
<!-- Date Column -->
|
||||
<ng-container matColumnDef="date">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Date</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"
|
||||
><a [routerLink]="row.url">{{ row.date }}</a></mat-cell
|
||||
>
|
||||
</ng-container>
|
||||
|
||||
<!-- Type Column -->
|
||||
<ng-container matColumnDef="voucherType">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Type</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{ row.type }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Narration Column -->
|
||||
<ng-container matColumnDef="narration">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Narration</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{ row.narration }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- DebitName Column -->
|
||||
<ng-container matColumnDef="debitName">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Debit</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{ row.debitName }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- DebitAmount Column -->
|
||||
<ng-container matColumnDef="debitAmount">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="right">Amount</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" class="right">{{
|
||||
row.debitAmount | currency: 'INR' | accounting
|
||||
}}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- CreditName Column -->
|
||||
<ng-container matColumnDef="creditName">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Credit</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{ row.creditName }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- CreditAmount Column -->
|
||||
<ng-container matColumnDef="creditAmount">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="right">Amount</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" class="right">{{
|
||||
row.creditAmount | currency: 'INR' | accounting
|
||||
}}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
[class.posted]="row.isPosted"
|
||||
></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator
|
||||
#paginator
|
||||
[length]="dataSource.data.length"
|
||||
[pageIndex]="0"
|
||||
[pageSize]="50"
|
||||
[pageSizeOptions]="[25, 50, 100, 250, 300]"
|
||||
>
|
||||
</mat-paginator>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@ -1,63 +0,0 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { Unposted } from './unposted';
|
||||
import { UnpostedDataSource } from './unposted-datasource';
|
||||
import { UnpostedService } from './unposted.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-unposted',
|
||||
templateUrl: './unposted.component.html',
|
||||
styleUrls: ['./unposted.component.css'],
|
||||
})
|
||||
export class UnpostedComponent implements OnInit {
|
||||
@ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator;
|
||||
@ViewChild(MatSort, { static: true }) sort?: MatSort;
|
||||
info: Unposted[] = [];
|
||||
dataSource: UnpostedDataSource = new UnpostedDataSource(this.info);
|
||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||
displayedColumns = [
|
||||
'date',
|
||||
'voucherType',
|
||||
'narration',
|
||||
'debitName',
|
||||
'debitAmount',
|
||||
'creditName',
|
||||
'creditAmount',
|
||||
];
|
||||
|
||||
panelOpenState = false;
|
||||
unposted: boolean | undefined = undefined;
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private ser: UnpostedService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.data.subscribe((value) => {
|
||||
const data = value as { info: Unposted[] };
|
||||
|
||||
this.info = data.info;
|
||||
});
|
||||
this.dataSource = new UnpostedDataSource(this.info, this.paginator, this.sort);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.ser.list().subscribe((info: Unposted[]) => {
|
||||
this.info = info;
|
||||
});
|
||||
}
|
||||
|
||||
toggleUnposted() {
|
||||
if (this.unposted === undefined) {
|
||||
this.unposted = false;
|
||||
} else if (!this.unposted) {
|
||||
this.unposted = true;
|
||||
} else {
|
||||
this.unposted = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
import { UnpostedModule } from './unposted.module';
|
||||
|
||||
describe('UnpostedModule', () => {
|
||||
let unpostedModule: UnpostedModule;
|
||||
|
||||
beforeEach(() => {
|
||||
unpostedModule = new UnpostedModule();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(unpostedModule).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -1,24 +0,0 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
import { ErrorLoggerService } from '../core/error-logger.service';
|
||||
|
||||
import { Unposted } from './unposted';
|
||||
|
||||
const url = '/api/unposted';
|
||||
const serviceName = 'UnpostedService';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class UnpostedService {
|
||||
constructor(private http: HttpClient, private log: ErrorLoggerService) {}
|
||||
|
||||
list(): Observable<Unposted[]> {
|
||||
return this.http
|
||||
.get<Unposted[]>(url)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<Unposted[]>;
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
export class Unposted {
|
||||
id: string;
|
||||
date: string;
|
||||
url: string[];
|
||||
type: string;
|
||||
narration: string;
|
||||
debitName: string;
|
||||
debitAmount: number;
|
||||
creditName: string;
|
||||
creditAmount: number;
|
||||
|
||||
public constructor(init?: Partial<Unposted>) {
|
||||
this.id = '';
|
||||
this.date = '';
|
||||
this.url = [];
|
||||
this.type = '';
|
||||
this.narration = '';
|
||||
this.debitName = '';
|
||||
this.debitAmount = 0;
|
||||
this.creditName = '';
|
||||
this.creditAmount = 0;
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user