mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 05:02:36 +01:00
Email Settings (#3615)
* Working on advanced email settings * working on document storage * Email Documents if they exist * UBL invoices * UBL Invoices * Fixes for tests
This commit is contained in:
parent
8154796193
commit
563d41c83a
@ -1,35 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
use App\Models\Document;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Generate url for the asset.
|
||||
*
|
||||
* @param Document $document
|
||||
* @param boolean $absolute
|
||||
* @return string|null
|
||||
*/
|
||||
function generateUrl(Document $document, $absolute = false)
|
||||
{
|
||||
$url = Storage::disk($document->disk)->url($document->path);
|
||||
|
||||
if ($url && $absolute) {
|
||||
return url($url);
|
||||
}
|
||||
|
||||
if ($url) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
@ -113,6 +113,17 @@ class EmailController extends BaseController
|
||||
$body = $request->input('body');
|
||||
$entity_string = strtolower(class_basename($entity_obj));
|
||||
|
||||
$company = $entity_obj->company;
|
||||
$settings = $company->settings;
|
||||
|
||||
$settings->reply_to_email = "Reply@example.com";
|
||||
$settings->bcc_email = "BCC@example.com";
|
||||
$settings->pdf_email_attachment = true;
|
||||
$settings->ubl_email_attachment = true;
|
||||
|
||||
$company->settings = $settings;
|
||||
$company->save();
|
||||
|
||||
$entity_obj->invitations->each(function ($invitation) use ($subject, $body, $entity_string, $entity_obj) {
|
||||
if ($invitation->contact->send_email && $invitation->contact->email) {
|
||||
$when = now()->addSeconds(1);
|
||||
|
@ -212,8 +212,6 @@ class InvoiceController extends BaseController
|
||||
|
||||
$invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||
|
||||
$invoice = StoreInvoice::dispatchNow($invoice, $request->all(), $invoice->company); //todo potentially this may return mixed ie PDF/$invoice... need to revisit when we implement UI
|
||||
|
||||
event(new InvoiceWasCreated($invoice, $invoice->company));
|
||||
|
||||
return $this->itemResponse($invoice);
|
||||
|
@ -35,9 +35,24 @@ class StoreInvoiceRequest extends Request
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'client_id' => 'required|exists:clients,id,company_id,'.auth()->user()->company()->id,
|
||||
];
|
||||
|
||||
$rules = [];
|
||||
|
||||
if($this->input('documents') && is_array($this->input('documents'))) {
|
||||
$documents = count($this->input('documents'));
|
||||
|
||||
foreach(range(0, $documents) as $index) {
|
||||
$rules['documents.' . $index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
||||
}
|
||||
}
|
||||
elseif($this->input('documents')){
|
||||
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
||||
}
|
||||
|
||||
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;
|
||||
|
||||
return $rules;
|
||||
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
|
@ -38,11 +38,22 @@ class UpdateInvoiceRequest extends Request
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx',
|
||||
//'client_id' => 'required|integer',
|
||||
//'invoice_type_id' => 'integer',
|
||||
];
|
||||
\Log::error(print_r($this->all(),1));
|
||||
|
||||
$rules = [];
|
||||
|
||||
if($this->input('documents') && is_array($this->input('documents'))) {
|
||||
$documents = count($this->input('documents'));
|
||||
|
||||
foreach(range(0, $documents) as $index) {
|
||||
$rules['documents.' . $index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
||||
}
|
||||
}
|
||||
elseif($this->input('documents')){
|
||||
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
|
@ -74,21 +74,19 @@ class CreateInvoicePdf implements ShouldQueue
|
||||
|
||||
$file_path = $path . $this->invoice->number . '.pdf';
|
||||
|
||||
$design = Design::find($this->decodePrimaryKey($this->invoice->client->getSetting('invoice_design_id')));
|
||||
$design = Design::find($this->decodePrimaryKey($this->invoice->client->getSetting('invoice_design_id')));
|
||||
|
||||
$designer = new Designer($this->invoice, $design, $this->invoice->client->getSetting('pdf_variables'), 'invoice');
|
||||
$designer = new Designer($this->invoice, $design, $this->invoice->client->getSetting('pdf_variables'), 'invoice');
|
||||
|
||||
//get invoice design
|
||||
//$html = $this->generateInvoiceHtml($designer->build()->getHtml(), $this->invoice, $this->contact);
|
||||
$html = $this->generateEntityHtml($designer, $this->invoice, $this->contact);
|
||||
|
||||
$html = $this->generateEntityHtml($designer, $this->invoice, $this->contact);
|
||||
|
||||
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
|
||||
Storage::makeDirectory($path, 0755);
|
||||
|
||||
$pdf = $this->makePdf(null, null, $html);
|
||||
$pdf = $this->makePdf(null, null, $html);
|
||||
|
||||
$instance = Storage::disk($this->disk)->put($file_path, $pdf);
|
||||
$instance = Storage::disk($this->disk)->put($file_path, $pdf);
|
||||
|
||||
return $file_path;
|
||||
}
|
||||
|
318
app/Jobs/Invoice/CreateUbl.php
Normal file
318
app/Jobs/Invoice/CreateUbl.php
Normal file
@ -0,0 +1,318 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Invoice;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use CleverIt\UBL\Invoice\Address;
|
||||
use CleverIt\UBL\Invoice\Contact;
|
||||
use CleverIt\UBL\Invoice\Country;
|
||||
use CleverIt\UBL\Invoice\Generator;
|
||||
use CleverIt\UBL\Invoice\Invoice as UBLInvoice;
|
||||
use CleverIt\UBL\Invoice\InvoiceLine;
|
||||
use CleverIt\UBL\Invoice\Item;
|
||||
use CleverIt\UBL\Invoice\LegalMonetaryTotal;
|
||||
use CleverIt\UBL\Invoice\Party;
|
||||
use CleverIt\UBL\Invoice\TaxCategory;
|
||||
use CleverIt\UBL\Invoice\TaxScheme;
|
||||
use CleverIt\UBL\Invoice\TaxSubTotal;
|
||||
use CleverIt\UBL\Invoice\TaxTotal;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CreateUbl implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
const INVOICE_TYPE_STANDARD = 380;
|
||||
const INVOICE_TYPE_CREDIT = 381;
|
||||
|
||||
public $invoice;
|
||||
|
||||
public function __construct(Invoice $invoice)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$invoice = $this->invoice;
|
||||
$company = $invoice->company;
|
||||
$client = $invoice->client;
|
||||
$ubl_invoice = new UBLInvoice();
|
||||
|
||||
// invoice
|
||||
$ubl_invoice->setId($invoice->number);
|
||||
$ubl_invoice->setIssueDate(date_create($invoice->date));
|
||||
$ubl_invoice->setInvoiceTypeCode($invoice->amount < 0 ? self::INVOICE_TYPE_CREDIT : self::INVOICE_TYPE_STANDARD);
|
||||
|
||||
$supplier_party = $this->createParty($company, $invoice->user);
|
||||
$ubl_invoice->setAccountingSupplierParty($supplier_party);
|
||||
|
||||
$customer_party = $this->createParty($client, $client->contacts[0]);
|
||||
$ubl_invoice->setAccountingCustomerParty($customer_party);
|
||||
|
||||
// line items
|
||||
$invoice_lines = [];
|
||||
$taxable = $this->getTaxable();
|
||||
|
||||
foreach ($invoice->line_items as $index => $item) {
|
||||
$itemTaxable = $this->getItemTaxable($item, $taxable);
|
||||
$invoice_lines[] = $this->createInvoiceLine($index, $item, $itemTaxable);
|
||||
}
|
||||
|
||||
$ubl_invoice->setInvoiceLines($invoice_lines);
|
||||
|
||||
$taxtotal = new TaxTotal();
|
||||
$taxAmount1 = $taxAmount2 = 0;
|
||||
|
||||
$taxAmount1 = $this->createTaxRate($taxtotal, $taxable, $invoice->tax_rate1, $invoice->tax_name1);
|
||||
if ($invoice->tax_name2 || floatval($invoice->tax_rate2)) {
|
||||
$taxAmount2 = $this->createTaxRate($taxtotal, $taxable, $invoice->tax_rate2, $invoice->tax_name2);
|
||||
}
|
||||
|
||||
$taxtotal->setTaxAmount($taxAmount1 + $taxAmount2);
|
||||
$ubl_invoice->setTaxTotal($taxtotal);
|
||||
|
||||
$ubl_invoice->setLegalMonetaryTotal((new LegalMonetaryTotal())
|
||||
//->setLineExtensionAmount()
|
||||
->setTaxExclusiveAmount($taxable)
|
||||
->setPayableAmount($invoice->balance));
|
||||
|
||||
try {
|
||||
return Generator::invoice($ubl_invoice, $invoice->client->getCurrencyCode());
|
||||
} catch (\Exception $exception) {
|
||||
info(print_r($exception,1));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function createParty($company, $user)
|
||||
{
|
||||
$party = new Party();
|
||||
$party->setName($company->present()->name);
|
||||
$address = (new Address())
|
||||
->setCityName($company->city)
|
||||
->setStreetName($company->address1)
|
||||
->setBuildingNumber($company->address2)
|
||||
->setPostalZone($company->postal_code);
|
||||
|
||||
if ($company->country_id) {
|
||||
$country = new Country();
|
||||
$country->setIdentificationCode($company->country->iso_3166_2);
|
||||
$address->setCountry($country);
|
||||
}
|
||||
|
||||
$party->setPostalAddress($address);
|
||||
$party->setPhysicalLocation($address);
|
||||
|
||||
$contact = new Contact();
|
||||
$contact->setElectronicMail($user->email);
|
||||
$party->setContact($contact);
|
||||
|
||||
return $party;
|
||||
}
|
||||
|
||||
private function createInvoiceLine($index, $item, $taxable)
|
||||
{
|
||||
$invoiceLine = (new InvoiceLine())
|
||||
->setId($index + 1)
|
||||
->setInvoicedQuantity($item->quantity)
|
||||
->setLineExtensionAmount($this->costWithDiscount($item))
|
||||
->setItem((new Item())
|
||||
->setName($item->product_key)
|
||||
->setDescription($item->notes));
|
||||
//->setSellersItemIdentification("1ABCD"));
|
||||
|
||||
$taxtotal = new TaxTotal();
|
||||
$itemTaxAmount1 = $itemTaxAmount2 = $itemTaxAmount3 = 0;
|
||||
|
||||
$itemTaxAmount1 = $this->createTaxRate($taxtotal, $taxable, $item->tax_rate1, $item->tax_name1);
|
||||
|
||||
if ($item->tax_name2 || floatval($item->tax_rate2)) {
|
||||
$itemTaxAmount2 = $this->createTaxRate($taxtotal, $taxable, $item->tax_rate2, $item->tax_name2);
|
||||
}
|
||||
|
||||
if($item->tax_name3 || floatval($item->tax_rate3)){
|
||||
$itemTaxAmount3 = $this->createTaxRate($taxtotal, $taxable, $item->tax_rate3, $item->tax_name3);
|
||||
}
|
||||
|
||||
$taxtotal->setTaxAmount($itemTaxAmount1 + $itemTaxAmount2 + $itemTaxAmount3);
|
||||
$invoiceLine->setTaxTotal($taxtotal);
|
||||
|
||||
return $invoiceLine;
|
||||
}
|
||||
|
||||
private function createTaxRate(&$taxtotal, $taxable, $taxRate, $taxName)
|
||||
{
|
||||
$invoice = $this->invoice;
|
||||
$taxAmount = $this->taxAmount($taxable, $taxRate);
|
||||
$taxScheme = ((new TaxScheme()))->setId($taxName);
|
||||
|
||||
$taxtotal->addTaxSubTotal((new TaxSubTotal())
|
||||
->setTaxAmount($taxAmount)
|
||||
->setTaxableAmount($taxable)
|
||||
->setTaxCategory((new TaxCategory())
|
||||
->setId($taxName)
|
||||
->setName($taxName)
|
||||
->setTaxScheme($taxScheme)
|
||||
->setPercent($taxRate)));
|
||||
|
||||
return $taxAmount;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param $invoiceItem
|
||||
* @param $invoiceTotal
|
||||
*
|
||||
* @return float|int
|
||||
*/
|
||||
private function getItemTaxable($item, $invoice_total)
|
||||
{
|
||||
$total = $item->quantity * $item->cost;
|
||||
|
||||
if ($this->invoice->discount != 0) {
|
||||
if ($this->invoice->is_amount_discount) {
|
||||
if ($invoice_total + $this->invoice->discount != 0) {
|
||||
$total -= $invoice_total ? ($total / ($invoice_total + $this->invoice->discount) * $this->invoice->discount) : 0;
|
||||
}
|
||||
} else {
|
||||
$total *= (100 - $this->invoice->discount) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
if ($item->discount != 0) {
|
||||
if ($this->invoice->is_amount_discount) {
|
||||
$total -= $item->discount;
|
||||
} else {
|
||||
$total -= $total * $item->discount / 100;
|
||||
}
|
||||
}
|
||||
|
||||
return round($total, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|int|mixed
|
||||
*/
|
||||
private function getTaxable()
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
foreach ($this->invoice->line_items as $item) {
|
||||
$line_total = $item->quantity * $item->cost;
|
||||
|
||||
if ($item->discount != 0) {
|
||||
if ($this->invoice->is_amount_discount) {
|
||||
$line_total -= $item->discount;
|
||||
} else {
|
||||
$line_total -= $line_total * $item->discount / 100;
|
||||
}
|
||||
}
|
||||
|
||||
$total += $line_total;
|
||||
}
|
||||
|
||||
if ($this->invoice->discount > 0) {
|
||||
if ($this->invoice->is_amount_discount) {
|
||||
$total -= $this->invoice->discount;
|
||||
} else {
|
||||
$total *= (100 - $this->invoice->discount) / 100;
|
||||
$total = round($total, 2);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->invoice->custom_surcharge1 && $this->invoice->custom_surcharge_tax1)
|
||||
$total += $this->invoice->custom_surcharge1;
|
||||
|
||||
|
||||
if ($this->invoice->custom_surcharge2 && $this->invoice->custom_surcharge_tax2)
|
||||
$total += $this->invoice->custom_surcharge2;
|
||||
|
||||
|
||||
if ($this->invoice->custom_surcharge3 && $this->invoice->custom_surcharge_tax3)
|
||||
$total += $this->invoice->custom_surcharge3;
|
||||
|
||||
|
||||
if ($this->invoice->custom_surcharge4 && $this->invoice->custom_surcharge_tax4)
|
||||
$total += $this->invoice->custom_surcharge4;
|
||||
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
public function costWithDiscount($item)
|
||||
{
|
||||
$cost = $item->cost;
|
||||
|
||||
if ($item->discount != 0) {
|
||||
if ($this->invoice->is_amount_discount) {
|
||||
$cost -= $item->discount / $item->quantity;
|
||||
} else {
|
||||
$cost -= $cost * $item->discount / 100;
|
||||
}
|
||||
}
|
||||
|
||||
return $cost;
|
||||
}
|
||||
|
||||
public function taxAmount($taxable, $rate)
|
||||
{
|
||||
if ($this->invoice->uses_inclusive_taxes) {
|
||||
return round($taxable - ($taxable / (1 + ($rate / 100))), 2);
|
||||
} else {
|
||||
return round($taxable * ($rate / 100), 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Invoice;
|
||||
|
||||
use App\Models\Invoice;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceNotification implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $invoice;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Invoice $invoice)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
//notification for the invoice.
|
||||
//
|
||||
//could mean a email, sms, slack, push
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Invoice;
|
||||
|
||||
use App\Jobs\Invoice\InvoiceNotification;
|
||||
use App\Jobs\Payment\PaymentNotification;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class StoreInvoice implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $invoice;
|
||||
|
||||
protected $data;
|
||||
|
||||
private $company;
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Invoice $invoice, array $data, Company $company)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
|
||||
$this->data = $data;
|
||||
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* We expect the Invoice object along with
|
||||
* the request in array form
|
||||
*
|
||||
* Embedded in the request may be additional
|
||||
* attributes which require additional work to be
|
||||
* done in this job, these include - but are not limited to:
|
||||
*
|
||||
* 1. email_invoice - Email the Invoice
|
||||
* 2. mark_paid - Mark the invoice as paid (Generates a payment against the invoice)
|
||||
* 3. ......
|
||||
*
|
||||
* @return NULL|Invoice
|
||||
*/
|
||||
public function handle(InvoiceRepository $invoice_repo) : ?Invoice
|
||||
{
|
||||
$payment = false;
|
||||
|
||||
// /* Test if we should auto-bill the invoice */
|
||||
// if(property_exists($this->invoice->client->getSetting('auto_bill')) && (bool)$this->invoice->client->getSetting('auto_bill'))
|
||||
// {
|
||||
|
||||
// $this->invoice = $invoice_repo->markSent($this->invoice);
|
||||
|
||||
// //fire autobill - todo - the PAYMENT class will update the INVOICE status.
|
||||
// // $payment =
|
||||
|
||||
// }
|
||||
|
||||
if (isset($this->data['email_invoice']) && (bool)$this->data['email_invoice']) {
|
||||
$this->invoice = $invoice_repo->markSent($this->invoice);
|
||||
|
||||
//fire invoice job (the job performs the filtering logic of the email recipients... if any.)
|
||||
InvoiceNotification::dispatch($invoice, $invoice->company);
|
||||
}
|
||||
|
||||
if (isset($this->data['mark_paid']) && (bool)$this->data['mark_paid']) {
|
||||
$this->invoice = $invoice_repo->markSent($this->invoice);
|
||||
|
||||
// generate a manual payment against the invoice
|
||||
// the PAYMENT class will update the INVOICE status.
|
||||
//$payment =
|
||||
}
|
||||
|
||||
/* Payment Notifications */
|
||||
if ($payment) {
|
||||
//fire payment notifications here
|
||||
PaymentNotification::dispatch($payment, $payment->company);
|
||||
}
|
||||
|
||||
if (isset($data['download_invoice']) && (bool)$this->data['download_invoice']) {
|
||||
//fire invoice download and return PDF response from here
|
||||
}
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
}
|
@ -56,7 +56,7 @@ class UploadFile implements ShouldQueue
|
||||
$this->entity = $entity;
|
||||
$this->disk = $disk ?? config('filesystems.default');
|
||||
|
||||
MultiDB::setDB($this->company->db);
|
||||
//MultiDB::setDB($this->company->db);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,12 +168,12 @@ class Account extends BaseModel
|
||||
$planDetails = $this->getPlanDetails(false, false);
|
||||
}
|
||||
|
||||
return $selfHost || ! empty($planDetails) && ($planDetails['plan'] == self::PLAN_ENTERPRISE);
|
||||
return $self_host || ! empty($planDetails) && ($planDetails['plan'] == self::PLAN_ENTERPRISE);
|
||||
|
||||
// Enterprise; No Trial allowed
|
||||
case self::FEATURE_DOCUMENTS:
|
||||
case self::FEATURE_USER_PERMISSIONS:
|
||||
return $selfHost || ! empty($planDetails) && $planDetails['plan'] == self::PLAN_ENTERPRISE && ! $planDetails['trial'];
|
||||
return $self_host || ! empty($planDetails) && $planDetails['plan'] == self::PLAN_ENTERPRISE && ! $planDetails['trial'];
|
||||
|
||||
default:
|
||||
return false;
|
||||
|
@ -173,4 +173,12 @@ class BaseModel extends Model
|
||||
|
||||
return new Designer($this, $design, $this->client->getSetting('pdf_variables'), $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFileName($extension = 'pdf')
|
||||
{
|
||||
return $this->number . '.' . $extension;
|
||||
}
|
||||
}
|
||||
|
@ -436,10 +436,6 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
return $languages->filter(function ($item) {
|
||||
return $item->id == $this->getSetting('language_id');
|
||||
})->first()->locale;
|
||||
|
||||
//$lang = Language::find($this->client->getSetting('language_id'));
|
||||
|
||||
//return $lang->locale;
|
||||
}
|
||||
|
||||
public function invoice_filepath()
|
||||
@ -462,6 +458,11 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
return $this->company->company_key . '/';
|
||||
}
|
||||
|
||||
public function document_filepath()
|
||||
{
|
||||
return $this->company->company_key . '/documents/';
|
||||
}
|
||||
|
||||
public function setCompanyDefaults($data, $entity_name) :array
|
||||
{
|
||||
$defaults = [];
|
||||
|
@ -13,6 +13,7 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class Document extends BaseModel
|
||||
{
|
||||
@ -92,4 +93,21 @@ class Document extends BaseModel
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
function generateUrl($absolute = false)
|
||||
{
|
||||
$url = Storage::disk($this->disk)->url($this->path);
|
||||
|
||||
if ($url && $absolute) {
|
||||
return url($url);
|
||||
}
|
||||
|
||||
if ($url) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -118,18 +118,6 @@ class EntitySentNotification extends Notification implements ShouldQueue
|
||||
$logo = $this->invitation->company->present()->logo();
|
||||
$amount = Number::formatMoney($this->entity->amount, $this->entity->client);
|
||||
|
||||
// return (new SlackMessage)
|
||||
// ->success()
|
||||
// ->from(ctrans('texts.notification_bot'))
|
||||
// ->image($logo)
|
||||
// ->content(ctrans('texts.notification_invoice_sent',
|
||||
// [
|
||||
// 'amount' => $amount,
|
||||
// 'client' => $this->contact->present()->name(),
|
||||
// 'invoice' => $this->invoice->number
|
||||
// ]));
|
||||
|
||||
|
||||
return (new SlackMessage)
|
||||
->from(ctrans('texts.notification_bot'))
|
||||
->success()
|
||||
|
@ -112,18 +112,6 @@ class InvoiceSentNotification extends Notification implements ShouldQueue
|
||||
$logo = $this->company->present()->logo();
|
||||
$amount = Number::formatMoney($this->invoice->amount, $this->invoice->client);
|
||||
|
||||
// return (new SlackMessage)
|
||||
// ->success()
|
||||
// ->from(ctrans('texts.notification_bot'))
|
||||
// ->image($logo)
|
||||
// ->content(ctrans('texts.notification_invoice_sent',
|
||||
// [
|
||||
// 'amount' => $amount,
|
||||
// 'client' => $this->contact->present()->name(),
|
||||
// 'invoice' => $this->invoice->number
|
||||
// ]));
|
||||
|
||||
|
||||
return (new SlackMessage)
|
||||
->from(ctrans('texts.notification_bot'))
|
||||
->success()
|
||||
|
@ -87,15 +87,6 @@ class InvoiceViewedNotification extends Notification implements ShouldQueue
|
||||
'logo' => $this->company->present()->logo(),
|
||||
];
|
||||
|
||||
// if($this->settings->email_style == 'custom'){
|
||||
|
||||
// $subject =
|
||||
|
||||
// return (new MailMessage)
|
||||
// ->subject($subject)
|
||||
// ->view('email.template.custom', ['body' => ]);
|
||||
// }
|
||||
|
||||
return (new MailMessage)
|
||||
->subject($subject)
|
||||
->markdown('email.admin.generic', $data);
|
||||
|
@ -48,10 +48,6 @@ class NewPaymentNotification extends Notification implements ShouldQueue
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
// public function via($notifiable)
|
||||
// {
|
||||
// return $this->is_system ? ['slack'] : ['mail'];
|
||||
//}
|
||||
|
||||
public function via($notifiable)
|
||||
{
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Jobs\Invoice\CreateUbl;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@ -36,7 +38,7 @@ class SendGenericNotification extends Notification implements ShouldQueue
|
||||
|
||||
protected $settings;
|
||||
|
||||
public $is_system;
|
||||
public $is_system;
|
||||
|
||||
protected $body;
|
||||
|
||||
@ -92,9 +94,30 @@ class SendGenericNotification extends Notification implements ShouldQueue
|
||||
|
||||
];
|
||||
|
||||
return (new MailMessage)
|
||||
$mail_message = (new MailMessage)
|
||||
->subject($subject)
|
||||
->markdown('email.admin.generic_email', $data);
|
||||
|
||||
if(strlen($this->settings->reply_to_email) > 1)
|
||||
$mail_message->replyTo($this->settings->reply_to_email);
|
||||
|
||||
if(strlen($this->settings->bcc_email) > 1)
|
||||
$mail_message->bcc($this->settings->bcc_email);
|
||||
|
||||
if($this->settings->pdf_email_attachment)
|
||||
$mail_message->attach(public_path($this->entity->pdf_file_path()));
|
||||
|
||||
foreach($this->entity->documents as $document){
|
||||
$mail_message->attach($document->generateUrl(), ['as' => $document->name]);
|
||||
}
|
||||
|
||||
if($this->entity instanceof Invoice && $this->settings->ubl_email_attachment){
|
||||
$ubl_string = CreateUbl::dispatchNow($this->entity);
|
||||
$mail_message->attachData($ubl_string, $this->entity->getFileName('xml'));
|
||||
}
|
||||
|
||||
return $mail_message;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,6 +21,7 @@ use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\SavesDocuments;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
@ -29,6 +30,8 @@ use ReflectionClass;
|
||||
class BaseRepository
|
||||
{
|
||||
use MakesHash;
|
||||
use SavesDocuments;
|
||||
|
||||
/**
|
||||
* @return null
|
||||
*/
|
||||
@ -221,10 +224,12 @@ class BaseRepository
|
||||
unset($tmp_data['client_contacts']);
|
||||
}
|
||||
|
||||
|
||||
$model->fill($tmp_data);
|
||||
$model->save();
|
||||
|
||||
if(array_key_exists('documents', $data))
|
||||
$this->saveDocuments($data['documents'], $model);
|
||||
|
||||
$invitation_factory_class = sprintf("App\\Factory\\%sInvitationFactory", $resource);
|
||||
|
||||
if (isset($data['client_contacts'])) {
|
||||
|
@ -11,8 +11,10 @@
|
||||
|
||||
namespace App\Transformers;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Transformers\DocumentTransformer;
|
||||
use App\Transformers\InvoiceInvitationTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
@ -21,7 +23,8 @@ class InvoiceTransformer extends EntityTransformer
|
||||
use MakesHash;
|
||||
|
||||
protected $defaultIncludes = [
|
||||
'invitations'
|
||||
'invitations',
|
||||
'documents',
|
||||
];
|
||||
|
||||
protected $availableIncludes = [
|
||||
@ -67,18 +70,18 @@ class InvoiceTransformer extends EntityTransformer
|
||||
|
||||
return $this->includeCollection($invoice->expenses, $transformer, ENTITY_EXPENSE);
|
||||
}
|
||||
|
||||
public function includeDocuments(Invoice $invoice)
|
||||
{
|
||||
$transformer = new DocumentTransformer($this->account, $this->serializer);
|
||||
|
||||
$invoice->documents->each(function ($document) use ($invoice) {
|
||||
$document->setRelation('invoice', $invoice);
|
||||
});
|
||||
|
||||
return $this->includeCollection($invoice->documents, $transformer, ENTITY_DOCUMENT);
|
||||
}
|
||||
*/
|
||||
public function includeDocuments(Invoice $invoice)
|
||||
{
|
||||
$transformer = new DocumentTransformer($this->serializer);
|
||||
|
||||
// $invoice->documents->each(function ($document) use ($invoice) {
|
||||
// $document->setRelation('invoice', $invoice);
|
||||
// });
|
||||
|
||||
return $this->includeCollection($invoice->documents, $transformer, Document::class);
|
||||
}
|
||||
|
||||
public function transform(Invoice $invoice)
|
||||
{
|
||||
return [
|
||||
|
49
app/Utils/Traits/SavesDocuments.php
Normal file
49
app/Utils/Traits/SavesDocuments.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Utils\Traits;
|
||||
|
||||
use App\Jobs\Util\UploadFile;
|
||||
use App\Models\Account;
|
||||
use App\Models\Company;
|
||||
|
||||
trait SavesDocuments
|
||||
{
|
||||
|
||||
public function saveDocuments($document_array, $entity)
|
||||
{
|
||||
|
||||
if($entity instanceof Company){
|
||||
$account = $entity->account;
|
||||
$company = $entity;
|
||||
}
|
||||
else{
|
||||
$account = $entity->company->account;
|
||||
$company = $entity->company;
|
||||
}
|
||||
|
||||
if(!$account->hasFeature(Account::FEATURE_DOCUMENTS))
|
||||
return false;
|
||||
|
||||
foreach($document_array as $document) {
|
||||
|
||||
$document = UploadFile::dispatchNow(
|
||||
$document,
|
||||
UploadFile::DOCUMENT,
|
||||
$entity->user,
|
||||
$entity->company,
|
||||
$entity
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@
|
||||
"php": ">=7.3",
|
||||
"ext-json": "*",
|
||||
"asgrim/ofxparser": "^1.2",
|
||||
"cleverit/ubl_invoice": "^1.3",
|
||||
"codedge/laravel-selfupdater": "2.5.1",
|
||||
"composer/composer": "^1.10",
|
||||
"dacastro4/laravel-gmail": "^3.2",
|
||||
|
42
tests/Unit/UBLInvoiceTest.php
Normal file
42
tests/Unit/UBLInvoiceTest.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Helpers\Invoice\InvoiceSumInclusive;
|
||||
use App\Jobs\Invoice\CreateUbl;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @covers App\Jobs\Invoice\CreateUbl
|
||||
*/
|
||||
class UBLInvoiceTest extends TestCase
|
||||
{
|
||||
use MockAccountData;
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
$this->makeTestData();
|
||||
|
||||
}
|
||||
|
||||
public function testUblGenerates()
|
||||
{
|
||||
$ubl = CreateUbl::dispatchNow($this->invoice);
|
||||
|
||||
$this->assertNotNull($ubl);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user