Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tabacitu/4db255042a62c7458ee17fe33d65522c to your computer and use it in GitHub Desktop.
Save tabacitu/4db255042a62c7458ee17fe33d65522c to your computer and use it in GitHub Desktop.
Invoice Manager example - with repeatable field used to create and edit InvoiceItems
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\InvoiceRequest;
use Backpack\CRUD\app\Http\Controllers\CrudController;
use Backpack\CRUD\app\Library\CrudPanel\CrudPanelFacade as CRUD;
/**
* Class InvoiceCrudController
* @package App\Http\Controllers\Admin
* @property-read \Backpack\CRUD\app\Library\CrudPanel\CrudPanel $crud
*/
class InvoiceCrudController extends CrudController
{
use \Backpack\CRUD\app\Http\Controllers\Operations\ListOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation { store as traitStore; }
use \Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation { update as traitUpdate; }
use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
use \Backpack\CRUD\app\Http\Controllers\Operations\ShowOperation;
/**
* Configure the CrudPanel object. Apply settings to all operations.
*
* @return void
*/
public function setup()
{
CRUD::setModel(\App\Models\Invoice::class);
CRUD::setRoute(config('backpack.base.route_prefix') . '/invoice');
CRUD::setEntityNameStrings('invoice', 'invoices');
}
/**
* Define what happens when the List operation is loaded.
*
* @see https://backpackforlaravel.com/docs/crud-operation-list-entries
* @return void
*/
protected function setupListOperation()
{
CRUD::column('number');
CRUD::column('series');
CRUD::column('customer_id');
CRUD::column('deleted_at');
CRUD::column('created_at');
CRUD::column('updated_at');
/**
* Columns can be defined using the fluent syntax or array syntax:
* - CRUD::column('price')->type('number');
* - CRUD::addColumn(['name' => 'price', 'type' => 'number']);
*/
}
/**
* Define what happens when the Create operation is loaded.
*
* @see https://backpackforlaravel.com/docs/crud-operation-create
* @return void
*/
protected function setupCreateOperation()
{
CRUD::setValidation(InvoiceRequest::class);
CRUD::field('series')->size(3);
CRUD::field('number')->size(3);
CRUD::field('customer_id')->size(6);
CRUD::field('payment_method')->size(6);
CRUD::field('payment_provider')->size(6);
CRUD::field('items')
->type('repeatable')
->label('Items')
->fields([
[
'name' => 'id',
'type' => 'hidden',
],
[
'name' => 'description',
'type' => 'text',
'wrapper' => ['class' => 'form-group col-md-6'],
],
[
'name' => 'quantity',
'type' => 'number',
'default' => 1,
'wrapper' => ['class' => 'form-group col-md-2'],
],
[
'name' => 'price',
'type' => 'number',
'wrapper' => ['class' => 'form-group col-md-2'],
],
[
'name' => 'tax',
'type' => 'number',
'default' => 0,
'wrapper' => ['class' => 'form-group col-md-2'],
],
]);
}
/**
* Define what happens when the Update operation is loaded.
*
* @see https://backpackforlaravel.com/docs/crud-operation-update
* @return void
*/
protected function setupUpdateOperation()
{
$this->setupCreateOperation();
}
protected function setupShowOperation()
{
CRUD::column('payment_method');
CRUD::column('payment_provider');
CRUD::column('items')->type('relationship');
}
public function store()
{
// create the main item as usual
$response = $this->traitStore();
// instead of returning, take a little time to create the InvoiceItems too
$invoice_id = $this->crud->entry->id;
$items = collect(json_decode(request('items'), true));
$items->each(function($item, $key) use ($invoice_id) {
$item['invoice_id'] = $invoice_id;
\App\Models\InvoiceItem::create($item);
});
return $response;
}
public function update()
{
$response = $this->traitUpdate();
// instead of returning, take a little time to update the InvoiceItems too
$invoice_id = $this->crud->entry->id;
$items = collect(json_decode(request('items'), true));
$items->each(function($item, $key) use ($invoice_id) {
$item['invoice_id'] = $invoice_id;
if ($item['id']) {
$invoice_item = \App\Models\InvoiceItem::find($item['id']);
$invoice_item->update($item);
} else {
\App\Models\InvoiceItem::create($item);
}
});
// delete removed InvoiceItems
$related_items_in_request = $items->pluck('id');
$related_items_in_db = $this->crud->entry->items;
$related_items_in_db->each(function($item, $key) use ($related_items_in_request) {
if (!$related_items_in_request->contains($item['id'])) {
$item->delete();
}
});
return $response;
}
}
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Foundation\Http\FormRequest;
class InvoiceRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
// only allow updates if the user is logged in
return backpack_auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'series' => 'required',
'number' => 'required',
'items' => 'required',
];
}
/**
* Get the validation attributes that apply to the request.
*
* @return array
*/
public function attributes()
{
return [
//
];
}
/**
* Get the validation messages that apply to the request.
*
* @return array
*/
public function messages()
{
return [
//
];
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Invoice extends Model
{
use \Backpack\CRUD\app\Models\Traits\CrudTrait;
use SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'number',
'series',
'payment_method',
'payment_provider',
'customer_id',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'customer_id' => 'integer',
];
public function items()
{
return $this->hasMany(\App\Models\InvoiceItem::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class InvoiceItem extends Model
{
use \Backpack\CRUD\app\Models\Traits\CrudTrait;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'invoice_id',
'order',
'description',
'quantity',
'price',
'tax',
'extras',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'invoice_id' => 'integer',
'extras' => 'array',
];
public function invoice()
{
return $this->belongsTo(\App\Models\Invoice::class);
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateInvoicesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->integer('number');
$table->string('series');
$table->string('payment_method')->nullable();
$table->string('payment_provider')->nullable();
$table->bigInteger('customer_id')->nullable();
$table->softDeletes();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('invoices');
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateInvoiceItemsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('invoice_items', function (Blueprint $table) {
$table->id();
$table->bigInteger('invoice_id');
$table->unsignedInteger('order');
$table->string('description');
$table->unsignedInteger('quantity');
$table->unsignedInteger('price');
$table->unsignedInteger('tax');
$table->json('extras');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('invoice_items');
}
}
@matovu-ronald
Copy link

matovu-ronald commented Sep 23, 2020

Hello, inquiring on how to populate the repeatable values to have the values of the related model for editing, the repeatable field doesn't seem to be filled on update by default and seem not to see the code doing that as per the screenshot you shared in the thread

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment