From 0da16e954821140d84b91cde88ca73aa7b6d27bf Mon Sep 17 00:00:00 2001 From: tanshu Date: Fri, 2 Apr 2021 06:34:31 +0530 Subject: [PATCH] Feature: Added the customer module to list / edit customers. This is needed to add the customer discount functionality --- barker/barker/routers/customer.py | 8 +- barker/barker/schemas/customer.py | 11 +- bookie/src/app/app-routing.module.ts | 4 + bookie/src/app/core/customer.ts | 14 +++ .../customer-detail.component.css | 3 + .../customer-detail.component.html | 51 +++++++++ .../customer-detail.component.spec.ts | 26 +++++ .../customer-detail.component.ts | 105 ++++++++++++++++++ .../customer-list-resolver.service.spec.ts | 15 +++ .../customer-list-resolver.service.ts | 18 +++ .../customer-list/customer-list-datasource.ts | 16 +++ .../customer-list/customer-list.component.css | 0 .../customer-list.component.html | 35 ++++++ .../customer-list/customer-list.component.ts | 28 +++++ .../customer-resolver.service.spec.ts | 15 +++ .../customers/customer-resolver.service.ts | 19 ++++ .../customers/customer-routing.module.spec.ts | 13 +++ .../app/customers/customer-routing.module.ts | 50 +++++++++ .../src/app/customers/customer.module.spec.ts | 13 +++ bookie/src/app/customers/customer.module.ts | 40 +++++++ .../app/customers/customer.service.spec.ts | 15 +++ bookie/src/app/customers/customer.service.ts | 58 ++++++++++ .../guest-book-detail.component.ts | 1 - bookie/src/app/home/home.component.html | 9 ++ 24 files changed, 560 insertions(+), 7 deletions(-) create mode 100644 bookie/src/app/core/customer.ts create mode 100644 bookie/src/app/customers/customer-detail/customer-detail.component.css create mode 100644 bookie/src/app/customers/customer-detail/customer-detail.component.html create mode 100644 bookie/src/app/customers/customer-detail/customer-detail.component.spec.ts create mode 100644 bookie/src/app/customers/customer-detail/customer-detail.component.ts create mode 100644 bookie/src/app/customers/customer-list-resolver.service.spec.ts create mode 100644 bookie/src/app/customers/customer-list-resolver.service.ts create mode 100644 bookie/src/app/customers/customer-list/customer-list-datasource.ts create mode 100644 bookie/src/app/customers/customer-list/customer-list.component.css create mode 100644 bookie/src/app/customers/customer-list/customer-list.component.html create mode 100644 bookie/src/app/customers/customer-list/customer-list.component.ts create mode 100644 bookie/src/app/customers/customer-resolver.service.spec.ts create mode 100644 bookie/src/app/customers/customer-resolver.service.ts create mode 100644 bookie/src/app/customers/customer-routing.module.spec.ts create mode 100644 bookie/src/app/customers/customer-routing.module.ts create mode 100644 bookie/src/app/customers/customer.module.spec.ts create mode 100644 bookie/src/app/customers/customer.module.ts create mode 100644 bookie/src/app/customers/customer.service.spec.ts create mode 100644 bookie/src/app/customers/customer.service.ts diff --git a/barker/barker/routers/customer.py b/barker/barker/routers/customer.py index 1aed153..1c82ad5 100644 --- a/barker/barker/routers/customer.py +++ b/barker/barker/routers/customer.py @@ -93,11 +93,11 @@ def delete( raise -@router.get("", response_model=schemas.CustomerIn) +@router.get("", response_model=schemas.CustomerBlank) def show_blank( db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["customers"]), -) -> schemas.CustomerIn: +) -> schemas.CustomerBlank: return blank_customer_info() @@ -137,5 +137,5 @@ def customer_info(item: Customer) -> schemas.Customer: ) -def blank_customer_info() -> schemas.CustomerIn: - return schemas.CustomerIn(name="", address="", phone="") +def blank_customer_info() -> schemas.CustomerBlank: + return schemas.CustomerBlank(name="", address="", phone="") diff --git a/barker/barker/schemas/customer.py b/barker/barker/schemas/customer.py index 0f4f018..2c08565 100644 --- a/barker/barker/schemas/customer.py +++ b/barker/barker/schemas/customer.py @@ -9,11 +9,11 @@ from . import to_camel class CustomerIn(BaseModel): name: str = Field(..., min_length=1) - phone: str = Field(..., min_length=1) + # phone: str = Field(..., min_length=1) + phone: str address: Optional[str] class Config: - fields = {"id_": "id"} anystr_strip_whitespace = True alias_generator = to_camel @@ -33,4 +33,11 @@ class CustomerLink(BaseModel): class Config: fields = {"id_": "id"} + + +class CustomerBlank(CustomerIn): + name: str + + class Config: + anystr_strip_whitespace = True alias_generator = to_camel diff --git a/bookie/src/app/app-routing.module.ts b/bookie/src/app/app-routing.module.ts index a4b73b1..6913db5 100644 --- a/bookie/src/app/app-routing.module.ts +++ b/bookie/src/app/app-routing.module.ts @@ -23,6 +23,10 @@ const routes: Routes = [ loadChildren: () => import('./cashier-report/cashier-report.module').then((mod) => mod.CashierReportModule), }, + { + path: 'customers', + loadChildren: () => import('./customers/customer.module').then((mod) => mod.CustomerModule), + }, { path: 'devices', loadChildren: () => import('./devices/devices.module').then((mod) => mod.DevicesModule), diff --git a/bookie/src/app/core/customer.ts b/bookie/src/app/core/customer.ts new file mode 100644 index 0000000..be92423 --- /dev/null +++ b/bookie/src/app/core/customer.ts @@ -0,0 +1,14 @@ +export class Customer { + id: string; + name: string; + phone: string; + address: string; + + public constructor(init?: Partial) { + this.id = ''; + this.name = ''; + this.phone = ''; + this.address = ''; + Object.assign(this, init); + } +} diff --git a/bookie/src/app/customers/customer-detail/customer-detail.component.css b/bookie/src/app/customers/customer-detail/customer-detail.component.css new file mode 100644 index 0000000..82c7afd --- /dev/null +++ b/bookie/src/app/customers/customer-detail/customer-detail.component.css @@ -0,0 +1,3 @@ +.example-card { + max-width: 400px; +} diff --git a/bookie/src/app/customers/customer-detail/customer-detail.component.html b/bookie/src/app/customers/customer-detail/customer-detail.component.html new file mode 100644 index 0000000..44d9ebc --- /dev/null +++ b/bookie/src/app/customers/customer-detail/customer-detail.component.html @@ -0,0 +1,51 @@ +
+ + + Customer + + +
+
+ + Name + + +
+
+ + Phone + + +
+
+ + Address + + +
+
+
+ + + + +
+
diff --git a/bookie/src/app/customers/customer-detail/customer-detail.component.spec.ts b/bookie/src/app/customers/customer-detail/customer-detail.component.spec.ts new file mode 100644 index 0000000..f2028ed --- /dev/null +++ b/bookie/src/app/customers/customer-detail/customer-detail.component.spec.ts @@ -0,0 +1,26 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { CustomerDetailComponent } from './customer-detail.component'; + +describe('CustomerDetailComponent', () => { + let component: CustomerDetailComponent; + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [CustomerDetailComponent], + }).compileComponents(); + }), + ); + + beforeEach(() => { + fixture = TestBed.createComponent(CustomerDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/bookie/src/app/customers/customer-detail/customer-detail.component.ts b/bookie/src/app/customers/customer-detail/customer-detail.component.ts new file mode 100644 index 0000000..f35f60c --- /dev/null +++ b/bookie/src/app/customers/customer-detail/customer-detail.component.ts @@ -0,0 +1,105 @@ +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { AbstractControl, FormArray, FormBuilder, FormGroup } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { Customer } from '../../core/customer'; +import { ToasterService } from '../../core/toaster.service'; +import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component'; +import { CustomerService } from '../customer.service'; + +@Component({ + selector: 'app-customer-detail', + templateUrl: './customer-detail.component.html', + styleUrls: ['./customer-detail.component.css'], +}) +export class CustomerDetailComponent implements OnInit, AfterViewInit { + @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; + form: FormGroup; + item: Customer = new Customer(); + hide: boolean; + + constructor( + private route: ActivatedRoute, + private router: Router, + private fb: FormBuilder, + private toaster: ToasterService, + private dialog: MatDialog, + private ser: CustomerService, + ) { + this.hide = true; + // Create form + this.form = this.fb.group({ + name: '', + phone: '', + address: '', + }); + } + + ngOnInit() { + this.route.data.subscribe((value) => { + const data = value as { item: Customer }; + this.showItem(data.item); + }); + } + + showItem(item: Customer) { + this.item = item; + (this.form.get('name') as AbstractControl).setValue(item.name); + (this.form.get('phone') as AbstractControl).setValue(item.phone); + (this.form.get('address') as AbstractControl).setValue(item.address); + } + + ngAfterViewInit() { + setTimeout(() => { + if (this.nameElement !== undefined) { + this.nameElement.nativeElement.focus(); + } + }, 0); + } + + save() { + this.ser.saveOrUpdate(this.getItem()).subscribe( + () => { + this.toaster.show('Success', ''); + this.router.navigateByUrl('/customers'); + }, + (error) => { + this.toaster.show('Error', error); + }, + ); + } + + delete() { + this.ser.delete(this.item.id as string).subscribe( + () => { + this.toaster.show('Success', ''); + this.router.navigateByUrl('/customers'); + }, + (error) => { + this.toaster.show('Error', error); + }, + ); + } + + confirmDelete(): void { + const dialogRef = this.dialog.open(ConfirmDialogComponent, { + width: '250px', + data: { title: 'Delete Customer?', content: 'Are you sure? This cannot be undone.' }, + }); + + dialogRef.afterClosed().subscribe((result: boolean) => { + if (result) { + this.delete(); + } + }); + } + + getItem(): Customer { + const formModel = this.form.value; + this.item.name = formModel.name; + this.item.phone = formModel.phone; + this.item.address = formModel.address; + return this.item; + } +} diff --git a/bookie/src/app/customers/customer-list-resolver.service.spec.ts b/bookie/src/app/customers/customer-list-resolver.service.spec.ts new file mode 100644 index 0000000..677c015 --- /dev/null +++ b/bookie/src/app/customers/customer-list-resolver.service.spec.ts @@ -0,0 +1,15 @@ +import { inject, TestBed } from '@angular/core/testing'; + +import { CustomerListResolver } from './customer-list-resolver.service'; + +describe('CustomerListResolver', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [CustomerListResolver], + }); + }); + + it('should be created', inject([CustomerListResolver], (service: CustomerListResolver) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/bookie/src/app/customers/customer-list-resolver.service.ts b/bookie/src/app/customers/customer-list-resolver.service.ts new file mode 100644 index 0000000..9d6cfd0 --- /dev/null +++ b/bookie/src/app/customers/customer-list-resolver.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { Resolve } from '@angular/router'; +import { Observable } from 'rxjs/internal/Observable'; + +import { Customer } from '../core/customer'; + +import { CustomerService } from './customer.service'; + +@Injectable({ + providedIn: 'root', +}) +export class CustomerListResolver implements Resolve { + constructor(private ser: CustomerService) {} + + resolve(): Observable { + return this.ser.list(); + } +} diff --git a/bookie/src/app/customers/customer-list/customer-list-datasource.ts b/bookie/src/app/customers/customer-list/customer-list-datasource.ts new file mode 100644 index 0000000..d5417f2 --- /dev/null +++ b/bookie/src/app/customers/customer-list/customer-list-datasource.ts @@ -0,0 +1,16 @@ +import { DataSource } from '@angular/cdk/collections'; +import { Observable, of as observableOf } from 'rxjs'; + +import { Customer } from '../../core/customer'; + +export class CustomerListDatasource extends DataSource { + constructor(public data: Customer[]) { + super(); + } + + connect(): Observable { + return observableOf(this.data); + } + + disconnect() {} +} diff --git a/bookie/src/app/customers/customer-list/customer-list.component.css b/bookie/src/app/customers/customer-list/customer-list.component.css new file mode 100644 index 0000000..e69de29 diff --git a/bookie/src/app/customers/customer-list/customer-list.component.html b/bookie/src/app/customers/customer-list/customer-list.component.html new file mode 100644 index 0000000..6166185 --- /dev/null +++ b/bookie/src/app/customers/customer-list/customer-list.component.html @@ -0,0 +1,35 @@ + + + Customers + + add_box + Add + + + + + + + Name + {{ row.name }} + + + + + Phone + {{ row.phone }} + + + + + Address + {{ row.address }} + + + + + + + diff --git a/bookie/src/app/customers/customer-list/customer-list.component.ts b/bookie/src/app/customers/customer-list/customer-list.component.ts new file mode 100644 index 0000000..6c5c056 --- /dev/null +++ b/bookie/src/app/customers/customer-list/customer-list.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { Customer } from '../../core/customer'; + +import { CustomerListDatasource } from './customer-list-datasource'; + +@Component({ + selector: 'app-customer-list', + templateUrl: './customer-list.component.html', + styleUrls: ['./customer-list.component.css'], +}) +export class CustomerListComponent implements OnInit { + dataSource: CustomerListDatasource = new CustomerListDatasource([]); + list: Customer[] = []; + /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ + displayedColumns = ['name', 'phone', 'address']; + + constructor(private route: ActivatedRoute) {} + + ngOnInit() { + this.route.data.subscribe((value) => { + const data = value as { list: Customer[] }; + this.list = data.list; + }); + this.dataSource = new CustomerListDatasource(this.list); + } +} diff --git a/bookie/src/app/customers/customer-resolver.service.spec.ts b/bookie/src/app/customers/customer-resolver.service.spec.ts new file mode 100644 index 0000000..ad34cd2 --- /dev/null +++ b/bookie/src/app/customers/customer-resolver.service.spec.ts @@ -0,0 +1,15 @@ +import { inject, TestBed } from '@angular/core/testing'; + +import { CustomerResolver } from './customer-resolver.service'; + +describe('CustomerResolver', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [CustomerResolver], + }); + }); + + it('should be created', inject([CustomerResolver], (service: CustomerResolver) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/bookie/src/app/customers/customer-resolver.service.ts b/bookie/src/app/customers/customer-resolver.service.ts new file mode 100644 index 0000000..a708f73 --- /dev/null +++ b/bookie/src/app/customers/customer-resolver.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; +import { Observable } from 'rxjs/internal/Observable'; + +import { Customer } from '../core/customer'; + +import { CustomerService } from './customer.service'; + +@Injectable({ + providedIn: 'root', +}) +export class CustomerResolver implements Resolve { + constructor(private ser: CustomerService) {} + + resolve(route: ActivatedRouteSnapshot): Observable { + const id = route.paramMap.get('id'); + return this.ser.get(id); + } +} diff --git a/bookie/src/app/customers/customer-routing.module.spec.ts b/bookie/src/app/customers/customer-routing.module.spec.ts new file mode 100644 index 0000000..c487a48 --- /dev/null +++ b/bookie/src/app/customers/customer-routing.module.spec.ts @@ -0,0 +1,13 @@ +import { CustomerRoutingModule } from './customer-routing.module'; + +describe('CustomersRoutingModule', () => { + let customerRoutingModule: CustomerRoutingModule; + + beforeEach(() => { + customerRoutingModule = new CustomerRoutingModule(); + }); + + it('should create an instance', () => { + expect(customerRoutingModule).toBeTruthy(); + }); +}); diff --git a/bookie/src/app/customers/customer-routing.module.ts b/bookie/src/app/customers/customer-routing.module.ts new file mode 100644 index 0000000..2a4e356 --- /dev/null +++ b/bookie/src/app/customers/customer-routing.module.ts @@ -0,0 +1,50 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { AuthGuard } from '../auth/auth-guard.service'; + +import { CustomerDetailComponent } from './customer-detail/customer-detail.component'; +import { CustomerListResolver } from './customer-list-resolver.service'; +import { CustomerListComponent } from './customer-list/customer-list.component'; +import { CustomerResolver } from './customer-resolver.service'; + +const customersRoutes: Routes = [ + { + path: '', + component: CustomerListComponent, + canActivate: [AuthGuard], + data: { + permission: 'Customers', + }, + resolve: { + list: CustomerListResolver, + }, + }, + { + path: 'new', + component: CustomerDetailComponent, + canActivate: [AuthGuard], + data: { + permission: 'Customers', + }, + resolve: { + item: CustomerResolver, + }, + }, + { + path: ':id', + component: CustomerDetailComponent, + canActivate: [AuthGuard], + resolve: { + item: CustomerResolver, + }, + }, +]; + +@NgModule({ + imports: [CommonModule, RouterModule.forChild(customersRoutes)], + exports: [RouterModule], + providers: [CustomerListResolver, CustomerResolver], +}) +export class CustomerRoutingModule {} diff --git a/bookie/src/app/customers/customer.module.spec.ts b/bookie/src/app/customers/customer.module.spec.ts new file mode 100644 index 0000000..fbe9160 --- /dev/null +++ b/bookie/src/app/customers/customer.module.spec.ts @@ -0,0 +1,13 @@ +import { CustomerModule } from './customer.module'; + +describe('CustomerModule', () => { + let customerModuleModule: CustomerModule; + + beforeEach(() => { + customerModuleModule = new CustomerModule(); + }); + + it('should create an instance', () => { + expect(customerModuleModule).toBeTruthy(); + }); +}); diff --git a/bookie/src/app/customers/customer.module.ts b/bookie/src/app/customers/customer.module.ts new file mode 100644 index 0000000..1598480 --- /dev/null +++ b/bookie/src/app/customers/customer.module.ts @@ -0,0 +1,40 @@ +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 { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatTableModule } from '@angular/material/table'; + +import { SharedModule } from '../shared/shared.module'; + +import { CustomerDetailComponent } from './customer-detail/customer-detail.component'; +import { CustomerListComponent } from './customer-list/customer-list.component'; +import { CustomerRoutingModule } from './customer-routing.module'; + +@NgModule({ + imports: [ + CommonModule, + CdkTableModule, + FlexLayoutModule, + MatButtonModule, + MatCardModule, + MatCheckboxModule, + MatDividerModule, + MatIconModule, + MatInputModule, + MatProgressSpinnerModule, + MatTableModule, + ReactiveFormsModule, + SharedModule, + CustomerRoutingModule, + ], + declarations: [CustomerListComponent, CustomerDetailComponent], +}) +export class CustomerModule {} diff --git a/bookie/src/app/customers/customer.service.spec.ts b/bookie/src/app/customers/customer.service.spec.ts new file mode 100644 index 0000000..b5af74d --- /dev/null +++ b/bookie/src/app/customers/customer.service.spec.ts @@ -0,0 +1,15 @@ +import { inject, TestBed } from '@angular/core/testing'; + +import { CustomerService } from './customer.service'; + +describe('CustomerService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [CustomerService], + }); + }); + + it('should be created', inject([CustomerService], (service: CustomerService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/bookie/src/app/customers/customer.service.ts b/bookie/src/app/customers/customer.service.ts new file mode 100644 index 0000000..8718308 --- /dev/null +++ b/bookie/src/app/customers/customer.service.ts @@ -0,0 +1,58 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/internal/Observable'; +import { catchError } from 'rxjs/operators'; + +import { Customer } from '../core/customer'; +import { ErrorLoggerService } from '../core/error-logger.service'; + +const httpOptions = { + headers: new HttpHeaders({ 'Content-Type': 'application/json' }), +}; +const url = '/api/customers'; +const serviceName = 'CustomerService'; + +@Injectable({ + providedIn: 'root', +}) +export class CustomerService { + constructor(private http: HttpClient, private log: ErrorLoggerService) {} + + get(id: string | null): Observable { + const getUrl: string = id === null ? `${url}` : `${url}/${id}`; + return this.http + .get(getUrl) + .pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable; + } + + list(): Observable { + return this.http + .get(`${url}/list`) + .pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable; + } + + save(customer: Customer): Observable { + return this.http + .post(`${url}`, customer, httpOptions) + .pipe(catchError(this.log.handleError(serviceName, 'save'))) as Observable; + } + + update(customer: Customer): Observable { + return this.http + .put(`${url}/${customer.id}`, customer, httpOptions) + .pipe(catchError(this.log.handleError(serviceName, 'update'))) as Observable; + } + + saveOrUpdate(customer: Customer): Observable { + if (!customer.id) { + return this.save(customer); + } + return this.update(customer); + } + + delete(id: string): Observable { + return this.http + .delete(`${url}/${id}`, httpOptions) + .pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable; + } +} 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 93ecf3c..69dab0b 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 @@ -10,7 +10,6 @@ import { Customer } from '../customer'; import { GuestBook } from '../guest-book'; import { GuestBookService } from '../guest-book.service'; - @Component({ selector: 'app-guest-book-detail', templateUrl: './guest-book-detail.component.html', diff --git a/bookie/src/app/home/home.component.html b/bookie/src/app/home/home.component.html index 51330e0..1b2f191 100644 --- a/bookie/src/app/home/home.component.html +++ b/bookie/src/app/home/home.component.html @@ -17,6 +17,15 @@ >

Sales

+ +

Customers

+