ProductController.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. namespace App\Http\Controllers\Admin\Products;
  3. use App\Shop\Attributes\Repositories\AttributeRepositoryInterface;
  4. use App\Shop\AttributeValues\Repositories\AttributeValueRepositoryInterface;
  5. use App\Shop\Brands\Repositories\BrandRepositoryInterface;
  6. use App\Shop\Categories\Repositories\Interfaces\CategoryRepositoryInterface;
  7. use App\Shop\ProductAttributes\ProductAttribute;
  8. use App\Shop\Products\Exceptions\ProductInvalidArgumentException;
  9. use App\Shop\Products\Exceptions\ProductNotFoundException;
  10. use App\Shop\Products\Product;
  11. use App\Shop\Products\Repositories\Interfaces\ProductRepositoryInterface;
  12. use App\Shop\Products\Repositories\ProductRepository;
  13. use App\Shop\Products\Requests\CreateProductRequest;
  14. use App\Shop\Products\Requests\UpdateProductRequest;
  15. use App\Http\Controllers\Controller;
  16. use App\Shop\Products\Transformations\ProductTransformable;
  17. use App\Shop\Tools\UploadableTrait;
  18. use Illuminate\Http\Request;
  19. use Illuminate\Http\UploadedFile;
  20. use Illuminate\Support\Facades\DB;
  21. use Illuminate\Support\Facades\Log;
  22. use Illuminate\Support\Facades\Validator;
  23. class ProductController extends Controller
  24. {
  25. use ProductTransformable, UploadableTrait;
  26. /**
  27. * @var ProductRepositoryInterface
  28. */
  29. private $productRepo;
  30. /**
  31. * @var CategoryRepositoryInterface
  32. */
  33. private $categoryRepo;
  34. /**
  35. * @var AttributeRepositoryInterface
  36. */
  37. private $attributeRepo;
  38. /**
  39. * @var AttributeValueRepositoryInterface
  40. */
  41. private $attributeValueRepository;
  42. /**
  43. * @var ProductAttribute
  44. */
  45. private $productAttribute;
  46. /**
  47. * @var BrandRepositoryInterface
  48. */
  49. private $brandRepo;
  50. /**
  51. * ProductController constructor.
  52. *
  53. * @param ProductRepositoryInterface $productRepository
  54. * @param CategoryRepositoryInterface $categoryRepository
  55. * @param AttributeRepositoryInterface $attributeRepository
  56. * @param AttributeValueRepositoryInterface $attributeValueRepository
  57. * @param ProductAttribute $productAttribute
  58. * @param BrandRepositoryInterface $brandRepository
  59. */
  60. public function __construct(
  61. ProductRepositoryInterface $productRepository,
  62. CategoryRepositoryInterface $categoryRepository,
  63. AttributeRepositoryInterface $attributeRepository,
  64. AttributeValueRepositoryInterface $attributeValueRepository,
  65. ProductAttribute $productAttribute,
  66. BrandRepositoryInterface $brandRepository
  67. ) {
  68. $this->productRepo = $productRepository;
  69. $this->categoryRepo = $categoryRepository;
  70. $this->attributeRepo = $attributeRepository;
  71. $this->attributeValueRepository = $attributeValueRepository;
  72. $this->productAttribute = $productAttribute;
  73. $this->brandRepo = $brandRepository;
  74. $this->middleware(['permission:create-product, guard:employee'], ['only' => ['create', 'store']]);
  75. $this->middleware(['permission:update-product, guard:employee'], ['only' => ['edit', 'update']]);
  76. $this->middleware(['permission:delete-product, guard:employee'], ['only' => ['destroy']]);
  77. $this->middleware(['permission:view-product, guard:employee'], ['only' => ['index', 'show']]);
  78. }
  79. /**
  80. * Display a listing of the resource.
  81. *
  82. * @return \Illuminate\Http\Response
  83. */
  84. public function index()
  85. {
  86. $list = $this->productRepo->listProducts('id');
  87. if (request()->has('q') && request()->input('q') != '') {
  88. $list = $this->productRepo->searchProduct(request()->input('q'));
  89. }
  90. $products = $list->map(function (Product $item) {
  91. return $this->transformProduct($item);
  92. })->all();
  93. return view('admin.products.list', [
  94. 'products' => $this->productRepo->paginateArrayResults($products, 25)
  95. ]);
  96. }
  97. /**
  98. * Show the form for creating a new resource.
  99. *
  100. * @return \Illuminate\Http\Response
  101. */
  102. public function create()
  103. {
  104. $categories = $this->categoryRepo->listCategories('name', 'asc');
  105. return view('admin.products.create', [
  106. 'categories' => $categories,
  107. 'brands' => $this->brandRepo->listBrands(['*'], 'name', 'asc'),
  108. 'default_weight' => env('SHOP_WEIGHT'),
  109. 'weight_units' => Product::MASS_UNIT,
  110. 'product' => new Product
  111. ]);
  112. }
  113. /**
  114. * Store a newly created resource in storage.
  115. *
  116. * @param CreateProductRequest $request
  117. *
  118. * @return \Illuminate\Http\Response
  119. */
  120. public function store(CreateProductRequest $request)
  121. {
  122. $data = $request->except('_token', '_method');
  123. $data['slug'] = str_slug($request->input('name'));
  124. if ($request->hasFile('cover') && $request->file('cover') instanceof UploadedFile) {
  125. $data['cover'] = $this->productRepo->saveCoverImage($request->file('cover'));
  126. }
  127. $product = $this->productRepo->createProduct($data);
  128. $productRepo = new ProductRepository($product);
  129. if ($request->hasFile('image')) {
  130. $productRepo->saveProductImages(collect($request->file('image')));
  131. }
  132. if ($request->has('categories')) {
  133. $productRepo->syncCategories($request->input('categories'));
  134. } else {
  135. $productRepo->detachCategories();
  136. }
  137. return redirect()->route('admin.products.edit', $product->id)->with('message', 'Create successful');
  138. }
  139. /**
  140. * Display the specified resource.
  141. *
  142. * @param int $id
  143. *
  144. * @return \Illuminate\Http\Response
  145. */
  146. public function show(int $id)
  147. {
  148. $product = $this->productRepo->findProductById($id);
  149. return view('admin.products.show', compact('product'));
  150. }
  151. /**
  152. * Show the form for editing the specified resource.
  153. *
  154. * @param int $id
  155. *
  156. * @return \Illuminate\Http\Response
  157. */
  158. public function edit(int $id)
  159. {
  160. $product = $this->productRepo->findProductById($id);
  161. $productAttributes = $product->attributes()->get();
  162. $qty = $productAttributes->map(function ($item) {
  163. return $item->quantity;
  164. })->sum();
  165. if (request()->has('delete') && request()->has('pa')) {
  166. $pa = $productAttributes->where('id', request()->input('pa'))->first();
  167. $pa->attributesValues()->detach();
  168. $pa->delete();
  169. request()->session()->flash('message', 'Delete successful');
  170. return redirect()->route('admin.products.edit', [$product->id, 'combination' => 1]);
  171. }
  172. $categories = $this->categoryRepo->listCategories('name', 'asc')->toTree();
  173. return view('admin.products.edit', [
  174. 'product' => $product,
  175. 'images' => $product->images()->get(['src']),
  176. 'categories' => $categories,
  177. 'selectedIds' => $product->categories()->pluck('category_id')->all(),
  178. 'attributes' => $this->attributeRepo->listAttributes(),
  179. 'productAttributes' => $productAttributes,
  180. 'qty' => $qty,
  181. 'brands' => $this->brandRepo->listBrands(['*'], 'name', 'asc'),
  182. 'weight' => $product->weight,
  183. 'default_weight' => $product->mass_unit,
  184. 'weight_units' => Product::MASS_UNIT
  185. ]);
  186. }
  187. /**
  188. * Update the specified resource in storage.
  189. *
  190. * @param UpdateProductRequest $request
  191. * @param int $id
  192. *
  193. * @return \Illuminate\Http\Response
  194. * @throws \App\Shop\Products\Exceptions\ProductUpdateErrorException
  195. */
  196. public function update(UpdateProductRequest $request, int $id)
  197. {
  198. $product = $this->productRepo->findProductById($id);
  199. $productRepo = new ProductRepository($product);
  200. if ($request->has('attributeValue')) {
  201. $this->saveProductCombinations($request, $product);
  202. return redirect()->route('admin.products.edit', [$id, 'combination' => 1])
  203. ->with('message', 'Attribute combination created successful');
  204. }
  205. $data = $request->except(
  206. 'categories',
  207. '_token',
  208. '_method',
  209. 'default',
  210. 'image',
  211. 'productAttributeQuantity',
  212. 'productAttributePrice',
  213. 'attributeValue',
  214. 'combination'
  215. );
  216. $data['slug'] = str_slug($request->input('name'));
  217. if ($request->hasFile('cover')) {
  218. $data['cover'] = $productRepo->saveCoverImage($request->file('cover'));
  219. }
  220. if ($request->hasFile('image')) {
  221. $productRepo->saveProductImages(collect($request->file('image')));
  222. }
  223. if ($request->has('categories')) {
  224. $productRepo->syncCategories($request->input('categories'));
  225. } else {
  226. $productRepo->detachCategories();
  227. }
  228. $productRepo->updateProduct($data);
  229. return redirect()->route('admin.products.edit', $id)
  230. ->with('message', 'Update successful');
  231. }
  232. /**
  233. * Remove the specified resource from storage.
  234. *
  235. * @param int $id
  236. *
  237. * @return \Illuminate\Http\Response
  238. * @throws \Exception
  239. */
  240. public function destroy($id)
  241. {
  242. $product = $this->productRepo->findProductById($id);
  243. $product->categories()->sync([]);
  244. $productAttr = $product->attributes();
  245. $productAttr->each(function ($pa) {
  246. DB::table('attribute_value_product_attribute')->where('product_attribute_id', $pa->id)->delete();
  247. });
  248. $productAttr->where('product_id', $product->id)->delete();
  249. $productRepo = new ProductRepository($product);
  250. $productRepo->removeProduct();
  251. return redirect()->route('admin.products.index')->with('message', 'Delete successful');
  252. }
  253. /**
  254. * @param Request $request
  255. *
  256. * @return \Illuminate\Http\RedirectResponse
  257. */
  258. public function removeImage(Request $request)
  259. {
  260. $this->productRepo->deleteFile($request->only('product', 'image'), 'uploads');
  261. return redirect()->back()->with('message', 'Image delete successful');
  262. }
  263. /**
  264. * @param Request $request
  265. *
  266. * @return \Illuminate\Http\RedirectResponse
  267. */
  268. public function removeThumbnail(Request $request)
  269. {
  270. $this->productRepo->deleteThumb($request->input('src'));
  271. return redirect()->back()->with('message', 'Image delete successful');
  272. }
  273. /**
  274. * @param Request $request
  275. * @param Product $product
  276. * @return boolean
  277. */
  278. private function saveProductCombinations(Request $request, Product $product): bool
  279. {
  280. $fields = $request->only(
  281. 'productAttributeQuantity',
  282. 'productAttributePrice',
  283. 'sale_price',
  284. 'default'
  285. );
  286. if ($errors = $this->validateFields($fields)) {
  287. return redirect()->route('admin.products.edit', [$product->id, 'combination' => 1])
  288. ->withErrors($errors);
  289. }
  290. $quantity = $fields['productAttributeQuantity'];
  291. $price = $fields['productAttributePrice'];
  292. $sale_price = null;
  293. if (isset($fields['sale_price'])) {
  294. $sale_price = $fields['sale_price'];
  295. }
  296. $attributeValues = $request->input('attributeValue');
  297. $productRepo = new ProductRepository($product);
  298. $hasDefault = $productRepo->listProductAttributes()->where('default', 1)->count();
  299. $default = 0;
  300. if ($request->has('default')) {
  301. $default = $fields['default'];
  302. }
  303. if ($default == 1 && $hasDefault > 0) {
  304. $default = 0;
  305. }
  306. $productAttribute = $productRepo->saveProductAttributes(
  307. new ProductAttribute(compact('quantity', 'price', 'sale_price', 'default'))
  308. );
  309. // save the combinations
  310. return collect($attributeValues)->each(function ($attributeValueId) use ($productRepo, $productAttribute) {
  311. $attribute = $this->attributeValueRepository->find($attributeValueId);
  312. return $productRepo->saveCombination($productAttribute, $attribute);
  313. })->count();
  314. }
  315. /**
  316. * @param array $data
  317. *
  318. * @return
  319. */
  320. private function validateFields(array $data)
  321. {
  322. $validator = Validator::make($data, [
  323. 'productAttributeQuantity' => 'required'
  324. ]);
  325. if ($validator->fails()) {
  326. return $validator;
  327. }
  328. }
  329. }