mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-09-18 23:42:25 +02:00
commit
ce8e8c620c
2
.env.ci
2
.env.ci
@ -24,4 +24,4 @@ PHANTOMJS_PDF_GENERATION=false
|
||||
CACHE_DRIVER=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
SESSION_DRIVER=redis
|
||||
PDF_GENERATOR=hosted_ninja
|
||||
PDF_GENERATOR=snappdf
|
@ -63,3 +63,5 @@ APPLE_REDIRECT_URI=
|
||||
|
||||
NORDIGEN_SECRET_ID=
|
||||
NORDIGEN_SECRET_KEY=
|
||||
|
||||
OPENEXCHANGE_APP_ID=
|
||||
|
7
.github/workflows/react_release.yml
vendored
7
.github/workflows/react_release.yml
vendored
@ -38,17 +38,22 @@ jobs:
|
||||
sudo php artisan cache:clear
|
||||
sudo find ./vendor/bin/ -type f -exec chmod +x {} \;
|
||||
sudo find ./ -type d -exec chmod 755 {} \;
|
||||
- name: Set current date to variable
|
||||
id: set_date
|
||||
run: echo "current_date=$(date '+%Y-%m-%d')" >> $GITHUB_ENV
|
||||
|
||||
- name: Prepare React FrontEnd
|
||||
run: |
|
||||
git clone https://${{secrets.commit_secret}}@github.com/invoiceninja/ui.git
|
||||
cd ui
|
||||
git checkout develop
|
||||
cp .env.example .env
|
||||
cp ../vite.config.ts.react ./vite.config.js
|
||||
sed -i '/"version"/c\ "version": " Latest Build - ${{ env.current_date }}",' package.json
|
||||
npm i
|
||||
npm run build
|
||||
cp -r dist/* ../public/
|
||||
mv dist/index.html ../resources/views/react/index.blade.php
|
||||
mv ../public/index.html ../resources/views/react/index.blade.php
|
||||
|
||||
- name: Prepare JS/CSS assets
|
||||
run: |
|
||||
|
44
README.md
44
README.md
@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/hillelcoren/invoice-ninja/master/public/images/round_logo.png" alt="Sublime's custom image"/>
|
||||
<a href ="https://www.youtube.com/watch?v=CxGxXiotv0I" target="_blank" title="Invoice Ninja Overview Video"><img src="https://raw.githubusercontent.com/hillelcoren/invoice-ninja/master/public/images/round_logo.png" alt="Sublime's custom image"/></a>
|
||||
</p>
|
||||
|
||||
![v5-develop phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-develop)
|
||||
@ -8,25 +8,30 @@
|
||||
|
||||
# Invoice Ninja 5
|
||||
|
||||
## [Hosted](https://www.invoiceninja.com) | [Self-Hosted](https://www.invoiceninja.org)
|
||||
Invoice Ninja Version 5 is here! We've taken the best parts of version 4 and added the most requested features to create an invoicing application like no other. Check the [Invoice Ninja YouTube Channel](https://www.youtube.com/@appinvoiceninja) to get up to speed, or try the [Demo](https://react.invoicing.co/demo) now.
|
||||
|
||||
Join us on [Slack](http://slack.invoiceninja.com), [Discord](https://discord.gg/ZwEdtfCwXA), [Support Forum](https://forum.invoiceninja.com)
|
||||
**Choose your setup**
|
||||
|
||||
## Introduction
|
||||
- [Hosted](https://www.invoiceninja.com): Our hosted version is a Software as a Service (SaaS) solution. You're up and running in under 5 minutes, with no need to worry about hosting or server infrastructure.
|
||||
- [Self-Hosted](https://www.invoiceninja.org): For those who prefer to manage their own hosting and server infrastructure. This version gives you full control and flexibility.
|
||||
|
||||
Version 5 of Invoice Ninja is here!
|
||||
We took the best parts of version 4 and add the most requested features
|
||||
to produce a invoicing application like no other.
|
||||
All Pro and Enterprise features from the hosted app are included in the open-source code. We offer a $30 per year white-label license to remove the Invoice Ninja branding from client-facing parts of the app.
|
||||
|
||||
All Pro and Enterprise features from the hosted app are included in the open code.
|
||||
We offer a $30 per year white-label license to remove the Invoice Ninja branding from client facing parts of the app.
|
||||
#### Get social with us
|
||||
|
||||
* [Videos](https://www.youtube.com/@appinvoiceninja)
|
||||
* [API Documentation](https://api-docs.invoicing.co/)
|
||||
* [APP Documentation](https://invoiceninja.github.io/)
|
||||
* [Support Forum](https://forum.invoiceninja.com)
|
||||
* [Slack](http://slack.invoiceninja.com)
|
||||
* [Discord](https://discord.gg/ZwEdtfCwXA)
|
||||
* [Instagram](https://www.instagram.com/appinvoiceninja)
|
||||
|
||||
## Setup
|
||||
#### Documentation
|
||||
|
||||
* [Invoice Ninja - API](https://api-docs.invoicing.co/)
|
||||
* [Invoice Ninja - Developer Guide](https://invoiceninja.github.io/en/developer-guide/)
|
||||
* [Invoice Ninja - User Guide](https://invoiceninja.github.io/en/user-guide/)
|
||||
* [Invoice Ninja - Self-Hosted Installation Guide](https://invoiceninja.github.io/en/self-host-installation/)
|
||||
|
||||
## Installation Options and Clients
|
||||
|
||||
### Mobile Apps
|
||||
* [iPhone](https://apps.apple.com/app/id1503970375?platform=iphone)
|
||||
@ -39,22 +44,26 @@ We offer a $30 per year white-label license to remove the Invoice Ninja branding
|
||||
* [Linux - Snap](https://snapcraft.io/invoiceninja)
|
||||
* [Linux - Flatpak](https://flathub.org/apps/com.invoiceninja.InvoiceNinja)
|
||||
|
||||
### Installation Options
|
||||
### Self-Hosted Server Installation
|
||||
**Note:** The self-hosted options do support the desktop and mobile apps.
|
||||
|
||||
* [Server or VM](https://invoiceninja.github.io/en/self-host-installation/)
|
||||
* [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/)
|
||||
* [Cloudron](https://cloudron.io/store/com.invoiceninja.cloudronapp.html)
|
||||
* [Cloudron](https://www.cloudron.io/store/com.invoiceninja.cloudronapp2.html)
|
||||
* [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja)
|
||||
|
||||
### Recommended Providers
|
||||
* [Stripe](https://stripe.com/)
|
||||
* [Postmark](https://postmarkapp.com/)
|
||||
|
||||
## Quick Hosting Setup
|
||||
## [Advanced] Quick Hosting Setup
|
||||
|
||||
In addition to the official [Invoice Ninja - Self-Hosted Installation Guide](https://invoiceninja.github.io/en/self-host-installation/) we have a few commands for you.
|
||||
|
||||
```sh
|
||||
git clone --single-branch --branch v5-stable https://github.com/invoiceninja/invoiceninja.git
|
||||
cp .env.example .env
|
||||
composer i -o --no-dev
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
Please Note:
|
||||
@ -85,6 +94,7 @@ pass: password
|
||||
```
|
||||
## Developers Guide
|
||||
|
||||
In addition to the official [Invoice Ninja - Developer Guide](https://invoiceninja.github.io/en/developer-guide/) we've got your back with some insights.
|
||||
|
||||
### App Design
|
||||
|
||||
|
@ -1 +1 @@
|
||||
5.10.5
|
||||
5.10.17
|
@ -177,7 +177,6 @@ class BackupUpdate extends Command
|
||||
$doc_bin = $document->getFile();
|
||||
} catch(\Exception $e) {
|
||||
nlog("Exception:: BackupUpdate::" . $e->getMessage());
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
|
||||
if ($doc_bin) {
|
||||
|
@ -385,6 +385,9 @@ class CreateSingleAccount extends Command
|
||||
|
||||
});
|
||||
|
||||
|
||||
$this->countryClients($company, $user);
|
||||
|
||||
$this->info("finished");
|
||||
|
||||
}
|
||||
@ -1109,4 +1112,44 @@ class CreateSingleAccount extends Command
|
||||
|
||||
event(new RecurringInvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars()));
|
||||
}
|
||||
|
||||
|
||||
private function countryClients($company, $user)
|
||||
{
|
||||
|
||||
Client::unguard();
|
||||
|
||||
Client::create([
|
||||
'company_id' => $company->id,
|
||||
'user_id' => $user->id,
|
||||
'name' => 'Swiss Company AG',
|
||||
'website' => 'https://www.testcompany.ch',
|
||||
'private_notes' => 'These are some private notes about the test client.',
|
||||
'balance' => 0,
|
||||
'paid_to_date' => 0,
|
||||
'vat_number' => '654321987',
|
||||
'id_number' => 'CH9300762011623852957', // Sample Swiss IBAN
|
||||
'custom_value1' => '2024-07-22 10:00:00',
|
||||
'custom_value2' => 'blue',
|
||||
'custom_value3' => 'sampleword',
|
||||
'custom_value4' => 'test@example.com',
|
||||
'address1' => '123',
|
||||
'address2' => 'Test Street 45',
|
||||
'city' => 'Zurich',
|
||||
'state' => 'Zurich',
|
||||
'postal_code' => '8001',
|
||||
'country_id' => '756', // Switzerland
|
||||
'shipping_address1' => '123',
|
||||
'shipping_address2' => 'Test Street 45',
|
||||
'shipping_city' => 'Zurich',
|
||||
'shipping_state' => 'Zurich',
|
||||
'shipping_postal_code' => '8001',
|
||||
'shipping_country_id' => '756', // Switzerland
|
||||
'settings' => ClientSettings::Defaults(),
|
||||
'client_hash' => \Illuminate\Support\Str::random(32),
|
||||
'routing_id' => '',
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ use App\Jobs\Cron\AutoBillCron;
|
||||
use App\Jobs\Cron\RecurringExpensesCron;
|
||||
use App\Jobs\Cron\RecurringInvoicesCron;
|
||||
use App\Jobs\Cron\SubscriptionCron;
|
||||
use App\Jobs\Cron\UpdateCalculatedFields;
|
||||
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
|
||||
use App\Jobs\Ninja\AdjustEmailQuota;
|
||||
use App\Jobs\Ninja\BankTransactionSync;
|
||||
@ -33,6 +32,7 @@ use App\Jobs\Util\SchedulerCheck;
|
||||
use App\Jobs\Util\UpdateExchangeRates;
|
||||
use App\Jobs\Util\VersionCheck;
|
||||
use App\Models\Account;
|
||||
use App\PaymentDrivers\Rotessa\Jobs\TransactionReport;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
@ -65,11 +65,11 @@ class Kernel extends ConsoleKernel
|
||||
/* Checks for scheduled tasks */
|
||||
$schedule->job(new TaskScheduler())->hourlyAt(10)->withoutOverlapping()->name('task-scheduler-job')->onOneServer();
|
||||
|
||||
/* Stale Invoice Cleanup*/
|
||||
$schedule->job(new CleanStaleInvoiceOrder())->hourlyAt(30)->withoutOverlapping()->name('stale-invoice-job')->onOneServer();
|
||||
/* Checks Rotessa Transactions */
|
||||
$schedule->job(new TransactionReport())->dailyAt('01:48')->withoutOverlapping()->name('rotessa-transaction-report')->onOneServer();
|
||||
|
||||
/* Stale Invoice Cleanup*/
|
||||
$schedule->job(new UpdateCalculatedFields())->hourlyAt(40)->withoutOverlapping()->name('update-calculated-fields-job')->onOneServer();
|
||||
$schedule->job(new CleanStaleInvoiceOrder())->hourlyAt(30)->withoutOverlapping()->name('stale-invoice-job')->onOneServer();
|
||||
|
||||
/* Checks for large companies and marked them as is_large */
|
||||
$schedule->job(new CompanySizeCheck())->dailyAt('23:20')->withoutOverlapping()->name('company-size-job')->onOneServer();
|
||||
|
44
app/DataMapper/Tax/PL/Rule.php
Normal file
44
app/DataMapper/Tax/PL/Rule.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\DataMapper\Tax\PL;
|
||||
|
||||
use App\DataMapper\Tax\DE\Rule as DERule;
|
||||
|
||||
class Rule extends DERule
|
||||
{
|
||||
/** @var string $seller_region */
|
||||
public string $seller_region = 'EU';
|
||||
|
||||
/** @var bool $consumer_tax_exempt */
|
||||
public bool $consumer_tax_exempt = false;
|
||||
|
||||
/** @var bool $business_tax_exempt */
|
||||
public bool $business_tax_exempt = false;
|
||||
|
||||
/** @var bool $eu_business_tax_exempt */
|
||||
public bool $eu_business_tax_exempt = true;
|
||||
|
||||
/** @var bool $foreign_business_tax_exempt */
|
||||
public bool $foreign_business_tax_exempt = false;
|
||||
|
||||
/** @var bool $foreign_consumer_tax_exempt */
|
||||
public bool $foreign_consumer_tax_exempt = false;
|
||||
|
||||
/** @var float $tax_rate */
|
||||
public float $tax_rate = 0;
|
||||
|
||||
/** @var float $reduced_tax_rate */
|
||||
public float $reduced_tax_rate = 0;
|
||||
|
||||
public string $tax_name1 = 'VAT';
|
||||
|
||||
}
|
@ -17,7 +17,7 @@ class TaxModel
|
||||
public string $seller_subregion = 'CA';
|
||||
|
||||
/** @var string $version */
|
||||
public string $version = 'alpha';
|
||||
public string $version = 'beta';
|
||||
|
||||
/** @var object $regions */
|
||||
public object $regions;
|
||||
@ -28,17 +28,40 @@ class TaxModel
|
||||
* @param TaxModel $model
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(public ?TaxModel $model = null)
|
||||
public function __construct(public mixed $model = null)
|
||||
{
|
||||
|
||||
if(!$this->model) {
|
||||
if(!$model) {
|
||||
$this->regions = $this->init();
|
||||
} else {
|
||||
$this->regions = $model;
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
foreach($model as $key => $value) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->migrate();
|
||||
}
|
||||
|
||||
public function migrate(): self
|
||||
{
|
||||
|
||||
if($this->version == 'alpha')
|
||||
{
|
||||
$this->regions->EU->subregions->PL = new \stdClass();
|
||||
$this->regions->EU->subregions->PL->tax_rate = 23;
|
||||
$this->regions->EU->subregions->PL->tax_name = 'VAT';
|
||||
$this->regions->EU->subregions->PL->reduced_tax_rate = 8;
|
||||
$this->regions->EU->subregions->PL->apply_tax = false;
|
||||
|
||||
$this->version = 'beta';
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the rules and builds any required data.
|
||||
*
|
||||
@ -474,6 +497,12 @@ class TaxModel
|
||||
$this->regions->EU->subregions->NL->reduced_tax_rate = 9;
|
||||
$this->regions->EU->subregions->NL->apply_tax = false;
|
||||
|
||||
$this->regions->EU->subregions->PL = new \stdClass();
|
||||
$this->regions->EU->subregions->PL->tax_rate = 23;
|
||||
$this->regions->EU->subregions->PL->tax_name = 'VAT';
|
||||
$this->regions->EU->subregions->PL->reduced_tax_rate = 8;
|
||||
$this->regions->EU->subregions->PL->apply_tax = false;
|
||||
|
||||
$this->regions->EU->subregions->PT = new \stdClass();
|
||||
$this->regions->EU->subregions->PT->tax_rate = 23;
|
||||
$this->regions->EU->subregions->PT->tax_name = 'IVA';
|
||||
|
@ -197,6 +197,10 @@ region:
|
||||
vat: 21
|
||||
reduced_vat: 9
|
||||
apply_tax: false
|
||||
PL:
|
||||
vat: 23
|
||||
reduced_vat: 8
|
||||
apply_tax: false
|
||||
PT:
|
||||
vat: 23
|
||||
reduced_vat: 6
|
||||
|
55
app/DataProviders/CAProvinces.php
Normal file
55
app/DataProviders/CAProvinces.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\DataProviders;
|
||||
|
||||
final class CAProvinces {
|
||||
/**
|
||||
* The provinces and territories of Canada
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $provinces = [
|
||||
'AB' => 'Alberta',
|
||||
'BC' => 'British Columbia',
|
||||
'MB' => 'Manitoba',
|
||||
'NB' => 'New Brunswick',
|
||||
'NL' => 'Newfoundland And Labrador',
|
||||
'NS' => 'Nova Scotia',
|
||||
'ON' => 'Ontario',
|
||||
'PE' => 'Prince Edward Island',
|
||||
'QC' => 'Quebec',
|
||||
'SK' => 'Saskatchewan',
|
||||
'NT' => 'Northwest Territories',
|
||||
'NU' => 'Nunavut',
|
||||
'YT' => 'Yukon'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the name of the province or territory for a given abbreviation.
|
||||
*
|
||||
* @param string $abbreviation
|
||||
* @return string
|
||||
*/
|
||||
public static function getName($abbreviation) {
|
||||
return self::$provinces[$abbreviation];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all provinces and territories.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get() {
|
||||
return self::$provinces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the abbreviation for a given province or territory name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
public static function getAbbreviation($name) {
|
||||
return array_search(ucwords($name), self::$provinces);
|
||||
}
|
||||
}
|
19
app/DataProviders/Frequencies.php
Normal file
19
app/DataProviders/Frequencies.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\DataProviders;
|
||||
|
||||
use Omnipay\Rotessa\Object\Frequency;
|
||||
|
||||
final class Frequencies
|
||||
{
|
||||
public static function get() : array {
|
||||
return Frequency::getTypes();
|
||||
}
|
||||
|
||||
public static function getFromType() {
|
||||
|
||||
}
|
||||
public static function getOnePayment() {
|
||||
return Frequency::ONCE;
|
||||
}
|
||||
}
|
@ -13,15 +13,21 @@ namespace App\Events\Client;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use League\Fractal\Manager;
|
||||
use League\Fractal\Resource\Item;
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use App\Transformers\ArraySerializer;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Transformers\ClientTransformer;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
|
||||
/**
|
||||
* Class ClientWasArchived.
|
||||
*/
|
||||
class ClientWasArchived
|
||||
class ClientWasArchived implements ShouldBroadcast
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithSockets;
|
||||
@ -50,13 +56,34 @@ class ClientWasArchived
|
||||
$this->event_vars = $event_vars;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Get the channels the event should broadcast on.
|
||||
// *
|
||||
// * @return Channel|array
|
||||
// */
|
||||
public function broadcastWith()
|
||||
{
|
||||
|
||||
$manager = new Manager();
|
||||
$manager->setSerializer(new ArraySerializer());
|
||||
$class = sprintf('App\\Transformers\\%sTransformer', class_basename($this->client));
|
||||
|
||||
$transformer = new $class();
|
||||
|
||||
$resource = new Item($this->client, $transformer, $this->client->getEntityType());
|
||||
$data = $manager->createData($resource)->toArray();
|
||||
|
||||
return $data;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return Channel|array
|
||||
*/
|
||||
public function broadcastOn()
|
||||
{
|
||||
return [];
|
||||
|
||||
return [
|
||||
new PrivateChannel("company-{$this->company->company_key}"),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
|
||||
use Illuminate\Database\Eloquent\RelationNotFoundException;
|
||||
use Illuminate\Encryption\MissingAppKeyException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Http\Exceptions\ThrottleRequestsException;
|
||||
use Illuminate\Http\Request;
|
||||
@ -94,18 +95,8 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public function report(Throwable $exception)
|
||||
{
|
||||
if (! Schema::hasTable('accounts')) {
|
||||
info('account table not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
|
||||
// if($exception instanceof ThrottleRequestsException && class_exists(\Modules\Admin\Events\ThrottledExceptionRaised::class)) {
|
||||
// $uri = urldecode(request()->getRequestUri());
|
||||
// event(new \Modules\Admin\Events\ThrottledExceptionRaised(auth()->user()?->account?->key, $uri, request()->ip()));
|
||||
// }
|
||||
|
||||
Integration::configureScope(function (Scope $scope): void {
|
||||
$name = 'hosted@invoiceninja.com';
|
||||
|
||||
@ -154,6 +145,10 @@ class Handler extends ExceptionHandler
|
||||
}
|
||||
|
||||
parent::report($exception);
|
||||
|
||||
if (Ninja::isSelfHost() && $exception instanceof MissingAppKeyException) {
|
||||
info('To setup the app run: cp .env.example .env');
|
||||
}
|
||||
}
|
||||
|
||||
private function validException($exception)
|
||||
|
@ -57,6 +57,7 @@ class ActivityExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($resource) {
|
||||
/** @var \App\Models\Activity $resource */
|
||||
$row = $this->buildActivityRow($resource);
|
||||
return $this->processMetaData($row, $resource);
|
||||
})->toArray();
|
||||
@ -128,6 +129,9 @@ class ActivityExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($entity) {
|
||||
|
||||
/** @var \App\Models\Activity $entity */
|
||||
|
||||
$this->buildRow($entity);
|
||||
});
|
||||
|
||||
|
@ -172,6 +172,7 @@ class BaseExport
|
||||
'tax_rate3' => 'invoice.tax_rate3',
|
||||
'recurring_invoice' => 'invoice.recurring_id',
|
||||
'auto_bill' => 'invoice.auto_bill_enabled',
|
||||
'project' => 'invoice.project',
|
||||
];
|
||||
|
||||
protected array $recurring_invoice_report_keys = [
|
||||
@ -449,6 +450,7 @@ class BaseExport
|
||||
'status' => 'task.status_id',
|
||||
'project' => 'task.project_id',
|
||||
'billable' => 'task.billable',
|
||||
'item_notes' => 'task.item_notes',
|
||||
];
|
||||
|
||||
protected array $forced_client_fields = [
|
||||
@ -1038,6 +1040,10 @@ class BaseExport
|
||||
|
||||
$recurring_filters = [];
|
||||
|
||||
if($this->company->getSetting('report_include_drafts')){
|
||||
$recurring_filters[] = RecurringInvoice::STATUS_DRAFT;
|
||||
}
|
||||
|
||||
if (in_array('active', $status_parameters)) {
|
||||
$recurring_filters[] = RecurringInvoice::STATUS_ACTIVE;
|
||||
}
|
||||
@ -1252,7 +1258,7 @@ class BaseExport
|
||||
|
||||
$date_range = $this->input['date_range'];
|
||||
|
||||
if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key']) > 1 && ($table_name && $this->columnExists($table_name, $this->input['date_key']))) {
|
||||
if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key'] ?? '') > 1 && ($table_name && $this->columnExists($table_name, $this->input['date_key']))) {
|
||||
$this->date_key = $this->input['date_key'];
|
||||
}
|
||||
|
||||
|
@ -102,6 +102,8 @@ class ClientExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($client) {
|
||||
|
||||
/** @var \App\Models\Client $client */
|
||||
$row = $this->buildRow($client);
|
||||
return $this->processMetaData($row, $client);
|
||||
})->toArray();
|
||||
@ -154,6 +156,8 @@ class ClientExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($client) {
|
||||
|
||||
/** @var \App\Models\Client $client */
|
||||
$this->csv->insertOne($this->buildRow($client));
|
||||
});
|
||||
|
||||
|
@ -82,6 +82,7 @@ class ContactExport extends BaseExport
|
||||
$this->csv->insertOne($this->buildHeader());
|
||||
|
||||
$query->cursor()->each(function ($contact) {
|
||||
/** @var \App\Models\ClientContact $contact */
|
||||
$this->csv->insertOne($this->buildRow($contact));
|
||||
});
|
||||
|
||||
@ -101,6 +102,7 @@ class ContactExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($contact) {
|
||||
/** @var \App\Models\ClientContact $contact */
|
||||
$row = $this->buildRow($contact);
|
||||
return $this->processMetaData($row, $contact);
|
||||
})->toArray();
|
||||
|
@ -52,6 +52,8 @@ class CreditExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($credit) {
|
||||
|
||||
/** @var \App\Models\Credit $credit */
|
||||
$row = $this->buildRow($credit);
|
||||
return $this->processMetaData($row, $credit);
|
||||
})->toArray();
|
||||
@ -139,6 +141,7 @@ class CreditExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($credit) {
|
||||
/** @var \App\Models\Credit $credit */
|
||||
$this->csv->insertOne($this->buildRow($credit));
|
||||
});
|
||||
|
||||
|
@ -54,6 +54,8 @@ class DocumentExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($document) {
|
||||
|
||||
/** @var \App\Models\Document $document */
|
||||
$row = $this->buildRow($document);
|
||||
return $this->processMetaData($row, $document);
|
||||
})->toArray();
|
||||
@ -99,6 +101,7 @@ class DocumentExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($entity) {
|
||||
/** @var mixed $entity */
|
||||
$this->csv->insertOne($this->buildRow($entity));
|
||||
});
|
||||
|
||||
|
@ -52,6 +52,8 @@ class ExpenseExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($resource) {
|
||||
|
||||
/** @var \App\Models\Expense $resource */
|
||||
$row = $this->buildRow($resource);
|
||||
return $this->processMetaData($row, $resource);
|
||||
})->toArray();
|
||||
@ -132,6 +134,8 @@ class ExpenseExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($expense) {
|
||||
|
||||
/** @var \App\Models\Expense $expense */
|
||||
$this->csv->insertOne($this->buildRow($expense));
|
||||
});
|
||||
|
||||
|
@ -99,6 +99,8 @@ class InvoiceExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($resource) {
|
||||
|
||||
/** @var \App\Models\Invoice $resource */
|
||||
$row = $this->buildRow($resource);
|
||||
return $this->processMetaData($row, $resource);
|
||||
})->toArray();
|
||||
@ -119,6 +121,8 @@ class InvoiceExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($invoice) {
|
||||
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$this->csv->insertOne($this->buildRow($invoice));
|
||||
});
|
||||
|
||||
@ -149,9 +153,9 @@ class InvoiceExport extends BaseExport
|
||||
private function decorateAdvancedFields(Invoice $invoice, array $entity): array
|
||||
{
|
||||
|
||||
// if (in_array('invoice.status', $this->input['report_keys'])) {
|
||||
// $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id);
|
||||
// }
|
||||
if (in_array('invoice.project', $this->input['report_keys'])) {
|
||||
$entity['invoice.project'] = $invoice->project ? $invoice->project->name : '';
|
||||
}
|
||||
|
||||
if (in_array('invoice.recurring_id', $this->input['report_keys'])) {
|
||||
$entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? '';
|
||||
|
@ -113,6 +113,8 @@ class InvoiceItemExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($resource) {
|
||||
|
||||
/** @var \App\Models\Invoice $resource */
|
||||
$this->iterateItems($resource);
|
||||
|
||||
foreach($this->storage_array as $row) {
|
||||
@ -141,6 +143,8 @@ class InvoiceItemExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($invoice) {
|
||||
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$this->iterateItems($invoice);
|
||||
});
|
||||
|
||||
@ -261,6 +265,10 @@ class InvoiceItemExport extends BaseExport
|
||||
$entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : '';// @phpstan-ignore-line
|
||||
}
|
||||
|
||||
if (in_array('invoice.project', $this->input['report_keys'])) {
|
||||
$entity['invoice.project'] = $invoice->project ? $invoice->project->name : '';// @phpstan-ignore-line
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,8 @@ class PaymentExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($resource) {
|
||||
|
||||
/** @var \App\Models\Payment $resource */
|
||||
$row = $this->buildRow($resource);
|
||||
return $this->processMetaData($row, $resource);
|
||||
})->toArray();
|
||||
@ -112,6 +114,8 @@ class PaymentExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($entity) {
|
||||
|
||||
/** @var \App\Models\Payment $entity */
|
||||
$this->csv->insertOne($this->buildRow($entity));
|
||||
});
|
||||
|
||||
|
@ -51,6 +51,8 @@ class ProductExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($resource) {
|
||||
|
||||
/** @var \App\Models\Product $resource */
|
||||
$row = $this->buildRow($resource);
|
||||
return $this->processMetaData($row, $resource);
|
||||
})->toArray();
|
||||
@ -103,6 +105,8 @@ class ProductExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($entity) {
|
||||
|
||||
/** @var \App\Models\Product $entity */
|
||||
$this->csv->insertOne($this->buildRow($entity));
|
||||
});
|
||||
|
||||
|
@ -98,6 +98,8 @@ class PurchaseOrderExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($resource) {
|
||||
|
||||
/** @var \App\Models\PurchaseOrder $resource */
|
||||
$row = $this->buildRow($resource);
|
||||
return $this->processMetaData($row, $resource);
|
||||
})->toArray();
|
||||
@ -119,6 +121,8 @@ class PurchaseOrderExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($purchase_order) {
|
||||
|
||||
/** @var \App\Models\PurchaseOrder $purchase_order */
|
||||
$this->csv->insertOne($this->buildRow($purchase_order));
|
||||
});
|
||||
|
||||
|
@ -101,6 +101,8 @@ class PurchaseOrderItemExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($resource) {
|
||||
|
||||
/** @var \App\Models\PurchaseOrder $resource */
|
||||
$this->iterateItems($resource);
|
||||
|
||||
foreach($this->storage_array as $row) {
|
||||
@ -127,6 +129,8 @@ class PurchaseOrderItemExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($purchase_order) {
|
||||
|
||||
/** @var \App\Models\PurchaseOrder $purchase_order */
|
||||
$this->iterateItems($purchase_order);
|
||||
});
|
||||
|
||||
|
@ -103,6 +103,8 @@ class QuoteExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($resource) {
|
||||
|
||||
/** @var \App\Models\Quote $resource */
|
||||
$row = $this->buildRow($resource);
|
||||
return $this->processMetaData($row, $resource);
|
||||
})->toArray();
|
||||
@ -125,6 +127,8 @@ class QuoteExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($quote) {
|
||||
|
||||
/** @var \App\Models\Quote $quote */
|
||||
$this->csv->insertOne($this->buildRow($quote));
|
||||
});
|
||||
|
||||
|
@ -104,6 +104,8 @@ class QuoteItemExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($resource) {
|
||||
|
||||
/** @var \App\Models\Quote $resource */
|
||||
$this->iterateItems($resource);
|
||||
|
||||
foreach($this->storage_array as $row) {
|
||||
@ -134,6 +136,8 @@ class QuoteItemExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($quote) {
|
||||
|
||||
/** @var \App\Models\Quote $quote */
|
||||
$this->iterateItems($quote);
|
||||
});
|
||||
|
||||
|
@ -93,6 +93,8 @@ class RecurringInvoiceExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($invoice) {
|
||||
|
||||
/** @var \App\Models\RecurringInvoice $invoice */
|
||||
$this->csv->insertOne($this->buildRow($invoice));
|
||||
});
|
||||
|
||||
@ -112,6 +114,8 @@ class RecurringInvoiceExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($resource) {
|
||||
|
||||
/** @var \App\Models\RecurringInvoice $resource */
|
||||
$row = $this->buildRow($resource);
|
||||
return $this->processMetaData($row, $resource);
|
||||
})->toArray();
|
||||
|
@ -29,7 +29,7 @@ class TaskExport extends BaseExport
|
||||
{
|
||||
private $entity_transformer;
|
||||
|
||||
public string $date_key = 'created_at';
|
||||
public string $date_key = 'calculated_start_date';
|
||||
|
||||
private string $date_format = 'Y-m-d';
|
||||
|
||||
@ -106,6 +106,8 @@ class TaskExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($entity) {
|
||||
|
||||
/** @var \App\Models\Task $entity*/
|
||||
$this->buildRow($entity);
|
||||
});
|
||||
|
||||
@ -128,6 +130,7 @@ class TaskExport extends BaseExport
|
||||
$query->cursor()
|
||||
->each(function ($resource) {
|
||||
|
||||
/** @var \App\Models\Task $resource*/
|
||||
$this->buildRow($resource);
|
||||
|
||||
foreach($this->storage_array as $row) {
|
||||
@ -153,7 +156,7 @@ class TaskExport extends BaseExport
|
||||
$entity[$key] = $transformed_entity[$parts[1]];
|
||||
} elseif (array_key_exists($key, $transformed_entity)) {
|
||||
$entity[$key] = $transformed_entity[$key];
|
||||
} elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration'])) {
|
||||
} elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration', 'task.billable', 'task.item_notes'])) {
|
||||
$entity[$key] = '';
|
||||
} else {
|
||||
$entity[$key] = $this->decorator->transform($key, $task);
|
||||
@ -161,7 +164,7 @@ class TaskExport extends BaseExport
|
||||
|
||||
}
|
||||
|
||||
if (is_null($task->time_log) || (is_array(json_decode($task->time_log, 1)) && count(json_decode($task->time_log, 1)) == 0)) {
|
||||
if (is_null($task->time_log) || (is_array(json_decode($task->time_log, true)) && count(json_decode($task->time_log, true)) == 0)) {
|
||||
$this->storage_array[] = $entity;
|
||||
} else {
|
||||
$this->iterateLogs($task, $entity);
|
||||
@ -172,13 +175,13 @@ class TaskExport extends BaseExport
|
||||
private function iterateLogs(Task $task, array $entity)
|
||||
{
|
||||
$timezone = Timezone::find($task->company->settings->timezone_id);
|
||||
$timezone_name = 'US/Eastern';
|
||||
$timezone_name = 'America/New_York';
|
||||
|
||||
if ($timezone) {
|
||||
$timezone_name = $timezone->name;
|
||||
}
|
||||
|
||||
$logs = json_decode($task->time_log, 1);
|
||||
$logs = json_decode($task->time_log, true);
|
||||
|
||||
$date_format_default = $this->date_format;
|
||||
|
||||
@ -206,6 +209,14 @@ class TaskExport extends BaseExport
|
||||
$entity['task.duration_words'] = $seconds > 86400 ? CarbonInterval::seconds($seconds)->locale($this->company->locale())->cascade()->forHumans() : now()->startOfDay()->addSeconds($seconds)->format('H:i:s');
|
||||
}
|
||||
|
||||
if (in_array('task.billable', $this->input['report_keys']) || in_array('billable', $this->input['report_keys'])) {
|
||||
$entity['task.billable'] = isset($item[3]) && $item[3] == 'true' ? ctrans('texts.yes') : ctrans('texts.no');
|
||||
}
|
||||
|
||||
if (in_array('task.item_notes', $this->input['report_keys']) || in_array('item_notes', $this->input['report_keys'])) {
|
||||
$entity['task.item_notes'] = isset($item[2]) ? (string)$item[2] : '';
|
||||
}
|
||||
|
||||
$entity = $this->decorateAdvancedFields($task, $entity);
|
||||
|
||||
$this->storage_array[] = $entity;
|
||||
@ -216,6 +227,8 @@ class TaskExport extends BaseExport
|
||||
$entity['task.end_time'] = '';
|
||||
$entity['task.duration'] = '';
|
||||
$entity['task.duration_words'] = '';
|
||||
$entity['task.billable'] = '';
|
||||
$entity['task.item_notes'] = '';
|
||||
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,8 @@ class VendorExport extends BaseExport
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($resource) {
|
||||
|
||||
/** @var \App\Models\Vendor $resource */
|
||||
$row = $this->buildRow($resource);
|
||||
return $this->processMetaData($row, $resource);
|
||||
})->toArray();
|
||||
@ -107,6 +109,8 @@ class VendorExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($vendor) {
|
||||
|
||||
/** @var \App\Models\Vendor $vendor */
|
||||
$this->csv->insertOne($this->buildRow($vendor));
|
||||
});
|
||||
|
||||
|
@ -92,6 +92,7 @@ class InvoiceDecorator extends Decorator implements DecoratorInterface
|
||||
{
|
||||
return $invoice->recurring_invoice ? $invoice->recurring_invoice->number : '';
|
||||
}
|
||||
|
||||
public function auto_bill_enabled(Invoice $invoice)
|
||||
{
|
||||
return $invoice->auto_bill_enabled ? ctrans('texts.yes') : ctrans('texts.no');
|
||||
|
@ -18,6 +18,7 @@ use Carbon\Carbon;
|
||||
|
||||
class TaskDecorator extends Decorator implements DecoratorInterface
|
||||
{
|
||||
//@todo - we do not handle iterating through the timelog here.
|
||||
public function transform(string $key, mixed $entity): mixed
|
||||
{
|
||||
$task = false;
|
||||
@ -42,13 +43,13 @@ class TaskDecorator extends Decorator implements DecoratorInterface
|
||||
{
|
||||
|
||||
$timezone = Timezone::find($task->company->settings->timezone_id);
|
||||
$timezone_name = 'US/Eastern';
|
||||
$timezone_name = 'America/New_York';
|
||||
|
||||
if ($timezone) {
|
||||
$timezone_name = $timezone->name;
|
||||
}
|
||||
|
||||
$logs = json_decode($task->time_log, 1);
|
||||
$logs = json_decode($task->time_log, true);
|
||||
|
||||
$date_format_default = 'Y-m-d';
|
||||
|
||||
@ -71,13 +72,13 @@ class TaskDecorator extends Decorator implements DecoratorInterface
|
||||
{
|
||||
|
||||
$timezone = Timezone::find($task->company->settings->timezone_id);
|
||||
$timezone_name = 'US/Eastern';
|
||||
$timezone_name = 'America/New_York';
|
||||
|
||||
if ($timezone) {
|
||||
$timezone_name = $timezone->name;
|
||||
}
|
||||
|
||||
$logs = json_decode($task->time_log, 1);
|
||||
$logs = json_decode($task->time_log, true);
|
||||
|
||||
$date_format_default = 'Y-m-d';
|
||||
|
||||
@ -95,6 +96,26 @@ class TaskDecorator extends Decorator implements DecoratorInterface
|
||||
return '';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* billable
|
||||
*
|
||||
* @todo
|
||||
*/
|
||||
public function billable(Task $task)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* items_notes
|
||||
* @todo
|
||||
*/
|
||||
public function items_notes(Task $task)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function duration(Task $task)
|
||||
{
|
||||
return $task->calcDuration();
|
||||
|
@ -175,7 +175,7 @@ class RecurringExpenseToExpenseFactory
|
||||
|
||||
$_value = explode($_operation, $right); // [MONTHYEAR, 4]
|
||||
|
||||
$_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y');
|
||||
$_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y'); //@phpstan-ignore-line
|
||||
}
|
||||
|
||||
$replacement = sprintf('%s to %s', $_left, $_right);
|
||||
|
@ -155,11 +155,13 @@ class BankTransactionFilters extends QueryFilters
|
||||
$dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
|
||||
|
||||
if ($sort_col[0] == 'deposit') {
|
||||
return $this->builder->where('base_type', 'CREDIT')->orderBy('amount', $dir);
|
||||
return $this->builder->orderByRaw("(CASE WHEN base_type = 'CREDIT' THEN amount END) $dir")->orderBy('amount', $dir);
|
||||
// return $this->builder->where('base_type', 'CREDIT')->orderBy('amount', $dir);
|
||||
}
|
||||
|
||||
if ($sort_col[0] == 'withdrawal') {
|
||||
return $this->builder->where('base_type', 'DEBIT')->orderBy('amount', $dir);
|
||||
return $this->builder->orderByRaw("(CASE WHEN base_type = 'DEBIT' THEN amount END) $dir")->orderBy('amount', $dir);
|
||||
// return $this->builder->where('base_type', 'DEBIT')->orderBy('amount', $dir);
|
||||
}
|
||||
|
||||
if ($sort_col[0] == 'status') {
|
||||
|
@ -41,7 +41,7 @@ class ClientFilters extends QueryFilters
|
||||
*/
|
||||
public function balance(string $balance = ''): Builder
|
||||
{
|
||||
if (strlen($balance) == 0) {
|
||||
if (strlen($balance) == 0 || count(explode(":", $balance)) < 2) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,8 @@ class CreditFilters extends QueryFilters
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,8 @@ class InvoiceFilters extends QueryFilters
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
|
||||
});
|
||||
}
|
||||
|
||||
@ -152,22 +153,22 @@ class InvoiceFilters extends QueryFilters
|
||||
{
|
||||
|
||||
return $this->builder->where(function ($query) {
|
||||
$query->whereIn('invoices.status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT])
|
||||
->where('invoices.is_deleted', 0)
|
||||
->where('invoices.balance', '>', 0)
|
||||
->orWhere(function ($query) {
|
||||
$query->whereIn('status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT])
|
||||
->where('is_deleted', 0)
|
||||
->where('balance', '>', 0)
|
||||
->where(function ($query) {
|
||||
|
||||
$query->whereNull('invoices.due_date')
|
||||
$query->whereNull('due_date')
|
||||
->orWhere(function ($q) {
|
||||
$q->where('invoices.due_date', '>=', now()->startOfDay()->subSecond())->where('invoices.partial', 0);
|
||||
$q->where('due_date', '>=', now()->startOfDay()->subSecond())->where('partial', 0);
|
||||
})
|
||||
->orWhere(function ($q) {
|
||||
$q->where('invoices.partial_due_date', '>=', now()->startOfDay()->subSecond())->where('invoices.partial', '>', 0);
|
||||
$q->where('partial_due_date', '>=', now()->startOfDay()->subSecond())->where('partial', '>', 0);
|
||||
});
|
||||
|
||||
})
|
||||
->orderByRaw('ISNULL(invoices.due_date), invoices.due_date ' . 'desc')
|
||||
->orderByRaw('ISNULL(invoices.partial_due_date), invoices.partial_due_date ' . 'desc');
|
||||
->orderByRaw('ISNULL(due_date), due_date ' . 'desc')
|
||||
->orderByRaw('ISNULL(partial_due_date), partial_due_date ' . 'desc');
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -45,7 +45,8 @@ class QuoteFilters extends QueryFilters
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,8 @@ class RecurringInvoiceFilters extends QueryFilters
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -171,7 +171,7 @@ class TransactionTransformer implements BankRevenueInterface
|
||||
private function formatDate(string $input)
|
||||
{
|
||||
$timezone = Timezone::find($this->company->settings->timezone_id);
|
||||
$timezone_name = 'US/Eastern';
|
||||
$timezone_name = 'America/New_York';
|
||||
|
||||
if ($timezone) {
|
||||
$timezone_name = $timezone->name;
|
||||
|
@ -27,7 +27,7 @@ function nlog($output, $context = []): void
|
||||
}
|
||||
|
||||
if (gettype($output) == 'object') {
|
||||
$output = print_r($output, 1);
|
||||
$output = print_r($output, true);
|
||||
}
|
||||
|
||||
// $trace = debug_backtrace();
|
||||
@ -53,7 +53,7 @@ function nrlog($output, $context = []): void
|
||||
}
|
||||
|
||||
if (gettype($output) == 'object') {
|
||||
$output = print_r($output, 1);
|
||||
$output = print_r($output, true);
|
||||
}
|
||||
|
||||
// $trace = debug_backtrace();
|
||||
|
@ -11,16 +11,18 @@
|
||||
|
||||
namespace App\Helpers\Invoice;
|
||||
|
||||
use App\DataMapper\BaseSettings;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\DataMapper\Tax\RuleInterface;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Number;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\RecurringQuote;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\DataMapper\BaseSettings;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\DataMapper\Tax\RuleInterface;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
|
||||
class InvoiceItemSum
|
||||
@ -120,7 +122,7 @@ class InvoiceItemSum
|
||||
|
||||
private $tax_collection;
|
||||
|
||||
private ?Client $client;
|
||||
private Client | Vendor $client;
|
||||
|
||||
private bool $calc_tax = false;
|
||||
|
||||
@ -131,10 +133,10 @@ class InvoiceItemSum
|
||||
$this->tax_collection = collect([]);
|
||||
|
||||
$this->invoice = $invoice;
|
||||
$this->client = $invoice->client ?? $invoice->vendor;
|
||||
|
||||
if ($this->invoice->client) {
|
||||
$this->currency = $this->invoice->client->currency();
|
||||
$this->client = $this->invoice->client;
|
||||
$this->shouldCalculateTax();
|
||||
} else {
|
||||
$this->currency = $this->invoice->vendor->currency();
|
||||
@ -313,7 +315,7 @@ class InvoiceItemSum
|
||||
|
||||
$key = str_replace(' ', '', $tax_name.$tax_rate);
|
||||
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.floatval($tax_rate).'%'];
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.Number::formatValueNoTrailingZeroes(floatval($tax_rate), $this->client).'%'];
|
||||
|
||||
$this->tax_collection->push(collect($group_tax));
|
||||
}
|
||||
|
@ -11,14 +11,16 @@
|
||||
|
||||
namespace App\Helpers\Invoice;
|
||||
|
||||
use App\DataMapper\Tax\RuleInterface;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Number;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\RecurringQuote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\DataMapper\Tax\RuleInterface;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
|
||||
class InvoiceItemSumInclusive
|
||||
@ -109,7 +111,7 @@ class InvoiceItemSumInclusive
|
||||
|
||||
private bool $calc_tax = false;
|
||||
|
||||
private ?Client $client;
|
||||
private Client | Vendor $client;
|
||||
|
||||
private RuleInterface $rule;
|
||||
|
||||
@ -118,10 +120,10 @@ class InvoiceItemSumInclusive
|
||||
$this->tax_collection = collect([]);
|
||||
|
||||
$this->invoice = $invoice;
|
||||
$this->client = $invoice->client ?? $invoice->vendor;
|
||||
|
||||
if ($this->invoice->client) {
|
||||
$this->currency = $this->invoice->client->currency();
|
||||
$this->client = $this->invoice->client;
|
||||
$this->shouldCalculateTax();
|
||||
} else {
|
||||
$this->currency = $this->invoice->vendor->currency();
|
||||
@ -265,7 +267,7 @@ class InvoiceItemSumInclusive
|
||||
|
||||
$key = str_replace(' ', '', $tax_name.$tax_rate);
|
||||
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.$tax_rate.'%'];
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.Number::formatValueNoTrailingZeroes(floatval($tax_rate), $this->client).'%'];
|
||||
|
||||
$this->tax_collection->push(collect($group_tax));
|
||||
}
|
||||
@ -411,7 +413,7 @@ class InvoiceItemSumInclusive
|
||||
|
||||
$this->rule = new $class();
|
||||
|
||||
if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2)) {
|
||||
if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2 ?? false)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -11,12 +11,14 @@
|
||||
|
||||
namespace App\Helpers\Invoice;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\RecurringQuote;
|
||||
use App\Models\Vendor;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use Illuminate\Support\Collection;
|
||||
@ -50,6 +52,8 @@ class InvoiceSum
|
||||
|
||||
private $precision;
|
||||
|
||||
private Client | Vendor $client;
|
||||
|
||||
public InvoiceItemSum $invoice_items;
|
||||
|
||||
private $rappen_rounding = false;
|
||||
@ -60,18 +64,15 @@ class InvoiceSum
|
||||
*/
|
||||
public function __construct($invoice)
|
||||
{
|
||||
|
||||
$this->invoice = $invoice;
|
||||
$this->client = $invoice->client ?? $invoice->vendor;
|
||||
|
||||
if ($this->invoice->client) {
|
||||
$this->precision = $this->invoice->client->currency()->precision;
|
||||
$this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding');
|
||||
} else {
|
||||
$this->precision = $this->invoice->vendor->currency()->precision;
|
||||
$this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding');
|
||||
|
||||
}
|
||||
$this->precision = $this->client->currency()->precision;
|
||||
$this->rappen_rounding = $this->client->getSetting('enable_rappen_rounding');
|
||||
|
||||
$this->tax_map = new Collection();
|
||||
|
||||
}
|
||||
|
||||
public function build()
|
||||
@ -131,7 +132,7 @@ class InvoiceSum
|
||||
$tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name1, $this->invoice->tax_rate1);
|
||||
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.floatval($this->invoice->tax_rate1).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate1), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if (is_string($this->invoice->tax_name2) && strlen($this->invoice->tax_name2) >= 2) {
|
||||
@ -139,7 +140,7 @@ class InvoiceSum
|
||||
$tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name2, $this->invoice->tax_rate2);
|
||||
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.floatval($this->invoice->tax_rate2).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate2), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if (is_string($this->invoice->tax_name3) && strlen($this->invoice->tax_name3) >= 2) {
|
||||
@ -147,7 +148,7 @@ class InvoiceSum
|
||||
$tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name3, $this->invoice->tax_rate3);
|
||||
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.floatval($this->invoice->tax_rate3).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate3), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -12,7 +12,10 @@
|
||||
namespace App\Helpers\Invoice;
|
||||
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Number;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\RecurringQuote;
|
||||
@ -49,6 +52,8 @@ class InvoiceSumInclusive
|
||||
|
||||
private $rappen_rounding = false;
|
||||
|
||||
private Client | Vendor $client;
|
||||
|
||||
public InvoiceItemSumInclusive $invoice_items;
|
||||
/**
|
||||
* Constructs the object with Invoice and Settings object.
|
||||
@ -58,14 +63,10 @@ class InvoiceSumInclusive
|
||||
public function __construct($invoice)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
$this->client = $invoice->client ?? $invoice->vendor;
|
||||
|
||||
if ($this->invoice->client) {
|
||||
$this->precision = $this->invoice->client->currency()->precision;
|
||||
$this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding');
|
||||
} else {
|
||||
$this->precision = $this->invoice->vendor->currency()->precision;
|
||||
$this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding');
|
||||
}
|
||||
$this->precision = $this->client->currency()->precision;
|
||||
$this->rappen_rounding = $this->client->getSetting('enable_rappen_rounding');
|
||||
|
||||
$this->tax_map = new Collection();
|
||||
}
|
||||
@ -157,19 +158,19 @@ class InvoiceSumInclusive
|
||||
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate1, $amount);
|
||||
$this->total_taxes += $tax;
|
||||
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.floatval($this->invoice->tax_rate1).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate1), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if (is_string($this->invoice->tax_name2) && strlen($this->invoice->tax_name2) > 1) {
|
||||
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate2, $amount);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.floatval($this->invoice->tax_rate2).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate2), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if (is_string($this->invoice->tax_name3) && strlen($this->invoice->tax_name3) > 1) {
|
||||
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate3, $amount);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.floatval($this->invoice->tax_rate3).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate3), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -31,7 +31,7 @@ class GmailTransport extends AbstractTransport
|
||||
protected function doSend(SentMessage $message): void
|
||||
{
|
||||
nlog("In Do Send");
|
||||
$message = MessageConverter::toEmail($message->getOriginalMessage());
|
||||
$message = MessageConverter::toEmail($message->getOriginalMessage()); //@phpstan-ignore-line
|
||||
|
||||
/** @phpstan-ignore-next-line **/
|
||||
$token = $message->getHeaders()->get('gmailtoken')->getValue(); // @phpstan-ignore-line
|
||||
|
@ -25,7 +25,8 @@ class Office365MailTransport extends AbstractTransport
|
||||
|
||||
protected function doSend(SentMessage $message): void
|
||||
{
|
||||
$symfony_message = MessageConverter::toEmail($message->getOriginalMessage());
|
||||
$symfony_message = MessageConverter::toEmail($message->getOriginalMessage()); //@phpstan-ignore-line
|
||||
|
||||
|
||||
$graph = new Graph();
|
||||
|
||||
|
@ -376,6 +376,7 @@ class LoginController extends BaseController
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
/** @var Builder $cu */
|
||||
$cu = CompanyUser::query()->where('user_id', $user->id);
|
||||
|
||||
if ($cu->count() == 0) {
|
||||
@ -398,18 +399,15 @@ class LoginController extends BaseController
|
||||
$truth->setCompany($set_company);
|
||||
|
||||
//21-03-2024
|
||||
|
||||
|
||||
$cu->each(function ($cu) {
|
||||
if(CompanyToken::where('company_id', $cu->company_id)->where('user_id', $cu->user_id)->where('is_system', true)->doesntExist()) {
|
||||
/** @var \App\Models\CompanyUser $cu */
|
||||
if(CompanyToken::query()->where('company_id', $cu->company_id)->where('user_id', $cu->user_id)->where('is_system', true)->doesntExist()) {
|
||||
(new CreateCompanyToken($cu->company, $cu->user, request()->server('HTTP_USER_AGENT')))->handle();
|
||||
}
|
||||
});
|
||||
|
||||
// $user->account->companies->each(function ($company) use ($user) {
|
||||
// if ($company->tokens()->where('user_id',$user->id)->where('is_system', true)->count() == 0) {
|
||||
// (new CreateCompanyToken($company, $user, request()->server('HTTP_USER_AGENT')))->handle();
|
||||
// }
|
||||
// });
|
||||
|
||||
$truth->setCompanyToken(CompanyToken::where('user_id', $user->id)->where('company_id', $set_company->id)->where('is_system', true)->first());
|
||||
|
||||
return CompanyUser::query()->where('user_id', $user->id);
|
||||
|
@ -97,7 +97,7 @@ class YodleeController extends BaseController
|
||||
}
|
||||
|
||||
$company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($company) { // TODO: filter to yodlee only
|
||||
ProcessBankTransactionsYodlee::dispatch($company->account->id, $bank_integration);
|
||||
ProcessBankTransactionsYodlee::dispatch($company->account->bank_integration_account_id, $bank_integration);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -197,6 +197,7 @@ class BankIntegrationController extends BaseController
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
/** @var \App\Models\Account $user_account */
|
||||
$user_account = $user->account;
|
||||
|
||||
$this->refreshAccountsYodlee($user);
|
||||
@ -210,12 +211,14 @@ class BankIntegrationController extends BaseController
|
||||
// Processing transactions for each bank account
|
||||
if (Ninja::isHosted() && $user->account->bank_integration_account_id) {
|
||||
$user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->each(function ($bank_integration) use ($user_account) {
|
||||
ProcessBankTransactionsYodlee::dispatch($user_account->id, $bank_integration);
|
||||
/** @var \App\Models\BankIntegration $bank_integration */
|
||||
ProcessBankTransactionsYodlee::dispatch($user_account->bank_integration_account_id, $bank_integration);
|
||||
});
|
||||
}
|
||||
|
||||
if (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key') && (Ninja::isSelfHost() || (Ninja::isHosted() && $user_account->isEnterprisePaidClient()))) {
|
||||
$user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) {
|
||||
/** @var \App\Models\BankIntegration $bank_integration */
|
||||
ProcessBankTransactionsNordigen::dispatch($bank_integration);
|
||||
});
|
||||
}
|
||||
@ -345,7 +348,7 @@ class BankIntegrationController extends BaseController
|
||||
|
||||
if (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise') {
|
||||
$account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) {
|
||||
(new ProcessBankTransactionsYodlee($account->id, $bank_integration))->handle();
|
||||
(new ProcessBankTransactionsYodlee($account->bank_integration_account_id, $bank_integration))->handle();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ use League\Fractal\Resource\Item;
|
||||
use App\Models\BankTransactionRule;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use App\Transformers\ArraySerializer;
|
||||
use Illuminate\Support\Facades\Schema as DbSchema;
|
||||
use App\Transformers\EntityTransformer;
|
||||
use League\Fractal\Resource\Collection;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@ -653,7 +654,7 @@ class BaseController extends Controller
|
||||
/**
|
||||
* Passes back the miniloaded data response
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param mixed $query
|
||||
*
|
||||
*/
|
||||
protected function timeConstrainedResponse($query)
|
||||
@ -894,11 +895,7 @@ class BaseController extends Controller
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
}
|
||||
|
||||
// else {
|
||||
// $resource = new Collection($query, $transformer, $this->entity_type);
|
||||
// }
|
||||
|
||||
return $this->response($this->manager->createData($resource)->toArray());
|
||||
return $this->response($this->manager->createData($resource)->toArray()); //@phpstan-ignore-line
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1106,7 +1103,7 @@ class BaseController extends Controller
|
||||
public function flutterRoute()
|
||||
{
|
||||
|
||||
if ((bool) $this->checkAppSetup() !== false && $account = Account::first()) {
|
||||
if ((bool) $this->checkAppSetup() !== false && DbSchema::hasTable('accounts') && $account = Account::first()) {
|
||||
|
||||
/** @var \App\Models\Account $account */
|
||||
|
||||
|
@ -66,7 +66,7 @@ class ChartController extends BaseController
|
||||
return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200);
|
||||
}
|
||||
|
||||
public function calculatedField(ShowCalculatedFieldRequest $request)
|
||||
public function calculatedFields(ShowCalculatedFieldRequest $request)
|
||||
{
|
||||
|
||||
/** @var \App\Models\User auth()->user() */
|
||||
|
@ -300,7 +300,9 @@ class InvitationController extends Controller
|
||||
'signature' => false,
|
||||
'contact_first_name' => $invitation->contact->first_name ?? '',
|
||||
'contact_last_name' => $invitation->contact->last_name ?? '',
|
||||
'contact_email' => $invitation->contact->email ?? ''
|
||||
'contact_email' => $invitation->contact->email ?? '',
|
||||
'client_city' => $invitation->client->city ?? '',
|
||||
'client_postal_code' => $invitation->client->postal_code ?? '',
|
||||
];
|
||||
|
||||
$request->replace($data);
|
||||
|
@ -108,11 +108,11 @@ class PaymentController extends Controller
|
||||
*/
|
||||
public function process(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'contact_first_name' => ['required'],
|
||||
'contact_last_name' => ['required'],
|
||||
'contact_email' => ['required', 'email'],
|
||||
]);
|
||||
// $request->validate([
|
||||
// 'contact_first_name' => ['required'],
|
||||
// 'contact_last_name' => ['required'],
|
||||
// 'contact_email' => ['required', 'email'],
|
||||
// ]);
|
||||
|
||||
return (new InstantPayment($request))->run();
|
||||
}
|
||||
@ -209,7 +209,7 @@ class PaymentController extends Controller
|
||||
|
||||
if (property_exists($payment_hash->data, 'billing_context')) {
|
||||
$billing_subscription = \App\Models\Subscription::find($this->decodePrimaryKey($payment_hash->data->billing_context->subscription_id));
|
||||
|
||||
/** @var \App\Models\Subscription $billing_subscription */
|
||||
return (new SubscriptionService($billing_subscription))->completePurchase($payment_hash);
|
||||
}
|
||||
|
||||
|
@ -567,9 +567,9 @@ class CompanyGatewayController extends BaseController
|
||||
{
|
||||
|
||||
//Throttle here
|
||||
if (Cache::has("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}")) {
|
||||
return response()->json(['message' => 'Please wait whilst your previous attempts complete.'], 200);
|
||||
}
|
||||
// if (Cache::has("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}")) {
|
||||
// return response()->json(['message' => 'Please wait whilst your previous attempts complete.'], 200);
|
||||
// }
|
||||
|
||||
dispatch(function () use ($company_gateway) {
|
||||
MultiDB::setDb($company_gateway->company->db);
|
||||
|
@ -183,7 +183,7 @@ class DocumentController extends BaseController
|
||||
}
|
||||
|
||||
if ($action == 'download') {
|
||||
ZipDocuments::dispatch($documents->pluck('id'), $user->company(), auth()->user());
|
||||
ZipDocuments::dispatch($documents->pluck('id'), $user->company(), auth()->user()); //@phpstan-ignore-line
|
||||
|
||||
return response()->json(['message' => ctrans('texts.sent_message')], 200);
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ class ImportController extends Controller
|
||||
$contents = $this->convertEncoding($contents);
|
||||
|
||||
// Store the csv in cache with an expiry of 10 minutes
|
||||
Cache::put($hash.'-'.$entityType, base64_encode($contents), 600);
|
||||
Cache::put($hash.'-'.$entityType, base64_encode($contents), 1200);
|
||||
|
||||
// Parse CSV
|
||||
$csv_array = $this->getCsvData($contents);
|
||||
|
@ -35,7 +35,7 @@ class MailgunWebhookController extends BaseController
|
||||
}
|
||||
|
||||
if(\hash_equals(\hash_hmac('sha256', $input['signature']['timestamp'] . $input['signature']['token'], config('services.mailgun.webhook_signing_key')), $input['signature']['signature'])) {
|
||||
ProcessMailgunWebhook::dispatch($request->all())->delay(10);
|
||||
ProcessMailgunWebhook::dispatch($request->all())->delay(rand(2,10));
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Success.'], 200);
|
||||
|
@ -267,7 +267,7 @@ class MigrationController extends BaseController
|
||||
|
||||
if ($request->companies) {
|
||||
//handle Laravel 5.5 UniHTTP
|
||||
$companies = json_decode($request->companies, 1);
|
||||
$companies = json_decode($request->companies, true);
|
||||
} else {
|
||||
//handle Laravel 6 Guzzle
|
||||
$companies = [];
|
||||
@ -275,7 +275,7 @@ class MigrationController extends BaseController
|
||||
foreach ($request->all() as $input) {
|
||||
if ($input instanceof UploadedFile) {
|
||||
} else {
|
||||
$companies[] = json_decode($input, 1);
|
||||
$companies[] = json_decode($input, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,9 @@ class PaymentNotificationWebhookController extends Controller
|
||||
public function __invoke(PaymentNotificationWebhookRequest $request, string $company_key, string $company_gateway_id, string $client_hash)
|
||||
{
|
||||
/** @var \App\Models\CompanyGateway $company_gateway */
|
||||
|
||||
$company_gateway = CompanyGateway::find($this->decodePrimaryKey($company_gateway_id));
|
||||
|
||||
/** @var \App\Models\Client $client */
|
||||
$client = Client::find($this->decodePrimaryKey($client_hash));
|
||||
|
||||
return $company_gateway
|
||||
|
@ -81,4 +81,34 @@ class PingController extends BaseController
|
||||
|
||||
return response()->json(SystemHealth::check(), 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last error from storage/logs/laravel.log
|
||||
*
|
||||
* @return Response| \Illuminate\Http\JsonResponse
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/last_error",
|
||||
* operationId="getLastError",
|
||||
* tags={"last_error"},
|
||||
* summary="Get the last error from storage/logs/laravel.log",
|
||||
* description="Get the last error from storage/logs/laravel.log",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="The last error from the logs",
|
||||
* @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"),
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function lastError()
|
||||
{
|
||||
if (Ninja::isNinja() || ! auth()->user()->isAdmin()) {
|
||||
return response()->json(['message' => ctrans('texts.route_not_available'), 'errors' => []], 403);
|
||||
}
|
||||
|
||||
return response()->json(['last_error' => SystemHealth::lastError()], 200);
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ class PreviewController extends BaseController
|
||||
|
||||
$response = Response::make($pdf, 200);
|
||||
$response->header('Content-Type', 'application/pdf');
|
||||
$response->header('Server-Timing', microtime(true) - $start);
|
||||
$response->header('Server-Timing', (string) (microtime(true) - $start));
|
||||
|
||||
return $response;
|
||||
}
|
||||
@ -288,7 +288,7 @@ class PreviewController extends BaseController
|
||||
/** @var \App\Models\Company $company */
|
||||
$company = $user->company();
|
||||
|
||||
$design_object = json_decode(json_encode(request()->input('design')), 1);
|
||||
$design_object = json_decode(json_encode(request()->input('design')), true);
|
||||
|
||||
$ts = (new TemplateService());
|
||||
|
||||
|
@ -181,6 +181,9 @@ class SelfUpdateController extends BaseController
|
||||
|
||||
public function checkVersion()
|
||||
{
|
||||
if(Ninja::isHosted())
|
||||
return '5.10.SaaS';
|
||||
|
||||
return trim(file_get_contents(config('ninja.version_url')));
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ class SetupController extends Controller
|
||||
|
||||
public function index()
|
||||
{
|
||||
$check = SystemHealth::check(false);
|
||||
$check = SystemHealth::check(false, false);
|
||||
|
||||
if ($check['system_health'] == true && $check['simple_db_check'] && Schema::hasTable('accounts') && $account = Account::first()) {
|
||||
return redirect('/');
|
||||
@ -59,7 +59,7 @@ class SetupController extends Controller
|
||||
public function doSetup(StoreSetupRequest $request)
|
||||
{
|
||||
try {
|
||||
$check = SystemHealth::check(false);
|
||||
$check = SystemHealth::check(false, false);
|
||||
} catch (Exception $e) {
|
||||
nlog(['message' => $e->getMessage(), 'action' => 'SetupController::doSetup()']);
|
||||
|
||||
@ -145,6 +145,7 @@ class SetupController extends Controller
|
||||
|
||||
Artisan::call('config:clear');
|
||||
|
||||
Artisan::call('key:generate', ['--force' => true]);
|
||||
|
||||
Artisan::call('migrate', ['--force' => true]);
|
||||
Artisan::call('db:seed', ['--force' => true]);
|
||||
|
@ -11,13 +11,14 @@
|
||||
|
||||
namespace App\Http\Requests\Client;
|
||||
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\ValidClientGroupSettingsRule;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Http\ValidationRules\EInvoice\ValidClientScheme;
|
||||
use App\Http\ValidationRules\ValidClientGroupSettingsRule;
|
||||
|
||||
class UpdateClientRequest extends Request
|
||||
{
|
||||
@ -66,6 +67,8 @@ class UpdateClientRequest extends Request
|
||||
$rules['id_number'] = ['sometimes', 'bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id)];
|
||||
$rules['number'] = ['sometimes', 'bail', Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id)];
|
||||
|
||||
$rules['e_invoice'] = ['sometimes','nullable', new ValidClientScheme()];
|
||||
|
||||
$rules['settings'] = new ValidClientGroupSettingsRule();
|
||||
$rules['contacts'] = 'array';
|
||||
$rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email';
|
||||
|
@ -29,7 +29,7 @@ class CreatePaymentMethodRequest extends FormRequest
|
||||
$available_methods = [];
|
||||
|
||||
collect($client->service()->getPaymentMethods(-1))
|
||||
->filter(function ($method) use (&$available_methods) {
|
||||
->filter(function ($method) use (&$available_methods) { //@phpstan-ignore-line
|
||||
$available_methods[] = $method['gateway_type_id'];
|
||||
});
|
||||
|
||||
|
@ -15,10 +15,8 @@ use App\Utils\Ninja;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use InvoiceNinja\EInvoice\EInvoice;
|
||||
use App\Http\ValidationRules\ValidSettingsRule;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\Invoice;
|
||||
use App\Http\ValidationRules\EInvoice\ValidScheme;
|
||||
use App\Http\ValidationRules\EInvoice\ValidCompanyScheme;
|
||||
use App\Http\ValidationRules\Company\ValidSubdomain;
|
||||
|
||||
class UpdateCompanyRequest extends Request
|
||||
@ -67,7 +65,7 @@ class UpdateCompanyRequest extends Request
|
||||
$rules['smtp_local_domain'] = 'sometimes|string|nullable';
|
||||
// $rules['smtp_verify_peer'] = 'sometimes|string';
|
||||
|
||||
// $rules['e_invoice'] = ['sometimes','nullable', new ValidScheme()];
|
||||
$rules['e_invoice'] = ['sometimes','nullable', new ValidCompanyScheme()];
|
||||
|
||||
if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) {
|
||||
$rules['portal_domain'] = 'bail|nullable|sometimes|url';
|
||||
|
@ -48,7 +48,7 @@ class StoreCompanyGatewayRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if ($gateway = Gateway::where('key', $input['gateway_key'])->first()) {
|
||||
if ($gateway = Gateway::query()->where('key', $input['gateway_key'])->first()) {
|
||||
$default_gateway_fields = json_decode($gateway->fields);
|
||||
|
||||
/*Force gateway properties */
|
||||
|
@ -48,7 +48,7 @@ class UpdateCompanyGatewayRequest extends Request
|
||||
|
||||
/*Force gateway properties */
|
||||
if (isset($input['config']) && is_object(json_decode($input['config'])) && array_key_exists('gateway_key', $input)) {
|
||||
$gateway = Gateway::where('key', $input['gateway_key'])->first();
|
||||
$gateway = Gateway::query()->where('key', $input['gateway_key'])->first();
|
||||
$default_gateway_fields = json_decode($gateway->fields);
|
||||
|
||||
foreach (json_decode($input['config']) as $key => $value) {
|
||||
|
@ -65,6 +65,9 @@ class StoreCreditRequest extends Request
|
||||
|
||||
$rules['client_id'] = 'required|exists:clients,id,company_id,'.$user->company()->id;
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
// $rules['number'] = new UniqueCreditNumberRule($this->all());
|
||||
$rules['number'] = ['nullable', Rule::unique('credits')->where('company_id', $user->company()->id)];
|
||||
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
|
||||
|
@ -66,6 +66,9 @@ class UpdateCreditRequest extends Request
|
||||
|
||||
$rules['client_id'] = ['bail', 'sometimes',Rule::in([$this->credit->client_id])];
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
$rules['date'] = 'bail|sometimes|date:Y-m-d';
|
||||
|
@ -63,7 +63,7 @@ class SendEmailRequest extends Request
|
||||
$user = auth()->user();
|
||||
|
||||
return [
|
||||
'template' => 'bail|required|in:'.implode(',', $this->templates),
|
||||
'template' => 'bail|required|string|in:'.implode(',', $this->templates),
|
||||
'entity' => 'bail|required|in:App\Models\Invoice,App\Models\Quote,App\Models\Credit,App\Models\RecurringInvoice,App\Models\PurchaseOrder,App\Models\Payment',
|
||||
'entity_id' => ['bail', 'required', Rule::exists($this->entity_plural, 'id')->where('company_id', $user->company()->id)],
|
||||
'cc_email.*' => 'bail|sometimes|email',
|
||||
@ -94,7 +94,7 @@ class SendEmailRequest extends Request
|
||||
|
||||
$this->entity_plural = Str::plural($input['entity']) ?? '';
|
||||
|
||||
if (isset($input['entity'])) {
|
||||
if (isset($input['entity']) && in_array($input['entity'], ['invoice','quote','credit','recurring_invoice','purchase_order','payment'])) {
|
||||
$input['entity'] = "App\Models\\".ucfirst(Str::camel($input['entity']));
|
||||
}
|
||||
|
||||
|
@ -38,11 +38,14 @@ class StoreInvoiceRequest extends Request
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$rules = [];
|
||||
|
||||
$rules['client_id'] = ['required', 'bail', Rule::exists('clients', 'id')->where('company_id', $user->company()->id)->where('is_deleted', 0)];
|
||||
|
||||
if ($this->file('documents') && is_array($this->file('documents'))) {
|
||||
$rules['documents.*'] = $this->fileValidation();
|
||||
} elseif ($this->file('documents')) {
|
||||
@ -57,16 +60,16 @@ class StoreInvoiceRequest extends Request
|
||||
$rules['file'] = $this->fileValidation();
|
||||
}
|
||||
|
||||
$rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0';
|
||||
|
||||
$rules['invitations.*.client_contact_id'] = 'distinct';
|
||||
|
||||
$rules['number'] = ['bail', 'nullable', Rule::unique('invoices')->where('company_id', $user->company()->id)];
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
|
||||
$rules['date'] = 'bail|sometimes|date:Y-m-d';
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date'];
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
|
||||
@ -79,18 +82,17 @@ class StoreInvoiceRequest extends Request
|
||||
$rules['exchange_rate'] = 'bail|sometimes|numeric';
|
||||
$rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0';
|
||||
$rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date'];
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date'];
|
||||
|
||||
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
|
||||
|
||||
// $rules['amount'] = ['sometimes', 'bail', 'max:99999999999999'];
|
||||
// $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
@ -102,24 +104,24 @@ class StoreInvoiceRequest extends Request
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
$input['amount'] = $this->entityTotalAmount($input['line_items']);
|
||||
}
|
||||
|
||||
if(isset($input['partial']) && $input['partial'] == 0) {
|
||||
$input['partial_due_date'] = null;
|
||||
}
|
||||
|
||||
if (array_key_exists('tax_rate1', $input) && is_null($input['tax_rate1'])) {
|
||||
if (!isset($input['tax_rate1'])) {
|
||||
$input['tax_rate1'] = 0;
|
||||
}
|
||||
if (array_key_exists('tax_rate2', $input) && is_null($input['tax_rate2'])) {
|
||||
if (!isset($input['tax_rate2'])) {
|
||||
$input['tax_rate2'] = 0;
|
||||
}
|
||||
if (array_key_exists('tax_rate3', $input) && is_null($input['tax_rate3'])) {
|
||||
if (!isset($input['tax_rate3'])) {
|
||||
$input['tax_rate3'] = 0;
|
||||
}
|
||||
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
|
||||
$input['exchange_rate'] = 1;
|
||||
}
|
||||
|
||||
if(!isset($input['date'])) {
|
||||
$input['date'] = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d');
|
||||
}
|
||||
//handles edge case where we need for force set the due date of the invoice.
|
||||
if((isset($input['partial_due_date']) && strlen($input['partial_due_date']) > 1) && (!array_key_exists('due_date', $input) || (empty($input['due_date']) && empty($this->invoice->due_date)))) {
|
||||
$client = \App\Models\Client::withTrashed()->find($input['client_id']);
|
||||
|
@ -67,6 +67,9 @@ class UpdateInvoiceRequest extends Request
|
||||
$rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->invoice->client_id])];
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
|
||||
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
|
||||
$rules['tax_rate1'] = 'bail|sometimes|numeric';
|
||||
|
@ -46,7 +46,7 @@ class StorePaymentRequest extends Request
|
||||
|
||||
$rules = [
|
||||
'client_id' => ['bail','required',Rule::exists('clients', 'id')->where('company_id', $user->company()->id)->where('is_deleted', 0)],
|
||||
'invoices' => ['bail','sometimes', 'nullable', 'array', new ValidPayableInvoicesRule()],
|
||||
'invoices' => ['bail', 'sometimes', 'nullable', 'array', new ValidPayableInvoicesRule()],
|
||||
'invoices.*.amount' => ['bail','required'],
|
||||
'invoices.*.invoice_id' => ['bail','required','distinct', new ValidInvoicesRules($this->all()),Rule::exists('invoices', 'id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)],
|
||||
'credits.*.credit_id' => ['bail','required','distinct', new ValidCreditsRules($this->all()),Rule::exists('credits', 'id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)],
|
||||
|
@ -50,6 +50,10 @@ class StorePurchaseOrderRequest extends Request
|
||||
|
||||
$rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', $user->company()->id)];
|
||||
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.vendor_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
$rules['line_items'] = 'array';
|
||||
|
@ -53,6 +53,9 @@ class UpdatePurchaseOrderRequest extends Request
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.vendor_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
|
||||
|
@ -11,12 +11,13 @@
|
||||
|
||||
namespace App\Http\Requests\Quote;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\Quote\UniqueQuoteNumberRule;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Http\ValidationRules\Quote\UniqueQuoteNumberRule;
|
||||
use App\Http\ValidationRules\Project\ValidProjectForClient;
|
||||
|
||||
class StoreQuoteRequest extends Request
|
||||
{
|
||||
@ -43,7 +44,7 @@ class StoreQuoteRequest extends Request
|
||||
|
||||
$rules = [];
|
||||
|
||||
$rules['client_id'] = ['required', 'bail', Rule::exists('clients', 'id')->where('company_id', $user->company()->id)];
|
||||
$rules['client_id'] = ['required', 'bail', Rule::exists('clients', 'id')->where('company_id', $user->company()->id)->where('is_deleted',0)];
|
||||
|
||||
if ($this->file('documents') && is_array($this->file('documents'))) {
|
||||
$rules['documents.*'] = $this->fileValidation();
|
||||
@ -59,15 +60,28 @@ class StoreQuoteRequest extends Request
|
||||
$rules['file'] = $this->fileValidation();
|
||||
}
|
||||
|
||||
$rules['number'] = ['nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)];
|
||||
$rules['number'] = ['bail','nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)];
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
$rules['date'] = 'bail|sometimes|date:Y-m-d';
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date'];
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
$rules['tax_rate1'] = 'bail|sometimes|numeric';
|
||||
$rules['tax_rate2'] = 'bail|sometimes|numeric';
|
||||
$rules['tax_rate3'] = 'bail|sometimes|numeric';
|
||||
$rules['tax_name1'] = 'bail|sometimes|string|nullable';
|
||||
$rules['tax_name2'] = 'bail|sometimes|string|nullable';
|
||||
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
|
||||
$rules['exchange_rate'] = 'bail|sometimes|numeric';
|
||||
$rules['line_items'] = 'array';
|
||||
$rules['date'] = 'bail|sometimes|date:Y-m-d';
|
||||
|
||||
$rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0';
|
||||
$rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date'];
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date'];
|
||||
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
|
||||
|
||||
return $rules;
|
||||
@ -89,19 +103,24 @@ class StoreQuoteRequest extends Request
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
$input['amount'] = $this->entityTotalAmount($input['line_items']);
|
||||
}
|
||||
|
||||
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
|
||||
$input['exchange_rate'] = 1;
|
||||
}
|
||||
|
||||
if(isset($input['partial']) && $input['partial'] == 0) {
|
||||
$input['partial_due_date'] = null;
|
||||
}
|
||||
|
||||
if (!isset($input['tax_rate1'])) {
|
||||
$input['tax_rate1'] = 0;
|
||||
}
|
||||
if (!isset($input['tax_rate2'])) {
|
||||
$input['tax_rate2'] = 0;
|
||||
}
|
||||
if (!isset($input['tax_rate3'])) {
|
||||
$input['tax_rate3'] = 0;
|
||||
}
|
||||
if (!isset($input['exchange_rate'])) {
|
||||
$input['exchange_rate'] = 1;
|
||||
}
|
||||
if(!isset($input['date'])) {
|
||||
$input['date'] = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d');
|
||||
}
|
||||
|
||||
if(isset($input['partial_due_date']) && (!isset($input['due_date']) || strlen($input['due_date']) <= 1)) {
|
||||
$client = \App\Models\Client::withTrashed()->find($input['client_id']);
|
||||
$valid_days = ($client && strlen($client->getSetting('valid_until')) >= 1) ? $client->getSetting('valid_until') : 7;
|
||||
|
@ -56,6 +56,9 @@ class UpdateQuoteRequest extends Request
|
||||
$rules['file'] = $this->fileValidation();
|
||||
}
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['number'] = ['bail', 'sometimes', 'nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)->ignore($this->quote->id)];
|
||||
$rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->quote->client_id])];
|
||||
$rules['line_items'] = 'array';
|
||||
|
@ -34,7 +34,10 @@ class BulkRecurringExpenseRequest extends Request
|
||||
return false;
|
||||
}
|
||||
|
||||
return auth()->user()->can(auth()->user()->isAdmin(), RecurringExpense::class);
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->can('edit', RecurringExpense::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,7 +61,8 @@ class StoreRecurringInvoiceRequest extends Request
|
||||
|
||||
$rules['client_id'] = 'required|exists:clients,id,company_id,'.$user->company()->id;
|
||||
|
||||
$rules['invitations.*.client_contact_id'] = 'distinct';
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['frequency_id'] = 'required|integer|digits_between:1,12';
|
||||
|
||||
|
@ -60,6 +60,8 @@ class UpdateRecurringInvoiceRequest extends Request
|
||||
|
||||
$rules['number'] = ['bail', 'sometimes', Rule::unique('recurring_invoices')->where('company_id', $user->company()->id)->ignore($this->recurring_invoice->id)];
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->recurring_invoice->client_id])];
|
||||
|
||||
|
@ -69,7 +69,7 @@ class StoreTaskRequest extends Request
|
||||
|
||||
foreach ($values as $k) {
|
||||
if (!is_int($k[0]) || !is_int($k[1])) {
|
||||
return $fail('The '.$attribute.' - '.print_r($k, 1).' is invalid. Unix timestamps only.');
|
||||
return $fail('The '.$attribute.' - '.print_r($k, true).' is invalid. Unix timestamps only.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ class UpdateTaskRequest extends Request
|
||||
|
||||
foreach ($values as $k) {
|
||||
if (!is_int($k[0]) || !is_int($k[1])) {
|
||||
return $fail('The '.$attribute.' - '.print_r($k, 1).' is invalid. Unix timestamps only.');
|
||||
return $fail('The '.$attribute.' - '.print_r($k, true).' is invalid. Unix timestamps only.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,11 @@ class BulkVendorRequest extends Request
|
||||
return false;
|
||||
}
|
||||
|
||||
return auth()->user()->can(auth()->user()->isAdmin(), Vendor::class);
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->can('edit', Vendor::class);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,8 +19,9 @@ use Illuminate\Contracts\Validation\ValidationRule;
|
||||
*/
|
||||
class BlackListRule implements ValidationRule
|
||||
{
|
||||
/** Bad domains +/- dispoable email domains */
|
||||
/** Bad domains +/- disposable email domains */
|
||||
private array $blacklist = [
|
||||
'padvn.com',
|
||||
'anonaddy.me',
|
||||
'nqmo.com',
|
||||
'wireconnected.com',
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Http\ValidationRules\EInvoice;
|
||||
|
||||
use App\Services\EDocument\Standards\Validation\Peppol\ClientLevel;
|
||||
use Closure;
|
||||
use InvoiceNinja\EInvoice\EInvoice;
|
||||
use Illuminate\Validation\Validator;
|
||||
@ -19,11 +20,10 @@ use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Contracts\Validation\ValidatorAwareRule;
|
||||
|
||||
/**
|
||||
* Class BlackListRule.
|
||||
* Class ValidClientScheme.
|
||||
*/
|
||||
class ValidScheme implements ValidationRule, ValidatorAwareRule
|
||||
class ValidClientScheme implements ValidationRule, ValidatorAwareRule
|
||||
{
|
||||
|
||||
/**
|
||||
* The validator instance.
|
||||
*
|
||||
@ -34,8 +34,10 @@ class ValidScheme implements ValidationRule, ValidatorAwareRule
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
|
||||
if(isset($value['Invoice']))
|
||||
{
|
||||
$r = new EInvoice();
|
||||
$errors = $r->validateRequest($value['Invoice'], Invoice::class);
|
||||
$errors = $r->validateRequest($value['Invoice'], ClientLevel::class);
|
||||
|
||||
foreach ($errors as $key => $msg) {
|
||||
|
||||
@ -45,7 +47,7 @@ class ValidScheme implements ValidationRule, ValidatorAwareRule
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,7 +56,6 @@ class ValidScheme implements ValidationRule, ValidatorAwareRule
|
||||
public function setValidator(Validator $validator): static
|
||||
{
|
||||
$this->validator = $validator;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
66
app/Http/ValidationRules/EInvoice/ValidCompanyScheme.php
Normal file
66
app/Http/ValidationRules/EInvoice/ValidCompanyScheme.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*1`
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\ValidationRules\EInvoice;
|
||||
|
||||
use App\Services\EDocument\Standards\Validation\Peppol\CompanyLevel;
|
||||
use Closure;
|
||||
use InvoiceNinja\EInvoice\EInvoice;
|
||||
use Illuminate\Validation\Validator;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\Invoice;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Contracts\Validation\ValidatorAwareRule;
|
||||
|
||||
/**
|
||||
* Class ValidScheme.
|
||||
*/
|
||||
class ValidCompanyScheme implements ValidationRule, ValidatorAwareRule
|
||||
{
|
||||
|
||||
/**
|
||||
* The validator instance.
|
||||
*
|
||||
* @var Validator
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
|
||||
if(isset($value['Invoice']))
|
||||
{
|
||||
$r = new EInvoice();
|
||||
$errors = $r->validateRequest($value['Invoice'], CompanyLevel::class);
|
||||
|
||||
foreach ($errors as $key => $msg) {
|
||||
|
||||
$this->validator->errors()->add(
|
||||
"e_invoice.{$key}",
|
||||
"{$key} - {$msg}"
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current validator.
|
||||
*/
|
||||
public function setValidator(Validator $validator): static
|
||||
{
|
||||
$this->validator = $validator;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -85,11 +85,12 @@ class ValidInvoicesRules implements Rule
|
||||
//catch here nothing to do - we need this to prevent the last elseif triggering
|
||||
} elseif ($inv->status_id == Invoice::STATUS_DRAFT && floatval($invoice['amount']) > floatval($inv->amount)) {
|
||||
$this->error_msg = 'Amount cannot be greater than invoice balance';
|
||||
|
||||
return false;
|
||||
} elseif (floatval($invoice['amount']) > floatval($inv->balance)) {
|
||||
$this->error_msg = ctrans('texts.amount_greater_than_balance_v5');
|
||||
|
||||
return false;
|
||||
} elseif($inv->is_deleted){
|
||||
$this->error_msg = 'One or more invoices in this request have since been deleted';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class ValidPayableInvoicesRule implements Rule
|
||||
$invoices = [];
|
||||
|
||||
if (is_array($value)) {
|
||||
$invoices = Invoice::query()->whereIn('id', array_column($value, 'invoice_id'))->company()->get();
|
||||
$invoices = Invoice::query()->withTrashed()->whereIn('id', array_column($value, 'invoice_id'))->company()->get();
|
||||
}
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
|
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\ViewComposers\Components\Rotessa;
|
||||
|
||||
use App\DataProviders\CAProvinces;
|
||||
use App\DataProviders\USStates;
|
||||
use Illuminate\View\Component;
|
||||
use App\Models\ClientContact;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\View\View;
|
||||
|
||||
// AmericanBankInfo Component
|
||||
class AccountComponent extends Component
|
||||
{
|
||||
private $fields = [
|
||||
'bank_account_type',
|
||||
'routing_number',
|
||||
'institution_number',
|
||||
'transit_number',
|
||||
'bank_name',
|
||||
'country',
|
||||
'account_number'
|
||||
];
|
||||
|
||||
private $defaults = [
|
||||
'bank_account_type' => null,
|
||||
'routing_number' => null,
|
||||
'institution_number' => null,
|
||||
'transit_number' => null,
|
||||
'bank_name' => null,
|
||||
'account_number' => null,
|
||||
'country' => 'US',
|
||||
"authorization_type" => 'Online'
|
||||
];
|
||||
|
||||
public function __construct(public array $account) {
|
||||
$this->attributes = $this->newAttributeBag(Arr::only($this->account, $this->fields) );
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
|
||||
return render('gateways.rotessa.components.account', $this->attributes->getAttributes() + $this->defaults);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\ViewComposers\Components\Rotessa;
|
||||
|
||||
use App\DataProviders\CAProvinces;
|
||||
use App\DataProviders\USStates;
|
||||
use Illuminate\View\Component;
|
||||
use App\Models\ClientContact;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\View\View;
|
||||
|
||||
// Address Component
|
||||
class AddressComponent extends Component
|
||||
{
|
||||
private $fields = [
|
||||
'address_1',
|
||||
'address_2',
|
||||
'city',
|
||||
'postal_code',
|
||||
'province_code',
|
||||
'country'
|
||||
];
|
||||
|
||||
private $defaults = [
|
||||
'country' => 'US'
|
||||
];
|
||||
|
||||
public function __construct(public array $address) {
|
||||
if(strlen($this->address['state']) > 2 ) {
|
||||
$this->address['state'] = $this->address['country'] == 'US' ? array_search($this->address['state'], USStates::$states) : CAProvinces::getAbbreviation($this->address['state']);
|
||||
}
|
||||
|
||||
$this->attributes = $this->newAttributeBag(
|
||||
Arr::only(Arr::mapWithKeys($this->address, function ($item, $key) {
|
||||
return in_array($key, ['address1','address2','state'])?[ (['address1'=>'address_1','address2'=>'address_2','state'=>'province_code'])[$key] => $item ] :[ $key => $item ];
|
||||
}),
|
||||
$this->fields) );
|
||||
}
|
||||
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('gateways.rotessa.components.address', $this->attributes->getAttributes() + $this->defaults );
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\ViewComposers\Components\Rotessa;
|
||||
|
||||
use App\DataProviders\CAProvinces;
|
||||
use App\DataProviders\USStates;
|
||||
use Illuminate\View\Component;
|
||||
use App\Models\ClientContact;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\View\View;
|
||||
|
||||
|
||||
// Contact Component
|
||||
class ContactComponent extends Component
|
||||
{
|
||||
|
||||
public function __construct(ClientContact $contact) {
|
||||
|
||||
$contact = collect($contact->client->contacts->firstWhere('is_primary', 1)->toArray())->merge([
|
||||
'home_phone' =>$contact->client->phone,
|
||||
'custom_identifier' => $contact->client->client_hash,
|
||||
'name' =>$contact->client->name,
|
||||
'id' => null,
|
||||
] )->all();
|
||||
|
||||
$this->attributes = $this->newAttributeBag(Arr::only($contact, $this->fields) );
|
||||
}
|
||||
|
||||
private $fields = [
|
||||
'name',
|
||||
'email',
|
||||
'home_phone',
|
||||
'phone',
|
||||
'custom_identifier',
|
||||
'customer_type' ,
|
||||
'id'
|
||||
];
|
||||
|
||||
private $defaults = [
|
||||
'customer_type' => "Business",
|
||||
'custom_identifier' => null,
|
||||
'customer_id' => null
|
||||
];
|
||||
|
||||
public function render()
|
||||
{
|
||||
\Debugbar::debug($this->attributes->getAttributes() + $this->defaults);
|
||||
return render('gateways.rotessa.components.contact', $this->attributes->getAttributes() + $this->defaults );
|
||||
}
|
||||
}
|
16
app/Http/ViewComposers/RotessaComposer.php
Normal file
16
app/Http/ViewComposers/RotessaComposer.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\View;
|
||||
use App\DataProviders\CAProvinces;
|
||||
use App\DataProviders\USStates;
|
||||
|
||||
View::composer(['*.rotessa.components.address','*.rotessa.components.banks.US.bank','*.rotessa.components.dropdowns.country.US'], function ($view) {
|
||||
$states = USStates::get();
|
||||
$view->with('states', $states);
|
||||
});
|
||||
|
||||
// CAProvinces View Composer
|
||||
View::composer(['*.rotessa.components.address','*.rotessa.components.banks.CA.bank','*.rotessa.components.dropdowns.country.CA'], function ($view) {
|
||||
$provinces = CAProvinces::get();
|
||||
$view->with('provinces', $provinces);
|
||||
});
|
@ -31,7 +31,10 @@ class ProductMap
|
||||
12 => 'product.custom_value2',
|
||||
13 => 'product.custom_value3',
|
||||
14 => 'product.custom_value4',
|
||||
15 => 'product.image_url'
|
||||
15 => 'product.image_url',
|
||||
16 => 'product.in_stock_quantity',
|
||||
17 => 'product.tax_category',
|
||||
18 => 'product.max_quantity',
|
||||
];
|
||||
}
|
||||
|
||||
@ -54,6 +57,9 @@ class ProductMap
|
||||
13 => 'texts.custom_value',
|
||||
14 => 'texts.custom_value',
|
||||
15 => 'texts.image_url',
|
||||
16 => 'texts.in_stock_quantity',
|
||||
17 => 'texts.tax_category',
|
||||
18 => 'texts.max_quantity',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ class BaseImport
|
||||
}
|
||||
|
||||
/** @var string $base64_encoded_csv */
|
||||
$base64_encoded_csv = Cache::pull($this->hash.'-'.$entity_type);
|
||||
$base64_encoded_csv = Cache::get($this->hash.'-'.$entity_type);
|
||||
|
||||
if (empty($base64_encoded_csv)) {
|
||||
return null;
|
||||
@ -473,6 +473,8 @@ class BaseImport
|
||||
|
||||
$tasks = $this->groupTasks($tasks, $task_number_key);
|
||||
|
||||
nlog($tasks);
|
||||
|
||||
foreach ($tasks as $raw_task) {
|
||||
$task_data = [];
|
||||
|
||||
@ -702,16 +704,16 @@ class BaseImport
|
||||
->save();
|
||||
}
|
||||
|
||||
if ($invoice->status_id === Invoice::STATUS_DRAFT) {
|
||||
} elseif ($invoice->status_id === Invoice::STATUS_SENT) {
|
||||
if ($invoice->status_id == Invoice::STATUS_DRAFT) {
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
$invoice = $invoice
|
||||
->service()
|
||||
->markSent()
|
||||
->save();
|
||||
} elseif (
|
||||
$invoice->status_id <= Invoice::STATUS_SENT &&
|
||||
$invoice->amount > 0
|
||||
) {
|
||||
|
||||
if ($invoice->status_id <= Invoice::STATUS_SENT && $invoice->amount > 0) {
|
||||
if ($invoice->balance <= 0) {
|
||||
$invoice->status_id = Invoice::STATUS_PAID;
|
||||
$invoice->save();
|
||||
|
@ -172,7 +172,7 @@ class Wave extends BaseImport implements ImportInterface
|
||||
{
|
||||
$entity_type = 'expense';
|
||||
|
||||
$data = $this->getCsvData($entity_type);
|
||||
$data = $this->getCsvData('invoice');
|
||||
|
||||
if (!$data) {
|
||||
$this->entity_count['expense'] = 0;
|
||||
@ -244,8 +244,10 @@ class Wave extends BaseImport implements ImportInterface
|
||||
if (empty($expense_data['vendor_id'])) {
|
||||
$vendor_data['user_id'] = $this->getUserIDForRecord($expense_data);
|
||||
|
||||
if(isset($raw_expense['Vendor Name']) || isset($raw_expense['Vendor']))
|
||||
{
|
||||
$vendor_repository->save(
|
||||
['name' => $raw_expense['Vendor Name']],
|
||||
['name' => isset($raw_expense['Vendor Name']) ? $raw_expense['Vendor Name'] : isset($raw_expense['Vendor'])],
|
||||
$vendor = VendorFactory::create(
|
||||
$this->company->id,
|
||||
$vendor_data['user_id']
|
||||
@ -253,6 +255,7 @@ class Wave extends BaseImport implements ImportInterface
|
||||
);
|
||||
$expense_data['vendor_id'] = $vendor->id;
|
||||
}
|
||||
}
|
||||
|
||||
$validator = Validator::make(
|
||||
$expense_data,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user