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('商品不存在'); } } }