Ported:
Change (Reprint bill) Chore: Refactored save voucher so that the save function can be reused in change voucher route Fix: Show voucher sent modifiers in a wrong format Inventory schema used rate instead of price
This commit is contained in:
@ -26,7 +26,7 @@ from .routers.reports import (
|
|||||||
sale_report,
|
sale_report,
|
||||||
tax_report
|
tax_report
|
||||||
)
|
)
|
||||||
from .routers.voucher import show, save, update, receive_payment, void, merge_move, split
|
from .routers.voucher import show, save, update, receive_payment, void, merge_move, split, change
|
||||||
|
|
||||||
from .db.base_class import Base
|
from .db.base_class import Base
|
||||||
from .core.config import settings
|
from .core.config import settings
|
||||||
@ -76,6 +76,7 @@ app.include_router(receive_payment.router, prefix="/api/voucher", tags=["voucher
|
|||||||
app.include_router(void.router, prefix="/api/voucher", tags=["voucher"])
|
app.include_router(void.router, prefix="/api/voucher", tags=["voucher"])
|
||||||
app.include_router(merge_move.router, prefix="/api", tags=["voucher"])
|
app.include_router(merge_move.router, prefix="/api", tags=["voucher"])
|
||||||
app.include_router(split.router, prefix="/api", tags=["voucher"])
|
app.include_router(split.router, prefix="/api", tags=["voucher"])
|
||||||
|
app.include_router(change.router, prefix="/api/voucher", tags=["voucher"])
|
||||||
|
|
||||||
# app.include_router(issue_grid.router, prefix="/api/issue-grid", tags=["vouchers"])
|
# app.include_router(issue_grid.router, prefix="/api/issue-grid", tags=["vouchers"])
|
||||||
# app.include_router(batch.router, prefix="/api/batch", tags=["vouchers"])
|
# app.include_router(batch.router, prefix="/api/batch", tags=["vouchers"])
|
||||||
|
|||||||
@ -194,8 +194,8 @@ class Settlement(Base):
|
|||||||
|
|
||||||
settle_option = relationship("SettleOption")
|
settle_option = relationship("SettleOption")
|
||||||
|
|
||||||
def __init__(self, voucher_id=None, settled=None, amount=None, id=None):
|
def __init__(self, voucher_id=None, settled=None, amount=None, id_=None):
|
||||||
self.id = id
|
self.id = id_
|
||||||
self.voucher_id = voucher_id
|
self.voucher_id = voucher_id
|
||||||
self.settled = settled
|
self.settled = settled
|
||||||
self.amount = amount
|
self.amount = amount
|
||||||
@ -211,8 +211,8 @@ class Reprint(Base):
|
|||||||
|
|
||||||
user = relationship("User", backref="reprints")
|
user = relationship("User", backref="reprints")
|
||||||
|
|
||||||
def __init__(self, voucher_id=None, user_id=None, id=None):
|
def __init__(self, voucher_id=None, user_id=None, id_=None):
|
||||||
self.id = id
|
self.id = id_
|
||||||
self.date = datetime.now()
|
self.date = datetime.now()
|
||||||
self.voucher_id = voucher_id
|
self.voucher_id = voucher_id
|
||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
|
|||||||
@ -1,42 +1,91 @@
|
|||||||
# import uuid
|
import uuid
|
||||||
#
|
from decimal import Decimal
|
||||||
# import transaction
|
from typing import Optional
|
||||||
# from pyramid.view import view_config
|
|
||||||
#
|
from fastapi import APIRouter, HTTPException, status, Depends, Security
|
||||||
# from barker.models import Voucher, Overview
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
# from barker.views.voucher.save import save
|
from sqlalchemy.orm import Session
|
||||||
# from barker.views.voucher.show import voucher_info
|
|
||||||
#
|
from .save import do_save
|
||||||
#
|
from ...schemas.auth import UserToken
|
||||||
# @view_config(
|
import barker.schemas.voucher as schemas
|
||||||
# request_method="POST",
|
from ...core.security import get_current_active_user as get_user
|
||||||
# route_name="voucher_reprint",
|
from ...db.session import SessionLocal
|
||||||
# renderer="json",
|
from ...models import Voucher, SettleOption, Overview, Reprint
|
||||||
# permission="Edit Printed Bill",
|
from ...routers.voucher import (
|
||||||
# trans=True,
|
do_update_settlements,
|
||||||
# ) # Permission error
|
get_guest_book,
|
||||||
# def voucher_change(request):
|
)
|
||||||
# json = request.json_body
|
from barker.schemas.receive_payment import ReceivePaymentItem as SettleSchema
|
||||||
# id_ = uuid.UUID(request.matchdict["id"])
|
|
||||||
# item = save(json, request.dbsession)
|
router = APIRouter()
|
||||||
# old = request.dbsession.query(Voucher).filter(Voucher.id == id_).first()
|
|
||||||
# old.void = True
|
|
||||||
# old.void_reason = "Bill Discounted / Changed. New Bill ID is {0}".format(
|
# Dependency
|
||||||
# item.full_bill_id
|
def get_db():
|
||||||
# )
|
try:
|
||||||
# # TODO: Set the Void Settlement
|
db = SessionLocal()
|
||||||
#
|
yield db
|
||||||
# if old.status is None:
|
finally:
|
||||||
# item.status = Overview(
|
db.close()
|
||||||
# voucher_id=None, food_table_id=item.food_table_id, status="printed"
|
|
||||||
# )
|
|
||||||
# request.dbsession.add(item.status)
|
@router.put("/change/{id_}")
|
||||||
# else:
|
def change(
|
||||||
# request.dbsession.query(Overview).filter(Overview.voucher_id == old.id).update(
|
id_: uuid.UUID,
|
||||||
# {Overview.voucher_id: item.id}
|
data: schemas.VoucherIn,
|
||||||
# )
|
u: bool, # Update table?
|
||||||
# transaction.commit()
|
g: Optional[uuid.UUID] = None, # Guest book id
|
||||||
# item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first()
|
db: Session = Depends(get_db),
|
||||||
# return voucher_info(item)
|
user: UserToken = Security(get_user),
|
||||||
#
|
):
|
||||||
#
|
try:
|
||||||
|
old: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
|
||||||
|
amount_changed: bool = old.amount != round(
|
||||||
|
sum(
|
||||||
|
Decimal((0 if i.is_happy_hour else i.price) * i.quantity * (1 - i.discount) * (1 + i.tax_rate))
|
||||||
|
for k in data.kots
|
||||||
|
for i in k.inventories
|
||||||
|
),
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
items_changed: bool = len([i for k in old.kots for i in k.inventories]) != len(
|
||||||
|
[i for k in data.kots for i in k.inventories]
|
||||||
|
)
|
||||||
|
if amount_changed or items_changed:
|
||||||
|
void_and_issue_new_bill(data, u, g, old, db, user)
|
||||||
|
else:
|
||||||
|
reprint_bill(id_, user.id_, db)
|
||||||
|
db.commit()
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
db.rollback()
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
db.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def reprint_bill(voucher_id: uuid.UUID, user_id: uuid.UUID, db: Session):
|
||||||
|
item = Reprint(voucher_id=voucher_id, user_id=user_id)
|
||||||
|
db.add(item)
|
||||||
|
|
||||||
|
|
||||||
|
def void_and_issue_new_bill(
|
||||||
|
data: schemas.VoucherIn, u: bool, g: Optional[uuid.UUID], old: Voucher, db: Session, user: UserToken
|
||||||
|
):
|
||||||
|
update_table = u
|
||||||
|
guest_book = get_guest_book(g, db)
|
||||||
|
item = do_save(data, old.voucher_type, guest_book, db, user)
|
||||||
|
db.flush()
|
||||||
|
old.void = True
|
||||||
|
old.void_reason = f"Bill Discounted / Changed. New Bill ID is {item.full_bill_id}"
|
||||||
|
do_update_settlements(old, [SettleSchema(id=SettleOption.VOID(), amount=round(old.amount))], db)
|
||||||
|
|
||||||
|
if update_table:
|
||||||
|
if old.status is None:
|
||||||
|
item.status = Overview(voucher_id=None, food_table_id=item.food_table_id, status="printed")
|
||||||
|
db.add(item.status)
|
||||||
|
else:
|
||||||
|
db.query(Overview).filter(Overview.voucher_id == old.id).update({Overview.voucher_id: item.id})
|
||||||
|
|||||||
@ -11,7 +11,7 @@ from ...schemas.auth import UserToken
|
|||||||
import barker.schemas.voucher as schemas
|
import barker.schemas.voucher as schemas
|
||||||
from ...core.security import get_current_active_user as get_user
|
from ...core.security import get_current_active_user as get_user
|
||||||
from ...db.session import SessionLocal
|
from ...db.session import SessionLocal
|
||||||
from ...models import Voucher, VoucherType, Kot, Product, Inventory, InventoryModifier
|
from ...models import Voucher, VoucherType, Kot, Product, Inventory, InventoryModifier, GuestBook
|
||||||
from ...routers.voucher import (
|
from ...routers.voucher import (
|
||||||
get_tax,
|
get_tax,
|
||||||
do_update_settlements,
|
do_update_settlements,
|
||||||
@ -43,58 +43,10 @@ def save(
|
|||||||
user: UserToken = Security(get_user),
|
user: UserToken = Security(get_user),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
now = datetime.now()
|
|
||||||
update_table = u
|
update_table = u
|
||||||
voucher_type = VoucherType[p]
|
voucher_type = VoucherType[p]
|
||||||
guest_book = get_guest_book(g, db)
|
guest_book = get_guest_book(g, db)
|
||||||
|
item: Voucher = do_save(data, voucher_type, guest_book, db, user)
|
||||||
check_permissions(None, voucher_type, user.permissions)
|
|
||||||
|
|
||||||
bill_id = get_bill_id(voucher_type, db)
|
|
||||||
kot_id = db.query(func.coalesce(func.max(Voucher.kot_id), 0) + 1).scalar()
|
|
||||||
|
|
||||||
item = Voucher(
|
|
||||||
now,
|
|
||||||
guest_book.pax if guest_book is not None else data.pax,
|
|
||||||
bill_id,
|
|
||||||
kot_id,
|
|
||||||
data.table.id_,
|
|
||||||
data.customer.id_ if data.customer is not None else None,
|
|
||||||
voucher_type,
|
|
||||||
user.id_,
|
|
||||||
)
|
|
||||||
db.add(item)
|
|
||||||
for k in data.kots:
|
|
||||||
code = db.query(func.coalesce(func.max(Kot.code), 0) + 1).scalar()
|
|
||||||
kot = Kot(item.id, code, item.food_table_id, item.date, item.user_id)
|
|
||||||
item.kots.append(kot)
|
|
||||||
db.add(kot)
|
|
||||||
for index, i in enumerate(k.inventories):
|
|
||||||
product = db.query(Product).filter(Product.id == i.product.id_).first()
|
|
||||||
tax_rate = get_tax(product.sale_category.tax.rate, voucher_type)
|
|
||||||
inv = Inventory(
|
|
||||||
kot.id,
|
|
||||||
product.id,
|
|
||||||
round(i.quantity, 2),
|
|
||||||
product.price,
|
|
||||||
round(i.discount, 5),
|
|
||||||
i.is_happy_hour,
|
|
||||||
product.sale_category.tax_id,
|
|
||||||
tax_rate,
|
|
||||||
index,
|
|
||||||
)
|
|
||||||
kot.inventories.append(inv)
|
|
||||||
db.add(inv)
|
|
||||||
for m in i.modifiers:
|
|
||||||
mod = InventoryModifier(None, m.id_, 0)
|
|
||||||
inv.modifiers.append(mod)
|
|
||||||
db.add(mod)
|
|
||||||
do_update_settlements(item, [], db)
|
|
||||||
if len(item.kots) == 0 or len(item.kots[0].inventories) == 0:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Please add some products",
|
|
||||||
)
|
|
||||||
|
|
||||||
if update_table:
|
if update_table:
|
||||||
do_update_table(item, guest_book, db)
|
do_update_table(item, guest_book, db)
|
||||||
db.commit()
|
db.commit()
|
||||||
@ -106,3 +58,64 @@ def save(
|
|||||||
except Exception:
|
except Exception:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def do_save(
|
||||||
|
data: schemas.VoucherIn,
|
||||||
|
voucher_type: VoucherType,
|
||||||
|
guest_book: GuestBook,
|
||||||
|
db: Session,
|
||||||
|
user: UserToken,
|
||||||
|
):
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
check_permissions(None, voucher_type, user.permissions)
|
||||||
|
|
||||||
|
bill_id = get_bill_id(voucher_type, db)
|
||||||
|
kot_id = db.query(func.coalesce(func.max(Voucher.kot_id), 0) + 1).scalar()
|
||||||
|
|
||||||
|
item = Voucher(
|
||||||
|
now,
|
||||||
|
guest_book.pax if guest_book is not None else data.pax,
|
||||||
|
bill_id,
|
||||||
|
kot_id,
|
||||||
|
data.table.id_,
|
||||||
|
data.customer.id_ if data.customer is not None else None,
|
||||||
|
voucher_type,
|
||||||
|
user.id_,
|
||||||
|
)
|
||||||
|
db.add(item)
|
||||||
|
for k in data.kots:
|
||||||
|
if not len(k.inventories):
|
||||||
|
continue
|
||||||
|
code = db.query(func.coalesce(func.max(Kot.code), 0) + 1).scalar()
|
||||||
|
kot = Kot(item.id, code, item.food_table_id, item.date, item.user_id)
|
||||||
|
item.kots.append(kot)
|
||||||
|
db.add(kot)
|
||||||
|
for index, i in enumerate(k.inventories):
|
||||||
|
product = db.query(Product).filter(Product.id == i.product.id_).first()
|
||||||
|
tax_rate = get_tax(product.sale_category.tax.rate, voucher_type)
|
||||||
|
inv = Inventory(
|
||||||
|
kot.id,
|
||||||
|
product.id,
|
||||||
|
round(i.quantity, 2),
|
||||||
|
product.price,
|
||||||
|
round(i.discount, 5),
|
||||||
|
i.is_happy_hour,
|
||||||
|
product.sale_category.tax_id,
|
||||||
|
tax_rate,
|
||||||
|
index,
|
||||||
|
)
|
||||||
|
kot.inventories.append(inv)
|
||||||
|
db.add(inv)
|
||||||
|
for m in i.modifiers:
|
||||||
|
mod = InventoryModifier(None, m.id_, 0)
|
||||||
|
inv.modifiers.append(mod)
|
||||||
|
db.add(mod)
|
||||||
|
db.flush()
|
||||||
|
do_update_settlements(item, [], db)
|
||||||
|
if len(item.kots) == 0 or len(item.kots[0].inventories) == 0:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Please add some products",
|
||||||
|
)
|
||||||
|
return item
|
||||||
|
|||||||
@ -49,9 +49,9 @@ def from_bill(
|
|||||||
|
|
||||||
@router.get("/from-table/{id_}")
|
@router.get("/from-table/{id_}")
|
||||||
def from_bill(
|
def from_bill(
|
||||||
id_: str, # Table ID
|
id_: str, # table id
|
||||||
v: Optional[uuid.UUID] = None, # Voucher ID
|
v: Optional[uuid.UUID] = None, # voucher id
|
||||||
g: Optional[uuid.UUID] = None, # Guest ID
|
g: Optional[uuid.UUID] = None, # guest id
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
user: UserToken = Security(get_user),
|
user: UserToken = Security(get_user),
|
||||||
):
|
):
|
||||||
@ -116,16 +116,8 @@ def voucher_info(item: Voucher):
|
|||||||
"taxRate": i.tax_rate,
|
"taxRate": i.tax_rate,
|
||||||
"tax": {"id": i.tax_id, "name": i.tax.name},
|
"tax": {"id": i.tax_id, "name": i.tax.name},
|
||||||
"discount": i.discount,
|
"discount": i.discount,
|
||||||
"inventoryModifier": [
|
"modifiers": [
|
||||||
{
|
{"id": m.modifier.id, "name": m.modifier.name, "price": m.price,} for m in i.modifiers
|
||||||
"modifier": {
|
|
||||||
"id": m.modifier.id,
|
|
||||||
"name": m.modifier.name,
|
|
||||||
"showInBill": m.modifier.show_in_bill,
|
|
||||||
},
|
|
||||||
"price": m.price,
|
|
||||||
}
|
|
||||||
for m in i.modifiers
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
for i in k.inventories
|
for i in k.inventories
|
||||||
@ -137,7 +129,7 @@ def voucher_info(item: Voucher):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def voucher_blank(table, guest):
|
def voucher_blank(table: FoodTable, guest: Optional[GuestBook]):
|
||||||
return {
|
return {
|
||||||
"pax": table.seats if guest is None else guest.pax,
|
"pax": table.seats if guest is None else guest.pax,
|
||||||
"table": {"id": table.id, "name": table.name},
|
"table": {"id": table.id, "name": table.name},
|
||||||
|
|||||||
@ -16,9 +16,9 @@ class Inventory(BaseModel):
|
|||||||
id_: Optional[uuid.UUID]
|
id_: Optional[uuid.UUID]
|
||||||
product: ProductLink
|
product: ProductLink
|
||||||
quantity: Decimal = Field(ge=0, multiple_of=0.01)
|
quantity: Decimal = Field(ge=0, multiple_of=0.01)
|
||||||
rate: Optional[Decimal]
|
price: Optional[Decimal]
|
||||||
tax: Optional[TaxLink]
|
tax: Optional[TaxLink]
|
||||||
taxRate: Optional[Decimal]
|
tax_rate: Optional[Decimal]
|
||||||
discount: Decimal = Field(ge=0, multiple_of=0.00001, le=1)
|
discount: Decimal = Field(ge=0, multiple_of=0.00001, le=1)
|
||||||
is_happy_hour: bool
|
is_happy_hour: bool
|
||||||
modifiers: Optional[List[ModifierLink]]
|
modifiers: Optional[List[ModifierLink]]
|
||||||
|
|||||||
@ -69,11 +69,24 @@ export class VoucherService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
change(voucher: Bill, printType: PrintType, guest_book_id: string, updateTable: boolean): Observable<boolean> {
|
||||||
|
const options = {params: new HttpParams().set('u', updateTable.toString())};
|
||||||
|
if (guest_book_id !== null) {
|
||||||
|
options.params = options.params.set('g', guest_book_id);
|
||||||
|
}
|
||||||
|
return <Observable<boolean>>this.http.put<boolean>(`${url}/change/${voucher.id}`, voucher, options)
|
||||||
|
.pipe(
|
||||||
|
catchError(this.log.handleError(serviceName, 'change'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
saveOrUpdate(voucher: Bill, printType: PrintType, guest_book_id: string, updateTable: boolean): Observable<boolean> {
|
saveOrUpdate(voucher: Bill, printType: PrintType, guest_book_id: string, updateTable: boolean): Observable<boolean> {
|
||||||
if (!voucher.id) {
|
if (!voucher.id) {
|
||||||
return this.save(voucher, printType, guest_book_id, updateTable);
|
return this.save(voucher, printType, guest_book_id, updateTable);
|
||||||
} else {
|
} else if (voucher.voucherType === PrintType.Kot) {
|
||||||
return this.update(voucher, printType, guest_book_id, updateTable);
|
return this.update(voucher, printType, guest_book_id, updateTable);
|
||||||
|
} else {
|
||||||
|
return this.change(voucher, printType, guest_book_id, updateTable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,17 +73,18 @@ export class SalesHomeComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showDiscount(): Observable<boolean | { id: string, name: string, discount: number }[]> {
|
showDiscount(): Observable<boolean | { id: string, name: string, discount: number }[]> {
|
||||||
const dialogRef = this.dialog.open(DiscountComponent, {
|
if (this.discountAllowed()) {
|
||||||
// width: '750px',
|
const dialogRef = this.dialog.open(DiscountComponent, {
|
||||||
data: this.mcSer.listForDiscount()
|
// width: '750px',
|
||||||
});
|
data: this.mcSer.listForDiscount()
|
||||||
return dialogRef.afterClosed();
|
});
|
||||||
|
return dialogRef.afterClosed();
|
||||||
|
} else {
|
||||||
|
return observableOf(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
discount(): void {
|
discount(): void {
|
||||||
if (!this.discountAllowed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.showDiscount().subscribe((result: boolean | { id: string, name: string, discount: number }[]) => {
|
this.showDiscount().subscribe((result: boolean | { id: string, name: string, discount: number }[]) => {
|
||||||
if (!!result) {
|
if (!!result) {
|
||||||
this.bs.discount(result as { id: string, name: string, discount: number }[]);
|
this.bs.discount(result as { id: string, name: string, discount: number }[]);
|
||||||
@ -92,13 +93,18 @@ export class SalesHomeComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
billTypeDialog() {
|
billTypeDialog() {
|
||||||
return this.dialog.open(BillTypeComponent).afterClosed().pipe(
|
if (this.bs.bill.voucherType !== PrintType.Kot) {
|
||||||
tap(x => {
|
return observableOf(this.bs.bill.voucherType);
|
||||||
if (!x) {
|
} else {
|
||||||
throwError ('No Bill Type Chosen');
|
return this.dialog.open(BillTypeComponent).afterClosed().pipe(
|
||||||
}
|
tap(x => {
|
||||||
})
|
if (!x) {
|
||||||
);
|
throwError('No Bill Type Chosen');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmTableDialog(table: Table): Observable<{table: Table, confirmed: boolean}> {
|
confirmTableDialog(table: Table): Observable<{table: Table, confirmed: boolean}> {
|
||||||
@ -143,9 +149,7 @@ export class SalesHomeComponent implements OnInit {
|
|||||||
if (this.route.snapshot.queryParamMap.has('guest')) {
|
if (this.route.snapshot.queryParamMap.has('guest')) {
|
||||||
guestBookId = this.route.snapshot.queryParamMap.get('guest');
|
guestBookId = this.route.snapshot.queryParamMap.get('guest');
|
||||||
}
|
}
|
||||||
const discountObservable: Observable<any> = (this.discountAllowed()) ? this.showDiscount() : observableOf('');
|
this.showDiscount().pipe(
|
||||||
|
|
||||||
discountObservable.pipe(
|
|
||||||
tap((result: boolean | { id: string, name: string, discount: number }[]) => {
|
tap((result: boolean | { id: string, name: string, discount: number }[]) => {
|
||||||
if (!!result) {
|
if (!!result) {
|
||||||
this.bs.discount(result as { id: string, name: string, discount: number }[]);
|
this.bs.discount(result as { id: string, name: string, discount: number }[]);
|
||||||
|
|||||||
Reference in New Issue
Block a user