ソースを参照

Export PDF des documents

dev
コミット
4076f5fecd
16個のファイルの変更460行の追加33行の削除
  1. +61
    -19
      backend/controllers/DocumentController.php
  2. +6
    -1
      backend/views/delivery-note/index.php
  3. +1
    -1
      backend/views/document/_form.php
  4. +87
    -0
      backend/views/document/download.php
  5. +6
    -1
      backend/views/invoice/index.php
  6. +3
    -1
      backend/views/producer/update.php
  7. +6
    -1
      backend/views/quotation/index.php
  8. +101
    -0
      backend/web/css/document/download.css
  9. +106
    -0
      backend/web/sass/document/download.scss
  10. +1
    -1
      common/helpers/Price.php
  11. +19
    -5
      common/models/Document.php
  12. +1
    -1
      common/models/Order.php
  13. +34
    -1
      common/models/Producer.php
  14. +5
    -0
      common/models/Product.php
  15. +6
    -1
      common/models/User.php
  16. +17
    -0
      console/migrations/m200118_134323_ajout_champs_producer_address.php

+ 61
- 19
backend/controllers/DocumentController.php ファイルの表示

@@ -44,6 +44,7 @@ use common\models\Document;
use common\helpers\GlobalParam;
use common\models\Order;
use yii\base\UserException;
use yii;

class DocumentController extends BackendController
{
@@ -134,16 +135,59 @@ class DocumentController extends BackendController
throw new UserException('Vous ne pouvez pas supprimer un document validé.');
}

$model->delete() ;
$model->delete();

Order::updateAll([
'order.id_delivery_note' => null
],[
], [
'order.id_delivery_note' => $id
]) ;
]);

Yii::$app->getSession()->setFlash('success', $this->getFlashMessage('delete', $model));
$this->redirect(['delivery-note/index']) ;
$this->redirect(['delivery-note/index']);
}

public function actionDownload($id)
{
$document = $this->findModel($id);
$producer = GlobalParam::getCurrentProducer();
$content = $this->renderPartial('/document/download', [
'producer' => $producer,
'document' => $document
]);

$contentFooter = '<div id="footer">';
$contentFooter .= '<div class="infos-bottom">' . Html::encode($producer->document_infos_bottom) . '</div>';
if ($document->isStatusValid() || $document->isStatusDraft()) {
$contentFooter .= '<div class="reference-document">';
if ($document->isStatusValid()) {
$contentFooter .= $document->getType() . ' N°' . $document->reference;
}
if ($document->isStatusDraft()) {
$contentFooter .= $document->getType() . ' non validé';
if($document->getType() == 'Facture') {
$contentFooter .= 'e' ;
}
}
$contentFooter .= '</div>';
}
$contentFooter .= '</div>';

$pdf = new Pdf([
'mode' => Pdf::MODE_UTF8,
'format' => Pdf::FORMAT_A4,
'orientation' => Pdf::ORIENT_PORTRAIT,
'destination' => Pdf::DEST_BROWSER,
'content' => $content,
'cssFile' => Yii::getAlias('@webroot/css/document/download.css'),
'methods' => [
//'SetHeader' => [''],
//'SetFooter' => [$contentFooter],
'SetHTMLFooter' => $contentFooter
]
]);

return $pdf->render();
}

public function actionAjaxAddressUser($idUser)
@@ -153,7 +197,7 @@ class DocumentController extends BackendController
if ($idUser > 0) {
$user = User::searchOne([
'id' => $idUser
]) ;
]);

if ($user) {
return [
@@ -168,7 +212,7 @@ class DocumentController extends BackendController

public function actionValidate($id)
{
$classDocument = $this->getClass() ;
$classDocument = $this->getClass();

if ($id > 0 && Document::isValidClass($classDocument)) {
$document = $classDocument::searchOne([
@@ -179,12 +223,12 @@ class DocumentController extends BackendController
$document->changeStatus(Document::STATUS_VALID);
$document->save();
Yii::$app->getSession()->setFlash('success', $this->getFlashMessage('validate', $document));
return $this->redirect(['delivery-note/index']) ;
return $this->redirect(['delivery-note/index']);
}
}

Yii::$app->getSession()->setFlash('danger', 'Une erreur est survenue lors de la validation du document.');
return $this->redirect(['delivery-note/index']) ;
return $this->redirect(['delivery-note/index']);
}

public function actionAjaxValidateDocument($idDocument, $classDocument)
@@ -290,6 +334,7 @@ class DocumentController extends BackendController
$order->id_user = $document->id_user;
$order->id_point_sale = null;
$order->id_distribution = null;
$order->status = 'tmp-order' ;
$order->origin = Order::ORIGIN_ADMIN;
$order->date = date('Y-m-d H:i:s');
$fieldIdDocument = 'id_' . $classDocument::tableName();
@@ -314,7 +359,7 @@ class DocumentController extends BackendController
$productOrder->save();

return [
'return' => 'error',
'return' => 'success',
'alert' => [
'type' => 'success',
'message' => 'Produit ajouté'
@@ -363,7 +408,7 @@ class DocumentController extends BackendController
];
}

protected function getClass()
public function getClass()
{
$class = get_class($this);
$class = str_replace('Controller', '', $class);
@@ -371,7 +416,7 @@ class DocumentController extends BackendController
return $class;
}

protected function getDocumentType()
public function getDocumentType()
{
$class = $this->getClass();

@@ -399,14 +444,11 @@ class DocumentController extends BackendController

if ($type == 'create') {
$message .= 'ajouté';
}
elseif ($type == 'update') {
} elseif ($type == 'update') {
$message .= 'modifié';
}
elseif ($type == 'delete') {
} elseif ($type == 'delete') {
$message .= 'supprimé';
}
elseif ($type == 'validate') {
} elseif ($type == 'validate') {
$message .= 'validé';
}

@@ -440,7 +482,7 @@ class DocumentController extends BackendController
* Recherche un Document en fonction de son ID.
*
* @param integer $id
* @return Produit
* @return Document
* @throws NotFoundHttpException si le modèle n'est pas trouvé
*/
protected function findModel($id)
@@ -449,7 +491,7 @@ class DocumentController extends BackendController

$model = $class::searchOne([
'id' => $id
]) ;
]);

if ($model) {
return $model;

+ 6
- 1
backend/views/delivery-note/index.php ファイルの表示

@@ -109,10 +109,15 @@ $this->addButton(['label' => 'Nouveau bon de livraison <span class="glyphicon gl
],
[
'class' => 'yii\grid\ActionColumn',
'template' => '{validate} {update} {delete}',
'template' => '{validate} {update} {delete} {download}',
'headerOptions' => ['class' => 'column-actions'],
'contentOptions' => ['class' => 'column-actions'],
'buttons' => [
'download' => function($url, $model) {
return Html::a('<span class="glyphicon glyphicon-download-alt"></span>', $url, [
'title' => Yii::t('app', 'Télécharger'), 'class' => 'btn btn-default'
]);
},
'validate' => function ($url, $model) {
return ($model->isStatusDraft() ? Html::a('<span class="glyphicon glyphicon-ok"></span>', $url, [
'title' => Yii::t('app', 'Valider'), 'class' => 'btn btn-default'

+ 1
- 1
backend/views/document/_form.php ファイルの表示

@@ -111,7 +111,7 @@ use common\models\Producer;
<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>
<a href="<?= Yii::$app->urlManager->createUrl([$model->getControllerUrlPath().'/download', 'id' => $model->id]) ?>" class="btn btn-default">Télécharger (PDF)</a>
</div>
</div>
<div id="" class="info-box">

+ 87
- 0
backend/views/document/download.php ファイルの表示

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

?>

<div class="document-download">

<div id="block-addresses">
<div class="producer">
<?php if(strlen($producer->logo)) : ?>
<div class="logo"><?= $producer->getHtmlLogo() ; ?></div>
<?php endif; ?>
<div class="address"><?= $producer->getFullAddress(true) ; ?></div>
</div>
<div class="user">
<?= $document->user->getFullAddress(true) ; ?>
</div>
</div>

<div id="block-infos-document">
<div class="date">
Le <?= strftime('%d %B %Y', strtotime($document->date)) ?>
</div>
<div class="reference">
<?php if(strlen($document->reference)) : ?>
<?= $document->getType(); ?> N°<?= $document->reference ; ?>
<?php else: ?>
<div class="block-is-draft"><?= $document->getType(); ?> non validé<?= ($document->getType() == 'Facture') ? 'e' : '' ?></div>
<?php endif; ?>
</div>
<div class="name">
<strong>Libellé : </strong><?= $document->name ; ?>
</div>
</div>

<div id="block-products">
<?php if(count($document->orders) > 0) : ?>
<table class="table table-bordered">
<thead>
<tr>
<th class="align-left">Produit</th>
<th>Prix unitaire HT</th>
<th>Quantité</th>
<th>Unité</th>
<th>Prix HT</th>
</tr>
</thead>
<tbody>
<?php foreach($document->orders as $order): ?>
<?php foreach($order->productOrder as $productOrder): ?>
<tr>
<td class="align-left"><?= Html::encode($productOrder->product->name) ?></td>
<td class="align-center"><?= Price::format($productOrder->product->getPrice()) ?></td>
<td class="align-center"><?= $productOrder->quantity ?></td>
<td class="align-center"><?= Product::strUnit($productOrder->unit, 'wording') ?></td>
<td class="align-center"><?= Price::format($productOrder->product->getPrice() * $productOrder->quantity) ?></td>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>
<tr>
<td class="align-right" colspan="4"><strong>Total HT</strong></td>
<td class="align-center"><?= Price::format($document->getAmount()) ?></td>
</tr>
<tr>
<td class="align-right" colspan="4"><strong>TVA</strong></td>
<td class="align-center"><?= Price::format($document->getAmountWithTax() - $document->getAmount()) ?></td>
</tr>
<tr>
<td class="align-right" colspan="4"><strong>Total TTC</strong></td>
<td class="align-center"><?= Price::format($document->getAmountWithTax()) ?></td>
</tr>
</tbody>
</table>
<?php else : ?>
<div id="block-no-product">
<strong>Aucun produit</strong>
</div>
<?php endif; ?>
</div>

<?php
$fieldProducerDocumentInfo = 'document_infos_'.str_replace('deliverynote','delivery_note',strtolower($document->getClass())) ; ?>
<?php if(strlen($producer->$fieldProducerDocumentInfo)): ?>
<div id="block-infos">
<?= nl2br(Html::encode($producer->$fieldProducerDocumentInfo)) ?>
</div>
<?php endif; ?>
</div>

+ 6
- 1
backend/views/invoice/index.php ファイルの表示

@@ -99,10 +99,15 @@ $this->addButton(['label' => 'Nouvelle facture <span class="glyphicon glyphicon-
],
[
'class' => 'yii\grid\ActionColumn',
'template' => '{update} {delete}',
'template' => '{update} {delete} {download}',
'headerOptions' => ['class' => 'column-actions'],
'contentOptions' => ['class' => 'column-actions'],
'buttons' => [
'download' => function($url, $model) {
return Html::a('<span class="glyphicon glyphicon-download-alt"></span>', $url, [
'title' => Yii::t('app', 'Télécharger'), 'class' => 'btn btn-default'
]);
},
'update' => function ($url, $model) {
return ($model->isStatusDraft() ? Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url, [
'title' => Yii::t('app', 'Modifier'), 'class' => 'btn btn-default'

+ 3
- 1
backend/views/producer/update.php ファイルの表示

@@ -76,8 +76,10 @@ $this->addBreadcrumb($this->getTitle()) ;
<?= $form->field($model, 'name') ?>
<?= $form->field($model, 'type') ?>
<?= $form->field($model, 'description')
->textarea(['rows' => 6])
->textarea(['rows' => 4])
->hint('Affiché sur la page d\'accueil') ?>
<?= $form->field($model, 'address')
->textarea(['rows' => 4]) ?>
<?= $form->field($model, 'postcode') ?>
<?= $form->field($model, 'city') ?>


+ 6
- 1
backend/views/quotation/index.php ファイルの表示

@@ -85,10 +85,15 @@ $this->addButton(['label' => 'Nouveau devis <span class="glyphicon glyphicon-plu
],
[
'class' => 'yii\grid\ActionColumn',
'template' => '{update} {delete}',
'template' => '{update} {delete} {download}',
'headerOptions' => ['class' => 'column-actions'],
'contentOptions' => ['class' => 'column-actions'],
'buttons' => [
'download' => function($url, $model) {
return Html::a('<span class="glyphicon glyphicon-download-alt"></span>', $url, [
'title' => Yii::t('app', 'Télécharger'), 'class' => 'btn btn-default'
]);
},
'update' => function ($url, $model) {
return ($model->isStatusDraft() ? Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url, [
'title' => Yii::t('app', 'Modifier'), 'class' => 'btn btn-default'

+ 101
- 0
backend/web/css/document/download.css ファイルの表示

@@ -0,0 +1,101 @@
/* line 5, ../../sass/document/download.scss */
.document-download {
font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
/* line 9, ../../sass/document/download.scss */
.document-download #block-addresses .producer {
text-align: left;
margin-bottom: 20px;
}
/* line 13, ../../sass/document/download.scss */
.document-download #block-addresses .producer .logo {
margin-bottom: 20px;
}
/* line 20, ../../sass/document/download.scss */
.document-download #block-addresses .user {
text-align: right;
}
/* line 25, ../../sass/document/download.scss */
.document-download #block-infos-document {
padding-top: 30px;
}
/* line 28, ../../sass/document/download.scss */
.document-download #block-infos-document .date {
padding-bottom: 10px;
}
/* line 31, ../../sass/document/download.scss */
.document-download #block-infos-document .reference {
padding-bottom: 10px;
font-size: 15px;
font-weight: bold;
}
/* line 36, ../../sass/document/download.scss */
.document-download #block-infos-document .reference .block-is-draft {
border: solid 2px black;
padding: 10px;
text-transform: uppercase;
}
/* line 47, ../../sass/document/download.scss */
.document-download #block-no-product {
font-weight: bold;
border: solid 2px black;
text-transform: uppercase;
padding: 10px;
}
/* line 54, ../../sass/document/download.scss */
.document-download #block-products {
padding-top: 20px;
}
/* line 57, ../../sass/document/download.scss */
.document-download #block-products table {
width: 100%;
padding: 0px;
margin: 0px;
border-bottom: solid 1px #c0c0c0;
border-right: solid 1px #c0c0c0;
border-collapse: collapse;
}
/* line 65, ../../sass/document/download.scss */
.document-download #block-products table td, .document-download #block-products table th {
padding: 5px;
border-top: solid 1px #c0c0c0;
border-left: solid 1px #c0c0c0;
font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
/* line 71, ../../sass/document/download.scss */
.document-download #block-products table td.align-left, .document-download #block-products table th.align-left {
text-align: left;
}
/* line 74, ../../sass/document/download.scss */
.document-download #block-products table td.align-center, .document-download #block-products table th.align-center {
text-align: center;
}
/* line 77, ../../sass/document/download.scss */
.document-download #block-products table td.align-right, .document-download #block-products table th.align-right {
text-align: right;
}
/* line 84, ../../sass/document/download.scss */
.document-download #block-infos {
margin-top: 20px;
padding: 10px;
border: solid 1px #c0c0c0;
}

/* line 91, ../../sass/document/download.scss */
#footer {
font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
text-align: center;
padding-top: 20px;
border-top: solid 1px gray;
}
/* line 97, ../../sass/document/download.scss */
#footer .infos-bottom {
padding-bottom: 20px;
margin-bottom: 40px;
font-size: 12px;
line-height: 18px;
}
/* line 103, ../../sass/document/download.scss */
#footer .reference-document {
font-weight: bold;
}

+ 106
- 0
backend/web/sass/document/download.scss ファイルの表示

@@ -0,0 +1,106 @@

$font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif ;
$border-color: #c0c0c0 ;

.document-download {
font-family: $font-family ;

#block-addresses {
.producer {
text-align: left ;
margin-bottom: 20px ;

.logo {
margin-bottom: 20px ;
}
.address {

}
}
.user {
text-align: right ;
}
}

#block-infos-document {
padding-top: 30px ;

.date {
padding-bottom: 10px ;
}
.reference {
padding-bottom: 10px ;
font-size: 15px ;
font-weight: bold ;

.block-is-draft {
border: solid 2px black ;
padding: 10px ;
text-transform: uppercase ;
}
}
.name {

}
}

#block-no-product {
font-weight: bold ;
border: solid 2px black ;
text-transform: uppercase ;
padding: 10px ;
}

#block-products {
padding-top: 20px ;

table {
width: 100% ;
padding: 0px ;
margin: 0px ;
border-bottom: solid 1px $border-color ;
border-right: solid 1px $border-color ;
border-collapse: collapse ;

td, th {
padding: 5px ;
border-top: solid 1px $border-color ;
border-left: solid 1px $border-color ;
font-family: $font-family ;

&.align-left {
text-align: left ;
}
&.align-center {
text-align: center ;
}
&.align-right {
text-align: right ;
}
}
}
}

#block-infos {
margin-top: 20px ;
padding: 10px ;
border: solid 1px $border-color ;
}
}

#footer {
font-family: $font-family ;
text-align: center ;
padding-top: 20px ;
border-top: solid 1px gray ;

.infos-bottom {
padding-bottom: 20px ;
margin-bottom: 40px ;
font-size: 12px ;
line-height: 18px ;
}
.reference-document {
font-weight: bold ;
}
}

+ 1
- 1
common/helpers/Price.php ファイルの表示

@@ -43,7 +43,7 @@ class Price

public static function format($number)
{
return number_format($number, 2) . ' €';
return str_replace('.', ',',number_format($number, 2)) . ' €';
}

public static function getPrice($priceWithTax, $taxRate)

+ 19
- 5
common/models/Document.php ファイルの表示

@@ -160,15 +160,29 @@ class Document extends ActiveRecordCommon
return str_replace('common\models\\','',get_class($this)) ;
}

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

if ($class == 'Invoice') {
$documentType = 'Facture';
} elseif ($class == 'DeliveryNote') {
$documentType = 'Bon de livraison';
} elseif ($class == 'Quotation') {
$documentType = 'Devis';
}

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

return '';
}

public function getControllerUrlPath()
{
$path = strtolower($this->getClass());
$path = str_replace('deliverynote', 'delivery-note', $path) ;
return $path ;
}

+ 1
- 1
common/models/Order.php ファイルの表示

@@ -97,7 +97,7 @@ class Order extends ActiveRecordCommon
public function rules()
{
return [
[['id_user', 'date', 'id_point_sale', 'id_distribution', 'status'], 'required', 'message' => ''],
[['id_user', 'date', 'status'], 'required', 'message' => ''],
[['id_user', 'id_point_sale', 'id_distribution', 'id_subscription', 'id_invoice', 'id_quotation', 'id_delivery_note'], 'integer'],
[['auto_payment', 'tiller_synchronization'], 'boolean'],
[['status'], 'string'],

+ 34
- 1
common/models/Producer.php ファイルの表示

@@ -117,7 +117,7 @@ class Producer extends ActiveRecordCommon
}
}],
[['description', 'mentions', 'gcs', 'order_infos', 'slug', 'secret_key_payplug', 'background_color_logo', 'option_behavior_cancel_order', 'tiller_provider_token', 'tiller_restaurant_token', 'status',
'document_infos_bottom', 'document_infos_quotation', 'document_infos_invoice', 'document_infos_delivery_note'], 'string'],
'document_infos_bottom', 'document_infos_quotation', 'document_infos_invoice', 'document_infos_delivery_note', 'address'], 'string'],
[['negative_balance', 'credit', 'active', 'online_payment', 'user_manage_subscription', 'option_allow_user_gift', 'use_credit_checked_default', 'tiller'], 'boolean'],
[['name', 'siret', 'logo', 'photo', 'postcode', 'city', 'code', 'type', 'credit_functioning', 'option_behavior_cancel_order', 'document_quotation_prefix', 'document_quotation_first_reference', 'document_invoice_prefix', 'document_invoice_first_reference', 'document_delivery_note_prefix', 'document_delivery_note_first_reference'], 'string', 'max' => 255],
[['free_price', 'credit_limit_reminder', 'credit_limit'], 'double'],
@@ -177,6 +177,7 @@ class Producer extends ActiveRecordCommon
'document_infos_quotation' => 'Informations affichées en bas des devis',
'document_infos_invoice' => 'Informations affichées en bas des factures',
'document_infos_delivery_note' => 'Informations affichées en bas des bons de livraison',
'address' => 'Adresse'
];
}

@@ -505,5 +506,37 @@ class Producer extends ActiveRecordCommon

return false;
}

public function getFullAddress($nl2br = false)
{
$address = '' ;
$address .= $this->name."\n" ;

if(strlen($this->address)) {
$address .= $this->address."\n" ;
}

if(strlen($this->postcode) || strlen($this->city)) {
$address .= $this->postcode.' '.$this->city ;
}

if($nl2br) {
$address = nl2br($address) ;
}

return $address ;
}

public function getHtmlLogo()
{
$html = '' ;

if(strlen($this->logo)) {
$html = '<img src="'.Yii::$app->urlManagerProducer->getHostInfo().'/'.Yii::$app->urlManagerProducer->baseUrl.'/uploads/'.$this->logo.'" class="producer-logo" />' ;
}

return $html ;
}

}

+ 5
- 0
common/models/Product.php ファイルの表示

@@ -320,6 +320,11 @@ class Product extends ActiveRecordCommon
return $strUnit;
}

public function getPrice()
{
return $this->price ;
}

/**
* Retourne le prix du produit avec taxe
*/

+ 6
- 1
common/models/User.php ファイルの表示

@@ -669,7 +669,7 @@ class User extends ActiveRecordCommon implements IdentityInterface
}
}

public function getFullAddress()
public function getFullAddress($nl2br = false)
{
$address = '';
if(isset($this->lastname) && isset($this->name) && strlen($this->lastname) && strlen($this->name)) {
@@ -679,6 +679,11 @@ class User extends ActiveRecordCommon implements IdentityInterface
$address .= $this->name_legal_person. "\n";
}
$address .= $this->address;

if($nl2br) {
$address = nl2br($address) ;
}

return $address;
}


+ 17
- 0
console/migrations/m200118_134323_ajout_champs_producer_address.php ファイルの表示

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

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

class m200118_134323_ajout_champs_producer_address extends Migration
{
public function safeUp()
{
$this->addColumn('producer', 'address', Schema::TYPE_TEXT) ;
}

public function safeDown()
{
$this->dropColumn('producer', 'address') ;
}
}

読み込み中…
キャンセル
保存