7.0.7 페이로드 전수 검증에서 발견된 error 4건·warn 14건을 릴리즈 전에 처리했다. 미출시 분량의 자체 결함(회원 통합검색 500, 관리자 스코프 403 의 500 위장 등)이므로 페이로드에 합류시키고, 출시 대상 26개 CHANGELOG 의 버전 섹션 날짜를 2026-08-18 로 정렬했다. - 통합검색 import 복원 + 이커머스 관리자 typed 403 catch 대칭(테스트 동반) - admin 판정 스니핑 사설 사본 전수 수렴(BoardResource) + 조건 스캔 테스트 - 스케줄 인터프리터 변형 표기(확장자·버전 접미사) 정규화 차단 - ckeditor5 참조 판정 배치화·fail-safe, 결제 플러그인 진단 페이로드 대칭 - 템플릿 다크 대비·상태 Select value-제어 + --production 재빌드 - Upgrade_7_0_7 잔존 중복 색인 정리 스텝, API 문서·i18n(ko/en/ja) 동기
271 lines
7.7 KiB
PHP
271 lines
7.7 KiB
PHP
<?php
|
|
|
|
namespace Modules\Sirsoft\Ecommerce\Repositories;
|
|
|
|
use App\Repositories\Concerns\PaginatesWithDeferredJoin;
|
|
use App\Repositories\Concerns\ResolvesSortSpec;
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
use Modules\Sirsoft\Ecommerce\Models\ExtraFeeTemplate;
|
|
use Modules\Sirsoft\Ecommerce\Repositories\Contracts\ExtraFeeTemplateRepositoryInterface;
|
|
|
|
/**
|
|
* 추가배송비 템플릿 Repository 구현체
|
|
*/
|
|
class ExtraFeeTemplateRepository implements ExtraFeeTemplateRepositoryInterface
|
|
{
|
|
use PaginatesWithDeferredJoin;
|
|
use ResolvesSortSpec;
|
|
|
|
/** 허용 정렬 컬럼 (ExtraFeeTemplate::scopeOrderByField 와 동일 집합) */
|
|
private const SORTABLE_COLUMNS = ['id', 'zipcode', 'fee', 'region', 'is_active', 'created_at', 'updated_at'];
|
|
|
|
public function __construct(
|
|
protected ExtraFeeTemplate $model
|
|
) {}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function find(int $id): ?ExtraFeeTemplate
|
|
{
|
|
return $this->model->find($id);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function findByZipcode(string $zipcode): ?ExtraFeeTemplate
|
|
{
|
|
return $this->model->where('zipcode', $zipcode)->first();
|
|
}
|
|
|
|
/**
|
|
* 여러 우편번호로 템플릿을 한 번에 조회
|
|
*
|
|
* @param array $zipcodes 우편번호 목록
|
|
* @return Collection
|
|
*/
|
|
public function findByZipcodes(array $zipcodes): Collection
|
|
{
|
|
if (empty($zipcodes)) {
|
|
return $this->model->newCollection();
|
|
}
|
|
|
|
return $this->model->whereIn('zipcode', $zipcodes)->get();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function getListWithFilters(array $filters, int $perPage = 20): LengthAwarePaginator
|
|
{
|
|
$query = $this->model->newQuery();
|
|
|
|
// 검색어 (우편번호 또는 지역명)
|
|
if (! empty($filters['search'])) {
|
|
$query->search($filters['search']);
|
|
}
|
|
|
|
// 지역 필터
|
|
if (! empty($filters['region'])) {
|
|
$query->inRegion($filters['region']);
|
|
}
|
|
|
|
// 사용여부 필터
|
|
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
|
|
$isActive = filter_var($filters['is_active'], FILTER_VALIDATE_BOOLEAN);
|
|
$query->where('is_active', $isActive);
|
|
}
|
|
|
|
// 정렬 (모델 scopeOrderByField 와 동일한 허용 집합)
|
|
$sort = $this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'zipcode', 'asc');
|
|
|
|
// 지연 조인: 우편번호 단위 행이라 건수가 커질 수 있고 description(text)이 있어
|
|
// OFFSET 이 훑는 구간에서 넓은 컬럼까지 읽힌다. inner 는 id 만 훑는다.
|
|
return $this->paginateWithDeferredJoin(
|
|
query: $query,
|
|
columns: ['*'],
|
|
sort: $sort,
|
|
perPage: $perPage,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function create(array $data): ExtraFeeTemplate
|
|
{
|
|
$data['created_by'] = auth()->id();
|
|
|
|
return $this->model->create($data);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function update(ExtraFeeTemplate $template, array $data): ExtraFeeTemplate
|
|
{
|
|
$data['updated_by'] = auth()->id();
|
|
$template->update($data);
|
|
|
|
return $template->fresh();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function delete(ExtraFeeTemplate $template): bool
|
|
{
|
|
return $template->delete();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function toggleActive(ExtraFeeTemplate $template): ExtraFeeTemplate
|
|
{
|
|
$template->update([
|
|
'is_active' => ! $template->is_active,
|
|
'updated_by' => auth()->id(),
|
|
]);
|
|
|
|
return $template->fresh();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function bulkDelete(array $ids): int
|
|
{
|
|
return $this->model->whereIn('id', $ids)->delete();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function bulkToggleActive(array $ids, bool $isActive): int
|
|
{
|
|
return $this->model
|
|
->whereIn('id', $ids)
|
|
->update([
|
|
'is_active' => $isActive,
|
|
'updated_by' => auth()->id(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function getActiveList(): Collection
|
|
{
|
|
// audit:allow query-unbounded-get reason: 추가배송비 템플릿은 운영자 등록 설정성 테이블 — 행 수가 운영자 행위(도서산간 우편번호 등록)에 묶여 데이터 증가에 비례하지 않는다
|
|
return $this->model
|
|
->active()
|
|
->orderBy('zipcode')
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function getAllAsExtraFeeSettings(): array
|
|
{
|
|
// audit:allow query-unbounded-get reason: 추가배송비 템플릿은 운영자 등록 설정성 테이블 — 행 수가 운영자 행위(도서산간 우편번호 등록)에 묶여 데이터 증가에 비례하지 않는다
|
|
return $this->model
|
|
->active()
|
|
->orderBy('zipcode')
|
|
->get()
|
|
->map(fn ($template) => $template->toExtraFeeSetting())
|
|
->toArray();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function bulkCreate(array $items): int
|
|
{
|
|
$userId = auth()->id();
|
|
$now = now();
|
|
$count = 0;
|
|
|
|
// Batch insert for performance
|
|
$chunks = array_chunk($items, 100);
|
|
|
|
foreach ($chunks as $chunk) {
|
|
$records = array_map(function ($item) use ($userId, $now) {
|
|
return [
|
|
'zipcode' => $item['zipcode'],
|
|
'fee' => $item['fee'] ?? 0,
|
|
'region' => $item['region'] ?? null,
|
|
'description' => $item['description'] ?? null,
|
|
'is_active' => $item['is_active'] ?? true,
|
|
'created_by' => $userId,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
}, $chunk);
|
|
|
|
// upsert: 우편번호 중복 시 업데이트
|
|
$this->model->upsert(
|
|
$records,
|
|
['zipcode'],
|
|
['fee', 'region', 'description', 'is_active', 'updated_by', 'updated_at']
|
|
);
|
|
|
|
$count += count($chunk);
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function getStatisticsByRegion(): array
|
|
{
|
|
$total = $this->model->count();
|
|
$active = $this->model->where('is_active', true)->count();
|
|
$inactive = $this->model->where('is_active', false)->count();
|
|
|
|
// 지역별 통계
|
|
$regionStats = $this->model
|
|
->selectRaw('region, COUNT(*) as count, AVG(fee) as avg_fee, MIN(fee) as min_fee, MAX(fee) as max_fee')
|
|
->whereNotNull('region')
|
|
->groupBy('region')
|
|
->orderBy('region')
|
|
->get()
|
|
->map(fn ($row) => [
|
|
'region' => $row->region,
|
|
'count' => $row->count,
|
|
'avg_fee' => round($row->avg_fee, 2),
|
|
'min_fee' => $row->min_fee,
|
|
'max_fee' => $row->max_fee,
|
|
])
|
|
->toArray();
|
|
|
|
return [
|
|
'total' => $total,
|
|
'active' => $active,
|
|
'inactive' => $inactive,
|
|
'by_region' => $regionStats,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* ID 목록으로 조회하고 ID 키 맵으로 반환합니다 (bulk activity log lookup).
|
|
*
|
|
* @param array<int, int> $ids ID 목록
|
|
* @return Collection
|
|
*/
|
|
public function findByIdsKeyed(array $ids): Collection
|
|
{
|
|
if (empty($ids)) {
|
|
return new Collection;
|
|
}
|
|
|
|
return ExtraFeeTemplate::whereIn('id', $ids)->get()->keyBy('id');
|
|
}
|
|
}
|