Add frontend/modules/loyalty with types, API client, design system, feature pages and thin App Router routes under app/loyalty/. BFF proxy at app/api/loyalty/. Include loyalty frontend docs. Co-authored-by: Cursor <cursoragent@cursor.com>
708 lines
31 KiB
TypeScript
708 lines
31 KiB
TypeScript
"use client";
|
|
|
|
import { formatMoney } from "@/lib/utils";
|
|
import { beautyBusinessApi } from "@/modules/beauty/services/beauty-business-api";
|
|
import type { ResourceConfig } from "@/modules/beauty/components/crud/BeautyResourceWorkspace";
|
|
import {
|
|
businessFormatOptions,
|
|
dayOfWeekOptions,
|
|
lifecycleStatusOptions,
|
|
resourceStatusOptions,
|
|
staffKindOptions,
|
|
staffKindLabel,
|
|
dayOfWeekLabel,
|
|
} from "@/modules/beauty/constants/labels";
|
|
import { BeautyStatusChip } from "@/modules/beauty/design-system";
|
|
import { JalaliDateText } from "@/components/ds";
|
|
|
|
const auditList = (tenantId: string, entityType: string, entityId: string) =>
|
|
beautyBusinessApi.audit.list(tenantId, entityType, entityId);
|
|
|
|
const stripEmpty = (values: Record<string, string>) => {
|
|
const body: Record<string, unknown> = {};
|
|
Object.entries(values).forEach(([k, v]) => {
|
|
if (v?.trim()) body[k] = v.trim();
|
|
});
|
|
return body;
|
|
};
|
|
|
|
export const organizationResource: ResourceConfig = {
|
|
storageKey: "beauty-organizations",
|
|
title: "سازمانها",
|
|
description: "مدیریت سازمانهای سالن و زیبایی.",
|
|
viewPermission: "beauty_business.organizations.view",
|
|
managePermission: "beauty_business.organizations.manage",
|
|
queryKey: ["beauty", "organizations"],
|
|
listFn: (tenantId) => beautyBusinessApi.organizations.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.organizations.create(tenantId, body as never),
|
|
updateFn: (tenantId, id, body) => beautyBusinessApi.organizations.update(tenantId, id, body as never),
|
|
deleteFn: (tenantId, id) => beautyBusinessApi.organizations.delete(tenantId, id),
|
|
auditEntityType: "organization",
|
|
auditFn: auditList,
|
|
createLabel: "سازمان جدید",
|
|
fields: [
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "description", label: "توضیحات" },
|
|
{
|
|
name: "business_format",
|
|
label: "نوع کسبوکار",
|
|
type: "select",
|
|
options: businessFormatOptions,
|
|
table: false,
|
|
form: (m) => m === "create",
|
|
},
|
|
{
|
|
name: "status",
|
|
label: "وضعیت",
|
|
type: "select",
|
|
options: lifecycleStatusOptions,
|
|
form: (m) => m === "edit",
|
|
},
|
|
],
|
|
};
|
|
|
|
export const branchResource: ResourceConfig = {
|
|
storageKey: "beauty-branches",
|
|
title: "شعب",
|
|
description: "مدیریت شعب سالن زیبایی.",
|
|
viewPermission: "beauty_business.branches.view",
|
|
managePermission: "beauty_business.branches.manage",
|
|
queryKey: ["beauty", "branches"],
|
|
listFn: (tenantId) => beautyBusinessApi.branches.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.branches.create(tenantId, body as never),
|
|
updateFn: (tenantId, id, body) => beautyBusinessApi.branches.update(tenantId, id, body as never),
|
|
deleteFn: (tenantId, id) => beautyBusinessApi.branches.delete(tenantId, id),
|
|
auditEntityType: "branch",
|
|
auditFn: auditList,
|
|
createLabel: "شعبه جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "description", label: "توضیحات" },
|
|
{
|
|
name: "status",
|
|
label: "وضعیت",
|
|
type: "select",
|
|
options: lifecycleStatusOptions,
|
|
form: (m) => m === "edit",
|
|
},
|
|
],
|
|
};
|
|
|
|
export const customerResource: ResourceConfig = {
|
|
storageKey: "beauty-customers",
|
|
title: "مشتریان",
|
|
description: "پروفایل مشتریان سالن زیبایی.",
|
|
viewPermission: "beauty_business.customers.view",
|
|
managePermission: "beauty_business.customers.manage",
|
|
queryKey: ["beauty", "customers"],
|
|
listFn: (tenantId) => beautyBusinessApi.customers.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.customers.create(tenantId, body as never),
|
|
createLabel: "مشتری جدید",
|
|
cardTitleKey: "display_name",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "display_name", label: "نام", required: true },
|
|
{ name: "mobile", label: "موبایل" },
|
|
{ name: "email", label: "ایمیل" },
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: stripEmpty,
|
|
};
|
|
|
|
export const staffResource: ResourceConfig = {
|
|
storageKey: "beauty-staff",
|
|
title: "پرسنل",
|
|
description: "مدیریت کارکنان سالن.",
|
|
viewPermission: "beauty_business.staff.view",
|
|
managePermission: "beauty_business.staff.manage",
|
|
queryKey: ["beauty", "staff"],
|
|
listFn: (tenantId) => beautyBusinessApi.staff.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.staff.create(tenantId, body as never),
|
|
createLabel: "پرسنل جدید",
|
|
cardTitleKey: "display_name",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "branch_id", label: "شعبه", type: "branch", form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "display_name", label: "نام", required: true },
|
|
{
|
|
name: "kind",
|
|
label: "نقش",
|
|
type: "select",
|
|
options: staffKindOptions,
|
|
render: (r) => staffKindLabel[String(r.kind)] ?? String(r.kind),
|
|
},
|
|
{ name: "mobile", label: "موبایل" },
|
|
{ name: "email", label: "ایمیل" },
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
branch_id: values.branch_id || undefined,
|
|
code: values.code,
|
|
display_name: values.display_name,
|
|
kind: values.kind || "stylist",
|
|
mobile: values.mobile || undefined,
|
|
email: values.email || undefined,
|
|
}),
|
|
};
|
|
|
|
export const serviceCategoryResource: ResourceConfig = {
|
|
storageKey: "beauty-service-categories",
|
|
title: "دستهبندی خدمات",
|
|
description: "دستههای کاتالوگ خدمات.",
|
|
viewPermission: "beauty_business.service_categories.view",
|
|
managePermission: "beauty_business.service_categories.manage",
|
|
queryKey: ["beauty", "service-categories"],
|
|
listFn: (tenantId) => beautyBusinessApi.serviceCategories.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.serviceCategories.create(tenantId, body as never),
|
|
createLabel: "دسته جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "description", label: "توضیحات" },
|
|
{ name: "sort_order", label: "ترتیب", type: "number" },
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
code: values.code,
|
|
name: values.name,
|
|
description: values.description || undefined,
|
|
sort_order: values.sort_order ? Number(values.sort_order) : undefined,
|
|
}),
|
|
};
|
|
|
|
export const serviceResource: ResourceConfig = {
|
|
storageKey: "beauty-services",
|
|
title: "خدمات",
|
|
description: "کاتالوگ خدمات زیبایی.",
|
|
viewPermission: "beauty_business.services.view",
|
|
managePermission: "beauty_business.services.manage",
|
|
queryKey: ["beauty", "services"],
|
|
listFn: (tenantId) => beautyBusinessApi.services.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.services.create(tenantId, body as never),
|
|
createLabel: "خدمت جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "category_id", label: "دسته", type: "category", form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "description", label: "توضیحات" },
|
|
{ name: "base_duration_minutes", label: "مدت (دقیقه)", type: "number" },
|
|
{
|
|
name: "base_price",
|
|
label: "قیمت پایه",
|
|
type: "number",
|
|
render: (r) => (r.base_price != null ? formatMoney(String(r.base_price)) : "—"),
|
|
},
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
category_id: values.category_id || undefined,
|
|
code: values.code,
|
|
name: values.name,
|
|
description: values.description || undefined,
|
|
base_duration_minutes: values.base_duration_minutes ? Number(values.base_duration_minutes) : 30,
|
|
base_price: values.base_price ? Number(values.base_price) : undefined,
|
|
}),
|
|
};
|
|
|
|
export const staffScheduleResource: ResourceConfig = {
|
|
storageKey: "beauty-staff-schedules",
|
|
title: "برنامه کاری",
|
|
description: "برنامه هفتگی پرسنل.",
|
|
viewPermission: "beauty_business.schedules.view",
|
|
managePermission: "beauty_business.schedules.manage",
|
|
queryKey: ["beauty", "staff-schedules"],
|
|
listFn: (tenantId) => beautyBusinessApi.staffSchedules.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.staffSchedules.create(tenantId, body as never),
|
|
createLabel: "برنامه جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "branch_id", label: "شعبه", type: "branch", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "staff_ref", label: "پرسنل", type: "staff", required: true, form: (m) => m === "create" },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{
|
|
name: "day_of_week",
|
|
label: "روز",
|
|
type: "select",
|
|
options: dayOfWeekOptions,
|
|
required: true,
|
|
render: (r) => dayOfWeekLabel[String(r.day_of_week)] ?? String(r.day_of_week),
|
|
},
|
|
{ name: "start_time", label: "شروع", required: true, placeholder: "09:00" },
|
|
{ name: "end_time", label: "پایان", required: true, placeholder: "18:00" },
|
|
{
|
|
name: "is_active",
|
|
label: "فعال",
|
|
render: (r) => (r.is_active ? "بله" : "خیر"),
|
|
},
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
branch_id: values.branch_id,
|
|
staff_ref: values.staff_ref,
|
|
code: values.code,
|
|
day_of_week: values.day_of_week,
|
|
start_time: values.start_time,
|
|
end_time: values.end_time,
|
|
is_active: true,
|
|
}),
|
|
};
|
|
|
|
export const waitingListResource: ResourceConfig = {
|
|
storageKey: "beauty-waiting-list",
|
|
title: "لیست انتظار",
|
|
description: "درخواستهای انتظار نوبت.",
|
|
viewPermission: "beauty_business.waiting_list.view",
|
|
managePermission: "beauty_business.waiting_list.manage",
|
|
queryKey: ["beauty", "waiting-list"],
|
|
listFn: (tenantId) => beautyBusinessApi.waitingList.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.waitingList.create(tenantId, body as never),
|
|
createLabel: "ثبت در لیست انتظار",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "branch_id", label: "شعبه", type: "branch", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "customer_ref", label: "مشتری", type: "customer" },
|
|
{ name: "service_ref", label: "خدمت", type: "service" },
|
|
{ name: "preferred_staff_ref", label: "پرسنل ترجیحی", type: "staff", table: false },
|
|
{ name: "requested_date", label: "تاریخ درخواست", type: "datetime-local" },
|
|
{ name: "priority", label: "اولویت", type: "number" },
|
|
{ name: "notes", label: "یادداشت", type: "textarea" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
branch_id: values.branch_id,
|
|
code: values.code,
|
|
customer_ref: values.customer_ref || undefined,
|
|
service_ref: values.service_ref || undefined,
|
|
preferred_staff_ref: values.preferred_staff_ref || undefined,
|
|
requested_date: values.requested_date ? new Date(values.requested_date).toISOString().slice(0, 10) : undefined,
|
|
priority: values.priority ? Number(values.priority) : 0,
|
|
notes: values.notes || undefined,
|
|
}),
|
|
};
|
|
|
|
export const treatmentRoomResource: ResourceConfig = {
|
|
storageKey: "beauty-treatment-rooms",
|
|
title: "اتاقهای درمان",
|
|
description: "اتاقها و فضاهای خدمات.",
|
|
viewPermission: "beauty_business.rooms.view",
|
|
managePermission: "beauty_business.rooms.manage",
|
|
queryKey: ["beauty", "treatment-rooms"],
|
|
listFn: (tenantId) => beautyBusinessApi.treatmentRooms.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.treatmentRooms.create(tenantId, body as never),
|
|
createLabel: "اتاق جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "branch_id", label: "شعبه", type: "branch", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "description", label: "توضیحات" },
|
|
{
|
|
name: "status",
|
|
label: "وضعیت",
|
|
type: "select",
|
|
options: resourceStatusOptions,
|
|
render: (r) => <BeautyStatusChip status={String(r.status)} domain="resource" />,
|
|
},
|
|
{ name: "capacity", label: "ظرفیت", type: "number" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
branch_id: values.branch_id,
|
|
code: values.code,
|
|
name: values.name,
|
|
description: values.description || undefined,
|
|
status: values.status || "available",
|
|
capacity: values.capacity ? Number(values.capacity) : 1,
|
|
}),
|
|
};
|
|
|
|
export const stationResource: ResourceConfig = {
|
|
storageKey: "beauty-stations",
|
|
title: "ایستگاهها",
|
|
description: "ایستگاههای کاری سالن.",
|
|
viewPermission: "beauty_business.stations.view",
|
|
managePermission: "beauty_business.stations.manage",
|
|
queryKey: ["beauty", "stations"],
|
|
listFn: (tenantId) => beautyBusinessApi.stations.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.stations.create(tenantId, body as never),
|
|
createLabel: "ایستگاه جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "branch_id", label: "شعبه", type: "branch", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "room_id", label: "اتاق", type: "room", form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{
|
|
name: "status",
|
|
label: "وضعیت",
|
|
type: "select",
|
|
options: resourceStatusOptions,
|
|
render: (r) => <BeautyStatusChip status={String(r.status)} domain="resource" />,
|
|
},
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
branch_id: values.branch_id,
|
|
room_id: values.room_id || undefined,
|
|
code: values.code,
|
|
name: values.name,
|
|
status: values.status || "available",
|
|
}),
|
|
};
|
|
|
|
export const branchPolicyResource: ResourceConfig = {
|
|
storageKey: "beauty-branch-policies",
|
|
title: "سیاستهای شعبه",
|
|
description: "قوانین و سیاستهای عملیاتی.",
|
|
viewPermission: "beauty_business.branch_policies.view",
|
|
managePermission: "beauty_business.branch_policies.manage",
|
|
queryKey: ["beauty", "branch-policies"],
|
|
listFn: (tenantId) => beautyBusinessApi.branchPolicies.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.branchPolicies.create(tenantId, body as never),
|
|
createLabel: "سیاست جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "branch_id", label: "شعبه", type: "branch", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "policy_type", label: "نوع سیاست", required: true },
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
branch_id: values.branch_id,
|
|
code: values.code,
|
|
name: values.name,
|
|
policy_type: values.policy_type,
|
|
status: values.status || "active",
|
|
}),
|
|
};
|
|
|
|
export const packageResource: ResourceConfig = {
|
|
storageKey: "beauty-packages",
|
|
title: "پکیجها",
|
|
description: "تعریف پکیجهای خدماتی.",
|
|
viewPermission: "beauty_business.packages.view",
|
|
managePermission: "beauty_business.packages.manage",
|
|
queryKey: ["beauty", "package-definitions"],
|
|
listFn: (tenantId) => beautyBusinessApi.packageDefinitions.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.packageDefinitions.create(tenantId, body as never),
|
|
createLabel: "پکیج جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "description", label: "توضیحات" },
|
|
{ name: "total_sessions", label: "تعداد جلسات", type: "number" },
|
|
{
|
|
name: "price",
|
|
label: "قیمت",
|
|
type: "number",
|
|
render: (r) => (r.price != null ? formatMoney(String(r.price)) : "—"),
|
|
},
|
|
{ name: "valid_days", label: "اعتبار (روز)", type: "number" },
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
code: values.code,
|
|
name: values.name,
|
|
description: values.description || undefined,
|
|
total_sessions: values.total_sessions ? Number(values.total_sessions) : 1,
|
|
price: values.price ? Number(values.price) : undefined,
|
|
valid_days: values.valid_days ? Number(values.valid_days) : undefined,
|
|
}),
|
|
};
|
|
|
|
export const membershipResource: ResourceConfig = {
|
|
storageKey: "beauty-memberships",
|
|
title: "طرحهای عضویت",
|
|
description: "عضویت و اشتراک مشتریان.",
|
|
viewPermission: "beauty_business.memberships.view",
|
|
managePermission: "beauty_business.memberships.manage",
|
|
queryKey: ["beauty", "membership-plans"],
|
|
listFn: (tenantId) => beautyBusinessApi.membershipPlans.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.membershipPlans.create(tenantId, body as never),
|
|
createLabel: "طرح جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "description", label: "توضیحات" },
|
|
{ name: "billing_interval_days", label: "دوره صورتحساب (روز)", type: "number" },
|
|
{
|
|
name: "price",
|
|
label: "قیمت",
|
|
type: "number",
|
|
render: (r) => (r.price != null ? formatMoney(String(r.price)) : "—"),
|
|
},
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
code: values.code,
|
|
name: values.name,
|
|
description: values.description || undefined,
|
|
billing_interval_days: values.billing_interval_days ? Number(values.billing_interval_days) : 30,
|
|
price: values.price ? Number(values.price) : undefined,
|
|
}),
|
|
};
|
|
|
|
export const commissionResource: ResourceConfig = {
|
|
storageKey: "beauty-commissions",
|
|
title: "قوانین پورسانت",
|
|
description: "قوانین محاسبه پورسانت پرسنل.",
|
|
viewPermission: "beauty_business.commissions.view",
|
|
managePermission: "beauty_business.commissions.manage",
|
|
queryKey: ["beauty", "commission-rules"],
|
|
listFn: (tenantId) => beautyBusinessApi.commissionRules.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.commissionRules.create(tenantId, body as never),
|
|
createLabel: "قانون جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "rule_type", label: "نوع قانون", required: true },
|
|
{ name: "rate_percent", label: "درصد", type: "number" },
|
|
{ name: "flat_amount", label: "مبلغ ثابت", type: "number" },
|
|
{ name: "applies_to_service_id", label: "خدمت", type: "service", table: false },
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
code: values.code,
|
|
name: values.name,
|
|
rule_type: values.rule_type,
|
|
rate_percent: values.rate_percent ? Number(values.rate_percent) : undefined,
|
|
flat_amount: values.flat_amount ? Number(values.flat_amount) : undefined,
|
|
applies_to_service_id: values.applies_to_service_id || undefined,
|
|
}),
|
|
};
|
|
|
|
export const marketingCampaignResource: ResourceConfig = {
|
|
storageKey: "beauty-marketing-campaigns",
|
|
title: "کمپینهای بازاریابی",
|
|
description: "مدیریت کمپینها و کانالهای ارتباطی.",
|
|
viewPermission: "beauty_business.marketing.view",
|
|
managePermission: "beauty_business.marketing.manage",
|
|
queryKey: ["beauty", "marketing-campaigns"],
|
|
listFn: (tenantId) => beautyBusinessApi.marketingCampaigns.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.marketingCampaigns.create(tenantId, body as never),
|
|
createLabel: "کمپین جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "channel", label: "کانال", placeholder: "sms, email, push" },
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id || undefined,
|
|
code: values.code,
|
|
name: values.name,
|
|
channel: values.channel || undefined,
|
|
}),
|
|
};
|
|
|
|
export const appointmentResource: ResourceConfig = {
|
|
storageKey: "beauty-appointments",
|
|
title: "نوبتها",
|
|
description: "مدیریت نوبتهای سالن زیبایی.",
|
|
viewPermission: "beauty_business.appointments.view",
|
|
managePermission: "beauty_business.appointments.manage",
|
|
queryKey: ["beauty", "appointments"],
|
|
listFn: (tenantId) => beautyBusinessApi.appointments.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.appointments.create(tenantId, body as never),
|
|
updateFn: (tenantId, id, body) => beautyBusinessApi.appointments.update(tenantId, id, body as never),
|
|
auditEntityType: "appointment",
|
|
auditFn: auditList,
|
|
createLabel: "نوبت جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "branch_id", label: "شعبه", type: "branch", required: true, form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{
|
|
name: "scheduled_start",
|
|
label: "شروع",
|
|
type: "datetime-local",
|
|
required: true,
|
|
render: (r) => <JalaliDateText value={String(r.scheduled_start ?? "")} />,
|
|
},
|
|
{
|
|
name: "scheduled_end",
|
|
label: "پایان",
|
|
type: "datetime-local",
|
|
required: true,
|
|
form: (m) => m === "create",
|
|
table: false,
|
|
},
|
|
{ name: "customer_ref", label: "مشتری", type: "customer", form: (m) => m === "create" },
|
|
{ name: "staff_ref", label: "پرسنل", type: "staff" },
|
|
{ name: "service_ref", label: "خدمت", type: "service" },
|
|
{ name: "room_ref", label: "اتاق", type: "room", table: false },
|
|
{ name: "notes", label: "یادداشت", type: "textarea" },
|
|
{
|
|
name: "status",
|
|
label: "وضعیت",
|
|
render: (r) => (
|
|
<BeautyStatusChip status={String(r.status)} domain="appointment" />
|
|
),
|
|
},
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id,
|
|
branch_id: values.branch_id,
|
|
code: values.code,
|
|
scheduled_start: new Date(values.scheduled_start).toISOString(),
|
|
scheduled_end: new Date(values.scheduled_end).toISOString(),
|
|
customer_ref: values.customer_ref || undefined,
|
|
notes: values.notes || undefined,
|
|
}),
|
|
mapUpdateBody: (values, row) => ({
|
|
staff_ref: values.staff_ref || undefined,
|
|
room_ref: values.room_ref || undefined,
|
|
service_ref: values.service_ref || undefined,
|
|
scheduled_start: values.scheduled_start ? new Date(values.scheduled_start).toISOString() : undefined,
|
|
scheduled_end: values.scheduled_end ? new Date(values.scheduled_end).toISOString() : undefined,
|
|
notes: values.notes || undefined,
|
|
version: row.version as number,
|
|
}),
|
|
workflowActions: [
|
|
{
|
|
id: "confirm",
|
|
label: "تأیید",
|
|
visible: (r) => r.status === "requested",
|
|
run: (r, tenantId) =>
|
|
beautyBusinessApi.appointments.confirm(tenantId, String(r.id), {
|
|
version: r.version as number,
|
|
}),
|
|
},
|
|
{
|
|
id: "check-in",
|
|
label: "پذیرش",
|
|
visible: (r) => r.status === "confirmed",
|
|
run: (r, tenantId) =>
|
|
beautyBusinessApi.appointments.checkIn(tenantId, String(r.id), {
|
|
version: r.version as number,
|
|
}),
|
|
},
|
|
{
|
|
id: "complete",
|
|
label: "تکمیل",
|
|
visible: (r) => r.status === "checked_in" || r.status === "in_progress",
|
|
run: (r, tenantId) =>
|
|
beautyBusinessApi.appointments.complete(tenantId, String(r.id), {
|
|
version: r.version as number,
|
|
}),
|
|
},
|
|
{
|
|
id: "cancel",
|
|
label: "لغو",
|
|
variant: "danger",
|
|
confirmTitle: "لغو نوبت",
|
|
confirmDescription: "نوبت لغو میشود.",
|
|
visible: (r) => !["completed", "cancelled", "no_show"].includes(String(r.status)),
|
|
run: (r, tenantId) =>
|
|
beautyBusinessApi.appointments.cancel(tenantId, String(r.id), {
|
|
version: r.version as number,
|
|
}),
|
|
},
|
|
{
|
|
id: "no-show",
|
|
label: "عدم حضور",
|
|
variant: "outline",
|
|
confirmTitle: "ثبت عدم حضور",
|
|
visible: (r) => r.status === "confirmed" || r.status === "checked_in",
|
|
run: (r, tenantId) =>
|
|
beautyBusinessApi.appointments.noShow(tenantId, String(r.id), {
|
|
version: r.version as number,
|
|
}),
|
|
},
|
|
],
|
|
};
|
|
|
|
export const loyaltyIntegrationResource: ResourceConfig = {
|
|
storageKey: "beauty-loyalty-integrations",
|
|
title: "یکپارچهسازی وفاداری",
|
|
description: "اتصال به برنامه وفاداری.",
|
|
viewPermission: "beauty_business.integrations.view",
|
|
managePermission: "beauty_business.integrations.manage",
|
|
queryKey: ["beauty", "loyalty-integrations"],
|
|
listFn: (tenantId) => beautyBusinessApi.loyaltyIntegrations.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.loyaltyIntegrations.create(tenantId, body as never),
|
|
createLabel: "یکپارچهسازی جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "external_program_ref", label: "مرجع خارجی" },
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id || undefined,
|
|
code: values.code,
|
|
name: values.name,
|
|
external_program_ref: values.external_program_ref || undefined,
|
|
}),
|
|
};
|
|
|
|
export const communicationIntegrationResource: ResourceConfig = {
|
|
storageKey: "beauty-communication-integrations",
|
|
title: "یکپارچهسازی ارتباطات",
|
|
description: "اتصال به کانالهای پیامرسانی.",
|
|
viewPermission: "beauty_business.integrations.view",
|
|
managePermission: "beauty_business.integrations.manage",
|
|
queryKey: ["beauty", "communication-integrations"],
|
|
listFn: (tenantId) => beautyBusinessApi.communicationIntegrations.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.communicationIntegrations.create(tenantId, body as never),
|
|
createLabel: "یکپارچهسازی جدید",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", form: (m) => m === "create", table: false },
|
|
{ name: "code", label: "کد", required: true, form: (m) => m === "create" },
|
|
{ name: "name", label: "نام", required: true },
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id || undefined,
|
|
code: values.code,
|
|
name: values.name,
|
|
}),
|
|
};
|
|
|
|
export const integrationDispatchResource: ResourceConfig = {
|
|
storageKey: "beauty-integration-dispatches",
|
|
title: "Dispatch رویدادها",
|
|
description: "ارسال رویداد به سیستمهای خارجی.",
|
|
viewPermission: "beauty_business.integrations.view",
|
|
managePermission: "beauty_business.integrations.manage",
|
|
queryKey: ["beauty", "integration-dispatches"],
|
|
listFn: (tenantId) => beautyBusinessApi.integrationDispatches.list(tenantId),
|
|
createFn: (tenantId, body) => beautyBusinessApi.integrationDispatches.create(tenantId, body as never),
|
|
createLabel: "Dispatch جدید",
|
|
cardTitleKey: "event_type",
|
|
fields: [
|
|
{ name: "organization_id", label: "سازمان", type: "organization", form: (m) => m === "create", table: false },
|
|
{ name: "integration_kind", label: "نوع", required: true },
|
|
{ name: "event_type", label: "رویداد", required: true },
|
|
{ name: "payload_ref", label: "مرجع payload" },
|
|
{ name: "status", label: "وضعیت" },
|
|
],
|
|
mapCreateBody: (values) => ({
|
|
organization_id: values.organization_id || undefined,
|
|
integration_kind: values.integration_kind,
|
|
event_type: values.event_type,
|
|
payload_ref: values.payload_ref || undefined,
|
|
}),
|
|
};
|