2025-12-14 20:34:15 +03:30
|
|
|
import { Maybe } from '@/core';
|
2026-03-10 13:36:45 +03:30
|
|
|
// import { ICustomerResponse } from '@/modules/customers/models';
|
2025-12-26 16:47:47 +03:30
|
|
|
import { computed, Inject, Injectable, InjectionToken, signal } from '@angular/core';
|
2026-01-07 15:25:41 +03:30
|
|
|
import { map, of } from 'rxjs';
|
2025-12-14 20:34:15 +03:30
|
|
|
import { IPosInfoResponse, IPosProductCategoriesResponse, IPosStockResponse } from '../models';
|
|
|
|
|
import { IPosInOrderProduct } from '../models/types';
|
|
|
|
|
import { PosService } from '../services/main.service';
|
|
|
|
|
|
2025-12-26 16:47:47 +03:30
|
|
|
export const POS_ID = new InjectionToken<number>('POS_ID');
|
|
|
|
|
|
|
|
|
|
export type TViewType = 'list' | 'grid';
|
|
|
|
|
|
2025-12-14 20:34:15 +03:30
|
|
|
interface IPosState {
|
|
|
|
|
getStockLoading: boolean;
|
|
|
|
|
stock: Maybe<IPosStockResponse[]>;
|
|
|
|
|
getInfoLoading: boolean;
|
|
|
|
|
info: Maybe<IPosInfoResponse>;
|
|
|
|
|
getProductCategoriesLoading: boolean;
|
|
|
|
|
productCategories: Maybe<IPosProductCategoriesResponse[]>;
|
|
|
|
|
activeProductCategory: Maybe<number>;
|
|
|
|
|
inOrderProducts: IPosInOrderProduct[];
|
2026-03-10 13:36:45 +03:30
|
|
|
selectedCustomer: Maybe<any>;
|
2025-12-26 16:47:47 +03:30
|
|
|
viewType: TViewType;
|
|
|
|
|
searchQuery: string;
|
2025-12-14 20:34:15 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const INITIAL_POS_STATE: IPosState = {
|
|
|
|
|
getStockLoading: true,
|
|
|
|
|
stock: null,
|
|
|
|
|
getInfoLoading: true,
|
|
|
|
|
info: null,
|
|
|
|
|
getProductCategoriesLoading: true,
|
|
|
|
|
productCategories: null,
|
|
|
|
|
activeProductCategory: null,
|
|
|
|
|
inOrderProducts: [],
|
2025-12-15 18:00:45 +03:30
|
|
|
selectedCustomer: null,
|
2025-12-26 16:47:47 +03:30
|
|
|
viewType: 'grid',
|
|
|
|
|
searchQuery: '',
|
2025-12-14 20:34:15 +03:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@Injectable({ providedIn: 'any' })
|
|
|
|
|
export class POSStore {
|
2025-12-26 16:47:47 +03:30
|
|
|
constructor(
|
|
|
|
|
@Inject(POS_ID) posId: number,
|
|
|
|
|
private service: PosService,
|
|
|
|
|
) {
|
|
|
|
|
this.posId = posId;
|
|
|
|
|
this.initial();
|
|
|
|
|
}
|
2025-12-14 20:34:15 +03:30
|
|
|
|
|
|
|
|
private state$ = signal<IPosState>({ ...INITIAL_POS_STATE });
|
|
|
|
|
|
|
|
|
|
readonly stock = computed(() => this.state$().stock);
|
|
|
|
|
readonly getStockLoading = computed(() => this.state$().getStockLoading);
|
|
|
|
|
readonly info = computed(() => this.state$().info);
|
|
|
|
|
readonly getInfoLoading = computed(() => this.state$().getInfoLoading);
|
|
|
|
|
readonly inOrderProducts = computed(() => this.state$().inOrderProducts);
|
|
|
|
|
readonly productCategories = computed(() => this.state$().productCategories);
|
|
|
|
|
readonly getProductCategoriesLoading = computed(() => this.state$().getProductCategoriesLoading);
|
|
|
|
|
readonly activeProductCategory = computed(() => this.state$().activeProductCategory);
|
2025-12-15 18:00:45 +03:30
|
|
|
readonly selectedCustomer = computed(() => this.state$().selectedCustomer);
|
2025-12-26 16:47:47 +03:30
|
|
|
readonly viewType = computed(() => this.state$().viewType);
|
|
|
|
|
readonly searchQuery = computed(() => this.state$().searchQuery);
|
2025-12-14 20:34:15 +03:30
|
|
|
readonly activatedCategoryProducts = computed(() => {
|
|
|
|
|
if (this.activeProductCategory() === 0) {
|
|
|
|
|
return this.state$().stock;
|
|
|
|
|
}
|
|
|
|
|
return this.state$().stock?.filter(
|
2026-01-07 15:25:41 +03:30
|
|
|
(s) => s.product.categoryId === this.state$().activeProductCategory,
|
2025-12-14 20:34:15 +03:30
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
readonly orderPricingInfo = computed(() => {
|
|
|
|
|
const totalAmount = this.inOrderProducts().reduce(
|
2026-01-04 13:45:45 +03:30
|
|
|
(acc, curr) => acc + curr.unitPrice * curr.count,
|
2025-12-14 20:34:15 +03:30
|
|
|
0,
|
|
|
|
|
);
|
|
|
|
|
const taxAmount = totalAmount * 0.1;
|
|
|
|
|
return {
|
2025-12-15 18:00:45 +03:30
|
|
|
totalItems: this.inOrderProducts().reduce((acc, curr) => acc + curr.count, 0),
|
2025-12-14 20:34:15 +03:30
|
|
|
totalBaseAmount: this.inOrderProducts().reduce(
|
2025-12-15 18:00:45 +03:30
|
|
|
(acc, curr) => acc + parseFloat(curr.product.salePrice) * curr.count,
|
2025-12-14 20:34:15 +03:30
|
|
|
0,
|
|
|
|
|
),
|
|
|
|
|
totalAmount,
|
|
|
|
|
taxAmount,
|
|
|
|
|
payableAmount: totalAmount + taxAmount,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-26 16:47:47 +03:30
|
|
|
posId!: number;
|
|
|
|
|
|
2025-12-14 20:34:15 +03:30
|
|
|
setState(partial: Partial<IPosState>) {
|
|
|
|
|
this.state$.set({ ...this.state$(), ...partial });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reset() {
|
|
|
|
|
this.state$.set({ ...INITIAL_POS_STATE });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
initial() {
|
|
|
|
|
this.getInfo();
|
|
|
|
|
this.getStock();
|
|
|
|
|
this.getProductCategories();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fillInitial(data: Partial<IPosState>) {
|
|
|
|
|
this.state$.set({ ...INITIAL_POS_STATE, ...data });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
this.setState({ getInfoLoading: true });
|
2025-12-26 16:47:47 +03:30
|
|
|
this.service.getInfo(this.posId).subscribe({
|
2025-12-14 20:34:15 +03:30
|
|
|
next: (res) => {
|
|
|
|
|
this.setState({ info: res, getInfoLoading: false });
|
|
|
|
|
},
|
|
|
|
|
error: () => {
|
|
|
|
|
this.setState({ getInfoLoading: false });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getStock() {
|
|
|
|
|
this.setState({ getStockLoading: true });
|
2025-12-26 16:47:47 +03:30
|
|
|
this.service.getStock(this.posId, this.state$().searchQuery).subscribe({
|
2025-12-14 20:34:15 +03:30
|
|
|
next: (res) => {
|
|
|
|
|
this.setState({ stock: res.data, getStockLoading: false });
|
|
|
|
|
},
|
|
|
|
|
error: () => {
|
|
|
|
|
this.setState({ getStockLoading: false });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getProductCategories() {
|
|
|
|
|
this.setState({ getProductCategoriesLoading: true });
|
|
|
|
|
this.service
|
2025-12-26 16:47:47 +03:30
|
|
|
.getCategories(this.posId)
|
2025-12-14 20:34:15 +03:30
|
|
|
.pipe(
|
|
|
|
|
map((res) => {
|
|
|
|
|
return {
|
|
|
|
|
data: [
|
|
|
|
|
{
|
|
|
|
|
id: 0,
|
|
|
|
|
name: 'همه',
|
|
|
|
|
totalQuantity: res.data.reduce((acc, curr) => acc + curr.totalQuantity, 0),
|
|
|
|
|
productCount: res.data.reduce((acc, curr) => acc + curr.productCount, 0),
|
|
|
|
|
},
|
|
|
|
|
...res.data,
|
|
|
|
|
],
|
|
|
|
|
meta: res.meta,
|
|
|
|
|
};
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.subscribe({
|
|
|
|
|
next: (res) => {
|
|
|
|
|
this.setState({
|
|
|
|
|
productCategories: res.data,
|
|
|
|
|
getProductCategoriesLoading: false,
|
|
|
|
|
activeProductCategory: res.data[0]?.id,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
error: () => {
|
|
|
|
|
this.setState({ getProductCategoriesLoading: false });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
changeActiveCategory(categoryId: number) {
|
|
|
|
|
this.setState({ activeProductCategory: categoryId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addToInOrderProducts(productId: number) {
|
|
|
|
|
const productStock = this.state$().stock?.find((s) => s.product.id === productId);
|
|
|
|
|
if (!productStock) return;
|
|
|
|
|
const { product } = productStock;
|
|
|
|
|
|
|
|
|
|
if (this.state$().inOrderProducts.some((p) => p.productId === productId)) {
|
|
|
|
|
this.updateInOrderProductQuantity(
|
|
|
|
|
productId,
|
2025-12-15 18:00:45 +03:30
|
|
|
this.state$().inOrderProducts.find((p) => p.productId === productId)!.count + 1,
|
2025-12-14 20:34:15 +03:30
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
this.setState({
|
|
|
|
|
inOrderProducts: [
|
|
|
|
|
...this.state$().inOrderProducts,
|
|
|
|
|
{
|
|
|
|
|
product,
|
|
|
|
|
productId: product.id,
|
2025-12-15 18:00:45 +03:30
|
|
|
count: 1,
|
2026-01-04 13:45:45 +03:30
|
|
|
unitPrice: parseFloat(product.salePrice),
|
2025-12-14 20:34:15 +03:30
|
|
|
maxQuantity: productStock.quantity,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
removeFromInOrderProducts(productId: number) {
|
|
|
|
|
this.setState({
|
|
|
|
|
inOrderProducts: this.state$().inOrderProducts.filter((p) => p.productId !== productId),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateInOrderProductQuantity(productId: number, quantity: number) {
|
|
|
|
|
const inOrderProducts = this.state$().inOrderProducts;
|
|
|
|
|
|
|
|
|
|
if (quantity < 1) {
|
|
|
|
|
this.removeFromInOrderProducts(productId);
|
|
|
|
|
} else if (inOrderProducts.findIndex((p) => p.productId === productId) === -1) {
|
|
|
|
|
this.addToInOrderProducts(productId);
|
|
|
|
|
} else {
|
|
|
|
|
const updatedProducts = inOrderProducts.map((p) => {
|
|
|
|
|
if (p.productId === productId) {
|
2025-12-15 18:00:45 +03:30
|
|
|
return { ...p, count: Math.min(quantity, p.maxQuantity) };
|
2025-12-14 20:34:15 +03:30
|
|
|
}
|
|
|
|
|
return p;
|
|
|
|
|
});
|
|
|
|
|
this.setState({ inOrderProducts: updatedProducts });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-04 13:45:45 +03:30
|
|
|
updateInOrderProductFee(productId: number, unitPrice: number) {
|
2025-12-14 20:34:15 +03:30
|
|
|
const inOrderProducts = this.state$().inOrderProducts;
|
|
|
|
|
|
|
|
|
|
if (inOrderProducts.some((p) => p.productId === productId)) {
|
|
|
|
|
const updatedProducts = inOrderProducts.map((p) => {
|
|
|
|
|
if (p.productId === productId) {
|
2026-01-04 13:45:45 +03:30
|
|
|
return { ...p, unitPrice: unitPrice };
|
2025-12-14 20:34:15 +03:30
|
|
|
}
|
|
|
|
|
return p;
|
|
|
|
|
});
|
|
|
|
|
this.setState({ inOrderProducts: updatedProducts });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-26 16:47:47 +03:30
|
|
|
updateViewType(viewType: TViewType) {
|
|
|
|
|
this.setState({ viewType });
|
|
|
|
|
}
|
|
|
|
|
updateSearchQuery(searchQuery: string) {
|
|
|
|
|
this.setState({ searchQuery });
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-14 20:34:15 +03:30
|
|
|
resetInOrderProducts() {
|
|
|
|
|
this.setState({ inOrderProducts: [] });
|
|
|
|
|
}
|
2025-12-15 18:00:45 +03:30
|
|
|
|
2026-03-10 13:36:45 +03:30
|
|
|
setSelectedCustomer(customer: Maybe<any>) {
|
2025-12-15 18:00:45 +03:30
|
|
|
this.setState({ selectedCustomer: customer });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
submitOrder() {
|
|
|
|
|
const orderPayload = {
|
2026-03-30 13:17:34 +03:30
|
|
|
customer_id: this.selectedCustomer()?.id,
|
2025-12-15 18:00:45 +03:30
|
|
|
items: this.inOrderProducts()?.map((item) => ({
|
|
|
|
|
productId: item.productId,
|
|
|
|
|
count: item.count,
|
2026-01-04 13:45:45 +03:30
|
|
|
unitPrice: item.unitPrice,
|
2025-12-15 18:00:45 +03:30
|
|
|
})),
|
|
|
|
|
};
|
2026-01-07 15:25:41 +03:30
|
|
|
return this.service.submitOrder(this.posId, orderPayload).subscribe({
|
|
|
|
|
next: (res) => {
|
|
|
|
|
this.resetInOrderProducts();
|
|
|
|
|
this.setSelectedCustomer(null);
|
|
|
|
|
return of(res);
|
|
|
|
|
},
|
|
|
|
|
error: (err) => {
|
|
|
|
|
return of(err);
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-12-15 18:00:45 +03:30
|
|
|
}
|
2025-12-14 20:34:15 +03:30
|
|
|
}
|