Merge remote-tracking branch 'upstream/v5-develop' into v5-2701-dynamic-dates-for-recurring

This commit is contained in:
Benjamin Beganović 2021-02-22 13:15:37 +01:00
commit 8e3875da96
327 changed files with 150399 additions and 142852 deletions

View File

@ -39,7 +39,7 @@ jobs:
mariadb:
image: mariadb:latest
ports:
- 3306
- 32768:3306
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_USER: ninja
@ -52,7 +52,6 @@ jobs:
- name: Start mysql service
run: |
sudo /etc/init.d/mysql start
- name: Verify MariaDB connection
env:
DB_PORT: ${{ job.services.mariadb.ports[3306] }}
@ -62,7 +61,6 @@ jobs:
while ! mysqladmin ping -h"127.0.0.1" -P"$DB_PORT" --silent; do
sleep 1
done
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
@ -77,33 +75,27 @@ jobs:
- name: Copy .env
run: |
cp .env.ci .env
- name: Install composer dependencies
run: |
composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }}
composer install
- name: Prepare Laravel Application
run: |
php artisan key:generate
php artisan optimize
php artisan cache:clear
php artisan config:cache
- name: Create DB and schemas
run: |
mkdir -p database
touch database/database.sqlite
- name: Migrate Database
run: |
php artisan migrate:fresh --seed --force && php artisan db:seed --force
- name: Prepare JS/CSS assets
run: |
npm i
npm run production
- name: Run Testsuite
run: |
cat .env
@ -114,4 +106,3 @@ jobs:
- name: Run php-cs-fixer
run: |
vendor/bin/php-cs-fixer fix

View File

@ -4,12 +4,18 @@
![v5-develop phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-develop)
![v5-stable phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-stable)
[![codecov](https://codecov.io/gh/invoiceninja/invoiceninja/branch/v2/graph/badge.svg)](https://codecov.io/gh/invoiceninja/invoiceninja)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d39acb4bf0f74a0698dc77f382769ba5)](https://www.codacy.com/app/turbo124/invoiceninja?utm_source=github.com&utm_medium=referral&utm_content=invoiceninja/invoiceninja&utm_campaign=Badge_Grade)
# Invoice Ninja version 5 is in Beta!
# Invoice Ninja version 5.1 RC2!
We will be using the lessons learnt in Invoice Ninja 4.0 to build a bigger better platform to work from. If you would like to contribute to the project we will gladly accept contributions for code, user guides, bug tracking and feedback! Please consider the following guidelines prior to submitting a pull request:
Invoice Ninja version 5.1 has now reached Release Candidate 2!
What does this mean exactly? We consider this version _almost_ stable. There may be some remaining small issues which we would love to get feedback on. We would really appreciate the community booting up this version and attempting the migration from their Invoice Ninja V4 application and inspect the migrated data.
We'd also like feedback on any issues that you can see, and help us nail down the few remaining issues before Version 5 graduates to Stable Gold Release.
Please note we do not consider this version ready for production use, please stick with your V4 installation for your production clients!
## Quick Start
@ -17,13 +23,10 @@ Currently the client portal and API are of alpha quality, to get started:
```bash
git clone https://github.com/invoiceninja/invoiceninja.git
git checkout v2
git checkout v5-stable
cp .env.example .env
cp .env.dusk.example .env.dusk.local
php artisan key:generate
composer update
npm i
npm run production
```
Please Note: Your APP_KEY in the .env file is used to encrypt data, if you lose this you will not be able to run the application.
@ -33,7 +36,7 @@ Run if you want to load sample data, remember to configure .env
php artisan migrate:fresh --seed && php artisan db:seed && php artisan ninja:create-test-data
```
To Run the web server
To run the web server
```
php artisan serve
```

View File

@ -1 +1 @@
5.0.54
5.1.7

View File

@ -73,6 +73,7 @@ class CheckData extends Command
protected $description = 'Check/fix data';
protected $log = '';
protected $isValid = true;
public function handle()
@ -90,13 +91,10 @@ class CheckData extends Command
$this->checkContacts();
$this->checkCompanyData();
//$this->checkLogoFiles();
if (! $this->option('client_id')) {
$this->checkOAuth();
//$this->checkInvitations();
$this->checkFailedJobs();
//$this->checkFailedJobs();
}
$this->logMessage('Done: '.strtoupper($this->isValid ? Account::RESULT_SUCCESS : Account::RESULT_FAILURE));
@ -289,30 +287,6 @@ class CheckData extends Command
}
}
private function checkInvoiceBalances()
{
$wrong_balances = 0;
$wrong_paid_to_dates = 0;
foreach (Client::where('is_deleted', 0)->cursor() as $client) {
$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
$credit_balance = $client->credits->where('is_deleted', false)->sum('balance');
// $invoice_balance -= $credit_balance;//doesn't make sense to remove the credit amount
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
if ($ledger && number_format($invoice_balance, 4) != number_format($client->balance, 4)) {
$wrong_balances++;
$this->logMessage("# {$client->id} " . $client->present()->name.' - '.$client->number." - Balance Failure - Invoice Balances = {$invoice_balance} Client Balance = {$client->balance} Ledger Balance = {$ledger->balance}");
$this->isValid = false;
}
}
$this->logMessage("{$wrong_balances} clients with incorrect balances");
}
private function checkPaidToDates()
{
$wrong_paid_to_dates = 0;
@ -322,8 +296,6 @@ class CheckData extends Command
$total_invoice_payments = 0;
foreach ($client->invoices->where('is_deleted', false)->where('status_id', '>', 1) as $invoice) {
// $total_amount = $invoice->payments->whereNull('deleted_at')->sum('pivot.amount');
// $total_refund = $invoice->payments->whereNull('deleted_at')->sum('pivot.refunded');
$total_amount = $invoice->payments->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->sum('pivot.amount');
$total_refund = $invoice->payments->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->sum('pivot.refunded');
@ -331,15 +303,15 @@ class CheckData extends Command
$total_invoice_payments += ($total_amount - $total_refund);
}
// 10/02/21
foreach ($client->payments as $payment) {
$credit_total_applied += $payment->paymentables->where('paymentable_type', App\Models\Credit::class)->sum(DB::raw('amount'));
}
if ($credit_total_applied < 0) {
$total_invoice_payments += $credit_total_applied;
} //todo this is contentious
}
nlog("total invoice payments = {$total_invoice_payments} with client paid to date of of {$client->paid_to_date}");
if (round($total_invoice_payments, 2) != round($client->paid_to_date, 2)) {
$wrong_paid_to_dates++;
@ -390,7 +362,9 @@ class CheckData extends Command
$invoice_balance = Invoice::where('client_id', $client->id)->where('is_deleted', false)->where('status_id', '>', 1)->withTrashed()->sum('balance');
$credit_balance = Credit::where('client_id', $client->id)->where('is_deleted', false)->withTrashed()->sum('balance');
// $invoice_balance -= $credit_balance;
/*Legacy - V4 will add credits to the balance - we may need to reverse engineer this and remove the credits from the client balance otherwise we need this hack here and in the invoice balance check.*/
if($client->balance != $invoice_balance)
$invoice_balance -= $credit_balance;
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
@ -405,6 +379,32 @@ class CheckData extends Command
$this->logMessage("{$wrong_paid_to_dates} clients with incorrect client balances");
}
private function checkInvoiceBalances()
{
$wrong_balances = 0;
$wrong_paid_to_dates = 0;
foreach (Client::where('is_deleted', 0)->cursor() as $client) {
$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
$credit_balance = $client->credits->where('is_deleted', false)->sum('balance');
// if($client->balance != $invoice_balance)
// $invoice_balance -= $credit_balance;//doesn't make sense to remove the credit amount
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
if ($ledger && number_format($invoice_balance, 4) != number_format($client->balance, 4)) {
$wrong_balances++;
$this->logMessage("# {$client->id} " . $client->present()->name.' - '.$client->number." - Balance Failure - Invoice Balances = {$invoice_balance} Client Balance = {$client->balance} Ledger Balance = {$ledger->balance}");
$this->isValid = false;
}
}
$this->logMessage("{$wrong_balances} clients with incorrect balances");
}
private function checkLogoFiles()
{
// $accounts = DB::table('accounts')

View File

@ -120,7 +120,7 @@ class CreateSingleAccount extends Command
$company_token->company_id = $company->id;
$company_token->account_id = $account->id;
$company_token->name = 'test token';
$company_token->token = Str::random(64);
$company_token->token = 'company-token-test';
$company_token->is_system = true;
$company_token->save();

View File

@ -339,7 +339,7 @@ class DemoMode extends Command
]);
$vendor->id_number = $this->getNextVendorNumber($vendor);
$vendor->number = $this->getNextVendorNumber($vendor);
$vendor->save();
}

View File

@ -11,6 +11,7 @@
namespace App\Console\Commands;
use App\Libraries\MultiDB;
use App\Models\Design;
use Illuminate\Console\Command;
use stdClass;
@ -47,6 +48,30 @@ class DesignUpdate extends Command
* @return mixed
*/
public function handle()
{
//always return state to first DB
$current_db = config('database.default');
if (! config('ninja.db.multi_db_enabled')) {
$this->handleOnDb();
} else {
//multiDB environment, need to
foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db);
$this->handleOnDb($db);
}
MultiDB::setDB($current_db);
}
}
private function handleOnDb()
{
foreach (Design::whereIsCustom(false)->get() as $design) {
$invoice_design = new \App\Services\PdfMaker\Design(strtolower($design->name));

View File

@ -80,6 +80,8 @@ class ImportMigrations extends Command
$path = $this->option('path') ?? public_path('storage/migrations/import');
nlog(public_path('storage/migrations/import'));
$directory = new DirectoryIterator($path);
foreach ($directory as $file) {

View File

@ -0,0 +1,66 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Console\Commands;
use App;
use App\Jobs\Ninja\CheckCompanyData;
use App\Models\Account;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\CompanyLedger;
use App\Models\Contact;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Payment;
use App\Utils\Ninja;
use DB;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Mail;
use Symfony\Component\Console\Input\InputOption;
/**
* Class CheckData.
*/
class ParallelCheckData extends Command
{
/**
* @var string
*/
protected $name = 'ninja:pcheck-data';
/**
* @var string
*/
protected $description = 'Check company data in parallel';
protected $log = '';
protected $isValid = true;
public function handle()
{
$hash = Str::random(32);
Company::cursor()->each(function ($company) use ($hash){
CheckCompanyData::dispatch($company, $hash)->onQueue('checkdata');
});
}
}

View File

@ -53,6 +53,7 @@ class PostUpdate extends Command
nlog("finished migrating");
exec('vendor/bin/composer install --no-dev');
exec('vendor/bin/composer dump');
nlog("finished running composer install ");

View File

@ -13,6 +13,7 @@ namespace App\Console\Commands;
use App\Jobs\Ninja\SendReminders;
use App\Jobs\Util\WebHookHandler;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use App\Models\Quote;
use App\Models\Webhook;
@ -58,6 +59,24 @@ class SendRemindersCron extends Command
}
private function webHookOverdueInvoices()
{
if (! config('ninja.db.multi_db_enabled')) {
$this->executeWebhooks();
} else {
//multiDB environment, need to
foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db);
$this->executeWebhooks();
}
}
}
private function executeWebhooks()
{
$invoices = Invoice::where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
@ -68,10 +87,7 @@ class SendRemindersCron extends Command
$invoices->each(function ($invoice) {
WebHookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company);
});
}
private function webHookExpiredQuotes()
{
$quotes = Quote::where('is_deleted', 0)
->where('status_id', Quote::STATUS_SENT)
->whereDate('due_date', '<=', now()->subDays(1)->startOfDay())
@ -81,4 +97,6 @@ class SendRemindersCron extends Command
WebHookHandler::dispatch(Webhook::EVENT_EXPIRED_QUOTE, $quote, $quote->company);
});
}
}

View File

@ -148,6 +148,7 @@ class CompanySettings extends BaseSettings
public $gmail_sending_user_id = '0'; //@implemented
public $reply_to_email = ''; //@TODO
public $reply_to_name = ''; //@TODO
public $bcc_email = ''; //@TODO
public $pdf_email_attachment = false; //@implemented
public $ubl_email_attachment = false; //@implemented
@ -232,11 +233,11 @@ class CompanySettings extends BaseSettings
public $id_number = ''; //@implemented
public $page_size = 'A4'; //Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5, A6
public $font_size = 9; //@implemented
public $font_size = 7; //@implemented
public $primary_font = 'Roboto';
public $secondary_font = 'Roboto';
public $primary_color = '#4caf50';
public $secondary_color = '#2196f3';
public $primary_color = '#142cb5';
public $secondary_color = '#7081e0';
public $hide_paid_to_date = false; //@TODO where?
public $embed_documents = false; //@TODO where?
@ -261,6 +262,7 @@ class CompanySettings extends BaseSettings
public $hide_empty_columns_on_pdf = false;
public static $casts = [
'reply_to_name' => 'string',
'hide_empty_columns_on_pdf' => 'bool',
'enable_reminder_endless' => 'bool',
'use_credits_payment' => 'string',

View File

@ -224,8 +224,7 @@ class EmailTemplateDefaults
private static function transformText($string)
{
//preformat the string, removing trailing colons.
$string = rtrim($string, ":");
return str_replace(':', '$', ctrans('texts.'.$string));
return str_replace(':', '$', rtrim( ctrans('texts.'.$string), ":"));
}
}

View File

@ -39,10 +39,10 @@ class PaymentWasEmailedAndFailed
* PaymentWasEmailedAndFailed constructor.
* @param Payment $payment
* @param $company
* @param array $errors
* @param string $errors
* @param array $event_vars
*/
public function __construct(Payment $payment, Company $company, array $errors, array $event_vars)
public function __construct(Payment $payment, Company $company, string $errors, array $event_vars)
{
$this->payment = $payment;

View File

@ -99,17 +99,21 @@ class Handler extends ExceptionHandler
private function validException($exception)
{
if (strpos($exception->getMessage(), 'file_put_contents') !== false) {
if (strpos($exception->getMessage(), 'file_put_contents') !== false)
return false;
}
if (strpos($exception->getMessage(), 'Permission denied') !== false) {
if (strpos($exception->getMessage(), 'Permission denied') !== false)
return false;
}
if (strpos($exception->getMessage(), 'flock()') !== false) {
if (strpos($exception->getMessage(), 'flock()') !== false)
return false;
}
if (strpos($exception->getMessage(), 'expects parameter 1 to be resource') !== false)
return false;
if (strpos($exception->getMessage(), 'fwrite()') !== false)
return false;
return true;
}

View File

@ -35,10 +35,10 @@ function nlog($output, $context = []): void
}
if (!function_exists('ray')) {
function ray($payload)
{
return true;
}
}
// if (!function_exists('ray')) {
// function ray($payload)
// {
// return true;
// }
// }

View File

@ -27,7 +27,8 @@ trait CustomValuer
public function valuerTax($custom_value, $has_custom_invoice_taxes)
{
if (isset($custom_value) && is_numeric($custom_value) && $has_custom_invoice_taxes === true) {
if (isset($custom_value) && is_numeric($custom_value) && $has_custom_invoice_taxes) {
return round($custom_value * ($this->invoice->tax_rate1 / 100), 2) + round($custom_value * ($this->invoice->tax_rate2 / 100), 2) + round($custom_value * ($this->invoice->tax_rate3 / 100), 2);
}

View File

@ -122,25 +122,23 @@ class InvoiceItemSum
$item_tax += $item_tax_rate1_total;
if ($item_tax_rate1_total > 0) {
if($item_tax_rate1_total != 0)
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total);
}
$item_tax_rate2_total = $this->calcAmountLineTax($this->item->tax_rate2, $amount);
$item_tax += $item_tax_rate2_total;
if ($item_tax_rate2_total > 0) {
if($item_tax_rate2_total != 0)
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total);
}
$item_tax_rate3_total = $this->calcAmountLineTax($this->item->tax_rate3, $amount);
$item_tax += $item_tax_rate3_total;
if ($item_tax_rate3_total > 0) {
if($item_tax_rate3_total != 0)
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total);
}
$this->setTotalTaxes($this->formatValue($item_tax, $this->currency->precision));
@ -240,7 +238,7 @@ class InvoiceItemSum
$item_tax += $item_tax_rate1_total;
if ($item_tax_rate1_total > 0) {
if ($item_tax_rate1_total != 0) {
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total);
}
@ -248,7 +246,7 @@ class InvoiceItemSum
$item_tax += $item_tax_rate2_total;
if ($item_tax_rate2_total > 0) {
if ($item_tax_rate2_total != 0) {
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total);
}
@ -256,7 +254,7 @@ class InvoiceItemSum
$item_tax += $item_tax_rate3_total;
if ($item_tax_rate3_total > 0) {
if ($item_tax_rate3_total != 0) {
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total);
}
}

View File

@ -119,25 +119,23 @@ class InvoiceItemSumInclusive
$item_tax += $this->formatValue($item_tax_rate1_total, $this->currency->precision);
if ($item_tax_rate1_total > 0) {
if($item_tax_rate1_total != 0)
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total);
}
$item_tax_rate2_total = $this->calcInclusiveLineTax($this->item->tax_rate2, $amount);
$item_tax += $this->formatValue($item_tax_rate2_total, $this->currency->precision);
if ($item_tax_rate2_total > 0) {
if($item_tax_rate2_total != 0)
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total);
}
$item_tax_rate3_total = $this->calcInclusiveLineTax($this->item->tax_rate3, $amount);
$item_tax += $this->formatValue($item_tax_rate3_total, $this->currency->precision);
if ($item_tax_rate3_total > 0) {
if($item_tax_rate3_total != 0)
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total);
}
$this->setTotalTaxes($this->formatValue($item_tax, $this->currency->precision));
@ -225,12 +223,17 @@ class InvoiceItemSumInclusive
$item_tax = 0;
foreach ($this->line_items as $this->item) {
$amount = $this->item->line_total - ($this->item->line_total * ($this->invoice->discount / $this->sub_total));
if($this->sub_total == 0)
$amount = $this->item->line_total;
else
$amount = $this->item->line_total - ($this->item->line_total * ($this->invoice->discount / $this->sub_total));
$item_tax_rate1_total = $this->calcInclusiveLineTax($this->item->tax_rate1, $amount);
$item_tax += $item_tax_rate1_total;
if ($item_tax_rate1_total > 0) {
if ($item_tax_rate1_total != 0) {
$this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total);
}
@ -238,7 +241,7 @@ class InvoiceItemSumInclusive
$item_tax += $item_tax_rate2_total;
if ($item_tax_rate2_total > 0) {
if ($item_tax_rate2_total != 0) {
$this->groupTax($this->item->tax_name2, $this->item->tax_rate2, $item_tax_rate2_total);
}
@ -246,7 +249,7 @@ class InvoiceItemSumInclusive
$item_tax += $item_tax_rate3_total;
if ($item_tax_rate3_total > 0) {
if ($item_tax_rate3_total != 0) {
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total);
}
}

View File

@ -57,8 +57,8 @@ class InvoiceSum
{
$this->calculateLineItems()
->calculateDiscount()
->calculateCustomValues()
->calculateInvoiceTaxes()
->calculateCustomValues()
->setTaxMap()
->calculateTotals()
->calculateBalance()
@ -89,16 +89,17 @@ class InvoiceSum
private function calculateCustomValues()
{
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_taxes1);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_tax1);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge1);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_taxes2);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_tax2);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge2);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_taxes3);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_tax3);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge3);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_taxes4);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_tax4);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge4);
$this->total += $this->total_custom_values;
@ -108,19 +109,20 @@ class InvoiceSum
private function calculateInvoiceTaxes()
{
if ($this->invoice->tax_rate1 > 0) {
if (strlen($this->invoice->tax_name1) > 1) {
$tax = $this->taxer($this->total, $this->invoice->tax_rate1);
$this->total_taxes += $tax;
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.floatval($this->invoice->tax_rate1).'%', 'total' => $tax];
}
if ($this->invoice->tax_rate2 > 0) {
if (strlen($this->invoice->tax_name2) > 1) {
$tax = $this->taxer($this->total, $this->invoice->tax_rate2);
$this->total_taxes += $tax;
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.floatval($this->invoice->tax_rate2).'%', 'total' => $tax];
}
if ($this->invoice->tax_rate3 > 0) {
if (strlen($this->invoice->tax_name3) > 1) {
$tax = $this->taxer($this->total, $this->invoice->tax_rate3);
$this->total_taxes += $tax;
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.floatval($this->invoice->tax_rate3).'%', 'total' => $tax];

View File

@ -89,16 +89,16 @@ class InvoiceSumInclusive
private function calculateCustomValues()
{
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_taxes1);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_tax1);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge1);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_taxes2);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_tax2);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge2);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_taxes3);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_tax3);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge3);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_taxes4);
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_tax4);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge4);
$this->total += $this->total_custom_values;

View File

@ -11,6 +11,8 @@
namespace App\Helpers\Mail;
use App\Utils\TempFile;
use Dacastro4\LaravelGmail\Facade\LaravelGmail;
use Dacastro4\LaravelGmail\Services\Message\Mail;
use Illuminate\Mail\Transport\Transport;
use Swift_Mime_SimpleMessage;
@ -27,49 +29,59 @@ class GmailTransport extends Transport
*/
protected $gmail;
/**
* The GMail OAuth Token.
* @var string token
*/
protected $token;
/**
* Create a new Gmail transport instance.
*
* @param Mail $gmail
* @param string $token
*/
public function __construct(Mail $gmail, string $token)
public function __construct(Mail $gmail)
{
$this->gmail = $gmail;
$this->token = $token;
}
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
/* For some reason the Injected Mail class carries cached tokens, so we need to reinit the Mail class*/
$this->gmail = null;
$this->gmail = new Mail;
/*We should nest the token in the message and then discard it as needed*/
$token = $message->getHeaders()->get('GmailToken')->getValue();
$message->getHeaders()->remove('GmailToken');
$this->beforeSendPerformed($message);
$this->gmail->using($this->token);
$this->gmail->using($token);
$this->gmail->to($message->getTo());
$this->gmail->from($message->getFrom());
$this->gmail->subject($message->getSubject());
$this->gmail->message($message->getBody());
//$this->gmail->message($message->toString());
$this->gmail->cc($message->getCc());
$this->gmail->bcc($message->getBcc());
nlog(print_r($message->getChildren(), 1));
foreach ($message->getChildren() as $child)
{
foreach ($message->getChildren() as $child) {
$this->gmail->attach($child);
} //todo this should 'just work'
nlog("trying to attach");
if($child->getContentType() != 'text/plain')
{
$this->gmail->attach(TempFile::filePath($child->getBody(), $child->getHeaders()->get('Content-Type')->getParameter('name') ));
}
}
$this->gmail->send();
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
}
}

View File

@ -1,17 +1,25 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Helpers\Mail;
use Illuminate\Mail\MailManager;
use App\CustomMailDriver\CustomTransport;
use Dacastro4\LaravelGmail\Services\Message\Mail;
use Illuminate\Mail\TransportManager;
use Illuminate\Support\Facades\Config;
class GmailTransportManager extends TransportManager
class GmailTransportManager extends MailManager
{
protected function createGmailDriver()
protected function createGmailTransport()
{
$token = $this->app['config']->get('services.gmail.token', []);
$mail = new Mail;
return new GmailTransport($mail, $token);
return new GmailTransport(new Mail);
}
}

View File

@ -12,8 +12,10 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Libraries\MultiDB;
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
use Illuminate\View\View;
@ -65,4 +67,31 @@ class ContactForgotPasswordController extends Controller
{
return Password::broker('contacts');
}
public function sendResetLinkEmail(Request $request)
{
//MultiDB::userFindAndSetDb($request->input('email'));
$user = MultiDB::hasContact(['email' => $request->input('email')]);
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$this->credentials($request)
);
if ($request->ajax()) {
return $response == Password::RESET_LINK_SENT
? response()->json(['message' => 'Reset link sent to your email.', 'status' => true], 201)
: response()->json(['message' => 'Email not found', 'status' => false], 401);
}
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}
}

View File

@ -18,11 +18,13 @@ class ContactRegisterController extends Controller
$this->middleware(['guest', 'contact.register']);
}
public function showRegisterForm(string $company_key)
public function showRegisterForm(string $company_key = '')
{
$company = Company::where('company_key', $company_key)->firstOrFail();
$key = request()->has('key') ? request('key') : $company_key;
return render('auth.register', compact(['company']));
$company = Company::where('company_key', $key)->firstOrFail();
return render('auth.register', ['company' => $company]);
}
public function register(RegisterRequest $request)

View File

@ -12,6 +12,7 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Libraries\MultiDB;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
@ -103,6 +104,10 @@ class ForgotPasswordController extends Controller
*/
public function sendResetLinkEmail(Request $request)
{
//MultiDB::userFindAndSetDb($request->input('email'));
$user = MultiDB::hasUser(['email' => $request->input('email')]);
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have attempted

View File

@ -289,7 +289,7 @@ class LoginController extends BaseController
$client = new Google_Client();
$client->setClientId(config('ninja.auth.google.client_id'));
$client->setClientSecret(config('ninja.auth.google.client_secret'));
$client->setRedirectUri(config('ninja.app_url'));
$token = $client->authenticate(request()->input('server_auth_code'));
$refresh_token = '';

View File

@ -184,7 +184,9 @@ class BaseController extends Controller
protected function refreshResponse($query)
{
if (auth()->user()->getCompany()->is_large)
$user = auth()->user();
if ($user->getCompany()->is_large)
$this->manager->parseIncludes($this->mini_load);
else
$this->manager->parseIncludes($this->first_load);
@ -200,74 +202,145 @@ class BaseController extends Controller
$transformer = new $this->entity_transformer($this->serializer);
$updated_at = request()->has('updated_at') ? request()->input('updated_at') : 0;
// if (auth()->user()->getCompany()->is_large && ! request()->has('updated_at')) {
// return response()->json(['message' => ctrans('texts.large_account_update_parameter'), 'errors' =>[]], 401);
// }
$updated_at = date('Y-m-d H:i:s', $updated_at);
$query->with(
[
'company' => function ($query) use ($updated_at) {
'company' => function ($query) use ($updated_at, $user) {
$query->whereNotNull('updated_at')->with('documents');
},
'company.clients' => function ($query) use ($updated_at) {
'company.clients' => function ($query) use ($updated_at, $user) {
$query->where('clients.updated_at', '>=', $updated_at)->with('contacts.company', 'gateway_tokens', 'documents');
if(!$user->hasPermission('view_client'))
$query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id);
},
'company.company_gateways' => function ($query) {
'company.company_gateways' => function ($query) use ($user) {
$query->whereNotNull('updated_at');
if(!$user->isAdmin())
$query->where('company_gateways.user_id', $user->id);
},
'company.credits'=> function ($query) use ($updated_at) {
'company.credits'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
if(!$user->hasPermission('view_credit'))
$query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id);
},
'company.designs'=> function ($query) use ($updated_at) {
'company.designs'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('company');
if(!$user->isAdmin())
$query->where('designs.user_id', $user->id);
},
'company.documents'=> function ($query) use ($updated_at) {
'company.documents'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
},
'company.expenses'=> function ($query) use ($updated_at) {
'company.expenses'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents');
if(!$user->hasPermission('view_expense'))
$query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id);
},
'company.groups' => function ($query) use ($updated_at) {
'company.groups' => function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
if(!$user->isAdmin())
$query->where('group_settings.user_id', $user->id);
},
'company.invoices'=> function ($query) use ($updated_at) {
'company.invoices'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
if(!$user->hasPermission('view_invoice'))
$query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id);
},
'company.payments'=> function ($query) use ($updated_at) {
'company.payments'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('paymentables', 'documents');
if(!$user->hasPermission('view_payment'))
$query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id);
},
'company.payment_terms'=> function ($query) use ($updated_at) {
'company.payment_terms'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
if(!$user->isAdmin())
$query->where('payment_terms.user_id', $user->id);
},
'company.products' => function ($query) use ($updated_at) {
'company.products' => function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents');
if(!$user->hasPermission('view_product'))
$query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id);
},
'company.projects'=> function ($query) use ($updated_at) {
'company.projects'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents');
if(!$user->hasPermission('view_project'))
$query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id);
},
'company.quotes'=> function ($query) use ($updated_at) {
'company.quotes'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
if(!$user->hasPermission('view_quote'))
$query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id);
},
'company.recurring_invoices'=> function ($query) use ($updated_at) {
'company.recurring_invoices'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
if(!$user->hasPermission('view_recurring_invoice'))
$query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id);
},
'company.tasks'=> function ($query) use ($updated_at) {
'company.tasks'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents');
if(!$user->hasPermission('view_task'))
$query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id);
},
'company.tax_rates' => function ($query) use ($updated_at) {
'company.tax_rates' => function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
if(!$user->isAdmin())
$query->where('tax_rates.user_id', $user->id);
},
'company.vendors'=> function ($query) use ($updated_at) {
'company.vendors'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('contacts', 'documents');
if(!$user->hasPermission('view_vendor'))
$query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id);
},
'company.expense_categories'=> function ($query) use ($updated_at) {
'company.expense_categories'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
if(!$user->isAdmin())
$query->where('expense_categories.user_id', $user->id);
},
'company.task_statuses'=> function ($query) use ($updated_at) {
'company.task_statuses'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
if(!$user->isAdmin())
$query->where('task_statuses.user_id', $user->id);
},
'company.activities'=> function ($query) use($user) {
if(!$user->isAdmin())
$query->where('activities.user_id', $user->id);
}
]
);

View File

@ -21,6 +21,7 @@ use App\Http\Requests\Client\EditClientRequest;
use App\Http\Requests\Client\ShowClientRequest;
use App\Http\Requests\Client\StoreClientRequest;
use App\Http\Requests\Client\UpdateClientRequest;
use App\Http\Requests\Client\UploadClientRequest;
use App\Jobs\Client\StoreClient;
use App\Jobs\Client\UpdateClient;
use App\Models\Client;
@ -29,6 +30,7 @@ use App\Transformers\ClientTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\BulkOptions;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Utils\Traits\Uploadable;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -42,6 +44,7 @@ class ClientController extends BaseController
use MakesHash;
use Uploadable;
use BulkOptions;
use SavesDocuments;
protected $entity_type = Client::class;
@ -269,6 +272,7 @@ class ClientController extends BaseController
*/
public function update(UpdateClientRequest $request, Client $client)
{
if ($request->entityIsDeleted($client)) {
return $request->disallowUpdate();
}
@ -515,4 +519,66 @@ class ClientController extends BaseController
{
//todo
}
/**
* Update the specified resource in storage.
*
* @param UploadClientRequest $request
* @param Client $client
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/clients/{id}/upload",
* operationId="uploadClient",
* tags={"clients"},
* summary="Uploads a document to a client",
* description="Handles the uploading of a document to a client",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Client Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Client"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadClientRequest $request, Client $client)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $client);
return $this->itemResponse($client->fresh());
}
}

View File

@ -76,10 +76,10 @@ class DocumentController extends Controller
$options->setSendHttpHeaders(true);
$zip = new ZipStream('files.zip', $options);
$zip = new ZipStream(now() . '-documents.zip', $options);
foreach ($documents as $document) {
$zip->addFileFromPath(basename($document->filePath()), TempFile::path($document->filePath()));
$zip->addFileFromPath(basename($document->diskPath()), TempFile::path($document->diskPath()));
}
$zip->finish();

View File

@ -33,6 +33,8 @@ class InvitationController extends Controller
public function router(string $entity, string $invitation_key)
{
Auth::logout();
return $this->genericRouter($entity, $invitation_key);
}
@ -43,6 +45,7 @@ class InvitationController extends Controller
private function genericRouter(string $entity, string $invitation_key)
{
$key = $entity.'_id';
$entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
@ -51,17 +54,22 @@ class InvitationController extends Controller
->with('contact.client')
->firstOrFail();
/* Return early if we have the correct client_hash embedded */
if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) {
auth()->guard('contact')->login($invitation->contact, true);
} elseif ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
//If no contact password is set - this will cause a 401 error - instead redirect to the client.login route
$this->middleware('auth:contact');
return redirect()->route('client.login');
} else {
auth()->guard('contact')->login($invitation->contact, true);
}
if (auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) {
$invitation->markViewed();

View File

@ -54,11 +54,11 @@ class InvoiceController extends Controller
'invoice' => $invoice,
];
if ($request->query('mode') === 'portal') {
return $this->render('invoices.show', $data);
if ($request->query('mode') === 'fullscreen') {
return response()->file($invoice->pdf_file_path(null, 'path'));
}
return $this->render('invoices.show.fullscreen', $data);
return $this->render('invoices.show', $data);
}
/**
@ -90,7 +90,7 @@ class InvoiceController extends Controller
//filter invoices which are payable
$invoices = $invoices->filter(function ($invoice) {
return $invoice->isPayable() && $invoice->balance > 0;
return $invoice->isPayable();
});
//return early if no invoices.

View File

@ -106,7 +106,7 @@ class PaymentController extends Controller
if ($payable_invoices->count() == 0) {
return redirect()
->route('client.invoices.index')
->with(['warning' => 'No payable invoices selected.']);
->with(['message' => 'No payable invoices selected.']);
}
$settings = auth()->user()->client->getMergedSettings();
@ -137,32 +137,36 @@ class PaymentController extends Controller
$payable_invoice['amount'] = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), auth()->user()->client->currency()->precision);
}
/* If we DO allow under payments check the minimum amount is present else return */
if (!$settings->client_portal_allow_under_payment && $payable_amount < $invoice_balance) {
return redirect()
->route('client.invoices.index')
->with('message', ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance]));
}
if ($settings->client_portal_allow_under_payment) {
if ($payable_invoice['amount'] < $settings->client_portal_under_payment_minimum) {
if ($invoice_balance < $settings->client_portal_under_payment_minimum && $payable_amount < $invoice_balance) {
return redirect()
->route('client.invoices.index')
->with('message', ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance]));
}
if ($invoice_balance < $settings->client_portal_under_payment_minimum) {
// Skip the under payment rule.
}
if ($invoice_balance >= $settings->client_portal_under_payment_minimum && $payable_amount < $settings->client_portal_under_payment_minimum) {
return redirect()
->route('client.invoices.index')
->with('message', ctrans('texts.minimum_required_payment', ['amount' => $settings->client_portal_under_payment_minimum]));
}
} else {
/*Double check!!*/
if ($payable_amount < $invoice_balance) {
return redirect()
->route('client.invoices.index')
->with('message', ctrans('texts.under_payments_disabled'));
}
}
/* If we don't allow over payments and the amount exceeds the balance */
if (!$settings->client_portal_allow_over_payment) {
if ($payable_amount > $invoice_balance) {
return redirect()
->route('client.invoices.index')
->with('message', ctrans('texts.over_payments_disabled'));
}
if (!$settings->client_portal_allow_over_payment && $payable_amount > $invoice_balance) {
return redirect()
->route('client.invoices.index')
->with('message', ctrans('texts.over_payments_disabled'));
}
}

View File

@ -35,7 +35,7 @@ class QuoteController extends Controller
*
* @param ShowQuoteRequest $request
* @param Quote $quote
* @return Factory|View
* @return Factory|View|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function show(ShowQuoteRequest $request, Quote $quote)
{
@ -43,11 +43,11 @@ class QuoteController extends Controller
'quote' => $quote,
];
if ($request->query('mode') === 'portal') {
return $this->render('quotes.show', $data);
if ($request->query('mode') === 'fullscreen') {
return response()->file($quote->pdf_file_path(null, 'path'));
}
return $this->render('quotes.show.fullscreen', $data);
return $this->render('quotes.show', $data);
}
public function bulk(ProcessQuotesInBulkRequest $request)

View File

@ -13,10 +13,15 @@ namespace App\Http\Controllers\ClientPortal;
use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\ShowRecurringInvoiceRequest;
use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Mail\RecurringInvoice\ClientContactRequestCancellationObject;
use App\Models\RecurringInvoice;
use App\Notifications\ClientContactRequestCancellation;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Notifications\UserNotifies;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\View\View;
@ -28,6 +33,7 @@ class RecurringInvoiceController extends Controller
{
use MakesHash;
use MakesDates;
use UserNotifies;
/**
* Show the list of recurring invoices.
@ -57,7 +63,22 @@ class RecurringInvoiceController extends Controller
{
//todo double check the user is able to request a cancellation
//can add locale specific by chaining ->locale();
$recurring_invoice->user->notify(new ClientContactRequestCancellation($recurring_invoice, auth()->user()));
$nmo = new NinjaMailerObject;
$nmo->mailable = (new NinjaMailer((new ClientContactRequestCancellationObject($recurring_invoice, auth()->user()))->build()));
$nmo->company = $recurring_invoice->company;
$nmo->settings = $recurring_invoice->company->settings;
$notifiable_users = $this->filterUsersByPermissions($recurring_invoice->company->company_users, $recurring_invoice, ['recurring_cancellation']);
$notifiable_users->each(function ($company_user) use($nmo){
$nmo->to_user = $company_user->user;
NinjaMailerJob::dispatch($nmo);
});
//$recurring_invoice->user->notify(new ClientContactRequestCancellation($recurring_invoice, auth()->user()));
return $this->render('recurring_invoices.cancellation.index', [
'invoice' => $recurring_invoice,

View File

@ -20,6 +20,7 @@ use App\Http\Requests\Company\EditCompanyRequest;
use App\Http\Requests\Company\ShowCompanyRequest;
use App\Http\Requests\Company\StoreCompanyRequest;
use App\Http\Requests\Company\UpdateCompanyRequest;
use App\Http\Requests\Company\UploadCompanyRequest;
use App\Jobs\Company\CreateCompany;
use App\Jobs\Company\CreateCompanyPaymentTerms;
use App\Jobs\Company\CreateCompanyTaskStatuses;
@ -503,4 +504,65 @@ class CompanyController extends BaseController
return response()->json(['message' => ctrans('texts.success')], 200);
}
/**
* Update the specified resource in storage.
*
* @param UploadCompanyRequest $request
* @param Company $client
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/companies/{id}/upload",
* operationId="uploadCompanies",
* tags={"companies"},
* summary="Uploads a document to a company",
* description="Handles the uploading of a document to a company",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Company Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Company"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadCompanyRequest $request, Company $company)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $company);
return $this->itemResponse($company->fresh());
}
}

View File

@ -0,0 +1,139 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Libraries\MultiDB;
use App\Libraries\OAuth\Providers\Google;
use Illuminate\Http\Request;
class ConnectedAccountController extends BaseController
{
public function __construct()
{
parent::__construct();
}
/**
* Connect an OAuth account to a regular email/password combination account
*
* @param Request $request
* @return User Refresh Feed.
*
*
* @OA\Post(
* path="/api/v1/connected_account",
* operationId="connected_account",
* tags={"connected_account"},
* summary="Connect an oauth user to an existing user",
* description="Refreshes the dataset",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(ref="#/components/parameters/include_static"),
* @OA\Parameter(ref="#/components/parameters/clear_cache"),
* @OA\Response(
* response=200,
* description="The Company User response",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/User"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function index(Request $request)
{
if ($request->input('provider') == 'google') {
return $this->handleGoogleOauth();
}
return response()
->json(['message' => 'Provider not supported'], 400)
->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_version'));
}
private function handleGoogleOauth()
{
$user = false;
$google = new Google();
$user = $google->getTokenResponse(request()->input('id_token'));
if (is_array($user)) {
$query = [
'oauth_user_id' => $google->harvestSubField($user),
'oauth_provider_id'=> 'google',
];
/* Cannot allow duplicates! */
if ($existing_user = MultiDB::hasUser($query)) {
return response()
->json(['message' => 'User already exists in system.'], 401)
->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_version'));
}
}
if ($user) {
$client = new Google_Client();
$client->setClientId(config('ninja.auth.google.client_id'));
$client->setClientSecret(config('ninja.auth.google.client_secret'));
$client->setRedirectUri(config('ninja.app_url'));
$token = $client->authenticate(request()->input('server_auth_code'));
$refresh_token = '';
if (array_key_exists('refresh_token', $token)) {
$refresh_token = $token['refresh_token'];
}
$connected_account = [
'password' => '',
'email' => $google->harvestEmail($user),
'oauth_user_id' => $google->harvestSubField($user),
'oauth_user_token' => $token,
'oauth_user_refresh_token' => $refresh_token,
'oauth_provider_id' => 'google',
'email_verified_at' =>now()
];
auth()->user()->update($connected_account);
auth()->user()->email_verified_at = now();
auth()->user()->save();
//$ct = CompanyUser::whereUserId(auth()->user()->id);
return $this->listResponse(auth()->user());
}
return response()
->json(['message' => ctrans('texts.invalid_credentials')], 401)
->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_version'));
}
}

View File

@ -1,4 +1,13 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
@ -14,6 +23,7 @@ use App\Http\Requests\Credit\EditCreditRequest;
use App\Http\Requests\Credit\ShowCreditRequest;
use App\Http\Requests\Credit\StoreCreditRequest;
use App\Http\Requests\Credit\UpdateCreditRequest;
use App\Http\Requests\Credit\UploadCreditRequest;
use App\Jobs\Entity\EmailEntity;
use App\Jobs\Invoice\EmailCredit;
use App\Models\Client;
@ -24,6 +34,7 @@ use App\Transformers\CreditTransformer;
use App\Utils\Ninja;
use App\Utils\TempFile;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Response;
/**
@ -32,6 +43,7 @@ use Illuminate\Http\Response;
class CreditController extends BaseController
{
use MakesHash;
use SavesDocuments;
protected $entity_type = Credit::class;
@ -56,7 +68,7 @@ class CreditController extends BaseController
* @OA\Get(
* path="/api/v1/credits",
* operationId="getCredits",
* tags={"invoices"},
* tags={"credits"},
* summary="Gets a list of credits",
* description="Lists credits, search and filters allow fine grained lists to be generated.
*
@ -576,4 +588,66 @@ class CreditController extends BaseController
return response()->download($file_path);
}
/**
* Update the specified resource in storage.
*
* @param UploadCreditRequest $request
* @param Credit $client
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/credits/{id}/upload",
* operationId="uploadCredits",
* tags={"credits"},
* summary="Uploads a document to a credit",
* description="Handles the uploading of a document to a credit",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Credit Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Credit object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Credit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadCreditRequest $request, Credit $credit)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $credit);
return $this->itemResponse($credit->fresh());
}
}

View File

@ -21,12 +21,14 @@ use App\Http\Requests\Expense\EditExpenseRequest;
use App\Http\Requests\Expense\ShowExpenseRequest;
use App\Http\Requests\Expense\StoreExpenseRequest;
use App\Http\Requests\Expense\UpdateExpenseRequest;
use App\Http\Requests\Expense\UploadExpenseRequest;
use App\Models\Expense;
use App\Repositories\ExpenseRepository;
use App\Transformers\ExpenseTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\BulkOptions;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Utils\Traits\Uploadable;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -40,6 +42,7 @@ class ExpenseController extends BaseController
use MakesHash;
use Uploadable;
use BulkOptions;
use SavesDocuments;
protected $entity_type = Expense::class;
@ -507,4 +510,65 @@ class ExpenseController extends BaseController
{
//todo
}
/**
* Update the specified resource in storage.
*
* @param UploadExpenseRequest $request
* @param Expense $expense
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/expenses/{id}/upload",
* operationId="uploadExpense",
* tags={"expense"},
* summary="Uploads a document to a expense",
* description="Handles the uploading of a document to a expense",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Expense Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Expense object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Expense"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadExpenseRequest $request, Expense $expense)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $expense);
return $this->itemResponse($expense->fresh());
}
}

View File

@ -14,99 +14,119 @@ namespace App\Http\Controllers;
use App\Http\Requests\Import\ImportRequest;
use App\Http\Requests\Import\PreImportRequest;
use App\Jobs\Import\CSVImport;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use League\Csv\Reader;
use League\Csv\Statement;
class ImportController extends Controller
{
class ImportController extends Controller {
/**
* Store a newly created resource in storage.
*
* @param StoreImportRequest $request
* @return Response
*
* @OA\Post(
* path="/api/v1/preimport",
* operationId="preimport",
* tags={"imports"},
* summary="Pre Import checks - returns a reference to the job and the headers of the CSV",
* description="Pre Import checks - returns a reference to the job and the headers of the CSV",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\RequestBody(
* description="The CSV file",
* required=true,
* @OA\MediaType(
* mediaType="multipart/form-data",
* @OA\Schema(
* type="string",
* format="binary"
* )
* )
* ),
* @OA\Response(
* response=200,
* description="Returns a reference to the file",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function preimport(PreImportRequest $request)
{
//create a reference
$hash = Str::random(32);
/**
* Store a newly created resource in storage.
*
* @param PreImportRequest $request
*
* @return \Illuminate\Http\JsonResponse
*
* @OA\Post(
* path="/api/v1/preimport",
* operationId="preimport",
* tags={"imports"},
* summary="Pre Import checks - returns a reference to the job and the headers of the CSV",
* description="Pre Import checks - returns a reference to the job and the headers of the CSV",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\RequestBody(
* description="The CSV file",
* required=true,
* @OA\MediaType(
* mediaType="multipart/form-data",
* @OA\Schema(
* type="string",
* format="binary"
* )
* )
* ),
* @OA\Response(
* response=200,
* description="Returns a reference to the file",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function preimport( PreImportRequest $request ) {
// Create a reference
$hash = Str::random( 32 );
//store the csv in cache with an expiry of 10 minutes
Cache::put($hash, base64_encode(file_get_contents($request->file('file')->getPathname())), 3600);
$data = [
'hash' => $hash,
'mappings' => [],
];
/** @var UploadedFile $file */
foreach ( $request->files->get( 'files' ) as $entityType => $file ) {
$contents = file_get_contents( $file->getPathname() );
//parse CSV
$csv_array = $this->getCsvData(file_get_contents($request->file('file')->getPathname()));
// Store the csv in cache with an expiry of 10 minutes
Cache::put( $hash . '-' . $entityType, base64_encode( $contents ), 3600 );
$class_map = $this->getEntityMap($request->input('entity_type'));
// Parse CSV
$csv_array = $this->getCsvData( $contents );
$data = [
'hash' => $hash,
'available' => $class_map::importable(),
'headers' => array_slice($csv_array, 0, 2)
];
$class_map = $this->getEntityMap( $entityType );
return response()->json($data);
}
$data['mappings'][ $entityType ] = [
'available' => $class_map::importable(),
'headers' => array_slice( $csv_array, 0, 2 ),
];
}
public function import(ImportRequest $request)
{
CSVImport::dispatch($request->all(), auth()->user()->company());
return response()->json( $data );
}
return response()->json(['message' => ctrans('texts.import_started')], 200);
}
public function import( ImportRequest $request ) {
$data = $request->all();
private function getEntityMap($entity_type)
{
return sprintf('App\\Import\\Definitions\%sMap', ucfirst($entity_type));
}
if ( empty( $data['hash'] ) ) {
// Create a reference
$data['hash'] = $hash = Str::random( 32 );
private function getCsvData($csvfile)
{
if (! ini_get('auto_detect_line_endings')) {
ini_set('auto_detect_line_endings', '1');
/** @var UploadedFile $file */
foreach ( $request->files->get( 'files' ) as $entityType => $file ) {
$contents = file_get_contents( $file->getPathname() );
// Store the csv in cache with an expiry of 10 minutes
Cache::put( $hash . '-' . $entityType, base64_encode( $contents ), 3600 );
}
}
CSVImport::dispatch( $data, auth()->user()->company() );
return response()->json( [ 'message' => ctrans( 'texts.import_started' ) ], 200 );
}
private function getEntityMap( $entity_type ) {
return sprintf( 'App\\Import\\Definitions\%sMap', ucfirst( $entity_type ) );
}
private function getCsvData( $csvfile ) {
if ( ! ini_get( 'auto_detect_line_endings' ) ) {
ini_set( 'auto_detect_line_endings', '1' );
}
$csv = Reader::createFromString($csvfile);
@ -121,10 +141,10 @@ class ImportController extends Controller
$firstCell = $headers[0];
if (strstr($firstCell, (string)config('ninja.app_name'))) {
array_shift($data); // Invoice Ninja...
array_shift($data); // <blank line>
array_shift($data); // Enitty Type Header
}
array_shift( $data ); // Invoice Ninja...
array_shift( $data ); // <blank line>
array_shift( $data ); // Entity Type Header
}
}
}

View File

@ -25,6 +25,7 @@ use App\Http\Requests\Invoice\EditInvoiceRequest;
use App\Http\Requests\Invoice\ShowInvoiceRequest;
use App\Http\Requests\Invoice\StoreInvoiceRequest;
use App\Http\Requests\Invoice\UpdateInvoiceRequest;
use App\Http\Requests\Invoice\UploadInvoiceRequest;
use App\Jobs\Entity\EmailEntity;
use App\Jobs\Invoice\StoreInvoice;
use App\Jobs\Invoice\ZipInvoices;
@ -38,6 +39,7 @@ use App\Transformers\QuoteTransformer;
use App\Utils\Ninja;
use App\Utils\TempFile;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\File;
@ -49,6 +51,7 @@ use Illuminate\Support\Facades\Storage;
class InvoiceController extends BaseController
{
use MakesHash;
use SavesDocuments;
protected $entity_type = Invoice::class;
@ -204,6 +207,7 @@ class InvoiceController extends BaseController
*/
public function store(StoreInvoiceRequest $request)
{
$client = Client::find($request->input('client_id'));
$invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id));
@ -392,8 +396,6 @@ class InvoiceController extends BaseController
$invoice = $this->invoice_repo->save($request->all(), $invoice);
UnlinkFile::dispatchNow(config('filesystems.default'), $invoice->client->invoice_filepath().$invoice->number.'.pdf');
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars()));
return $this->itemResponse($invoice);
@ -530,7 +532,7 @@ class InvoiceController extends BaseController
}
});
ZipInvoices::dispatch($invoices, $invoices->first()->company, auth()->user()->email);
ZipInvoices::dispatch($invoices, $invoices->first()->company, auth()->user());
return response()->json(['message' => ctrans('texts.sent_message')], 200);
}
@ -850,4 +852,65 @@ class InvoiceController extends BaseController
return response(['message' => 'Oops, something went wrong. Make sure you have symlink to storage/ in public/ directory.'], 500);
}
}
/**
* Update the specified resource in storage.
*
* @param UploadInvoiceRequest $request
* @param Invoice $invoice
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/invoices/{id}/upload",
* operationId="uploadInvoice",
* tags={"invoices"},
* summary="Uploads a document to a invoice",
* description="Handles the uploading of a document to a invoice",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Invoice Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Invoice object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Invoice"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadInvoiceRequest $request, Invoice $invoice)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $invoice);
return $this->itemResponse($invoice->fresh());
}
}

View File

@ -14,7 +14,8 @@ namespace App\Http\Controllers;
use App\Console\Commands\ImportMigrations;
use App\DataMapper\CompanySettings;
use App\Jobs\Mail\MailRouter;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Util\StartMigration;
use App\Mail\ExistingMigration;
use App\Models\Company;
@ -218,6 +219,8 @@ class MigrationController extends BaseController
*/
public function startMigration(Request $request)
{
nlog("Starting Migration");
$companies = json_decode($request->companies);
if (app()->environment() === 'local') {
@ -246,7 +249,13 @@ class MigrationController extends BaseController
if ($checks['existing_company'] == true && $checks['force'] == false) {
nlog('Migrating: Existing company without force. (CASE_01)');
MailRouter::dispatch(new ExistingMigration(), $existing_company, $user);
$nmo = new NinjaMailerObject;
$nmo->mailable = new ExistingMigration();
$nmo->company = $existing_company;
$nmo->settings = $existing_company->settings;
$nmo->to_user = $user;
NinjaMailerJob::dispatch($nmo);
return response()->json([
'_id' => Str::uuid(),
@ -290,6 +299,9 @@ class MigrationController extends BaseController
// If there's no existing company migrate just normally.
if ($checks['existing_company'] == false) {
nlog("creating fresh company");
$account = auth()->user()->account;
$fresh_company = (new ImportMigrations())->getCompany($account);
@ -325,11 +337,13 @@ class MigrationController extends BaseController
);
if (app()->environment() == 'testing') {
nlog("environment is testing = bailing out now");
return;
}
try {
// StartMigration::dispatch(base_path("storage/app/public/$migration_file"), $user, $fresh_company)->delay(now()->addSeconds(5));
nlog("starting migration job");
nlog($migration_file);
StartMigration::dispatch($migration_file, $user, $fresh_company);
} catch (\Exception $e) {

View File

@ -49,6 +49,9 @@
* @OA\Property(property="custom_surcharge2", type="number", format="float", example="10.00", description="Second Custom Surcharge"),
* @OA\Property(property="custom_surcharge3", type="number", format="float", example="10.00", description="Third Custom Surcharge"),
* @OA\Property(property="custom_surcharge4", type="number", format="float", example="10.00", description="Fourth Custom Surcharge"),
* @OA\Property(property="custom_surcharge_taxes", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax1", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax2", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax3", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax4", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* )
*/

View File

@ -0,0 +1,23 @@
<?php
/**
* @OA\Schema(
* schema="Document",
* type="object",
* @OA\Property(property="id", type="string", example="AS3df3A", description="The design hashed id"),
* @OA\Property(property="user_id", type="string", example="", description="__________"),
* @OA\Property(property="assigned_user_id", type="string", example="", description="__________"),
* @OA\Property(property="project_id", type="string", example="", description="__________"),
* @OA\Property(property="vendor_id", type="string", example="", description="__________"),
* @OA\Property(property="name", type="string", example="Beauty", description="The design name"),
* @OA\Property(property="url", type="string", example="Beauty", description="The design name"),
* @OA\Property(property="preview", type="string", example="Beauty", description="The design name"),
* @OA\Property(property="type", type="string", example="Beauty", description="The design name"),
* @OA\Property(property="disk", type="string", example="Beauty", description="The design name"),
* @OA\Property(property="hash", type="string", example="Beauty", description="The design name"),
* @OA\Property(property="is_deleted", type="boolean", example=true, description="Flag to determine if the design is deleted"),
* @OA\Property(property="is_default", type="boolean", example=true, description="Flag to determine if the document is a default doc"),
* @OA\Property(property="created_at", type="number", format="integer", example="134341234234", description="Timestamp"),
* @OA\Property(property="updated_at", type="number", format="integer", example="134341234234", description="Timestamp"),
* @OA\Property(property="deleted_at", type="number", format="integer", example="134341234234", description="Timestamp"),
* )
*/

View File

@ -48,6 +48,9 @@
* @OA\Property(property="custom_surcharge2", type="number", format="float", example="10.00", description="Second Custom Surcharge"),
* @OA\Property(property="custom_surcharge3", type="number", format="float", example="10.00", description="Third Custom Surcharge"),
* @OA\Property(property="custom_surcharge4", type="number", format="float", example="10.00", description="Fourth Custom Surcharge"),
* @OA\Property(property="custom_surcharge_taxes", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax1", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax2", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax3", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax4", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* )
*/

View File

@ -48,6 +48,9 @@
* @OA\Property(property="custom_surcharge2", type="number", format="float", example="10.00", description="Second Custom Surcharge"),
* @OA\Property(property="custom_surcharge3", type="number", format="float", example="10.00", description="Third Custom Surcharge"),
* @OA\Property(property="custom_surcharge4", type="number", format="float", example="10.00", description="Fourth Custom Surcharge"),
* @OA\Property(property="custom_surcharge_taxes", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax1", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax2", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax3", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* @OA\Property(property="custom_surcharge_tax4", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
* )
*/

View File

@ -22,12 +22,14 @@ use App\Http\Requests\Payment\RefundPaymentRequest;
use App\Http\Requests\Payment\ShowPaymentRequest;
use App\Http\Requests\Payment\StorePaymentRequest;
use App\Http\Requests\Payment\UpdatePaymentRequest;
use App\Http\Requests\Payment\UploadPaymentRequest;
use App\Models\Invoice;
use App\Models\Payment;
use App\Repositories\PaymentRepository;
use App\Transformers\PaymentTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -37,6 +39,7 @@ use Illuminate\Http\Response;
class PaymentController extends BaseController
{
use MakesHash;
use SavesDocuments;
protected $entity_type = Payment::class;
@ -671,4 +674,65 @@ class PaymentController extends BaseController
return $this->itemResponse($payment);
}
/**
* Update the specified resource in storage.
*
* @param UploadPaymentRequest $request
* @param Payment $payment
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/payments/{id}/upload",
* operationId="uploadPayment",
* tags={"payments"},
* summary="Uploads a document to a payment",
* description="Handles the uploading of a document to a payment",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Payment Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Payment object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Payment"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadPaymentRequest $request, Payment $payment)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $payment);
return $this->itemResponse($payment->fresh());
}
}

View File

@ -0,0 +1,156 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use Illuminate\Http\Request;
/**
* Class PostMarkController.
*/
class PostMarkController extends BaseController
{
public function __construct()
{
}
/**
* Process Postmark Webhook.
*
*
* @OA\Post(
* path="/api/v1/postmark_webhook",
* operationId="postmarkWebhook",
* tags={"postmark"},
* summary="Processing webhooks from PostMark",
* description="Adds an credit to the system",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="Returns the saved credit object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Credit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function webhook(Request $request)
{
if($request->header('X-API-SECURITY') && $request->header('X-API-SECURITY') == config('postmark.secret'))
{
}
}
// {
// "RecordType": "Delivery",
// "ServerID": 23,
// "MessageStream": "outbound",
// "MessageID": "00000000-0000-0000-0000-000000000000",
// "Recipient": "john@example.com",
// "Tag": "welcome-email",
// "DeliveredAt": "2021-02-21T16:34:52Z",
// "Details": "Test delivery webhook details",
// "Metadata": {
// "example": "value",
// "example_2": "value"
// }
// }
private function processDelivery($request)
{
}
// {
// "Metadata": {
// "example": "value",
// "example_2": "value"
// },
// "RecordType": "Bounce",
// "ID": 42,
// "Type": "HardBounce",
// "TypeCode": 1,
// "Name": "Hard bounce",
// "Tag": "Test",
// "MessageID": "00000000-0000-0000-0000-000000000000",
// "ServerID": 1234,
// "MessageStream": "outbound",
// "Description": "The server was unable to deliver your message (ex: unknown user, mailbox not found).",
// "Details": "Test bounce details",
// "Email": "john@example.com",
// "From": "sender@example.com",
// "BouncedAt": "2021-02-21T16:34:52Z",
// "DumpAvailable": true,
// "Inactive": true,
// "CanActivate": true,
// "Subject": "Test subject",
// "Content": "Test content"
// }
private function processBounce($request)
{
}
// {
// "Metadata": {
// "example": "value",
// "example_2": "value"
// },
// "RecordType": "SpamComplaint",
// "ID": 42,
// "Type": "SpamComplaint",
// "TypeCode": 100001,
// "Name": "Spam complaint",
// "Tag": "Test",
// "MessageID": "00000000-0000-0000-0000-000000000000",
// "ServerID": 1234,
// "MessageStream": "outbound",
// "Description": "The subscriber explicitly marked this message as spam.",
// "Details": "Test spam complaint details",
// "Email": "john@example.com",
// "From": "sender@example.com",
// "BouncedAt": "2021-02-21T16:34:52Z",
// "DumpAvailable": true,
// "Inactive": true,
// "CanActivate": false,
// "Subject": "Test subject",
// "Content": "Test content"
// }
private function processSpamComplaint($request)
{
}
}

View File

@ -19,16 +19,19 @@ use App\Http\Requests\Product\EditProductRequest;
use App\Http\Requests\Product\ShowProductRequest;
use App\Http\Requests\Product\StoreProductRequest;
use App\Http\Requests\Product\UpdateProductRequest;
use App\Http\Requests\Product\UploadProductRequest;
use App\Models\Product;
use App\Repositories\ProductRepository;
use App\Transformers\ProductTransformer;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class ProductController extends BaseController
{
use MakesHash;
use SavesDocuments;
protected $entity_type = Product::class;
@ -476,4 +479,65 @@ class ProductController extends BaseController
return $this->listResponse(Product::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
/**
* Update the specified resource in storage.
*
* @param UploadProductRequest $request
* @param Product $product
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/products/{id}/upload",
* operationId="uploadProduct",
* tags={"products"},
* summary="Uploads a document to a product",
* description="Handles the uploading of a document to a product",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Product Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Product object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Product"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadProductRequest $request, Product $product)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $product);
return $this->itemResponse($product->fresh());
}
}

View File

@ -19,6 +19,7 @@ use App\Http\Requests\Project\EditProjectRequest;
use App\Http\Requests\Project\ShowProjectRequest;
use App\Http\Requests\Project\StoreProjectRequest;
use App\Http\Requests\Project\UpdateProjectRequest;
use App\Http\Requests\Project\UploadProjectRequest;
use App\Models\Project;
use App\Repositories\ProjectRepository;
use App\Transformers\ProjectTransformer;
@ -266,6 +267,10 @@ class ProjectController extends BaseController
$project->number = empty($project->number) ? $this->getNextProjectNumber($project) : $project->number;
$project->save();
if ($request->has('documents')) {
$this->saveDocuments($request->input('documents'), $project);
}
return $this->itemResponse($project->fresh());
}
@ -499,4 +504,65 @@ class ProjectController extends BaseController
return $this->listResponse(Project::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
/**
* Update the specified resource in storage.
*
* @param UploadProductRequest $request
* @param Product $project
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/projects/{id}/upload",
* operationId="uploadProject",
* tags={"projects"},
* summary="Uploads a document to a project",
* description="Handles the uploading of a document to a project",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Project Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Project object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Project"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadProjectRequest $request, Project $project)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $project);
return $this->itemResponse($project->fresh());
}
}

View File

@ -24,6 +24,7 @@ use App\Http\Requests\Quote\EditQuoteRequest;
use App\Http\Requests\Quote\ShowQuoteRequest;
use App\Http\Requests\Quote\StoreQuoteRequest;
use App\Http\Requests\Quote\UpdateQuoteRequest;
use App\Http\Requests\Quote\UploadQuoteRequest;
use App\Jobs\Invoice\ZipInvoices;
use App\Models\Client;
use App\Models\Invoice;
@ -34,6 +35,7 @@ use App\Transformers\QuoteTransformer;
use App\Utils\Ninja;
use App\Utils\TempFile;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -43,6 +45,7 @@ use Illuminate\Http\Response;
class QuoteController extends BaseController
{
use MakesHash;
use SavesDocuments;
protected $entity_type = Quote::class;
@ -521,7 +524,7 @@ class QuoteController extends BaseController
}
});
ZipInvoices::dispatch($quotes, $quotes->first()->company, auth()->user()->email);
ZipInvoices::dispatch($quotes, $quotes->first()->company, auth()->user());
return response()->json(['message' => ctrans('texts.sent_message')], 200);
}
@ -717,4 +720,65 @@ class QuoteController extends BaseController
return response()->download($file_path);
}
/**
* Update the specified resource in storage.
*
* @param UploadQuoteRequest $request
* @param Quote $quote
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/quotes/{id}/upload",
* operationId="uploadQuote",
* tags={"quotes"},
* summary="Uploads a document to a quote",
* description="Handles the uploading of a document to a quote",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Quote Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Quote object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Quote"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadQuoteRequest $request, Quote $quote)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $quote);
return $this->itemResponse($quote->fresh());
}
}

View File

@ -20,10 +20,12 @@ use App\Http\Requests\RecurringInvoice\EditRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\ShowRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\StoreRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\UpdateRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\UploadRecurringInvoiceRequest;
use App\Models\RecurringInvoice;
use App\Repositories\RecurringInvoiceRepository;
use App\Transformers\RecurringInvoiceTransformer;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -33,6 +35,7 @@ use Illuminate\Http\Response;
class RecurringInvoiceController extends BaseController
{
use MakesHash;
use SavesDocuments;
protected $entity_type = RecurringInvoice::class;
@ -680,4 +683,65 @@ class RecurringInvoiceController extends BaseController
break;
}
}
/**
* Update the specified resource in storage.
*
* @param UploadRecurringInvoiceRequest $request
* @param RecurringInvoice $recurring_invoice
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/recurring_invoices/{id}/upload",
* operationId="uploadRecurringInvoice",
* tags={"recurring_invoices"},
* summary="Uploads a document to a recurring_invoice",
* description="Handles the uploading of a document to a recurring_invoice",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The RecurringInvoice Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the RecurringInvoice object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/RecurringInvoice"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $recurring_invoice);
return $this->itemResponse($recurring_invoice->fresh());
}
}

View File

@ -21,12 +21,14 @@ use App\Http\Requests\Task\EditTaskRequest;
use App\Http\Requests\Task\ShowTaskRequest;
use App\Http\Requests\Task\StoreTaskRequest;
use App\Http\Requests\Task\UpdateTaskRequest;
use App\Http\Requests\Task\UploadTaskRequest;
use App\Models\Task;
use App\Repositories\TaskRepository;
use App\Transformers\TaskTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\BulkOptions;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Utils\Traits\Uploadable;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -40,6 +42,7 @@ class TaskController extends BaseController
use MakesHash;
use Uploadable;
use BulkOptions;
use SavesDocuments;
protected $entity_type = Task::class;
@ -506,4 +509,65 @@ class TaskController extends BaseController
{
//todo
}
/**
* Update the specified resource in storage.
*
* @param UploadTaskRequest $request
* @param Task $task
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/tasks/{id}/upload",
* operationId="uploadTask",
* tags={"tasks"},
* summary="Uploads a document to a task",
* description="Handles the uploading of a document to a task",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Task Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Task object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Task"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadTaskRequest $request, Task $task)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $task);
return $this->itemResponse($task->fresh());
}
}

View File

@ -0,0 +1,63 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use PragmaRX\Google2FA\Google2FA;
use Crypt;
class TwoFactorController extends BaseController
{
public function setupTwoFactor()
{
$user = auth()->user();
if ($user->google_2fa_secret)
return response()->json(['message' => '2FA already enabled'], 400);
elseif(! $user->phone)
return response()->json(['message' => ctrans('texts.set_phone_for_two_factor')], 400);
elseif(! $user->confirmed)
return response()->json(['message' => 'Please confirm your account first'], 400);
$google2fa = new Google2FA();
$secret = $google2fa->generateSecretKey();
$qr_code = $google2fa->getQRCodeGoogleUrl(
config('ninja.app_name')
$user->email,
$secret
);
$data = [
'secret' => $secret,
'qrCode' => $qrCode,
];
return response()->json(['data' => $data], 200);
}
public function enableTwoFactor()
{
$user = auth()->user();
$secret = request()->input('secret');
$oneTimePassword = request()->input('one_time_password');
if (! $secret || ! \Google2FA::verifyKey($secret, $oneTimePassword)) {
return response()->json('message' > ctrans('texts.invalid_one_time_password'));
} elseif (! $user->google_2fa_secret && $user->phone && $user->confirmed) {
$user->google_2fa_secret = encrypt($secret);
$user->save();
}
return response()->json(['message' => ctrans('texts.enabled_two_factor')], 200);
}
}

View File

@ -23,11 +23,16 @@ use App\Http\Requests\User\CreateUserRequest;
use App\Http\Requests\User\DestroyUserRequest;
use App\Http\Requests\User\DetachCompanyUserRequest;
use App\Http\Requests\User\EditUserRequest;
use App\Http\Requests\User\ReconfirmUserRequest;
use App\Http\Requests\User\ShowUserRequest;
use App\Http\Requests\User\StoreUserRequest;
use App\Http\Requests\User\UpdateUserRequest;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\User\UserEmailChanged;
use App\Mail\Admin\VerifyUserObject;
use App\Models\CompanyUser;
use App\Models\User;
use App\Repositories\UserRepository;
@ -369,13 +374,28 @@ class UserController extends BaseController
*/
public function update(UpdateUserRequest $request, User $user)
{
$old_email = $user->email;
$old_company_user = $user->company_user;
$old_user = json_encode($user);
$old_user_email = $user->getOriginal('email');
$new_email = $request->input('email');
$new_user = $this->user_repo->save($request->all(), $user);
$new_user = $user->fresh();
$user = $this->user_repo->save($request->all(), $user);
/* When changing email address we store the former email in case we need to rollback */
if ($old_user_email != $new_email) {
$user->last_confirmed_email_address = $old_user_email;
$user->save();
UserEmailChanged::dispatch($new_user, json_decode($old_user), auth()->user()->company());
}
if ($old_email != $new_email) {
UserEmailChanged::dispatch($new_email, $old_email, auth()->user()->company());
if(
strcasecmp($old_company_user->permissions, $user->company_user->permissions) != 0 ||
$old_company_user->is_admin != $user->company_user->is_admin
){
$user->company_user()->update(["permissions_updated_at" => now()]);
}
event(new UserWasUpdated($user, auth()->user(), auth()->user()->company, Ninja::eventVars()));
@ -670,4 +690,70 @@ class UserController extends BaseController
return response()->json(['message' => ctrans('texts.user_detached')], 200);
}
/**
* Detach an existing user to a company.
*
* @OA\Post(
* path="/api/v1/users/{user}/reconfirm",
* operationId="reconfirmUser",
* tags={"users"},
* summary="Reconfirm an existing user to a company",
* description="Reconfirm an existing user from a company",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="user",
* in="path",
* description="The user hashed_id",
* example="FD767dfd7",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Success response",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
* @param ReconfirmUserRequest $request
* @param User $user
* @return \Illuminate\Http\JsonResponse
*/
public function reconfirm(ReconfirmUserRequest $request, User $user)
{
$user->confirmation_code = $this->createDbHash($user->company()->db);
$user->save();
$nmo = new NinjaMailerObject;
$nmo->mailable = new NinjaMailer((new VerifyUserObject($user, $user->company()))->build());
$nmo->company = $user->company();
$nmo->to_user = $user;
$nmo->settings = $user->company->settings;
NinjaMailerJob::dispatch($nmo);
return response()->json(['message' => ctrans('texts.confirmation_resent')], 200);
}
}

View File

@ -21,12 +21,14 @@ use App\Http\Requests\Vendor\EditVendorRequest;
use App\Http\Requests\Vendor\ShowVendorRequest;
use App\Http\Requests\Vendor\StoreVendorRequest;
use App\Http\Requests\Vendor\UpdateVendorRequest;
use App\Http\Requests\Vendor\UploadVendorRequest;
use App\Models\Vendor;
use App\Repositories\VendorRepository;
use App\Transformers\VendorTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\BulkOptions;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Utils\Traits\Uploadable;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -39,6 +41,7 @@ class VendorController extends BaseController
use MakesHash;
use Uploadable;
use BulkOptions;
use SavesDocuments;
protected $entity_type = Vendor::class;
@ -511,4 +514,65 @@ class VendorController extends BaseController
{
//todo
}
/**
* Update the specified resource in storage.
*
* @param UploadVendorRequest $request
* @param Vendor $vendor
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/vendors/{id}/upload",
* operationId="uploadVendor",
* tags={"vendors"},
* summary="Uploads a document to a vendor",
* description="Handles the uploading of a document to a vendor",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Vendor Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the Vendor object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Vendor"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadVendorRequest $request, Vendor $vendor)
{
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $vendor);
return $this->itemResponse($vendor->fresh());
}
}

View File

@ -28,6 +28,7 @@ class CreditsTable extends Component
{
$query = Credit::query()
->where('client_id', auth('contact')->user()->client->id)
->where('status_id', '<>', Credit::STATUS_DRAFT)
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->paginate($this->per_page);

View File

@ -25,38 +25,15 @@ class DocumentsTable extends Component
public $per_page = 10;
public $status = [
'resources',
];
public function mount($client)
{
$this->client = $client;
}
public function statusChange($status)
{
if (in_array($status, $this->status)) {
return $this->status = array_diff($this->status, [$status]);
}
array_push($this->status, $status);
}
public function render()
{
$query = $this->client->documents();
if (in_array('resources', $this->status) && ! in_array('client', $this->status)) {
$query = $query->where('documentable_type', '!=', Client::class);
}
if (in_array('client', $this->status) && ! in_array('resources', $this->status)) {
$query = $query->where('documentable_type', Client::class);
}
$query = $query
->where('is_public', true)
$query = $this->client
->documents()
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->paginate($this->per_page);

View File

@ -0,0 +1,34 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Livewire;
use Livewire\Component;
class PayNowDropdown extends Component
{
public $total;
public $methods;
public function mount(int $total)
{
$this->total = $total;
$this->methods = auth()->user()->client->service()->getPaymentMethods($total);
}
public function render()
{
return render('components.livewire.pay-now-dropdown');
}
}

View File

@ -2,7 +2,9 @@
namespace App\Http\Middleware;
use App\Models\Account;
use App\Models\Company;
use App\Utils\Ninja;
use Closure;
use Illuminate\Http\Request;
@ -11,35 +13,45 @@ class ContactRegister
/**
* Handle an incoming request.
*
* @param Request $request
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
/*
* Notes:
*
* 1. If request supports subdomain (for hosted) check domain and continue request.
* 2. If request doesn't support subdomain and doesn' have company_key, abort
* 3. firstOrFail() will abort with 404 if company with company_key wasn't found.
* 4. Abort if setting isn't enabled.
*/
// Resolving based on subdomain. Used in version 5 hosted platform.
if ($request->subdomain) {
$company = Company::where('subdomain', $request->subdomain)->firstOrFail();
abort_unless($company->getSetting('enable_client_registration'), 404);
$request->merge(['key' => $company->company_key]);
return $next($request);
}
abort_unless($request->company_key, 404);
// For self-hosted platforms with multiple companies, resolving is done using company key
// if it doesn't resolve using a domain.
if ($request->route()->parameter('company_key') && Ninja::isSelfHost()) {
$company = Company::where('company_key', $request->company_key)->firstOrFail();
$company = Company::where('company_key', $request->company_key)->firstOrFail();
abort_unless($company->client_can_register, 404);
abort_unless($company->client_can_register, 404);
return $next($request);
}
return $next($request);
// As a fallback for self-hosted, it will use default company in the system
// if key isn't provided in the url.
if (!$request->route()->parameter('company_key') && Ninja::isSelfHost()) {
$company = Account::first()->default_company;
abort_unless($company->client_can_register, 404);
$request->merge(['key' => $company->company_key]);
return $next($request);
}
return abort(404);
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Client;
use App\Http\Requests\Request;
class UploadClientRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->client);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -27,7 +27,8 @@ class ShowDocumentRequest extends FormRequest
*/
public function authorize()
{
return auth()->user('contact')->client->id == $this->document->documentable_id;
return auth()->user('contact')->client->id == $this->document->documentable_id
|| $this->document->documentable->client_id == auth()->user('contact')->client->id;
}
/**

View File

@ -0,0 +1,39 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Company;
use App\Http\Requests\Request;
class UploadCompanyRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->company);
}
public function rules()
{
$rules = [];
if($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;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Credit;
use App\Http\Requests\Request;
class UploadCreditRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->credit);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Expense;
use App\Http\Requests\Request;
class UploadExpenseRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->expense);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -28,10 +28,12 @@ class ImportRequest extends Request
public function rules()
{
return [
'hash' => 'required|string',
'entity_type' => 'required|string',
'column_map' => 'required|array',
'skip_header' => 'required|boolean'
'import_type' => 'required',
'files' => 'required_without:hash|array|min:1|max:6',
'hash' => 'nullable|string',
'column_map' => 'required_with:hash|array',
'skip_header' => 'required_with:hash|boolean',
'files.*' => 'file|mimes:csv,txt',
];
}
}

View File

@ -28,8 +28,9 @@ class PreImportRequest extends Request
public function rules()
{
return [
'file' => 'required|file|mimes:csv,txt',
'entity_type' => 'required',
'files.*' => 'file|mimes:csv,txt',
'files' => 'required|array|min:1|max:6',
'import_type' => 'required',
];
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Invoice;
use App\Http\Requests\Request;
class UploadInvoiceRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->invoice);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Payment Ninja (https://paymentninja.com).
*
* @link https://github.com/paymentninja/paymentninja source repository
*
* @copyright Copyright (c) 2021. Payment Ninja LLC (https://paymentninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Payment;
use App\Http\Requests\Request;
class UploadPaymentRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->payment);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Product Ninja (https://paymentninja.com).
*
* @link https://github.com/paymentninja/paymentninja source repository
*
* @copyright Copyright (c) 2021. Product Ninja LLC (https://paymentninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Product;
use App\Http\Requests\Request;
class UploadProductRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->product);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Project Ninja (https://paymentninja.com).
*
* @link https://github.com/paymentninja/paymentninja source repository
*
* @copyright Copyright (c) 2021. Project Ninja LLC (https://paymentninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Project;
use App\Http\Requests\Request;
class UploadProjectRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->project);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Quote Ninja (https://paymentninja.com).
*
* @link https://github.com/paymentninja/paymentninja source repository
*
* @copyright Copyright (c) 2021. Quote Ninja LLC (https://paymentninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Quote;
use App\Http\Requests\Request;
class UploadQuoteRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->quote);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Quote Ninja (https://paymentninja.com).
*
* @link https://github.com/paymentninja/paymentninja source repository
*
* @copyright Copyright (c) 2021. Quote Ninja LLC (https://paymentninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\RecurringInvoice;
use App\Http\Requests\Request;
class UploadRecurringInvoiceRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->recurring_invoice);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Quote Ninja (https://paymentninja.com).
*
* @link https://github.com/paymentninja/paymentninja source repository
*
* @copyright Copyright (c) 2021. Quote Ninja LLC (https://paymentninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Task;
use App\Http\Requests\Request;
class UploadTaskRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->task);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -0,0 +1,28 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\User;
use App\Http\Requests\Request;
use App\Models\User;
class ReconfirmUserRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->Admin();
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Quote Ninja (https://paymentninja.com).
*
* @link https://github.com/paymentninja/paymentninja source repository
*
* @copyright Copyright (c) 2021. Quote Ninja LLC (https://paymentninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Vendor;
use App\Http\Requests\Request;
class UploadVendorRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->vendor);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -57,6 +57,7 @@ class PortalComposer
$data['client'] = auth()->user()->client;
$data['settings'] = $this->settings;
$data['currencies'] = TranslationHelper::getCurrencies();
$data['contact'] = auth('contact')->user();
$data['multiple_contacts'] = session()->get('multiple_contacts');
@ -69,8 +70,8 @@ class PortalComposer
//@todo wire this back in when we are happy with dashboard.
// if($this->settings->enable_client_portal_dashboard == TRUE)
// $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
// $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
$data[] = ['title' => ctrans('texts.invoices'), 'url' => 'client.invoices.index', 'icon' => 'file-text'];
$data[] = ['title' => ctrans('texts.recurring_invoices'), 'url' => 'client.recurring_invoices.index', 'icon' => 'file'];
$data[] = ['title' => ctrans('texts.payments'), 'url' => 'client.payments.index', 'icon' => 'credit-card'];

View File

@ -1,10 +1,10 @@
<?php
/**
* client Ninja (https://clientninja.com).
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -0,0 +1,51 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Definitions;
class ExpenseMap
{
public static function importable()
{
return [
0 => 'expense.vendor',
1 => 'expense.client',
2 => 'expense.project',
3 => 'expense.category',
4 => 'expense.amount',
5 => 'expense.currency',
6 => 'expense.date',
7 => 'expense.payment_type',
8 => 'expense.payment_date',
9 => 'expense.transaction_reference',
10 => 'expense.public_notes',
11 => 'expense.private_notes',
];
}
public static function import_keys()
{
return [
0 => 'texts.vendor',
1 => 'texts.client',
2 => 'texts.project',
3 => 'texts.category',
4 => 'texts.amount',
5 => 'texts.currency',
6 => 'texts.date',
7 => 'texts.payment_type',
8 => 'texts.payment_date',
9 => 'texts.transaction_reference',
10 => 'texts.public_notes',
11 => 'texts.private_notes',
];
}
}

View File

@ -26,50 +26,51 @@ class InvoiceMap
7 => 'invoice.date',
8 => 'invoice.due_date',
9 => 'invoice.terms',
10 => 'invoice.public_notes',
11 => 'invoice.is_sent',
12 => 'invoice.private_notes',
13 => 'invoice.uses_inclusive_taxes',
14 => 'invoice.tax_name1',
15 => 'invoice.tax_rate1',
16 => 'invoice.tax_name2',
17 => 'invoice.tax_rate2',
18 => 'invoice.tax_name3',
19 => 'invoice.tax_rate3',
20 => 'invoice.is_amount_discount',
21 => 'invoice.footer',
22 => 'invoice.partial',
23 => 'invoice.partial_due_date',
24 => 'invoice.custom_value1',
25 => 'invoice.custom_value2',
26 => 'invoice.custom_value3',
27 => 'invoice.custom_value4',
28 => 'invoice.custom_surcharge1',
29 => 'invoice.custom_surcharge2',
30 => 'invoice.custom_surcharge3',
31 => 'invoice.custom_surcharge4',
32 => 'invoice.exchange_rate',
33 => 'payment.date',
34 => 'payment.amount',
35 => 'payment.transaction_reference',
36 => 'item.quantity',
37 => 'item.cost',
38 => 'item.product_key',
39 => 'item.notes',
40 => 'item.discount',
41 => 'item.is_amount_discount',
42 => 'item.tax_name1',
43 => 'item.tax_rate1',
44 => 'item.tax_name2',
45 => 'item.tax_rate2',
46 => 'item.tax_name3',
47 => 'item.tax_rate3',
48 => 'item.custom_value1',
49 => 'item.custom_value2',
50 => 'item.custom_value3',
51 => 'item.custom_value4',
52 => 'item.type_id',
53 => 'client.email',
10 => 'invoice.status',
11 => 'invoice.public_notes',
12 => 'invoice.is_sent',
13 => 'invoice.private_notes',
14 => 'invoice.uses_inclusive_taxes',
15 => 'invoice.tax_name1',
16 => 'invoice.tax_rate1',
17 => 'invoice.tax_name2',
18 => 'invoice.tax_rate2',
19 => 'invoice.tax_name3',
20 => 'invoice.tax_rate3',
21 => 'invoice.is_amount_discount',
22 => 'invoice.footer',
23 => 'invoice.partial',
24 => 'invoice.partial_due_date',
25 => 'invoice.custom_value1',
26 => 'invoice.custom_value2',
27 => 'invoice.custom_value3',
28 => 'invoice.custom_value4',
29 => 'invoice.custom_surcharge1',
30 => 'invoice.custom_surcharge2',
31 => 'invoice.custom_surcharge3',
32 => 'invoice.custom_surcharge4',
33 => 'invoice.exchange_rate',
34 => 'payment.date',
35 => 'payment.amount',
36 => 'payment.transaction_reference',
37 => 'item.quantity',
38 => 'item.cost',
39 => 'item.product_key',
40 => 'item.notes',
41 => 'item.discount',
42 => 'item.is_amount_discount',
43 => 'item.tax_name1',
44 => 'item.tax_rate1',
45 => 'item.tax_name2',
46 => 'item.tax_rate2',
47 => 'item.tax_name3',
48 => 'item.tax_rate3',
49 => 'item.custom_value1',
50 => 'item.custom_value2',
51 => 'item.custom_value3',
52 => 'item.custom_value4',
53 => 'item.type_id',
54 => 'client.email',
];
}
@ -86,50 +87,51 @@ class InvoiceMap
7 => 'texts.date',
8 => 'texts.due_date',
9 => 'texts.terms',
10 => 'texts.public_notes',
11 => 'texts.sent',
12 => 'texts.private_notes',
13 => 'texts.uses_inclusive_taxes',
14 => 'texts.tax_name',
15 => 'texts.tax_rate',
16 => 'texts.tax_name',
17 => 'texts.tax_rate',
18 => 'texts.tax_name',
19 => 'texts.tax_rate',
20 => 'texts.is_amount_discount',
21 => 'texts.footer',
22 => 'texts.partial',
23 => 'texts.partial_due_date',
24 => 'texts.custom_value1',
25 => 'texts.custom_value2',
26 => 'texts.custom_value3',
27 => 'texts.custom_value4',
28 => 'texts.surcharge',
10 => 'texts.status',
11 => 'texts.public_notes',
12 => 'texts.sent',
13 => 'texts.private_notes',
14 => 'texts.uses_inclusive_taxes',
15 => 'texts.tax_name',
16 => 'texts.tax_rate',
17 => 'texts.tax_name',
18 => 'texts.tax_rate',
19 => 'texts.tax_name',
20 => 'texts.tax_rate',
21 => 'texts.is_amount_discount',
22 => 'texts.footer',
23 => 'texts.partial',
24 => 'texts.partial_due_date',
25 => 'texts.custom_value1',
26 => 'texts.custom_value2',
27 => 'texts.custom_value3',
28 => 'texts.custom_value4',
29 => 'texts.surcharge',
30 => 'texts.surcharge',
31 => 'texts.surcharge',
32 => 'texts.exchange_rate',
33 => 'texts.payment_date',
34 => 'texts.payment_amount',
35 => 'texts.transaction_reference',
36 => 'texts.quantity',
37 => 'texts.cost',
38 => 'texts.product_key',
39 => 'texts.notes',
40 => 'texts.discount',
41 => 'texts.is_amount_discount',
42 => 'texts.tax_name',
43 => 'texts.tax_rate',
44 => 'texts.tax_name',
45 => 'texts.tax_rate',
46 => 'texts.tax_name',
47 => 'texts.tax_rate',
48 => 'texts.custom_value',
32 => 'texts.surcharge',
33 => 'texts.exchange_rate',
34 => 'texts.payment_date',
35 => 'texts.payment_amount',
36 => 'texts.transaction_reference',
37 => 'texts.quantity',
38 => 'texts.cost',
39 => 'texts.product_key',
40 => 'texts.notes',
41 => 'texts.discount',
42 => 'texts.is_amount_discount',
43 => 'texts.tax_name',
44 => 'texts.tax_rate',
45 => 'texts.tax_name',
46 => 'texts.tax_rate',
47 => 'texts.tax_name',
48 => 'texts.tax_rate',
49 => 'texts.custom_value',
50 => 'texts.custom_value',
51 => 'texts.custom_value',
52 => 'texts.type',
53 => 'texts.email',
52 => 'texts.custom_value',
53 => 'texts.type',
54 => 'texts.email',
];
}
}

View File

@ -1,10 +1,10 @@
<?php
/**
* client Ninja (https://clientninja.com).
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -1,10 +1,10 @@
<?php
/**
* client Ninja (https://clientninja.com).
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -0,0 +1,61 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Definitions;
class VendorMap
{
public static function importable()
{
return [
0 => 'vendor.name',
1 => 'vendor.phone',
2 => 'vendor.id_number',
3 => 'vendor.vat_number',
4 => 'vendor.website',
5 => 'vendor.first_name',
6 => 'vendor.last_name',
7 => 'vendor.email',
8 => 'vendor.currency_id',
9 => 'vendor.public_notes',
10 => 'vendor.private_notes',
11 => 'vendor.address1',
12 => 'vendor.address2',
13 => 'vendor.city',
14 => 'vendor.state',
15 => 'vendor.postal_code',
16 => 'vendor.country_id',
];
}
public static function import_keys()
{
return [
0 => 'texts.name',
1 => 'texts.phone',
2 => 'texts.id_number',
3 => 'texts.vat_number',
4 => 'texts.website',
5 => 'texts.first_name',
6 => 'texts.last_name',
7 => 'texts.email',
8 => 'texts.currency',
9 => 'texts.public_notes',
10 => 'texts.private_notes',
11 => 'texts.address1',
12 => 'texts.address2',
13 => 'texts.city',
14 => 'texts.state',
15 => 'texts.postal_code',
16 => 'texts.country',
];
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace App\Import;
class ImportException extends \Exception{
}

View File

@ -1,10 +1,10 @@
<?php
/**
* client Ninja (https://clientninja.com).
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
@ -53,41 +53,32 @@ class BaseTransformer
return (isset($data[$field]) && $data[$field]) ? $data[$field] : '1';
}
public function getCurrencyByCode($data)
{
$code = array_key_exists('client.currency_id', $data) ? $data['client.currency_id'] : false;
public function getCurrencyByCode( $data, $key = 'client.currency_id' ) {
$code = array_key_exists( $key, $data ) ? $data[ $key ] : false;
if ($code) {
$currency = $this->maps['currencies']->where('code', $code)->first();
return $this->maps['currencies'][ $code ] ?? $this->maps['company']->settings->currency_id;
}
if ($currency) {
return $currency->id;
}
}
public function getClient($client_name, $client_email) {
$clients = $this->maps['company']->clients;
return $this->maps['company']->settings->currency_id;
}
$clients = $clients->where( 'name', $client_name );
public function getClient($client_name, $client_email)
{
$clients = $this->maps['company']->clients;
if ( $clients->count() >= 1 ) {
return $clients->first()->id;
}
$clients = $clients->where('name', $client_name);
if ( ! empty( $client_email ) ) {
$contacts = ClientContact::where( 'company_id', $this->maps['company']->id )
->where( 'email', $client_email );
if ($clients->count() >= 1) {
return $clients->first()->id;
}
if ( $contacts->count() >= 1 ) {
return $contacts->first()->client_id;
}
}
$contacts = ClientContact::where('company_id', $this->maps['company']->id)
->where('email', $client_email);
if ($contacts->count() >=1) {
return $contacts->first()->client_id;
}
return null;
}
return null;
}
@ -101,7 +92,7 @@ class BaseTransformer
{
$name = trim(strtolower($name));
return isset($this->maps[ENTITY_CLIENT][$name]);
return isset( $this->maps['client'][ $name ] );
}
/**
@ -113,7 +104,7 @@ class BaseTransformer
{
$name = trim(strtolower($name));
return isset($this->maps[ENTITY_VENDOR][$name]);
return isset( $this->maps['vendor'][ $name ] );
}
@ -126,7 +117,7 @@ class BaseTransformer
{
$key = trim(strtolower($key));
return isset($this->maps[ENTITY_PRODUCT][$key]);
return isset( $this->maps['product'][ $key ] );
}
@ -167,7 +158,7 @@ class BaseTransformer
{
$name = strtolower(trim($name));
return isset($this->maps[ENTITY_CLIENT][$name]) ? $this->maps[ENTITY_CLIENT][$name] : null;
return isset( $this->maps['client'][ $name ] ) ? $this->maps['client'][ $name ] : null;
}
/**
@ -322,7 +313,7 @@ class BaseTransformer
*/
public function getInvoiceNumber($number)
{
return $number ? str_pad(trim($number), 4, '0', STR_PAD_LEFT) : null;
return $number ? ltrim( trim( $number ), '0' ) : null;
}
/**
@ -334,7 +325,8 @@ class BaseTransformer
{
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps[ENTITY_INVOICE][$invoiceNumber]) ? $this->maps[ENTITY_INVOICE][$invoiceNumber] : null;
return isset( $this->maps['invoice'][ $invoiceNumber ] ) ? $this->maps['invoice'][ $invoiceNumber ] : null;
}
/**
@ -346,7 +338,8 @@ class BaseTransformer
{
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps['invoices'][$invoiceNumber]) ? $this->maps['invoices'][$invoiceNumber]->public_id : null;
return isset( $this->maps['invoice'][ $invoiceNumber ] ) ? $this->maps['invoices'][ $invoiceNumber ]->public_id : null;
}
/**
@ -359,7 +352,7 @@ class BaseTransformer
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps[ENTITY_INVOICE][$invoiceNumber]);
return $this->maps['invoice'][ $invoiceNumber ] ?? null;
}
/**
@ -372,7 +365,7 @@ class BaseTransformer
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber]) ? $this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber] : null;
return $this->maps['invoice_client'][ $invoiceNumber ] ?? null;
}
/**
@ -384,18 +377,39 @@ class BaseTransformer
{
$name = strtolower(trim($name));
return isset($this->maps[ENTITY_VENDOR][$name]) ? $this->maps[ENTITY_VENDOR][$name] : null;
return $this->maps['vendor'][ $name ] ?? null;
}
/**
* @param $name
*
* @return null
*/
public function getExpenseCategoryId($name)
{
$name = strtolower(trim($name));
/**
* @param $name
*
* @return null
*/
public function getExpenseCategoryId( $name ) {
$name = strtolower( trim( $name ) );
return isset($this->maps[ENTITY_EXPENSE_CATEGORY][$name]) ? $this->maps[ENTITY_EXPENSE_CATEGORY][$name] : null;
}
return $this->maps['expense_category'][ $name ] ?? null;
}
/**
* @param $name
*
* @return null
*/
public function getProjectId( $name ) {
$name = strtolower( trim( $name ) );
return $this->maps['project'][ $name ] ?? null;
}
/**
* @param $name
*
* @return null
*/
public function getPaymentTypeId( $name ) {
$name = strtolower( trim( $name ) );
return $this->maps['payment_type'][ $name ] ?? null;
}
}

View File

@ -1,10 +1,10 @@
<?php
/**
* client Ninja (https://clientninja.com).
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -0,0 +1,79 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Csv;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use Illuminate\Support\Str;
/**
* Class ClientTransformer.
*/
class ClientTransformer extends BaseTransformer
{
/**
* @param $data
*
* @return array|bool
*/
public function transform($data)
{
if (isset($data->name) && $this->hasClient($data->name)) {
throw new ImportException('Client already exists');
}
$settings = new \stdClass;
$settings->currency_id = (string)$this->getCurrencyByCode($data);
return [
'company_id' => $this->maps['company']->id,
'name' => $this->getString( $data, 'client.name' ),
'work_phone' => $this->getString( $data, 'client.phone' ),
'address1' => $this->getString( $data, 'client.address1' ),
'address2' => $this->getString( $data, 'client.address2' ),
'city' => $this->getString( $data, 'client.city' ),
'state' => $this->getString( $data, 'client.state' ),
'shipping_address1' => $this->getString( $data, 'client.shipping_address1' ),
'shipping_address2' => $this->getString( $data, 'client.shipping_address2' ),
'shipping_city' => $this->getString( $data, 'client.shipping_city' ),
'shipping_state' => $this->getString( $data, 'client.shipping_state' ),
'shipping_postal_code' => $this->getString( $data, 'client.shipping_postal_code' ),
'public_notes' => $this->getString( $data, 'client.public_notes' ),
'private_notes' => $this->getString( $data, 'client.private_notes' ),
'website' => $this->getString( $data, 'client.website' ),
'vat_number' => $this->getString( $data, 'client.vat_number' ),
'id_number' => $this->getString( $data, 'client.id_number' ),
'custom_value1' => $this->getString( $data, 'client.custom1' ),
'custom_value2' => $this->getString( $data, 'client.custom2' ),
'custom_value3' => $this->getString( $data, 'client.custom3' ),
'custom_value4' => $this->getString( $data, 'client.custom4' ),
'balance' => preg_replace( '/[^0-9,.]+/', '', $this->getFloat( $data, 'client.balance' ) ),
'paid_to_date' => preg_replace( '/[^0-9,.]+/', '', $this->getFloat( $data, 'client.paid_to_date' ) ),
'credit_balance' => 0,
'settings' => $settings,
'client_hash' => Str::random( 40 ),
'contacts' => [
[
'first_name' => $this->getString( $data, 'contact.first_name' ),
'last_name' => $this->getString( $data, 'contact.last_name' ),
'email' => $this->getString( $data, 'contact.email' ),
'phone' => $this->getString( $data, 'contact.phone' ),
'custom_value1' => $this->getString( $data, 'contact.custom1' ),
'custom_value2' => $this->getString( $data, 'contact.custom2' ),
'custom_value3' => $this->getString( $data, 'contact.custom3' ),
'custom_value4' => $this->getString( $data, 'contact.custom4' ),
],
],
'country_id' => isset( $data['client.country'] ) ? $this->getCountryId( $data['client.country']) : null,
'shipping_country_id' => isset($data['client.shipping_country'] ) ? $this->getCountryId( $data['client.shipping_country'] ) : null,
];
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Import\Transformers\Csv;
use App\Import\Transformers\BaseTransformer;
/**
* Class InvoiceTransformer.
*/
class ExpenseTransformer extends BaseTransformer {
/**
* @param $data
*
* @return bool|array
*/
public function transform( $data ) {
$clientId = isset( $data['expense.client'] ) ? $this->getClientId( $data['expense.client'] ) : null;
return [
'company_id' => $this->maps['company']->id,
'amount' => $this->getFloat( $data, 'expense.amount' ),
'currency_id' => $this->getCurrencyByCode( $data, 'expense.currency_id' ),
'vendor_id' => isset( $data['expense.vendor'] ) ? $this->getVendorId( $data['expense.vendor'] ) : null,
'client_id' => isset( $data['expense.client'] ) ? $this->getClientId( $data['expense.client'] ) : null,
'expense_date' => isset( $data['expense.date'] ) ? date( 'Y-m-d', strtotime( $data['expense.date'] ) ) : null,
'public_notes' => $this->getString( $data, 'expense.public_notes' ),
'private_notes' => $this->getString( $data, 'expense.private_notes' ),
'expense_category_id' => isset( $data['expense.category'] ) ? $this->getExpenseCategoryId( $data['expense.category'] ) : null,
'project_id' => isset( $data['expense.project'] ) ? $this->getProjectId( $data['expense.project'] ) : null,
'payment_type_id' => isset( $data['expense.payment_type'] ) ? $this->getPaymentTypeId( $data['expense.payment_type'] ) : null,
'payment_date' => isset( $data['expense.payment_date'] ) ? date( 'Y-m-d', strtotime( $data['expense.payment_date'] ) ) : null,
'transaction_reference' => $this->getString( $data, 'expense.transaction_reference' ),
'should_be_invoiced' => $clientId ? true : false,
];
}
}

View File

@ -0,0 +1,131 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Csv;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use App\Models\Invoice;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer {
/**
* @param $data
*
* @return bool|array
*/
public function transform( $line_items_data ) {
$invoice_data = reset( $line_items_data );
if ( $this->hasInvoice( $invoice_data['invoice.number'] ) ) {
throw new ImportException( 'Invoice number already exists' );
}
$invoiceStatusMap = [
'sent' => Invoice::STATUS_SENT,
'draft' => Invoice::STATUS_DRAFT,
];
$transformed = [
'company_id' => $this->maps['company']->id,
'number' => $this->getString( $invoice_data, 'invoice.number' ),
'user_id' => $this->getString( $invoice_data, 'invoice.user_id' ),
'amount' => $amount = $this->getFloat( $invoice_data, 'invoice.amount' ),
'balance' => isset( $invoice_data['invoice.balance'] ) ? $this->getFloat( $invoice_data, 'invoice.balance' ) : $amount,
'client_id' => $this->getClient( $this->getString( $invoice_data, 'client.name' ), $this->getString( $invoice_data, 'client.email' ) ),
'discount' => $this->getFloat( $invoice_data, 'invoice.discount' ),
'po_number' => $this->getString( $invoice_data, 'invoice.po_number' ),
'date' => isset( $invoice_data['invoice.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.date'] ) ) : null,
'due_date' => isset( $invoice_data['invoice.due_date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.due_date'] ) ) : null,
'terms' => $this->getString( $invoice_data, 'invoice.terms' ),
'public_notes' => $this->getString( $invoice_data, 'invoice.public_notes' ),
'is_sent' => $this->getString( $invoice_data, 'invoice.is_sent' ),
'private_notes' => $this->getString( $invoice_data, 'invoice.private_notes' ),
'tax_name1' => $this->getString( $invoice_data, 'invoice.tax_name1' ),
'tax_rate1' => $this->getFloat( $invoice_data, 'invoice.tax_rate1' ),
'tax_name2' => $this->getString( $invoice_data, 'invoice.tax_name2' ),
'tax_rate2' => $this->getFloat( $invoice_data, 'invoice.tax_rate2' ),
'tax_name3' => $this->getString( $invoice_data, 'invoice.tax_name3' ),
'tax_rate3' => $this->getFloat( $invoice_data, 'invoice.tax_rate3' ),
'custom_value1' => $this->getString( $invoice_data, 'invoice.custom_value1' ),
'custom_value2' => $this->getString( $invoice_data, 'invoice.custom_value2' ),
'custom_value3' => $this->getString( $invoice_data, 'invoice.custom_value3' ),
'custom_value4' => $this->getString( $invoice_data, 'invoice.custom_value4' ),
'footer' => $this->getString( $invoice_data, 'invoice.footer' ),
'partial' => $this->getFloat( $invoice_data, 'invoice.partial' ),
'partial_due_date' => $this->getString( $invoice_data, 'invoice.partial_due_date' ),
'custom_surcharge1' => $this->getString( $invoice_data, 'invoice.custom_surcharge1' ),
'custom_surcharge2' => $this->getString( $invoice_data, 'invoice.custom_surcharge2' ),
'custom_surcharge3' => $this->getString( $invoice_data, 'invoice.custom_surcharge3' ),
'custom_surcharge4' => $this->getString( $invoice_data, 'invoice.custom_surcharge4' ),
'exchange_rate' => $this->getString( $invoice_data, 'invoice.exchange_rate' ),
'status_id' => $invoiceStatusMap[ $status =
strtolower( $this->getString( $invoice_data, 'invoice.status' ) ) ] ??
Invoice::STATUS_SENT,
'viewed' => $status === 'viewed',
'archived' => $status === 'archived',
];
if ( isset( $invoice_data['payment.amount'] ) ) {
$transformed['payments'] = [
[
'date' => isset( $invoice_data['payment.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['payment.date'] ) ) : date( 'y-m-d' ),
'transaction_reference' => $this->getString( $invoice_data, 'payment.transaction_reference' ),
'amount' => $this->getFloat( $invoice_data, 'payment.amount' ),
],
];
} elseif ( $status === 'paid' ) {
$transformed['payments'] = [
[
'date' => isset( $invoice_data['payment.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['payment.date'] ) ) : date( 'y-m-d' ),
'transaction_reference' => $this->getString( $invoice_data, 'payment.transaction_reference' ),
'amount' => $this->getFloat( $invoice_data, 'invoice.amount' ),
],
];
} elseif ( isset( $transformed['amount'] ) && isset( $transformed['balance'] ) ) {
$transformed['payments'] = [
[
'date' => isset( $invoice_data['payment.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['payment.date'] ) ) : date( 'y-m-d' ),
'transaction_reference' => $this->getString( $invoice_data, 'payment.transaction_reference' ),
'amount' => $transformed['amount'] - $transformed['balance'],
],
];
}
$line_items = [];
foreach ( $line_items_data as $record ) {
$line_items[] = [
'quantity' => $this->getFloat( $record, 'item.quantity' ),
'cost' => $this->getFloat( $record, 'item.cost' ),
'product_key' => $this->getString( $record, 'item.product_key' ),
'notes' => $this->getString( $record, 'item.notes' ),
'discount' => $this->getFloat( $record, 'item.discount' ),
'is_amount_discount' => filter_var( $this->getString( $record, 'item.is_amount_discount' ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ),
'tax_name1' => $this->getString( $record, 'item.tax_name1' ),
'tax_rate1' => $this->getFloat( $record, 'item.tax_rate1' ),
'tax_name2' => $this->getString( $record, 'item.tax_name2' ),
'tax_rate2' => $this->getFloat( $record, 'item.tax_rate2' ),
'tax_name3' => $this->getString( $record, 'item.tax_name3' ),
'tax_rate3' => $this->getFloat( $record, 'item.tax_rate3' ),
'custom_value1' => $this->getString( $record, 'item.custom_value1' ),
'custom_value2' => $this->getString( $record, 'item.custom_value2' ),
'custom_value3' => $this->getString( $record, 'item.custom_value3' ),
'custom_value4' => $this->getString( $record, 'item.custom_value4' ),
'type_id' => $this->getInvoiceTypeId( $record, 'item.type_id' ),
];
}
$transformed['line_items'] = $line_items;
return $transformed;
}
}

View File

@ -0,0 +1,64 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Csv;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
/**
* Class PaymentTransformer.
*/
class PaymentTransformer extends BaseTransformer {
/**
* @param $data
*
* @return array
*/
public function transform( $data ) {
$client_id =
$this->getClient( $this->getString( $data, 'payment.client_id' ), $this->getString( $data, 'payment.client_id' ) );
if ( empty( $client_id ) ) {
throw new ImportException( 'Could not find client.' );
}
$transformed = [
'company_id' => $this->maps['company']->id,
'number' => $this->getString( $data, 'payment.number' ),
'user_id' => $this->getString( $data, 'payment.user_id' ),
'amount' => $this->getFloat( $data, 'payment.amount' ),
'refunded' => $this->getFloat( $data, 'payment.refunded' ),
'applied' => $this->getFloat( $data, 'payment.applied' ),
'transaction_reference' => $this->getString( $data, 'payment.transaction_reference ' ),
'date' => $this->getString( $data, 'payment.date' ),
'private_notes' => $this->getString( $data, 'payment.private_notes' ),
'custom_value1' => $this->getString( $data, 'payment.custom_value1' ),
'custom_value2' => $this->getString( $data, 'payment.custom_value2' ),
'custom_value3' => $this->getString( $data, 'payment.custom_value3' ),
'custom_value4' => $this->getString( $data, 'payment.custom_value4' ),
'client_id' => $client_id,
];
if ( isset( $data['payment.invoice_number'] ) &&
$invoice_id = $this->getInvoiceId( $data['payment.invoice_number'] ) ) {
$transformed['invoices'] = [
[
'invoice_id' => $invoice_id,
'amount' => $transformed['amount'] ?? null,
],
];
}
return $transformed;
}
}

View File

@ -1,16 +1,16 @@
<?php
/**
* client Ninja (https://clientninja.com).
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers;
namespace App\Import\Transformers\Csv;
use App\Import\Transformers\BaseTransformer;
/**
* Class ProductTransformer.
*/
@ -19,7 +19,7 @@ class ProductTransformer extends BaseTransformer
/**
* @param $data
*
* @return bool|Item
* @return array
*/
public function transform($data)
{

View File

@ -0,0 +1,47 @@
<?php
namespace App\Import\Transformers\Csv;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
/**
* Class VendorTransformer.
*/
class VendorTransformer extends BaseTransformer {
/**
* @param $data
*
* @return array|bool
*/
public function transform( $data ) {
if ( isset( $data->name ) && $this->hasVendor( $data->name ) ) {
throw new ImportException('Vendor already exists');
}
return [
'company_id' => $this->maps['company']->id,
'name' => $this->getString( $data, 'vendor.name' ),
'phone' => $this->getString( $data, 'vendor.phone' ),
'id_number' => $this->getString( $data, 'vendor.id_number' ),
'vat_number' => $this->getString( $data, 'vendor.vat_number' ),
'website' => $this->getString( $data, 'vendor.website' ),
'currency_id' => $this->getCurrencyByCode( $data, 'vendor.currency_id' ),
'public_notes' => $this->getString( $data, 'vendor.public_notes' ),
'private_notes' => $this->getString( $data, 'vendor.private_notes' ),
'address1' => $this->getString( $data, 'vendor.address1' ),
'address2' => $this->getString( $data, 'vendor.address2' ),
'city' => $this->getString( $data, 'vendor.city' ),
'state' => $this->getString( $data, 'vendor.state' ),
'postal_code' => $this->getString( $data, 'vendor.postal_code' ),
'vendor_contacts' => [
[
'first_name' => $this->getString( $data, 'vendor.first_name' ),
'last_name' => $this->getString( $data, 'vendor.last_name' ),
'email' => $this->getString( $data, 'vendor.email' ),
'phone' => $this->getString( $data, 'vendor.phone' ),
],
],
'country_id' => isset( $data['vendor.country_id'] ) ? $this->getCountryId( $data['vendor.country_id'] ) : null,
];
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* Invoice Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Freshbooks;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use Illuminate\Support\Str;
/**
* Class ClientTransformer.
*/
class ClientTransformer extends BaseTransformer {
/**
* @param $data
*
* @return array|bool
*/
public function transform( $data ) {
if ( isset( $data['Organization'] ) && $this->hasClient( $data['Organization'] ) ) {
throw new ImportException('Client already exists');
}
return [
'company_id' => $this->maps['company']->id,
'name' => $this->getString( $data, 'Organization' ),
'work_phone' => $this->getString( $data, 'Phone' ),
'address1' => $this->getString( $data, 'Street' ),
'city' => $this->getString( $data, 'City' ),
'state' => $this->getString( $data, 'Province/State' ),
'postal_code' => $this->getString( $data, 'Postal Code' ),
'country_id' => isset( $data['Country'] ) ? $this->getCountryId( $data['Country'] ) : null,
'private_notes' => $this->getString( $data, 'Notes' ),
'credit_balance' => 0,
'settings' => new \stdClass,
'client_hash' => Str::random( 40 ),
'contacts' => [
[
'first_name' => $this->getString( $data, 'First Name' ),
'last_name' => $this->getString( $data, 'Last Name' ),
'email' => $this->getString( $data, 'Email' ),
'phone' => $this->getString( $data, 'Phone' ),
],
],
];
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Freshbooks;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use App\Models\Invoice;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer {
/**
* @param $line_items_data
*
* @return bool|array
*/
public function transform( $line_items_data ) {
$invoice_data = reset( $line_items_data );
if ( $this->hasInvoice( $invoice_data['Invoice #'] ) ) {
throw new ImportException( 'Invoice number already exists' );
}
$invoiceStatusMap = [
'sent' => Invoice::STATUS_SENT,
'draft' => Invoice::STATUS_DRAFT,
];
$transformed = [
'company_id' => $this->maps['company']->id,
'client_id' => $this->getClient( $this->getString( $invoice_data, 'Client Name' ), null ),
'number' => $this->getString( $invoice_data, 'Invoice #' ),
'date' => isset( $invoice_data['Date Issued'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Date Issued'] ) ) : null,
'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ),
'amount' => 0,
'status_id' => $invoiceStatusMap[ $status =
strtolower( $this->getString( $invoice_data, 'Invoice Status' ) ) ] ?? Invoice::STATUS_SENT,
'viewed' => $status === 'viewed',
];
$line_items = [];
foreach ( $line_items_data as $record ) {
$line_items[] = [
'product_key' => $this->getString( $record, 'Item Name' ),
'notes' => $this->getString( $record, 'Item Description' ),
'cost' => $this->getFloat( $record, 'Rate' ),
'quantity' => $this->getFloat( $record, 'Quantity' ),
'discount' => $this->getFloat( $record, 'Discount Percentage' ),
'is_amount_discount' => false,
'tax_name1' => $this->getString( $record, 'Tax 1 Type' ),
'tax_rate1' => $this->getFloat( $record, 'Tax 1 Amount' ),
'tax_name2' => $this->getString( $record, 'Tax 2 Type' ),
'tax_rate2' => $this->getFloat( $record, 'Tax 2 Amount' ),
];
$transformed['amount'] += $this->getFloat( $record, 'Line Total' );
}
$transformed['line_items'] = $line_items;
if ( ! empty( $invoice_data['Date Paid'] ) ) {
$transformed['payments'] = [[
'date' => date( 'Y-m-d', strtotime( $invoice_data['Date Paid'] ) ),
'amount' => $transformed['amount'],
]];
}
return $transformed;
}
}

View File

@ -0,0 +1,89 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Invoice2Go;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use App\Models\Invoice;
use Illuminate\Support\Str;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer {
/**
* @param $line_items_data
*
* @return bool|array
*/
public function transform( $invoice_data ) {
if ( $this->hasInvoice( $invoice_data['DocumentNumber'] ) ) {
throw new ImportException( 'Invoice number already exists' );
}
$invoiceStatusMap = [
'unsent' => Invoice::STATUS_DRAFT,
'sent' => Invoice::STATUS_SENT,
];
$transformed = [
'company_id' => $this->maps['company']->id,
'number' => $this->getString( $invoice_data, 'DocumentNumber' ),
'notes' => $this->getString( $invoice_data, 'Comment' ),
'date' => isset( $invoice_data['DocumentDate'] ) ? date( 'Y-m-d', strtotime( $invoice_data['DocumentDate'] ) ) : null,
'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ),
'amount' => 0,
'status_id' => $invoiceStatusMap[ $status =
strtolower( $this->getString( $invoice_data, 'DocumentStatus' ) ) ] ?? Invoice::STATUS_SENT,
'viewed' => $status === 'viewed',
'line_items' => [
[
'amount' => $amount = $this->getFloat( $invoice_data, 'TotalAmount' ),
'quantity' => 1,
'discount' => $this->getFloat( $invoice_data, 'DiscountValue' ),
'is_amount_discount' => false,
],
],
];
$client_id =
$this->getClient( $this->getString( $invoice_data, 'Name' ), $this->getString( $invoice_data, 'EmailRecipient' ) );
if ( $client_id ) {
$transformed['client_id'] = $client_id;
} else {
$transformed['client'] = [
'name' => $this->getString( $invoice_data, 'Name' ),
'address1' => $this->getString( $invoice_data, 'DocumentRecipientAddress' ),
'shipping_address1' => $this->getString( $invoice_data, 'ShipAddress' ),
'credit_balance' => 0,
'settings' => new \stdClass,
'client_hash' => Str::random( 40 ),
'contacts' => [
[
'email' => $this->getString( $invoice_data, 'Email' ),
],
],
];
}
if ( ! empty( $invoice_data['Date Paid'] ) ) {
$transformed['payments'] = [
[
'date' => date( 'Y-m-d', strtotime( $invoice_data['DatePaid'] ) ),
'amount' => $this->getFloat( $invoice_data, 'Payments' ),
],
];
}
return $transformed;
}
}

View File

@ -1,10 +1,10 @@
<?php
/**
* client Ninja (https://clientninja.com).
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -1,10 +1,10 @@
<?php
/**
* client Ninja (https://clientninja.com).
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -0,0 +1,48 @@
<?php
/**
* Invoice Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Invoicely;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use Illuminate\Support\Str;
/**
* Class ClientTransformer.
*/
class ClientTransformer extends BaseTransformer {
/**
* @param $data
*
* @return array|bool
*/
public function transform( $data ) {
if ( isset( $data['Client Name'] ) && $this->hasClient( $data['Client Name'] ) ) {
throw new ImportException('Client already exists');
}
return [
'company_id' => $this->maps['company']->id,
'name' => $this->getString( $data, 'Client Name' ),
'work_phone' => $this->getString( $data, 'Phone' ),
'country_id' => isset( $data['Country'] ) ? $this->getCountryIdBy2( $data['Country'] ) : null,
'credit_balance' => 0,
'settings' => new \stdClass,
'client_hash' => Str::random( 40 ),
'contacts' => [
[
'email' => $this->getString( $data, 'Email' ),
'phone' => $this->getString( $data, 'Phone' ),
],
],
];
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Transformers\Invoicely;
use App\Import\ImportException;
use App\Import\Transformers\BaseTransformer;
use App\Models\Invoice;
/**
* Class InvoiceTransformer.
*/
class InvoiceTransformer extends BaseTransformer {
/**
* @param $data
*
* @return bool|array
*/
public function transform( $data ) {
if ( $this->hasInvoice( $data['Details'] ) ) {
throw new ImportException( 'Invoice number already exists' );
}
$transformed = [
'company_id' => $this->maps['company']->id,
'client_id' => $this->getClient( $this->getString( $data, 'Client' ), null ),
'number' => $this->getString( $data, 'Details' ),
'date' => isset( $data['Date'] ) ? date( 'Y-m-d', strtotime( $data['Date'] ) ) : null,
'due_date' => isset( $data['Due'] ) ? date( 'Y-m-d', strtotime( $data['Due'] ) ) : null,
'status_id' => Invoice::STATUS_SENT,
'line_items' => [
[
'cost' => $amount = $this->getFloat( $data, 'Total' ),
'quantity' => 1,
],
],
];
if ( strtolower( $data['Status'] ) === 'paid' ) {
$transformed['payments'] = [
[
'date' => date( 'Y-m-d' ),
'amount' => $amount,
],
];
}
return $transformed;
}
}

View File

@ -1,10 +1,10 @@
<?php
/**
* client Ninja (https://clientninja.com).
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. client Ninja LLC (https://clientninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

Some files were not shown because too many files have changed in this diff Show More