194 lines
6.2 KiB
PHP
194 lines
6.2 KiB
PHP
<?php
|
||
|
||
namespace App\Service\business;
|
||
|
||
use App\BaseApp\BaseService;
|
||
use App\Models\business\CatalogueModel;
|
||
use App\Models\business\CategoryModel;
|
||
use App\Models\business\ImageModel;
|
||
use App\Models\business\PriceSheetModel;
|
||
use App\Service\common\MediaUrlService;
|
||
use Exception;
|
||
use Psr\Container\ContainerExceptionInterface;
|
||
use Psr\Container\NotFoundExceptionInterface;
|
||
|
||
/**
|
||
* 商品图册
|
||
*/
|
||
class CatalogueService extends BaseService
|
||
{
|
||
private MediaUrlService $media;
|
||
|
||
public function __construct()
|
||
{
|
||
parent::__construct();
|
||
$this->model = CatalogueModel::class;
|
||
$this->selectField = ['id', 'title', 'category_id', 'cover', 'pdf', 'price', 'alias', 'identifier', 'status', 'created_at', 'updated_at'];
|
||
$this->queryField = ['title' => 'like', 'alias' => 'like', 'identifier' => 'like', 'status' => '='];
|
||
$this->media = MediaUrlService::getInstance();
|
||
}
|
||
|
||
/**
|
||
* 列表
|
||
*
|
||
* 两处沿用老行为:
|
||
* 1. 按分类筛选时连同该分类的子分类一起查,否则选了父分类会一条都搜不到
|
||
* 2. 报价单里 6 个历史材质列绝大多数为空,只把有值的列名回给前端(show_field),
|
||
* 前端据此决定表格显示哪几列
|
||
* @throws ContainerExceptionInterface
|
||
* @throws NotFoundExceptionInterface
|
||
*/
|
||
public function list(): array
|
||
{
|
||
$this->with = ['category', 'priceSheet'];
|
||
|
||
$categoryId = (int) request()->get('category_id', 0);
|
||
if ($categoryId > 0) {
|
||
$ids = CategoryModel::where('pid', $categoryId)->where('deleted_at', 0)->pluck('id')->all();
|
||
$ids[] = $categoryId;
|
||
$this->whereIn = ['category_id', $ids];
|
||
}
|
||
|
||
$result = $this->getPageList();
|
||
foreach ($result['items'] as &$item) {
|
||
$item['category_name'] = $item['category']['name'] ?? '';
|
||
unset($item['category']);
|
||
$item['cover'] = $this->media->toPublic($item['cover'] ?? '');
|
||
$item['pdf'] = $this->media->toPublic($item['pdf'] ?? '');
|
||
$item['show_field'] = $this->pickUsedMaterialFields($item['price_sheet'] ?? []);
|
||
}
|
||
unset($item);
|
||
return $result;
|
||
}
|
||
|
||
public function option(): mixed
|
||
{
|
||
$this->optionField = ['id', 'title as name', 'identifier'];
|
||
return $this->getOption();
|
||
}
|
||
|
||
/**
|
||
* 详情,带规格与两类相册
|
||
* @throws Exception
|
||
*/
|
||
public function detail($id): mixed
|
||
{
|
||
$info = $this->getDetail($id);
|
||
$info->cover = $this->media->toPublic($info->cover);
|
||
$info->pdf = $this->media->toPublic($info->pdf);
|
||
|
||
$priceSheet = PriceSheetModel::where('catalogue_id', $id)
|
||
->where('deleted_at', 0)
|
||
->orderBy('id')
|
||
->get()
|
||
->toArray();
|
||
$info->show_field = $this->pickUsedMaterialFields($priceSheet);
|
||
$info->price_sheet = $priceSheet;
|
||
$info->render_images = $this->imagesOf($id, ImageModel::TYPE_RENDER);
|
||
$info->physical_images = $this->imagesOf($id, ImageModel::TYPE_PHYSICAL);
|
||
return $info;
|
||
}
|
||
|
||
public function create($params): mixed
|
||
{
|
||
$params = $this->normalize($params);
|
||
$this->assertIdentifierUnique((string) ($params['identifier'] ?? ''));
|
||
return $this->insert($params);
|
||
}
|
||
|
||
/**
|
||
* @throws Exception
|
||
*/
|
||
public function update($id, $params): mixed
|
||
{
|
||
$params = $this->normalize($params);
|
||
if (array_key_exists('identifier', $params)) {
|
||
$this->assertIdentifierUnique((string) $params['identifier'], (int) $id);
|
||
}
|
||
return $this->save($id, $params);
|
||
}
|
||
|
||
/**
|
||
* 删除商品:连带软删它的规格与相册,否则会留下一堆查不到主体的孤儿数据
|
||
* @throws Exception
|
||
*/
|
||
public function delete($id): mixed
|
||
{
|
||
$ids = array_values(array_filter(array_map('intval', is_array($id) ? $id : [$id])));
|
||
if (empty($ids)) {
|
||
$this->utils->errorThrow('参数错误');
|
||
}
|
||
$now = time();
|
||
PriceSheetModel::whereIn('catalogue_id', $ids)->where('deleted_at', 0)
|
||
->update(['deleted_at' => $now, 'updated_at' => $now]);
|
||
ImageModel::whereIn('catalogue_id', $ids)->where('deleted_at', 0)
|
||
->update(['deleted_at' => $now, 'updated_at' => $now]);
|
||
return $this->del($ids);
|
||
}
|
||
|
||
/**
|
||
* @throws Exception
|
||
*/
|
||
public function status($id, $status): mixed
|
||
{
|
||
return $this->save($id, ['status' => (int) $status]);
|
||
}
|
||
|
||
/**
|
||
* 报价单里真正有值的材质列
|
||
*/
|
||
private function pickUsedMaterialFields(array $priceSheetRows): array
|
||
{
|
||
$used = [];
|
||
foreach ($priceSheetRows as $row) {
|
||
foreach (PriceSheetModel::MATERIAL_FIELDS as $field) {
|
||
if (!empty($row[$field] ?? '')) {
|
||
$used[$field] = true;
|
||
}
|
||
}
|
||
}
|
||
return array_keys($used);
|
||
}
|
||
|
||
private function imagesOf(int|string $catalogueId, int $type): array
|
||
{
|
||
$rows = ImageModel::where('catalogue_id', $catalogueId)
|
||
->where('type', $type)
|
||
->where('deleted_at', 0)
|
||
->orderBy('id')
|
||
->get(['id', 'url', 'type'])
|
||
->toArray();
|
||
$this->media->publicEach($rows, ['url']);
|
||
return $rows;
|
||
}
|
||
|
||
private function normalize(array $params): array
|
||
{
|
||
foreach (['cover', 'pdf'] as $field) {
|
||
if (array_key_exists($field, $params)) {
|
||
$params[$field] = $this->media->firstOf($params[$field]);
|
||
}
|
||
}
|
||
return $params;
|
||
}
|
||
|
||
/**
|
||
* 商品编号是小程序搜索和线下对单的依据,重复了就没法定位货品
|
||
* @throws Exception
|
||
*/
|
||
private function assertIdentifierUnique(string $identifier, int $exceptId = 0): void
|
||
{
|
||
$identifier = trim($identifier);
|
||
if ($identifier === '') {
|
||
return;
|
||
}
|
||
$exists = CatalogueModel::where('identifier', $identifier)
|
||
->where('deleted_at', 0)
|
||
->when($exceptId > 0, fn ($q) => $q->where('id', '<>', $exceptId))
|
||
->exists();
|
||
if ($exists) {
|
||
$this->utils->errorThrow('商品编号已存在:' . $identifier);
|
||
}
|
||
}
|
||
}
|