TaskController.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. <?php
  2. namespace App\Http\Controllers\API;
  3. use App\Exports\DownloadHelper;
  4. use App\Exports\TaskExport;
  5. use App\Http\Controllers\Controller;
  6. use App\Http\Requests\API\Task\AssignRequest;
  7. use App\Http\Requests\API\Task\BatchCreateItemRules;
  8. use App\Http\Requests\API\Task\BatchCreateRequest;
  9. use App\Http\Requests\API\Task\CreateOrUpdateRequest;
  10. use App\Http\Requests\API\Task\LinkContainerRequest;
  11. use App\Http\Resources\API\TaskDetailResource;
  12. use App\Http\Resources\API\TaskResource;
  13. use App\Imports\TaskImport;
  14. use App\Models\Approval;
  15. use App\Models\Container;
  16. use App\Models\Enums\ActionObjectType;
  17. use App\Models\Enums\FileObjectType;
  18. use App\Models\Enums\ObjectAction;
  19. use App\Models\Enums\TaskStatus;
  20. use App\Models\Library;
  21. use App\Models\Project;
  22. use App\Models\Requirement;
  23. use App\Models\Task;
  24. use App\Models\TaskContainer;
  25. use App\Repositories\ActionRepository;
  26. use App\Repositories\CustomFieldRepository;
  27. use App\Services\File\FileAssociationService;
  28. use App\Services\History\ModelChangeDetector;
  29. use Carbon\Carbon;
  30. use Illuminate\Http\Request;
  31. use Illuminate\Support\Facades\Auth;
  32. use Illuminate\Support\Facades\Validator;
  33. use Maatwebsite\Excel\Facades\Excel;
  34. class TaskController extends Controller
  35. {
  36. use DownloadHelper;
  37. /**
  38. * Display a listing of the resource.
  39. */
  40. public function index(Request $request)
  41. {
  42. $tasks= $this->getTasks($request);
  43. return TaskResource::collection($tasks);
  44. }
  45. public function publicSearch(Request $request){
  46. $tasks=$this->getTasks($request);
  47. return TaskResource::collection($tasks);
  48. }
  49. protected function getTasks(Request $request){
  50. $pageSize=$request->get('page_size') ?? 10;
  51. $sort=$request->input('sort','desc');
  52. $tasks = Task::query()
  53. ->where("parent_id", 0)
  54. ->with(['children', 'assignTo', 'createdBy'])
  55. ->filter($request->all())
  56. ->allowed()
  57. ->orderBy('created_at',$sort)
  58. ->paginate($pageSize);
  59. return $tasks;
  60. }
  61. public function export(Request $request)
  62. {
  63. return $this->downloadExcelHelper(
  64. new TaskExport(),
  65. "task",
  66. $request->get("extension"),
  67. );
  68. }
  69. /**
  70. * Store a newly created resource in storage.
  71. */
  72. public function store(FileAssociationService $service, CreateOrUpdateRequest $request, CustomFieldRepository $customFieldRepo)
  73. {
  74. $isAction=true;
  75. $projectId=$request->get("project_id");
  76. $project = Project::allowed($projectId,$isAction)->find($projectId);
  77. if($project===null||is_null($project)){
  78. return $this->badRequest('Permission denied or project not found. Please contact the administrator.');
  79. };
  80. $requirement = $request->has('requirement_id')
  81. ? Requirement::query()->findOrFail($request->get("requirement_id"))
  82. : null;
  83. $service->check(
  84. $request->get("file_ids", []),
  85. FileObjectType::TASK,
  86. $request->get("file_uuid"),
  87. );
  88. $formData = [
  89. ...$request->all(),
  90. 'company_id' => Auth::user()->company_id,
  91. 'created_by' => Auth::id(),
  92. 'whitelist' => $request->whitelist ? sprintf(",%s,", implode(',', $request->whitelist)) : null,
  93. 'asset_id' => $requirement?->asset_id,
  94. 'description' => $request->description? (new \App\Services\File\ImageUrlService)->interceptImageUrl($request->description) : null,
  95. 'requirement_group_id'=> $requirement?->requirement_group_id,
  96. ];
  97. if ($request->has("naming_rule_id") && $request->get("naming_rule_id") > 0) {
  98. $keys = $customFieldRepo->keysByGroup($request->get("naming_rule_id"));
  99. $formData['naming_rules'] = $request->only($keys);
  100. }
  101. $task = Task::create($formData);
  102. ActionRepository::createByTask($task, ObjectAction::CREATED);
  103. $service->association($task->id);
  104. return $this->created();
  105. }
  106. public function import(Request $request)
  107. {
  108. Excel::import(new TaskImport(), $request->file("file"));
  109. return $this->created();
  110. }
  111. /**
  112. * Display the specified resource.
  113. */
  114. public function show(string $id)
  115. {
  116. $task = Task::query()->allowed($id)->with([
  117. 'containers'
  118. ])->findOrFail($id);
  119. return new TaskDetailResource($task);
  120. }
  121. public function start(Request $request, string $id)
  122. {
  123. $isAction=true;
  124. $task = Task::query()->allowed($id,$isAction)->find($id);
  125. if($task===null||is_null($task)){
  126. return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
  127. };
  128. $task->status = TaskStatus::DOING->value;
  129. $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
  130. $task->save();
  131. ActionRepository::createByTask(
  132. $task,
  133. ObjectAction::STARTED,
  134. $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
  135. objectChanges: $changes
  136. );
  137. return $this->noContent();
  138. }
  139. public function pause(Request $request, string $id)
  140. {
  141. $isAction=true;
  142. $task = Task::query()->allowed($id,$isAction)->find($id);
  143. if($task===null||is_null($task)){
  144. return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
  145. };
  146. $task->status = TaskStatus::PAUSE->value;
  147. $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
  148. $task->save();
  149. ActionRepository::createByTask(
  150. $task, ObjectAction::PAUSED,
  151. $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
  152. objectChanges: $changes
  153. );
  154. return $this->noContent();
  155. }
  156. public function closed(Request $request, string $id)
  157. {
  158. $isAction=true;
  159. $task = Task::query()->allowed($id,$isAction)->find($id);
  160. if($task===null||is_null($task)){
  161. return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
  162. };
  163. $task->status = TaskStatus::CLOSED->value;
  164. $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
  165. $task->save();
  166. ActionRepository::createByTask(
  167. $task, ObjectAction::CLOSED,
  168. $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
  169. objectChanges: $changes
  170. );
  171. return $this->noContent();
  172. }
  173. public function done(Request $request, string $id)
  174. {
  175. $isAction=true;
  176. $task = Task::query()->allowed($id,$isAction)->find($id);
  177. if($task===null||is_null($task)){
  178. return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
  179. };
  180. $task->fill([
  181. 'status' => TaskStatus::DONE->value,
  182. 'finished_by' => Auth::user()->id,
  183. 'finished_at' => Carbon::now(),
  184. ]);
  185. $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
  186. $task->save();
  187. ActionRepository::createByTask(
  188. $task, ObjectAction::DONE,
  189. $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
  190. objectChanges: $changes
  191. );
  192. return $this->noContent();
  193. }
  194. public function cancel(Request $request, string $id)
  195. {
  196. $isAction=true;
  197. $task = Task::query()->allowed($id,$isAction)->find($id);
  198. if($task===null||is_null($task)){
  199. return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
  200. };
  201. $task->fill([
  202. 'status' => TaskStatus::CANCEL->value,
  203. 'canceled_by' => Auth::user()->id,
  204. 'canceled_at' => Carbon::now(),
  205. ]);
  206. $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
  207. $task->save();
  208. ActionRepository::createByTask(
  209. $task, ObjectAction::CANCELED,
  210. $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
  211. objectChanges: $changes
  212. );
  213. return $this->noContent();
  214. }
  215. public function wait(Request $request,string $id){
  216. $isAction=true;
  217. $task = Task::query()->allowed($id,$isAction)->find($id);
  218. if($task===null||is_null($task)){
  219. return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
  220. };
  221. $task->status=TaskStatus::WAIT->value;
  222. $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
  223. $task->save();
  224. ActionRepository::createByTask(
  225. $task, ObjectAction::WAITED,
  226. $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
  227. objectChanges: $changes
  228. );
  229. return $this->noContent();
  230. }
  231. public function assign(AssignRequest $request,string $id){
  232. $isAction=true;
  233. $task = Task::query()->allowed($id,$isAction)->find($id);
  234. if($task===null||is_null($task)){
  235. return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
  236. };
  237. $task->fill([
  238. 'assign'=>$request->get('assign'),
  239. ...$request->all(),
  240. ]);
  241. $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
  242. $task->save();
  243. ActionRepository::createByTask(
  244. $task, ObjectAction::ASSIGNED,
  245. $request->comment?(new \App\Services\File\ImageUrlService)->interceptImageUrl($request->comment) : null,
  246. objectChanges: $changes
  247. );
  248. return $this->noContent();
  249. }
  250. /**
  251. * Update the specified resource in storage.
  252. */
  253. public function update(CreateOrUpdateRequest $request, string $id, CustomFieldRepository $customFieldRepo)
  254. {
  255. $isAction=true;
  256. $task = Task::query()->allowed($id,$isAction)->find($id);
  257. if($task===null||is_null($task)){
  258. return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
  259. };
  260. $requirement = $request->has('requirement_id') && $task->requirement_id != $request->get('requirement_id')
  261. ? Requirement::query()->findOrFail($request->get("requirement_id"))
  262. : null;
  263. $formData = [
  264. ...$request->except('created_by'),
  265. 'whitelist' => $request->whitelist ? sprintf(",%s,", implode(',', $request->whitelist)) : null,
  266. 'description' => $request->description? (new \App\Services\File\ImageUrlService)->interceptImageUrl($request->description) : null,
  267. 'asset_id' => $requirement?->asset_id,
  268. ];
  269. if ($request->has("naming_rule_id") && $request->get("naming_rule_id") > 0) {
  270. $keys = $customFieldRepo->keysByGroup($request->get("naming_rule_id"));
  271. $formData['naming_rules'] = $request->only($keys);
  272. }
  273. $task->fill($formData);
  274. $changes = ModelChangeDetector::detector(ActionObjectType::TASK, $task);
  275. $task->save();
  276. ActionRepository::createByTask($task, ObjectAction::EDITED, objectChanges: $changes);
  277. return $this->noContent();
  278. }
  279. /**
  280. * Remove the specified resource from storage.
  281. */
  282. public function destroy(string $id)
  283. {
  284. $isAction=true;
  285. $task = Task::query()->allowed($id,$isAction)->find($id);
  286. if($task===null||is_null($task)){
  287. return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
  288. };
  289. $task->delete();
  290. Approval::query()->where('object_type','task')->where('object_id',$id)->delete();
  291. ActionRepository::createByTask($task, ObjectAction::DELETED);
  292. return $this->noContent();
  293. }
  294. public function batchStore(BatchCreateRequest $request, CustomFieldRepository $customFieldRepo)
  295. {
  296. $isAction=true;
  297. $projectId=$request->get("project_id");
  298. $project = Project::allowed($projectId,$isAction)->find($projectId);
  299. if($project===null||is_null($project)){
  300. return $this->badRequest('Permission denied or project not found. Please contact the administrator.');
  301. };
  302. $parsedItems = [];
  303. $previousItem = [];
  304. foreach ($request->items as $index => $item) {
  305. if ($index == 0) {
  306. $newItem = $item;
  307. } else {
  308. $newItem = [];
  309. foreach ($item as $k => $v) {
  310. $newItem[$k] = $v == "ditto" ? data_get($previousItem, $k) : $v;
  311. }
  312. }
  313. $previousItem = $newItem;
  314. $parsedItems[] = $newItem;
  315. }
  316. $itemRules = new BatchCreateItemRules();
  317. foreach ($parsedItems as $index => $item) {
  318. $rules = $itemRules->rules($item);
  319. try {
  320. $validator = Validator::make($item, $rules);
  321. if ($validator->fails()) {
  322. return $this->badRequest(sprintf("line: %d, %s", $index + 1, $validator->errors()->first()));
  323. }
  324. } catch (\Exception $e) {
  325. return $this->badRequest(sprintf("line: %d, %s", $index + 1, $e->getMessage()));
  326. }
  327. }
  328. foreach ($parsedItems as $item) {
  329. $requirement=$item['requirement_id']>0 ? Requirement::query()->findOrFail($item['requirement_id']) : null;
  330. $item["whitelist"]=!empty($item['whitelist']) ? sprintf(",%s,", implode(',',$item['whitelist'])) : null;
  331. $namingRuleId = data_get($item, "naming_rule_id", 0);
  332. if ($namingRuleId > 0) {
  333. $keys = $customFieldRepo->keysByGroup($namingRuleId);
  334. $item['naming_rules'] = collect($item)->only($keys)->toArray();
  335. }
  336. $task = Task::query()->create([
  337. ...$item,
  338. 'project_id' => $project->id,
  339. 'parent_id' => $request->parent_id,
  340. 'company_id' => Auth::user()->company_id,
  341. 'created_by' => Auth::id(),
  342. 'asset_id' => $requirement?->asset_id,
  343. 'requirement_group_id'=> $requirement?->requirement_group_id,
  344. ]);
  345. ActionRepository::createByTask($task, ObjectAction::CREATED);
  346. }
  347. return $this->noContent();
  348. }
  349. /**
  350. * 容器链接
  351. *
  352. * @param LinkContainerRequest $request
  353. * @param string $id
  354. * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
  355. */
  356. public function linkContainer(LinkContainerRequest $request, string $id)
  357. {
  358. $isAction=true;
  359. $task = Task::query()->allowed($id,$isAction)->find($id);
  360. if($task===null||is_null($task)){
  361. return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
  362. };
  363. $libraryIds = Library::query()->allowed()->where("project_id", $task->project_id)->pluck("id");
  364. if ($libraryIds->isEmpty()) {
  365. return $this->badRequest("No container exists for the current task item");
  366. }
  367. $containers = Container::query()
  368. ->allowed()
  369. ->whereIn("library_id", $libraryIds->toArray())
  370. ->whereIn("id", $request->get("container_ids", []))
  371. ->get(['id']);
  372. if (! $containers) {
  373. return $this->badRequest("Please select a valid container");
  374. }
  375. foreach ($containers as $container) {
  376. TaskContainer::query()->firstOrCreate([
  377. 'container_id' => $container->id,
  378. 'task_id' => $task->id
  379. ]);
  380. }
  381. return $this->noContent();
  382. }
  383. public function unlinkContainer(string $id)
  384. {
  385. $taskContainer = TaskContainer::query()->findOrFail($id);
  386. $taskId=$taskContainer->task_id;
  387. $isAction=true;
  388. $task = Task::query()->allowed($taskId,$isAction)->find($taskId);
  389. if($task===null||is_null($task)){
  390. return $this->badRequest('Permission denied or task not found. Please contact the administrator.');
  391. };
  392. $taskContainer->delete();
  393. return $this->noContent();
  394. }
  395. /**
  396. * 待关联的容器
  397. *
  398. * @param Request $request
  399. * @param string $id
  400. * @return \Illuminate\Http\JsonResponse
  401. */
  402. public function containerToBeLinked(Request $request, string $id)
  403. {
  404. $task = Task::query()->allowed($id)->findOrFail($id);
  405. $libraryIds = Library::query()->allowed()->where("project_id", $task->project_id)->pluck("id");
  406. if ($libraryIds->isEmpty()) {
  407. return $this->badRequest("No container exists for the current task item");
  408. }
  409. $containers = Container::query()
  410. ->allowed()
  411. ->leftJoin("task_container", "containers.id", "=", "task_container.container_id")
  412. ->where($request->only(['library_id']))
  413. ->whereIn("library_id", $libraryIds->toArray())
  414. ->whereNull("task_container.id")
  415. ->selectRaw("containers.id, containers.name")
  416. ->get();
  417. return $this->success([
  418. 'data' => $containers
  419. ]);
  420. }
  421. }