c-act-service.ts 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
  1. import { apiTypes } from "#api/current-api.js";
  2. import {
  3. CustomFieldWithUserCopyValue,
  4. CustomFieldWithValidators,
  5. CustomFieldWithValidatorsAndValue,
  6. CustomFieldWithValue,
  7. } from "#api/v_0.1.0/types/custom-fields-types.js";
  8. import { DbSchema } from "#db/db-schema.js";
  9. import { selPool } from "#db/db.js";
  10. import { ApiError } from "#exceptions/api-error.js";
  11. import { logger } from "#plugins/logger.js";
  12. import { DatabaseTransactionConnection, sql } from "slonik";
  13. import { z } from "zod";
  14. import { ordersService } from "../shop/orders-service.js";
  15. import { v7 } from "uuid";
  16. class CActService {
  17. async addDataToActValidator(
  18. validator: z.infer<typeof apiTypes.activities.ActValidator>,
  19. activityId: string,
  20. ): Promise<z.infer<typeof apiTypes.activities.ActValidatorWithData>> {
  21. switch (validator.code) {
  22. case "max-regs": {
  23. const currentRegs = await selPool.oneFirst(sql.type(
  24. z.object({
  25. count: z.number(),
  26. }),
  27. )`
  28. select
  29. count(*)
  30. from
  31. act.activity_regs
  32. where
  33. activity_id = ${activityId}
  34. `);
  35. return {
  36. ...validator,
  37. serverData: {
  38. currentRegs,
  39. },
  40. };
  41. }
  42. default: {
  43. return validator;
  44. }
  45. }
  46. }
  47. async addDataToActValidators({
  48. validators,
  49. activityId,
  50. }: {
  51. validators: z.infer<typeof apiTypes.activities.ActValidator>[];
  52. activityId: string;
  53. }) {
  54. return await Promise.all(
  55. validators.map(
  56. async (validator) =>
  57. await cActService.addDataToActValidator(validator, activityId),
  58. ),
  59. );
  60. }
  61. async getActRegs(userId: string) {
  62. const actRegsOwner = await selPool.any(sql.type(
  63. z.object({
  64. activityRegId: DbSchema.act.activityRegs.activityRegId,
  65. activityRegNumber: DbSchema.act.activityRegs.number,
  66. activityId: DbSchema.act.activityRegs.activityId,
  67. activityCode: DbSchema.act.activities.code,
  68. activityPublicName: DbSchema.act.activities.publicName,
  69. peId: DbSchema.act.activityRegs.peId,
  70. peName: DbSchema.act.partEntities.name.nullable(),
  71. peOwnerId: DbSchema.act.partEntities.ownerId.nullable(),
  72. userId: DbSchema.act.activityRegs.userId.nullable(),
  73. isPaid: DbSchema.act.activityRegs.isPaid,
  74. isCanceled: DbSchema.act.activityRegs.isCanceled,
  75. statusHistory: z.array(
  76. z.object({
  77. statusHistoryId: DbSchema.act.actRegStatusHistory.statusHistoryId,
  78. name: DbSchema.act.actRegStatuses.name,
  79. code: DbSchema.act.actRegStatuses.code,
  80. note: DbSchema.act.actRegStatusHistory.note,
  81. setDate: DbSchema.act.actRegStatusHistory.setDate,
  82. actRegStatusId: DbSchema.act.actRegStatuses.actRegStatusId,
  83. color: DbSchema.act.actRegStatuses.color,
  84. isPaymentOpen: DbSchema.act.actRegStatuses.isPaymentOpen,
  85. }),
  86. ),
  87. isUserReg: DbSchema.act.activities.isUserReg,
  88. }),
  89. )`
  90. select
  91. ar.activity_reg_id "activityRegId",
  92. ar.number "activityRegNumber",
  93. ar.activity_id "activityId",
  94. ar.activity_code "activityCode",
  95. ar.activity_public_name "activityPublicName",
  96. ar.pe_id "peId",
  97. ar.pe_name "peName",
  98. ar.pe_owner_id "peOwnerId",
  99. ar.user_id "userId",
  100. ar.is_paid "isPaid",
  101. ar.status_history "statusHistory",
  102. ar.is_user_reg "isUserReg",
  103. ar.is_canceled "isCanceled"
  104. from
  105. act.act_regs_with_status ar
  106. where
  107. ar.user_id = ${userId}
  108. or
  109. ar.pe_owner_id = ${userId}
  110. `);
  111. const actRegsMember = await selPool.any(sql.type(
  112. z.object({
  113. activityRegId: DbSchema.act.activityRegs.activityRegId,
  114. activityRegNumber: DbSchema.act.activityRegs.number,
  115. activityId: DbSchema.act.activityRegs.activityId,
  116. activityCode: DbSchema.act.activities.code,
  117. activityPublicName: DbSchema.act.activities.publicName,
  118. peId: DbSchema.act.activityRegs.peId,
  119. peName: DbSchema.act.partEntities.name.nullable(),
  120. peOwnerId: DbSchema.act.partEntities.ownerId.nullable(),
  121. userId: DbSchema.act.activityRegs.userId.nullable(),
  122. isPaid: DbSchema.act.activityRegs.isPaid,
  123. isCanceled: DbSchema.act.activityRegs.isCanceled,
  124. statusHistory: z.array(
  125. z.object({
  126. statusHistoryId: DbSchema.act.actRegStatusHistory.statusHistoryId,
  127. name: DbSchema.act.actRegStatuses.name,
  128. code: DbSchema.act.actRegStatuses.code,
  129. note: DbSchema.act.actRegStatusHistory.note,
  130. setDate: DbSchema.act.actRegStatusHistory.setDate,
  131. actRegStatusId: DbSchema.act.actRegStatuses.actRegStatusId,
  132. color: DbSchema.act.actRegStatuses.color,
  133. isPaymentOpen: DbSchema.act.actRegStatuses.isPaymentOpen,
  134. }),
  135. ),
  136. isUserReg: DbSchema.act.activities.isUserReg,
  137. }),
  138. )`
  139. select
  140. ar.activity_reg_id "activityRegId",
  141. ar.number "activityRegNumber",
  142. ar.activity_id "activityId",
  143. ar.activity_code "activityCode",
  144. ar.activity_public_name "activityPublicName",
  145. ar.pe_id "peId",
  146. ar.pe_name "peName",
  147. ar.pe_owner_id "peOwnerId",
  148. ar.user_id "userId",
  149. ar.is_paid "isPaid",
  150. ar.status_history "statusHistory",
  151. ar.is_user_reg "isUserReg",
  152. ar.is_canceled "isCanceled"
  153. from
  154. act.act_regs_with_status ar
  155. where
  156. EXISTS (
  157. select 1
  158. from act.pe_members pm_check
  159. where pm_check.pe_id = ar.pe_id
  160. and pm_check.user_id = ${userId}
  161. and pm_check.is_active = true
  162. )
  163. `);
  164. const actRegsChildrenMember = await selPool.any(sql.type(
  165. z.object({
  166. activityRegId: DbSchema.act.activityRegs.activityRegId,
  167. activityRegNumber: DbSchema.act.activityRegs.number,
  168. activityId: DbSchema.act.activityRegs.activityId,
  169. activityCode: DbSchema.act.activities.code,
  170. activityPublicName: DbSchema.act.activities.publicName,
  171. peId: DbSchema.act.activityRegs.peId,
  172. peName: DbSchema.act.partEntities.name.nullable(),
  173. peOwnerId: DbSchema.act.partEntities.ownerId.nullable(),
  174. userId: DbSchema.act.activityRegs.userId.nullable(),
  175. isPaid: DbSchema.act.activityRegs.isPaid,
  176. isCanceled: DbSchema.act.activityRegs.isCanceled,
  177. statusHistory: z.array(
  178. z.object({
  179. statusHistoryId: DbSchema.act.actRegStatusHistory.statusHistoryId,
  180. name: DbSchema.act.actRegStatuses.name,
  181. code: DbSchema.act.actRegStatuses.code,
  182. note: DbSchema.act.actRegStatusHistory.note,
  183. setDate: DbSchema.act.actRegStatusHistory.setDate,
  184. actRegStatusId: DbSchema.act.actRegStatuses.actRegStatusId,
  185. color: DbSchema.act.actRegStatuses.color,
  186. isPaymentOpen: DbSchema.act.actRegStatuses.isPaymentOpen,
  187. }),
  188. ),
  189. isUserReg: DbSchema.act.activities.isUserReg,
  190. }),
  191. )`
  192. select distinct -- DISTINCT нужен для удаления дубликатов, если несколько детей соответствуют условию
  193. ar.activity_reg_id "activityRegId",
  194. ar.number "activityRegNumber",
  195. ar.activity_id "activityId",
  196. ar.activity_code "activityCode",
  197. ar.activity_public_name "activityPublicName",
  198. ar.pe_id "peId",
  199. ar.pe_name "peName",
  200. ar.pe_owner_id "peOwnerId",
  201. ar.user_id "userId", -- ID пользователя из регистрации
  202. usr.user_id "childUserId", -- ID ребенка, через которого найдена связь
  203. ar.is_paid "isPaid",
  204. ar.status_history "statusHistory",
  205. ar.is_user_reg "isUserReg",
  206. ar.is_canceled "isCanceled"
  207. from
  208. act.act_regs_with_status ar
  209. join
  210. act.pe_members pm on ar.pe_id = pm.pe_id -- Присоединяем участников по pe_id
  211. join
  212. usr.users usr on pm.user_id = usr.user_id -- Присоединяем пользователей, чтобы проверить, является ли участник ребенком
  213. where
  214. pm.is_active = true
  215. and usr.is_child = true
  216. and usr.parent_id = ${userId}; -- Фильтруем по ID родителя
  217. `);
  218. return {
  219. owner: [...actRegsOwner],
  220. member: [...actRegsMember],
  221. childrenMember: [...actRegsChildrenMember],
  222. };
  223. }
  224. async getActRegWithValues(activityRegId: string) {
  225. const actReg = await selPool.maybeOne(sql.type(
  226. z.object({
  227. activityRegId: DbSchema.act.activityRegs.activityRegId,
  228. activityRegNumber: DbSchema.act.activityRegs.number,
  229. activityId: DbSchema.act.activityRegs.activityId,
  230. activityCode: DbSchema.act.activities.code,
  231. activityPublicName: DbSchema.act.activities.publicName,
  232. peId: DbSchema.act.activityRegs.peId,
  233. peName: DbSchema.act.partEntities.name.nullable(),
  234. peOwnerId: DbSchema.act.partEntities.ownerId.nullable(),
  235. userId: DbSchema.act.activityRegs.userId.nullable(),
  236. isPaid: DbSchema.act.activityRegs.isPaid,
  237. isCanceled: DbSchema.act.activityRegs.isCanceled,
  238. statusHistory: z.array(
  239. z.object({
  240. statusHistoryId: DbSchema.act.actRegStatusHistory.statusHistoryId,
  241. name: DbSchema.act.actRegStatuses.name,
  242. code: DbSchema.act.actRegStatuses.code,
  243. note: DbSchema.act.actRegStatusHistory.note,
  244. setDate: DbSchema.act.actRegStatusHistory.setDate,
  245. actRegStatusId: DbSchema.act.actRegStatuses.actRegStatusId,
  246. color: DbSchema.act.actRegStatuses.color,
  247. isPaymentOpen: DbSchema.act.actRegStatuses.isPaymentOpen,
  248. }),
  249. ),
  250. fields: z.array(
  251. CustomFieldWithValue.extend({
  252. arffId: DbSchema.act.activityRegFormFields.arffId,
  253. isChangeResetStatus:
  254. DbSchema.act.activityRegFormFields.isChangeResetStatus,
  255. }),
  256. ),
  257. isUserReg: DbSchema.act.activities.isUserReg,
  258. }),
  259. )`
  260. select
  261. ar.activity_reg_id "activityRegId",
  262. ar.number "activityRegNumber",
  263. ar.activity_id "activityId",
  264. ar.activity_code "activityCode",
  265. ar.activity_public_name "activityPublicName",
  266. ar.pe_id "peId",
  267. ar.pe_name "peName",
  268. ar.pe_owner_id "peOwnerId",
  269. ar.user_id "userId",
  270. ar.is_paid "isPaid",
  271. ar.status_history "statusHistory",
  272. ar.fields "fields",
  273. ar.is_user_reg "isUserReg",
  274. ar.is_canceled "isCanceled"
  275. from
  276. act.act_regs_with_values ar
  277. where
  278. ar.activity_reg_id = ${activityRegId}
  279. `);
  280. return actReg;
  281. }
  282. async getActRegForPeMember({
  283. activityRegId,
  284. tr,
  285. }: {
  286. activityRegId: string;
  287. tr?: DatabaseTransactionConnection;
  288. }) {
  289. const db = this.getConnection(tr);
  290. const actReg = await db.maybeOne(sql.type(
  291. z.object({
  292. activityRegId: DbSchema.act.activityRegs.activityRegId,
  293. activityRegNumber: DbSchema.act.activityRegs.number,
  294. activityId: DbSchema.act.activityRegs.activityId,
  295. activityCode: DbSchema.act.activities.code,
  296. activityPublicName: DbSchema.act.activities.publicName,
  297. peId: DbSchema.act.activityRegs.peId,
  298. peName: DbSchema.act.partEntities.name.nullable(),
  299. peOwnerId: DbSchema.act.partEntities.ownerId.nullable(),
  300. userId: DbSchema.act.activityRegs.userId.nullable(),
  301. isPaid: DbSchema.act.activityRegs.isPaid,
  302. isCanceled: DbSchema.act.activityRegs.isCanceled,
  303. statusHistory: z.array(
  304. z.object({
  305. statusHistoryId: DbSchema.act.actRegStatusHistory.statusHistoryId,
  306. name: DbSchema.act.actRegStatuses.name,
  307. code: DbSchema.act.actRegStatuses.code,
  308. note: DbSchema.act.actRegStatusHistory.note,
  309. setDate: DbSchema.act.actRegStatusHistory.setDate,
  310. actRegStatusId: DbSchema.act.actRegStatuses.actRegStatusId,
  311. color: DbSchema.act.actRegStatuses.color,
  312. isPaymentOpen: DbSchema.act.actRegStatuses.isPaymentOpen,
  313. }),
  314. ),
  315. isUserReg: DbSchema.act.activities.isUserReg,
  316. }),
  317. )`
  318. select
  319. ar.activity_reg_id "activityRegId",
  320. ar.number "activityRegNumber",
  321. ar.activity_id "activityId",
  322. ar.activity_code "activityCode",
  323. ar.activity_public_name "activityPublicName",
  324. ar.pe_id "peId",
  325. ar.pe_name "peName",
  326. ar.pe_owner_id "peOwnerId",
  327. ar.user_id "userId",
  328. ar.is_paid "isPaid",
  329. ar.status_history "statusHistory",
  330. ar.is_user_reg "isUserReg",
  331. ar.is_canceled "isCanceled"
  332. from
  333. act.act_regs_with_status ar
  334. where
  335. ar.activity_reg_id = ${activityRegId}
  336. `);
  337. return actReg;
  338. }
  339. async getInitialRegStatusId(activityId: string) {
  340. const initialRegStatusId = await selPool.oneFirst(sql.type(
  341. z.object({
  342. initialRegStatusId: DbSchema.act.activities.initialRegStatusId,
  343. }),
  344. )`
  345. select
  346. initial_reg_status_id "initialRegStatusId"
  347. from
  348. act.activities
  349. where
  350. activity_id = ${activityId}
  351. `);
  352. return initialRegStatusId;
  353. }
  354. async getActivity({
  355. activityCode,
  356. tr,
  357. }: {
  358. activityCode: string;
  359. tr?: DatabaseTransactionConnection;
  360. }) {
  361. const db = this.getConnection(tr);
  362. return await db.maybeOne(sql.type(
  363. z.object({
  364. activityId: DbSchema.act.activities.activityId,
  365. code: DbSchema.act.activities.code,
  366. publicName: DbSchema.act.activities.publicName,
  367. eventInstId: DbSchema.act.activities.eventInstId,
  368. categoryId: DbSchema.act.activities.categoryId,
  369. isUserReg: DbSchema.act.activities.isUserReg,
  370. paymentConfig: DbSchema.act.activities.paymentConfig,
  371. registrationProductId: DbSchema.act.activities.registrationProductId,
  372. participantProductId: DbSchema.act.activities.participantProductId,
  373. nextRegStatusIdAfterPayment:
  374. DbSchema.act.activities.nextRegStatusIdAfterPayment,
  375. }),
  376. )`
  377. select
  378. a.activity_id "activityId",
  379. a.code "code",
  380. a.public_name "publicName",
  381. a.event_inst_id "eventInstId",
  382. a.category_id "categoryId",
  383. a.is_user_reg "isUserReg",
  384. a.payment_config "paymentConfig",
  385. a.registration_product_id "registrationProductId",
  386. a.participant_product_id "participantProductId",
  387. a.next_reg_status_id_after_payment "nextRegStatusIdAfterPayment"
  388. from
  389. act.activities a
  390. where
  391. a.code = ${activityCode}
  392. `);
  393. }
  394. async checkActivityRegNumber(activityRegNumber: string) {
  395. const isExist = await selPool.exists(sql.unsafe`
  396. select
  397. number
  398. from
  399. act.activity_regs
  400. where
  401. number = ${activityRegNumber}
  402. `);
  403. return !!isExist;
  404. }
  405. async updateActRegPaymentStatus({
  406. tr,
  407. activityRegId,
  408. }: {
  409. tr: DatabaseTransactionConnection;
  410. activityRegId: string;
  411. }) {
  412. logger.info(`Обновление статуса оплаты для регистрации ${activityRegId}`);
  413. const actReg = await this.getActRegForPeMember({ activityRegId, tr });
  414. if (!actReg) {
  415. throw ApiError.BadRequest("actRegNotFound", "Не найдена регистрация");
  416. }
  417. const activity = await this.getActivity({
  418. activityCode: actReg.activityCode,
  419. tr,
  420. });
  421. if (!activity) {
  422. throw ApiError.BadRequest("activityNotFound", "Не найдена активность");
  423. }
  424. // оплата за всю регистрацию
  425. if (activity.paymentConfig === "PER_REGISTRATION") {
  426. const isPaid = await this.checkActivityRegPayment({
  427. activityRegId,
  428. tr,
  429. });
  430. // если надо поменять
  431. if (actReg.isPaid !== isPaid) {
  432. if (isPaid) {
  433. await tr.query(sql.unsafe`
  434. update act.activity_regs
  435. set is_paid = true
  436. where activity_reg_id = ${activityRegId}
  437. `);
  438. await tr.query(sql.unsafe`
  439. insert into act.act_reg_status_history (
  440. activity_reg_id,
  441. act_reg_status_id,
  442. note
  443. )
  444. values (
  445. ${activityRegId},
  446. ${activity.nextRegStatusIdAfterPayment},
  447. 'Оплачено'
  448. )
  449. `);
  450. } else {
  451. // такого по идее не должно быть
  452. logger.error("isPaid !== actReg.isPaid", {
  453. isPaid,
  454. actReg,
  455. activity,
  456. });
  457. }
  458. }
  459. // TODO: QR
  460. }
  461. // оплата за каждого участника
  462. if (activity.paymentConfig === "PER_PARTICIPANT") {
  463. if (!actReg.peId) {
  464. throw Error("peId not found");
  465. }
  466. const isAllPaid = await this.checkMembersPayment({
  467. activityRegId,
  468. peId: actReg.peId,
  469. tr,
  470. });
  471. // если надо поменять
  472. if (isAllPaid !== actReg.isPaid) {
  473. if (!isAllPaid) {
  474. await tr.query(sql.unsafe`
  475. update act.activity_regs
  476. set is_paid = false
  477. where activity_reg_id = ${activityRegId}
  478. `);
  479. // TODO: Возможно стоит добавить в act.activities поле payment_status_id
  480. await tr.query(sql.unsafe`
  481. insert into act.act_reg_status_history (
  482. activity_reg_id,
  483. act_reg_status_id,
  484. note
  485. )
  486. values (
  487. ${activityRegId},
  488. 'd6d27702-cded-4625-be07-e339c4003c2f',
  489. 'Оплачены не все участники'
  490. )
  491. `);
  492. } else {
  493. await tr.query(sql.unsafe`
  494. update act.activity_regs
  495. set is_paid = true
  496. where activity_reg_id = ${activityRegId}
  497. `);
  498. await tr.query(sql.unsafe`
  499. insert into act.act_reg_status_history (
  500. activity_reg_id,
  501. act_reg_status_id,
  502. note
  503. )
  504. values (
  505. ${activityRegId},
  506. ${activity.nextRegStatusIdAfterPayment},
  507. 'Оплачены все участники'
  508. )
  509. `);
  510. }
  511. }
  512. }
  513. }
  514. private getConnection(connection?: DatabaseTransactionConnection) {
  515. return connection || selPool;
  516. }
  517. async checkMembersPayment({
  518. peId,
  519. activityRegId,
  520. tr,
  521. }: {
  522. peId: string;
  523. activityRegId: string;
  524. tr?: DatabaseTransactionConnection;
  525. }) {
  526. const db = this.getConnection(tr);
  527. const members = await db.any(sql.type(
  528. z.object({
  529. peMemberId: DbSchema.act.peMembers.peMemberId,
  530. userId: DbSchema.act.peMembers.userId,
  531. }),
  532. )`
  533. select
  534. pm.pe_member_id "peMemberId",
  535. pm.user_id "userId"
  536. from
  537. act.pe_members pm
  538. where
  539. pm.pe_id = ${peId}
  540. and pm.is_active = true
  541. `);
  542. const memberIds = members.map((member) => member.peMemberId);
  543. const paidMemberRows = await db.any(sql.unsafe`
  544. select distinct
  545. oi.pe_member_id -- Выбираем ID тех, кто заплатил
  546. from
  547. shop.order_items oi
  548. where
  549. oi.pe_member_id = ANY(${sql.array(memberIds, "uuid")})
  550. and oi.status = 'PAID'
  551. and oi.activity_reg_id = ${activityRegId}
  552. `);
  553. return memberIds.length === paidMemberRows.length;
  554. }
  555. async checkActivityRegPayment({
  556. activityRegId,
  557. tr,
  558. }: {
  559. activityRegId: string;
  560. tr?: DatabaseTransactionConnection;
  561. }) {
  562. const db = this.getConnection(tr);
  563. const isPaid = await db.exists(sql.unsafe`
  564. select 1
  565. from shop.order_items oi
  566. where oi.activity_reg_id = ${activityRegId}
  567. and oi.status = 'PAID'
  568. `);
  569. return !!isPaid;
  570. }
  571. async checkActRegRole(userId: string, activityRegId: string) {
  572. const actReg = await this.getActRegWithValues(activityRegId);
  573. if (!actReg) {
  574. throw ApiError.BadRequest("actRegNotFound", "Не найдена регистрация");
  575. }
  576. if (actReg.peOwnerId === userId || actReg.userId === userId) {
  577. return "owner";
  578. }
  579. const isMemeber = await selPool.exists(sql.unsafe`
  580. select 1
  581. from act.pe_members
  582. where pe_id = ${actReg.peId}
  583. and user_id = ${userId}
  584. and is_active = true
  585. `);
  586. if (isMemeber) return "member";
  587. const isParent = await selPool.exists(sql.unsafe`
  588. select 1
  589. from act.pe_members m
  590. join usr.users u on
  591. m.user_id = u.user_id and
  592. u.parent_id = ${userId} and
  593. u.is_child = true
  594. where
  595. m.pe_id = ${actReg.peId}
  596. and m.is_active = true
  597. `);
  598. if (isParent) return "parent";
  599. return undefined;
  600. }
  601. async getPeForActReg({
  602. peId,
  603. activityRegId,
  604. }: {
  605. peId: string;
  606. activityRegId: string;
  607. }) {
  608. return await selPool.maybeOne(sql.type(
  609. z.object({
  610. peId: DbSchema.act.partEntities.peId,
  611. peTypeId: DbSchema.act.partEntities.peTypeId,
  612. peTypeCode: DbSchema.act.peTypes.code,
  613. peTypeName: DbSchema.act.peTypes.name,
  614. name: DbSchema.act.partEntities.name,
  615. eventInstId: DbSchema.act.partEntities.eventInstId,
  616. ownerId: DbSchema.act.partEntities.ownerId,
  617. ownerIdentity: z.string(),
  618. members: z.array(
  619. z.object({
  620. peMemberId: DbSchema.act.peMembers.peMemberId,
  621. userId: DbSchema.act.peMembers.userId,
  622. identity: z.string(),
  623. isPaid: z.boolean(),
  624. }),
  625. ),
  626. }),
  627. )`
  628. select
  629. pe.pe_id "peId",
  630. pt.pe_type_id "peTypeId",
  631. pt.code "peTypeCode",
  632. pt.name "peTypeName",
  633. pe.event_inst_id "eventInstId",
  634. pe.owner_id "ownerId",
  635. pe.name,
  636. ui.identity "ownerIdentity",
  637. coalesce(m.members, '[]'::jsonb) "members"
  638. from
  639. act.part_entities pe
  640. left join ev.users_identity ui on
  641. ui.user_id = pe.owner_id
  642. left join act.pe_types pt on
  643. pt.pe_type_id = pe.pe_type_id
  644. -- members
  645. left join lateral (
  646. select
  647. jsonb_agg(jsonb_build_object(
  648. 'peMemberId', pm.pe_member_id,
  649. 'userId', pm.user_id,
  650. 'identity', ui.identity,
  651. 'isPaid', oi.order_item_id is not null
  652. )) as members
  653. from
  654. act.pe_members pm
  655. left join ev.users_identity ui on
  656. ui.user_id = pm.user_id
  657. -- is_paid
  658. left join shop.order_items oi on
  659. oi.pe_member_id = pm.pe_member_id and
  660. oi.activity_reg_id = ${activityRegId} and
  661. oi.status = 'PAID'
  662. where
  663. pm.pe_id = ${peId} and
  664. pm.is_active = true
  665. ) m on true
  666. where
  667. pe.pe_id = ${peId}
  668. `);
  669. }
  670. async checkPeMemberActivityRegPayment({
  671. peMemberId,
  672. activityRegId,
  673. }: {
  674. peMemberId: string;
  675. activityRegId: string;
  676. }) {
  677. const isPaid = await selPool.exists(sql.unsafe`
  678. select 1
  679. from shop.order_items oi
  680. where oi.pe_member_id = ${peMemberId}
  681. and oi.activity_reg_id = ${activityRegId}
  682. and oi.status = 'PAID'
  683. `);
  684. return !!isPaid;
  685. }
  686. async getActRegDataWithUserCopyValuesAndActValidators(
  687. userId: string,
  688. eventId: string,
  689. activityCode: string,
  690. ) {
  691. return await selPool.maybeOne(sql.type(
  692. z.object({
  693. activityId: DbSchema.act.activities.activityId,
  694. code: DbSchema.act.activities.code,
  695. publicName: DbSchema.act.activities.publicName,
  696. eventInstId: DbSchema.act.activities.eventInstId,
  697. categoryId: DbSchema.act.activities.categoryId,
  698. categoryCode: DbSchema.act.activityCategories.code,
  699. validators: z.array(apiTypes.activities.ActValidator),
  700. peTypes: z.array(
  701. z.object({
  702. peTypeId: DbSchema.act.peTypes.peTypeId,
  703. code: DbSchema.act.peTypes.code,
  704. name: DbSchema.act.peTypes.name,
  705. }),
  706. ),
  707. fields: z.array(
  708. CustomFieldWithUserCopyValue.extend({ arffId: z.string() }),
  709. ),
  710. isUserReg: DbSchema.act.activities.isUserReg,
  711. }),
  712. )`
  713. select
  714. a.activity_id "activityId",
  715. a.code,
  716. a.public_name "publicName",
  717. a.event_inst_id "eventInstId",
  718. a.category_id "categoryId",
  719. a.category_code "categoryCode",
  720. a.validators,
  721. a.pe_types "peTypes",
  722. coalesce(f.fields, '[]'::jsonb) "fields",
  723. a.is_user_reg "isUserReg"
  724. from
  725. act.act_with_validators a
  726. left join lateral (
  727. select
  728. jsonb_agg(jsonb_build_object(
  729. 'fieldDefinitionId', f.field_definition_id,
  730. 'arffId', af.arff_id,
  731. 'isCopyUserValue', af.is_copy_user_value,
  732. 'code', f.code,
  733. 'userCopyValue', ufwv.value,
  734. 'fieldTypeCode', f.field_type_code,
  735. 'title', COALESCE(af.field_title_override, f.title),
  736. 'mask', f.mask,
  737. 'options', f."options",
  738. 'validators', f.validators,
  739. 'orderNumber', af.order_number
  740. )) as fields
  741. from
  742. act.activity_reg_form_fields af
  743. left join cf.custom_fields_with_validators f on
  744. f.field_definition_id = af.field_definition_id
  745. -- значение из профиля юзера
  746. left join ev.user_fields_with_values ufwv on
  747. af.field_definition_id = ufwv.field_definition_id
  748. and ufwv.user_id = ${userId}
  749. and ufwv.event_id = ${eventId}
  750. and ufwv.value is not null
  751. -- только если нужно копировать
  752. and af.is_copy_user_value = true
  753. where
  754. af.activity_id = a.activity_id
  755. ) f on true
  756. where a.code = ${activityCode}
  757. `);
  758. }
  759. async getActRegDataWithFieldsAndValidatorsAndActValidators(
  760. activityCode: string,
  761. ) {
  762. return await selPool.maybeOne(sql.type(
  763. z.object({
  764. activityId: DbSchema.act.activities.activityId,
  765. code: DbSchema.act.activities.code,
  766. publicName: DbSchema.act.activities.publicName,
  767. eventInstId: DbSchema.act.activities.eventInstId,
  768. categoryId: DbSchema.act.activities.categoryId,
  769. categoryCode: DbSchema.act.activityCategories.code,
  770. validators: z.array(apiTypes.activities.ActValidator),
  771. peTypes: z.array(
  772. z.object({
  773. peTypeId: DbSchema.act.peTypes.peTypeId,
  774. code: DbSchema.act.peTypes.code,
  775. name: DbSchema.act.peTypes.name,
  776. }),
  777. ),
  778. fields: z.array(
  779. CustomFieldWithValidators.extend({ arffId: z.string() }),
  780. ),
  781. isUserReg: DbSchema.act.activities.isUserReg,
  782. }),
  783. )`
  784. select
  785. a.activity_id "activityId",
  786. a.code,
  787. a.public_name "publicName",
  788. a.event_inst_id "eventInstId",
  789. a.category_id "categoryId",
  790. a.category_code "categoryCode",
  791. a.validators,
  792. a.pe_types "peTypes",
  793. coalesce(f.fields, '[]'::jsonb) "fields",
  794. a.is_user_reg "isUserReg"
  795. from
  796. act.act_with_validators a
  797. left join lateral (
  798. select
  799. jsonb_agg(jsonb_build_object(
  800. 'fieldDefinitionId', f.field_definition_id,
  801. 'arffId', af.arff_id,
  802. 'isCopyUserValue', af.is_copy_user_value,
  803. 'code', f.code,
  804. 'fieldTypeCode', f.field_type_code,
  805. 'title', COALESCE(af.field_title_override, f.title),
  806. 'mask', f.mask,
  807. 'options', f."options",
  808. 'validators', f.validators,
  809. 'orderNumber', af.order_number
  810. )) as fields
  811. from
  812. act.activity_reg_form_fields af
  813. left join cf.custom_fields_with_validators f on
  814. f.field_definition_id = af.field_definition_id
  815. where
  816. af.activity_id = a.activity_id
  817. ) f on true
  818. where a.code = ${activityCode}
  819. `);
  820. }
  821. async getActRegWithFieldsAndValuesWithValidators(activityRegId: string) {
  822. return selPool.maybeOne(sql.type(
  823. z.object({
  824. activityRegId: DbSchema.act.activityRegs.activityRegId,
  825. activityRegNumber: DbSchema.act.activityRegs.number,
  826. activityId: DbSchema.act.activityRegs.activityId,
  827. activityCode: DbSchema.act.activities.code,
  828. activityPublicName: DbSchema.act.activities.publicName,
  829. peId: DbSchema.act.activityRegs.peId,
  830. peName: DbSchema.act.partEntities.name.nullable(),
  831. peOwnerId: DbSchema.act.partEntities.ownerId.nullable(),
  832. userId: DbSchema.act.activityRegs.userId.nullable(),
  833. isPaid: DbSchema.act.activityRegs.isPaid,
  834. isCanceled: DbSchema.act.activityRegs.isCanceled,
  835. statusHistory: z.array(
  836. z.object({
  837. statusHistoryId: DbSchema.act.actRegStatusHistory.statusHistoryId,
  838. name: DbSchema.act.actRegStatuses.name,
  839. code: DbSchema.act.actRegStatuses.code,
  840. note: DbSchema.act.actRegStatusHistory.note,
  841. setDate: DbSchema.act.actRegStatusHistory.setDate,
  842. actRegStatusId: DbSchema.act.actRegStatuses.actRegStatusId,
  843. color: DbSchema.act.actRegStatuses.color,
  844. isPaymentOpen: DbSchema.act.actRegStatuses.isPaymentOpen,
  845. }),
  846. ),
  847. fields: z.array(
  848. CustomFieldWithValidatorsAndValue.extend({
  849. arffId: DbSchema.act.activityRegFormFields.arffId,
  850. isChangeResetStatus:
  851. DbSchema.act.activityRegFormFields.isChangeResetStatus,
  852. }),
  853. ),
  854. isUserReg: DbSchema.act.activities.isUserReg,
  855. }),
  856. )`
  857. select
  858. ar.activity_reg_id "activityRegId",
  859. ar.number "activityRegNumber",
  860. ar.activity_id "activityId",
  861. ar.activity_code "activityCode",
  862. ar.activity_public_name "activityPublicName",
  863. ar.pe_id "peId",
  864. ar.pe_name "peName",
  865. ar.pe_owner_id "peOwnerId",
  866. ar.user_id "userId",
  867. ar.is_paid "isPaid",
  868. ar.is_canceled "isCanceled",
  869. ar.status_history "statusHistory",
  870. coalesce(f.fields "fields", '[]'::jsonb),
  871. ar.is_user_reg "isUserReg"
  872. from
  873. act.act_regs_with_status ar
  874. left join lateral (
  875. select
  876. jsonb_agg(jsonb_build_object(
  877. 'fieldDefinitionId', cfd.field_definition_id,
  878. 'arffId', f_1.arff_id,
  879. 'isCopyUserValue', f_1.is_copy_user_value,
  880. 'code', cfd.code,
  881. 'value', afv.value,
  882. 'fieldTypeCode', ft.code,
  883. 'title', coalesce(f_1.field_title_override, cfd.title),
  884. 'mask', cfd.mask,
  885. 'options', cfd.options,
  886. 'isChangeResetStatus', f_1.is_change_reset_status,
  887. 'validators', cfwv.validators,
  888. 'orderNumber', f_1.order_number
  889. )) as fields
  890. from
  891. act.activity_reg_form_fields f_1
  892. left join cf.custom_field_definitions cfd on
  893. f_1.field_definition_id = cfd.field_definition_id
  894. left join cf.field_types ft on
  895. cfd.field_type_id = ft.field_type_id
  896. left join act.ar_field_values afv on
  897. f_1.arff_id = afv.arff_id
  898. and afv.activity_reg_id = ar.activity_reg_id
  899. left join cf.custom_fields_with_validators cfwv on
  900. cfwv.field_definition_id = cfd.field_definition_id
  901. where
  902. f_1.activity_id = ar.activity_id) f on
  903. true
  904. where ar.activity_reg_id = ${activityRegId}
  905. `);
  906. }
  907. async refundByPeMemberId(peMember: {
  908. peMemberId: string;
  909. peId: string;
  910. userId: string;
  911. }) {
  912. const actRegs = await selPool.any(sql.type(
  913. z.object({
  914. activityRegId: DbSchema.act.activityRegs.activityRegId,
  915. activityId: DbSchema.act.activityRegs.activityId,
  916. }),
  917. )`
  918. select
  919. r.activity_reg_id "activityRegId",
  920. r.activity_id "activityId"
  921. from
  922. act.activity_regs r
  923. left join act.activities a on
  924. a.activity_id = r.activity_id and
  925. a.payment_config = 'PER_PARTICIPANT'
  926. where
  927. pe_id = ${peMember.peId}
  928. `);
  929. for (const actReg of actRegs) {
  930. const orderItem = await selPool.maybeOne(sql.type(
  931. z.object({
  932. orderItemId: DbSchema.shop.orderItems.orderItemId,
  933. orderId: DbSchema.shop.orderItems.orderId,
  934. productId: DbSchema.shop.orderItems.productId,
  935. unitPrice: DbSchema.shop.orderItems.unitPrice,
  936. }),
  937. )`
  938. select
  939. oi.order_item_id "orderItemId",
  940. oi.order_id "orderId",
  941. oi.product_id "productId",
  942. oi.unit_price::float "unitPrice"
  943. from
  944. shop.order_items oi
  945. where
  946. oi.activity_reg_id = ${actReg.activityRegId} and
  947. oi.pe_member_id = ${peMember.peMemberId} and
  948. oi.status = 'PAID'
  949. `);
  950. if (!orderItem) {
  951. logger.error(
  952. `Order item for activity reg ${actReg.activityRegId} and pe member ${peMember.peMemberId} not found`,
  953. );
  954. throw new Error("Order item not found");
  955. }
  956. await ordersService.refundOrderItem(orderItem.orderItemId);
  957. }
  958. }
  959. async getActivityRegsByPeId(peId: string) {
  960. return await selPool.any(sql.type(
  961. z.object({
  962. activityRegId: DbSchema.act.activityRegs.activityRegId,
  963. activityId: DbSchema.act.activityRegs.activityId,
  964. activityRegNumber: DbSchema.act.activityRegs.number,
  965. }),
  966. )`
  967. select
  968. r.activity_reg_id "activityRegId",
  969. r.activity_id "activityId",
  970. r.number "activityRegNumber"
  971. from
  972. act.activity_regs r
  973. where
  974. pe_id = ${peId}
  975. `);
  976. }
  977. async refundByActivityRegId(activityRegId: string) {
  978. const activityId = await selPool.maybeOneFirst(sql.type(
  979. z.object({
  980. activityId: DbSchema.act.activityRegs.activityId,
  981. }),
  982. )`
  983. select
  984. r.activity_id "activityId"
  985. from
  986. act.activity_regs r
  987. left join act.activities a on
  988. a.activity_id = r.activity_id
  989. where
  990. r.activity_reg_id = ${activityRegId}
  991. `);
  992. if (!activityId) {
  993. throw new Error("Activity reg not found");
  994. }
  995. const orderItems = await selPool.any(sql.type(
  996. z.object({
  997. orderItemId: DbSchema.shop.orderItems.orderItemId,
  998. orderId: DbSchema.shop.orderItems.orderId,
  999. }),
  1000. )`
  1001. select
  1002. oi.order_item_id "orderItemId",
  1003. oi.order_id "orderId"
  1004. from
  1005. shop.order_items oi
  1006. where
  1007. oi.activity_reg_id = ${activityRegId}
  1008. `);
  1009. for (const orderItem of orderItems) {
  1010. await ordersService.refundOrderItem(orderItem.orderItemId);
  1011. }
  1012. }
  1013. async resetAllActivityRegsByPe(
  1014. tr: DatabaseTransactionConnection,
  1015. peid: string,
  1016. ) {
  1017. const actRegs = await this.getActivityRegsByPeId(peid);
  1018. for (const actReg of actRegs) {
  1019. const initialRegStatusId = await this.getInitialRegStatusId(
  1020. actReg.activityId,
  1021. );
  1022. await tr.query(sql.unsafe`
  1023. insert into act.act_reg_status_history
  1024. (act_reg_status_history_id, act_reg_id, act_reg_status_id, note)
  1025. values
  1026. (${v7()}, ${actReg.activityRegId}, ${initialRegStatusId}, 'Изменена сущность участия ${peid}')
  1027. `);
  1028. }
  1029. }
  1030. async getActWithActValidators(activityCode: string) {
  1031. return await selPool.maybeOne(sql.type(
  1032. z.object({
  1033. activityId: DbSchema.act.activities.activityId,
  1034. code: DbSchema.act.activities.code,
  1035. publicName: DbSchema.act.activities.publicName,
  1036. eventInstId: DbSchema.act.activities.eventInstId,
  1037. categoryId: DbSchema.act.activities.categoryId,
  1038. categoryCode: DbSchema.act.activityCategories.code,
  1039. validators: z.array(apiTypes.activities.ActValidator),
  1040. peTypes: z.array(
  1041. z.object({
  1042. peTypeId: DbSchema.act.peTypes.peTypeId,
  1043. code: DbSchema.act.peTypes.code,
  1044. name: DbSchema.act.peTypes.name,
  1045. }),
  1046. ),
  1047. isUserReg: DbSchema.act.activities.isUserReg,
  1048. }),
  1049. )`
  1050. select
  1051. a.activity_id "activityId",
  1052. a.code,
  1053. a.public_name "publicName",
  1054. a.event_inst_id "eventInstId",
  1055. a.category_id "categoryId",
  1056. a.category_code "categoryCode",
  1057. a.validators,
  1058. a.pe_types "peTypes",
  1059. a.is_user_reg "isUserReg"
  1060. from
  1061. act.act_with_validators a
  1062. where a.code = ${activityCode}
  1063. `);
  1064. }
  1065. }
  1066. export const cActService = new CActService();