Have you ever wanted to modify an order or an invoice directly in Prestashop? Tired of bypassing PS limitations via phpMyAdmin? You can create an Admin Controller in PS 1.7 to edit your invoices directly in your browser!

You can also view this tuto to completely customize an Admin Controller from scratch, with your Custom SQL Table

Objective

With this tutorial, you will be able to create an Admin Controller to create and edit any Prestashop class. We will take Order Invoice as an example. This way, we can add, remove and edit any field of any invoice in Prestashop.

This extension will be available in your Backoffice through a URL that looks like this:

https://my.presta.com/admin/index.php?controller=AdminCustomInvoices

We have to:

  1. Create the module and a simple Admin Controller
  2. Edit the Admin Controller to present all invoices in a list
  3. Edit it again to add/edit invoices

Create the Admin Controller (via a Module)

Admin Controllers only exist in a module. So we need to create a module first! Let’s create a dummy module in modules/my_module/my_module.php :

<?php
if (!defined('_PS_VERSION_')) {exit;}
class My_Module extends Module {
  public function __construct() {
    $this->name = 'my_module';
    $this->tab = 'administration';
    $this->version = '1.0.0';
    $this->author = 'Florian Courgey';
    $this->bootstrap = true;
    parent::__construct();
    $this->displayName = $this->l('PrestaShop Module by FC');
    $this->description = $this->l('Improve your store by [...]');
    $this->ps_versions_compliancy = ['min' => '1.7', 'max' => _PS_VERSION_];
  }
}

Then, activate it through the PrestShop Backoffice by looking for my_module:

Finally, we can create our actual AdminCustomInvoicesController in modules/my_module/controllers/admin/AdminCustomInvoicesController.php<:

class AdminCustomInvoicesController extends ModuleAdminController {
  public function __construct(){
    $this->bootstrap = true; // use Bootstrap CSS
    $this->table = 'order_invoice'; // SQL table name, will be prefixed with _DB_PREFIX_
    $this->className = 'OrderInvoice'; // PHP class name
    $this->allow_export = true; // allow export in CSV, XLS..
  }

  public function access($action, $disable = false){
    return true;
  }
}

Let’s check that everything is working well by heading to https://my.presta.com/admin/index.php?controller=AdminCustomInvoices :

Snap! It doesn’t work. We have to allow this Controller in order to use it. Prestashop uses Tab to whitelist Controllers.

Directly in the database, execute:

INSERT INTO `ps_tab` (id_parent, position, module, class_name, active)
VALUES (3, 6, 'my_module', 'AdminCustomInvoices', 1);

Refresh your tab, and Voila! Our empty Controller is looking great:

Add the List feature

For example, an Admin Controller to handle your invoices (list, view, create, edit and delete)

class AdminCustomInvoicesController extends ModuleAdminController {
  public function __construct(){
    // [...]

    $this->fields_list = [
      'id_order_invoice' => ['title' => $this->trans('ID', [], 'Admin.Global'),'class' => 'fixed-width-xs'],
      'number' => ['title' => $this->trans('Number', [], 'Admin.Global'),'class' => 'fixed-width-xs'],
      'date_add' => ['title' => $this->trans('Date', [], 'Admin.Global'), 'type'=>'datetime'],
      'total_products_wt' => ['title' => $this->trans('Total products', [], 'Admin.Global'),
        'align' => 'text-right',
        'type' => 'price',
      ],
      'total_shipping_tax_incl' => ['title' => $this->trans('Total shipping', [], 'Admin.Global'),
        'align' => 'text-right',
        'type' => 'price',
      ],
      'total_paid_tax_incl' => ['title' => $this->trans('Total paid', [], 'Admin.Global'),
        'align' => 'text-right',
        'type' => 'price',
      ],
    ];
  }
}

Which gives us the following list (sortable and filterable!!):

Add the edit & add features

Add $this->fields_form and $this->addRowAction:

class AdminCustomInvoicesController extends ModuleAdminController {
  public function __construct(){
    // [...]

    $this->addRowAction('edit');
    $this->addRowAction('details');

    $this->fields_form = [
      'legend' => ['title' => $this->l('Custom Invoice'),'icon' => 'icon-list-ul'],
      'input' => [
        ['name' => 'date_add','type' => 'datetime','label' => 'Date add',],
        ['name'=>'number','type'=>'text','required' => true,'label' => 'Number',],
        ['name'=>'note','type'=>'textarea','label' => 'Note',],
        ['name'=>'delivery_number','type'=>'text','label'=>'Delivery number'],
        ['name'=>'delivery_date','type'=>'datetime','label'=>'Delivery date'],
        ['name'=>'total_discount_tax_excl','type'=>'text','label'=>'Total amount of discounts (no tax)'],
        ['name'=>'total_discount_tax_incl','type'=>'text','label'=>'Total amount of discounts (with tax)'],
        ['name'=>'total_shipping_tax_excl','type'=>'text','label'=>'Total cost of shipping (no tax)'],
        ['name'=>'total_shipping_tax_incl','type'=>'text','label'=>'Total cost of shipping (with tax)'],
        ['name'=>'total_products','type'=>'text','label'=>'Total cost of products (no tax)'],
        ['name'=>'total_products_wt','type'=>'text','label'=>'Total cost of products (with tax)'],
        ['name'=>'total_wrapping_tax_excl','type'=>'text','label'=>'Total cost of wrapping (no tax)'],
        ['name'=>'total_wrapping_tax_incl','type'=>'text','label'=>'Total cost of wrapping (with tax)'],
        ['name'=>'total_paid_tax_excl','type'=>'text','label'=>'Total paid (no tax)'],
        ['name'=>'total_paid_tax_incl','type'=>'text','label'=>'Total paid (with tax)'],
        ['name'=>'shop_address','type'=>'textarea','label'=>'Shop address'],
      ],
      'submit' => ['title' => $this->trans('Save', [], 'Admin.Actions'),],
    ];
  }
}

This new code adds buttons in the last column:

And hitting Edit results in a nice form, ready to be edited!

Going further

4. a) Add customer info in the list

class AdminCustomInvoicesController extends ModuleAdminController {
  public function __construct(){
    // [...]
    $this->_select = 'concat(upper(c.lastname), " ", c.firstname) as customer';
    $this->_join = '
      JOIN '._DB_PREFIX_.'orders o ON (o.id_order = a.id_order)
      JOIN '._DB_PREFIX_.'customer c ON (c.id_customer = o.id_customer)
    ';

    $this->fields_list = [
      // [...]
      'customer' => ['title' => $this->trans('Customer', [], 'Admin.Global')],
      // [...]
    ];
  }
}

Displays the customer name in a column:

4. b) Add order info above the form

class AdminCustomInvoicesController extends ModuleAdminController {
  // [...]

  public function renderForm(){
    $invoice = $this->object; // get invoice
    $order = $invoice->getOrder(); // get order
    $customer = $order->getCustomer(); // get customer
    $currency = new Currency($order->id_currency); // get currency
    // add some info in an HTML string
    $info = '<div class="panel">';
    $info .= '<div class="panel-heading"><i class="icon-list-ul"></i> Order informations</div>';
    $info .= "Order reference: {$order->reference}<br/>";
    $info .= "Customer: {$customer->firstname} {$customer->lastname}<br/>";
    $info .= "Total_paid : ".Tools::displayPrice($order->total_paid, $currency)."<br/>";
    $info .= "Get total paid : ".Tools::displayPrice($order->getTotalPaid(), $currency)."<br/>";
    $info .= "Payment: {$order->payment}<br/>";
    $info .= "Order state : {$order->getCurrentOrderState()->name[$this->context->language->id]}";
    $info .= '</div>';
    // push the HTML to $this->content
    $this->content .= $info;
    return parent::renderForm();
  }
}

And we now have a simple panel that serves as a header to render some data about the order and the customer:

4. c) Delete an invoice

USE WITH CARE, you CANNOT undo a deletion!!!

class AdminCustomInvoicesController extends ModuleAdminController {
  public function __construct(){
    // [...]

    $this->addRowAction('delete');
    $this->bulk_actions = [
      'delete' => [
        'text' => 'Delete',
        'icon' => 'icon-power-off'
      ],
    ];
  }
}

This new code will add buttons to delete via one-shot and bulk:

Full version of the Code

Hosted on https://gist.github.com/floriancourgey/cb63fd5abd93e1109fcf624a68307403

Mobile and responsive version: