更新若干功能
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled

This commit is contained in:
2026-08-20 19:16:49 +08:00
parent 72bc6502eb
commit 4ddf5e3c5f
48 changed files with 1908 additions and 18 deletions

View File

@@ -9,6 +9,7 @@ use App\Models\business\ImageModel;
use App\Models\business\PriceSheetModel;
use App\Service\common\MediaUrlService;
use Exception;
use Illuminate\Support\Facades\DB;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
@@ -23,7 +24,7 @@ class CatalogueService extends BaseService
{
parent::__construct();
$this->model = CatalogueModel::class;
$this->selectField = ['id', 'title', 'category_id', 'cover', 'pdf', 'price', 'alias', 'identifier', 'status', 'created_at', 'updated_at'];
$this->selectField = ['id', 'title', 'category_id', 'cover', 'pdf', 'video', 'price', 'alias', 'identifier', 'status', 'created_at', 'updated_at'];
$this->queryField = ['title' => 'like', 'alias' => 'like', 'identifier' => 'like', 'status' => '='];
$this->media = MediaUrlService::getInstance();
}
@@ -55,6 +56,7 @@ class CatalogueService extends BaseService
unset($item['category']);
$item['cover'] = $this->media->toPublic($item['cover'] ?? '');
$item['pdf'] = $this->media->toPublic($item['pdf'] ?? '');
$item['video'] = $this->media->toPublic($item['video'] ?? '');
$item['show_field'] = $this->pickUsedMaterialFields($item['price_sheet'] ?? []);
}
unset($item);
@@ -76,6 +78,7 @@ class CatalogueService extends BaseService
$info = $this->getDetail($id);
$info->cover = $this->media->toPublic($info->cover);
$info->pdf = $this->media->toPublic($info->pdf);
$info->video = $this->media->toPublic($info->video);
$priceSheet = PriceSheetModel::where('catalogue_id', $id)
->where('deleted_at', 0)
@@ -134,6 +137,141 @@ class CatalogueService extends BaseService
return $this->save($id, ['status' => (int) $status]);
}
/**
* 导出商品 + 关联报价单两个工作表xlsx 由前端 ExcelJS 美化
*/
public function exportExcel(): array
{
$rows = CatalogueModel::where('deleted_at', 0)->orderByDesc('id')->get([
'id', 'title', 'identifier', 'alias', 'category_id', 'price', 'status',
])->toArray();
$byId = [];
foreach ($rows as &$row) {
$row['status'] = (int) ($row['status'] ?? 0) === 0 ? '上架' : '下架';
$byId[(int) $row['id']] = $row;
}
unset($row);
$ids = array_keys($byId);
$priceRows = [];
if (!empty($ids)) {
$sheets = PriceSheetModel::where('deleted_at', 0)
->whereIn('catalogue_id', $ids)
->orderBy('catalogue_id')
->orderBy('id')
->get(['id', 'catalogue_id', 'specification', 'dimension', 'routine', 'stock', 'status'])
->toArray();
foreach ($sheets as $sheet) {
$cat = $byId[(int) $sheet['catalogue_id']] ?? [];
$priceRows[] = [
'id' => $sheet['id'],
'catalogue_id' => $sheet['catalogue_id'],
'identifier' => $cat['identifier'] ?? '',
'title' => $cat['title'] ?? '',
'specification' => $sheet['specification'] ?? '',
'dimension' => $sheet['dimension'] ?? '',
'routine' => $sheet['routine'] ?? '',
'stock' => (int) ($sheet['stock'] ?? -1),
'status' => (int) ($sheet['status'] ?? 0) === 0 ? '启用' : '禁用',
];
}
}
return [
'filename' => 'catalogue.xlsx',
'title' => '商品图册',
'sheets' => [
[
'title' => '商品图册',
'sheet' => '商品',
'columns' => [
['key' => 'id', 'header' => 'ID', 'width' => 10, 'align' => 'center'],
['key' => 'title', 'header' => '商品名称', 'width' => 28],
['key' => 'identifier', 'header' => '商品编号', 'width' => 16],
['key' => 'alias', 'header' => '别名', 'width' => 24],
['key' => 'category_id', 'header' => '分类ID', 'width' => 12, 'align' => 'center'],
['key' => 'price', 'header' => '参考价', 'width' => 14, 'align' => 'right'],
['key' => 'status', 'header' => '状态', 'width' => 10, 'align' => 'center'],
],
'rows' => $rows,
],
[
'title' => '关联报价单',
'sheet' => '报价单',
'subtitle' => '按商品编号回写;库存 -1 表示不限',
'columns' => [
['key' => 'id', 'header' => 'ID', 'width' => 10, 'align' => 'center'],
['key' => 'catalogue_id', 'header' => '商品ID', 'width' => 12, 'align' => 'center'],
['key' => 'identifier', 'header' => '商品编号', 'width' => 16],
['key' => 'title', 'header' => '商品名称', 'width' => 24],
['key' => 'specification', 'header' => '规格', 'width' => 20],
['key' => 'dimension', 'header' => '尺寸', 'width' => 16],
['key' => 'routine', 'header' => '常规价', 'width' => 14, 'align' => 'right'],
['key' => 'stock', 'header' => '库存', 'width' => 10, 'align' => 'center'],
['key' => 'status', 'header' => '状态', 'width' => 10, 'align' => 'center'],
],
'rows' => $priceRows,
],
],
];
}
/**
* 导入商品 + 报价单:先落商品,再按编号挂规格
*/
public function importRows(array $rows, array $priceSheets = []): array
{
$created = 0;
$updated = 0;
$priceCreated = 0;
$priceUpdated = 0;
$now = time();
DB::connection('business')->beginTransaction();
try {
foreach ($rows as $row) {
if (!is_array($row)) {
continue;
}
$identifier = trim((string) ($row['identifier'] ?? $row['商品编号'] ?? ''));
$title = trim((string) ($row['title'] ?? $row['商品名称'] ?? ''));
if ($title === '') {
continue;
}
$payload = [
'title' => $title,
'identifier' => $identifier,
'alias' => (string) ($row['alias'] ?? $row['别名'] ?? ''),
'category_id' => (int) ($row['category_id'] ?? $row['分类ID'] ?? 0),
'price' => (string) ($row['price'] ?? $row['参考价'] ?? ''),
'status' => $this->parseStatus($row['status'] ?? $row['状态'] ?? 0),
'updated_at' => $now,
];
$exists = $identifier !== ''
? CatalogueModel::where('identifier', $identifier)->where('deleted_at', 0)->first()
: null;
if (!empty($exists)) {
CatalogueModel::where('id', $exists['id'])->update($payload);
$updated++;
} else {
$payload['created_at'] = $now;
CatalogueModel::insert($payload);
$created++;
}
}
[$priceCreated, $priceUpdated] = $this->importPriceSheets($priceSheets, $now);
DB::connection('business')->commit();
} catch (Exception $e) {
DB::connection('business')->rollBack();
$this->utils->errorThrow($e->getMessage());
}
return [
'created' => $created,
'updated' => $updated,
'price_created' => $priceCreated,
'price_updated' => $priceUpdated,
];
}
/**
* 报价单里真正有值的材质列
*/
@@ -164,7 +302,7 @@ class CatalogueService extends BaseService
private function normalize(array $params): array
{
foreach (['cover', 'pdf'] as $field) {
foreach (['cover', 'pdf', 'video'] as $field) {
if (array_key_exists($field, $params)) {
$params[$field] = $this->media->firstOf($params[$field]);
}
@@ -190,4 +328,100 @@ class CatalogueService extends BaseService
$this->utils->errorThrow('商品编号已存在:' . $identifier);
}
}
/**
* 导入报价单优先商品编号其次商品ID同商品同规格同尺寸则更新
*/
private function importPriceSheets(array $rows, int $now): array
{
$created = 0;
$updated = 0;
foreach ($rows as $row) {
if (!is_array($row)) {
continue;
}
$catalogueId = $this->resolveCatalogueId($row);
$specification = trim((string) ($row['specification'] ?? $row['规格'] ?? ''));
if ($catalogueId <= 0 || $specification === '') {
continue;
}
$dimension = (string) ($row['dimension'] ?? $row['尺寸'] ?? '');
$payload = [
'catalogue_id' => $catalogueId,
'specification' => $specification,
'dimension' => $dimension,
'routine' => (string) ($row['routine'] ?? $row['常规价'] ?? ''),
'stock' => $this->parseStock($row['stock'] ?? $row['库存'] ?? -1),
'status' => $this->parseStatus($row['status'] ?? $row['状态'] ?? 0),
'updated_at' => $now,
];
$id = (int) ($row['id'] ?? $row['ID'] ?? 0);
$exists = $id > 0
? PriceSheetModel::where('id', $id)->where('catalogue_id', $catalogueId)->where('deleted_at', 0)->first()
: null;
if (empty($exists)) {
$exists = PriceSheetModel::where('catalogue_id', $catalogueId)
->where('specification', $specification)
->where('dimension', $dimension)
->where('deleted_at', 0)
->first();
}
if (!empty($exists)) {
PriceSheetModel::where('id', $exists['id'])->update($payload);
$updated++;
} else {
$payload['created_at'] = $now;
PriceSheetModel::insert($payload);
$created++;
}
}
return [$created, $updated];
}
/**
* 报价单行挂到哪件商品编号优先兼容导出里的商品ID
*/
private function resolveCatalogueId(array $row): int
{
$identifier = trim((string) ($row['identifier'] ?? $row['商品编号'] ?? ''));
if ($identifier !== '') {
$cat = CatalogueModel::where('identifier', $identifier)->where('deleted_at', 0)->first();
if (!empty($cat)) {
return (int) $cat['id'];
}
}
$id = (int) ($row['catalogue_id'] ?? $row['商品ID'] ?? 0);
if ($id <= 0) {
return 0;
}
$exists = CatalogueModel::where('id', $id)->where('deleted_at', 0)->exists();
return $exists ? $id : 0;
}
/**
* Excel 状态列可能是「上架/下架」或 0/1,统一成库里的数字
*/
private function parseStatus(mixed $value): int
{
$raw = trim((string) $value);
if (in_array($raw, ['上架', '启用', '正常', '0', ''], true)) {
return 0;
}
if (in_array($raw, ['下架', '禁用', '1'], true)) {
return 1;
}
return ((int) $raw) === 1 ? 1 : 0;
}
/**
* 库存:空 / 不限 -1
*/
private function parseStock(mixed $value): int
{
$raw = trim((string) $value);
if ($raw === '' || in_array($raw, ['不限', '无限', '-1'], true)) {
return -1;
}
return (int) $raw;
}
}