diff --git a/barker/barker/routers/customer.py b/barker/barker/routers/customer.py
index 8275a01..f26303a 100644
--- a/barker/barker/routers/customer.py
+++ b/barker/barker/routers/customer.py
@@ -5,7 +5,7 @@ from typing import List
import barker.schemas.customer as schemas
from fastapi import APIRouter, Depends, HTTPException, Security, status
-from sqlalchemy import or_, select
+from sqlalchemy import delete, or_, select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
@@ -62,7 +62,7 @@ def update_route(
)
-def add_discounts(customer: Customer, discounts: List[schemas.DiscountItem], db: Session):
+def add_discounts(customer: Customer, discounts: List[schemas.DiscountItem], db: Session) -> None:
for discount in discounts:
cd = next((d for d in customer.discounts if d.sale_category_id == discount.id_), None)
if cd is None:
@@ -77,10 +77,10 @@ def add_discounts(customer: Customer, discounts: List[schemas.DiscountItem], db:
def delete_route(
id_: uuid.UUID,
user: UserToken = Security(get_user, scopes=["customers"]),
-):
+) -> None:
with SessionFuture() as db:
- item: Customer = db.execute(select(Customer).where(Customer.id == id_)).scalar_one()
- db.delete(item)
+ db.execute(delete(CustomerDiscount).where(CustomerDiscount.customer_id == id_))
+ db.execute(delete(Customer).where(Customer.id == id_))
db.commit()
@@ -88,7 +88,8 @@ def delete_route(
def show_blank(
user: UserToken = Security(get_user, scopes=["customers"]),
) -> schemas.CustomerBlank:
- return blank_customer_info()
+ with SessionFuture() as db:
+ return blank_customer_info(db)
@router.get("/list", response_model=List[schemas.Customer])
@@ -162,5 +163,18 @@ def customer_info_for_list(item: Customer) -> schemas.Customer:
)
-def blank_customer_info() -> schemas.CustomerBlank:
- return schemas.CustomerBlank(name="", address="", phone="", printInBill=False, discounts=[])
+def blank_customer_info(db: Session) -> schemas.CustomerBlank:
+ return schemas.CustomerBlank(
+ name="",
+ address="",
+ phone="",
+ printInBill=False,
+ discounts=[
+ {
+ "id": sc.id,
+ "name": sc.name,
+ "discount": 0,
+ }
+ for sc in db.execute(select(SaleCategory).order_by(SaleCategory.name)).scalars().all()
+ ],
+ )
diff --git a/bookie/src/app/customers/customer-detail/customer-detail.component.css b/bookie/src/app/customers/customer-detail/customer-detail.component.css
index 82c7afd..8442f47 100644
--- a/bookie/src/app/customers/customer-detail/customer-detail.component.css
+++ b/bookie/src/app/customers/customer-detail/customer-detail.component.css
@@ -1,3 +1,23 @@
.example-card {
max-width: 400px;
}
+/*.discounts > div:nth-child(even) .mat-form-field {*/
+/* margin-left: 8px;*/
+/*}*/
+
+/*.discounts > div:nth-child(odd) .mat-form-field {*/
+/* margin-right: 8px;*/
+/*}*/
+
+.discounts > div:nth-child(3n + 1) .mat-form-field {
+ margin-right: 4px;
+}
+
+.discounts > div:nth-child(3n + 2) .mat-form-field {
+ margin-left: 4px;
+ margin-right: 4px;
+}
+
+.discounts > div:nth-child(3n) .mat-form-field {
+ margin-left: 4px;
+}
diff --git a/bookie/src/app/customers/customer-detail/customer-detail.component.html b/bookie/src/app/customers/customer-detail/customer-detail.component.html
index 0f0b63c..f21e8a4 100644
--- a/bookie/src/app/customers/customer-detail/customer-detail.component.html
+++ b/bookie/src/app/customers/customer-detail/customer-detail.component.html
@@ -50,18 +50,16 @@
>
Print in Bill?
-
-
-
+
+
Discount on {{ r.name }}
diff --git a/bookie/src/app/customers/customer.service.ts b/bookie/src/app/customers/customer.service.ts
index 8718308..cea80c1 100644
--- a/bookie/src/app/customers/customer.service.ts
+++ b/bookie/src/app/customers/customer.service.ts
@@ -1,4 +1,4 @@
-import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { catchError } from 'rxjs/operators';
@@ -55,4 +55,13 @@ export class CustomerService {
.delete(`${url}/${id}`, httpOptions)
.pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable;
}
+
+ autocomplete(query: string): Observable {
+ const options = { params: new HttpParams().set('q', query) };
+ return this.http
+ .get(`${url}/query`, options)
+ .pipe(catchError(this.log.handleError(serviceName, 'autocomplete'))) as Observable<
+ Customer[]
+ >;
+ }
}
diff --git a/bookie/src/app/guest-book/customer.ts b/bookie/src/app/guest-book/customer.ts
deleted file mode 100644
index 08aad4d..0000000
--- a/bookie/src/app/guest-book/customer.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export class Customer {
- name: string;
- phone: string;
- address: string;
-
- public constructor(init?: Partial) {
- this.name = '';
- this.phone = '';
- this.address = '';
- Object.assign(this, init);
- }
-}
diff --git a/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.ts b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.ts
index 69dab0b..762e129 100644
--- a/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.ts
+++ b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.ts
@@ -5,8 +5,9 @@ import { ActivatedRoute, Router } from '@angular/router';
import { Observable, of as observableOf } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
+import { Customer } from '../../core/customer';
import { ToasterService } from '../../core/toaster.service';
-import { Customer } from '../customer';
+import { CustomerService } from '../../customers/customer.service';
import { GuestBook } from '../guest-book';
import { GuestBookService } from '../guest-book.service';
@@ -27,6 +28,7 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit {
private router: Router,
private toaster: ToasterService,
private ser: GuestBookService,
+ private customerService: CustomerService,
) {
// Create form
this.form = this.fb.group({
@@ -41,7 +43,7 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit {
map((x) => (x !== null && x.length >= 1 ? x : null)),
debounceTime(150),
distinctUntilChanged(),
- switchMap((x) => (x === null ? observableOf([]) : this.ser.autocomplete(x))),
+ switchMap((x) => (x === null ? observableOf([]) : this.customerService.autocomplete(x))),
);
}
diff --git a/bookie/src/app/guest-book/guest-book.service.ts b/bookie/src/app/guest-book/guest-book.service.ts
index 5da050a..a3971f7 100644
--- a/bookie/src/app/guest-book/guest-book.service.ts
+++ b/bookie/src/app/guest-book/guest-book.service.ts
@@ -5,7 +5,6 @@ import { catchError } from 'rxjs/operators';
import { ErrorLoggerService } from '../core/error-logger.service';
-import { Customer } from './customer';
import { GuestBook } from './guest-book';
import { GuestBookList } from './guest-book-list';
@@ -59,13 +58,4 @@ export class GuestBookService {
.delete(`${url}/${id}`, httpOptions)
.pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable;
}
-
- autocomplete(query: string): Observable {
- const options = { params: new HttpParams().set('q', query) };
- return this.http
- .get(`${customerUrl}/query`, options)
- .pipe(catchError(this.log.handleError(serviceName, 'autocomplete'))) as Observable<
- Customer[]
- >;
- }
}
diff --git a/bookie/src/app/sales/bills/bills.component.html b/bookie/src/app/sales/bills/bills.component.html
index 51facf9..546f200 100644
--- a/bookie/src/app/sales/bills/bills.component.html
+++ b/bookie/src/app/sales/bills/bills.component.html
@@ -45,7 +45,9 @@
/
/
-
+ {{ bs.bill.customer?.name || 'Customer' }}
+
diff --git a/bookie/src/app/sales/bills/bills.component.ts b/bookie/src/app/sales/bills/bills.component.ts
index d578920..19f8765 100644
--- a/bookie/src/app/sales/bills/bills.component.ts
+++ b/bookie/src/app/sales/bills/bills.component.ts
@@ -6,11 +6,13 @@ import { map, switchMap } from 'rxjs/operators';
import { AuthService } from '../../auth/auth.service';
import { BillViewItem } from '../../core/bill-view-item';
+import { Customer } from '../../core/customer';
import { Table } from '../../core/table';
import { ToasterService } from '../../core/toaster.service';
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
import { TableService } from '../../tables/table.service';
import { BillService } from '../bill.service';
+import { ChooseCustomerComponent } from '../choose-customer/choose-customer.component';
import { PaxComponent } from '../pax/pax.component';
import { QuantityComponent } from '../quantity/quantity.component';
import { TablesDialogComponent } from '../tables-dialog/tables-dialog.component';
@@ -72,6 +74,20 @@ export class BillsComponent implements OnInit {
});
}
+ chooseCustomer() {
+ const dialogRef = this.dialog.open(ChooseCustomerComponent, {
+ // width: '750px',
+ data: this.bs.bill.customer?.id,
+ });
+
+ dialogRef.afterClosed().subscribe((result: boolean | Customer) => {
+ if (!result) {
+ return;
+ }
+ this.bs.bill.customer = result as Customer;
+ });
+ }
+
isAllSelected(kotView: BillViewItem): boolean {
const kot = this.bs.bill.kots.find((k) => k.id === kotView.kotId) as Kot;
return kot.inventories.reduce(
diff --git a/bookie/src/app/sales/choose-customer/choose-customer.component.css b/bookie/src/app/sales/choose-customer/choose-customer.component.css
new file mode 100644
index 0000000..8442f47
--- /dev/null
+++ b/bookie/src/app/sales/choose-customer/choose-customer.component.css
@@ -0,0 +1,23 @@
+.example-card {
+ max-width: 400px;
+}
+/*.discounts > div:nth-child(even) .mat-form-field {*/
+/* margin-left: 8px;*/
+/*}*/
+
+/*.discounts > div:nth-child(odd) .mat-form-field {*/
+/* margin-right: 8px;*/
+/*}*/
+
+.discounts > div:nth-child(3n + 1) .mat-form-field {
+ margin-right: 4px;
+}
+
+.discounts > div:nth-child(3n + 2) .mat-form-field {
+ margin-left: 4px;
+ margin-right: 4px;
+}
+
+.discounts > div:nth-child(3n) .mat-form-field {
+ margin-left: 4px;
+}
diff --git a/bookie/src/app/sales/choose-customer/choose-customer.component.html b/bookie/src/app/sales/choose-customer/choose-customer.component.html
new file mode 100644
index 0000000..5882318
--- /dev/null
+++ b/bookie/src/app/sales/choose-customer/choose-customer.component.html
@@ -0,0 +1,90 @@
+Customer
+
+
+
+
+
+
+
diff --git a/bookie/src/app/sales/choose-customer/choose-customer.component.spec.ts b/bookie/src/app/sales/choose-customer/choose-customer.component.spec.ts
new file mode 100644
index 0000000..302e141
--- /dev/null
+++ b/bookie/src/app/sales/choose-customer/choose-customer.component.spec.ts
@@ -0,0 +1,26 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { ChooseCustomerComponent } from './choose-customer.component';
+
+describe('ChooseCustomerComponent', () => {
+ let component: ChooseCustomerComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(
+ waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [ChooseCustomerComponent],
+ }).compileComponents();
+ }),
+ );
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ChooseCustomerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/bookie/src/app/sales/choose-customer/choose-customer.component.ts b/bookie/src/app/sales/choose-customer/choose-customer.component.ts
new file mode 100644
index 0000000..c5aee34
--- /dev/null
+++ b/bookie/src/app/sales/choose-customer/choose-customer.component.ts
@@ -0,0 +1,105 @@
+import { Component, Inject } from '@angular/core';
+import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms';
+import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { round } from 'mathjs';
+import { Observable, of as observableOf } from 'rxjs';
+import { debounceTime, distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
+
+import { Customer } from '../../core/customer';
+import { CustomerService } from '../../customers/customer.service';
+
+@Component({
+ selector: 'app-choose-customer',
+ templateUrl: './choose-customer.component.html',
+ styleUrls: ['./choose-customer.component.css'],
+})
+export class ChooseCustomerComponent {
+ form: FormGroup;
+ item: Customer = new Customer();
+ customers: Observable;
+
+ constructor(
+ public dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: string | undefined,
+ private fb: FormBuilder,
+ private ser: CustomerService,
+ ) {
+ // Create form
+ this.form = this.fb.group({
+ name: '',
+ phone: '',
+ address: '',
+ printInBill: false,
+ discounts: this.fb.array([]),
+ });
+ // Setup Account Autocomplete
+ this.customers = (this.form.get('phone') as FormControl).valueChanges.pipe(
+ startWith(null),
+ map((x) => (x === this.item.phone ? '' : x)),
+ map((x) => (x !== null && x.length >= 1 ? x : null)),
+ debounceTime(150),
+ distinctUntilChanged(),
+ switchMap((x) => (x === null ? observableOf([]) : this.ser.autocomplete(x))),
+ );
+ if (data) {
+ this.ser.get(data).subscribe((x) => this.showItem(x));
+ }
+ }
+
+ showItem(item: Customer) {
+ this.item = item;
+ this.form.patchValue({
+ name: item.name,
+ phone: item.phone,
+ address: item.address,
+ printInBill: item.printInBill,
+ });
+ this.form.setControl(
+ 'discounts',
+ this.fb.array(
+ item.discounts.map((x) =>
+ this.fb.group({
+ discount: '' + x.discount * 100,
+ }),
+ ),
+ ),
+ );
+ this.form.markAsPristine();
+ }
+
+ save() {
+ const customer = this.getItem();
+ const promise = this.form.pristine ? observableOf(customer) : this.ser.saveOrUpdate(customer);
+ promise.subscribe((x) => this.dialogRef.close(x));
+ }
+
+ displayFn(customer?: Customer | string): string {
+ if (!customer) {
+ return '';
+ }
+ return typeof customer === 'string' ? customer : customer.phone;
+ }
+
+ selected(event: MatAutocompleteSelectedEvent): void {
+ const customer: Customer = event.option.value;
+ this.showItem(customer);
+ }
+
+ getItem(): Customer {
+ const formModel = this.form.value;
+ this.item.id = this.item.phone.trim() !== formModel.phone.trim() ? '' : this.item.id;
+ this.item.name = formModel.name;
+ this.item.phone = formModel.phone;
+ this.item.address = formModel.address;
+ this.item.printInBill = formModel.printInBill;
+ const array = this.form.get('discounts') as FormArray;
+ this.item.discounts.forEach((item, index) => {
+ item.discount = Math.max(
+ Math.min(round(array.controls[index].value.discount / 100, 5), 100),
+ 0,
+ );
+ });
+ return this.item;
+ }
+}
diff --git a/bookie/src/app/sales/sales.module.ts b/bookie/src/app/sales/sales.module.ts
index 7c2568a..91128df 100644
--- a/bookie/src/app/sales/sales.module.ts
+++ b/bookie/src/app/sales/sales.module.ts
@@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
+import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
@@ -25,6 +26,7 @@ import { BillTypeComponent } from './bill-type/bill-type.component';
import { BillService } from './bill.service';
import { BillsComponent } from './bills/bills.component';
import { CanDeactivateBillGuard } from './can-deactivate-bill.guard';
+import { ChooseCustomerComponent } from './choose-customer/choose-customer.component';
import { DiscountComponent } from './discount/discount.component';
import { SalesHomeComponent } from './home/sales-home.component';
import { MenuCategoriesComponent } from './menu-categories/menu-categories.component';
@@ -49,6 +51,7 @@ import { TablesDialogComponent } from './tables-dialog/tables-dialog.component';
MenuCategoriesComponent,
ModifiersComponent,
PaxComponent,
+ ChooseCustomerComponent,
ProductsComponent,
QuantityComponent,
ReceivePaymentComponent,
@@ -80,6 +83,7 @@ import { TablesDialogComponent } from './tables-dialog/tables-dialog.component';
SharedModule,
SalesRoutingModule,
MatSelectModule,
+ MatAutocompleteModule,
],
})
export class SalesModule {}