Files
webMontime/src/utils/service-pricing.ts
T
2026-05-31 21:38:09 +03:30

180 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { BusinessService, BusinessServiceClass } from '@/api/types';
import { formatDuration, formatPriceToman } from '@/utils/format';
export function activeServiceClasses(classes: BusinessServiceClass[]): BusinessServiceClass[] {
return [...classes]
.filter((item) => item.isActive)
.sort((a, b) => a.sortOrder - b.sortOrder || a.title.localeCompare(b.title, 'fa'));
}
export function usesServiceClasses(
classesEnabled: boolean,
classes: BusinessServiceClass[],
): boolean {
return classesEnabled && activeServiceClasses(classes).length > 0;
}
export function getClassPriceForService(
service: BusinessService,
classId: string,
) {
return service.classPrices?.find(
(item) => item.classId === classId && item.isActive,
);
}
export function serviceHasActiveClassPrices(
service: BusinessService,
classes: BusinessServiceClass[],
): boolean {
const activeIds = new Set(activeServiceClasses(classes).map((item) => item.id));
return (service.classPrices ?? []).some(
(item) => item.isActive && activeIds.has(item.classId),
);
}
export function isServiceBookable(
service: BusinessService,
classesEnabled: boolean,
classes: BusinessServiceClass[],
): boolean {
if (!service.isActive) return false;
if (!usesServiceClasses(classesEnabled, classes)) return true;
return serviceHasActiveClassPrices(service, classes);
}
export function servicePriceLabel(
service: BusinessService,
classesEnabled: boolean,
classes: BusinessServiceClass[],
): string {
if (!usesServiceClasses(classesEnabled, classes)) {
return `${formatPriceToman(service.price)} تومان`;
}
const activeClasses = activeServiceClasses(classes);
const prices = activeClasses
.map((item) => getClassPriceForService(service, item.id))
.filter(Boolean)
.map((item) => Number.parseFloat(item!.price));
if (!prices.length) return 'قیمت تعیین نشده';
const min = Math.min(...prices);
const max = Math.max(...prices);
if (min === max) {
return `${formatPriceToman(String(min))} تومان`;
}
return `از ${formatPriceToman(String(min))} تومان`;
}
export function serviceDurationLabel(
service: BusinessService,
classesEnabled: boolean,
classes: BusinessServiceClass[],
classId?: string | null,
): string {
if (classId) {
const classPrice = getClassPriceForService(service, classId);
return formatDuration(
classPrice?.durationMinutes ?? service.durationMinutes,
service.serviceTemplate?.defaultDurationMinutes,
);
}
if (!usesServiceClasses(classesEnabled, classes)) {
return formatDuration(
service.durationMinutes,
service.serviceTemplate?.defaultDurationMinutes,
);
}
const activeClasses = activeServiceClasses(classes);
const durations = activeClasses
.map((item) => getClassPriceForService(service, item.id))
.filter(Boolean)
.map((item) =>
item!.durationMinutes ?? service.durationMinutes ?? service.serviceTemplate?.defaultDurationMinutes,
)
.filter((value): value is number => value != null && value > 0);
if (!durations.length) {
return formatDuration(
service.durationMinutes,
service.serviceTemplate?.defaultDurationMinutes,
);
}
const min = Math.min(...durations);
const max = Math.max(...durations);
if (min === max) return `${min} دقیقه`;
return `${min}–${max} دقیقه`;
}
export function resolvedServicePrice(
service: BusinessService,
classId: string,
): string | null {
const classPrice = getClassPriceForService(service, classId);
return classPrice?.price ?? null;
}
export function resolveServiceDurationMinutes(
service: BusinessService,
classesEnabled: boolean,
classes: BusinessServiceClass[],
classId?: string | null,
): number {
if (classId) {
const classPrice = getClassPriceForService(service, classId);
const fromClass =
classPrice?.durationMinutes ?? service.durationMinutes ?? service.serviceTemplate?.defaultDurationMinutes;
if (fromClass != null && fromClass > 0) return fromClass;
}
if (usesServiceClasses(classesEnabled, classes) && classId) {
const classPrice = getClassPriceForService(service, classId);
const value =
classPrice?.durationMinutes ??
service.durationMinutes ??
service.serviceTemplate?.defaultDurationMinutes;
if (value != null && value > 0) return value;
}
const fallback =
service.durationMinutes ?? service.serviceTemplate?.defaultDurationMinutes;
return fallback != null && fallback > 0 ? fallback : 30;
}
export function totalDurationForServices(
services: BusinessService[],
classesEnabled: boolean,
classes: BusinessServiceClass[],
classId?: string | null,
): number {
return services.reduce(
(sum, service) =>
sum + resolveServiceDurationMinutes(service, classesEnabled, classes, classId),
0,
);
}
export function totalPriceForServices(
services: BusinessService[],
classesEnabled: boolean,
classes: BusinessServiceClass[],
classId?: string | null,
): number | null {
if (usesServiceClasses(classesEnabled, classes)) {
if (!classId) return null;
let total = 0;
for (const service of services) {
const price = resolvedServicePrice(service, classId);
if (!price) return null;
total += Number.parseFloat(price);
}
return total;
}
return services.reduce((sum, service) => sum + Number.parseFloat(service.price), 0);
}