geek-custom-api-core 0.0.44 → 0.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.history/{package_20260115025656.json → package_20260213160542.json} +1 -1
  2. package/.history/{package_20260115025802.json → package_20260219172011.json} +3 -3
  3. package/.history/package_20260219172015.json +96 -0
  4. package/.history/sql/deposit_20260214030527.sql +122 -0
  5. package/.history/sql/deposit_20260214050226.sql +120 -0
  6. package/.history/sql/deposit_20260215014103.sql +121 -0
  7. package/.history/sql/deposit_20260215030109.sql +122 -0
  8. package/.history/sql/deposit_20260215040258.sql +123 -0
  9. package/.history/sql/deposit_20260215043546.sql +124 -0
  10. package/.history/sql/deposit_20260215045642.sql +125 -0
  11. package/dist/api/app/deposit/deposit.batch.service.d.ts +4 -0
  12. package/dist/api/app/deposit/deposit.batch.service.js +46 -0
  13. package/dist/api/app/deposit/deposit.batch.service.js.map +1 -0
  14. package/dist/api/app/deposit/deposit.controller.d.ts +16 -3
  15. package/dist/api/app/deposit/deposit.controller.js +167 -26
  16. package/dist/api/app/deposit/deposit.controller.js.map +1 -1
  17. package/dist/api/app/deposit/deposit.module.js +2 -1
  18. package/dist/api/app/deposit/deposit.module.js.map +1 -1
  19. package/dist/api/app/deposit/deposit.service.d.ts +18 -5
  20. package/dist/api/app/deposit/deposit.service.js +386 -229
  21. package/dist/api/app/deposit/deposit.service.js.map +1 -1
  22. package/dist/api/app/deposit/dto/depositCouponCodeSearch.dto.js.map +1 -1
  23. package/dist/api/app/deposit/dto/depositManager.dto.d.ts +1 -1
  24. package/dist/api/app/deposit/dto/depositManager.dto.js +3 -3
  25. package/dist/api/app/deposit/dto/depositManager.dto.js.map +1 -1
  26. package/dist/api/app/deposit/dto/depositOrderUse.dto.d.ts +1 -0
  27. package/dist/api/app/deposit/dto/depositOrderUse.dto.js +8 -7
  28. package/dist/api/app/deposit/dto/depositOrderUse.dto.js.map +1 -1
  29. package/dist/api/app/deposit/entity/depositOrder.entity.d.ts +1 -0
  30. package/dist/api/app/deposit/entity/depositOrder.entity.js +10 -0
  31. package/dist/api/app/deposit/entity/depositOrder.entity.js.map +1 -1
  32. package/dist/api/app/deposit/entity/depositOrderDetail.entity.d.ts +10 -4
  33. package/dist/api/app/deposit/entity/depositOrderDetail.entity.js +57 -12
  34. package/dist/api/app/deposit/entity/depositOrderDetail.entity.js.map +1 -1
  35. package/dist/common/config/load-secrets.js +5 -1
  36. package/dist/common/config/load-secrets.js.map +1 -1
  37. package/dist/common/util/gsUtil.js +0 -3
  38. package/dist/common/util/gsUtil.js.map +1 -1
  39. package/dist/tsconfig.build.tsbuildinfo +1 -1
  40. package/package.json +4 -3
  41. package/sql/deposit.sql +8 -4
@@ -55,9 +55,10 @@ const depositCoupon_entity_1 = require("./entity/depositCoupon.entity");
55
55
  const depositCouponManager_entity_1 = require("./entity/depositCouponManager.entity");
56
56
  const typeorm_1 = require("typeorm");
57
57
  const depositOrderDetail_entity_1 = require("./entity/depositOrderDetail.entity");
58
- const depositOrder_entity_1 = require("./entity/depositOrder.entity");
59
58
  const partner_service_1 = require("../../admin/partner/partner.service");
60
59
  const core_1 = require("@nestjs/core");
60
+ const date_fns_1 = require("date-fns");
61
+ const depositOrder_entity_1 = require("./entity/depositOrder.entity");
61
62
  let DepositService = class DepositService extends default_service_1.DefaultService {
62
63
  constructor(partnerService, moduleRef) {
63
64
  super(moduleRef);
@@ -88,6 +89,8 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
88
89
  memberNo: dto.memberNo,
89
90
  memberId: dto.memberId,
90
91
  memberName: dto.memberName,
92
+ depositAmount: dto.amount,
93
+ reason: dto.reason,
91
94
  expiredDt: dto.expiredDt,
92
95
  depositType: enum_1.DEPOSIT_TYPE.ISSUE,
93
96
  source: enum_1.DEPOSIT_SOURCE.ADMIN,
@@ -104,12 +107,14 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
104
107
  const prevBalance = (_a = lastLedger === null || lastLedger === void 0 ? void 0 : lastLedger.balanceAfter) !== null && _a !== void 0 ? _a : 0;
105
108
  await manager.query(`
106
109
  INSERT INTO gs_depositManager
107
- (partnerSno, memberNo, depositType, source, managerSno,
110
+ (partnerSno, memberNo, memberId, memberName, depositType, source, managerSno,
108
111
  refType, refSno, depositAmount, balanceAfter, reason)
109
- VALUES (?, ?, 'ISSUE', 'ADMIN', ?, 'JOB_DETAIL', ?, ?, ?, ?)
112
+ VALUES (?, ?, ?, ?, 'ISSUE', 'ADMIN', ?, 'JOB_DETAIL', ?, ?, ?, ?)
110
113
  `, [
111
114
  dto.partnerSno,
112
115
  dto.memberNo,
116
+ dto.memberId,
117
+ dto.memberName,
113
118
  dto.managerSno,
114
119
  jobDetail.sno,
115
120
  dto.amount,
@@ -180,70 +185,92 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
180
185
  }
181
186
  async issueSingleRow({ row, jobSno, managerSno, partnerSno, systemKey, longLivedToken, }) {
182
187
  const { memberNo, amount, reason, expiredDt } = row;
183
- if (!memberNo || !amount || !reason) {
184
- throw new Error('필수 컬럼 누락');
188
+ const targetAmount = Number(amount);
189
+ if (!memberNo || isNaN(targetAmount) || targetAmount === 0 || !reason) {
190
+ throw new Error('필수 컬럼 누락 또는 금액 오류');
185
191
  }
186
- if (Number(amount) <= 0) {
187
- throw new Error('지급 금액 오류');
188
- }
189
- const member = (0, gsUtil_1.shopByServerApi)('/profile', enum_1.HTTP_METHOD.GET, systemKey, longLivedToken, {
190
- memberNo: memberNo,
191
- });
192
+ const member = await (0, gsUtil_1.shopByServerApi)('/profile', enum_1.HTTP_METHOD.GET, systemKey, longLivedToken, { memberNo: memberNo });
193
+ if (!member)
194
+ throw new Error('존재하지 않는 회원');
192
195
  const entityManager = await this.getEntityManager();
193
196
  return await entityManager.transaction(async (manager) => {
194
197
  var _a;
195
- const jdResult = await manager.query(`
196
- INSERT INTO gs_depositJobDetail
197
- (jobSno, partnerSno, memberNo, memberId, memberName, depositAmount, reason, expiredDt)
198
- VALUES (?, ?, ?, ?, ?, ?)
199
- `, [
200
- jobSno,
201
- partnerSno,
202
- memberNo,
203
- member['memberId'],
204
- member['memberName'],
205
- amount,
206
- reason,
207
- expiredDt !== null && expiredDt !== void 0 ? expiredDt : null,
208
- ]);
209
- const jobDetailSno = jdResult.insertId;
210
- const [lastLedger] = await manager.query(`
211
- SELECT balanceAfter
212
- FROM gs_depositManager
213
- WHERE partnerSno = ?
214
- AND memberNo = ?
215
- ORDER BY sno DESC
216
- LIMIT 1
217
- FOR UPDATE
218
- `, [partnerSno, memberNo]);
219
- const prevBalance = (_a = lastLedger === null || lastLedger === void 0 ? void 0 : lastLedger.balanceAfter) !== null && _a !== void 0 ? _a : 0;
220
- const nextBalance = prevBalance + Number(amount);
221
- await manager.query(`
222
- INSERT INTO gs_depositManager
223
- (
224
- partnerSno,
225
- memberNo,
226
- memberId,
227
- memberName,
228
- depositType,
229
- source,
230
- managerSno,
231
- refType,
232
- refSno,
233
- depositAmount,
234
- balanceAfter,
235
- reason
236
- )
237
- VALUES (?, ?, ?, ?, 'ISSUE', 'ADMIN', ?, 'JOB_DETAIL', ?, ?, ?)
238
- `, [
239
- partnerSno,
240
- memberNo,
241
- managerSno,
242
- jobDetailSno,
243
- amount,
244
- nextBalance,
245
- reason,
246
- ]);
198
+ const [lastLedger] = await manager.query(`SELECT balanceAfter FROM gs_depositManager WHERE partnerSno = ? AND memberNo = ? ORDER BY sno DESC LIMIT 1 FOR UPDATE`, [partnerSno, memberNo]);
199
+ const prevBalance = Number((_a = lastLedger === null || lastLedger === void 0 ? void 0 : lastLedger.balanceAfter) !== null && _a !== void 0 ? _a : 0);
200
+ if (targetAmount > 0) {
201
+ const jdResult = await manager.query(`INSERT INTO gs_depositJobDetail (jobSno, partnerSno, memberNo, memberId, memberName, depositAmount, reason, expiredDt) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
202
+ jobSno,
203
+ partnerSno,
204
+ memberNo,
205
+ member['memberId'],
206
+ member['memberName'],
207
+ targetAmount,
208
+ reason,
209
+ expiredDt !== null && expiredDt !== void 0 ? expiredDt : null,
210
+ ]);
211
+ await manager.query(`INSERT INTO gs_depositManager (partnerSno, memberNo, memberId, memberName, depositType, source, managerSno, refType, refSno, depositAmount, balanceAfter, reason)
212
+ VALUES (?, ?, ?, ?, 'ISSUE', 'ADMIN', ?, 'JOB_DETAIL', ?, ?, ?, ?)`, [
213
+ partnerSno,
214
+ memberNo,
215
+ member['memberId'],
216
+ member['memberName'],
217
+ managerSno,
218
+ jdResult.insertId,
219
+ targetAmount,
220
+ prevBalance + targetAmount,
221
+ reason,
222
+ ]);
223
+ }
224
+ else {
225
+ const deductAmount = Math.abs(targetAmount);
226
+ if (prevBalance < deductAmount)
227
+ throw new Error(`잔액 부족 (가용: ${prevBalance})`);
228
+ const deductOrder = await manager.save(depositOrder_entity_1.DepositOrder, {
229
+ partnerSno,
230
+ memberNo,
231
+ memberId: member['memberId'],
232
+ memberName: member['memberName'],
233
+ orderNo: `DEDUCT_EXCEL_${Date.now()}`,
234
+ usedAmount: deductAmount,
235
+ orderStatus: 'DONE',
236
+ });
237
+ const availableIssues = await manager.query(`SELECT jd.sno AS issueDetailSno, jd.depositAmount, jd.expiredDt, (jd.depositAmount - IFNULL(usedSub.netUsed, 0)) AS remainAmount
238
+ FROM gs_depositJobDetail jd
239
+ LEFT JOIN (SELECT issueDetailSno, SUM(usedDepositAmount - refundAmount) AS netUsed FROM gs_depositOrderDetail GROUP BY issueDetailSno) usedSub ON jd.sno = usedSub.issueDetailSno
240
+ WHERE jd.partnerSno = ? AND jd.memberNo = ? AND jd.expireFl = 'N' AND (jd.expiredDt IS NULL OR jd.expiredDt >= NOW())
241
+ HAVING remainAmount > 0 ORDER BY CASE WHEN jd.expiredDt IS NULL THEN 1 ELSE 0 END ASC, jd.expiredDt ASC, jd.sno ASC FOR UPDATE`, [partnerSno, memberNo]);
242
+ let remainToDeduct = deductAmount;
243
+ for (const issue of availableIssues) {
244
+ if (remainToDeduct <= 0)
245
+ break;
246
+ const take = Math.min(Number(issue.remainAmount), remainToDeduct);
247
+ await manager.save(depositOrderDetail_entity_1.DepositOrderDetail, {
248
+ orderSno: deductOrder.sno,
249
+ partnerSno,
250
+ memberNo,
251
+ orderProductOptionNo: 'ADMIN_DEDUCT',
252
+ mallProductNo: 'ADMIN_DEDUCT',
253
+ orderProductName: `관리자 차감: ${reason}`,
254
+ usedDepositAmount: take,
255
+ issueDetailSno: issue.issueDetailSno,
256
+ expireDt: issue.expiredDt,
257
+ refundYn: enum_1.YN.N,
258
+ });
259
+ remainToDeduct -= take;
260
+ }
261
+ await manager.query(`INSERT INTO gs_depositManager (partnerSno, memberNo, memberId, memberName, depositType, source, managerSno, refType, refSno, depositAmount, balanceAfter, reason)
262
+ VALUES (?, ?, ?, ?, 'CANCEL', 'ADMIN', ?, 'ORDER', ?, ?, ?, ?)`, [
263
+ partnerSno,
264
+ memberNo,
265
+ member['memberId'],
266
+ member['memberName'],
267
+ managerSno,
268
+ deductOrder.sno,
269
+ -deductAmount,
270
+ prevBalance - deductAmount,
271
+ reason,
272
+ ]);
273
+ }
247
274
  });
248
275
  }
249
276
  async getDepositManagerList(dto) {
@@ -267,12 +294,12 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
267
294
  }
268
295
  if (dto.fromDt) {
269
296
  qb.andWhere('dm.regDt >= :fromDt', {
270
- fromDt: dto.fromDt,
297
+ fromDt: dto.fromDt + ' 00:00:00',
271
298
  });
272
299
  }
273
300
  if (dto.endDt) {
274
301
  qb.andWhere('dm.regDt <= :endDt', {
275
- endDt: dto.endDt,
302
+ endDt: dto.endDt + ' 23:59:59',
276
303
  });
277
304
  }
278
305
  qb.select([
@@ -347,8 +374,8 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
347
374
  'dm.*',
348
375
  'o.orderNo as orderNo',
349
376
  'o.usedAmount as usedAmount',
350
- 'od.orderGoodsNo as orderGoodsNo',
351
- 'od.orderGoodsName as orderGoodsName',
377
+ 'od.orderProductNo as orderProductNo',
378
+ 'od.orderProductName as orderProductName',
352
379
  'od.orderAmount as orderAmount',
353
380
  'od.usedDepositAmount as usedDepositAmount',
354
381
  'od.allocatedRatio as allocatedRatio',
@@ -362,7 +389,7 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
362
389
  throw new common_1.HttpException('쿠폰 등록 시작일은 쿠폰 등록 종료일 이전이어야 합니다.', common_1.HttpStatus.BAD_REQUEST);
363
390
  }
364
391
  const entityManager = await this.getEntityManager();
365
- const couponManager = await entityManager.transaction(async (manager) => {
392
+ return await entityManager.transaction(async (manager) => {
366
393
  var _a;
367
394
  const result = await manager.save(depositCouponManager_entity_1.DepositCouponManager, {
368
395
  partnerSno: dto.partnerSno,
@@ -375,24 +402,24 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
375
402
  useFl: (_a = dto.useFl) !== null && _a !== void 0 ? _a : enum_1.YN.Y,
376
403
  });
377
404
  const managerSno = result.sno;
405
+ const coupons = [];
378
406
  for (let i = 0; i < dto.issueLimit; i++) {
379
- const couponCode = (0, gsUtil_1.getRandomString)(10);
380
- await manager.save(depositCoupon_entity_1.DepositCoupon, {
407
+ coupons.push({
381
408
  policySno: managerSno,
382
- couponCode: couponCode,
409
+ couponCode: (0, gsUtil_1.getRandomString)(10),
383
410
  partnerSno: dto.partnerSno,
384
411
  });
385
412
  }
413
+ if (coupons.length > 0) {
414
+ await manager.insert(depositCoupon_entity_1.DepositCoupon, coupons);
415
+ }
386
416
  return result;
387
417
  });
388
- return couponManager;
389
418
  }
390
419
  async getCouponManagerList(dto) {
391
420
  const entityManager = await this.getEntityManager();
392
421
  const qb = entityManager.createQueryBuilder(depositCouponManager_entity_1.DepositCouponManager, 'dcm');
393
- qb.andWhere('dcm.partnerSno = :partnerSno', {
394
- partnerSno: dto.partnerSno,
395
- });
422
+ qb.andWhere('dcm.partnerSno = :partnerSno', { partnerSno: dto.partnerSno });
396
423
  if (dto.title) {
397
424
  qb.andWhere('dcm.title LIKE :title', {
398
425
  title: `%${dto.title}%`,
@@ -420,18 +447,14 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
420
447
  async getCouponCodeList(dto) {
421
448
  const entityManager = await this.getEntityManager();
422
449
  const qb = entityManager.createQueryBuilder(depositCoupon_entity_1.DepositCoupon, 'dc');
423
- qb.andWhere('dc.partnerSno = :partnerSno', {
424
- partnerSno: dto.partnerSno,
425
- });
450
+ qb.andWhere('dc.partnerSno = :partnerSno', { partnerSno: dto.partnerSno });
426
451
  if (dto.couponCode) {
427
452
  qb.andWhere('dc.couponCode LIKE :couponCode', {
428
453
  couponCode: `%${dto.couponCode}%`,
429
454
  });
430
455
  }
431
456
  if (dto.policySno) {
432
- qb.andWhere('dc.policySno = :policySno', {
433
- policySno: dto.policySno,
434
- });
457
+ qb.andWhere('dc.policySno = :policySno', { policySno: dto.policySno });
435
458
  }
436
459
  qb.select(['dc.*']);
437
460
  qb.orderBy('dc.sno', 'DESC');
@@ -457,9 +480,7 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
457
480
  .addSelect('SUM(od.usedDepositAmount)', 'usedAmount')
458
481
  .from(depositOrderDetail_entity_1.DepositOrderDetail, 'od')
459
482
  .groupBy('od.issueDetailSno'), 'od', 'jd.sno = od.issueDetailSno')
460
- .where('jd.partnerSno = :partnerSno', {
461
- partnerSno: dto.partnerSno,
462
- })
483
+ .where('jd.partnerSno = :partnerSno', { partnerSno: dto.partnerSno })
463
484
  .andWhere('jd.expireFl = :expireFl', { expireFl: 'n' })
464
485
  .andWhere('(jd.expiredDt IS NULL OR jd.expiredDt >= NOW())')
465
486
  .groupBy('jd.memberNo')
@@ -470,9 +491,7 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
470
491
  const countQb = entityManager
471
492
  .createQueryBuilder(depositJobDetail_entity_1.DepositJobDetail, 'jd')
472
493
  .select('COUNT(DISTINCT jd.memberNo)', 'cnt')
473
- .where('jd.partnerSno = :partnerSno', {
474
- partnerSno: dto.partnerSno,
475
- })
494
+ .where('jd.partnerSno = :partnerSno', { partnerSno: dto.partnerSno })
476
495
  .andWhere('jd.expireFl = :expireFl', { expireFl: 'n' })
477
496
  .andWhere('(jd.expiredDt IS NULL OR jd.expiredDt >= NOW())');
478
497
  const count = await countQb.getRawOne();
@@ -484,8 +503,9 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
484
503
  return deposit;
485
504
  }
486
505
  async redeemCoupon(dto) {
506
+ console.log(dto);
487
507
  const entityManager = await this.getEntityManager();
488
- entityManager.transaction(async (manager) => {
508
+ return await entityManager.transaction(async (manager) => {
489
509
  var _a;
490
510
  const coupon = await manager.findOne(depositCoupon_entity_1.DepositCoupon, {
491
511
  where: {
@@ -495,6 +515,8 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
495
515
  if (!coupon) {
496
516
  throw new common_1.HttpException('쿠폰이 존재하지 않습니다.', common_1.HttpStatus.BAD_REQUEST);
497
517
  }
518
+ console.log('########################');
519
+ console.log(coupon);
498
520
  if (coupon.usedFl === enum_1.YN.Y) {
499
521
  throw new common_1.HttpException('이미 사용된 쿠폰입니다.', common_1.HttpStatus.BAD_REQUEST);
500
522
  }
@@ -510,7 +532,8 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
510
532
  throw new common_1.HttpException('사용 불가능한 쿠폰입니다.', common_1.HttpStatus.BAD_REQUEST);
511
533
  }
512
534
  if (couponManager.issueLimit !== null &&
513
- couponManager.issueCount >= couponManager.issueLimit) {
535
+ couponManager.issueCount >= couponManager.issueLimit &&
536
+ couponManager.issueLimit !== 0) {
514
537
  throw new common_1.HttpException('쿠폰 발급 수량이 초과되었습니다.', common_1.HttpStatus.BAD_REQUEST);
515
538
  }
516
539
  const job = await manager.save(depositJob_entity_1.DepositJob, {
@@ -524,17 +547,14 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
524
547
  });
525
548
  let expireDt = null;
526
549
  if (couponManager.expireAfterDays > 0) {
527
- expireDt = new Date();
528
- expireDt
529
- .setDate(expireDt.getDate() + couponManager.expireAfterDays)
530
- .format('YYYY-MM-DD 23:59:59');
550
+ expireDt = (0, date_fns_1.endOfDay)((0, date_fns_1.addDays)(new Date(), couponManager.expireAfterDays));
531
551
  }
532
552
  const jobDetail = await manager.save(depositJobDetail_entity_1.DepositJobDetail, {
533
553
  jobSno: job.sno,
534
554
  partnerSno: coupon.partnerSno,
535
555
  depositAmount: couponManager.depositAmount,
536
556
  reason: couponManager.title,
537
- memberNo: coupon.memberNo,
557
+ memberNo: dto.memberNo,
538
558
  memberId: dto.memberId,
539
559
  memberName: dto.memberName,
540
560
  expiredDt: expireDt,
@@ -581,18 +601,34 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
581
601
  });
582
602
  }
583
603
  async getDeposit(memberNo, partnerSno) {
584
- var _a;
585
604
  const entityManager = await this.getEntityManager();
586
- const deposit = await entityManager.findOne(depositManager_entity_1.DepositManager, {
587
- where: {
588
- memberNo: memberNo,
589
- partnerSno: partnerSno,
590
- },
591
- order: {
592
- sno: 'DESC',
593
- },
594
- });
595
- return { depositAmount: (_a = deposit === null || deposit === void 0 ? void 0 : deposit.balanceAfter) !== null && _a !== void 0 ? _a : 0 };
605
+ const [balanceRow] = await entityManager.query(`
606
+ SELECT (
607
+ -- 1. 유효한 총 지급액 합계
608
+ (SELECT IFNULL(SUM(depositAmount), 0)
609
+ FROM gs_depositJobDetail
610
+ WHERE partnerSno = ?
611
+ AND memberNo = ?
612
+ AND expireFl = 'N'
613
+ AND depositAmount > 0
614
+ AND (expiredDt IS NULL OR expiredDt >= NOW())
615
+ )
616
+ -
617
+ -- 2. 해당 지급건들에 연결된 실제 순사용액(사용-환불) 합계
618
+ (SELECT IFNULL(SUM(usedDepositAmount - refundAmount), 0)
619
+ FROM gs_depositOrderDetail
620
+ WHERE partnerSno = ?
621
+ AND memberNo = ?
622
+ AND issueDetailSno IN (
623
+ SELECT sno FROM gs_depositJobDetail
624
+ WHERE partnerSno = ? AND memberNo = ? AND expireFl = 'N'
625
+ )
626
+ )
627
+ ) AS totalAvailable
628
+ `, [partnerSno, memberNo, partnerSno, memberNo, partnerSno, memberNo]);
629
+ return {
630
+ depositAmount: Number((balanceRow === null || balanceRow === void 0 ? void 0 : balanceRow.totalAvailable) || 0),
631
+ };
596
632
  }
597
633
  async getDepositHistory(dto) {
598
634
  const entityManager = await this.getEntityManager();
@@ -612,147 +648,268 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
612
648
  const data = [list, count];
613
649
  return (0, gsUtil_1.paginateResponse)(data, dto.page, dto.take);
614
650
  }
615
- async useDepositForOrder(dto) {
651
+ async useDepositPrepare(dto) {
652
+ const entityManager = await this.getEntityManager();
616
653
  const partner = await this.partnerService.findOne(dto.partnerSno);
617
- const user = await (0, gsUtil_1.shopByServerApi)('/profile', enum_1.HTTP_METHOD.GET, partner.systemKey, partner.longLivedToken, {
618
- memberNo: Number(dto.memberMappingKey),
619
- });
620
- if (!user) {
621
- return {
622
- rescode: '0001',
623
- resmessage: '사용자 정보를 찾을 수 없습니다.',
624
- };
625
- }
626
- dto.memberId = user.memberId;
627
- dto.memberName = user.memberName;
628
- if (dto.payAmt <= 0) {
629
- return {
630
- rescode: '0002',
631
- resmessage: '사용 금액은 0보다 커야 합니다.',
632
- };
633
- }
634
- const order = await (0, gsUtil_1.shopByServerApi)(`/order/${dto.orderNo}`, enum_1.HTTP_METHOD.GET, partner.systemKey, partner.longLivedToken);
635
- if (!order) {
636
- return {
637
- rescode: '0003',
638
- resmessage: '주문번호가 존재하지 않습니다.',
639
- };
654
+ if (!partner) {
655
+ return { rescode: '0002', message: '유효하지 않은 파트너 정보입니다.' };
640
656
  }
641
- for (const orderProducts of order.orderProducts) {
642
- dto.orderProducts.push({
643
- orderProductNo: orderProducts.orderProductNo,
644
- orderProductName: orderProducts.productName,
645
- orderProductAmount: orderProducts.productSalePrice,
646
- });
647
- }
648
- if (!dto.orderProducts || dto.orderProducts.length === 0) {
649
- throw new common_1.HttpException('주문 상품 정보가 필요합니다.', common_1.HttpStatus.BAD_REQUEST);
657
+ const user = await (0, gsUtil_1.shopByServerApi)('/profile', enum_1.HTTP_METHOD.GET, partner.systemKey, partner.longLivedToken, { memberNo: Number(dto.memberMappingKey) });
658
+ if (!user) {
659
+ return { rescode: '0001', message: '사용자 정보를 찾을 수 없습니다.' };
650
660
  }
651
- const entityManager = await this.getEntityManager();
661
+ const targetMemberNo = Number(user.memberNo);
662
+ const targetPartnerSno = partner.sno;
652
663
  return await entityManager.transaction(async (manager) => {
653
- var _a, _b;
654
- const availableIssues = await manager.query(`
655
- SELECT
656
- jd.sno AS issueDetailSno,
657
- jd.depositAmount,
658
- jd.expiredDt,
659
- (jd.depositAmount - IFNULL(usedSub.usedTotal, 0)) AS remainAmount
660
- FROM gs_depositJobDetail jd
661
- LEFT JOIN (
662
- SELECT issueDetailSno, SUM(usedDepositAmount) AS usedTotal
663
- FROM gs_depositOrderDetail
664
- GROUP BY issueDetailSno
665
- ) usedSub ON jd.sno = usedSub.issueDetailSno
666
- WHERE jd.partnerSno = ?
667
- AND jd.memberNo = ?
668
- AND jd.expireFl = 'N'
669
- AND (jd.expiredDt IS NULL OR jd.expiredDt >= NOW())
670
- HAVING remainAmount > 0
671
- ORDER BY
672
- CASE WHEN jd.expiredDt IS NULL THEN 1 ELSE 0 END ASC, -- 만료일 없는 건 뒤로
673
- jd.expiredDt ASC,
674
- jd.sno ASC
675
- FOR UPDATE
676
- `, [dto.partnerSno, user.memberNo]);
677
- const totalAvailable = availableIssues.reduce((sum, row) => sum + Number(row.remainAmount), 0);
664
+ const [balanceRow] = await manager.query(`
665
+ SELECT
666
+ (SUM(jd.depositAmount) - IFNULL(SUM(usedSub.netUsed), 0)) AS totalAvailable
667
+ FROM gs_depositJobDetail jd
668
+ LEFT JOIN (
669
+ SELECT
670
+ issueDetailSno,
671
+ -- 사용 금액에서 환불 금액을 뺀 '순수 사용액'을 합산
672
+ SUM(usedDepositAmount - refundAmount) AS netUsed
673
+ FROM gs_depositOrderDetail
674
+ GROUP BY issueDetailSno
675
+ ) usedSub ON jd.sno = usedSub.issueDetailSno
676
+ WHERE jd.partnerSno = ?
677
+ AND jd.memberNo = ?
678
+ AND jd.expireFl = 'N'
679
+ AND (jd.expiredDt IS NULL OR jd.expiredDt >= NOW())
680
+ `, [targetPartnerSno, targetMemberNo]);
681
+ const totalAvailable = Number((balanceRow === null || balanceRow === void 0 ? void 0 : balanceRow.totalAvailable) || 0);
678
682
  if (totalAvailable < dto.payAmt) {
679
- throw new common_1.HttpException(`예치금 잔액이 부족합니다. (가용: ${totalAvailable}, 요청: ${dto.payAmt})`, common_1.HttpStatus.BAD_REQUEST);
683
+ return {
684
+ rescode: '0006',
685
+ message: `예치금 잔액이 부족합니다. (가용: ${totalAvailable}, 요청: ${dto.payAmt})`,
686
+ };
680
687
  }
681
688
  const depositOrder = await manager.save(depositOrder_entity_1.DepositOrder, {
689
+ partnerSno: targetPartnerSno,
690
+ memberNo: targetMemberNo,
682
691
  orderNo: dto.orderNo,
683
- partnerSno: dto.partnerSno,
684
- memberNo: user.memberNo,
685
- memberId: dto.memberId,
686
- memberName: dto.memberName,
692
+ memberId: user.memberId,
693
+ memberName: user.memberName,
687
694
  usedAmount: dto.payAmt,
695
+ orderStatus: 'PENDING',
696
+ });
697
+ return {
698
+ rescode: '0000',
699
+ message: 'SUCCESS',
700
+ transactionId: depositOrder.sno,
701
+ };
702
+ });
703
+ }
704
+ async confirmDepositOrder(orderInfo) {
705
+ const entityManager = await this.getEntityManager();
706
+ const orderNo = orderInfo.order.orderNo;
707
+ const depositOrder = await entityManager.findOne(depositOrder_entity_1.DepositOrder, {
708
+ where: { orderNo: orderNo },
709
+ });
710
+ if (!depositOrder || depositOrder.orderStatus !== 'PENDING') {
711
+ throw new common_1.HttpException('유효하지 않거나 이미 처리된 예약 건입니다.', common_1.HttpStatus.BAD_REQUEST);
712
+ }
713
+ const targetOrderSno = depositOrder.sno;
714
+ const targetPartnerSno = depositOrder.partnerSno;
715
+ const targetMemberNo = depositOrder.memberNo;
716
+ return await entityManager.transaction(async (manager) => {
717
+ var _a;
718
+ const availableIssues = await manager.query(`
719
+ SELECT
720
+ jd.sno AS issueDetailSno,
721
+ jd.depositAmount,
722
+ jd.expiredDt,
723
+ -- ✅ 사용액에서 환불액을 뺀 '순사용액'을 빼야 진짜 남은 잔액이 나옴
724
+ (jd.depositAmount - IFNULL(usedSub.netUsed, 0)) AS remainAmount
725
+ FROM gs_depositJobDetail jd
726
+ LEFT JOIN (
727
+ SELECT
728
+ issueDetailSno,
729
+ SUM(usedDepositAmount - refundAmount) AS netUsed -- ✅ 수정 포인트
730
+ FROM gs_depositOrderDetail
731
+ GROUP BY issueDetailSno
732
+ ) usedSub ON jd.sno = usedSub.issueDetailSno
733
+ WHERE jd.partnerSno = ?
734
+ AND jd.memberNo = ?
735
+ AND jd.expireFl = 'N'
736
+ AND (jd.expiredDt IS NULL OR jd.expiredDt >= NOW())
737
+ HAVING remainAmount > 0
738
+ ORDER BY
739
+ CASE WHEN jd.expiredDt IS NULL THEN 1 ELSE 0 END ASC,
740
+ jd.expiredDt ASC,
741
+ jd.sno ASC
742
+ FOR UPDATE
743
+ `, [targetPartnerSno, targetMemberNo]);
744
+ const flattenedOptions = [];
745
+ orderInfo.order.orderProducts.forEach((product) => {
746
+ product.orderProductOptions.forEach((option) => {
747
+ flattenedOptions.push({
748
+ mallProductNo: String(product.mallProductNo),
749
+ productName: product.productName,
750
+ orderProductOptionNo: String(option.orderProductOptionNo),
751
+ orderCnt: option.orderCnt,
752
+ optionName: option.optionName,
753
+ optionValue: option.optionValue,
754
+ mallOptionNo: option.mallOptionNo,
755
+ price: Number(option.adjustedAmt || option.salePrice),
756
+ });
757
+ });
688
758
  });
689
- let remainToDeduct = dto.payAmt;
690
- const totalOrderAmount = dto.orderProducts.reduce((sum, g) => sum + g.orderProductAmount, 0);
759
+ const totalBaseAmount = orderInfo.order.lastStandardAmt ||
760
+ flattenedOptions.reduce((sum, opt) => sum + opt.price, 0);
761
+ let remainToDeductMaster = depositOrder.usedAmount;
762
+ let issueIndex = 0;
691
763
  const orderDetails = [];
692
- for (const issue of availableIssues) {
693
- if (remainToDeduct <= 0)
694
- break;
695
- const deductFromThisIssue = Math.min(Number(issue.remainAmount), remainToDeduct);
696
- remainToDeduct -= deductFromThisIssue;
697
- let allocatedSumInIssue = 0;
698
- for (let i = 0; i < dto.orderProducts.length; i++) {
699
- const goods = dto.orderProducts[i];
700
- const ratio = totalOrderAmount > 0
701
- ? order.lastMainPayAmt / totalOrderAmount
702
- : 1 / dto.orderProducts.length;
703
- const allocatedInIssue = i === dto.orderProducts.length - 1
704
- ? deductFromThisIssue - allocatedSumInIssue
705
- : Math.round(deductFromThisIssue * ratio);
706
- allocatedSumInIssue += allocatedInIssue;
707
- if (allocatedInIssue <= 0)
708
- continue;
709
- orderDetails.push(manager.create(depositOrderDetail_entity_1.DepositOrderDetail, {
710
- orderSno: depositOrder.sno,
711
- partnerSno: dto.partnerSno,
712
- memberNo: user.memberNo,
713
- memberId: dto.memberId,
714
- memberName: dto.memberName,
715
- orderProductNo: goods.orderProductNo,
716
- orderProductName: goods.orderProductName,
717
- orderProductAmount: goods.orderProductAmount,
718
- issueDetailSno: issue.issueDetailSno,
719
- usedDepositAmount: allocatedInIssue,
720
- allocatedRatio: Number(ratio.toFixed(6)),
721
- allocatedAmount: allocatedInIssue,
722
- expireDt: (_a = issue.expiredDt) !== null && _a !== void 0 ? _a : null,
723
- }));
764
+ for (let i = 0; i < flattenedOptions.length; i++) {
765
+ const opt = flattenedOptions[i];
766
+ const ratio = totalBaseAmount > 0
767
+ ? parseFloat((opt.price / totalBaseAmount).toFixed(6))
768
+ : 0;
769
+ let amountToAllocate = i === flattenedOptions.length - 1
770
+ ? remainToDeductMaster
771
+ : Math.round(depositOrder.usedAmount * ratio);
772
+ remainToDeductMaster -= amountToAllocate;
773
+ while (amountToAllocate > 0 && issueIndex < availableIssues.length) {
774
+ const currentIssue = availableIssues[issueIndex];
775
+ const canTake = Math.min(Number(currentIssue.remainAmount), amountToAllocate);
776
+ if (canTake > 0) {
777
+ orderDetails.push(manager.create(depositOrderDetail_entity_1.DepositOrderDetail, {
778
+ orderSno: targetOrderSno,
779
+ partnerSno: targetPartnerSno,
780
+ memberNo: targetMemberNo,
781
+ orderProductOptionNo: opt.orderProductOptionNo,
782
+ mallProductNo: opt.mallProductNo,
783
+ mallOptionNo: opt.mallOptionNo,
784
+ orderProductName: `${opt.productName} (${opt.optionName}:${opt.optionValue})`,
785
+ orderCnt: opt.orderCnt,
786
+ orderAmount: opt.price,
787
+ allocatedRatio: ratio,
788
+ allocatedAmount: canTake,
789
+ issueDetailSno: currentIssue.issueDetailSno,
790
+ usedDepositAmount: canTake,
791
+ expireDt: (_a = currentIssue.expiredDt) !== null && _a !== void 0 ? _a : null,
792
+ refundYn: enum_1.YN.N,
793
+ }));
794
+ amountToAllocate -= canTake;
795
+ currentIssue.remainAmount =
796
+ Number(currentIssue.remainAmount) - canTake;
797
+ }
798
+ if (Number(currentIssue.remainAmount) <= 0) {
799
+ issueIndex++;
800
+ }
724
801
  }
725
802
  }
726
- await manager.save(depositOrderDetail_entity_1.DepositOrderDetail, orderDetails);
727
- const [lastLedger] = await manager.query(`
728
- SELECT balanceAfter
729
- FROM gs_depositManager
730
- WHERE partnerSno = ? AND memberNo = ?
731
- ORDER BY sno DESC
732
- LIMIT 1
733
- FOR UPDATE
734
- `, [dto.partnerSno, user.memberNo]);
735
- const prevBalance = (_b = lastLedger === null || lastLedger === void 0 ? void 0 : lastLedger.balanceAfter) !== null && _b !== void 0 ? _b : 0;
736
- const nextBalance = prevBalance - dto.payAmt;
737
- const depositManager = await manager.save(depositManager_entity_1.DepositManager, {
738
- partnerSno: dto.partnerSno,
739
- memberNo: user.memberNo,
740
- memberId: dto.memberId,
741
- memberName: dto.memberName,
803
+ if (orderDetails.length === 0 && depositOrder.usedAmount > 0) {
804
+ throw new common_1.HttpException('주문번호: ' +
805
+ orderNo +
806
+ ' :: 결제에 사용할 수 있는 예치금 상세 내역이 없습니다. (잔액 부족 또는 만료)', common_1.HttpStatus.INTERNAL_SERVER_ERROR);
807
+ }
808
+ if (orderDetails.length > 0) {
809
+ await manager.save(depositOrderDetail_entity_1.DepositOrderDetail, orderDetails);
810
+ }
811
+ await manager.update(depositOrder_entity_1.DepositOrder, targetOrderSno, {
812
+ orderStatus: 'DONE',
813
+ memberId: orderInfo.order.memberId || depositOrder.memberId,
814
+ memberName: orderInfo.order.ordererName || depositOrder.memberName,
815
+ });
816
+ const [lastLedger] = await manager.query(`SELECT balanceAfter FROM gs_depositManager WHERE partnerSno = ? AND memberNo = ? ORDER BY sno DESC LIMIT 1 FOR UPDATE`, [targetPartnerSno, targetMemberNo]);
817
+ const prevBalance = Number((lastLedger === null || lastLedger === void 0 ? void 0 : lastLedger.balanceAfter) || 0);
818
+ await manager.save(depositManager_entity_1.DepositManager, {
819
+ partnerSno: targetPartnerSno,
820
+ memberNo: targetMemberNo,
821
+ memberId: orderInfo.order.memberId || depositOrder.memberId,
822
+ memberName: orderInfo.order.ordererName || depositOrder.memberName,
742
823
  depositType: enum_1.DEPOSIT_TYPE.USE,
743
824
  source: enum_1.DEPOSIT_SOURCE.ORDER,
744
- managerSno: 0,
745
825
  refType: enum_1.DEPOSIT_REF_TYPE.ORDER,
746
- refSno: depositOrder.sno,
747
- depositAmount: -dto.payAmt,
748
- balanceAfter: nextBalance,
749
- reason: `주문 예치금 사용 (주문번호: ${dto.orderNo})`,
826
+ refSno: targetOrderSno,
827
+ depositAmount: -depositOrder.usedAmount,
828
+ balanceAfter: prevBalance - depositOrder.usedAmount,
829
+ reason: `주문 예치금 사용 완료 (주문번호: ${orderNo})`,
750
830
  });
751
- return {
752
- transactionId: depositManager.sno,
753
- rescode: '0000',
754
- message: 'SUCCESS',
755
- };
831
+ return { rescode: '0000', message: 'SUCCESS' };
832
+ });
833
+ }
834
+ async refundDepositOrder(targetItems) {
835
+ const entityManager = await this.getEntityManager();
836
+ return await entityManager.transaction(async (manager) => {
837
+ for (const item of targetItems) {
838
+ const orderNo = item.orderNo;
839
+ const productNo = String(item.mallProductNo);
840
+ const optionNo = String(item.mallOptionNo);
841
+ const cancelQty = Number(item.orderCnt || 0);
842
+ const depositOrder = await manager.findOne(depositOrder_entity_1.DepositOrder, {
843
+ where: { orderNo },
844
+ });
845
+ if (!depositOrder)
846
+ continue;
847
+ const usedDetails = await manager.find(depositOrderDetail_entity_1.DepositOrderDetail, {
848
+ where: {
849
+ orderSno: depositOrder.sno,
850
+ mallProductNo: productNo,
851
+ mallOptionNo: Number(optionNo),
852
+ },
853
+ order: { sno: 'ASC' },
854
+ });
855
+ if (!usedDetails.length)
856
+ continue;
857
+ const totalRemainQty = Number(usedDetails[0].orderCnt) - Number(usedDetails[0].refundCnt);
858
+ const totalRemainAmt = usedDetails.reduce((sum, d) => sum + (Number(d.usedDepositAmount) - Number(d.refundAmount)), 0);
859
+ if (totalRemainAmt <= 0 || totalRemainQty <= 0)
860
+ continue;
861
+ const isFullCancel = Number(item.orderCnt) === Number(item.originalOrderCnt);
862
+ const unitPrice = totalRemainAmt / totalRemainQty;
863
+ const actualTakeQty = Math.min(cancelQty, totalRemainQty);
864
+ let totalRefundAmt = isFullCancel
865
+ ? totalRemainAmt
866
+ : Math.round(unitPrice * actualTakeQty);
867
+ if (totalRefundAmt > totalRemainAmt)
868
+ totalRefundAmt = totalRemainAmt;
869
+ let remainRefundAmt = totalRefundAmt;
870
+ for (const detail of usedDetails) {
871
+ if (remainRefundAmt <= 0)
872
+ break;
873
+ const recordRemainAmt = Number(detail.usedDepositAmount) - Number(detail.refundAmount);
874
+ if (recordRemainAmt <= 0)
875
+ continue;
876
+ const takeAmt = Math.min(recordRemainAmt, remainRefundAmt);
877
+ const newRefundAmount = Number(detail.refundAmount) + takeAmt;
878
+ const isRecordDone = newRefundAmount >= Number(detail.usedDepositAmount);
879
+ await manager.update(depositOrderDetail_entity_1.DepositOrderDetail, detail.sno, {
880
+ refundAmount: newRefundAmount,
881
+ refundYn: isRecordDone ? enum_1.YN.Y : enum_1.YN.N,
882
+ refundDt: new Date(),
883
+ });
884
+ remainRefundAmt -= takeAmt;
885
+ }
886
+ const updatedRefundCnt = Math.min(Number(usedDetails[0].refundCnt) + actualTakeQty, Number(usedDetails[0].orderCnt));
887
+ const isFullyRefunded = updatedRefundCnt >= Number(usedDetails[0].orderCnt) &&
888
+ Number(usedDetails[0].refundAmount) >=
889
+ Number(usedDetails[0].usedDepositAmount);
890
+ await manager.update(depositOrderDetail_entity_1.DepositOrderDetail, usedDetails[0].sno, {
891
+ refundCnt: updatedRefundCnt,
892
+ refundYn: isFullyRefunded ? enum_1.YN.Y : usedDetails[0].refundYn,
893
+ });
894
+ if (totalRefundAmt > 0) {
895
+ const [lastLedger] = await manager.query(`SELECT balanceAfter FROM gs_depositManager WHERE partnerSno = ? AND memberNo = ? ORDER BY sno DESC LIMIT 1 FOR UPDATE`, [depositOrder.partnerSno, depositOrder.memberNo]);
896
+ const prevBalance = Number((lastLedger === null || lastLedger === void 0 ? void 0 : lastLedger.balanceAfter) || 0);
897
+ await manager.save(depositManager_entity_1.DepositManager, {
898
+ partnerSno: depositOrder.partnerSno,
899
+ memberNo: depositOrder.memberNo,
900
+ memberId: depositOrder.memberId,
901
+ memberName: depositOrder.memberName,
902
+ depositType: enum_1.DEPOSIT_TYPE.REFUND,
903
+ source: enum_1.DEPOSIT_SOURCE.ORDER,
904
+ refType: enum_1.DEPOSIT_REF_TYPE.ORDER,
905
+ refSno: depositOrder.sno,
906
+ depositAmount: totalRefundAmt,
907
+ balanceAfter: prevBalance + totalRefundAmt,
908
+ reason: `주문 환불 (상품:${(item.productName || productNo).substring(0, 15)}, 수량:${actualTakeQty})`,
909
+ });
910
+ }
911
+ }
912
+ return { rescode: '0000', message: 'SUCCESS' };
756
913
  });
757
914
  }
758
915
  };