본문으로 건너뛰기

회원 등급 평가 스케줄러 개선

PHP 8 · GODO25
변경 파일 수: 8개
변경 파일 목록
총 8개
변경 파일 코드
추가삭제
Component/Coupon/CouponAdmin.php+3-2
변경 전CouponAdmin.php
}
$arrData = [];
$arrData['couponNo'] = $getValue['couponNo'];
$arrData['couponSaveAdminId'] = Session::get('manager.managerId');
$arrData['managerNo'] = Session::get('manager.sno');
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($getValue['couponNo']);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($getValue['couponNo']);
// 저장
$arrBind = $this->db->get_binding(DBTableField::tableMemberCoupon(), $arrData, 'insert', gd_array_keys($arrData), ['memberCouponNo']);
$this->db->set_insert_db(DB_MEMBER_COUPON, $arrBind['param'], $arrBind['bind'], 'y');
$this->setCouponMemberSaveCount($getValue['couponNo']);
}
 
 
return 'T';
exit;
}
 
변경 후CouponAdmin.php
}
$arrData = [];
$arrData['couponNo'] = $getValue['couponNo'];
$arrData['couponSaveAdminId'] = gd_isset(Request::post()->get('couponSaveAdminId'), Session::get('manager.managerId'));
$arrData['managerNo'] = Session::get('manager.sno');
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($getValue['couponNo']);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($getValue['couponNo']);
// 저장
$arrBind = $this->db->get_binding(DBTableField::tableMemberCoupon(), $arrData, 'insert', gd_array_keys($arrData), ['memberCouponNo']);
$this->db->set_insert_db(DB_MEMBER_COUPON, $arrBind['param'], $arrBind['bind'], 'y');
 
}
// 발급 카운트 일괄 증가 (루프 내 N회 UPDATE → 1회 UPDATE)
$this->setCouponMemberSaveCountAll($getValue['couponNo'], count($memNoArr));
return 'T';
exit;
}
 
Component/Database/MemberTableField.php+5-1
변경 전MemberTableField.php
'typ' => 's',
'def' => null,
]; // 평생회원 전환일
 
 
 
 
 
return $arrField;
}
 
 
변경 후MemberTableField.php
'typ' => 's',
'def' => null,
]; // 평생회원 전환일
$arrField[] = [
'val' => 'autoGroupAppraisalDt',
'typ' => 's',
'def' => null,
]; // 회원등급평가 실행일
return $arrField;
}
 
 
Component/Member/Group/AppraisalMailSender.php+17-4
변경 전AppraisalMailSender.php
use Component\Mail\MailUtil;
 
use Component\Validator\Validator;
/**
public function send()
{
 
 
foreach ($this->receiver as $index => $item) {
$groupVo = new \Component\Member\Group\GroupDomain($item[1]);
if (!($groupVo instanceof GroupDomain)) {
\App::getInstance('logger')->error('그룹정보 객체 오류', $item);
continue;
 
 
 
 
 
 
 
 
 
}
 
MailUtil::sendMemberGroupChangeMail($item[0], $groupVo);
}
}
 
변경 후AppraisalMailSender.php
use Component\Mail\MailUtil;
use Component\Member\Group\GroupDomain;
use Component\Validator\Validator;
/**
public function send()
{
// 같은 등급 GroupDomain을 재사용해 setGroupDcData()의 회원 수만큼 반복 DB조회(N+1)를 제거한다.
$groupVoCache = [];
foreach ($this->receiver as $index => $item) {
$groupSno = isset($item[1]['sno']) ? $item[1]['sno'] : null;
if ($groupSno !== null && isset($groupVoCache[$groupSno])) {
$groupVo = $groupVoCache[$groupSno];
} else {
$groupVo = new GroupDomain($item[1]);
if (!($groupVo instanceof GroupDomain)) {
\App::getInstance('logger')->error('그룹정보 객체 오류', $item);
continue;
}
if ($groupSno !== null) {
$groupVoCache[$groupSno] = $groupVo;
}
}
MailUtil::sendMemberGroupChangeMail($item[0], $groupVo);
}
}
 
Component/Member/Group/Appraisal.php+584-114
변경 전Appraisal.php
namespace Bundle\Component\Member\Group;
use App;
 
use Component\Database\DBTableField;
use Component\Member\Member;
use Component\Sms\Code;
use Framework\Utility\ComponentUtils;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\NumberUtils;
 
 
use Session;
/**
protected $search;
/** @var AppraisalMailSender 등급평가 메일발송 */
protected $mailSender;
/** @var array 등급평가 완료된 회원번호 */
protected $completeMemNo = [];
/** @var string 등급평가일시 */
protected $appraisalDateTime;
/** @var bool 현재 평가 등급 기준치 만족 여부 */
/** @var array 현재 평가대상 회원 */
protected $currentMembers;
/** @var array 등급평가 제외 설정된 등급 sno 배열 */
protected $apprExclusionOfRating;
/** @var array 그룹 sno별 등급 */
protected $groupSort;
 
 
/** @var array 그룹 관리 설정 */
public $policy;
/** @var array 기본 설정 */
public $basicInfo;
 
 
 
 
 
 
 
 
 
 
 
 
/**
* Appraisal constructor.
*
public function __construct(AppraisalSearch $search = null)
{
parent::__construct();
 
 
 
$this->setSearch($search);
$this->groupConfig();
$this->validateAppraisalGroupConfig();
$this->policy = ComponentUtils::getPolicy('member.group');
$this->basicInfo = gd_policy('basic.info');
 
ini_set('memory_limit', '-1');
}
}
if ($data['apprSystem'] == 'point' && $data['appraisalPointOrderPriceFl'] != 'y' && $data['appraisalPointOrderRepeatFl'] != 'y' && $data['appraisalPointReviewRepeatFl'] != 'y' && $data['appraisalPointLoginRepeatFl'] != 'y') {
$logger = \App::getInstance('logger');
$logger->warning('Not set appraisal performance point');
throw new Exception(__('실적 점수제 평가 설정 값이 없습니다.'));
}
$this->appraisalGroupConfig = new SimpleStorage($data);
$validation->validateOverlapStandard();
$this->groupSort[$group['sno']] = $group['groupSort']; // 그룹 key: sno, value: sort 배열 생성
}
 
 
 
 
 
 
// 회원등급 평가시 제외인 등급 그룹 배열 생성
$exclusionGroups = $dao->selectGroupExclusion();
foreach ($exclusionGroups as $key){
$this->groupConfig = $groups;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* setGroupNames
* @return void
*/
public function appraisalGroupBySearch()
{
$logger = \App::getInstance('logger');
$logger->notice('Start group appraisal.');
$total = $this->countTotalMember();
$limit = $this->search->getAppraisalLimit();
$this->countByTotalMember = $this->appraisalCountByTotalMember($total, $limit);
$logger->notice('총 회원[' . $total . '], 1회 평가당 제한 인원수[' . $limit . '], 평가 실행 횟수[' . $this->countByTotalMember . ']');
$this->completeMemNo = [];
$this->appraisalDateTime = DateTimeUtils::dateFormat('Y-m-d H:i:s', 'now');
 
 
 
for ($offset = 0; $offset < $this->countByTotalMember; $offset++) {
$this->appraisalGroupByOffset($offset);
// 평가 대상으로 검색된 회원 초기화
$this->currentMembers = null;
}
$logger->notice('End group appraisal.');
}
/**
*/
public function appraisalGroupByOffset($offset)
{
$logger = \App::getInstance('logger');
$db = \App::getInstance('DB');
$this->search->setAppraisalOffset($offset);
$logger->info(sprintf('Start appraisal group set offset %s', $offset));
 
 
 
foreach ($this->groupConfig as $index => $item) {
$db->ping();
$logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
$this->group = new SimpleStorage($item);
if ($this->currentMembers === null) {
$this->currentMembers = $this->search->search($this->group);
}
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$logger->info(sprintf('has appraisal member count is %d', gd_count($this->currentMembers)));
$this->updateGroupWithAddMailReceiver($this->currentMembers);
}
$logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
}
if ($this->policy['downwardAdjustment'] == 'y') {
$this->isDefaultGroupMember = true;
$logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
$this->group = new SimpleStorage($this->defaultGroupConfig);
$this->search->setAppraisalDateTime($this->appraisalDateTime);
$this->currentMembers = $this->search->searchDefaultGroup($this->appraisalDateTime);
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$db->ping();
$logger->info(sprintf('has appraisal member count is %d', gd_count($this->currentMembers)));
$this->updateGroupWithAddMailReceiver($this->currentMembers);
} else {
$logger->info('Not found appraisal members by default group', $this->defaultGroupConfig);
}
}
$logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
$this->isDefaultGroupMember = false;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* appraisalMailSend
* @return void
*/
public function updateGroupWithAddMailReceiver(array $members)
{
$logger = \App::getInstance('logger');
$session = \App::getInstance('session');
$db = \App::getInstance('DB');
 
 
try {
$groupSno = $this->group->get('sno');
$settleGb = $this->group->get('settleGb');
$smsSender = \App::load(\Component\Sms\SmsSender::class);
$smsSender->validPassword(\Request::post()->get('password'));
}
$count = 500;
 
 
foreach ($members as $index => $item) {
$count--;
if ($count == 0) {
$db->ping();
$count = 500;
}
$session->set(Member::SESSION_MODIFY_MEMBER_INFO, $item);
$storage = new SimpleStorage($item);
$storage->set('settleGb', $settleGb);
$memNo = $storage->get('memNo');
array_push($this->completeMemNo, $memNo);
// 현재 등급이 '등급평가시 제외' 설정이 아닐 때
if(gd_array_key_exists($item['groupSno'], $this->apprExclusionOfRating) == false) {
$this->checkGroupByAppraisalSystem($item);
$beforeGroupSort = $this->groupSort[$item['groupSno']];
$afterGroupSort = $this->groupSort[$groupSno];
 
 
 
 
 
//하향펑가를 설정하지 않았거나, 하향평가를 사용하거나, 하향평가를 사용하지 않고 변경 등급이 기존 등급보다 높거나 같을 때 만 업데이트
if (empty($this->policy['downwardAdjustment']) || $this->policy['downwardAdjustment'] == 'y' || ($this->policy['downwardAdjustment'] == 'n' && $beforeGroupSort < $afterGroupSort)) {
if ($this->isCurrentGroupMember || $this->isDefaultGroupMember) {
$logger->info('Group change target. sno[' . $this->group->get('sno', 0) . '], name[' . $this->group->get('', '-') . ']', $item);
 
$this->updateGroup($memNo);
$this->writeHistory($memNo);
$this->addMailReceiver($storage, $this->group);
$this->addSmsReceiver($storage, $this->group);
unset($members[$index]);
}
}
}
}
 
 
 
 
 
 
if ($this->isTran) {
$db->commit();
$this->currentMembers = $members;
}
 
 
 
 
 
} catch (Exception $e) {
$logger->error(__METHOD__ . ', ' . $e->getFile() . '[' . $e->getLine() . '], ' . $e->getMessage(), $e->getTrace());
$db->rollback();
 
 
 
 
 
}
}
/**
* checkGroupByAppraisalSystem
 
 
*
* @param array $item 등급평가 대상의 기준치 정보
*
}
}
if ($point > 0) {
$logger = \App::getInstance('logger');
$logger->info(
__METHOD__, [
'memNo:'.$memNo,
$point,
*/
public function checkGroupByFigure($item)
{
$logger = \App::getInstance('logger');
$this->isCurrentGroupMember = true;
$priceUnit = 10000;
*/
public function updateGroupWithInsertHistory(SimpleStorage $storage)
{
 
 
 
 
 
 
$memNo = $storage->get('memNo');
$this->updateGroup($memNo);
$this->writeHistory($memNo);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
/**
}
}
$passwordCheckFl = gd_isset(\Request::post()->get('passwordCheckFl'), 'y');
if($passwordCheckFl != 'n') {
$smsSender = \App::load(\Component\Sms\SmsSender::class);
$smsSender->validPassword(\Request::post()->get('password'));
}
$passwordCheckFl = $passwordCheckFl == 'n' ? false : true;
//쿠폰 지급 하는 코드
if($applyCoupon === true){
$applyCouponList = explode(INT_DIVISION, $this->group->get('groupCoupon'));
foreach($applyCouponList as $value){
$coupon = new \Component\Coupon\CouponAdmin;
\Request::post()->set('couponNo', $value);
\Request::post()->set('couponSaveAdminId', '회원등급 쿠폰 혜택');
\Request::post()->set('managerNo', Session::get('manager.sno'));
\Request::post()->set('memberCouponStartDate', $coupon->getMemberCouponStartDate($value));
\Request::post()->set('memberCouponEndDate', $coupon->getMemberCouponEndDate($value));
\Request::post()->set('memberCouponState', 'y');
$memberArr[] = $memNo;
$coupon->saveMemberCouponSms($memberArr, null, $passwordCheckFl);
unset($memberArr);
}
}
$db = \App::getInstance('DB');
$fields = DBTableField::getFieldTypes('tableMember');
$arrUpdate[] = 'groupSno=?';
$arrBind = [];
$db->bind_param_push($arrBind, $fields['groupSno'], $this->group->get('sno'));
$arrUpdate[] = 'groupModDt=?';
$db->bind_param_push($arrBind, $fields['groupModDt'], $this->appraisalDateTime);
$db->bind_param_push($arrBind, $fields['memNo'], $memNo);
$db->bind_param_push($arrBind, $fields['appFl'], 'y');
$db->bind_param_push($arrBind, $fields['sleepFl'], 'n');
 
 
$db->set_update_db(DB_MEMBER, $arrUpdate, 'memNo = ? AND appFl = ? AND sleepFl = ?', $arrBind);
}
$limit = $this->search->getAppraisalLimit();
// 처리 반복 횟수 계산
$countByTotalMember = $this->appraisalCountByTotalMember($total, $limit);
$logger = \App::getInstance('logger');
$logger->notice('Start apply coupon to all members.');
$logger->notice('총 회원[' . $total . '], 1회 처리당 제한 인원수[' . $limit . '], 처리 실행 횟수[' . $countByTotalMember . ']');
//그룹 설정 가져오기
$group = \App::load('Component\Member\Group\GroupDAO');
$passwordCheckFl = gd_isset(\Request::post()->get('passwordCheckFl'), 'y');
if($passwordCheckFl != 'n') {
// 쿠폰 관리 객체 미리 생성
$coupon = new \Component\Coupon\CouponAdmin;
// 그룹별 쿠폰 정보 캐시
$groupList = [];
 
 
 
 
 
 
 
// 페이징 처리를 위한 반복문
for ($offset = 0; $offset < $countByTotalMember; $offset++) {
$start = $offset * $limit;
$logger->info(sprintf('Start apply coupon with offset %s, limit %s', $start, $limit));
// 페이지별 회원 목록 조회
$memberPageList = $member->getMemberList($arrWhere, null, null, $start, $limit, $field);
if (!empty($memberPageList) && gd_count($memberPageList) > 0) {
foreach ($memberPageList as $key => $value) {
//그룹 정보 가져오기 (캐시 활용)
if (empty($groupList[$value['groupSno']])) {
$groupList[$value['groupSno']] = $group->selectGroup($value['groupSno'])['groupCoupon'];
}
//하나씩 쿠폰 발급 하기
if (!empty($groupList[$value['groupSno']]) && gd_count($groupList[$value['groupSno']]) > 0) {
$applyCouponList = explode(INT_DIVISION, $groupList[$value['groupSno']]);
}
if (!empty($applyCouponList) && gd_count($applyCouponList) > 0) {
\Request::post()->set('couponSaveAdminId', '회원등급 쿠폰 혜택');
\Request::post()->set('memberCouponState', 'y');
\Request::post()->set('managerNo', Session::get('manager.sno'));
$memberArr[] = $value['memNo'];
foreach ($applyCouponList as $applyCoupon) {
if (is_numeric($applyCoupon) && $applyCoupon > 0) {
\Request::post()->set('couponNo', $applyCoupon);
\Request::post()->set('memberCouponStartDate', $coupon->getMemberCouponStartDate($applyCoupon));
\Request::post()->set('memberCouponEndDate', $coupon->getMemberCouponEndDate($applyCoupon));
$coupon->saveMemberCouponSms($memberArr, null, $passwordCheckFl);
}
 
 
 
}
unset($memberArr, $applyCouponList);
}
 
}
}
// 메모리 정리
unset($memberPageList);
}
$logger->notice('End apply coupon to all members.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
/**
*/
public function addSmsReceiver(SimpleStorage $storage, SimpleStorage $group)
{
$logger = \App::getInstance('logger');
if ($storage->get('cellPhone') != null && $storage->get('memNm') != null) {
if ($storage->get('beforeGroupSno') != $group->get('sno')) {
$smsAuto = \App::load(\Component\Sms\SmsAuto::class);
$observer = new SmsAutoObserver();
$observer->setSmsType(SmsAutoCode::MEMBER);
$observer->setSmsAutoCodeType(Code::GROUP_CHANGE);
$observer->setReceiver($storage->get('cellPhone'));
 
 
 
 
 
$observer->setReplaceArguments(
[
'rc_groupNm' => $group->get('groupNm'),
'name' => $storage->get('memNm'),
'memNm' => $storage->get('memNm'),
 
'groupNm' => $group->get('groupNm'),
'rc_mallNm' => \Globals::get('gMall.mallNm'),
'shopUrl' => $this->basicInfo['mallDomain'],
);
$smsAuto->attach($observer);
} else {
$logger->info(sprintf('The membership level is the same. before[%d], now[%d]', $storage->get('beforeGroupSno'), $group->get('sno')));
}
} else {
$logger->info(sprintf('Not have a cell phone number or member name. memNo[%d]', $storage->get('memNo', 0)));
}
}
{
return $this->appraisalGroupConfig->get('apprSystem') == 'point';
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후Appraisal.php
namespace Bundle\Component\Member\Group;
use App;
use Component\Coupon\CouponAdmin;
use Component\Database\DBTableField;
use Component\Member\Member;
use Component\Sms\Code;
use Framework\Utility\ComponentUtils;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\NumberUtils;
use Origin\Service\Member\Group\MemberGroupCouponService;
use Origin\Service\Member\Group\MemberGroupService;
use Session;
/**
protected $search;
/** @var AppraisalMailSender 등급평가 메일발송 */
protected $mailSender;
 
 
/** @var string 등급평가일시 */
protected $appraisalDateTime;
/** @var bool 현재 평가 등급 기준치 만족 여부 */
/** @var array 현재 평가대상 회원 */
protected $currentMembers;
/** @var array 등급평가 제외 설정된 등급 sno 배열 */
protected $apprExclusionOfRating = [];
/** @var array 평가 대상 그룹 sno별 등급 */
protected $groupSort;
/** @var array 평가 대상 여부와 무관한 전체 그룹 sno별 등급 */
protected $allGroupSort = [];
/** @var array 그룹 관리 설정 */
public $policy;
/** @var array 기본 설정 */
public $basicInfo;
/** @var string 수행일자 */
public $targetDateTime;
/** @var array es_member 필드 타입 */
protected $memberFields;
/** @var object 로거 */
protected $logger;
/** @var CouponAdmin 쿠폰 어드민 (updateGroup 내 쿠폰 발급 재사용) */
protected $couponAdmin;
/** @var array [couponNo => [memNo, ...]] — updateGroup에서 누적 후 flush 로 쿠폰별 일괄 발급 */
protected $couponSmsBuffer = [];
/** @var array rollback 된 배치의 memNo — 유지회원 stamp 제외 대상 */
protected $rolledBackMemNos = [];
/**
* Appraisal constructor.
*
public function __construct(AppraisalSearch $search = null)
{
parent::__construct();
$this->memberFields = DBTableField::getFieldTypes('tableMember');
$this->logger = App::getInstance('logger');
$this->setSearch($search);
$this->groupConfig();
$this->validateAppraisalGroupConfig();
$this->policy = ComponentUtils::getPolicy('member.group');
$this->basicInfo = gd_policy('basic.info');
$this->couponAdmin = new CouponAdmin();
ini_set('memory_limit', '-1');
}
}
if ($data['apprSystem'] == 'point' && $data['appraisalPointOrderPriceFl'] != 'y' && $data['appraisalPointOrderRepeatFl'] != 'y' && $data['appraisalPointReviewRepeatFl'] != 'y' && $data['appraisalPointLoginRepeatFl'] != 'y') {
$this->logger->warning('Not set appraisal performance point');
 
throw new Exception(__('실적 점수제 평가 설정 값이 없습니다.'));
}
$this->appraisalGroupConfig = new SimpleStorage($data);
$validation->validateOverlapStandard();
$this->groupSort[$group['sno']] = $group['groupSort']; // 그룹 key: sno, value: sort 배열 생성
}
$this->allGroupSort = (array)$this->groupSort;
if ($isAppraisalFigure) {
foreach ($dao->selectAppraisalGroups() as $group) {
$this->allGroupSort[$group['sno']] = $group['groupSort'];
}
}
// 회원등급 평가시 제외인 등급 그룹 배열 생성
$exclusionGroups = $dao->selectGroupExclusion();
foreach ($exclusionGroups as $key){
$this->groupConfig = $groups;
}
/**
* 직전 평가일(autoAppraisalDateTime)은 배치 완료 후에 갱신되므로, 평가주기만큼 전진시켜 이번 회차를 찾는다.
*
* @param string $targetDate Y-m-d
*
* @return string Y-m-d
*/
protected function resolveAppraisalCycleStartDate($targetDate)
{
$calcCycleDay = (int)$this->getPolicyValue('calcCycleDay');
$base = date_create((string)$targetDate);
if ($calcCycleDay < 1 || $base === false) {
return $targetDate;
}
$base = date_create($base->format('Y-m-d'));
$cycleStart = $this->resolveCycleStartByPreviousAppraisal($base, $calcCycleDay);
if ($cycleStart === null) {
$cycleStart = $this->cycleDateInMonth($base, $calcCycleDay);
if ($cycleStart > $base) {
$cycleStart = $this->cycleDateInMonth($this->shiftMonth($base, -1), $calcCycleDay);
}
}
return $cycleStart->format('Y-m-d');
}
/**
* @param \DateTime $base
* @param int $calcCycleDay
*
* @return \DateTime|null
*/
protected function resolveCycleStartByPreviousAppraisal(\DateTime $base, $calcCycleDay)
{
$calcCycleMonth = (int)$this->getPolicyValue('calcCycleMonth');
$previous = (string)$this->getPolicyValue('autoAppraisalDateTime');
if ($calcCycleMonth < 1 || $previous === '') {
return null;
}
$cycleStart = date_create($previous);
if ($cycleStart === false) {
return null;
}
$cycleStart = date_create($cycleStart->format('Y-m-d'));
if ($cycleStart > $base) {
return null;
}
while (true) {
$next = $this->cycleDateInMonth($this->shiftMonth($cycleStart, $calcCycleMonth), $calcCycleDay);
if ($next > $base) {
return $cycleStart;
}
$cycleStart = $next;
}
}
/**
* @param \DateTime $base
* @param int $months
*
* @return \DateTime
*/
protected function shiftMonth(\DateTime $base, $months)
{
return date_create($base->format('Y-m-01'))->modify(sprintf('%+d month', $months));
}
/**
* @param \DateTime $base
* @param int $calcCycleDay
*
* @return \DateTime
*/
protected function cycleDateInMonth(\DateTime $base, $calcCycleDay)
{
$day = min($calcCycleDay, (int)$base->format('t'));
return date_create($base->format('Y-m-') . sprintf('%02d', $day));
}
/**
* @param string $key
*
* @return mixed
*/
protected function getPolicyValue($key)
{
return isset($this->policy[$key]) ? $this->policy[$key] : '';
}
/**
* @param int $groupSno
*
* @return int
*/
protected function resolveGroupSort($groupSno)
{
if (array_key_exists($groupSno, $this->allGroupSort)) {
return (int)$this->allGroupSort[$groupSno];
}
if (array_key_exists($groupSno, (array)$this->groupSort)) {
return (int)$this->groupSort[$groupSno];
}
$this->logger->warning('Not found groupSort. groupSno[' . $groupSno . ']');
return 0;
}
/**
* setGroupNames
* @return void
*/
public function appraisalGroupBySearch()
{
$this->logger->notice('Start group appraisal.');
 
$total = $this->countTotalMember();
$limit = $this->search->getAppraisalLimit();
$this->countByTotalMember = $this->appraisalCountByTotalMember($total, $limit);
$this->logger->notice('총 회원[' . $total . '], 1회 평가당 제한 인원수[' . $limit . '], 평가 실행 횟수[' . $this->countByTotalMember . ']');
 
$this->appraisalDateTime = DateTimeUtils::dateFormat('Y-m-d H:i:s', 'now');
if (method_exists($this->search, 'setRequiredData')) {
$this->search->setRequiredData($this->resolveRequiredData());
}
for ($offset = 0; $offset < $this->countByTotalMember; $offset++) {
$this->appraisalGroupByOffset($offset);
// 평가 대상으로 검색된 회원 초기화
$this->currentMembers = null;
}
$this->logger->notice('End group appraisal.');
}
/**
*/
public function appraisalGroupByOffset($offset)
{
 
$db = \App::getInstance('DB');
$this->search->setAppraisalOffset($offset);
if (method_exists($this->search, 'setTargetDateTime')) {
$this->search->setTargetDateTime($this->targetDateTime);
}
$this->logger->info(sprintf('Start appraisal group set offset %s', $offset));
foreach ($this->groupConfig as $index => $item) {
$this->logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
 
$this->group = new SimpleStorage($item);
if ($this->currentMembers === null) {
$this->currentMembers = $this->search->search($this->group);
}
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$this->logger->info(sprintf('has appraisal member count is %d', count($this->currentMembers)));
$this->updateGroupWithAddMailReceiver($this->currentMembers);
}
$this->logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
}
if ($this->policy['downwardAdjustment'] == 'y') {
$this->isDefaultGroupMember = true;
$this->logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
$this->group = new SimpleStorage($this->defaultGroupConfig);
$this->search->setAppraisalDateTime($this->appraisalDateTime);
$this->currentMembers = $this->search->searchDefaultGroup($this->appraisalDateTime);
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$this->logger->info(sprintf('has appraisal member count is %d', count($this->currentMembers)));
 
$this->updateGroupWithAddMailReceiver($this->currentMembers);
} else {
$this->logger->info('Not found appraisal members by default group', $this->defaultGroupConfig);
}
}
$this->logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
$this->isDefaultGroupMember = false;
}
/**
* 특정 회원에 대한 그룹평가
*
* @param array $memNos 타겟 memNo 배열
* appraisalGroupBySearch() 와 appraisalGroupByOffset() deprecate 및 해당 메서드로 대체 필요
*/
public function evaluateGroup(array $memNos)
{
if (empty($memNos)) {
return;
}
if (!method_exists($this->search, 'setTargetMembers')) {
throw new Exception('AppraisalSearch 구버전 : 자동 등급평가는 신규 setTargetMembers/setRequiredData/setTargetDateTime 메서드가 필요합니다. AppraisalSearch.php 를 코어와 동기화해주세요.');
}
$this->logger->notice('Start group appraisal.');
$this->appraisalDateTime = DateTimeUtils::dateFormat('Y-m-d H:i:s', 'now');
$this->search->setTargetDateTime($this->targetDateTime);
$this->search->setTargetMembers($memNos);
$this->search->setRequiredData($this->resolveRequiredData());
foreach ($this->groupConfig as $index => $item) {
$this->logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
$this->group = new SimpleStorage($item);
if ($this->currentMembers === null) {
$this->currentMembers = $this->search->search($this->group);
}
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$this->logger->info(sprintf('has appraisal member count is %d', count($this->currentMembers)));
$this->updateGroupWithAddMailReceiver($this->currentMembers);
}
$this->logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
}
if ($this->policy['downwardAdjustment'] == 'y') {
$this->isDefaultGroupMember = true;
$this->logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
$this->group = new SimpleStorage($this->defaultGroupConfig);
$this->search->setAppraisalDateTime($this->appraisalDateTime);
$this->currentMembers = $this->search->searchDefaultGroup($this->appraisalDateTime);
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$this->logger->info(sprintf('has appraisal member count is %d', count($this->currentMembers)));
$this->updateGroupWithAddMailReceiver($this->currentMembers);
} else {
$this->logger->info('Not found appraisal members by default group', $this->defaultGroupConfig);
}
$this->logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
}
$this->isDefaultGroupMember = false;
$this->logger->notice('End group appraisal.');
}
/**
* appraisalMailSend
* @return void
*/
public function updateGroupWithAddMailReceiver(array $members)
{
 
$session = \App::getInstance('session');
$db = \App::getInstance('DB');
$updateTargetMemNos = [];
$pendingReceivers = [];
try {
$groupSno = $this->group->get('sno');
$settleGb = $this->group->get('settleGb');
$smsSender = \App::load(\Component\Sms\SmsSender::class);
$smsSender->validPassword(\Request::post()->get('password'));
}
$checkGroupByPoint = $this->_isAppraisalSystemPoint();
foreach ($members as $index => $item) {
 
 
 
 
 
$session->set(Member::SESSION_MODIFY_MEMBER_INFO, $item);
$storage = new SimpleStorage($item);
$storage->set('settleGb', $settleGb);
$memNo = $storage->get('memNo');
 
// 현재 등급이 '등급평가시 제외' 설정이 아닐 때
if(array_key_exists($item['groupSno'], $this->apprExclusionOfRating) == false) {
if ($checkGroupByPoint) {
$this->checkGroupByPoint($item);
} else {
$this->checkGroupByFigure($item);
}
$beforeGroupSort = $this->resolveGroupSort($item['groupSno']);
$afterGroupSort = $this->resolveGroupSort($groupSno);
//하향펑가를 설정하지 않았거나, 하향평가를 사용하거나, 하향평가를 사용하지 않고 변경 등급이 기존 등급보다 높거나 같을 때 만 업데이트
if (empty($this->policy['downwardAdjustment']) || $this->policy['downwardAdjustment'] == 'y' || ($this->policy['downwardAdjustment'] == 'n' && $beforeGroupSort < $afterGroupSort)) {
if ($this->isCurrentGroupMember || $this->isDefaultGroupMember) {
$this->logger->info('Group change target. sno[' . $this->group->get('sno', 0) . '], name[' . $this->group->get('', '-') . ']', $item);
$updateTargetMemNos[] = $memNo;
$this->updateGroup($memNo);
$this->writeHistory($memNo);
$pendingReceivers[] = $storage;
 
unset($members[$index]);
}
}
}
}
// 쿠폰 발급은 회원 1명씩 호출하는 대신 쿠폰 번호별로 memNos 를 묶어 일괄 호출
if (!method_exists($this, 'flushCouponSmsBuffer')) {
throw new Exception('Appraisal 구버전 감지: flushCouponSmsBuffer 메서드 누락. 등급평가 쿠폰 버퍼 일괄발급 로직 미적용 시 쿠폰 미발급이 발생할 수 있어 사전 차단합니다.');
}
$this->flushCouponSmsBuffer();
if ($this->isTran) {
$db->commit();
$this->currentMembers = $members;
}
foreach ($pendingReceivers as $receiver) {
$this->addMailReceiver($receiver, $this->group);
$this->addSmsReceiver($receiver, $this->group);
}
} catch (Exception $e) {
$this->logger->error(__METHOD__ . ', ' . $e->getMessage(), ['exception' => $e]);
$db->rollback();
$this->couponSmsBuffer = [];
$this->rolledBackMemNos = array_merge($this->rolledBackMemNos, $updateTargetMemNos);
if (method_exists($this->search, 'removeEvaluatedMemberSnapshots')) {
$this->search->removeEvaluatedMemberSnapshots($updateTargetMemNos);
}
}
}
/**
* @deprecated 반복문 내 다중 호출 시 성능 저하가 발생하므로
* 반복문 밖에서 한 번만 호출하여 결과를 캐싱해 사용하세요.
* 향후 내부 로직에서 캐싱되거나 제거될 예정입니다.
*
* @param array $item 등급평가 대상의 기준치 정보
*
}
}
if ($point > 0) {
$this->logger->info(
 
__METHOD__, [
'memNo:'.$memNo,
$point,
*/
public function checkGroupByFigure($item)
{
 
$this->isCurrentGroupMember = true;
$priceUnit = 10000;
*/
public function updateGroupWithInsertHistory(SimpleStorage $storage)
{
$passwordCheckFl = gd_isset(\Request::post()->get('passwordCheckFl'), 'y');
if ($passwordCheckFl != 'n') {
$smsSender = \App::load(\Component\Sms\SmsSender::class);
$smsSender->validPassword(\Request::post()->get('password'));
}
$memNo = $storage->get('memNo');
$this->updateGroup($memNo);
$this->writeHistory($memNo);
if (!method_exists($this, 'flushCouponSmsBuffer')) {
throw new Exception('Appraisal 구버전 감지: flushCouponSmsBuffer 메서드 누락. 등급평가 쿠폰 버퍼 일괄발급 로직 미적용 시 쿠폰 미발급이 발생할 수 있어 사전 차단합니다.');
}
$this->flushCouponSmsBuffer();
}
/**
* updateGroup 루프에서 쌓인 쿠폰 발급 대상을 쿠폰 번호별로 묶어 일괄 발급.
* 호출당 saveMemberCouponSms 내부 셋업 쿼리 (checkCouponType, getCouponInfo,
* getMemberCouponStartDate/EndDate) 를 회원 수가 아닌 쿠폰 수 만큼만 수행.
*
* @return void
*/
public function flushCouponSmsBuffer()
{
if (empty($this->couponSmsBuffer)) {
return;
}
$passwordCheckFlBool = \Request::post()->get('passwordCheckFl') == 'n' ? false : true;
\Request::post()->set('couponSaveAdminId', '회원등급 쿠폰 혜택');
\Request::post()->set('managerNo', Session::get('manager.sno'));
\Request::post()->set('memberCouponState', 'y');
foreach ($this->couponSmsBuffer as $couponNo => $memNos) {
\Request::post()->set('couponNo', $couponNo);
$result = $this->couponAdmin->saveMemberCouponSms($memNos, null, $passwordCheckFlBool);
if ($result === false) {
$this->logger->warning(sprintf('Coupon issue skipped (checkCouponType failed). couponNo[%d] memNo_cnt[%d]', $couponNo, count($memNos)));
}
}
$this->couponSmsBuffer = [];
}
/**
}
}
//쿠폰 지급 대상 버퍼에 적재 (실제 발급은 flushCouponSmsBuffer 에서 쿠폰별 일괄 호출)
 
 
 
 
 
 
if($applyCoupon === true){
foreach(explode(INT_DIVISION, $this->group->get('groupCoupon')) as $value){
$couponNo = (int)$value;
if ($couponNo > 0) {
$this->couponSmsBuffer[$couponNo][] = $memNo;
}
 
 
 
 
 
 
 
 
 
}
}
$db = \App::getInstance('DB');
 
$arrUpdate[] = 'groupSno = ?';
$arrBind = [];
$db->bind_param_push($arrBind, $this->memberFields['groupSno'], $this->group->get('sno'));
$arrUpdate[] = 'groupModDt = ?';
$db->bind_param_push($arrBind, $this->memberFields['groupModDt'], $this->appraisalDateTime);
$arrUpdate[] = 'autoGroupAppraisalDt = ?';
$db->bind_param_push($arrBind, $this->memberFields['autoGroupAppraisalDt'], $this->appraisalDateTime);
$db->bind_param_push($arrBind, $this->memberFields['memNo'], $memNo);
$db->bind_param_push($arrBind, $this->memberFields['appFl'], 'y');
$db->bind_param_push($arrBind, $this->memberFields['sleepFl'], 'n');
$db->set_update_db(DB_MEMBER, $arrUpdate, 'memNo = ? AND appFl = ? AND sleepFl = ?', $arrBind);
}
$limit = $this->search->getAppraisalLimit();
// 처리 반복 횟수 계산
$countByTotalMember = $this->appraisalCountByTotalMember($total, $limit);
 
 
 
 
$this->logger->notice('Start apply coupon to all members.');
$this->logger->notice('총 회원[' . $total . '], 1회 처리당 제한 인원수[' . $limit . '], 처리 실행 횟수[' . $countByTotalMember . ']');
$passwordCheckFl = gd_isset(\Request::post()->get('passwordCheckFl'), 'y');
if($passwordCheckFl != 'n') {
// 쿠폰 관리 객체 미리 생성
$coupon = new \Component\Coupon\CouponAdmin;
// 쿠폰 정보 미리 생성
\Request::post()->set('couponSaveAdminId', '회원등급 쿠폰 혜택');
\Request::post()->set('memberCouponState', 'y');
\Request::post()->set('managerNo', Session::get('manager.sno'));
// 그룹 쿠폰 정보
/** @var MemberGroupService $memberGroupService */
$memberGroupService = App::getInstance(MemberGroupService::class);
$allGroupList = $memberGroupService->findAllGroupsKeyBySno();
// 페이징 처리를 위한 반복문
for ($offset = 0; $offset < $countByTotalMember; $offset++) {
$start = $offset * $limit;
$this->logger->info(sprintf('Start apply coupon with offset %s, limit %s', $start, $limit));
// 페이지별 회원 목록 조회
$memberPageList = $member->getMemberList($arrWhere, null, null, $start, $limit, $field);
 
 
 
 
 
 
 
if (count($memberPageList) === 0) {
continue;
}
 
foreach ($memberPageList as $memberInfo) {
$groupCoupon = $allGroupList[$memberInfo['groupSno']]['groupCoupon'] ?? '';
if (empty($groupCoupon)) {
continue;
}
$applyCouponList = explode(INT_DIVISION, $groupCoupon);
// 하나씩 쿠폰 발급 하기
if (!empty($applyCouponList) && count($applyCouponList) > 0) {
foreach ($applyCouponList as $applyCoupon) {
if (is_numeric($applyCoupon) && $applyCoupon > 0) {
\Request::post()->set('couponNo', $applyCoupon);
\Request::post()->set('memberCouponStartDate', $coupon->getMemberCouponStartDate($applyCoupon));
\Request::post()->set('memberCouponEndDate', $coupon->getMemberCouponEndDate($applyCoupon));
$coupon->saveMemberCouponSms((array)$memberInfo['memNo'], null, $passwordCheckFl);
}
 
}
unset($applyCouponList);
}
}
// 메모리 정리
unset($memberPageList);
}
$this->logger->notice('End apply coupon to all members.');
}
/**
* 특정 회원에게 쿠폰 발급
* - 이미 등급평가를 통해 쿠폰이 발급된 회원 제외
*
* applyCouponAllMember() 를 deprecate 및 해당 메서드로 대체 필요
* @param string $targetDate 대상일자
* @param array $memNos 대상 회원번호 배열
*
* @return void
*/
public function applyCouponSpecificMember(string $targetDate, array $memNos)
{
if (empty($memNos)) {
return;
}
$this->logger->notice('Start apply coupon to specific members.');
$passwordCheckFl = gd_isset(\Request::post()->get('passwordCheckFl'), 'y');
if($passwordCheckFl != 'n') {
$smsSender = \App::load(\Component\Sms\SmsSender::class);
$smsSender->validPassword(\Request::post()->get('password'));
}
$passwordCheckFl = $passwordCheckFl == 'n' ? false : true;
// 쿠폰의 경우 재수행되었을 때 구분할 수 있는 값이 필요하여 대상일자를 추가함
$couponSaveAdminId = '회원등급 쿠폰 혜택(' . str_replace('-','', $targetDate) . ')';
\Request::post()->set('couponSaveAdminId', $couponSaveAdminId);
\Request::post()->set('memberCouponState', 'y');
\Request::post()->set('managerNo', Session::get('manager.sno'));
/** @var MemberGroupCouponService $memberGroupCouponService */
$memberGroupCouponService = App::getInstance(MemberGroupCouponService::class);
$memberGroupCouponService->applyGroupCouponsToMembers(
$memNos,
$couponSaveAdminId,
$passwordCheckFl,
$targetDate,
$this->resolveAppraisalCycleStartDate($targetDate)
);
}
/**
* 변경자는 updateGroup 에서 이미 stamp 되므로, 유지자만 보완해 동일자 재평가 대상에서 제외되도록 한다.
*
* @param array $memNos
* @param string $targetDate Y-m-d
*
* @return void
*/
public function updateAppraisalCompletedForDefaultGroupMembers(array $memNos, string $targetDate)
{
if (empty($memNos)) {
return;
}
if (!method_exists($this->search, 'getEvaluatedMemberSnapshots')) {
throw new Exception('AppraisalSearch 구버전 감지: getEvaluatedMemberSnapshots 누락. 평가 스냅샷 없이 유지회원 stamp 시 미평가 회원까지 평가완료 처리되어 다음 회차에서 누락될 수 있어 사전 차단합니다.');
}
$db = \App::getInstance('DB');
$touchDateTime = $this->appraisalDateTime ?: ($targetDate . ' 00:00:00');
$from = $targetDate . ' 00:00:00';
$snapshots = $this->search->getEvaluatedMemberSnapshots();
$exclusion = $this->apprExclusionOfRating ?? [];
$appraisalGroupSnos = $this->resolveAppraisalGroupSnos();
if (empty($appraisalGroupSnos)) {
$this->logger->warning('Skip retained member stamp. Not found appraisal target group.', [
'groupSort' => $this->groupSort,
'exclusion' => $exclusion,
]);
return;
}
if (empty($snapshots)) {
// 평가 검색이 실행되지 않은 실행(평가기준 그룹 없음 등) — 종전 라이브 조회 방식으로 stamp
$memNos = array_values(array_diff($memNos, $this->rolledBackMemNos));
if (empty($memNos)) {
return;
}
$arrBind = [];
$db->bind_param_push($arrBind, 's', $touchDateTime);
$placeholders = implode(',', array_fill(0, count($memNos), '?'));
foreach ($memNos as $memNo) {
$db->bind_param_push($arrBind, 'i', $memNo);
}
$db->bind_param_push($arrBind, 's', 'y');
$db->bind_param_push($arrBind, 's', 'n');
$groupPlaceholders = implode(',', array_fill(0, count($appraisalGroupSnos), '?'));
foreach ($appraisalGroupSnos as $sno) {
$db->bind_param_push($arrBind, 'i', $sno);
}
$db->bind_param_push($arrBind, 's', $from);
$strWhere = 'memNo IN (' . $placeholders . ')'
. ' AND appFl = ?'
. ' AND sleepFl = ?'
. ' AND groupSno IN (' . $groupPlaceholders . ')'
. ' AND (autoGroupAppraisalDt IS NULL OR autoGroupAppraisalDt < ?)';
$db->set_update_db(DB_MEMBER, 'autoGroupAppraisalDt = ?', $strWhere, $arrBind);
$this->logger->info('Update autoGroupAppraisalDt for retained members (no snapshot fallback)', [
'table' => DB_MEMBER,
'memNoCount' => count($memNos),
'from' => $from,
]);
return;
}
$retainedMemNos = $this->resolveRetainedStampTargets($snapshots, $exclusion, $appraisalGroupSnos);
if (empty($retainedMemNos)) {
return;
}
$arrBind = [];
$db->bind_param_push($arrBind, 's', $touchDateTime);
$placeholders = implode(',', array_fill(0, count($retainedMemNos), '?'));
foreach ($retainedMemNos as $memNo) {
$db->bind_param_push($arrBind, 'i', $memNo);
}
$db->bind_param_push($arrBind, 's', 'y');
$db->bind_param_push($arrBind, 's', 'n');
$db->bind_param_push($arrBind, 's', $from);
$strWhere = 'memNo IN (' . $placeholders . ')'
. ' AND appFl = ?'
. ' AND sleepFl = ?'
. ' AND (autoGroupAppraisalDt IS NULL OR autoGroupAppraisalDt < ?)';
$db->set_update_db(DB_MEMBER, 'autoGroupAppraisalDt = ?', $strWhere, $arrBind);
$this->logger->info('Update autoGroupAppraisalDt for retained members', [
'table' => DB_MEMBER,
'memNoCount' => count($retainedMemNos),
'from' => $from,
]);
}
/**
* @param array $snapshots memNo => search 결과 row
* @param array $exclusion 제외그룹 sno => sno
* @param array $appraisalGroupSnos 평가 대상 등급 sno 배열
*
* @return array
*/
public function resolveRetainedStampTargets(array $snapshots, array $exclusion, array $appraisalGroupSnos)
{
$retainedMemNos = [];
$appraisalGroupSnos = array_map('intval', $appraisalGroupSnos);
foreach ($snapshots as $memNo => $row) {
$groupSno = is_array($row) ? ($row['groupSno'] ?? null) : null;
if ($groupSno !== null && array_key_exists($groupSno, $exclusion)) {
continue;
}
if ($groupSno !== null && in_array((int)$groupSno, $appraisalGroupSnos, true) === false) {
continue;
}
$retainedMemNos[] = (int)$memNo;
}
return $retainedMemNos;
}
/**
* @return array
*/
public function resolveAppraisalGroupSnos()
{
$groupSnos = array_keys((array)$this->groupSort);
$exclusionSnos = array_values((array)$this->apprExclusionOfRating);
return array_values(array_diff($groupSnos, $exclusionSnos));
}
/**
*/
public function addSmsReceiver(SimpleStorage $storage, SimpleStorage $group)
{
 
if ($storage->get('cellPhone') != null && $storage->get('memNm') != null) {
if ($storage->get('beforeGroupSno') != $group->get('sno')) {
$smsAuto = \App::load(\Component\Sms\SmsAuto::class);
$observer = new SmsAutoObserver();
$observer->setSmsType(SmsAutoCode::MEMBER);
$observer->setSmsAutoCodeType(Code::GROUP_CHANGE);
$observer->setReceiver([
'memNo' => $storage->get('memNo'),
'memNm' => $storage->get('memNm'),
'smsFl' => $storage->get('smsFl', 'y'),
'cellPhone' => $storage->get('cellPhone'),
]);
$observer->setReplaceArguments(
[
'rc_groupNm' => $group->get('groupNm'),
'name' => $storage->get('memNm'),
'memNm' => $storage->get('memNm'),
'memNo' => $storage->get('memNo'),
'groupNm' => $group->get('groupNm'),
'rc_mallNm' => \Globals::get('gMall.mallNm'),
'shopUrl' => $this->basicInfo['mallDomain'],
);
$smsAuto->attach($observer);
} else {
$this->logger->info(sprintf('The membership level is the same. before[%d], now[%d]', $storage->get('beforeGroupSno'), $group->get('sno')));
}
} else {
$this->logger->info(sprintf('Not have a cell phone number or member name. memNo[%d]', $storage->get('memNo', 0)));
}
}
{
return $this->appraisalGroupConfig->get('apprSystem') == 'point';
}
/*
* 회원등급평가 타겟일자 셋팅
*
* @param $targetDateTime 수행일자
*/
public function setTargetDateTime($targetDateTime = null)
{
$this->targetDateTime = empty($targetDateTime) ? date('Y-m-d') : $targetDateTime;
}
/**
* 전체 등급 설정을 스캔하여 실제 필요한 데이터 유형을 결정
* @return array ['order' => bool, 'review' => bool, 'login' => bool]
*/
protected function resolveRequiredData(): array
{
$isPoint = $this->_isAppraisalSystemPoint();
if ($isPoint) {
return [
'order' => $this->appraisalGroupConfig->get('appraisalPointOrderPriceFl', 'n') == 'y'
|| $this->appraisalGroupConfig->get('appraisalPointOrderRepeatFl', 'n') == 'y',
'review' => $this->appraisalGroupConfig->get('appraisalPointReviewRepeatFl', 'n') == 'y',
'login' => $this->appraisalGroupConfig->get('appraisalPointLoginRepeatFl', 'n') == 'y',
];
}
// 수치제: 등급별 설정이므로 전체 등급을 스캔
$allGroups = array_merge($this->groupConfig, [$this->defaultGroupConfig]);
$needsOrder = false;
$needsReview = false;
foreach ($allGroups as $group) {
if (!$needsOrder && (($group['apprFigureOrderPriceFl'] ?? 'n') == 'y' || ($group['apprFigureOrderRepeatFl'] ?? 'n') == 'y')) {
$needsOrder = true;
}
if (!$needsReview && ($group['apprFigureReviewRepeatFl'] ?? 'n') == 'y') {
$needsReview = true;
}
if ($needsOrder && $needsReview) {
break;
}
}
return [
'order' => $needsOrder,
'review' => $needsReview,
'login' => false, // 수치제에서는 로그인 기준 없음
];
}
}
 
Component/Member/Group/AppraisalSearch.php+156-27
변경 전AppraisalSearch.php
private $periodQuery;
/** @var string $periodDateFormat 검색기간 포맷 */
private $periodDateFormat = 'Y-m-d';
 
 
 
 
 
 
 
 
/**
* 조회
unset($this->query, $this->column, $this->where);
$this->periodQuery();
$this->boardQuery();
$this->orderQuery();
$this->loginQuery();
$this->buildQuery();
 
 
 
 
 
 
 
$logger->info(sprintf('Build query complete. %s', $this->query));
$results = $this->db->query_fetch($this->query);
$logger->debug('Appraisal search result keys', gd_array_keys($results[0]));
$return = [];
foreach ($results as $index => $result) {
if (gd_array_key_exists($result['memNo'], $return)) {
$return[$result['memNo']] = $result;
}
 
 
 
 
 
 
 
 
return $return;
}
$query[] = 'SELECT memNo, COUNT(sno) AS cnt';
$query[] = ', COUNT(IF(loginCntMobile > 0, 1, NULL)) AS mcnt';
$query[] = 'FROM ' . DB_MEMBER_LOGINLOG;
 
if ($this->usePeriod()) {
$query[] = 'WHERE regDt ' . $this->periodQuery;
 
 
 
 
 
 
}
$query[] = 'GROUP BY memNo';
$this->loginQuery = '(' . gd_implode(' ', $query) . ')';
$query[] = ', COUNT(IF(isMobile=\'y\', sno, NULL)) AS mcnt';
$query[] = 'FROM ' . DB_BD_ . Board::BASIC_GOODS_REIVEW_ID;
$query[] = 'WHERE memNo > 0';
 
 
 
if ($this->usePeriod()) {
$query[] = 'AND regDt ' . $this->periodQuery;
}
$subQuery[] = 'LEFT JOIN ' . DB_ORDER_GOODS . ' AS og ON o.orderNo = og.orderNo';
$subQuery[] = 'LEFT JOIN ' . DB_ORDER_ADD_GOODS . ' AS oag ON o.orderNo = oag.orderNo';
$subQuery[] = 'WHERE og.orderStatus=\'s1\' AND o.memNo > 0';
 
 
 
if ($this->usePeriod()) {
$subQuery[] = 'AND og.finishDt ' . $this->periodQuery;
}
unset($this->query, $this->column, $this->where);
$this->periodQuery();
$this->boardQuery();
$this->orderQuery();
$this->loginQuery();
$this->buildQuery('m.groupSno!=' . $groupSno);
 
 
 
 
 
 
 
 
 
 
$logger = \App::getInstance('logger');
$logger->info(sprintf('Build query complete. %s', $this->query));
*/
public function searchDefaultGroup($appraisalDateTime)
{
$this->db->strField = 'm.memNo, m.memNm, m.email, m.cellPhone, m.groupSno, m.adminMemo, m.groupModDt';
// 다른등급 대상 회원 조회(_pushWhere())와 조건 동일하게 설정
$this->db->strWhere = 'm.sleepFl=\'n\' AND m.appFl=\'y\'';
$this->db->strLimit = ($this->appraisalOffset * $this->appraisalLimit) . ', ' . $this->appraisalLimit;
 
 
 
 
 
 
 
$this->db->strGroup = 'm.memNo';
$query = $this->db->query_complete();
$return[$result['memNo']] = $result;
}
}
 
return $return;
}
if (!empty($where)) {
$this->where[] = $where;
}
$this->db->strField = gd_implode(',', $this->column);
$this->db->strWhere = gd_implode(' AND ', $this->where);
$this->db->strJoin = 'LEFT JOIN ' . $this->boardQuery . ' AS b ON m.memNo = b.memNo';
$this->db->strJoin .= ' LEFT JOIN ' . $this->orderQuery . ' AS o ON m.memNo = o.memNo';
$this->db->strJoin .= ' LEFT JOIN ' . $this->loginQuery . ' AS l ON m.memNo = l.memNo';
$this->db->strLimit = ($this->appraisalOffset * $this->appraisalLimit) . ', ' . $this->appraisalLimit;
 
 
 
 
 
 
 
 
 
 
 
 
$this->db->strGroup = 'm.memNo';
$query = $this->db->query_complete();
*/
private function _pushColumn()
{
$this->column[] = 'm.memNo, m.memNm, m.email, m.cellPhone, m.groupSno, m.adminMemo, m.groupModDt';
$this->column[] = 'IFNULL(l.cnt, 0) AS loginCount';
$this->column[] = 'IFNULL(b.cnt, 0) AS reviewCount';
$this->column[] = 'IFNULL(o.cnt, 0) AS orderCount';
$this->column[] = 'IFNULL(o.price, 0) AS orderPrice';
$this->column[] = 'IFNULL(l.mcnt, 0) AS loginCountMobile';
$this->column[] = 'IFNULL(b.mcnt, 0) AS reviewCountMobile';
$this->column[] = 'IFNULL(o.mcnt, 0) AS orderCountMobile';
$this->column[] = 'IFNULL(o.mprice, 0) AS orderPriceMobile';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
/**
$column = 'IFNULL(' . $column . ', 0)';
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후AppraisalSearch.php
private $periodQuery;
/** @var string $periodDateFormat 검색기간 포맷 */
private $periodDateFormat = 'Y-m-d';
/** @var string 수행일자 */
protected $targetDateTime;
/** @var array airflow 가 지정한 memNo 배열 */
protected $targetMembers = [];
/** @var array 실제 평가된 회원 스냅샷 (memNo => search 결과 row). 유지회원 stamp 대상 판정 기준 */
protected $evaluatedMembers = [];
/** @var array 필요한 서브쿼리 플래그 ['order' => bool, 'review' => bool, 'login' => bool] */
protected $requiredData = ['order' => true, 'review' => true, 'login' => true];
/**
* 조회
unset($this->query, $this->column, $this->where);
$this->periodQuery();
if ($this->requiredData['review']) {
$this->boardQuery();
}
if ($this->requiredData['order']) {
$this->orderQuery();
}
if ($this->requiredData['login']) {
$this->loginQuery();
}
// 수동평가(targetMembers 없음)는 동일자 재평가 허용을 위해 완료대상 제외 필터 미적용
$this->buildQuery(empty($this->targetMembers) ? '' : $this->whereExcludeCompleteMember());
$logger->info(sprintf('Build query complete. %s', $this->query));
$results = $this->db->query_fetch($this->query);
$logger->debug('Appraisal search result keys', empty($results) ? [] : array_keys($results[0]));
$return = [];
foreach ($results as $index => $result) {
if (gd_array_key_exists($result['memNo'], $return)) {
$return[$result['memNo']] = $result;
}
$logger->info(sprintf('Appraisal search matched %d of %d requested members. groupSno[%s]', count($return), count($this->targetMembers), $group->get('sno', '-')));
if (!empty($this->targetMembers)) {
foreach ($return as $memNo => $row) {
$this->evaluatedMembers[$memNo] = $row;
}
}
return $return;
}
$query[] = 'SELECT memNo, COUNT(sno) AS cnt';
$query[] = ', COUNT(IF(loginCntMobile > 0, 1, NULL)) AS mcnt';
$query[] = 'FROM ' . DB_MEMBER_LOGINLOG;
$loginWhere = [];
if ($this->usePeriod()) {
$loginWhere[] = 'regDt ' . $this->periodQuery;
}
if (!empty($this->targetMembers)) {
$loginWhere[] = 'memNo IN (' . implode(',', array_map('intval', $this->targetMembers)) . ')';
}
if (!empty($loginWhere)) {
$query[] = 'WHERE ' . implode(' AND ', $loginWhere);
}
$query[] = 'GROUP BY memNo';
$this->loginQuery = '(' . gd_implode(' ', $query) . ')';
$query[] = ', COUNT(IF(isMobile=\'y\', sno, NULL)) AS mcnt';
$query[] = 'FROM ' . DB_BD_ . Board::BASIC_GOODS_REIVEW_ID;
$query[] = 'WHERE memNo > 0';
if (!empty($this->targetMembers)) {
$query[] = 'AND memNo IN (' . implode(',', array_map('intval', $this->targetMembers)) . ')';
}
if ($this->usePeriod()) {
$query[] = 'AND regDt ' . $this->periodQuery;
}
$subQuery[] = 'LEFT JOIN ' . DB_ORDER_GOODS . ' AS og ON o.orderNo = og.orderNo';
$subQuery[] = 'LEFT JOIN ' . DB_ORDER_ADD_GOODS . ' AS oag ON o.orderNo = oag.orderNo';
$subQuery[] = 'WHERE og.orderStatus=\'s1\' AND o.memNo > 0';
if (!empty($this->targetMembers)) {
$subQuery[] = 'AND o.memNo IN (' . implode(',', array_map('intval', $this->targetMembers)) . ')';
}
if ($this->usePeriod()) {
$subQuery[] = 'AND og.finishDt ' . $this->periodQuery;
}
unset($this->query, $this->column, $this->where);
$this->periodQuery();
if ($this->requiredData['review']) {
$this->boardQuery();
}
if ($this->requiredData['order']) {
$this->orderQuery();
}
if ($this->requiredData['login']) {
$this->loginQuery();
}
$where = 'm.groupSno != ' . $groupSno;
if (!empty($this->targetMembers)) {
$where .= ' AND ' . $this->whereExcludeCompleteMember();
}
$this->buildQuery($where);
$logger = \App::getInstance('logger');
$logger->info(sprintf('Build query complete. %s', $this->query));
*/
public function searchDefaultGroup($appraisalDateTime)
{
$this->db->strField = 'm.memNo, m.memNm, m.email, m.cellPhone, m.smsFl, m.maillingFl, m.groupSno, m.adminMemo, m.groupModDt';
// 다른등급 대상 회원 조회(_pushWhere())와 조건 동일하게 설정
$this->db->strWhere = 'm.sleepFl=\'n\' AND m.appFl=\'y\'';
if (!empty($this->targetMembers)) {
$this->db->strWhere .= ' AND ' . $this->whereExcludeCompleteMember();
}
if (empty($this->targetMembers)) {
$this->db->strLimit = ($this->appraisalOffset * $this->appraisalLimit) . ', ' . $this->appraisalLimit;
} else {
$this->db->strWhere .= ' AND m.memNo IN (' . implode(',', array_map('intval', $this->targetMembers)) . ')';
}
$this->db->strGroup = 'm.memNo';
$query = $this->db->query_complete();
$return[$result['memNo']] = $result;
}
}
return $return;
}
if (!empty($where)) {
$this->where[] = $where;
}
$this->db->strField = implode(',', $this->column);
$this->db->strWhere = implode(' AND ', $this->where);
$joins = [];
if ($this->requiredData['review']) {
$joins[] = 'LEFT JOIN ' . $this->boardQuery . ' AS b ON m.memNo = b.memNo';
}
if ($this->requiredData['order']) {
$joins[] = 'LEFT JOIN ' . $this->orderQuery . ' AS o ON m.memNo = o.memNo';
}
if ($this->requiredData['login']) {
$joins[] = 'LEFT JOIN ' . $this->loginQuery . ' AS l ON m.memNo = l.memNo';
}
$this->db->strJoin = implode(' ', $joins);
if (empty($this->targetMembers)) {
$this->db->strLimit = ($this->appraisalOffset * $this->appraisalLimit) . ', ' . $this->appraisalLimit;
} else {
$this->db->strWhere .= ' AND m.memNo IN (' . implode(',', array_map('intval', $this->targetMembers)) . ')';
}
$this->db->strGroup = 'm.memNo';
$query = $this->db->query_complete();
*/
private function _pushColumn()
{
$this->column[] = 'm.memNo, m.memNm, m.email, m.cellPhone, m.smsFl, m.maillingFl, m.groupSno, m.adminMemo, m.groupModDt';
if ($this->requiredData['login']) {
$this->column[] = 'IFNULL(l.cnt, 0) AS loginCount';
$this->column[] = 'IFNULL(l.mcnt, 0) AS loginCountMobile';
} else {
$this->column[] = '0 AS loginCount';
$this->column[] = '0 AS loginCountMobile';
}
if ($this->requiredData['review']) {
$this->column[] = 'IFNULL(b.cnt, 0) AS reviewCount';
$this->column[] = 'IFNULL(b.mcnt, 0) AS reviewCountMobile';
} else {
$this->column[] = '0 AS reviewCount';
$this->column[] = '0 AS reviewCountMobile';
}
if ($this->requiredData['order']) {
$this->column[] = 'IFNULL(o.cnt, 0) AS orderCount';
$this->column[] = 'IFNULL(o.price, 0) AS orderPrice';
$this->column[] = 'IFNULL(o.mcnt, 0) AS orderCountMobile';
$this->column[] = 'IFNULL(o.mprice, 0) AS orderPriceMobile';
} else {
$this->column[] = '0 AS orderCount';
$this->column[] = '0 AS orderPrice';
$this->column[] = '0 AS orderCountMobile';
$this->column[] = '0 AS orderPriceMobile';
}
}
/**
$column = 'IFNULL(' . $column . ', 0)';
}
}
public function setRequiredData(array $requiredData)
{
$this->requiredData = array_merge($this->requiredData, $requiredData);
}
/*
* 회원등급평가 타겟일자 셋팅
*
* @param string $targetDateTime 수행일자
*/
public function setTargetDateTime($targetDateTime = null)
{
$this->targetDateTime = $targetDateTime;
}
public function setTargetMembers(array $target = [])
{
$this->targetMembers = $target;
}
public function getEvaluatedMemberSnapshots()
{
return $this->evaluatedMembers;
}
public function removeEvaluatedMemberSnapshots(array $memNos)
{
foreach ($memNos as $memNo) {
unset($this->evaluatedMembers[$memNo]);
}
}
/*
* 타겟일자기준 회원등급평가 완료대상 제외 where 조건
*
* @return string
*/
public function whereExcludeCompleteMember()
{
$from = $this->db->escape("{$this->targetDateTime} 00:00:00");
return "(m.autoGroupAppraisalDt IS NULL OR m.autoGroupAppraisalDt < '{$from}')";
}
}
 
Component/Member/Group/GroupDomain.php+13-1
변경 전GroupDomain.php
private $managerNo;
private $regDt;
private $modDt;
 
 
function __construct(array $arr = null)
{
/**
* 객체에 설정된 추가할인적용제외/중복할인적용에 대한 화면에 보여질 정보를 데이터베이스 조회를 통해 설정하는 함수
 
 
*/
public function setGroupDcData()
{
 
 
 
 
 
$this->toStrip('dcExOption')->toJsonDecode('dcExOption', true);
$this->toStrip('dcExScm')->toJsonDecode('dcExScm', true);
$this->toStrip('dcExCategory')->toJsonDecode('dcExCategory', true);
public function toStrip($key = null)
{
$this->$key = StringUtils::htmlSpecialCharsStripSlashes($this->$key);
 
 
 
return $this;
}
 
변경 후GroupDomain.php
private $managerNo;
private $regDt;
private $modDt;
/** @var bool setGroupDcData() 중복 실행 방지 플래그 */
private $isGroupDcDataSet = false;
function __construct(array $arr = null)
{
/**
* 객체에 설정된 추가할인적용제외/중복할인적용에 대한 화면에 보여질 정보를 데이터베이스 조회를 통해 설정하는 함수
*
* 조회 결과를 원본 필드에 덮어쓰므로 두 번 호출하면 값이 깨진다. 인스턴스 재사용 시 최초 1회만 수행한다.
*/
public function setGroupDcData()
{
if ($this->isGroupDcDataSet) {
return;
}
$this->isGroupDcDataSet = true;
$this->toStrip('dcExOption')->toJsonDecode('dcExOption', true);
$this->toStrip('dcExScm')->toJsonDecode('dcExScm', true);
$this->toStrip('dcExCategory')->toJsonDecode('dcExCategory', true);
public function toStrip($key = null)
{
// 캐시된 인스턴스를 회원 수만큼 재사용해도 멱등하게 동작한다.
if (is_string($this->$key)) {
$this->$key = StringUtils::htmlSpecialCharsStripSlashes($this->$key);
}
return $this;
}
 
Component/Member/History.php+9
변경 전History.php
$before = $session->get(Member::SESSION_MODIFY_MEMBER_INFO, null);
$after = $this->db->getData(DB_MEMBER, $this->memNo, 'memNo');
 
 
 
 
 
 
 
 
 
 
MemberUtil::combineMemberData($before);
MemberUtil::combineMemberData($after);
 
 
변경 후History.php
$before = $session->get(Member::SESSION_MODIFY_MEMBER_INFO, null);
$after = $this->db->getData(DB_MEMBER, $this->memNo, 'memNo');
 
// 자동 등급평가 등 세션 미세팅 경로에서 null 이 들어올 수 있음.
// combineMemberData → ArrayUtils::unsetDiff(array $keys) 가 strict 타입이라 TypeError 발생 → 빈 배열로 보정.
if (!is_array($before)) {
$before = [];
}
if (!is_array($after)) {
$after = [];
}
 
MemberUtil::combineMemberData($before);
MemberUtil::combineMemberData($after);
 
 
Controller/Admin/Member/MemberGroupPsController.php+4
변경 전MemberGroupPsController.php
$appraisal = \App::load('\\Component\\Member\\Group\\Appraisal');
$appraisal->setRemoteAddress(Request::getRemoteAddress());
ini_set('max_execution_time', 0);
 
 
 
 
$appraisal->appraisalGroupBySearch();
$appraisal->appraisalMailSend();
if($appraisal->policy['couponConditionComplete'] == 'y' && $appraisal->policy['couponConditionCompleteChange'] != 'y') {
 
변경 후MemberGroupPsController.php
$appraisal = \App::load('\\Component\\Member\\Group\\Appraisal');
$appraisal->setRemoteAddress(Request::getRemoteAddress());
ini_set('max_execution_time', 0);
if (!method_exists($appraisal, 'setTargetDateTime')) {
throw new \Exception('Appraisal 구버전 감지: setTargetDateTime 메서드 누락. Appraisal.php 를 코어와 동기화해주세요.');
}
$appraisal->setTargetDateTime();
$appraisal->appraisalGroupBySearch();
$appraisal->appraisalMailSend();
if($appraisal->policy['couponConditionComplete'] == 'y' && $appraisal->policy['couponConditionCompleteChange'] != 'y') {