diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index fa4bcebc2..beffa7492 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,12 +1,14 @@ \n", '2011 - '. date('Y')); + $output .= sprintf("# Copyright %s FOSSBilling \n", '2022 - '. date('Y')); $output .= "#\n"; $output .= "#, fuzzy\n"; $output .= "msgid \"\"\n"; diff --git a/data/class-header.txt b/data/class-header.txt index 8837bacf3..9e4293c10 100755 --- a/data/class-header.txt +++ b/data/class-header.txt @@ -1,11 +1,13 @@ +# en_US translation of FOSSBilling Application +# Copyright 2022 FOSSBilling +# Copyright 2011-2021 BoxBilling # msgid "" msgstr "" diff --git a/src/bb-locale/messages.pot b/src/bb-locale/messages.pot index de4f3bdcf..0adcd06f4 100644 --- a/src/bb-locale/messages.pot +++ b/src/bb-locale/messages.pot @@ -1,5 +1,6 @@ -# en_US translation of BoxBilling Application -# Copyright 2011-2021 BoxBilling +# en_US translation of FOSSBilling Application +# Copyright 2022 FOSSBilling +# Copyright 2011-2021 BoxBilling # #, fuzzy msgid "" diff --git a/src/bb-modules/Activity/Api/Admin.php b/src/bb-modules/Activity/Api/Admin.php index 1b4db8616..ccbb3b791 100644 --- a/src/bb-modules/Activity/Api/Admin.php +++ b/src/bb-modules/Activity/Api/Admin.php @@ -1,11 +1,13 @@ di = $di; - } - - /** - * @return mixed - */ - public function getDi() - { - return $this->di; - } - - public function fetchNavigation() - { - return [ - 'group' => [ - 'index' => 400, - 'location' => 'invoice', - 'label' => 'Invoices', - 'uri' => 'invoice', - 'class' => 'invoices', - 'sprite_class' => 'dark-sprite-icon sprite-money', - ], - 'subpages' => [ - [ - 'location' => 'invoice', - 'label' => 'Overview', - 'uri' => $this->di['url']->adminLink('invoice'), - 'index' => 100, - 'class' => '', - ], - [ - 'location' => 'invoice', - 'label' => 'Advanced search', - 'uri' => $this->di['url']->adminLink('invoice', ['show_filter' => 1]), - 'index' => 200, - 'class' => '', - ], - [ - 'location' => 'invoice', - 'label' => 'Subscriptions', - 'uri' => $this->di['url']->adminLink('invoice/subscriptions'), - 'index' => 300, - 'class' => '', - ], - [ - 'location' => 'invoice', - 'label' => 'Transactions overview', - 'uri' => $this->di['url']->adminLink('invoice/transactions'), - 'index' => 400, - 'class' => '', - ], - [ - 'location' => 'invoice', - 'label' => 'Transactions search', - 'uri' => $this->di['url']->adminLink('invoice/transactions', ['show_filter' => 1]), - 'index' => 500, - 'class' => '', - ], - [ - 'location' => 'system', - 'label' => 'Tax rules', - 'uri' => $this->di['url']->adminLink('invoice/tax'), - 'index' => 180, - 'class' => '', - ], - [ - 'location' => 'system', - 'label' => 'Payment gateways', - 'uri' => $this->di['url']->adminLink('invoice/gateways'), - 'index' => 160, - 'class' => '', - ], - ], - ]; - } - - public function register(\Box_App &$app) - { - $app->get('/invoice', 'get_index', [], get_class($this)); - $app->get('/invoice/subscriptions', 'get_subscriptions', [], get_class($this)); - $app->get('/invoice/transactions', 'get_transactions', [], get_class($this)); - $app->get('/invoice/gateways', 'get_gateways', [], get_class($this)); - $app->get('/invoice/gateway/:id', 'get_gateway', ['id' => '[0-9]+'], get_class($this)); - $app->get('/invoice/manage/:id', 'get_invoice', ['id' => '[0-9]+'], get_class($this)); - $app->get('/invoice/transaction/:id', 'get_transaction', ['id' => '[0-9]+'], get_class($this)); - $app->get('/invoice/subscription/:id', 'get_subscription', ['id' => '[0-9]+'], get_class($this)); - $app->get('/invoice/tax', 'get_taxes', [], get_class($this)); - $app->get('/invoice/tax/:id', 'get_tax', [], get_class($this)); - $app->get('/invoice/pdf/:hash', 'get_pdf', ['hash' => '[a-z0-9]+'], get_class($this)); - } - - public function get_taxes(\Box_App $app) - { - $this->di['is_admin_logged']; - - return $app->render('mod_invoice_tax'); - } - - public function get_tax(\Box_App $app, $id) - { - $api = $this->di['api_admin']; - $tax = $api->invoice_tax_get(['id' => $id]); - - return $app->render('mod_invoice_taxupdate', ['tax' => $tax]); - } - - public function get_index(\Box_App $app) - { - $this->di['is_admin_logged']; - - return $app->render('mod_invoice_index'); - } - - public function get_invoice(\Box_App $app, $id) - { - $api = $this->di['api_admin']; - $invoice = $api->invoice_get(['id' => $id]); - - return $app->render('mod_invoice_invoice', ['invoice' => $invoice]); - } - - public function get_transaction(\Box_App $app, $id) - { - $api = $this->di['api_admin']; - $tx = $api->invoice_transaction_get(['id' => $id]); - - return $app->render('mod_invoice_transaction', ['transaction' => $tx]); - } - - public function get_transactions(\Box_App $app) - { - $this->di['is_admin_logged']; - - return $app->render('mod_invoice_transactions'); - } - - public function get_subscriptions(\Box_App $app) - { - $this->di['is_admin_logged']; - - return $app->render('mod_invoice_subscriptions'); - } - - public function get_subscription(\Box_App $app, $id) - { - $api = $this->di['api_admin']; - $tx = $api->invoice_subscription_get(['id' => $id]); - - return $app->render('mod_invoice_subscription', ['subscription' => $tx]); - } - - public function get_gateways(\Box_App $app) - { - $this->di['is_admin_logged']; - - return $app->render('mod_invoice_gateways'); - } - - public function get_gateway(\Box_App $app, $id) - { - $api = $this->di['api_admin']; - $gateway = $api->invoice_gateway_get(['id' => $id]); - - return $app->render('mod_invoice_gateway', ['gateway' => $gateway]); - } - - public function get_pdf(\Box_App $app, $hash) - { - $api = $this->di['api_guest']; - $data = [ - 'hash' => $hash, - ]; - $invoice = $api->invoice_pdf($data); - - return $app->render('mod_invoice_pdf', ['invoice' => $invoice]); - } -} diff --git a/src/bb-modules/Invoice/Controller/Client.php b/src/bb-modules/Invoice/Controller/Client.php index d9ef33d09..e69de29bb 100644 --- a/src/bb-modules/Invoice/Controller/Client.php +++ b/src/bb-modules/Invoice/Controller/Client.php @@ -1,114 +0,0 @@ -di = $di; - } - - /** - * @return mixed - */ - public function getDi() - { - return $this->di; - } - - public function register(\Box_App &$app) - { - $app->get('/invoice', 'get_invoices', [], get_class($this)); - $app->post('/invoice', 'get_invoices', [], get_class($this)); - $app->get('/invoice/:hash', 'get_invoice', ['hash' => '[a-z0-9]+'], get_class($this)); - $app->post('/invoice/:hash', 'get_invoice', ['hash' => '[a-z0-9]+'], get_class($this)); - $app->get('/invoice/print/:hash', 'get_invoice_print', ['hash' => '[a-z0-9]+'], get_class($this)); - $app->post('/invoice/print/:hash', 'get_invoice_print', ['hash' => '[a-z0-9]+'], get_class($this)); - $app->get('/invoice/banklink/:hash/:id', 'get_banklink', ['id' => '[0-9]+', 'hash' => '[a-z0-9]+'], get_class($this)); - $app->get('/invoice/thank-you/:hash', 'get_thankyoupage', ['hash' => '[a-z0-9]+'], get_class($this)); - $app->get('/invoice/pdf/:hash', 'get_pdf', ['hash' => '[a-z0-9]+'], get_class($this)); - } - - public function get_invoices(\Box_App $app) - { - $this->di['is_client_logged']; - - return $app->render('mod_invoice_index'); - } - - public function get_invoice(\Box_App $app, $hash) - { - $api = $this->di['api_guest']; - $data = [ - 'hash' => $hash, - ]; - $invoice = $api->invoice_get($data); - - return $app->render('mod_invoice_invoice', ['invoice' => $invoice]); - } - - public function get_invoice_print(\Box_App $app, $hash) - { - $api = $this->di['api_guest']; - $data = [ - 'hash' => $hash, - ]; - $invoice = $api->invoice_get($data); - - return $app->render('mod_invoice_print', ['invoice' => $invoice]); - } - - public function get_thankyoupage(\Box_App $app, $hash) - { - $api = $this->di['api_guest']; - $data = [ - 'hash' => $hash, - ]; - $invoice = $api->invoice_get($data); - - return $app->render('mod_invoice_thankyou', ['invoice' => $invoice]); - } - - public function get_banklink(\Box_App $app, $hash, $id) - { - $api = $this->di['api_guest']; - $data = [ - 'subscription' => $this->di['request']->getQuery('subscription'), - 'hash' => $hash, - 'gateway_id' => $id, - 'auto_redirect' => true, - ]; - - $invoice = $api->invoice_get($data); - $result = $api->invoice_payment($data); - - return $app->render('mod_invoice_banklink', ['payment' => $result, 'invoice' => $invoice]); - } - - public function get_pdf(\Box_App $app, $hash) - { - $api = $this->di['api_guest']; - $data = [ - 'hash' => $hash, - ]; - $invoice = $api->invoice_pdf($data); - - return $app->render('mod_invoice_pdf', ['invoice' => $invoice]); - } -} diff --git a/src/bb-modules/Invoice/Service.php b/src/bb-modules/Invoice/Service.php index ccd2346cf..e69de29bb 100644 --- a/src/bb-modules/Invoice/Service.php +++ b/src/bb-modules/Invoice/Service.php @@ -1,1540 +0,0 @@ -di = $di; - } - - /** - * @return \Box_Di - */ - public function getDi() - { - return $this->di; - } - - public function getSearchQuery($data) - { - $sql = 'SELECT p.* - FROM invoice p - LEFT JOIN invoice_item pi ON (p.id = pi.invoice_id) - LEFT JOIN client cl ON (cl.id = p.client_id) - WHERE 1 '; - - $params = []; - - $search = $this->di['array_get']($data, 'search', null); - $order_id = $this->di['array_get']($data, 'order_id', null); - $id = $this->di['array_get']($data, 'id', null); - $id_nr = $this->di['array_get']($data, 'nr', null); - $client_id = $this->di['array_get']($data, 'client_id', null); - $client = $this->di['array_get']($data, 'client', null); - $created_at = $this->di['array_get']($data, 'created_at', null); - $date_from = $this->di['array_get']($data, 'date_from', null); - $date_to = $this->di['array_get']($data, 'date_to', null); - $paid_at = $this->di['array_get']($data, 'paid_at', null); - $status = $this->di['array_get']($data, 'status', null); - $approved = $this->di['array_get']($data, 'approved', null); - $currency = $this->di['array_get']($data, 'currency', null); - - if ($order_id) { - $sql .= ' AND pi.type = :item_type AND pi.rel_id = :order_id'; - $params['item_type'] = \Model_InvoiceItem::TYPE_ORDER; - $params['order_id'] = $order_id; - } - - if ($id) { - $sql .= ' AND p.id = :id'; - $params['id'] = $id; - } - - if ($id_nr) { - $sql .= ' AND (p.id = :id_nr OR p.nr = :id_nr)'; - $params['id_nr'] = $id_nr; - } - - if ($approved) { - $sql .= ' AND p.approved = :approved'; - $params['approved'] = $approved; - } - - if ($status) { - $sql .= ' AND p.status = :status'; - $params['status'] = $status; - } - - if ($currency) { - $sql .= ' AND p.currency = :currency'; - $params['currency'] = $currency; - } - - if (null !== $client_id) { - $sql .= ' AND p.client_id = :client_id'; - $params['client_id'] = $client_id; - } - - if (null !== $client) { - $sql .= ' AND (cl.first_name LIKE :client_search OR cl.last_name LIKE :client_search OR cl.id = :client OR cl.email = :client)'; - $params['client_search'] = $client . '%'; - $params['client'] = $client; - } - - if ($created_at) { - $sql .= " AND DATE_FORMAT(p.created_at, '%Y-%m-%d') = :created_at"; - $params['created_at'] = date('Y-m-d', strtotime($created_at)); - } - - if ($date_from) { - $sql .= ' AND UNIX_TIMESTAMP(p.created_at) >= :date_from'; - $params['date_from'] = strtotime($date_from); - } - - if ($date_to) { - $sql .= ' AND UNIX_TIMESTAMP(p.created_at) <= :date_to'; - $params['date_to'] = strtotime($date_to); - } - - if ($paid_at) { - $sql .= " AND DATE_FORMAT(p.paid_at, '%Y-%m-%d') = :paid_at"; - $params['paid_at'] = date('Y-m-d', strtotime($paid_at)); - } - - if ($search) { - $sql .= ' AND (p.id = :int OR p.nr LIKE :search_like OR p.id LIKE :search OR pi.title LIKE :search_like)'; - $params['int'] = (int) preg_replace('/[^0-9]/', '', $search); - $params['search_like'] = '%' . $search . '%'; - $params['search'] = $search; - } - - $sql .= ' GROUP BY p.id ORDER BY p.id DESC'; - - return [$sql, $params]; - } - - public function toApiArray(\Model_Invoice $invoice, $deep = true, $identity = null) - { - $row = $this->di['db']->toArray($invoice); - - $items = $this->di['db']->find('InvoiceItem', 'invoice_id = :iid', ['iid' => $row['id']]); - - $lines = []; - $total = $tax_total = 0; - $invoiceItemService = $this->di['mod_service']('Invoice', 'InvoiceItem'); - foreach ($items as $item) { - $order_id = (\Model_InvoiceItem::TYPE_ORDER == $item->type) ? $item->rel_id : null; - - $line_total = $item->price * $item->quantity; - $total += $line_total; - $line_tax = $invoiceItemService->getTax($item) * $item->quantity; - $tax_total += $line_tax; - $line = [ - 'id' => $item->id, - 'title' => $item->title, - 'period' => $item->period, - 'quantity' => $item->quantity, - 'unit' => $item->unit, - 'price' => $item->price, - 'tax' => $line_tax, - 'taxed' => $item->taxed, - 'charged' => $item->charged, - 'total' => $line_total, - 'order_id' => $order_id, - 'type' => $item->type, - 'rel_id' => $item->rel_id, - 'task' => $item->task, - 'status' => $item->status, - ]; - $lines[] = $line; - } - $tax = $tax_total; - - $result = []; - $result['id'] = $row['id']; - $result['serie'] = $row['serie']; - $result['nr'] = $row['nr']; - - $nr = (is_numeric($result['nr'])) ? $result['nr'] : $result['id']; - $result['serie_nr'] = $result['serie'] . sprintf('%05s', $nr); - - $result['hash'] = $row['hash']; - $result['gateway_id'] = $row['gateway_id']; - $result['taxname'] = $row['taxname']; - $result['taxrate'] = $row['taxrate']; - $result['currency'] = $row['currency']; - $result['currency_rate'] = $row['currency_rate']; - $result['tax'] = $tax; - $result['subtotal'] = $total; - $result['total'] = $total + $tax; - $result['status'] = $row['status']; - $result['notes'] = $row['notes']; - $result['text_1'] = $row['text_1']; - $result['text_2'] = $row['text_2']; - $result['due_at'] = $row['due_at']; - $result['paid_at'] = $row['paid_at']; - $result['created_at'] = $row['created_at']; - $result['updated_at'] = $row['updated_at']; - $result['lines'] = $lines; - - $result['buyer'] = [ - 'first_name' => $row['buyer_first_name'], - 'last_name' => $row['buyer_last_name'], - 'company' => $row['buyer_company'], - 'company_vat' => $row['buyer_company_vat'], - 'company_number' => $row['buyer_company_number'], - 'address' => $row['buyer_address'], - 'city' => $row['buyer_city'], - 'state' => $row['buyer_state'], - 'country' => $row['buyer_country'], - 'phone' => $row['buyer_phone'], - 'phone_cc' => $row['buyer_phone_cc'], - 'email' => $row['buyer_email'], - 'zip' => $row['buyer_zip'], - ]; - - $systemService = $this->di['mod_service']('system'); - $c = $systemService->getCompany(); - $result['seller'] = [ - 'company' => !empty($row['seller_company']) ? $row['seller_company'] : $c['name'], - 'company_vat' => $row['seller_company_vat'], - 'company_number' => $row['seller_company_number'], - 'address' => !empty($row['seller_address']) ? $row['seller_address'] : trim($c['address_1'] . ' ' . $c['address_2'] . ' ' . $c['address_2']), - 'phone' => !empty($row['seller_phone']) ? $row['seller_phone'] : $c['tel'], - 'email' => !empty($row['seller_email']) ? $row['seller_email'] : $c['email'], - 'account_number' => !empty($c['account_number']) ? $c['account_number'] : null, - ]; - - /** - * Removed if($identity instanceof \Model_Admin) {} - * Generates error when this function is called by cron. - */ - $client = $this->di['db']->load('Client', $row['client_id']); - $clientService = $this->di['mod_service']('client'); - if ($client instanceof \Model_Client) { - $result['client'] = $clientService->toApiArray($client); - } else { - $result['client'] = null; - } - $result['reminded_at'] = $row['reminded_at']; - $result['approved'] = (bool) $row['approved']; - $result['income'] = $row['base_income'] - $row['base_refund']; - $result['refund'] = $row['refund']; - $result['credit'] = $row['credit']; - - $subscriptionService = $this->di['mod_service']('Invoice', 'Subscription'); - $result['subscribable'] = $subscriptionService->isSubscribable($row['id']); - if ($deep && $result['subscribable']) { - $ip = $this->di['db']->getCell('SELECT period FROM invoice_item WHERE invoice_id = :id', ['id' => $row['id']]); - $period = $this->di['period']($ip); - $result['subscription'] = [ - 'unit' => $period->getUnit(), - 'cycle' => $period->getQty(), - 'period' => $ip, - ]; - } - - return $result; - } - - public static function onAfterAdminInvoicePaymentReceived(\Box_Event $event) - { - $params = $event->getParameters(); - $di = $event->getDi(); - $service = $di['mod_service']('invoice'); - - try { - $invoiceModel = $di['db']->load('Invoice', $params['id']); - $invoice = $service->toApiArray($invoiceModel, ['id' => $params['id']]); - if ($invoice['total'] > 0) { - $email = []; - $email['to_client'] = $invoiceModel->client_id; - $email['code'] = 'mod_invoice_paid'; - $email['invoice'] = $invoice; - $emailService = $di['mod_service']('email'); - $emailService->sendTemplate($email); - } - } catch (\Exception $exc) { - error_log($exc->getMessage()); - } - - return true; - } - - public static function onAfterAdminInvoiceApprove(\Box_Event $event) - { - $params = $event->getParameters(); - $di = $event->getDi(); - $service = $di['mod_service']('invoice'); - - try { - $invoiceModel = $di['db']->load('Invoice', $params['id']); - $invoice = $service->toApiArray($invoiceModel, ['id' => $params['id']]); - $email = []; - $email['to_client'] = $invoiceModel->client_id; - $email['code'] = 'mod_invoice_created'; - $email['invoice'] = $invoice; - $emailService = $di['mod_service']('Email'); - $emailService->sendTemplate($email); - } catch (\Exception $exc) { - error_log($exc->getMessage()); - } - - return true; - } - - public static function onAfterAdminInvoiceReminderSent(\Box_Event $event) - { - $params = $event->getParameters(); - $di = $event->getDi(); - $service = $di['mod_service']('invoice'); - - try { - $invoiceModel = $di['db']->load('Invoice', $params['id']); - $invoice = $service->toApiArray($invoiceModel, ['id' => $params['id']]); - $email = []; - $email['to_client'] = $invoiceModel->client_id; - $email['code'] = 'mod_invoice_payment_reminder'; - $email['invoice'] = $invoice; - $emailService = $di['mod_service']('Email'); - $emailService->sendTemplate($email); - } catch (\Exception $exc) { - error_log($exc->getMessage()); - } - } - - public static function onAfterAdminCronRun(\Box_Event $event) - { - $di = $event->getDi(); - $systemService = $di['mod_service']('System'); - $remove_after_days = $systemService->getParamValue('remove_after_days'); - if (isset($remove_after_days) && $remove_after_days) { - // removing old invoices - $days = (int) $remove_after_days; - $sql = "DELETE FROM invoice WHERE status = 'unpaid' AND DATEDIFF(NOW(), due_at) > $days"; - $di['db']->exec($sql); - } - } - - public static function onEventAfterInvoiceIsDue(\Box_Event $event) - { - $params = $event->getParameters(); - $di = $event->getDi(); - $service = $di['mod_service']('invoice'); - - // send reminder once a day when 5 days has passed - if (5 != $params['days_passed']) { - return; - } - - try { - $invoiceModel = $di['db']->load('Invoice', $params['id']); - $invoice = $service->toApiArray($invoiceModel, ['id' => $params['id']]); - $email = []; - $email['to_client'] = $invoice['client']['id']; - $email['code'] = 'mod_invoice_due_after'; - $email['days_passed'] = $params['days_passed']; - $email['invoice'] = $invoice; - - $emailService = $di['mod_service']('email'); - $emailService->sendTemplate($email); - } catch (\Exception $exc) { - error_log($exc->getMessage()); - } - } - - public function markAsPaid(\Model_Invoice $invoice, $charge = true, $execute = false) - { - if (\Model_Invoice::STATUS_PAID == $invoice->status) { - return true; - } - - $invoiceItems = $this->di['db']->find('InvoiceItem', 'invoice_id = ?', [$invoice->id]); - $invoiceItemService = $this->di['mod_service']('Invoice', 'InvoiceItem'); - foreach ($invoiceItems as $item) { - $invoiceItemService->markAsPaid($item, $charge); - } - - $systemService = $this->di['mod_service']('system'); - $ctable = $this->di['mod_service']('Currency'); - - $invoice->serie = $systemService->getParamValue('invoice_series_paid'); - $invoice->nr = $this->getNextInvoiceNumber($invoice); - $invoice->approved = true; - $invoice->currency_rate = $ctable->getRateByCode($invoice->currency); - $invoice->status = \Model_Invoice::STATUS_PAID; - $invoice->paid_at = date('Y-m-d H:i:s'); - $invoice->updated_at = date('Y-m-d H:i:s'); - $this->di['db']->store($invoice); - - $this->countIncome($invoice); - - $this->di['events_manager']->fire(['event' => 'onAfterAdminInvoicePaymentReceived', 'params' => ['id' => $invoice->id]]); - - if ($execute) { - foreach ($invoiceItems as $item) { - try { - $invoiceItemService->executeTask($item); - } catch (\Exception $e) { - error_log($e); - } - } - } - - $this->di['logger']->info('Marked invoice "%s" as paid', $invoice->id); - - return true; - } - - public function getNextInvoiceNumber(\Model_Invoice $model) - { - $p = 'invoice_starting_number'; - $systemService = $this->di['mod_service']('system'); - $next_nr = $systemService->getParamValue($p); - if (empty($next_nr)) { - $next_nr = $model->id; - - // get last invoice number - - $r = $this->di['db']->findOne('Invoice', 'nr is not null order by id desc'); - if ($r instanceof \Model_Invoice && is_numeric($r->nr)) { - $next_nr = $r->nr + 1; - } - } - $systemService->setParamValue($p, $next_nr + 1); - - return $next_nr; - } - - public function countIncome(\Model_Invoice $invoice) - { - $table = $this->di['mod_service']('Currency'); - - $invoice->base_income = $table->toBaseCurrency($invoice->currency, $this->getTotal($invoice)); - $invoice->base_refund = $table->toBaseCurrency($invoice->currency, $invoice->refund); - $this->di['db']->store($invoice); - } - - public function prepareInvoice(\Model_Client $client, array $data) - { - if (!$client->currency) { - $currencyService = $this->di['mod_service']('Currency'); - $currency = $currencyService->getDefault(); - $client->currency = $currency->code; - $this->di['db']->store($client); - error_log(sprintf('Client #%s currency was not defined. Set default currency %s', $client->id, $currency->code)); - } - - $model = $this->di['db']->dispense('Invoice'); - $model->client_id = $client->id; - $model->status = \Model_Invoice::STATUS_UNPAID; - $model->currency = $client->currency; - $model->approved = 0; - - $model->gateway_id = $this->di['array_get']($data, 'gateway_id', $model->gateway_id); - $model->text_1 = $this->di['array_get']($data, 'text_1', $model->text_1); - $model->text_2 = $this->di['array_get']($data, 'text_2', $model->text_2); - $model->created_at = date('Y-m-d H:i:s'); - $model->updated_at = date('Y-m-d H:i:s'); - $invoiceId = $this->di['db']->store($model); - - $this->setInvoiceDefaults($model); - - if (isset($data['items']) && is_array($data['items'])) { - $invoiceItemService = $this->di['mod_service']('Invoice', 'InvoiceItem'); - foreach ($data['items'] as $d) { - $invoiceItemService->addNew($model, $d); - } - } - - $this->di['logger']->info('Prepared new invoice "%s"', $invoiceId); - - if (isset($data['approve']) && $data['approve']) { - try { - $this->approveInvoice($model, ['id' => $invoiceId]); - $this->di['logger']->info('Approved invoice %s instantly', $invoiceId); - } catch (\Exception $e) { - error_log($e->getMessage()); - } - } - - return $model; - } - - public function setInvoiceDefaults(\Model_Invoice $model) - { - $clientService = $this->di['mod_service']('Client'); - $systemService = $this->di['mod_service']('system'); - $client = $this->di['db']->load('Client', $model->client_id); - $seller = $systemService->getCompany(); - - $buyer = $clientService->toApiArray($client); - - $model->seller_company = $seller['name']; - $model->seller_company_vat = $seller['vat_number']; - $model->seller_company_number = $seller['number']; - $model->seller_address = trim($seller['address_1'] . ' ' . $seller['address_2'] . ' ' . $seller['address_3']); - $model->seller_phone = $seller['tel']; - $model->seller_email = $seller['email']; - - $model->buyer_first_name = $buyer['first_name']; - $model->buyer_last_name = $buyer['last_name']; - $model->buyer_company = $buyer['company']; - $model->buyer_company_vat = $buyer['company_vat']; - $model->buyer_company_number = $buyer['company_number']; - $model->buyer_address = $buyer['address_1'] . ' ' . $buyer['address_2']; - $model->buyer_city = $buyer['city']; - $model->buyer_state = $buyer['state']; - $model->buyer_country = $buyer['country']; - $model->buyer_phone = $buyer['phone_cc'] . ' ' . $buyer['phone']; - $model->buyer_email = $buyer['email']; - $model->buyer_zip = $buyer['postcode']; - - $invoice_due_days = $systemService->getParamValue('invoice_due_days'); - if (!is_numeric($invoice_due_days)) { - $invoice_due_days = 1; - } - $due_time = strtotime('+' . $invoice_due_days . ' day'); - $model->due_at = date('Y-m-d H:i:s', $due_time); - - $model->serie = $systemService->getParamValue('invoice_series'); - $model->nr = $model->id; - $model->hash = sha1(uniqid()); - - $taxtitle = ''; - $taxService = $this->di['mod_service']('Invoice', 'Tax'); - $tax = $taxService->getTaxRateForClient($client, $taxtitle); - $model->taxname = $taxtitle; - $model->taxrate = $tax; - - $this->di['db']->store($model); - } - - public function approveInvoice(\Model_Invoice $invoice, array $data) - { - $this->di['events_manager']->fire(['event' => 'onBeforeAdminInvoiceApprove', 'params' => ['id' => $invoice->id]]); - - $invoice->approved = 1; - $invoice->updated_at = date('Y-m-d H:i:s'); - $this->di['db']->store($invoice); - - $this->di['events_manager']->fire(['event' => 'onAfterAdminInvoiceApprove', 'params' => ['id' => $invoice->id]]); - - if (isset($data['use_credits']) && $data['use_credits']) { - $this->tryPayWithCredits($invoice); - } - - $this->di['logger']->info('Approved invoice "%s"', $invoice->id); - - return true; - } - - public function tryPayWithCredits(\Model_Invoice $invoice) - { - if (!$invoice->approved) { - return; - } - - if ($this->isInvoiceTypeDeposit($invoice)) { - $this->markAsPaid($invoice, false); - } - - $client = $this->di['db']->load('Client', $invoice->client_id); - $cbrepo = $this->di['mod_service']('Client', 'Balance'); - $balance = $cbrepo->getClientBalance($client); - $required = $this->getTotalWithTax($invoice); - $epsilon = 0.05; - - if (abs($balance - $required) < $epsilon) { - if ($this->di['config']['debug']) { - error_log(sprintf('Setting invoice %s as paid with credits', $invoice->id)); - } - $this->markAsPaid($invoice); - - return true; - } - - if ($balance - $required > 0.00001) { - if ($this->di['config']['debug']) { - error_log(sprintf('Setting invoice %s as paid with credits', $invoice->id)); - } - $this->markAsPaid($invoice); - - return true; - } - if ($this->di['config']['debug']) { - error_log(sprintf('Invoice %s could not be paid with credits. Money in balance %s Required: %s', $invoice->id, $balance, $required)); - } - } - - public function getTotalWithTax(\Model_Invoice $invoice) - { - $total = $this->getTotal($invoice) + $this->getTax($invoice); - - return (float) $total; - } - - public function getTax(\Model_Invoice $invoice) - { - if ($invoice->taxrate <= 0) { - return 0; - } - - $iiService = $this->di['mod_service']('Invoice', 'InvoiceItem'); - $items = $this->di['db']->find('InvoiceItem', 'invoice_id = ? ', [$invoice->id]); - $tax = 0; - foreach ($items as $item) { - $tax += $iiService->getTax($item) * $item->quantity; - } - - return $tax; - } - - public function getTotal(\Model_Invoice $invoice) - { - $total = 0; - $invoiceItems = $this->di['db']->find('InvoiceItem', 'invoice_id = ?', [$invoice->id]); - $invoiceItemService = $this->di['mod_service']('Invoice', 'InvoiceItem'); - foreach ($invoiceItems as $item) { - $total += $invoiceItemService->getTotal($item); - } - - return (float) $total; - } - - public function refundInvoice(\Model_Invoice $invoice, $note = null) - { - $this->di['events_manager']->fire(['event' => 'onBeforeAdminInvoiceRefund', 'params' => ['id' => $invoice->id]]); - - $systemService = $this->di['mod_service']('system'); - $logic = $systemService->getParamValue('invoice_refund_logic', 'manual'); - $result = null; - - switch ($logic) { - case 'credit_note': - case 'negative_invoice': - $total = $this->getTotalWithTax($invoice); - if ($total <= 0) { - throw new \Box_Exception('Can not refund invoice with negative amount'); - } - - $new = $this->di['db']->dispense('Invoice'); - $new->client_id = $invoice->client_id; - $new->hash = md5(random_bytes(13)); - $new->status = \Model_Invoice::STATUS_REFUNDED; - $new->currency = $invoice->currency; - $new->approved = true; - $new->taxname = $invoice->taxname; - $new->taxrate = $invoice->taxrate; - - $new->seller_company = $invoice->seller_company; - $new->seller_address = $invoice->seller_address; - $new->seller_phone = $invoice->seller_phone; - $new->seller_email = $invoice->seller_email; - - $new->buyer_first_name = $invoice->buyer_first_name; - $new->buyer_last_name = $invoice->buyer_last_name; - $new->buyer_company = $invoice->buyer_company; - $new->buyer_address = $invoice->buyer_address; - $new->buyer_city = $invoice->buyer_city; - $new->buyer_state = $invoice->buyer_state; - $new->buyer_country = $invoice->buyer_country; - $new->buyer_phone = $invoice->buyer_phone; - $new->buyer_email = $invoice->buyer_email; - $new->buyer_zip = $invoice->buyer_zip; - - $new->paid_at = date('Y-m-d H:i:s'); - $new->created_at = date('Y-m-d H:i:s'); - $new->updated_at = date('Y-m-d H:i:s'); - $this->di['db']->store($new); - - $invoiceItems = $this->di['db']->find('InvoiceItem', 'invoice_id = ?', [$invoice->id]); - foreach ($invoiceItems as $item) { - $pi = $this->di['db']->dispense('InvoiceItem'); - $pi->invoice_id = $new->id; - $pi->type = $item->type; - $pi->rel_id = $item->rel_id; - $pi->task = $item->task; - $pi->status = \Model_InvoiceItem::STATUS_EXECUTED; // ark refund invoce as executed - $pi->title = $item->title; - $pi->period = $item->period; - $pi->quantity = $item->quantity; - $pi->unit = $item->unit; - $pi->charged = 1; - $pi->price = -$item->price; - $pi->taxed = $item->taxed; - $pi->created_at = date('Y-m-d H:i:s'); - $pi->updated_at = date('Y-m-d H:i:s'); - $this->di['db']->store($pi); - } - - $this->countIncome($new); - - $this->addNote($invoice, sprintf('Refund invoice #%s generated', $new->id)); - $this->addNote($new, sprintf('Refund for #%s invoice', $invoice->id)); - if (!empty($note)) { - $this->addNote($new, $note); - } - - if ('negative_invoice' == $logic) { - $new->serie = $systemService->getParamValue('invoice_series_paid'); - $new->nr = $this->getNextInvoiceNumber($new); - $this->di['db']->store($new); - } - - if ('credit_note' == $logic) { - $next_nr = $systemService->getParamValue('invoice_cn_starting_number', 1); - $new->serie = $systemService->getParamValue('invoice_cn_series', 'CN-'); - $new->nr = $next_nr; - $this->di['db']->store($new); - - // update next credit note starting number - $systemService->setParamValue('invoice_cn_starting_number', ++$next_nr, true); - } - $result = (int) $new->id; - break; - - // @todo undocumented - // @deprecated - case 'same_invoice': - $amount = $this->getTotalWithTax($invoice); - $invoice->refund = empty($amount) ? null : $amount; - $invoice->updated_at = date('Y-m-d H:i:s'); - $this->di['db']->store($invoice); - - if (!empty($note)) { - $this->addNote($invoice, $note); - } - - // mark invoice as refunded if refund amount is equal or greater - // than refund amount - $total = $this->getTotalWithTax($invoice); - if ($invoice->refund >= $total) { - $invoice->status = \Model_Invoice::STATUS_REFUNDED; - $this->di['db']->store($invoice); - } - - $this->countIncome($invoice); - break; - - case 'manual': - if ($this->di['config']['debug']) { - error_log('Refunds are managed manually. No actions performed'); - } - break; - default: - break; - } - - $this->di['events_manager']->fire(['event' => 'onAfterAdminInvoiceRefund', 'params' => ['id' => $invoice->id]]); - - $this->di['logger']->info('Refunded invoice #%s', $invoice->id); - - return $result; - } - - public function updateInvoice(\Model_Invoice $model, array $data) - { - $invoiceItemService = $this->di['mod_service']('Invoice', 'InvoiceItem'); - - $this->di['events_manager']->fire(['event' => 'onBeforeAdminInvoiceUpdate', 'params' => $data]); - - $model->gateway_id = $this->di['array_get']($data, 'gateway_id', $model->gateway_id); - $model->text_1 = $this->di['array_get']($data, 'text_1', $model->text_1); - $model->text_2 = $this->di['array_get']($data, 'text_2', $model->text_2); - $model->seller_company = $this->di['array_get']($data, 'seller_company', $model->seller_company); - $model->seller_company_vat = $this->di['array_get']($data, 'seller_company_vat', $model->seller_company_vat); - $model->seller_company_number = $this->di['array_get']($data, 'seller_company_number', $model->seller_company_number); - $model->seller_address = $this->di['array_get']($data, 'seller_address', $model->seller_address); - $model->seller_phone = $this->di['array_get']($data, 'seller_phone', $model->seller_phone); - $model->seller_email = $this->di['array_get']($data, 'seller_email', $model->seller_email); - $model->buyer_first_name = $this->di['array_get']($data, 'buyer_first_name', $model->buyer_first_name); - $model->buyer_last_name = $this->di['array_get']($data, 'buyer_last_name', $model->buyer_last_name); - $model->buyer_company = $this->di['array_get']($data, 'buyer_company', $model->buyer_company); - $model->buyer_company_vat = $this->di['array_get']($data, 'buyer_company_vat', $model->buyer_company_vat); - $model->buyer_company_number = $this->di['array_get']($data, 'buyer_company_number', $model->buyer_company_number); - $model->buyer_address = $this->di['array_get']($data, 'buyer_address', $model->buyer_address); - $model->buyer_city = $this->di['array_get']($data, 'buyer_city', $model->buyer_city); - $model->buyer_state = $this->di['array_get']($data, 'buyer_state', $model->buyer_state); - $model->buyer_country = $this->di['array_get']($data, 'buyer_country', $model->buyer_country); - $model->buyer_zip = $this->di['array_get']($data, 'buyer_zip', $model->buyer_zip); - $model->buyer_phone = $this->di['array_get']($data, 'buyer_phone', $model->buyer_phone); - $model->buyer_email = $this->di['array_get']($data, 'buyer_email', $model->buyer_email); - - $paid_at = $this->di['array_get']($data, 'paid_at', $model->paid_at); - if (empty($paid_at)) { - $model->paid_at = null; - } else { - $model->paid_at = date('Y-m-d H:i:s', strtotime($paid_at)); - } - - $due_at = $this->di['array_get']($data, 'due_at', $model->due_at); - if (empty($due_at)) { - $model->due_at = null; - } else { - $model->due_at = date('Y-m-d H:i:s', strtotime($due_at)); - } - - $model->serie = $this->di['array_get']($data, 'serie', $model->serie); - $model->nr = $this->di['array_get']($data, 'nr', $model->nr); - $model->status = $this->di['array_get']($data, 'status', $model->status); - $model->taxrate = $this->di['array_get']($data, 'taxrate', $model->taxrate); - $model->taxname = $this->di['array_get']($data, 'taxname', $model->taxname); - $model->approved = (int) $this->di['array_get']($data, 'approved', $model->approved); - $model->notes = $this->di['array_get']($data, 'notes', $model->notes); - - $created_at = $this->di['array_get']($data, 'created_at', ''); - if (!empty($created_at)) { - $model->created_at = date('Y-m-d H:i:s', strtotime($created_at)); - } - - $ni = $this->di['array_get']($data, 'new_item', []); - if (isset($ni['title']) && !empty($ni['title'])) { - $invoiceItemService->addNew($model, $ni); - } - - $items = $this->di['array_get']($data, 'items', []); - foreach ($items as $id => $d) { - $item = $this->di['db']->load('InvoiceItem', $id); - if ($item instanceof \Model_InvoiceItem) { - $invoiceItemService->update($item, $d); - } - } - - $model->updated_at = date('Y-m-d H:i:s'); - $this->di['db']->store($model); - - $this->di['events_manager']->fire(['event' => 'onAfterAdminInvoiceUpdate', 'params' => ['id' => $model->id]]); - - $this->di['logger']->info('Updated invoice "%s"', $model->id); - - return true; - } - - public function rmInvoice(\Model_Invoice $model) - { - // remove related invoice from orders - $sql = ' - UPDATE client_order - SET unpaid_invoice_id = NULL - WHERE unpaid_invoice_id = :id'; - $this->di['db']->exec($sql, ['id' => $model->id]); - - $invoiceItems = $this->di['db']->find('InvoiceItem', 'invoice_id = ?', [$model->id]); - foreach ($invoiceItems as $item) { - $this->di['db']->trash($item); - } - $this->di['db']->trash($model); - - return true; - } - - public function deleteInvoiceByAdmin(\Model_Invoice $model) - { - $this->di['events_manager']->fire(['event' => 'onBeforeAdminInvoiceDelete', 'params' => ['id' => $model->id]]); - - $id = $model->id; - $this->rmInvoice($model); - - $this->di['events_manager']->fire(['event' => 'onAfterAdminInvoiceDelete', 'params' => ['id' => $id]]); - - $this->di['logger']->info('Removed invoice #%s', $id); - - return true; - } - - public function deleteInvoiceByClient(\Model_Invoice $model) - { - $this->di['events_manager']->fire(['event' => 'onBeforeClientInvoiceDelete', 'params' => ['id' => $model->id]]); - - // check if invoice is associated with order - $invoiceItem = $this->di['db']->find('InvoiceItem', 'invoice_id = ?', [$model->id]); - foreach ($invoiceItem as $item) { - if (\Model_InvoiceItem::TYPE_ORDER == $item->type) { - throw new \Box_Exception('Invoice is related to order #:id. Please cancel order first.', [':id' => $item->rel_id]); - } - } - - $this->rmInvoice($model); - $this->di['logger']->info('Removed invoice #%s', $model->id); - - return true; - } - - public function renewInvoice(\Model_ClientOrder $model, array $data) - { - $this->di['events_manager']->fire(['event' => 'onBeforeAdminGenerateRenewalInvoice', 'params' => ['order_id' => $model->id]]); - - $due_days = isset($data['due_days']) ? (int) $data['due_days'] : null; - $invoice = $this->generateForOrder($model, $due_days); - $this->approveInvoice($invoice, (['id' => $invoice->id, 'use_credits' => true])); - - $this->di['events_manager']->fire(['event' => 'onAfterAdminGenerateRenewalInvoice', 'params' => ['order_id' => $model->id, 'id' => $invoice->id]]); - - $this->di['logger']->info('Generated renewal invoice #%s', $invoice->id); - - return $invoice->id; - } - - public function doBatchPayWithCredits(array $data) - { - $unpaid = $this->findAllUnpaid($data); - foreach ($unpaid as $proforma) { - try { - $model = $this->di['db']->getExistingModelById('Invoice', $proforma['id']); - $this->tryPayWithCredits($model); - } catch (\Exception $e) { - if ($this->di['config']['debug']) { - error_log($e->getMessage()); - } - } - } - $this->di['logger']->info('Executed action to try cover unpaid invoices with client credits'); - - return true; - } - - public function payInvoiceWithCredits(\Model_Invoice $model) - { - $this->tryPayWithCredits($model); - $this->di['logger']->info('Cover invoice with client credits'); - - return true; - } - - /** - * @param int $due_days - * - * @return \Model_Invoice - */ - public function generateForOrder(\Model_ClientOrder $order, $due_days = null) - { - // check if we do have invoice prepared already - if (null !== $order->unpaid_invoice_id) { - $p = $this->di['db']->load('Invoice', $order->unpaid_invoice_id); - if ($p instanceof \Model_Invoice) { - return $p; - } - } - - if ($order->price <= 0) { - throw new \Box_Exception('Invoices are not generated for 0 amount orders'); - } - - $client = $this->di['db']->getExistingModelById('Client', $order->client_id, 'Client not found'); - - // generate proforma - $proforma = $this->di['db']->dispense('Invoice'); - $proforma->client_id = $client->id; - $proforma->status = \Model_Invoice::STATUS_UNPAID; - $proforma->currency = $order->currency; - $proforma->approved = false; - $proforma->created_at = date('Y-m-d H:i:s'); - $proforma->updated_at = date('Y-m-d H:i:s'); - $this->di['db']->store($proforma); - - $this->setInvoiceDefaults($proforma); - - $price = $order->price; - - $invoiceItemService = $this->di['mod_service']('Invoice', 'InvoiceItem'); - $invoiceItemService->generateFromOrder($proforma, $order, \Model_InvoiceItem::TASK_RENEW, $price); - - // invoice due date - if ($due_days > 0) { - $proforma->due_at = date('Y-m-d H:i:s', strtotime('+' . $due_days . ' days')); - $this->di['db']->store($proforma); - } elseif ($order->expires_at) { - $proforma->due_at = $order->expires_at; - $this->di['db']->store($proforma); - } - - return $proforma; - } - - public function generateInvoicesForExpiringOrders() - { - $orderService = $this->di['mod_service']('Order'); - $orders = $orderService->getSoonExpiringActiveOrders(); - - if (0 == count($orders)) { - return true; - } - - foreach ($orders as $order) { - try { - $model = $this->di['db']->getExistingModelById('ClientOrder', $order['id']); - $invoice = $this->generateForOrder($model); - $this->approveInvoice($invoice, ['id' => $invoice->id, 'use_credits' => true]); - } catch (\Exception $e) { - error_log($e->getMessage()); - } - } - - $this->di['logger']->info('Executed action to generate new invoices for expiring orders'); - - return true; - } - - public function doBatchPaidInvoiceActivation() - { - $invoiceItemService = $this->di['mod_service']('Invoice', 'InvoiceItem'); - - $invoiceItems = (array) $invoiceItemService->getAllNotExecutePaidItems(); - foreach ($invoiceItems as $item) { - try { - $model = $this->di['db']->getExistingModelById('InvoiceItem', $item['id']); - $invoiceItemService->executeTask($model); - } catch (\Exception $e) { - error_log($e->getMessage()); - } - } - $this->di['logger']->info('Executed action to activate paid invoices'); - - return true; - } - - public function doBatchRemindersSend() - { - $this->di['events_manager']->fire(['event' => 'onBeforeAdminInvoiceSendReminders']); - $list = $this->getUnpaidInvoicesLateFor(); - foreach ($list as $invoice) { - $this->sendInvoiceReminder($invoice); - } - $this->di['logger']->info('Executed action to send invoice payment reminders'); - - return true; - } - - public function doBatchInvokeDueEvent(array $data) - { - $once_per_day = isset($data['once_per_day']) ? (bool) $data['once_per_day'] : true; - $key = 'invoice_overdue_invoked'; - - // do not use api call to get system param to avoid invoking system module event hooks - $ss = $this->di['mod_service']('System'); - $last_time = $ss->getParamValue($key); - if ($once_per_day && $last_time && (time() - strtotime($last_time)) < 86400) { - // error_log('Already executed today.'); - return false; - } - - $before_due_list = $this->di['db']->getAll("SELECT id, DATEDIFF(due_at, NOW()) as days_left FROM invoice WHERE status = 'unpaid' AND approved = 1 AND due_at > NOW()"); - foreach ($before_due_list as $params) { - $this->di['events_manager']->fire(['event' => 'onEventBeforeInvoiceIsDue', 'params' => $params]); - } - - $after_due_list = $this->di['db']->getAll("SELECT id, ABS(DATEDIFF(due_at, NOW())) as days_passed FROM invoice WHERE status = 'unpaid' AND approved = 1 AND ((due_at < NOW()) OR (ABS(DATEDIFF(due_at, NOW())) = 0 ))"); - foreach ($after_due_list as $params) { - $this->di['events_manager']->fire(['event' => 'onEventAfterInvoiceIsDue', 'params' => $params]); - } - - $ss->setParamValue($key, date('Y-m-d H:i:s')); - $this->di['logger']->info('Executed action to invoke invoice due event'); - - return true; - } - - public function sendInvoiceReminder(\Model_Invoice $invoice) - { - // do not send accidental reminder for paid invoices - if (\Model_Invoice::STATUS_PAID == $invoice->status) { - return true; - } - - $this->di['events_manager']->fire(['event' => 'onBeforeAdminInvoiceSendReminder', 'params' => ['id' => $invoice->id]]); - - $invoice->reminded_at = date('Y-m-d H:i:s'); - $invoice->updated_at = date('Y-m-d H:i:s'); - $this->di['db']->store($invoice); - - $this->di['events_manager']->fire(['event' => 'onAfterAdminInvoiceReminderSent', 'params' => ['id' => $invoice->id]]); - - $this->di['logger']->info('Invoice payment reminder sent'); - - return true; - } - - public function counter() - { - $sql = 'SELECT status, count(id) as counter - FROM invoice - group by status'; - $rows = $this->di['db']->getAll($sql); - $data = []; - foreach ($rows as $row) { - $data[$row['status']] = $row['counter']; - } - - return [ - 'total' => array_sum($data), - \Model_Invoice::STATUS_PAID => $data[\Model_Invoice::STATUS_PAID] ?? 0, - \Model_Invoice::STATUS_UNPAID => $data[\Model_Invoice::STATUS_UNPAID] ?? 0, - \Model_Invoice::STATUS_REFUNDED => $data[\Model_Invoice::STATUS_REFUNDED] ?? 0, - \Model_Invoice::STATUS_CANCELED => $data[\Model_Invoice::STATUS_CANCELED] ?? 0, - ]; - } - - public function generateFundsInvoice(\Model_Client $client, $amount) - { - if (!$client->currency) { - throw new \Box_Exception('You must have at least one active order before you can add funds so you cannot proceed at the current time!'); - } - - $systemService = $this->di['mod_service']('system'); - - $min_amount = $systemService->getParamValue('funds_min_amount', null); - $max_amount = $systemService->getParamValue('funds_max_amount', null); - - if ($min_amount && $amount < $min_amount) { - throw new \Box_Exception('Amount is not valid', null, 981); - } - - if ($max_amount && $amount > $max_amount) { - throw new \Box_Exception('Amount is not valid', null, 982); - } - - $proforma = $this->di['db']->dispense('Invoice'); - $proforma->client_id = $client->id; - $proforma->status = \Model_Invoice::STATUS_UNPAID; - $proforma->currency = $client->currency; - $proforma->approved = $this->_isAutoApproved(); - $proforma->created_at = date('Y-m-d H:i:s'); - $proforma->updated_at = date('Y-m-d H:i:s'); - $this->di['db']->store($proforma); - - $this->setInvoiceDefaults($proforma); - - $invoiceItemService = $this->di['mod_service']('Invoice', 'InvoiceItem'); - $invoiceItemService->generateForAddFunds($proforma, $amount); - - return $proforma; - } - - public function processInvoice(array $data) - { - $subscribe = false; - - $invoice = $this->di['db']->findOne('Invoice', 'hash = ?', [$data['hash']]); - if (!$invoice instanceof \Model_Invoice) { - throw new \Box_Exception('Invoice not found', null, 812); - } - - $gtw = $this->di['db']->load('PayGateway', $data['gateway_id']); - if (!$gtw instanceof \Model_PayGateway) { - throw new \Box_Exception('Payment method not found', null, 813); - } - - if (!$gtw->enabled) { - throw new \Box_Exception('Payment method not enabled', null, 814); - } - - $subscribeService = $this->di['mod_service']('Invoice', 'Subscription'); - $payGatewayService = $this->di['mod_service']('Invoice', 'PayGateway'); - if ($subscribeService->isSubscribable($invoice->id) && $payGatewayService->canPerformRecurrentPayment($gtw)) { - $subscribe = true; - } - - $adapter = $payGatewayService->getPaymentAdapter($gtw, $invoice, $data); - if (method_exists($adapter, 'setDi')) { - $adapter->setDi($this->di); - } - - if (method_exists($adapter, 'setLog')) { - $adapter->setLog($this->di['logger']); - } - - $pgc = $adapter->getConfig(); - - // @since v2.9.15 - if (method_exists($adapter, 'getHtml')) { - $html = $adapter->getHtml($this->di['api_system'], $invoice->id, $subscribe); - - return [ - 'iframe' => isset($pgc['can_load_in_iframe']) ? (bool) $pgc['can_load_in_iframe'] : false, - 'type' => 'html', - 'service_url' => '', - 'subscription' => $subscribe, - 'result' => $html, - ]; - } - - $i = clone $invoice; - $mpi = $this->getPaymentInvoice($i, $subscribe); - $r = ($subscribe) ? $adapter->recurrentPayment($mpi) : $adapter->singlePayment($mpi); - $this->di['logger']->info('Went to pay for invoice #%s via %s', $invoice->id, $gtw->gateway); - - // @bug https://github.com/boxbilling/boxbilling/issues/108 - if ('html' != $adapter->getType()) { - $r = (array) $r; - } - - return [ - 'type' => $adapter->getType(), - 'service_url' => $adapter->getServiceURL(), - 'subscription' => $subscribe, - 'result' => $r, - ]; - } - - public function generatePDF($hash, $identity) - { - $invoice = $this->di['db']->findOne('Invoice', 'hash = :hash', [':hash' => $hash]); - if (!$invoice instanceof \Model_Invoice) { - throw new \Box_Exception('Invoice not found'); - } - - if (isset($invoice->currency)) { - $currencyCode = $invoice->currency; - } else { - $client = $this->di['db']->getExistingModelById('Client', $invoice->client_id, 'Client not found'); - $currencyCode = $client->currency; - } - - $localeDateFormat = $this->di['config']['locale_date_format']; - $invoice = $this->toApiArray($invoice, false, $identity); - $systemService = $this->di['mod_service']('System'); - $company = $systemService->getCompany(); - - $pdf = $this->di['pdf']; - $pdf->AddPage(); - $pdf->AddFont('DejaVu', '', 'DejaVuSansCondensed.ttf', true); - $pdf->AddFont('DejaVu', 'B', 'DejaVuSansCondensed-Bold.ttf', true); - - $left = 10; - $fontSize = 12; - - if (isset($company['logo_url']) && !empty($company['logo_url'])) { - $url = $company['logo_url']; - if ('.png' === substr($url, -4)) { - $pdf->ImagePngWithAlpha($_SERVER['DOCUMENT_ROOT'] . $url, $left + 15, 15, 50); - } else { - // Converting to .png - $img = imagecreatefromstring(file_get_contents($url)); - if ($img) { - $filename = 'logo.png'; - if (imagepng($img, $filename)) { - $pdf->ImagePngWithAlpha($filename, $left + 15, 15, 50); - unlink($filename); - } - } else { - throw new \Box_Exception('Error converting image to .png'); - } - } - } - - $invoiceDate = strftime($localeDateFormat, strtotime($this->di['array_get']($invoice, 'due_at', $invoice['created_at']))); - - $invoiceInfo = sprintf("%s: %s\n", __('Invoice number'), $invoice['serie_nr']); - $invoiceInfo .= sprintf("%s: %s\n", __('Invoice date'), $invoiceDate); - $invoiceInfo .= sprintf("%s: %s\n", __('Due date'), strftime($localeDateFormat, strtotime($invoice['due_at']))); - $invoiceInfo .= sprintf("%s: %s\n", __('Invoice status'), ucfirst($invoice['status'])); - - $pdf->SetXY($pdf->GetPageWidth() / 2, $pdf->GetY()); - $pdf->SetFont('DejaVu', '', $fontSize); - $pdf->MultiCell($pdf->GetPageWidth() / 2, 6, "\n" . $invoiceInfo, 0, 'L', 0); - - $pdf->SetXY($left, 50); - $pdf->SetLineWidth(0.5); - $pdf->Line($pdf->GetX(), $pdf->GetY(), $pdf->GetPageWidth() - $pdf->GetX(), $pdf->GetY()); - - $pdf->SetFont('DejaVu', 'B', $fontSize); - $pdf->Text($left, 75, __('Company')); - - $companyInfo = sprintf("%s: %s\n", __('Name'), $invoice['seller']['company']); - $companyInfo .= sprintf("%s: %s\n", __('Address'), $invoice['seller']['address']); - $companyInfo .= sprintf("%s: %s\n", __('Company VAT'), $invoice['seller']['company_vat']); - $companyInfo .= sprintf("%s: %s\n", __('Company number'), $invoice['seller']['company_number']); - $companyInfo .= sprintf("%s: %s\n", __('Account'), $invoice['seller']['account_number']); - $companyInfo .= sprintf("%s: %s\n", __('Phone'), $invoice['seller']['phone']); - $companyInfo .= sprintf("%s: %s\n", __('Email'), $invoice['seller']['email']); - - $pdf->SetXY($left, 75); - $pdf->SetFont('DejaVu', '', $fontSize); - $pdf->MultiCell(90, 6, "\n" . $companyInfo, 0, 'L', 0); - - $pdf->SetFont('DejaVu', 'B', $fontSize); - $pdf->Text($pdf->GetPageWidth() / 2, 75, __('Billing and delivery address')); - - $buyerInfo = sprintf("%s: %s %s\n", __('Name'), $invoice['buyer']['first_name'], $invoice['buyer']['last_name']); - $buyerInfo .= sprintf("%s: %s\n", __('Company'), $invoice['buyer']['company']); - $buyerInfo .= sprintf("%s: %s\n", __('Address'), $invoice['buyer']['address']); - $buyerInfo .= sprintf("%s: %s\n", __('Company VAT'), $invoice['seller']['company_vat']); - $buyerInfo .= sprintf("%s: %s\n", __('Company number'), $invoice['seller']['company_number']); - $buyerInfo .= sprintf("%s: %s\n", __('Phone'), $invoice['buyer']['phone']); - - $pdf->SetXY($pdf->GetPageWidth() / 2, 75); - $pdf->SetFont('DejaVu', '', $fontSize); - $pdf->MultiCell(90, 6, "\n" . $buyerInfo, 0, 'L', 0); - - $header = [__('#'), __('Title'), __('Price'), __('Total')]; - $pdf->SetXY($left, 155); - $pdf->SetLineWidth(0.1); - $align = ['C', 'L', 'C', 'R']; - $width = [10, 120, 30, 30]; - - $counted = count($header); - for ($i = 0; $i < $counted; ++$i) { - $pdf->SetFillColor(74, 74, 74); - $pdf->SetTextColor(255); - $pdf->Cell($width[$i], 10, $header[$i], 0, 0, $align[$i], true); - } - $pdf->SetTextColor(0); - $pdf->Ln(); - - $pdf->SetFillColor(240, 240, 240); - $fill = true; - $nr = 1; - foreach ($invoice['lines'] as $row) { - $pdf->Cell($width[0], 10, $nr++, 0, 0, 'C', $fill); - $pdf->Cell($width[1], 10, $row['title'], 0, 0, 'L', $fill); - $pdf->Cell($width[2], 10, ($row['quantity'] > 1) ? $row['quantity'] . ' x ' . $this->money($row['price'], $currencyCode) : $this->money($row['price'], $currencyCode), 0, 0, 'C', $fill); - $pdf->Cell($width[3], 10, $this->money($row['total'], $currencyCode), 0, 0, 'R', $fill); - $pdf->Ln(); - $fill = !$fill; - } - $pdf->Cell(array_sum($width), 0, '', 'T'); - - $pdf->SetXY(140, $pdf->GetY() + 10); - if ($invoice['tax'] > 0) { - $pdf->Cell(30, 6, sprintf('%s %s%%', $invoice['taxname'], $invoice['taxrate']), '', 0, 'C'); - $pdf->Cell(30, 6, $this->money($invoice['tax'], $currencyCode), '', 0, 'R'); - $pdf->Ln(); - } - - $pdf->SetX(140); - if (isset($invoice['discount']) && $invoice['discount'] > 0) { - $pdf->Cell(40, 8, __('Discount '), '', 0, 'C'); - $pdf->Cell(40, 8, $this->money($invoice['discount'], $currencyCode), '', 0, 'R'); - $pdf->Ln(); - } - - $pdf->SetX(140); - $pdf->SetFont('DejaVu', 'B', $fontSize + 2); - $pdf->Cell(30, 10, __('Total'), '', 0, 'C'); - $pdf->Cell(30, 10, $this->money($invoice['total'], $currencyCode), '', 0, 'R'); - $pdf->Ln(); - - $pdf->Output($invoice['serie_nr'] . '.pdf', 'I'); - } - - private function money($price, $currencyCode) - { - return $this->di['api_guest']->currency_format(['price' => $price, 'code' => $currencyCode, 'convert' => false]); - } - - public function addNote(\Model_Invoice $model, $note) - { - $n = $model->notes; - $model->notes = $n . date('Y-m-d H:i:s') . ': ' . $note . ' ' . PHP_EOL; - $model->updated_at = date('Y-m-d H:i:s'); - $this->di['db']->store($model); - - return true; - } - - /** - * Return list of unpaid invoices which can be covered from client balance. - * - * @param array $filter - * - * @return array - */ - public function findAllUnpaid(array $filter = null) - { - $sql = 'SELECT m.* - FROM invoice as m - LEFT JOIN client as cl on m.client_id = cl.id - LEFT JOIN client_balance as cb on m.client_id = cb.client_id - LEFT JOIN invoice_item as pi on pi.invoice_id = m.id - WHERE m.status = :status - AND m.approved = 1 - AND cb.amount >= pi.price'; - $params = ['status' => \Model_Invoice::STATUS_UNPAID]; - - $client_id = isset($filter['client_id']) ? (int) $filter['client_id'] : null; - - if ($client_id) { - $sql .= ' AND m.client_id = :client_id '; - $params['client_id'] = $client_id; - } - - $sql .= ' GROUP BY m.id, cl.id - ORDER BY m.id DESC'; - - return $this->di['db']->getAll($sql, $params); - } - - public function findAllPaid() - { - return $this->di['db']->find('Invoice', 'status = ? order by id desc', [\Model_Invoice::STATUS_PAID]); - } - - public function getUnpaidInvoicesLateFor($days_after_issue = 2) - { - $conditions = 'status = ? and approved = 1 and reminded_at is null and DATEDIFF(NOW(), created_at) > ?'; - - return $this->di['db']->find('Invoice', $conditions, [\Model_Invoice::STATUS_UNPAID, $days_after_issue]); - } - - /** - * @return bool - */ - private function _isAutoApproved() - { - /** - * @var \Box\Mod\System\Service $systemService - */ - $systemService = $this->di['mod_service']('system'); - - return (bool) $systemService->getParamValue('invoice_auto_approval', true); - } - - /** - * @param bool $subscribe - * - * @return \Payment_Invoice - */ - public function getPaymentInvoice(\Model_Invoice $invoice, $subscribe = false) - { - $proforma = $this->toApiArray($invoice); - $client = $this->getBuyer($invoice); - - $buyer = new \Payment_Invoice_Buyer(); - $buyer - ->setEmail($client['email']) - ->setFirstName($client['first_name']) - ->setLastName($client['last_name']) - ->setCompany($client['company']) - ->setAddress($client['address']) - ->setCity($client['city']) - ->setState($client['state']) - ->setZip($client['zip']) - ->setPhone($client['phone']) - ->setPhoneCountryCode($client['phone_cc']) - ->setCountry($client['country']); - - $first_title = null; - $items = []; - foreach ($proforma['lines'] as $item) { - $pi = new \Payment_Invoice_Item(); - $pi - ->setId($item['id']) - ->setTitle($item['title']) - ->setDescription($item['title']) - ->setPrice($item['price']) - ->setTax($item['tax']) - ->setQuantity($item['quantity']); - $items[] = $pi; - if (is_null($first_title) && 1 == count($proforma['lines'])) { - $first_title = $item['title']; - } - } - - $params = [ - ':id' => sprintf('%05s', $proforma['nr']), - ':serie' => $proforma['serie'], - ':title' => $first_title, ]; - if ($first_title) { - $title = __('Payment for invoice :serie:id [:title]', $params); - } else { - $title = __('Payment for invoice :serie:id', $params); - } - - $mpi = new \Payment_Invoice(); - $mpi->setId($invoice->id); - $mpi->setNumber($proforma['nr']); - $mpi->setBuyer($buyer); - $mpi->setCurrency($proforma['currency']); - $mpi->setTitle($title); - $mpi->setItems($items); - - $subscribeService = $this->di['mod_service']('Invoice', 'Subscription'); - // can subscribe only if proforma has one item with defined period - if ($subscribe && $subscribeService->isSubscribable($invoice->id)) { - $subitem = $invoice->InvoiceItem->getFirst(); - $period = $this->di['period']($subitem->period); - - $bs = new \Payment_Invoice_Subscription(); - $bs->setId($proforma['id']); - $bs->setAmount($mpi->getTotalWithTax()); - $bs->setCycle($period->getQty()); - $bs->setUnit($period->getUnit()); - - $mpi->setSubscription($bs); - $mpi->setTitle('Subscription for ' . $subitem->title); - } - - return $mpi; - } - - public function getBuyer(\Model_Invoice $invoice) - { - return [ - 'first_name' => $invoice->buyer_first_name, - 'last_name' => $invoice->buyer_last_name, - 'company' => $invoice->buyer_company, - 'address' => $invoice->buyer_address, - 'city' => $invoice->buyer_city, - 'state' => $invoice->buyer_state, - 'country' => $invoice->buyer_country, - 'phone' => $invoice->buyer_phone, - 'phone_cc' => '', - 'email' => $invoice->buyer_email, - 'zip' => $invoice->buyer_zip, - ]; - } - - public function rmByClient(\Model_Client $client) - { - $invoices = $this->di['db']->find('Invoice', 'client_id = ?', [$client->id]); - foreach ($invoices as $invoice) { - $invoiceItems = $this->di['db']->find('InvoiceItem', 'invoice_id = ?', [$invoice->id]); - - foreach ($invoiceItems as $invoiceItem) { - $this->di['db']->trash($invoiceItem); - } - - $this->di['db']->trash($invoice); - } - } - - /** - * @return bool - */ - public function isInvoiceTypeDeposit(\Model_Invoice $invoice) - { - $invoiceItems = $this->di['db']->find('InvoiceItem', 'invoice_id = ?', [$invoice->id]); - - foreach ($invoiceItems as $item) { - if (\Model_InvoiceItem::TYPE_DEPOSIT == $item->type) { - return true; - } - } - - return false; - } -} diff --git a/src/bb-modules/Invoice/ServiceInvoiceItem.php b/src/bb-modules/Invoice/ServiceInvoiceItem.php index cbdb3ed7a..1eaf6d368 100644 --- a/src/bb-modules/Invoice/ServiceInvoiceItem.php +++ b/src/bb-modules/Invoice/ServiceInvoiceItem.php @@ -1,14 +1,16 @@ di = $di; - } - - /** - * @return mixed - */ - public function getDi() - { - return $this->di; - } - - public function getRedirects() - { - $sql = ' - SELECT id, meta_key as path, meta_value as target - FROM extension_meta - WHERE extension = "mod_redirect" - ORDER BY id ASC - '; - - return $this->di['db']->getAll($sql); - } - - public function getRedirectByPath($path) - { - $sql = ' - SELECT meta_value - FROM extension_meta - WHERE extension = "mod_redirect" - AND meta_key = :path - LIMIT 1 - '; - - return $this->di['db']->getCell($sql, ['path' => $path]); - } -} diff --git a/src/bb-modules/Seo/Service.php b/src/bb-modules/Seo/Service.php index 54e25d7eb..fc9998763 100644 --- a/src/bb-modules/Seo/Service.php +++ b/src/bb-modules/Seo/Service.php @@ -1,11 +1,13 @@ getQuery('_url'); diff --git a/src/install/install.php b/src/install/install.php index 93752a6b3..d115cb8e8 100644 --- a/src/install/install.php +++ b/src/install/install.php @@ -1,12 +1,14 @@