72 changed files with 3735 additions and 73 deletions
@ -0,0 +1,24 @@ |
|||||
|
# Ordering |
||||
|
|
||||
|
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.0. |
||||
|
|
||||
|
## Code scaffolding |
||||
|
|
||||
|
Run `ng generate component component-name --project ordering` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project ordering`. |
||||
|
> Note: Don't forget to add `--project ordering` or else it will be added to the default project in your `angular.json` file. |
||||
|
|
||||
|
## Build |
||||
|
|
||||
|
Run `ng build ordering` to build the project. The build artifacts will be stored in the `dist/` directory. |
||||
|
|
||||
|
## Publishing |
||||
|
|
||||
|
After building your library with `ng build ordering`, go to the dist folder `cd dist/ordering` and run `npm publish`. |
||||
|
|
||||
|
## Running unit tests |
||||
|
|
||||
|
Run `ng test ordering` to execute the unit tests via [Karma](https://karma-runner.github.io). |
||||
|
|
||||
|
## Further help |
||||
|
|
||||
|
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", |
||||
|
"dest": "../../dist/ordering/config", |
||||
|
"lib": { |
||||
|
"entryFile": "src/public-api.ts" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
export * from './policy-names'; |
||||
|
export * from './route-names'; |
||||
@ -0,0 +1,6 @@ |
|||||
|
export const enum eOrderingPolicyNames { |
||||
|
ordering = 'OrderingService.Orders', |
||||
|
detail = 'OrderingService.Orders', |
||||
|
setAsShipped = 'OrderingService.SetAsShipped', |
||||
|
setAsCancelled = 'OrderingService.SetAsCancelled', |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
export const enum eOrderingRouteNames { |
||||
|
ordering = 'OrderingService::Menu:OrderManagement' |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { ModuleWithProviders, NgModule } from '@angular/core'; |
||||
|
import { ORDERING_ROUTE_PROVIDERS } from './providers/route.provider'; |
||||
|
|
||||
|
@NgModule() |
||||
|
export class OrderingConfigModule { |
||||
|
static forRoot(): ModuleWithProviders<OrderingConfigModule> { |
||||
|
return { |
||||
|
ngModule: OrderingConfigModule, |
||||
|
providers: [ORDERING_ROUTE_PROVIDERS], |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
export * from './route.provider'; |
||||
@ -0,0 +1,23 @@ |
|||||
|
import { eLayoutType, RoutesService } from '@abp/ng.core'; |
||||
|
import { APP_INITIALIZER } from '@angular/core'; |
||||
|
import { eOrderingPolicyNames } from '../enums/policy-names'; |
||||
|
import { eOrderingRouteNames } from '../enums/route-names'; |
||||
|
|
||||
|
export const ORDERING_ROUTE_PROVIDERS = [ |
||||
|
{ provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true }, |
||||
|
]; |
||||
|
|
||||
|
export function configureRoutes(routesService: RoutesService) { |
||||
|
return () => { |
||||
|
routesService.add([ |
||||
|
{ |
||||
|
path: '/ordering', |
||||
|
name: eOrderingRouteNames.ordering, |
||||
|
layout: eLayoutType.application, |
||||
|
requiredPolicy: eOrderingPolicyNames.ordering, |
||||
|
parentName: null, |
||||
|
iconClass: 'bi bi-collection-fill', |
||||
|
}, |
||||
|
]); |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
export * from './enums'; |
||||
|
export * from './providers'; |
||||
|
export * from './ordering-config.module'; |
||||
@ -0,0 +1,44 @@ |
|||||
|
// Karma configuration file, see link for more information
|
||||
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
|
||||
|
module.exports = function (config) { |
||||
|
config.set({ |
||||
|
basePath: '', |
||||
|
frameworks: ['jasmine', '@angular-devkit/build-angular'], |
||||
|
plugins: [ |
||||
|
require('karma-jasmine'), |
||||
|
require('karma-chrome-launcher'), |
||||
|
require('karma-jasmine-html-reporter'), |
||||
|
require('karma-coverage'), |
||||
|
require('@angular-devkit/build-angular/plugins/karma') |
||||
|
], |
||||
|
client: { |
||||
|
jasmine: { |
||||
|
// you can add configuration options for Jasmine here
|
||||
|
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
|
// for example, you can disable the random execution with `random: false`
|
||||
|
// or set a specific seed with `seed: 4321`
|
||||
|
}, |
||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
|
}, |
||||
|
jasmineHtmlReporter: { |
||||
|
suppressAll: true // removes the duplicated traces
|
||||
|
}, |
||||
|
coverageReporter: { |
||||
|
dir: require('path').join(__dirname, '../../coverage/ordering'), |
||||
|
subdir: '.', |
||||
|
reporters: [ |
||||
|
{ type: 'html' }, |
||||
|
{ type: 'text-summary' } |
||||
|
] |
||||
|
}, |
||||
|
reporters: ['progress', 'kjhtml'], |
||||
|
port: 9876, |
||||
|
colors: true, |
||||
|
logLevel: config.LOG_INFO, |
||||
|
autoWatch: true, |
||||
|
browsers: ['Chrome'], |
||||
|
singleRun: false, |
||||
|
restartOnFileChange: true |
||||
|
}); |
||||
|
}; |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json", |
||||
|
"dest": "../../dist/ordering", |
||||
|
"lib": { |
||||
|
"entryFile": "src/public-api.ts" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
{ |
||||
|
"name": "@eshoponabp/ordering", |
||||
|
"version": "0.0.1", |
||||
|
"peerDependencies": { |
||||
|
"@angular/common": "^12.2.0", |
||||
|
"@angular/core": "^12.2.0" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"tslib": "^2.3.0" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
export * from './order-view-model'; |
||||
|
export * from './proxy'; |
||||
|
export * from './to-order-view-model'; |
||||
@ -0,0 +1,5 @@ |
|||||
|
import { OrderDto } from './proxy/orders'; |
||||
|
|
||||
|
export interface OrderViewModel extends OrderDto { |
||||
|
orderTotal: number; |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
# Proxy Generation Output |
||||
|
|
||||
|
This directory includes the output of the latest proxy generation. |
||||
|
The files and folders in it will be overwritten when proxy generation is run again. |
||||
|
Therefore, please do not place your own content in this folder. |
||||
|
|
||||
|
In addition, `generate-proxy.json` works like a lock file. |
||||
|
It includes information used by the proxy generator, so please do not delete or modify it. |
||||
|
|
||||
|
Finally, the name of the files and folders should not be changed for two reasons: |
||||
|
- Proxy generator will keep creating them at those paths and you will have multiple copies of the same content. |
||||
|
- ABP Suite generates files which include imports from this folder. |
||||
|
|
||||
|
> **Important Notice:** If you are building a module and are planning to publish to npm, |
||||
|
> some of the generated proxies are likely to be exported from public-api.ts file. In such a case, |
||||
|
> please make sure you export files directly and not from barrel exports. In other words, |
||||
|
> do not include index.ts exports in your public-api.ts exports. |
||||
File diff suppressed because it is too large
@ -0,0 +1,2 @@ |
|||||
|
import * as Orders from './orders'; |
||||
|
export { Orders }; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export * from './models'; |
||||
|
export * from './order-status.enum'; |
||||
|
export * from './order.service'; |
||||
@ -0,0 +1,58 @@ |
|||||
|
import type { EntityDto } from '@abp/ng.core'; |
||||
|
import type { OrderStatus } from './order-status.enum'; |
||||
|
|
||||
|
export interface BuyerDto extends EntityDto<string> { |
||||
|
name?: string; |
||||
|
email?: string; |
||||
|
} |
||||
|
|
||||
|
export interface GetMyOrdersInput { |
||||
|
filter?: string; |
||||
|
} |
||||
|
|
||||
|
export interface GetOrdersInput { |
||||
|
filter?: string; |
||||
|
} |
||||
|
|
||||
|
export interface OrderAddressDto { |
||||
|
description?: string; |
||||
|
street: string; |
||||
|
city: string; |
||||
|
country: string; |
||||
|
zipCode: string; |
||||
|
} |
||||
|
|
||||
|
export interface OrderCreateDto { |
||||
|
paymentMethod?: string; |
||||
|
address: OrderAddressDto; |
||||
|
products: OrderItemCreateDto[]; |
||||
|
} |
||||
|
|
||||
|
export interface OrderDto extends EntityDto<string> { |
||||
|
orderDate?: string; |
||||
|
orderNo: number; |
||||
|
orderStatus: OrderStatus; |
||||
|
paymentMethod?: string; |
||||
|
buyer: BuyerDto; |
||||
|
address: OrderAddressDto; |
||||
|
items: OrderItemDto[]; |
||||
|
} |
||||
|
|
||||
|
export interface OrderItemCreateDto { |
||||
|
productId?: string; |
||||
|
productCode?: string; |
||||
|
productName?: string; |
||||
|
pictureUrl?: string; |
||||
|
unitPrice: number; |
||||
|
discount: number; |
||||
|
units: number; |
||||
|
} |
||||
|
|
||||
|
export interface OrderItemDto extends EntityDto<string> { |
||||
|
productId?: string; |
||||
|
productName?: string; |
||||
|
pictureUrl?: string; |
||||
|
unitPrice: number; |
||||
|
discount: number; |
||||
|
units: number; |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
import { mapEnumToOptions } from '@abp/ng.core'; |
||||
|
|
||||
|
export enum OrderStatus { |
||||
|
Placed = 0, |
||||
|
Paid = 1, |
||||
|
Shipped = 2, |
||||
|
Cancelled = 3, |
||||
|
} |
||||
|
|
||||
|
export const orderStatusOptions = mapEnumToOptions(OrderStatus); |
||||
@ -0,0 +1,74 @@ |
|||||
|
import type { GetMyOrdersInput, GetOrdersInput, OrderCreateDto, OrderDto } from './models'; |
||||
|
import { RestService } from '@abp/ng.core'; |
||||
|
import type { PagedAndSortedResultRequestDto, PagedResultDto } from '@abp/ng.core'; |
||||
|
import { Injectable } from '@angular/core'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root', |
||||
|
}) |
||||
|
export class OrderService { |
||||
|
apiName = 'Ordering'; |
||||
|
|
||||
|
create = (input: OrderCreateDto) => |
||||
|
this.restService.request<any, OrderDto>({ |
||||
|
method: 'POST', |
||||
|
url: '/api/ordering/order', |
||||
|
body: input, |
||||
|
}, |
||||
|
{ apiName: this.apiName }); |
||||
|
|
||||
|
get = (id: string) => |
||||
|
this.restService.request<any, OrderDto>({ |
||||
|
method: 'GET', |
||||
|
url: `/api/ordering/order/${id}`, |
||||
|
}, |
||||
|
{ apiName: this.apiName }); |
||||
|
|
||||
|
getByOrderNo = (orderNo: number) => |
||||
|
this.restService.request<any, OrderDto>({ |
||||
|
method: 'GET', |
||||
|
url: '/api/ordering/order/by-order-no', |
||||
|
params: { orderNo }, |
||||
|
}, |
||||
|
{ apiName: this.apiName }); |
||||
|
|
||||
|
getListPaged = (input: PagedAndSortedResultRequestDto) => |
||||
|
this.restService.request<any, PagedResultDto<OrderDto>>({ |
||||
|
method: 'GET', |
||||
|
url: '/api/ordering/order/paged', |
||||
|
params: { sorting: input.sorting, skipCount: input.skipCount, maxResultCount: input.maxResultCount }, |
||||
|
}, |
||||
|
{ apiName: this.apiName }); |
||||
|
|
||||
|
getMyOrders = (input: GetMyOrdersInput) => |
||||
|
this.restService.request<any, OrderDto[]>({ |
||||
|
method: 'GET', |
||||
|
url: '/api/ordering/order/my-orders', |
||||
|
params: { filter: input.filter }, |
||||
|
}, |
||||
|
{ apiName: this.apiName }); |
||||
|
|
||||
|
getOrders = (input: GetOrdersInput) => |
||||
|
this.restService.request<any, OrderDto[]>({ |
||||
|
method: 'GET', |
||||
|
url: '/api/ordering/order/orders', |
||||
|
params: { filter: input.filter }, |
||||
|
}, |
||||
|
{ apiName: this.apiName }); |
||||
|
|
||||
|
setAsCancelled = (id: string) => |
||||
|
this.restService.request<any, void>({ |
||||
|
method: 'POST', |
||||
|
url: `/api/ordering/order/${id}/set-as-cancelled`, |
||||
|
}, |
||||
|
{ apiName: this.apiName }); |
||||
|
|
||||
|
setAsShipped = (id: string) => |
||||
|
this.restService.request<any, void>({ |
||||
|
method: 'POST', |
||||
|
url: `/api/ordering/order/${id}/set-as-shipped`, |
||||
|
}, |
||||
|
{ apiName: this.apiName }); |
||||
|
|
||||
|
constructor(private restService: RestService) {} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
import { OrderDto } from './proxy/orders'; |
||||
|
import { OrderViewModel } from './order-view-model'; |
||||
|
|
||||
|
const mapItem = (x: OrderDto): OrderViewModel => { |
||||
|
const orderTotal = x.items?.reduce((acc, curr) => ( acc + (curr.unitPrice * curr.units)), 0) || 0; |
||||
|
return {...x, orderTotal}; |
||||
|
}; |
||||
|
export const toOrderViewModel = (orders: OrderDto[]) => orders.map(mapItem); |
||||
@ -0,0 +1,13 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { RouterModule, Routes } from '@angular/router'; |
||||
|
|
||||
|
const routes: Routes = [ |
||||
|
{ path: '', redirectTo: 'orders', pathMatch: 'full' }, |
||||
|
{ path: 'orders', loadChildren: () => import('./pages/orders/orders.module').then(m => m.OrdersModule) }, |
||||
|
]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
imports: [RouterModule.forChild(routes)], |
||||
|
exports: [RouterModule], |
||||
|
}) |
||||
|
export class OrderingRoutingModule {} |
||||
@ -0,0 +1,9 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { OrderingRoutingModule } from './ordering-routing.module'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [], |
||||
|
imports: [OrderingRoutingModule], |
||||
|
exports: [], |
||||
|
}) |
||||
|
export class OrderingModule {} |
||||
@ -0,0 +1 @@ |
|||||
|
export * from './orders'; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export * from './orders-routing.module'; |
||||
|
export * from './orders.component'; |
||||
|
export * from './orders.module'; |
||||
@ -0,0 +1,20 @@ |
|||||
|
import { Component, Input } from '@angular/core'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'lib-order-detail-item', |
||||
|
template: ` |
||||
|
<div class="row"> |
||||
|
<div class="col-3"> |
||||
|
{{ label | abpLocalization }} |
||||
|
</div> |
||||
|
<b class="col-9"> |
||||
|
<ng-content></ng-content> |
||||
|
</b> |
||||
|
</div> |
||||
|
`,
|
||||
|
styles: [], |
||||
|
}) |
||||
|
export class OrderDetailItemComponent { |
||||
|
@Input() |
||||
|
label = ''; |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
<abp-modal [visible]="visible" (visibleChange)="visibleChange.emit($event)" [options]="modalOption"> |
||||
|
<ng-template #abpHeader> |
||||
|
<h3>{{ 'OrderingService::ModalTitle' }}</h3> |
||||
|
</ng-template> |
||||
|
|
||||
|
<ng-template #abpBody> |
||||
|
<div *ngIf="order"> |
||||
|
<div class="container"> |
||||
|
<lib-order-detail-item label="OrderingService::DisplayName:OrderNo" |
||||
|
>#{{ order.orderNo }}</lib-order-detail-item |
||||
|
> |
||||
|
<lib-order-detail-item label="OrderingService::DisplayName:OrderStatus">{{ |
||||
|
order.orderStatus |
||||
|
}}</lib-order-detail-item> |
||||
|
<lib-order-detail-item label="OrderingService::DisplayName:BuyerName">{{ |
||||
|
order.buyer.name |
||||
|
}}</lib-order-detail-item> |
||||
|
<lib-order-detail-item label="OrderingService::DisplayName:BuyerEmail">{{ |
||||
|
order.buyer.email |
||||
|
}}</lib-order-detail-item> |
||||
|
|
||||
|
<lib-order-detail-item label="OrderingService::DisplayName:Address"> |
||||
|
<span *ngIf="order.address as address"> |
||||
|
{{ address.description }} <br /> |
||||
|
{{ address.street }} <br /> |
||||
|
{{ address.zipCode }} <br /> |
||||
|
{{ address.city }} / {{ address.country }} <br /> |
||||
|
</span> |
||||
|
</lib-order-detail-item> |
||||
|
<lib-order-detail-item label="OrderingService::DisplayName:PaymentMethod">{{ |
||||
|
order.paymentMethod |
||||
|
}}</lib-order-detail-item> |
||||
|
<lib-order-detail-item label="OrderingService::DisplayName:Total">{{ |
||||
|
order.orderTotal | currency |
||||
|
}}</lib-order-detail-item> |
||||
|
</div> |
||||
|
<br /><br /><br /> |
||||
|
|
||||
|
<ngx-datatable [rows]="order.items" default> |
||||
|
<!-- TODO: localize column headers --> |
||||
|
<ngx-datatable-column |
||||
|
[name]="'OrderingService::DisplayName:ProductId'" |
||||
|
prop="productId" |
||||
|
></ngx-datatable-column> |
||||
|
<ngx-datatable-column [name]="'OrderingService::DisplayName:PictureUrl'" prop="pictureUrl"> |
||||
|
<ng-template let-value="value" ngx-datatable-cell-template> |
||||
|
<img [src]="mediaServerUrl + '/product-images/' + value" width="80" alt="" /> |
||||
|
</ng-template> |
||||
|
</ngx-datatable-column> |
||||
|
<ngx-datatable-column |
||||
|
[name]="'OrderingService::DisplayName:ProductName'" |
||||
|
prop="productName" |
||||
|
></ngx-datatable-column> |
||||
|
<ngx-datatable-column [name]="'OrderingService::DisplayName:UnitPrice'" prop="unitPrice"> |
||||
|
<ng-template let-value="value" ngx-datatable-cell-template> |
||||
|
{{ value | currency }} |
||||
|
</ng-template> |
||||
|
</ngx-datatable-column> |
||||
|
<ngx-datatable-column |
||||
|
[name]="'OrderingService::DisplayName:Units'" |
||||
|
prop="units" |
||||
|
></ngx-datatable-column> |
||||
|
<ngx-datatable-column [name]="'OrderingService::DisplayName:Discount'" prop="discount"> |
||||
|
<ng-template let-value="value" ngx-datatable-cell-template> {{ value }} % </ng-template> |
||||
|
</ngx-datatable-column> |
||||
|
<ngx-datatable-column [name]="'OrderingService::DisplayName:TotalPrice'"> |
||||
|
<ng-template let-row="row" ngx-datatable-cell-template> |
||||
|
{{ row.units * row.unitPrice | currency }} |
||||
|
</ng-template> |
||||
|
</ngx-datatable-column> |
||||
|
</ngx-datatable> |
||||
|
</div> |
||||
|
</ng-template> |
||||
|
|
||||
|
<ng-template #abpFooter> |
||||
|
<button type="button" class="btn btn-secondary" abpClose>{{ 'AbpUi::Close' }}</button> |
||||
|
</ng-template> |
||||
|
</abp-modal> |
||||
@ -0,0 +1,17 @@ |
|||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core'; |
||||
|
import { OrderViewModel } from '../../../lib/order-view-model'; |
||||
|
import { environment } from '../../../../../../src/environments/environment'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'lib-order-detail', |
||||
|
templateUrl: './order-detail.component.html', |
||||
|
}) |
||||
|
export class OrderDetailComponent { |
||||
|
modalOption = { size: 'xl' }; |
||||
|
@Input() |
||||
|
visible: boolean; |
||||
|
@Input() |
||||
|
order: OrderViewModel | undefined; |
||||
|
mediaServerUrl = environment.mediaServerUrl; |
||||
|
@Output() readonly visibleChange = new EventEmitter<boolean>(); |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { RouterModule, Routes } from '@angular/router'; |
||||
|
import { OrdersComponent } from './orders.component'; |
||||
|
|
||||
|
const routes: Routes = [{ path: '', component: OrdersComponent }]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
imports: [RouterModule.forChild(routes)], |
||||
|
exports: [RouterModule] |
||||
|
}) |
||||
|
export class OrdersRoutingModule { } |
||||
@ -0,0 +1,87 @@ |
|||||
|
<div class="card"> |
||||
|
<div class="card-header"> |
||||
|
<div class="row"> |
||||
|
<div class="col col-md-6"> |
||||
|
<h5 class="card-title">{{ 'AbpOrdering::Orders' | abpLocalization }}</h5> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="card-body"> |
||||
|
<ngx-datatable [rows]="items" [count]="count" [list]="list" default> |
||||
|
<!-- TODO: localize column headers --> |
||||
|
<ngx-datatable-column name="" [sortable]="false" prop="id"> |
||||
|
<ng-template let-row="row" ngx-datatable-cell-template> |
||||
|
<div ngbDropdown container="body" class="d-inline-block"> |
||||
|
<button |
||||
|
class="btn btn-primary btn-sm dropdown-toggle" |
||||
|
data-toggle="dropdown" |
||||
|
aria-haspopup="true" |
||||
|
ngbDropdownToggle |
||||
|
> |
||||
|
<i class="mr-1 fa fa-cog"></i>{{ 'AbpUi::Actions' | abpLocalization }} |
||||
|
</button> |
||||
|
<div ngbDropdownMenu> |
||||
|
<button |
||||
|
class="dropdown-item" |
||||
|
(click)="openModal(row)" |
||||
|
*abpPermission="permissions.detail" |
||||
|
> |
||||
|
{{ 'AbpUi::Detail' | abpLocalization }} |
||||
|
</button> |
||||
|
<button |
||||
|
class="dropdown-item" |
||||
|
(click)="setAsShipped(row)" |
||||
|
*abpPermission="permissions.setAsShipped" |
||||
|
> |
||||
|
{{ 'OrderingService::Permission:Orders.SetAsShipped' | abpLocalization }} |
||||
|
</button> |
||||
|
<!-- Todo: Add permissions --> |
||||
|
<button |
||||
|
class="dropdown-item" |
||||
|
*abpPermission="permissions.setAsCancelled" |
||||
|
(click)="setAsCancelled(row)" |
||||
|
> |
||||
|
{{ 'OrderingService::Permission:Orders.SetAsCancelled' | abpLocalization }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-template> |
||||
|
</ngx-datatable-column> |
||||
|
<ngx-datatable-column |
||||
|
[name]="'OrderingService::DisplayName:OrderNo' | abpLocalization" |
||||
|
prop="orderNo" |
||||
|
></ngx-datatable-column> |
||||
|
<ngx-datatable-column |
||||
|
[name]="'OrderingService::DisplayName:OrderDate' | abpLocalization" |
||||
|
prop="orderDate" |
||||
|
> |
||||
|
<ng-template let-value="value" ngx-datatable-cell-template> |
||||
|
<span>{{ value | date }}</span> |
||||
|
</ng-template> |
||||
|
</ngx-datatable-column> |
||||
|
<ngx-datatable-column |
||||
|
[name]="'OrderingService::DisplayName:OrderTotal' | abpLocalization" |
||||
|
prop="orderTotal" |
||||
|
> |
||||
|
<ng-template let-value="value" ngx-datatable-cell-template> |
||||
|
<span>{{ value | currency }}</span> |
||||
|
</ng-template> |
||||
|
</ngx-datatable-column> |
||||
|
<ngx-datatable-column |
||||
|
[name]="'OrderingService::DisplayName:OrderStatus' | abpLocalization" |
||||
|
prop="orderStatus" |
||||
|
> |
||||
|
<ng-template let-value="value" ngx-datatable-cell-template> |
||||
|
{{ 'OrderingService::Enum:OrderStatus:' + value | abpLocalization }} |
||||
|
</ng-template> |
||||
|
</ngx-datatable-column> |
||||
|
</ngx-datatable> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<lib-order-detail |
||||
|
[visible]="isModalVisible" |
||||
|
(visibleChange)="closeModal($event)" |
||||
|
[order]="selectedOrder" |
||||
|
> |
||||
|
</lib-order-detail> |
||||
@ -0,0 +1,25 @@ |
|||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
||||
|
|
||||
|
import { OrdersComponent } from './orders.component'; |
||||
|
|
||||
|
describe('OrdersComponent', () => { |
||||
|
let component: OrdersComponent; |
||||
|
let fixture: ComponentFixture<OrdersComponent>; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
await TestBed.configureTestingModule({ |
||||
|
declarations: [ OrdersComponent ] |
||||
|
}) |
||||
|
.compileComponents(); |
||||
|
}); |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
fixture = TestBed.createComponent(OrdersComponent); |
||||
|
component = fixture.componentInstance; |
||||
|
fixture.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
it('should create', () => { |
||||
|
expect(component).toBeTruthy(); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,83 @@ |
|||||
|
import { Component, OnInit } from '@angular/core'; |
||||
|
import { OrderService } from '../../lib/proxy/orders'; |
||||
|
import { OrderViewModel, toOrderViewModel } from '../../lib'; |
||||
|
import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared'; |
||||
|
import { ListService } from '@abp/ng.core'; |
||||
|
import { eOrderingPolicyNames } from '@eshoponabp/ordering/config'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'lib-orders', |
||||
|
templateUrl: './orders.component.html', |
||||
|
providers: [ListService], |
||||
|
}) |
||||
|
export class OrdersComponent implements OnInit { |
||||
|
constructor( |
||||
|
private service: OrderService, |
||||
|
public list: ListService, |
||||
|
private confirmationService: ConfirmationService |
||||
|
) {} |
||||
|
|
||||
|
selectedOrder: OrderViewModel | undefined; |
||||
|
isModalVisible = false; |
||||
|
items: OrderViewModel[]; |
||||
|
count = 0; |
||||
|
permissions = { |
||||
|
detail: eOrderingPolicyNames.ordering, |
||||
|
setAsShipped: eOrderingPolicyNames.setAsShipped, |
||||
|
setAsCancelled: eOrderingPolicyNames.setAsCancelled, |
||||
|
}; |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
const ordersStreamCreator = query => this.service.getListPaged(query); |
||||
|
|
||||
|
this.list.hookToQuery(ordersStreamCreator).subscribe(response => { |
||||
|
this.items = toOrderViewModel(response.items); |
||||
|
this.count = response.totalCount; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
openModal(order: OrderViewModel) { |
||||
|
if (!order) { |
||||
|
return; |
||||
|
} |
||||
|
this.selectedOrder = order; |
||||
|
this.isModalVisible = true; |
||||
|
} |
||||
|
|
||||
|
closeModal(isVisible: boolean) { |
||||
|
if (isVisible) { |
||||
|
return; |
||||
|
} |
||||
|
this.selectedOrder = null; |
||||
|
this.isModalVisible = false; |
||||
|
} |
||||
|
|
||||
|
setAsShipped(row: OrderViewModel) { |
||||
|
this.confirmationService |
||||
|
.warn('AbpOrdering::WillSetAsShipped', { key: '::AreYouSure', defaultValue: 'Are you sure?' }) |
||||
|
.subscribe(status => { |
||||
|
if (status !== Confirmation.Status.confirm) { |
||||
|
return; |
||||
|
} |
||||
|
this.service.setAsShipped(row.id).subscribe(() => { |
||||
|
this.list.get(); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
setAsCancelled(row: OrderViewModel) { |
||||
|
this.confirmationService |
||||
|
.warn('AbpOrdering::WillSetAsCancelled', { |
||||
|
key: '::AreYouSure', |
||||
|
defaultValue: 'Are you sure?', |
||||
|
}) |
||||
|
.subscribe(status => { |
||||
|
if (status !== Confirmation.Status.confirm) { |
||||
|
return; |
||||
|
} |
||||
|
this.service.setAsCancelled(row.id).subscribe(() => { |
||||
|
this.list.get(); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
|
||||
|
import { OrdersRoutingModule } from './orders-routing.module'; |
||||
|
import { OrdersComponent } from './orders.component'; |
||||
|
import { ThemeSharedModule } from '@abp/ng.theme.shared'; |
||||
|
import { CoreModule } from '@abp/ng.core'; |
||||
|
import { OrderDetailComponent } from './order-detail/order-detail.component'; |
||||
|
import { OrderDetailItemComponent } from './order-detail/order-detail-item/order-detail-item.component'; |
||||
|
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [OrdersComponent, OrderDetailComponent, OrderDetailItemComponent], |
||||
|
imports: [CommonModule, NgbDropdownModule, OrdersRoutingModule, ThemeSharedModule, CoreModule], |
||||
|
}) |
||||
|
export class OrdersModule {} |
||||
@ -0,0 +1,7 @@ |
|||||
|
/* |
||||
|
* Public API Surface of ordering |
||||
|
*/ |
||||
|
|
||||
|
export * from './pages/index'; |
||||
|
export * from './ordering.module'; |
||||
|
export * from './lib/proxy/index'; |
||||
@ -0,0 +1,28 @@ |
|||||
|
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
|
||||
|
import 'zone.js'; |
||||
|
import 'zone.js/testing'; |
||||
|
import { getTestBed } from '@angular/core/testing'; |
||||
|
import { |
||||
|
BrowserDynamicTestingModule, |
||||
|
platformBrowserDynamicTesting |
||||
|
} from '@angular/platform-browser-dynamic/testing'; |
||||
|
|
||||
|
declare const require: { |
||||
|
context(path: string, deep?: boolean, filter?: RegExp): { |
||||
|
keys(): string[]; |
||||
|
<T>(id: string): T; |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
// First, initialize the Angular testing environment.
|
||||
|
getTestBed().initTestEnvironment( |
||||
|
BrowserDynamicTestingModule, |
||||
|
platformBrowserDynamicTesting(), |
||||
|
{ teardown: { destroyAfterEach: true }}, |
||||
|
); |
||||
|
|
||||
|
// Then we find all the tests.
|
||||
|
const context = require.context('./', true, /\.spec\.ts$/); |
||||
|
// And load the modules.
|
||||
|
context.keys().map(context); |
||||
@ -0,0 +1,19 @@ |
|||||
|
{ |
||||
|
"extends": "../../tsconfig.json", |
||||
|
"compilerOptions": { |
||||
|
"outDir": "../../out-tsc/lib", |
||||
|
"target": "es2015", |
||||
|
"declaration": true, |
||||
|
"declarationMap": true, |
||||
|
"inlineSources": true, |
||||
|
"types": [], |
||||
|
"lib": [ |
||||
|
"dom", |
||||
|
"es2018" |
||||
|
] |
||||
|
}, |
||||
|
"exclude": [ |
||||
|
"src/test.ts", |
||||
|
"**/*.spec.ts" |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
{ |
||||
|
"extends": "./tsconfig.lib.json", |
||||
|
"compilerOptions": { |
||||
|
"declarationMap": false |
||||
|
}, |
||||
|
"angularCompilerOptions": { |
||||
|
"compilationMode": "partial" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */ |
||||
|
{ |
||||
|
"extends": "../../tsconfig.json", |
||||
|
"compilerOptions": { |
||||
|
"outDir": "../../out-tsc/spec", |
||||
|
"types": [ |
||||
|
"jasmine" |
||||
|
] |
||||
|
}, |
||||
|
"files": [ |
||||
|
"src/test.ts" |
||||
|
], |
||||
|
"include": [ |
||||
|
"**/*.spec.ts", |
||||
|
"**/*.d.ts" |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
import { Environment } from '@abp/ng.core'; |
||||
|
|
||||
|
export interface MyEnvironment extends Environment { |
||||
|
mediaServerUrl?: string; |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace EShopOnAbp.OrderingService.Orders |
||||
|
{ |
||||
|
public class GetOrdersInput |
||||
|
{ |
||||
|
public string Filter { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -1,12 +1,43 @@ |
|||||
{ |
{ |
||||
"culture": "en", |
"culture": "en", |
||||
"texts": { |
"texts": { |
||||
"MyAccount": "My account", |
"MyAccount": "My account", |
||||
"SamplePageMessage": "A sample page for the OrderingService module", |
"SamplePageMessage": "A sample page for the OrderingService module", |
||||
"Ordering:00001": "Possible values for Order status: {OrderStatus}", |
"Ordering:00001": "Possible values for Order status: {OrderStatus}", |
||||
"Ordering:00002": "Invalid number of units", |
"Ordering:00002": "Invalid number of units", |
||||
"Ordering:00003": "Invalid discount", |
"Ordering:00003": "Invalid discount", |
||||
"Ordering:00004": "The total of order item is lower than applied discount", |
"Ordering:00004": "The total of order item is lower than applied discount", |
||||
"Ordering:01001": "Order with {0} id could not be found!" |
"Ordering:01001": "Order with {0} id could not be found!", |
||||
|
"Permission:Orders": "Orders", |
||||
|
"Permission:OrderingService": "Ordering Service", |
||||
|
"Menu:Orders": "Orders", |
||||
|
"Menu:OrderManagement": "Order Management", |
||||
|
|
||||
|
"ModalTitle":"Order Detail", |
||||
|
|
||||
|
"DisplayName:OrderNo": "Order No", |
||||
|
"DisplayName:OrderDate": "Order Date", |
||||
|
"DisplayName:OrderTotal": "Order Total", |
||||
|
"DisplayName:OrderStatus": "Order Status", |
||||
|
"DisplayName:ProductId": "Product Id", |
||||
|
"DisplayName:ProductName": "Product Name", |
||||
|
"DisplayName:PictureUrl": "Picture", |
||||
|
"DisplayName:Units": "Unit", |
||||
|
"DisplayName:UnitPrice": "Unit Price", |
||||
|
"DisplayName:Discount": "Discount", |
||||
|
"DisplayName:TotalPrice": "Total Price", |
||||
|
"DisplayName:BuyerName": "Buyer Name", |
||||
|
"DisplayName:BuyerEmail": "Buyer E-mail", |
||||
|
"DisplayName:Address": "Address", |
||||
|
"DisplayName:PaymentMethod": "Payment Method", |
||||
|
"DisplayName:Total": "Total", |
||||
|
|
||||
|
"Permission:Orders.SetAsCancelled": "Set As Cancelled", |
||||
|
"Permission:Orders.SetAsShipped": "Set As Shipped", |
||||
|
|
||||
|
"Enum:OrderStatus:0":"Placed", |
||||
|
"Enum:OrderStatus:1":"Paid", |
||||
|
"Enum:OrderStatus:2":"Shipped", |
||||
|
"Enum:OrderStatus:3":"Cancelled" |
||||
} |
} |
||||
} |
} |
||||
@ -1,12 +1,43 @@ |
|||||
{ |
{ |
||||
"culture": "tr", |
"culture": "tr", |
||||
"texts": { |
"texts": { |
||||
"MyAccount": "Hesabım", |
"MyAccount": "Hesabım", |
||||
"SamplePageMessage": "OrderingService modulünden örnek bir sayfa", |
"SamplePageMessage": "OrderingService modulünden örnek bir sayfa", |
||||
"Ordering:00001": "Muhtemel Sipariş durum değerleri: {OrderStatus}", |
"Ordering:00001": "Muhtemel Sipariş durum değerleri: {OrderStatus}", |
||||
"Ordering:00002": "Ürün adedi negatif olamaz", |
"Ordering:00002": "Ürün adedi negatif olamaz", |
||||
"Ordering:00003": "Geçersiz indirim", |
"Ordering:00003": "Geçersiz indirim", |
||||
"Ordering:00004": "Ürünlerin toplam bedeli, uygulanan indirimden daha az olamaz", |
"Ordering:00004": "Ürünlerin toplam bedeli, uygulanan indirimden daha az olamaz", |
||||
"Ordering:01001": "{0} ID numaralı ürün bulunamadı!" |
"Ordering:01001": "{0} ID numaralı ürün bulunamadı!", |
||||
|
"Permission:Orders": "Siparişler", |
||||
|
"Permission:OrderingService": "Sipariş Servisi", |
||||
|
"Menu:Orders": "Siparişler", |
||||
|
"Menu:OrderManagement": "Sipariş Yönetimi", |
||||
|
|
||||
|
"ModalTitle": "Sipariş Detayı", |
||||
|
|
||||
|
"DisplayName:OrderNo": "Sipariş Numarası", |
||||
|
"DisplayName:OrderDate": "Sipariş Tarihi", |
||||
|
"DisplayName:OrderTotal": "Sipariş Toplamı", |
||||
|
"DisplayName:OrderStatus": "Sipariş Durumu", |
||||
|
"DisplayName:ProductId": "Ürün Anahtarı", |
||||
|
"DisplayName:ProductName": "Ürün Adı", |
||||
|
"DisplayName:PictureUrl": "Resim", |
||||
|
"DisplayName:Units": "Adet", |
||||
|
"DisplayName:UnitPrice": "Adet Fiyatı", |
||||
|
"DisplayName:Discount": "İndirim", |
||||
|
"DisplayName:TotalPrice": "Toplam Fiyat", |
||||
|
"DisplayName:BuyerName": "Alan Kişi", |
||||
|
"DisplayName:BuyerEmail": "Alan Kişi Mail", |
||||
|
"DisplayName:Address": "Adres", |
||||
|
"DisplayName:PaymentMethod": "Ödeme Şekli", |
||||
|
"DisplayName:Total": "Toplam", |
||||
|
|
||||
|
"Permission:Orders.SetAsCancelled": "İptal Edildi Olarak İşaretle", |
||||
|
"Permission:Orders.SetAsShipped": "Sevk Edildi Olarak İşaretle", |
||||
|
|
||||
|
"Enum:OrderStatus:0": "Oluşturuldu", |
||||
|
"Enum:OrderStatus:1": "Ödendi", |
||||
|
"Enum:OrderStatus:2": "Sevk Edildi", |
||||
|
"Enum:OrderStatus:3": "İptal Edildi" |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Domain.Entities.Events.Distributed; |
||||
|
using Volo.Abp.EventBus; |
||||
|
|
||||
|
namespace EShopOnAbp.OrderingService.Orders; |
||||
|
|
||||
|
[EventName("EShopOnAbp.Order.Cancelled")] |
||||
|
public class OrderCancelledEto : EtoBase |
||||
|
{ |
||||
|
public Guid PaymentRequestId { get; set; } |
||||
|
public Guid OrderId { get; set; } |
||||
|
public int OrderNo { get; set; } |
||||
|
public DateTime OrderDate { get; set; } |
||||
|
public BuyerEto Buyer { get; set; } |
||||
|
public List<OrderItemEto> Items { get; set; } = new(); |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using EShopOnAbp.OrderingService.Orders; |
||||
|
using EShopOnAbp.PaymentService.PaymentRequests; |
||||
|
using Serilog; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.EventBus.Distributed; |
||||
|
|
||||
|
|
||||
|
namespace EShopOnAbp.PaymentService.EventHandlers |
||||
|
{ |
||||
|
public class OrderCancelledEventHandler : IDistributedEventHandler<OrderCancelledEto>, ITransientDependency |
||||
|
{ |
||||
|
private readonly IPaymentRequestRepository _paymentRepository; |
||||
|
|
||||
|
public OrderCancelledEventHandler(IPaymentRequestRepository paymenRepository) |
||||
|
{ |
||||
|
_paymentRepository = paymenRepository; |
||||
|
} |
||||
|
|
||||
|
public async Task HandleEventAsync(OrderCancelledEto eventData) |
||||
|
{ |
||||
|
var payment = await _paymentRepository.GetAsync(eventData.PaymentRequestId); |
||||
|
Log.Information($"Cancelled the order: {payment.OrderId} payment:{payment.Id}"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue