변경 파일 목록
총 51개
Admin (11)
epost_return_request_result.phporder/epost_return_request/epost_return_request_search.phporder/epost_return_request/epost_return_request.phporder/inc_claim_view_exchange.phporder/inc_claim_view.phporder/inc_order_view.phporder/layout_order_claim_list.phporder/layout_order_goods_list.phporder/layout_order_view_status_back.phporder/popup_epost_apply_frame.phporder/post_request.phporder/Component (10)
DBTableField.phpDatabase/Delivery.phpDelivery/EpostReturnAdmin.phpOrder/EpostReturnRequestValidator.phpOrder/OrderAdminNew.phpOrder/OrderAdmin.phpOrder/OrderNew.phpOrder/Order.phpOrder/ReOrderCalculation.phpOrder/ReturnInvoice.phpOrder/Controller (15)
EpostReturnRequestController.phpAdmin/Order/EpostReturnRequestPsController.phpAdmin/Order/OrderChangePsController.phpAdmin/Order/OrderPsController.phpAdmin/Order/PostIssueController.phpAdmin/Order/PostPsController.phpAdmin/Order/PostRequestController.phpAdmin/Order/PostReserveController.phpAdmin/Order/CancelListController.phpFront/Mypage/IndexController.phpFront/Mypage/OrderListController.phpFront/Mypage/OrderViewController.phpFront/Mypage/RefundListController.phpFront/Mypage/OrderListController.phpMobile/Mypage/OrderViewController.phpMobile/Mypage/data (12)
_order_goods_list.htmlskin/front/moment/mypage/_reorder_goods_list.htmlskin/front/moment/mypage/_order_goods_list.htmlskin/front/mygreek/mypage/_reorder_goods_list.htmlskin/front/mygreek/mypage/_order_goods_list.htmlskin/front/story_g/mypage/_reorder_goods_list.htmlskin/front/story_g/mypage/_order_goods_list.htmlskin/mobile/moment/mypage/_reorder_goods_list.htmlskin/mobile/moment/mypage/_order_goods_list.htmlskin/mobile/mygreek/mypage/_reorder_goods_list.htmlskin/mobile/mygreek/mypage/_order_goods_list.htmlskin/mobile/story_g/mypage/_reorder_goods_list.htmlskin/mobile/story_g/mypage/변경 파일 코드
추가삭제
변경 전epost_return_request_result.php
변경 후epost_return_request_result.php
| <div class="ncua-data-grid ncua-data-grid--search-result"> | |
| <div class="ncua-data-grid__summary"><span id="epr-summary-text">검색 <strong class="ncua-data-grid__summary-count"><?= number_format($total) ?></strong>개 / 전체 <strong class="ncua-data-grid__summary-count"><?= number_format($grandTotal) ?></strong>개</span></div> | |
| <div class="ncua-data-grid__table-wrapper"> | |
| <div class="ncua-data-grid__action-bar ncua-data-grid__action-bar--top ncua-data-grid__action-bar--space-between"> | |
| <div class="ncua-data-grid__action-bar-group"> | |
| <span class="epr-leadin"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.667" d="M10 3 4.5 8.5 2 6"></path></svg>선택한 주문을</span> | |
| <button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray js-epr-request"><span class="ncua-btn__label">수거 신청</span></button> | |
| </div> | |
| <div class="ncua-data-grid__action-bar-group ncua-data-grid__action-bar-group--right"> | |
| <div id="ncua-select-box-sort"></div> | |
| <div id="ncua-select-box-pagesize"></div> | |
| </div> | |
| </div> | |
| <div class="ncua-data-grid__table"> | |
| <div class="ncua-table-wrapper" style="--ncua-table-header-height:40px;--ncua-table-default-min-width:1140px"> | |
| <div class="ncua-table ncua-table--in-data-grid ncua-table--horizontal ncua-table--hoverable ncua-table--selectable ncua-border-radius-none"> | |
| <div class="ncua-table__h-scroll-container"> | |
| <div class="ncua-table__h-scroll-inner" style="--ncua-table-min-width:1500px"> | |
| <table class="ncua-table__table" role="table"> | |
| <colgroup> | |
| <col style="width:44px"/> | |
| <col style="width:56px"/> | |
| <col style="width:110px"/> | |
| <col style="width:170px"/> | |
| <col style="width:110px"/> | |
| <col style="width:80px"/><col/> | |
| <col style="width:150px"/> | |
| <col style="width:130px"/> | |
| <col style="width:80px"/> | |
| <col style="width:130px"/> | |
| <col style="width:110px"/> | |
| <col style="width:90px"/> | |
| </colgroup> | |
| <thead class="ncua-table__header"> | |
| <tr class="ncua-table__row"> | |
| <th class="ncua-table__header-cell"><label class="ncua-checkbox-field ncua-checkbox-field--sm"><span class="ncua-checkbox-input ncua-checkbox-input--sm ncua-checkbox-field__input"><input type="checkbox"<?= empty($rows) ? ' disabled' : '' ?>/></span></label></th> | |
| <th class="ncua-table__header-cell">번호</th> | |
| <th class="ncua-table__header-cell">주문일시</th> | |
| <th class="ncua-table__header-cell">주문번호</th> | |
| <th class="ncua-table__header-cell">주문자</th> | |
| <th class="ncua-table__header-cell">상품주문번호</th> | |
| <th class="ncua-table__header-cell">주문상품</th> | |
| <th class="ncua-table__header-cell">사은품</th> | |
| <th class="ncua-table__header-cell">처리상태</th> | |
| <th class="ncua-table__header-cell">배송번호</th> | |
| <th class="ncua-table__header-cell">송장번호</th> | |
| <th class="ncua-table__header-cell">공급사</th> | |
| <th class="ncua-table__header-cell">수령자</th> | |
| </tr> | |
| </thead> | |
| <tbody class="ncua-table__body"> | |
| <?php if (empty($rows)) : ?> | |
| <tr class="epr-empty-row"><td class="ncua-table__empty" colspan="13" role="status" aria-live="polite"><div class="ncua-empty-state"><span class="ncua-empty-state__description">검색된 주문이 없습니다.</span></div></td></tr> | |
| <?php else : ?> | |
| <?php foreach ($groups as $gKey => $group) : ?> | |
| <?php foreach ($group['rows'] as $row) : ?> | |
| <tr class="ncua-table__row" data-group="g<?= $gKey ?>" data-sno="<?= (int)$row['sno'] ?>"> | |
| <td class="ncua-table__cell"><label class="ncua-checkbox-field ncua-checkbox-field--sm"><span class="ncua-checkbox-input ncua-checkbox-input--sm ncua-checkbox-field__input"><input type="checkbox" value="<?= (int)$row['sno'] ?>"/></span></label></td> | |
| <td class="ncua-table__cell"><?= $row['rowNo'] ?></td> | |
| <?php if ($row['isFirst']) : ?> | |
| <td class="ncua-table__cell"<?= $row['groupRowCount'] > 1 ? ' rowspan="' . $row['groupRowCount'] . '"' : '' ?>><?= gd_date_format('Y-m-d', $row['regDt']) ?><br><?= gd_date_format('H:i', $row['regDt']) ?></td> | |
| <td class="ncua-table__cell"<?= $row['groupRowCount'] > 1 ? ' rowspan="' . $row['groupRowCount'] . '"' : '' ?>><span class="epr-ordno"><?php if ($row['firstSaleFl'] === 'y') : ?><span class="ncua-badge ncua-badge--pill-outline ncua-badge--blue ncua-badge--xs"><span class="ncua-badge__label">첫주문</span></span><?php endif; ?><a class="epr-link" href="./order_view.php?orderNo=<?= urlencode($row['orderNo']) ?>" target="_blank"><?= htmlspecialchars($row['orderNo']) ?></a></span></td> | |
| <td class="ncua-table__cell"<?= $row['groupRowCount'] > 1 ? ' rowspan="' . $row['groupRowCount'] . '"' : '' ?>><?= $row['orderName'] ?><br>(<?php if ($row['memNo']) : ?><a class="epr-link epr-link--plain js-layer-crm" href="#" data-member-no="<?= (int)$row['memNo'] ?>" title="회원 관리 팝업"><?= $row['memId'] ?></a><?php else : ?>비회원<?php endif; ?>)</td> | |
| <?php endif; ?> | |
| <td class="ncua-table__cell"><a class="epr-link" href="./order_view.php?orderNo=<?= urlencode($row['orderNo']) ?>" target="_blank"><?= (int)$row['sno'] ?></a></td> | |
| <td class="ncua-table__cell epr-cell--goods"> | |
| <div class="epr-goods"> | |
| <a class="epr-goods__thumb" href="<?= URI_HOME ?>goods/goods_view.php?goodsNo=<?= (int)$row['goodsNo'] ?>" target="_blank" title="쇼핑몰 상품 상세 새 탭" aria-label="상품 이미지"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="none" aria-hidden="true"><path fill="currentColor" fill-rule="evenodd" d="M8 6a3 3 0 1 0 0 6 3 3 0 0 0 0-6" clip-rule="evenodd"></path><path fill="currentColor" fill-rule="evenodd" d="M17.241 2H6.76c-.805 0-1.47 0-2.01.044-.563.046-1.08.145-1.565.392a4 4 0 0 0-1.748 1.748c-.247.485-.346 1.002-.392 1.564C1 6.29 1 6.954 1 7.758v8.483c0 .805 0 1.47.044 2.01.046.563.145 1.08.392 1.565a4 4 0 0 0 1.748 1.748c.485.247 1.002.346 1.564.392q.178.015.374.023l.022.002c.214.019.48.019.75.019h12.212c.27 0 .536 0 .75-.019l.022-.002q.195-.01.374-.023c.562-.046 1.079-.145 1.564-.392a4 4 0 0 0 1.748-1.748c.247-.485.346-1.002.392-1.564.044-.541.044-1.206.044-2.01V7.758c0-.805 0-1.47-.044-2.01-.046-.563-.145-1.08-.392-1.565a4 4 0 0 0-1.748-1.748c-.485-.247-1.002-.346-1.564-.392C18.71 2 18.046 2 17.242 2"></path></svg><?php if (!empty($row['goodsImageSrc'])) : ?><img src="<?= $row['goodsImageSrc'] ?>" alt="<?= htmlspecialchars($row['goodsNm']) ?>"/><?php endif; ?></a> | |
| <span class="epr-goods__info"> | |
| <a class="epr-goods__name epr-goods__name--link js-goods-modify" href="#" data-goods-no="<?= (int)$row['goodsNo'] ?>" title="상품 수정 팝업"><?= htmlspecialchars($row['goodsNm']) ?></a> | |
| <?php if ($row['optionText'] !== '') : ?><span class="epr-goods__option"><?= htmlspecialchars($row['optionText']) ?></span><?php endif; ?> | |
| </span> | |
| </div> | |
| </td> | |
| <?php if ($row['isFirst']) : ?> | |
| <td class="ncua-table__cell"<?= $row['groupRowCount'] > 1 ? ' rowspan="' . $row['groupRowCount'] . '"' : '' ?>><?= empty($row['giftLines']) ? '-' : implode('<br>', array_map('htmlspecialchars', $row['giftLines'])) ?></td> | |
| <?php endif; ?> | |
| <td class="ncua-table__cell">반품접수<?php if ($row['progressLabel'] !== null) : ?><br>(<?= $row['progressLabel'] ?>)<?php endif; ?></td> | |
| <td class="ncua-table__cell"><?= $row['deliverySno'] ? (int)$row['deliverySno'] : '' ?></td> | |
| <td class="ncua-table__cell"><?= htmlspecialchars($row['invoiceNo']) ?></td> | |
| <?php if ($row['scmRowspan'] > 0) : ?> | |
| <td class="ncua-table__cell"<?= $row['scmRowspan'] > 1 ? ' rowspan="' . $row['scmRowspan'] . '"' : '' ?>><?= htmlspecialchars($row['companyNm']) ?></td> | |
| <?php endif; ?> | |
| <?php if ($row['isFirst']) : ?> | |
| <td class="ncua-table__cell"<?= $row['groupRowCount'] > 1 ? ' rowspan="' . $row['groupRowCount'] . '"' : '' ?>><?= $row['receiverName'] ?></td> | |
| <?php endif; ?> | |
| </tr> | |
| <?php endforeach; ?> | |
| <?php endforeach; ?> | |
| <?php endif; ?> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="ncua-table__h-scrollbar" aria-hidden="true"><div class="ncua-table__h-scrollbar-thumb"></div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="ncua-data-grid__action-bar ncua-data-grid__action-bar--bottom ncua-data-grid__action-bar--space-between"> | |
| <?php if (!empty($rows)) : ?> | |
| <div class="ncua-data-grid__action-bar-group"> | |
| <span class="epr-leadin"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.667" d="M10 3 4.5 8.5 2 6"></path></svg>선택한 주문을</span> | |
| <button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray js-epr-request"><span class="ncua-btn__label">수거 신청</span></button> | |
| </div> | |
| <?php endif; ?> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="ncua-data-grid__pagination"> | |
| <div class="ncua-pagination ncua-pagination--pc" role="navigation" aria-label="pagination"> | |
| <div class="pagination"><?= $pagination ?></div> | |
| <p class="ncua-pagination__page-info"><em class="ncua-pagination__current-num"><?= $page ?></em> / <?= $totalPages ?></p> | |
| </div> | |
| </div> | |
| </div> | |
변경 전epost_return_request_search.php
변경 후epost_return_request_search.php
| <div class="epr-filter"> | |
| <div class="ncua-table-wrapper" style="--ncua-table-header-height:40px;--ncua-table-default-min-width:1140px"> | |
| <div class="ncua-table ncua-table--vertical"> | |
| <table class="ncua-table__table" role="table"> | |
| <tbody class="ncua-table__body"> | |
| <tr class="ncua-table__row"> | |
| <th scope="row" class="ncua-table__cell"><div>공급사 구분</div></th> | |
| <td class="ncua-table__cell"> | |
| <div class="epr-search__radio-group"> | |
| <label class="ncua-radio-field ncua-radio-field--xs has-text"> | |
| <span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input"><input type="radio" name="epr-supplier" value="all"/></span> | |
| <span><span class="ncua-radio-field__text">전체</span></span> | |
| </label> | |
| <label class="ncua-radio-field ncua-radio-field--xs has-text"> | |
| <span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input"><input type="radio" name="epr-supplier" value="0"/></span> | |
| <span><span class="ncua-radio-field__text">본사</span></span> | |
| </label> | |
| <label class="ncua-radio-field ncua-radio-field--xs has-text"> | |
| <span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input"><input type="radio" name="epr-supplier" value="1"/></span> | |
| <span><span class="ncua-radio-field__text">공급사</span></span> | |
| </label> | |
| <div class="epr-search__supplier" id="epr-supplier-combo-wrap" hidden><div id="ncua-combo-box-supplier"></div></div> | |
| </div> | |
| <div id="epr-supplier-tags" class="ncua-combobox__tags"></div> | |
| </td> | |
| </tr> | |
| <tr class="ncua-table__row"> | |
| <th scope="row" class="ncua-table__cell"><div>검색어</div></th> | |
| <td class="ncua-table__cell"> | |
| <div class="epr-search__row"> | |
| <div id="ncua-select-box-searchkey"></div> | |
| <div id="epr-match-wrap" style="display:none"><div id="ncua-select-box-match"></div></div> | |
| <div class="epr-search__input"> | |
| <div class="ncua-input ncua-input--xs full-width"> | |
| <div class="ncua-input__content-wrap"> | |
| <div class="ncua-input__content"> | |
| <div class="ncua-input__field ncua-input__field--xs"><input type="text" name="keyword" value="<?= $searchValue('keyword') ?>" placeholder="검색어 전체를 정확히 입력하세요."/></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </td> | |
| </tr> | |
| <tr class="ncua-table__row"> | |
| <th scope="row" class="ncua-table__cell"><div>기간검색</div></th> | |
| <td class="ncua-table__cell"> | |
| <div class="epr-search__row"> | |
| <div id="ncua-select-box-datekey"></div> | |
| <div id="ncua-range-date-picker"></div> | |
| </div> | |
| </td> | |
| </tr> | |
| <tr class="ncua-table__row"> | |
| <th scope="row" class="ncua-table__cell"><div>주문상태</div></th> | |
| <td class="ncua-table__cell"> | |
| <div class="epr-search__radio-group" id="epr-ordstatus"> | |
| <label class="ncua-checkbox-field ncua-checkbox-field--xs has-text"> | |
| <span class="ncua-checkbox-input ncua-checkbox-input--xs ncua-checkbox-field__input"><input type="checkbox" value="all"<?= empty($collectionStatuses) ? ' checked' : '' ?>/></span> | |
| <span><span class="ncua-checkbox-field__text">전체</span></span> | |
| </label> | |
| <?php foreach ($collectionStatusFields as $statusValue => $statusLabel) : ?> | |
| <label class="ncua-checkbox-field ncua-checkbox-field--xs has-text"> | |
| <span class="ncua-checkbox-input ncua-checkbox-input--xs ncua-checkbox-field__input"><input type="checkbox" value="<?= $statusValue ?>"<?= in_array($statusValue, $collectionStatuses, true) ? ' checked' : '' ?>/></span> | |
| <span><span class="ncua-checkbox-field__text"><?= $statusLabel ?></span></span> | |
| </label> | |
| <?php endforeach; ?> | |
| </div> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="epr-searchbar"> | |
| <button type="button" class="ncua-btn ncua-btn--xs has-underline ncua-btn--text" id="epr-btn-reset"><span class="ncua-btn__label">초기화</span></button> | |
| <button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary" id="epr-btn-search"><span class="ncua-btn__label">검색</span></button> | |
| </div> | |
| </div> | |
변경 전epost_return_request.php
변경 후epost_return_request.php
| <?php | |
| $searchValue = static fn (string $key, string $default = '') => htmlspecialchars((string)($search[$key] ?? $default), ENT_QUOTES); | |
| ?> | |
| <link rel="stylesheet" href="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/css/order/epost-return-request.css') ?>"> | |
| <header class="page-header ncua-page-header js-affix"> | |
| <h3><?= end($naviMenu->location); ?></h3> | |
| </header> | |
| <article class="ncua-content epr-page"> | |
| <form id="frmEprSearch" method="get" action="./epost_return_request.php"> | |
| <input type="hidden" name="searchKey" value="<?= $searchValue('searchKey', 'o.orderNo') ?>"> | |
| <input type="hidden" name="matchType" value="<?= $searchValue('matchType', 'equalSearch') ?>"> | |
| <input type="hidden" name="scmFl" value="<?= $searchValue('scmFl', 'all') ?>"> | |
| <input type="hidden" name="treatDateFl" value="<?= $searchValue('treatDateFl', 'oh.regDt.b') ?>"> | |
| <input type="hidden" name="treatDateStart" value="<?= $searchValue('treatDateStart') ?>"> | |
| <input type="hidden" name="treatDateEnd" value="<?= $searchValue('treatDateEnd') ?>"> | |
| <input type="hidden" name="sort" value="<?= $searchValue('sort', 'o.regDt desc') ?>"> | |
| <input type="hidden" name="pageSize" value="<?= (int)$pageSize ?>"> | |
| <input type="hidden" name="page" value="1"> | |
| <span id="epr-scmnos-slot"></span> | |
| <span id="epr-status-slot"></span> | |
| <section class="ncua-block-container"> | |
| <header class="ncua-block-header"> | |
| <div class="ncua-block-header__row"> | |
| <div class="ncua-block-header__title-area"> | |
| <span class="ncua-block-header__title">우체국택배 수거신청</span> | |
| </div> | |
| <div class="ncua-block-header__action-area"></div> | |
| </div> | |
| </header> | |
| <div class="ncua-block-container__body"> | |
| <?php include $epostReturnSearch; ?> | |
| <div id="epr-result"> | |
| <?php include $epostReturnResult; ?> | |
| </div> | |
| </div> | |
| </section> | |
| </form> | |
| </article> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function () { | |
| var EPR = { | |
| hasWriteAuth: <?= $hasWriteAuth ? 'true' : 'false' ?>, | |
| search: <?= json_encode([ | |
| 'searchKey' => (string)($search['searchKey'] ?? 'o.orderNo'), | |
| 'matchType' => (string)($search['matchType'] ?? 'equalSearch'), | |
| 'scmFl' => (string)($search['scmFl'] ?? 'all'), | |
| 'scmNos' => $scmNos, | |
| 'treatDateFl' => (string)($search['treatDateFl'] ?? 'oh.regDt.b'), | |
| 'treatDateStart' => (string)($search['treatDateStart'] ?? ''), | |
| 'treatDateEnd' => (string)($search['treatDateEnd'] ?? ''), | |
| 'sort' => (string)($search['sort'] ?? 'o.regDt desc'), | |
| 'pageSize' => (string)$pageSize, | |
| ], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) ?>, | |
| suppliers: <?= json_encode(array_map(static fn ($scm) => ['id' => (string)$scm['scmNo'], 'label' => $scm['companyNm']], $suppliers), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) ?> | |
| }; | |
| var form = document.querySelector('#frmEprSearch'); | |
| function setHidden(name, value) { form.querySelector('input[name="' + name + '"]').value = value; } | |
| // ── 검색어 항목 + 매칭 (개인정보 항목만 매칭 select 노출) | |
| var FIXED_EXACT = ['o.orderNo', 'og.goodsNo', 'og.goodsCd', 'og.goodsModelNo', 'og.makerNm', 'sm.companyNm']; | |
| var FIXED_PARTIAL = ['og.invoiceNo', 'og.goodsNm']; | |
| var PH_EXACT = '검색어 전체를 정확히 입력하세요.'; | |
| var PH_PARTIAL = '검색어에 포함된 내용을 입력하세요.'; | |
| var matchWrap = document.querySelector('#epr-match-wrap'); | |
| var keywordInput = form.querySelector('input[name="keyword"]'); | |
| function matchType() { | |
| if (FIXED_EXACT.indexOf(EPR.search.searchKey) >= 0) return 'equalSearch'; | |
| if (FIXED_PARTIAL.indexOf(EPR.search.searchKey) >= 0) return 'fullLikeSearch'; | |
| return EPR.search.matchType; | |
| } | |
| function refreshMatchUi() { | |
| var fixed = FIXED_EXACT.indexOf(EPR.search.searchKey) >= 0 || FIXED_PARTIAL.indexOf(EPR.search.searchKey) >= 0; | |
| matchWrap.style.display = fixed ? 'none' : ''; | |
| keywordInput.placeholder = matchType() === 'fullLikeSearch' ? PH_PARTIAL : PH_EXACT; | |
| } | |
| new ncua.SelectBox(document.querySelector('#ncua-select-box-searchkey'), { | |
| size: 'xs', value: EPR.search.searchKey, | |
| onChange: function (v) { EPR.search.searchKey = v; refreshMatchUi(); }, | |
| options: <?= json_encode($searchKeyOptions, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) ?> | |
| }); | |
| new ncua.SelectBox(document.querySelector('#ncua-select-box-match'), { | |
| size: 'xs', value: EPR.search.matchType === 'fullLikeSearch' ? 'fullLikeSearch' : 'equalSearch', | |
| options: <?= json_encode($matchTypeOptions, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) ?>, | |
| onChange: function (v) { EPR.search.matchType = v; refreshMatchUi(); } | |
| }); | |
| refreshMatchUi(); | |
| // ── 공급사 라디오 + 콤보박스 + 칩 (칩이 선택 목록의 정본, 해제는 칩 X 로만) | |
| var supplierSel = []; | |
| EPR.search.scmNos.forEach(function (no) { | |
| var found = EPR.suppliers.filter(function (s) { return s.id === String(no); })[0]; | |
| if (found) supplierSel.push({ id: found.id, label: found.label }); | |
| }); | |
| var radios = Array.prototype.slice.call(document.querySelectorAll('input[name="epr-supplier"]')); | |
| radios.forEach(function (r) { r.checked = r.value === EPR.search.scmFl; }); | |
| if (!radios.some(function (r) { return r.checked; })) radios[0].checked = true; | |
| var comboWrap = document.querySelector('#epr-supplier-combo-wrap'); | |
| var tagBox = document.querySelector('#epr-supplier-tags'); | |
| var supplierClear = function () {}; | |
| (function () { | |
| var host = document.querySelector('#ncua-combo-box-supplier'); | |
| if (!host) return; | |
| var combo = new ncua.ComboBox(host, { | |
| size: 'xs', multiple: true, showFooterButtons: true, placeholder: '공급사 선택', | |
| options: EPR.suppliers, | |
| onComplete: function (ids) { | |
| var list = (Array.isArray(ids) ? ids : (ids ? [ids] : [])).map(function (id) { | |
| return EPR.suppliers.filter(function (s) { return s.id === id; })[0]; | |
| }).filter(Boolean); | |
| if (!list.length) return; | |
| var fresh = list.filter(function (v) { return !supplierSel.some(function (x) { return x.id === v.id; }); }); | |
| if (!fresh.length) { openAlert('동일한 공급사가 이미 존재합니다.', '', 'close'); return; } | |
| fresh.forEach(function (v) { supplierSel.push({ id: v.id, label: v.label }); }); | |
| renderTags(); | |
| if (fresh.length < list.length) { | |
| ncua.Notification.success('선택한 ' + list.length + '개의 공급사 중 ' + fresh.length + '개의 공급사가 추가되었습니다.', '', { autoClose: 5000 }); | |
| } | |
| } | |
| }); | |
| supplierClear = function () { | |
| supplierSel = []; | |
| if (combo.setValues) combo.setValues([]); | |
| renderTags(); | |
| }; | |
| })(); | |
| function syncSupplierVisibility() { | |
| var on = (radios.filter(function (r) { return r.checked; })[0] || {}).value === '1'; | |
| comboWrap.hidden = !on; | |
| tagBox.hidden = !on; | |
| } | |
| function renderTags() { | |
| tagBox.innerHTML = ''; | |
| supplierSel.forEach(function (v) { | |
| var tag = new ncua.Tag({ | |
| text: v.label, size: 'sm', close: true, | |
| onButtonClick: function () { | |
| supplierSel = supplierSel.filter(function (x) { return x.id !== v.id; }); | |
| renderTags(); | |
| } | |
| }); | |
| tagBox.appendChild(tag.getElement()); | |
| }); | |
| syncSupplierVisibility(); | |
| } | |
| radios.forEach(function (r) { r.addEventListener('change', syncSupplierVisibility); }); | |
| renderTags(); | |
| // ── 기간검색 | |
| new ncua.SelectBox(document.querySelector('#ncua-select-box-datekey'), { | |
| size: 'xs', value: EPR.search.treatDateFl, | |
| onChange: function (v) { EPR.search.treatDateFl = v; }, | |
| options: <?= json_encode($treatDateOptions, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) ?> | |
| }); | |
| var datePickerHost = document.querySelector('#ncua-range-date-picker'); | |
| // 재조회 때마다 다시 그려지므로, 지난 검색 범위와 실제로 일치하는 버튼을 매번 다시 찾아 표시함 | |
| // (버튼 하나에 isCurrent 고정하면 다른 기간을 선택해도 재조회 후 그 버튼으로 되돌아가 보임) | |
| function fmtYmd(d) { | |
| var pad = function (n) { return (n < 10 ? '0' : '') + n; }; | |
| return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()); | |
| } | |
| function isPeriodCurrent(period) { | |
| // 첫 진입 기본 7일은 서버가 채워서 내려줌 — 여기는 실제 검색 범위와 일치하는 버튼만 표시함 | |
| if (!EPR.search.treatDateStart || !EPR.search.treatDateEnd) return false; | |
| var today = new Date(); | |
| today.setHours(0, 0, 0, 0); | |
| if (EPR.search.treatDateEnd !== fmtYmd(today)) return false; | |
| var start = new Date(today); | |
| start.setDate(start.getDate() - period); | |
| return EPR.search.treatDateStart === fmtYmd(start); | |
| } | |
| var datePicker = new ncua.DatePicker(datePickerHost, { | |
| size: 'xs', | |
| // mode 'single' 명시 — 기본 'range'로 두면 flatpickr가 선택 진행 중 상태로 남아 커서를 따라 헛 프리뷰 밴드가 칠해짐 | |
| datePickerOptions: [ | |
| { element: 'start-date', placeholder: 'YYYY-MM-DD', options: { mode: 'single', static: true, dateFormat: 'Y-m-d', clickOpens: true, allowInvalidPreload: true, locale: 'ko', defaultDate: EPR.search.treatDateStart || undefined } }, | |
| { element: 'end-date', placeholder: 'YYYY-MM-DD', options: { mode: 'single', static: true, dateFormat: 'Y-m-d', clickOpens: true, allowInvalidPreload: true, locale: 'ko', defaultDate: EPR.search.treatDateEnd || undefined } } | |
| ], | |
| buttons: [ | |
| { text: '오늘', period: 0, unit: 'days', isCurrent: isPeriodCurrent(0) }, | |
| { text: '7일', period: 6, unit: 'days', isCurrent: isPeriodCurrent(6) }, | |
| { text: '15일', period: 14, unit: 'days', isCurrent: isPeriodCurrent(14) }, | |
| { text: '1개월', period: 29, unit: 'days', isCurrent: isPeriodCurrent(29) }, | |
| { text: '3개월', period: 89, unit: 'days', isCurrent: isPeriodCurrent(89) }, | |
| { text: '1년', period: 364, unit: 'days', isCurrent: isPeriodCurrent(364) } | |
| ] | |
| }); | |
| // 지난 검색 기간 복원 — 버튼과 무관한 수동 기간은 defaultDate 가 입력창에 반영되지 않아 직접 채움 | |
| (function () { | |
| var dateInputs = datePickerHost.querySelectorAll('input[type="text"]'); | |
| if (dateInputs[0] && EPR.search.treatDateStart) dateInputs[0].value = EPR.search.treatDateStart; | |
| if (dateInputs[1] && EPR.search.treatDateEnd) dateInputs[1].value = EPR.search.treatDateEnd; | |
| })(); | |
| // ── 주문상태 전체 ↔ 개별 토글 | |
| (function () { | |
| var box = document.querySelector('#epr-ordstatus'); | |
| var all = box.querySelector('input[value="all"]'); | |
| var items = Array.prototype.slice.call(box.querySelectorAll('input:not([value="all"])')); | |
| all.addEventListener('change', function () { | |
| if (all.checked) { items.forEach(function (c) { c.checked = false; }); } | |
| else if (!items.some(function (c) { return c.checked; })) { all.checked = true; } | |
| }); | |
| items.forEach(function (c) { | |
| c.addEventListener('change', function () { | |
| if (c.checked) { all.checked = false; } | |
| if (!items.some(function (x) { return x.checked; })) { all.checked = true; } | |
| }); | |
| }); | |
| })(); | |
| // ── 검색 실행 — 서버 재조회 (판정·필터는 전부 서버) | |
| function readDates() { | |
| // datePickerHost.querySelectorAll('input')는 static 캘린더 내부의 연도 이동 input(type=number)까지 잡힘 — type=text만 골라 읽음. | |
| // datePicker.getDates()는 defaultDate로 채워진 값(재조회 시 서버가 되돌려준 이전 검색값)은 추적 못 해 재조회마다 비어버려서 쓰지 않음 | |
| var textInputs = datePickerHost.querySelectorAll('input[type="text"]'); | |
| EPR.search.treatDateStart = textInputs[0] ? textInputs[0].value : ''; | |
| EPR.search.treatDateEnd = textInputs[1] ? textInputs[1].value : ''; | |
| } | |
| function submitSearch(page) { | |
| readDates(); | |
| var checkedRadio = radios.filter(function (r) { return r.checked; })[0]; | |
| setHidden('searchKey', EPR.search.searchKey); | |
| setHidden('matchType', matchType()); | |
| setHidden('scmFl', checkedRadio ? checkedRadio.value : 'all'); | |
| setHidden('treatDateFl', EPR.search.treatDateFl); | |
| setHidden('treatDateStart', EPR.search.treatDateStart); | |
| setHidden('treatDateEnd', EPR.search.treatDateEnd); | |
| setHidden('sort', EPR.search.sort); | |
| setHidden('pageSize', EPR.search.pageSize); | |
| setHidden('page', page); | |
| var scmSlot = document.querySelector('#epr-scmnos-slot'); | |
| scmSlot.innerHTML = ''; | |
| if ((checkedRadio || {}).value === '1') { | |
| supplierSel.forEach(function (v) { | |
| var input = document.createElement('input'); | |
| input.type = 'hidden'; input.name = 'scmNos[]'; input.value = v.id; | |
| scmSlot.appendChild(input); | |
| }); | |
| } | |
| var statusSlot = document.querySelector('#epr-status-slot'); | |
| statusSlot.innerHTML = ''; | |
| Array.prototype.slice.call(document.querySelectorAll('#epr-ordstatus input:not([value="all"])')).forEach(function (c) { | |
| if (!c.checked) return; | |
| var input = document.createElement('input'); | |
| input.type = 'hidden'; input.name = 'collectionStatuses[]'; input.value = c.value; | |
| statusSlot.appendChild(input); | |
| }); | |
| form.submit(); | |
| } | |
| // ── 수거 신청 처리 후 재조회 — 스크롤 위치를 유지해야 해서 검색과 달리 전체 이동 없이 결과 영역만 fetch | |
| function refreshResult() { | |
| var params = new URLSearchParams(new FormData(form)); | |
| fetch('./epost_return_request.php?' + params.toString(), { headers: { 'X-Requested-With': 'XMLHttpRequest' } }) | |
| .then(function (res) { | |
| if (!res.ok) throw new Error('HTTP ' + res.status); | |
| return res.text(); | |
| }) | |
| .then(function (html) { | |
| document.querySelector('#epr-result').innerHTML = html; | |
| bindResultEvents(); | |
| }) | |
| .catch(function () { | |
| openAlert('조회에 실패하였습니다.', '잠시 후 다시 시도해 주세요.', 'close'); | |
| }); | |
| } | |
| document.querySelector('#epr-btn-search').addEventListener('click', function () { submitSearch(1); }); | |
| document.querySelector('#epr-btn-reset').addEventListener('click', function () { | |
| supplierClear(); | |
| location.href = './epost_return_request.php'; | |
| }); | |
| // 공용 Page 컴포넌트가 부르는 페이지 이동 — PAGELINK 치환값(page=N&쿼리)에서 번호만 취해 폼 상태 유지 재조회 | |
| window.goEprPage = function (link) { | |
| var matched = /(?:^|&)page=(\d+)/.exec(String(link)); | |
| submitSearch(matched ? parseInt(matched[1], 10) : 1); | |
| }; | |
| // ── 그리드: hover 그룹 강조 · 선택 강조 · 전체선택 · [수거 신청] 버튼 — #epr-result 재조회 시마다 다시 바인딩 | |
| var tableBody, headCheck, rowChecks = []; | |
| function syncChecks() { | |
| var on = rowChecks.filter(function (c) { return c.checked; }).length; | |
| if (headCheck) { | |
| headCheck.checked = rowChecks.length > 0 && on === rowChecks.length; | |
| headCheck.indeterminate = on > 0 && on < rowChecks.length; | |
| } | |
| var groupOn = {}; | |
| rowChecks.forEach(function (c) { | |
| var tr = c.closest('tr'); | |
| tr.classList.toggle('ncua-table__row--selected', c.checked); | |
| if (!(tr.dataset.group in groupOn)) groupOn[tr.dataset.group] = false; | |
| if (c.checked) groupOn[tr.dataset.group] = true; | |
| }); | |
| Object.keys(groupOn).forEach(function (g) { | |
| tableBody.querySelectorAll('tr[data-group="' + g + '"] > td[rowspan]').forEach(function (td) { | |
| td.classList.toggle('epr-cell--group-selected', groupOn[g]); | |
| }); | |
| }); | |
| } | |
| function selectedSnos() { | |
| return rowChecks.filter(function (c) { return c.checked; }).map(function (c) { return c.value; }); | |
| } | |
| function bindResultEvents() { | |
| tableBody = document.querySelector('.ncua-data-grid__table .ncua-table__body'); | |
| if (tableBody) { | |
| var clearAll = function () { | |
| tableBody.querySelectorAll('td.epr-cell-hover').forEach(function (td) { td.classList.remove('epr-cell-hover'); }); | |
| }; | |
| var mark = function (tr) { | |
| clearAll(); | |
| tr.querySelectorAll(':scope > td').forEach(function (td) { td.classList.add('epr-cell-hover'); }); | |
| tableBody.querySelectorAll('tr[data-group="' + tr.dataset.group + '"]').forEach(function (row) { | |
| row.querySelectorAll(':scope > td[rowspan]').forEach(function (td) { td.classList.add('epr-cell-hover'); }); | |
| }); | |
| }; | |
| tableBody.addEventListener('mouseover', function (e) { | |
| var tr = e.target.closest('tr[data-group]'); | |
| if (tr) mark(tr); | |
| }); | |
| tableBody.addEventListener('mouseleave', clearAll); | |
| } | |
| headCheck = document.querySelector('.ncua-data-grid__table thead input[type="checkbox"]'); | |
| rowChecks = Array.prototype.slice.call(document.querySelectorAll('.ncua-data-grid__table tbody input[type="checkbox"]')); | |
| if (headCheck) { | |
| headCheck.addEventListener('change', function () { | |
| rowChecks.forEach(function (c) { c.checked = headCheck.checked; }); | |
| syncChecks(); | |
| }); | |
| } | |
| rowChecks.forEach(function (c) { c.addEventListener('change', syncChecks); }); | |
| Array.prototype.slice.call(document.querySelectorAll('.js-epr-request')).forEach(function (btn) { | |
| btn.addEventListener('click', function () { | |
| if (!EPR.hasWriteAuth) { | |
| openAlert('우체국택배 수거신청의 쓰기 권한이 없습니다.', '권한은 대표운영자에게 문의하시기 바랍니다.', 'close'); | |
| return; | |
| } | |
| var snos = selectedSnos(); | |
| if (!snos.length) { | |
| openAlert('수거 신청할 주문을 선택해 주세요.', '', 'close'); | |
| return; | |
| } | |
| NCDSConfirm({ message: '선택한 ' + snos.length + '건을 수거 신청하시겠습니까?' }).then(function (ok) { | |
| if (ok) requestPickup(); | |
| }); | |
| }); | |
| }); | |
| // 정렬·N개 보기 — 결과 영역과 함께 매번 새로 그려지므로 재조회 때마다 다시 초기화 | |
| var sortBox = document.querySelector('#ncua-select-box-sort'); | |
| if (sortBox) { | |
| new ncua.SelectBox(sortBox, { | |
| size: 'xs', value: EPR.search.sort, | |
| onChange: function (v) { EPR.search.sort = v; submitSearch(1); }, | |
| options: <?= json_encode($sortOptions, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) ?> | |
| }); | |
| } | |
| var pageSizeBox = document.querySelector('#ncua-select-box-pagesize'); | |
| if (pageSizeBox) { | |
| new ncua.SelectBox(pageSizeBox, { | |
| size: 'xs', value: EPR.search.pageSize, | |
| onChange: function (v) { EPR.search.pageSize = v; submitSearch(1); }, | |
| options: <?= json_encode($pageSizeOptions, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) ?> | |
| }); | |
| } | |
| } | |
| bindResultEvents(); | |
| // ── 알림 — 공용 NCDSAlert·NCDSConfirm(head.php 전역). [확인] 후 동작은 케이스별로 갈림 | |
| function openAlert(title, subtitle, after) { | |
| NCDSAlert({ | |
| message: title, | |
| subMessage: subtitle || '', | |
| iconType: 'error', | |
| callback: function () { | |
| if (after === 'reload') refreshResult(); | |
| } | |
| }); | |
| } | |
| // ── [수거 신청] — 검사 순서: 쓰기 권한 → 선택 여부 → 확인 다이얼로그 → 서버 검증 (버튼 클릭 바인딩은 bindResultEvents 참고) | |
| var requesting = false; | |
| function requestPickup() { | |
| if (requesting) { | |
| openAlert('수거 신청을 처리하고 있습니다.', '처리가 완료될 때까지 잠시 기다려 주세요.', 'close'); | |
| return; | |
| } | |
| requesting = true; | |
| var submittedSnos = selectedSnos(); | |
| var params = new URLSearchParams(); | |
| params.append('mode', 'pickupRequest'); | |
| submittedSnos.forEach(function (sno) { params.append('orderGoodsSnos[]', sno); }); | |
| fetch('./epost_return_request_ps.php', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' }, | |
| body: params.toString() | |
| }).then(function (res) { return res.json(); }).then(function (data) { | |
| requesting = false; | |
| if (data.code === 200) { | |
| // 선점 제외(다른 요청이 처리 중)는 실패가 아니라 별도 안내로 처리함 — 실패로 묶으면 진행 중인 건에 재신청을 유도하게 됨 | |
| var success = data.successCount || 0; | |
| var failure = data.failureCount || 0; | |
| var excluded = data.excludedCount || 0; | |
| if (failure > 0 && success > 0) { | |
| openAlert('성공 ' + success + '건 / 실패 ' + failure + '건 처리되었습니다.', '실패한 주문을 선택해 다시 [수거 신청]해 주세요.', 'reload'); | |
| } else if (failure > 0) { | |
| openAlert('수거 신청에 실패하였습니다.', '잠시 후 다시 시도해 주세요.', 'reload'); | |
| } else if (excluded > 0) { | |
| openAlert('수거 신청을 처리하고 있습니다.', '처리가 완료될 때까지 잠시 기다려 주세요.', 'reload'); | |
| } else if (success > 0) { | |
| ncua.Notification.success('수거 신청되었습니다.', '반품 리스트에서 수거 신청된 주문을 확인해 주세요.', { autoClose: 5000 }); | |
| refreshResult(); | |
| } else { | |
| // 선점도 실패도 성공도 없음 — 응답에 배정·실패가 모두 비어 돌아온 경우라 성공으로 안내하면 안 됨 | |
| openAlert('수거 신청에 실패하였습니다.', '잠시 후 다시 시도해 주세요.', 'reload'); | |
| } | |
| return; | |
| } | |
| var after = (data.reason === 'NOT_ELIGIBLE' || data.reason === 'DUPLICATED' || data.reason === 'ERROR') ? 'reload' : 'close'; | |
| openAlert(data.message || '수거 신청에 실패하였습니다.', data.subMessage || '', after); | |
| }).catch(function () { | |
| requesting = false; | |
| openAlert('수거 신청에 실패하였습니다.', '잠시 후 다시 시도해 주세요.', 'reload'); | |
| }); | |
| } | |
| // ── 상품 수정 팝업 (기존 어드민 전례 창) · 회원 관리 팝업(.js-layer-crm)은 common.js 전역 바인딩을 그대로 씀 | |
| document.addEventListener('click', function (e) { | |
| var goods = e.target.closest('.js-goods-modify'); | |
| if (goods) { | |
| e.preventDefault(); | |
| window.open('../goods/goods_register.php?goodsNo=' + goods.dataset.goodsNo, 'goodsModify', 'width=1400,height=900,scrollbars=yes'); | |
| } | |
| }); | |
| }); | |
| </script> | |
| <script type="text/javascript"> | |
| const code = '260921002'; | |
| if (window.GodoCosGuide) { | |
| window.GodoCosGuide.init({ | |
| apiUrl: <?= json_encode($cosGuideApiUrl) ?>, | |
| guideCode: code, | |
| }); | |
| } | |
| </script> | |
변경 전inc_claim_view_exchange.php
| <?= $val['handleRegDt']; ?> | |
| </td> | |
| <?php } ?> | |
| <td class="center"><?= $val['orderStatusStr']; ?></td> | |
| <td class="border-left text-center"><?= $val['companyNm']; ?></td> | |
| <td> | |
| <?php if ($val['goodsType'] === 'addGoods') { ?> | |
변경 후inc_claim_view_exchange.php
| <?= $val['handleRegDt']; ?> | |
| </td> | |
| <?php } ?> | |
| <td class="center"><?= $val['orderStatusStr']; ?><?= $val['returnStatusStr']; ?></td> | |
| <td class="border-left text-center"><?= $val['companyNm']; ?></td> | |
| <td> | |
| <?php if ($val['goodsType'] === 'addGoods') { ?> | |
변경 전inc_claim_view.php
| <?php if ($val['beforeStatusStr'] && $statusMode == 'r') { ?> | |
| <div class="text-muted" title="이전 상품별 주문 상태"><?= $val['beforeStatusStr']; ?> ></div> | |
| <?php } ?> | |
| <p><?= $val['orderStatusStr']; ?></p> | |
| </td> | |
| <?php if ($rowScm == 0) { ?> | |
| <td <?=$orderScmRowSpan?> class="border-left text-center"><?= $val['companyNm']; ?></td> | |
변경 후inc_claim_view.php
| <?php if ($val['beforeStatusStr'] && $statusMode == 'r') { ?> | |
| <div class="text-muted" title="이전 상품별 주문 상태"><?= $val['beforeStatusStr']; ?> ></div> | |
| <?php } ?> | |
| <p><?= $val['orderStatusStr']; ?><?= $val['returnStatusStr']; ?></p> | |
| </td> | |
| <?php if ($rowScm == 0) { ?> | |
| <td <?=$orderScmRowSpan?> class="border-left text-center"><?= $val['companyNm']; ?></td> | |
변경 전inc_order_view.php
| <p> | |
| <?= $val['orderStatusStr']; ?> | |
| <input type="button" data-sno="<?= $val['sno']; ?>" data-name="<?= $val['goodsNm']; ?>" value="로그" class="btn btn-sm btn-white js-order-log"/> | |
| </p> | |
| <?php if($val['naverpayStatus']['code'] == 'DelayProductOrder'){ //발송지연?> | |
| <div style="padding-bottom:5px" data-sno="<?=$val['sno']?>" data-info="<?=$val['naverpayStatus']['text']?>" class="js-btn-naverpay-status-detail"> | |
| <input type="button" onclick="delivery_trace('<?= $val['invoiceCompanySno']; ?>', '<?= $val['invoiceNo']; ?>');" value="배송추적" class="btn btn-sm btn-gray mgt5"/> | |
| </div> | |
| <?php } ?> | |
| </td> | |
| <?php } // 처리상태 ?> | |
변경 후inc_order_view.php
| <p> | |
| <?= $val['orderStatusStr']; ?> | |
| <input type="button" data-sno="<?= $val['sno']; ?>" data-name="<?= $val['goodsNm']; ?>" value="로그" class="btn btn-sm btn-white js-order-log"/> | |
| <?= $val['returnStatusStr']; ?> | |
| </p> | |
| <?php if($val['naverpayStatus']['code'] == 'DelayProductOrder'){ //발송지연?> | |
| <div style="padding-bottom:5px" data-sno="<?=$val['sno']?>" data-info="<?=$val['naverpayStatus']['text']?>" class="js-btn-naverpay-status-detail"> | |
| <input type="button" onclick="delivery_trace('<?= $val['invoiceCompanySno']; ?>', '<?= $val['invoiceNo']; ?>');" value="배송추적" class="btn btn-sm btn-gray mgt5"/> | |
| </div> | |
| <?php } ?> | |
| <?php if (!empty($val['returnTraceUrl'])) { ?> | |
| <div> | |
| <input type="button" onclick="return_trace('<?= gd_htmlspecialchars_addslashes($val['returnTraceUrl']); ?>');" value="반송추적" class="btn btn-sm btn-gray mgt5"/> | |
| </div> | |
| <?php } ?> | |
| </td> | |
| <?php } // 처리상태 ?> | |
변경 전layout_order_claim_list.php
| <?php } ?> | |
| <td <?= $orderDeliveryRowSpan ?> class="text-right"><?= gd_currency_display($val['realDeliveryCharge']); ?></td> | |
| <?php } ?> | |
| <td <?= $orderAddGoodsRowSpan ?>><?= $val['orderStatusStr']; ?></td> | |
| </tr> | |
| <?php | |
| if ($val['addGoodsCnt'] > 0) { | |
변경 후layout_order_claim_list.php
| <?php } ?> | |
| <td <?= $orderDeliveryRowSpan ?> class="text-right"><?= gd_currency_display($val['realDeliveryCharge']); ?></td> | |
| <?php } ?> | |
| <td <?= $orderAddGoodsRowSpan ?>><?= $val['orderStatusStr']; ?><?= $val['returnStatusStr']; ?></td> | |
| </tr> | |
| <?php | |
| if ($val['addGoodsCnt'] > 0) { | |
변경 전layout_order_goods_list.php
| <?php if ($currentStatusCode == 'r') { ?> | |
| <div class="text-muted" title="이전 상품별 주문 상태"><?= $val['beforeStatusStr']; ?> ></div> | |
| <?php } ?> | |
| <div title="주문 상품별 주문 상태"><?= $val['orderStatusStr']; ?></div> | |
| </td> | |
| <?php } ?> | |
| <?php } // 처리상태 ?> | |
| </div> | |
| <?php } ?> | |
| <?php } ?> | |
| <?php } ?> | |
| </td> | |
| <?php } // 송장번호 ?> | |
변경 후layout_order_goods_list.php
| <?php if ($currentStatusCode == 'r') { ?> | |
| <div class="text-muted" title="이전 상품별 주문 상태"><?= $val['beforeStatusStr']; ?> ></div> | |
| <?php } ?> | |
| <div title="주문 상품별 주문 상태"><?= $val['orderStatusStr']; ?><?= $val['returnStatusStr']; ?></div> | |
| </td> | |
| <?php } ?> | |
| <?php } // 처리상태 ?> | |
| </div> | |
| <?php } ?> | |
| <?php } ?> | |
| <?php if (!empty($val['returnTraceUrl'])) : ?> | |
| <div class="delivery-trace"> | |
| <input type="button" onclick="return_trace('<?= gd_htmlspecialchars_addslashes($val['returnTraceUrl']); ?>');" value="반송추적" class="btn btn-sm btn-black"> | |
| </div> | |
| <?php endif; ?> | |
| <?php } ?> | |
| </td> | |
| <?php } // 송장번호 ?> | |
변경 전layout_order_view_status_back.php
| <?php if ($val['beforeStatusStr'] && $statusMode == 'r') { ?> | |
| <div class="text-muted" title="이전 상품별 주문 상태"><?= $val['beforeStatusStr']; ?> ></div> | |
| <?php } ?> | |
| <p><?= $val['orderStatusStr']; ?></p> | |
| <?php if ($val['naverpayStatus']['code'] == 'DelayProductOrder') { //발송지연?> | |
| <div style="padding-bottom:5px" data-sno="<?= $val['sno'] ?>" data-info="<?= $val['naverpayStatus']['text'] ?>" class="js-btn-naverpay-status-detail"> | |
| (<?= $val['naverpayStatus']['notice'] ?>) | |
| <input type="button" onclick="delivery_trace('<?= $val['invoiceCompanySno']; ?>', '<?= $val['invoiceNo']; ?>');" value="배송추적" class="btn btn-sm btn-gray mgt5"/> | |
| </div> | |
| <?php } ?> | |
| </td> | |
| <!-- 처리상태 --> | |
| </tr> | |
변경 후layout_order_view_status_back.php
| <?php if ($val['beforeStatusStr'] && $statusMode == 'r') { ?> | |
| <div class="text-muted" title="이전 상품별 주문 상태"><?= $val['beforeStatusStr']; ?> ></div> | |
| <?php } ?> | |
| <p><?= $val['orderStatusStr']; ?><?= $val['returnStatusStr']; ?></p> | |
| <?php if ($val['naverpayStatus']['code'] == 'DelayProductOrder') { //발송지연?> | |
| <div style="padding-bottom:5px" data-sno="<?= $val['sno'] ?>" data-info="<?= $val['naverpayStatus']['text'] ?>" class="js-btn-naverpay-status-detail"> | |
| (<?= $val['naverpayStatus']['notice'] ?>) | |
| <input type="button" onclick="delivery_trace('<?= $val['invoiceCompanySno']; ?>', '<?= $val['invoiceNo']; ?>');" value="배송추적" class="btn btn-sm btn-gray mgt5"/> | |
| </div> | |
| <?php } ?> | |
| <?php if (!empty($val['returnTraceUrl'])) : ?> | |
| <div> | |
| <input type="button" onclick="return_trace('<?= gd_htmlspecialchars_addslashes($val['returnTraceUrl']); ?>');" value="반송추적" class="btn btn-sm btn-gray mgt5"/> | |
| </div> | |
| <?php endif; ?> | |
| </td> | |
| <!-- 처리상태 --> | |
| </tr> | |
변경 전popup_epost_apply_frame.php
변경 후popup_epost_apply_frame.php
| <script src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/aggregator-message-router.js') ?>"></script> | |
| <style> | |
| /* 팝업(720px)이 어드민 공통 body min-width(970px)보다 좁아 오른쪽이 잘리는 것 방지 */ | |
| body { min-width: 0; overflow: hidden; } | |
| </style> | |
| <iframe name="epostApply" src="<?= htmlspecialchars($pageUrl, ENT_QUOTES, 'UTF-8') ?>" frameborder="0" marginwidth="0" marginheight="0" width="100%" style="height: 100vh;"></iframe> | |
| <script> | |
| // 허브가 신청 완료 시 MODAL_CLOSE 전송 — iframe 은 창을 못 닫으므로 여기서 팝업 창을 닫는다 | |
| IframeMessageRouter.init({ | |
| onModalClose: function () { | |
| window.close(); | |
| }, | |
| }); | |
| </script> | |
변경 전post_request.php
| <div class="page-header js-affix"> | |
| <h3><?=end($naviMenu->location); ?></h3> | |
| </div> | |
| <div class="table-title">우체국택배 배송상태 자동 업데이트 </div> | |
| <table class="table table-cols"> | |
| <tbody> | |
| <tr> | |
| <td style="padding: 15px 0;"> | |
| <div class="notice-info" style="margin-top: 0;margin-bottom: 0"> | |
| 우체국택배 배송상태를 쇼핑몰에 2시간마다 자동으로 업데이트 합니다.<br/> | |
| 2시간마다 자동으로 배송상태를 확인하여 배송이 완료된 주문은 ‘배송완료’로 업데이트 됩니다. | |
| </div> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <div class="table-title">우체국택배 배송상태 수동 업데이트 </div> | |
| <table class="table table-cols"> | |
| <tbody> | |
| <tr> | |
| <td style="padding: 15px 0 20px 0;"> | |
| <div class="notice-info" style="margin-top: 0;margin-bottom: 0"> | |
| 우체국택배 배송상태를 쇼핑몰에 수동 업데이트하려면, 아래 버튼을 클릭해 주세요.<br/> | |
| 수동으로 배송상태를 확인하여 배송이 완료된 주문은 ‘배송완료’로 업데이트 됩니다. | |
| </div> | |
| <div style="padding-top: 15px;"><a href="./post_ps.php?mode=manual" class="btn btn-lg btn-black" target="ifrmProcess" style="color:#fff">배송상태 수동 업데이트</a></div> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <div class="table-title ">우체국택배 신청 </div> | |
| <iframe name="requestPostIfrm" src="https://www.nhn-commerce.com/service/godopost/regist.php?shopSno=<?=$shopSno?>&shopHost=<?=$domainUrl?>" frameborder="0" height="830px" width="100%"></iframe> | |
변경 후post_request.php
| <?php | |
| ?> | |
| <link rel="stylesheet" href="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/css/order/epost-service-management.css') ?>"> | |
| <header class="page-header ncua-page-header js-affix"> | |
| <h3><?= end($naviMenu->location); ?></h3> | |
| <div class="ncua-page-header__actions"> | |
| <button type="button" class="ncua-btn ncua-btn--md ncua-btn--primary" id="epr-btn-save"> | |
| <span class="ncua-btn__label">저장</span> | |
| </button> | |
| </div> | |
| </header> | |
| <?php if (!empty($usageBanner)) : ?> | |
| <div class="ncua-full-width-notification ncua-full-width-notification--info" role="alert"> | |
| <div class="ncua-full-width-notification__container"> | |
| <div class="ncua-full-width-notification__content"> | |
| <div class="ncua-full-width-notification__content-wrapper"> | |
| <div class="ncua-info-circle-violet"></div> | |
| <div class="ncua-full-width-notification__text-container"> | |
| <span class="ncua-full-width-notification__title"><?= htmlspecialchars($usageBanner['title']) ?></span> | |
| <?php if ($usageBanner['showApplyLink']) : ?> | |
| <a href="javascript:void(0)" id="epr-btn-apply" class="ncua-full-width-notification__link"> | |
| <span class="ncua-full-width-notification__supporting-text">서비스 신청하기</span> | |
| </a> | |
| <?php else : ?> | |
| <span class="ncua-full-width-notification__supporting-text"><?= htmlspecialchars($usageBanner['body']) ?></span> | |
| <?php endif; ?> | |
| </div> | |
| </div> | |
| <div class="ncua-full-width-notification__actions-container"> | |
| <button type="button" class="ncua-full-width-notification__close-button ncua-close-icon-violet" style="visibility:hidden" aria-label="알림 닫기"></button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <?php endif; ?> | |
| <article class="ncua-content epr-page"> | |
| <section class="ncua-block-container"> | |
| <header class="ncua-block-header"> | |
| <div class="ncua-block-header__row"> | |
| <div class="ncua-block-header__title-area"> | |
| <span class="ncua-block-header__title">우체국택배 관리</span> | |
| </div> | |
| <div class="ncua-block-header__action-area"></div> | |
| </div> | |
| </header> | |
| <div class="ncua-block-container__body"> | |
| <div class="ncua-table-wrapper" style="--ncua-table-header-height:40px;--ncua-table-default-min-width:1140px"> | |
| <div class="ncua-table ncua-table--vertical"> | |
| <table class="ncua-table__table" role="table"> | |
| <tbody class="ncua-table__body"> | |
| <tr class="ncua-table__row"> | |
| <th scope="row" class="ncua-table__cell"> | |
| <div data-tooltip-seq="002">우체국택배 배송상태 수동 업데이트</div> | |
| </th> | |
| <td class="ncua-table__cell"> | |
| <div> | |
| <button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray" id="epr-btn-manual"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2 14s.121.85 3.636 4.364A9 9 0 0 0 20.776 14M2 14v6m0-6h6m14-4s-.121-.85-3.636-4.364A9 9 0 0 0 3.224 10M22 10V4m0 6h-6"></path></svg> | |
| <span class="ncua-btn__label">배송상태 수동 업데이트</span> | |
| </button> | |
| </div> | |
| </td> | |
| </tr> | |
| <tr class="ncua-table__row"> | |
| <th scope="row" class="ncua-table__cell"> | |
| <div data-tooltip-seq="003">우체국택배 수거신청 자동 접수</div> | |
| </th> | |
| <td class="ncua-table__cell"> | |
| <div> | |
| <div class="ncua-switch ncua-switch--xs" id="epr-auto-switch"> | |
| <label class="ncua-switch__option ncua-switch__option--left<?= $autoAcceptSavedEnabled ? ' ncua-switch__option--active' : ' ncua-switch__option--inactive' ?>"> | |
| <input type="radio" name="epr-auto-collect" class="ncua-switch__radio ncua-switch__radio--left" value="y"<?= $autoAcceptSavedEnabled ? ' checked' : '' ?>/> | |
| <span class="ncua-switch__label">사용함</span> | |
| </label> | |
| <label class="ncua-switch__option ncua-switch__option--right<?= $autoAcceptSavedEnabled ? ' ncua-switch__option--inactive' : ' ncua-switch__option--active' ?>"> | |
| <input type="radio" name="epr-auto-collect" class="ncua-switch__radio ncua-switch__radio--right" value="n"<?= $autoAcceptSavedEnabled ? '' : ' checked' ?>/> | |
| <span class="ncua-switch__label">사용안함</span> | |
| </label> | |
| </div> | |
| </div> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| </article> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function () { | |
| var EPR = { | |
| hasWriteAuth: <?= $hasWriteAuth ? 'true' : 'false' ?>, | |
| connected: <?= $epostConnected ? 'true' : 'false' ?>, | |
| savedEnabled: <?= $autoAcceptSavedEnabled ? 'true' : 'false' ?> | |
| }; | |
| // 미신청 배너 [서비스 신청하기] — 허브 신청 페이지를 감싼 어드민 팝업. 신청 결과는 재진입·새로고침 시 진행 상태 API 로 갱신됨(기획 확정) | |
| var applyBtn = document.querySelector('#epr-btn-apply'); | |
| if (applyBtn) { | |
| applyBtn.addEventListener('click', function () { | |
| window.open('./popup_epost_apply.php', 'epostApply', 'width=720,height=800,scrollbars=yes'); | |
| }); | |
| } | |
| // 스위치 활성 시각 동기화 — 전환만으로 저장되지 않음([저장] 필요) | |
| var switchBox = document.querySelector('#epr-auto-switch'); | |
| function currentEnabled() { | |
| return switchBox.querySelector('input:checked').value === 'y'; | |
| } | |
| switchBox.querySelectorAll('input').forEach(function (radio) { | |
| radio.addEventListener('change', function () { | |
| switchBox.querySelectorAll('.ncua-switch__option').forEach(function (opt) { | |
| var on = opt.querySelector('input').checked; | |
| opt.classList.toggle('ncua-switch__option--active', on); | |
| opt.classList.toggle('ncua-switch__option--inactive', !on); | |
| }); | |
| }); | |
| }); | |
| // ── 알림 — 공용 NCDSAlert·NCDSConfirm(head.php 전역) | |
| function openAlert(title, subtitle) { | |
| NCDSAlert({ message: title, subMessage: subtitle || '', iconType: 'error' }); | |
| } | |
| // [배송상태 수동 업데이트] — 쓰기 권한 검증 유지(현행), 0건이어도 동일 문구 Toast | |
| document.querySelector('#epr-btn-manual').addEventListener('click', function () { | |
| if (!EPR.hasWriteAuth) { | |
| openAlert('우체국택배 신청/관리의 쓰기 권한이 없습니다.', '권한은 대표운영자에게 문의하시기 바랍니다.'); | |
| return; | |
| } | |
| fetch('./post_ps.php?mode=manualUpdateJson', { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' } }) | |
| .then(function (res) { return res.json(); }) | |
| .then(function (data) { | |
| if (data.code !== 200) { | |
| openAlert(data.message || '배송상태 수동 업데이트에 실패하였습니다.', data.subMessage || ''); | |
| return; | |
| } | |
| ncua.Notification.success('주문 ' + (data.updatedCount || 0) + '건의 우체국택배 배송상태를 수동 업데이트 하였습니다.', '', { autoClose: 5000 }); | |
| }) | |
| .catch(function () { | |
| openAlert('배송상태 수동 업데이트에 실패하였습니다.', '잠시 후 다시 시도해 주세요.'); | |
| }); | |
| }); | |
| // [저장] — 검사 순서: 쓰기 권한 Alert → 미신청 Alert → (사용안함→사용함이면) Confirm → 저장 Toast | |
| function saveAutoAccept() { | |
| var params = new URLSearchParams(); | |
| params.append('mode', 'saveAutoAccept'); | |
| params.append('autoAcceptFl', currentEnabled() ? 'y' : 'n'); | |
| fetch('./post_ps.php', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' }, | |
| body: params.toString() | |
| }).then(function (res) { return res.json(); }).then(function (data) { | |
| if (data.code === 200) { | |
| EPR.savedEnabled = currentEnabled(); | |
| ncua.Notification.success('저장이 완료되었습니다.', '', { autoClose: 5000 }); | |
| return; | |
| } | |
| openAlert(data.message, data.subMessage || ''); | |
| }).catch(function () { | |
| openAlert('저장에 실패하였습니다.', '잠시 후 다시 시도해 주세요.'); | |
| }); | |
| } | |
| document.querySelector('#epr-btn-save').addEventListener('click', function () { | |
| if (!EPR.hasWriteAuth) { | |
| openAlert('우체국택배 신청/관리의 쓰기 권한이 없습니다.', '권한은 대표운영자에게 문의하시기 바랍니다.'); | |
| return; | |
| } | |
| if (!EPR.connected) { | |
| openAlert('우체국택배 서비스 신청 후 이용 가능합니다.', ''); | |
| return; | |
| } | |
| // Confirm 노출 = 직전 저장값 기준 사용안함 → 사용함 방향만 | |
| if (!EPR.savedEnabled && currentEnabled()) { | |
| NCDSConfirm({ | |
| message: '우체국택배 수거신청 자동 접수를 사용하시겠습니까?', | |
| subMessage: '반품접수와 동시에 수거가 접수되며, 접수된 수거 신청은 관리자에서 취소할 수 없습니다.' | |
| }).then(function (ok) { | |
| if (ok) saveAutoAccept(); // 취소 시 스위치는 사용함 그대로 유지, 저장하지 않음 | |
| }); | |
| return; | |
| } | |
| saveAutoAccept(); | |
| }); | |
| }); | |
| </script> | |
| <script type="text/javascript"> | |
| const code = '260921001'; | |
| if (window.GodoCosGuide) { | |
| window.GodoCosGuide.init({ | |
| apiUrl: <?= json_encode($cosGuideApiUrl) ?>, | |
| guideCode: code, | |
| }); | |
| } | |
| </script> | |
변경 전DBTableField.php
| return $arrField; | |
| } | |
| /** | |
| * [주문] orderHandle 필드 기본값 | |
| * | |
변경 후DBTableField.php
| return $arrField; | |
| } | |
| /** | |
| * [주문] orderReturnInvoice 필드 기본값 | |
| * | |
| * @return array orderReturnInvoice 테이블 필드 정보 | |
| */ | |
| public static function tableOrderReturnInvoice() | |
| { | |
| // @formatter:off | |
| $arrField = [ | |
| ['val' => 'orderGoodsNo', 'typ' => 'i', 'def' => null], // 주문상품번호 (es_orderGoods.sno) | |
| ['val' => 'orderNo', 'typ' => 's', 'def' => null], // 주문번호 | |
| ['val' => 'returnCarrierNo', 'typ' => 'i', 'def' => null], // 반품 수거 택배사 (es_manageDeliveryCompany.sno) | |
| ['val' => 'returnInvoiceNo', 'typ' => 's', 'def' => null], // 반품 수거 송장번호 | |
| ['val' => 'returnInvoiceStatus', 'typ' => 's', 'def' => null], // 반품 수거 상태 (REQUESTED:발행, FAILED:신청실패, 그외:택배사 배송상태) | |
| ['val' => 'returnInvoiceStatusDt', 'typ' => 's', 'def' => null], // 택배사 배송상태 발생일시 | |
| ]; | |
| // @formatter:on | |
| return $arrField; | |
| } | |
| /** | |
| * [주문] orderHandle 필드 기본값 | |
| * | |
변경 전Delivery.php
| throw new Exception(__('송장 번호는 필수 항목입니다.')); | |
| } | |
| // bind 데이타 | |
| $arrBind = []; | |
| $this->db->bind_param_push($arrBind, 'i', $invoiceCompanySno); | |
| $strSQL = 'SELECT companyName, traceUrl FROM ' . DB_MANAGE_DELIVERY_COMPANY . ' WHERE sno = ?'; | |
| $getData = $this->db->query_fetch($strSQL, $arrBind, false); | |
| unset($arrBind); | |
| $traceUrl = str_replace('__INVOICENO__', str_replace('-', '', $invoiceNo), $getData['traceUrl']); | |
| return $traceUrl; | |
| } | |
| /** | |
| * 배송추적시 json 처리 해야할 배송업체인지 체크 | |
| * | |
변경 후Delivery.php
| throw new Exception(__('송장 번호는 필수 항목입니다.')); | |
| } | |
| $getData = $this->getDeliveryTraceUrlTemplate($invoiceCompanySno); | |
| $traceUrl = str_replace('__INVOICENO__', str_replace('-', '', $invoiceNo), $getData['traceUrl']); | |
| return $traceUrl; | |
| } | |
| /** | |
| * 배송추적 URL 템플릿(치환 전) 조회 — 같은 배송 업체를 여러 건 조회하는 호출부가 반복 쿼리를 피하려고 씀 | |
| * | |
| * @param integer $invoiceCompanySno 배송 업체 sno | |
| * | |
| * @return array companyName·traceUrl (치환 전) | |
| */ | |
| public function getDeliveryTraceUrlTemplate($invoiceCompanySno): array | |
| { | |
| $arrBind = []; | |
| $this->db->bind_param_push($arrBind, 'i', $invoiceCompanySno); | |
| $strSQL = 'SELECT companyName, traceUrl FROM ' . DB_MANAGE_DELIVERY_COMPANY . ' WHERE sno = ?'; | |
| $getData = $this->db->query_fetch($strSQL, $arrBind, false); | |
| unset($arrBind); | |
| return [ | |
| 'companyName' => $getData['companyName'] ?? null, | |
| 'traceUrl' => $getData['traceUrl'] ?? null, | |
| ]; | |
| } | |
| /** | |
| * 배송추적시 json 처리 해야할 배송업체인지 체크 | |
| * | |
변경 전EpostReturnAdmin.php
변경 후EpostReturnAdmin.php
| <?php | |
| /* | |
| * Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved | |
| * | |
| * Unauthorized copying or redistribution of this file in source and binary forms via any medium | |
| * is strictly prohibited. | |
| */ | |
| namespace Bundle\Component\Order; | |
| use Component\Admin\AdminLogDAO; | |
| use Component\Admin\AdminMenu; | |
| use Component\Member\MemberMasking; | |
| use Component\Policy\Policy; | |
| use DTO\Order\EpostReturnSearchDTO; | |
| use Framework\Http\Request; | |
| use Framework\Http\Session\SessionManager; | |
| use Framework\Log\Channel; | |
| use Origin\Enum\ApiClient\Epost\EpostUsageRequestStatus; | |
| use Origin\Service\ApiClient\Commerce\Epost\EpostUsageAdminApiClientService; | |
| use Framework\Log\Logger; | |
| use Framework\Registry\Globals; | |
| use Framework\Utility\SkinUtils; | |
| use Origin\DTO\Member\AdminLogDTO; | |
| use Origin\DTO\Order\EpostReturnRequestResultDTO; | |
| use Origin\Enum\Order\EpostDeliveryStatus; | |
| use Origin\Enum\Order\EpostReturnRequestRejectReason; | |
| use Origin\Exception\Order\EpostReturnRequestRejectedException; | |
| use Origin\Repository\Member\AdminLogRepository; | |
| use Origin\Repository\Order\OrderReturnInvoiceRepository; | |
| use Origin\Service\Aggregator\GodomallAggregatorService; | |
| use Origin\Service\Order\EpostReturnService; | |
| use Repository\Order\OrderGiftRepository; | |
| use Repository\Order\OrderGoodsRepository; | |
| use Exception; | |
| use Throwable; | |
| /** | |
| * 우체국택배 수거신청 어드민 | |
| */ | |
| class EpostReturnAdmin | |
| { | |
| /** | |
| * 우체국택배 정책 키 | |
| */ | |
| public const POLICY_KEY = 'order.godopost'; | |
| /** | |
| * 자동접수 사용 여부 값 (order.godopost.autoAcceptFl) | |
| */ | |
| public const AUTO_ACCEPT_ENABLED = 'y'; | |
| public const AUTO_ACCEPT_DISABLED = 'n'; | |
| /** | |
| * @var Channel epost 채널로 고정한 로거 | |
| */ | |
| protected Channel $logger; | |
| public function __construct( | |
| protected OrderGoodsRepository $repository, | |
| protected Policy $policy, | |
| protected AdminLogRepository $adminLogRepository, | |
| protected EpostReturnService $service, | |
| protected Globals $globals, | |
| Logger $logger, | |
| protected OrderReturnInvoiceRepository $returnInvoiceRepository, | |
| protected OrderGiftRepository $giftRepository, | |
| protected ReturnInvoice $returnInvoice, | |
| protected MemberMasking $memberMasking, | |
| protected Request $request, | |
| protected SessionManager $session, | |
| protected EpostUsageAdminApiClientService $usageAdminApiClientService | |
| ) { | |
| $this->logger = $logger->channel('epost'); | |
| } | |
| /** | |
| * @param array $getValue 화면 검색 파라미터 | |
| * @return array data(행 목록) · total(검색 건수) · grandTotal(전체 건수) · page·pageSize(보정 적용값) | |
| */ | |
| public function getTargetList(array $getValue): array | |
| { | |
| $search = new EpostReturnSearchDTO($getValue); | |
| $rows = $this->repository->findTargets($search); | |
| $total = $this->repository->countTargets($search); | |
| $giftsByOrderNo = []; | |
| $orderNos = array_values(array_unique(array_column($rows, 'orderNo'))); | |
| if (!empty($orderNos)) { | |
| foreach ($this->giftRepository->findOrderGiftLinesByOrderNos($orderNos) as $gift) { | |
| $giftsByOrderNo[$gift['orderNo']][] = $gift; | |
| } | |
| } | |
| foreach ($rows as $key => $row) { | |
| // 옵션 파싱은 주문 화면 전례를 따름 — optionInfo 는 통째 stripslashes 후 decode, optionTextInfo 는 decode 후 항목별 stripslashes | |
| $rows[$key]['optionInfo'] = json_decode(gd_htmlspecialchars_stripslashes((string) ($row['optionInfo'] ?? '')), true) ?: []; | |
| $rows[$key]['gifts'] = $giftsByOrderNo[$row['orderNo']] ?? []; | |
| $formatOption = static function (string $name, string $value): string { | |
| return $name !== '' ? $name . ': ' . $value : $value; | |
| }; | |
| $optionLines = []; | |
| foreach ($rows[$key]['optionInfo'] as $option) { | |
| $optionLines[] = $formatOption(trim((string) ($option[0] ?? '')), trim((string) ($option[1] ?? ''))); | |
| } | |
| foreach ((array) (json_decode((string) ($row['optionTextInfo'] ?? ''), true) ?: []) as $textOption) { | |
| $optionLines[] = $formatOption( | |
| trim(gd_htmlspecialchars_stripslashes((string) ($textOption[0] ?? ''))), | |
| trim(gd_htmlspecialchars_stripslashes((string) ($textOption[1] ?? ''))) | |
| ); | |
| } | |
| $optionLines = array_filter($optionLines, static fn (string $line) => $line !== ''); | |
| $rows[$key]['optionText'] = implode(' / ', $optionLines); | |
| $rows[$key]['giftLines'] = array_map(static fn (array $gift) => ($gift['presentTitle'] ?: '-') . ' | ' . $gift['giftNm'] . ' | ' . $gift['giveCnt'] . '개', $rows[$key]['gifts']); | |
| $rows[$key]['progressLabel'] = $this->returnInvoice->getProgressLabel($row['returnInvoiceStatus']); | |
| $rows[$key]['orderName'] = $this->memberMasking->masking('order', 'name', $row['orderName']); | |
| $rows[$key]['memId'] = $this->memberMasking->masking('order', 'id', $row['memId']); | |
| $rows[$key]['receiverName'] = $this->memberMasking->masking('order', 'name', $row['receiverName']); | |
| $rows[$key]['goodsImageSrc'] = empty($row['imageName']) ? null : SkinUtils::imageViewStorageConfig( | |
| $row['imageName'], | |
| $row['imagePath'], | |
| $row['imageStorage'], | |
| 40, | |
| 'goods', | |
| true, | |
| $row['goodsImageStorage'] === 'obs' ? $row['imageUrl'] : null | |
| )[0]; | |
| } | |
| return [ | |
| 'data' => $rows, | |
| 'total' => $total, | |
| 'grandTotal' => $this->repository->countTargets(new EpostReturnSearchDTO([])), | |
| 'page' => $search->getPage(), | |
| 'pageSize' => $search->getPageSize(), | |
| ]; | |
| } | |
| /** | |
| * 서비스 신청 진행 상태 배너 표시 재료 — 조회 실패나 배너 없는 단계(서비스 시작)는 null | |
| * | |
| * 실패해도 화면은 정상 노출해야 해서 여기서 기록만 남기고 배너를 생략함 | |
| * | |
| * @return array|null [title, body, showApplyLink] | |
| */ | |
| public function getUsageBanner(): ?array | |
| { | |
| try { | |
| $usageRequestStatus = $this->usageAdminApiClientService | |
| ->findUsageRequestStatus() | |
| ->getStatus(); | |
| } catch (\Throwable $e) { | |
| $this->logger->warning('우체국택배 신청 진행 상태 조회 실패 — 배너 생략', ['exception' => $e]); | |
| return null; | |
| } | |
| if (!$usageRequestStatus->hasBanner()) { | |
| return null; | |
| } | |
| return [ | |
| 'title' => $usageRequestStatus->bannerTitle(), | |
| 'body' => $usageRequestStatus->bannerBody(), | |
| 'showApplyLink' => $usageRequestStatus === EpostUsageRequestStatus::NONE, | |
| ]; | |
| } | |
| /** | |
| * 서비스 미신청 상점 판정 — compdivcd·API 상태값(NONE) 둘 중 하나라도 미신청을 가리키면 true. | |
| * compdivcd 는 있어도 API 상태가 NONE 이면(불일치) 신뢰하지 않고 차단함 | |
| */ | |
| public function isUsageNotRequested(): bool | |
| { | |
| if (empty($this->policy->getValue(self::POLICY_KEY)['compdivcd'])) { | |
| return true; | |
| } | |
| try { | |
| return $this->usageAdminApiClientService | |
| ->findUsageRequestStatus() | |
| ->getStatus() === EpostUsageRequestStatus::NONE; | |
| } catch (\Throwable $e) { | |
| $this->logger->warning('우체국택배 신청 진행 상태 조회 실패', ['exception' => $e]); | |
| return false; | |
| } | |
| } | |
| /** | |
| * 미신청 배너 [서비스 신청하기] 팝업 URL — 허브 신청 페이지 | |
| * 연동 파라미터는 CRM 애그리게이터 호출 규격과 동일 (solution·shopNo·code·menuCode·adminUrl) | |
| */ | |
| public function getUsageApplyUrl(): string | |
| { | |
| $hubConfig = \App::getSystemConfig('internal')['hub']; | |
| $params = [ | |
| 'solution' => 'GODOMALL', | |
| 'shopNo' => $this->globals->get('gLicense.godosno'), | |
| 'code' => \App::getInstance(GodomallAggregatorService::class)->registerDisposalKey(), | |
| 'menuCode' => \App::load(AdminMenu::class)->getAdminMenuNo('order', 'epostParcel', 'request'), | |
| 'adminUrl' => parse_url(URI_ADMIN)['host'], | |
| ]; | |
| return $hubConfig['url'] . $hubConfig['path']['epost-apply'] . '?' . http_build_query($params); | |
| } | |
| /** | |
| * 자동접수 사용 여부. 우체국택배 미사용 상점(승인번호 부재)과 정책·필드 부재는 사용안함 | |
| * | |
| * @return bool 자동접수 사용이면 true | |
| */ | |
| public function isAutoAcceptEnabled(): bool | |
| { | |
| $config = $this->policy->getValue(self::POLICY_KEY); | |
| if (empty($config['compdivcd'])) { | |
| return false; | |
| } | |
| return ($config['autoAcceptFl'] ?? '') === self::AUTO_ACCEPT_ENABLED; | |
| } | |
| /** | |
| * 자동접수 사용 여부 저장. 저장 성공 건만 감사기록을 남김 | |
| * | |
| * @param bool $enabled 사용 여부 | |
| * | |
| * @throws Exception 정책 저장 실패 | |
| */ | |
| public function setAutoAccept(bool $enabled): void | |
| { | |
| $config = $this->policy->getValue(self::POLICY_KEY) ?: []; | |
| $config['autoAcceptFl'] = $enabled ? self::AUTO_ACCEPT_ENABLED : self::AUTO_ACCEPT_DISABLED; | |
| $saved = $this->policy->setValue(self::POLICY_KEY, $config); | |
| if (!$saved) { | |
| throw new Exception('우체국택배 자동접수 설정 저장 실패'); | |
| } | |
| $this->adminLogRepository->insert($this->buildAutoAcceptAdminLog($enabled)); | |
| } | |
| /** | |
| * 자동접수 설정 저장 감사기록 조립 | |
| * | |
| * @param bool $enabled 사용 여부 | |
| */ | |
| protected function buildAutoAcceptAdminLog(bool $enabled): AdminLogDTO | |
| { | |
| $requestUri = preg_replace('#^/{2,}#', '/', ($this->request->getRequestUri() ?? '')); | |
| return (new AdminLogDTO()) | |
| ->setBaseUri(strtok($requestUri, '?')) | |
| ->setUri($requestUri) | |
| ->setData(json_encode(['autoAcceptFl' => $enabled ? self::AUTO_ACCEPT_ENABLED : self::AUTO_ACCEPT_DISABLED])) | |
| ->setMenu(__('주문/배송>택배연동 서비스>우체국택배 신청/관리')) | |
| ->setReferer((string)$this->request->getReferer()) | |
| ->setType(AdminLogDAO::LOG_TYPE_P2Y) | |
| ->setPage(__('우체국택배 신청/관리')) | |
| ->setAction(__('수거신청 자동 접수 설정 저장')) | |
| ->setManagerNo((string)$this->session->get('manager.sno')) | |
| ->setManagerId((string)$this->session->get('manager.managerId')) | |
| ->setScmNo((string)$this->session->get('manager.scmNo')) | |
| ->setIp((string)$this->request->getRemoteAddress()) | |
| ->setAccessControlReason(''); | |
| } | |
| /** | |
| * 수거 진행 건 상태변경 잠금 여부. 수거 상태가 있는 상품이 하나라도 있으면 잠김 | |
| * | |
| * DELIVERY_COMPLETE는 잠그면 반품회수완료(b4) 자동전이가 자기잠금에 걸려서 제외함. | |
| * FAILED는 재신청([수거 신청] 버튼)만 허용하고 일반 상태변경은 계속 잠금 대상임 | |
| * | |
| * @param string $orderNo 주문번호 | |
| * @param array $orderGoodsSnos 반품접수 상태인 상품주문번호 목록 | |
| * @return bool 잠금이면 true | |
| */ | |
| public function isStatusChangeLocked(string $orderNo, array $orderGoodsSnos): bool | |
| { | |
| if (empty($orderGoodsSnos)) { | |
| return false; | |
| } | |
| $returnInvoices = $this->returnInvoiceRepository->findReturnInvoiceStatusRows([$orderNo], $orderGoodsSnos); | |
| foreach ($returnInvoices as $returnInvoice) { | |
| if ($returnInvoice['returnInvoiceStatus'] !== null | |
| && $returnInvoice['returnInvoiceStatus'] !== EpostDeliveryStatus::DELIVERY_COMPLETE->value) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * 반품접수 자동접수 — 운영자 직접 접수 경로. 실패는 전파하지 않고 epost 채널 로그로만 남김 | |
| * | |
| * @param array $orderGoodsSnos 반품접수된 상품주문번호 목록 | |
| */ | |
| public function applyAutoAccept(array $orderGoodsSnos): void | |
| { | |
| if (empty($orderGoodsSnos)) { | |
| return; | |
| } | |
| try { | |
| if (!$this->isAutoAcceptEnabled()) { | |
| return; | |
| } | |
| $eligibleSnos = $this->repository->findTargetSnos($orderGoodsSnos); | |
| $excludedSnos = array_values(array_diff($orderGoodsSnos, $eligibleSnos)); | |
| if (!empty($excludedSnos)) { | |
| $this->logger->info('우체국택배 자동접수 대상 아님', ['orderGoodsSnos' => $excludedSnos]); | |
| } | |
| $this->requestPickup($eligibleSnos); | |
| } catch (EpostReturnRequestRejectedException $e) { | |
| $this->logger->warning('우체국택배 자동접수 거부', [ | |
| 'reason' => $e->getReason()->name, | |
| 'orderGoodsSnos' => $orderGoodsSnos, | |
| ]); | |
| } catch (Throwable $e) { | |
| $this->logger->warning('우체국택배 자동접수 실행 실패', [ | |
| 'orderGoodsSnos' => $orderGoodsSnos, | |
| 'exception' => $e, | |
| ]); | |
| } | |
| } | |
| /** | |
| * 반품 승인 자동접수 — 고객 신청 승인 경로. 실패는 전파하지 않고 epost 채널 로그로만 남김 | |
| * | |
| * @param array $userHandleSnos 승인한 고객 신청 sno 목록 | |
| */ | |
| public function applyAutoAcceptByUserHandles(array $userHandleSnos): void | |
| { | |
| // 신청 없는 행은 userHandleSno=0 | |
| $userHandleSnos = array_values(array_filter(array_map('intval', $userHandleSnos), fn (int $sno) => $sno > 0)); | |
| if (empty($userHandleSnos)) { | |
| return; | |
| } | |
| try { | |
| if (!$this->isAutoAcceptEnabled()) { | |
| return; | |
| } | |
| $this->requestPickup($this->repository->findTargetSnosByUserHandleSnos($userHandleSnos)); | |
| } catch (EpostReturnRequestRejectedException $e) { | |
| $this->logger->warning('우체국택배 자동접수 거부', [ | |
| 'reason' => $e->getReason()->name, | |
| 'userHandleSnos' => $userHandleSnos, | |
| ]); | |
| } catch (Throwable $e) { | |
| $this->logger->warning('우체국택배 자동접수 실행 실패', [ | |
| 'userHandleSnos' => $userHandleSnos, | |
| 'exception' => $e, | |
| ]); | |
| } | |
| } | |
| /** | |
| * 수동 수거신청 — 「우체국택배 수거신청」 화면 진입점. 권한·본사 여부는 어드민 세션에서 호출부가 평가해서 넘김 | |
| * | |
| * @param bool $hasMenuWritePermission 메뉴 쓰기 권한 여부 | |
| * @param bool $isHeadquarters 본사 운영자 여부 | |
| * @param array $orderGoodsSnos 신청할 상품주문번호 목록 | |
| * @return EpostReturnRequestResultDTO 실행 결과 (성공·실패·선점 제외) | |
| * @throws EpostReturnRequestRejectedException 거부 사유를 담아 던짐 | |
| */ | |
| public function applyManualRequest(bool $hasMenuWritePermission, bool $isHeadquarters, array $orderGoodsSnos): EpostReturnRequestResultDTO | |
| { | |
| $epostApprovalNo = (string)($this->policy->getValue(self::POLICY_KEY)['compdivcd'] ?? ''); | |
| $eligibleSnos = $this->repository->findTargetSnos($orderGoodsSnos); | |
| try { | |
| EpostReturnRequestValidator::validateManualRequest( | |
| $hasMenuWritePermission, | |
| $isHeadquarters, | |
| $epostApprovalNo !== '', | |
| $orderGoodsSnos, | |
| $eligibleSnos | |
| ); | |
| } catch (EpostReturnRequestRejectedException $e) { | |
| // 화면 얼럿이 갈리는 두 사유(상태 아님 · 이미 신청)를 부적격 건의 수거 진행 행 유무로 세분화함 | |
| $ineligibleSnos = array_values(array_diff($orderGoodsSnos, $eligibleSnos)); | |
| if ($e->getReason() === EpostReturnRequestRejectReason::NOT_ELIGIBLE | |
| && !empty($this->returnInvoiceRepository->findRequestedSnos($ineligibleSnos))) { | |
| throw new EpostReturnRequestRejectedException(EpostReturnRequestRejectReason::DUPLICATED); | |
| } | |
| throw $e; | |
| } | |
| return $this->service->apply((int)$this->globals->get('gLicense.godosno'), $epostApprovalNo, $orderGoodsSnos); | |
| } | |
| /** | |
| * 대상 조건 통과분 수거 신청 공통부 | |
| * | |
| * @param array $eligibleSnos 대상 조건을 통과한 상품주문번호 목록 | |
| */ | |
| protected function requestPickup(array $eligibleSnos): void | |
| { | |
| if (empty($eligibleSnos)) { | |
| return; | |
| } | |
| $epostApprovalNo = (string)($this->policy->getValue(self::POLICY_KEY)['compdivcd'] ?? ''); | |
| EpostReturnRequestValidator::validateAutoRequest(true, $epostApprovalNo !== '', $eligibleSnos, $eligibleSnos); | |
| $result = $this->service->apply((int)$this->globals->get('gLicense.godosno'), $epostApprovalNo, $eligibleSnos); | |
| if (!empty($result->getExcludedOrderGoodsSnos())) { | |
| $this->logger->warning('우체국택배 자동접수 선점 제외', ['orderGoodsSnos' => $result->getExcludedOrderGoodsSnos()]); | |
| } | |
| if ($result->getFailureCount() > 0) { | |
| $this->logger->warning('우체국택배 자동접수 실패 건', ['orderNos' => $result->getFailedOrderNos()]); | |
| } | |
| } | |
| } | |
변경 전EpostReturnRequestValidator.php
변경 후EpostReturnRequestValidator.php
| <?php | |
| /* | |
| * Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved | |
| * | |
| * Unauthorized copying or redistribution of this file in source and binary forms via any medium | |
| * is strictly prohibited. | |
| */ | |
| namespace Bundle\Component\Order; | |
| use Origin\Enum\Order\EpostReturnRequestRejectReason; | |
| use Origin\Exception\Order\EpostReturnRequestRejectedException; | |
| /** | |
| * 우체국택배 수거 신청 서버측 검증 — 판정 근거는 전부 파라미터로 받는 순수 판정 | |
| */ | |
| class EpostReturnRequestValidator | |
| { | |
| /** | |
| * 1회 신청 건수 상한 — 실행당 1회 배치 호출 전제의 값 | |
| */ | |
| public const BATCH_LIMIT = 500; | |
| /** | |
| * 수동 신청 검증 — 권한·본사 여부는 어드민 세션에서 호출부가 평가해서 넘김 | |
| * | |
| * @param array $orderGoodsSnos 신청할 상품주문번호 목록 | |
| * @param array $eligibleSnos 대상 술어를 통과한 상품주문번호 목록 | |
| * @throws EpostReturnRequestRejectedException 거부 사유를 담아 던짐 | |
| */ | |
| public static function validateManualRequest( | |
| bool $hasMenuWritePermission, | |
| bool $isHeadquarters, | |
| bool $epostConnected, | |
| array $orderGoodsSnos, | |
| array $eligibleSnos | |
| ): void { | |
| if (!$hasMenuWritePermission) { | |
| throw new EpostReturnRequestRejectedException(EpostReturnRequestRejectReason::NO_PERMISSION); | |
| } | |
| if (!$isHeadquarters) { | |
| throw new EpostReturnRequestRejectedException(EpostReturnRequestRejectReason::NOT_HEADQUARTERS); | |
| } | |
| static::validateRequestTargets($epostConnected, $orderGoodsSnos, $eligibleSnos); | |
| } | |
| /** | |
| * 자동 신청 검증 — 세션 대신 상점 자동접수 설정으로 평가함 | |
| * | |
| * @param array $orderGoodsSnos 신청할 상품주문번호 목록 | |
| * @param array $eligibleSnos 대상 술어를 통과한 상품주문번호 목록 | |
| * @throws EpostReturnRequestRejectedException 거부 사유를 담아 던짐 | |
| */ | |
| public static function validateAutoRequest( | |
| bool $autoAcceptEnabled, | |
| bool $epostConnected, | |
| array $orderGoodsSnos, | |
| array $eligibleSnos | |
| ): void { | |
| if (!$autoAcceptEnabled) { | |
| throw new EpostReturnRequestRejectedException(EpostReturnRequestRejectReason::AUTO_DISABLED); | |
| } | |
| static::validateRequestTargets($epostConnected, $orderGoodsSnos, $eligibleSnos); | |
| } | |
| /** | |
| * 화면 필터와 별개로 서버가 최종 거부를 판정함 — 접수는 취소 창구가 없어 되돌릴 수 없음 | |
| * | |
| * @param array $orderGoodsSnos 신청할 상품주문번호 목록 | |
| * @param array $eligibleSnos 대상 술어를 통과한 상품주문번호 목록 | |
| * @throws EpostReturnRequestRejectedException 거부 사유를 담아 던짐 | |
| */ | |
| protected static function validateRequestTargets(bool $epostConnected, array $orderGoodsSnos, array $eligibleSnos): void | |
| { | |
| if (!$epostConnected) { | |
| throw new EpostReturnRequestRejectedException(EpostReturnRequestRejectReason::NOT_CONNECTED); | |
| } | |
| if (count($orderGoodsSnos) > self::BATCH_LIMIT) { | |
| throw new EpostReturnRequestRejectedException(EpostReturnRequestRejectReason::OVER_LIMIT); | |
| } | |
| if (array_diff(array_map('intval', $orderGoodsSnos), array_map('intval', $eligibleSnos)) !== []) { | |
| throw new EpostReturnRequestRejectedException(EpostReturnRequestRejectReason::NOT_ELIGIBLE); | |
| } | |
| } | |
| } | |
변경 전OrderAdminNew.php
| use Origin\Enum\Order\OrderStatusCode; | |
| use Origin\Enum\Order\OrderStatusGroup; | |
| use Origin\Enum\Statistics\Analytics\Order\ClaimType; | |
| use Origin\Service\Statistics\Analytics\Order\ClaimCompleteAnalyticsCollectorService; | |
| use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService; | |
| use Origin\Service\WebHook\OrderWebHookService; | |
| */ | |
| class OrderAdminNew extends \Component\Order\OrderNew | |
| { | |
| use CommonLogStorageTrait; // 공용 로그 저장 | |
| /** | |
| if ($methodType != 'cancel') { | |
| $beforeStatus = $bundleData['orderStatus'][0]; | |
| $handleData['handler'] = 'admin'; | |
| foreach ($bundleData['sno'] as $orderGoodsSno) { | |
| $newOrderGoodsData = $this->setHandleAccept($arrData['orderNo'], [$orderGoodsSno], $handleMode, $handleData, $beforeStatus); | |
| // 주문상품이 새로 생성됐을시, 클레임신청 정보 수정 | |
| if (is_array($newOrderGoodsData) === true && empty($newOrderGoodsData['userHandleGoodsNo']) === false) { | |
| $bundleData['userHandleGoodsNo'] = $newOrderGoodsData['userHandleGoodsNo']; | |
| unset($newOrderGoodsData); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| $addData['memberDcPrice'] = $memberDcPrice; | |
| $addData['memberOverlapDcPrice'] = $memberOverlapDcPrice; | |
| $addData['couponGoodsDcPrice'] = $couponGoodsDcPrice; | |
| // 마이앱 사용에 따른 분기 처리 | |
| if ($this->useMyapp) { | |
| // 수량이 0보다 작은경우 처리하지 않는다. | |
| if (isset($bundleData['goodsCnt']) && $bundleData['goodsCnt'][$orderGoodsSno] > 0) { | |
| // 주문상품 update용 데이터 | |
| $fieldData = $this->divideHandleGoodsCntData($orderNo, $orderGoodsSno, $bundleData['goodsCnt'][$orderGoodsSno]); | |
| $tmpOrderGoodsData = ''; | |
| } | |
| } | |
| /** | |
| * 환불/반품/교환 단계 복구 처리 (환불상세내 상품리스트) | |
| * | |
| $changeStatus = $arrData['changeStatus']; | |
| $isUpdate = false; | |
| // 핸들로 오기 전의 주문상태로 돌릴 수 있는지에 대한 체크 (롤백용) | |
| foreach ($arrData['bundle']['statusCheck'] as $orderGoodsSno) { | |
| continue; | |
| } | |
| // 주문 상태 변경 처리 | |
| $this->updateStatusPreprocess($orderNo, $this->getOrderGoods($orderNo, null, $handleSno, ['orderStatus']), substr($orderStatus, 0, 1), $changeStatus, __('[이전 단계 복구]')); | |
| // 반품/교환/환불과 동일한 주문상태로 변경하지 않는 경우 예외 처리 (ex. 환불->반품, 반품->교환 등) | |
| if (gd_in_array($orderStatus, $this->statusHandleListExclude) && !gd_in_array($changeStatus, $this->statusHandleListExclude)) { | |
| // 반품회수 완료시 자동으로 환불접수 단계 이동 처리 | |
| if ($isAutoRefund) { | |
| $this->updateStatusPreprocess($orderNo, $this->getOrderGoods($orderNo, null, $handleSno, ['orderStatus']), 'b', 'r1', __('반품회수완료로 인한 자동')); | |
| // 기존 반품/교환/환불 레코드에서 데이터 추출 | |
| $getData = $this->getOrderGoodsData($orderNo, null, $handleSno, null, null, false); | |
| */ | |
| public function updateStatusPreprocess($orderNo, $goodsData, $statusMode, $changeStatus, $reason = false, $bundleFl = false, $mode = null, $useVisit = null, $autoProcess = false, $userHandleFl = null) | |
| { | |
| if (($result = $this->updateStatusUnconditionalPreprocess($orderNo, $goodsData, $statusMode, $changeStatus, $reason, $bundleFl, $mode, $useVisit, $autoProcess, $userHandleFl))!== true) { | |
| if($result['errorMsg']){ | |
| $message = sprintf(__('"주문상태변경 실패하였습니다.(%s")'), $result['errorMsg']); | |
| } | |
| $bundleData['changeStatus'] = $changeStatus; | |
| $bundleData['reason'] = $reason . ' ' . $this->getOrderStatusAdmin($changeStatus) . __(' 처리'); | |
| if($autoProcess == true){ | |
| if($bundleData['changeStatus'] == 'r1'){ | |
| $bundleData['reason'] = '고객 자동환불 신청'; | |
| return $refundAccountData; | |
| } | |
| } | |
변경 후OrderAdminNew.php
| use Origin\Enum\Order\OrderStatusCode; | |
| use Origin\Enum\Order\OrderStatusGroup; | |
| use Origin\Enum\Statistics\Analytics\Order\ClaimType; | |
| use Origin\Exception\Order\EpostReturnStatusLockedException; | |
| use Origin\Service\Statistics\Analytics\Order\ClaimCompleteAnalyticsCollectorService; | |
| use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService; | |
| use Origin\Service\WebHook\OrderWebHookService; | |
| */ | |
| class OrderAdminNew extends \Component\Order\OrderNew | |
| { | |
| /** | |
| * @var array 반품접수(b1) 처리된 상품주문번호 — 우체국 자동접수용, 호출부가 커밋 후 읽음 | |
| */ | |
| protected array $epostReturnAcceptedSnos = []; | |
| /** | |
| * @var string divideHandleGoodsCntData() 가 참조하는 처리모드('r'/'b'/'e'/'z') — 기존 메서드 시그니처를 건드리지 않기 위해 속성으로 전달 | |
| */ | |
| protected string $divideHandleMode = ''; | |
| use CommonLogStorageTrait; // 공용 로그 저장 | |
| /** | |
| if ($methodType != 'cancel') { | |
| $beforeStatus = $bundleData['orderStatus'][0]; | |
| $handleData['handler'] = 'admin'; | |
| $epostReturnSnos = []; | |
| foreach ($bundleData['sno'] as $orderGoodsSno) { | |
| $newOrderGoodsData = $this->setHandleAccept($arrData['orderNo'], [$orderGoodsSno], $handleMode, $handleData, $beforeStatus); | |
| // 부분수량 접수는 새로 생긴 상품주문번호가 접수 상태가 됨 | |
| if (is_array($newOrderGoodsData) && !empty($newOrderGoodsData['userHandleGoodsNo'])) { | |
| $epostReturnSnos[] = (int)$newOrderGoodsData['userHandleGoodsNo']; | |
| } else { | |
| $epostReturnSnos[] = (int)$orderGoodsSno; | |
| } | |
| // 주문상품이 새로 생성됐을시, 클레임신청 정보 수정 | |
| if (is_array($newOrderGoodsData) === true && empty($newOrderGoodsData['userHandleGoodsNo']) === false) { | |
| $bundleData['userHandleGoodsNo'] = $newOrderGoodsData['userHandleGoodsNo']; | |
| unset($newOrderGoodsData); | |
| } | |
| } | |
| if ($changeStatus === OrderStatusCode::BACK_REQUEST->value) { | |
| $this->epostReturnAcceptedSnos = $epostReturnSnos; | |
| } | |
| } | |
| } | |
| } | |
| $addData['memberDcPrice'] = $memberDcPrice; | |
| $addData['memberOverlapDcPrice'] = $memberOverlapDcPrice; | |
| $addData['couponGoodsDcPrice'] = $couponGoodsDcPrice; | |
| if ($this->divideHandleMode === 'b') { | |
| // 반품은 이미 나간 배송을 그대로 수거하는 것이라 원 송장을 그대로 승계함(신규 출고가 아님) | |
| $addData['invoiceCompanySno'] = $originData['invoiceCompanySno']; | |
| $addData['invoiceNo'] = $originData['invoiceNo']; | |
| } else { | |
| // 교환/환불 등 신규 출고가 필요한 추가상품은 송장번호 초기화 | |
| $addData['invoiceCompanySno'] = ''; | |
| $addData['invoiceNo'] = ''; | |
| } | |
| // 마이앱 사용에 따른 분기 처리 | |
| if ($this->useMyapp) { | |
| // 수량이 0보다 작은경우 처리하지 않는다. | |
| if (isset($bundleData['goodsCnt']) && $bundleData['goodsCnt'][$orderGoodsSno] > 0) { | |
| // 주문상품 update용 데이터 | |
| $this->divideHandleMode = $handleMode; | |
| $fieldData = $this->divideHandleGoodsCntData($orderNo, $orderGoodsSno, $bundleData['goodsCnt'][$orderGoodsSno]); | |
| $tmpOrderGoodsData = ''; | |
| } | |
| } | |
| /** | |
| * setHandleRollback 주문 로그 사유 지정값 — 자동 전이(예: 우체국 반품회수완료)가 기본 문구 대신 쓸 문구 | |
| */ | |
| protected $handleRollbackReason = null; | |
| /** | |
| * setHandleRollback 의 b4 전이 로그 처리자 지정값 | |
| */ | |
| protected $handleRollbackManagerId = null; | |
| /** | |
| * setAutoRefundAccept 의 r1 전이 로그 처리자 IP 지정값 | |
| */ | |
| protected $handleRollbackManagerIp = null; | |
| public function setHandleRollbackReason(?string $reason): void | |
| { | |
| $this->handleRollbackReason = $reason; | |
| } | |
| public function setHandleRollbackManagerId(?string $managerId): void | |
| { | |
| $this->handleRollbackManagerId = $managerId; | |
| } | |
| public function setHandleRollbackManagerIp(?string $managerIp): void | |
| { | |
| $this->handleRollbackManagerIp = $managerIp; | |
| } | |
| /** | |
| * 환불/반품/교환 단계 복구 처리 (환불상세내 상품리스트) | |
| * | |
| $changeStatus = $arrData['changeStatus']; | |
| $isUpdate = false; | |
| $this->blockEpostPickupInProgress($orderNo, array_intersect_key($arrData['bundle']['orderStatus'] ?? [], array_flip($arrData['bundle']['statusCheck'] ?? [])), $changeStatus); | |
| // 핸들로 오기 전의 주문상태로 돌릴 수 있는지에 대한 체크 (롤백용) | |
| foreach ($arrData['bundle']['statusCheck'] as $orderGoodsSno) { | |
| continue; | |
| } | |
| // 지정 처리자가 있으면 이 전이 로그에만 남김 — 뒤따르는 자동 환불접수(r1) 로그는 공란 유지 | |
| $canSetManagerId = method_exists($this, 'setLogManagerId'); | |
| if ($this->handleRollbackManagerId !== null && $canSetManagerId) { | |
| $this->setLogManagerId($this->handleRollbackManagerId); | |
| } | |
| try { | |
| $this->updateStatusPreprocess($orderNo, $this->getOrderGoods($orderNo, null, $handleSno, ['orderStatus']), substr($orderStatus, 0, 1), $changeStatus, $this->handleRollbackReason ?? __('[이전 단계 복구]')); | |
| } catch (\Throwable $e) { | |
| throw $e; | |
| } finally { | |
| if ($this->handleRollbackManagerId !== null && $canSetManagerId) { | |
| $this->setLogManagerId(null); | |
| } | |
| } | |
| // 반품/교환/환불과 동일한 주문상태로 변경하지 않는 경우 예외 처리 (ex. 환불->반품, 반품->교환 등) | |
| if (gd_in_array($orderStatus, $this->statusHandleListExclude) && !gd_in_array($changeStatus, $this->statusHandleListExclude)) { | |
| // 반품회수 완료시 자동으로 환불접수 단계 이동 처리 | |
| if ($isAutoRefund) { | |
| $autoRefundReason = __('반품회수완료로 인한 자동 환불접수 처리'); | |
| // 공유 인스턴스라 반드시 되돌림 — 안 그러면 바깥 루프의 다음 상품이 이 문구를 그대로 물려받음 | |
| $prevRollbackReason = $this->handleRollbackReason; | |
| $canSetManagerIp = method_exists($this, 'setLogManagerIp'); | |
| if ($prevRollbackReason !== null) { | |
| // 지정 사유 흐름이면 뒤따르는 이 로그도 전문 그대로 — 안 하면 기본 꼬리('환불접수 처리')가 붙어 문구가 중복됨 | |
| $this->handleRollbackReason = $autoRefundReason; | |
| } | |
| if ($this->handleRollbackManagerIp !== null && $canSetManagerIp) { | |
| $this->setLogManagerIp($this->handleRollbackManagerIp); | |
| } | |
| try { | |
| $this->updateStatusPreprocess($orderNo, $this->getOrderGoods($orderNo, null, $handleSno, ['orderStatus']), 'b', 'r1', $autoRefundReason); | |
| } catch (\Throwable $e) { | |
| throw $e; | |
| } finally { | |
| if ($prevRollbackReason !== null) { | |
| $this->handleRollbackReason = $prevRollbackReason; | |
| } | |
| if ($this->handleRollbackManagerIp !== null && $canSetManagerIp) { | |
| $this->setLogManagerIp(null); | |
| } | |
| } | |
| // 기존 반품/교환/환불 레코드에서 데이터 추출 | |
| $getData = $this->getOrderGoodsData($orderNo, null, $handleSno, null, null, false); | |
| */ | |
| public function updateStatusPreprocess($orderNo, $goodsData, $statusMode, $changeStatus, $reason = false, $bundleFl = false, $mode = null, $useVisit = null, $autoProcess = false, $userHandleFl = null) | |
| { | |
| // statusMode 는 호출부마다 실제 orderStatus 와 어긋날 수 있어 신뢰하지 않음 — b1 판정은 아래에서 실제 상태로 다시 거름 | |
| $this->blockEpostPickupInProgress($orderNo, array_column((array)$goodsData, 'orderStatus', 'sno'), $changeStatus); | |
| if (($result = $this->updateStatusUnconditionalPreprocess($orderNo, $goodsData, $statusMode, $changeStatus, $reason, $bundleFl, $mode, $useVisit, $autoProcess, $userHandleFl))!== true) { | |
| if($result['errorMsg']){ | |
| $message = sprintf(__('"주문상태변경 실패하였습니다.(%s")'), $result['errorMsg']); | |
| } | |
| $bundleData['changeStatus'] = $changeStatus; | |
| if ($this->handleRollbackReason !== null && $reason === $this->handleRollbackReason) { | |
| // 지정 사유는 전문 그대로 — 기본 꼬리('{상태} 처리')를 붙이면 문구가 중복됨 | |
| $bundleData['reason'] = $reason; | |
| } else { | |
| $bundleData['reason'] = $reason . ' ' . $this->getOrderStatusAdmin($changeStatus) . __(' 처리'); | |
| } | |
| if($autoProcess == true){ | |
| if($bundleData['changeStatus'] == 'r1'){ | |
| $bundleData['reason'] = '고객 자동환불 신청'; | |
| return $refundAccountData; | |
| } | |
| /** | |
| * 반품접수(b1) 처리된 상품주문번호 — 우체국 자동접수용 | |
| */ | |
| public function getEpostReturnAcceptedSnos(): array | |
| { | |
| return $this->epostReturnAcceptedSnos; | |
| } | |
| /** | |
| * 우체국택배 수거 진행 건이 있으면 상태변경을 거부함 | |
| * | |
| * @param string $orderNo 주문번호 | |
| * @param array $orderStatusBySno 상품주문번호별 주문상태 맵 | |
| * @param string $changeStatus 변경할 주문상태 코드 | |
| * | |
| * @throws EpostReturnStatusLockedException 수거 진행 건 포함 | |
| */ | |
| protected function blockEpostPickupInProgress(string $orderNo, array $orderStatusBySno, string $changeStatus): void | |
| { | |
| $epostLockSnos = array_keys($orderStatusBySno, OrderStatusCode::BACK_REQUEST->value, true); | |
| if (!empty($epostLockSnos) && \App::getInstance(\Component\Order\EpostReturnAdmin::class)->isStatusChangeLocked($orderNo, $epostLockSnos)) { | |
| throw new EpostReturnStatusLockedException((string) $this->getOrderStatusAdmin($changeStatus)); | |
| } | |
| } | |
| } | |
변경 전OrderAdmin.php
| use Origin\Enum\Order\OrderStatusCode; | |
| use Origin\Enum\Order\OrderStatusGroup; | |
| use Origin\Enum\Statistics\Analytics\Order\ClaimType; | |
| use Origin\Service\Statistics\Analytics\Order\ClaimCompleteAnalyticsCollectorService; | |
| use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService; | |
| use Origin\Service\WebHook\OrderWebHookService; | |
| */ | |
| class OrderAdmin extends \Component\Order\Order | |
| { | |
| use CommonLogStorageTrait; // 공용 로그 저장 | |
| /** | |
| // 택배사 sno에 매핑된 택배사 회사명 배열 가져오기 | |
| $invoiceCompanyNameData = $this->getInvoiceCompanyNames(); | |
| // 결제방법과 처리 상태 설정 | |
| foreach ($data as $key => &$val) { | |
| $orderGoods = $val['goods']; | |
| } | |
| } | |
| //총 할인금액 | |
| $totalDcPriceArray = [ | |
| $orderData[$oVal['orderNo']]['totalGoodsDcPrice'], | |
| $totalAddGoodsPrice = 0; //추가상품 | |
| $_naverpayDelivery = []; | |
| $delivery = new Delivery(); | |
| foreach ($orderGoods as $scmNo => $dataVal) { | |
| // 공급사로 로그인한 경우 처리 해당 공급사 상품만 보여지도록 수정 처리 | |
| if($useMultiShippingKey !== true){ | |
| $val['userHandleInfo'] = $this->getUserHandleInfo($orderNo, $val['sno']); | |
| } | |
| if($useMultiShippingKey === true){ | |
| // 복수배송지 사용시 상품배열을 [orderInfoSno][orderDeliverySno][goods]의 3차 배열로 재구성 | |
| $getData['goods'][$val['orderInfoSno']][$val['orderDeliverySno']][] = $val; | |
| if ($methodType != 'cancel') { | |
| $beforeStatus = $bundleData['orderStatus'][0]; | |
| $handleData['handler'] = 'admin'; | |
| foreach ($bundleData['sno'] as $orderGoodsSno) { | |
| $newOrderGoodsData = $this->setHandleAccept($arrData['orderNo'], [$orderGoodsSno], $handleMode, $handleData, $beforeStatus); | |
| // 주문상품이 새로 생성됐을시, 클레임신청 정보 수정 | |
| if (is_array($newOrderGoodsData) && !empty($newOrderGoodsData['userHandleGoodsNo']) && !is_array($bundleData['sno'])) { | |
| $bundleData['userHandleGoodsNo'] = $newOrderGoodsData['userHandleGoodsNo']; | |
| unset($newOrderGoodsData); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| $addData['memberDcPrice'] = $memberDcPrice; | |
| $addData['memberOverlapDcPrice'] = $memberOverlapDcPrice; | |
| $addData['couponGoodsDcPrice'] = $couponGoodsDcPrice; | |
| // 추가상품은 송장번호 초기화 | |
| $addData['invoiceCompanySno'] = ''; | |
| $addData['invoiceNo'] = ''; | |
| // 마이앱 사용에 따른 분기 처리 | |
| if ($this->useMyapp) { | |
| // 수량이 0보다 작은경우 처리하지 않는다. | |
| if (isset($bundleData['goodsCnt']) && $bundleData['goodsCnt'][$orderGoodsSno] > 0) { | |
| // 주문상품 update용 데이터 | |
| $fieldData = $this->divideHandleGoodsCntData($orderNo, $orderGoodsSno, $bundleData['goodsCnt'][$orderGoodsSno]); | |
| $tmpOrderGoodsData = []; | |
| } | |
| } | |
| /** | |
| * 환불/반품/교환 단계 복구 처리 (환불상세내 상품리스트) | |
| * | |
| $changeStatus = $arrData['changeStatus']; | |
| $isUpdate = false; | |
| // 핸들로 오기 전의 주문상태로 돌릴 수 있는지에 대한 체크 (롤백용) | |
| foreach ($arrData['bundle']['statusCheck'] as $orderGoodsSno) { | |
| continue; | |
| } | |
| // 주문 상태 변경 처리 | |
| $this->updateStatusPreprocess($orderNo, $this->getOrderGoods($orderNo, null, $handleSno, ['orderStatus']), substr($orderStatus, 0, 1), $changeStatus, __('[이전 단계 복구]')); | |
| // 반품/교환/환불과 동일한 주문상태로 변경하지 않는 경우 예외 처리 (ex. 환불->반품, 반품->교환 등) | |
| if (gd_in_array($orderStatus, $this->statusHandleListExclude) && !gd_in_array($changeStatus, $this->statusHandleListExclude)) { | |
| // 반품회수 완료시 자동으로 환불접수 단계 이동 처리 | |
| if ($isAutoRefund) { | |
| $this->updateStatusPreprocess($orderNo, $this->getOrderGoods($orderNo, null, $handleSno, ['orderStatus']), 'b', 'r1', __('반품회수완료로 인한 자동')); | |
| // 기존 반품/교환/환불 레코드에서 데이터 추출 | |
| $getData = $this->getOrderGoodsData($orderNo, null, $handleSno, null, null, false); | |
| */ | |
| public function updateStatusPreprocess($orderNo, $goodsData, $statusMode, $changeStatus, $reason = false, $bundleFl = false, $mode = null, $useVisit = null, $autoProcess = false, $userHandleFl = null) | |
| { | |
| if (($result = $this->updateStatusUnconditionalPreprocess($orderNo, $goodsData, $statusMode, $changeStatus, $reason, $bundleFl, $mode, $useVisit, $autoProcess, $userHandleFl))!== true) { | |
| if($userHandleFl == false){ | |
| if($result['errorMsg']){ | |
| } | |
| $bundleData['changeStatus'] = $changeStatus; | |
| $bundleData['reason'] = $reason . ' ' . $this->getOrderStatusAdmin($changeStatus) . __(' 처리'); | |
| if($autoProcess == true){ | |
| if($bundleData['changeStatus'] == 'r1'){ | |
| $bundleData['reason'] = '고객 자동환불 신청'; | |
| return $refundAccountData; | |
| } | |
| } | |
변경 후OrderAdmin.php
| use Origin\Enum\Order\OrderStatusCode; | |
| use Origin\Enum\Order\OrderStatusGroup; | |
| use Origin\Enum\Statistics\Analytics\Order\ClaimType; | |
| use Origin\Exception\Order\EpostReturnStatusLockedException; | |
| use Origin\Service\Statistics\Analytics\Order\ClaimCompleteAnalyticsCollectorService; | |
| use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService; | |
| use Origin\Service\WebHook\OrderWebHookService; | |
| */ | |
| class OrderAdmin extends \Component\Order\Order | |
| { | |
| /** | |
| * @var array 반품접수(b1) 처리된 상품주문번호 — 우체국 자동접수용, 호출부가 커밋 후 읽음 | |
| */ | |
| protected array $epostReturnAcceptedSnos = []; | |
| /** | |
| * @var string divideHandleGoodsCntData() 가 참조하는 처리모드('r'/'b'/'e'/'z') — 기존 메서드 시그니처를 건드리지 않기 위해 속성으로 전달 | |
| */ | |
| protected string $divideHandleMode = ''; | |
| use CommonLogStorageTrait; // 공용 로그 저장 | |
| /** | |
| // 택배사 sno에 매핑된 택배사 회사명 배열 가져오기 | |
| $invoiceCompanyNameData = $this->getInvoiceCompanyNames(); | |
| $returnDisplayMap = \App::getInstance(\Component\Order\ReturnInvoice::class)->getDisplayMap( | |
| array_keys($data), | |
| array_column(array_merge(...array_column($data, 'goods')), 'sno') | |
| ); | |
| $epostIcon = sprintf("<img src='%s' > ", \UserFilePath::adminSkin('gd_share', 'img', 'channel_icon', 'epost.gif')->www()); | |
| // 결제방법과 처리 상태 설정 | |
| foreach ($data as $key => &$val) { | |
| $orderGoods = $val['goods']; | |
| } | |
| } | |
| $returnProgressLabel = $returnDisplayMap[$oVal['sno']]['progressLabel'] ?? null; | |
| $oVal['returnStatusStr'] = $returnProgressLabel === null ? '' : '<br>(' . $epostIcon . $returnProgressLabel . ')'; | |
| $oVal['returnTraceUrl'] = $returnDisplayMap[$oVal['sno']]['traceUrl'] ?? null; | |
| //총 할인금액 | |
| $totalDcPriceArray = [ | |
| $orderData[$oVal['orderNo']]['totalGoodsDcPrice'], | |
| $totalAddGoodsPrice = 0; //추가상품 | |
| $_naverpayDelivery = []; | |
| $delivery = new Delivery(); | |
| $returnDisplayMap = \App::getInstance(\Component\Order\ReturnInvoice::class)->getDisplayMap( | |
| [$orderNo], | |
| array_column(array_merge(...array_values($orderGoods)), 'sno') | |
| ); | |
| $epostIcon = sprintf("<img src='%s' > ", \UserFilePath::adminSkin('gd_share', 'img', 'channel_icon', 'epost.gif')->www()); | |
| foreach ($orderGoods as $scmNo => $dataVal) { | |
| // 공급사로 로그인한 경우 처리 해당 공급사 상품만 보여지도록 수정 처리 | |
| if($useMultiShippingKey !== true){ | |
| $val['userHandleInfo'] = $this->getUserHandleInfo($orderNo, $val['sno']); | |
| } | |
| $returnProgressLabel = $returnDisplayMap[$val['sno']]['progressLabel'] ?? null; | |
| $val['returnStatusStr'] = $returnProgressLabel === null ? '' : '<br>(' . $epostIcon . $returnProgressLabel . ')'; | |
| $val['returnTraceUrl'] = $returnDisplayMap[$val['sno']]['traceUrl'] ?? null; | |
| if($useMultiShippingKey === true){ | |
| // 복수배송지 사용시 상품배열을 [orderInfoSno][orderDeliverySno][goods]의 3차 배열로 재구성 | |
| $getData['goods'][$val['orderInfoSno']][$val['orderDeliverySno']][] = $val; | |
| if ($methodType != 'cancel') { | |
| $beforeStatus = $bundleData['orderStatus'][0]; | |
| $handleData['handler'] = 'admin'; | |
| $epostReturnSnos = []; | |
| foreach ($bundleData['sno'] as $orderGoodsSno) { | |
| $newOrderGoodsData = $this->setHandleAccept($arrData['orderNo'], [$orderGoodsSno], $handleMode, $handleData, $beforeStatus); | |
| // 부분수량 접수는 새로 생긴 상품주문번호가 접수 상태가 됨 | |
| if (is_array($newOrderGoodsData) && !empty($newOrderGoodsData['userHandleGoodsNo'])) { | |
| $epostReturnSnos[] = (int)$newOrderGoodsData['userHandleGoodsNo']; | |
| } else { | |
| $epostReturnSnos[] = (int)$orderGoodsSno; | |
| } | |
| // 주문상품이 새로 생성됐을시, 클레임신청 정보 수정 | |
| if (is_array($newOrderGoodsData) && !empty($newOrderGoodsData['userHandleGoodsNo']) && !is_array($bundleData['sno'])) { | |
| $bundleData['userHandleGoodsNo'] = $newOrderGoodsData['userHandleGoodsNo']; | |
| unset($newOrderGoodsData); | |
| } | |
| } | |
| if ($changeStatus === OrderStatusCode::BACK_REQUEST->value) { | |
| $this->epostReturnAcceptedSnos = $epostReturnSnos; | |
| } | |
| } | |
| } | |
| } | |
| $addData['memberDcPrice'] = $memberDcPrice; | |
| $addData['memberOverlapDcPrice'] = $memberOverlapDcPrice; | |
| $addData['couponGoodsDcPrice'] = $couponGoodsDcPrice; | |
| if ($this->divideHandleMode === 'b') { | |
| // 반품은 이미 나간 배송을 그대로 수거하는 것이라 원 송장을 그대로 승계함(신규 출고가 아님) | |
| $addData['invoiceCompanySno'] = $originData['invoiceCompanySno']; | |
| $addData['invoiceNo'] = $originData['invoiceNo']; | |
| } else { | |
| // 교환/환불 등 신규 출고가 필요한 추가상품은 송장번호 초기화 | |
| $addData['invoiceCompanySno'] = ''; | |
| $addData['invoiceNo'] = ''; | |
| } | |
| // 마이앱 사용에 따른 분기 처리 | |
| if ($this->useMyapp) { | |
| // 수량이 0보다 작은경우 처리하지 않는다. | |
| if (isset($bundleData['goodsCnt']) && $bundleData['goodsCnt'][$orderGoodsSno] > 0) { | |
| // 주문상품 update용 데이터 | |
| $this->divideHandleMode = $handleMode; | |
| $fieldData = $this->divideHandleGoodsCntData($orderNo, $orderGoodsSno, $bundleData['goodsCnt'][$orderGoodsSno]); | |
| $tmpOrderGoodsData = []; | |
| } | |
| } | |
| /** | |
| * setHandleRollback 주문 로그 사유 지정값 — 자동 전이(예: 우체국 반품회수완료)가 기본 문구 대신 쓸 문구 | |
| */ | |
| protected $handleRollbackReason = null; | |
| /** | |
| * setHandleRollback 의 b4 전이 로그 처리자 지정값 | |
| */ | |
| protected $handleRollbackManagerId = null; | |
| /** | |
| * setAutoRefundAccept 의 r1 전이 로그 처리자 IP 지정값 | |
| */ | |
| protected $handleRollbackManagerIp = null; | |
| public function setHandleRollbackReason(?string $reason): void | |
| { | |
| $this->handleRollbackReason = $reason; | |
| } | |
| public function setHandleRollbackManagerId(?string $managerId): void | |
| { | |
| $this->handleRollbackManagerId = $managerId; | |
| } | |
| public function setHandleRollbackManagerIp(?string $managerIp): void | |
| { | |
| $this->handleRollbackManagerIp = $managerIp; | |
| } | |
| /** | |
| * 환불/반품/교환 단계 복구 처리 (환불상세내 상품리스트) | |
| * | |
| $changeStatus = $arrData['changeStatus']; | |
| $isUpdate = false; | |
| $this->blockEpostPickupInProgress($orderNo, array_intersect_key($arrData['bundle']['orderStatus'] ?? [], array_flip($arrData['bundle']['statusCheck'] ?? [])), $changeStatus); | |
| // 핸들로 오기 전의 주문상태로 돌릴 수 있는지에 대한 체크 (롤백용) | |
| foreach ($arrData['bundle']['statusCheck'] as $orderGoodsSno) { | |
| continue; | |
| } | |
| // 지정 처리자가 있으면 이 전이 로그에만 남김 — 뒤따르는 자동 환불접수(r1) 로그는 공란 유지 | |
| $canSetManagerId = method_exists($this, 'setLogManagerId'); | |
| if ($this->handleRollbackManagerId !== null && $canSetManagerId) { | |
| $this->setLogManagerId($this->handleRollbackManagerId); | |
| } | |
| try { | |
| $this->updateStatusPreprocess($orderNo, $this->getOrderGoods($orderNo, null, $handleSno, ['orderStatus']), substr($orderStatus, 0, 1), $changeStatus, $this->handleRollbackReason ?? __('[이전 단계 복구]')); | |
| } catch (\Throwable $e) { | |
| throw $e; | |
| } finally { | |
| if ($this->handleRollbackManagerId !== null && $canSetManagerId) { | |
| $this->setLogManagerId(null); | |
| } | |
| } | |
| // 반품/교환/환불과 동일한 주문상태로 변경하지 않는 경우 예외 처리 (ex. 환불->반품, 반품->교환 등) | |
| if (gd_in_array($orderStatus, $this->statusHandleListExclude) && !gd_in_array($changeStatus, $this->statusHandleListExclude)) { | |
| // 반품회수 완료시 자동으로 환불접수 단계 이동 처리 | |
| if ($isAutoRefund) { | |
| $autoRefundReason = __('반품회수완료로 인한 자동 환불접수 처리'); | |
| // 공유 인스턴스라 반드시 되돌림 — 안 그러면 setHandleRollback() 바깥 루프의 다음 상품이 이 문구를 그대로 물려받음 | |
| $prevRollbackReason = $this->handleRollbackReason; | |
| $canSetManagerIp = method_exists($this, 'setLogManagerIp'); | |
| if ($prevRollbackReason !== null) { | |
| // 지정 사유 흐름이면 뒤따르는 이 로그도 전문 그대로 — 안 하면 기본 꼬리('환불접수 처리')가 붙어 문구가 중복됨 | |
| $this->handleRollbackReason = $autoRefundReason; | |
| } | |
| if ($this->handleRollbackManagerIp !== null && $canSetManagerIp) { | |
| $this->setLogManagerIp($this->handleRollbackManagerIp); | |
| } | |
| try { | |
| $this->updateStatusPreprocess($orderNo, $this->getOrderGoods($orderNo, null, $handleSno, ['orderStatus']), 'b', 'r1', $autoRefundReason); | |
| } catch (\Throwable $e) { | |
| throw $e; | |
| } finally { | |
| if ($prevRollbackReason !== null) { | |
| $this->handleRollbackReason = $prevRollbackReason; | |
| } | |
| if ($this->handleRollbackManagerIp !== null && $canSetManagerIp) { | |
| $this->setLogManagerIp(null); | |
| } | |
| } | |
| // 기존 반품/교환/환불 레코드에서 데이터 추출 | |
| $getData = $this->getOrderGoodsData($orderNo, null, $handleSno, null, null, false); | |
| */ | |
| public function updateStatusPreprocess($orderNo, $goodsData, $statusMode, $changeStatus, $reason = false, $bundleFl = false, $mode = null, $useVisit = null, $autoProcess = false, $userHandleFl = null) | |
| { | |
| // statusMode 는 호출부마다 실제 orderStatus 와 어긋날 수 있어 신뢰하지 않음 — b1 판정은 아래에서 실제 상태로 다시 거름 | |
| $this->blockEpostPickupInProgress($orderNo, array_column((array)$goodsData, 'orderStatus', 'sno'), $changeStatus); | |
| if (($result = $this->updateStatusUnconditionalPreprocess($orderNo, $goodsData, $statusMode, $changeStatus, $reason, $bundleFl, $mode, $useVisit, $autoProcess, $userHandleFl))!== true) { | |
| if($userHandleFl == false){ | |
| if($result['errorMsg']){ | |
| } | |
| $bundleData['changeStatus'] = $changeStatus; | |
| if ($this->handleRollbackReason !== null && $reason === $this->handleRollbackReason) { | |
| // 지정 사유는 전문 그대로 — 기본 꼬리('{상태} 처리')를 붙이면 문구가 중복됨 | |
| $bundleData['reason'] = $reason; | |
| } else { | |
| $bundleData['reason'] = $reason . ' ' . $this->getOrderStatusAdmin($changeStatus) . __(' 처리'); | |
| } | |
| if($autoProcess == true){ | |
| if($bundleData['changeStatus'] == 'r1'){ | |
| $bundleData['reason'] = '고객 자동환불 신청'; | |
| return $refundAccountData; | |
| } | |
| /** | |
| * 반품접수(b1) 처리된 상품주문번호 — 우체국 자동접수용 | |
| */ | |
| public function getEpostReturnAcceptedSnos(): array | |
| { | |
| return $this->epostReturnAcceptedSnos; | |
| } | |
| /** | |
| * 우체국택배 수거 진행 건이 있으면 상태변경을 거부함 | |
| * | |
| * @param string $orderNo 주문번호 | |
| * @param array $orderStatusBySno 상품주문번호별 주문상태 맵 | |
| * @param string $changeStatus 변경할 주문상태 코드 | |
| * | |
| * @throws EpostReturnStatusLockedException 수거 진행 건 포함 | |
| */ | |
| protected function blockEpostPickupInProgress(string $orderNo, array $orderStatusBySno, string $changeStatus): void | |
| { | |
| $epostLockSnos = array_keys($orderStatusBySno, OrderStatusCode::BACK_REQUEST->value, true); | |
| if (!empty($epostLockSnos) && \App::getInstance(\Component\Order\EpostReturnAdmin::class)->isStatusChangeLocked($orderNo, $epostLockSnos)) { | |
| throw new EpostReturnStatusLockedException((string) $this->getOrderStatusAdmin($changeStatus)); | |
| } | |
| } | |
| } | |
변경 전OrderNew.php
| protected $logManagerNo = ''; | |
| public $orderExchangeChangeDate; | |
| /** | |
| return $this->channel; | |
| } | |
| public function setLogManagerNo($logManagerNo){ | |
| $this->logManagerNo = $logManagerNo; | |
| } | |
| } | |
| } | |
| $arrBind = $this->db->get_binding(DBTableField::tableLogOrder(), $arrData, 'insert'); | |
| $this->db->set_insert_db(DB_LOG_ORDER, $arrBind['param'], $arrBind['bind'], 'y'); | |
| unset($arrBind); | |
변경 후OrderNew.php
| protected $logManagerNo = ''; | |
| /** | |
| * 주문 로그 처리자 표기 지정값 — 세션 관리자 대신 시스템 주체(예: 우체국)로 남길 때 세터로 지정함 | |
| */ | |
| protected $logManagerId = null; | |
| /** | |
| * 주문 로그 처리자 IP 표기 지정값 — 세션 없는 자동 전이(예: 우체국 자동 환불접수)에서 요청 IP 대신 공란으로 남길 때 세터로 지정함 | |
| */ | |
| protected $logManagerIp = null; | |
| public $orderExchangeChangeDate; | |
| /** | |
| return $this->channel; | |
| } | |
| public function setLogManagerId(?string $logManagerId): void | |
| { | |
| $this->logManagerId = $logManagerId; | |
| } | |
| public function setLogManagerIp(?string $logManagerIp): void | |
| { | |
| $this->logManagerIp = $logManagerIp; | |
| } | |
| public function setLogManagerNo($logManagerNo){ | |
| $this->logManagerNo = $logManagerNo; | |
| } | |
| } | |
| } | |
| // 시스템 주체 처리자 표기(예: 우체국) — 지정돼 있으면 세션 관리자 대신 남김 (네이버페이센터 표기와 같은 계열) | |
| if ($this->logManagerId !== null) { | |
| $arrData['managerNo'] = 0; | |
| $arrData['managerId'] = $this->logManagerId; | |
| } | |
| // 처리자 공란 지정된 자동 전이(예: 우체국 자동 환불접수)는 요청 IP도 함께 공란 — 세션 없는 배치·API 호출 IP가 노출되는 것을 막음 | |
| if ($this->logManagerIp !== null) { | |
| $arrData['managerIp'] = $this->logManagerIp; | |
| } | |
| $arrBind = $this->db->get_binding(DBTableField::tableLogOrder(), $arrData, 'insert'); | |
| $this->db->set_insert_db(DB_LOG_ORDER, $arrBind['param'], $arrBind['bind'], 'y'); | |
| unset($arrBind); | |
변경 전Order.php
| protected $logManagerNo = ''; | |
| public $orderExchangeChangeDate; | |
| /** | |
| return $this->channel; | |
| } | |
| public function setLogManagerNo($logManagerNo){ | |
| $this->logManagerNo = $logManagerNo; | |
| } | |
| } | |
| } | |
| $arrBind = $this->db->get_binding(DBTableField::tableLogOrder(), $arrData, 'insert'); | |
| $this->db->set_insert_db(DB_LOG_ORDER, $arrBind['param'], $arrBind['bind'], 'y'); | |
| unset($arrBind); | |
변경 후Order.php
| protected $logManagerNo = ''; | |
| /** | |
| * 주문 로그 처리자 표기 지정값 — 세션 관리자 대신 시스템 주체(예: 우체국)로 남길 때 세터로 지정함 | |
| */ | |
| protected $logManagerId = null; | |
| /** | |
| * 주문 로그 처리자 IP 표기 지정값 — 세션 없는 자동 전이(예: 우체국 자동 환불접수)에서 요청 IP 대신 공란으로 남길 때 세터로 지정함 | |
| */ | |
| protected $logManagerIp = null; | |
| public $orderExchangeChangeDate; | |
| /** | |
| return $this->channel; | |
| } | |
| public function setLogManagerId(?string $logManagerId): void | |
| { | |
| $this->logManagerId = $logManagerId; | |
| } | |
| public function setLogManagerIp(?string $logManagerIp): void | |
| { | |
| $this->logManagerIp = $logManagerIp; | |
| } | |
| public function setLogManagerNo($logManagerNo){ | |
| $this->logManagerNo = $logManagerNo; | |
| } | |
| } | |
| } | |
| // 시스템 주체 처리자 표기(예: 우체국) — 지정돼 있으면 세션 관리자 대신 남김 (네이버페이센터 표기와 같은 계열) | |
| if ($this->logManagerId !== null) { | |
| $arrData['managerNo'] = 0; | |
| $arrData['managerId'] = $this->logManagerId; | |
| } | |
| // 처리자 공란 지정된 자동 전이(예: 우체국 자동 환불접수)는 요청 IP도 함께 공란 — 세션 없는 배치·API 호출 IP가 노출되는 것을 막음 | |
| if ($this->logManagerIp !== null) { | |
| $arrData['managerIp'] = $this->logManagerIp; | |
| } | |
| $arrBind = $this->db->get_binding(DBTableField::tableLogOrder(), $arrData, 'insert'); | |
| $this->db->set_insert_db(DB_LOG_ORDER, $arrBind['param'], $arrBind['bind'], 'y'); | |
| unset($arrBind); | |
변경 전ReOrderCalculation.php
| } | |
| $webhookOrderGoodsSnos = []; | |
| // 각 상태별 처리 및 취소 테이블 등록 | |
| if (!empty($bundleData)) { | |
| // 상태 변경 처리 | |
| if (!empty($newOrderGoodsData['userHandleGoodsNo'])) { | |
| $webhookOrderGoodsSnos[] = $newOrderGoodsData['userHandleGoodsNo']; | |
| $webhookOrderGoodsSnos[] = $orderGoodsSno; | |
| } | |
| // 주문상품이 새로 생성됐을시, 클레임신청 정보 수정 | |
| if (is_array($webhookOrderGoodsSnos)){ | |
| $this->orderWebHookService->sendOrderGoodsWebHook($webhookOrderGoodsSnos, 'restore'); | |
| } | |
| } | |
| /** | |
변경 후ReOrderCalculation.php
| } | |
| $webhookOrderGoodsSnos = []; | |
| $epostReturnAcceptedSnos = []; | |
| // 각 상태별 처리 및 취소 테이블 등록 | |
| if (!empty($bundleData)) { | |
| // 상태 변경 처리 | |
| if (!empty($newOrderGoodsData['userHandleGoodsNo'])) { | |
| $webhookOrderGoodsSnos[] = $newOrderGoodsData['userHandleGoodsNo']; | |
| $webhookOrderGoodsSnos[] = $orderGoodsSno; | |
| // 부분수량 접수는 새로 생긴 상품주문번호가 접수 상태가 됨 | |
| $epostReturnAcceptedSnos[] = (int) $newOrderGoodsData['userHandleGoodsNo']; | |
| } else { | |
| $epostReturnAcceptedSnos[] = (int) $orderGoodsSno; | |
| } | |
| // 주문상품이 새로 생성됐을시, 클레임신청 정보 수정 | |
| if (is_array($webhookOrderGoodsSnos)){ | |
| $this->orderWebHookService->sendOrderGoodsWebHook($webhookOrderGoodsSnos, 'restore'); | |
| } | |
| // 반품접수(b1)만 우체국 자동접수 대상 — 컨트롤러가 트랜잭션 커밋 후 트리거를 부름 | |
| return $methodType === 'back' ? $epostReturnAcceptedSnos : null; | |
| } | |
| /** | |
변경 전ReturnInvoice.php
변경 후ReturnInvoice.php
| <?php | |
| /* | |
| * Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved | |
| * | |
| * Unauthorized copying or redistribution of this file in source and binary forms via any medium | |
| * is strictly prohibited. | |
| */ | |
| namespace Bundle\Component\Order; | |
| use Bundle\Component\Delivery\Delivery; | |
| use Framework\Log\Channel; | |
| use Framework\Log\Logger; | |
| use Origin\Enum\Order\EpostDeliveryStatus; | |
| use Origin\Enum\Order\EpostReturnInvoiceStatus; | |
| use Origin\Repository\Order\OrderReturnInvoiceRepository; | |
| /** | |
| * 반품 수거 송장·상태의 화면 지원 — 병기 라벨·반송추적 노출 판정·조회 URL 조립의 공용 지점. | |
| * 판정·조립 값은 화면 목록이 조인해 온 행에서 받으며, 마이페이지 목록만 보충 조회 1회로 주입함 | |
| */ | |
| class ReturnInvoice | |
| { | |
| private const LOG_CHANNEL = 'epost'; | |
| /** | |
| * @var Channel epost 채널로 고정한 로거 | |
| */ | |
| protected readonly Channel $logger; | |
| public function __construct( | |
| protected Delivery $delivery, | |
| protected OrderReturnInvoiceRepository $returnInvoiceRepository, | |
| Logger $logger | |
| ) { | |
| $this->logger = $logger->channel(self::LOG_CHANNEL); | |
| } | |
| /** | |
| * 처리상태에 괄호로 병기할 우체국 진행 라벨 | |
| * | |
| * 한 컬럼에 고도몰 자체 상태와 우체국 배송상태가 함께 실리므로 둘을 차례로 판정함 | |
| * | |
| * @param string|null $returnInvoiceStatus 반품 수거 상태 | |
| * @return string|null 병기 대상이 아니면 null | |
| */ | |
| public function getProgressLabel(?string $returnInvoiceStatus): ?string | |
| { | |
| if ($returnInvoiceStatus === null) { | |
| return null; | |
| } | |
| if ($returnInvoiceStatus === EpostReturnInvoiceStatus::FAILED->value) { | |
| return __('수거신청 실패'); | |
| } | |
| if ($returnInvoiceStatus === EpostReturnInvoiceStatus::REQUESTED->value) { | |
| return __('수거 접수'); | |
| } | |
| return match (EpostDeliveryStatus::tryFrom($returnInvoiceStatus)) { | |
| EpostDeliveryStatus::PRINT_INVOICE => __('수거 접수'), | |
| EpostDeliveryStatus::COLLECT => __('수거 완료'), | |
| EpostDeliveryStatus::NOT_COLLECT => __('미수거'), | |
| EpostDeliveryStatus::TAKE_OVER, | |
| EpostDeliveryStatus::SEND, | |
| EpostDeliveryStatus::RECEIVE, | |
| EpostDeliveryStatus::DELIVERY_READY => __('반송중'), | |
| EpostDeliveryStatus::NOT_DELIVERY => __('미배달'), | |
| default => null, | |
| }; | |
| } | |
| /** | |
| * 반송추적 노출 여부 | |
| * | |
| * @param string|null $returnInvoiceNo 반품 수거 송장번호 | |
| * @param string|null $returnInvoiceStatus 반품 수거 상태 | |
| * @return bool 노출이면 true | |
| */ | |
| public function isTraceVisible(?string $returnInvoiceNo, ?string $returnInvoiceStatus): bool | |
| { | |
| return !empty($returnInvoiceNo) | |
| && $returnInvoiceStatus !== null | |
| && $returnInvoiceStatus !== EpostReturnInvoiceStatus::FAILED->value; | |
| } | |
| /** | |
| * 반송추적 조회 URL. 서버가 조립해 화면에 내려주며 클라이언트 조립 진입점을 두지 않음 | |
| * | |
| * @param string|null $returnInvoiceNo 반품 수거 송장번호 | |
| * @param string|null $returnInvoiceStatus 반품 수거 상태 | |
| * @param int|null $returnCarrierNo 반품 수거 택배사 | |
| * @return string|null 노출 대상이 아니면 null | |
| */ | |
| public function getTraceUrl(?string $returnInvoiceNo, ?string $returnInvoiceStatus, ?int $returnCarrierNo): ?string | |
| { | |
| if (!$this->isTraceVisible($returnInvoiceNo, $returnInvoiceStatus) || empty($returnCarrierNo)) { | |
| return null; | |
| } | |
| return $this->delivery->getDeliveryTrace($returnCarrierNo, $returnInvoiceNo); | |
| } | |
| /** | |
| * 조회 행 목록의 반송추적 URL 맵 | |
| * | |
| * 한 페이지의 택배사는 사실상 우체국 하나로 고정되므로, 택배사별 URL 템플릿을 1회만 조회해 | |
| * 메모이제이션하고 행마다는 송장번호 치환만 함(행 수만큼 조회하던 N+1 제거) | |
| * | |
| * @param array $rows findReturnInvoiceStatusRows 행 목록 | |
| * @return array [orderGoodsNo => 서버 조립 URL] — 노출 대상 아닌 행은 뺌 | |
| */ | |
| public function getTraceUrlMap(array $rows): array | |
| { | |
| $traceUrlMap = []; | |
| $templatesByCarrier = []; | |
| foreach ($rows as $row) { | |
| if (!$this->isTraceVisible($row['returnInvoiceNo'], $row['returnInvoiceStatus']) || empty($row['returnCarrierNo'])) { | |
| continue; | |
| } | |
| $carrierSno = (int) $row['returnCarrierNo']; | |
| if (!array_key_exists($carrierSno, $templatesByCarrier)) { | |
| $templatesByCarrier[$carrierSno] = $this->delivery->getDeliveryTraceUrlTemplate($carrierSno); | |
| } | |
| $template = $templatesByCarrier[$carrierSno]; | |
| $traceUrl = str_replace('__INVOICENO__', str_replace('-', '', $row['returnInvoiceNo']), (string) $template['traceUrl']); | |
| if (!filter_var($traceUrl, FILTER_VALIDATE_URL)) { | |
| $this->logger->warning('반송추적 URL 조립 실패 — 택배사 traceUrl 미설정', [ | |
| 'orderGoodsNo' => $row['orderGoodsNo'], | |
| 'returnCarrierNo' => $carrierSno, | |
| ]); | |
| continue; | |
| } | |
| $traceUrlMap[$row['orderGoodsNo']] = $traceUrl; | |
| } | |
| return $traceUrlMap; | |
| } | |
| /** | |
| * 상품주문번호별 병기 라벨·반송추적 URL — 목록이 조인하지 않는 화면용 보충 조회 1회 | |
| * | |
| * @param array $orderNos 주문번호 목록 | |
| * @param array $orderGoodsSnos 상품주문번호 목록 | |
| * @return array [orderGoodsNo => ['progressLabel' => 라벨, 'traceUrl' => URL]] — 대상 아닌 값은 null | |
| */ | |
| public function getDisplayMap(array $orderNos, array $orderGoodsSnos): array | |
| { | |
| if (empty($orderNos) || empty($orderGoodsSnos)) { | |
| return []; | |
| } | |
| $rows = $this->returnInvoiceRepository->findReturnInvoiceStatusRows($orderNos, $orderGoodsSnos); | |
| $traceUrlMap = $this->getTraceUrlMap($rows); | |
| $displayMap = []; | |
| foreach ($rows as $row) { | |
| $displayMap[$row['orderGoodsNo']] = [ | |
| 'progressLabel' => $this->getProgressLabel($row['returnInvoiceStatus']), | |
| 'traceUrl' => $traceUrlMap[$row['orderGoodsNo']] ?? null, | |
| ]; | |
| } | |
| return $displayMap; | |
| } | |
| /** | |
| * 마이페이지 주문 목록 행에 반송추적 URL 주입 — 보충 조회 1회, 노출 대상 아니면 null (템플릿은 null 여부로만 분기) | |
| * | |
| * @param array $orderData 주문 목록 (각 행에 orderNo·goods) | |
| * @return array returnTraceUrl 이 주입된 주문 목록 | |
| */ | |
| public function assignTraceUrls(array $orderData): array | |
| { | |
| $orderGoodsSnos = array_column(array_merge(...array_column($orderData, 'goods')), 'sno'); | |
| if (empty($orderGoodsSnos)) { | |
| return $orderData; | |
| } | |
| $traceUrlMap = $this->getTraceUrlMap($this->returnInvoiceRepository->findReturnInvoiceStatusRows( | |
| array_values(array_unique(array_column($orderData, 'orderNo'))), | |
| $orderGoodsSnos | |
| )); | |
| foreach ($orderData as &$order) { | |
| foreach ($order['goods'] ?? [] as $goodsKey => $orderGoods) { | |
| $order['goods'][$goodsKey]['returnTraceUrl'] = $traceUrlMap[$orderGoods['sno']] ?? null; | |
| } | |
| } | |
| return $orderData; | |
| } | |
| } | |
변경 전EpostReturnRequestController.php
변경 후EpostReturnRequestController.php
| <?php | |
| /* | |
| * Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved | |
| * | |
| * Unauthorized copying or redistribution of this file in source and binary forms via any medium | |
| * is strictly prohibited. | |
| */ | |
| namespace Bundle\Controller\Admin\Order; | |
| use Component\Order\EpostReturnAdmin; | |
| use Component\Page\Page; | |
| use DTO\Order\EpostReturnSearchDTO; | |
| use Framework\Debug\Exception\AlertBackException; | |
| use Origin\Service\Order\EpostReturnAccessLogService; | |
| use Request; | |
| /** | |
| * 우체국택배 수거신청 | |
| */ | |
| class EpostReturnRequestController extends \Controller\Admin\Controller | |
| { | |
| private const LOG_MENU = '주문/배송>택배연동 서비스>우체국택배 수거신청'; | |
| private const LOG_PAGE = '우체국택배 수거신청'; | |
| private const LOG_ACTION = '수거신청 대상 조회'; | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function index() | |
| { | |
| $this->callMenu('order', 'epostParcel', 'collectRequest'); | |
| $this->setNcdsSubBodyClass(); | |
| // 서비스 미신청 상점 진입 차단 — 앱 미설치 상점은 메뉴 자체가 없어 여기 오지 않음 | |
| if (\App::getInstance(EpostReturnAdmin::class)->isUsageNotRequested()) { | |
| throw new AlertBackException(__('우체국택배 서비스 신청 후 이용 가능합니다.')); | |
| } | |
| $getValue = Request::get()->toArray(); | |
| // 첫 진입 기간 기본 7일 — 파라미터가 아예 없을 때만. 비워서 제출한 검색은 전체 기간 의도라 그대로 둠 | |
| if (!isset($getValue['treatDateStart']) && !isset($getValue['treatDateEnd'])) { | |
| $getValue['treatDateStart'] = date('Y-m-d', strtotime('-6 days')); | |
| $getValue['treatDateEnd'] = date('Y-m-d'); | |
| } | |
| $targetList = \App::getInstance(EpostReturnAdmin::class)->getTargetList($getValue); | |
| $this->writeAccessLog($targetList['data'], $targetList['total']); | |
| // [수거 신청] 쓰기 권한 — 화면은 얼럿 판정 재료만 받고 최종 차단은 PS 서버 검증 | |
| $naviMenu = $this->getData('naviMenu'); | |
| $writable = $naviMenu->getAdminMenuWritableAuth($naviMenu->lno, $naviMenu->location, $naviMenu->accessMenu, $naviMenu->writeEnabledMenu); | |
| // 공급사 콤보박스 목록 — 본사도 하나의 공급사로 목록 하단에 둠 | |
| $scmList = \App::load(\Component\Scm\Scm::class)->selectOperationScmList(); | |
| $suppliers = []; | |
| $headquarters = null; | |
| foreach ((array)$scmList as $scm) { | |
| if ((int)$scm['scmNo'] === DEFAULT_CODE_SCMNO) { | |
| $headquarters = $scm; | |
| continue; | |
| } | |
| $suppliers[] = $scm; | |
| } | |
| if ($headquarters !== null) { | |
| $suppliers[] = $headquarters; | |
| } | |
| // 페이지·페이지크기 보정은 EpostReturnSearchDTO 한 곳 — 보정 적용값을 그대로 사용 | |
| $page = $targetList['page']; | |
| $pageSize = $targetList['pageSize']; | |
| // 행 번호 — 뷰는 출력만 함 | |
| $rowNo = $targetList['total'] - (($page - 1) * $pageSize); | |
| foreach (array_keys($targetList['data']) as $key) { | |
| $targetList['data'][$key]['rowNo'] = $rowNo--; | |
| } | |
| $this->setData('groups', $this->buildRowGroups($targetList['data'])); | |
| $this->setData('rows', $targetList['data']); | |
| $this->setData('total', $targetList['total']); | |
| $this->setData('grandTotal', $targetList['grandTotal']); | |
| $totalPages = max(1, (int)ceil($targetList['total'] / $pageSize)); | |
| $pagination = new Page($page, $targetList['total'], $targetList['grandTotal'], $pageSize); | |
| $this->setData('page', $page); | |
| $this->setData('totalPages', $totalPages); | |
| $this->setData('pagination', $pagination->getPage("goEprPage('PAGELINK')")); | |
| $this->setData('pageSize', $pageSize); | |
| $this->setData('search', $getValue); | |
| $this->setData('searchKeyOptions', $this->toSelectOptions(EpostReturnSearchDTO::SEARCH_FIELDS)); | |
| $this->setData('treatDateOptions', $this->toSelectOptions(EpostReturnSearchDTO::DATE_FIELDS)); | |
| $this->setData('matchTypeOptions', $this->toSelectOptions(EpostReturnSearchDTO::MATCH_TYPES)); | |
| $this->setData('sortOptions', $this->buildSortOptions()); | |
| $this->setData('pageSizeOptions', array_map( | |
| static fn (int $size) => ['id' => (string)$size, 'text' => $size . '개 보기'], | |
| EpostReturnSearchDTO::PAGE_SIZES | |
| )); | |
| $this->setData('collectionStatusFields', EpostReturnSearchDTO::COLLECTION_STATUSES); | |
| $this->setData('collectionStatuses', (array)($getValue['collectionStatuses'] ?? [])); | |
| $this->setData('scmNos', array_map('intval', (array)($getValue['scmNos'] ?? []))); | |
| $this->setData('suppliers', $suppliers); | |
| $this->setData('hasWriteAuth', (bool)$writable['check']); | |
| $this->getView()->setDefine('epostReturnResult', 'order/epost_return_request/epost_return_request_result.php'); | |
| // 수거 신청 처리 후 재조회 | |
| if (Request::isAjax()) { | |
| $this->getView()->setData($this->getData()); | |
| echo $this->getView()->render('epostReturnResult'); | |
| exit; | |
| } | |
| $this->getView()->setDefine('epostReturnSearch', 'order/epost_return_request/epost_return_request_search.php'); | |
| $this->getView()->setPageName('order/epost_return_request.php'); | |
| } | |
| /** | |
| * 정렬 선택지 — 정렬 컬럼마다 내림/오름차순 조합 | |
| * | |
| * @return array [['id' => '컬럼 방향', 'text' => 라벨], ...] | |
| */ | |
| protected function buildSortOptions(): array | |
| { | |
| $options = []; | |
| foreach (EpostReturnSearchDTO::SORT_FIELDS as $column => $label) { | |
| $options[] = ['id' => $column . ' desc', 'text' => $label . ' 내림차순']; | |
| $options[] = ['id' => $column . ' asc', 'text' => $label . ' 오름차순']; | |
| } | |
| return $options; | |
| } | |
| /** | |
| * 라벨 맵을 NCUA SelectBox 옵션 형태로 변환 | |
| * | |
| * @param array $fields 값 => 라벨 맵 | |
| * @return array [['id' => 값, 'text' => 라벨], ...] | |
| */ | |
| protected function toSelectOptions(array $fields): array | |
| { | |
| return array_map( | |
| static fn (string $id, string $text) => ['id' => $id, 'text' => $text], | |
| array_keys($fields), | |
| $fields | |
| ); | |
| } | |
| /** | |
| * 그리드 병합 구조 — 연속 동일 주문번호 = 병합 그룹, 그룹 안 연속 동일 공급사 = 공급사 병합 구간 | |
| * | |
| * @param array $rows 조회 행 | |
| * @return array 그룹 목록 [orderNo, rows, scmRunStart(행 인덱스 => rowspan)] | |
| */ | |
| protected function buildRowGroups(array $rows): array | |
| { | |
| $groups = []; | |
| foreach ($rows as $row) { | |
| $last = count($groups) - 1; | |
| if ($last >= 0 && $groups[$last]['orderNo'] === $row['orderNo']) { | |
| $groups[$last]['rows'][] = $row; | |
| } else { | |
| $groups[] = ['orderNo' => $row['orderNo'], 'rows' => [$row], 'scmRunStart' => []]; | |
| } | |
| } | |
| foreach ($groups as $gKey => $group) { | |
| $runs = []; | |
| foreach ($group['rows'] as $idx => $row) { | |
| $lastRun = count($runs) - 1; | |
| if ($lastRun >= 0 && $runs[$lastRun]['scmNo'] === $row['scmNo']) { | |
| $runs[$lastRun]['count']++; | |
| } else { | |
| $runs[] = ['scmNo' => $row['scmNo'], 'start' => $idx, 'count' => 1]; | |
| } | |
| } | |
| foreach ($runs as $run) { | |
| $groups[$gKey]['scmRunStart'][$run['start']] = $run['count']; | |
| } | |
| } | |
| // 행 병합 표시 재료 — 뷰는 출력만 함 | |
| foreach ($groups as $gKey => $group) { | |
| $groupRowCount = count($group['rows']); | |
| foreach (array_keys($group['rows']) as $idx) { | |
| $groups[$gKey]['rows'][$idx]['isFirst'] = $idx === 0; | |
| $groups[$gKey]['rows'][$idx]['groupRowCount'] = $groupRowCount; | |
| $groups[$gKey]['rows'][$idx]['scmRowspan'] = $group['scmRunStart'][$idx] ?? 0; | |
| } | |
| } | |
| return $groups; | |
| } | |
| /** | |
| * 수거신청 대상 조회 접속기록 | |
| * | |
| * @param array $rows 조회된 대상 행 | |
| * @param int $total 검색 건수 | |
| */ | |
| protected function writeAccessLog(array $rows, int $total): void | |
| { | |
| $session = \App::getInstance('session'); | |
| $orderNos = array_values(array_unique(array_column($rows, 'orderNo'))); | |
| \App::getInstance(EpostReturnAccessLogService::class)->write( | |
| __(self::LOG_MENU), | |
| __(self::LOG_PAGE), | |
| __(self::LOG_ACTION), | |
| (string)$session->get('manager.sno'), | |
| (string)$session->get('manager.managerId'), | |
| (string)$session->get('manager.scmNo'), | |
| preg_replace('#^/{2,}#', '/', (Request::getRequestUri() ?? '')), | |
| (string)Request::getReferer(), | |
| (string)Request::getRemoteAddress(), | |
| ['searchData' => gd_implode(', ', $orderNos), 'searchCnt' => $total] | |
| ); | |
| } | |
| } | |
변경 전EpostReturnRequestPsController.php
변경 후EpostReturnRequestPsController.php
| <?php | |
| /* | |
| * Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved | |
| * | |
| * Unauthorized copying or redistribution of this file in source and binary forms via any medium | |
| * is strictly prohibited. | |
| */ | |
| namespace Bundle\Controller\Admin\Order; | |
| use Component\Order\EpostReturnAdmin; | |
| use Origin\DTO\Order\EpostReturnRequestResultDTO; | |
| use Origin\Exception\Order\EpostReturnRequestRejectedException; | |
| use Origin\Service\Order\EpostReturnAccessLogService; | |
| use Request; | |
| use Throwable; | |
| /** | |
| * 우체국택배 수거신청 실행 | |
| */ | |
| class EpostReturnRequestPsController extends \Controller\Admin\Controller | |
| { | |
| private const LOG_MENU = '주문/배송>택배연동 서비스>우체국택배 수거신청'; | |
| private const LOG_PAGE = '우체국택배 수거신청'; | |
| private const LOG_ACTION = '우체국 수거 예약 전송(고객 정보 조회·전송 포함)'; | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function index() | |
| { | |
| // 레이아웃 미사용 Ajax — callMenu 는 쓰기 권한 판정 재료(naviMenu) 세팅용 | |
| $this->callMenu('order', 'epostParcel', 'collectRequest'); | |
| $post = Request::post()->toArray(); | |
| if (($post['mode'] ?? '') !== 'pickupRequest') { | |
| $this->json(['code' => 0, 'message' => __('잘못된 요청입니다.')]); | |
| return; | |
| } | |
| $orderGoodsSnos = array_values(array_unique(array_map('intval', (array)($post['orderGoodsSnos'] ?? [])))); | |
| if (empty($orderGoodsSnos)) { | |
| $this->json(['code' => 0, 'reason' => 'NO_SELECTION', 'message' => __('수거 신청할 주문을 선택해 주세요.'), 'subMessage' => '']); | |
| return; | |
| } | |
| $naviMenu = $this->getData('naviMenu'); | |
| $writable = $naviMenu->getAdminMenuWritableAuth($naviMenu->lno, $naviMenu->location, $naviMenu->accessMenu, $naviMenu->writeEnabledMenu); | |
| try { | |
| /** @var EpostReturnRequestResultDTO $result */ | |
| $result = \App::getInstance(EpostReturnAdmin::class) | |
| ->applyManualRequest((bool)$writable['check'], !gd_is_provider(), $orderGoodsSnos); | |
| // 자격 검증을 통과해 전송 단계까지 갔을 때만 남김 — 거부는 예외로 빠져 여기 오지 않음 | |
| $this->writePickupRequestAccessLog($orderGoodsSnos, $result); | |
| $this->json([ | |
| 'code' => 200, | |
| 'requestedCount' => count($orderGoodsSnos), | |
| 'successCount' => $result->getSuccessCount(), | |
| 'failureCount' => $result->getFailureCount(), | |
| 'excludedCount' => count($result->getExcludedOrderGoodsSnos()), | |
| ]); | |
| } catch (EpostReturnRequestRejectedException $e) { | |
| $reason = $e->getReason(); | |
| $this->json(['code' => 0, 'reason' => $reason->value, 'message' => __($reason->alertTitle()), 'subMessage' => __($reason->alertSubtitle())]); | |
| } catch (Throwable $e) { | |
| // 전송 도중 실패라도 고객 정보 조회·전송이 이미 일어났을 수 있어 접속기록은 남김(신청 결과가 없으니 대상 목록만) | |
| \App::getInstance('logger')->channel('epost')->error('우체국택배 수거 신청 실패', ['orderGoodsSnos' => $orderGoodsSnos, 'exception' => $e]); | |
| $this->writePickupRequestAccessLog($orderGoodsSnos, null); | |
| $this->json(['code' => 0, 'reason' => 'ERROR', 'message' => __('수거 신청에 실패하였습니다.'), 'subMessage' => __('잠시 후 다시 시도해 주세요.')]); | |
| } | |
| } | |
| /** | |
| * 수거 신청 접속기록 — 실행당 1건, 처리 대상은 data 에 담음 | |
| * | |
| * @param array $orderGoodsSnos 신청 대상 주문상품번호 | |
| * @param ?EpostReturnRequestResultDTO $result 신청 결과 — 전송 도중 실패로 없으면 null | |
| */ | |
| protected function writePickupRequestAccessLog(array $orderGoodsSnos, ?EpostReturnRequestResultDTO $result): void | |
| { | |
| $session = \App::getInstance('session'); | |
| \App::getInstance(EpostReturnAccessLogService::class)->write( | |
| __(self::LOG_MENU), | |
| __(self::LOG_PAGE), | |
| __(self::LOG_ACTION), | |
| (string)$session->get('manager.sno'), | |
| (string)$session->get('manager.managerId'), | |
| (string)$session->get('manager.scmNo'), | |
| preg_replace('#^/{2,}#', '/', (Request::getRequestUri() ?? '')), | |
| (string)Request::getReferer(), | |
| (string)Request::getRemoteAddress(), | |
| $result !== null ? [ | |
| 'orderGoodsSnos' => $orderGoodsSnos, | |
| 'successCount' => $result->getSuccessCount(), | |
| 'failureCount' => $result->getFailureCount(), | |
| 'excludedCount' => count($result->getExcludedOrderGoodsSnos()), | |
| ] : ['orderGoodsSnos' => $orderGoodsSnos, 'error' => true] | |
| ); | |
| } | |
| } | |
변경 전OrderChangePsController.php
| use App; | |
| use Bundle\Component\Godo\NaverPayAPI; | |
| use Component\Order\UserHandleAutoProcess; | |
| use Exception; | |
| use Request; | |
| case "back" : // 반품 | |
| try { | |
| $autoResult = ['approve' => [], 'reject' => []]; | |
| \DB::transaction(function () use ($orderReorderCalculation, $autoProcess, $postValue, &$autoResult) { | |
| // 고객 클레임 신청 건의 자동 승인/거절 처리 (관리자 반품 접수 = 'b') | |
| if (!empty($postValue['back']['statusCheck'])) { | |
| $orderGoodsSnoList = array_keys($postValue['back']['statusCheck']); | |
| // 트랜잭션 커밋 후 자동 승인/거절 건의 SMS/알림톡 자동 발송 | |
| $this->sendAutoApproveRejectSms($postValue['orderNo'], $autoResult); | |
| $script = "parent.opener.location.href='./order_view.php?orderNo=".$postValue['orderNo']."';parent.self.close();"; | |
| $this->layerNotReload(__('반품 접수가 되었습니다.'), $script); | |
| } catch (Exception $e) { | |
변경 후OrderChangePsController.php
| use App; | |
| use Bundle\Component\Godo\NaverPayAPI; | |
| use Component\Order\EpostReturnAdmin; | |
| use Component\Order\UserHandleAutoProcess; | |
| use Exception; | |
| use Request; | |
| case "back" : // 반품 | |
| try { | |
| $autoResult = ['approve' => [], 'reject' => []]; | |
| $epostReturnAcceptedSnos = \DB::transaction(function () use ($orderReorderCalculation, $autoProcess, $postValue, &$autoResult) { | |
| // 고객 클레임 신청 건의 자동 승인/거절 처리 (관리자 반품 접수 = 'b') | |
| if (!empty($postValue['back']['statusCheck'])) { | |
| $orderGoodsSnoList = array_keys($postValue['back']['statusCheck']); | |
| // 트랜잭션 커밋 후 자동 승인/거절 건의 SMS/알림톡 자동 발송 | |
| $this->sendAutoApproveRejectSms($postValue['orderNo'], $autoResult); | |
| // 우체국택배 수거 자동접수 — 클레임 행 INSERT 가 끝난 뒤(커밋 후)에만 호출, 미사용 상점은 정책을 먼저 직접 확인해 컴포넌트를 안 만듦 | |
| $godopostConfig = gd_policy(EpostReturnAdmin::POLICY_KEY); | |
| if (!empty($epostReturnAcceptedSnos) && !empty($godopostConfig['compdivcd']) && ($godopostConfig['autoAcceptFl'] ?? '') === 'y') { | |
| \App::getInstance(EpostReturnAdmin::class)->applyAutoAccept($epostReturnAcceptedSnos); | |
| } | |
| $script = "parent.opener.location.href='./order_view.php?orderNo=".$postValue['orderNo']."';parent.self.close();"; | |
| $this->layerNotReload(__('반품 접수가 되었습니다.'), $script); | |
| } catch (Exception $e) { | |
변경 전OrderPsController.php
| use Component\Godo\MyGodoSmsServerApi; | |
| use Component\Godo\NaverPayAPI; | |
| use Component\Member\Manager; | |
| use Component\Order\Order; | |
| use Component\Order\OrderAdmin; | |
| use Component\Order\OrderDelete; | |
| } | |
| } | |
| ); | |
| $this->layer(__('저장이 완료되었습니다.'), null, 2000); | |
| } catch (Exception $e) { | |
| if (Request::isAjax()) { | |
| } | |
| else { | |
| $aOrderGoodsSno = $order->approveUserHandle(Request::post()->toArray(), 'y'); // 주문 부분환불시 | |
| } | |
| $statusCheck = Request::post()->get('statusCheck', []); | |
| $config = ['smsAutoCodeType' => Code::ADMIN_APPROVAL]; | |
변경 후OrderPsController.php
| use Component\Godo\MyGodoSmsServerApi; | |
| use Component\Godo\NaverPayAPI; | |
| use Component\Member\Manager; | |
| use Component\Order\EpostReturnAdmin; | |
| use Component\Order\Order; | |
| use Component\Order\OrderAdmin; | |
| use Component\Order\OrderDelete; | |
| } | |
| } | |
| ); | |
| // 우체국택배 수거 자동접수 — 미사용 상점에서는 컴포넌트를 만들지 않도록 정책을 먼저 직접 확인함 | |
| $godopostConfig = gd_policy(EpostReturnAdmin::POLICY_KEY); | |
| if (!empty($godopostConfig['compdivcd']) && ($godopostConfig['autoAcceptFl'] ?? '') === 'y' | |
| && method_exists($order, 'getEpostReturnAcceptedSnos') && !empty($order->getEpostReturnAcceptedSnos())) { | |
| \App::getInstance(EpostReturnAdmin::class)->applyAutoAccept($order->getEpostReturnAcceptedSnos()); | |
| } | |
| $this->layer(__('저장이 완료되었습니다.'), null, 2000); | |
| } catch (Exception $e) { | |
| if (Request::isAjax()) { | |
| } | |
| else { | |
| $aOrderGoodsSno = $order->approveUserHandle(Request::post()->toArray(), 'y'); // 주문 부분환불시 | |
| // 우체국택배 수거 자동접수 — 미사용 상점에서는 컴포넌트를 만들지 않도록 정책을 먼저 직접 확인함 | |
| $godopostConfig = gd_policy(EpostReturnAdmin::POLICY_KEY); | |
| if (!empty($godopostConfig['compdivcd']) && ($godopostConfig['autoAcceptFl'] ?? '') === 'y' | |
| && Request::post()->get('statusMode') === 'b') { | |
| \App::getInstance(EpostReturnAdmin::class)->applyAutoAcceptByUserHandles( | |
| array_map(fn (string $item) => (int)explode(INT_DIVISION, $item)[2], Request::post()->get('statusCheck', [])) | |
| ); | |
| } | |
| } | |
| $statusCheck = Request::post()->get('statusCheck', []); | |
| $config = ['smsAutoCodeType' => Code::ADMIN_APPROVAL]; | |
변경 전PostIssueController.php
| */ | |
| namespace Bundle\Controller\Admin\Order; | |
| use Framework\Debug\Exception\AlertBackException; | |
| use Exception; | |
| use Globals; | |
| use Request; | |
| try { | |
| // --- 메뉴 설정 | |
| $this->callMenu('order', 'epostParcel', 'issue'); | |
| $this->addScript( | |
| [ | |
| 'jquery/jquery.multi_select_box.js', | |
| ] | |
| ); | |
| $godopost= gd_policy('order.godopost'); | |
| if(empty($godopost['compdivcd']) === true) { | |
| throw new AlertBackException(__("우체국택배 서비스 신청 후 이용 가능합니다.")); | |
| } | |
| // --- 모듈 호출 | |
변경 후PostIssueController.php
| */ | |
| namespace Bundle\Controller\Admin\Order; | |
| use Component\Order\EpostReturnAdmin; | |
| use Exception; | |
| use Framework\Debug\Exception\AlertBackException; | |
| use Globals; | |
| use Request; | |
| try { | |
| // --- 메뉴 설정 | |
| $this->callMenu('order', 'epostParcel', 'issue'); | |
| $this->setMenuCode('order', 'epostParcel', 'issue'); | |
| $this->addScript( | |
| [ | |
| 'jquery/jquery.multi_select_box.js', | |
| ] | |
| ); | |
| if (\App::getInstance(EpostReturnAdmin::class)->isUsageNotRequested()) { | |
| throw new AlertBackException(__('우체국택배 서비스 신청 후 이용 가능합니다.')); | |
| } | |
| // --- 모듈 호출 | |
변경 전PostPsController.php
| <?php | |
| /** | |
| * This is commercial software, only users who have purchased a valid license | |
| * and accept to the terms of the License Agreement can install and use this | |
| * program. | |
| * | |
| * Do not edit or add to this file if you wish to upgrade Godomall5 to newer | |
| * versions in the future. | |
| * | |
| * @copyright ⓒ 2016, NHN godo: Corp. | |
| * @link http://www.godo.co.kr | |
| */ | |
| namespace Bundle\Controller\Admin\Order; | |
| use Framework\Debug\Exception\LayerException; | |
| use Framework\Debug\Exception\AlertRedirectException; | |
| use Message; | |
| use Request; | |
| /** | |
| * 우체국택배연동 처리 페이지 | |
| * @author Young Eun Jung <atomyang@godo.co.kr> | |
| */ | |
| class PostPsController extends \Controller\Admin\Controller | |
| { | |
| /** | |
| * index | |
| * | |
| */ | |
| public function index() | |
| { | |
| $postValue = Request::post()->toArray(); | |
| $orderAdmin = \App::load('\\Component\\Order\\OrderAdmin'); | |
| try { | |
| switch (Request::request()->get('mode')) { | |
| // 우체국택배 송장 발급 | |
| case 'issue': | |
| $result = $orderAdmin->saveOrderInvoice($postValue); | |
| if(gd_count($result) > 0 ) { | |
| throw new LayerException(sprintf(__("필수 정보(수령자명,수령자 핸드폰번호,수령자 주소,수령자 우편번호,주문자명) 가 누락된 주문(%s) 외 우체국 택배 송장번호가 발급되었습니다."), gd_implode(",",$result)),null,null,null,6000); | |
| } else { | |
| throw new LayerException("우체국 택배 송장번호가 발급되었습니다."); | |
| } | |
| break; | |
| // 우체국택배 예약 | |
| case 'reserve': | |
| $orderAdmin->reserveOrderInvoice($postValue); | |
| throw new LayerException(__("우체국택배로 예약되었습니다.")); | |
| break; | |
| // 우체국택배 취소 | |
| case 'cancel': | |
| $orderAdmin->cancelOrderInvoice($postValue); | |
| throw new LayerException(__("송장발급이 취소 되었습니다.")); | |
| break; | |
| // 우체국수동업데이트 | |
| case 'manual': | |
| $godoPost = \App::load('\\Component\\Godo\\GodoPostServerApi'); | |
| $data = $godoPost->saveManualUpdate(); | |
| throw new LayerException(sprintf(__("주문 %s 건의 우체국 택배 배송상태를 수동 업데이트 하였습니다."), $data)); | |
| break; | |
| } | |
| } catch (\Exception $e) { | |
| throw new LayerException($e->getMessage()); | |
| } | |
| exit(); | |
| } | |
| } | |
변경 후PostPsController.php
| <?php | |
| /** | |
| * This is commercial software, only users who have purchased a valid license | |
| * and accept to the terms of the License Agreement can install and use this | |
| * program. | |
| * | |
| * Do not edit or add to this file if you wish to upgrade Godomall5 to newer | |
| * versions in the future. | |
| * | |
| * @copyright ⓒ 2016, NHN godo: Corp. | |
| * @link http://www.godo.co.kr | |
| */ | |
| namespace Bundle\Controller\Admin\Order; | |
| use Component\Order\EpostReturnAdmin; | |
| use Framework\Debug\Exception\LayerException; | |
| use Framework\Debug\Exception\AlertRedirectException; | |
| use Message; | |
| use Origin\Service\Order\EpostReturnAccessLogService; | |
| use Request; | |
| /** | |
| * 우체국택배연동 처리 페이지 | |
| * @author Young Eun Jung <atomyang@godo.co.kr> | |
| */ | |
| class PostPsController extends \Controller\Admin\Controller | |
| { | |
| private const LOG_MENU = '주문/배송>택배연동 서비스>우체국택배 신청/관리'; | |
| private const LOG_PAGE = '우체국택배 신청/관리'; | |
| private const LOG_ACTION = '배송상태 수동 업데이트'; | |
| /** | |
| * index | |
| * | |
| */ | |
| public function index() | |
| { | |
| $postValue = Request::post()->toArray(); | |
| $orderAdmin = \App::load('\\Component\\Order\\OrderAdmin'); | |
| try { | |
| switch (Request::request()->get('mode')) { | |
| // 우체국택배 송장 발급 | |
| case 'issue': | |
| $this->setMenuCode('order', 'epostParcel', 'issue'); | |
| if (\App::getInstance(EpostReturnAdmin::class)->isUsageNotRequested()) { | |
| throw new LayerException(__('우체국택배 서비스 신청 후 이용 가능합니다.')); | |
| } | |
| $result = $orderAdmin->saveOrderInvoice($postValue); | |
| if(gd_count($result) > 0 ) { | |
| throw new LayerException(sprintf(__("필수 정보(수령자명,수령자 핸드폰번호,수령자 주소,수령자 우편번호,주문자명) 가 누락된 주문(%s) 외 우체국 택배 송장번호가 발급되었습니다."), gd_implode(",",$result)),null,null,null,6000); | |
| } else { | |
| throw new LayerException("우체국 택배 송장번호가 발급되었습니다."); | |
| } | |
| break; | |
| // 우체국택배 예약 | |
| case 'reserve': | |
| $this->setMenuCode('order', 'epostParcel', 'reserve'); | |
| if (\App::getInstance(EpostReturnAdmin::class)->isUsageNotRequested()) { | |
| throw new LayerException(__('우체국택배 서비스 신청 후 이용 가능합니다.')); | |
| } | |
| $orderAdmin->reserveOrderInvoice($postValue); | |
| throw new LayerException(__("우체국택배로 예약되었습니다.")); | |
| break; | |
| // 우체국택배 취소 | |
| case 'cancel': | |
| $orderAdmin->cancelOrderInvoice($postValue); | |
| throw new LayerException(__("송장발급이 취소 되었습니다.")); | |
| break; | |
| // 우체국수동업데이트(Toast용 json 응답) — 검사 순서: 쓰기 권한 → 발송(01) → 반품(02) | |
| case 'manualUpdateJson': | |
| $this->callMenu('order', 'epostParcel', 'request'); | |
| $naviMenu = $this->getData('naviMenu'); | |
| $writable = $naviMenu->getAdminMenuWritableAuth($naviMenu->lno, $naviMenu->location, $naviMenu->accessMenu, $naviMenu->writeEnabledMenu); | |
| if (!$writable['check']) { | |
| $this->json(['code' => 0, 'reason' => 'NO_PERMISSION', 'message' => __('우체국택배 신청/관리의 쓰기 권한이 없습니다.'), 'subMessage' => __('권한은 대표운영자에게 문의하시기 바랍니다.')]); | |
| return; | |
| } | |
| $updated = (int)\App::load('\\Component\\Godo\\GodoPostServerApi')->saveManualUpdate(); | |
| // 반품(02) 재동기화 — 연동 신청을 마친 상점에만 조회할 이력이 생김. | |
| // 실패해도 이미 커밋된 발송(01) 갱신의 감사기록·응답은 이어가야 해 여기서 막음(원인은 resync() 자신이 로깅함) | |
| $epostApprovalNo = (string)(gd_policy(EpostReturnAdmin::POLICY_KEY)['compdivcd'] ?? ''); | |
| if ($epostApprovalNo !== '') { | |
| try { | |
| \App::getInstance(\Origin\Service\Order\EpostReturnResyncService::class) | |
| ->resync((int)\Globals::get('gLicense.godosno'), $epostApprovalNo); | |
| } catch (\Throwable $e) { | |
| } | |
| } | |
| $this->writeManualUpdateAccessLog($updated, $epostApprovalNo !== ''); | |
| $this->json(['code' => 200, 'updatedCount' => $updated]); | |
| return; | |
| // 수거신청 자동 접수 저장 — 검사 순서: 쓰기 권한 → 미신청 → 값 검증 → 저장 | |
| case 'saveAutoAccept': | |
| $this->callMenu('order', 'epostParcel', 'request'); | |
| $naviMenu = $this->getData('naviMenu'); | |
| $writable = $naviMenu->getAdminMenuWritableAuth($naviMenu->lno, $naviMenu->location, $naviMenu->accessMenu, $naviMenu->writeEnabledMenu); | |
| if (!$writable['check']) { | |
| $this->json(['code' => 0, 'reason' => 'NO_PERMISSION', 'message' => __('우체국택배 신청/관리의 쓰기 권한이 없습니다.'), 'subMessage' => __('권한은 대표운영자에게 문의하시기 바랍니다.')]); | |
| return; | |
| } | |
| $epostReturnAdmin = \App::getInstance(EpostReturnAdmin::class); | |
| if (empty(gd_policy(EpostReturnAdmin::POLICY_KEY)['compdivcd'])) { | |
| $this->json(['code' => 0, 'reason' => 'NOT_CONNECTED', 'message' => __('우체국택배 서비스 신청 후 이용 가능합니다.'), 'subMessage' => '']); | |
| return; | |
| } | |
| $autoAcceptFl = Request::post()->get('autoAcceptFl'); | |
| if (!in_array($autoAcceptFl, [EpostReturnAdmin::AUTO_ACCEPT_ENABLED, EpostReturnAdmin::AUTO_ACCEPT_DISABLED], true)) { | |
| $this->json(['code' => 0, 'reason' => 'INVALID_PARAM', 'message' => __('잘못된 요청입니다.'), 'subMessage' => ''], 400); | |
| } | |
| $enabled = $autoAcceptFl === EpostReturnAdmin::AUTO_ACCEPT_ENABLED; | |
| try { | |
| $epostReturnAdmin->setAutoAccept($enabled); | |
| } catch (\Throwable $e) { | |
| \App::getInstance('logger')->channel('epost')->error('우체국택배 자동접수 설정 저장 실패', ['exception' => $e]); | |
| $this->json(['code' => 0, 'reason' => 'ERROR', 'message' => __('저장에 실패하였습니다.'), 'subMessage' => __('잠시 후 다시 시도해 주세요.')]); | |
| return; | |
| } | |
| $this->json(['code' => 200]); | |
| return; | |
| } | |
| } catch (\Exception $e) { | |
| throw new LayerException($e->getMessage()); | |
| } | |
| exit(); | |
| } | |
| /** | |
| * 배송상태 수동 업데이트 접속기록 — 실행당 1건, 처리 대상은 data 에 담음 | |
| * | |
| * @param int $updatedCount 발송(01) 갱신 건수 | |
| * @param bool $returnResync 반품(02) 재동기화 실행 여부 | |
| */ | |
| protected function writeManualUpdateAccessLog(int $updatedCount, bool $returnResync): void | |
| { | |
| $session = \App::getInstance('session'); | |
| \App::getInstance(EpostReturnAccessLogService::class)->write( | |
| __(self::LOG_MENU), | |
| __(self::LOG_PAGE), | |
| __(self::LOG_ACTION), | |
| (string)$session->get('manager.sno'), | |
| (string)$session->get('manager.managerId'), | |
| (string)$session->get('manager.scmNo'), | |
| preg_replace('#^/{2,}#', '/', (Request::getRequestUri() ?? '')), | |
| (string)Request::getReferer(), | |
| (string)Request::getRemoteAddress(), | |
| ['updatedCount' => $updatedCount, 'returnResync' => $returnResync ? 'y' : 'n'] | |
| ); | |
| } | |
| } | |
변경 전PostRequestController.php
| */ | |
| namespace Bundle\Controller\Admin\Order; | |
| use Globals; | |
| use Request; | |
| class PostRequestController extends \Controller\Admin\Controller | |
| { | |
| { | |
| // --- 메뉴 설정 | |
| $this->callMenu('order', 'epostParcel', 'request'); | |
| // --- 관리자 디자인 템플릿 | |
| $gLicense = Globals::get('gLicense'); | |
| $this->setData('shopSno', $gLicense['godosno']); | |
| $this->setData('domainUrl', Request::getHostNoPort()); | |
| } | |
| } | |
변경 후PostRequestController.php
| */ | |
| namespace Bundle\Controller\Admin\Order; | |
| class PostRequestController extends \Controller\Admin\Controller | |
| { | |
| { | |
| // --- 메뉴 설정 | |
| $this->callMenu('order', 'epostParcel', 'request'); | |
| $this->setMenuCode('order', 'epostParcel', 'request'); | |
| $this->setNcdsSubBodyClass(); | |
| // 수거신청 자동 접수 — 직전 저장값·연동 여부·쓰기 권한 (배너 4종·신청 상태 표기는 상태 전달 API 확정 후) | |
| $policyConfig = gd_policy(\Component\Order\EpostReturnAdmin::POLICY_KEY); | |
| $naviMenu = $this->getData('naviMenu'); | |
| $writable = $naviMenu->getAdminMenuWritableAuth($naviMenu->lno, $naviMenu->location, $naviMenu->accessMenu, $naviMenu->writeEnabledMenu); | |
| $this->setData('epostConnected', !empty($policyConfig['compdivcd'])); | |
| $this->setData('autoAcceptSavedEnabled', ($policyConfig['autoAcceptFl'] ?? '') === \Component\Order\EpostReturnAdmin::AUTO_ACCEPT_ENABLED); | |
| $this->setData('hasWriteAuth', (bool)$writable['check']); | |
| // 우체국택배 서비스 신청 진행 상태 배너 — 표시 재료 가공·실패 처리 모두 컴포넌트 몫, 뷰는 출력만 함 | |
| $epostReturnAdmin = \App::getInstance(\Component\Order\EpostReturnAdmin::class); | |
| $this->setData('usageBanner', $epostReturnAdmin->getUsageBanner()); | |
| } | |
| } | |
변경 전PostReserveController.php
| */ | |
| namespace Bundle\Controller\Admin\Order; | |
| use Framework\Debug\Exception\AlertBackException; | |
| use Exception; | |
| use Globals; | |
| use Request; | |
| try { | |
| // --- 메뉴 설정 | |
| $this->callMenu('order', 'epostParcel', 'reserve'); | |
| $godopost= gd_policy('order.godopost'); | |
| if(empty($godopost['compdivcd']) === true) { | |
| throw new AlertBackException(__("우체국택배 서비스 신청 후 이용 가능합니다.")); | |
| } | |
| // --- 모듈 호출 | |
변경 후PostReserveController.php
| */ | |
| namespace Bundle\Controller\Admin\Order; | |
| use Component\Order\EpostReturnAdmin; | |
| use Exception; | |
| use Framework\Debug\Exception\AlertBackException; | |
| use Globals; | |
| use Request; | |
| try { | |
| // --- 메뉴 설정 | |
| $this->callMenu('order', 'epostParcel', 'reserve'); | |
| $this->setMenuCode('order', 'epostParcel', 'reserve'); | |
| if (\App::getInstance(EpostReturnAdmin::class)->isUsageNotRequested()) { | |
| throw new AlertBackException(__('우체국택배 서비스 신청 후 이용 가능합니다.')); | |
| } | |
| // --- 모듈 호출 | |
변경 전CancelListController.php
| use Exception; | |
| use Request; | |
| use Component\Validator\Validator; | |
| /** | |
| * 마이페이지 > 주문배송/조회 | |
| } | |
| } | |
| $this->setData('mode', $mode); | |
| $this->setData('orderData', gd_isset($orderData)); | |
변경 후CancelListController.php
| use Exception; | |
| use Request; | |
| use Component\Validator\Validator; | |
| use Component\Order\ReturnInvoice; | |
| /** | |
| * 마이페이지 > 주문배송/조회 | |
| } | |
| } | |
| // 우체국 반품 수거 반송추적 — 보충 조회 1회로 행에 서버 조립 URL 주입 (송장 원문 미노출) | |
| $orderData = \App::getInstance(ReturnInvoice::class)->assignTraceUrls($orderData); | |
| $this->setData('mode', $mode); | |
| $this->setData('orderData', gd_isset($orderData)); | |
변경 전IndexController.php
| use Request; | |
| use Session; | |
| use Component\Cart\ReorderCart; | |
| /** | |
| * Class MypageQnaController | |
| // 배송 중, 배송 완료된 상품 카운트해서 버튼 생성 여부 | |
| $orderData = $order->getOrderSettleButton($orderData); | |
| $this->setData('orderData', gd_isset($orderData)); | |
| // 사용자 반품/교환/환불 신청 사용여부 | |
| $orderBasic = gd_policy('order.basic'); | |
변경 후IndexController.php
| use Request; | |
| use Session; | |
| use Component\Cart\ReorderCart; | |
| use Component\Order\ReturnInvoice; | |
| /** | |
| * Class MypageQnaController | |
| // 배송 중, 배송 완료된 상품 카운트해서 버튼 생성 여부 | |
| $orderData = $order->getOrderSettleButton($orderData); | |
| // 우체국 반품 수거 반송추적 — 보충 조회 1회로 행에 서버 조립 URL 주입 (송장 원문 미노출) | |
| $orderData = \App::getInstance(ReturnInvoice::class)->assignTraceUrls($orderData); | |
| $this->setData('orderData', gd_isset($orderData)); | |
| // 사용자 반품/교환/환불 신청 사용여부 | |
| $orderBasic = gd_policy('order.basic'); | |
변경 전OrderListController.php
| use Exception; | |
| use Request; | |
| use Component\Cart\ReorderCart; | |
| /** | |
| * 마이페이지 > 주문배송/조회 | |
| // 배송 중, 배송 완료된 상품 카운트해서 버튼 생성 여부 | |
| $orderData = $order->getOrderSettleButton($orderData); | |
| $this->setData('orderData', gd_isset($orderData)); | |
| // 주문셀 합치는 조건 | |
변경 후OrderListController.php
| use Exception; | |
| use Request; | |
| use Component\Cart\ReorderCart; | |
| use Component\Order\ReturnInvoice; | |
| /** | |
| * 마이페이지 > 주문배송/조회 | |
| // 배송 중, 배송 완료된 상품 카운트해서 버튼 생성 여부 | |
| $orderData = $order->getOrderSettleButton($orderData); | |
| // 우체국 반품 수거 반송추적 — 보충 조회 1회로 행에 서버 조립 URL 주입 (송장 원문 미노출) | |
| $orderData = \App::getInstance(ReturnInvoice::class)->assignTraceUrls($orderData); | |
| $this->setData('orderData', gd_isset($orderData)); | |
| // 주문셀 합치는 조건 | |
변경 전OrderViewController.php
| use Request; | |
| use App; | |
| use Component\Cart\ReorderCart; | |
| /** | |
| * 주문 상세 보기 페이지 | |
| * | |
| // 배송 중, 배송 완료된 상품 카운트해서 버튼 생성 여부 | |
| $orderData = $order->getOrderSettleButton($orderData); | |
| $this->setData('orderData', $orderData); | |
| $orderData = $orderData[0]; | |
| // 회원할인에 회원 배송비 할인 추가 | |
변경 후OrderViewController.php
| use Request; | |
| use App; | |
| use Component\Cart\ReorderCart; | |
| use Component\Order\ReturnInvoice; | |
| /** | |
| * 주문 상세 보기 페이지 | |
| * | |
| // 배송 중, 배송 완료된 상품 카운트해서 버튼 생성 여부 | |
| $orderData = $order->getOrderSettleButton($orderData); | |
| // 우체국 반품 수거 반송추적 — 보충 조회 1회로 행에 서버 조립 URL 주입 (송장 원문 미노출) | |
| $orderData = \App::getInstance(ReturnInvoice::class)->assignTraceUrls($orderData); | |
| $this->setData('orderData', $orderData); | |
| $orderData = $orderData[0]; | |
| // 회원할인에 회원 배송비 할인 추가 | |
변경 전RefundListController.php
| use Request; | |
| use Framework\Utility\DateTimeUtils; | |
| use Framework\Utility\StringUtils; | |
| /** | |
| * 마이페이지 > 주문배송/조회 | |
| $mode = 'refund'; | |
| $orderData = $order->getOrderList(10, $wDate, $mode); | |
| } | |
| $this->setData('mode', $mode); | |
| $this->setData('orderData', gd_isset($orderData)); | |
변경 후RefundListController.php
| use Request; | |
| use Framework\Utility\DateTimeUtils; | |
| use Framework\Utility\StringUtils; | |
| use Component\Order\ReturnInvoice; | |
| /** | |
| * 마이페이지 > 주문배송/조회 | |
| $mode = 'refund'; | |
| $orderData = $order->getOrderList(10, $wDate, $mode); | |
| } | |
| // 우체국 반품 수거 반송추적 — 보충 조회 1회로 행에 서버 조립 URL 주입 (송장 원문 미노출) | |
| $orderData = \App::getInstance(ReturnInvoice::class)->assignTraceUrls($orderData); | |
| $this->setData('mode', $mode); | |
| $this->setData('orderData', gd_isset($orderData)); | |
변경 전OrderListController.php
| use Framework\Utility\StringUtils; | |
| use Request; | |
| use Globals; | |
| /** | |
| * 마이페이지 > 주문배송/조회 | |
| // 배송 중, 배송 완료된 상품 카운트해서 버튼 생성 여부 | |
| $orderData = $order->getOrderSettleButton($orderData); | |
| // 주문 리스트 정보 | |
| $this->setData('orderData', gd_isset($orderData)); | |
| $this->setData('pageName', 'list'); | |
변경 후OrderListController.php
| use Framework\Utility\StringUtils; | |
| use Request; | |
| use Globals; | |
| use Component\Order\ReturnInvoice; | |
| /** | |
| * 마이페이지 > 주문배송/조회 | |
| // 배송 중, 배송 완료된 상품 카운트해서 버튼 생성 여부 | |
| $orderData = $order->getOrderSettleButton($orderData); | |
| // 우체국 반품 수거 반송추적 — 보충 조회 1회로 행에 서버 조립 URL 주입 (송장 원문 미노출) | |
| $orderData = \App::getInstance(ReturnInvoice::class)->assignTraceUrls($orderData); | |
| // 주문 리스트 정보 | |
| $this->setData('orderData', gd_isset($orderData)); | |
| $this->setData('pageName', 'list'); | |
변경 전OrderViewController.php
| use App; | |
| use Session; | |
| use Component\Cart\ReorderCart; | |
| /** | |
| * 주문 상세 보기 페이지 | |
| // 배송 중, 배송 완료된 상품 카운트해서 버튼 생성 여부 | |
| $orderData = $order->getOrderSettleButton($orderData); | |
| $this->setData('orderData', $orderData); | |
| $ordersByRegisterDay = []; | |
변경 후OrderViewController.php
| use App; | |
| use Session; | |
| use Component\Cart\ReorderCart; | |
| use Component\Order\ReturnInvoice; | |
| /** | |
| * 주문 상세 보기 페이지 | |
| // 배송 중, 배송 완료된 상품 카운트해서 버튼 생성 여부 | |
| $orderData = $order->getOrderSettleButton($orderData); | |
| // 우체국 반품 수거 반송추적 — 보충 조회 1회로 행에 서버 조립 URL 주입 (송장 원문 미노출) | |
| $orderData = \App::getInstance(ReturnInvoice::class)->assignTraceUrls($orderData); | |
| $this->setData('orderData', $orderData); | |
| $ordersByRegisterDay = []; | |
변경 전EpostReturnSearchDTO.php
변경 후EpostReturnSearchDTO.php
| <?php | |
| /* | |
| * Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved | |
| * | |
| * Unauthorized copying or redistribution of this file in source and binary forms via any medium | |
| * is strictly prohibited. | |
| */ | |
| namespace Bundle\DTO\Order; | |
| use Origin\DTO\AbstractDTO; | |
| /** | |
| * 우체국택배 수거신청 대상 검색 조건 | |
| */ | |
| class EpostReturnSearchDTO extends AbstractDTO | |
| { | |
| /** | |
| * 기존 주문 목록 검색어 항목을 승계함 | |
| */ | |
| public const SEARCH_FIELDS = [ | |
| 'o.orderNo' => '주문번호', | |
| 'og.invoiceNo' => '송장번호', | |
| 'og.goodsNm' => '상품명', | |
| 'og.goodsNo' => '상품코드', | |
| 'og.goodsCd' => '자체 상품코드', | |
| 'og.goodsModelNo' => '상품모델명', | |
| 'og.makerNm' => '제조사', | |
| 'oi.orderName' => '주문자명', | |
| 'oi.orderPhone' => '주문자 전화번호', | |
| 'oi.orderCellPhone' => '주문자 휴대폰번호', | |
| 'oi.orderEmail' => '주문자 이메일', | |
| 'oi.receiverName' => '수령자명', | |
| 'oi.receiverPhone' => '수령자 전화번호', | |
| 'oi.receiverCellPhone' => '수령자 휴대폰번호', | |
| 'o.bankSender' => '입금자명', | |
| 'm.memId' => '아이디', | |
| 'm.nickNm' => '닉네임', | |
| 'sm.companyNm' => '공급사명', | |
| ]; | |
| /** | |
| * 기존 처리일자 검색 토큰을 승계함 — 키가 토큰(.b/.e/.r 접미는 클레임 모드)이고 값은 화면 표기 라벨 | |
| */ | |
| public const DATE_FIELDS = [ | |
| 'o.regDt' => '주문일', | |
| 'og.paymentDt' => '결제확인일', | |
| 'og.invoiceDt' => '송장입력일', | |
| 'og.deliveryDt' => '배송일', | |
| 'og.deliveryCompleteDt' => '배송완료일', | |
| 'og.finishDt' => '구매확정일', | |
| 'oh.regDt.b' => '반품접수일', | |
| ]; | |
| /** | |
| * 기존 정렬 컬럼을 승계함 — 키가 정렬 컬럼이고 값은 화면 표기 라벨 | |
| */ | |
| public const SORT_FIELDS = [ | |
| 'o.regDt' => '주문일', | |
| 'oh.regDt' => '반품접수일', | |
| 'og.orderNo' => '주문번호', | |
| 'og.goodsNm' => '상품명', | |
| 'oi.orderName' => '주문자', | |
| 'o.settlePrice' => '총 결제금액', | |
| 'oi.receiverName' => '수령자', | |
| 'sm.companyNm' => '공급사', | |
| ]; | |
| /** | |
| * 검색어 매칭 방식 — 키가 matchType 값이고 값은 화면 표기 라벨 | |
| */ | |
| public const MATCH_TYPES = [ | |
| 'equalSearch' => '검색어 전체일치', | |
| 'fullLikeSearch' => '검색어 부분포함', | |
| ]; | |
| /** | |
| * 페이지 크기 선택지 — 상한은 PAGE_SIZE_MAX 와 같음 | |
| */ | |
| public const PAGE_SIZES = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 500]; | |
| /** | |
| * 주문상태 필터 — 키가 collectionStatuses 값이고 값은 화면 표기 라벨 | |
| */ | |
| public const COLLECTION_STATUSES = [ | |
| 'pending' => '반품접수', | |
| 'failed' => '반품접수(수거신청 실패)', | |
| ]; | |
| private const PAGE_MIN = 1; | |
| private const PAGE_SIZE_MIN = 1; | |
| private const PAGE_SIZE_MAX = 500; | |
| protected string $searchKey; | |
| protected string $keyword; | |
| protected string $matchType; | |
| protected string $scmFl; | |
| protected array $scmNos; | |
| protected string $treatDateFl; | |
| protected string $treatDateStart; | |
| protected string $treatDateEnd; | |
| protected array $collectionStatuses; | |
| protected string $sort; | |
| protected int $page; | |
| protected int $pageSize; | |
| /** | |
| * @param array $getValue 화면 검색 파라미터 | |
| */ | |
| public function __construct(array $getValue = []) | |
| { | |
| $this->searchKey = (string) ($getValue['searchKey'] ?? ''); | |
| $this->keyword = trim((string) ($getValue['keyword'] ?? '')); | |
| $this->matchType = (string) ($getValue['matchType'] ?? 'equalSearch'); | |
| $this->scmFl = (string) ($getValue['scmFl'] ?? 'all'); | |
| $this->scmNos = array_map('intval', (array) ($getValue['scmNos'] ?? [])); | |
| $this->treatDateFl = (string) ($getValue['treatDateFl'] ?? 'oh.regDt.b'); | |
| $this->treatDateStart = (string) ($getValue['treatDateStart'] ?? ''); | |
| $this->treatDateEnd = (string) ($getValue['treatDateEnd'] ?? ''); | |
| $this->collectionStatuses = (array) ($getValue['collectionStatuses'] ?? []); | |
| $this->sort = (string) ($getValue['sort'] ?? 'o.regDt desc'); | |
| // GET 파라미터는 문자열이고 0·음수 페이지는 OFFSET 오류를 만들므로 여기서 잘라냄 | |
| $this->page = max(self::PAGE_MIN, (int) ($getValue['page'] ?? self::PAGE_MIN)); | |
| $this->pageSize = min(self::PAGE_SIZE_MAX, max(self::PAGE_SIZE_MIN, (int) ($getValue['pageSize'] ?? 20))); | |
| } | |
| public function getSearchColumn(): ?string | |
| { | |
| if ($this->keyword === '') { | |
| return null; | |
| } | |
| return array_key_exists($this->searchKey, self::SEARCH_FIELDS) ? $this->searchKey : null; | |
| } | |
| public function getKeyword(): string | |
| { | |
| return $this->keyword; | |
| } | |
| public function isLikeMatch(): bool | |
| { | |
| return $this->matchType === 'fullLikeSearch'; | |
| } | |
| /** | |
| * @return ?bool true 본사만 · false 본사 제외 · null 무조건 | |
| */ | |
| public function getHeadquartersOnly(): ?bool | |
| { | |
| if ($this->scmFl === '0') { | |
| return true; | |
| } | |
| if ($this->scmFl === '1' && empty($this->scmNos)) { | |
| return false; | |
| } | |
| return null; | |
| } | |
| /** | |
| * @return array 공급사 번호 목록 (scmFl 이 1 이고 선택이 있을 때만) | |
| */ | |
| public function getScmNos(): array | |
| { | |
| return $this->scmFl === '1' ? $this->scmNos : []; | |
| } | |
| public function getDateColumn(): ?string | |
| { | |
| if ($this->treatDateStart === '' || $this->treatDateEnd === '' | |
| || !array_key_exists($this->treatDateFl, self::DATE_FIELDS)) { | |
| return null; | |
| } | |
| $suffix = substr($this->treatDateFl, -2); | |
| return in_array($suffix, ['.b', '.e', '.r'], true) | |
| ? substr($this->treatDateFl, 0, -2) | |
| : $this->treatDateFl; | |
| } | |
| public function getHandleMode(): ?string | |
| { | |
| if ($this->getDateColumn() === null) { | |
| return null; | |
| } | |
| $suffix = substr($this->treatDateFl, -2); | |
| return in_array($suffix, ['.b', '.e', '.r'], true) ? substr($suffix, -1) : null; | |
| } | |
| public function getDateStart(): string | |
| { | |
| return $this->treatDateStart; | |
| } | |
| public function getDateEnd(): string | |
| { | |
| return $this->treatDateEnd; | |
| } | |
| public function isPendingOnly(): bool | |
| { | |
| return in_array('pending', $this->collectionStatuses, true) | |
| && !in_array('failed', $this->collectionStatuses, true); | |
| } | |
| public function isFailedOnly(): bool | |
| { | |
| return in_array('failed', $this->collectionStatuses, true) | |
| && !in_array('pending', $this->collectionStatuses, true); | |
| } | |
| /** | |
| * @return array [정렬 컬럼, 방향] | |
| */ | |
| public function getSort(): array | |
| { | |
| $parts = preg_split('/\\s+/', trim($this->sort)); | |
| $column = $parts[0] ?? ''; | |
| $direction = strtolower($parts[1] ?? 'desc'); | |
| if (!array_key_exists($column, self::SORT_FIELDS) || !in_array($direction, ['asc', 'desc'], true)) { | |
| return ['o.regDt', 'desc']; | |
| } | |
| return [$column, $direction]; | |
| } | |
| public function getPage(): int | |
| { | |
| return $this->page; | |
| } | |
| public function getPageSize(): int | |
| { | |
| return $this->pageSize; | |
| } | |
| } | |
변경 전OrderGiftRepository.php
| class OrderGiftRepository | |
| { | |
| /** | |
| * 주문 사은품 정보 조회 | |
| * | |
| * SELECT es_gift.giftNm, es_orderGift.giveCnt | |
| * FROM es_orderGift | |
| * JOIN es_gift ON es_orderGift.giftNo = es_gift.giftNo | |
| * WHERE es_orderGift.orderNo = ? | |
| * GROUP BY es_orderGift.orderNo | |
| * LIMIT 1 | |
| * | |
| * @param string $orderNo | |
| * @return array | |
| */ | |
변경 후OrderGiftRepository.php
| class OrderGiftRepository | |
| { | |
| /** | |
| * 주문 목록의 사은품 행 조회 — 지급조건명·사은품명·수량 (그리드 사은품 컬럼용) | |
| * | |
| * @param array $orderNos 주문번호 목록 | |
| * @return array 행 목록 [orderNo, presentTitle, giftNm, giveCnt] | |
| */ | |
| public function findOrderGiftLinesByOrderNos(array $orderNos): array | |
| { | |
| return OrderGift::query() | |
| ->select('es_orderGift.orderNo', 'gp.presentTitle', 'g.giftNm', 'es_orderGift.giveCnt') | |
| ->leftJoin(DB_GIFT_PRESENT . ' AS gp', 'es_orderGift.presentSno', '=', 'gp.sno') | |
| ->join(DB_GIFT . ' AS g', 'es_orderGift.giftNo', '=', 'g.giftNo') | |
| ->whereIn('es_orderGift.orderNo', array_map('strval', $orderNos)) | |
| ->orderBy('es_orderGift.orderNo') | |
| ->get() | |
| ->toArray(); | |
| } | |
| /** | |
| * 주문 사은품 정보 조회 | |
| * | |
| * SELECT es_gift.giftNm, es_orderGift.giveCnt | |
| * FROM es_orderGift | |
| * JOIN es_gift ON es_orderGift.giftNo = es_gift.giftNo | |
| * WHERE es_orderGift.orderNo = ? | |
| * GROUP BY es_orderGift.orderNo | |
| * LIMIT 1 | |
| * | |
| * @param string $orderNo | |
| * @return array | |
| */ | |
변경 전OrderGoodsRepository.php
변경 후OrderGoodsRepository.php
| <?php | |
| /* | |
| * Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved | |
| * | |
| * Unauthorized copying or redistribution of this file in source and binary forms via any medium | |
| * is strictly prohibited. | |
| */ | |
| namespace Bundle\Repository\Order; | |
| use DTO\Order\EpostReturnSearchDTO; | |
| use Illuminate\Database\Eloquent\Builder; | |
| use Origin\Enum\Order\EpostReturnInvoiceStatus; | |
| use Origin\Enum\Order\OrderChannel; | |
| use Origin\Enum\Order\OrderStatusCode; | |
| use Origin\Model\Order\OrderGoods; | |
| /** | |
| * 주문상품(es_orderGoods) 조회 | |
| */ | |
| class OrderGoodsRepository | |
| { | |
| /** | |
| * @return array 행 목록 [sno, orderNo, regDt, orderName, goodsNm, goodsCnt, orderStatus, | |
| * returnInvoiceStatus, returnInvoiceNo, orderDeliverySno, deliverySno, invoiceNo, companyNm, receiverName, scmNo, handleSno] | |
| */ | |
| public function findTargets(EpostReturnSearchDTO $search): array | |
| { | |
| [$sortColumn, $sortDirection] = $search->getSort(); | |
| return $this->buildTargetQuery($search) | |
| ->groupBy('og.sno') | |
| ->orderBy($sortColumn, $sortDirection) | |
| ->orderBy('og.sno', 'desc') | |
| ->forPage($search->getPage(), $search->getPageSize()) | |
| ->get([ | |
| 'og.sno', 'og.orderNo', 'o.regDt', 'oi.orderName', 'og.goodsNm', 'og.goodsCnt', | |
| 'og.orderStatus', 'rt.returnInvoiceStatus', 'rt.returnInvoiceNo', 'og.orderDeliverySno', 'sdb.sno AS deliverySno', | |
| 'og.invoiceNo', 'sm.companyNm', 'oi.receiverName', 'og.scmNo', 'og.handleSno', | |
| 'og.goodsNo', 'og.optionInfo', 'og.optionTextInfo', 'o.firstSaleFl', 'o.memNo', 'm.memId', | |
| 'g.imagePath', 'g.imageStorage', 'gi.imageName', 'gi.imageUrl', 'gi.goodsImageStorage', | |
| ]) | |
| ->toArray(); | |
| } | |
| /** | |
| * 요청 상품주문번호 중 대상 술어를 통과하는 것만 돌려줌 — 목록과 같은 술어를 지남 | |
| * | |
| * @param array $orderGoodsSnos 상품주문번호 목록 | |
| * @return array 통과한 상품주문번호 목록 | |
| */ | |
| public function findTargetSnos(array $orderGoodsSnos): array | |
| { | |
| return $this->buildTargetQuery(null) | |
| ->whereIn('og.sno', array_map('intval', $orderGoodsSnos)) | |
| ->distinct() | |
| ->pluck('og.sno') | |
| ->all(); | |
| } | |
| /** | |
| * 신청 sno(og.userHandleSno) 기준으로 대상 조건을 통과하는 상품주문번호를 돌려줌 — 목록과 같은 대상 조건을 지남 | |
| * | |
| * @param array $userHandleSnos 고객 신청 sno 목록 | |
| * @return array 통과한 상품주문번호 목록 | |
| */ | |
| public function findTargetSnosByUserHandleSnos(array $userHandleSnos): array | |
| { | |
| return $this->buildTargetQuery(null) | |
| ->whereIn('og.userHandleSno', $userHandleSnos) | |
| ->distinct() | |
| ->pluck('og.sno') | |
| ->all(); | |
| } | |
| public function countTargets(EpostReturnSearchDTO $search): int | |
| { | |
| // oi.orderInfoCd=1 행이 한 주문에 여럿일 수 있어 주문상품 기준으로 센다 | |
| return $this->buildTargetQuery($search)->distinct()->count('og.sno'); | |
| } | |
| /** | |
| * 수거신청 대상 술어 — 목록·건수·게이트 재검증이 전부 이 한 지점을 지남 | |
| * | |
| * @param ?EpostReturnSearchDTO $search 화면 검색 조건 — null 이면 술어만 적용함 | |
| */ | |
| protected function buildTargetQuery(?EpostReturnSearchDTO $search): Builder | |
| { | |
| $query = OrderGoods::query() | |
| ->from(DB_ORDER_GOODS . ' AS og') | |
| ->join(DB_ORDER . ' AS o', 'o.orderNo', '=', 'og.orderNo') | |
| ->join(DB_ORDER_INFO . ' AS oi', function ($join) { | |
| $join->on('oi.orderNo', '=', 'og.orderNo') | |
| ->where('oi.orderInfoCd', 1); | |
| }) | |
| ->join(DB_ORDER_HANDLE . ' AS oh', function ($join) { | |
| $join->on('oh.sno', '=', 'og.handleSno') | |
| ->on('oh.orderNo', '=', 'og.orderNo'); | |
| }) | |
| ->leftJoin(DB_ORDER_RETURN_INVOICE . ' AS rt', 'rt.orderGoodsNo', '=', 'og.sno') | |
| ->leftJoin(DB_ORDER_DELIVERY . ' AS od', 'od.sno', '=', 'og.orderDeliverySno') | |
| // 배송번호 = 상품별 배송비 코드(배송비조건 sno) — 삭제된 코드는 공란 처리라 존재 확인 조인을 거침 | |
| ->leftJoin(DB_SCM_DELIVERY_BASIC . ' AS sdb', 'sdb.sno', '=', 'od.deliverySno') | |
| ->leftJoin(DB_SCM_MANAGE . ' AS sm', 'sm.scmNo', '=', 'og.scmNo') | |
| ->leftJoin(DB_MEMBER . ' AS m', 'm.memNo', '=', 'o.memNo') | |
| ->leftJoin(DB_GOODS . ' AS g', 'g.goodsNo', '=', 'og.goodsNo') | |
| ->leftJoin(DB_GOODS_IMAGE . ' AS gi', function ($join) { | |
| $join->on('gi.goodsNo', '=', 'og.goodsNo') | |
| ->where('gi.imageKind', 'list'); | |
| }) | |
| ->where('og.orderStatus', OrderStatusCode::BACK_REQUEST->value) | |
| ->where('og.invoiceCompanySno', DEFAULT_CODE_GODOPOST) | |
| ->whereRaw("TRIM(og.invoiceNo) != ''") | |
| ->where('o.orderChannelFl', '!=', OrderChannel::NAVERPAY->value) | |
| ->where(function ($where) { | |
| $where->whereNull('rt.returnInvoiceStatus') | |
| ->orWhere('rt.returnInvoiceStatus', EpostReturnInvoiceStatus::FAILED->value); | |
| }); | |
| if ($search !== null) { | |
| $this->applyStatusFilter($query, $search); | |
| $this->applyScmFilter($query, $search); | |
| $this->applyDateFilter($query, $search); | |
| $this->applyKeywordFilter($query, $search); | |
| } | |
| return $query; | |
| } | |
| protected function applyStatusFilter(Builder $query, EpostReturnSearchDTO $search): void | |
| { | |
| if ($search->isPendingOnly()) { | |
| $query->whereNull('rt.returnInvoiceStatus'); | |
| } elseif ($search->isFailedOnly()) { | |
| $query->where('rt.returnInvoiceStatus', EpostReturnInvoiceStatus::FAILED->value); | |
| } | |
| } | |
| protected function applyScmFilter(Builder $query, EpostReturnSearchDTO $search): void | |
| { | |
| $headquartersOnly = $search->getHeadquartersOnly(); | |
| if ($headquartersOnly === true) { | |
| $query->where('og.scmNo', DEFAULT_CODE_SCMNO); | |
| } elseif ($headquartersOnly === false) { | |
| $query->where('og.scmNo', '!=', DEFAULT_CODE_SCMNO); | |
| } elseif (!empty($search->getScmNos())) { | |
| $query->whereIn('og.scmNo', $search->getScmNos()); | |
| } | |
| } | |
| protected function applyDateFilter(Builder $query, EpostReturnSearchDTO $search): void | |
| { | |
| $column = $search->getDateColumn(); | |
| if ($column === null) { | |
| return; | |
| } | |
| if ($search->getHandleMode() !== null) { | |
| $query->where('oh.handleMode', $search->getHandleMode()); | |
| } | |
| // 종료일이 YYYY-MM-DD만 오면 00:00:00으로 해석되어 종료일 당일 데이터가 전부 빠지므로 하루 끝까지 포함시킴 | |
| $query->whereBetween($column, [$search->getDateStart(), $search->getDateEnd() . ' 23:59:59']); | |
| } | |
| protected function applyKeywordFilter(Builder $query, EpostReturnSearchDTO $search): void | |
| { | |
| $column = $search->getSearchColumn(); | |
| if ($column === null) { | |
| return; | |
| } | |
| if ($search->isLikeMatch()) { | |
| $query->where($column, 'LIKE', '%' . addcslashes($search->getKeyword(), '%_\\') . '%'); | |
| } else { | |
| $query->where($column, $search->getKeyword()); | |
| } | |
| } | |
| } | |
변경 전_order_goods_list.html
| </div> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <a href="#" class="btn_gray_small js_btn_order_delivery"><span>{=__('수취확인')}</span></a> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 배송추적 | |
| $('.js_btn_delivery_trace').click(function (e) { | |
| win = gd_popup({ | |
변경 후_order_goods_list.html
| </div> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ..returnTraceUrl }--> | |
| ({=__(..deliveryMethodFlText)}) - <a href="{..returnTraceUrl}" class="btn_gray_small js_btn_return_trace"><span>{=__('반송추적')}</span></a> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <a href="#" class="btn_gray_small js_btn_order_delivery"><span>{=__('수취확인')}</span></a> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 반송추적 — 서버 조립 URL 팝업 (송장 원문 미노출) | |
| $('.js_btn_return_trace').click(function (e) { | |
| e.preventDefault(); | |
| win = gd_popup({ | |
| url: $(this).attr('href'), | |
| target: 'returnTrace', | |
| width: 650, | |
| height: 660, | |
| resizable: 'yes', | |
| scrollbars: 'yes' | |
| }); | |
| win.focus(); | |
| return win; | |
| }); | |
| // 배송추적 | |
| $('.js_btn_delivery_trace').click(function (e) { | |
| win = gd_popup({ | |
변경 전_reorder_goods_list.html
| </div> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <a href="#" class="btn_gray_small js_btn_order_delivery"><span>{=__('수취확인')}</span></a> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 배송추적 | |
| $('.js_btn_delivery_trace').click(function (e) { | |
| win = gd_popup({ | |
변경 후_reorder_goods_list.html
| </div> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ..returnTraceUrl }--> | |
| ({=__(..deliveryMethodFlText)}) - <a href="{..returnTraceUrl}" class="btn_gray_small js_btn_return_trace"><span>{=__('반송추적')}</span></a> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <a href="#" class="btn_gray_small js_btn_order_delivery"><span>{=__('수취확인')}</span></a> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 반송추적 — 서버 조립 URL 팝업 (송장 원문 미노출) | |
| $('.js_btn_return_trace').click(function (e) { | |
| e.preventDefault(); | |
| win = gd_popup({ | |
| url: $(this).attr('href'), | |
| target: 'returnTrace', | |
| width: 650, | |
| height: 660, | |
| resizable: 'yes', | |
| scrollbars: 'yes' | |
| }); | |
| win.focus(); | |
| return win; | |
| }); | |
| // 배송추적 | |
| $('.js_btn_delivery_trace').click(function (e) { | |
| win = gd_popup({ | |
변경 전_order_goods_list.html
| </div> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <a href="#" class="btn_gray_small js_btn_order_delivery"><span>{=__('수취확인')}</span></a> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 배송추적 | |
| $('.js_btn_delivery_trace').click(function (e) { | |
| win = gd_popup({ | |
변경 후_order_goods_list.html
| </div> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ..returnTraceUrl }--> | |
| ({=__(..deliveryMethodFlText)}) - <a href="{..returnTraceUrl}" class="btn_gray_small js_btn_return_trace"><span>{=__('반송추적')}</span></a> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <a href="#" class="btn_gray_small js_btn_order_delivery"><span>{=__('수취확인')}</span></a> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 반송추적 — 서버 조립 URL 팝업 (송장 원문 미노출) | |
| $('.js_btn_return_trace').click(function (e) { | |
| e.preventDefault(); | |
| win = gd_popup({ | |
| url: $(this).attr('href'), | |
| target: 'returnTrace', | |
| width: 650, | |
| height: 660, | |
| resizable: 'yes', | |
| scrollbars: 'yes' | |
| }); | |
| win.focus(); | |
| return win; | |
| }); | |
| // 배송추적 | |
| $('.js_btn_delivery_trace').click(function (e) { | |
| win = gd_popup({ | |
변경 전_reorder_goods_list.html
| </div> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <a href="#" class="btn_gray_small js_btn_order_delivery"><span>{=__('수취확인')}</span></a> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 배송추적 | |
| $('.js_btn_delivery_trace').click(function (e) { | |
| win = gd_popup({ | |
변경 후_reorder_goods_list.html
| </div> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ..returnTraceUrl }--> | |
| ({=__(..deliveryMethodFlText)}) - <a href="{..returnTraceUrl}" class="btn_gray_small js_btn_return_trace"><span>{=__('반송추적')}</span></a> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <a href="#" class="btn_gray_small js_btn_order_delivery"><span>{=__('수취확인')}</span></a> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 반송추적 — 서버 조립 URL 팝업 (송장 원문 미노출) | |
| $('.js_btn_return_trace').click(function (e) { | |
| e.preventDefault(); | |
| win = gd_popup({ | |
| url: $(this).attr('href'), | |
| target: 'returnTrace', | |
| width: 650, | |
| height: 660, | |
| resizable: 'yes', | |
| scrollbars: 'yes' | |
| }); | |
| win.focus(); | |
| return win; | |
| }); | |
| // 배송추적 | |
| $('.js_btn_delivery_trace').click(function (e) { | |
| win = gd_popup({ | |
변경 전_order_goods_list.html
| </span> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <div class="button"><a href="#" class="normal-btn small1 btn-order-delivery"><em>{=__('수취확인')}</em></a></div> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 배송추적 | |
| $('.btn-delivery-trace').click(function (e) { | |
| win = popup({ | |
변경 후_order_goods_list.html
| </span> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ..returnTraceUrl }--> | |
| ({=__(..deliveryMethodFlText)}) - <span class="button"><a href="{..returnTraceUrl}" class="normal-btn small1 btn-return-trace"><em>{=__('반송추적')}</em></a></span> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <div class="button"><a href="#" class="normal-btn small1 btn-order-delivery"><em>{=__('수취확인')}</em></a></div> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 반송추적 — 서버 조립 URL 팝업 (송장 원문 미노출) | |
| $('.btn-return-trace').click(function (e) { | |
| e.preventDefault(); | |
| win = popup({ | |
| url: $(this).attr('href'), | |
| target: 'returnTrace', | |
| width: 650, | |
| height: 660, | |
| resizable: 'yes', | |
| scrollbars: 'yes' | |
| }); | |
| win.focus(); | |
| return win; | |
| }); | |
| // 배송추적 | |
| $('.btn-delivery-trace').click(function (e) { | |
| win = popup({ | |
변경 전_reorder_goods_list.html
| </span> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <div class="button"><a href="#" class="normal-btn small1 btn-order-delivery"><em>{=__('수취확인')}</em></a></div> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 배송추적 | |
| $('.btn-delivery-trace').click(function (e) { | |
| win = popup({ | |
변경 후_reorder_goods_list.html
| </span> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ..returnTraceUrl }--> | |
| ({=__(..deliveryMethodFlText)}) - <span class="button"><a href="{..returnTraceUrl}" class="normal-btn small1 btn-return-trace"><em>{=__('반송추적')}</em></a></span> | |
| <!--{ : }--> | |
| <div>({=__(..deliveryMethodFlText)})</div> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ..orderStatus == 'd1' }--> | |
| <div class="button"><a href="#" class="normal-btn small1 btn-order-delivery"><em>{=__('수취확인')}</em></a></div> | |
| <!--{ / }--> | |
| } | |
| }); | |
| // 반송추적 — 서버 조립 URL 팝업 (송장 원문 미노출) | |
| $('.btn-return-trace').click(function (e) { | |
| e.preventDefault(); | |
| win = popup({ | |
| url: $(this).attr('href'), | |
| target: 'returnTrace', | |
| width: 650, | |
| height: 660, | |
| resizable: 'yes', | |
| scrollbars: 'yes' | |
| }); | |
| win.focus(); | |
| return win; | |
| }); | |
| // 배송추적 | |
| $('.btn-delivery-trace').click(function (e) { | |
| win = popup({ | |
변경 전_order_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <li> | |
| <a class="delivery_trace_btn" style="max-width:100px; overflow:hidden;">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="delivery_order_btn btn_order_delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |
변경 후_order_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ...returnTraceUrl }--> | |
| <li><a href="{...returnTraceUrl}" class="delivery_trace_btn" target="_blank">{=__('반송추적')}</a></li> | |
| <!--{ : }--> | |
| <li> | |
| <a class="delivery_trace_btn" style="max-width:100px; overflow:hidden;">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="delivery_order_btn btn_order_delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |
변경 전_reorder_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <li> | |
| <a class="delivery_trace_btn" style="max-width:100px; overflow:hidden;">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="delivery_order_btn btn_order_delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |
변경 후_reorder_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ...returnTraceUrl }--> | |
| <li><a href="{...returnTraceUrl}" class="delivery_trace_btn" target="_blank">{=__('반송추적')}</a></li> | |
| <!--{ : }--> | |
| <li> | |
| <a class="delivery_trace_btn" style="max-width:100px; overflow:hidden;">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="delivery_order_btn btn_order_delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |
변경 전_order_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <li> | |
| <a class="delivery_trace_btn" style="max-width:100px; overflow:hidden;">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="delivery_order_btn btn_order_delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |
변경 후_order_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ...returnTraceUrl }--> | |
| <li><a href="{...returnTraceUrl}" class="delivery_trace_btn" target="_blank">{=__('반송추적')}</a></li> | |
| <!--{ : }--> | |
| <li> | |
| <a class="delivery_trace_btn" style="max-width:100px; overflow:hidden;">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="delivery_order_btn btn_order_delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |
변경 전_reorder_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <li> | |
| <a class="delivery_trace_btn" style="max-width:100px; overflow:hidden;">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="delivery_order_btn btn_order_delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |
변경 후_reorder_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ...returnTraceUrl }--> | |
| <li><a href="{...returnTraceUrl}" class="delivery_trace_btn" target="_blank">{=__('반송추적')}</a></li> | |
| <!--{ : }--> | |
| <li> | |
| <a class="delivery_trace_btn" style="max-width:100px; overflow:hidden;">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="delivery_order_btn btn_order_delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |
변경 전_order_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <li> | |
| <a class="btn btn_w">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="btn btn_y btn-order-delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |
변경 후_order_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ...returnTraceUrl }--> | |
| <li><a href="{...returnTraceUrl}" class="btn btn_w" target="_blank">{=__('반송추적')}</a></li> | |
| <!--{ : }--> | |
| <li> | |
| <a class="btn btn_w">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="btn btn_y btn-order-delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |
변경 전_reorder_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <li> | |
| <a class="btn btn_w">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="btn btn_y btn-order-delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |
변경 후_reorder_goods_list.html
| </li> | |
| <!--{ / }--> | |
| <!--{ : }--> | |
| <!--{ ? ...returnTraceUrl }--> | |
| <li><a href="{...returnTraceUrl}" class="btn btn_w" target="_blank">{=__('반송추적')}</a></li> | |
| <!--{ : }--> | |
| <li> | |
| <a class="btn btn_w">{...deliveryMethodFlText}</a> | |
| </li> | |
| <!--{ / }--> | |
| <!--{ / }--> | |
| <!--{ ? ...orderStatus == 'd1' }--> | |
| <li><a href="#" class="btn btn_y btn-order-delivery" order-no="{..orderNo}" order-goodsno="{...sno}">{=__('수취확인')}</a></li> | |
| <!--{ / }--> | |