InfluencerPartnershipModel.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <?php
  2. namespace App\Models;
  3. use CodeIgniter\Model;
  4. class InfluencerPartnershipModel extends Model
  5. {
  6. protected $table = 'VENDOR_INFLUENCER_MAPPING';
  7. protected $primaryKey = 'SEQ';
  8. protected $useAutoIncrement = true;
  9. protected $returnType = 'array';
  10. protected $useSoftDeletes = false;
  11. protected $allowedFields = [
  12. 'VENDOR_SEQ',
  13. 'INFLUENCER_SEQ',
  14. 'REQUEST_TYPE',
  15. 'REQUEST_MESSAGE',
  16. 'RESPONSE_MESSAGE',
  17. 'REQUESTED_BY',
  18. 'APPROVED_BY',
  19. 'COMMISSION_RATE',
  20. 'SPECIAL_CONDITIONS',
  21. 'EXPIRED_DATE',
  22. 'REQUEST_DATE',
  23. 'RESPONSE_DATE',
  24. 'PARTNERSHIP_START_DATE',
  25. 'PARTNERSHIP_END_DATE',
  26. 'ADD_INFO1',
  27. 'ADD_INFO2',
  28. 'ADD_INFO3',
  29. 'IS_ACT'
  30. ];
  31. protected $useTimestamps = true;
  32. protected $createdField = 'REG_DATE';
  33. protected $updatedField = 'MOD_DATE';
  34. protected $dateFormat = 'datetime';
  35. protected $validationRules = [
  36. 'VENDOR_SEQ' => 'required|integer',
  37. 'INFLUENCER_SEQ' => 'required|integer',
  38. 'REQUEST_TYPE' => 'required|in_list[INFLUENCER_REQUEST,VENDOR_PROPOSAL,INFLUENCER_REAPPLY]',
  39. 'REQUESTED_BY' => 'required|integer',
  40. 'COMMISSION_RATE' => 'permit_empty|decimal|greater_than_equal_to[0]|less_than_equal_to[100]',
  41. 'IS_ACT' => 'required|in_list[Y,N]'
  42. ];
  43. // 히스토리 모델
  44. protected $statusHistoryModel;
  45. protected $mappingModel;
  46. public function __construct()
  47. {
  48. parent::__construct();
  49. $this->statusHistoryModel = new VendorInfluencerStatusHistoryModel();
  50. $this->mappingModel = new VendorInfluencerMappingModel();
  51. }
  52. /**
  53. * 인플루언서의 파트너십 목록 조회
  54. */
  55. public function getInfluencerPartnerships($influencerSeq, $filters = [])
  56. {
  57. $builder = $this->db->table('VENDOR_INFLUENCER_MAPPING vim');
  58. $builder->select('
  59. vim.*,
  60. vsh.STATUS as CURRENT_STATUS,
  61. vsh.STATUS_MESSAGE as CURRENT_STATUS_MESSAGE,
  62. vsh.CHANGED_DATE as STATUS_CHANGED_DATE,
  63. v.COMPANY_NAME as VENDOR_NAME,
  64. v.COMPANY_EMAIL as VENDOR_EMAIL,
  65. v.COMPANY_PHONE as VENDOR_PHONE,
  66. v.LOGO_IMAGE as VENDOR_LOGO,
  67. v.CATEGORY as VENDOR_CATEGORY,
  68. v.REGION as VENDOR_REGION,
  69. v.DESCRIPTION as VENDOR_DESCRIPTION,
  70. v.RATING as VENDOR_RATING
  71. ');
  72. $builder->join('VENDOR_INFLUENCER_STATUS_HISTORY vsh',
  73. 'vsh.MAPPING_SEQ = vim.SEQ AND vsh.IS_CURRENT = "Y"', 'left');
  74. $builder->join('VENDOR_LIST v', 'v.SEQ = vim.VENDOR_SEQ', 'left');
  75. $builder->where('vim.INFLUENCER_SEQ', $influencerSeq);
  76. $builder->where('vim.IS_ACT', 'Y');
  77. // 상태 필터
  78. if (isset($filters['status'])) {
  79. if (is_array($filters['status'])) {
  80. $builder->whereIn('vsh.STATUS', $filters['status']);
  81. } else {
  82. $builder->where('vsh.STATUS', $filters['status']);
  83. }
  84. }
  85. // 요청 타입 필터
  86. if (isset($filters['request_type'])) {
  87. $builder->where('vim.REQUEST_TYPE', $filters['request_type']);
  88. }
  89. // 기간 필터
  90. if (isset($filters['start_date'])) {
  91. $builder->where('vim.REG_DATE >=', $filters['start_date']);
  92. }
  93. if (isset($filters['end_date'])) {
  94. $builder->where('vim.REG_DATE <=', $filters['end_date']);
  95. }
  96. // 벤더사 카테고리 필터
  97. if (isset($filters['vendor_category'])) {
  98. $builder->where('v.CATEGORY', $filters['vendor_category']);
  99. }
  100. // 재승인 요청 필터
  101. if (isset($filters['is_reapply'])) {
  102. $builder->where('vim.ADD_INFO1', 'REAPPLY');
  103. }
  104. $builder->orderBy('vim.REG_DATE', 'DESC');
  105. return $builder;
  106. }
  107. /**
  108. * 인플루언서 승인 요청 생성
  109. */
  110. public function createApprovalRequest($data)
  111. {
  112. // 중복 요청 확인
  113. $existing = $this->mappingModel->checkExistingPendingRequest(
  114. $data['VENDOR_SEQ'],
  115. $data['INFLUENCER_SEQ']
  116. );
  117. if ($existing) {
  118. throw new \Exception('이미 처리 중인 요청이 있습니다.');
  119. }
  120. $insertData = array_merge($data, [
  121. 'REQUEST_TYPE' => 'INFLUENCER_REQUEST',
  122. 'REQUEST_DATE' => date('Y-m-d H:i:s'),
  123. 'IS_ACT' => 'Y'
  124. ]);
  125. return $this->insert($insertData);
  126. }
  127. /**
  128. * 재승인 요청 생성
  129. */
  130. public function createReapplyRequest($data)
  131. {
  132. // 재승인 가능한 파트너십 확인
  133. $terminated = $this->mappingModel->checkReapplyEligiblePartnership(
  134. $data['VENDOR_SEQ'],
  135. $data['INFLUENCER_SEQ']
  136. );
  137. if (!$terminated) {
  138. throw new \Exception('해지된 파트너십이 없어 재승인을 요청할 수 없습니다.');
  139. }
  140. // 이미 재승인 요청 중인지 확인
  141. $existingReapply = $this->mappingModel->checkExistingPendingRequest(
  142. $data['VENDOR_SEQ'],
  143. $data['INFLUENCER_SEQ']
  144. );
  145. if ($existingReapply) {
  146. throw new \Exception('이미 재승인 요청이 진행 중입니다.');
  147. }
  148. $insertData = array_merge($data, [
  149. 'REQUEST_TYPE' => 'INFLUENCER_REAPPLY',
  150. 'REQUEST_DATE' => date('Y-m-d H:i:s'),
  151. 'ADD_INFO1' => 'REAPPLY',
  152. 'ADD_INFO2' => $terminated['SEQ'], // 이전 파트너십 SEQ
  153. 'ADD_INFO3' => date('Y-m-d H:i:s'), // 재신청 일시
  154. 'COMMISSION_RATE' => $data['COMMISSION_RATE'] ?? $terminated['COMMISSION_RATE'],
  155. 'SPECIAL_CONDITIONS' => $data['SPECIAL_CONDITIONS'] ?? $terminated['SPECIAL_CONDITIONS'],
  156. 'IS_ACT' => 'Y'
  157. ]);
  158. return $this->insert($insertData);
  159. }
  160. /**
  161. * 파트너십 해지 (인플루언서가 해지)
  162. */
  163. public function terminateByInfluencer($mappingSeq, $influencerSeq, $reason = '')
  164. {
  165. $partnership = $this->mappingModel->getBasicMapping($mappingSeq);
  166. if (!$partnership) {
  167. throw new \Exception('파트너십을 찾을 수 없습니다.');
  168. }
  169. if ($partnership['INFLUENCER_SEQ'] != $influencerSeq) {
  170. throw new \Exception('본인의 파트너십만 해지할 수 있습니다.');
  171. }
  172. // 현재 상태 확인
  173. $currentStatus = $this->statusHistoryModel->getCurrentStatus($mappingSeq);
  174. if (!$currentStatus || $currentStatus['STATUS'] !== 'APPROVED') {
  175. throw new \Exception('승인된 파트너십만 해지할 수 있습니다.');
  176. }
  177. // 상태를 TERMINATED로 변경
  178. $statusResult = $this->statusHistoryModel->changeStatus(
  179. $mappingSeq,
  180. 'TERMINATED',
  181. $reason,
  182. $influencerSeq
  183. );
  184. // 파트너십 종료일 설정
  185. $this->update($mappingSeq, [
  186. 'PARTNERSHIP_END_DATE' => date('Y-m-d H:i:s'),
  187. 'ADD_INFO1' => $reason, // 해지 사유
  188. 'ADD_INFO2' => $influencerSeq // 해지 처리자
  189. ]);
  190. return $statusResult;
  191. }
  192. /**
  193. * 인플루언서 통계 조회
  194. */
  195. public function getInfluencerStats($influencerSeq)
  196. {
  197. $stats = [];
  198. // 전체 파트너십 수
  199. $stats['total_partnerships'] = $this->where('INFLUENCER_SEQ', $influencerSeq)
  200. ->where('IS_ACT', 'Y')
  201. ->countAllResults();
  202. // 상태별 통계는 히스토리 모델에서 조회
  203. $statusStats = $this->statusHistoryModel->getStatusStatsByInfluencer($influencerSeq);
  204. $statusCounts = [];
  205. foreach ($statusStats as $stat) {
  206. $statusCounts[$stat['STATUS']] = $stat['count'];
  207. }
  208. $stats['approved_partnerships'] = $statusCounts['APPROVED'] ?? 0;
  209. $stats['active_partnerships'] = $statusCounts['APPROVED'] ?? 0;
  210. $stats['terminated_partnerships'] = $statusCounts['TERMINATED'] ?? 0;
  211. $stats['pending_requests'] = $statusCounts['PENDING'] ?? 0;
  212. $stats['rejected_requests'] = $statusCounts['REJECTED'] ?? 0;
  213. // 평균 커미션율
  214. $avgCommission = $this->db->table('VENDOR_INFLUENCER_MAPPING vim')
  215. ->select('AVG(vim.COMMISSION_RATE) as avg_rate')
  216. ->join('VENDOR_INFLUENCER_STATUS_HISTORY vsh',
  217. 'vsh.MAPPING_SEQ = vim.SEQ AND vsh.IS_CURRENT = "Y"')
  218. ->where('vim.INFLUENCER_SEQ', $influencerSeq)
  219. ->where('vsh.STATUS', 'APPROVED')
  220. ->where('vim.IS_ACT', 'Y')
  221. ->get()
  222. ->getRowArray();
  223. $stats['avg_commission_rate'] = round($avgCommission['avg_rate'] ?? 0, 2);
  224. // 카테고리별 파트너십 분포
  225. $stats['category_distribution'] = $this->db->table('VENDOR_INFLUENCER_MAPPING vim')
  226. ->select('v.CATEGORY, COUNT(*) as count')
  227. ->join('VENDOR_INFLUENCER_STATUS_HISTORY vsh',
  228. 'vsh.MAPPING_SEQ = vim.SEQ AND vsh.IS_CURRENT = "Y"')
  229. ->join('VENDOR_LIST v', 'v.SEQ = vim.VENDOR_SEQ', 'left')
  230. ->where('vim.INFLUENCER_SEQ', $influencerSeq)
  231. ->where('vsh.STATUS', 'APPROVED')
  232. ->where('vim.IS_ACT', 'Y')
  233. ->groupBy('v.CATEGORY')
  234. ->get()
  235. ->getResultArray();
  236. // 최근 6개월 월별 파트너십 생성 수
  237. $stats['monthly_partnerships'] = $this->db->table('VENDOR_INFLUENCER_MAPPING vim')
  238. ->select('DATE_FORMAT(vim.PARTNERSHIP_START_DATE, "%Y-%m") as month, COUNT(*) as count')
  239. ->join('VENDOR_INFLUENCER_STATUS_HISTORY vsh',
  240. 'vsh.MAPPING_SEQ = vim.SEQ AND vsh.IS_CURRENT = "Y"')
  241. ->where('vim.INFLUENCER_SEQ', $influencerSeq)
  242. ->where('vsh.STATUS', 'APPROVED')
  243. ->where('vim.PARTNERSHIP_START_DATE >=', date('Y-m-d', strtotime('-6 months')))
  244. ->where('vim.IS_ACT', 'Y')
  245. ->groupBy('month')
  246. ->orderBy('month', 'ASC')
  247. ->get()
  248. ->getResultArray();
  249. return $stats;
  250. }
  251. /**
  252. * 인플루언서의 현재 활성 파트너십 조회
  253. */
  254. public function getActivePartnerships($influencerSeq)
  255. {
  256. return $this->getInfluencerPartnerships($influencerSeq, [
  257. 'status' => 'APPROVED'
  258. ])->get()->getResultArray();
  259. }
  260. /**
  261. * 인플루언서의 요청 이력 조회
  262. */
  263. public function getRequestHistory($influencerSeq, $limit = 10)
  264. {
  265. return $this->getInfluencerPartnerships($influencerSeq)
  266. ->limit($limit)
  267. ->get()
  268. ->getResultArray();
  269. }
  270. /**
  271. * 재승인 가능한 벤더사 목록 조회
  272. */
  273. public function getReapplyableVendors($influencerSeq)
  274. {
  275. return $this->db->table('VENDOR_INFLUENCER_MAPPING vim')
  276. ->select('
  277. DISTINCT v.SEQ, v.COMPANY_NAME, v.LOGO_IMAGE, v.CATEGORY,
  278. vim.COMMISSION_RATE, vim.SPECIAL_CONDITIONS, vim.PARTNERSHIP_END_DATE
  279. ')
  280. ->join('VENDOR_INFLUENCER_STATUS_HISTORY vsh',
  281. 'vsh.MAPPING_SEQ = vim.SEQ AND vsh.IS_CURRENT = "Y"')
  282. ->join('VENDOR_LIST v', 'v.SEQ = vim.VENDOR_SEQ', 'left')
  283. ->where('vim.INFLUENCER_SEQ', $influencerSeq)
  284. ->where('vsh.STATUS', 'TERMINATED')
  285. ->where('vim.IS_ACT', 'Y')
  286. ->where('v.IS_ACT', 'Y')
  287. ->whereNotIn('vim.VENDOR_SEQ', function($builder) use ($influencerSeq) {
  288. // 현재 재승인 요청 중인 벤더사 제외
  289. return $builder->select('vim2.VENDOR_SEQ')
  290. ->from('VENDOR_INFLUENCER_MAPPING vim2')
  291. ->join('VENDOR_INFLUENCER_STATUS_HISTORY vsh2',
  292. 'vsh2.MAPPING_SEQ = vim2.SEQ AND vsh2.IS_CURRENT = "Y"')
  293. ->where('vim2.INFLUENCER_SEQ', $influencerSeq)
  294. ->where('vsh2.STATUS', 'PENDING')
  295. ->where('vim2.ADD_INFO1', 'REAPPLY')
  296. ->where('vim2.IS_ACT', 'Y');
  297. })
  298. ->orderBy('vim.PARTNERSHIP_END_DATE', 'DESC')
  299. ->get()
  300. ->getResultArray();
  301. }
  302. /**
  303. * 파트너십 상세 정보 조회
  304. */
  305. public function getPartnershipDetail($mappingSeq, $influencerSeq)
  306. {
  307. return $this->db->table('VENDOR_INFLUENCER_MAPPING vim')
  308. ->select('
  309. vim.*,
  310. vsh.STATUS as CURRENT_STATUS,
  311. vsh.STATUS_MESSAGE as CURRENT_STATUS_MESSAGE,
  312. vsh.CHANGED_DATE as STATUS_CHANGED_DATE,
  313. v.COMPANY_NAME, v.COMPANY_EMAIL, v.COMPANY_PHONE,
  314. v.LOGO_IMAGE, v.CATEGORY, v.REGION, v.DESCRIPTION,
  315. v.RATING as VENDOR_RATING,
  316. u.NICK_NAME as REQUESTED_BY_NAME
  317. ')
  318. ->join('VENDOR_INFLUENCER_STATUS_HISTORY vsh',
  319. 'vsh.MAPPING_SEQ = vim.SEQ AND vsh.IS_CURRENT = "Y"', 'left')
  320. ->join('VENDOR_LIST v', 'v.SEQ = vim.VENDOR_SEQ', 'left')
  321. ->join('USER_LIST u', 'u.SEQ = vim.REQUESTED_BY', 'left')
  322. ->where('vim.SEQ', $mappingSeq)
  323. ->where('vim.INFLUENCER_SEQ', $influencerSeq)
  324. ->where('vim.IS_ACT', 'Y')
  325. ->first();
  326. }
  327. }