Browse Source

Merge branch '1.11.x_facturas' of https://github.com/nosolored/chamilo-lms into nosolored-1.11.x_facturas

Yannick Warnier 6 years ago
parent
commit
16931ecb2a
29 changed files with 1225 additions and 55 deletions
  1. 2 1
      main/inc/lib/CoursesAndSessionsCatalog.class.php
  2. 10 0
      plugin/buycourses/CHANGELOG.md
  3. 86 0
      plugin/buycourses/database.php
  4. 23 0
      plugin/buycourses/lang/english.php
  5. 37 2
      plugin/buycourses/lang/spanish.php
  6. 9 0
      plugin/buycourses/resources/css/style.css
  7. 493 18
      plugin/buycourses/src/buy_course_plugin.class.php
  8. 3 0
      plugin/buycourses/src/buycourses.ajax.php
  9. 9 0
      plugin/buycourses/src/configuration.php
  10. 16 1
      plugin/buycourses/src/configure_course.php
  11. 122 0
      plugin/buycourses/src/invoice.php
  12. 96 12
      plugin/buycourses/src/paymentsetup.php
  13. 48 4
      plugin/buycourses/src/process_confirm.php
  14. 4 0
      plugin/buycourses/src/sales_report.php
  15. 52 8
      plugin/buycourses/src/service_process_confirm.php
  16. 17 1
      plugin/buycourses/src/service_sales_report.php
  17. 9 0
      plugin/buycourses/src/services_add.php
  18. 8 0
      plugin/buycourses/src/services_edit.php
  19. 14 0
      plugin/buycourses/update.php
  20. 39 1
      plugin/buycourses/view/configuration.tpl
  21. 14 0
      plugin/buycourses/view/message_confirm.tpl
  22. 4 4
      plugin/buycourses/view/message_transfer.tpl
  23. 18 0
      plugin/buycourses/view/process.tpl
  24. 1 1
      plugin/buycourses/view/process_confirm.tpl
  25. 13 0
      plugin/buycourses/view/sales_report.tpl
  26. 9 1
      plugin/buycourses/view/service_information.tpl
  27. 36 0
      plugin/buycourses/view/service_message_transfer.tpl
  28. 20 1
      plugin/buycourses/view/service_process.tpl
  29. 13 0
      plugin/buycourses/view/service_sales_report.tpl

+ 2 - 1
main/inc/lib/CoursesAndSessionsCatalog.class.php

@@ -477,7 +477,7 @@ class CoursesAndSessionsCatalog
     {
         $em = Database::getManager();
         $urlId = api_get_current_access_url_id();
-        $date = Database::escape_string($date);
+
         $sql = "SELECT s.id FROM session s ";
         $sql .= "
             INNER JOIN access_url_rel_session ars
@@ -490,6 +490,7 @@ class CoursesAndSessionsCatalog
         ";
 
         if (!is_null($date)) {
+            $date = Database::escape_string($date);
             $sql .= "
                 AND (
                     ('$date' BETWEEN DATE(s.access_start_date) AND DATE(s.access_end_date))

+ 10 - 0
plugin/buycourses/CHANGELOG.md

@@ -1,3 +1,13 @@
+v5.0 - 2019-02-06
+====
+
+This version includes two additional modules (taxes and invoices), 
+which can be enabled from the configuration.
+
+The file update.php must be executed to update the structure of the tables
+ in the database.
+ 
+
 v4.0 - 2017-04-25
 ====
 

+ 86 - 0
plugin/buycourses/database.php

@@ -90,6 +90,11 @@ $itemTable->addColumn(
     \Doctrine\DBAL\Types\Type::INTEGER,
     ['unsigned' => true]
 );
+$itemTable->addColumn(
+    'tax_perc',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['unsigned' => true, 'notnull' => false]
+);
 $itemTable->setPrimaryKey(['id']);
 $itemTable->addForeignKeyConstraint(
     $currencyTable,
@@ -195,6 +200,21 @@ $saleTable->addColumn(
     \Doctrine\DBAL\Types\Type::DECIMAL,
     ['scale' => 2]
 );
+$saleTable->addColumn(
+    'price_without_tax',
+    \Doctrine\DBAL\Types\Type::DECIMAL,
+    ['scale' => 2, 'notnull' => false]
+);
+$saleTable->addColumn(
+    'tax_perc',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['unsigned' => true, 'notnull' => false]
+);
+$saleTable->addColumn(
+    'tax_amount',
+    \Doctrine\DBAL\Types\Type::DECIMAL,
+        ['scale' => 2, 'notnull' => false]
+);
 $saleTable->addColumn(
     'currency_id',
     \Doctrine\DBAL\Types\Type::INTEGER,
@@ -202,6 +222,7 @@ $saleTable->addColumn(
 );
 $saleTable->addColumn('status', \Doctrine\DBAL\Types\Type::INTEGER);
 $saleTable->addColumn('payment_type', \Doctrine\DBAL\Types\Type::INTEGER);
+$saleTable->addColumn('invoice', \Doctrine\DBAL\Types\Type::INTEGER);
 $saleTable->setPrimaryKey(['id']);
 $saleTable->addForeignKeyConstraint(
     $currencyTable,
@@ -250,6 +271,21 @@ $servicesNodeTable->addColumn(
     \Doctrine\DBAL\Types\Type::DECIMAL,
     ['scale' => 2]
 );
+$servicesNodeTable->addColumn(
+    'price_without_tax',
+    \Doctrine\DBAL\Types\Type::DECIMAL,
+    ['scale' => 2, 'notnull' => false]
+);
+$servicesNodeTable->addColumn(
+    'tax_perc',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['unsigned' => true, 'notnull' => false]
+);
+$servicesNodeTable->addColumn(
+    'tax_amount',
+    \Doctrine\DBAL\Types\Type::DECIMAL,
+    ['scale' => 2, 'notnull' => false]
+);
 $servicesNodeTable->addColumn('node_type', \Doctrine\DBAL\Types\Type::INTEGER);
 $servicesNodeTable->addColumn('node_id', \Doctrine\DBAL\Types\Type::INTEGER);
 $servicesNodeTable->addColumn('buyer_id', \Doctrine\DBAL\Types\Type::INTEGER);
@@ -265,6 +301,7 @@ $servicesNodeTable->addColumn(
 );
 $servicesNodeTable->addColumn('status', \Doctrine\DBAL\Types\Type::INTEGER);
 $servicesNodeTable->addColumn('payment_type', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->addColumn('invoice', \Doctrine\DBAL\Types\Type::INTEGER);
 $servicesNodeTable->setPrimaryKey(['id']);
 $servicesNodeTable->addForeignKeyConstraint(
     $servicesTable,
@@ -291,8 +328,39 @@ $globalTable->addColumn(
     ['autoincrement' => true, 'unsigned' => true]
 );
 $globalTable->addColumn('terms_and_conditions', \Doctrine\DBAL\Types\Type::TEXT);
+$globalTable->addColumn('global_tax_perc', \Doctrine\DBAL\Types\Type::INTEGER);
+$globalTable->addColumn('tax_applies_to', \Doctrine\DBAL\Types\Type::INTEGER);
+$globalTable->addColumn('tax_name', \Doctrine\DBAL\Types\Type::STRING);
+$globalTable->addColumn('seller_name', \Doctrine\DBAL\Types\Type::STRING);
+$globalTable->addColumn('seller_id', \Doctrine\DBAL\Types\Type::STRING);
+$globalTable->addColumn('seller_address', \Doctrine\DBAL\Types\Type::STRING);
+$globalTable->addColumn('seller_email', \Doctrine\DBAL\Types\Type::STRING);
+$globalTable->addColumn('next_number_invoice', \Doctrine\DBAL\Types\Type::INTEGER);
+$globalTable->addColumn('invoice_series', \Doctrine\DBAL\Types\Type::STRING);
 $globalTable->setPrimaryKey(['id']);
 
+$invoiceTable = $pluginSchema->createTable(BuyCoursesPlugin::TABLE_INVOICE);
+$invoiceTable->addColumn(
+    'id',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['autoincrement' => true, 'unsigned' => true]
+);
+$invoiceTable->addColumn('sale_id', \Doctrine\DBAL\Types\Type::INTEGER);
+$invoiceTable->addColumn('is_service', \Doctrine\DBAL\Types\Type::INTEGER);
+$invoiceTable->addColumn(
+    'num_invoice',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['unsigned' => true, 'notnull' => false]
+);
+$invoiceTable->addColumn(
+    'year',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['unsigned' => true, 'notnull' => false]
+);
+$invoiceTable->addColumn('serie', \Doctrine\DBAL\Types\Type::STRING);
+$invoiceTable->addColumn('date_invoice', \Doctrine\DBAL\Types\Type::DATETIME);
+$invoiceTable->setPrimaryKey(['id']);
+
 $queries = $pluginSchema->toSql($platform);
 
 foreach ($queries as $query) {
@@ -631,3 +699,21 @@ foreach ($currencies as $currency) {
         ]
     );
 }
+
+$fieldlabel = 'buycourses_company';
+$fieldtype = '1';
+$fieldtitle = BuyCoursesPlugin::get_lang('Company');
+$fielddefault = '';
+$field_id = UserManager::create_extra_field($fieldlabel, $fieldtype, $fieldtitle, $fielddefault);
+
+$fieldlabel = 'buycourses_vat';
+$fieldtype = '1';
+$fieldtitle = BuyCoursesPlugin::get_lang('VAT');
+$fielddefault = '';
+$field_id = UserManager::create_extra_field($fieldlabel, $fieldtype, $fieldtitle, $fielddefault);
+
+$fieldlabel = 'buycourses_address';
+$fieldtype = '1';
+$fieldtitle = BuyCoursesPlugin::get_lang('Address');
+$fielddefault = '';
+$field_id = UserManager::create_extra_field($fieldlabel, $fieldtype, $fieldtitle, $fielddefault);

+ 23 - 0
plugin/buycourses/lang/english.php

@@ -9,6 +9,8 @@ $strings['paypal_enable'] = "Enable PayPal";
 $strings['commissions_enable'] = "Enable Commissions";
 $strings['transfer_enable'] = "Enable bank transfer";
 $strings['unregistered_users_enable'] = "Allow anonymous users";
+$strings['invoicing_enable'] = "Enable Invoicing";
+$strings['tax_enable'] = "Enable Taxation";
 $strings['Free'] = "FREE";
 $strings['PaypalPayoutCommissions'] = "Paypal Payout Commissions";
 $strings['MyPayouts'] = "My payments";
@@ -160,3 +162,24 @@ $strings['BoughtBy'] = "BoughtBy";
 $strings['PurchaserUser'] = "Purchaser user";
 $strings['Pending'] = "Pending";
 $strings['Names'] = "Names";
+$strings['SellerName'] = "Seller name";
+$strings['SellerId'] = "Seller id";
+$strings['SellerAddress'] = "Seller address";
+$strings['SellerEmail'] = "Seller e-mail";
+$strings['NextNumberInvoice'] = "Next invoice number";
+$strings['NextNumberInvoiceDescription'] = "Number of the following invoice";
+$strings['InvoiceSeries'] = "Invoice series";
+$strings['InvoiceSeriesDescription'] = "Optional parameter: Example invoice Id <series><year>/<number>";
+$strings['InvoiceView'] = "View invoice";
+$strings['NoInvoiceEnable'] = "No invoicing block enable";
+$strings['Company'] = "Company";
+$strings['VAT'] = "VAT";
+$strings['Address'] = "Address";
+$strings['InvoiceNumber'] = "Invoice number";
+$strings['InvoiceDate'] = "Invoice date";
+$strings['Invoice'] = "Invoice";
+$strings['SaleEmail'] = "Sales e-mail";
+$strings['PurchaseDetailsIntro'] = "Purchase details";
+$strings['PurchaseDetailsEnd'] = "Regards";
+$strings['ProductName'] = "Product name";
+$strings['BankAccountIntro'] = "Bank Account Info";

+ 37 - 2
plugin/buycourses/lang/spanish.php

@@ -9,8 +9,9 @@ $strings['paypal_enable'] = "Habilitar PayPal";
 $strings['commissions_enable'] = "Habilitar Comisiones";
 $strings['transfer_enable'] = "Habilitar transferencia";
 $strings['unregistered_users_enable'] = "Permitir usuarios sin registro en la plataforma";
+$strings['invoicing_enable'] = "Habilitar Facturación";
+$strings['tax_enable'] = "Habilitar Impuestos";
 $strings['Free'] = "GRATIS";
-
 $strings['PaypalPayoutCommissions'] = "Pagar comisiones por Paypal";
 $strings['MyPayouts'] = "Mis Pagos";
 $strings['Commission'] = "Comisión";
@@ -40,7 +41,7 @@ $strings['Buyer'] = "Comprador";
 $strings['BankTransfer'] = "Transferencia Bancaria";
 $strings['SaleInfo'] = "Información de la venta";
 $strings['SaleStatusPending'] = "Venta pendiente";
-$strings['SaleStatusCancelled'] = "Venta cancelada";
+$strings['SaleStatusCanceled'] = "Venta cancelada";
 $strings['SaleStatusCompleted'] = "Venta completada";
 $strings['PayoutStatusPending'] = "Pago pendiente";
 $strings['PayoutStatusCanceled'] = "Pago cancelado";
@@ -173,3 +174,37 @@ $strings['DeleteThisService'] = 'Eliminar este servicio';
 $strings['YourCoursesNeedAtLeastOneLearningPath'] = 'Los cursos en los que estás registrado necesitan tener al menos una lección que contenga un item de cerficado final';
 $strings['NoPaymentOptionAvailable'] = 'No existen opciones de pago. Por favor reporte este problema al administrador.';
 $strings['XIsOnlyPaymentMethodAvailable'] = '%s es el único método de pago disponible para esta compra.';
+$strings['GlobalTaxPerc'] = "Porcentaje del impuesto global";
+$strings['GlobalTaxPercDescription'] = "Porcentaje por defecto que se usará, excepto si existe un impuesto específico en el curso, sesión o servicio.";
+$strings['TaxPerc'] = "Porcentaje del impuesto";
+$strings['TaxPercDescription'] = "Si se deja vacío se usará valor global por defecto.";
+$strings['ByDefault'] = "por defecto (valor global)";
+$strings['OnlyCourses'] = "Solo Cursos";
+$strings['OnlySessions'] = "Solo Sesiones";
+$strings['OnlyServices'] = "Solo Servicios";
+$strings['TaxAppliesTo'] = "Impuestos aplicados a";
+$strings['AllCoursesSessionsAndServices'] = "Todos (Cursos, sesiones y servicios)";
+$strings['TaxNameCustom'] = "Nombre del impuesto";
+$strings['TaxNameExamples'] = "VAT, IVA, IGV, TVA, IV ...";
+$strings['ErrorUpdateFieldDB'] = "Error al actualizar los campos de la base de datos";
+$strings['SellerName'] = "Nombre vendedor";
+$strings['SellerId'] = "Identificador vendedor";
+$strings['SellerAddress'] = "Dirección vendedor";
+$strings['SellerEmail'] = "E-mail vendedor";
+$strings['NextNumberInvoice'] = "Número siguiente factura";
+$strings['NextNumberInvoiceDescription'] = "Número de la siguiente factura asignado de forma manual";
+$strings['InvoiceSeries'] = "Serie factura";
+$strings['InvoiceSeriesDescription'] = "Parámetro opcional: Ejemplo de numeración factura  <serie><año>/<número>";
+$strings['InvoiceView'] = "Ver factura";
+$strings['NoInvoiceEnable'] = "No está habilitado el bloque de facturación";
+$strings['Company'] = "Empresa";
+$strings['VAT'] = "CIF";
+$strings['Address'] = "Dirección";
+$strings['InvoiceNumber'] = "Num. factura";
+$strings['InvoiceDate'] = "Fecha de emisión";
+$strings['Invoice'] = "Factura";
+$strings['SaleEmail'] = "E-mail de ventas";
+$strings['PurchaseDetailsIntro'] = "Detalles de la comprar";
+$strings['PurchaseDetailsEnd'] = "Atentamente";
+$strings['ProductName'] = "Nombre producto";
+$strings['BankAccountIntro'] = "Información cuentas bancarias";

+ 9 - 0
plugin/buycourses/resources/css/style.css

@@ -41,6 +41,15 @@
     letter-spacing: -0.020em;
 }
 
+.buy-info .price-details-tax, .service-buy .price-details-tax {
+    font-size: 18px;
+    line-height: 24px;
+    font-weight: bold;
+    padding-bottom: 0px;
+    padding-top: 10px;
+    letter-spacing: -0.020em;
+}
+
 .buy-info .buy-item .title {
     margin-top: 5px;
     font-weight: bold;

+ 493 - 18
plugin/buycourses/src/buy_course_plugin.class.php

@@ -31,6 +31,7 @@ class BuyCoursesPlugin extends Plugin
     const TABLE_SERVICES_SALE = 'plugin_buycourses_service_sale';
     const TABLE_CULQI = 'plugin_buycourses_culqi';
     const TABLE_GLOBAL_CONFIG = 'plugin_buycourses_global_config';
+    const TABLE_INVOICE = 'plugin_buycourses_invoices';
     const PRODUCT_TYPE_COURSE = 1;
     const PRODUCT_TYPE_SESSION = 2;
     const PAYMENT_TYPE_PAYPAL = 1;
@@ -51,6 +52,10 @@ class BuyCoursesPlugin extends Plugin
     const SERVICE_TYPE_LP_FINAL_ITEM = 4;
     const CULQI_INTEGRATION_TYPE = 'INTEG';
     const CULQI_PRODUCTION_TYPE = 'PRODUC';
+    const TAX_APPLIES_TO_ALL = 1;
+    const TAX_APPLIES_TO_ONLY_COURSE = 2;
+    const TAX_APPLIES_TO_ONLY_SESSION = 3;
+    const TAX_APPLIES_TO_ONLY_SERVICES = 4;
 
     public $isAdminPlugin = true;
 
@@ -60,7 +65,7 @@ class BuyCoursesPlugin extends Plugin
     public function __construct()
     {
         parent::__construct(
-            '1.0',
+            '5.0',
             "
                 Jose Angel Ruiz - NoSoloRed (original author) <br/>
                 Francis Gonzales and Yannick Warnier - BeezNest (integration) <br/>
@@ -81,6 +86,8 @@ class BuyCoursesPlugin extends Plugin
                 'commissions_enable' => 'boolean',
                 'unregistered_users_enable' => 'boolean',
                 'hide_free_text' => 'boolean',
+                'invoicing_enable' => 'boolean',
+                'tax_enable' => 'boolean',
             ]
         );
     }
@@ -164,6 +171,131 @@ class BuyCoursesPlugin extends Plugin
         $this->manageTab(false);
     }
 
+    public function update()
+    {
+        $table = self::TABLE_GLOBAL_CONFIG;
+        $sql = "SHOW COLUMNS FROM $table WHERE Field = 'global_tax_perc'";
+        $res = Database::query($sql);
+
+        if (Database::num_rows($res) === 0) {
+            $sql = "ALTER TABLE $table ADD (
+                sale_email varchar(255) NOT NULL,
+                global_tax_perc int(11) unsigned NOT NULL,
+                tax_applies_to int(11) unsigned NOT NULL,
+                tax_name varchar(255) NOT NULL,
+                seller_name varchar(255) NOT NULL,
+                seller_id varchar(255) NOT NULL,
+                seller_address varchar(255) NOT NULL,
+                seller_email varchar(255) NOT NULL,
+                next_number_invoice int(11) unsigned NOT NULL,
+                invoice_series varchar(255) NOT NULL
+            )";
+            $res = Database::query($sql);
+            if (!$res) {
+                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
+            }
+        }
+
+        $table = self::TABLE_ITEM;
+        $sql = "SHOW COLUMNS FROM $table WHERE Field = 'tax_perc'";
+        $res = Database::query($sql);
+
+        if (Database::num_rows($res) === 0) {
+            $sql = "ALTER TABLE $table ADD tax_perc int(11) unsigned NULL";
+            $res = Database::query($sql);
+            if (!$res) {
+                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
+            }
+        }
+
+        $table = self::TABLE_SERVICES;
+        $sql = "SHOW COLUMNS FROM $table WHERE Field = 'tax_perc'";
+        $res = Database::query($sql);
+
+        if (Database::num_rows($res) === 0) {
+            $sql = "ALTER TABLE $table ADD tax_perc int(11) unsigned NULL";
+            $res = Database::query($sql);
+            if (!$res) {
+                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
+            }
+        }
+
+        $table = self::TABLE_SALE;
+        $sql = "SHOW COLUMNS FROM $table WHERE Field = 'tax_perc'";
+        $res = Database::query($sql);
+
+        if (Database::num_rows($res) === 0) {
+            $sql = "ALTER TABLE $table ADD (
+                price_without_tax decimal(10,2) NULL,
+                tax_perc int(11) unsigned NULL,
+                tax_amount decimal(10,2) NULL,
+                invoice int(11) unsigned NULL
+            )";
+            $res = Database::query($sql);
+            if (!$res) {
+                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
+            }
+        }
+
+        $table = self::TABLE_SERVICES_SALE;
+        $sql = "SHOW COLUMNS FROM $table WHERE Field = 'tax_perc'";
+        $res = Database::query($sql);
+
+        if (Database::num_rows($res) === 0) {
+            $sql = "ALTER TABLE $table ADD (
+                price_without_tax decimal(10,2) NULL,
+                tax_perc int(11) unsigned NULL,
+                tax_amount decimal(10,2) NULL,
+                invoice int(11) unsigned NULL
+            )";
+            $res = Database::query($sql);
+            if (!$res) {
+                echo Display::return_message($this->get_lang('ErrorUpdateFieldDB'), 'warning');
+            }
+        }
+
+        $table = self::TABLE_INVOICE;
+        $sql = "CREATE TABLE IF NOT EXISTS $table (
+            id int(11) unsigned NOT NULL AUTO_INCREMENT,
+            sale_id int(11) unsigned NOT NULL,
+            is_service int(11) unsigned NOT NULL,
+            num_invoice int(11) unsigned NOT NULL,
+            year int(4) unsigned NOT NULL,
+            serie varchar(255) NOT NULL,
+            date_invoice datetime NOT NULL,
+            PRIMARY KEY (id)
+        )";
+        $res = Database::query($sql);
+
+        Display::addFlash(
+            Display::return_message(
+                $this->get_lang('Updated'),
+                'info',
+                false
+            )
+        );
+
+        $fieldlabel = 'buycourses_company';
+        $fieldtype = '1';
+        $fieldtitle = $this->get_lang('Company');
+        $fielddefault = '';
+        $field_id = UserManager::create_extra_field($fieldlabel, $fieldtype, $fieldtitle, $fielddefault);
+
+        $fieldlabel = 'buycourses_vat';
+        $fieldtype = '1';
+        $fieldtitle = $this->get_lang('VAT');
+        $fielddefault = '';
+        $field_id = UserManager::create_extra_field($fieldlabel, $fieldtype, $fieldtitle, $fielddefault);
+
+        $fieldlabel = 'buycourses_address';
+        $fieldtype = '1';
+        $fieldtitle = $this->get_lang('Address');
+        $fielddefault = '';
+        $field_id = UserManager::create_extra_field($fieldlabel, $fieldtype, $fieldtitle, $fielddefault);
+
+        header('Location: '.api_get_path(WEB_PLUGIN_PATH).'buycourses');
+    }
+
     /**
      * This function verify if the plugin is enable and return the price info for a course or session in the new grid
      * catalog for 1.11.x , the main purpose is to show if a course or session is in sale it shows in the main platform
@@ -525,12 +657,29 @@ class BuyCoursesPlugin extends Plugin
                 continue;
             }
 
+            $price = $item['price'];
+            $taxPerc = null;
+            $priceWithoutTax = $item['price'];
+
+            $taxEnable = $this->get('tax_enable') === 'true';
+            $globalParameters = $this->getGlobalParameters();
+            $taxAppliesTo = $globalParameters['tax_applies_to'];
+            if ($taxEnable &&
+                ($taxAppliesTo == self::TAX_APPLIES_TO_ALL || $taxAppliesTo == self::TAX_APPLIES_TO_ONLY_COURSE)
+            ) {
+                $globalTaxPerc = $globalParameters['global_tax_perc'];
+                $precision = 2;
+                $taxPerc = is_null($item['tax_perc']) ? $globalTaxPerc : $item['tax_perc'];
+                $taxAmount = round($priceWithoutTax * $taxPerc / 100, $precision);
+                $price = $priceWithoutTax + $taxAmount;
+            }
+
             $courseItem = [
                 'id' => $course->getId(),
                 'title' => $course->getTitle(),
                 'code' => $course->getCode(),
                 'course_img' => null,
-                'price' => $item['price'],
+                'price' => $price,
                 'currency' => $item['iso_code'],
                 'teachers' => [],
                 'enrolled' => $this->getUserStatusForCourse(api_get_user_id(), $course),
@@ -592,6 +741,24 @@ class BuyCoursesPlugin extends Plugin
                 ]
             );
 
+        $price = $item['price'];
+        $taxPerc = null;
+        $priceWithoutTax = $item['price'];
+        $precision = 2;
+
+        $taxEnable = $this->get('tax_enable') === 'true';
+        $globalParameters = $this->getGlobalParameters();
+        $taxAppliesTo = $globalParameters['tax_applies_to'];
+        if ($taxEnable &&
+            ($taxAppliesTo == self::TAX_APPLIES_TO_ALL || $taxAppliesTo == self::TAX_APPLIES_TO_ONLY_COURSE)
+        ) {
+            $globalTaxPerc = $globalParameters['global_tax_perc'];
+            $precision = 2;
+            $taxPerc = is_null($item['tax_perc']) ? $globalTaxPerc : $item['tax_perc'];
+            $taxAmount = round($priceWithoutTax * $taxPerc / 100, $precision);
+            $price = $priceWithoutTax + $taxAmount;
+        }
+
         $courseInfo = [
             'id' => $course->getId(),
             'title' => $course->getTitle(),
@@ -599,7 +766,13 @@ class BuyCoursesPlugin extends Plugin
             'code' => $course->getCode(),
             'visual_code' => $course->getVisualCode(),
             'teachers' => [],
-            'price' => $item['price'],
+            'price' => number_format($price, $precision),
+            'price_without_tax' => number_format($priceWithoutTax, $precision),
+            'tax_amount' => number_format($taxAmount, $precision),
+            'tax_perc' => $taxPerc,
+            'tax_name' => $globalParameters['tax_name'],
+            'tax_enable' => $taxEnable &&
+                ($taxAppliesTo == self::TAX_APPLIES_TO_ALL || $taxAppliesTo == self::TAX_APPLIES_TO_ONLY_COURSE),
             'currency' => $item['iso_code'],
             'course_img' => null,
         ];
@@ -658,13 +831,36 @@ class BuyCoursesPlugin extends Plugin
             'coach_access_end_date' => $session->getCoachAccessEndDate(),
         ]);
 
+        $price = $item['price'];
+        $taxPerc = null;
+        $priceWithoutTax = $item['price'];
+        $precision = 2;
+
+        $taxEnable = $this->get('tax_enable') === 'true';
+        $globalParameters = $this->getGlobalParameters();
+        $taxAppliesTo = $globalParameters['tax_applies_to'];
+        if ($taxEnable &&
+            ($taxAppliesTo == self::TAX_APPLIES_TO_ALL || $taxAppliesTo == self::TAX_APPLIES_TO_ONLY_SESSION)
+        ) {
+            $globalTaxPerc = $globalParameters['global_tax_perc'];
+            $taxPerc = is_null($item['tax_perc']) ? $globalTaxPerc : $item['tax_perc'];
+            $taxAmount = round($priceWithoutTax * $taxPerc / 100, $precision);
+            $price = $priceWithoutTax + $taxAmount;
+        }
+
         $sessionInfo = [
             'id' => $session->getId(),
             'name' => $session->getName(),
             'description' => $session->getDescription(),
             'dates' => $sessionDates,
             'courses' => [],
-            'price' => $item['price'],
+            'price' => number_format($price, $precision),
+            'price_without_tax' => number_format($priceWithoutTax, $precision),
+            'tax_amount' => number_format($taxAmount, $precision),
+            'tax_perc' => $taxPerc,
+            'tax_name' => $globalParameters['tax_name'],
+            'tax_enable' => $taxEnable &&
+                ($taxAppliesTo == self::TAX_APPLIES_TO_ALL || $taxAppliesTo == self::TAX_APPLIES_TO_ONLY_SESSION),
             'currency' => $item['iso_code'],
             'image' => null,
             'nbrCourses' => $session->getNbrCourses(),
@@ -771,6 +967,26 @@ class BuyCoursesPlugin extends Plugin
             $productName = $session->getName();
         }
 
+        $price = $item['price'];
+        $priceWithoutTax = null;
+        $taxPerc = null;
+
+        $taxEnable = $this->get('tax_enable') === 'true';
+        $globalParameters = $this->getGlobalParameters();
+        $taxAppliesTo = $globalParameters['tax_applies_to'];
+        if ($taxEnable &&
+            ($taxAppliesTo == self::TAX_APPLIES_TO_ALL ||
+            ($taxAppliesTo == TAX_APPLIES_TO_ONLY_COURSE && $item['product_type'] == self::PRODUCT_TYPE_COURSE) ||
+            ($taxAppliesTo == TAX_APPLIES_TO_ONLY_SESSION && $item['product_type'] == self::PRODUCT_TYPE_SESSION))
+        ) {
+            $priceWithoutTax = $item['price'];
+            $globalTaxPerc = $globalParameters['global_tax_perc'];
+            $precision = 2;
+            $taxPerc = is_null($item['tax_perc']) ? $globalTaxPerc : $item['tax_perc'];
+            $taxAmount = round($priceWithoutTax * $taxPerc / 100, $precision);
+            $price = $priceWithoutTax + $taxAmount;
+        }
+
         $values = [
             'reference' => $this->generateReference(
                 api_get_user_id(),
@@ -783,7 +999,10 @@ class BuyCoursesPlugin extends Plugin
             'product_type' => $item['product_type'],
             'product_name' => $productName,
             'product_id' => $item['product_id'],
-            'price' => $item['price'],
+            'price' => $price,
+            'price_without_tax' => $priceWithoutTax,
+            'tax_perc' => $taxPerc,
+            'tax_amount' => $taxAmount,
             'status' => self::SALE_STATUS_PENDING,
             'payment_type' => intval($paymentType),
         ];
@@ -843,6 +1062,87 @@ class BuyCoursesPlugin extends Plugin
         );
     }
 
+    /**
+     * Get data of sales.
+     *
+     * @param int $saleId    The sale id
+     * @param int $isService Check if a service
+     *
+     * @return array The sale data
+     */
+    public function getDataSaleInvoice($saleId, $isService)
+    {
+        $data = [];
+        if ($isService) {
+            $sale = $this->getServiceSale($saleId);
+            $data['reference'] = $sale['reference'];
+            $data['product_name'] = $sale['service']['name'];
+            $data['payment_type'] = $sale['payment_type'];
+            $data['user_id'] = $sale['buyer']['id'];
+            $data['price'] = $sale['price'];
+            $data['price_without_tax'] = $sale['price_without_tax'];
+            $data['tax_perc'] = $sale['tax_perc'];
+            $data['tax_amount'] = $sale['tax_amount'];
+            $data['currency_id'] = $sale['currency_id'];
+            $data['date'] = $sale['buy_date'];
+        } else {
+            $sale = $this->getSale($saleId);
+            $data['reference'] = $sale['reference'];
+            $data['product_name'] = $sale['product_name'];
+            $data['payment_type'] = $sale['payment_type'];
+            $data['user_id'] = $sale['user_id'];
+            $data['price'] = $sale['price'];
+            $data['price_without_tax'] = $sale['price_without_tax'];
+            $data['tax_perc'] = $sale['tax_perc'];
+            $data['tax_amount'] = $sale['tax_amount'];
+            $data['currency_id'] = $sale['currency_id'];
+            $data['date'] = $sale['date'];
+        }
+
+        return $data;
+    }
+
+    /**
+     * Get data of invoice.
+     *
+     * @param int $saleId    The sale id
+     * @param int $isService Check if a service
+     *
+     * @return array The invoice data
+     */
+    public function getDataInvoice($saleId, $isService)
+    {
+        return Database::select(
+            '*',
+            Database::get_main_table(self::TABLE_INVOICE),
+            [
+                'where' => [
+                    'sale_id = ? AND ' => intval($saleId),
+                    'is_service = ?' => intval($isService),
+                ],
+            ],
+            'first'
+        );
+    }
+
+    /**
+     * Get numeration of invoice.
+     *
+     * @param int $saleId    The sale id
+     * @param int $isService Check if a service
+     *
+     * @return array The numeration invoice
+     */
+    public function getNumInvoice($saleId, $isService)
+    {
+        $dataInvoice = $this->getDataInvoice($saleId, $isService);
+        if (empty($dataInvoice)) {
+            return '-';
+        }
+
+        return $dataInvoice['serie'].$dataInvoice['year'].'/'.$dataInvoice['num_invoice'];
+    }
+
     /**
      * Get currency data by ID.
      *
@@ -897,6 +1197,9 @@ class BuyCoursesPlugin extends Plugin
 
         if ($saleIsCompleted) {
             $this->updateSaleStatus($sale['id'], self::SALE_STATUS_COMPLETED);
+            if ($this->get('invoicing_enable') === 'true') {
+                $this->setInvoice($sale['id']);
+            }
         }
 
         return $saleIsCompleted;
@@ -926,6 +1229,82 @@ class BuyCoursesPlugin extends Plugin
         ];
     }
 
+    /**
+     * Register a invoice.
+     *
+     * @param int $saleId    The sale ID
+     * @param int $isService The service type to filter (default : 0)
+     */
+    public function setInvoice($saleId, $isService = 0)
+    {
+        $invoiceTable = Database::get_main_table(self::TABLE_INVOICE);
+        $year = date("Y");
+
+        $globalParameters = $this->getGlobalParameters();
+        $numInvoice = $globalParameters['next_number_invoice'];
+        $serie = $globalParameters['invoice_series'];
+
+        if (empty($numInvoice)) {
+            $item = Database::select(
+                ['MAX(num_invoice) AS num_invoice'],
+                $invoiceTable,
+                [
+                    'where' => ['year = ?' => $year],
+                ],
+                'first'
+            );
+
+            $numInvoice = 1;
+            if ($item !== false) {
+                $numInvoice = (int) ($item['num_invoice'] + 1);
+            }
+        } else {
+            Database::update(
+                Database::get_main_table(self::TABLE_GLOBAL_CONFIG),
+                ['next_number_invoice' => 0],
+                ['id = ?' => 1]
+            );
+        }
+
+        Database::insert(
+            $invoiceTable,
+            [
+                'sale_id' => $saleId,
+                'is_service' => $isService,
+                'num_invoice' => $numInvoice,
+                'year' => $year,
+                'serie' => $serie,
+                'date_invoice' => api_get_utc_datetime(),
+            ]
+        );
+
+        // Record invoice in the sales table
+        $table = Database::get_main_table(self::TABLE_SALE);
+        if (empty($isService)) {
+            $table = Database::get_main_table(self::TABLE_SERVICES_SALE);
+        }
+        Database::update(
+            $table,
+            ['invoice' => 1],
+            ['id = ?' => $saleId]
+        );
+    }
+
+    /**
+     * Get Tax's types.
+     *
+     * @return array
+     */
+    public function getTaxAppliesTo()
+    {
+        return [
+            self::TAX_APPLIES_TO_ALL => $this->get_lang('AllCoursesSessionsAndServices'),
+            self::TAX_APPLIES_TO_ONLY_COURSE => $this->get_lang('OnlyCourses'),
+            self::TAX_APPLIES_TO_ONLY_SESSION => $this->get_lang('OnlySessions'),
+            self::TAX_APPLIES_TO_ONLY_SERVICES => $this->get_lang('OnlyServices'),
+        ];
+    }
+
     /**
      * Get a list of sales by the status.
      *
@@ -1155,6 +1534,7 @@ class BuyCoursesPlugin extends Plugin
             'visible' => false,
             'currency' => empty($defaultCurrency) ? null : $defaultCurrency['iso_code'],
             'price' => 0.00,
+            'tax_perc' => null,
         ];
 
         $item = $this->getItemByProduct($course->getId(), self::PRODUCT_TYPE_COURSE);
@@ -1164,6 +1544,7 @@ class BuyCoursesPlugin extends Plugin
             $courseItem['visible'] = true;
             $courseItem['currency'] = $item['iso_code'];
             $courseItem['price'] = $item['price'];
+            $courseItem['tax_perc'] = $item['tax_perc'];
         }
 
         return $courseItem;
@@ -1197,6 +1578,7 @@ class BuyCoursesPlugin extends Plugin
             'visible' => false,
             'currency' => empty($defaultCurrency) ? null : $defaultCurrency['iso_code'],
             'price' => 0.00,
+            'tax_perc' => null,
         ];
 
         $displayStartDate = $session->getDisplayStartDate();
@@ -1233,6 +1615,7 @@ class BuyCoursesPlugin extends Plugin
             $sessionItem['visible'] = true;
             $sessionItem['currency'] = $item['iso_code'];
             $sessionItem['price'] = $item['price'];
+            $sessionItem['tax_perc'] = $item['tax_perc'];
         }
 
         return $sessionItem;
@@ -1604,6 +1987,7 @@ class BuyCoursesPlugin extends Plugin
                 'name' => Security::remove_XSS($service['name']),
                 'description' => Security::remove_XSS($service['description']),
                 'price' => $service['price'],
+                'tax_perc' => $service['tax_perc'] != '' ? (int) $service['tax_perc'] : null,
                 'duration_days' => intval($service['duration_days']),
                 'applies_to' => intval($service['applies_to']),
                 'owner_id' => intval($service['owner_id']),
@@ -1658,15 +2042,16 @@ class BuyCoursesPlugin extends Plugin
                 'name' => Security::remove_XSS($service['name']),
                 'description' => Security::remove_XSS($service['description']),
                 'price' => $service['price'],
-                'duration_days' => intval($service['duration_days']),
-                'applies_to' => intval($service['applies_to']),
-                'owner_id' => intval($service['owner_id']),
-                'visibility' => intval($service['visibility']),
+                'tax_perc' => $service['tax_perc'] != '' ? (int) $service['tax_perc'] : null,
+                'duration_days' => (int) $service['duration_days'],
+                'applies_to' => (int) $service['applies_to'],
+                'owner_id' => (int) $service['owner_id'],
+                'visibility' => (int) $service['visibility'],
                 'image' => 'simg-'.$id.'.png',
                 'video_url' => $service['video_url'],
                 'service_information' => $service['service_information'],
             ],
-            ['id = ?' => intval($id)]
+            ['id = ?' => (int) $id]
         );
     }
 
@@ -1723,10 +2108,36 @@ class BuyCoursesPlugin extends Plugin
         $services = [];
 
         if ($id) {
+            $price = $return['price'];
+            $taxPerc = null;
+            $priceWithoutTax = $priceWithTax = $return['price'];
+            $precision = 2;
+
+            $taxEnable = $this->get('tax_enable') === 'true';
+            $globalParameters = $this->getGlobalParameters();
+            $taxAppliesTo = $globalParameters['tax_applies_to'];
+            if ($taxEnable &&
+                ($taxAppliesTo == self::TAX_APPLIES_TO_ALL || $taxAppliesTo == self::TAX_APPLIES_TO_ONLY_SERVICES)
+            ) {
+                $globalTaxPerc = $globalParameters['global_tax_perc'];
+                $precision = 2;
+                $taxPerc = is_null($return['tax_perc']) ? $globalTaxPerc : $return['tax_perc'];
+                $taxAmount = round($priceWithoutTax * $taxPerc / 100, $precision);
+                $priceWithTax = $priceWithoutTax + $taxAmount;
+            }
+
             $services['id'] = $return['id'];
             $services['name'] = $return['name'];
             $services['description'] = $return['description'];
-            $services['price'] = $return['price'];
+            $services['price'] = $price;
+            $services['tax_perc'] = $return['tax_perc'];
+            $services['price_with_tax'] = number_format($priceWithTax, $precision);
+            $services['price_without_tax'] = number_format($priceWithoutTax, $precision);
+            $services['tax_amount'] = number_format($taxAmount, $precision);
+            $services['tax_perc_show'] = $taxPerc;
+            $services['tax_name'] = $globalParameters['tax_name'];
+            $services['tax_enable'] = $taxEnable &&
+            ($taxAppliesTo == self::TAX_APPLIES_TO_ALL || $taxAppliesTo == self::TAX_APPLIES_TO_ONLY_SERVICES);
             $services['currency'] = $return['currency'];
             $services['duration_days'] = $return['duration_days'];
             $services['applies_to'] = $return['applies_to'];
@@ -1747,6 +2158,7 @@ class BuyCoursesPlugin extends Plugin
             $services[$index]['name'] = $service['name'];
             $services[$index]['description'] = $service['description'];
             $services[$index]['price'] = $service['price'];
+            $services[$index]['tax_perc'] = $service['tax_perc'];
             $services[$index]['currency'] = $service['currency'];
             $services[$index]['duration_days'] = $service['duration_days'];
             $services[$index]['applies_to'] = $service['applies_to'];
@@ -1818,7 +2230,7 @@ class BuyCoursesPlugin extends Plugin
             $conditions = ['WHERE' => ['ss.buyer_id = ?' => $buyerId], 'ORDER' => 'id ASC'];
         }
 
-        if (is_numeric($status)) {
+        if (is_numeric($status) && empty($id)) {
             $conditions = ['WHERE' => ['ss.status = ?' => $status], 'ORDER' => 'id ASC'];
         }
 
@@ -1862,7 +2274,6 @@ class BuyCoursesPlugin extends Plugin
             $conditions,
             $showData
         );
-
         $servicesSale = [];
 
         if ($id) {
@@ -1885,6 +2296,9 @@ class BuyCoursesPlugin extends Plugin
             $servicesSale['currency_id'] = $return['currency_id'];
             $servicesSale['currency'] = $return['currency'];
             $servicesSale['price'] = $return['price'];
+            $servicesSale['price_without_tax'] = $return['price_without_tax'];
+            $servicesSale['tax_perc'] = $return['tax_perc'];
+            $servicesSale['tax_amount'] = $return['tax_amount'];
             $servicesSale['node_type'] = $return['node_type'];
             $servicesSale['node_id'] = $return['node_id'];
             $servicesSale['buyer']['id'] = $buyer['user_id'];
@@ -1895,6 +2309,7 @@ class BuyCoursesPlugin extends Plugin
             $servicesSale['date_end'] = $return['date_end'];
             $servicesSale['status'] = $return['status'];
             $servicesSale['payment_type'] = $return['payment_type'];
+            $servicesSale['invoice'] = $return['invoice'];
 
             return $servicesSale;
         }
@@ -1931,6 +2346,7 @@ class BuyCoursesPlugin extends Plugin
             $servicesSale[$index]['date_end'] = $service['date_end'];
             $servicesSale[$index]['status'] = $service['status'];
             $servicesSale[$index]['payment_type'] = $service['payment_type'];
+            $servicesSale[$index]['invoice'] = $service['invoice'];
         }
 
         return $servicesSale;
@@ -1972,6 +2388,10 @@ class BuyCoursesPlugin extends Plugin
             self::SERVICE_STATUS_COMPLETED
         );
 
+        if ($this->get('invoicing_enable') === 'true') {
+            $this->setInvoice($serviceSaleId, 1);
+        }
+
         return true;
     }
 
@@ -2022,10 +2442,27 @@ class BuyCoursesPlugin extends Plugin
         $services = [];
 
         foreach ($return as $index => $service) {
+            $price = $service['price'];
+            $taxPerc = null;
+            $priceWithoutTax = $service['price'];
+
+            $taxEnable = $this->get('tax_enable') === 'true';
+            $globalParameters = $this->getGlobalParameters();
+            $taxAppliesTo = $globalParameters['tax_applies_to'];
+            if ($taxEnable &&
+                ($taxAppliesTo == self::TAX_APPLIES_TO_ALL || $taxAppliesTo == self::TAX_APPLIES_TO_ONLY_SERVICES)
+            ) {
+                $globalTaxPerc = $globalParameters['global_tax_perc'];
+                $precision = 2;
+                $taxPerc = is_null($service['tax_perc']) ? $globalTaxPerc : $service['tax_perc'];
+                $taxAmount = round($priceWithoutTax * $taxPerc / 100, $precision);
+                $price = $priceWithoutTax + $taxAmount;
+            }
+
             $services[$index]['id'] = $service['id'];
             $services[$index]['name'] = $service['name'];
             $services[$index]['description'] = $service['description'];
-            $services[$index]['price'] = $service['price'];
+            $services[$index]['price'] = number_format($price, $precision);
             $services[$index]['currency'] = $service['currency'];
             $services[$index]['duration_days'] = $service['duration_days'];
             $services[$index]['applies_to'] = $service['applies_to'];
@@ -2070,6 +2507,23 @@ class BuyCoursesPlugin extends Plugin
         }
 
         $currency = $this->getSelectedCurrency();
+        $price = $service['price'];
+        $priceWithoutTax = null;
+        $taxPerc = null;
+
+        $taxEnable = $this->get('tax_enable') === 'true';
+        $globalParameters = $this->getGlobalParameters();
+        $taxAppliesTo = $globalParameters['tax_applies_to'];
+        if ($taxEnable &&
+            ($taxAppliesTo == self::TAX_APPLIES_TO_ALL || $taxAppliesTo == TAX_APPLIES_TO_ONLY_SERVICES)
+        ) {
+            $priceWithoutTax = $service['price'];
+            $globalTaxPerc = $globalParameters['global_tax_perc'];
+            $precision = 2;
+            $taxPerc = is_null($service['tax_perc']) ? $globalTaxPerc : $service['tax_perc'];
+            $taxAmount = round($priceWithoutTax * $taxPerc / 100, $precision);
+            $price = $priceWithoutTax + $taxAmount;
+        }
 
         $values = [
             'service_id' => $serviceId,
@@ -2079,7 +2533,10 @@ class BuyCoursesPlugin extends Plugin
                 $infoSelect
             ),
             'currency_id' => $currency['id'],
-            'price' => $service['price'],
+            'price' => $price,
+            'price_without_tax' => $priceWithoutTax,
+            'tax_perc' => $taxPerc,
+            'tax_amount' => $taxAmount,
             'node_type' => $service['applies_to'],
             'node_id' => intval($infoSelect),
             'buyer_id' => $userId,
@@ -2145,11 +2602,29 @@ class BuyCoursesPlugin extends Plugin
      */
     public function saveGlobalParameters($params)
     {
+        $sqlParams = [
+            'terms_and_conditions' => $params['terms_and_conditions'],
+            'sale_email' => $params['sale_email'],
+        ];
+
+        if ($this->get('tax_enable') === 'true') {
+            $sqlParams['global_tax_perc'] = $params['global_tax_perc'];
+            $sqlParams['tax_applies_to'] = $params['tax_applies_to'];
+            $sqlParams['tax_name'] = $params['tax_name'];
+        }
+
+        if ($this->get('invoicing_enable') === 'true') {
+            $sqlParams['seller_name'] = $params['seller_name'];
+            $sqlParams['seller_id'] = $params['seller_id'];
+            $sqlParams['seller_address'] = $params['seller_address'];
+            $sqlParams['seller_email'] = $params['seller_email'];
+            $sqlParams['next_number_invoice'] = $params['next_number_invoice'];
+            $sqlParams['invoice_series'] = $params['invoice_series'];
+        }
+
         return Database::update(
             Database::get_main_table(self::TABLE_GLOBAL_CONFIG),
-            [
-                'terms_and_conditions' => $params['terms_and_conditions'],
-            ],
+            $sqlParams,
             ['id = ?' => 1]
         );
     }

+ 3 - 0
plugin/buycourses/src/buycourses.ajax.php

@@ -235,6 +235,9 @@ switch ($action) {
                     $payout['id'],
                     BuyCoursesPlugin::PAYOUT_STATUS_COMPLETED
                 );
+                if ($plugin->get('invoicing_enable') === 'true') {
+                    $plugin->setInvoice($payout['id']);
+                }
             }
 
             echo Display::return_message(

+ 9 - 0
plugin/buycourses/src/configuration.php

@@ -12,6 +12,7 @@ require_once __DIR__.'/../../../main/inc/global.inc.php';
 $plugin = BuyCoursesPlugin::create();
 $includeSession = $plugin->get('include_sessions') === 'true';
 $includeServices = $plugin->get('include_services') === 'true';
+$taxEnable = $plugin->get('tax_enable') === 'true';
 
 api_protect_admin_script(true);
 
@@ -39,6 +40,7 @@ $tpl->assign('product_type_session', BuyCoursesPlugin::PRODUCT_TYPE_SESSION);
 $tpl->assign('courses', $courses);
 $tpl->assign('sessions_are_included', $includeSession);
 $tpl->assign('services_are_included', $includeServices);
+$tpl->assign('tax_enable', $taxEnable);
 
 if ($includeSession) {
     $sessions = $plugin->getSessionsForConfiguration();
@@ -50,6 +52,13 @@ if ($includeServices) {
     $tpl->assign('services', $services);
 }
 
+if ($taxEnable) {
+    $globalParameters = $plugin->getGlobalParameters();
+    $tpl->assign('global_tax_perc', $globalParameters['global_tax_perc']);
+    $tpl->assign('tax_applies_to', $globalParameters['tax_applies_to']);
+    $tpl->assign('tax_name', $globalParameters['tax_name']);
+}
+
 $content = $tpl->fetch('buycourses/view/configuration.tpl');
 
 $tpl->assign('header', $templateName);

+ 16 - 1
plugin/buycourses/src/configure_course.php

@@ -88,6 +88,7 @@ if ($editingCourse) {
         'name' => $courseItem['course_title'],
         'visible' => $courseItem['visible'],
         'price' => $courseItem['price'],
+        'tax_perc' => $courseItem['tax_perc'],
         'beneficiaries' => $defaultBeneficiaries,
         ($commissionsEnable == "true") ? 'commissions' : '' => ($commissionsEnable == "true") ? $commissions : '',
     ];
@@ -154,6 +155,7 @@ if ($editingCourse) {
         'name' => $sessionItem['session_name'],
         'visible' => $sessionItem['visible'],
         'price' => $sessionItem['price'],
+        'tax_perc' => $sessionItem['tax_perc'],
         'beneficiaries' => $defaultBeneficiaries,
         ($commissionsEnable == "true") ? 'commissions' : '' => ($commissionsEnable == "true") ? $commissions : '',
     ];
@@ -190,6 +192,8 @@ if ($commissionsEnable === 'true') {
     ";
 }
 
+$globalSettingsParams = $plugin->getGlobalParameters();
+
 $form = new FormValidator('beneficiaries');
 $form->addText('product_type', $plugin->get_lang('ProductType'), false);
 $form->addText('name', get_lang('Name'), false);
@@ -204,6 +208,12 @@ $form->addElement(
     [$plugin->get_lang('Price'), null, $currencyIso],
     ['step' => 0.01]
 );
+$form->addElement(
+    'number',
+    'tax_perc',
+    [$plugin->get_lang('TaxPerc'), $plugin->get_lang('TaxPercDescription'), '%'],
+    ['step' => 1, 'placeholder' => $globalSettingsParams['global_tax_perc'].'% '.$plugin->get_lang('ByDefault')]
+);
 $beneficiariesSelect = $form->addSelect(
     'beneficiaries',
     $plugin->get_lang('Beneficiaries'),
@@ -252,9 +262,13 @@ if ($form->validate()) {
     $productItem = $plugin->getItemByProduct($formValues['i'], $formValues['t']);
 
     if (isset($formValues['visible'])) {
+        $taxPerc = $formValues['tax_perc'] != '' ? (int) $formValues['tax_perc'] : null;
         if (!empty($productItem)) {
             $plugin->updateItem(
-                ['price' => floatval($formValues['price'])],
+                [
+                    'price' => floatval($formValues['price']),
+                    'tax_perc' => $taxPerc,
+                ],
                 $formValues['i'],
                 $formValues['t']
             );
@@ -264,6 +278,7 @@ if ($form->validate()) {
                 'product_type' => $formValues['t'],
                 'product_id' => intval($formValues['i']),
                 'price' => floatval($_POST['price']),
+                'tax_perc' => $taxPerc,
             ]);
             $productItem['id'] = $itemId;
         }

+ 122 - 0
plugin/buycourses/src/invoice.php

@@ -0,0 +1,122 @@
+<?php
+/* For license terms, see /license.txt */
+use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
+
+/**
+ * Print invoice of the Buy Courses plugin.
+ *
+ * @package chamilo.plugin.buycourses
+ */
+$cidReset = true;
+
+require_once '../config.php';
+
+api_protect_admin_script();
+
+$plugin = BuyCoursesPlugin::create();
+
+$invoicingEnable = $plugin->get('invoicing_enable') === 'true';
+if (!$invoicingEnable) {
+    api_not_allowed(true, $plugin->get_lang('NoInvoiceEnable'));
+}
+
+$saleId = isset($_GET['invoice']) ? (int) $_GET['invoice'] : 0;
+$isService = isset($_GET['is_service']) ? (int) $_GET['is_service'] : 0;
+
+$globalParameters = $plugin->getGlobalParameters();
+$infoSale = $plugin->getDataSaleInvoice($saleId, $isService);
+$buyer = api_get_user_info($infoSale['user_id']);
+$extraUserInfoData = UserManager::get_extra_user_data($infoSale['user_id']);
+$infoInvoice = $plugin->getDataInvoice($saleId, $isService);
+
+$htmlText = '<html>';
+$htmlText .= '<link rel="stylesheet" type="text/css" href="plugin.css">';
+$htmlText .= '<link rel="stylesheet" type="text/css" href="'.api_get_path(WEB_CSS_PATH).'base.css">';
+$htmlText .= '<body>';
+
+$organization = ChamiloApi::getPlatformLogo('', [], true);
+// Use custom logo image.
+$pdfLogo = api_get_setting('pdf_logo_header');
+if ($pdfLogo === 'true') {
+    $visualTheme = api_get_visual_theme();
+    $img = api_get_path(SYS_CSS_PATH).'themes/'.$visualTheme.'/images/pdf_logo_header.png';
+    if (file_exists($img)) {
+        $organization = "<img src='$img'>";
+    }
+}
+$htmlText .= $organization;
+
+// Seller and customer info
+$htmlText .= '<table width="100%">';
+$htmlText .= '<tr>';
+$htmlText .= '<td>';
+    $htmlText .= '<b>'.$globalParameters['seller_name'].'</b><br/>';
+    $htmlText .= $globalParameters['seller_id'].'<br/>';
+    $htmlText .= $globalParameters['seller_address'].'<br/>';
+    $htmlText .= $globalParameters['seller_email'].'<br/>';
+$htmlText .= '</td>';
+$htmlText .= '<td style="text-align:right;">';
+    $htmlText .= '<b>'.$buyer['complete_name'].'</b><br/>';
+    $htmlText .= ($extraUserInfoData['buycourses_company'] ? $extraUserInfoData['buycourses_company'].'<br>' : '');
+    $htmlText .= ($extraUserInfoData['buycourses_vat'] ? $extraUserInfoData['buycourses_vat'].'<br>' : '');
+    $htmlText .= ($extraUserInfoData['buycourses_address'] ? $extraUserInfoData['buycourses_address'].'<br/>' : '');
+    $htmlText .= ($buyer['phone'] ? $buyer['phone'].'<br/>' : '');
+    $htmlText .= ($buyer['email'] ? $buyer['email'].'<br>' : '');
+$htmlText .= '</td>';
+$htmlText .= '</tr>';
+$htmlText .= '</table>';
+
+$htmlText .= '<br><br>';
+$htmlText .= '<p>';
+$htmlText .= $plugin->get_lang('InvoiceDate').': <span style="font-weight:bold;">'.api_format_date($infoInvoice['date_invoice'], DATE_TIME_FORMAT_LONG_24H).'</span><br>';
+$htmlText .= $plugin->get_lang('InvoiceNumber').': <span style="font-weight:bold;">'.$infoInvoice['serie'].$infoInvoice['year'].'/'.$infoInvoice['num_invoice'].'</span><br>';
+$htmlText .= '</p><br><br>';
+
+$header = [
+        $plugin->get_lang('OrderReference'),
+        $plugin->get_lang('ProductType'),
+        $plugin->get_lang('Price'),
+        $globalParameters['tax_name'],
+        $plugin->get_lang('TotalPayout'),
+];
+
+$data = [];
+$row = [
+    $infoSale['reference'],
+    $infoSale['product_name'],
+    $plugin->getCurrency($infoSale['currency_id'])['iso_code'].
+        ' '.$infoSale['price_without_tax'],
+    $plugin->getCurrency($infoSale['currency_id'])['iso_code'].
+        ' '.$infoSale['tax_amount'].
+        ' ('.(int) $infoSale['tax_perc'].'%)',
+    $plugin->getCurrency($infoSale['currency_id'])['iso_code'].
+        ' '.$infoSale['price'],
+];
+$data[] = $row;
+
+$row = [
+    '',
+    '',
+    '',
+    $plugin->get_lang('TotalPayout'),
+    $plugin->getCurrency($infoSale['currency_id'])['iso_code'].' '.$infoSale['price'],
+];
+$data[] = $row;
+$attr = [];
+$attr['class'] = "table data_table";
+$attr['width'] = "100%";
+$htmlText .= Display::table($header, $data, $attr);
+$htmlText .= '</body></html>';
+
+$fileName = $infoInvoice['serie'].$infoInvoice['year'].'-'.$infoInvoice['num_invoice'];
+$fileName = api_replace_dangerous_char($fileName);
+$params = [
+    'filename' => $fileName,
+    'pdf_title' => $plugin->get_lang('Invoice'),
+    'pdf_description' => '',
+    'format' => 'A4',
+    'orientation' => 'P',
+];
+$pdf = new PDF($params['format'], $params['orientation'], $params);
+$pdf->content_to_pdf($htmlText, '', $fileName, null, 'D', false, null, false, false, false);
+exit;

+ 96 - 12
plugin/buycourses/src/paymentsetup.php

@@ -32,14 +32,14 @@ if (isset($_GET['action'], $_GET['id'])) {
     }
 }
 
-$currencyForm = new FormValidator('currency');
+$globalSettingForm = new FormValidator('currency');
 
-if ($currencyForm->validate()) {
-    $currencyFormValues = $currencyForm->getSubmitValues();
+if ($globalSettingForm->validate()) {
+    $globalSettingFormValues = $globalSettingForm->getSubmitValues();
 
-    $plugin->selectCurrency($currencyFormValues['currency']);
-    unset($currencyFormValues['currency']);
-    $plugin->saveGlobalParameters($currencyFormValues);
+    $plugin->selectCurrency($globalSettingFormValues['currency']);
+    unset($globalSettingFormValues['currency']);
+    $plugin->saveGlobalParameters($globalSettingFormValues);
 
     Display::addFlash(
         Display::return_message(get_lang('Saved'), 'success')
@@ -51,7 +51,7 @@ if ($currencyForm->validate()) {
 
 $currencies = $plugin->getCurrencies();
 
-$currencySelect = $currencyForm->addSelect(
+$currencySelect = $globalSettingForm->addSelect(
     'currency',
     [
         $plugin->get_lang('CurrencyType'),
@@ -77,14 +77,98 @@ foreach ($currencies as $currency) {
     }
 }
 
-$currencyForm->addTextarea(
+$globalSettingForm->addTextarea(
     'terms_and_conditions',
     [get_lang('TermsAndConditions'),
-     $plugin->get_lang('WriteHereTheTermsAndConditionsOfYourECommerce'), ],
+            $plugin->get_lang('WriteHereTheTermsAndConditionsOfYourECommerce'), ],
     []
 );
-$currencyForm->addButtonSave(get_lang('Save'));
-$currencyForm->setDefaults($plugin->getGlobalParameters());
+
+$globalSettingForm->addElement(
+    'text',
+    'sale_email',
+    $plugin->get_lang('SaleEmail')
+);
+
+$taxEnable = $plugin->get('tax_enable') === 'true';
+$invoicingEnable = $plugin->get('invoicing_enable') === 'true';
+
+if ($taxEnable) {
+    $globalSettingForm->addHtml('<hr/>');
+
+    $globalSettingForm->addElement(
+        'number',
+        'global_tax_perc',
+        [$plugin->get_lang('GlobalTaxPerc'), $plugin->get_lang('GlobalTaxPercDescription'), '%'],
+        ['step' => 1]
+    );
+
+    $taxAppliesTo = $plugin->getTaxAppliesTo();
+
+    $taxTypeSelect = $globalSettingForm->addSelect(
+        'tax_applies_to',
+        $plugin->get_lang('TaxAppliesTo'),
+        [get_lang('Select')]
+    );
+
+    foreach ($taxAppliesTo as $key => $value) {
+        $optionText = $value;
+        $optionyValue = $key;
+
+        $taxTypeSelect->addOption($optionText, $optionyValue);
+    }
+
+    $globalSettingForm->addElement(
+        'text',
+        'tax_name',
+        $plugin->get_lang('TaxNameCustom'),
+        ['placeholder' => $plugin->get_lang('TaxNameExamples')]
+    );
+}
+
+if ($invoicingEnable) {
+    $globalSettingForm->addHtml('<hr/>');
+
+    $globalSettingForm->addElement(
+        'text',
+        'seller_name',
+        $plugin->get_lang('SellerName')
+    );
+
+    $globalSettingForm->addElement(
+        'text',
+        'seller_id',
+        $plugin->get_lang('SellerId')
+    );
+
+    $globalSettingForm->addElement(
+        'text',
+        'seller_address',
+        $plugin->get_lang('SellerAddress')
+    );
+
+    $globalSettingForm->addElement(
+        'text',
+        'seller_email',
+        $plugin->get_lang('SellerEmail')
+    );
+
+    $globalSettingForm->addElement(
+        'number',
+        'next_number_invoice',
+        [$plugin->get_lang('NextNumberInvoice'), $plugin->get_lang('NextNumberInvoiceDescription')],
+        ['step' => 1]
+    );
+
+    $globalSettingForm->addElement(
+        'text',
+        'invoice_series',
+        [$plugin->get_lang('InvoiceSeries'), $plugin->get_lang('InvoiceSeriesDescription')]
+    );
+}
+
+$globalSettingForm->addButtonSave(get_lang('Save'));
+$globalSettingForm->setDefaults($plugin->getGlobalParameters());
 
 $termsAndConditionsForm = new FormValidator('termsconditions');
 
@@ -231,7 +315,7 @@ $interbreadcrumb[] = [
 $templateName = $plugin->get_lang('PaymentsConfiguration');
 $tpl = new Template($templateName);
 $tpl->assign('header', $templateName);
-$tpl->assign('global_config_form', $currencyForm->returnForm());
+$tpl->assign('global_config_form', $globalSettingForm->returnForm());
 $tpl->assign('paypal_form', $paypalForm->returnForm());
 $tpl->assign('commission_form', $commissionForm->returnForm());
 $tpl->assign('transfer_form', $transferForm->returnForm());

+ 48 - 4
plugin/buycourses/src/process_confirm.php

@@ -25,7 +25,7 @@ if (empty($sale)) {
 }
 
 $currency = $plugin->getCurrency($sale['currency_id']);
-$terms = $plugin->getGlobalParameters();
+$globalParameters = $plugin->getGlobalParameters();
 
 switch ($sale['payment_type']) {
     case BuyCoursesPlugin::PAYMENT_TYPE_PAYPAL:
@@ -64,6 +64,28 @@ switch ($sale['payment_type']) {
             exit;
         }
 
+        if (!empty($globalParameters['sale_email'])) {
+            $messageConfirmTemplate = new Template();
+            $messageConfirmTemplate->assign('user', $userInfo);
+            $messageConfirmTemplate->assign(
+                'sale',
+                [
+                    'date' => $sale['date'],
+                    'product' => $sale['product_name'],
+                    'currency' => $currency['iso_code'],
+                    'price' => $sale['price'],
+                    'reference' => $sale['reference'],
+                ]
+            );
+
+            api_mail_html(
+                '',
+                $globalParameters['sale_email'],
+                $plugin->get_lang('bc_subject'),
+                $messageConfirmTemplate->fetch('buycourses/view/message_confirm.tpl')
+            );
+        }
+
         RedirectToPayPal($expressCheckout["TOKEN"]);
         break;
     case BuyCoursesPlugin::PAYMENT_TYPE_TRANSFER:
@@ -109,7 +131,7 @@ switch ($sale['payment_type']) {
             $messageTemplate->assign(
                 'sale',
                 [
-                    'date' => api_format_date($sale['date'], DATE_FORMAT_LONG_NO_DAY),
+                    'date' => $sale['date'],
                     'product' => $sale['product_name'],
                     'currency' => $currency['iso_code'],
                     'price' => $sale['price'],
@@ -125,6 +147,28 @@ switch ($sale['payment_type']) {
                 $messageTemplate->fetch('buycourses/view/message_transfer.tpl')
             );
 
+            if (!empty($globalParameters['sale_email'])) {
+                $messageConfirmTemplate = new Template();
+                $messageConfirmTemplate->assign('user', $userInfo);
+                $messageConfirmTemplate->assign(
+                    'sale',
+                    [
+                        'date' => $sale['date'],
+                        'product' => $sale['product_name'],
+                        'currency' => $currency['iso_code'],
+                        'price' => $sale['price'],
+                        'reference' => $sale['reference'],
+                    ]
+                );
+
+                api_mail_html(
+                    '',
+                    $globalParameters['sale_email'],
+                    $plugin->get_lang('bc_subject'),
+                    $messageConfirmTemplate->fetch('buycourses/view/message_confirm.tpl')
+                );
+            }
+
             Display::addFlash(
                 Display::return_message(
                     sprintf(
@@ -162,7 +206,7 @@ switch ($sale['payment_type']) {
 
         $template->assign('buying_course', $buyingCourse);
         $template->assign('buying_session', $buyingSession);
-        $template->assign('terms', $terms['terms_and_conditions']);
+        $template->assign('terms', $globalParameters['terms_and_conditions']);
         $template->assign('title', $sale['product_name']);
         $template->assign('price', $sale['price']);
         $template->assign('currency', $sale['currency_id']);
@@ -253,7 +297,7 @@ switch ($sale['payment_type']) {
 
         $template->assign('buying_course', $buyingCourse);
         $template->assign('buying_session', $buyingSession);
-        $template->assign('terms', $terms['terms_and_conditions']);
+        $template->assign('terms', $globalParameters['terms_and_conditions']);
         $template->assign('title', $sale['product_name']);
         $template->assign('price', floatval($sale['price']));
         $template->assign('currency', $plugin->getSelectedCurrency());

+ 4 - 0
plugin/buycourses/src/sales_report.php

@@ -18,6 +18,7 @@ $plugin = BuyCoursesPlugin::create();
 $paypalEnable = $plugin->get('paypal_enable');
 $commissionsEnable = $plugin->get('commissions_enable');
 $includeServices = $plugin->get('include_services');
+$invoicingEnable = $plugin->get('invoicing_enable') === 'true';
 
 if (isset($_GET['order'])) {
     $sale = $plugin->getSale($_GET['order']);
@@ -127,6 +128,8 @@ foreach ($sales as $sale) {
         'product_type' => $productTypes[$sale['product_type']],
         'complete_user_name' => api_get_person_name($sale['firstname'], $sale['lastname']),
         'payment_type' => $paymentTypes[$sale['payment_type']],
+        'invoice' => $sale['invoice'],
+        'num_invoice' => $plugin->getNumInvoice($sale['id'], 0),
     ];
 }
 
@@ -176,6 +179,7 @@ $template->assign('sale_list', $saleList);
 $template->assign('sale_status_canceled', BuyCoursesPlugin::SALE_STATUS_CANCELED);
 $template->assign('sale_status_pending', BuyCoursesPlugin::SALE_STATUS_PENDING);
 $template->assign('sale_status_completed', BuyCoursesPlugin::SALE_STATUS_COMPLETED);
+$template->assign('invoicing_enable', $invoicingEnable);
 
 $content = $template->fetch('buycourses/view/sales_report.tpl');
 

+ 52 - 8
plugin/buycourses/src/service_process_confirm.php

@@ -25,7 +25,7 @@ if (empty($serviceSale)) {
 }
 
 $currency = $plugin->getCurrency($serviceSale['currency_id']);
-$terms = $plugin->getGlobalParameters();
+$globalParameters = $plugin->getGlobalParameters();
 
 switch ($serviceSale['payment_type']) {
     case BuyCoursesPlugin::PAYMENT_TYPE_PAYPAL:
@@ -78,6 +78,28 @@ switch ($serviceSale['payment_type']) {
             exit;
         }
 
+        if (!empty($globalParameters['sale_email'])) {
+            $messageConfirmTemplate = new Template();
+            $messageConfirmTemplate->assign('user', $userInfo);
+            $messageConfirmTemplate->assign(
+                'sale',
+                [
+                    'date' => $serviceSale['buy_date'],
+                    'product' => $serviceSale['service']['name'],
+                    'currency' => $currency['iso_code'],
+                    'price' => $serviceSale['price'],
+                    'reference' => $serviceSale['reference'],
+                ]
+            );
+
+            api_mail_html(
+                '',
+                $globalParameters['sale_email'],
+                $plugin->get_lang('bc_subject'),
+                $messageConfirmTemplate->fetch('buycourses/view/message_confirm.tpl')
+            );
+        }
+
         RedirectToPayPal($expressCheckout['TOKEN']);
         break;
     case BuyCoursesPlugin::PAYMENT_TYPE_TRANSFER:
@@ -112,10 +134,10 @@ switch ($serviceSale['payment_type']) {
                 [
                     'name' => $serviceSale['service']['name'],
                     'buyer' => $serviceSale['buyer']['name'],
-                    'buy_date' => api_format_date($serviceSale['buy_date'], DATE_TIME_FORMAT_LONG_24H),
-                    'start_date' => api_format_date($serviceSale['start_date'], DATE_TIME_FORMAT_LONG_24H),
-                    'end_date' => api_format_date($serviceSale['end_date'], DATE_TIME_FORMAT_LONG_24H),
-                    'currency' => $currency['currency'],
+                    'buy_date' => $serviceSale['buy_date'],
+                    'start_date' => $serviceSale['start_date'],
+                    'end_date' => $serviceSale['end_date'],
+                    'currency' => $currency['iso_code'],
                     'price' => $serviceSale['price'],
                     'reference' => $serviceSale['reference'],
                 ]
@@ -126,9 +148,31 @@ switch ($serviceSale['payment_type']) {
                 $buyer['complete_name'],
                 $buyer['email'],
                 $plugin->get_lang('bc_subject'),
-                $messageTemplate->fetch('buycourses/view/message_transfer.tpl')
+                $messageTemplate->fetch('buycourses/view/service_message_transfer.tpl')
             );
 
+            if (!empty($globalParameters['sale_email'])) {
+                $messageConfirmTemplate = new Template();
+                $messageConfirmTemplate->assign('user', $userInfo);
+                $messageConfirmTemplate->assign(
+                    'sale',
+                    [
+                        'date' => $serviceSale['buy_date'],
+                        'product' => $serviceSale['service']['name'],
+                        'currency' => $currency['iso_code'],
+                        'price' => $serviceSale['price'],
+                        'reference' => $serviceSale['reference'],
+                    ]
+                );
+
+                api_mail_html(
+                    '',
+                    $globalParameters['sale_email'],
+                    $plugin->get_lang('bc_subject'),
+                    $messageConfirmTemplate->fetch('buycourses/view/message_confirm.tpl')
+                );
+            }
+
             Display::addFlash(
                 Display::return_message(
                     sprintf(
@@ -166,7 +210,7 @@ switch ($serviceSale['payment_type']) {
 
         $template = new Template();
 
-        $template->assign('terms', $terms['terms_and_conditions']);
+        $template->assign('terms', $globalParameters['terms_and_conditions']);
         $template->assign('title', $serviceSale['service']['name']);
         $template->assign('price', $serviceSale['price']);
         $template->assign('currency', $serviceSale['currency_id']);
@@ -235,7 +279,7 @@ switch ($serviceSale['payment_type']) {
         );
 
         $template = new Template();
-        $template->assign('terms', $terms['terms_and_conditions']);
+        $template->assign('terms', $globalParameters['terms_and_conditions']);
         $template->assign('title', $serviceSale['service']['name']);
         $template->assign('price', floatval($serviceSale['price']));
         $template->assign('currency', $plugin->getSelectedCurrency());

+ 17 - 1
plugin/buycourses/src/service_sales_report.php

@@ -17,17 +17,30 @@ $plugin = BuyCoursesPlugin::create();
 $paypalEnable = $plugin->get('paypal_enable');
 $commissionsEnable = $plugin->get('commissions_enable');
 $includeServices = $plugin->get('include_services');
+$invoicingEnable = $plugin->get('invoicing_enable') === 'true';
 
 $saleStatuses = $plugin->getServiceSaleStatuses();
 $paymentTypes = $plugin->getPaymentTypes();
 
+$selectedStatus = isset($_GET['status']) ? $_GET['status'] : BuyCoursesPlugin::SALE_STATUS_PENDING;
+$searchTerm = '';
+
 $form = new FormValidator('search', 'get');
 
+if ($form->validate()) {
+    $selectedStatus = $form->getSubmitValue('status');
+    $searchTerm = $form->getSubmitValue('user');
+
+    if ($selectedStatus === false) {
+        $selectedStatus = BuyCoursesPlugin::SALE_STATUS_PENDING;
+    }
+}
+
 $form->addSelect('status', $plugin->get_lang('OrderStatus'), $saleStatuses, ['cols-size' => [0, 0, 0]]);
 $form->addText('user', get_lang('User'), false, ['cols-size' => [0, 0, 0]]);
 $form->addButtonSearch(get_lang('Search'), 'search');
 
-$servicesSales = $plugin->getServiceSale();
+$servicesSales = $plugin->getServiceSale(null, null, $selectedStatus);
 $serviceSaleList = [];
 
 foreach ($servicesSales as $sale) {
@@ -41,6 +54,8 @@ foreach ($servicesSales as $sale) {
         'service_type' => $sale['service']['applies_to'],
         'service_name' => $sale['service']['name'],
         'complete_user_name' => $sale['buyer']['name'],
+        'invoice' => $sale['invoice'],
+        'num_invoice' => $plugin->getNumInvoice($sale['id'], 1),
     ];
 }
 
@@ -87,6 +102,7 @@ $template->assign('sale_list', $serviceSaleList);
 $template->assign('sale_status_cancelled', BuyCoursesPlugin::SERVICE_STATUS_CANCELLED);
 $template->assign('sale_status_pending', BuyCoursesPlugin::SERVICE_STATUS_PENDING);
 $template->assign('sale_status_completed', BuyCoursesPlugin::SERVICE_STATUS_COMPLETED);
+$template->assign('invoicing_enable', $invoicingEnable);
 $content = $template->fetch('buycourses/view/service_sales_report.tpl');
 $template->assign('content', $content);
 $template->display_one_col_template();

+ 9 - 0
plugin/buycourses/src/services_add.php

@@ -32,8 +32,11 @@ $interbreadcrumb[] = [
     'name' => $plugin->get_lang('Configuration'),
 ];
 
+$globalSettingsParams = $plugin->getGlobalParameters();
+
 $formDefaultValues = [
     'price' => 0,
+    'tax_perc' => $globalSettingsParams['global_tax_perc'],
     'duration_days' => 0,
     'applies_to' => 0,
     'visibility' => true,
@@ -48,6 +51,12 @@ $form->addElement(
     [$plugin->get_lang('Price'), null, $currency['iso_code']],
     ['step' => 0.01]
 );
+$form->addElement(
+    'number',
+    'tax_perc',
+    [$plugin->get_lang('TaxPerc'), $plugin->get_lang('TaxPercDescription'), '%'],
+    ['step' => 1, 'placeholder' => $globalSettingsParams['global_tax_perc'].'% '.$plugin->get_lang('ByDefault')]
+);
 $form->addElement(
     'number',
     'duration_days',

+ 8 - 0
plugin/buycourses/src/services_edit.php

@@ -37,12 +37,14 @@ $interbreadcrumb[] = [
     'name' => $plugin->get_lang('Configuration'),
 ];
 
+$globalSettingsParams = $plugin->getGlobalParameters();
 $service = $plugin->getServices($serviceId);
 
 $formDefaultValues = [
     'name' => $service['name'],
     'description' => $service['description'],
     'price' => $service['price'],
+    'tax_perc' => $service['tax_perc'],
     'duration_days' => $service['duration_days'],
     'owner_id' => intval($service['owner_id']),
     'applies_to' => intval($service['applies_to']),
@@ -63,6 +65,12 @@ $form->addElement(
     [$plugin->get_lang('Price'), null, $currency['iso_code']],
     ['step' => 0.01]
 );
+$form->addElement(
+    'number',
+    'tax_perc',
+    [$plugin->get_lang('TaxPerc'), $plugin->get_lang('TaxPercDescription'), '%'],
+    ['step' => 1, 'placeholder' => $globalSettingsParams['global_tax_perc'].'% '.$plugin->get_lang('ByDefault')]
+);
 $form->addElement(
     'number',
     'duration_days',

+ 14 - 0
plugin/buycourses/update.php

@@ -0,0 +1,14 @@
+<?php
+/* For licensing terms, see /license.txt */
+/**
+ * Update the plugin.
+ *
+ * @package chamilo.plugin.buycourses
+ */
+require_once __DIR__.'/config.php';
+
+if (!api_is_platform_admin()) {
+    die('You must have admin permissions to install plugins');
+}
+
+BuyCoursesPlugin::create()->update();

+ 39 - 1
plugin/buycourses/view/configuration.tpl

@@ -1,13 +1,15 @@
 <link rel="stylesheet" type="text/css" href="../resources/css/style.css"/>
 
-{% if sessions_are_included %}
+{% if sessions_are_included or services_are_included %}
     <ul class="nav nav-tabs buy-courses-tabs" role="tablist">
         <li role="presentation" class="active">
             <a href="#courses" aria-controls="courses" role="tab" data-toggle="tab">{{ 'Courses'|get_lang }}</a>
         </li>
+        {% if sessions_are_included %}
         <li role="presentation">
             <a href="#sessions" aria-controls="sessions" role="tab" data-toggle="tab">{{ 'Sessions'|get_lang }}</a>
         </li>
+        {% endif %}
         {% if services_are_included %}
             <li role="presentation">
                 <a href="#services" aria-controls="services" role="tab"
@@ -27,6 +29,9 @@
                     <th class="text-center">{{ 'OfficialCode'|get_lang }}</th>
                     <th class="text-center">{{ 'VisibleInCatalog'|get_plugin_lang('BuyCoursesPlugin') }}</th>
                     <th class="text-right" width="200">{{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    {% if tax_enable and (tax_applies_to == 1 or tax_applies_to == 2) %}
+                        <th class="text-center" width="100">{{ tax_name }}</th>
+                    {% endif %}
                     <th class="text-right">{{ 'Options'|get_lang }}</th>
                 </tr>
                 </thead>
@@ -70,6 +75,15 @@
                         <td width="200" class="text-right">
                             {{ "#{item.price} #{tem.currency ?: item.currency}" }}
                         </td>
+                        {% if tax_enable and (tax_applies_to == 1 or tax_applies_to == 2) %}
+                            <td class="text-center">
+                                {% if item.tax_perc is null %}
+                                    {{ global_tax_perc }} %
+                                {% else %}
+                                    {{ item.tax_perc }} %
+                                {% endif %}
+                            </td>
+                        {% endif %}
                         <td class="text-right">
                             <a href="{{ _p.web_plugin ~ 'buycourses/src/configure_course.php?' ~ {'i': item.course_id, 't':product_type_course}|url_encode() }}"
                                class="btn btn-info btn-sm">
@@ -94,6 +108,9 @@
                         <th class="text-center">{{ 'EndDate'|get_lang }}</th>
                         <th class="text-center">{{ 'VisibleInCatalog'|get_plugin_lang('BuyCoursesPlugin') }}</th>
                         <th class="text-right">{{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                        {% if tax_enable and (tax_applies_to == 1 or tax_applies_to == 3) %}
+                            <th class="text-center" width="100">{{ tax_name }}</th>
+                        {% endif %}
                         <th class="text-right">{{ 'Options'|get_lang }}</th>
                     </tr>
                     </thead>
@@ -119,6 +136,15 @@
                             <td class="text-right" width="200">
                                 {{ "#{item.price} #{tem.currency ?: item.currency}" }}
                             </td>
+                            {% if tax_enable and (tax_applies_to == 1 or tax_applies_to == 3) %}
+                                <td class="text-center">
+                                    {% if item.tax_perc is null %}
+                                        {{ global_tax_perc }} %
+                                    {% else %}
+                                        {{ item.tax_perc }} %
+                                    {% endif %}
+                                </td>
+                            {% endif %}
                             <td class="text-right">
                                 <a href="{{ _p.web_plugin ~ 'buycourses/src/configure_course.php?' ~ {'i': item.session_id, 't': product_type_session}|url_encode() }}"
                                    class="btn btn-info btn-sm">
@@ -149,6 +175,9 @@
                         <th class="text-center">{{ 'VisibleInCatalog'|get_plugin_lang('BuyCoursesPlugin') }}</th>
                         <th class="text-center">{{ 'Owner'|get_lang }}</th>
                         <th class="text-right">{{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                        {% if tax_enable and (tax_applies_to == 1 or tax_applies_to == 4) %}
+                            <th class="text-center" width="100">{{ tax_name }}</th>
+                        {% endif %}
                         <th class="text-right">{{ 'Options'|get_lang }}</th>
                     </tr>
                     </thead>
@@ -181,6 +210,15 @@
                             <td class="text-right" width="200">
                                 {{ "#{item.price} #{tem.currency ?: item.currency}" }}
                             </td>
+                            {% if tax_enable and (tax_applies_to == 1 or tax_applies_to == 4) %}
+                                <td class="text-center">
+                                    {% if item.tax_perc is null %}
+                                        {{ global_tax_perc }} %
+                                    {% else %}
+                                        {{ item.tax_perc }} %
+                                    {% endif %}
+                                </td>
+                            {% endif %}
                             <td class="text-right">
                                 <a href="{{ _p.web_plugin ~ 'buycourses/src/services_edit.php?' ~ {'id': item.id}|url_encode() }}"
                                    class="btn btn-info btn-sm">

+ 14 - 0
plugin/buycourses/view/message_confirm.tpl

@@ -0,0 +1,14 @@
+<div>
+    <dl>
+        <dt>{{ 'OrderDate'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ sale.date|api_convert_and_format_date(5) }}</dd>
+        <dt>{{ 'OrderReference'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ sale.reference }}</dd>
+        <dt>{{ 'UserName'|get_lang }}</dt>
+        <dd>{{ user.complete_name }}</dd>
+        <dt>{{ 'Course'|get_lang }}</dt>
+        <dd>{{ sale.product }}</dd>
+        <dt>{{ 'SalePrice'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ sale.currency ~ ' ' ~ sale.price }}</dd>
+    </dl>
+</div>

+ 4 - 4
plugin/buycourses/view/message_transfer.tpl

@@ -3,14 +3,14 @@
     <p>{{ 'PurchaseDetailsIntro'|get_plugin_lang('BuyCoursesPlugin') }}</p>
     <dl>
         <dt>{{ 'OrderDate'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
-        <dd>{{ sale.date }}</dd>
+        <dd>{{ sale.date|api_convert_and_format_date(5) }}</dd>
         <dt>{{ 'OrderReference'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
-        <dd>sale.reference</dd>
+        <dd>{{ sale.reference }}</dd>
         <dt>{{ 'UserName'|get_lang }}</dt>
         <dd>{{ user.complete_name }}</dd>
-        <dt>{{ 'Course'|get_lang }}</dt>
-        <dd>{{ sale.product }}</dd>
         <dt>{{ 'ProductName'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ sale.product }}</dd>
+        <dt>{{ 'SalePrice'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
         <dd>{{ sale.currency ~ ' ' ~ sale.price }}</dd>
     </dl>
     <p>{{ 'BankAccountIntro'|get_plugin_lang('BuyCoursesPlugin')|format(sale.product) }}</p>

+ 18 - 0
plugin/buycourses/view/process.tpl

@@ -20,6 +20,15 @@
                                 <img alt="{{ course.title }}" class="img-rounded img-responsive"
                                      src="{{ course.course_img ? course.course_img : 'session_default.png'|icon() }}">
                             </a>
+                            {% if course.tax_enable %}
+                            <div class="price-details-tax">
+                                {{ 'Price'|get_plugin_lang('BuyCoursesPlugin')}} :
+                                {{ course.currency == 'BRL' ? 'R$' : course.currency }} {{ course.price_without_tax }}
+                                <br>
+                                {{ course.tax_name }} ({{ course.tax_perc }}%):
+                                {{ course.currency == 'BRL' ? 'R$' : course.currency }} {{ course.tax_amount }}
+                            </div>
+                            {% endif %}
                             <div class="price">
                                 {{ 'Total'|get_plugin_lang('BuyCoursesPlugin')}} :
                                 {{ course.currency == 'BRL' ? 'R$' : course.currency }} {{ course.price }}
@@ -56,6 +65,15 @@
                         <div class="col-md-3">
                             <img alt="{{ session.name }}" class="img-rounded img-responsive""
                             src="{{ session.image ? session.image : 'session_default.png'|icon() }}">
+                            {% if session.tax_enable %}
+                            <div class="price-details-tax">
+                                {{ 'Price'|get_plugin_lang('BuyCoursesPlugin')}} :
+                                {{ session.currency == 'BRL' ? 'R$' : session.currency }} {{ session.price_without_tax }}
+                                <br>
+                                {{ session.tax_name }} ({{ session.tax_perc }}%):
+                                {{ session.currency == 'BRL' ? 'R$' : session.currency }} {{ session.tax_amount }}
+                            </div>
+                            {% endif %}
                             <div class="price">
                                 {{ 'Total'|get_plugin_lang('BuyCoursesPlugin')}} :
                                 {{ session.currency == 'BRL' ? 'R$' : session.currency }} {{ session.price }}

+ 1 - 1
plugin/buycourses/view/process_confirm.tpl

@@ -22,7 +22,7 @@
                             </h3>
                             <ul class="list-unstyled">
                                 {% for teacher in course.teachers %}
-                                    <li><em class="fa fa-user"></em> {{ teacher }}</li>
+                                    <li><em class="fa fa-user"></em> {{ teacher.name }}</li>
                                 {% endfor %}
                             </ul>
                             <p id="n-price" class="lead text-right" style="color: white;">

+ 13 - 0
plugin/buycourses/view/sales_report.tpl

@@ -27,6 +27,9 @@
             <th class="text-center">{{ 'ProductType'|get_plugin_lang('BuyCoursesPlugin') }}</th>
             <th>{{ 'Name'|get_lang }}</th>
             <th>{{ 'UserName'|get_lang }}</th>
+            {% if invoicing_enable %}
+                <th class="text-center">{{ 'Invoice'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+            {% endif %}
             <th class="text-center">{{ 'Options'|get_lang }}</th>
         </tr>
         </thead>
@@ -49,6 +52,16 @@
                 <td class="text-center">{{ sale.product_type }}</td>
                 <td>{{ sale.product_name }}</td>
                 <td>{{ sale.complete_user_name }}</td>
+                {% if invoicing_enable %}
+                    <td class="text-center">
+                    {% if sale.invoice == 1 %}
+                        <a href="{{ _p.web_plugin ~ 'buycourses/src/invoice.php?' ~ {'invoice': sale.id, 'is_service': 0}|url_encode() }}" title="{{ 'InvoiceView'|get_plugin_lang('BuyCoursesPlugin') }}" >
+                            <img src="{{ _p.web_img }}/icons/32/default.png" alt="{{ 'InvoiceView'|get_plugin_lang('BuyCoursesPlugin') }}" />
+                            <br/>{{ sale.num_invoice }}
+                        </a>
+                    {% endif %}
+                    </td>
+                {% endif %}
                 <td class="text-center">
                     {% if sale.status == sale_status_pending %}
                         <a href="{{ _p.web_self ~ '?' ~ {'order': sale.id, 'action': 'confirm'}|url_encode() }}"

+ 9 - 1
plugin/buycourses/view/service_information.tpl

@@ -68,14 +68,22 @@
                                 {% endif %}
                             </p>
                             <p><em class="fa fa-money"></em> <b>{{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }}</b>
+                            {% if service.tax_enable %}
+                                : {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price_with_tax }}
+                            {% else %}
                                 : {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price }}</p>
+                            {% endif %}
                         </div>
                         <div class="service-buy">
                             <div class="row">
                                 <div class="col-sm-6">
                                     <div class="price">
                                         {{ 'Total'|get_lang }}
-                                        : {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price }}
+                                        {% if service.tax_enable %}
+                                            : {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price_with_tax }}
+                                        {% else %}
+                                            : {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price }}
+                                        {% endif %}
                                     </div>
                                 </div>
                                 <div class="col-sm-6">

+ 36 - 0
plugin/buycourses/view/service_message_transfer.tpl

@@ -0,0 +1,36 @@
+<div>
+    <p>{{ 'DearUser'|get_lang }}</p>
+    <p>{{ 'PurchaseDetailsIntro'|get_plugin_lang('BuyCoursesPlugin') }}</p>
+    <dl>
+        <dt>{{ 'OrderDate'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ service_sale.buy_date|api_convert_and_format_date(5) }}</dd>
+        <dt>{{ 'OrderReference'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ service_sale.reference }}</dd>
+        <dt>{{ 'UserName'|get_lang }}</dt>
+        <dd>{{ service_sale.buyer }}</dd>
+        <dt>{{ 'Service'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ service_sale.name }}</dd>
+        <dt>{{ 'SalePrice'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ service_sale.currency ~ ' ' ~ service_sale.price }}</dd>
+    </dl>
+    <p>{{ 'BankAccountIntro'|get_plugin_lang('BuyCoursesPlugin')|format(service_sale.name) }}</p>
+    <table>
+        <thead>
+        <tr>
+            <th>{{ 'Name'|get_lang }}</th>
+            <th>{{ 'BankAccount'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+            <th>{{ 'SWIFT'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+        </tr>
+        </thead>
+        <tbody>
+        {% for account in transfer_accounts %}
+            <tr>
+                <td>{{ account.name }}</td>
+                <td>{{ account.account }}</td>
+                <td>{{ account.swift }}</td>
+            </tr>
+        {% endfor %}
+        </tbody>
+    </table>
+    <p>{{ 'PurchaseDetailsEnd'|get_plugin_lang('BuyCoursesPlugin') }}</p>
+</div>

+ 20 - 1
plugin/buycourses/view/service_process.tpl

@@ -19,10 +19,25 @@
                                 <img alt="{{ service.name }}" class="img-rounded img-responsive"
                                      src="{{ service.image ? service.image : 'session_default.png'|icon() }}">
                             </a>
+                            {% if service.tax_enable %}
+                            <div class="price-details-tax">
+                                {{ 'Price'|get_plugin_lang('BuyCoursesPlugin')}} :
+                                {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price_without_tax }}
+                                <br>
+                                {{ service.tax_name }} ({{ service.tax_perc_show }}%):
+                                {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.tax_amount }}
+                            </div>
+                            <div class="price">
+                                {{ 'Total'|get_plugin_lang('BuyCoursesPlugin')}} :
+                                {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price_with_tax }}
+                            </div>
+                            {% else %}
                             <div class="price">
                                 {{ 'Total'|get_plugin_lang('BuyCoursesPlugin')}} :
                                 {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price }}
                             </div>
+                            {% endif %}
+                            
                         </div>
                         <div class="col-md-9">
                             <div class="buy-item">
@@ -54,7 +69,11 @@
                                     <li>
                                         <em class="fa-li fa fa-money" aria-hidden="true"></em>
                                         {{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }}
-                                        : {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price }}
+                                        {% if service.tax_enable %}
+                                            : {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price_with_tax }}
+                                        {% else %}
+                                            : {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price }}
+                                        {% endif %}
                                         / {{ service.duration_days == 0 ? 'NoLimit'|get_lang  : service.duration_days ~ ' ' ~ 'Days'|get_lang }}
                                     </li>
                                     <li><em class="fa-li fa fa-user" aria-hidden="true"></em> {{ service.owner_name }}</li>

+ 13 - 0
plugin/buycourses/view/service_sales_report.tpl

@@ -28,6 +28,9 @@
                     <th class="text-center">{{ 'OrderStatus'|get_plugin_lang('BuyCoursesPlugin') }}</th>
                     <th class="text-center">{{ 'OrderDate'|get_plugin_lang('BuyCoursesPlugin') }}</th>
                     <th class="text-right">{{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    {% if sale.invoice == 1 and invoicing_enable %}
+                        <th class="text-right">{{ 'Invoice'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    {% endif %}
                     <th class="text-center">{{ 'ServiceSaleInfo'|get_plugin_lang('BuyCoursesPlugin') }}</th>
                 </tr>
                 </thead>
@@ -47,6 +50,16 @@
                         </td>
                         <td class="text-center">{{ sale.date }}</td>
                         <td class="text-right">{{ sale.currency ~ ' ' ~ sale.price }}</td>
+                        {% if invoicing_enable %}
+                            <td class="text-center">
+                            {% if sale.invoice == 1 %}
+                                <a href="{{ _p.web_plugin ~ 'buycourses/src/invoice.php?' ~ {'invoice': sale.id, 'is_service': 1}|url_encode() }}" title="{{ 'InvoiceView'|get_plugin_lang('BuyCoursesPlugin') }}" >
+                                    <img src="{{ _p.web_img }}/icons/32/default.png" alt="{{ 'InvoiceView'|get_plugin_lang('BuyCoursesPlugin') }}" />
+                                    <br>{{ sale.num_invoice }}
+                                </a>
+                            {% endif %}
+                            </td>
+                        {% endif %}
                         <td class="text-center">
                             <a id="service_sale_info" tag="{{ sale.id }}" name="s_{{ sale.id }}"
                                class="btn btn-info btn-sm">{{ 'Info'|get_lang }}</a>