Generator Invoice Sederhana Menggunakan Yii2 (part 1)

Pada post kali ini kita akan belajar menggunakan yiiframework dengan case generator invoice sederhana. Feature-feature yang akan dibuat adalah create, update, delete, payment invoice dan generate invoice ke dalam format PDF dengan tambahan gambar stempel PAID atau UNPAID tergantung status pembayaran nya.

Database Generator Invoice

Buat dua buah tabel untuk menyimpan data invoice yaitu tabel invoice dan invoice_item dengan skema relasi seperti di bawah ini :

relasi tabel invoice dan invoice_item

 

Copy paste sql di bawah ini.

CREATE TABLE `invoice` ( 
    `id` INT NOT NULL AUTO_INCREMENT , 
    `invoice_number` VARCHAR(10) NULL , 
    `due_date` DATE NOT NULL , 
    `name` VARCHAR(15) NOT NULL , 
    `attn` VARCHAR(30) NOT NULL , 
    `address` TEXT NOT NULL ,
    `amount` DOUBLE NOT NULL DEFAULT '0' , 
    `transaction_date` DATE NULL , 
    `payment_method` VARCHAR(50) NULL , 
    `transaction_id` VARCHAR(10) NULL , 
    `payment` DOUBLE NOT NULL DEFAULT '0' , 
    `status` VARCHAR(15) NOT NULL , 
    `created_at` DATETIME NOT NULL , 
    PRIMARY KEY (`id`), INDEX (`invoice_number`), INDEX (`name`)
) ENGINE = InnoDB;

Dan ini

CREATE TABLE `invoice_item` ( 
     `id` INT NOT NULL AUTO_INCREMENT , 
     `id_invoice` INT NOT NULL , 
     `item` TEXT NOT NULL , 
     `total` DOUBLE NOT NULL , 
     PRIMARY KEY (`id`), INDEX (`id_invoice`)
) ENGINE = InnoDB;

 

Generate CRUD Invoice

Generate model invoice dan invoice_item menggunakan GII. Masuk ke aplikasi gii lalu pilih model generator.

model invoice

 

model invoiceitem

 

Generate CRUD tabel invoice. Masuk ke aplikasi gii lalu pilih CRUD generator.

CRUD invoice

Sebelum masuk ke bahasan inti, kita akan menggunakan plugin dynamic form saat membuat invoice. Silahkan install terlebih dahulu plugin nya.

composer require --prefer-dist wbraganca/yii2-dynamicform "*"

Lalu kita juga akan memerlukan datepicker untuk input tanggal pada invoice, silahkan install plugin datepicker dari kartik.

composer require kartik-v/yii2-widget-datepicker "dev-master"

1. Create Invoice

Buat file Model.php di folder models/ lalu copy paste skrip di bawah ini.

<?php
namespace app\models;

use Yii;
use yii\helpers\ArrayHelper;

class Model extends \yii\base\Model
{
    /**
     * Creates and populates a set of models.
     *
     * @param string $modelClass
     * @param array $multipleModels
     * @return array
     */
    public static function createMultiple($modelClass, $multipleModels = [])
    {
        $model    = new $modelClass;
        $formName = $model->formName();
        $post     = Yii::$app->request->post($formName);
        $models   = [];

        if (! empty($multipleModels)) {
            $keys = array_keys(ArrayHelper::map($multipleModels, 'id', 'id'));
            $multipleModels = array_combine($keys, $multipleModels);
        }

        if ($post && is_array($post)) {
            foreach ($post as $i => $item) {
                if (isset($item['id']) && !empty($item['id']) && isset($multipleModels[$item['id']])) {
                    $models[] = $multipleModels[$item['id']];
                } else {
                    $models[] = new $modelClass;
                }
            }
        }
        unset($model, $formName, $post);

        return $models;
    }
}

Kita gunakan class di atas untuk membuat multiple model saat proses pembuatan item invoice. Sekarang buka InvoiceController.php lalu replace actionCreate() dengan skrip berikut ini.

public function actionCreate()
{
    $model = new Invoice();
    $items = [new InvoiceItem()];

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

        $model->created_at = date('Y-m-d H:i:s');
        $model->status = Invoice::STATUS_CREATED;

        $items = Model::createMultiple(InvoiceItem::classname());
        Model::loadMultiple($items, Yii::$app->request->post());

        // ajax validation
        if (Yii::$app->request->isAjax) {
            Yii::$app->response->format = Response::FORMAT_JSON;
            return ArrayHelper::merge(
                ActiveForm::validateMultiple($items),
                ActiveForm::validate($model)
            );
        }

        // validate all invoice
        $valid = $model->validate();
        // validate item invoice
        $valid = Model::validateMultiple($items) && $valid;

        if ($valid) {
            $amount = 0;
            $transaction = \Yii::$app->db->beginTransaction();
            try {
                //jika invoice berhasil disave, save semua item
                if ($flag = $model->save(false)) {
                    foreach ($items as $m) {
                        $m->id_invoice = $model->id;
                        if (! ($flag = $m->save(false))) {
                            $transaction->rollBack();
                            break;
                        }else{
                            $amount +=$m->total;
                        }
                    }
                    //update amout invoice sesuai total item
                    $model->amount = $amount;
                    $model->save(false);
                }

                if ($flag) {
                    $transaction->commit();
                    Yii::$app->session->setFlash('success', 'Invoice telah diterbitkan');
                    return $this->redirect(['index']);
                }
            } catch (\Exception $e) {
                $transaction->rollBack();
                $model->invoice_number = '';
                Yii::$app->session->setFlash('error', 'Uups! rollback!');
            }
        }else{
            Yii::$app->session->setFlash('error', 'Uups! ada kesalahan sistem!');
        }
    }

    return $this->render('create', [
        'model' => $model,
        'items' => (empty($items)) ? [new InvoiceItem()] : $items
    ]);
}

Tambahkan fungsi afterSave() pada model Invoice.php untuk merubah invoice_number dengan format seperti di bawah.

public function afterSave($insert, $changedAttributes)
{
    if($insert){
        //sebagai sample saja untuk mengenerate nomor invoice
        //format : tahun-bulan-id database
        $number = date('Y').date('m').str_pad($this->id,4,0,STR_PAD_LEFT);
        $this->updateAttributes(['invoice_number'=>$number]);
    }
    parent::afterSave($insert, $changedAttributes);
}

Tambahkan juga definisi status invoice dalam bentuk konstanta pada model Invoice.php

const STATUS_CREATED = 'created'; //invoice diterbitkan
const STATUS_PAID = 'paid'; //invoice dibayar
const STATUS_CANCELED = 'canceled'; //invoice dicancel

Replace file view invoice/create.php dengan skrip di bawah ini.

<?php
$this->title = 'Create Invoice';
$this->params['breadcrumbs'][] = ['label' => 'Invoices', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
?>
<h1><?= \yii\helpers\Html::encode($this->title) ?></h1>

<?php echo $this->render('_form',[
    'model'=>$model,
    'items'=>$items
]);?>


Lalu replace invoice/_form.php dengan skrip berikut ini

<?php

use yii\helpers\Html;
use kartik\date\DatePicker;
use wbraganca\dynamicform\DynamicFormWidget;

?>

<div class="customer-form">

    <?php $form = \yii\widgets\ActiveForm::begin([
        'id' => 'dynamic-form',
    ]); ?>

    <div class="row">
        <div class="col-md-5">
            <?= $form->field($model, 'invoice_number')->textInput(['maxlength' => true,'readonly'=>true,'placeholder'=>'Number'])->label(false) ?>

            <?php
            echo $form->field($model, 'due_date')->widget(DatePicker::classname(), [
                'options' => ['placeholder' => 'Due date'],
                'removeButton' => false,
                'pluginOptions' => [
                    'autoclose'=>true,
                    'format' => 'yyyy-mm-dd'
                ]
            ])->label(false);
            ?>
        </div>
        <div class="col-md-7">
            <?= $form->field($model, 'name')->textInput(['maxlength' => true,'placeholder'=>'Name'])->label(false) ?>
            <?= $form->field($model, 'attn')->textInput(['maxlength' => true,'placeholder'=>'ATTN'])->label(false) ?>
            <?= $form->field($model, 'address')->textarea(['rows' => 6,'placeholder'=>'Address'])->label(false) ?>
        </div>
    </div>

    <?php DynamicFormWidget::begin([
        'widgetContainer' => 'dynamicform_wrapper', // required: only alphanumeric characters plus "_" [A-Za-z0-9_]
        'widgetBody' => '.container-items', // required: css class selector
        'widgetItem' => '.item', // required: css class
        'limit' => 999, // the maximum times, an element can be added (default 999)
        'min' => 1, // 0 or 1 (default 1)
        'insertButton' => '.add-item', // css class
        'deleteButton' => '.remove-item', // css class
        'model' => $items[0],
        'formId' => 'dynamic-form',
        'formFields' => [
            'desciption',
            'price'
        ],
    ]); ?>

    <div class="panel panel-default">
        <div class="panel-heading">
            <h4>
                <i class="glyphicon glyphicon-briefcase"></i> Items
                <button type="button" class="add-item btn btn-success btn-sm pull-right"><i class="glyphicon glyphicon-plus"></i> Add</button>
            </h4>
        </div>
        <div class="panel-body">
            <div class="container-items"><!-- widgetBody -->
                <?php foreach ($items as $i => $m): ?>
                    <div class="item"><!-- widgetItem -->
                        <div class="rows">
                            <?php
                            // necessary for update action.
                            if (! $m->isNewRecord) {
                                echo Html::activeHiddenInput($m, "[{$i}]id");
                            }
                            ?>
                            <table width="80%">
                                <tr>
                                    <td valign="top" width="5%"><button type="button" class="remove-item btn btn-danger btn-xs"><i class="glyphicon glyphicon-minus"></i></button></td>
                                    <td width="70%"><?= $form->field($m, "[{$i}]item")->textInput(['maxlength' => true,'placeholder'=>'description'])->label(false) ?></td>
                                    <td width="25%">
                                        <?php echo $form->field($m, "[{$i}]total")->textInput(['placeholder'=>'price'])->label(false);?>
                                    </td>
                                </tr>
                            </table>
                        </div><!-- .row -->
                    </div>
                <?php endforeach; ?>
            </div>
        </div>
    </div><!-- .panel -->
    <?php DynamicFormWidget::end(); ?>

    <div class="form-group">
        <?= Html::submitButton($m->isNewRecord ? 'Create' : 'Update', ['class' => 'btn btn-primary']) ?>
    </div>

    <?php \yii\widgets\ActiveForm::end(); ?>
</div>


 

Silahkan coba masuk ke halaman create invoice lalu buat sample invoice.

Create_Invoice

 

2. Update Invoice

Replace actionUpdate() pada InvoiceController.php dengan skrip berikut.

public function actionUpdate($id)
{
    $model = $this->findModel($id);
    $items = InvoiceItem::find()->where(['id_invoice'=>$id])->all(); //$model->items;

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

        $oldIDs = ArrayHelper::map($items, 'id', 'id');
        $items = Model::createMultiple(InvoiceItem::classname(),$items);
        Model::loadMultiple($items, Yii::$app->request->post());
        //list item yang akan didelete
        $deletedIDs = array_diff($oldIDs, array_filter(ArrayHelper::map($items, 'id', 'id')));


        // ajax validation
        if (Yii::$app->request->isAjax) {
            Yii::$app->response->format = Response::FORMAT_JSON;
            return ArrayHelper::merge(
                ActiveForm::validateMultiple($items),
                ActiveForm::validate($model)
            );
        }

        // validate all models
        $valid = $model->validate();
        $valid = Model::validateMultiple($items) && $valid;

        if ($valid) {
            $amount = 0;
            $transaction = \Yii::$app->db->beginTransaction();
            try {
                if ($flag = $model->save(false)) {
                    if (! empty($deletedIDs)) {
                        InvoiceItem::deleteAll(['id' => $deletedIDs]);
                    }
                    foreach ($items as $m) {
                        $m->id_invoice = $model->id;
                        if (! ($flag = $m->save(false))) {
                            $transaction->rollBack();
                            break;
                        }else{
                            $amount +=$m->total;
                        }
                    }

                    //update amount sesuai item baru
                    $model->amount = $amount;
                    $model->save(false);
                }
                if ($flag) {
                    $transaction->commit();
                    Yii::$app->session->setFlash('success', 'Invoice telah diterbitkan');
                    return $this->redirect(['index']);
                }
            } catch (\Exception $e) {
                $transaction->rollBack();
                Yii::$app->session->setFlash('error', 'Uups! rollback!');
            }
        }else{
            Yii::$app->session->setFlash('error', 'Uups! ada kesalahan sistem!');
        }
    }

    return $this->render('update', [
        'model' => $model,
        'items' => (empty($items)) ? [new InvoiceItem()] : $items
    ]);
}

Lalu tambahkan passing variabel $items pada render view update.php.

<?php echo $this->render('_form',[
    'model'=>$model,
    'items'=>$items
]);?>

Silahkan coba update invoice yang ada.

Fungsi create dan update┬ápada generator invoice sudah selesai, cukup sederhana bukan?, tinggal buat feature pembayaran invoice dan generate pdf, supaya gak kepanjangan kita buat di post terpisah ya… silahkan lanjut ke post selanjutnya di sini.

 

catatan :

Source code generator invoice ini disimpan di github, link nya saya sertakan pada post bagian kedua.

https://github.com/wbraganca/yii2-dynamicform

http://demos.krajee.com/widget-details/datepicker

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *