geek-custom-api-core 0.0.47 → 0.0.49

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 (36) hide show
  1. package/.history/package_20260221164010.json +97 -0
  2. package/.history/package_20260221164247.json +98 -0
  3. package/.history/package_20260228094315.json +99 -0
  4. package/.history/sql/deposit_20260221172413.sql +136 -0
  5. package/.history/sql/deposit_20260221172422.sql +136 -0
  6. package/.history/sql/deposit_20260221172436.sql +136 -0
  7. package/.history/sql/deposit_20260221172455.sql +136 -0
  8. package/.history/sql/deposit_20260221172502.sql +136 -0
  9. package/.history/sql/deposit_20260221172519.sql +136 -0
  10. package/.history/sql/deposit_20260221172816.sql +141 -0
  11. package/.history/sql/deposit_20260221172832.sql +141 -0
  12. package/.history/sql/deposit_20260221172911.sql +140 -0
  13. package/.history/sql/deposit_20260221175833.sql +141 -0
  14. package/.history/sql/deposit_20260221223728.sql +141 -0
  15. package/dist/api/app/deposit/deposit.admin.controller.d.ts +33 -2
  16. package/dist/api/app/deposit/deposit.admin.controller.js +135 -36
  17. package/dist/api/app/deposit/deposit.admin.controller.js.map +1 -1
  18. package/dist/api/app/deposit/deposit.service.d.ts +39 -4
  19. package/dist/api/app/deposit/deposit.service.js +335 -170
  20. package/dist/api/app/deposit/deposit.service.js.map +1 -1
  21. package/dist/api/app/deposit/dto/depositJobSearch.dto.d.ts +8 -0
  22. package/dist/api/app/deposit/dto/depositJobSearch.dto.js +53 -0
  23. package/dist/api/app/deposit/dto/depositJobSearch.dto.js.map +1 -0
  24. package/dist/api/app/deposit/entity/depositJobResult.entity.d.ts +12 -0
  25. package/dist/api/app/deposit/entity/depositJobResult.entity.js +72 -0
  26. package/dist/api/app/deposit/entity/depositJobResult.entity.js.map +1 -0
  27. package/dist/common/config/orm.config.js +1 -1
  28. package/dist/common/config/orm.config.js.map +1 -1
  29. package/dist/common/util/gsUtil.js +3 -0
  30. package/dist/common/util/gsUtil.js.map +1 -1
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +2 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/tsconfig.build.tsbuildinfo +1 -1
  35. package/package.json +4 -2
  36. package/sql/deposit.sql +24 -8
@@ -41,6 +41,7 @@ var __importStar = (this && this.__importStar) || (function () {
41
41
  var __metadata = (this && this.__metadata) || function (k, v) {
42
42
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
43
  };
44
+ var DepositService_1;
44
45
  Object.defineProperty(exports, "__esModule", { value: true });
45
46
  exports.DepositService = void 0;
46
47
  const common_1 = require("@nestjs/common");
@@ -48,6 +49,7 @@ const default_service_1 = require("../../../common/service/default.service");
48
49
  const depositJob_entity_1 = require("./entity/depositJob.entity");
49
50
  const enum_1 = require("../../../common/enum/enum");
50
51
  const depositJobDetail_entity_1 = require("./entity/depositJobDetail.entity");
52
+ const depositJobResult_entity_1 = require("./entity/depositJobResult.entity");
51
53
  const XLSX = __importStar(require("xlsx"));
52
54
  const depositManager_entity_1 = require("./entity/depositManager.entity");
53
55
  const gsUtil_1 = require("../../../common/util/gsUtil");
@@ -59,70 +61,136 @@ const partner_service_1 = require("../../admin/partner/partner.service");
59
61
  const core_1 = require("@nestjs/core");
60
62
  const date_fns_1 = require("date-fns");
61
63
  const depositOrder_entity_1 = require("./entity/depositOrder.entity");
62
- let DepositService = class DepositService extends default_service_1.DefaultService {
64
+ let DepositService = DepositService_1 = class DepositService extends default_service_1.DefaultService {
63
65
  constructor(partnerService, moduleRef) {
64
66
  super(moduleRef);
65
67
  this.partnerService = partnerService;
66
68
  this.moduleRef = moduleRef;
69
+ this.logger = new common_1.Logger(DepositService_1.name);
67
70
  }
68
71
  async issueSingle(dto) {
69
- if (dto.amount <= 0) {
70
- throw new common_1.HttpException('예치금 금액은 0보다 큰 값이어야 합니다.', common_1.HttpStatus.BAD_REQUEST);
71
- }
72
- if (dto.expiredDt && dto.expiredDt < new Date()) {
73
- throw new common_1.HttpException('만료일시는 현재 시간 이후여야 합니다.', common_1.HttpStatus.BAD_REQUEST);
74
- }
72
+ const targetAmount = Number(dto.amount);
75
73
  const entityManager = await this.getEntityManager();
76
- entityManager.transaction(async (manager) => {
77
- var _a;
74
+ return await entityManager.transaction(async (manager) => {
75
+ const partner = await this.partnerService.findOne(dto.partnerSno);
76
+ const member = await (0, gsUtil_1.shopByServerApi)('/profile', enum_1.HTTP_METHOD.GET, partner.systemKey, partner.longLivedToken, { memberNo: dto.memberNo });
77
+ if (!member) {
78
+ throw new Error('존재하지 않는 회원');
79
+ }
80
+ const memberId = member.memberId;
81
+ const memberName = member.memberName;
78
82
  const job = await manager.save(depositJob_entity_1.DepositJob, {
79
83
  managerSno: dto.managerSno,
80
84
  jobType: enum_1.DEPOSIT_JOB_TYPE.SINGLE,
85
+ jobName: targetAmount > 0 ? '단건 지급' : '단건 차감',
81
86
  totalCount: 1,
82
- successCount: 0,
87
+ successCount: 1,
83
88
  failCount: 0,
84
89
  status: enum_1.DEPOSIT_STATUS.DONE,
85
90
  });
86
- const jobDetail = await manager.save(depositJobDetail_entity_1.DepositJobDetail, {
87
- jobSno: job.sno,
88
- partnerSno: dto.partnerSno,
89
- memberNo: dto.memberNo,
90
- memberId: dto.memberId,
91
- memberName: dto.memberName,
92
- depositAmount: dto.amount,
93
- reason: dto.reason,
94
- expiredDt: dto.expiredDt,
95
- depositType: enum_1.DEPOSIT_TYPE.ISSUE,
96
- source: enum_1.DEPOSIT_SOURCE.ADMIN,
97
- });
98
- const [lastLedger] = await manager.query(`
99
- SELECT balanceAfter
100
- FROM gs_depositManager
101
- WHERE partnerSno = ?
102
- AND memberNo = ?
103
- ORDER BY sno DESC
104
- LIMIT 1
105
- FOR UPDATE
106
- `, [dto.partnerSno, dto.memberNo]);
107
- const prevBalance = (_a = lastLedger === null || lastLedger === void 0 ? void 0 : lastLedger.balanceAfter) !== null && _a !== void 0 ? _a : 0;
108
- await manager.query(`
109
- INSERT INTO gs_depositManager
110
- (partnerSno, memberNo, memberId, memberName, depositType, source, managerSno,
111
- refType, refSno, depositAmount, balanceAfter, reason)
112
- VALUES (?, ?, ?, ?, 'ISSUE', 'ADMIN', ?, 'JOB_DETAIL', ?, ?, ?, ?)
113
- `, [
114
- dto.partnerSno,
115
- dto.memberNo,
116
- dto.memberId,
117
- dto.memberName,
118
- dto.managerSno,
119
- jobDetail.sno,
120
- dto.amount,
121
- prevBalance + dto.amount,
122
- dto.reason,
123
- ]);
124
- return { success: true };
91
+ if (targetAmount > 0) {
92
+ return await this.processIssue(manager, Object.assign(Object.assign({}, dto), { memberId: member.memberId, memberName: member.memberName, jobSno: job.sno, amount: targetAmount }));
93
+ }
94
+ else {
95
+ return await this.executeFifoDeduct(manager, Object.assign(Object.assign({}, dto), { memberId: member.memberId, memberName: member.memberName, amount: Math.abs(targetAmount), orderNo: `DEDUCT_SINGLE_${Date.now()}` }));
96
+ }
97
+ });
98
+ }
99
+ async executeFifoDeduct(manager, data) {
100
+ const deductOrder = await manager.save(depositOrder_entity_1.DepositOrder, {
101
+ partnerSno: data.partnerSno,
102
+ memberNo: data.memberNo,
103
+ orderNo: data.orderNo,
104
+ memberId: data.memberId,
105
+ memberName: data.memberName,
106
+ usedAmount: data.amount,
107
+ orderStatus: 'DONE',
108
+ });
109
+ const availableIssues = await manager.query(`
110
+ SELECT jd.sno AS issueDetailSno, jd.depositAmount, jd.expiredDt,
111
+ (jd.depositAmount - IFNULL(usedSub.netUsed, 0)) AS remainAmount
112
+ FROM gs_depositJobDetail jd
113
+ LEFT JOIN (
114
+ SELECT issueDetailSno, SUM(usedDepositAmount - refundAmount) AS netUsed
115
+ FROM gs_depositOrderDetail GROUP BY issueDetailSno
116
+ ) usedSub ON jd.sno = usedSub.issueDetailSno
117
+ WHERE jd.partnerSno = ? AND jd.memberNo = ? AND jd.expireFl = 'N'
118
+ AND (jd.expiredDt IS NULL OR jd.expiredDt >= NOW())
119
+ HAVING remainAmount > 0
120
+ ORDER BY CASE WHEN jd.expiredDt IS NULL THEN 1 ELSE 0 END ASC, jd.expiredDt ASC, jd.sno ASC
121
+ FOR UPDATE
122
+ `, [data.partnerSno, data.memberNo]);
123
+ let remainToDeduct = data.amount;
124
+ const details = [];
125
+ for (const issue of availableIssues) {
126
+ if (remainToDeduct <= 0)
127
+ break;
128
+ const take = Math.min(Number(issue.remainAmount), remainToDeduct);
129
+ details.push(manager.create(depositOrderDetail_entity_1.DepositOrderDetail, {
130
+ orderSno: deductOrder.sno,
131
+ partnerSno: data.partnerSno,
132
+ memberNo: data.memberNo,
133
+ orderProductOptionNo: 'DEDUCT',
134
+ mallProductNo: 'DEDUCT',
135
+ orderProductName: `[관리자차감] ${data.reason}`,
136
+ usedDepositAmount: take,
137
+ issueDetailSno: issue.issueDetailSno,
138
+ refundYn: enum_1.YN.N,
139
+ }));
140
+ remainToDeduct -= take;
141
+ }
142
+ if (remainToDeduct > 0)
143
+ throw new Error(`잔액 부족 (부족분: ${remainToDeduct})`);
144
+ await manager.save(depositOrderDetail_entity_1.DepositOrderDetail, details);
145
+ const [lastLedger] = await manager.query(`SELECT balanceAfter FROM gs_depositManager WHERE partnerSno = ? AND memberNo = ? ORDER BY sno DESC LIMIT 1 FOR UPDATE`, [data.partnerSno, data.memberNo]);
146
+ const prevBalance = Number((lastLedger === null || lastLedger === void 0 ? void 0 : lastLedger.balanceAfter) || 0);
147
+ await manager.save(depositManager_entity_1.DepositManager, {
148
+ partnerSno: data.partnerSno,
149
+ memberNo: data.memberNo,
150
+ memberId: data.memberId,
151
+ memberName: data.memberName,
152
+ depositType: enum_1.DEPOSIT_TYPE.ADJUST,
153
+ source: enum_1.DEPOSIT_SOURCE.ADMIN,
154
+ refType: enum_1.DEPOSIT_REF_TYPE.ORDER,
155
+ refSno: deductOrder.sno,
156
+ depositAmount: -data.amount,
157
+ balanceAfter: prevBalance - data.amount,
158
+ reason: data.reason,
159
+ managerSno: data.managerSno,
125
160
  });
161
+ return { success: true };
162
+ }
163
+ async processIssue(manager, data) {
164
+ var _a;
165
+ const jobDetail = await manager.save(depositJobDetail_entity_1.DepositJobDetail, {
166
+ jobSno: data.jobSno,
167
+ partnerSno: data.partnerSno,
168
+ memberNo: data.memberNo,
169
+ memberId: data.memberId,
170
+ memberName: data.memberName,
171
+ depositAmount: data.amount,
172
+ reason: data.reason,
173
+ expiredDt: data.expiredDt || null,
174
+ });
175
+ const [lastLedger] = await manager.query(`SELECT balanceAfter FROM gs_depositManager
176
+ WHERE partnerSno = ? AND memberNo = ?
177
+ ORDER BY sno DESC LIMIT 1 FOR UPDATE`, [data.partnerSno, data.memberNo]);
178
+ const prevBalance = Number((_a = lastLedger === null || lastLedger === void 0 ? void 0 : lastLedger.balanceAfter) !== null && _a !== void 0 ? _a : 0);
179
+ await manager.save(depositManager_entity_1.DepositManager, {
180
+ partnerSno: data.partnerSno,
181
+ memberNo: data.memberNo,
182
+ memberId: data.memberId,
183
+ memberName: data.memberName,
184
+ depositType: enum_1.DEPOSIT_TYPE.ISSUE,
185
+ source: enum_1.DEPOSIT_SOURCE.ADMIN,
186
+ managerSno: data.managerSno,
187
+ refType: enum_1.DEPOSIT_REF_TYPE.JOB_DETAIL,
188
+ refSno: jobDetail.sno,
189
+ depositAmount: data.amount,
190
+ balanceAfter: prevBalance + data.amount,
191
+ reason: data.reason,
192
+ });
193
+ return { success: true, jobDetailSno: jobDetail.sno };
126
194
  }
127
195
  async issueByExcel(file, managerSno, partnerSno, jobName) {
128
196
  const workbook = XLSX.read(file.buffer, { type: 'buffer' });
@@ -133,146 +201,221 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
133
201
  }
134
202
  const entityManager = await this.getEntityManager();
135
203
  const job = await entityManager.save(depositJob_entity_1.DepositJob, {
136
- managerSno: managerSno,
204
+ managerSno,
137
205
  jobType: enum_1.DEPOSIT_JOB_TYPE.EXCEL,
138
- jobName: jobName,
139
- totalCount: rows.length,
206
+ jobName,
207
+ totalCount: rows.length - 1,
140
208
  successCount: 0,
141
209
  failCount: 0,
142
210
  status: enum_1.DEPOSIT_STATUS.PROCESSING,
143
211
  });
212
+ this.processExcelJobAsync(rows, job.sno, managerSno, partnerSno).catch((err) => this.logger.error(`Excel job ${job.sno} failed unexpectedly: ${err.message}`, err.stack));
213
+ return {
214
+ jobSno: job.sno,
215
+ totalCount: rows.length - 1,
216
+ status: enum_1.DEPOSIT_STATUS.PROCESSING,
217
+ };
218
+ }
219
+ async processExcelJobAsync(rows, jobSno, managerSno, partnerSno) {
220
+ const entityManager = await this.getEntityManager();
221
+ const partner = await this.partnerService.findOne(partnerSno);
144
222
  let successCount = 0;
145
223
  let failCount = 0;
146
- const results = [];
147
- const partner = await this.partnerService.findOne(partnerSno);
148
224
  for (const [index, row] of rows.entries()) {
225
+ if (index === 0)
226
+ continue;
227
+ const memberNo = row.memberNo ? Number(row.memberNo) : 0;
228
+ const amount = Number(row.amount || 0);
229
+ const member = await (0, gsUtil_1.shopByServerApi)('/profile', enum_1.HTTP_METHOD.GET, partner.systemKey, partner.longLivedToken, { memberNo: memberNo });
230
+ if (!member) {
231
+ throw new Error('존재하지 않는 회원');
232
+ }
233
+ const memberId = member.memberId;
234
+ const memberName = member.memberName;
149
235
  try {
150
- if (index === 0) {
151
- continue;
152
- }
153
236
  await this.issueSingleRow({
154
237
  row,
155
- jobSno: job.sno,
238
+ jobSno,
156
239
  managerSno,
157
240
  partnerSno,
158
241
  systemKey: partner.systemKey,
159
242
  longLivedToken: partner.longLivedToken,
160
243
  });
161
244
  successCount++;
162
- results.push({ row: index + 2, result: 'SUCCESS' });
245
+ await entityManager.save(depositJobResult_entity_1.DepositJobResult, {
246
+ jobSno,
247
+ partnerSno,
248
+ memberNo,
249
+ memberId,
250
+ memberName,
251
+ depositAmount: amount,
252
+ status: enum_1.DEPOSIT_RESULT.SUCCESS,
253
+ reason: row.reason || '',
254
+ });
163
255
  }
164
256
  catch (e) {
165
257
  failCount++;
166
- results.push({
167
- row: index + 2,
168
- result: 'FAIL',
169
- reason: e.message,
258
+ await entityManager.save(depositJobResult_entity_1.DepositJobResult, {
259
+ jobSno,
260
+ partnerSno,
261
+ memberNo,
262
+ memberId,
263
+ memberName,
264
+ depositAmount: amount,
265
+ status: enum_1.DEPOSIT_RESULT.FAIL,
266
+ reason: e.message || '알 수 없는 오류',
170
267
  });
171
268
  }
172
269
  }
173
- await entityManager.update(depositJob_entity_1.DepositJob, job.sno, {
270
+ await entityManager.update(depositJob_entity_1.DepositJob, jobSno, {
174
271
  successCount,
175
272
  failCount,
176
273
  status: enum_1.DEPOSIT_STATUS.DONE,
177
274
  });
275
+ }
276
+ async getJobList(dto) {
277
+ const entityManager = await this.getEntityManager();
278
+ const qb = entityManager.createQueryBuilder(depositJob_entity_1.DepositJob, 'j');
279
+ qb.andWhere('j.managerSno IN (SELECT sno FROM gs_manager WHERE partnerSno = :partnerSno)', {
280
+ partnerSno: dto.partnerSno,
281
+ });
282
+ if (dto.jobName) {
283
+ qb.andWhere('j.jobName LIKE :jobName', {
284
+ jobName: `%${dto.jobName}%`,
285
+ });
286
+ }
287
+ if (dto.jobType) {
288
+ qb.andWhere('j.jobType = :jobType', { jobType: dto.jobType });
289
+ }
290
+ if (dto.status) {
291
+ qb.andWhere('j.status = :status', { status: dto.status });
292
+ }
293
+ qb.select(['j.*']);
294
+ qb.orderBy('j.regDt', 'DESC');
295
+ qb.offset(dto.skip);
296
+ qb.limit(dto.take);
297
+ const list = await qb.getRawMany();
298
+ const count = await qb.getCount();
299
+ return (0, gsUtil_1.paginateResponse)([list, count], dto.page, dto.take);
300
+ }
301
+ async getJobResultList(jobSno, dto) {
302
+ const entityManager = await this.getEntityManager();
303
+ const qb = entityManager.createQueryBuilder(depositJobResult_entity_1.DepositJobResult, 'jr');
304
+ qb.andWhere('jr.jobSno = :jobSno', { jobSno });
305
+ qb.select(['jr.*']);
306
+ qb.orderBy('jr.sno', 'ASC');
307
+ qb.offset(dto.skip);
308
+ qb.limit(dto.take);
309
+ const list = await qb.getRawMany();
310
+ const count = await qb.getCount();
311
+ return (0, gsUtil_1.paginateResponse)([list, count], dto.page, dto.take);
312
+ }
313
+ async getJobStatus(jobSno, partnerSno) {
314
+ const entityManager = await this.getEntityManager();
315
+ const job = await entityManager.findOne(depositJob_entity_1.DepositJob, {
316
+ where: { sno: jobSno },
317
+ });
318
+ if (!job) {
319
+ throw new common_1.HttpException('작업을 찾을 수 없습니다.', common_1.HttpStatus.NOT_FOUND);
320
+ }
178
321
  return {
179
322
  jobSno: job.sno,
180
- totalCount: rows.length - 1,
181
- successCount,
182
- failCount,
183
- results,
323
+ jobName: job.jobName,
324
+ totalCount: job.totalCount,
325
+ successCount: job.successCount,
326
+ failCount: job.failCount,
327
+ status: job.status,
184
328
  };
185
329
  }
330
+ async getJobResultExcel(jobSno, partnerSno) {
331
+ const entityManager = await this.getEntityManager();
332
+ const job = await entityManager.findOne(depositJob_entity_1.DepositJob, {
333
+ where: { sno: jobSno },
334
+ });
335
+ if (!job) {
336
+ throw new common_1.HttpException('작업을 찾을 수 없습니다.', common_1.HttpStatus.NOT_FOUND);
337
+ }
338
+ if (job.status !== enum_1.DEPOSIT_STATUS.DONE) {
339
+ throw new common_1.HttpException('작업이 아직 진행 중입니다.', common_1.HttpStatus.BAD_REQUEST);
340
+ }
341
+ const results = await entityManager.find(depositJobResult_entity_1.DepositJobResult, {
342
+ where: { jobSno },
343
+ order: { sno: 'ASC' },
344
+ });
345
+ const excelData = results.map((r) => ({
346
+ 회원번호: r.memberNo,
347
+ 회원아이디: r.memberId || '',
348
+ 회원명: r.memberName || '',
349
+ 금액: r.depositAmount,
350
+ 처리결과: r.status === enum_1.DEPOSIT_RESULT.SUCCESS ? '성공' : '실패',
351
+ 실패사유: r.status === enum_1.DEPOSIT_RESULT.FAIL ? r.reason : '',
352
+ }));
353
+ const workbook = XLSX.utils.book_new();
354
+ const sheet = XLSX.utils.json_to_sheet(excelData);
355
+ XLSX.utils.book_append_sheet(workbook, sheet, '결과');
356
+ return Buffer.from(XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }));
357
+ }
186
358
  async issueSingleRow({ row, jobSno, managerSno, partnerSno, systemKey, longLivedToken, }) {
187
359
  const { memberNo, amount, reason, expiredDt } = row;
188
360
  const targetAmount = Number(amount);
361
+ const formattedExpiredDt = this.parseExcelDate(expiredDt);
189
362
  if (!memberNo || isNaN(targetAmount) || targetAmount === 0 || !reason) {
190
363
  throw new Error('필수 컬럼 누락 또는 금액 오류');
191
364
  }
192
- const member = await (0, gsUtil_1.shopByServerApi)('/profile', enum_1.HTTP_METHOD.GET, systemKey, longLivedToken, { memberNo: memberNo });
365
+ const member = await (0, gsUtil_1.shopByServerApi)('/profile', enum_1.HTTP_METHOD.GET, systemKey, longLivedToken, { memberNo });
193
366
  if (!member)
194
367
  throw new Error('존재하지 않는 회원');
195
368
  const entityManager = await this.getEntityManager();
196
369
  return await entityManager.transaction(async (manager) => {
197
- var _a;
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
370
  if (targetAmount > 0) {
201
- const jdResult = await manager.query(`INSERT INTO gs_depositJobDetail (jobSno, partnerSno, memberNo, memberId, memberName, depositAmount, reason, expiredDt) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
202
- jobSno,
371
+ return await this.processIssue(manager, {
203
372
  partnerSno,
204
- memberNo,
205
- member['memberId'],
206
- member['memberName'],
207
- targetAmount,
373
+ memberNo: Number(memberNo),
374
+ memberId: member.memberId,
375
+ memberName: member.memberName,
376
+ amount: targetAmount,
208
377
  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
378
  managerSno,
218
- jdResult.insertId,
219
- targetAmount,
220
- prevBalance + targetAmount,
221
- reason,
222
- ]);
379
+ jobSno,
380
+ expiredDt: formattedExpiredDt ? formattedExpiredDt : null,
381
+ });
223
382
  }
224
383
  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, {
384
+ return await this.executeFifoDeduct(manager, {
229
385
  partnerSno,
230
- memberNo,
231
- memberId: member['memberId'],
232
- memberName: member['memberName'],
386
+ memberNo: Number(memberNo),
387
+ memberId: member.memberId,
388
+ memberName: member.memberName,
389
+ amount: Math.abs(targetAmount),
390
+ reason,
391
+ managerSno,
233
392
  orderNo: `DEDUCT_EXCEL_${Date.now()}`,
234
- usedAmount: deductAmount,
235
- orderStatus: 'DONE',
236
393
  });
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
394
  }
274
395
  });
275
396
  }
397
+ parseExcelDate(dateStr) {
398
+ if (!dateStr)
399
+ return null;
400
+ if (dateStr instanceof Date) {
401
+ dateStr.setHours(23, 59, 59, 0);
402
+ return dateStr;
403
+ }
404
+ if (typeof dateStr === 'number') {
405
+ const date = new Date((dateStr - 25569) * 86400 * 1000);
406
+ date.setHours(23, 59, 59, 0);
407
+ return isNaN(date.getTime()) ? null : date;
408
+ }
409
+ if (typeof dateStr === 'string') {
410
+ const normalized = dateStr.replace(/\./g, '-');
411
+ const date = new Date(normalized);
412
+ if (!isNaN(date.getTime())) {
413
+ date.setHours(23, 59, 59, 0);
414
+ return date;
415
+ }
416
+ }
417
+ return null;
418
+ }
276
419
  async getDepositManagerList(dto) {
277
420
  const entityManager = await this.getEntityManager();
278
421
  const qb = entityManager.createQueryBuilder(depositManager_entity_1.DepositManager, 'dm');
@@ -361,28 +504,30 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
361
504
  return detail;
362
505
  }
363
506
  async getOrderDetail(entityManager, sno, partnerSno) {
364
- const qb = entityManager.createQueryBuilder(depositManager_entity_1.DepositManager, 'dm');
365
- qb.leftJoinAndSelect('gs_depositOrder', 'o', 'dm.refType = :refType AND dm.refSno = o.sno', { refType: enum_1.DEPOSIT_REF_TYPE.ORDER });
366
- qb.leftJoinAndSelect('gs_depositOrderDetail', 'od', 'o.sno = od.orderSno');
367
- qb.andWhere('dm.partnerSno = :partnerSno', {
368
- partnerSno: partnerSno,
369
- });
370
- qb.andWhere('dm.sno = :sno', {
371
- sno: sno,
372
- });
373
- qb.select([
374
- 'dm.*',
375
- 'o.orderNo as orderNo',
376
- 'o.usedAmount as usedAmount',
377
- 'od.orderProductNo as orderProductNo',
378
- 'od.orderProductName as orderProductName',
379
- 'od.orderAmount as orderAmount',
380
- 'od.usedDepositAmount as usedDepositAmount',
381
- 'od.allocatedRatio as allocatedRatio',
382
- 'od.allocatedAmount as allocatedAmount',
383
- ]);
384
- const detail = await qb.getRawOne();
385
- return detail;
507
+ const managerEntry = await entityManager
508
+ .createQueryBuilder(depositManager_entity_1.DepositManager, 'dm')
509
+ .leftJoinAndSelect('gs_depositOrder', 'o', 'dm.refSno = o.sno AND dm.refType = "ORDER"')
510
+ .where('dm.sno = :sno AND dm.partnerSno = :partnerSno', {
511
+ sno,
512
+ partnerSno,
513
+ })
514
+ .getRawOne();
515
+ if (!managerEntry || !managerEntry.o_sno)
516
+ return null;
517
+ const usageDetails = await entityManager
518
+ .createQueryBuilder(depositOrderDetail_entity_1.DepositOrderDetail, 'od')
519
+ .leftJoin('gs_depositJobDetail', 'jd', 'od.issueDetailSno = jd.sno')
520
+ .select([
521
+ 'jd.sno AS originSno',
522
+ 'jd.regDt AS originRegDt',
523
+ 'jd.reason AS originReason',
524
+ 'jd.depositAmount AS originAmount',
525
+ 'od.usedDepositAmount AS used',
526
+ 'od.orderProductName AS pName',
527
+ ])
528
+ .where('od.orderSno = :orderSno', { orderSno: managerEntry.o_sno })
529
+ .getRawMany();
530
+ return Object.assign(Object.assign({}, managerEntry), { usageDetails });
386
531
  }
387
532
  async createCouponManager(dto) {
388
533
  if (dto.registerStartDt > dto.registerEndDt) {
@@ -419,7 +564,9 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
419
564
  async getCouponManagerList(dto) {
420
565
  const entityManager = await this.getEntityManager();
421
566
  const qb = entityManager.createQueryBuilder(depositCouponManager_entity_1.DepositCouponManager, 'dcm');
422
- qb.andWhere('dcm.partnerSno = :partnerSno', { partnerSno: dto.partnerSno });
567
+ qb.andWhere('dcm.partnerSno = :partnerSno', {
568
+ partnerSno: dto.partnerSno,
569
+ });
423
570
  if (dto.title) {
424
571
  qb.andWhere('dcm.title LIKE :title', {
425
572
  title: `%${dto.title}%`,
@@ -447,14 +594,18 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
447
594
  async getCouponCodeList(dto) {
448
595
  const entityManager = await this.getEntityManager();
449
596
  const qb = entityManager.createQueryBuilder(depositCoupon_entity_1.DepositCoupon, 'dc');
450
- qb.andWhere('dc.partnerSno = :partnerSno', { partnerSno: dto.partnerSno });
597
+ qb.andWhere('dc.partnerSno = :partnerSno', {
598
+ partnerSno: dto.partnerSno,
599
+ });
451
600
  if (dto.couponCode) {
452
601
  qb.andWhere('dc.couponCode LIKE :couponCode', {
453
602
  couponCode: `%${dto.couponCode}%`,
454
603
  });
455
604
  }
456
605
  if (dto.policySno) {
457
- qb.andWhere('dc.policySno = :policySno', { policySno: dto.policySno });
606
+ qb.andWhere('dc.policySno = :policySno', {
607
+ policySno: dto.policySno,
608
+ });
458
609
  }
459
610
  qb.select(['dc.*']);
460
611
  qb.orderBy('dc.sno', 'DESC');
@@ -480,7 +631,9 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
480
631
  .addSelect('SUM(od.usedDepositAmount)', 'usedAmount')
481
632
  .from(depositOrderDetail_entity_1.DepositOrderDetail, 'od')
482
633
  .groupBy('od.issueDetailSno'), 'od', 'jd.sno = od.issueDetailSno')
483
- .where('jd.partnerSno = :partnerSno', { partnerSno: dto.partnerSno })
634
+ .where('jd.partnerSno = :partnerSno', {
635
+ partnerSno: dto.partnerSno,
636
+ })
484
637
  .andWhere('jd.expireFl = :expireFl', { expireFl: 'n' })
485
638
  .andWhere('(jd.expiredDt IS NULL OR jd.expiredDt >= NOW())')
486
639
  .groupBy('jd.memberNo')
@@ -491,7 +644,9 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
491
644
  const countQb = entityManager
492
645
  .createQueryBuilder(depositJobDetail_entity_1.DepositJobDetail, 'jd')
493
646
  .select('COUNT(DISTINCT jd.memberNo)', 'cnt')
494
- .where('jd.partnerSno = :partnerSno', { partnerSno: dto.partnerSno })
647
+ .where('jd.partnerSno = :partnerSno', {
648
+ partnerSno: dto.partnerSno,
649
+ })
495
650
  .andWhere('jd.expireFl = :expireFl', { expireFl: 'n' })
496
651
  .andWhere('(jd.expiredDt IS NULL OR jd.expiredDt >= NOW())');
497
652
  const count = await countQb.getRawOne();
@@ -652,11 +807,17 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
652
807
  const entityManager = await this.getEntityManager();
653
808
  const partner = await this.partnerService.findOne(dto.partnerSno);
654
809
  if (!partner) {
655
- return { rescode: '0002', message: '유효하지 않은 파트너 정보입니다.' };
810
+ return {
811
+ rescode: '0002',
812
+ message: '유효하지 않은 파트너 정보입니다.',
813
+ };
656
814
  }
657
815
  const user = await (0, gsUtil_1.shopByServerApi)('/profile', enum_1.HTTP_METHOD.GET, partner.systemKey, partner.longLivedToken, { memberNo: Number(dto.memberMappingKey) });
658
816
  if (!user) {
659
- return { rescode: '0001', message: '사용자 정보를 찾을 수 없습니다.' };
817
+ return {
818
+ rescode: '0001',
819
+ message: '사용자 정보를 찾을 수 없습니다.',
820
+ };
660
821
  }
661
822
  const targetMemberNo = Number(user.memberNo);
662
823
  const targetPartnerSno = partner.sno;
@@ -770,7 +931,8 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
770
931
  ? remainToDeductMaster
771
932
  : Math.round(depositOrder.usedAmount * ratio);
772
933
  remainToDeductMaster -= amountToAllocate;
773
- while (amountToAllocate > 0 && issueIndex < availableIssues.length) {
934
+ while (amountToAllocate > 0 &&
935
+ issueIndex < availableIssues.length) {
774
936
  const currentIssue = availableIssues[issueIndex];
775
937
  const canTake = Math.min(Number(currentIssue.remainAmount), amountToAllocate);
776
938
  if (canTake > 0) {
@@ -854,8 +1016,10 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
854
1016
  });
855
1017
  if (!usedDetails.length)
856
1018
  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);
1019
+ const totalRemainQty = Number(usedDetails[0].orderCnt) -
1020
+ Number(usedDetails[0].refundCnt);
1021
+ const totalRemainAmt = usedDetails.reduce((sum, d) => sum +
1022
+ (Number(d.usedDepositAmount) - Number(d.refundAmount)), 0);
859
1023
  if (totalRemainAmt <= 0 || totalRemainQty <= 0)
860
1024
  continue;
861
1025
  const isFullCancel = Number(item.orderCnt) === Number(item.originalOrderCnt);
@@ -870,7 +1034,8 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
870
1034
  for (const detail of usedDetails) {
871
1035
  if (remainRefundAmt <= 0)
872
1036
  break;
873
- const recordRemainAmt = Number(detail.usedDepositAmount) - Number(detail.refundAmount);
1037
+ const recordRemainAmt = Number(detail.usedDepositAmount) -
1038
+ Number(detail.refundAmount);
874
1039
  if (recordRemainAmt <= 0)
875
1040
  continue;
876
1041
  const takeAmt = Math.min(recordRemainAmt, remainRefundAmt);
@@ -914,7 +1079,7 @@ let DepositService = class DepositService extends default_service_1.DefaultServi
914
1079
  }
915
1080
  };
916
1081
  exports.DepositService = DepositService;
917
- exports.DepositService = DepositService = __decorate([
1082
+ exports.DepositService = DepositService = DepositService_1 = __decorate([
918
1083
  (0, common_1.Injectable)(),
919
1084
  __metadata("design:paramtypes", [partner_service_1.PartnerService,
920
1085
  core_1.ModuleRef])