180 lines
5.3 KiB
TypeScript
180 lines
5.3 KiB
TypeScript
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);
|
|||
|
|
}
|