1
0
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:
David Bomba 2020-04-10 15:07:36 +10:00 committed by GitHub
parent 8154796193
commit 563d41c83a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 540 additions and 269 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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()

View File

@ -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()

View File

@ -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;
}

View 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);
}
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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);
}
/**

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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 = [];

View File

@ -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;
}
}

View File

@ -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()

View File

@ -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()

View File

@ -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);

View File

@ -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)
{

View File

@ -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;
}
/**

View File

@ -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'])) {

View File

@ -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 [

View 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
);
}
}
}

View File

@ -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",

View 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);
}
}