Pārlūkot izejas kodu

Добавлена поддержка детей

Vadim 2 mēneši atpakaļ
vecāks
revīzija
2bf6fbcbd8

+ 5 - 1
src/api/v_0.1.0/client/client-activities-api.ts

@@ -111,7 +111,11 @@ class ClientActivitiesApi {
   GET_ActRegs = {
     res: z.object({
       code: z.enum(["success"]),
-      actRegs: z.array(actTypes.ActivityReg),
+      actRegs: z.object({
+        owner: z.array(actTypes.ActivityReg),
+        member: z.array(actTypes.ActivityReg),
+        childrenMember: z.array(actTypes.ActivityReg),
+      }),
     }),
   };
 

+ 24 - 4
src/api/v_0.1.0/client/client-pe-api.ts

@@ -30,6 +30,7 @@ class ClientPartEntitiesApi {
         peTypeId: z.string().uuid(),
         code: z.string(),
         name: z.string(),
+        isJoinAfterCreate: z.boolean(),
         fields: z.array(
           CustomFieldWithUserCopyValue.extend({
             peFfId: z.string(),
@@ -64,6 +65,19 @@ class ClientPartEntitiesApi {
           name: z.string(),
         }),
       ),
+      children: z.array(
+        z.object({
+          peMemberId: z.string().uuid(),
+          peId: z.string().uuid(),
+          peTypeId: z.string().uuid(),
+          peTypeCode: z.string(),
+          peTypeName: z.string(),
+          ownerId: z.string().uuid(),
+          name: z.string(),
+          childId: z.string().uuid(),
+          childIdentity: z.string(),
+        }),
+      ),
     }),
   };
 
@@ -92,10 +106,7 @@ class ClientPartEntitiesApi {
             z.object({
               peMemberId: z.string(),
               userId: z.string().uuid(),
-              email: z.string().email(),
-              fields: z.array(
-                CustomFieldWithValue.extend({ userEfId: z.string().uuid() }),
-              ),
+              identity: z.string(),
             }),
           ),
           invites: z.array(
@@ -147,6 +158,9 @@ class ClientPartEntitiesApi {
           ),
         }),
       },
+      query: z.object({
+        childId: z.string().uuid().optional(),
+      }),
     },
     res: z.object({
       code: z.enum(["success"]),
@@ -260,6 +274,9 @@ class ClientPartEntitiesApi {
       params: z.object({
         peInviteUuid: z.string().uuid(),
       }),
+      query: z.object({
+        childId: z.string().uuid().optional(),
+      }),
     },
     res: z.discriminatedUnion("code", [
       z.object({
@@ -331,6 +348,9 @@ class ClientPartEntitiesApi {
       params: z.object({
         peId: z.string().uuid(),
       }),
+      query: z.object({
+        childId: z.string().uuid().optional(),
+      }),
     },
     res: z.object({
       code: z.enum(["success"]),

+ 1 - 0
src/api/v_0.1.0/client/client-users-api.ts

@@ -10,6 +10,7 @@ class ClientUsersApi {
     res: z.object({
       code: z.enum(["success"]),
       userData: z.object({
+        userId: z.string().uuid(),
         fields: z.array(
           CustomFieldWithValue.extend({
             userEfId: z.string().uuid(),

+ 1 - 1
src/api/v_0.1.0/types/act-types.ts

@@ -57,7 +57,7 @@ class ActTypes {
       z.object({
         peMemberId: z.string(),
         userId: z.string().uuid(),
-        email: z.string().email(),
+        email: z.string().email().nullable(),
         fields: z.array(
           CustomFieldWithValue.extend({
             userEfId: z.string().uuid(),

+ 1 - 1
src/modules/client/activities/c-act-controller.ts

@@ -466,7 +466,7 @@ class ClientActivitiesController {
       res,
       {
         code: "success",
-        actRegs: [...actRegs],
+        actRegs,
       },
     );
   }

+ 105 - 4
src/modules/client/activities/c-act-service.ts

@@ -64,7 +64,7 @@ class CActService {
   }
 
   async getActRegs(userId: string) {
-    const actRegs = await selPool.any(sql.type(
+    const actRegsOwner = await selPool.any(sql.type(
       z.object({
         activityRegId: DbSchema.act.activityRegs.activityRegId,
         activityId: DbSchema.act.activityRegs.activityId,
@@ -110,16 +110,117 @@ class CActService {
         ar.user_id = ${userId}
       or 
           ar.pe_owner_id = ${userId}
-      or 
+    `);
+
+    const actRegsMember = await selPool.any(sql.type(
+      z.object({
+        activityRegId: DbSchema.act.activityRegs.activityRegId,
+        activityId: DbSchema.act.activityRegs.activityId,
+        activityCode: DbSchema.act.activities.code,
+        activityPublicName: DbSchema.act.activities.publicName,
+        peId: DbSchema.act.activityRegs.peId,
+        peName: DbSchema.act.partEntities.name.nullable(),
+        peOwnerId: DbSchema.act.partEntities.ownerId.nullable(),
+        userId: DbSchema.act.activityRegs.userId.nullable(),
+        isPaid: DbSchema.act.activityRegs.isPaid,
+        isCanceled: DbSchema.act.activityRegs.isCanceled,
+        statusHistory: z.array(
+          z.object({
+            statusHistoryId: DbSchema.act.actRegStatusHistory.statusHistoryId,
+            name: DbSchema.act.actRegStatuses.name,
+            code: DbSchema.act.actRegStatuses.code,
+            note: DbSchema.act.actRegStatusHistory.note,
+            setDate: DbSchema.act.actRegStatusHistory.setDate,
+            actRegStatusId: DbSchema.act.actRegStatuses.actRegStatusId,
+            color: DbSchema.act.actRegStatuses.color,
+            isPaymentOpen: DbSchema.act.actRegStatuses.isPaymentOpen,
+          }),
+        ),
+        isUserReg: DbSchema.act.activities.isUserReg,
+      }),
+    )`
+      select
+        ar.activity_reg_id "activityRegId",
+        ar.activity_id "activityId",
+        ar.activity_code "activityCode",
+        ar.activity_public_name "activityPublicName",
+        ar.pe_id "peId",
+        ar.pe_name "peName",
+        ar.pe_owner_id "peOwnerId",
+        ar.user_id "userId",
+        ar.is_paid "isPaid",
+        ar.status_history "statusHistory",
+        ar.is_user_reg "isUserReg",
+        ar.is_canceled "isCanceled"
+      from
+        act.act_regs_with_status ar
+      where
           EXISTS (
               select 1
               from act.pe_members pm_check
               where pm_check.pe_id = ar.pe_id
               and pm_check.user_id = ${userId}
               and pm_check.is_active = true
-          );
+          )
+    `);
+
+    const actRegsChildrenMember = await selPool.any(sql.type(
+      z.object({
+        activityRegId: DbSchema.act.activityRegs.activityRegId,
+        activityId: DbSchema.act.activityRegs.activityId,
+        activityCode: DbSchema.act.activities.code,
+        activityPublicName: DbSchema.act.activities.publicName,
+        peId: DbSchema.act.activityRegs.peId,
+        peName: DbSchema.act.partEntities.name.nullable(),
+        peOwnerId: DbSchema.act.partEntities.ownerId.nullable(),
+        userId: DbSchema.act.activityRegs.userId.nullable(),
+        isPaid: DbSchema.act.activityRegs.isPaid,
+        isCanceled: DbSchema.act.activityRegs.isCanceled,
+        statusHistory: z.array(
+          z.object({
+            statusHistoryId: DbSchema.act.actRegStatusHistory.statusHistoryId,
+            name: DbSchema.act.actRegStatuses.name,
+            code: DbSchema.act.actRegStatuses.code,
+            note: DbSchema.act.actRegStatusHistory.note,
+            setDate: DbSchema.act.actRegStatusHistory.setDate,
+            actRegStatusId: DbSchema.act.actRegStatuses.actRegStatusId,
+            color: DbSchema.act.actRegStatuses.color,
+            isPaymentOpen: DbSchema.act.actRegStatuses.isPaymentOpen,
+          }),
+        ),
+        isUserReg: DbSchema.act.activities.isUserReg,
+      }),
+    )`
+select distinct -- DISTINCT нужен для удаления дубликатов, если несколько детей соответствуют условию
+  ar.activity_reg_id "activityRegId",
+  ar.activity_id "activityId",
+  ar.activity_code "activityCode",
+  ar.activity_public_name "activityPublicName",
+  ar.pe_id "peId",
+  ar.pe_name "peName",
+  ar.pe_owner_id "peOwnerId",
+  ar.user_id "userId", -- ID пользователя из регистрации
+  usr.user_id "childUserId", -- ID ребенка, через которого найдена связь
+  ar.is_paid "isPaid",
+  ar.status_history "statusHistory",
+  ar.is_user_reg "isUserReg",
+  ar.is_canceled "isCanceled"
+from
+  act.act_regs_with_status ar
+join
+  act.pe_members pm on ar.pe_id = pm.pe_id -- Присоединяем участников по pe_id
+join
+  usr.users usr on pm.user_id = usr.user_id -- Присоединяем пользователей, чтобы проверить, является ли участник ребенком
+where
+  pm.is_active = true
+  and usr.is_child = true
+  and usr.parent_id = ${userId}; -- Фильтруем по ID родителя
     `);
-    return actRegs;
+    return {
+      owner: [...actRegsOwner],
+      member: [...actRegsMember],
+      childrenMember: [...actRegsChildrenMember],
+    };
   }
 
   async getActRegWithValues(activityRegId: string) {

+ 87 - 7
src/modules/client/activities/participant-entities/c-pe-controller.ts

@@ -19,6 +19,7 @@ import { cPeService } from "./c-pe-service.js";
 import { cCustomFieldsValidateService } from "#modules/client/custom-fields/c-cf-validate-service.js";
 import dayjs from "dayjs";
 import { cActService } from "../c-act-service.js";
+import { cUsersService } from "#modules/client/users/c-users-service.js";
 
 class ClientPeController {
   async getEventPeTypes(
@@ -88,6 +89,18 @@ class ClientPeController {
       api.client.pe.POST_PartEntity.req.formData.body.parse(
         JSON.parse(req.body.body),
       );
+    const { childId } = api.client.pe.POST_PartEntity.req.query.parse(
+      req.query,
+    );
+
+    // проверка родителя
+    if (childId) {
+      const isParent = await cUsersService.checkChildParent({
+        userId: user.userId,
+        childId,
+      });
+      if (!isParent) throw ApiError.ForbiddenError();
+    }
 
     const files = req.files;
 
@@ -139,7 +152,7 @@ class ClientPeController {
           insert into act.pe_members
             (pe_member_id, pe_id, user_id)
           values
-            (${v7()}, ${peId}, ${user.userId})
+            (${v7()}, ${peId}, ${childId || user.userId})
         `);
       }
 
@@ -319,10 +332,52 @@ class ClientPeController {
         and pm.is_active = true 
     `);
 
+    const childrenPes = await selPool.any(sql.type(
+      z.object({
+        peMemberId: DbSchema.act.peMembers.peMemberId,
+        peId: DbSchema.act.partEntities.peId,
+        peTypeId: DbSchema.act.partEntities.peTypeId,
+        peTypeCode: DbSchema.act.peTypes.code,
+        peTypeName: DbSchema.act.peTypes.name,
+        ownerId: DbSchema.act.partEntities.ownerId,
+        name: DbSchema.act.partEntities.name,
+        childId: DbSchema.usr.users.userId,
+        childIdentity: z.string(),
+      }),
+    )`
+      select
+        pm.pe_member_id "peMemberId",
+        pe.pe_id "peId",
+        pe.event_inst_id "peInstId",
+        pe.owner_id "ownerId",
+        pe.pe_type_id "peTypeId",
+        pt.code "peTypeCode",
+        pt."name" "peTypeName",
+        pe."name",
+        ui.user_id "childId",
+        ui."identity" "childIdentity"
+      from
+        usr.users u
+      join act.pe_members pm on
+        u.user_id = pm.user_id
+      join act.part_entities pe on
+        pe.pe_id = pm.pe_id
+      join act.pe_types pt on
+        pt.pe_type_id = pe.pe_type_id
+      left join ev.users_identity ui on
+        ui.user_id = u.user_id
+      where
+        u.is_child = true
+        and pe.event_inst_id = ${event.eventInstId}
+        and pm.is_active = true
+        and u.parent_id = ${user.userId}
+    `);
+
     RouterUtils.validAndSendResponse(api.client.pe.GET_MyPes.res, res, {
       code: "success",
       owner: [...ownerPes],
       memeber: [...memberPes],
+      children: [...childrenPes],
     });
   }
 
@@ -345,7 +400,7 @@ class ClientPeController {
     // валделец
     if (isOwner) {
       const pe = await cPeService.getPeWithValues(peId);
-      const members = await cPeService.getPeMembersWithFields(peId);
+      const members = await cPeService.getPeMembersWithIdentity(peId);
       const invites = await cPeService.getInvites(peId);
       const peMembersRequests = await cPeService.getPeMembersRequests(peId);
 
@@ -395,6 +450,15 @@ class ClientPeController {
   async joinToPe(req: Request, res: Response) {
     const user = sessionService.getUserFromReq(req);
     const { peId } = api.client.pe.POST_JoinToPe.req.params.parse(req.params);
+    const { childId } = api.client.pe.POST_JoinToPe.req.query.parse(req.query);
+
+    if (childId) {
+      const isParent = await cUsersService.checkChildParent({
+        userId: user.userId,
+        childId,
+      });
+      if (!isParent) throw ApiError.ForbiddenError();
+    }
 
     const isOwner = await cPeService.checkPeOwner(user.userId, peId);
     if (!isOwner) throw ApiError.ForbiddenError();
@@ -412,7 +476,7 @@ class ClientPeController {
         act.pe_members pm
       where
         pm.pe_id = ${peId} and
-        pm.user_id = ${user.userId}
+        pm.user_id = ${childId || user.userId}
     `);
 
     if (peMember && peMember.isActive) {
@@ -438,7 +502,7 @@ class ClientPeController {
         insert into act.pe_members 
           (pe_member_id, pe_id, user_id, is_active)
         values
-          (${memberId}, ${peId}, ${user.userId}, true)
+          (${memberId}, ${peId}, ${childId || user.userId}, true)
       `);
       }
 
@@ -527,6 +591,20 @@ class ClientPeController {
     const { peInviteUuid } = api.client.pe.POST_AcceptInvite.req.params.parse(
       req.params,
     );
+    const { childId } = api.client.pe.POST_AcceptInvite.req.query.parse(
+      req.query,
+    );
+
+    if (childId) {
+      const isChildParent = await cUsersService.checkChildParent({
+        userId: user.userId,
+        childId,
+      });
+
+      if (!isChildParent) {
+        throw ApiError.ForbiddenError();
+      }
+    }
 
     const invite = await cPeService.getInviteInfo(peInviteUuid);
 
@@ -607,7 +685,9 @@ class ClientPeController {
     // TODO: много лишних данных
     // участник уже в pe
     const peMembers = await cPeService.getPeMembersWithFields(invite.peId);
-    const isFound = peMembers.find((m) => m.userId === user.userId);
+    const isFound = peMembers.find(
+      (m) => m.userId === (childId || user.userId),
+    );
     if (isFound) {
       RouterUtils.validAndSendResponse(
         api.client.pe.POST_AcceptInvite.res,
@@ -623,7 +703,7 @@ class ClientPeController {
     // запрос уже отправлен
     const isPending = await cPeService.checkPeMemberInvite({
       peInviteId: invite.peInviteId,
-      userId: user.userId,
+      userId: childId || user.userId,
     });
     if (isPending) {
       RouterUtils.validAndSendResponse(
@@ -643,7 +723,7 @@ class ClientPeController {
         insert into act.pe_members_requests
           (pe_member_request_id, pe_invite_id, user_id, status)
         values
-          (${id}, ${invite.peInviteId}, ${user.userId}, 'PENDING')
+          (${id}, ${invite.peInviteId}, ${childId || user.userId}, 'PENDING')
       `);
 
       await t.query(sql.unsafe`

+ 5 - 3
src/modules/client/activities/participant-entities/c-pe-service.ts

@@ -182,9 +182,9 @@ class CPeService {
   async getPeMembersWithFields(peId: string) {
     return await selPool.any(sql.type(
       z.object({
-        peMemberId: z.string().uuid(),
-        userId: z.string().uuid(),
-        email: z.string().email(),
+        peMemberId: DbSchema.act.peMembers.peMemberId,
+        userId: DbSchema.act.peMembers.userId,
+        email: DbSchema.usr.users.email,
         fields: z.array(
           CustomFieldWithValue.extend({
             userEfId: z.string().uuid(),
@@ -253,6 +253,7 @@ class CPeService {
         fields: z.array(
           CustomFieldWithUserCopyValue.extend({ peFfId: z.string() }),
         ),
+        isJoinAfterCreate: DbSchema.act.peTypes.isJoinAfterCreate,
       }),
     )`
           select
@@ -260,6 +261,7 @@ class CPeService {
             pt.code,
             pt.name,
             pt.event_inst_id as "eventInstId",
+            pt.is_join_after_create as "isJoinAfterCreate",
             coalesce(jsonb_agg(jsonb_build_object(
                 'fieldDefinitionId',
                 cfwv.field_definition_id,

+ 1 - 0
src/modules/client/shop/c-orders-controller.ts

@@ -162,6 +162,7 @@ class ClientOrdersController {
     });
   }
 
+  // FIXME: Expected string, received null
   async cancelOrder(req: Request, res: Response) {
     const { orderId } = api.client.shop.POST_CancelOrder.req.params.parse(
       req.params,

+ 1 - 0
src/modules/client/users/c-users-controller.ts

@@ -35,6 +35,7 @@ class ClientUsersController {
       {
         code: "success",
         userData: {
+          userId: user.userId,
           fields: [...userData],
           isChild: user.isChild,
         },

+ 7 - 1
src/modules/client/users/c-users-service.ts

@@ -127,7 +127,13 @@ class CUsersService {
         `);
   }
 
-  async checkChildParent(userId: string, childId: string) {
+  async checkChildParent({
+    userId,
+    childId,
+  }: {
+    userId: string;
+    childId: string;
+  }) {
     return await selPool.exists(sql.unsafe`
       select 1 from usr.users where user_id = ${childId} and parent_id = ${userId}
     `);

+ 9 - 9
src/modules/client/users/children-controller.ts

@@ -95,10 +95,10 @@ class ChildrenController {
     const event = await sessionService.getCurrentEventFromReq(req);
     const files = req.files;
 
-    const isChildParent = await cUsersService.checkChildParent(
-      user.userId,
+    const isChildParent = await cUsersService.checkChildParent({
+      userId: user.userId,
       childId,
-    );
+    });
 
     if (!isChildParent) {
       throw ApiError.ForbiddenError();
@@ -185,10 +185,10 @@ class ChildrenController {
 
     const { childId } = api.client.users.GET_Child.req.params.parse(req.params);
 
-    const isChildParent = await cUsersService.checkChildParent(
-      user.userId,
+    const isChildParent = await cUsersService.checkChildParent({
+      userId: user.userId,
       childId,
-    );
+    });
     if (!isChildParent) {
       throw ApiError.ForbiddenError();
     }
@@ -214,10 +214,10 @@ class ChildrenController {
       req.params.childId,
     );
 
-    const isChildParent = await cUsersService.checkChildParent(
-      user.userId,
+    const isChildParent = await cUsersService.checkChildParent({
+      userId: user.userId,
       childId,
-    );
+    });
 
     if (!isChildParent) {
       throw ApiError.ForbiddenError();