|
|
@@ -8301,6 +8301,301 @@ class AnimeService
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 转让剧本归属(剧本及其关联资产)
|
|
|
+ *
|
|
|
+ * 仅调整 mp_scripts.user_id 与 mp_products.user_id,其他表保持不变。
|
|
|
+ * 权限:仅 admin / superadmin 可操作;目标用户必须是同组(cpid 相同)且角色为 user(组员)的用户,禁止跨组转让。
|
|
|
+ * 多个剧本逐个独立事务处理,单个剧本失败不影响其他剧本;
|
|
|
+ * 单个剧本内的剧本记录与关联资产必须全部更新成功,否则整体回滚。
|
|
|
+ *
|
|
|
+ * @param array $data 请求参数:script_ids(数组或英文逗号分隔,兼容单个 script_id)、target_user_id
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ public function transferScriptOwner($data) {
|
|
|
+ $operatorRole = Site::getRole();
|
|
|
+ $operatorCpid = Site::getCpid();
|
|
|
+
|
|
|
+ // 1.权限校验:仅管理员和超管可操作
|
|
|
+ if (!in_array($operatorRole, ['admin', 'superadmin'], true)) {
|
|
|
+ Utils::throwError(ErrorConst::NOT_ACCESS);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2.目标用户校验:必须存在且角色为 user(组员)
|
|
|
+ $targetUserId = (int)getProp($data, 'target_user_id', 0);
|
|
|
+ if ($targetUserId < 1) {
|
|
|
+ Utils::throwError('1002:请选择转让的目标用户');
|
|
|
+ }
|
|
|
+
|
|
|
+ $targetUser = DB::table('mp_manage_users')
|
|
|
+ ->where('id', $targetUserId)
|
|
|
+ ->where('is_deleted', 0)
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (!$targetUser) {
|
|
|
+ Utils::throwError('20003:目标用户不存在');
|
|
|
+ }
|
|
|
+ if ((string)getProp($targetUser, 'role') !== 'user') {
|
|
|
+ Utils::throwError('20003:只能转让给角色为组员(user)的用户');
|
|
|
+ }
|
|
|
+
|
|
|
+ $targetCpid = (int)getProp($targetUser, 'cpid');
|
|
|
+
|
|
|
+ // 3.规范化剧本ID列表(兼容数组、逗号分隔字符串、script_id 单值)
|
|
|
+ $rawScriptIds = getProp($data, 'script_ids');
|
|
|
+ if (empty($rawScriptIds)) {
|
|
|
+ $rawScriptIds = getProp($data, 'script_id');
|
|
|
+ }
|
|
|
+ $scriptIds = $this->normalizeIdList($rawScriptIds);
|
|
|
+ if (empty($scriptIds)) {
|
|
|
+ Utils::throwError('20003:请提供剧本ID');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 预取剧本名称,便于失败结果中回显
|
|
|
+ $scriptNameMap = DB::table('mp_scripts')
|
|
|
+ ->whereIn('id', $scriptIds)
|
|
|
+ ->pluck('script_name', 'id')
|
|
|
+ ->all();
|
|
|
+
|
|
|
+ $successList = [];
|
|
|
+ $failedList = [];
|
|
|
+
|
|
|
+ // 4.逐个剧本独立事务处理,单个失败不影响其他剧本
|
|
|
+ foreach ($scriptIds as $scriptId) {
|
|
|
+ DB::beginTransaction();
|
|
|
+
|
|
|
+ try {
|
|
|
+ $successList[] = $this->transferSingleScriptOwner(
|
|
|
+ $scriptId,
|
|
|
+ $targetUserId,
|
|
|
+ $targetCpid,
|
|
|
+ $operatorRole,
|
|
|
+ $operatorCpid,
|
|
|
+ Site::getUid()
|
|
|
+ );
|
|
|
+
|
|
|
+ DB::commit();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ DB::rollBack();
|
|
|
+
|
|
|
+ $failedList[] = [
|
|
|
+ 'script_id' => (int)$scriptId,
|
|
|
+ 'script_name' => (string)($scriptNameMap[$scriptId] ?? ''),
|
|
|
+ 'reason' => $e->getMessage() !== '' ? $e->getMessage() : '转让失败',
|
|
|
+ ];
|
|
|
+
|
|
|
+ dLog('anime')->error('剧本归属转让失败', [
|
|
|
+ 'script_id' => $scriptId,
|
|
|
+ 'target_user_id' => $targetUserId,
|
|
|
+ 'operator_uid' => Site::getUid(),
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'target_user_id' => $targetUserId,
|
|
|
+ 'success_count' => count($successList),
|
|
|
+ 'failed_count' => count($failedList),
|
|
|
+ 'success_list' => $successList,
|
|
|
+ 'failed_list' => $failedList,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 单个剧本的归属转让(需在事务中调用)
|
|
|
+ *
|
|
|
+ * @param int $scriptId 剧本ID
|
|
|
+ * @param int $targetUserId 目标用户ID
|
|
|
+ * @param int $targetCpid 目标用户所属公司ID
|
|
|
+ * @param string $operatorRole 操作人角色
|
|
|
+ * @param int $operatorCpid 操作人所属公司ID
|
|
|
+ * @param int $operatorUid 操作人用户ID
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ private function transferSingleScriptOwner($scriptId, $targetUserId, $targetCpid, $operatorRole, $operatorCpid, $operatorUid) {
|
|
|
+ $script = DB::table('mp_scripts')
|
|
|
+ ->where('id', $scriptId)
|
|
|
+ ->where('is_deleted', 0)
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (!$script) {
|
|
|
+ Utils::throwError('20003:剧本不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ $scriptCpid = (int)getProp($script, 'cpid');
|
|
|
+ $fromUserId = (int)getProp($script, 'user_id');
|
|
|
+
|
|
|
+ // 管理员仅可操作本组剧本
|
|
|
+ if ($operatorRole !== 'superadmin' && $scriptCpid !== $operatorCpid) {
|
|
|
+ Utils::throwError(ErrorConst::NOT_ACCESS);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 管理员仅可转让自己创建的剧本(超管不做此验证)
|
|
|
+ if ($operatorRole !== 'superadmin' && $fromUserId !== (int)$operatorUid) {
|
|
|
+ Utils::throwError('20003:管理员仅能转让自己创建的剧本和资产');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 禁止跨组转让:目标用户必须与剧本同组
|
|
|
+ if ($targetCpid !== $scriptCpid) {
|
|
|
+ Utils::throwError('20003:不能跨组转让,目标用户与剧本不在同一组');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关联资产ID(同一资产可能关联多个剧集序号,需去重)
|
|
|
+ $productIds = $this->normalizeIdList(
|
|
|
+ DB::table('mp_script_product_mappings')
|
|
|
+ ->where('script_id', $scriptId)
|
|
|
+ ->pluck('product_id')
|
|
|
+ ->all()
|
|
|
+ );
|
|
|
+
|
|
|
+ $folderIds = [];
|
|
|
+
|
|
|
+ if (!empty($productIds)) {
|
|
|
+ $products = DB::table('mp_products')
|
|
|
+ ->whereIn('id', $productIds)
|
|
|
+ ->select('id', 'cpid', 'parent_id', 'user_id')
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ // 关联资产必须全部存在,否则视为数据异常,不提交
|
|
|
+ if ($products->count() !== count($productIds)) {
|
|
|
+ Utils::throwError('20003:剧本存在已丢失的关联资产,无法完成转让');
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($products as $product) {
|
|
|
+ // 关联资产必须与剧本同组,避免误转让跨组资产
|
|
|
+ if ((int)getProp($product, 'cpid') !== $scriptCpid) {
|
|
|
+ Utils::throwError('20003:剧本存在跨组关联资产,无法完成转让');
|
|
|
+ }
|
|
|
+
|
|
|
+ $parentId = (int)getProp($product, 'parent_id', 0);
|
|
|
+ if ($parentId > 0 && !in_array($parentId, $folderIds, true)) {
|
|
|
+ $folderIds[] = $parentId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 仅转让归属于原剧本所有者的资产
|
|
|
+ $folderIds = DB::table('mp_products')
|
|
|
+ ->whereIn('id', $folderIds)
|
|
|
+ ->where('type', 2)
|
|
|
+ ->where('cpid', $scriptCpid)
|
|
|
+ ->where('user_id', $fromUserId)
|
|
|
+ ->pluck('id')
|
|
|
+ ->map('intval')
|
|
|
+ ->all();
|
|
|
+
|
|
|
+ // 管理员仅可转让自己创建的资产及其所属文件夹(超管不做此验证)
|
|
|
+ if ($operatorRole !== 'superadmin') {
|
|
|
+ foreach ($products as $product) {
|
|
|
+ if ((int)getProp($product, 'user_id') !== (int)$operatorUid) {
|
|
|
+ Utils::throwError('20003:管理员仅能转让自己创建的剧本和资产');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($products as $product) {
|
|
|
+ $parentId = (int)getProp($product, 'parent_id', 0);
|
|
|
+ if ($parentId > 0 && !in_array($parentId, $folderIds, true)) {
|
|
|
+ Utils::throwError('20003:管理员仅能转让自己创建的剧本和资产');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $now = now();
|
|
|
+
|
|
|
+ // 5.更新剧本归属
|
|
|
+ DB::table('mp_scripts')
|
|
|
+ ->where('id', $scriptId)
|
|
|
+ ->update([
|
|
|
+ 'user_id' => $targetUserId,
|
|
|
+ 'updated_at' => $now,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 6.更新关联资产归属
|
|
|
+ if (!empty($productIds)) {
|
|
|
+ DB::table('mp_products')
|
|
|
+ ->whereIn('id', $productIds)
|
|
|
+ ->update([
|
|
|
+ 'user_id' => $targetUserId,
|
|
|
+ 'updated_at' => $now,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7.更新资产所属的类型根文件夹归属(文件夹属于 mp_products,随资产一并转让)
|
|
|
+ if (!empty($folderIds)) {
|
|
|
+ DB::table('mp_products')
|
|
|
+ ->whereIn('id', $folderIds)
|
|
|
+ ->update([
|
|
|
+ 'user_id' => $targetUserId,
|
|
|
+ 'updated_at' => $now,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 8.校验剧本与资产是否全部更新成功,任一未成功则回滚
|
|
|
+ $scriptOwner = (int)DB::table('mp_scripts')->where('id', $scriptId)->value('user_id');
|
|
|
+ if ($scriptOwner !== $targetUserId) {
|
|
|
+ Utils::throwError('20003:剧本归属更新失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!empty($productIds)) {
|
|
|
+ $updatedProductCount = DB::table('mp_products')
|
|
|
+ ->whereIn('id', $productIds)
|
|
|
+ ->where('user_id', $targetUserId)
|
|
|
+ ->count();
|
|
|
+
|
|
|
+ if ($updatedProductCount !== count($productIds)) {
|
|
|
+ Utils::throwError('20003:部分剧本资产转让失败,已回滚');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!empty($folderIds)) {
|
|
|
+ $updatedFolderCount = DB::table('mp_products')
|
|
|
+ ->whereIn('id', $folderIds)
|
|
|
+ ->where('user_id', $targetUserId)
|
|
|
+ ->count();
|
|
|
+
|
|
|
+ if ($updatedFolderCount !== count($folderIds)) {
|
|
|
+ Utils::throwError('20003:部分资产文件夹转让失败,已回滚');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'script_id' => (int)$scriptId,
|
|
|
+ 'script_name' => (string)getProp($script, 'script_name', ''),
|
|
|
+ 'from_user_id' => $fromUserId,
|
|
|
+ 'to_user_id' => $targetUserId,
|
|
|
+ 'product_count' => count($productIds),
|
|
|
+ 'folder_count' => count($folderIds),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 规范化ID列表,兼容数组、逗号分隔字符串以及字符串数组
|
|
|
+ *
|
|
|
+ * @param mixed $ids 待处理的ID集合
|
|
|
+ * @return array 去重后的正整数ID数组
|
|
|
+ */
|
|
|
+ private function normalizeIdList($ids) {
|
|
|
+ if (!is_array($ids)) {
|
|
|
+ $ids = explode(',', (string)$ids);
|
|
|
+ }
|
|
|
+
|
|
|
+ $result = [];
|
|
|
+ foreach ($ids as $item) {
|
|
|
+ if (is_array($item)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ foreach (explode(',', (string)$item) as $part) {
|
|
|
+ $part = trim($part);
|
|
|
+ if ($part !== '' && is_numeric($part) && (int)$part > 0) {
|
|
|
+ $result[] = (int)$part;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return array_values(array_unique($result));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* 保存剧本资产关联关系
|
|
|
* @param array $data 请求参数
|
|
|
* @return bool
|