瀏覽代碼

Formulaire produits & déclinaisons

reduction
Fab 4 年之前
父節點
當前提交
6d2c6355ca
共有 12 個檔案被更改,包括 748 行新增283 行删除
  1. +19
    -6
      ShopBundle/Controller/Admin/ProductFamilyController.php
  2. +32
    -1
      ShopBundle/Form/ProductType.php
  3. +4
    -2
      ShopBundle/Model/Product.php
  4. +18
    -0
      ShopBundle/Model/ProductFamily.php
  5. +2
    -1
      ShopBundle/Resources/config/easy_admin/base.yaml
  6. +5
    -7
      ShopBundle/Resources/public/css/backend/custom.css
  7. +16
    -0
      ShopBundle/Resources/public/js/backend/script/custom.js
  8. +0
    -147
      ShopBundle/Resources/public/js/backend/script/vueapp.js
  9. +117
    -0
      ShopBundle/Resources/public/js/backend/script/vuejs-mixins.js
  10. +284
    -0
      ShopBundle/Resources/public/js/backend/script/vuejs-product-family.js
  11. +198
    -0
      ShopBundle/Resources/views/backend/default/product_macro.html.twig
  12. +53
    -119
      ShopBundle/Resources/views/backend/default/productfamily_form.html.twig

+ 19
- 6
ShopBundle/Controller/Admin/ProductFamilyController.php 查看文件

@@ -49,6 +49,7 @@ class ProductFamilyController extends AdminController
'label' => 'TVA',
'choices' => $choicesTaxRate,
'mapped' => false,
'data'=> 0,
'choice_attr' => function ($choice, $key, $value) {
return ['data-tax-rate-value' => $this->choicesTaxRateParam[$choice]];
},
@@ -90,9 +91,10 @@ class ProductFamilyController extends AdminController

$formBuilder->add('behaviorCountStock', ChoiceType::class, array(
'label' => 'Stock',
'empty_data' => 'by-product-family',
'choices' => array(
'Gèrer le stock par déclinaison' => 'by-product',
'Gèrer le stock par produit' => 'by-product-family'
'Gèrer le stock par produit' => 'by-product-family',
'Gèrer le stock par déclinaison' => 'by-product'
),
'multiple' => false,
'expanded' => true
@@ -143,9 +145,9 @@ class ProductFamilyController extends AdminController

protected function processProducts($entity)
{
//si il existe un et un seul produit pour ce product family n'ajoute rien supprime rien
if(count($entity->getProducts()) == 0) {
$product = new Product();
$product->setTitle($entity->getTitle()) ;
$product->setCreatedBy($this->getUser()) ;
$product->setUpdatedBy($this->getUser()) ;
$product->setProductFamily($entity) ;
@@ -154,10 +156,15 @@ class ProductFamilyController extends AdminController
$entity->addProduct($product) ;
}
else {

foreach($entity->getProducts() as $product) {
$product->setProductFamily($entity) ;
$product->setCreatedBy($this->getUser()) ;
$product->setUpdatedBy($this->getUser()) ;
$this->em->persist($product);
$entity->addProduct($product) ;

// die('ncici');
}
}
}
@@ -211,7 +218,11 @@ class ProductFamilyController extends AdminController
$editForm = $this->executeDynamicMethod('create<EntityName>EditForm', [$entity, $fields]);
$deleteForm = $this->createDeleteForm($this->entity['name'], $id);


$sortableProductsField = array();
foreach($editForm->get('products')->getData() as $k=>$product){
$sortableProductsField[$product->getPosition()] = $k;
}
ksort($sortableProductsField);

$editForm->handleRequest($this->request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
@@ -234,7 +245,8 @@ class ProductFamilyController extends AdminController
'entity_fields' => $fields,
'entity' => $entity,
'delete_form' => $deleteForm->createView(),
'categories' => $categories
'categories' => $categories,
'sortableProductsField' => $sortableProductsField
];

return $this->executeDynamicMethod('render<EntityName>Template', ['edit', $this->entity['templates']['edit'], $parameters]);
@@ -269,7 +281,7 @@ class ProductFamilyController extends AdminController
$this->dispatch(EasyAdminEvents::POST_NEW, [
'entity_fields' => $fields,
'form' => $newForm,
'entity' => $entity,
'entity' => $entity
]);


@@ -281,6 +293,7 @@ class ProductFamilyController extends AdminController
'entity_fields' => $fields,
'entity' => $entity,
'categories' => $categories,
'sortableProductsField' => array()
];

return $this->executeDynamicMethod('render<EntityName>Template', ['new', $this->entity['templates']['new'], $parameters]);

+ 32
- 1
ShopBundle/Form/ProductType.php 查看文件

@@ -8,11 +8,13 @@ use FOS\CKEditorBundle\Form\Type\CKEditorType;
use Lc\ShopBundle\Context\ProductFamilyInterface;
use Lc\ShopBundle\Context\ProductInterface;
use Lc\ShopBundle\Context\TaxRateInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use function PHPSTORM_META\type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

@@ -27,10 +29,22 @@ class ProductType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
{

$builder->add('title') ;
$builder->add('titleInherited', CheckboxType::class, array(
'mapped'=>false,
'required'=>false
));

$builder->add('title', TextType::class, array(
"required" => false
)) ;

$builder->add('unitInherited', CheckboxType::class, array(
'mapped'=>false,
'required'=>false
));
$builder->add('unit', ChoiceType::class, array(
'label' => 'Unité',
"required" => false,
'choices' => array(
'pièce' => 'piece',
'g' => 'g',
@@ -40,20 +54,35 @@ class ProductType extends AbstractType
),
));

$builder->add('weightInherited', CheckboxType::class, array(
'mapped'=>false,
'required'=>false
));
$builder->add('weight', NumberType::class, array(
'label' => 'Poids',
"required" => false,
'attr' => [
'append_html' => 'g'
]
));

$builder->add('stepInherited', CheckboxType::class, array(
'mapped'=>false,
'required'=>false
));
$builder->add('step', NumberType::class, array(
'label' => 'Pas',
"required" => false,
'help' => 'Quantité à incrémenter / décrémenter lors des mouvements de quantité',
));

$builder->add('priceInherited', CheckboxType::class, array(
'mapped'=>false,
'required'=>false
));
$builder->add('price', NumberType::class, array(
'label' => 'Prix',
"required" => false
));

$builder->add('priceWithTax', NumberType::class, array(
@@ -67,6 +96,8 @@ class ProductType extends AbstractType
'required' => false,
));

$builder->add('position', HiddenType::class);

}

public function configureOptions(OptionsResolver $resolver)

+ 4
- 2
ShopBundle/Model/Product.php 查看文件

@@ -3,12 +3,14 @@
namespace Lc\ShopBundle\Model;

use Doctrine\ORM\Mapping as ORM;
use Lc\ShopBundle\Context\SortableInterface;

/**
* @ORM\MappedSuperclass()
*/
abstract class Product extends AbstractEntity
abstract class Product extends AbstractEntity implements SortableInterface
{
use SortableTrait;
/**
* @ORM\ManyToOne(targetEntity="Lc\ShopBundle\Context\ProductFamilyInterface", inversedBy="products")
* @ORM\JoinColumn(nullable=false)
@@ -16,7 +18,7 @@ abstract class Product extends AbstractEntity
protected $productFamily;

/**
* @ORM\Column(type="string", length=255)
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $title;


+ 18
- 0
ShopBundle/Model/ProductFamily.php 查看文件

@@ -74,6 +74,12 @@ abstract class ProductFamily extends AbstractDocumentEntity
*/
protected $behaviorCountStock;


/**
* @ORM\Column(type="boolean")
*/
protected $activeProducts;

public function __construct()
{
$this->productCategories = new ArrayCollection();
@@ -261,4 +267,16 @@ abstract class ProductFamily extends AbstractDocumentEntity

return $this;
}

public function getActiveProducts(): ?bool
{
return $this->activeProducts;
}

public function setActiveProducts(bool $activeProducts): self
{
$this->activeProducts = $activeProducts;

return $this;
}
}

+ 2
- 1
ShopBundle/Resources/config/easy_admin/base.yaml 查看文件

@@ -14,7 +14,8 @@ easy_admin:
- '/bundles/lcshop/js/backend/script/setup-ckfinder.js'
- '/bundles/lcshop/js/backend/script/utils.js'
- '/bundles/lcshop/js/backend/script/custom.js'
- '/bundles/lcshop/js/backend/script/vueapp.js'
- '/bundles/lcshop/js/backend/script/vuejs-mixins.js'
- '/bundles/lcshop/js/backend/script/vuejs-product-family.js'
css:
- '/bundles/lcshop/css/backend/custom.css'


+ 5
- 7
ShopBundle/Resources/public/css/backend/custom.css 查看文件

@@ -77,10 +77,8 @@
text-align: right ;
}

.new-productfamily .btn-remove-product,
.edit-productfamily .btn-remove-product {
position: absolute ;
top: 30px ;
right: 20px ;
}

/* Product */
.product-form-modal{display: none;}
.product-form.modal .form-check-label{font-style: italic; color: #666; text-align: left;}
.products-collection-table .inherited{color: #888; font-style: italic; font-weight: initial;}
.products-collection-table td{}

+ 16
- 0
ShopBundle/Resources/public/js/backend/script/custom.js 查看文件

@@ -2,6 +2,8 @@ jQuery(document).ready(function () {
custom_switch_merchants();
initLcSortableList();
initLcCkEditor();
initLcSortableProductsList();
initLcSortableList();
});

function custom_switch_merchants() {
@@ -10,6 +12,20 @@ function custom_switch_merchants() {
});
}

function initLcSortableProductsList(){
$('.lc-sortable-products tbody').sortable({
placeholder: "ui-state-highlight"
});
$('.lc-sortable-products tbody').on("sortupdate", function (event, ui) {
updateSortableProducts();
});
}
function updateSortableProducts(){
$('.lc-sortable-products tr.lc-draggable').each(function (index, li) {
$(li).find('.field-position').val(index);
});
}

function initLcSortableList() {

$('.lc-sortable tbody').sortable({

+ 0
- 147
ShopBundle/Resources/public/js/backend/script/vueapp.js 查看文件

@@ -1,147 +0,0 @@

// Reference array sent to dynamic staticRenderFns
var staticRenderFns = [];

Vue.component('component-unit', {
props: ['template', 'keyForm', 'taxRateValue'],
data() {
return {
templateRender: null,
unit: null,
price: null,
priceWithTax: null
};
},
mounted: function() {
this.unit = $('#value-unit-'+this.keyForm).val() ;
this.price = parseFloat($('#value-price-'+this.keyForm).val()).toFixed(3) ;
this.priceUpdate('priceWithTax') ;
},
methods: {
getUnitReference: function () {
if (this.unit == 'g') {
return 'kg';
} else if (this.unit == 'ml') {
return 'L';
} else {
return this.unit;
}
},
changeTaxRate: function() {
this.$emit('tax-rate-change') ;
this.changePriceWithTax() ;
},
changePrice: function() {
this.priceUpdate('price') ;
},
changePriceWithTax: function() {
this.priceUpdate('priceWithTax') ;
},
priceUpdate: function(priceType) {
var taxRate = this.getTaxRate() ;
if(priceType == 'priceWithTax') {
this.price = parseFloat(this.price.replace(',','.')).toFixed(3) ;
this.priceWithTax = getPriceWithTax(this.price, taxRate);
}
else {
this.priceWithTax = parseFloat(this.priceWithTax.replace(',','.')).toFixed(2) ;
this.price = getPrice(this.priceWithTax, taxRate) ;
}
},
getTaxRate: function() {
var taxRate = this.taxRateValue ;
if(this.taxRateValue == -1) {
var taxRate = $('#productfamily_taxRate').find('option:selected').data('tax-rate-value');
if(typeof taxRate == 'undefined') {
taxRate = 0 ;
}
}

return taxRate ;
}
},
render(h) {
if (!this.templateRender) {
return h('div', 'loading...');
} else { // If there is a template, I'll show it
return this.templateRender();
}
},
watch: {
taxRateValue: function(newVal, oldVal) {
this.changePriceWithTax() ;
},
// Every time the template prop changes, I recompile it to update the DOM
template: {
immediate: true, // makes the watcher fire on first render, too.
handler() {
if (this.template) {
var res = Vue.compile(this.template);

this.templateRender = res.render;

// staticRenderFns belong into $options,
// appearantly
this.$options.staticRenderFns = []

// clean the cache of static elements
// this is a cache with the results from the staticRenderFns
this._staticTrees = []

// Fill it with the new staticRenderFns
for (var i in res.staticRenderFns) {
//staticRenderFns.push(res.staticRenderFns[i]);
this.$options.staticRenderFns.push(res.staticRenderFns[i])
}
}
}
}
}
});


appProductFamily = new Vue({
el: '#lc-product-family-edit',
delimiters: ['${', '}'],
data: {
indexFormProduct: 0,
taxRateValue: -1,
formProductArray: [],
currentSection: 'general',
sectionsArray: [
{
name: 'general',
nameDisplay: 'Général'
},
{
name: 'price',
nameDisplay: 'Prix / stock'
},
{
name: 'products',
nameDisplay: 'Déclinaisons'
}
]
},
methods: {
changeSection: function (section) {
this.currentSection = section.name;
},
setTaxRateValue: function()  {
this.taxRateValue = $('#productfamily_taxRate').find('option:selected').data('tax-rate-value') ;
},
addFormProduct: function() {
var collectionHolder = $('ul.products');
var prototype = collectionHolder.data('prototype');
var newForm = prototype;
newForm = newForm.replace(/__name__/g, this.indexFormProduct);
this.formProductArray.push(newForm) ;
this.indexFormProduct ++ ;
},
deleteFormProduct: function(key) {
this.formProductArray.splice(key, 1) ;
}
},
mounted() {
}
});

+ 117
- 0
ShopBundle/Resources/public/js/backend/script/vuejs-mixins.js 查看文件

@@ -0,0 +1,117 @@

let mixinPriceProduct = {
data() {
return {
price: null,
priceWithTax: null
};
},
methods: {
changePrice: function () {
this.priceUpdate('price');
},
changePriceWithTax: function () {
this.priceUpdate('priceWithTax');
},
priceUpdate: function (priceType) {
if (priceType == 'priceWithTax' && this.price) {
this.price = parseFloat(this.price.replace(',', '.')).toFixed(3);
this.priceWithTax = getPriceWithTax(this.price, this.productFamily.taxRateValue );
} else if (this.priceWithTax){
this.priceWithTax = parseFloat(this.priceWithTax.replace(',', '.')).toFixed(2);
this.price = getPrice(this.priceWithTax, this.productFamily.taxRateValue );
}
}
}

};

let mixinPriceWithTaxField = {
data() {
return {
price: null,
priceWithTax: null,
taxRate:null,
taxRateValue:null
};
},
mounted: function () {
this.setTaxRateValue();
},
methods: {
/*changeTaxRate: function () {
this.$emit('tax-rate-change');
this.changePriceWithTax();
},*/
changePrice: function () {
this.priceUpdate('price');
},
changePriceWithTax: function () {
this.priceUpdate('priceWithTax');
},
priceUpdate: function (priceType) {

if (priceType == 'priceWithTax' && this.price) {
this.price = parseFloat(this.price.replace(',', '.')).toFixed(3);
this.priceWithTax = getPriceWithTax(this.price, this.taxRateValue );
} else if (this.priceWithTax){
this.priceWithTax = parseFloat(this.priceWithTax.replace(',', '.')).toFixed(2);
this.price = getPrice(this.priceWithTax, this.taxRateValue );
}
},
setTaxRateValue: function () {
this.taxRateValue = $('#productfamily_taxRate').find('option:selected').data('tax-rate-value');
}
},
watch:{
taxRate: function (){
this.setTaxRateValue();
this.changePriceWithTax();
}
}

};


let mixinTemplate = {
data() {
return {
templateRender: null,
}
},
render(h) {
if (!this.templateRender) {
return h('div', 'loading...');
} else { // If there is a template, I'll show it
return this.templateRender();
}
},
watch: {
// Every time the template prop changes, I recompile it to update the DOM
template: {
immediate: true, // makes the watcher fire on first render, too.
handler() {
if (this.template) {
var res = Vue.compile(this.template);

this.templateRender = res.render;

// staticRenderFns belong into $options,
// appearantly
this.$options.staticRenderFns = []

// clean the cache of static elements
// this is a cache with the results from the staticRenderFns
this._staticTrees = []

// Fill it with the new staticRenderFns
for (var i in res.staticRenderFns) {
//staticRenderFns.push(res.staticRenderFns[i]);
this.$options.staticRenderFns.push(res.staticRenderFns[i])
}
}
}
}
}
}


+ 284
- 0
ShopBundle/Resources/public/js/backend/script/vuejs-product-family.js 查看文件

@@ -0,0 +1,284 @@
// Reference array sent to dynamic staticRenderFns
var staticRenderFns = [];


Vue.component('product-unit-price', {
mixins: [mixinPriceWithTaxField, mixinTemplate],
props: ['template', 'keyForm'],
data() {
return Object.assign(
{
step: null,
unit: null,
weight: null
}, window.productUnitPriceValues);

},

methods: {
getUnitReference: function () {
if (this.unit == 'g') {
return 'kg';
} else if (this.unit == 'ml') {
return 'L';
} else {
return this.unit;
}
}
},
mounted: function () {
this.priceUpdate('priceWithTax');
},
watch: {

/*log($('.product-form.modal .select-unit'));
$('.product-form.modal .select-unit').find('option:selected').each(function(i,elm){
$(this).prop("selected", false);
});
log($('.product-form.modal .select-unit'));*/

}
});

Vue.component('product-form', {
mixins: [mixinPriceProduct, mixinTemplate],
props: ['template', 'keyForm', 'productFamily'],
data() {
return Object.assign(
{
title: null,
titleInherited: null,
fieldToUpdate: ['title', 'unit', 'weight', 'step', 'price'],
unit: null,
unitInherited: null,
step: null,
stepInherited: null,
weight: null,
weightInherited: null,
price: null,
priceInherited: null,
priceWithTax: null,
stock: null,
availableQuantity: null
}, window.productForm[this.keyForm])
},
mounted: function () {
//INIT VAR
updateSortableProducts();
//METHOD
this.updateProductForm();
this.updateProductView();
},
methods: {
getUnitReference: function () {
if (this.productFamily.unit == 'g') {
return 'kg';
} else if (this.productFamily.unit == 'ml') {
return 'L';
} else {
return this.productFamily.unit;
}
},
updateProductForm: function () {
this.changePriceWithTax();
$('#productfamily_products_' + this.keyForm + '_unit').find('option').hide();
log('fefe');
switch (this.productFamily.unit) {
case 'kg' :
case 'g' :
$('.products-collection-table').find('th.step, td.step').show();
$('.products-collection-table').find('th.weight, td.weight').hide();
$('#productfamily_products_' + this.keyForm + '_unit').find('option[value="g"]').show();
$('#productfamily_products_' + this.keyForm + '_unit').find('option[value="kg"]').show();
break;
case 'ml':
case 'L' :
$('.products-collection-table').find('th.step, td.step').show();
$('.products-collection-table').find('th.weight, td.weight').hide();
$('#productfamily_products_' + this.keyForm + '_unit').find('option[value="ml"]').show();
$('#productfamily_products_' + this.keyForm + '_unit').find('option[value="L"]').show();
break;
case 'piece':
$('.products-collection-table').find('th.step, td.step').hide();
$('.products-collection-table').find('th.weight, td.weight').show();
$('#productfamily_products_' + this.keyForm + '_unit').find('option[value="piece"]').show();
break;
}
},

updateProductView: function () {

//NE PAS SUPPRIMER
/* for (i = 0; i < this.fieldToUpdate.length; i++) {
value = this[this.fieldToUpdate[i]];
inherited = this[this.fieldToUpdate[i]+'Inherited'];
inheritedValue = this['productFamily'][this.fieldToUpdate[i]];

if (value) {
this[this.fieldToUpdate[i]+'Inherited'] = false;
$('#product-item-' + this.keyForm).find('.'+this.fieldToUpdate[i]).html(value);
} else {
this[this.fieldToUpdate[i]+'Inherited'] = true;
$('#product-item-' + this.keyForm).find('.'+this.fieldToUpdate[i]).html('<span class="inherited">' + inheritedValue + '</span>');
}
}*/

if (this.title) {
this.titleInherited = false;
$('#product-item-' + this.keyForm).find('.title').html(this.title);
} else {
this.titleInherited = true;
$('#product-item-' + this.keyForm).find('.title').html('<span class="inherited">' + this.productFamily.title + '</span>');
}

if (this.unit) {
this.unitInherited = false;
$('#product-item-' + this.keyForm).find('.unit').html(this.unit);
} else {
this.unitInherited = true;
$('#product-item-' + this.keyForm).find('.unit').html('<span class="inherited">' + this.productFamily.unit + '</span>');
}

if (this.step) {
this.stepInherited = false;
$('#product-item-' + this.keyForm).find('.step').html(this.step + ' / '+this.unit);
} else {
this.stepInherited = true;
$('#product-item-' + this.keyForm).find('.step').html('<span class="inherited">' + this.productFamily.step + ' / '+ this.productFamily.unit+'</span>');
}

if (this.weight) {
this.weightInherited = false;
$('#product-item-' + this.keyForm).find('.weight').html(this.weight + ' / '+this.getUnitReference());
} else {
this.weightInherited = true;
$('#product-item-' + this.keyForm).find('.weight').html('<span class="inherited">' + this.productFamily.weight + ' / '+this.getUnitReference()+'</span>');
}

if (this.price) {
this.priceInherited = false;
$('#product-item-' + this.keyForm).find('.price').html(this.price + ' € HT / '+this.getUnitReference());
} else {
this.priceInherited = true;
$('#product-item-' + this.keyForm).find('.price').html('<span class="inherited">' + this.productFamily.price + ' € HT/ '+this.getUnitReference()+'</span>');
}
log(this.productFamily.behaviorCountStock);
if (this.productFamily.behaviorCountStock == 'by-product-family') {
$('.products-collection-table').find('.stock').hide();
} else {
$('.products-collection-table').find('.stock').show();
$('#product-item-' + this.keyForm).find('.stock').html(this.availableQuantity + ' / '+this.getUnitReference());
}
},
saveProductForm: function () {
this.updateProductView();

$('#form-product-modal-' + this.keyForm).modal('hide');

},
},
watch: {
titleInherited: function (val) {
if(val)this.title = null;
},
unitInherited: function (val) {
if(val)this.unit = null;
},
weightInherited: function (val) {
if(val)this.weight = null;
},
stepInherited: function (val) {
if(val)this.step = null;
},
priceInherited: function (val) {
if(val)this.price = null;
},
}
});


appProductFamily = new Vue({
el: '#lc-product-family-edit',
delimiters: ['${', '}'],
computed: {
productFamily: function () {
return {
'title': this.title,
'behaviorCountStock': this.behaviorCountStock,
'unit': this.$refs.productUnitPrice.unit,
'step': this.$refs.productUnitPrice.step,
'weight': this.$refs.productUnitPrice.weight,
'price': this.$refs.productUnitPrice.price,
'taxRate': this.$refs.productUnitPrice.taxRate,
'taxRateValue': this.$refs.productUnitPrice.taxRateValue
};
},
},
data() {

return Object.assign(
{
indexFormProduct: 0,
title: null,
activeProducts: false,
behaviorCountStock: null,
formProductArray: [],
currentSection: 'general',
sectionsArray: [
{
name: 'general',
nameDisplay: 'Général'
},
{
name: 'price',
nameDisplay: 'Prix / stock'
},
{
name: 'products',
nameDisplay: 'Déclinaisons'
}
]
}, window.appProductFamilyValues);
},
methods: {

changeSection: function (section) {
this.updateChild();
this.currentSection = section.name;
},
addProductForm: function () {
var $collectionHolder = $('tbody.products-collection');
var prototype = $collectionHolder.data('prototype');
var newForm = prototype;
newForm = newForm.replace(/__name__/g, this.indexFormProduct);
this.formProductArray.push(newForm);
this.indexFormProduct++;
},
editProductForm: function (key) {
$('#form-product-modal-' + key).modal();
this.updateChild();
},

deleteProductForm: function (key) {
this.formProductArray.splice(key, 1);
},
getUnitReference: function () {
if (typeof this.$refs.productUnitPrice !== 'undefined') {
return this.$refs.productUnitPrice.getUnitReference();
}
},
updateChild: function () {
if (typeof this.$refs.productForm !== 'undefined') {
for (i = 0; i < this.$refs.productForm.length; i++) {
this.$refs.productForm[i].updateProductForm();
this.$refs.productForm[i].updateProductView();
}
}
}
},
watch: {
title: function () {
this.updateChild()
},
}
});

+ 198
- 0
ShopBundle/Resources/views/backend/default/product_macro.html.twig 查看文件

@@ -0,0 +1,198 @@
{% macro printProductRow(product) %}

<div :id="'form-product-modal-'+ keyForm" class="product-form modal" tabindex="-1" role="dialog"
data-backdrop="static">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Déclinaison</h5>
</div>
<div class="modal-body">
<div class="form-group row">
{{ form_label(product.title, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}

<div class="form-widget col-10">
{{ form_widget(product.titleInherited, {'label': 'Utiliser la valeur par défaut : {{ productFamily.title }}','attr' : {'v-model' : 'titleInherited'}}) }}

<div v-show="titleInherited == false">
{{ form_widget(product.title, {'attr' : {'v-model' : 'title'}}) }}
{{ form_help(product.title) }}
</div>

</div>
</div>
<div class="form-group row">
{{ form_label(product.unit, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}
<div class="form-widget col-10">
{{ form_widget(product.unitInherited, {'label': 'Utiliser la valeur par défaut : {{ productFamily.unit }}','attr' : {'v-model' : 'unitInherited'}}) }}

<div v-show="unitInherited == false">
{{ form_widget(product.unit, {"attr":{'v-model': 'unit', 'class' : 'select-unit'}}) }}
{{ form_help(product.unit) }}
</div>
</div>
</div>
<div v-if="unit == 'piece'" class="form-group row field-weight">
{{ form_label(product.weight, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}
<div class="form-widget col-10">
{{ form_widget(product.weightInherited, {'label': 'Utiliser la valeur par défaut : {{ productFamily.weight }}','attr' : {'v-model' : 'weightInherited'}}) }}

<div v-show="weightInherited == false" class="input-group">
{{ form_widget(product.weight, {'attr' : {'v-model' : 'weight'}}) }}
<div class="input-group-append">
<span class="input-group-text">g</span>
</div>
</div>
{{ form_help(product.weight) }}
</div>
</div>
<div v-else class="form-group row field-step">
{{ form_label(product.step, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}
<div class="form-widget col-8">
{{ form_widget(product.stepInherited, {'label': 'Utiliser la valeur par défaut : {{ productFamily.step }}','attr' : {'v-model' : 'stepInherited'}}) }}

<div v-show="stepInherited == false" class="input-group">
{{ form_widget(product.step, {'attr' : {'v-model' : 'step'}}) }}
<div class="input-group-append">
<span class="input-group-text">{% verbatim %}{{ unit ? unit : productFamily.unit }}{% endverbatim %}</span>
</div>
</div>
{{ form_help(product.step) }}
</div>
</div>

<div class="form-group row field-price">
{{ form_label(product.price, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}
<div class="form-widget col-10">

{{ form_widget(product.priceInherited, {'label': 'Utiliser la valeur par défaut : {{ productFamily.price }}','attr' : {'v-model' : 'priceInherited'}}) }}

<div v-show="priceInherited == false" class="input-group">
{{ form_widget(product.price, {'attr' : {'v-model': 'price', '@change' : 'changePriceWithTax'}}) }}
<div class="input-group-append">
<span class="input-group-text">€ HT / {% verbatim %}{{ getUnitReference() }}{% endverbatim %}</span>
</div>
</div>
</div>
</div>

<div class="form-group row field-price">
<div class="col-2">&nbsp;</div>
<div class="form-widget col-10">
<div v-show="priceInherited == false" class="input-group">
{{ form_widget(product.priceWithTax, {'attr' : {'v-model': 'priceWithTax', '@change' : 'changePrice'}}) }}
<div class="input-group-append">
<span class="input-group-text">€ TTC / {% verbatim %}{{ getUnitReference() }}{% endverbatim %}</span>
</div>
</div>
</div>
</div>

<div v-show="productFamily.behaviorCountStock == 'by-product'"
class="form-group row field-available-quantity">
{{ form_label(product.availableQuantity, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}
<div class="form-widget">
<div class="input-group">
{{ form_widget(product.availableQuantity, {'attr' : {'v-model': 'availableQuantity'}}) }}
<div class="input-group-append">
<span class="input-group-text">{% verbatim %}{{ getUnitReference() }}{% endverbatim %}</span>
</div>
</div>
{{ form_help(product.availableQuantity) }}
</div>
</div>
</div>
{{ form_widget(product.position, {'attr' : {'class' : 'field-position'}}) }}
<div class="modal-footer">
<button @click="saveProductForm()" type="button" class="btn btn-primary">Fermer
</button>
</div>
</div>
</div>
</div>
{% endmacro %}
{% import _self as formMacros %}
<table class="table datagrid sortable lc-sortable-products products-collection-table">
<thead>
<tr>
<th>
<span></span>
</th>
<th class="string">
<span>Titre</span>
</th>
<th class="string ">
<span>Unité</span>
</th>
<th class="weight">
<span>Poids</span>
</th>
<th class="step">
<span>Pas</span>
</th>
<th class="price">
<span>Prix HT</span>
</th>
<th class="stock">
<span>Stock</span>
</th>
<th class="">
<span>Action</span>
</th>
</tr>
</thead>
<tbody class="products-collection" :data-index="formProductArray.length"
data-prototype="{{ formMacros.printProductRow(form.products.vars.prototype)|e('html_attr') }}">
<tr class="product-item lc-draggable" :id="'product-item-'+key"
v-for="(formProduct, key) in formProductArray">
<td><i class="fa fa-fw fa-sort"></i></td>
<td class="title"></td>
<td class="unit"></td>
<td class="weight"></td>
<td class="step"></td>
<td class="price"></td>
<td class="stock"></td>
<td>
<button type="button" class="btn-edit-product btn btn-default"
@click="editProductForm(key)"><i class="fa fa-pen"></i> Éditer cette déclinaison
</button>
<button type="button" class="btn-remove-product btn btn-default"
@click="deleteProductForm(key)"><i class="fa fa-trash"></i> Supprimer cette
déclinaison
</button>
<product-form ref="productForm" v-bind:product-family="productFamily" :template="formProduct"
:key-form="key"></product-form>
</td>
</tr>
</tbody>
</table>
<button type="button" class="add_tag_link btn-add-product btn btn-default" @click="addProductForm"><span
class="fa fa-plus"></span> Ajouter une déclinaison
</button>
<script>
window.productForm = new Array();
</script>

{% for keyForm,i in sortableProductsField %}
{% set product = form.products[i] %}
<script>
window.productForm[{{ keyForm }}] = {
{% if product.vars.value.title %}title: "{{ product.vars.value.title }}",{% endif %}
{% if product.vars.value.unit %}unit: "{{ product.vars.value.unit }}",{% endif %}
{% if product.vars.value.step %}step: parseInt({{ product.vars.value.step }}),{% endif %}
{% if product.vars.value.weight %}weight: parseFloat({{ product.vars.value.weight }}),{% endif %}
{% if product.vars.value.price %}price: parseFloat({{ product.vars.value.price }}).toFixed(3),{% endif %}
{% if product.vars.value.availableQuantity %}availableQuantity: parseInt({{ product.vars.value.availableQuantity }}){% endif %}
};
jQuery(document).ready(function () {
var formProduct = '{{ formMacros.printProductRow(product)|replace({"\n":' ', "\r":' ', "'" : "\\'"})|raw }}';

appProductFamily.formProductArray.push(formProduct);
appProductFamily.indexFormProduct++;
});
</script>

{% endfor %}

{% do form.products.setRendered %}


+ 53
- 119
ShopBundle/Resources/views/backend/default/productfamily_form.html.twig 查看文件

@@ -1,7 +1,9 @@
{{ form_start(form) }}
{{ form_start(form, {"attr": {'@change' : 'formUpdated'}}) }}

<div class="lc-vue-js-container" id="lc-product-family-edit">
<div id="nav-params">
<button type="button" v-for="section in sectionsArray"
v-if="(section.name == 'products' && activeProducts == true) || (section.name != 'products')"
:class="'btn '+((currentSection == section.name) ? 'btn-primary' : 'btn-default')"
@click="changeSection(section)">
${ section.nameDisplay }
@@ -10,6 +12,23 @@
</button>
</div>
<div class="form">

<script>// rendered by server
window.appProductFamilyValues = {
{% if form.vars.value.title %}title: "{{ form.vars.value.title }}",{% endif %}
{% if form.behaviorCountStock.vars.value %}behaviorCountStock: "{{ form.behaviorCountStock.vars.value }}",{% endif %}
{% if form.vars.value.availableQuantity %}availableQuantity: "{{ form.vars.value.availableQuantity }}",{% endif %}
{% if form.vars.value.activeProducts %}activeProducts: "{{ form.vars.value.activeProducts }}"{% endif %}
};
window.productUnitPriceValues = {
{% if form.vars.value.unit %}unit: "{{ form.vars.value.unit }}",{% endif %}
{% if form.vars.value.step %}step: parseInt({{ form.vars.value.step }}),{% endif %}
{% if form.vars.value.weight %}weight: parseFloat({{ form.vars.value.weight }}),{% endif %}
{% if form.taxRate.vars.value != "" %}taxRate: "{{ form.taxRate.vars.value }}",{% endif %}
{% if form.vars.value.price %}price: parseFloat({{ form.vars.value.price }}).toFixed(3){% endif %}
}
</script>

<div v-show="currentSection == 'general'" class="panel panel-default">
<div class="row">
<div class="field-group col-8">
@@ -20,7 +39,7 @@
{{ form_row(form.supplier) }}
</div>
<div class="col-12">
{{ form_row(form.title) }}
{{ form_row(form.title, {"attr" : {"v-model" : "title"}}) }}
</div>
<div class="col-12">
{{ form_row(form.image) }}
@@ -57,16 +76,13 @@
<div class="field-group col-6">
<fieldset>
<legend>Prix</legend>
<component-unit inline-template key-form="productfamily" tax-rate-value="-1" v-on:tax-rate-change="setTaxRateValue">
<product-unit-price ref="productUnitPrice" inline-template key-form="productfamily">
<div class="row">
<div class="col-10">
{{ form_row(form.taxRate, {'attr': {'@change':'changeTaxRate'}}) }}
{{ form_row(form.taxRate, {'attr': {'v-model':'taxRate'}}) }}
</div>
<div class="col-10">
{{ form_row(form.unit, {"attr":{'v-model': 'unit'}}) }}
{% if form.vars.value %}
<input type="hidden" :id="'value-unit-'+keyForm" value="{{ form.vars.value.unit }}" />
{% endif %}
</div>

<div class="col-10">
@@ -74,7 +90,7 @@
{{ form_label(form.weight) }}
<div class="form-widget">
<div class="input-group">
{{ form_widget(form.weight) }}
{{ form_widget(form.weight, {'attr' : {'v-model': 'weight'}}) }}
<div class="input-group-append">
<span class="input-group-text">g</span>
</div>
@@ -86,7 +102,7 @@
{{ form_label(form.step) }}
<div class="form-widget">
<div class="input-group">
{{ form_widget(form.step) }}
{{ form_widget(form.step, {'attr' : {'v-model': 'step'}}) }}
<div class="input-group-append">
<span class="input-group-text">${ unit }</span>
</div>
@@ -105,9 +121,6 @@
<div class="input-group-append">
<span class="input-group-text">€ HT / ${ getUnitReference() }</span>
</div>
{% if form.vars.value %}
<input type="hidden" :id="'value-price-'+keyForm" value="{{ form.vars.value.price }}" />
{% endif %}
</div>
</div>
</div>
@@ -126,16 +139,40 @@
</div>
</div>
</div>
</component-unit>
</product-unit-price>
</fieldset>
</div>
<div class="field-group col-6">
<fieldset>
<legend>Déclinaisons</legend>
{{ form_row(form.activeProducts, {"attr": {"v-model": 'activeProducts'}}) }}
</fieldset>
<fieldset>
<legend>Stock</legend>
<div class="row">
<div class="col-12">
{{ form_row(form.behaviorCountStock) }}
{{ form_row(form.availableQuantity) }}
{{ form_label(form.behaviorCountStock) }}
{% for field in form.behaviorCountStock %}
{% if field.vars.value == "by-product" %}
<div v-if="activeProducts == true">
{{ form_widget(field, {"attr" : {"v-model" : 'behaviorCountStock'}}) }}
</div>
{% else %}
{{ form_widget(field, {"attr" : {"v-model" : 'behaviorCountStock'}}) }}
{% endif %}
{% endfor %}
<div v-show="behaviorCountStock == 'by-product-family'" class="form-group">
{{ form_label(form.availableQuantity) }}
<div class="form-widget">
<div class="input-group">
{{ form_widget(form.availableQuantity) }}
<div class="input-group-append">
<span class="input-group-text">${ getUnitReference() }</span>
</div>
</div>
{{ form_help(form.availableQuantity) }}
</div>
</div>
</div>
</div>
</fieldset>
@@ -143,110 +180,7 @@
</div>
</div>
<div v-show="currentSection == 'products'" class="panel panel-default">
{% macro printProductRow(product) %}
<div :id="'form-product-'+ keyForm">
<div class="field-group">
<fieldset>
<legend>Déclinaison</legend>
<div class="form-group row">
{{ form_label(product.title, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}
<div class="form-widget col-10">
{{ form_widget(product.title) }}
{{ form_help(product.title) }}
{% if product.vars.value %}
<input type="hidden" :id="'value-unit-'+keyForm" value="{{ product.vars.value.unit }}" />
{% endif %}
</div>
</div>
<div class="form-group row">
{{ form_label(product.unit, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}
<div class="form-widget col-10">
{{ form_widget(product.unit, {"attr":{'v-model': 'unit', 'class' : 'select-unit'}}) }}
{{ form_help(product.unit) }}
</div>
</div>
<div v-if="unit == 'piece'" class="form-group row field-weight">
{{ form_label(product.weight, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}
<div class="form-widget col-10">
<div class="input-group">
{{ form_widget(product.weight) }}
<div class="input-group-append">
<span class="input-group-text">g</span>
</div>
</div>
{{ form_help(product.weight) }}
</div>
</div>
<div v-else class="form-group row field-step">
{{ form_label(product.step, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}
<div class="form-widget col-8">
<div class="input-group">
{{ form_widget(product.step) }}
<div class="input-group-append">
<span class="input-group-text">{% verbatim %}{{ unit }}{% endverbatim %}</span>
</div>
</div>
{{ form_help(product.step) }}
</div>
</div>

<div class="form-group row field-price">
{{ form_label(product.price, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}
<div class="form-widget col-10">
<div class="input-group">
{{ form_widget(product.price, {'attr' : {'v-model': 'price', '@change' : 'changePriceWithTax'}}) }}
<div class="input-group-append">
<span class="input-group-text">€ HT / {% verbatim %}{{ getUnitReference() }}{% endverbatim %}</span>
</div>
{% if product.vars.value %}
<input type="hidden" :id="'value-price-'+keyForm" value="{{ product.vars.value.price }}" />
{% endif %}
</div>
</div>
</div>

<div class="form-group row field-price">
<div class="col-2">&nbsp;</div>
<div class="form-widget col-10">
<div class="input-group">
{{ form_widget(product.priceWithTax, {'attr' : {'v-model': 'priceWithTax','@change' : 'changePrice'}}) }}
<div class="input-group-append">
<span class="input-group-text">€ TTC / {% verbatim %}{{ getUnitReference() }}{% endverbatim %}</span>
</div>
</div>
</div>
</div>

<div class="form-group row field-available-quantity">
{{ form_label(product.availableQuantity, null, {'label_attr': {'class': 'col-2 col-form-label text-right'}}) }}
<div class="form-widget col-10">
{{ form_widget(product.availableQuantity) }}
</div>
</div>
</fieldset>
</div>
</div>
{% endmacro %}
{% import _self as formMacros %}

<ul class="products" :data-index="formProductArray.length" data-prototype="{{ formMacros.printProductRow(form.products.vars.prototype)|e('html_attr') }}">
<li class="product" v-for="(formProduct, key) in formProductArray">
<button type="button" class="btn-remove-product btn btn-default" @click="deleteFormProduct(key)"><i class="fa fa-trash"></i> Supprimer cette déclinaison</button>
<component-unit :template="formProduct" :key-form="key" :tax-rate-value="taxRateValue"></component-unit>
</li>
<li class="add">
<button type="button" class="add_tag_link btn-add-product btn btn-default" @click="addFormProduct"><span class="fa fa-plus"></span> Ajouter une déclinaison</button>
</li>
{% for product in form.products %}
<script>
jQuery(document).ready(function() {
var formProduct = '{{ formMacros.printProductRow(product)|replace({"\n":' ', "\r":' ', "'" : "\\'"})|raw }}' ;
appProductFamily.formProductArray.push(formProduct) ;
appProductFamily.indexFormProduct ++ ;
}) ;
</script>
{% endfor %}
</ul>
{% include '@LcShop/backend/default/product_macro.html.twig' %}
</div>
</div>
</div>

Loading…
取消
儲存