Feature: Mozimo Product Register
This commit is contained in:
parent
bd0ee4facd
commit
3b46ac97bc
brewman
overlord/src/app
app.routes.ts
core/nav-bar
mozimo-product-register
mozimo-product-register-datasource.tsmozimo-product-register-item.tsmozimo-product-register.component.cssmozimo-product-register.component.htmlmozimo-product-register.component.spec.tsmozimo-product-register.component.tsmozimo-product-register.resolver.spec.tsmozimo-product-register.resolver.tsmozimo-product-register.routes.tsmozimo-product-register.service.spec.tsmozimo-product-register.service.tsmozimo-product-register.ts
@ -0,0 +1,46 @@
|
||||
"""mozimo product ledger
|
||||
|
||||
Revision ID: 82e5c8d18382
|
||||
Revises: fab52fb911e4
|
||||
Create Date: 2024-08-16 06:57:58.336202
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "82e5c8d18382"
|
||||
down_revision = "fab52fb911e4"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"mozimo_stock_register",
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("date", sa.Date(), nullable=False),
|
||||
sa.Column("sku_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("received", sa.Numeric(precision=15, scale=2), nullable=False),
|
||||
sa.Column("sale", sa.Numeric(precision=15, scale=2), nullable=False),
|
||||
sa.Column("nc", sa.Numeric(precision=15, scale=2), nullable=False),
|
||||
sa.Column("display", sa.Numeric(precision=15, scale=2), nullable=True),
|
||||
sa.Column("ageing", sa.Numeric(precision=15, scale=2), nullable=True),
|
||||
sa.Column("last_edit_date", sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["sku_id"], ["stock_keeping_units.id"], name=op.f("fk_mozimo_stock_register_sku_id_stock_keeping_units")
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_mozimo_stock_register")),
|
||||
sa.UniqueConstraint("date", "sku_id", name=op.f("uq_mozimo_stock_register_date")),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("mozimo_stock_register")
|
||||
# ### end Alembic commands ###
|
@ -17,6 +17,7 @@ from ..models.incentive import Incentive # noqa: F401
|
||||
from ..models.inventory import Inventory # noqa: F401
|
||||
from ..models.journal import Journal # noqa: F401
|
||||
from ..models.login_history import LoginHistory # noqa: F401
|
||||
from ..models.mozimo_stock_register import MozimoStockRegister # noqa: F401
|
||||
from ..models.period import Period # noqa: F401
|
||||
from ..models.permission import Permission # noqa: F401
|
||||
from ..models.price import Price # noqa: F401
|
||||
|
@ -53,6 +53,7 @@ from .routers.reports import (
|
||||
daybook,
|
||||
entries,
|
||||
ledger,
|
||||
mozimo_product_register,
|
||||
net_transactions,
|
||||
non_contract_purchase,
|
||||
product_ledger,
|
||||
@ -114,6 +115,8 @@ app.include_router(trial_balance.router, prefix="/api/trial-balance", tags=["rep
|
||||
app.include_router(entries.router, prefix="/api/entries", tags=["reports"])
|
||||
app.include_router(batch_integrity.router, prefix="/api/batch-integrity", tags=["reports"])
|
||||
app.include_router(non_contract_purchase.router, prefix="/api/non-contract-purchase", tags=["reports"])
|
||||
app.include_router(mozimo_product_register.router, prefix="/api/mozimo-product-register", tags=["mozimo"])
|
||||
|
||||
|
||||
app.include_router(issue_grid.router, prefix="/api/issue-grid", tags=["vouchers"])
|
||||
app.include_router(batch.router, prefix="/api/batch", tags=["vouchers"])
|
||||
|
63
brewman/brewman/models/mozimo_stock_register.py
Normal file
63
brewman/brewman/models/mozimo_stock_register.py
Normal file
@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
from datetime import UTC, date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import Date, DateTime, ForeignKey, Numeric, UniqueConstraint, Uuid
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from ..db.base_class import reg
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .stock_keeping_unit import StockKeepingUnit
|
||||
|
||||
|
||||
@reg.mapped_as_dataclass(unsafe_hash=True)
|
||||
class MozimoStockRegister:
|
||||
__tablename__ = "mozimo_stock_register"
|
||||
__table_args__ = (UniqueConstraint("date", "sku_id"),)
|
||||
id_: Mapped[uuid.UUID] = mapped_column("id", Uuid, primary_key=True, insert_default=uuid.uuid4)
|
||||
date_: Mapped[date] = mapped_column("date", Date, nullable=False)
|
||||
sku_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("stock_keeping_units.id"), nullable=False)
|
||||
received: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=2), nullable=False)
|
||||
sale: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=2), nullable=False)
|
||||
nc: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=2), nullable=False)
|
||||
# The physical stock in the display area
|
||||
display: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=2), nullable=True)
|
||||
# The physical stock in the ageing room
|
||||
ageing: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=2), nullable=True)
|
||||
last_edit_date: Mapped[datetime] = mapped_column(DateTime(), nullable=False)
|
||||
|
||||
sku: Mapped[StockKeepingUnit] = relationship("StockKeepingUnit")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
date_: datetime.date,
|
||||
received: Decimal,
|
||||
sale: Decimal,
|
||||
nc: Decimal,
|
||||
display: Decimal,
|
||||
ageing: Decimal,
|
||||
sku_id: uuid.UUID | None = None,
|
||||
sku: StockKeepingUnit | None = None,
|
||||
id_: uuid.UUID | None = None,
|
||||
last_edit_date: datetime | None = None,
|
||||
):
|
||||
self.date_ = date_
|
||||
self.received = received
|
||||
self.sale = sale
|
||||
self.nc = nc
|
||||
self.display = display
|
||||
self.ageing = ageing
|
||||
if sku_id is not None:
|
||||
self.sku_id = sku_id
|
||||
if sku is not None:
|
||||
self.sku = sku
|
||||
self.sku_id = sku.id
|
||||
if id_ is not None:
|
||||
self.id_ = id_
|
||||
self.last_edit_date = last_edit_date or datetime.now(UTC).replace(tzinfo=None)
|
203
brewman/brewman/routers/reports/mozimo_product_register.py
Normal file
203
brewman/brewman/routers/reports/mozimo_product_register.py
Normal file
@ -0,0 +1,203 @@
|
||||
import uuid
|
||||
|
||||
from datetime import UTC, date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request, Security, status
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.sql.expression import select
|
||||
|
||||
import brewman.schemas.mozimo_product_register as schemas
|
||||
|
||||
from ...core.security import get_current_active_user as get_user
|
||||
from ...core.session import get_finish_date, get_start_date, set_period
|
||||
from ...db.session import SessionFuture
|
||||
from ...models.mozimo_stock_register import MozimoStockRegister
|
||||
from ...models.stock_keeping_unit import StockKeepingUnit
|
||||
from ...schemas.user import UserToken
|
||||
from ..attendance import date_range
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", response_model=schemas.MozimoProductRegister)
|
||||
def show_blank(
|
||||
request: Request,
|
||||
user: UserToken = Security(get_user, scopes=["ledger"]),
|
||||
) -> schemas.MozimoProductRegister:
|
||||
return schemas.MozimoProductRegister(
|
||||
start_date=get_start_date(request.session),
|
||||
finish_date=get_finish_date(request.session),
|
||||
product=None,
|
||||
body=[],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{id_}", response_model=schemas.MozimoProductRegister)
|
||||
def show_data(
|
||||
id_: uuid.UUID,
|
||||
request: Request,
|
||||
s: str | None = None,
|
||||
f: str | None = None,
|
||||
user: UserToken = Security(get_user, scopes=["ledger"]),
|
||||
) -> schemas.MozimoProductRegister:
|
||||
with SessionFuture() as db:
|
||||
sku = db.execute(select(StockKeepingUnit).where(StockKeepingUnit.id == id_)).scalar_one()
|
||||
start_date = datetime.strptime(s or get_start_date(request.session), "%d-%b-%Y").date()
|
||||
finish_date = datetime.strptime(f or get_finish_date(request.session), "%d-%b-%Y").date()
|
||||
body = build_report(sku.id, start_date, finish_date, db)
|
||||
set_period(start_date, finish_date, request.session)
|
||||
return schemas.MozimoProductRegister(
|
||||
start_date=start_date,
|
||||
finish_date=finish_date,
|
||||
product=schemas.ProductLink(id_=sku.id, name=f"{sku.product.name} ({sku.units})"),
|
||||
body=body,
|
||||
)
|
||||
|
||||
|
||||
def build_report(
|
||||
product_id: uuid.UUID, start_date: date, finish_date: date, db: Session
|
||||
) -> list[schemas.MozimoProductRegisterItem]:
|
||||
body = []
|
||||
ob = opening_balance(product_id, start_date, db)
|
||||
|
||||
for date_ in date_range(start_date, finish_date, inclusive=True):
|
||||
item = (
|
||||
db.execute(
|
||||
select(MozimoStockRegister).where(
|
||||
MozimoStockRegister.sku_id == product_id,
|
||||
MozimoStockRegister.date_ == date_,
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.one_or_none()
|
||||
)
|
||||
|
||||
body.append(
|
||||
schemas.MozimoProductRegisterItem(
|
||||
id_=None if item is None else item.id_,
|
||||
date_=date_,
|
||||
opening=ob,
|
||||
received=Decimal(0) if item is None else item.received,
|
||||
sale=Decimal(0) if item is None else item.sale,
|
||||
nc=Decimal(0) if item is None else item.nc,
|
||||
display=None if item is None else item.display,
|
||||
ageing=None if item is None else item.ageing,
|
||||
last_edit_date=None if item is None else item.last_edit_date,
|
||||
)
|
||||
)
|
||||
if item is not None:
|
||||
if item.ageing is not None or item.display is not None:
|
||||
closing = (item.ageing or 0) + (item.display or 0)
|
||||
else:
|
||||
closing = ob + item.received - item.sale - item.nc
|
||||
ob = closing # Setting the cb as ob for next iteration
|
||||
return body
|
||||
|
||||
|
||||
def opening_balance(product_id: uuid.UUID, start_date: date, db: Session) -> Decimal:
|
||||
opening = db.execute(
|
||||
select(MozimoStockRegister.display + MozimoStockRegister.ageing)
|
||||
.order_by(desc(MozimoStockRegister.date_))
|
||||
.where(
|
||||
MozimoStockRegister.sku_id == product_id,
|
||||
MozimoStockRegister.date_ < start_date,
|
||||
)
|
||||
).scalar()
|
||||
return Decimal(0) if opening is None else opening
|
||||
|
||||
|
||||
@router.post("", response_model=schemas.MozimoProductRegister)
|
||||
def save_route(
|
||||
request: Request,
|
||||
data: schemas.MozimoProductRegister,
|
||||
user: UserToken = Security(get_user, scopes=["ledger"]),
|
||||
) -> schemas.MozimoProductRegister:
|
||||
try:
|
||||
if any(i for i in data.body if i.received < 0):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Received cannot be less than 0.",
|
||||
)
|
||||
if any(i for i in data.body if i.sale < 0):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Sale cannot be less than 0.",
|
||||
)
|
||||
if any(i for i in data.body if i.nc < 0):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="No Charge cannot be less than 0.",
|
||||
)
|
||||
if any(i for i in data.body if i.display is not None and i.display < 0):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Quantity on display can be Blank, but cannot be less than 0.",
|
||||
)
|
||||
if any(i for i in data.body if i.ageing is not None and i.ageing < 0):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Quantity in ageing room can be Blank, but cannot be less than 0.",
|
||||
)
|
||||
with SessionFuture() as db:
|
||||
now = datetime.now(UTC).replace(tzinfo=None)
|
||||
ob = opening_balance(data.product.id_, data.start_date, db)
|
||||
for item in data.body:
|
||||
if item.ageing is not None or item.display is not None:
|
||||
closing = (item.ageing or 0) + (item.display or 0)
|
||||
else:
|
||||
closing = ob + item.received - item.sale - item.nc
|
||||
ob = closing # Setting the cb as ob for next iteration
|
||||
if closing < 0:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Closing Stock cannot be less than 0.",
|
||||
)
|
||||
|
||||
for item in data.body:
|
||||
old = (
|
||||
db.execute(
|
||||
select(MozimoStockRegister).where(
|
||||
MozimoStockRegister.sku_id == data.product.id_,
|
||||
MozimoStockRegister.date_ == item.date_,
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.one_or_none()
|
||||
)
|
||||
if old is not None:
|
||||
if (
|
||||
old.received != item.received
|
||||
or old.sale != item.sale
|
||||
or old.nc != item.nc
|
||||
or old.display != item.display
|
||||
or old.ageing != item.ageing
|
||||
):
|
||||
old.received = item.received
|
||||
old.sale = item.sale
|
||||
old.nc = item.nc
|
||||
old.display = item.display
|
||||
old.ageing = item.ageing
|
||||
old.last_edit_date = now
|
||||
else:
|
||||
entry = MozimoStockRegister(
|
||||
item.date_, item.received, item.sale, item.nc, item.display, item.ageing, data.product.id_
|
||||
)
|
||||
db.add(entry)
|
||||
|
||||
body = build_report(data.product.id_, data.start_date, data.finish_date, db)
|
||||
db.commit()
|
||||
return schemas.MozimoProductRegister(
|
||||
start_date=data.start_date,
|
||||
finish_date=data.finish_date,
|
||||
product=data.product,
|
||||
body=body,
|
||||
)
|
||||
except SQLAlchemyError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=str(e),
|
||||
)
|
85
brewman/brewman/schemas/mozimo_product_register.py
Normal file
85
brewman/brewman/schemas/mozimo_product_register.py
Normal file
@ -0,0 +1,85 @@
|
||||
import uuid
|
||||
|
||||
from datetime import date, datetime
|
||||
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
FieldSerializationInfo,
|
||||
field_serializer,
|
||||
field_validator,
|
||||
)
|
||||
|
||||
from . import Daf, to_camel
|
||||
from .product import ProductLink
|
||||
|
||||
|
||||
class MozimoProductRegisterItem(BaseModel):
|
||||
id_: uuid.UUID | None = None
|
||||
date_: date
|
||||
opening: Daf
|
||||
received: Daf
|
||||
sale: Daf
|
||||
nc: Daf
|
||||
display: Daf | None
|
||||
ageing: Daf | None
|
||||
last_edit_date: datetime | None = None
|
||||
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True)
|
||||
|
||||
@field_validator("date_", mode="before")
|
||||
@classmethod
|
||||
def parse_date(cls, value: date | str) -> date:
|
||||
if isinstance(value, date):
|
||||
return value
|
||||
return datetime.strptime(value, "%d-%b-%Y").date()
|
||||
|
||||
@field_serializer("date_")
|
||||
def serialize_date(self, value: date, info: FieldSerializationInfo) -> str:
|
||||
return value.strftime("%d-%b-%Y")
|
||||
|
||||
@field_validator("last_edit_date", mode="before")
|
||||
@classmethod
|
||||
def parse_last_edit_date(cls, value: None | datetime | str) -> datetime | None:
|
||||
if value is None or value == "":
|
||||
return None
|
||||
if isinstance(value, datetime):
|
||||
return value
|
||||
return datetime.strptime(value, "%d-%b-%Y %H:%M")
|
||||
|
||||
@field_serializer("last_edit_date")
|
||||
def serialize_last_edit_date(self, value: datetime, info: FieldSerializationInfo) -> str | None:
|
||||
return None if value is None else value.strftime("%d-%b-%Y %H:%M")
|
||||
|
||||
|
||||
class MozimoProductRegister(BaseModel):
|
||||
start_date: date | None = None
|
||||
finish_date: date | None = None
|
||||
product: ProductLink | None = None
|
||||
body: list[MozimoProductRegisterItem]
|
||||
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True)
|
||||
|
||||
@field_validator("start_date", mode="before")
|
||||
@classmethod
|
||||
def parse_start_date(cls, value: date | str | None) -> date | None:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, date):
|
||||
return value
|
||||
return datetime.strptime(value, "%d-%b-%Y").date()
|
||||
|
||||
@field_serializer("start_date")
|
||||
def serialize_start_date(self, value: date, info: FieldSerializationInfo) -> str | None:
|
||||
return None if value is None else value.strftime("%d-%b-%Y")
|
||||
|
||||
@field_validator("finish_date", mode="before")
|
||||
@classmethod
|
||||
def parse_finish_date(cls, value: date | str | None) -> date | None:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, date):
|
||||
return value
|
||||
return datetime.strptime(value, "%d-%b-%Y").date()
|
||||
|
||||
@field_serializer("finish_date")
|
||||
def serialize_finish_date(self, value: date, info: FieldSerializationInfo) -> str | None:
|
||||
return None if value is None else value.strftime("%d-%b-%Y")
|
@ -89,6 +89,10 @@ export const routes: Routes = [
|
||||
path: 'ledger',
|
||||
loadChildren: () => import('./ledger/ledger.routes').then((mod) => mod.routes),
|
||||
},
|
||||
{
|
||||
path: 'mozimo-product-register',
|
||||
loadChildren: () => import('./mozimo-product-register/mozimo-product-register.routes').then((mod) => mod.routes),
|
||||
},
|
||||
{
|
||||
path: 'net-transactions',
|
||||
loadChildren: () => import('./net-transactions/net-transactions.routes').then((mod) => mod.routes),
|
||||
|
@ -35,6 +35,7 @@
|
||||
<a mat-menu-item routerLink="/rate-contracts">Rate Contracts</a>
|
||||
<a mat-menu-item routerLink="/batch-integrity-report">Batch Integrity</a>
|
||||
<a mat-menu-item routerLink="/non-contract-purchase">Non Contract Purchases</a>
|
||||
<a mat-menu-item routerLink="/mozimo-product-register">Mozimo Product Register</a>
|
||||
</mat-menu>
|
||||
<button mat-button [matMenuTriggerFor]="productReportMenu">Product Reports</button>
|
||||
|
||||
|
@ -0,0 +1,66 @@
|
||||
import { DataSource } from '@angular/cdk/collections';
|
||||
import { EventEmitter } from '@angular/core';
|
||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||
import { merge, Observable } from 'rxjs';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
|
||||
import { MozimoProductRegisterItem } from './mozimo-product-register-item';
|
||||
|
||||
export class MozimoProductRegisterDataSource extends DataSource<MozimoProductRegisterItem> {
|
||||
public data: MozimoProductRegisterItem[] = [];
|
||||
public paginator?: MatPaginator;
|
||||
constructor(public dataObs: Observable<MozimoProductRegisterItem[]>) {
|
||||
super();
|
||||
}
|
||||
|
||||
connect(): Observable<MozimoProductRegisterItem[]> {
|
||||
const dataMutations: EventEmitter<PageEvent>[] = [];
|
||||
const d = this.dataObs.pipe(
|
||||
tap((x) => {
|
||||
this.data = x;
|
||||
}),
|
||||
);
|
||||
if (this.paginator) {
|
||||
dataMutations.push((this.paginator as MatPaginator).page);
|
||||
}
|
||||
|
||||
return merge(d, ...dataMutations).pipe(
|
||||
map(() => this.calculate([...this.data])),
|
||||
tap(() => {
|
||||
if (this.paginator) {
|
||||
this.paginator.length = this.data.length;
|
||||
}
|
||||
}),
|
||||
map((x: MozimoProductRegisterItem[]) => this.getPagedData(x)),
|
||||
);
|
||||
}
|
||||
|
||||
disconnect() {}
|
||||
|
||||
private calculate(data: MozimoProductRegisterItem[]): MozimoProductRegisterItem[] {
|
||||
if (data.length === 0) {
|
||||
return data;
|
||||
}
|
||||
let ob = data[0].opening;
|
||||
data.forEach((item) => {
|
||||
item.opening = ob;
|
||||
if (item.ageing !== null || item.display !== null) {
|
||||
item.closing = (item.ageing ?? 0) + (item.display ?? 0);
|
||||
item.variance = item.opening + item.received - item.sale - item.nc - item.closing;
|
||||
} else {
|
||||
item.closing = item.opening + item.received - item.sale - item.nc;
|
||||
item.variance = 0;
|
||||
}
|
||||
ob = item.closing;
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
private getPagedData(data: MozimoProductRegisterItem[]) {
|
||||
if (this.paginator === undefined) {
|
||||
return data;
|
||||
}
|
||||
const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
|
||||
return data.splice(startIndex, this.paginator.pageSize);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
export class MozimoProductRegisterItem {
|
||||
id: string | null;
|
||||
date: string;
|
||||
opening: number;
|
||||
received: number;
|
||||
sale: number;
|
||||
nc: number;
|
||||
display: number | null;
|
||||
ageing: number | null;
|
||||
variance: number | null;
|
||||
closing: number;
|
||||
lastEditDate: string | null;
|
||||
|
||||
public constructor(init?: Partial<MozimoProductRegisterItem>) {
|
||||
this.id = null;
|
||||
this.date = '';
|
||||
this.opening = 0;
|
||||
this.received = 0;
|
||||
this.sale = 0;
|
||||
this.nc = 0;
|
||||
this.display = null;
|
||||
this.ageing = null;
|
||||
this.variance = null;
|
||||
this.closing = 0;
|
||||
this.lastEditDate = null;
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.middle {
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.last {
|
||||
margin-left: 4px;
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title-group>
|
||||
<mat-card-title>Mozimo Product Register</mat-card-title>
|
||||
@if (dataSource.data.length) {
|
||||
<button mat-icon-button (click)="exportCsv()">
|
||||
<mat-icon>save_alt</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-card-title-group>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="form" class="flex flex-col">
|
||||
<div class="flex flex-row justify-around content-start items-start sm:max-lg:flex-col">
|
||||
<mat-form-field class="flex-auto mr-5">
|
||||
<mat-label>Start Date</mat-label>
|
||||
<input
|
||||
matInput
|
||||
#startDateElement
|
||||
[matDatepicker]="startDate"
|
||||
formControlName="startDate"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="startDate"></mat-datepicker-toggle>
|
||||
<mat-datepicker #startDate></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Finish Date</mat-label>
|
||||
<input matInput [matDatepicker]="finishDate" formControlName="finishDate" autocomplete="off" />
|
||||
<mat-datepicker-toggle matSuffix [for]="finishDate"></mat-datepicker-toggle>
|
||||
<mat-datepicker #finishDate></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="flex flex-row justify-around content-start items-start sm:max-lg:flex-col">
|
||||
<mat-form-field class="flex-auto basis-4/5 mr-5">
|
||||
<mat-label>Product</mat-label>
|
||||
<input
|
||||
type="text"
|
||||
matInput
|
||||
#productElement
|
||||
[matAutocomplete]="auto"
|
||||
formControlName="product"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
autoActiveFirstOption
|
||||
[displayWith]="displayFn"
|
||||
(optionSelected)="selected($event)"
|
||||
>
|
||||
@for (product of products | async; track product) {
|
||||
<mat-option [value]="product">{{ product.name }}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button class="flex-auto basis-1/5" color="primary" (click)="show()">Show</button>
|
||||
</div>
|
||||
<mat-table #table [dataSource]="dataSource" aria-label="Elements" formArrayName="items">
|
||||
<!-- Date Column -->
|
||||
<ng-container matColumnDef="date">
|
||||
<mat-header-cell *matHeaderCellDef class="center first">Date</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" class="center first">
|
||||
<span
|
||||
matBadge="1"
|
||||
matBadgeSize="small"
|
||||
[matBadgeHidden]="!row.lastEditDate"
|
||||
matTooltip="{{ row.lastEditDate | localTime }}"
|
||||
[matTooltipDisabled]="!row.lastEditDate"
|
||||
>{{ row.date }}</span
|
||||
>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Opening Column -->
|
||||
<ng-container matColumnDef="opening">
|
||||
<mat-header-cell *matHeaderCellDef class="right middle">Opening</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" class="right middle">{{ row.opening | number: '0.2-2' }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Received Column -->
|
||||
<ng-container matColumnDef="received">
|
||||
<mat-header-cell *matHeaderCellDef class="middle">Received</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row; let i = index" [formGroupName]="i" class="middle">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Received</mat-label>
|
||||
<input matInput type="number" formControlName="received" (change)="updateReceived($event, row)" />
|
||||
</mat-form-field>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Sale Column -->
|
||||
<ng-container matColumnDef="sale">
|
||||
<mat-header-cell *matHeaderCellDef class="middle">Sale</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row; let i = index" [formGroupName]="i" class="middle">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Sale</mat-label>
|
||||
<input matInput type="number" formControlName="sale" (change)="updateSale($event, row)" />
|
||||
</mat-form-field>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Nc Column -->
|
||||
<ng-container matColumnDef="nc">
|
||||
<mat-header-cell *matHeaderCellDef class="middle">Nc</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row; let i = index" [formGroupName]="i" class="middle">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Nc</mat-label>
|
||||
<input matInput type="number" formControlName="nc" (change)="updateNc($event, row)" />
|
||||
</mat-form-field>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Display Column -->
|
||||
<ng-container matColumnDef="display">
|
||||
<mat-header-cell *matHeaderCellDef class="middle">Display</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row; let i = index" [formGroupName]="i" class="middle">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Display</mat-label>
|
||||
<input matInput type="number" formControlName="display" (change)="updateDisplay($event, row)" />
|
||||
</mat-form-field>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Ageing Column -->
|
||||
<ng-container matColumnDef="ageing">
|
||||
<mat-header-cell *matHeaderCellDef class="middle">Ageing</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row; let i = index" [formGroupName]="i" class="middle">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Ageing</mat-label>
|
||||
<input matInput type="number" formControlName="ageing" (change)="updateAgeing($event, row)" />
|
||||
</mat-form-field>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Variance Column -->
|
||||
<ng-container matColumnDef="variance">
|
||||
<mat-header-cell *matHeaderCellDef class="right middle">Variance</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" class="right middle">{{ row.variance | number: '0.2-2' }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Closing Column -->
|
||||
<ng-container matColumnDef="closing">
|
||||
<mat-header-cell *matHeaderCellDef class="right last">Closing</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" class="right last">{{ row.closing | number: '0.2-2' }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator
|
||||
#paginator
|
||||
[length]="dataSource.data.length"
|
||||
[pageIndex]="0"
|
||||
[pageSize]="50"
|
||||
[pageSizeOptions]="[25, 50, 100, 250, 300, 5000]"
|
||||
>
|
||||
</mat-paginator>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button color="primary" (click)="save()" [disabled]="form.pristine">Save</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MozimoProductRegisterComponent } from './mozimo-product-register.component';
|
||||
|
||||
describe('MozimoProductRegisterComponent', () => {
|
||||
let component: MozimoProductRegisterComponent;
|
||||
let fixture: ComponentFixture<MozimoProductRegisterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MozimoProductRegisterComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MozimoProductRegisterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,250 @@
|
||||
import { DecimalPipe, CurrencyPipe, AsyncPipe } from '@angular/common';
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { FormGroup, FormArray, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import moment from 'moment';
|
||||
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ToCsvService } from '../shared/to-csv.service';
|
||||
|
||||
import { MozimoProductRegister } from './mozimo-product-register';
|
||||
import { MozimoProductRegisterDataSource } from './mozimo-product-register-datasource';
|
||||
import { MozimoProductRegisterItem } from './mozimo-product-register-item';
|
||||
import { MozimoProductRegisterService } from './mozimo-product-register.service';
|
||||
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||
import { Product } from '../core/product';
|
||||
import { debounceTime, distinctUntilChanged, Observable, switchMap, of as observableOf, BehaviorSubject } from 'rxjs';
|
||||
import { ProductSku } from '../core/product-sku';
|
||||
import { ProductService } from '../product/product.service';
|
||||
import { LocalTimePipe } from '../shared/local-time.pipe';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
|
||||
@Component({
|
||||
selector: 'app-mozimo-product-register',
|
||||
templateUrl: './mozimo-product-register.component.html',
|
||||
styleUrls: ['./mozimo-product-register.component.css'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
CurrencyPipe,
|
||||
DecimalPipe,
|
||||
LocalTimePipe,
|
||||
MatAutocompleteModule,
|
||||
MatBadgeModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDatepickerModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatPaginatorModule,
|
||||
MatTableModule,
|
||||
MatTooltipModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
})
|
||||
export class MozimoProductRegisterComponent implements OnInit {
|
||||
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
|
||||
info: MozimoProductRegister = new MozimoProductRegister();
|
||||
body = new BehaviorSubject<MozimoProductRegisterItem[]>([]);
|
||||
dataSource: MozimoProductRegisterDataSource = new MozimoProductRegisterDataSource(this.body);
|
||||
|
||||
form: FormGroup<{
|
||||
startDate: FormControl<Date>;
|
||||
finishDate: FormControl<Date>;
|
||||
product: FormControl<string | null>;
|
||||
items: FormArray<
|
||||
FormGroup<{
|
||||
received: FormControl<number>;
|
||||
sale: FormControl<number>;
|
||||
nc: FormControl<number>;
|
||||
display: FormControl<number | null>;
|
||||
ageing: FormControl<number | null>;
|
||||
}>
|
||||
>;
|
||||
}>;
|
||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||
|
||||
displayedColumns = ['date', 'opening', 'received', 'sale', 'nc', 'display', 'ageing', 'variance', 'closing'];
|
||||
|
||||
products: Observable<ProductSku[]>;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private toCsv: ToCsvService,
|
||||
private dialog: MatDialog,
|
||||
private snackBar: MatSnackBar,
|
||||
public auth: AuthService,
|
||||
private ser: MozimoProductRegisterService,
|
||||
private productSer: ProductService,
|
||||
) {
|
||||
this.form = new FormGroup({
|
||||
startDate: new FormControl(new Date(), { nonNullable: true }),
|
||||
finishDate: new FormControl(new Date(), { nonNullable: true }),
|
||||
product: new FormControl<string | null>(null),
|
||||
items: new FormArray<
|
||||
FormGroup<{
|
||||
received: FormControl<number>;
|
||||
sale: FormControl<number>;
|
||||
nc: FormControl<number>;
|
||||
display: FormControl<number | null>;
|
||||
ageing: FormControl<number | null>;
|
||||
}>
|
||||
>([]),
|
||||
});
|
||||
|
||||
this.products = this.form.controls.product.valueChanges.pipe(
|
||||
debounceTime(150),
|
||||
distinctUntilChanged(),
|
||||
switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocompleteSku(x, null))),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.data.subscribe((value) => {
|
||||
const data = value as { info: MozimoProductRegister };
|
||||
this.info = data.info;
|
||||
this.form.patchValue({
|
||||
product: this.info.product?.name ?? '',
|
||||
startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(),
|
||||
finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(),
|
||||
});
|
||||
this.form.controls.items.clear();
|
||||
this.info.body.forEach((x) =>
|
||||
this.form.controls.items.push(
|
||||
new FormGroup({
|
||||
received: new FormControl(x.received, { nonNullable: true, validators: [Validators.min(0)] }),
|
||||
sale: new FormControl(x.sale, { nonNullable: true, validators: [Validators.min(0)] }),
|
||||
nc: new FormControl(x.nc, { nonNullable: true, validators: [Validators.min(0)] }),
|
||||
display: new FormControl(x.display, { nonNullable: false, validators: [Validators.min(0)] }),
|
||||
ageing: new FormControl(x.ageing, { nonNullable: false, validators: [Validators.min(0)] }),
|
||||
}),
|
||||
),
|
||||
);
|
||||
if (!this.dataSource.paginator) {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
}
|
||||
this.body.next(this.info.body);
|
||||
});
|
||||
}
|
||||
|
||||
displayFn(product?: Product | string): string {
|
||||
return !product ? '' : typeof product === 'string' ? product : product.name;
|
||||
}
|
||||
|
||||
selected(event: MatAutocompleteSelectedEvent): void {
|
||||
this.info.product = event.option.value;
|
||||
}
|
||||
|
||||
show() {
|
||||
const info = this.getInfo();
|
||||
if (info.product) {
|
||||
this.router.navigate(['mozimo-product-register', info.product.id], {
|
||||
queryParams: {
|
||||
startDate: info.startDate,
|
||||
finishDate: info.finishDate,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
save() {
|
||||
this.ser.save(this.getMozimoProductRegister()).subscribe({
|
||||
next: () => {
|
||||
this.snackBar.open('', 'Success');
|
||||
},
|
||||
error: (error) => {
|
||||
this.snackBar.open(error, 'Danger');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getMozimoProductRegister(): MozimoProductRegister {
|
||||
const formModel = this.form.value;
|
||||
this.info.startDate = moment(formModel.startDate).format('DD-MMM-YYYY');
|
||||
this.info.finishDate = moment(formModel.finishDate).format('DD-MMM-YYYY');
|
||||
|
||||
const array = this.form.controls.items;
|
||||
this.info.body.forEach((item, index) => {
|
||||
item.received = +(array.controls[index].value.received ?? 0);
|
||||
item.sale = +(array.controls[index].value.sale ?? 0);
|
||||
item.nc = +(array.controls[index].value.nc ?? 0);
|
||||
const display = array.controls[index].value.display ?? null;
|
||||
const ageing = array.controls[index].value.ageing ?? null;
|
||||
item.display = display == null ? null : +display;
|
||||
item.ageing = ageing == null ? null : +ageing;
|
||||
});
|
||||
return this.info;
|
||||
}
|
||||
|
||||
getInfo(): MozimoProductRegister {
|
||||
const formModel = this.form.value;
|
||||
|
||||
return new MozimoProductRegister({
|
||||
product: this.info.product,
|
||||
startDate: moment(formModel.startDate).format('DD-MMM-YYYY'),
|
||||
finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'),
|
||||
});
|
||||
}
|
||||
|
||||
exportCsv() {
|
||||
const headers = {
|
||||
Date: 'date',
|
||||
Opening: 'opening',
|
||||
Received: 'received',
|
||||
Sale: 'sale',
|
||||
};
|
||||
|
||||
const d = JSON.parse(JSON.stringify(this.dataSource.data)).map((x: MozimoProductRegisterItem) => ({
|
||||
x,
|
||||
}));
|
||||
const csvData = new Blob([this.toCsv.toCsv(headers, d)], {
|
||||
type: 'text/csv;charset=utf-8;',
|
||||
});
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(csvData);
|
||||
link.setAttribute('download', 'mozimo-product-register.csv');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
updateReceived($event: Event, row: MozimoProductRegisterItem) {
|
||||
row.received = +($event.target as HTMLInputElement).value;
|
||||
this.body.next(this.info.body);
|
||||
}
|
||||
|
||||
updateSale($event: Event, row: MozimoProductRegisterItem) {
|
||||
row.sale = +($event.target as HTMLInputElement).value;
|
||||
this.body.next(this.info.body);
|
||||
}
|
||||
|
||||
updateNc($event: Event, row: MozimoProductRegisterItem) {
|
||||
row.nc = +($event.target as HTMLInputElement).value;
|
||||
this.body.next(this.info.body);
|
||||
}
|
||||
|
||||
updateDisplay($event: Event, row: MozimoProductRegisterItem) {
|
||||
const val = ($event.target as HTMLInputElement).value;
|
||||
row.display = val === '' ? null : +val;
|
||||
this.body.next(this.info.body);
|
||||
}
|
||||
|
||||
updateAgeing($event: Event, row: MozimoProductRegisterItem) {
|
||||
const val = ($event.target as HTMLInputElement).value;
|
||||
row.ageing = val === '' ? null : +val;
|
||||
this.body.next(this.info.body);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
|
||||
import { MozimoProductRegister } from './mozimo-product-register';
|
||||
import { mozimoProductRegisterResolver } from './mozimo-product-register.resolver';
|
||||
|
||||
describe('mozimoProductRegisterResolver', () => {
|
||||
const executeResolver: ResolveFn<MozimoProductRegister> = (...resolverParameters) =>
|
||||
TestBed.runInInjectionContext(() => mozimoProductRegisterResolver(...resolverParameters));
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(executeResolver).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
|
||||
import { MozimoProductRegister } from './mozimo-product-register';
|
||||
import { MozimoProductRegisterService } from './mozimo-product-register.service';
|
||||
|
||||
export const mozimoProductRegisterResolver: ResolveFn<MozimoProductRegister> = (route) => {
|
||||
const id = route.paramMap.get('id');
|
||||
const startDate = route.queryParamMap.get('startDate') || null;
|
||||
const finishDate = route.queryParamMap.get('finishDate') || null;
|
||||
return inject(MozimoProductRegisterService).list(id, startDate, finishDate);
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
import { authGuard } from '../auth/auth-guard.service';
|
||||
|
||||
import { MozimoProductRegisterComponent } from './mozimo-product-register.component';
|
||||
import { mozimoProductRegisterResolver } from './mozimo-product-register.resolver';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: MozimoProductRegisterComponent,
|
||||
canActivate: [authGuard],
|
||||
data: {
|
||||
permission: 'Ledger',
|
||||
},
|
||||
resolve: {
|
||||
info: mozimoProductRegisterResolver,
|
||||
},
|
||||
runGuardsAndResolvers: 'always',
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: MozimoProductRegisterComponent,
|
||||
canActivate: [authGuard],
|
||||
data: {
|
||||
// permission: 'Mozimo Product Register',
|
||||
permission: 'Ledger',
|
||||
},
|
||||
resolve: {
|
||||
info: mozimoProductRegisterResolver,
|
||||
},
|
||||
runGuardsAndResolvers: 'always',
|
||||
},
|
||||
];
|
@ -0,0 +1,17 @@
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MozimoProductRegisterService } from './mozimo-product-register.service';
|
||||
|
||||
describe('MozimoProductRegisterService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
providers: [MozimoProductRegisterService, provideHttpClient(withInterceptorsFromDi())],
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([MozimoProductRegisterService], (service: MozimoProductRegisterService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
@ -0,0 +1,55 @@
|
||||
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 { MozimoProductRegister } from './mozimo-product-register';
|
||||
|
||||
const url = '/api/mozimo-product-register';
|
||||
const serviceName = 'MozimoProductRegisterService';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class MozimoProductRegisterService {
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private log: ErrorLoggerService,
|
||||
) {}
|
||||
|
||||
list(id: string | null, startDate: string | null, finishDate: string | null): Observable<MozimoProductRegister> {
|
||||
const listUrl = id === null ? url : `${url}/${id}`;
|
||||
const options = { params: new HttpParams() };
|
||||
if (startDate !== null) {
|
||||
options.params = options.params.set('s', startDate);
|
||||
}
|
||||
if (finishDate !== null) {
|
||||
options.params = options.params.set('f', finishDate);
|
||||
}
|
||||
return this.http
|
||||
.get<MozimoProductRegister>(listUrl, options)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<MozimoProductRegister>;
|
||||
}
|
||||
|
||||
save(mozimoProductRegister: MozimoProductRegister): Observable<MozimoProductRegister> {
|
||||
return this.http
|
||||
.post<MozimoProductRegister>(url, mozimoProductRegister)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'save'))) as Observable<MozimoProductRegister>;
|
||||
}
|
||||
|
||||
post(date: string, costCentre: string): Observable<MozimoProductRegister> {
|
||||
const options = { params: new HttpParams().set('d', costCentre) };
|
||||
return this.http
|
||||
.post<MozimoProductRegister>(`${url}/${date}`, {}, options)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'Post Voucher'))) as Observable<MozimoProductRegister>;
|
||||
}
|
||||
|
||||
delete(date: string, costCentre: string): Observable<MozimoProductRegister> {
|
||||
const options = { params: new HttpParams().set('d', costCentre) };
|
||||
return this.http
|
||||
.delete<MozimoProductRegister>(`${url}/${date}`, options)
|
||||
.pipe(catchError(this.log.handleError(serviceName, 'Delete Voucher'))) as Observable<MozimoProductRegister>;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import { Product } from '../core/product';
|
||||
|
||||
import { MozimoProductRegisterItem } from './mozimo-product-register-item';
|
||||
|
||||
export class MozimoProductRegister {
|
||||
startDate: string;
|
||||
finishDate: string;
|
||||
product: Product;
|
||||
body: MozimoProductRegisterItem[];
|
||||
|
||||
public constructor(init?: Partial<MozimoProductRegister>) {
|
||||
this.startDate = '';
|
||||
this.finishDate = '';
|
||||
this.product = new Product();
|
||||
this.body = [];
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user