Files
webMontime/src/utils/service-pricing.ts
T

180 lines
5.3 KiB
TypeScript
Raw Normal View History

2026-05-31 21:38:09 +03:30
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);
}