update pos consumer
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
<div class="flex gap-3 overflow-auto">
|
||||
@if (loading()) {
|
||||
@for (i of [1, 2, 3, 4, 5]; track i) {
|
||||
<p-skeleton width="6rem" height="2.5rem" />
|
||||
}
|
||||
} @else {
|
||||
@for (category of categories(); track category.id) {
|
||||
<div
|
||||
[class]="
|
||||
'h-10 rounded-lg px-3 flex items-center gap-3 border border-surface-border cursor-pointer bg-surface-100 hover:bg-surface-200 transition-all' +
|
||||
(selectedCategory() === category.id ? ' bg-surface-card' : '')
|
||||
"
|
||||
(click)="changeSelectedCategory(category)"
|
||||
>
|
||||
<span class="text-sm text-muted-color font-bold"> {{ category.name }}</span>
|
||||
<div
|
||||
[class]="
|
||||
'flex items-center justify-center py-0.5 px-2 rounded-sm bg-surface-300 text-xs text-muted-color' +
|
||||
(selectedCategory() === category.id ? ' bg-amber-400! text-white' : '')
|
||||
"
|
||||
>
|
||||
<span>
|
||||
{{ category.goods_count }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Component, EventEmitter, inject, Output } from '@angular/core';
|
||||
import { Skeleton } from 'primeng/skeleton';
|
||||
import { PosLandingStore } from '../store/main.store';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-good-categories',
|
||||
templateUrl: './categories.component.html',
|
||||
imports: [Skeleton],
|
||||
})
|
||||
export class PosGoodCategoriesComponent {
|
||||
@Output() categoryChange = new EventEmitter<string>();
|
||||
|
||||
private readonly store = inject(PosLandingStore);
|
||||
|
||||
categories = this.store.goodCategories;
|
||||
loading = this.store.getGoodCategoriesLoading;
|
||||
|
||||
selectedCategory = this.store.activeGoodCategory;
|
||||
|
||||
changeSelectedCategory(category: { id: string; name: string }) {
|
||||
this.store.changeActiveCategory(category.id);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<p-dialog [(visible)]="visible" header="اطلاعات مشتری" [modal]="true" [style]="{ width: '560px' }">
|
||||
<div class="card p-2! flex justify-center">
|
||||
<p-selectbutton [options]="customerTypes" [(ngModel)]="selectedCustomerType" optionValue="value">
|
||||
<ng-template #item let-item>
|
||||
<span class="text-sm">{{ item.label }}</span>
|
||||
</ng-template>
|
||||
</p-selectbutton>
|
||||
</div>
|
||||
@if (selectedCustomerType() === "UNKNOWN") {
|
||||
<pos-customer-unknown-form (onSubmit)="onSubmitCustomer()" (onClose)="close()" />
|
||||
} @else if (selectedCustomerType() === "INDIVIDUAL") {
|
||||
<pos-customer-individual-form (onSubmit)="onSubmitCustomer()" (onClose)="close()" />
|
||||
} @else if (selectedCustomerType() === "LEGAL") {
|
||||
<pos-customer-legal-form (onSubmit)="onSubmitCustomer()" (onClose)="close()" />
|
||||
}
|
||||
</p-dialog>
|
||||
@@ -0,0 +1,53 @@
|
||||
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
||||
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
||||
import { Component, EventEmitter, inject, Output, signal } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Dialog } from 'primeng/dialog';
|
||||
import { SelectButton } from 'primeng/selectbutton';
|
||||
import { PosLandingStore } from '../../store/main.store';
|
||||
import { CustomerIndividualFormComponent } from './individual/form.component';
|
||||
import { CustomerLegalFormComponent } from './legal/form.component';
|
||||
import { CustomerUnknownFormComponent } from './unknown/form.component';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-order-customer-dialog',
|
||||
templateUrl: './customer-dialog.component.html',
|
||||
imports: [
|
||||
Dialog,
|
||||
CustomerIndividualFormComponent,
|
||||
FormsModule,
|
||||
CustomerLegalFormComponent,
|
||||
SelectButton,
|
||||
CustomerUnknownFormComponent,
|
||||
],
|
||||
})
|
||||
export class PosOrderCustomerDialogComponent extends AbstractDialog {
|
||||
@Output() onSubmit = new EventEmitter<any>();
|
||||
|
||||
private readonly store = inject(PosLandingStore);
|
||||
|
||||
customerTypes = [
|
||||
{
|
||||
label: 'بدون اطلاعات خریدار (نوع دوم)',
|
||||
value: 'UNKNOWN',
|
||||
},
|
||||
{
|
||||
label: 'خریدار حقیقی (نوع اول)',
|
||||
value: 'INDIVIDUAL',
|
||||
},
|
||||
{
|
||||
label: 'خریدار حقوقی (نوع اول)',
|
||||
value: 'LEGAL',
|
||||
},
|
||||
] as {
|
||||
label: string;
|
||||
value: CustomerType;
|
||||
}[];
|
||||
|
||||
selectedCustomerType = signal<CustomerType>(this.store.customer().type);
|
||||
|
||||
onSubmitCustomer() {
|
||||
this.close();
|
||||
this.onSubmit.emit();
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<form [formGroup]="form" (submit)="submit()">
|
||||
<app-input [control]="form.controls.first_name" name="first_name" label="نام" />
|
||||
<app-input [control]="form.controls.last_name" name="last_name" label="نام خانوادگی" />
|
||||
<app-input [control]="form.controls.national_id" name="national_id" label="کد ملی" type="nationalId" />
|
||||
<app-input
|
||||
[control]="form.controls.economic_code"
|
||||
name="economic_code"
|
||||
label="کد اقتصادی"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
/>
|
||||
|
||||
<app-form-footer-actions submitLabel="ثبت مشتری و ادامه" (onCancel)="close()" (onSubmit)="submit()" />
|
||||
</form>
|
||||
@@ -0,0 +1,61 @@
|
||||
import { postalCodeValidator } from '@/core/validators';
|
||||
import { nationalIdValidator } from '@/core/validators/national-id.validator';
|
||||
import { AbstractForm } from '@/shared/abstractClasses';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { Observable } from 'rxjs';
|
||||
import { IIndividualCustomer } from '../../../models/customer';
|
||||
import { PosLandingStore } from '../../../store/main.store';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-customer-individual-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [ReactiveFormsModule, InputComponent, FormFooterActionsComponent],
|
||||
})
|
||||
export class CustomerIndividualFormComponent extends AbstractForm<
|
||||
IIndividualCustomer,
|
||||
IIndividualCustomer
|
||||
> {
|
||||
private readonly store = inject(PosLandingStore);
|
||||
|
||||
override showSuccessMessage = false;
|
||||
form = this.fb.group({
|
||||
first_name: [
|
||||
this.store.customer().info?.customerIndividuals?.first_name || '',
|
||||
[Validators.required],
|
||||
],
|
||||
last_name: [
|
||||
this.store.customer().info?.customerIndividuals?.last_name || '',
|
||||
[Validators.required],
|
||||
],
|
||||
national_id: [
|
||||
this.store.customer().info?.customerIndividuals?.national_id || '',
|
||||
[Validators.required, nationalIdValidator()],
|
||||
],
|
||||
economic_code: [
|
||||
this.store.customer().info?.customerIndividuals?.economic_code || '',
|
||||
[Validators.required],
|
||||
],
|
||||
postal_code: [
|
||||
this.store.customer().info?.customerIndividuals?.postal_code || '',
|
||||
[postalCodeValidator()],
|
||||
],
|
||||
});
|
||||
|
||||
override submitForm() {
|
||||
return new Observable<IIndividualCustomer>((observer) => {
|
||||
const info = this.form.value as IIndividualCustomer;
|
||||
this.store.setCustomer({
|
||||
customer_id: '',
|
||||
type: CustomerType.INDIVIDUAL,
|
||||
info: {
|
||||
customerIndividuals: info,
|
||||
},
|
||||
});
|
||||
return observer.next(info);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<form [formGroup]="form" (submit)="submit()">
|
||||
<app-input [control]="form.controls.company_name" name="company_name" label="نام مجموعه" />
|
||||
<app-input [control]="form.controls.registration_number" name="registration_number" label="شماره ثبت" />
|
||||
<app-input
|
||||
[control]="form.controls.economic_code"
|
||||
name="economic_code"
|
||||
label="کد اقتصادی"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
/>
|
||||
<app-input [control]="form.controls.postal_code" name="postal_code" label="کد پستی" type="postalCode" />
|
||||
|
||||
<app-form-footer-actions submitLabel="ثبت مشتری و ادامه" (onCancel)="close()" (onSubmit)="submit()" />
|
||||
</form>
|
||||
@@ -0,0 +1,54 @@
|
||||
import { postalCodeValidator } from '@/core/validators';
|
||||
import { AbstractForm } from '@/shared/abstractClasses';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ILegalCustomer } from '../../../models/customer';
|
||||
import { PosLandingStore } from '../../../store/main.store';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-customer-legal-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [ReactiveFormsModule, InputComponent, FormFooterActionsComponent],
|
||||
})
|
||||
export class CustomerLegalFormComponent extends AbstractForm<ILegalCustomer, ILegalCustomer> {
|
||||
private readonly store = inject(PosLandingStore);
|
||||
|
||||
override showSuccessMessage = false;
|
||||
|
||||
form = this.fb.group({
|
||||
company_name: [
|
||||
this.store.customer().info?.customerLegals?.company_name || '',
|
||||
[Validators.required],
|
||||
],
|
||||
registration_number: [
|
||||
this.store.customer().info?.customerLegals?.registration_number || '',
|
||||
[Validators.required],
|
||||
],
|
||||
economic_code: [
|
||||
this.store.customer().info?.customerLegals?.economic_code || '',
|
||||
[Validators.required],
|
||||
],
|
||||
postal_code: [
|
||||
this.store.customer().info?.customerLegals?.postal_code || '',
|
||||
[postalCodeValidator()],
|
||||
],
|
||||
});
|
||||
|
||||
override submitForm() {
|
||||
return new Observable<ILegalCustomer>((observer) => {
|
||||
const info = this.form.value as ILegalCustomer;
|
||||
this.store.setCustomer({
|
||||
customer_id: '',
|
||||
type: CustomerType.LEGAL,
|
||||
info: {
|
||||
customerLegals: info,
|
||||
},
|
||||
});
|
||||
return observer.next(info);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<form [formGroup]="form" (submit)="submit()">
|
||||
<app-input [control]="form.controls.national_id" name="national_id" label="شماره ملی" type="nationalId" />
|
||||
<app-input [control]="form.controls.name" name="name" label="نام" />
|
||||
<app-form-footer-actions submitLabel="ثبت بدون اطلاعات خریدار و ادامه" (onCancel)="close()" />
|
||||
</form>
|
||||
@@ -0,0 +1,42 @@
|
||||
import { nationalIdValidator } from '@/core/validators/national-id.validator';
|
||||
import { AbstractForm } from '@/shared/abstractClasses';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { Observable } from 'rxjs';
|
||||
import { IUnknownCustomer } from '../../../models/customer';
|
||||
import { PosLandingStore } from '../../../store/main.store';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-customer-unknown-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [ReactiveFormsModule, InputComponent, FormFooterActionsComponent],
|
||||
})
|
||||
export class CustomerUnknownFormComponent extends AbstractForm<IUnknownCustomer, IUnknownCustomer> {
|
||||
private readonly store = inject(PosLandingStore);
|
||||
|
||||
override showSuccessMessage = false;
|
||||
form = this.fb.group({
|
||||
national_id: [
|
||||
this.store?.customer().info?.customerUnknown?.national_id || '',
|
||||
[nationalIdValidator()],
|
||||
],
|
||||
name: [this.store?.customer().info?.customerUnknown?.name || ''],
|
||||
});
|
||||
|
||||
override submitForm() {
|
||||
return new Observable<IUnknownCustomer>((observer) => {
|
||||
const info = this.form.value as IUnknownCustomer;
|
||||
this.store.setCustomer({
|
||||
customer_id: '',
|
||||
type: CustomerType.UNKNOWN,
|
||||
info: {
|
||||
customerUnknown: info,
|
||||
},
|
||||
});
|
||||
return observer.next(info);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<div class="flex flex-col">
|
||||
<div class="sticky top-0 bg-surface-ground z-10 pb-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2 text-muted-color">
|
||||
<i class="pi pi-list"></i>
|
||||
<span class="text-base font-bold">لیست کالاها</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<app-search-input (search)="onSearch($event)"></app-search-input>
|
||||
|
||||
<div class="card p-2! flex justify-center">
|
||||
<p-selectbutton [options]="viewOptions" [(ngModel)]="viewType" optionValue="value">
|
||||
<ng-template #item let-item>
|
||||
<i [class]="item.icon"></i>
|
||||
</ng-template>
|
||||
</p-selectbutton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pos-good-categories (categoryChange)="onCategoryChange($event)" />
|
||||
</div>
|
||||
@if (viewType === "grid") {
|
||||
<pos-good-grid-view (onAdd)="addGood($event)" />
|
||||
} @else {
|
||||
<pos-goods-list-view (onAdd)="addGood($event)" />
|
||||
}
|
||||
|
||||
@if (selectedGoodToAdd()) {
|
||||
<pos-payload-form-dialog
|
||||
[(visible)]="showPayloadForm"
|
||||
[good]="selectedGoodToAdd()!"
|
||||
(onClose)="onClosePayloadForm()"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { IGoodResponse } from '@/domains/pos/models/good.io';
|
||||
import { SearchInputComponent } from '@/shared/components/search/search-input.component';
|
||||
import { Component, computed, inject, Input, signal } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { SelectButton } from 'primeng/selectbutton';
|
||||
import { PosLandingStore, TViewType } from '../store/main.store';
|
||||
import { PosGoodCategoriesComponent } from './categories.component';
|
||||
import { PosGoodsGridViewComponent } from './grid-view.component';
|
||||
import { PosGoodsListViewComponent } from './list-view.component';
|
||||
import { PayloadFormDialogComponent } from './payloads';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-goods',
|
||||
templateUrl: './goods.component.html',
|
||||
imports: [
|
||||
PosGoodCategoriesComponent,
|
||||
PosGoodsListViewComponent,
|
||||
SelectButton,
|
||||
FormsModule,
|
||||
PosGoodsGridViewComponent,
|
||||
SearchInputComponent,
|
||||
PayloadFormDialogComponent,
|
||||
],
|
||||
})
|
||||
export class PosGoodsComponent {
|
||||
@Input() activeCategory = signal<Maybe<string>>(null);
|
||||
|
||||
private readonly store = inject(PosLandingStore);
|
||||
|
||||
constructor() {
|
||||
this.store.initial();
|
||||
}
|
||||
|
||||
viewOptions = [
|
||||
{ value: 'list', icon: 'pi pi-list' },
|
||||
{ value: 'grid', icon: 'pi pi-th-large' },
|
||||
];
|
||||
|
||||
showPayloadForm = signal(false);
|
||||
selectedGoodToAdd = signal<Maybe<IGoodResponse>>(null);
|
||||
|
||||
readonly activatedCategoryProducts = computed(() => this.store.activatedCategoryGoods());
|
||||
readonly stock = computed(() => this.store.goods());
|
||||
|
||||
get viewType() {
|
||||
return this.store.viewType();
|
||||
}
|
||||
set viewType(val: TViewType) {
|
||||
this.store.updateViewType(val);
|
||||
}
|
||||
|
||||
onSearch(searchTerm: string) {
|
||||
this.store.updateSearchQuery(searchTerm);
|
||||
this.store.getGoods();
|
||||
}
|
||||
|
||||
onCategoryChange(categoryId: string) {
|
||||
this.store.changeActiveCategory(categoryId);
|
||||
}
|
||||
|
||||
addGood(good: IGoodResponse) {
|
||||
this.showPayloadForm.set(true);
|
||||
|
||||
this.selectedGoodToAdd.set(good);
|
||||
}
|
||||
|
||||
onClosePayloadForm() {
|
||||
this.selectedGoodToAdd.set(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<div class="grid 2xl:grid-cols-8 xl:grid-cols-6 lg:grid-cols-4 grid-cols-2 gap-3 flex-wrap">
|
||||
@if (loading()) {
|
||||
@for (_ of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15]; track $index) {
|
||||
<div class="flex-1 aspect-[0.9]">
|
||||
<p-skeleton width="100%" height="100%"></p-skeleton>
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
@for (good of goods(); track good.id) {
|
||||
<div class="flex-1 bg-surface-card rounded-lg p-1 shadow-xs">
|
||||
<img [src]="goodPlaceholder" class="w-full aspect-[1.3] object-cover rounded-md" />
|
||||
<div class="mt-2">
|
||||
<span class="text-lg font-bold">
|
||||
{{ good.name }}
|
||||
<!-- <small class="text-xs text-muted-color">({{ good.quantity }})</small> -->
|
||||
</span>
|
||||
<div class="flex items-center justify-between mt-1">
|
||||
<!-- <span [appPriceMask]="good.salePrice" class="text-base font-bold"></span> -->
|
||||
<button pButton type="button" icon="pi pi-plus" size="small" (click)="addProduct(good)"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { IGoodResponse } from '@/domains/pos/models/good.io';
|
||||
import { Component, computed, EventEmitter, inject, Output } from '@angular/core';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import { Skeleton } from 'primeng/skeleton';
|
||||
import images from 'src/assets/images';
|
||||
import { PosLandingStore } from '../store/main.store';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-good-grid-view',
|
||||
templateUrl: './grid-view.component.html',
|
||||
imports: [ButtonDirective, Skeleton],
|
||||
})
|
||||
export class PosGoodsGridViewComponent {
|
||||
private readonly store = inject(PosLandingStore);
|
||||
@Output() onAdd = new EventEmitter<IGoodResponse>();
|
||||
|
||||
goods = computed(() => this.store.activatedCategoryGoods());
|
||||
loading = computed(() => this.store.getGoodsLoading());
|
||||
|
||||
goodPlaceholder = images.placeholders.default;
|
||||
|
||||
addProduct(good: IGoodResponse) {
|
||||
this.onAdd.emit(good);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<div class="flex flex-col gap-3 flex-wrap">
|
||||
@if (loading()) {
|
||||
@for (_ of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15]; track $index) {
|
||||
<div class="w-100 h-12">
|
||||
<p-skeleton width="100%" height="100%"></p-skeleton>
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
@for (good of goods(); track good.id) {
|
||||
<div class="flex items-center bg-surface-card rounded-lg p-1 shadow-xs gap-2">
|
||||
<div class="grow flex items-center gap-2">
|
||||
<img [src]="goodPlaceholder" class="w-12 h-12 object-cover rounded-md" />
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-lg font-bold">
|
||||
{{ good.name }}
|
||||
<!-- <small class="text-xs text-muted-color">({{ good.quantity }})</small> -->
|
||||
</span>
|
||||
<span class="text-sm text-muted-color">
|
||||
{{ good.sku }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0 flex items-center justify-between gap-3">
|
||||
<!-- <span [appPriceMask]="good.salePrice" class="text-base font-bold"></span> -->
|
||||
<button pButton type="button" icon="pi pi-plus" size="small" (click)="addGood(good)"></button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { IGoodResponse } from '@/domains/pos/models/good.io';
|
||||
import { Component, computed, EventEmitter, inject, Output } from '@angular/core';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import { Skeleton } from 'primeng/skeleton';
|
||||
import images from 'src/assets/images';
|
||||
import { PosLandingStore } from '../store/main.store';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-goods-list-view',
|
||||
templateUrl: './list-view.component.html',
|
||||
imports: [ButtonDirective, Skeleton],
|
||||
})
|
||||
export class PosGoodsListViewComponent {
|
||||
private readonly store = inject(PosLandingStore);
|
||||
@Output() onAdd = new EventEmitter<IGoodResponse>();
|
||||
|
||||
goods = computed(() => this.store.activatedCategoryGoods());
|
||||
loading = computed(() => this.store.getGoodsLoading());
|
||||
|
||||
goodPlaceholder = images.placeholders.default;
|
||||
|
||||
addGood(good: IGoodResponse) {
|
||||
this.onAdd.emit(good);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<div class="flex items-center gap-2 justify-between">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold text-lg">سفارش #{{ heldOrder.orderNumber }}</span>
|
||||
<span class="text-sm text-muted-color">
|
||||
{{ heldOrder.orderItems.length }} کالا -
|
||||
<span [appPriceMask]="heldOrder.totalAmount"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
pButton
|
||||
size="small"
|
||||
type="button"
|
||||
label="بارگذاری"
|
||||
icon="pi pi-download"
|
||||
(click)="loadHeldOrder(heldOrder.id)"
|
||||
></button>
|
||||
<button
|
||||
pButton
|
||||
size="small"
|
||||
type="button"
|
||||
label="حذف"
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
(click)="cancelHeldOrder(heldOrder.id)"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,57 @@
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
import { IPosHeldOrderResponse } from '@/modules/pos/models';
|
||||
import { ConfirmationDialogService } from '@/shared/components/confirmationDialog/confirmation-dialog.service';
|
||||
import { PriceMaskDirective } from '@/shared/directives';
|
||||
import { Component, EventEmitter, Input, Output, signal } from '@angular/core';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import { PosService } from '../../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-held-order-item',
|
||||
templateUrl: './held-order-item.component.html',
|
||||
imports: [PriceMaskDirective, ButtonDirective],
|
||||
})
|
||||
export class HeldOrderItemComponent {
|
||||
@Input() heldOrder!: IPosHeldOrderResponse;
|
||||
@Input() posId!: number;
|
||||
|
||||
@Output() onCancel = new EventEmitter<number>();
|
||||
@Output() onLoad = new EventEmitter<number>();
|
||||
|
||||
constructor(
|
||||
private readonly posService: PosService,
|
||||
private readonly confirmationService: ConfirmationDialogService,
|
||||
private readonly toastService: ToastService,
|
||||
) {}
|
||||
|
||||
actionLoading = signal<boolean>(false);
|
||||
|
||||
cancelHeldOrder(heldOrderId: number) {
|
||||
this.confirmationService.confirm({
|
||||
message: `آیا از لغو سفارش شمارهی #${this.heldOrder.orderNumber} اطمینان دارید؟`,
|
||||
header: 'لغو سفارش موقت',
|
||||
acceptLabel: 'بله',
|
||||
rejectLabel: 'خیر',
|
||||
accept: () => {
|
||||
this.actionLoading.set(true);
|
||||
// this.posService.cancelOrder(this.posId, heldOrderId).subscribe({
|
||||
// next: () => {
|
||||
// this.onCancel.emit(heldOrderId);
|
||||
// this.actionLoading.set(false);
|
||||
// this.toastService.success({
|
||||
// text: `سفارش شمارهی #${this.heldOrder.orderNumber} با موفقیت لغو شد.`,
|
||||
// });
|
||||
// },
|
||||
// error: () => {
|
||||
// this.actionLoading.set(false);
|
||||
// },
|
||||
// });
|
||||
},
|
||||
reject: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
loadHeldOrder(heldOrderId: number) {
|
||||
this.onLoad.emit(heldOrderId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<div class="d-flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between shrink-0">
|
||||
<div class="flex items-center gap-2 text-muted-color">
|
||||
<i class="pi pi-shopping-cart"></i>
|
||||
<span class="text-base font-bold">سفارشهای نگهداشته شده</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (!heldOrders()?.length) {
|
||||
<div class="grow flex flex-col gap-5 justify-center items-center text-center text-muted-color">
|
||||
<span class="text-base text-muted-color pt-4">هیچ سفارش نگهداشته شدهای وجود ندارد.</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="flex flex-col gap-2 mt-3">
|
||||
@for (heldOrder of heldOrders(); track heldOrder.id) {
|
||||
<app-held-order-item [heldOrder]="heldOrder" (onCancel)="removeHeldOrder($event)" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { IPosHeldOrderResponse } from '@/modules/pos/models';
|
||||
import { Component, inject, signal } from '@angular/core';
|
||||
import { PosService } from '../../services/main.service';
|
||||
import { HeldOrderItemComponent } from './held-order-item.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-held-orders',
|
||||
templateUrl: './held-orders.component.html',
|
||||
imports: [HeldOrderItemComponent],
|
||||
})
|
||||
export class HeldOrdersComponent {
|
||||
private readonly service = inject(PosService);
|
||||
|
||||
heldOrders = signal<Maybe<IPosHeldOrderResponse[]>>(null);
|
||||
loading = signal<boolean>(false);
|
||||
|
||||
ngOnInit() {
|
||||
this.getHeldOrders();
|
||||
}
|
||||
|
||||
getHeldOrders() {
|
||||
this.loading.set(true);
|
||||
// this.service.getHeldOrders().subscribe({
|
||||
// next: (res) => {
|
||||
// this.heldOrders.set(res.data);
|
||||
// this.loading.set(false);
|
||||
// },
|
||||
// error: () => {
|
||||
// this.loading.set(false);
|
||||
// },
|
||||
// });
|
||||
}
|
||||
|
||||
removeHeldOrder(orderId: number) {
|
||||
const currentOrders = this.heldOrders() || [];
|
||||
this.heldOrders.set(currentOrders.filter((order) => order.id !== orderId));
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<div class="border border-surface-border rounded-xl p-2 shadow-sm">
|
||||
<div class="flex gap-3 items-stretch">
|
||||
<img [src]="placeholderImage" class="w-24 h-24 rounded-md bg-surface-100 object-cover shrink-0" />
|
||||
<div class="flex flex-col grow justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="flex flex-col grow">
|
||||
<span class="font-bold text-lg">{{ inOrderGood.good.name }}</span>
|
||||
<span class="text-sm text-muted-color">{{ inOrderGood.good.sku }}</span>
|
||||
</div>
|
||||
<div class="flex shrink-0 gap-1">
|
||||
<button
|
||||
pButton
|
||||
type="button"
|
||||
icon="pi pi-pencil"
|
||||
size="small"
|
||||
severity="secondary"
|
||||
class="ms-auto"
|
||||
(click)="editOrderItem()"
|
||||
></button>
|
||||
<button
|
||||
pButton
|
||||
type="button"
|
||||
icon="pi pi-trash"
|
||||
size="small"
|
||||
severity="danger"
|
||||
class="ms-auto"
|
||||
(click)="removeGoodFromOrder()"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mt-auto">
|
||||
<div class="ms-auto shrink-0">
|
||||
<!-- <app-uikit-counter
|
||||
[min]="1"
|
||||
[max]="inOrderGood.maxQuantity"
|
||||
[value]="inOrderGood.count"
|
||||
(valueChange)="updateInOrderGoodQuantity(inOrderGood.goodId, $event)"
|
||||
></app-uikit-counter> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pos-payload-form-dialog
|
||||
[(visible)]="visible"
|
||||
[good]="inOrderGood.good"
|
||||
[initialValues]="inOrderGood"
|
||||
[orderItemId]="inOrderGood.id"
|
||||
/>
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Component, inject, Input, signal } from '@angular/core';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import images from 'src/assets/images';
|
||||
import { IPosInOrderGood } from '../../models';
|
||||
import { PosLandingStore } from '../../store/main.store';
|
||||
import { PayloadFormDialogComponent } from '../payload-form.component';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-order-card-template',
|
||||
templateUrl: './order-card-template.component.html',
|
||||
imports: [ButtonDirective, PayloadFormDialogComponent],
|
||||
})
|
||||
export class OrderCardTemplateComponent {
|
||||
@Input({ required: true }) inOrderGood!: IPosInOrderGood;
|
||||
|
||||
private readonly store = inject(PosLandingStore);
|
||||
|
||||
visible = signal(false);
|
||||
|
||||
placeholderImage = images.placeholders.default;
|
||||
|
||||
removeGoodFromOrder() {
|
||||
this.store.removeFromInOrderGoods(this.inOrderGood.id);
|
||||
}
|
||||
|
||||
editOrderItem() {
|
||||
this.visible.set(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<div class="bg-surface-card w-md shrink-0 h-full overflow-hidden rounded-xl p-4 pb-0 flex flex-col">
|
||||
<div class="grow overflow-auto flex flex-col">
|
||||
<div class="flex gap-2 items-center justify-between shrink-0">
|
||||
<app-key-value label="مشتری" [value]="customerNameToShow()" />
|
||||
<button pButton outlined icon="pi pi-pencil" size="small" (click)="openCustomerDialog()"></button>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="flex items-center justify-between shrink-0">
|
||||
<div class="flex items-center gap-2 text-muted-color">
|
||||
<i class="pi pi-shopping-cart"></i>
|
||||
<span class="text-base font-bold">سفارشها</span>
|
||||
</div>
|
||||
<button pButton type="button" icon="pi pi-refresh" outlined size="small" (click)="clearOrderList()">
|
||||
پاک کردن سفارشها
|
||||
</button>
|
||||
</div>
|
||||
@if (!inOrderGoods().length) {
|
||||
<div class="grow flex flex-col gap-5 justify-center items-center text-center text-muted-color">
|
||||
<i class="pi pi-inbox text-6xl!"></i>
|
||||
<span class="text-lg text-muted-color">هیچ سفارشی ثبت نشده است.</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="flex flex-col gap-2 mt-3">
|
||||
@for (inOrderGood of inOrderGoods(); track inOrderGood.id) {
|
||||
@if (inOrderGood.good.pricing_model === "GOLD") {
|
||||
<gold-payload-order-card [orderItem]="inOrderGood" />
|
||||
} @else {
|
||||
<standard-payload-order-card [orderItem]="inOrderGood" />
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="flex flex-col sticky bottom-0 py-2">
|
||||
<pos-order-price-info-card />
|
||||
<div class="sticky bottom-0 pt-4">
|
||||
<button
|
||||
pButton
|
||||
type="button"
|
||||
label="ثبت و پرداخت"
|
||||
severity="primary"
|
||||
icon="pi pi-check"
|
||||
class="w-full"
|
||||
[attr.disabled]="inOrderGoods().length === 0 ? true : null"
|
||||
(click)="submitAndPay()"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pos-order-customer-dialog [(visible)]="isVisibleCustomerForm" (onSubmit)="submitCustomer()" />
|
||||
<!-- <pos-pre-invoice-dialog [(visible)]="isVisiblePreInvoiceForm" /> -->
|
||||
<pos-payment-form-dialog [(visible)]="isVisiblePaymentForm" (onSubmit)="submitPayment()" />
|
||||
@@ -0,0 +1,82 @@
|
||||
// import { CustomersSelectComponent } from '@/modules/customers/components/select/select.component';
|
||||
// import { ICustomerResponse } from '@/modules/customers/models';
|
||||
import { KeyValueComponent } from '@/shared/components';
|
||||
import { Component, computed, inject, signal } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import images from 'src/assets/images';
|
||||
import { PosLandingStore } from '../../store/main.store';
|
||||
import { PosOrderCustomerDialogComponent } from '../customers/customer-dialog.component';
|
||||
import { GoldPayloadOrderCardComponent } from '../payloads/gold/order-card.component';
|
||||
import { StandardPayloadOrderCardComponent } from '../payloads/standard/order-card.component';
|
||||
import { PosPaymentFormDialogComponent } from '../payment/form-dialog.component';
|
||||
import { POSOrderPriceInfoCardComponent } from './price-info-card.component';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-order-section',
|
||||
templateUrl: './order-section.component.html',
|
||||
imports: [
|
||||
ButtonDirective,
|
||||
POSOrderPriceInfoCardComponent,
|
||||
FormsModule,
|
||||
GoldPayloadOrderCardComponent,
|
||||
StandardPayloadOrderCardComponent,
|
||||
PosOrderCustomerDialogComponent,
|
||||
KeyValueComponent,
|
||||
PosPaymentFormDialogComponent,
|
||||
],
|
||||
})
|
||||
export class PosOrderSectionComponent {
|
||||
private readonly store = inject(PosLandingStore);
|
||||
|
||||
placeholderImage = images.placeholders.default;
|
||||
|
||||
isVisibleCustomerForm = signal(false);
|
||||
isVisiblePaymentForm = signal(false);
|
||||
isVisiblePreInvoiceForm = signal(false);
|
||||
|
||||
inOrderGoods = computed(() => this.store.inOrderGoods());
|
||||
customer = computed(() => this.store.customer());
|
||||
customerNameToShow = computed(() => {
|
||||
const type = this.store.customer().type;
|
||||
const info = this.store.customer().info;
|
||||
let customerNameToShow = 'نامشخص';
|
||||
|
||||
switch (type) {
|
||||
case 'UNKNOWN':
|
||||
customerNameToShow = `${info?.customerUnknown?.name || 'نامشخص'} (نوع دوم)`;
|
||||
break;
|
||||
case 'INDIVIDUAL':
|
||||
customerNameToShow = `${info?.customerIndividuals ? info?.customerIndividuals.first_name + ' ' + info?.customerIndividuals.last_name : 'نامشخص'} (نوع اول)`;
|
||||
break;
|
||||
case 'LEGAL':
|
||||
customerNameToShow = `${info?.customerLegals?.company_name || 'نامشخص'} (نوع اول)`;
|
||||
break;
|
||||
}
|
||||
|
||||
return customerNameToShow;
|
||||
});
|
||||
|
||||
clearOrderList() {
|
||||
this.store.resetInOrderGoods();
|
||||
}
|
||||
|
||||
submit() {
|
||||
this.store.submitOrder();
|
||||
}
|
||||
|
||||
openCustomerDialog() {
|
||||
// this.this.store.submitOrder();
|
||||
this.isVisibleCustomerForm.set(true);
|
||||
}
|
||||
|
||||
submitAndPay() {
|
||||
// this.this.store.submitOrder();
|
||||
this.isVisiblePaymentForm.set(true);
|
||||
}
|
||||
|
||||
submitCustomer() {
|
||||
console.log('submitCustomer');
|
||||
}
|
||||
submitPayment() {}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<p-card class="border border-surface-border">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<span>جمع قیمت</span>
|
||||
<div class="flex flex-col items-end">
|
||||
<!-- @if (priceInfo().discountAmount) {
|
||||
<span class="line-through text-muted-color text-xs" [appPriceMask]="priceInfo().totalBaseAmount"></span>
|
||||
}
|
||||
<span [appPriceMask]="priceInfo().totalBaseAmount - priceInfo().discountAmount"></span>-->
|
||||
<span [appPriceMask]="priceInfo().totalBaseAmount"></span>
|
||||
<!-- </div>
|
||||
} @else {
|
||||
<span [appPriceMask]="priceInfo().totalBaseAmount"></span> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span>تخفیف</span>
|
||||
<span [appPriceMask]="priceInfo().discountAmount"></span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span>مالیات</span>
|
||||
<span [appPriceMask]="priceInfo().taxAmount"></span>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="flex justify-between items-center font-bold text-lg">
|
||||
<span>مجموع کل</span>
|
||||
<span [appPriceMask]="priceInfo().totalAmount"></span>
|
||||
</div>
|
||||
</div>
|
||||
</p-card>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { PriceMaskDirective } from '@/shared/directives';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { Card } from 'primeng/card';
|
||||
import { PosLandingStore } from '../../store/main.store';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-order-price-info-card',
|
||||
templateUrl: './price-info-card.component.html',
|
||||
imports: [Card, PriceMaskDirective],
|
||||
})
|
||||
export class POSOrderPriceInfoCardComponent {
|
||||
private readonly store = inject(PosLandingStore);
|
||||
|
||||
priceInfo = computed(() => this.store.orderPricingInfo());
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<p-dialog
|
||||
[(visible)]="visible"
|
||||
[header]="preparedTitle()"
|
||||
[modal]="true"
|
||||
[style]="{ width: '500px' }"
|
||||
[closable]="true"
|
||||
(onHide)="close()"
|
||||
>
|
||||
@if (good) {
|
||||
@if (isGoldMode()) {
|
||||
<pos-gold-payload-form [initialValues]="goldPayload()" (onSubmit)="submit($event)" />
|
||||
} @else if (isStandardMode()) {
|
||||
<pos-standard-payload-form
|
||||
[initialValues]="standardPayload()"
|
||||
[unitType]="good.unit_type"
|
||||
(onSubmit)="submit($event)"
|
||||
/>
|
||||
}
|
||||
}
|
||||
</p-dialog>
|
||||
@@ -0,0 +1,53 @@
|
||||
import { IGoodResponse } from '@/domains/pos/models/good.io';
|
||||
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
||||
import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core';
|
||||
import { Dialog } from 'primeng/dialog';
|
||||
import { IGoldPayload, IPosOrderItem, IStandardPayload, TPosOrderGoodPayload } from '../models';
|
||||
import { PosLandingStore } from '../store/main.store';
|
||||
import { PosGoldPayloadFormComponent } from './payloads/gold/form.component';
|
||||
import { PosStandardPayloadFormComponent } from './payloads/standard/form.component';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-payload-form-dialog',
|
||||
templateUrl: './payload-form.component.html',
|
||||
imports: [PosGoldPayloadFormComponent, Dialog, PosStandardPayloadFormComponent],
|
||||
})
|
||||
export class PayloadFormDialogComponent extends AbstractDialog {
|
||||
@Input({ required: true }) good!: IGoodResponse;
|
||||
@Input() initialValues?: IPosOrderItem;
|
||||
@Input() orderItemId?: string;
|
||||
@Output() onSubmit = new EventEmitter<TPosOrderGoodPayload>();
|
||||
|
||||
private readonly store = inject(PosLandingStore);
|
||||
|
||||
totalAmount = signal<number>(0);
|
||||
preparedTitle = computed(() => this.good?.name);
|
||||
|
||||
editMode = computed(() => Boolean(this.initialValues) && Boolean(this.orderItemId));
|
||||
isGoldMode = computed(() => this.good.pricing_model === 'GOLD');
|
||||
isStandardMode = computed(() => this.good.pricing_model === 'STANDARD');
|
||||
|
||||
goldPayload = computed(() =>
|
||||
this.isGoldMode() && this.editMode()
|
||||
? (this.initialValues as IPosOrderItem<IGoldPayload>)
|
||||
: undefined,
|
||||
);
|
||||
standardPayload = computed(() =>
|
||||
this.isStandardMode() && this.editMode()
|
||||
? (this.initialValues as IPosOrderItem<IStandardPayload>)
|
||||
: undefined,
|
||||
);
|
||||
|
||||
onChangeTotalAmount = (totalAmount: number) => {
|
||||
this.totalAmount.set(totalAmount);
|
||||
};
|
||||
|
||||
submit(payload: IPosOrderItem) {
|
||||
if (this.editMode()) {
|
||||
this.store.editInOrderGoods({ id: this.orderItemId!, good: this.good, ...payload });
|
||||
} else {
|
||||
this.store.addToInOrderGoods(this.good.id, payload);
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<p-card class="border-none w-full">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<span>جمع قیمت</span>
|
||||
@if (!discountAmount) {
|
||||
<span [appPriceMask]="baseTotalAmount"></span>
|
||||
} @else {
|
||||
<div class="flex flex-col items-end">
|
||||
<span class="line-through text-muted-color text-xs" [appPriceMask]="baseTotalAmount"></span>
|
||||
<span [appPriceMask]="baseTotalAmount - discountAmount"></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span>مالیات (۱۰٪)</span>
|
||||
<span [appPriceMask]="taxAmount"></span>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="flex justify-between items-center font-bold text-lg">
|
||||
<span>مجموع کل</span>
|
||||
<span [appPriceMask]="totalAmount"></span>
|
||||
</div>
|
||||
</div>
|
||||
</p-card>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { PriceMaskDirective } from '@/shared/directives';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Card } from 'primeng/card';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-form-dialog-amount-card-template',
|
||||
templateUrl: './form-dialog-amount-card-template.component.html',
|
||||
imports: [Card, PriceMaskDirective],
|
||||
})
|
||||
export class PosFormDialogAmountCardTemplateComponent {
|
||||
@Input({ required: true }) baseTotalAmount!: number;
|
||||
@Input({ required: true }) totalAmount!: number;
|
||||
@Input({ required: true }) discountAmount!: number;
|
||||
@Input({ required: true }) taxAmount!: number;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<form [formGroup]="form">
|
||||
<app-input [control]="form.controls.unit_price" name="unit_price" label="قیمت پایه هر گرم" type="price" />
|
||||
<app-input [control]="form.controls.quantity" name="quantity" label="مقدار" type="number" suffix="گرم" />
|
||||
<app-enum-select [control]="form.controls.payload.controls.karat" type="goldKarat" />
|
||||
<app-input
|
||||
[control]="form.controls.payload.controls.wages"
|
||||
name="wages"
|
||||
[label]="`اجرت ${toPriceFormat(wagePrice())}`"
|
||||
type="number"
|
||||
suffix="درصد"
|
||||
[min]="0"
|
||||
[max]="100"
|
||||
/>
|
||||
<app-input
|
||||
[control]="form.controls.payload.controls.commission"
|
||||
name="commission"
|
||||
[label]="`حقالعمل ${toPriceFormat(commissionPrice())}`"
|
||||
type="number"
|
||||
suffix="درصد"
|
||||
[min]="0"
|
||||
[max]="100"
|
||||
/>
|
||||
<app-input
|
||||
[control]="form.controls.payload.controls.profit"
|
||||
name="profit"
|
||||
[label]="`سود ${toPriceFormat(profitPrice())}`"
|
||||
type="number"
|
||||
suffix="درصد"
|
||||
[min]="0"
|
||||
[max]="100"
|
||||
/>
|
||||
|
||||
<app-input
|
||||
[control]="form.controls.discount"
|
||||
name="discount"
|
||||
label="تخفیف"
|
||||
type="number"
|
||||
suffix="درصد"
|
||||
[min]="0"
|
||||
[max]="100"
|
||||
/>
|
||||
<hr />
|
||||
<div class="flex flex-col gap-4 justify-center w-full">
|
||||
<pos-form-dialog-amount-card-template
|
||||
[totalAmount]="totalAmount()"
|
||||
[baseTotalAmount]="baseTotalAmount()"
|
||||
[discountAmount]="discountAmount()"
|
||||
[taxAmount]="taxAmount()"
|
||||
/>
|
||||
<p-button (onClick)="submit()"> افزودن به لیست خرید </p-button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,108 @@
|
||||
import { AbstractForm } from '@/shared/abstractClasses';
|
||||
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat';
|
||||
import { formatWithCurrency } from '@/utils';
|
||||
import { Component, EventEmitter, Output, signal } from '@angular/core';
|
||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { Button } from 'primeng/button';
|
||||
import { IGoldPayload, IPosOrderItem } from '../../../models';
|
||||
import { PosFormDialogAmountCardTemplateComponent } from '../form-dialog-amount-card-template.component';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-gold-payload-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
InputComponent,
|
||||
EnumSelectComponent,
|
||||
Button,
|
||||
PosFormDialogAmountCardTemplateComponent,
|
||||
],
|
||||
})
|
||||
export class PosGoldPayloadFormComponent extends AbstractForm<
|
||||
IPosOrderItem<IGoldPayload>,
|
||||
IPosOrderItem<IGoldPayload>
|
||||
> {
|
||||
@Output() onSubmitForm = new EventEmitter<IGoldPayload>();
|
||||
@Output() onChangeTotalAmount = new EventEmitter<number>();
|
||||
|
||||
private readonly initialForm = () => {
|
||||
const form = this.fb.group({
|
||||
unit_price: [this.initialValues?.unit_price || 200_000_000, [Validators.required]],
|
||||
quantity: [this.initialValues?.quantity || 2, [Validators.required]],
|
||||
discount: [this.initialValues?.discount || 0],
|
||||
payload: this.fb.group({
|
||||
commission: [this.initialValues?.payload?.commission || 10, [Validators.required]],
|
||||
karat: [this.initialValues?.payload?.karat || '', [Validators.required]],
|
||||
wages: [this.initialValues?.payload?.wages || 10, [Validators.required]],
|
||||
profit: [this.initialValues?.payload?.profit || 10, [Validators.required]],
|
||||
}),
|
||||
});
|
||||
|
||||
form.valueChanges.subscribe((value) => {
|
||||
this.updateCalculateAmount(value as IPosOrderItem<IGoldPayload>);
|
||||
});
|
||||
|
||||
return form;
|
||||
};
|
||||
|
||||
form = this.initialForm();
|
||||
override submitForm(payload: IPosOrderItem) {
|
||||
this.onSubmit.emit({
|
||||
...payload,
|
||||
total_amount: this.totalAmount(),
|
||||
base_total_amount: this.baseTotalAmount(),
|
||||
discount_amount: this.discountAmount(),
|
||||
tax_amount: this.taxAmount(),
|
||||
payload: {
|
||||
commission_amount: this.commissionPrice(),
|
||||
profit_amount: this.profitPrice(),
|
||||
wages_amount: this.wagePrice(),
|
||||
commission: this.form.controls.payload.controls.commission.value || 0,
|
||||
karat: this.form.controls.payload.controls.karat.value as TGoldKarat,
|
||||
profit: this.form.controls.payload.controls.profit.value || 0,
|
||||
wages: this.form.controls.payload.controls.wages.value || 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
wagePrice = signal<number>(0);
|
||||
karatPrice = signal<number>(0);
|
||||
commissionPrice = signal<number>(0);
|
||||
profitPrice = signal<number>(0);
|
||||
totalAmount = signal<number>(0);
|
||||
baseTotalAmount = signal<number>(0);
|
||||
discountAmount = signal<number>(0);
|
||||
taxAmount = signal<number>(0);
|
||||
|
||||
toPriceFormat(amount: number) {
|
||||
if (!amount) {
|
||||
return '';
|
||||
}
|
||||
return `(${formatWithCurrency(amount)})`;
|
||||
}
|
||||
|
||||
updateCalculateAmount(value: Partial<IPosOrderItem<IGoldPayload>>) {
|
||||
const unitWithQuantity = (value.unit_price ?? 0) * (value.quantity ?? 0);
|
||||
const commissionPrice = (unitWithQuantity * Number(value.payload?.commission ?? '0')) / 100;
|
||||
const wagePrice = (unitWithQuantity * Number(value.payload?.wages ?? '0')) / 100;
|
||||
const totalPriceBeforeProfit = unitWithQuantity + wagePrice + commissionPrice;
|
||||
|
||||
const profitPrice = (totalPriceBeforeProfit * Number(value.payload?.profit ?? '0')) / 100;
|
||||
|
||||
const baseTotalAmount = unitWithQuantity + profitPrice + commissionPrice + wagePrice;
|
||||
|
||||
const discountPrice = (baseTotalAmount * Number(value.discount || '0')) / 100;
|
||||
const taxAmount = (profitPrice + commissionPrice + wagePrice - discountPrice) * 0.1;
|
||||
|
||||
this.wagePrice.set(wagePrice);
|
||||
this.commissionPrice.set(commissionPrice);
|
||||
this.profitPrice.set(profitPrice);
|
||||
this.discountAmount.set(discountPrice);
|
||||
this.taxAmount.set(taxAmount);
|
||||
this.baseTotalAmount.set(baseTotalAmount);
|
||||
|
||||
this.totalAmount.set(baseTotalAmount - discountPrice + taxAmount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<pos-order-card-template [inOrderGood]="orderItem"></pos-order-card-template>
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { IPosInOrderGood } from '../../../models';
|
||||
import { OrderCardTemplateComponent } from '../../order/order-card-template.component';
|
||||
|
||||
@Component({
|
||||
selector: 'gold-payload-order-card',
|
||||
templateUrl: './order-card.component.html',
|
||||
imports: [OrderCardTemplateComponent],
|
||||
})
|
||||
export class GoldPayloadOrderCardComponent {
|
||||
@Input({ required: true }) orderItem!: IPosInOrderGood;
|
||||
constructor() {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from '../payload-form.component';
|
||||
@@ -0,0 +1,31 @@
|
||||
<form [formGroup]="form" (submit)="submit()">
|
||||
<app-input [control]="form.controls.unit_price" name="unit_price" label="قیمت پایه" type="price" />
|
||||
<app-input
|
||||
[control]="form.controls.quantity"
|
||||
name="quantity"
|
||||
[label]="preparedFormVisuals().quantityLabel"
|
||||
type="number"
|
||||
[suffix]="preparedFormVisuals().quantitySymbolText"
|
||||
[min]="0"
|
||||
/>
|
||||
|
||||
<app-input
|
||||
[control]="form.controls.discount"
|
||||
name="discount"
|
||||
label="تخفیف"
|
||||
type="number"
|
||||
suffix="درصد"
|
||||
[min]="0"
|
||||
[max]="100"
|
||||
/>
|
||||
<hr />
|
||||
<div class="flex flex-col gap-4 justify-center w-full">
|
||||
<pos-form-dialog-amount-card-template
|
||||
[totalAmount]="totalAmount()"
|
||||
[baseTotalAmount]="baseTotalAmount()"
|
||||
[discountAmount]="discountAmount()"
|
||||
[taxAmount]="taxAmount()"
|
||||
/>
|
||||
<p-button (onClick)="submit()"> افزودن به لیست خرید </p-button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,72 @@
|
||||
import { AbstractForm } from '@/shared/abstractClasses';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { formatWithCurrency, getGoodUnitTypeProperties, UnitType } from '@/utils';
|
||||
import { Component, computed, EventEmitter, Input, Output, signal } from '@angular/core';
|
||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { Button } from 'primeng/button';
|
||||
import { IPosOrderItem, IStandardPayload } from '../../../models';
|
||||
import { PosFormDialogAmountCardTemplateComponent } from '../form-dialog-amount-card-template.component';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-standard-payload-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [ReactiveFormsModule, InputComponent, Button, PosFormDialogAmountCardTemplateComponent],
|
||||
})
|
||||
export class PosStandardPayloadFormComponent extends AbstractForm<
|
||||
IPosOrderItem<IStandardPayload>,
|
||||
IPosOrderItem<IStandardPayload>
|
||||
> {
|
||||
@Input({ required: true }) unitType!: UnitType;
|
||||
@Output() onChangeTotalAmount = new EventEmitter<number>();
|
||||
|
||||
private readonly initialForm = () => {
|
||||
const form = this.fb.group({
|
||||
unit_price: [this.initialValues?.unit_price || 0, [Validators.required]],
|
||||
quantity: [this.initialValues?.quantity || 0, [Validators.required]],
|
||||
discount: [this.initialValues?.discount || ''],
|
||||
});
|
||||
|
||||
form.valueChanges.subscribe((value) => {
|
||||
this.updateCalculateAmount(value as Partial<IPosOrderItem<IStandardPayload>>);
|
||||
});
|
||||
|
||||
return form;
|
||||
};
|
||||
|
||||
form = this.initialForm();
|
||||
override submitForm(payload: IPosOrderItem<IStandardPayload>) {
|
||||
this.onSubmit.emit({
|
||||
...payload,
|
||||
total_amount: this.totalAmount(),
|
||||
discount_amount: this.discountAmount(),
|
||||
tax_amount: this.taxAmount(),
|
||||
base_total_amount: this.baseTotalAmount(),
|
||||
});
|
||||
}
|
||||
|
||||
preparedFormVisuals = computed(() => getGoodUnitTypeProperties(this.unitType));
|
||||
|
||||
baseTotalAmount = signal<number>(0);
|
||||
totalAmount = signal<number>(0);
|
||||
discountAmount = signal<number>(0);
|
||||
taxAmount = signal<number>(0);
|
||||
|
||||
toPriceFormat(amount: number) {
|
||||
if (!amount) {
|
||||
return '';
|
||||
}
|
||||
return `(${formatWithCurrency(amount)})`;
|
||||
}
|
||||
|
||||
updateCalculateAmount(value: Partial<IPosOrderItem<IStandardPayload>>) {
|
||||
const baseTotalAmount = (value.unit_price ?? 0) * (value.quantity ?? 0);
|
||||
const discountAmount = (baseTotalAmount * Number(value.discount || '0')) / 100;
|
||||
const baseTotalAmountWithoutTax = baseTotalAmount - discountAmount;
|
||||
const taxAmount = baseTotalAmountWithoutTax * 0.1;
|
||||
|
||||
this.baseTotalAmount.set(baseTotalAmount);
|
||||
this.discountAmount.set(discountAmount);
|
||||
this.taxAmount.set(taxAmount);
|
||||
this.totalAmount.set(baseTotalAmountWithoutTax + taxAmount);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<pos-order-card-template [inOrderGood]="orderItem"></pos-order-card-template>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { IPosInOrderGood } from '../../../models';
|
||||
import { OrderCardTemplateComponent } from '../../order/order-card-template.component';
|
||||
|
||||
@Component({
|
||||
selector: 'standard-payload-order-card',
|
||||
templateUrl: './order-card.component.html',
|
||||
imports: [OrderCardTemplateComponent],
|
||||
})
|
||||
export class StandardPayloadOrderCardComponent {
|
||||
@Input({ required: true }) orderItem!: IPosInOrderGood;
|
||||
constructor() {}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<p-dialog
|
||||
[(visible)]="visible"
|
||||
header="اطلاعات پرداخت"
|
||||
[modal]="true"
|
||||
[style]="{ width: '500px' }"
|
||||
[closable]="true"
|
||||
(onHide)="close()"
|
||||
>
|
||||
<form [formGroup]="form" (submit)="submit()">
|
||||
<app-key-value label="مبلغ قابل پرداخت" [value]="totalAmount().toString()" type="price" />
|
||||
<app-key-value label="مبلغ باقیمانده" [value]="remainedAmount().toString()" type="price" />
|
||||
<hr />
|
||||
|
||||
<app-input
|
||||
[control]="form.controls.terminal"
|
||||
name="terminal"
|
||||
label="پرداخت با پایانه"
|
||||
type="price"
|
||||
[max]="terminalMax()"
|
||||
>
|
||||
<ng-template #suffixTemp>
|
||||
<button pButton size="small" outlined type="button" class="border-s-0!" (click)="fillRemained('TERMINAL')">
|
||||
تمامی بدهی باقیمانده
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-input>
|
||||
<app-input [control]="form.controls.cash" name="cash" label="نقدی" type="price" [max]="cashMax()">
|
||||
<ng-template #suffixTemp>
|
||||
<button pButton size="small" outlined type="button" class="border-s-0!" (click)="fillRemained('CASH')">
|
||||
تمامی بدهی باقیمانده
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-input>
|
||||
<app-input [control]="form.controls.set_off" name="setOff" label="تهاتر" type="price" [max]="setOffMax()">
|
||||
<ng-template #suffixTemp>
|
||||
<button pButton size="small" outlined type="button" class="border-s-0!" (click)="fillRemained('SET_OFF')">
|
||||
تمامی بدهی باقیمانده
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-input>
|
||||
|
||||
<app-form-footer-actions
|
||||
submitLabel="ثبت اطلاعات پرداخت و ادامه"
|
||||
[loading]="submitOrderLoading()"
|
||||
(onCancel)="close()"
|
||||
(onSubmit)="submit()"
|
||||
/>
|
||||
</form>
|
||||
</p-dialog>
|
||||
@@ -0,0 +1,116 @@
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
||||
import { InputComponent, KeyValueComponent } from '@/shared/components';
|
||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||
import { Component, computed, inject, signal } from '@angular/core';
|
||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import { Dialog } from 'primeng/dialog';
|
||||
import { Observable } from 'rxjs';
|
||||
import { IPayment, TOrderPaymentTypes } from '../../models';
|
||||
import { PosLandingStore } from '../../store/main.store';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-payment-form-dialog',
|
||||
templateUrl: './form-dialog.component.html',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
InputComponent,
|
||||
FormFooterActionsComponent,
|
||||
KeyValueComponent,
|
||||
ButtonDirective,
|
||||
Dialog,
|
||||
],
|
||||
})
|
||||
export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment, IPayment> {
|
||||
private readonly store = inject(PosLandingStore);
|
||||
private readonly toastServices = inject(ToastService);
|
||||
|
||||
readonly totalAmount = computed(() => this.store.orderPricingInfo().totalAmount);
|
||||
|
||||
readonly remainedAmount = signal(this.totalAmount());
|
||||
setOffMax = signal(this.remainedAmount());
|
||||
cashMax = signal(this.remainedAmount());
|
||||
terminalMax = signal(this.remainedAmount());
|
||||
|
||||
submitOrderLoading = computed(() => this.store.submitOrderLoading());
|
||||
|
||||
private initForm = () => {
|
||||
const form = this.fb.group({
|
||||
set_off: [this.initialValues?.set_off || 0],
|
||||
cash: [this.initialValues?.cash || 0],
|
||||
terminal: [this.initialValues?.terminal || 0],
|
||||
});
|
||||
|
||||
form.valueChanges.subscribe((value) => {
|
||||
const totalValue = (value.cash || 0) + (value.set_off || 0) + (value.terminal || 0);
|
||||
const remainedAmount = this.totalAmount() - totalValue;
|
||||
|
||||
const setOffMax = remainedAmount + (value.set_off || 0);
|
||||
const cashMax = remainedAmount + (value.cash || 0);
|
||||
const terminalMax = remainedAmount + (value.terminal || 0);
|
||||
|
||||
form.controls.set_off.clearValidators();
|
||||
form.controls.set_off.addValidators([Validators.max(setOffMax)]);
|
||||
form.controls.cash.clearValidators();
|
||||
form.controls.cash.addValidators([Validators.max(cashMax)]);
|
||||
form.controls.terminal.clearValidators();
|
||||
form.controls.terminal.addValidators([Validators.max(terminalMax)]);
|
||||
|
||||
this.remainedAmount.set(remainedAmount);
|
||||
this.cashMax.set(cashMax);
|
||||
this.terminalMax.set(terminalMax);
|
||||
this.setOffMax.set(setOffMax);
|
||||
});
|
||||
|
||||
return form;
|
||||
};
|
||||
|
||||
form = this.initForm();
|
||||
|
||||
fillRemained(type: TOrderPaymentTypes) {
|
||||
switch (type) {
|
||||
case 'TERMINAL':
|
||||
this.form.controls.terminal.setValue(
|
||||
(this.form.controls.terminal.value || 0) + this.remainedAmount(),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'CASH':
|
||||
this.form.controls.cash.setValue(
|
||||
(this.form.controls.cash.value || 0) + this.remainedAmount(),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'SET_OFF':
|
||||
this.form.controls.set_off.setValue(
|
||||
(this.form.controls.set_off.value || 0) + this.remainedAmount(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
override submitForm() {
|
||||
if (this.remainedAmount() > 0) {
|
||||
return this.toastServices.warn({
|
||||
text: 'فاکتور تسویه نشده است. لطفا مبلغ پرداخت را کامل کنید..',
|
||||
});
|
||||
}
|
||||
|
||||
const payment = this.form.value as IPayment;
|
||||
this.store.setPayment(payment);
|
||||
|
||||
this.store.submitOrder().then((res) => {
|
||||
if (res) {
|
||||
this.close();
|
||||
this.toastServices.success({
|
||||
text: 'فاکتور شما با موفقیت ایجاد شد',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return new Observable<IPayment>((observer) => observer.next(payment));
|
||||
}
|
||||
|
||||
override onSuccess(response: IPayment): void {}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<p-dialog
|
||||
[(visible)]="visible"
|
||||
header="پیش فاکتور"
|
||||
[modal]="true"
|
||||
[style]="{ width: '500px' }"
|
||||
[closable]="true"
|
||||
(onHide)="close()"
|
||||
>
|
||||
<form [formGroup]="form"></form>
|
||||
<uikit-datepicker [control]="form.controls.invoiceDate" label="تاریخ فاکتور" />
|
||||
</p-dialog>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
||||
import { UikitFlatpickrJalaliComponent } from '@/uikit';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { Dialog } from 'primeng/dialog';
|
||||
import { PosLandingStore } from '../store/main.store';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-pre-invoice-dialog',
|
||||
templateUrl: './pre-invoice-dialog.component.html',
|
||||
imports: [Dialog, UikitFlatpickrJalaliComponent, ReactiveFormsModule],
|
||||
})
|
||||
export class PosPreInvoiceDialogComponent extends AbstractDialog {
|
||||
private readonly store = inject(PosLandingStore);
|
||||
private readonly fb = inject(FormBuilder);
|
||||
|
||||
private initForm() {
|
||||
const form = this.fb.group({
|
||||
invoiceDate: [this.store.invoiceDate(), [Validators.required]],
|
||||
});
|
||||
return form;
|
||||
}
|
||||
|
||||
form = this.initForm();
|
||||
}
|
||||
Reference in New Issue
Block a user