src/Entity/ProductModel.php line 415

Open in your IDE?
  1. <?php
  2. namespace App\Entity;
  3. use App\Entity\ProductModelDescription;
  4. use Doctrine\ORM\Mapping as ORM;
  5. use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
  6. use App\Helpers\Encoder;
  7. /**
  8. * Products
  9. *
  10. * @ORM\Table(name="products_models", indexes={@ORM\Index(name="IDX_PRODUCTS_MODELS_CODE", columns={"code"})})
  11. * @ORM\Entity(repositoryClass="App\Repository\ProductRepository")
  12. * @ORM\HasLifecycleCallbacks()
  13. */
  14. class ProductModel extends TranslatedEntity
  15. {
  16. public const VAT_RATE = 0.2;
  17. public const SEARCH_INDICES = [
  18. 'fr' => 'fr-models',
  19. 'en' => 'en-models',
  20. ];
  21. protected $tranlatedEntity = 'ProductModelDescription';
  22. /**
  23. * @var int
  24. *
  25. * @ORM\Column(name="id", type="integer", nullable=false)
  26. * @ORM\Id
  27. * @ORM\GeneratedValue(strategy="IDENTITY")
  28. */
  29. private $id;
  30. /**
  31. * @var string
  32. *
  33. * @ORM\Column(name="code", type="string", length=50, nullable=true)
  34. */
  35. private $code;
  36. /**
  37. * @ORM\OneToMany(targetEntity="App\Entity\ProductModelAttribute", mappedBy="productModel")
  38. */
  39. private $attributes;
  40. /**
  41. * @ORM\OneToMany(targetEntity="App\Entity\ProductModel", mappedBy="parent")
  42. */
  43. private $children = [];
  44. /**
  45. * @ORM\ManyToOne(targetEntity="App\Entity\ProductModel", inversedBy="children")
  46. * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
  47. */
  48. private $parent;
  49. /**
  50. * @ORM\OneToMany(targetEntity="App\Entity\ProductModelDescription", mappedBy="model")
  51. */
  52. private $descriptions = [];
  53. /**
  54. * @var App\Entity\Manufacturer|null
  55. *
  56. * @ORM\ManyToOne(targetEntity="App\Entity\Manufacturer")
  57. * @ORM\JoinColumn(name="manufacturers_id", referencedColumnName="manufacturers_id", nullable=true)
  58. */
  59. private $manufacturer;
  60. /**
  61. * @ORM\OneToMany(targetEntity="App\Entity\ProductModelCountryRestriction", mappedBy="model")
  62. */
  63. private $countryRestrictions;
  64. /**
  65. * @var \DateTime
  66. *
  67. * @ORM\Column(name="date_added", type="datetime", nullable=false)
  68. */
  69. private $dateAdded;
  70. /**
  71. * @var \DateTime|null
  72. *
  73. * @ORM\Column(name="last_modified", type="datetime", nullable=true)
  74. */
  75. private $lastModified;
  76. /**
  77. * @var bool
  78. *
  79. * @ORM\Column(name="status", type="boolean", nullable=false)
  80. */
  81. private $status = '0';
  82. /**
  83. * @ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="productModel")
  84. */
  85. private $products = [];
  86. /**
  87. * @var App\Entity\ProductModelVariant|null
  88. *
  89. * @ORM\ManyToOne(targetEntity="App\Entity\ProductModelVariant")
  90. * @ORM\JoinColumn(name="variant_id", referencedColumnName="id", nullable=true)
  91. */
  92. private $variant;
  93. /**
  94. * Axe de déclinaison (1 = axis1, 2 = axis2 du ProductModelVariant) sur lequel le modèle est
  95. * éclaté dans les listings catégorie : une vignette par valeur de cet axe.
  96. * Null = une seule vignette pour tout le modèle.
  97. *
  98. * @var int|null
  99. *
  100. * @ORM\Column(name="listing_axis", type="smallint", nullable=true)
  101. */
  102. private $listingAxis;
  103. /**
  104. * Déclinaison affichée à l'arrivée sur la fiche modèle (route product_model).
  105. * Null = repli sur le produit marqué is_default_variant, sinon le premier produit.
  106. *
  107. * @var Product|null
  108. *
  109. * @ORM\ManyToOne(targetEntity="App\Entity\Product")
  110. * @ORM\JoinColumn(name="default_product_id", referencedColumnName="products_id", nullable=true, onDelete="SET NULL")
  111. */
  112. private $defaultProduct;
  113. /**
  114. * Catégorie par défaut (première catégorie transmise par Akeneo)
  115. *
  116. * @var int|null
  117. *
  118. * @ORM\Column(name="products_cat", type="integer", nullable=true)
  119. */
  120. private $defaultCategoryId;
  121. public function __construct()
  122. {
  123. $this->children = new \Doctrine\Common\Collections\ArrayCollection();
  124. $this->descriptions = new \Doctrine\Common\Collections\ArrayCollection();
  125. }
  126. public function getId(): int {
  127. return $this->id;
  128. }
  129. public function getAttributes() {
  130. return $this->attributes;
  131. }
  132. public function getChildren() {
  133. return $this->children;
  134. }
  135. public function getParent() {
  136. return $this->parent;
  137. }
  138. public function getDescriptions() {
  139. return $this->descriptions;
  140. }
  141. public function getPictures() {
  142. return $this->pictures;
  143. }
  144. public function getManufacturer(): ? Manufacturer {
  145. return $this->manufacturer;
  146. }
  147. public function getCountryRestrictions() {
  148. return $this->countryRestrictions;
  149. }
  150. public function getCode(): ?string {
  151. return $this->code;
  152. }
  153. public function getDateAdded(): \DateTime {
  154. return $this->dateAdded;
  155. }
  156. public function getLastModified(): ?\DateTime {
  157. return $this->lastModified;
  158. }
  159. public function getStatus(): bool {
  160. return $this->status;
  161. }
  162. public function getVariant(): ?ProductModelVariant {
  163. return $this->variant;
  164. }
  165. public function setId(int $id): void {
  166. $this->id = $id;
  167. }
  168. public function setAttributes($attributes): void {
  169. $this->attributes = $attributes;
  170. }
  171. public function setChildren($children): void {
  172. $this->children = $children;
  173. }
  174. public function setParent($parent): void {
  175. $this->parent = $parent;
  176. }
  177. public function setDescriptions($descriptions): void {
  178. $this->descriptions = $descriptions;
  179. }
  180. public function setPictures($pictures): void {
  181. $this->pictures = $pictures;
  182. }
  183. public function setManufacturer(?Manufacturer $manufacturer): void {
  184. $this->manufacturer = $manufacturer;
  185. }
  186. public function setCountryRestrictions($countryRestrictions): void {
  187. $this->countryRestrictions = $countryRestrictions;
  188. }
  189. public function setCode(?string $code): void {
  190. $this->code = $code;
  191. }
  192. public function setDateAdded(\DateTime $dateAdded): void {
  193. $this->dateAdded = $dateAdded;
  194. }
  195. public function setLastModified(?\DateTime $lastModified): void {
  196. $this->lastModified = $lastModified;
  197. }
  198. public function setStatus(bool $status): void {
  199. $this->status = $status;
  200. }
  201. public function setVariant(?ProductModelVariant $variant): void {
  202. $this->variant = $variant;
  203. }
  204. public function getListingAxis(): ?int {
  205. return $this->listingAxis;
  206. }
  207. public function setListingAxis(?int $listingAxis): void {
  208. $this->listingAxis = $listingAxis;
  209. }
  210. public function getDefaultCategoryId(): ?int {
  211. return $this->defaultCategoryId;
  212. }
  213. public function setDefaultCategoryId(?int $defaultCategoryId): void {
  214. $this->defaultCategoryId = $defaultCategoryId;
  215. }
  216. /**
  217. * Codes des attributs de l'axe utilisé pour l'affichage en listing (plusieurs pour un axe
  218. * composite, ex. "taille,couleur"). Vide si aucun axe n'est configuré.
  219. */
  220. public function getListingAxisCodes(): array {
  221. if (!$this->listingAxis || !$this->variant) {
  222. return [];
  223. }
  224. $axis = $this->listingAxis === 2 ? $this->variant->getAxis2() : $this->variant->getAxis1();
  225. return array_values(array_filter(array_map('trim', explode(',', (string) $axis))));
  226. }
  227. public function hasParent() {
  228. return !empty($this->parent);
  229. }
  230. public function hasChildren() {
  231. return count($this->children) > 0;
  232. }
  233. public function toArray() : array{
  234. $parent = $this->getParent();
  235. $manufacturer = $this->getManufacturer();
  236. $output = [
  237. 'id' => $this->getId(),
  238. 'code' => $this->getCode(),
  239. 'name' => $this->getName(),
  240. 'description' => $this->getDescription(),
  241. 'quantity' => $this->getQuantity(),
  242. 'image' => $this->getImage(),
  243. 'price' => $this->getPrice(),
  244. 'dateAdded' => $this->getDateAdded(),
  245. 'lastModified' => $this->getLastModified(),
  246. 'dateAvailable' => $this->getDateAvailable(),
  247. 'weight' => $this->getWeight(),
  248. 'status' => $this->getStatus()?1:0,
  249. 'taxClass ' => $this->getTaxClass()?$this->getTaxClass()->toArray():0,
  250. 'ordered' => $this->getOrdered(),
  251. 'parent' => empty($parent)?null:$parent->toArray(),
  252. 'manufacturer' => empty($manufacturer)?null:$manufacturer->toArray(),
  253. 'productsQtyBlocks' => $this->getProductsQtyBlocks(),
  254. 'soleil' => $this->getSoleil(),
  255. 'nbChildren' => count($this->getChildren()),
  256. 'limitePanier' => $this->getLimitePanier(),
  257. 'page' => $this->getPage(),
  258. 'garantie' => $this->getGuaranty(),
  259. 'defaultCategoryId' => $this->getDefaultCategoryId(),
  260. 'index' => $this->getIndex(),
  261. 'mvente' => $this->getMvente(),
  262. 'strategique' => $this->getStrategique(),
  263. 'reapproNon' => $this->getReapproNon(),
  264. 'multiple' => $this->getMultiple()
  265. ];
  266. return $output;
  267. }
  268. /**
  269. * @todo Remove once 0 values in the table are converted to NULL.
  270. */
  271. public function loadDescriptions(\Doctrine\ORM\EntityManager $em)
  272. {
  273. $conn = $em->getConnection();
  274. $sql = $conn->prepare('select * from products_description where products_id = '.$this->getId());
  275. $sql->execute();
  276. $rows = $sql->fetchAll();
  277. $descriptions = [];
  278. foreach($rows as $desc){
  279. $description = new ProductModelDescription();
  280. $description->setName($desc['products_name']);
  281. $description->setDescription($desc['products_description']);
  282. $description->setLanguage($em->getRepository('App:Language')->find($desc['language_id']==1?1:4));
  283. $descriptions[] = $description;
  284. }
  285. $this->setDescriptions($descriptions);
  286. }
  287. public function getProductModelDescription($lang='fr') : ?ProductModelDescription {
  288. foreach($this->getDescriptions() as $desc){
  289. if($desc->getLanguage()->getCode() == $lang)
  290. return $desc;
  291. }
  292. return null;
  293. }
  294. public function getProducts() {
  295. return $this->products;
  296. }
  297. public function setProducts($products): void {
  298. $this->products = $products;
  299. }
  300. public function setDefaultProduct(?Product $defaultProduct): void {
  301. $this->defaultProduct = $defaultProduct;
  302. }
  303. /**
  304. * Produit par défaut saisi au backoffice, tel quel (sans repli)
  305. */
  306. public function getConfiguredDefaultProduct(): ?Product {
  307. return $this->defaultProduct;
  308. }
  309. public function getDefaultProduct() : ?Product {
  310. // le produit saisi n'est retenu que s'il fait toujours partie du modèle et reste actif
  311. if($this->defaultProduct && $this->defaultProduct->isActive() && in_array($this->defaultProduct, $this->getAllProducts(), true)) {
  312. return $this->defaultProduct;
  313. }
  314. foreach($this->getAllProducts() as $product) {
  315. if($product->isDefaultVariant())
  316. return $product;
  317. }
  318. return count($this->getAllProducts()) > 0 ? $this->getAllProducts()[0] : null;
  319. }
  320. public function getAllProducts() {
  321. $products = [];
  322. if($this->hasChildren()) {
  323. foreach($this->getChildren() as $child) {
  324. $cProducts = $child->getAllProducts();
  325. if(!empty($cProducts)) {
  326. $products = array_merge ($products, $cProducts);
  327. }
  328. }
  329. }else{
  330. $products = $this->getProducts()->getValues();
  331. }
  332. return $products;
  333. }
  334. /**
  335. * Première image du premier produit du modèle qui en possède une (sous-modèles compris)
  336. */
  337. public function getPicture(): ?PictureProduct {
  338. foreach($this->getAllProducts() as $product) {
  339. foreach($product->getPictures() as $picture) {
  340. return $picture;
  341. }
  342. }
  343. return null;
  344. }
  345. /**
  346. * Image d'une entrée de listing : celle du produit affiché, sinon celle du produit par défaut
  347. * s'il appartient à la même entrée (ex. même couleur), sinon celle d'un autre produit de
  348. * l'entrée, et en dernier recours celle du modèle.
  349. */
  350. public function getListingPicture(?Product $displayed = null): ?PictureProduct {
  351. if($displayed && $displayed->getPicture()) {
  352. return $displayed->getPicture();
  353. }
  354. $listingProducts = $this->getListingProducts($displayed);
  355. $default = $this->getDefaultProduct();
  356. if($default && in_array($default, $listingProducts, true) && $default->getPicture()) {
  357. return $default->getPicture();
  358. }
  359. foreach($listingProducts as $product) {
  360. if($product->getPicture()) {
  361. return $product->getPicture();
  362. }
  363. }
  364. return $this->getPicture();
  365. }
  366. public function getAssets() {
  367. $output = [];
  368. foreach($this->getAllProducts() as $p) {
  369. foreach($p->getPictures() as $picture) {
  370. if(isset($output[$p->getModel()])) {
  371. $output[$p->getModel()][] = $picture;
  372. }else{
  373. $output[$p->getModel()] = [$picture];
  374. }
  375. }
  376. }
  377. return $output;
  378. }
  379. /**
  380. * Description de la langue courante (cf. translate()), à défaut la première disponible
  381. */
  382. private function getCurrentDescription(): ?ProductModelDescription {
  383. if(!empty($this->languageId)){
  384. foreach($this->descriptions as $d){
  385. if($d->getLanguage()->getId()==$this->languageId){
  386. return $d;
  387. }
  388. }
  389. }
  390. return count($this->descriptions) ? $this->descriptions[0] : null;
  391. }
  392. public function getName() {
  393. $description = $this->getCurrentDescription();
  394. return empty($description)?'':$description->getName();
  395. }
  396. public function getUrl() {
  397. $description = $this->getCurrentDescription();
  398. return empty($description)?'':$description->getUrl();
  399. }
  400. /**
  401. * Produits du modèle listés avec $displayed : ceux qui partagent sa valeur sur l'axe de
  402. * listing (ex. même couleur). Tous les produits si aucun axe n'est configuré.
  403. */
  404. public function getListingProducts(?Product $displayed = null): array {
  405. $products = $this->getAllProducts();
  406. $key = $displayed ? $displayed->getListingAxisKey() : null;
  407. if(is_null($key)) {
  408. return $products;
  409. }
  410. return array_values(array_filter($products, fn(Product $p) => $p->getListingAxisKey() === $key));
  411. }
  412. /**
  413. * Prix le plus bas du modèle ; avec $displayed, limité aux produits de la même entrée de
  414. * listing (cf. getListingProducts()).
  415. */
  416. public function getFromPrice($withEcotax = true, ?PriceGroup $priceGroup = null, $withVat = false, ?Product $displayed = null) {
  417. $tmp = null;
  418. foreach($this->getListingProducts($displayed) as $product) {
  419. if(!$product->isStopped() && $product->isActive()){
  420. if(!empty($priceGroup) && !$product->hasPrice($priceGroup)) {
  421. continue;
  422. }
  423. if(is_null($tmp) || ($tmp>$product->getPrice($withEcotax, $priceGroup, $withVat))){
  424. $tmp = $product->getPrice($withEcotax, $priceGroup, $withVat);
  425. }
  426. }
  427. }
  428. return $tmp;
  429. }
  430. /**
  431. * @ORM\PrePersist
  432. */
  433. public function prePersit(\Doctrine\ORM\Event\LifecycleEventArgs $args) {
  434. $this->setDateAdded(new \DateTime);
  435. }
  436. public static function getSearchIndex($lang = 'fr')
  437. {
  438. return self::SEARCH_INDICES[$lang];
  439. }
  440. }