Kaynağa Gözat

Merge evol #166 #155

dev
Fab 4 yıl önce
ebeveyn
işleme
3e45267b81
13 değiştirilmiş dosya ile 499 ekleme ve 41 silme
  1. +136
    -5
      backend/controllers/DocumentController.php
  2. +146
    -17
      backend/views/document/_form.php
  3. +21
    -5
      backend/views/invoice/index.php
  4. +17
    -0
      backend/web/css/screen.css
  5. +69
    -5
      backend/web/js/vuejs/document-form.js
  6. +21
    -0
      backend/web/sass/document/_form.scss
  7. +2
    -1
      backend/web/sass/screen.scss
  8. +1
    -1
      common/models/DeliveryNoteSearch.php
  9. +61
    -5
      common/models/Document.php
  10. +1
    -1
      common/models/InvoiceSearch.php
  11. +3
    -0
      common/models/Order.php
  12. +1
    -1
      common/models/QuotationSearch.php
  13. +20
    -0
      console/migrations/m200109_070952_module_devis_bl_factures_champs_order_valeur_default.php

+ 136
- 5
backend/controllers/DocumentController.php Dosyayı Görüntüle

@@ -38,8 +38,12 @@

namespace backend\controllers;

use common\models\Product;
use common\models\User ;
use common\models\Document ;
use common\helpers\GlobalParam ;
use common\models\Order ;
use yii\base\UserException;

class DocumentController extends BackendController
{
@@ -74,11 +78,10 @@ class DocumentController extends BackendController
if ($model->load(Yii::$app->request->post())) {
$model->id_producer = GlobalParam::getCurrentProducerId() ;
$model->reference = $model->generateReference() ;
if($model->save()) {
Yii::$app->getSession()->setFlash('success', $this->getFlashMessage('create', $model));
return $this->redirect(['index']);
return $this->redirect(['/'.$model->getControllerUrlPath().'/update', 'id' => $model->id]);
}
else {
Yii::$app->getSession()->setFlash('error', 'Un problème est survenu lors de la création du document.');
@@ -108,6 +111,10 @@ class DocumentController extends BackendController
'id' => $id
]) ;

if($model->isStatusValid()) {
throw new UserException('Vous ne pouvez pas modifier un document validé.');
}

if ($model && $model->load(Yii::$app->request->post()) && $model->save()) {

Yii::$app->getSession()->setFlash('success', $this->getFlashMessage('update', $model));
@@ -140,6 +147,25 @@ class DocumentController extends BackendController
return ['return' => 'error'] ;
}

public function actionAjaxValidateDocument($idDocument, $classDocument)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;

if($idDocument > 0 && Document::isValidClass($classDocument)) {
$document = $classDocument::searchOne([
'id' => $idDocument
]) ;

if($document) {
$document->changeStatus(Document::STATUS_VALID) ;
$document->save() ;
return ['return' => 'success'] ;
}
}

return ['return' => 'error'] ;
}
public function actionAjaxInit($idDocument, $classDocument)
{
@@ -152,10 +178,39 @@ class DocumentController extends BackendController
]) ;
if($document) {
$productsArray = Product::searchAll([], [
'as_array' => true,
]) ;
$ordersArray = [] ;
foreach($document->orders as $order) {
$order->init() ;
$productsOrderArray = [] ;
foreach($order->productOrder as $productOrder) {
$productsOrderArray[$productOrder->id] = $productOrder->getAttributes() ;
}
$ordersArray[$order->id] = array_merge(
$order->getAttributes(),
[
'productOrder' => $productsOrderArray,
]
);
}
return [
'return' => 'success',
'address' => $document->address,
'idUser' => $document->user->id
'return' => 'success',
'document' => array_merge($document->getAttributes(), [
'html_label' => $document->getHtmlLabel(),
'class' => $document->getClass()
]),
'idUser' => $document->user->id,
'products' => ArrayHelper::map($productsArray, 'id', function($product) {
$product['wording_unit'] = Product::strUnit($product['unit']) ;
return $product;
}),
'orders' => $ordersArray,
'total' => $document->getAmount(Order::AMOUNT_TOTAL)
] ;
}
}
@@ -163,6 +218,82 @@ class DocumentController extends BackendController
return ['return' => 'error'] ;
}
public function actionAjaxAddProduct($idDocument, $classDocument, $idProduct, $quantity, $price)
{

\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
if(Document::isValidClass($classDocument)) {
$document = $classDocument::searchOne([
'id' => $idDocument
]) ;
$product = Product::searchOne([
'id' => $idProduct
]) ;
if($document && $product) {
if(count($document->orders) == 0) {
$order = new Order ;
$order->id_user = $document->id_user ;
$order->id_point_sale = null ;
$order->id_distribution = null ;
$order->origin = Order::ORIGIN_ADMIN ;
$order->date = date('Y-m-d H:i:s');
$fieldIdDocument = 'id_'.$classDocument::tableName() ;
$order->$fieldIdDocument = $document->id ;
$order->save() ;
}
else {
$order = $document->orders[0] ;
}

if($order) {
$productOrder = new ProductOrder ;
$productOrder->id_order = $order->id ;
$productOrder->id_product = $idProduct ;
$quantity = $quantity / Product::$unitsArray[$product->unit]['coefficient'];
$productOrder->quantity = $quantity ;
$productOrder->price = (float) $price ;
$productOrder->unit = $product->unit ;
$productOrder->step = $product->step ;
$productOrder->id_tax_rate = $productOrder->id_tax_rate ?
$product->taxRate->id : GlobalParam::getCurrentProducer()->taxRate->id ;
$productOrder->save() ;
return [
'return' => 'success',
] ;
}
}
}
return [
'return' => 'error'
] ;
}

public function actionAjaxDeleteProductOrder($idProductOrder)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;

$productOrder = ProductOrder::searchOne([
'id' => $idProductOrder
]) ;

if($productOrder) {
$productOrder->delete() ;
return [
'return' => 'success'
] ;
}

return [
'return' => 'error'
] ;
}
protected function getClass()
{
$class = get_class($this);

+ 146
- 17
backend/views/document/_form.php Dosyayı Görüntüle

@@ -35,6 +35,7 @@
* pris connaissance de la licence CeCILL, et que vous en avez accepté les
* termes.
*/

use yii\helpers\Html;
use yii\widgets\ActiveForm;
use common\models\Product;
@@ -46,29 +47,35 @@ use common\models\Producer;

?>

<div class="document-form" id="app-document-form" data-class-document="<?= $model->getClass() ?>" data-id-document="<?= ($model->id > 0) ? $model->id : $model->id ?>">
<div class="document-form" id="app-document-form" data-class-document="<?= $model->getClass() ?>"
data-id-document="<?= ($model->id > 0) ? $model->id : $model->id ?>">

<div class="<?php if($action == 'create') : ?>col-md-12<?php else : ?>col-md-4<?php endif; ?>">
<div class="<?= ($action == 'update') ? 'col-md-6' : '' ?>">
<div class="panel panel-default">
<div class="panel-heading">
Informations
Général
</div>
<div class="panel-body">
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'name')->label('Nom du document') ?>
<?php $usersArray = User::findBy()->all() ; ?>
<?= $form->field($model, 'id_user', [
'template' => '{label} <a href="'.Yii::$app->urlManager->createUrl(['user/create']).'" class="btn btn-xs btn-default">Nouvel utilisateur <span class="glyphicon glyphicon-plus"></span></a><div>{input}</div>{hint}',
])
<?php $usersArray = User::findBy()->all(); ?>
<?= $form->field($model, 'id_user', [
'template' => '{label} <a href="' . Yii::$app->urlManager->createUrl(['user/create']) . '" class="btn btn-xs btn-default">Nouvel utilisateur <span class="glyphicon glyphicon-plus"></span></a><div>{input}</div>{hint}',
])
->dropDownList(
ArrayHelper::map($usersArray, 'user_id', function($model) { return $model['lastname'].' '.$model['name']; }),
ArrayHelper::map($usersArray, 'user_id', function ($model) {
return $model['lastname'] . ' ' . $model['name'];
}),
[
'@change' => 'changeUser',
'prompt' => '--',
'v-model' => 'idUser',
'@change' => 'changeUser',
'prompt' => '--',
'v-model' => 'idUser',
]
); ?>
<?= $form->field($model, 'address')->textarea(['rows' => 3, 'v-model' => 'address']) ?>
<?= $form->field($model, 'address')->textarea(['rows' => 2, 'v-model' => 'document.address']) ?>
<?php if ($action == 'update'): ?>
<?= $form->field($model, 'comment')->textarea(['rows' => 2])->hint('Affiché en bas de la facture') ?>
<?php endif; ?>
<div class="form-group">
<?= Html::submitButton($model->isNewRecord ? 'Ajouter' : 'Modifier', ['class' => 'btn btn-primary']) ?>
</div>
@@ -76,18 +83,140 @@ use common\models\Producer;
</div>
</div>
</div>
<?php if($action == 'update'): ?>
<div class="col-md-8">

<?php if ($action == 'update'): ?>

<div class="col-md-6">
<div id="" class="info-box">
<span class="info-box-icon bg-green"><i class="fa fa-sticky-note-o"></i></span>
<div class="info-box-content">
<span class="info-box-text"><?= $typeDocument ?> <span v-html="document.html_label"></span></span>
<span class="info-box-number">{{ document.reference }}</span>
<span class="info-box-text">Date</span>
<span class="info-box-number">{{ document.date }}</span>
</div>
</div>
<div id="" class="info-box">
<span class="info-box-icon bg-yellow"><i class="fa fa-euro"></i></span>
<div class="info-box-content">
<span class="info-box-text">Total (TTC)</span>
<span class="info-box-number">{{ formatPrice(total) }}</span>
</div>
</div>
<div id="" class="info-box">
<span class="info-box-icon bg-blue"><i class="fa fa-download"></i></span>
<div class="info-box-content">
<a href="#" class="btn btn-default">Télécharger (PDF)</a>
</div>
</div>
<div id="" class="info-box">
<span class="info-box-icon bg-red"><i class="fa fa-flash"></i></span>
<div class="info-box-content">
<a v-if="document.status == 'draft' && document.class == 'Invoice'" class="btn btn-default" @click="validateDocument">Valider le document</a>
</div>
</div>
</div>
<div class="clr"></div>

<div class="">
<div class="panel panel-default" id="block-add-product">
<div class="panel-heading">
Ajouter un produit
</div>
<div class="panel-body">
<div class="col-md-5">
<select class="form-control" v-model="productAddId"
@change="changeProductAdd">
<option value="0" selected="selected">--</option>
<option v-for="product in productsArray" :value="product.id">
{{ product.name }}
</option>
</select>
</div>
<template v-if="productAddId > 0">
<div class="col-md-2">
<div class="input-group">
<input type="text" class="form-control input-price"
v-model="productAddPrice"/>
<span class="input-group-addon"><span
class="glyphicon glyphicon-euro"></span></span>
</div>
</div>
<div class="col-md-3">
<div class="input-group input-group-quantity">
<span class="input-group-btn">
<button class="btn btn-default" type="button"
@click="changeQuantityProductAdd(-1)">-</button>
</span>
<input type="text" class="form-control input-quantity"
v-model="productAddQuantity"/>
<span class="input-group-addon">{{ productsArray[productAddId].wording_unit }}</span>
<span class="input-group-btn">
<button class="btn btn-default"
type="button"
@click="changeQuantityProductAdd(1)">+</button>
</span>
</div>
</div>
<div class="col-md-2">
<button class="btn btn-primary" value="Ajouter"
@click="submitProductAdd">Ajouter
</button>
</div>
</template>
<div class="clr"></div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
Produits
</div>
<div class="panel-body">
<div id="block-list-products">
<table class="table table-bordered" v-if="total > 0">
<thead>
<tr>
<td>Nom</td>
<td>Prix (unité)</td>
<td>Quantité</td>
<td>Total</td>
<td>Supprimer</td>
</tr>
</thead>
<tbody>
<template v-for="order in ordersArray">
<tr v-for="productOrder in order.productOrder">
<td>{{
productsArray[productOrder.id_product].name
}}
</td>
<td>{{ formatPrice(productOrder.price) }}</td>
<td>{{ productOrder.quantity }}</td>
<td>{{ formatPrice(productOrder.quantity *
productOrder.price) }}
</td>
<td>
<a class="btn btn-default" @click="deleteProductOrder(productOrder.id)">
<span class="glyphicon glyphicon-trash"></span>
</a>
</td>
</tr>
</template>
<tr>
<td colspan="3"><strong>Total</strong></td>
<td><strong>{{ formatPrice(total) }}</strong></td>
<td></td>
</tr>
</tbody>
</table>
<div v-else class="alert alert-info">
Aucun produit.
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>

+ 21
- 5
backend/views/invoice/index.php Dosyayı Görüntüle

@@ -54,7 +54,23 @@ $this->addButton(['label' => 'Nouvelle facture <span class="glyphicon glyphicon-
'filterModel' => $searchModel,
'dataProvider' => $dataProvider,
'columns' => [
'reference',
[
'attribute' => 'status',
'header' => 'Statut',
'format' => 'raw',
'value' => function($model) {
return $model->getHtmlLabel() ;
}
],
[
'attribute' => 'reference',
'value' => function($model) {
if(strlen($model->reference) > 0) {
return $model->reference ;
}
return '' ;
}
],
'name',
[
'attribute' => 'id_user',
@@ -84,14 +100,14 @@ $this->addButton(['label' => 'Nouvelle facture <span class="glyphicon glyphicon-
'contentOptions' => ['class' => 'column-actions'],
'buttons' => [
'update' => function ($url, $model) {
return Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url, [
return ($model->isStatusDraft() ? Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url, [
'title' => Yii::t('app', 'Modifier'), 'class' => 'btn btn-default'
]);
]) : '');
},
'delete' => function ($url, $model) {
return Html::a('<span class="glyphicon glyphicon-trash"></span>', $url, [
return ($model->isStatusDraft() ? Html::a('<span class="glyphicon glyphicon-trash"></span>', $url, [
'title' => Yii::t('app', 'Supprimer'), 'class' => 'btn btn-default'
]);
]) : '');
}
],
],

+ 17
- 0
backend/web/css/screen.css Dosyayı Görüntüle

@@ -2265,3 +2265,20 @@ termes.
.report-index #report .section .comma:last-child {
display: none;
}

/* line 4, ../sass/document/_form.scss */
.document-form .info-box .info-box-text {
font-size: 13px;
}
/* line 7, ../sass/document/_form.scss */
.document-form .info-box .info-box-number {
font-size: 15px;
}
/* line 12, ../sass/document/_form.scss */
.document-form #block-add-product .input-price {
text-align: center;
}
/* line 16, ../sass/document/_form.scss */
.document-form #block-add-product .input-group-quantity .input-quantity {
text-align: center;
}

+ 69
- 5
backend/web/js/vuejs/document-form.js Dosyayı Görüntüle

@@ -38,18 +38,27 @@ termes.
var app = new Vue({
el: '#app-document-form',
data: {
document: [],
idDocument: 0,
typeDocument: '',
idUser: '',
address : ''
idUser: '',
productsArray: [],
productAddId: 0,
productAddPrice: '',
productAddQuantity: 1,
ordersArray: [],
total: 0
},
mounted: function() {
this.init() ;
},
methods: {
formatPrice: formatPrice,
init: function() {
var idDocument = $('#app-document-form').attr('data-id-document') ;
this.idDocument = idDocument ;
var classDocument = $('#app-document-form').attr('data-class-document') ;
this.classDocument = classDocument ;
if(idDocument) {
var app = this ;
@@ -59,8 +68,11 @@ var app = new Vue({
}})
.then(function(response) {
if(response.data.return == 'success') {
app.address = response.data.address ;
app.document = response.data.document ;
app.idUser = response.data.idUser ;
app.productsArray = response.data.products ;
app.ordersArray = response.data.orders ;
app.total = response.data.total ;
}
}) ;
}
@@ -72,12 +84,64 @@ var app = new Vue({
}})
.then(function(response) {
if(response.data.return == 'success') {
app.address = response.data.address ;
Vue.set(app.document, 'address', response.data.address);
}
else {
app.address = '' ;
app.document.address = '' ;
}
}) ;
},
formatPrice: formatPrice,
validateDocument: function() {
var app = this ;
axios.get(UrlManager.getBaseUrlAbsolute()+"document/ajax-validate-document",{params: {
idDocument: app.idDocument,
classDocument: app.classDocument,
}})
.then(function(response) {
app.init() ;
}) ;
},
getStepProductAdd: function() {
return parseInt(this.productsArray[this.productAddId].step) ;
},
changeProductAdd: function(event) {
var idProduct = event.currentTarget.value ;
this.productAddId = idProduct ;
this.productAddPrice = parseFloat(this.productsArray[idProduct].price).toFixed(2) ;
this.productAddQuantity = this.getStepProductAdd() ;
},
changeQuantityProductAdd: function(quantity) {
var step = this.getStepProductAdd() ;
quantity = quantity * step ;
this.productAddQuantity += quantity ;
if(this.productAddQuantity < 1) {
this.productAddQuantity = step ;
}
},
submitProductAdd: function() {
var app = this ;
axios.get(UrlManager.getBaseUrlAbsolute()+"document/ajax-add-product",{params: {
idDocument: app.idDocument,
classDocument: app.classDocument,
idProduct: app.productAddId,
quantity: app.productAddQuantity,
price: app.productAddPrice,
}})
.then(function(response) {
app.productAddId = 0 ;
app.init() ;
alert('Produit ajouté') ;
}) ;
},
deleteProductOrder: function(idProductOrder) {
var app = this ;
axios.get(UrlManager.getBaseUrlAbsolute()+"document/ajax-delete-product-order",{params: {
idProductOrder: idProductOrder
}})
.then(function(response) {
app.init() ;
}) ;
}
}
});

+ 21
- 0
backend/web/sass/document/_form.scss Dosyayı Görüntüle

@@ -0,0 +1,21 @@

.document-form {
.info-box {
.info-box-text {
font-size: 13px ;
}
.info-box-number {
font-size: 15px ;
}
}
#block-add-product {
.input-price {
text-align: center ;
}
.input-group-quantity {
.input-quantity {
text-align: center ;
}
}
}
}

+ 2
- 1
backend/web/sass/screen.scss Dosyayı Görüntüle

@@ -1480,4 +1480,5 @@ a.btn, button.btn {
@import "user/_form.scss" ;
@import "producer/_update.scss" ;
@import "point_sale/_index.scss" ;
@import "report/_index.scss" ;
@import "report/_index.scss" ;
@import "document/_form.scss" ;

+ 1
- 1
common/models/DeliveryNoteSearch.php Dosyayı Görüntüle

@@ -62,7 +62,7 @@ class DeliveryNoteSearch extends DeliveryNote
->with($optionsSearch['with'])
->joinWith($optionsSearch['join_with'], true)
->where(['delivery_note.id_producer' => GlobalParam::getCurrentProducerId()])
->orderBy('delivery_note.reference DESC')
->orderBy('delivery_note.status ASC, delivery_note.reference DESC')
;
$dataProvider = new ActiveDataProvider([

+ 61
- 5
common/models/Document.php Dosyayı Görüntüle

@@ -40,6 +40,8 @@ namespace common\models;

class Document extends ActiveRecordCommon
{
const STATUS_DRAFT = 'draft' ;
const STATUS_VALID = 'valid' ;
/**
* @inheritdoc
@@ -51,7 +53,7 @@ class Document extends ActiveRecordCommon
[['date'], 'safe'],
[['comment', 'address'], 'string'],
[['id_user','id_producer'], 'integer'],
[['name', 'reference'], 'string', 'max' => 255],
[['name', 'reference', 'status'], 'string', 'max' => 255],
];
}

@@ -68,7 +70,8 @@ class Document extends ActiveRecordCommon
'comment' => 'Commentaire',
'id_user' => 'Utilisateur',
'address' => 'Adresse',
'id_producer' => 'Producteur'
'id_producer' => 'Producteur',
'status' => 'Statut',
];
}
@@ -141,6 +144,23 @@ class Document extends ActiveRecordCommon
{
return str_replace('common\models\\','',get_class($this)) ;
}

public function getControllerUrlPath()
{
$class = $this->getClass() ;
$path = strtolower($class) ;

if($path == 'deliverynote') {
$path = 'delivery_note' ;
}

return $path ;
}
public function isValidClass($typeDocument)
{
return in_array($typeDocument, ['Invoice', 'DeliveryNote', 'Quotation']) ;
}
public function generateReference()
{
@@ -174,9 +194,45 @@ class Document extends ActiveRecordCommon
}
}
}
public function isValidClass($typeDocument)
public function changeStatus($status)
{
return in_array($typeDocument, ['Invoice', 'DeliveryNote', 'Quotation']) ;
if($status == Document::STATUS_VALID) {
$this->status = $status ;
$this->reference = $this->generateReference() ;
}
}

public function getStatusWording()
{
return ($this->status == self::STATUS_DRAFT) ? 'Brouillon' : 'Validé' ;
}

public function getStatusCssClass()
{
return ($this->status == self::STATUS_DRAFT) ? 'default' : 'success' ;
}

public function getHtmlLabel()
{
$label = $this->getStatusWording();
$classLabel = $this->getStatusCssClass() ;
return '<span class="label label-'.$classLabel.'">'.$label.'</span>' ;
}

public function isStatus($status)
{
return $this->status == $status ;
}

public function isStatusDraft()
{
return $this->isStatus(self::STATUS_DRAFT) ;
}

public function isStatusValid()
{
return $this->isStatus(self::STATUS_VALID) ;
}

}

+ 1
- 1
common/models/InvoiceSearch.php Dosyayı Görüntüle

@@ -62,7 +62,7 @@ class InvoiceSearch extends Invoice
->with($optionsSearch['with'])
->joinWith($optionsSearch['join_with'], true)
->where(['invoice.id_producer' => GlobalParam::getCurrentProducerId()])
->orderBy('invoice.reference DESC')
->orderBy('invoice.status ASC, invoice.reference DESC')
;
$dataProvider = new ActiveDataProvider([

+ 3
- 0
common/models/Order.php Dosyayı Görüntüle

@@ -205,6 +205,9 @@ class Order extends ActiveRecordCommon
*/
public function initAmount()
{
$this->amount = 0 ;
$this->weight = 0 ;

if (isset($this->productOrder)) {
foreach ($this->productOrder as $productOrder) {
$this->amount += $productOrder->price * $productOrder->quantity;

+ 1
- 1
common/models/QuotationSearch.php Dosyayı Görüntüle

@@ -62,7 +62,7 @@ class QuotationSearch extends Quotation
->with($optionsSearch['with'])
->joinWith($optionsSearch['join_with'], true)
->where(['quotation.id_producer' => GlobalParam::getCurrentProducerId()])
->orderBy('quotation.reference DESC')
->orderBy('quotation.status ASC, quotation.reference DESC')
;
$dataProvider = new ActiveDataProvider([

+ 20
- 0
console/migrations/m200109_070952_module_devis_bl_factures_champs_order_valeur_default.php Dosyayı Görüntüle

@@ -0,0 +1,20 @@
<?php

use yii\db\Migration;
use yii\db\Schema;

class m200109_070952_module_devis_bl_factures_champs_order_valeur_default extends Migration
{
public function safeUp()
{
$this->alterColumn('order', 'id_point_sale', Schema::TYPE_INTEGER.' DEFAULT NULL') ;
$this->alterColumn('order', 'id_distribution', Schema::TYPE_INTEGER.' DEFAULT NULL') ;
}

public function safeDown()
{
$this->alterColumn('order', 'id_point_sale', Schema::TYPE_INTEGER) ;
$this->alterColumn('order', 'id_distribution', Schema::TYPE_INTEGER) ;
}

}

Yükleniyor…
İptal
Kaydet