diff --git a/app/Console/Commands/CreateSingleAccount.php b/app/Console/Commands/CreateSingleAccount.php index 155d92a1d91c..c650fe2ad382 100644 --- a/app/Console/Commands/CreateSingleAccount.php +++ b/app/Console/Commands/CreateSingleAccount.php @@ -112,6 +112,7 @@ class CreateSingleAccount extends Command $company = Company::factory()->create([ 'account_id' => $account->id, 'slack_webhook_url' => config('ninja.notification.slack'), + 'use_credits_payment' => 'always', ]); $account->default_company_id = $company->id; diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index fc2c783e3102..bad4f90c691b 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -207,7 +207,7 @@ class BaseController extends Controller $query->whereNotNull('updated_at')->with('documents'); }, 'company.clients' => function ($query) use ($updated_at) { - $query->where('clients.updated_at', '>=', $updated_at)->with('contacts.company', 'gateway_tokens','documents','company'); + $query->where('clients.updated_at', '>=', $updated_at)->with('contacts.company', 'gateway_tokens','documents'); }, 'company.company_gateways' => function ($query) { $query->whereNotNull('updated_at'); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b7f4db0c3774..8c754ff78d42 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -52,10 +52,9 @@ class Kernel extends HttpKernel ], 'api' => [ - 'throttle:60,1', + 'throttle:300,1', 'bindings', 'query_logging', - //\App\Http\Middleware\StartupCheck::class, \App\Http\Middleware\Cors::class, ], 'contact' => [ @@ -75,7 +74,7 @@ class Kernel extends HttpKernel \App\Http\Middleware\QueryLogging::class, ], 'shop' => [ - 'throttle:60,1', + 'throttle:120,1', 'bindings', 'query_logging', ], diff --git a/app/Http/Middleware/QueryLogging.php b/app/Http/Middleware/QueryLogging.php index d0cc13c98f65..bf71e2969221 100644 --- a/app/Http/Middleware/QueryLogging.php +++ b/app/Http/Middleware/QueryLogging.php @@ -54,6 +54,7 @@ class QueryLogging // if($count > 50) // Log::info($queries); + } } diff --git a/app/Http/Requests/Expense/UpdateExpenseRequest.php b/app/Http/Requests/Expense/UpdateExpenseRequest.php index 62ca15c72b12..06f42e6b414d 100644 --- a/app/Http/Requests/Expense/UpdateExpenseRequest.php +++ b/app/Http/Requests/Expense/UpdateExpenseRequest.php @@ -46,16 +46,7 @@ class UpdateExpenseRequest extends Request $rules['number'] = 'unique:expenses,number,'.$this->id.',id,company_id,'.$this->expense->company_id; } - $contacts = request('contacts'); - - if (is_array($contacts)) { - // for ($i = 0; $i < count($contacts); $i++) { - // // $rules['contacts.' . $i . '.email'] = 'nullable|email|unique:client_contacts,email,' . isset($contacts[$i]['id'].',company_id,'.$this->company_id); - // //$rules['contacts.' . $i . '.email'] = 'nullable|email'; - // } - } - - return $rules; + return $this->globalRules($rules); } public function messages() @@ -72,6 +63,8 @@ class UpdateExpenseRequest extends Request { $input = $this->all(); + $input = $this->decodePrimaryKeys($input); + $this->replace($input); } } diff --git a/app/Http/Requests/Project/StoreProjectRequest.php b/app/Http/Requests/Project/StoreProjectRequest.php index 1903854bbd0f..6070d11fb874 100644 --- a/app/Http/Requests/Project/StoreProjectRequest.php +++ b/app/Http/Requests/Project/StoreProjectRequest.php @@ -34,7 +34,6 @@ class StoreProjectRequest extends Request { $rules = []; - //$rules['name'] ='required|unique:projects,name,null,null,company_id,'.auth()->user()->companyId(); $rules['name'] = 'required'; $rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; @@ -48,7 +47,6 @@ class StoreProjectRequest extends Request if (array_key_exists('client_id', $input) && is_string($input['client_id'])) { $input['client_id'] = $this->decodePrimaryKey($input['client_id']); } - $this->replace($input); } diff --git a/app/Http/Requests/Project/UpdateProjectRequest.php b/app/Http/Requests/Project/UpdateProjectRequest.php index 73b1c642e105..2382abfc3820 100644 --- a/app/Http/Requests/Project/UpdateProjectRequest.php +++ b/app/Http/Requests/Project/UpdateProjectRequest.php @@ -38,6 +38,10 @@ class UpdateProjectRequest extends Request { $input = $this->all(); + if (array_key_exists('client_id', $input) && is_string($input['client_id'])) { + unset($input['client_id']); + } + $this->replace($input); } } diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 22397743ddbe..2b254144074b 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -63,7 +63,7 @@ class Request extends FormRequest private function vendor_id($rules) { - $rules['vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id; + $rules['vendor_id'] = 'bail|nullable|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id; return $rules; } diff --git a/app/Jobs/Mail/BaseMailerJob.php b/app/Jobs/Mail/BaseMailerJob.php index 2567f954eef6..38ef8ade78e9 100644 --- a/app/Jobs/Mail/BaseMailerJob.php +++ b/app/Jobs/Mail/BaseMailerJob.php @@ -82,7 +82,7 @@ class BaseMailerJob implements ShouldQueue public function failed($exception = null) { - info('the job failed'); + // info('the job failed'); $job_failure = new EmailFailure(); $job_failure->string_metric5 = get_parent_class($this); diff --git a/app/Models/Client.php b/app/Models/Client.php index afda91176ea4..86d4fd90b65c 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -240,7 +240,6 @@ class Client extends BaseModel implements HasLocalePreference return $item->id == $this->getSetting('date_format_id'); })->first()->format; - //return DateFormat::find($this->getSetting('date_format_id'))->format; } public function currency() diff --git a/app/Models/Project.php b/app/Models/Project.php index e3669bc3ad7b..6e43247fd3a6 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -11,7 +11,7 @@ use Laracasts\Presenter\PresentableTrait; */ class Project extends BaseModel { - // Expense Categories + use SoftDeletes; use PresentableTrait; use Filterable; diff --git a/app/PaymentDrivers/Authorize/AuthorizeCreditCard.php b/app/PaymentDrivers/Authorize/AuthorizeCreditCard.php index c531d6d82852..38ad15a89400 100644 --- a/app/PaymentDrivers/Authorize/AuthorizeCreditCard.php +++ b/app/PaymentDrivers/Authorize/AuthorizeCreditCard.php @@ -201,7 +201,7 @@ class AuthorizeCreditCard private function processFailedResponse($data, $request) { //dd($data); - info(print_r($data, 1)); +// info(print_r($data, 1)); } private function formatGatewayResponse($data, $vars) diff --git a/app/Services/Credit/CreditService.php b/app/Services/Credit/CreditService.php index 8b8dca70ae9d..cd4982814508 100644 --- a/app/Services/Credit/CreditService.php +++ b/app/Services/Credit/CreditService.php @@ -55,6 +55,19 @@ class CreditService return $this; } + public function setCalculatedStatus() + { + + if((int)$this->credit->balance == 0) + $this->credit->status_id = Credit::STATUS_APPLIED; + elseif((string)$this->credit->amount == (string)$this->credit->balance) + $this->credit->status_id = Credit::STATUS_SENT; + elseif($this->credit->balance > 0) + $this->credit->status_id = Credit::STATUS_PARTIAL; + + return $this; + } + public function markSent() { $this->credit = (new MarkSent($this->credit->client, $this->credit))->run(); @@ -69,6 +82,13 @@ class CreditService return $this; } + public function adjustBalance($adjustment) + { + $this->credit->balance += $adjustment; + + return $this; + } + /** * Saves the credit. * @return Credit object diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index ebaebbaf3cdf..36b296c0658c 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -56,7 +56,7 @@ class AutoBillInvoice extends AbstractService //if the credits cover the payments, we stop here, build the payment with credits and exit early - if($this->invoice->company->use_credits_payment == 'always' || $this->invoice->company->use_credits_payment == 'option') + if($this->invoice->company->use_credits_payment != 'off') $this->applyCreditPayment(); info("partial = {$this->invoice->partial}"); @@ -68,7 +68,7 @@ class AutoBillInvoice extends AbstractService elseif($this->invoice->balance > 0) $amount = $this->invoice->balance; else - return $this->finalizePaymentUsingCredits(); + return $this->invoice; info("balance remains to be paid!!"); @@ -127,8 +127,11 @@ class AutoBillInvoice extends AbstractService $current_credit = Credit::find($credit['credit_id']); $payment->credits()->attach($current_credit->id, ['amount' => $credit['amount']]); + info("adjusting credit balance {$current_credit->balance} by this amount ". $credit['amount']); + $current_credit->balance -= $credit['amount']; - $current_credit->save(); + + $current_credit->service()->setCalculatedStatus()->save(); // $this->applyPaymentToCredit($current_credit, $credit['amount']); } @@ -147,9 +150,9 @@ class AutoBillInvoice extends AbstractService ->updateCreditBalance($amount * -1, 'Credits used to pay down Invoice ' . $this->invoice->number) ->save(); - event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars())); + event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars())); - return $this->invoice->service()->setStatus(Invoice::STATUS_PAID)->save(); + return $this->invoice->service()->setCalculatedStatus()->save(); } /** @@ -169,6 +172,8 @@ class AutoBillInvoice extends AbstractService $available_credit_balance = $available_credits->sum('balance'); + info("available credit balance = {$available_credit_balance}"); + if((int)$available_credit_balance == 0) return; @@ -217,7 +222,7 @@ class AutoBillInvoice extends AbstractService } } - + $this->finalizePaymentUsingCredits(); return $this; } diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index 325b8328ccea..89de0c7bdf81 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -210,6 +210,16 @@ class InvoiceService return $this; } + public function setCalculatedStatus() + { + if((int)$this->invoice->balance == 0) + $this->setStatus(Invoice::STATUS_PAID); + elseif($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) + $this->setStatus(Invoice::STATUS_PARTIAL); + + return $this; + } + public function updateStatus() { info("invoice balance = {$this->invoice->balance}"); diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php index 250b4229518b..1826f7297460 100644 --- a/app/Services/Payment/RefundPayment.php +++ b/app/Services/Payment/RefundPayment.php @@ -241,6 +241,7 @@ class RefundPayment $adjustment_amount += $refunded_invoice['amount']; $client->balance += $refunded_invoice['amount']; + //$client->paid_to_date -= $refunded_invoice['amount'];//todo refund balancing $client->save(); //todo adjust ledger balance here? or after and reference the credit and its total @@ -251,7 +252,8 @@ class RefundPayment // $this->credit_note->ledger()->updateCreditBalance($adjustment_amount, $ledger_string); $client = $this->payment->client->fresh(); - $client->service()->updatePaidToDate(-1 * $this->total_refund)->save(); + //$client->service()->updatePaidToDate(-1 * $this->total_refund)->save(); + $client->service()->updatePaidToDate(-1 * $refunded_invoice['amount'])->save(); } return $this; diff --git a/app/Services/PdfMaker/PdfMakerUtilities.php b/app/Services/PdfMaker/PdfMakerUtilities.php index 8ffe05594295..17c23d2c737f 100644 --- a/app/Services/PdfMaker/PdfMakerUtilities.php +++ b/app/Services/PdfMaker/PdfMakerUtilities.php @@ -293,7 +293,7 @@ trait PdfMakerUtilities $this->document->getElementById('repeat-content')->appendChild($clone); } - info($this->data['options']); + // info($this->data['options']); if ( $header = $this->document->getElementById('header') && diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 51315c0e7b57..6bba99350e1b 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -144,6 +144,9 @@ class CompanyTransformer extends EntityTransformer 'enable_shop_api' => (bool) $company->enable_shop_api, 'mark_expenses_invoiceable'=> (bool) $company->mark_expenses_invoiceable, 'mark_expenses_paid' => (bool) $company->mark_expenses_paid, + 'invoice_expense_documents' => (bool) $company->invoice_expense_documents, + 'invoice_task_timelog' => (bool) $company->invoice_task_timelog, + 'auto_start_tasks' => (bool) $company->auto_start_tasks, 'use_credits_payment' => (string) $company->use_credits_payment, ]; } diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 6befe6d676db..6c3cafa73a0e 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -15,10 +15,13 @@ namespace App\Utils; use App\Designs\Designer; use App\Models\Country; use App\Utils\Number; +use App\Utils\Traits\MakesDates; use Illuminate\Support\Facades\App; class HtmlEngine { + use MakesDates; + public $entity; public $invitation; @@ -172,10 +175,10 @@ class HtmlEngine $data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->client) ?: ' ', 'label' => ctrans('texts.taxes')]; $data['$invoice.taxes'] = &$data['$taxes']; - $data['$invoice.custom1'] = ['value' => $this->entity->custom_value1 ?: ' ', 'label' => $this->makeCustomField('invoice1')]; - $data['$invoice.custom2'] = ['value' => $this->entity->custom_value2 ?: ' ', 'label' => $this->makeCustomField('invoice2')]; - $data['$invoice.custom3'] = ['value' => $this->entity->custom_value3 ?: ' ', 'label' => $this->makeCustomField('invoice3')]; - $data['$invoice.custom4'] = ['value' => $this->entity->custom_value4 ?: ' ', 'label' => $this->makeCustomField('invoice4')]; + $data['$invoice.custom1'] = ['value' => $this->formatCustomFieldValue('invoice1', $this->entity->custom_value1) ?: ' ', 'label' => $this->makeCustomField('invoice1')]; + $data['$invoice.custom2'] = ['value' => $this->formatCustomFieldValue('invoice2', $this->entity->custom_value2) ?: ' ', 'label' => $this->makeCustomField('invoice2')]; + $data['$invoice.custom3'] = ['value' => $this->formatCustomFieldValue('invoice3', $this->entity->custom_value3) ?: ' ', 'label' => $this->makeCustomField('invoice3')]; + $data['$invoice.custom4'] = ['value' => $this->formatCustomFieldValue('invoice4', $this->entity->custom_value4) ?: ' ', 'label' => $this->makeCustomField('invoice4')]; $data['$invoice.public_notes'] = ['value' => $this->entity->public_notes ?: ' ', 'label' => ctrans('texts.public_notes')]; $data['$entity.public_notes'] = &$data['$invoice.public_notes']; @@ -481,6 +484,7 @@ class HtmlEngine if ($custom_fields && property_exists($custom_fields, $field)) { $custom_field = $custom_fields->{$field}; + $custom_field_parts = explode('|', $custom_field); return $custom_field_parts[0]; @@ -489,6 +493,28 @@ class HtmlEngine return ''; } + private function formatCustomFieldValue($field, $value) :string + { + $custom_fields = $this->company->custom_fields; + $custom_field = ''; + + if ($custom_fields && property_exists($custom_fields, $field)) { + $custom_field = $custom_fields->{$field}; + $custom_field_parts = explode('|', $custom_field); + $custom_field = $custom_field_parts[1]; + } + + switch ($custom_field) { + case 'date': + return $this->formatDate($value, $this->client->date_format()); + break; + + default: + return $value; + break; + } + } + private function makeTotalTaxes() :string { $data = ''; diff --git a/composer.lock b/composer.lock index a013bee1626f..4e7250b8470a 100644 --- a/composer.lock +++ b/composer.lock @@ -3879,16 +3879,16 @@ }, { "name": "nesbot/carbon", - "version": "2.41.3", + "version": "2.41.4", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "e148788eeae9b9b7b87996520358b86faad37b52" + "reference": "6571aec754a648ef476a8d8f57993f7bc965afe4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e148788eeae9b9b7b87996520358b86faad37b52", - "reference": "e148788eeae9b9b7b87996520358b86faad37b52", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/6571aec754a648ef476a8d8f57993f7bc965afe4", + "reference": "6571aec754a648ef476a8d8f57993f7bc965afe4", "shasum": "" }, "require": { @@ -3964,7 +3964,7 @@ "type": "tidelift" } ], - "time": "2020-10-12T20:36:09+00:00" + "time": "2020-10-22T07:28:05+00:00" }, { "name": "nikic/php-parser", @@ -6588,7 +6588,7 @@ "type": "tidelift" } ], - "time": "2020-10-07T15:23:00+00:00" + "time": "2020-09-18T14:27:32+00:00" }, { "name": "symfony/css-selector", diff --git a/config/app.php b/config/app.php index bbd643def594..c1dd0812a8ef 100644 --- a/config/app.php +++ b/config/app.php @@ -181,6 +181,7 @@ return [ App\Providers\MultiDBProvider::class, App\Providers\ClientPortalServiceProvider::class, App\Providers\NinjaTranslationServiceProvider::class, + ], /* diff --git a/database/migrations/2020_10_22_204900_company_table_fields.php b/database/migrations/2020_10_22_204900_company_table_fields.php new file mode 100644 index 000000000000..14d8bd57faa1 --- /dev/null +++ b/database/migrations/2020_10_22_204900_company_table_fields.php @@ -0,0 +1,30 @@ +boolean('invoice_task_timelog')->default(true); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/tests/Feature/ActivityApiTest.php b/tests/Feature/ActivityApiTest.php index 70357b0d5110..633d05a95d99 100644 --- a/tests/Feature/ActivityApiTest.php +++ b/tests/Feature/ActivityApiTest.php @@ -14,6 +14,7 @@ use Faker\Factory; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\MockAccountData; use Tests\TestCase; +use Illuminate\Routing\Middleware\ThrottleRequests; /** * @test @@ -31,6 +32,11 @@ class ActivityApiTest extends TestCase $this->makeTestData(); + $this->withoutMiddleware( + ThrottleRequests::class + ); + + } public function testActivityGet() diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php index 5a2f51c530c3..a087dac97d66 100644 --- a/tests/Feature/ClientTest.php +++ b/tests/Feature/ClientTest.php @@ -516,7 +516,8 @@ class ClientTest extends TestCase $response->assertStatus(200); $arr = $response->json(); -info($arr); + + $this->client = Client::find($this->decodePrimaryKey($arr['data']['id'])); $this->client->fresh(); diff --git a/tests/Feature/CompanyGatewayResolutionTest.php b/tests/Feature/CompanyGatewayResolutionTest.php index fd2cc0db0f1d..8de3d96a7f9c 100644 --- a/tests/Feature/CompanyGatewayResolutionTest.php +++ b/tests/Feature/CompanyGatewayResolutionTest.php @@ -172,6 +172,6 @@ class CompanyGatewayResolutionTest extends TestCase $this->assertEquals(4, count($this->cg->driver($this->client)->gatewayTypes())); - info(print_r($this->client->getPaymentMethods(10),1)); + // info(print_r($this->client->getPaymentMethods(10),1)); } } diff --git a/tests/Feature/PaymentTermsApiTest.php b/tests/Feature/PaymentTermsApiTest.php index 1662d71c945c..92b0e3577b47 100644 --- a/tests/Feature/PaymentTermsApiTest.php +++ b/tests/Feature/PaymentTermsApiTest.php @@ -29,6 +29,7 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Session; use Tests\MockAccountData; use Tests\TestCase; +use Illuminate\Routing\Middleware\ThrottleRequests; /** * @test @@ -51,6 +52,11 @@ class PaymentTermsApiTest extends TestCase $this->faker = \Faker\Factory::create(); Model::reguard(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + } public function testPaymentTermsGet() diff --git a/tests/Feature/PaymentTest.php b/tests/Feature/PaymentTest.php index 48419f546de9..b9f36304697a 100644 --- a/tests/Feature/PaymentTest.php +++ b/tests/Feature/PaymentTest.php @@ -1295,7 +1295,7 @@ $contact = ClientContact::factory()->create([ $payment = Payment::find($this->decodePrimaryKey($payment_id))->first(); - info($payment); + // info($payment); $this->assertNotNull($payment); $this->assertNotNull($payment->invoices()); diff --git a/tests/Feature/PingTest.php b/tests/Feature/PingTest.php index ae808d5d7d01..fa795f716d30 100644 --- a/tests/Feature/PingTest.php +++ b/tests/Feature/PingTest.php @@ -13,6 +13,7 @@ namespace Tests\Feature; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\MockAccountData; use Tests\TestCase; +use Illuminate\Routing\Middleware\ThrottleRequests; /** * @test @@ -28,6 +29,11 @@ class PingTest extends TestCase parent::setUp(); $this->makeTestData(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + } public function testPingEndPoint()