<?php
namespace App\Entity;
use App\Entity\ProductModelDescription;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use App\Helpers\Encoder;
/**
* Products
*
* @ORM\Table(name="products_models", indexes={@ORM\Index(name="IDX_PRODUCTS_MODELS_CODE", columns={"code"})})
* @ORM\Entity(repositoryClass="App\Repository\ProductRepository")
* @ORM\HasLifecycleCallbacks()
*/
class ProductModel extends TranslatedEntity
{
public const VAT_RATE = 0.2;
public const SEARCH_INDICES = [
'fr' => 'fr-models',
'en' => 'en-models',
];
protected $tranlatedEntity = 'ProductModelDescription';
/**
* @var int
*
* @ORM\Column(name="id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="code", type="string", length=50, nullable=true)
*/
private $code;
/**
* @ORM\OneToMany(targetEntity="App\Entity\ProductModelAttribute", mappedBy="productModel")
*/
private $attributes;
/**
* @ORM\OneToMany(targetEntity="App\Entity\ProductModel", mappedBy="parent")
*/
private $children = [];
/**
* @ORM\ManyToOne(targetEntity="App\Entity\ProductModel", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
*/
private $parent;
/**
* @ORM\OneToMany(targetEntity="App\Entity\ProductModelDescription", mappedBy="model")
*/
private $descriptions = [];
/**
* @var App\Entity\Manufacturer|null
*
* @ORM\ManyToOne(targetEntity="App\Entity\Manufacturer")
* @ORM\JoinColumn(name="manufacturers_id", referencedColumnName="manufacturers_id", nullable=true)
*/
private $manufacturer;
/**
* @ORM\OneToMany(targetEntity="App\Entity\ProductModelCountryRestriction", mappedBy="model")
*/
private $countryRestrictions;
/**
* @var \DateTime
*
* @ORM\Column(name="date_added", type="datetime", nullable=false)
*/
private $dateAdded;
/**
* @var \DateTime|null
*
* @ORM\Column(name="last_modified", type="datetime", nullable=true)
*/
private $lastModified;
/**
* @var bool
*
* @ORM\Column(name="status", type="boolean", nullable=false)
*/
private $status = '0';
/**
* @ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="productModel")
*/
private $products = [];
/**
* @var App\Entity\ProductModelVariant|null
*
* @ORM\ManyToOne(targetEntity="App\Entity\ProductModelVariant")
* @ORM\JoinColumn(name="variant_id", referencedColumnName="id", nullable=true)
*/
private $variant;
/**
* Axe de déclinaison (1 = axis1, 2 = axis2 du ProductModelVariant) sur lequel le modèle est
* éclaté dans les listings catégorie : une vignette par valeur de cet axe.
* Null = une seule vignette pour tout le modèle.
*
* @var int|null
*
* @ORM\Column(name="listing_axis", type="smallint", nullable=true)
*/
private $listingAxis;
/**
* Déclinaison affichée à l'arrivée sur la fiche modèle (route product_model).
* Null = repli sur le produit marqué is_default_variant, sinon le premier produit.
*
* @var Product|null
*
* @ORM\ManyToOne(targetEntity="App\Entity\Product")
* @ORM\JoinColumn(name="default_product_id", referencedColumnName="products_id", nullable=true, onDelete="SET NULL")
*/
private $defaultProduct;
/**
* Catégorie par défaut (première catégorie transmise par Akeneo)
*
* @var int|null
*
* @ORM\Column(name="products_cat", type="integer", nullable=true)
*/
private $defaultCategoryId;
public function __construct()
{
$this->children = new \Doctrine\Common\Collections\ArrayCollection();
$this->descriptions = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getId(): int {
return $this->id;
}
public function getAttributes() {
return $this->attributes;
}
public function getChildren() {
return $this->children;
}
public function getParent() {
return $this->parent;
}
public function getDescriptions() {
return $this->descriptions;
}
public function getPictures() {
return $this->pictures;
}
public function getManufacturer(): ? Manufacturer {
return $this->manufacturer;
}
public function getCountryRestrictions() {
return $this->countryRestrictions;
}
public function getCode(): ?string {
return $this->code;
}
public function getDateAdded(): \DateTime {
return $this->dateAdded;
}
public function getLastModified(): ?\DateTime {
return $this->lastModified;
}
public function getStatus(): bool {
return $this->status;
}
public function getVariant(): ?ProductModelVariant {
return $this->variant;
}
public function setId(int $id): void {
$this->id = $id;
}
public function setAttributes($attributes): void {
$this->attributes = $attributes;
}
public function setChildren($children): void {
$this->children = $children;
}
public function setParent($parent): void {
$this->parent = $parent;
}
public function setDescriptions($descriptions): void {
$this->descriptions = $descriptions;
}
public function setPictures($pictures): void {
$this->pictures = $pictures;
}
public function setManufacturer(?Manufacturer $manufacturer): void {
$this->manufacturer = $manufacturer;
}
public function setCountryRestrictions($countryRestrictions): void {
$this->countryRestrictions = $countryRestrictions;
}
public function setCode(?string $code): void {
$this->code = $code;
}
public function setDateAdded(\DateTime $dateAdded): void {
$this->dateAdded = $dateAdded;
}
public function setLastModified(?\DateTime $lastModified): void {
$this->lastModified = $lastModified;
}
public function setStatus(bool $status): void {
$this->status = $status;
}
public function setVariant(?ProductModelVariant $variant): void {
$this->variant = $variant;
}
public function getListingAxis(): ?int {
return $this->listingAxis;
}
public function setListingAxis(?int $listingAxis): void {
$this->listingAxis = $listingAxis;
}
public function getDefaultCategoryId(): ?int {
return $this->defaultCategoryId;
}
public function setDefaultCategoryId(?int $defaultCategoryId): void {
$this->defaultCategoryId = $defaultCategoryId;
}
/**
* Codes des attributs de l'axe utilisé pour l'affichage en listing (plusieurs pour un axe
* composite, ex. "taille,couleur"). Vide si aucun axe n'est configuré.
*/
public function getListingAxisCodes(): array {
if (!$this->listingAxis || !$this->variant) {
return [];
}
$axis = $this->listingAxis === 2 ? $this->variant->getAxis2() : $this->variant->getAxis1();
return array_values(array_filter(array_map('trim', explode(',', (string) $axis))));
}
public function hasParent() {
return !empty($this->parent);
}
public function hasChildren() {
return count($this->children) > 0;
}
public function toArray() : array{
$parent = $this->getParent();
$manufacturer = $this->getManufacturer();
$output = [
'id' => $this->getId(),
'code' => $this->getCode(),
'name' => $this->getName(),
'description' => $this->getDescription(),
'quantity' => $this->getQuantity(),
'image' => $this->getImage(),
'price' => $this->getPrice(),
'dateAdded' => $this->getDateAdded(),
'lastModified' => $this->getLastModified(),
'dateAvailable' => $this->getDateAvailable(),
'weight' => $this->getWeight(),
'status' => $this->getStatus()?1:0,
'taxClass ' => $this->getTaxClass()?$this->getTaxClass()->toArray():0,
'ordered' => $this->getOrdered(),
'parent' => empty($parent)?null:$parent->toArray(),
'manufacturer' => empty($manufacturer)?null:$manufacturer->toArray(),
'productsQtyBlocks' => $this->getProductsQtyBlocks(),
'soleil' => $this->getSoleil(),
'nbChildren' => count($this->getChildren()),
'limitePanier' => $this->getLimitePanier(),
'page' => $this->getPage(),
'garantie' => $this->getGuaranty(),
'defaultCategoryId' => $this->getDefaultCategoryId(),
'index' => $this->getIndex(),
'mvente' => $this->getMvente(),
'strategique' => $this->getStrategique(),
'reapproNon' => $this->getReapproNon(),
'multiple' => $this->getMultiple()
];
return $output;
}
/**
* @todo Remove once 0 values in the table are converted to NULL.
*/
public function loadDescriptions(\Doctrine\ORM\EntityManager $em)
{
$conn = $em->getConnection();
$sql = $conn->prepare('select * from products_description where products_id = '.$this->getId());
$sql->execute();
$rows = $sql->fetchAll();
$descriptions = [];
foreach($rows as $desc){
$description = new ProductModelDescription();
$description->setName($desc['products_name']);
$description->setDescription($desc['products_description']);
$description->setLanguage($em->getRepository('App:Language')->find($desc['language_id']==1?1:4));
$descriptions[] = $description;
}
$this->setDescriptions($descriptions);
}
public function getProductModelDescription($lang='fr') : ?ProductModelDescription {
foreach($this->getDescriptions() as $desc){
if($desc->getLanguage()->getCode() == $lang)
return $desc;
}
return null;
}
public function getProducts() {
return $this->products;
}
public function setProducts($products): void {
$this->products = $products;
}
public function setDefaultProduct(?Product $defaultProduct): void {
$this->defaultProduct = $defaultProduct;
}
/**
* Produit par défaut saisi au backoffice, tel quel (sans repli)
*/
public function getConfiguredDefaultProduct(): ?Product {
return $this->defaultProduct;
}
public function getDefaultProduct() : ?Product {
// le produit saisi n'est retenu que s'il fait toujours partie du modèle et reste actif
if($this->defaultProduct && $this->defaultProduct->isActive() && in_array($this->defaultProduct, $this->getAllProducts(), true)) {
return $this->defaultProduct;
}
foreach($this->getAllProducts() as $product) {
if($product->isDefaultVariant())
return $product;
}
return count($this->getAllProducts()) > 0 ? $this->getAllProducts()[0] : null;
}
public function getAllProducts() {
$products = [];
if($this->hasChildren()) {
foreach($this->getChildren() as $child) {
$cProducts = $child->getAllProducts();
if(!empty($cProducts)) {
$products = array_merge ($products, $cProducts);
}
}
}else{
$products = $this->getProducts()->getValues();
}
return $products;
}
/**
* Première image du premier produit du modèle qui en possède une (sous-modèles compris)
*/
public function getPicture(): ?PictureProduct {
foreach($this->getAllProducts() as $product) {
foreach($product->getPictures() as $picture) {
return $picture;
}
}
return null;
}
/**
* Image d'une entrée de listing : celle du produit affiché, sinon celle du produit par défaut
* s'il appartient à la même entrée (ex. même couleur), sinon celle d'un autre produit de
* l'entrée, et en dernier recours celle du modèle.
*/
public function getListingPicture(?Product $displayed = null): ?PictureProduct {
if($displayed && $displayed->getPicture()) {
return $displayed->getPicture();
}
$listingProducts = $this->getListingProducts($displayed);
$default = $this->getDefaultProduct();
if($default && in_array($default, $listingProducts, true) && $default->getPicture()) {
return $default->getPicture();
}
foreach($listingProducts as $product) {
if($product->getPicture()) {
return $product->getPicture();
}
}
return $this->getPicture();
}
public function getAssets() {
$output = [];
foreach($this->getAllProducts() as $p) {
foreach($p->getPictures() as $picture) {
if(isset($output[$p->getModel()])) {
$output[$p->getModel()][] = $picture;
}else{
$output[$p->getModel()] = [$picture];
}
}
}
return $output;
}
/**
* Description de la langue courante (cf. translate()), à défaut la première disponible
*/
private function getCurrentDescription(): ?ProductModelDescription {
if(!empty($this->languageId)){
foreach($this->descriptions as $d){
if($d->getLanguage()->getId()==$this->languageId){
return $d;
}
}
}
return count($this->descriptions) ? $this->descriptions[0] : null;
}
public function getName() {
$description = $this->getCurrentDescription();
return empty($description)?'':$description->getName();
}
public function getUrl() {
$description = $this->getCurrentDescription();
return empty($description)?'':$description->getUrl();
}
/**
* Produits du modèle listés avec $displayed : ceux qui partagent sa valeur sur l'axe de
* listing (ex. même couleur). Tous les produits si aucun axe n'est configuré.
*/
public function getListingProducts(?Product $displayed = null): array {
$products = $this->getAllProducts();
$key = $displayed ? $displayed->getListingAxisKey() : null;
if(is_null($key)) {
return $products;
}
return array_values(array_filter($products, fn(Product $p) => $p->getListingAxisKey() === $key));
}
/**
* Prix le plus bas du modèle ; avec $displayed, limité aux produits de la même entrée de
* listing (cf. getListingProducts()).
*/
public function getFromPrice($withEcotax = true, ?PriceGroup $priceGroup = null, $withVat = false, ?Product $displayed = null) {
$tmp = null;
foreach($this->getListingProducts($displayed) as $product) {
if(!$product->isStopped() && $product->isActive()){
if(!empty($priceGroup) && !$product->hasPrice($priceGroup)) {
continue;
}
if(is_null($tmp) || ($tmp>$product->getPrice($withEcotax, $priceGroup, $withVat))){
$tmp = $product->getPrice($withEcotax, $priceGroup, $withVat);
}
}
}
return $tmp;
}
/**
* @ORM\PrePersist
*/
public function prePersit(\Doctrine\ORM\Event\LifecycleEventArgs $args) {
$this->setDateAdded(new \DateTime);
}
public static function getSearchIndex($lang = 'fr')
{
return self::SEARCH_INDICES[$lang];
}
}