215 lines
6.4 KiB
PHP
215 lines
6.4 KiB
PHP
<?php
|
||
|
||
namespace App\Service\business;
|
||
|
||
use App\BaseApp\BaseService;
|
||
use App\Models\business\CatalogueModel;
|
||
use App\Models\business\PriceSheetModel;
|
||
use Exception;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Psr\Container\ContainerExceptionInterface;
|
||
use Psr\Container\NotFoundExceptionInterface;
|
||
|
||
/**
|
||
* 报价单(商品规格 + 常规价)
|
||
*
|
||
* 老接口靠 specification-1a / dimension-1b / routine-1b 这种动态键接收多行表单,
|
||
* 数量还得靠 count($params) 猜,多一个无关字段就会多循环一轮。
|
||
* 新接口收 rows 数组,并提供 saveRows 一次性覆盖某商品的全部规格行——抽屉里就是这个语义。
|
||
*/
|
||
class PriceSheetService extends BaseService
|
||
{
|
||
public function __construct()
|
||
{
|
||
parent::__construct();
|
||
$this->model = PriceSheetModel::class;
|
||
$this->selectField = array_merge(
|
||
['id', 'catalogue_id', 'specification', 'dimension'],
|
||
PriceSheetModel::MATERIAL_FIELDS,
|
||
['status', 'created_at', 'updated_at']
|
||
);
|
||
$this->queryField = ['catalogue_id' => '=', 'specification' => 'like', 'status' => '='];
|
||
$this->orderBy = ['name' => 'id', 'sort' => 'asc'];
|
||
}
|
||
|
||
/**
|
||
* @throws ContainerExceptionInterface
|
||
* @throws NotFoundExceptionInterface
|
||
*/
|
||
public function list(): array
|
||
{
|
||
return $this->getPageList();
|
||
}
|
||
|
||
public function option(): mixed
|
||
{
|
||
$this->optionField = ['id', 'specification as name'];
|
||
return $this->getOption();
|
||
}
|
||
|
||
/**
|
||
* @throws Exception
|
||
*/
|
||
public function detail($id): mixed
|
||
{
|
||
return $this->getDetail($id);
|
||
}
|
||
|
||
/**
|
||
* 新增:单行或多行(rows)
|
||
* @throws Exception
|
||
*/
|
||
public function create($params): mixed
|
||
{
|
||
$catalogueId = (int) ($params['catalogue_id'] ?? 0);
|
||
$this->assertCatalogue($catalogueId);
|
||
|
||
$rows = $this->normalizeRows($catalogueId, $params);
|
||
if (empty($rows)) {
|
||
$this->utils->errorThrow('请至少填写一行规格');
|
||
}
|
||
if (count($rows) === 1) {
|
||
return $this->insert($rows[0]);
|
||
}
|
||
return PriceSheetModel::insert($rows);
|
||
}
|
||
|
||
/**
|
||
* @throws Exception
|
||
*/
|
||
public function update($id, $params): mixed
|
||
{
|
||
unset($params['rows']);
|
||
return $this->save($id, $params);
|
||
}
|
||
|
||
/**
|
||
* @throws Exception
|
||
*/
|
||
public function delete($id): mixed
|
||
{
|
||
return $this->del(is_array($id) ? $id : [$id]);
|
||
}
|
||
|
||
/**
|
||
* @throws Exception
|
||
*/
|
||
public function status($id, $status): mixed
|
||
{
|
||
return $this->save($id, ['status' => (int) $status]);
|
||
}
|
||
|
||
/**
|
||
* 覆盖式保存某商品的全部规格行:带 id 的更新,不带的新增,界面上被删掉的软删。
|
||
* 一次事务完成,避免中途失败留下半套规格。
|
||
* @throws Exception
|
||
*/
|
||
public function saveRows(int $catalogueId, array $rows): bool
|
||
{
|
||
$this->assertCatalogue($catalogueId);
|
||
|
||
$now = time();
|
||
$keepIds = [];
|
||
DB::connection('business')->beginTransaction();
|
||
try {
|
||
foreach ($rows as $row) {
|
||
$payload = $this->pickRowFields($row);
|
||
if (trim((string) ($payload['specification'] ?? '')) === '') {
|
||
continue;
|
||
}
|
||
$payload['catalogue_id'] = $catalogueId;
|
||
$id = (int) ($row['id'] ?? 0);
|
||
if ($id > 0) {
|
||
$payload['updated_at'] = $now;
|
||
PriceSheetModel::where('id', $id)->where('catalogue_id', $catalogueId)->update($payload);
|
||
$keepIds[] = $id;
|
||
} else {
|
||
$payload['created_at'] = $now;
|
||
$keepIds[] = (int) PriceSheetModel::insertGetId($payload);
|
||
}
|
||
}
|
||
|
||
PriceSheetModel::where('catalogue_id', $catalogueId)
|
||
->where('deleted_at', 0)
|
||
->when(!empty($keepIds), fn ($q) => $q->whereNotIn('id', $keepIds))
|
||
->update(['deleted_at' => $now, 'updated_at' => $now]);
|
||
|
||
DB::connection('business')->commit();
|
||
} catch (Exception $e) {
|
||
DB::connection('business')->rollBack();
|
||
$this->utils->errorThrow($e->getMessage());
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 某商品的全部规格行,供报价单抽屉回填
|
||
*/
|
||
public function rowsOf(int $catalogueId): array
|
||
{
|
||
return PriceSheetModel::where('catalogue_id', $catalogueId)
|
||
->where('deleted_at', 0)
|
||
->orderBy('id')
|
||
->get($this->selectField)
|
||
->toArray();
|
||
}
|
||
|
||
/**
|
||
* 兼容单行字段与 rows 数组两种入参
|
||
*/
|
||
private function normalizeRows(int $catalogueId, array $params): array
|
||
{
|
||
$now = time();
|
||
$source = [];
|
||
if (!empty($params['rows']) && is_array($params['rows'])) {
|
||
$source = $params['rows'];
|
||
} elseif (trim((string) ($params['specification'] ?? '')) !== '') {
|
||
$source = [$params];
|
||
}
|
||
|
||
$rows = [];
|
||
foreach ($source as $row) {
|
||
if (!is_array($row)) {
|
||
continue;
|
||
}
|
||
$payload = $this->pickRowFields($row);
|
||
if (trim((string) ($payload['specification'] ?? '')) === '') {
|
||
continue;
|
||
}
|
||
$payload['catalogue_id'] = $catalogueId;
|
||
$payload['created_at'] = $now;
|
||
$rows[] = $payload;
|
||
}
|
||
return $rows;
|
||
}
|
||
|
||
/**
|
||
* 只取表里真实存在的列,把前端多传的字段挡在外面
|
||
*/
|
||
private function pickRowFields(array $row): array
|
||
{
|
||
$allowed = array_merge(['specification', 'dimension'], PriceSheetModel::MATERIAL_FIELDS);
|
||
$payload = [];
|
||
foreach ($allowed as $field) {
|
||
if (array_key_exists($field, $row)) {
|
||
$payload[$field] = is_scalar($row[$field]) ? (string) $row[$field] : '';
|
||
}
|
||
}
|
||
return $payload;
|
||
}
|
||
|
||
/**
|
||
* @throws Exception
|
||
*/
|
||
private function assertCatalogue(int $catalogueId): void
|
||
{
|
||
if ($catalogueId <= 0) {
|
||
$this->utils->errorThrow('请选择商品');
|
||
}
|
||
$exists = CatalogueModel::where('id', $catalogueId)->where('deleted_at', 0)->exists();
|
||
if (!$exists) {
|
||
$this->utils->errorThrow('商品不存在');
|
||
}
|
||
}
|
||
}
|