mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 05:02:36 +01:00
Merge pull request #8728 from turbo124/v5-develop
Report Previews for Credits
This commit is contained in:
commit
e819b9ba62
@ -122,7 +122,7 @@ class TypeCheck extends Command
|
||||
$client->save();
|
||||
});
|
||||
|
||||
Company::cursor()->each(function ($company) {
|
||||
Company::query()->cursor()->each(function ($company) {
|
||||
$this->logMessage("Checking company {$company->id}");
|
||||
$company->saveSettings($company->settings, $company);
|
||||
});
|
||||
|
@ -11,13 +11,15 @@
|
||||
|
||||
namespace App\Export\CSV;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\Credit;
|
||||
use App\Transformers\CreditTransformer;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use App\Utils\Number;
|
||||
use App\Models\Credit;
|
||||
use League\Csv\Writer;
|
||||
use App\Models\Company;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use App\Transformers\CreditTransformer;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
|
||||
class CreditExport extends BaseExport
|
||||
{
|
||||
@ -80,24 +82,58 @@ class CreditExport extends BaseExport
|
||||
$this->credit_transformer = new CreditTransformer();
|
||||
}
|
||||
|
||||
public function run()
|
||||
public function returnJson()
|
||||
{
|
||||
$query = $this->init();
|
||||
|
||||
$header = $this->buildHeader();
|
||||
|
||||
$report = $query->cursor()
|
||||
->map(function ($credit) {
|
||||
$row = $this->buildRow($credit);
|
||||
return $this->processMetaData($row, $credit);
|
||||
})->toArray();
|
||||
|
||||
return array_merge([$header], $report);
|
||||
}
|
||||
|
||||
private function processMetaData(array $row, Credit $credit): array
|
||||
{
|
||||
$clean_row = [];
|
||||
|
||||
foreach ($this->input['report_keys'] as $key => $value) {
|
||||
|
||||
$report_keys = explode(".", $value);
|
||||
|
||||
$column_key = str_replace("credit.", "", $value);
|
||||
$clean_row[$key]['entity'] = $report_keys[0];
|
||||
$clean_row[$key]['id'] = $report_keys[1];
|
||||
$clean_row[$key]['hashed_id'] = $report_keys[0] == 'credit' ? null : $credit->{$report_keys[0]}->hashed_id ?? null;
|
||||
$clean_row[$key]['value'] = $row[$column_key];
|
||||
|
||||
if(in_array($report_keys[1], ['amount', 'balance', 'partial', 'refunded', 'applied','unit_cost','cost','price']))
|
||||
$clean_row[$key]['display_value'] = Number::formatMoney($row[$column_key], $credit->client);
|
||||
else
|
||||
$clean_row[$key]['display_value'] = $row[$column_key];
|
||||
|
||||
}
|
||||
|
||||
return $clean_row;
|
||||
}
|
||||
|
||||
private function init(): Builder
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
App::forgetInstance('translator');
|
||||
App::setLocale($this->company->locale());
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
//load the CSV document from a string
|
||||
$this->csv = Writer::createFromString();
|
||||
|
||||
if (count($this->input['report_keys']) == 0) {
|
||||
$this->input['report_keys'] = array_values($this->entity_keys);
|
||||
}
|
||||
|
||||
//insert the header
|
||||
$this->csv->insertOne($this->buildHeader());
|
||||
|
||||
$query = Credit::query()
|
||||
->withTrashed()
|
||||
->with('client')->where('company_id', $this->company->id)
|
||||
@ -105,8 +141,22 @@ class CreditExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function run(): string
|
||||
{
|
||||
$query = $this->init();
|
||||
//load the CSV document from a string
|
||||
$this->csv = Writer::createFromString();
|
||||
|
||||
//insert the header
|
||||
$this->csv->insertOne($this->buildHeader());
|
||||
// nlog($this->input['report_keys']);
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($credit) {
|
||||
nlog($this->buildRow($credit));
|
||||
$this->csv->insertOne($this->buildRow($credit));
|
||||
});
|
||||
|
||||
|
@ -30,7 +30,8 @@ class CreditController extends Controller
|
||||
{
|
||||
set_time_limit(0);
|
||||
|
||||
$invitation = $credit->invitations()->where('client_contact_id', auth()->user()->id)->first();
|
||||
// $invitation = $credit->invitations()->where('client_contact_id', auth()->user()->id)->first();
|
||||
$invitation = $credit->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
|
||||
|
||||
$data = [
|
||||
'credit' => $credit,
|
||||
|
@ -53,7 +53,7 @@ class QuoteController extends Controller
|
||||
{
|
||||
/* If the quote is expired, convert the status here */
|
||||
|
||||
$invitation = $quote->invitations()->where('client_contact_id', auth()->user()->id)->first();
|
||||
$invitation = $quote->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
|
||||
|
||||
$data = [
|
||||
'quote' => $quote,
|
||||
|
@ -78,7 +78,9 @@ class EmailController extends BaseController
|
||||
$entity_obj->service()->markSent()->save();
|
||||
|
||||
$mo->invitation_id = $invitation->id;
|
||||
|
||||
$mo->client_id = $invitation->contact->client_id ?? null;
|
||||
$mo->vendor_id = $invitation->contact->vendor_id ?? null;
|
||||
|
||||
Email::dispatch($mo, $invitation->company);
|
||||
}
|
||||
});
|
||||
|
@ -14,6 +14,7 @@ namespace App\Http\Controllers\Reports;
|
||||
use App\Export\CSV\CreditExport;
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Requests\Report\GenericReportRequest;
|
||||
use App\Jobs\Report\PreviewReport;
|
||||
use App\Jobs\Report\SendToAdmin;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Response;
|
||||
@ -62,14 +63,26 @@ class CreditReportController extends BaseController
|
||||
*/
|
||||
public function __invoke(GenericReportRequest $request)
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
if ($request->has('send_email') && $request->get('send_email')) {
|
||||
SendToAdmin::dispatch(auth()->user()->company(), $request->all(), CreditExport::class, $this->filename);
|
||||
SendToAdmin::dispatch($user->company(), $request->all(), CreditExport::class, $this->filename);
|
||||
|
||||
return response()->json(['message' => 'working...'], 200);
|
||||
}
|
||||
// expect a list of visible fields, or use the default
|
||||
|
||||
$export = new CreditExport(auth()->user()->company(), $request->all());
|
||||
if($request->has('output') && $request->input('output') == 'json') {
|
||||
|
||||
$hash = \Illuminate\Support\Str::uuid();
|
||||
|
||||
PreviewReport::dispatch($user->company(), $request->all(), CreditExport::class, $hash);
|
||||
|
||||
return response()->json(['message' => $hash], 200);
|
||||
}
|
||||
|
||||
$export = new CreditExport($user->company(), $request->all());
|
||||
|
||||
$csv = $export->run();
|
||||
|
||||
|
45
app/Http/Controllers/Reports/ReportPreviewController.php
Normal file
45
app/Http/Controllers/Reports/ReportPreviewController.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Reports;
|
||||
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Requests\Report\ReportPreviewRequest;
|
||||
|
||||
class ReportPreviewController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function __invoke(ReportPreviewRequest $request, ?string $hash)
|
||||
{
|
||||
|
||||
$report = Cache::get($hash);
|
||||
|
||||
if(!$report)
|
||||
return response()->json(['message' => 'Still working.....'], 409);
|
||||
|
||||
if($report){
|
||||
|
||||
Cache::forget($hash);
|
||||
|
||||
return response()->json($report, 200);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -53,7 +53,7 @@ class SelfUpdateController extends BaseController
|
||||
|
||||
nlog('Test filesystem is writable');
|
||||
|
||||
// $this->testWritable();
|
||||
$this->testWritable();
|
||||
|
||||
nlog('Clear cache directory');
|
||||
|
||||
@ -61,12 +61,22 @@ class SelfUpdateController extends BaseController
|
||||
|
||||
nlog('copying release file');
|
||||
|
||||
if (copy($this->getDownloadUrl(), storage_path("app/{$this->filename}"))) {
|
||||
nlog('Copied file from URL');
|
||||
} else {
|
||||
$file_headers = @get_headers($this->getDownloadUrl());
|
||||
|
||||
if (stripos($file_headers[0], "404 Not Found") >0 || (stripos($file_headers[0], "302 Found") > 0 && stripos($file_headers[7], "404 Not Found") > 0)) {
|
||||
return response()->json(['message' => 'Download not yet available. Please try again shortly.'], 410);
|
||||
}
|
||||
|
||||
try {
|
||||
if (copy($this->getDownloadUrl(), storage_path("app/{$this->filename}"))) {
|
||||
nlog('Copied file from URL');
|
||||
}
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
return response()->json(['message' => 'File exists on the server, however there was a problem downloading and copying to the local filesystem'], 500);
|
||||
}
|
||||
|
||||
nlog('Finished copying');
|
||||
|
||||
$file = Storage::disk('local')->path($this->filename);
|
||||
|
41
app/Http/Requests/Report/ReportPreviewRequest.php
Normal file
41
app/Http/Requests/Report/ReportPreviewRequest.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Report;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class ReportPreviewRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->isAdmin() || $user->hasPermission('view_reports');
|
||||
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
}
|
||||
}
|
51
app/Jobs/Report/PreviewReport.php
Normal file
51
app/Jobs/Report/PreviewReport.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Report;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
|
||||
class PreviewReport implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new job instance
|
||||
*/
|
||||
public function __construct(protected Company $company, protected array $request, private string $report_class, protected string $hash)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
/** @var \App\Export\CSV\CreditExport $export */
|
||||
$export = new $this->report_class($this->company, $this->request);
|
||||
$report = $export->returnJson();
|
||||
|
||||
nlog($report);
|
||||
Cache::put($this->hash, $report, 60 * 60);
|
||||
}
|
||||
|
||||
public function middleware()
|
||||
{
|
||||
return [new WithoutOverlapping("report-{$this->company->company_key}")];
|
||||
}
|
||||
}
|
@ -93,6 +93,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
});
|
||||
|
||||
Mailer::macro('postmark_config', function (string $postmark_key) {
|
||||
// @phpstan-ignore /** @phpstan-ignore-next-line **/
|
||||
Mailer::setSymfonyTransport(app('mail.manager')->createSymfonyTransport([
|
||||
'transport' => 'postmark',
|
||||
'token' => $postmark_key
|
||||
@ -101,8 +102,10 @@ class AppServiceProvider extends ServiceProvider
|
||||
return $this;
|
||||
});
|
||||
|
||||
|
||||
Mailer::macro('mailgun_config', function (string $secret, string $domain, string $endpoint = 'api.mailgun.net') {
|
||||
Mailer::setSymfonyTransport(app('mail.manager')->createSymfonyTransport([
|
||||
// @phpstan-ignore /** @phpstan-ignore-next-line **/
|
||||
Mailer::setSymfonyTransport(app('mail.manager')->createSymfonyTransport([
|
||||
'transport' => 'mailgun',
|
||||
'secret' => $secret,
|
||||
'domain' => $domain,
|
||||
|
@ -138,7 +138,9 @@ class Email implements ShouldQueue
|
||||
|
||||
$this->email_object->company = $this->company;
|
||||
|
||||
$this->email_object->client_id ? $this->email_object->settings = $this->email_object->client->getMergedSettings() : $this->email_object->settings = $this->company->settings;
|
||||
$this->email_object->client_id ? $this->email_object->settings = $this->email_object->client->getMergedSettings() : $this->email_object->settings = $this->company->settings;
|
||||
|
||||
$this->email_object->client_id ? nlog("client settings") : nlog("company settings ");
|
||||
|
||||
$this->email_object->whitelabel = $this->company->account->isPaid() ? true : false;
|
||||
|
||||
|
@ -30,6 +30,7 @@ class TaskTransformer extends EntityTransformer
|
||||
|
||||
protected $defaultIncludes = [
|
||||
'documents',
|
||||
'project',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -3342,9 +3342,9 @@ $LANG = array(
|
||||
'freq_three_years' => 'Three Years',
|
||||
'military_time_help' => '24 Hour Display',
|
||||
'click_here_capital' => 'Click here',
|
||||
'marked_invoice_as_paid' => 'Successfully marked invoice as sent',
|
||||
'marked_invoice_as_paid' => 'Successfully marked invoice as paid',
|
||||
'marked_invoices_as_sent' => 'Successfully marked invoices as sent',
|
||||
'marked_invoices_as_paid' => 'Successfully marked invoices as sent',
|
||||
'marked_invoices_as_paid' => 'Successfully marked invoices as paid',
|
||||
'activity_57' => 'System failed to email invoice :invoice',
|
||||
'custom_value3' => 'Custom Value 3',
|
||||
'custom_value4' => 'Custom Value 4',
|
||||
@ -5157,6 +5157,8 @@ $LANG = array(
|
||||
'unlinked_transactions' => 'Successfully unlinked :count transactions',
|
||||
'unlinked_transaction' => 'Successfully unlinked transaction',
|
||||
'view_dashboard_permission' => 'Allow user to access the dashboard, data is limited to available permissions',
|
||||
'marked_sent_credits' => 'Successfully marked credits sent',
|
||||
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
10
phpstan.neon
10
phpstan.neon
@ -1,19 +1,23 @@
|
||||
includes:
|
||||
- ./vendor/nunomaduro/larastan/extension.neon
|
||||
- ./vendor/spaze/phpstan-stripe/extension.neon
|
||||
- phpstan-baseline.neon
|
||||
parameters:
|
||||
level: 2
|
||||
paths:
|
||||
- 'app/'
|
||||
- app
|
||||
excludePaths:
|
||||
- 'vendor/'
|
||||
- 'vendor/*'
|
||||
- '../resources/*'
|
||||
- resources/
|
||||
- resources/*
|
||||
- 'app/Jobs/Ninja/*'
|
||||
- 'app/Models/Presenters/*'
|
||||
- 'app/Console/Commands/*'
|
||||
- 'app/DataMapper/Analytics/*'
|
||||
- 'app/PaymentDrivers/Authorize/*'
|
||||
- 'app/PaymentDrivers/AuthorizePaymentDriver.php'
|
||||
- 'app/Utils/Traits/*'
|
||||
- 'resources/views/*'
|
||||
universalObjectCratesClasses:
|
||||
- App\DataMapper\Tax\RuleInterface
|
||||
- App\DataMapper\FeesAndLimits
|
||||
|
@ -92,6 +92,7 @@ use App\Http\Controllers\Reports\InvoiceReportController;
|
||||
use App\Http\Controllers\Reports\PaymentReportController;
|
||||
use App\Http\Controllers\Reports\ProductReportController;
|
||||
use App\Http\Controllers\Reports\ProfitAndLossController;
|
||||
use App\Http\Controllers\Reports\ReportPreviewController;
|
||||
use App\Http\Controllers\Reports\ActivityReportController;
|
||||
use App\Http\Controllers\Reports\ARDetailReportController;
|
||||
use App\Http\Controllers\Reports\DocumentReportController;
|
||||
@ -312,7 +313,9 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
|
||||
Route::post('reports/client_sales_report', ClientSalesReportController::class);
|
||||
Route::post('reports/tax_summary_report', TaxSummaryReportController::class);
|
||||
Route::post('reports/user_sales_report', UserSalesReportController::class);
|
||||
|
||||
Route::post('reports/preview/{hash}', ReportPreviewController::class);
|
||||
|
||||
|
||||
Route::resource('task_schedulers', TaskSchedulerController::class);
|
||||
Route::post('task_schedulers/bulk', [TaskSchedulerController::class, 'bulk'])->name('task_schedulers.bulk');
|
||||
|
||||
|
@ -674,6 +674,62 @@ class ReportCsvGenerationTest extends TestCase
|
||||
|
||||
}
|
||||
|
||||
public function testCreditJsonReport()
|
||||
{
|
||||
|
||||
Credit::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->company->id,
|
||||
'client_id' => $this->client->id,
|
||||
'amount' => 100,
|
||||
'balance' => 50,
|
||||
'number' => '1234',
|
||||
'status_id' => 2,
|
||||
'discount' => 10,
|
||||
'po_number' => '1234',
|
||||
'public_notes' => 'Public',
|
||||
'private_notes' => 'Private',
|
||||
'terms' => 'Terms',
|
||||
]);
|
||||
|
||||
$data = [
|
||||
'date_range' => 'all',
|
||||
'report_keys' => ["client.name","credit.number","credit.amount","payment.date", "payment.amount"],
|
||||
'send_email' => false,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/reports/credits?output=json', $data);
|
||||
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
nlog($arr['message']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/reports/preview/'.$arr['message']);
|
||||
|
||||
$response->assertStatus(409);
|
||||
|
||||
sleep(1);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/reports/preview/'.$arr['message']);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
nlog($response->json());
|
||||
|
||||
}
|
||||
|
||||
public function testCreditCustomColumnsCsvGeneration()
|
||||
{
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user