mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-08-30 23:20:34 -04:00
Update Purchase Order Scaffold
This commit is contained in:
commit
4a2ecdb6a7
@ -1 +1 @@
|
||||
5.3.93
|
||||
5.3.96
|
@ -103,7 +103,7 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
if (config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && !config('ninja.is_docker')) {
|
||||
|
||||
$schedule->command('queue:work database --stop-when-empty')->everyMinute()->withoutOverlapping();
|
||||
$schedule->command('queue:work database --stop-when-empty --memory=256')->everyMinute()->withoutOverlapping();
|
||||
|
||||
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping();
|
||||
|
||||
|
@ -98,11 +98,11 @@ class CompanySettings extends BaseSettings
|
||||
public $expense_number_pattern = ''; //@implemented
|
||||
public $expense_number_counter = 1; //@implemented
|
||||
|
||||
public $recurring_expense_number_pattern = '';
|
||||
public $recurring_expense_number_counter = 1;
|
||||
public $recurring_expense_number_pattern = '';
|
||||
public $recurring_expense_number_counter = 1;
|
||||
|
||||
public $recurring_quote_number_pattern = '';
|
||||
public $recurring_quote_number_counter = 1;
|
||||
public $recurring_quote_number_pattern = '';
|
||||
public $recurring_quote_number_counter = 1;
|
||||
|
||||
public $vendor_number_pattern = ''; //@implemented
|
||||
public $vendor_number_counter = 1; //@implemented
|
||||
@ -279,6 +279,9 @@ class CompanySettings extends BaseSettings
|
||||
public $email_from_name = '';
|
||||
public $auto_archive_invoice_cancelled = false;
|
||||
|
||||
|
||||
public $purchase_order_number_counter = 1; //TODO
|
||||
|
||||
public static $casts = [
|
||||
'purchase_order_number_pattern' => 'purchase_order_number_pattern',
|
||||
'purchase_order_number_counter' => 'int',
|
||||
@ -479,6 +482,7 @@ class CompanySettings extends BaseSettings
|
||||
'portal_custom_footer' => 'string',
|
||||
'portal_custom_js' => 'string',
|
||||
'client_portal_enable_uploads' => 'bool',
|
||||
'purchase_order_number_counter' => 'integer',
|
||||
];
|
||||
|
||||
public static $free_plan_casts = [
|
||||
|
38
app/Events/PurchaseOrder/PurchaseOrderWasMarkedSent.php
Normal file
38
app/Events/PurchaseOrder/PurchaseOrderWasMarkedSent.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Events\PurchaseOrder;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\PurchaseOrder;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class PurchaseOrderWasMarkedSent.
|
||||
*/
|
||||
class PurchaseOrderWasMarkedSent
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* @var \App\Models\PurchaseOrder
|
||||
*/
|
||||
public $purchase_order;
|
||||
|
||||
public $company;
|
||||
|
||||
public $event_vars;
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param PurchaseOrder $purchase_order
|
||||
* @param Company $company
|
||||
* @param array $event_vars
|
||||
*/
|
||||
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
$this->company = $company;
|
||||
$this->event_vars = $event_vars;
|
||||
}
|
||||
}
|
@ -78,7 +78,7 @@ class QuoteItemExport extends BaseExport
|
||||
'tax_name2' => 'item.tax_name2',
|
||||
'tax_name3' => 'item.tax_name3',
|
||||
'line_total' => 'item.line_total',
|
||||
'gross_line_total' => 'item.gross_line_total',
|
||||
// 'gross_line_total' => 'item.gross_line_total',
|
||||
'custom_value1' => 'item.custom_value1',
|
||||
'custom_value2' => 'item.custom_value2',
|
||||
'custom_value3' => 'item.custom_value3',
|
||||
|
31
app/Factory/PurchaseOrderInvitationFactory.php
Normal file
31
app/Factory/PurchaseOrderInvitationFactory.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PurchaseOrderInvitationFactory
|
||||
{
|
||||
public static function create(int $company_id, int $user_id) :PurchaseOrderInvitation
|
||||
{
|
||||
$ci = new PurchaseOrderInvitation();
|
||||
$ci->company_id = $company_id;
|
||||
$ci->user_id = $user_id;
|
||||
$ci->vendor_contact_id = null;
|
||||
$ci->purchase_order_id = null;
|
||||
$ci->key = Str::random(config('ninja.key_length'));
|
||||
$ci->transaction_reference = null;
|
||||
$ci->message_id = null;
|
||||
$ci->email_error = '';
|
||||
$ci->signature_base64 = '';
|
||||
$ci->signature_date = null;
|
||||
$ci->sent_date = null;
|
||||
$ci->viewed_date = null;
|
||||
$ci->opened_date = null;
|
||||
|
||||
return $ci;
|
||||
}
|
||||
}
|
@ -48,6 +48,7 @@ class RecurringInvoiceFactory
|
||||
$invoice->frequency_id = RecurringInvoice::FREQUENCY_MONTHLY;
|
||||
$invoice->last_sent_date = null;
|
||||
$invoice->next_send_date = null;
|
||||
$invoice->next_send_date_client = null;
|
||||
$invoice->remaining_cycles = -1;
|
||||
$invoice->paid_to_date = 0;
|
||||
$invoice->auto_bill_enabled = false;
|
||||
|
@ -22,6 +22,7 @@ use App\Models\ClientContact;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Services\ClientPortal\InstantPayment;
|
||||
use App\Utils\CurlUtils;
|
||||
@ -41,7 +42,7 @@ class InvitationController extends Controller
|
||||
use MakesDates;
|
||||
|
||||
public function router(string $entity, string $invitation_key)
|
||||
{
|
||||
{
|
||||
Auth::logout();
|
||||
|
||||
return $this->genericRouter($entity, $invitation_key);
|
||||
@ -166,7 +167,7 @@ class InvitationController extends Controller
|
||||
{
|
||||
|
||||
set_time_limit(45);
|
||||
|
||||
|
||||
if(Ninja::isHosted())
|
||||
return $this->returnRawPdf($entity, $invitation_key);
|
||||
|
||||
@ -202,7 +203,7 @@ class InvitationController extends Controller
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo $file;
|
||||
}, $file_name, $headers);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function routerForIframe(string $entity, string $client_hash, string $invitation_key)
|
||||
@ -228,14 +229,14 @@ class InvitationController extends Controller
|
||||
$invitation = InvoiceInvitation::where('key', $invitation_key)
|
||||
->with('contact.client')
|
||||
->firstOrFail();
|
||||
|
||||
|
||||
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
if($invoice->partial > 0)
|
||||
$amount = round($invoice->partial, (int)$invoice->client->currency()->precision);
|
||||
else
|
||||
else
|
||||
$amount = round($invoice->balance, (int)$invoice->client->currency()->precision);
|
||||
|
||||
$gateways = $invitation->contact->client->service()->getPaymentMethods($amount);
|
||||
@ -279,6 +280,10 @@ class InvitationController extends Controller
|
||||
$invite = CreditInvitation::withTrashed()->where('key', $invitation_key)->first();
|
||||
$invite->contact->send_email = false;
|
||||
$invite->contact->save();
|
||||
}elseif($entity == 'purchase_order'){
|
||||
$invite = PurchaseOrderInvitation::withTrashed()->where('key', $invitation_key)->first();
|
||||
$invite->contact->send_email = false;
|
||||
$invite->contact->save();
|
||||
}
|
||||
else
|
||||
return abort(404);
|
||||
|
@ -204,9 +204,9 @@ class RecurringInvoiceController extends BaseController
|
||||
{
|
||||
$recurring_invoice = $this->recurring_invoice_repo->save($request->all(), RecurringInvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||
|
||||
$offset = $recurring_invoice->client->timezone_offset();
|
||||
$recurring_invoice->next_send_date = Carbon::parse($recurring_invoice->next_send_date)->startOfDay()->addSeconds($offset);
|
||||
$recurring_invoice->saveQuietly();
|
||||
// $offset = $recurring_invoice->client->timezone_offset();
|
||||
// $recurring_invoice->next_send_date = Carbon::parse($recurring_invoice->next_send_date)->startOfDay()->addSeconds($offset);
|
||||
// $recurring_invoice->saveQuietly();
|
||||
|
||||
$recurring_invoice->service()
|
||||
->triggeredActions($request)
|
||||
|
@ -16,6 +16,7 @@ use App\Models\Client;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\AppSetup;
|
||||
use App\Utils\Traits\ClientGroupSettingsSaver;
|
||||
use Beganovich\Snappdf\Snappdf;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@ -134,21 +135,30 @@ class SelfUpdateController extends BaseController
|
||||
|
||||
nlog("Extracting zip");
|
||||
|
||||
// $zipFile = new \PhpZip\ZipFile();
|
||||
// try{
|
||||
// $s = new Snappdf;
|
||||
// $s->getChromiumPath();
|
||||
// chmod($this->generatePlatformExecutable($s->getChromiumPath()), 0755);
|
||||
// }
|
||||
// catch(\Exception $e){
|
||||
// nlog("I could not set the file permissions for chrome");
|
||||
// }
|
||||
|
||||
// $zipFile->openFile($file);
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
|
||||
// $zipFile->extractTo(base_path());
|
||||
$zipFile->openFile($file);
|
||||
|
||||
// $zipFile->close();
|
||||
$zipFile->extractTo(base_path());
|
||||
|
||||
$zip = new \ZipArchive;
|
||||
$zipFile->close();
|
||||
|
||||
// $zip = new \ZipArchive;
|
||||
|
||||
$res = $zip->open($file);
|
||||
if ($res === TRUE) {
|
||||
$zip->extractTo(base_path());
|
||||
$zip->close();
|
||||
}
|
||||
// $res = $zip->open($file);
|
||||
// if ($res === TRUE) {
|
||||
// $zip->extractTo(base_path());
|
||||
// $zip->close();
|
||||
// }
|
||||
|
||||
nlog("Finished extracting files");
|
||||
|
||||
|
@ -55,6 +55,10 @@ class StoreRecurringExpenseRequest extends Request
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) {
|
||||
$input['next_send_date_client'] = $input['next_send_date'];
|
||||
}
|
||||
|
||||
if (array_key_exists('category_id', $input) && is_string($input['category_id'])) {
|
||||
$input['category_id'] = $this->decodePrimaryKey($input['category_id']);
|
||||
}
|
||||
|
@ -66,6 +66,10 @@ class UpdateRecurringExpenseRequest extends Request
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) {
|
||||
$input['next_send_date_client'] = $input['next_send_date'];
|
||||
}
|
||||
|
||||
if (array_key_exists('category_id', $input) && is_string($input['category_id'])) {
|
||||
$input['category_id'] = $this->decodePrimaryKey($input['category_id']);
|
||||
}
|
||||
|
@ -67,6 +67,10 @@ class StoreRecurringInvoiceRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) {
|
||||
$input['next_send_date_client'] = $input['next_send_date'];
|
||||
}
|
||||
|
||||
if (array_key_exists('design_id', $input) && is_string($input['design_id'])) {
|
||||
$input['design_id'] = $this->decodePrimaryKey($input['design_id']);
|
||||
}
|
||||
|
@ -61,6 +61,10 @@ class UpdateRecurringInvoiceRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) {
|
||||
$input['next_send_date_client'] = $input['next_send_date'];
|
||||
}
|
||||
|
||||
if (array_key_exists('design_id', $input) && is_string($input['design_id'])) {
|
||||
$input['design_id'] = $this->decodePrimaryKey($input['design_id']);
|
||||
}
|
||||
|
@ -562,7 +562,7 @@ class BaseImport
|
||||
}
|
||||
}
|
||||
|
||||
protected function finalizeImport()
|
||||
public function finalizeImport()
|
||||
{
|
||||
$data = [
|
||||
'errors' => $this->error_array,
|
||||
|
@ -60,10 +60,7 @@ class Csv extends BaseImport implements ImportInterface
|
||||
) {
|
||||
$this->{$entity}();
|
||||
}
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
|
||||
}
|
||||
|
||||
public function client()
|
||||
|
@ -41,7 +41,7 @@ class Freshbooks extends BaseImport
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
// $this->finalizeImport();
|
||||
}
|
||||
|
||||
public function client()
|
||||
|
@ -39,7 +39,7 @@ class Invoice2Go extends BaseImport
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
// $this->finalizeImport();
|
||||
}
|
||||
|
||||
|
||||
|
@ -38,7 +38,7 @@ class Invoicely extends BaseImport
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
// $this->finalizeImport();
|
||||
}
|
||||
|
||||
public function client()
|
||||
|
@ -54,7 +54,7 @@ class Wave extends BaseImport implements ImportInterface
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
// $this->finalizeImport();
|
||||
}
|
||||
|
||||
public function client()
|
||||
|
@ -40,7 +40,7 @@ class Zoho extends BaseImport
|
||||
|
||||
//collate any errors
|
||||
|
||||
$this->finalizeImport();
|
||||
// $this->finalizeImport();
|
||||
}
|
||||
|
||||
public function client()
|
||||
|
@ -94,6 +94,8 @@ class RecurringExpensesCron
|
||||
$expense->save();
|
||||
|
||||
$recurring_expense->next_send_date = $recurring_expense->nextSendDate();
|
||||
$recurring_expense->next_send_date_client = $recurring_expense->next_send_date;
|
||||
|
||||
$recurring_expense->remaining_cycles = $recurring_expense->remainingCycles();
|
||||
$recurring_expense->save();
|
||||
}
|
||||
|
@ -79,6 +79,8 @@ class CSVIngest implements ShouldQueue {
|
||||
|
||||
}
|
||||
|
||||
$engine->finalizeImport();
|
||||
|
||||
$this->checkContacts();
|
||||
}
|
||||
|
||||
|
95
app/Jobs/Inventory/AdjustProductInventory.php
Normal file
95
app/Jobs/Inventory/AdjustProductInventory.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Inventory;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Product;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
//todo - ensure we are MultiDB Aware in dispatched jobs
|
||||
|
||||
class AdjustProductInventory implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public Company $company;
|
||||
|
||||
public Invoice $invoice;
|
||||
|
||||
public array $old_invoice;
|
||||
|
||||
public function __construct(Company $company, Invoice $invoice, array $old_invoice = [])
|
||||
{
|
||||
|
||||
$this->company = $company;
|
||||
$this->invoice = $invoice;
|
||||
$this->old_invoice = $old_invoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return false
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
if(count($this->old_invoice) > 0)
|
||||
return $this->existingInventoryAdjustment();
|
||||
|
||||
return $this->newInventoryAdjustment();
|
||||
|
||||
}
|
||||
|
||||
private function newInventoryAdjustment()
|
||||
{
|
||||
|
||||
$line_items = $this->invoice->line_items;
|
||||
|
||||
foreach($line_items as $item)
|
||||
{
|
||||
|
||||
$p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->where('in_stock_quantity', '>', 0)->first();
|
||||
$p->in_stock_quantity -= $item->quantity;
|
||||
$p->save();
|
||||
|
||||
//check threshols and notify user
|
||||
|
||||
if($p->stock_notification_threshold && $p->in_stock_quantity <= $p->stock_notification_threshold)
|
||||
$this->notifyStockLevels($p, 'product');
|
||||
elseif($this->company->stock_notification_threshold && $p->in_stock_quantity <= $this->company->stock_notification_threshold){
|
||||
$this->notifyStocklevels($p, 'company');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function existingInventoryAdjustment()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private function notifyStocklevels(Product $product, string $notification_level)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -105,6 +105,7 @@ class SendRecurring implements ShouldQueue
|
||||
nlog("updating recurring invoice dates");
|
||||
/* Set next date here to prevent a recurring loop forming */
|
||||
$this->recurring_invoice->next_send_date = $this->recurring_invoice->nextSendDate();
|
||||
$this->recurring_invoice->next_send_date_client = $this->recurring_invoice->nextSendDateClient();
|
||||
$this->recurring_invoice->remaining_cycles = $this->recurring_invoice->remainingCycles();
|
||||
$this->recurring_invoice->last_sent_date = now();
|
||||
|
||||
|
@ -666,7 +666,7 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
|
||||
$offset -= $timezone->utc_offset;
|
||||
$offset += ($entity_send_time * 3600);
|
||||
|
||||
|
||||
return $offset;
|
||||
}
|
||||
|
||||
|
@ -103,6 +103,10 @@ class Company extends BaseModel
|
||||
'markdown_email_enabled',
|
||||
'stop_on_unpaid_recurring',
|
||||
'use_quote_terms_on_conversion',
|
||||
'enable_applying_payments',
|
||||
'track_inventory',
|
||||
'inventory_notification_threshold',
|
||||
'stock_notification'
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
|
@ -36,6 +36,9 @@ class Product extends BaseModel
|
||||
'tax_rate1',
|
||||
'tax_rate2',
|
||||
'tax_rate3',
|
||||
'in_stock_quantity',
|
||||
'stock_notification_threshold',
|
||||
'stock_notification',
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
|
@ -12,10 +12,12 @@
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Helpers\Invoice\InvoiceSumInclusive;
|
||||
use App\Jobs\Entity\CreateEntityPdf;
|
||||
use App\Services\PurchaseOrder\PurchaseOrderService;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class PurchaseOrder extends BaseModel
|
||||
{
|
||||
@ -94,9 +96,8 @@ class PurchaseOrder extends BaseModel
|
||||
|
||||
const STATUS_DRAFT = 1;
|
||||
const STATUS_SENT = 2;
|
||||
const STATUS_APPROVED = 3;
|
||||
const STATUS_CONVERTED = 4;
|
||||
const STATUS_EXPIRED = -1;
|
||||
const STATUS_PARTIAL = 3;
|
||||
const STATUS_APPLIED = 4;
|
||||
|
||||
public function assigned_user()
|
||||
{
|
||||
@ -132,10 +133,52 @@ class PurchaseOrder extends BaseModel
|
||||
{
|
||||
return $this->belongsTo(Client::class)->withTrashed();
|
||||
}
|
||||
public function markInvitationsSent()
|
||||
{
|
||||
$this->invitations->each(function ($invitation) {
|
||||
if (! isset($invitation->sent_date)) {
|
||||
$invitation->sent_date = Carbon::now();
|
||||
$invitation->save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function pdf_file_path($invitation = null, string $type = 'path', bool $portal = false)
|
||||
{
|
||||
if (! $invitation) {
|
||||
|
||||
if($this->invitations()->exists())
|
||||
$invitation = $this->invitations()->first();
|
||||
else{
|
||||
$this->service()->createInvitations();
|
||||
$invitation = $this->invitations()->first();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(!$invitation)
|
||||
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
|
||||
|
||||
$file_path = $this->client->credit_filepath($invitation).$this->numberFormatter().'.pdf';
|
||||
|
||||
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
elseif(Ninja::isHosted() && $portal){
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation,config('filesystems.default'));
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
if(Storage::disk('public')->exists($file_path))
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation);
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
}
|
||||
|
||||
public function invitations()
|
||||
{
|
||||
return $this->hasMany(CreditInvitation::class);
|
||||
return $this->hasMany(PurchaseOrderInvitation::class);
|
||||
}
|
||||
|
||||
public function project()
|
||||
@ -168,17 +211,4 @@ class PurchaseOrder extends BaseModel
|
||||
return $this->morphMany(Document::class, 'documentable');
|
||||
}
|
||||
|
||||
public function calc()
|
||||
{
|
||||
$credit_calc = null;
|
||||
|
||||
if ($this->uses_inclusive_taxes) {
|
||||
$credit_calc = new InvoiceSumInclusive($this);
|
||||
} else {
|
||||
$credit_calc = new InvoiceSum($this);
|
||||
}
|
||||
|
||||
return $credit_calc->build();
|
||||
}
|
||||
|
||||
}
|
||||
|
81
app/Models/PurchaseOrderInvitation.php
Normal file
81
app/Models/PurchaseOrderInvitation.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
use App\Utils\Traits\Inviteable;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class PurchaseOrderInvitation extends BaseModel
|
||||
{
|
||||
use MakesDates;
|
||||
use SoftDeletes;
|
||||
use Inviteable;
|
||||
|
||||
protected $fillable = [
|
||||
'id',
|
||||
'vendor_contact_id',
|
||||
];
|
||||
|
||||
protected $with = [
|
||||
'company',
|
||||
'contact',
|
||||
];
|
||||
protected $touches = ['purchase_order'];
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
}
|
||||
|
||||
public function entityType()
|
||||
{
|
||||
return PurchaseOrder::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function purchase_order()
|
||||
{
|
||||
return $this->belongsTo(PurchaseOrder::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function contact()
|
||||
{
|
||||
return $this->belongsTo(VendorContact::class, 'vendor_contact_id', 'id')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class)->withTrashed();
|
||||
}
|
||||
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function markViewed()
|
||||
{
|
||||
$this->viewed_date = Carbon::now();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -63,6 +63,7 @@ class RecurringExpense extends BaseModel
|
||||
'last_sent_date',
|
||||
'next_send_date',
|
||||
'remaining_cycles',
|
||||
'next_send_date_client',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -153,6 +154,43 @@ class RecurringExpense extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function nextSendDateClient() :?Carbon
|
||||
{
|
||||
if (!$this->next_send_date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ($this->frequency_id) {
|
||||
case RecurringInvoice::FREQUENCY_DAILY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addDay();
|
||||
case RecurringInvoice::FREQUENCY_WEEKLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeek();
|
||||
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(2);
|
||||
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(4);
|
||||
case RecurringInvoice::FREQUENCY_MONTHLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthNoOverflow();
|
||||
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(2);
|
||||
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(3);
|
||||
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(4);
|
||||
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(6);
|
||||
case RecurringInvoice::FREQUENCY_ANNUALLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYear();
|
||||
case RecurringInvoice::FREQUENCY_TWO_YEARS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(2);
|
||||
case RecurringInvoice::FREQUENCY_THREE_YEARS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(3);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function remainingCycles() : int
|
||||
{
|
||||
if ($this->remaining_cycles == 0) {
|
||||
|
@ -108,6 +108,7 @@ class RecurringInvoice extends BaseModel
|
||||
'assigned_user_id',
|
||||
'exchange_rate',
|
||||
'vendor_id',
|
||||
'next_send_date_client',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -224,7 +225,7 @@ class RecurringInvoice extends BaseModel
|
||||
|
||||
public function nextSendDate() :?Carbon
|
||||
{
|
||||
if (!$this->next_send_date) {
|
||||
if (!$this->next_send_date_client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -236,49 +237,93 @@ class RecurringInvoice extends BaseModel
|
||||
|
||||
/* Lets set the next send date to now so we increment from today, rather than in the past*/
|
||||
if(Carbon::parse($this->next_send_date)->lt(now()->subDays(3)))
|
||||
$this->next_send_date = now()->format('Y-m-d');
|
||||
$this->next_send_date_client = now()->format('Y-m-d');
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
As we are firing at UTC+0 if our offset is negative it is technically firing the day before so we always need
|
||||
to add ON a day - a day = 86400 seconds
|
||||
*/
|
||||
if($offset < 0)
|
||||
$offset += 86400;
|
||||
// if($offset < 0)
|
||||
// $offset += 86400;
|
||||
|
||||
switch ($this->frequency_id) {
|
||||
case self::FREQUENCY_DAILY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addDay()->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addDay()->addSeconds($offset);
|
||||
case self::FREQUENCY_WEEKLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeek()->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeek()->addSeconds($offset);
|
||||
case self::FREQUENCY_TWO_WEEKS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(2)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(2)->addSeconds($offset);
|
||||
case self::FREQUENCY_FOUR_WEEKS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(4)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(4)->addSeconds($offset);
|
||||
case self::FREQUENCY_MONTHLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
|
||||
case self::FREQUENCY_TWO_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset);
|
||||
case self::FREQUENCY_THREE_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
|
||||
case self::FREQUENCY_FOUR_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset);
|
||||
case self::FREQUENCY_SIX_MONTHS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(6)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(6)->addSeconds($offset);
|
||||
case self::FREQUENCY_ANNUALLY:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYear()->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYear()->addSeconds($offset);
|
||||
case self::FREQUENCY_TWO_YEARS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(2)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(2)->addSeconds($offset);
|
||||
case self::FREQUENCY_THREE_YEARS:
|
||||
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(3)->addSeconds($offset);
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(3)->addSeconds($offset);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function nextSendDateClient() :?Carbon
|
||||
{
|
||||
if (!$this->next_send_date_client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* If this setting is enabled, the recurring invoice may be set in the past */
|
||||
|
||||
if($this->company->stop_on_unpaid_recurring) {
|
||||
|
||||
/* Lets set the next send date to now so we increment from today, rather than in the past*/
|
||||
if(Carbon::parse($this->next_send_date)->lt(now()->subDays(3)))
|
||||
$this->next_send_date_client = now()->format('Y-m-d');
|
||||
|
||||
}
|
||||
|
||||
switch ($this->frequency_id) {
|
||||
case self::FREQUENCY_DAILY:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addDay();
|
||||
case self::FREQUENCY_WEEKLY:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeek();
|
||||
case self::FREQUENCY_TWO_WEEKS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(2);
|
||||
case self::FREQUENCY_FOUR_WEEKS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(4);
|
||||
case self::FREQUENCY_MONTHLY:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthNoOverflow();
|
||||
case self::FREQUENCY_TWO_MONTHS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(2);
|
||||
case self::FREQUENCY_THREE_MONTHS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(3);
|
||||
case self::FREQUENCY_FOUR_MONTHS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(4);
|
||||
case self::FREQUENCY_SIX_MONTHS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(6);
|
||||
case self::FREQUENCY_ANNUALLY:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYear();
|
||||
case self::FREQUENCY_TWO_YEARS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(2);
|
||||
case self::FREQUENCY_THREE_YEARS:
|
||||
return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(3);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function nextDateByFrequency($date)
|
||||
{
|
||||
$offset = $this->client->timezone_offset();
|
||||
@ -463,11 +508,11 @@ class RecurringInvoice extends BaseModel
|
||||
|
||||
$data = [];
|
||||
|
||||
if (!Carbon::parse($this->next_send_date)) {
|
||||
if (!Carbon::parse($this->next_send_date_client)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$next_send_date = Carbon::parse($this->next_send_date)->copy();
|
||||
$next_send_date = Carbon::parse($this->next_send_date_client)->copy();
|
||||
|
||||
for ($x=0; $x<$iterations; $x++) {
|
||||
// we don't add the days... we calc the day of the month!!
|
||||
|
@ -136,4 +136,8 @@ class VendorContact extends Authenticatable implements HasLocalePreference
|
||||
->withTrashed()
|
||||
->where('id', $this->decodePrimaryKey($value))->firstOrFail();
|
||||
}
|
||||
public function purchase_order_invitations(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(PurchaseOrderInvitation::class);
|
||||
}
|
||||
}
|
||||
|
@ -12,11 +12,14 @@
|
||||
|
||||
namespace App\PaymentDrivers\Authorize;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\PaymentDrivers\AuthorizePaymentDriver;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use net\authorize\api\contract\v1\CreateTransactionRequest;
|
||||
use net\authorize\api\contract\v1\CustomerProfilePaymentType;
|
||||
use net\authorize\api\contract\v1\OrderType;
|
||||
use net\authorize\api\contract\v1\PaymentProfileType;
|
||||
use net\authorize\api\contract\v1\ExtendedAmountType;
|
||||
use net\authorize\api\contract\v1\TransactionRequestType;
|
||||
use net\authorize\api\controller\CreateTransactionController;
|
||||
|
||||
@ -25,6 +28,8 @@ use net\authorize\api\controller\CreateTransactionController;
|
||||
*/
|
||||
class ChargePaymentProfile
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public function __construct(AuthorizePaymentDriver $authorize)
|
||||
{
|
||||
$this->authorize = $authorize;
|
||||
@ -44,19 +49,40 @@ class ChargePaymentProfile
|
||||
$profileToCharge->setPaymentProfile($paymentProfile);
|
||||
|
||||
$invoice_numbers = '';
|
||||
$taxAmount = 0;
|
||||
$invoiceTotal = 0;
|
||||
$invoiceTaxes = 0;
|
||||
|
||||
if($this->authorize->payment_hash->data)
|
||||
$invoice_numbers = collect($this->authorize->payment_hash->data->invoices)->pluck('invoice_number')->implode(',');
|
||||
if($this->authorize->payment_hash->data) {
|
||||
$invoice_numbers = collect($this->authorize->payment_hash->data->invoices)->pluck('invoice_number')->implode(",");
|
||||
$invObj = Invoice::whereIn('id', $this->transformKeys(array_column($this->authorize->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
|
||||
|
||||
$invoiceTotal = round($invObj->pluck('amount')->sum(), 2);
|
||||
$invoiceTaxes = round($invObj->pluck('total_taxes')->sum(), 2);
|
||||
|
||||
if ($invoiceTotal != $amount) {
|
||||
$taxRatio = $amount/$invoiceTotal;
|
||||
$taxAmount = round($invoiceTaxes*$taxRatio, 2);
|
||||
} else {
|
||||
$taxAmount = $invoiceTaxes;
|
||||
}
|
||||
}
|
||||
|
||||
$description = "Invoices: {$invoice_numbers} for {$amount} for client {$this->authorize->client->present()->name()}";
|
||||
|
||||
$order = new OrderType();
|
||||
$order->setInvoiceNumber(substr($invoice_numbers,0,19));
|
||||
$order->setDescription(substr($description,0,255));
|
||||
|
||||
$tax = new ExtendedAmountType();
|
||||
$tax->setName('tax');
|
||||
$tax->setAmount($taxAmount);
|
||||
|
||||
$transactionRequestType = new TransactionRequestType();
|
||||
$transactionRequestType->setTransactionType('authCaptureTransaction');
|
||||
$transactionRequestType->setAmount($amount);
|
||||
$transactionRequestType->setTax($tax);
|
||||
$transactionRequestType->setTaxExempt(empty($taxAmount));
|
||||
$transactionRequestType->setOrder($order);
|
||||
$transactionRequestType->setProfile($profileToCharge);
|
||||
$transactionRequestType->setCurrencyCode($this->authorize->client->currency()->code);
|
||||
|
@ -245,7 +245,7 @@ class GoCardlessPaymentDriver extends BaseDriver
|
||||
sleep(1);
|
||||
|
||||
foreach ($request->events as $event) {
|
||||
if ($event['action'] === 'confirmed' || $event['action'] === 'paid_out') {
|
||||
if ($event['action'] === 'confirmed' || $event['action'] === 'paid_out' || $event['action'] === 'paid') {
|
||||
|
||||
nlog("Searching for transaction reference");
|
||||
|
||||
|
@ -60,6 +60,7 @@ use App\Events\Payment\PaymentWasRefunded;
|
||||
use App\Events\Payment\PaymentWasRestored;
|
||||
use App\Events\Payment\PaymentWasUpdated;
|
||||
use App\Events\Payment\PaymentWasVoided;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasMarkedSent;
|
||||
use App\Events\Quote\QuoteWasApproved;
|
||||
use App\Events\Quote\QuoteWasArchived;
|
||||
use App\Events\Quote\QuoteWasCreated;
|
||||
@ -558,6 +559,8 @@ class EventServiceProvider extends ServiceProvider
|
||||
VendorWasUpdated::class => [
|
||||
VendorUpdatedActivity::class,
|
||||
],
|
||||
PurchaseOrderWasMarkedSent::class => [
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
|
@ -11,10 +11,9 @@
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Factory\PurchaseOrderFactory;
|
||||
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\VendorContact;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PurchaseOrderRepository extends BaseRepository
|
||||
@ -27,144 +26,14 @@ class PurchaseOrderRepository extends BaseRepository
|
||||
|
||||
public function save(array $data, PurchaseOrder $purchase_order) : ?PurchaseOrder
|
||||
{
|
||||
|
||||
if(array_key_exists('vendor_id', $data))
|
||||
$purchase_order->vendor_id = $data['vendor_id'];
|
||||
|
||||
$vendor = Vendor::where('id', $purchase_order->vendor_id)->withTrashed()->firstOrFail();
|
||||
|
||||
$state = [];
|
||||
|
||||
$resource = class_basename($purchase_order); //ie Invoice
|
||||
|
||||
if (! $purchase_order->id) {
|
||||
$company_defaults = $vendor->setCompanyDefaults($data, lcfirst($resource));
|
||||
$purchase_order->uses_inclusive_taxes = $vendor->getSetting('inclusive_taxes');
|
||||
$data = array_merge($company_defaults, $data);
|
||||
}
|
||||
|
||||
$tmp_data = $data; //preserves the $data array
|
||||
|
||||
/* We need to unset some variable as we sometimes unguard the model */
|
||||
if (isset($tmp_data['invitations']))
|
||||
unset($tmp_data['invitations']);
|
||||
|
||||
if (isset($tmp_data['vendor_contacts']))
|
||||
unset($tmp_data['vendor_contacts']);
|
||||
|
||||
$purchase_order->fill($tmp_data);
|
||||
|
||||
$purchase_order->custom_surcharge_tax1 = $vendor->company->custom_surcharge_taxes1;
|
||||
$purchase_order->custom_surcharge_tax2 = $vendor->company->custom_surcharge_taxes2;
|
||||
$purchase_order->custom_surcharge_tax3 = $vendor->company->custom_surcharge_taxes3;
|
||||
$purchase_order->custom_surcharge_tax4 = $vendor->company->custom_surcharge_taxes4;
|
||||
|
||||
if(!$purchase_order->id)
|
||||
$this->new_model = true;
|
||||
|
||||
$purchase_order->saveQuietly();
|
||||
|
||||
/* Save any documents */
|
||||
if (array_key_exists('documents', $data))
|
||||
$this->saveDocuments($data['documents'], $purchase_order);
|
||||
|
||||
if (array_key_exists('file', $data))
|
||||
$this->saveDocuments($data['file'], $purchase_order);
|
||||
|
||||
/* If invitations are present we need to filter existing invitations with the new ones */
|
||||
if (isset($data['invitations'])) {
|
||||
$invitations = collect($data['invitations']);
|
||||
|
||||
/* Get array of Keys which have been removed from the invitations array and soft delete each invitation */
|
||||
$purchase_order->invitations->pluck('key')->diff($invitations->pluck('key'))->each(function ($invitation) {
|
||||
$invitation = PurchaseOrderInvitation::where('key', $invitation)->first();
|
||||
|
||||
if ($invitation)
|
||||
$invitation->delete();
|
||||
|
||||
});
|
||||
|
||||
foreach ($data['invitations'] as $invitation) {
|
||||
|
||||
//if no invitations are present - create one.
|
||||
if (! $this->getInvitation($invitation, $resource)) {
|
||||
|
||||
if (isset($invitation['id']))
|
||||
unset($invitation['id']);
|
||||
|
||||
//make sure we are creating an invite for a contact who belongs to the client only!
|
||||
$contact = VendorContact::find($invitation['vendor_contact_id']);
|
||||
|
||||
if ($contact && $purchase_order->client_id == $contact->client_id) {
|
||||
|
||||
$new_invitation = PurchaseOrderInvitation::withTrashed()
|
||||
->where('vendor_contact_id', $contact->id)
|
||||
->where('purchase_order_id', $purchase_order->id)
|
||||
->first();
|
||||
|
||||
if ($new_invitation && $new_invitation->trashed()) {
|
||||
|
||||
$new_invitation->restore();
|
||||
|
||||
} else {
|
||||
|
||||
$new_invitation = PurchaseOrderFactory::create($purchase_order->company_id, $purchase_order->user_id);
|
||||
$new_invitation->purchase_order_id = $purchase_order->id;
|
||||
$new_invitation->vendor_contact_id = $contact->id;
|
||||
$new_invitation->key = $this->createDbHash($purchase_order->company->db);
|
||||
$new_invitation->save();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If no invitations have been created, this is our fail safe to maintain state*/
|
||||
if ($purchase_order->invitations()->count() == 0)
|
||||
$purchase_order->service()->createInvitations();
|
||||
|
||||
/* Apply entity number */
|
||||
$purchase_order = $purchase_order->service()->applyNumber()->save();
|
||||
|
||||
/* Handle attempts where the deposit is greater than the amount/balance of the invoice */
|
||||
if((int)$purchase_order->balance != 0 && $purchase_order->partial > $purchase_order->amount)
|
||||
$purchase_order->partial = min($purchase_order->amount, $purchase_order->balance);
|
||||
|
||||
$purchase_order = $purchase_order->calc()->getPurchaseOrder();
|
||||
|
||||
if (! $purchase_order->design_id)
|
||||
$purchase_order->design_id = $this->decodePrimaryKey($client->getSetting('credit_design_id'));
|
||||
|
||||
if(array_key_exists('invoice_id', $data) && $data['invoice_id'])
|
||||
$purchase_order->invoice_id = $data['invoice_id'];
|
||||
|
||||
if($this->new_model)
|
||||
event('eloquent.created: App\Models\PurchaseOrder', $purchase_order);
|
||||
else
|
||||
event('eloquent.updated: App\Models\PurchaseOrder', $purchase_order);
|
||||
|
||||
|
||||
$purchase_order->fill($data);
|
||||
$purchase_order->save();
|
||||
|
||||
return $purchase_order->fresh();
|
||||
|
||||
|
||||
// $purchase_order->fill($data);
|
||||
// $purchase_order->save();
|
||||
|
||||
// return $purchase_order;
|
||||
return $purchase_order;
|
||||
}
|
||||
|
||||
public function getInvitation($invitation, $resource)
|
||||
public function getInvitationByKey($key) :?PurchaseOrderInvitation
|
||||
{
|
||||
// if (is_array($invitation) && ! array_key_exists('key', $invitation))
|
||||
// return false;
|
||||
|
||||
// $invitation = PurchaseOrderInvitation::where('key', $invitation['key'])->first();
|
||||
|
||||
return $invitation;
|
||||
return PurchaseOrderInvitation::where('key', $key)->first();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace App\Services\Invoice;
|
||||
|
||||
use App\Events\Invoice\InvoiceWasArchived;
|
||||
use App\Jobs\Entity\CreateEntityPdf;
|
||||
use App\Jobs\Inventory\AdjustProductInventory;
|
||||
use App\Jobs\Invoice\InvoiceWorkflowSettings;
|
||||
use App\Jobs\Util\UnlinkFile;
|
||||
use App\Libraries\Currency\Conversion\CurrencyApi;
|
||||
@ -564,6 +565,14 @@ class InvoiceService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function adjustInventory()
|
||||
{
|
||||
if($this->invoice->company->track_inventory)
|
||||
AdjustProductInventory::dispatch($this->invoice->company, $this->invoice, null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the invoice.
|
||||
* @return Invoice object
|
||||
|
@ -763,7 +763,7 @@ class Design extends BaseDesign
|
||||
} elseif (Str::startsWith($variable, '$custom_surcharge')) {
|
||||
$_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1
|
||||
|
||||
$visible = $this->entity->{$_variable} > 0 || $this->entity->{$_variable} > '0';
|
||||
$visible = $this->entity->{$_variable} != 0 || $this->entity->{$_variable} != '0';
|
||||
|
||||
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
|
||||
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],
|
||||
|
@ -1,18 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Vendor;
|
||||
use App\Services\AbstractService;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use Illuminate\Database\QueryException;
|
||||
@ -21,15 +15,15 @@ class ApplyNumber extends AbstractService
|
||||
{
|
||||
use GeneratesCounter;
|
||||
|
||||
private $vendor;
|
||||
private Client $client;
|
||||
|
||||
private $purchase_order;
|
||||
private PurchaseOrder $purchase_order;
|
||||
|
||||
private $completed = true;
|
||||
private bool $completed = true;
|
||||
|
||||
public function __construct(Vendor $vendor, PurchaseOrder $purchase_order)
|
||||
public function __construct(Client $client, PurchaseOrder $purchase_order)
|
||||
{
|
||||
$this->vendor = $vendor;
|
||||
$this->client = $client;
|
||||
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
@ -40,47 +34,24 @@ class ApplyNumber extends AbstractService
|
||||
return $this->purchase_order;
|
||||
}
|
||||
|
||||
switch ($this->client->getSetting('counter_number_applied')) {
|
||||
case 'when_saved':
|
||||
$this->trySaving();
|
||||
break;
|
||||
case 'when_sent':
|
||||
if ($this->purchase_order->status_id == PurchaseOrder::STATUS_SENT) {
|
||||
$this->trySaving();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$this->trySaving();
|
||||
|
||||
return $this->purchase_order;
|
||||
}
|
||||
|
||||
private function trySaving()
|
||||
{
|
||||
|
||||
$x=1;
|
||||
|
||||
do{
|
||||
|
||||
try{
|
||||
|
||||
$this->purchase_order->number = $this->getNextPurchaseOrderNumber($this->purchase_order);
|
||||
$this->purchase_order->number = $this->getNextPurchaseOrderNumber($this->client, $this->purchase_order);
|
||||
$this->purchase_order->saveQuietly();
|
||||
|
||||
$this->completed = false;
|
||||
|
||||
|
||||
}
|
||||
catch(QueryException $e){
|
||||
|
||||
$x++;
|
||||
|
||||
if($x>10)
|
||||
$this->completed = false;
|
||||
}
|
||||
|
||||
}
|
||||
while($this->completed);
|
||||
|
||||
|
@ -1,20 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
use App\Factory\ClientContactFactory;
|
||||
|
||||
use App\Factory\PurchaseOrderInvitationFactory;
|
||||
use App\Factory\VendorContactFactory;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Services\AbstractService;
|
||||
@ -24,18 +14,24 @@ use Illuminate\Support\Str;
|
||||
class CreateInvitations extends AbstractService
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
private $purchase_order;
|
||||
|
||||
public PurchaseOrder $purchase_order;
|
||||
|
||||
public function __construct(PurchaseOrder $purchase_order)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
|
||||
private function createBlankContact()
|
||||
{
|
||||
$new_contact = PurchaseOrderInvitationFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
$new_contact->client_id = $this->purchase_order->client_id;
|
||||
$new_contact->contact_key = Str::random(40);
|
||||
$new_contact->is_primary = true;
|
||||
$new_contact->save();
|
||||
}
|
||||
public function run()
|
||||
{
|
||||
|
||||
$contacts = $this->purchase_order->vendor->contacts()->where('send_email', true)->get();
|
||||
$contacts = $this->purchase_order->vendor->contacts;
|
||||
|
||||
if($contacts->count() == 0){
|
||||
$this->createBlankContact();
|
||||
@ -45,62 +41,51 @@ class CreateInvitations extends AbstractService
|
||||
}
|
||||
|
||||
$contacts->each(function ($contact) {
|
||||
$invitation = PurchaseOrderInvitation::where('company_id', $this->purchase_order->company_id)
|
||||
->where('vendor_contact_id', $contact->id)
|
||||
->where('purchase_order_id', $this->purchase_order->id)
|
||||
->withTrashed()
|
||||
->first();
|
||||
$invitation = PurchaseOrderInvitation::whereCompanyId($this->purchase_order->company_id)
|
||||
->whereClientContactId($contact->id)
|
||||
->whereCreditId($this->purchase_order->id)
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
if (! $invitation && $contact->send_email) {
|
||||
$ii = PurchaseOrderInvitationFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
if (! $invitation) {
|
||||
$ii = PurchaseOrderInvitation::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
$ii->key = $this->createDbHash($this->purchase_order->company->db);
|
||||
$ii->purchase_order_id = $this->purchase_order->id;
|
||||
$ii->vendor_contact_id = $contact->id;
|
||||
$ii->save();
|
||||
} elseif ($invitation && ! $contact->send_email) {
|
||||
} elseif (! $contact->send_email) {
|
||||
$invitation->delete();
|
||||
}
|
||||
});
|
||||
|
||||
if($this->purchase_order->invitations()->count() == 0) {
|
||||
|
||||
|
||||
if($contacts->count() == 0){
|
||||
$contact = $this->createBlankContact();
|
||||
}
|
||||
else{
|
||||
$contact = $contacts->first();
|
||||
|
||||
$invitation = PurchaseOrderInvitation::where('company_id', $this->purchase_order->company_id)
|
||||
->where('vendor_contact_id', $contact->id)
|
||||
->where('purchase_order_id', $this->purchase_order->id)
|
||||
->withTrashed()
|
||||
->first();
|
||||
$invitation = PurchaseOrder::where('company_id', $this->purchase_order->company_id)
|
||||
->where('vendor_contact_id', $contact->id)
|
||||
->where('purchase_order_id', $this->purchase_order->id)
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
if($invitation){
|
||||
$invitation->restore();
|
||||
return $this->purchase_order;
|
||||
}
|
||||
if($invitation){
|
||||
$invitation->restore();
|
||||
return $this->purchase_order;
|
||||
}
|
||||
}
|
||||
|
||||
$ii = PurchaseOrderInvitationFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
$ii->key = $this->createDbHash($this->purchase_order->company->db);
|
||||
$ii->purchase_order_id = $this->purchase_order->id;
|
||||
$ii->vendor_contact_id = $contact->id;
|
||||
$ii->save();
|
||||
$ii = PurchaseOrderInvitation::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
$ii->key = $this->createDbHash($this->purchase_order->company->db);
|
||||
$ii->purchase_order_id = $this->purchase_order->id;
|
||||
$ii->vendor_contact_id = $contact->id;
|
||||
$ii->save();
|
||||
}
|
||||
|
||||
|
||||
return $this->purchase_order;
|
||||
}
|
||||
|
||||
private function createBlankContact()
|
||||
{
|
||||
$new_contact = VendorContactFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
$new_contact->vendor_id = $this->purchase_order->vendor_id;
|
||||
$new_contact->contact_key = Str::random(40);
|
||||
$new_contact->is_primary = true;
|
||||
$new_contact->send_email = true;
|
||||
$new_contact->save();
|
||||
|
||||
return $new_contact;
|
||||
}
|
||||
}
|
||||
|
45
app/Services/PurchaseOrder/MarkSent.php
Normal file
45
app/Services/PurchaseOrder/MarkSent.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasMarkedSent;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Ninja;
|
||||
|
||||
class MarkSent
|
||||
{
|
||||
private $vendor;
|
||||
|
||||
private $purchase_order;
|
||||
|
||||
public function __construct($vendor, $purchase_order)
|
||||
{
|
||||
$this->vendor = $vendor;
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
/* Return immediately if status is not draft */
|
||||
if ($this->purchase_order->status_id != PurchaseOrder::STATUS_DRAFT) {
|
||||
return $this->purchase_order;
|
||||
}
|
||||
|
||||
$this->purchase_order->markInvitationsSent();
|
||||
|
||||
$this->purchase_order
|
||||
->service()
|
||||
->setStatus(PurchaseOrder::STATUS_SENT)
|
||||
->applyNumber()
|
||||
// ->adjustBalance($this->purchase_order->amount)
|
||||
// ->touchPdf()
|
||||
->save();
|
||||
|
||||
event(new PurchaseOrderWasMarkedSent($this->purchase_order, $this->purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
return $this->purchase_order;
|
||||
}
|
||||
}
|
@ -71,4 +71,19 @@ class PurchaseOrderService
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStatus($status)
|
||||
{
|
||||
$this->purchase_order->status_id = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function markSent()
|
||||
{
|
||||
$this->purchase_order = (new MarkSent($this->purchase_order->vendor, $this->purchase_order))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -106,6 +106,12 @@ class RecurringService
|
||||
$this->stop();
|
||||
}
|
||||
|
||||
if(isset($this->recurring_entity->client))
|
||||
{
|
||||
$offset = $this->recurring_entity->client->timezone_offset();
|
||||
$this->recurring_entity->next_send_date = Carbon::parse($this->recurring_entity->next_send_date_client)->startOfDay()->addSeconds($offset);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -170,6 +170,10 @@ class CompanyTransformer extends EntityTransformer
|
||||
'markdown_email_enabled' => (bool) $company->markdown_email_enabled,
|
||||
'stop_on_unpaid_recurring' => (bool) $company->stop_on_unpaid_recurring,
|
||||
'use_quote_terms_on_conversion' => (bool) $company->use_quote_terms_on_conversion,
|
||||
'stock_notification' => (bool) $company->stock_notification,
|
||||
'inventory_notification_threshold' => (int) $company->inventory_notification_threshold,
|
||||
'track_inventory' => (bool) $company->track_inventory,
|
||||
'enable_applying_payments' => (bool) $company->enable_applying_payments,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,9 @@ class ProductTransformer extends EntityTransformer
|
||||
'custom_value3' => $product->custom_value3 ?: '',
|
||||
'custom_value4' => $product->custom_value4 ?: '',
|
||||
'is_deleted' => (bool) $product->is_deleted,
|
||||
'in_stock_quantity' => (int) $product->in_stock_quantity ?: 0,
|
||||
'stock_notification' => (bool) $product->stock_notification,
|
||||
'stock_notification_threshold' => (int) $product->stock_notification_threshold,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
32
app/Transformers/PurchaseOrderInvitationTransformer.php
Normal file
32
app/Transformers/PurchaseOrderInvitationTransformer.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Transformers;
|
||||
|
||||
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PurchaseOrderInvitationTransformer extends EntityTransformer
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public function transform(PurchaseOrderInvitation $invitation)
|
||||
{
|
||||
return [
|
||||
'id' => $this->encodePrimaryKey($invitation->id),
|
||||
'vendor_contact_id' => $this->encodePrimaryKey($invitation->vendor_contact_id),
|
||||
'key' => $invitation->key,
|
||||
'link' => $invitation->getLink() ?: '',
|
||||
'sent_date' => $invitation->sent_date ?: '',
|
||||
'viewed_date' => $invitation->viewed_date ?: '',
|
||||
'opened_date' => $invitation->opened_date ?: '',
|
||||
'updated_at' => (int)$invitation->updated_at,
|
||||
'archived_at' => (int)$invitation->deleted_at,
|
||||
'created_at' => (int)$invitation->created_at,
|
||||
'email_status' => $invitation->email_status ?: '',
|
||||
'email_error' => (string)$invitation->email_error,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -13,12 +13,24 @@ namespace App\Transformers;
|
||||
|
||||
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PurchaseOrderTransformer extends EntityTransformer
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected $defaultIncludes = [
|
||||
'invitations',
|
||||
];
|
||||
|
||||
public function includeInvitations(PurchaseOrder $purchase_order)
|
||||
{
|
||||
$transformer = new PurchaseOrderInvitationTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($purchase_order->invitations, $transformer, PurchaseOrderInvitation::class);
|
||||
}
|
||||
|
||||
public function transform(PurchaseOrder $purchase_order)
|
||||
{
|
||||
return [
|
||||
@ -26,19 +38,18 @@ class PurchaseOrderTransformer extends EntityTransformer
|
||||
'user_id' => $this->encodePrimaryKey($purchase_order->user_id),
|
||||
'project_id' => $this->encodePrimaryKey($purchase_order->project_id),
|
||||
'assigned_user_id' => $this->encodePrimaryKey($purchase_order->assigned_user_id),
|
||||
'vendor_id' => (string) $this->encodePrimaryKey($purchase_order->vendor_id),
|
||||
'amount' => (float) $purchase_order->amount,
|
||||
'balance' => (float) $purchase_order->balance,
|
||||
'client_id' => (string) $this->encodePrimaryKey($purchase_order->client_id),
|
||||
'vendor_id' => (string) $this->encodePrimaryKey($purchase_order->vendor_id),
|
||||
'status_id' => (string) ($purchase_order->status_id ?: 1),
|
||||
'design_id' => (string) $this->encodePrimaryKey($purchase_order->design_id),
|
||||
'created_at' => (int) $purchase_order->created_at,
|
||||
'updated_at' => (int) $purchase_order->updated_at,
|
||||
'archived_at' => (int) $purchase_order->deleted_at,
|
||||
'is_deleted' => (bool) $purchase_order->is_deleted,
|
||||
'vendor_id' => (string)$this->encodePrimaryKey($purchase_order->vendor_id),
|
||||
'amount' => (float)$purchase_order->amount,
|
||||
'balance' => (float)$purchase_order->balance,
|
||||
'client_id' => (string)$this->encodePrimaryKey($purchase_order->client_id),
|
||||
'status_id' => (string)($purchase_order->status_id ?: 1),
|
||||
'design_id' => (string)$this->encodePrimaryKey($purchase_order->design_id),
|
||||
'created_at' => (int)$purchase_order->created_at,
|
||||
'updated_at' => (int)$purchase_order->updated_at,
|
||||
'archived_at' => (int)$purchase_order->deleted_at,
|
||||
'is_deleted' => (bool)$purchase_order->is_deleted,
|
||||
'number' => $purchase_order->number ?: '',
|
||||
'discount' => (float) $purchase_order->discount,
|
||||
'discount' => (float)$purchase_order->discount,
|
||||
'po_number' => $purchase_order->po_number ?: '',
|
||||
'date' => $purchase_order->date ?: '',
|
||||
'last_sent_date' => $purchase_order->last_sent_date ?: '',
|
||||
@ -51,36 +62,36 @@ class PurchaseOrderTransformer extends EntityTransformer
|
||||
'terms' => $purchase_order->terms ?: '',
|
||||
'public_notes' => $purchase_order->public_notes ?: '',
|
||||
'private_notes' => $purchase_order->private_notes ?: '',
|
||||
'uses_inclusive_taxes' => (bool) $purchase_order->uses_inclusive_taxes,
|
||||
'uses_inclusive_taxes' => (bool)$purchase_order->uses_inclusive_taxes,
|
||||
'tax_name1' => $purchase_order->tax_name1 ? $purchase_order->tax_name1 : '',
|
||||
'tax_rate1' => (float) $purchase_order->tax_rate1,
|
||||
'tax_rate1' => (float)$purchase_order->tax_rate1,
|
||||
'tax_name2' => $purchase_order->tax_name2 ? $purchase_order->tax_name2 : '',
|
||||
'tax_rate2' => (float) $purchase_order->tax_rate2,
|
||||
'tax_rate2' => (float)$purchase_order->tax_rate2,
|
||||
'tax_name3' => $purchase_order->tax_name3 ? $purchase_order->tax_name3 : '',
|
||||
'tax_rate3' => (float) $purchase_order->tax_rate3,
|
||||
'total_taxes' => (float) $purchase_order->total_taxes,
|
||||
'is_amount_discount' => (bool) ($purchase_order->is_amount_discount ?: false),
|
||||
'tax_rate3' => (float)$purchase_order->tax_rate3,
|
||||
'total_taxes' => (float)$purchase_order->total_taxes,
|
||||
'is_amount_discount' => (bool)($purchase_order->is_amount_discount ?: false),
|
||||
'footer' => $purchase_order->footer ?: '',
|
||||
'partial' => (float) ($purchase_order->partial ?: 0.0),
|
||||
'partial' => (float)($purchase_order->partial ?: 0.0),
|
||||
'partial_due_date' => $purchase_order->partial_due_date ?: '',
|
||||
'custom_value1' => (string) $purchase_order->custom_value1 ?: '',
|
||||
'custom_value2' => (string) $purchase_order->custom_value2 ?: '',
|
||||
'custom_value3' => (string) $purchase_order->custom_value3 ?: '',
|
||||
'custom_value4' => (string) $purchase_order->custom_value4 ?: '',
|
||||
'has_tasks' => (bool) $purchase_order->has_tasks,
|
||||
'has_expenses' => (bool) $purchase_order->has_expenses,
|
||||
'custom_surcharge1' => (float) $purchase_order->custom_surcharge1,
|
||||
'custom_surcharge2' => (float) $purchase_order->custom_surcharge2,
|
||||
'custom_surcharge3' => (float) $purchase_order->custom_surcharge3,
|
||||
'custom_surcharge4' => (float) $purchase_order->custom_surcharge4,
|
||||
'custom_surcharge_tax1' => (bool) $purchase_order->custom_surcharge_tax1,
|
||||
'custom_surcharge_tax2' => (bool) $purchase_order->custom_surcharge_tax2,
|
||||
'custom_surcharge_tax3' => (bool) $purchase_order->custom_surcharge_tax3,
|
||||
'custom_surcharge_tax4' => (bool) $purchase_order->custom_surcharge_tax4,
|
||||
'line_items' => $purchase_order->line_items ?: (array) [],
|
||||
'custom_value1' => (string)$purchase_order->custom_value1 ?: '',
|
||||
'custom_value2' => (string)$purchase_order->custom_value2 ?: '',
|
||||
'custom_value3' => (string)$purchase_order->custom_value3 ?: '',
|
||||
'custom_value4' => (string)$purchase_order->custom_value4 ?: '',
|
||||
'has_tasks' => (bool)$purchase_order->has_tasks,
|
||||
'has_expenses' => (bool)$purchase_order->has_expenses,
|
||||
'custom_surcharge1' => (float)$purchase_order->custom_surcharge1,
|
||||
'custom_surcharge2' => (float)$purchase_order->custom_surcharge2,
|
||||
'custom_surcharge3' => (float)$purchase_order->custom_surcharge3,
|
||||
'custom_surcharge4' => (float)$purchase_order->custom_surcharge4,
|
||||
'custom_surcharge_tax1' => (bool)$purchase_order->custom_surcharge_tax1,
|
||||
'custom_surcharge_tax2' => (bool)$purchase_order->custom_surcharge_tax2,
|
||||
'custom_surcharge_tax3' => (bool)$purchase_order->custom_surcharge_tax3,
|
||||
'custom_surcharge_tax4' => (bool)$purchase_order->custom_surcharge_tax4,
|
||||
'line_items' => $purchase_order->line_items ?: (array)[],
|
||||
'entity_type' => 'credit',
|
||||
'exchange_rate' => (float) $purchase_order->exchange_rate,
|
||||
'paid_to_date' => (float) $purchase_order->paid_to_date,
|
||||
'exchange_rate' => (float)$purchase_order->exchange_rate,
|
||||
'paid_to_date' => (float)$purchase_order->paid_to_date,
|
||||
'subscription_id' => $this->encodePrimaryKey($purchase_order->subscription_id),
|
||||
];
|
||||
}
|
||||
|
@ -100,7 +100,8 @@ class RecurringExpenseTransformer extends EntityTransformer
|
||||
'frequency_id' => (string) $recurring_expense->frequency_id,
|
||||
'remaining_cycles' => (int) $recurring_expense->remaining_cycles,
|
||||
'last_sent_date' => $recurring_expense->last_sent_date ?: '',
|
||||
'next_send_date' => $recurring_expense->next_send_date ?: '',
|
||||
// 'next_send_date' => $recurring_expense->next_send_date ?: '',
|
||||
'next_send_date' => $recurring_expense->next_send_date_client ?: '',
|
||||
'recurring_dates' => (array) [],
|
||||
];
|
||||
|
||||
|
@ -95,7 +95,8 @@ class RecurringInvoiceTransformer extends EntityTransformer
|
||||
'po_number' => $invoice->po_number ?: '',
|
||||
'date' => $invoice->date ?: '',
|
||||
'last_sent_date' => $invoice->last_sent_date ?: '',
|
||||
'next_send_date' => $invoice->next_send_date ?: '',
|
||||
// 'next_send_date' => $invoice->next_send_date ?: '',
|
||||
'next_send_date' => $invoice->next_send_date_client ?: '',
|
||||
'due_date' => $invoice->due_date ?: '',
|
||||
'terms' => $invoice->terms ?: '',
|
||||
'public_notes' => $invoice->public_notes ?: '',
|
||||
|
@ -45,8 +45,8 @@ trait GeneratesCounter
|
||||
|
||||
$is_client_counter = false;
|
||||
|
||||
$counter_string = $this->getEntityCounter($entity, $client);
|
||||
$pattern = $this->getNumberPattern($entity, $client);
|
||||
$counter_string = $this->getEntityCounter($entity, $client);
|
||||
$pattern = $this->getNumberPattern($entity, $client);
|
||||
|
||||
if ((strpos($pattern, 'clientCounter') !== false) || (strpos($pattern, 'client_counter') !==false) ) {
|
||||
|
||||
@ -72,9 +72,9 @@ trait GeneratesCounter
|
||||
$counter_entity = $client->company;
|
||||
}
|
||||
|
||||
//If it is a quote - we need to
|
||||
//If it is a quote - we need to
|
||||
$pattern = $this->getNumberPattern($entity, $client);
|
||||
|
||||
|
||||
if(strlen($pattern) > 1 && (stripos($pattern, 'counter') === false)){
|
||||
$pattern = $pattern.'{$counter}';
|
||||
}
|
||||
@ -128,9 +128,9 @@ trait GeneratesCounter
|
||||
break;
|
||||
case Quote::class:
|
||||
|
||||
if ($this->hasSharedCounter($client, 'quote'))
|
||||
if ($this->hasSharedCounter($client, 'quote'))
|
||||
return 'invoice_number_counter';
|
||||
|
||||
|
||||
return 'quote_number_counter';
|
||||
break;
|
||||
case RecurringInvoice::class:
|
||||
@ -146,14 +146,17 @@ trait GeneratesCounter
|
||||
return 'payment_number_counter';
|
||||
break;
|
||||
case Credit::class:
|
||||
if ($this->hasSharedCounter($client, 'credit'))
|
||||
if ($this->hasSharedCounter($client, 'credit'))
|
||||
return 'invoice_number_counter';
|
||||
|
||||
|
||||
return 'credit_number_counter';
|
||||
break;
|
||||
case Project::class:
|
||||
return 'project_number_counter';
|
||||
break;
|
||||
case PurchaseOrder::class:
|
||||
return 'purchase_order_number_counter';
|
||||
break;
|
||||
|
||||
case PurchaseOrder::class:
|
||||
return 'purchase_order_number_counter';
|
||||
@ -193,6 +196,20 @@ trait GeneratesCounter
|
||||
|
||||
return $this->replaceUserVars($credit, $entity_number);
|
||||
|
||||
}
|
||||
/**
|
||||
* Gets the next purchase order number.
|
||||
*
|
||||
* @param PurchaseOrder $purchase_order The purchase order
|
||||
*
|
||||
* @return string The next purchase order number.
|
||||
*/
|
||||
public function getNextPurchaseOrderNumber(Client $client, ?PurchaseOrder $purchase_order) :string
|
||||
{
|
||||
$entity_number = $this->getNextEntityNumber(PurchaseOrder::class, $client);
|
||||
|
||||
return $this->replaceUserVars($purchase_order, $entity_number);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -407,7 +424,7 @@ trait GeneratesCounter
|
||||
*
|
||||
* @return bool True if has shared counter, False otherwise.
|
||||
*/
|
||||
public function hasSharedCounter(Client $client, string $type = 'quote') : bool
|
||||
public function hasSharedCounter(Client $client, string $type = 'quote') : bool
|
||||
{
|
||||
if($type == 'quote')
|
||||
return (bool) $client->getSetting('shared_invoice_quote_counter');
|
||||
@ -460,9 +477,9 @@ trait GeneratesCounter
|
||||
public function checkNumberAvailable($class, $entity, $number) :bool
|
||||
{
|
||||
|
||||
if ($entity = $class::whereCompanyId($entity->company_id)->whereNumber($number)->withTrashed()->exists())
|
||||
if ($entity = $class::whereCompanyId($entity->company_id)->whereNumber($number)->withTrashed()->exists())
|
||||
return false;
|
||||
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
@ -526,7 +543,7 @@ trait GeneratesCounter
|
||||
|
||||
if($reset_counter_frequency == 0)
|
||||
return;
|
||||
|
||||
|
||||
$timezone = Timezone::find($client->getSetting('timezone_id'));
|
||||
|
||||
$reset_date = Carbon::parse($client->getSetting('reset_counter_date'), $timezone->name);
|
||||
@ -580,6 +597,7 @@ trait GeneratesCounter
|
||||
$settings->invoice_number_counter = 1;
|
||||
$settings->quote_number_counter = 1;
|
||||
$settings->credit_number_counter = 1;
|
||||
$settings->purchase_order_number_counter = 1;
|
||||
|
||||
$client->company->settings = $settings;
|
||||
$client->company->save();
|
||||
@ -644,6 +662,7 @@ trait GeneratesCounter
|
||||
$settings->task_number_counter = 1;
|
||||
$settings->expense_number_counter = 1;
|
||||
$settings->recurring_expense_number_counter =1;
|
||||
$settings->purchase_order_number_counter = 1;
|
||||
|
||||
$company->settings = $settings;
|
||||
$company->save();
|
||||
@ -666,7 +685,7 @@ trait GeneratesCounter
|
||||
|
||||
$search = [];
|
||||
$replace = [];
|
||||
|
||||
|
||||
$search[] = '{$counter}';
|
||||
$replace[] = $counter;
|
||||
|
||||
@ -681,7 +700,7 @@ trait GeneratesCounter
|
||||
|
||||
$search[] = '{$year}';
|
||||
$replace[] = Carbon::now($entity->company->timezone()->name)->format('Y');
|
||||
|
||||
|
||||
if (strstr($pattern, '{$user_id}') || strstr($pattern, '{$userId}')) {
|
||||
$user_id = $entity->user_id ? $entity->user_id : 0;
|
||||
$search[] = '{$user_id}';
|
||||
@ -705,7 +724,7 @@ trait GeneratesCounter
|
||||
$search[] = '{$vendor_id_number}';
|
||||
$replace[] = $entity->id_number;
|
||||
}
|
||||
|
||||
|
||||
if ($entity instanceof Expense) {
|
||||
if ($entity->vendor) {
|
||||
$search[] = '{$vendor_id_number}';
|
||||
@ -730,7 +749,7 @@ trait GeneratesCounter
|
||||
$search[] = '{$expense_id_number}';
|
||||
$replace[] = $entity->id_number;
|
||||
}
|
||||
|
||||
|
||||
if ($entity->client || ($entity instanceof Client)) {
|
||||
$client = $entity->client ?: $entity;
|
||||
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.3.93',
|
||||
'app_tag' => '5.3.93',
|
||||
'app_version' => '5.3.96',
|
||||
'app_tag' => '5.3.96',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
31
database/factories/PurchaseOrderInvitationFactory.php
Normal file
31
database/factories/PurchaseOrderInvitationFactory.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PurchaseOrderInvitationFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = PurchaseOrderInvitation::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'key' => Str::random(40),
|
||||
];
|
||||
}
|
||||
}
|
@ -51,6 +51,7 @@ class RecurringInvoiceFactory extends Factory
|
||||
'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY,
|
||||
'last_sent_date' => now()->subMonth(),
|
||||
'next_send_date' => now()->addMonthNoOverflow(),
|
||||
'next_send_date_client' => now()->addMonthNoOverflow(),
|
||||
'remaining_cycles' => $this->faker->numberBetween(1, 10),
|
||||
'amount' => $this->faker->randomFloat(2, $min = 1, $max = 1000), // 48.8932
|
||||
|
||||
|
@ -30,9 +30,6 @@ class CreateSchedulersTable extends Migration
|
||||
$table->timestamp('start_from');
|
||||
$table->timestamp('scheduled_run');
|
||||
$table->foreignIdFor(\App\Models\Company::class);
|
||||
$table->string('action_name')->index();
|
||||
$table->string('action_class');
|
||||
$table->json('parameters')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
@ -122,6 +122,5 @@ class CreatePurchaseOrdersTable extends Migration
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('purchase_orders');
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddJobRelatedFieldsToSchedulersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('schedulers', function (Blueprint $table) {
|
||||
$table->string('action_name')->index();
|
||||
$table->string('action_class');
|
||||
$table->json('parameters')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class InventoryManagementSchema extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->boolean('enable_applying_payments')->default(0);
|
||||
$table->boolean('track_inventory')->default(0);
|
||||
$table->integer('inventory_notification_threshold')->default(0);
|
||||
$table->boolean('stock_notification')->default(1);
|
||||
});
|
||||
|
||||
Schema::table('products', function (Blueprint $table){
|
||||
$table->integer('in_stock_quantity')->default(0);
|
||||
$table->boolean('stock_notification')->default(1);
|
||||
$table->integer('stock_notification_threshold')->default(0);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
use App\Models\RecurringExpense;
|
||||
use App\Models\RecurringInvoice;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class SetRecurringClientTimestamp extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('recurring_invoices', function (Blueprint $table) {
|
||||
$table->datetime('next_send_date_client')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('recurring_expenses', function (Blueprint $table) {
|
||||
$table->datetime('next_send_date_client')->nullable();
|
||||
});
|
||||
|
||||
|
||||
RecurringInvoice::whereNotNull('next_send_date')->cursor()->each(function ($recurring_invoice){
|
||||
|
||||
// $offset = $recurring_invoice->client->timezone_offset();
|
||||
// $re = Carbon::parse($recurring_invoice->next_send_date)->subSeconds($offset)->format('Y-m-d');
|
||||
$re = Carbon::parse($recurring_invoice->next_send_date)->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date_client = $re;
|
||||
$recurring_invoice->saveQuietly();
|
||||
|
||||
});
|
||||
|
||||
RecurringExpense::whereNotNull('next_send_date')->cursor()->each(function ($recurring_expense){
|
||||
$recurring_expense->next_send_date_client = $recurring_expense->next_send_date;
|
||||
$recurring_expense->saveQuietly();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreatePurchaseOrderInvitationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('purchase_order_invitations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('company_id')->index();
|
||||
$table->unsignedInteger('user_id');
|
||||
$table->unsignedInteger('vendor_contact_id')->unique();
|
||||
$table->unsignedBigInteger('purchase_order_id')->index()->unique();
|
||||
$table->string('key')->index();
|
||||
$table->string('transaction_reference')->nullable();
|
||||
$table->string('message_id')->nullable()->index();
|
||||
$table->mediumText('email_error')->nullable();
|
||||
$table->text('signature_base64')->nullable();
|
||||
$table->datetime('signature_date')->nullable();
|
||||
|
||||
$table->datetime('sent_date')->nullable();
|
||||
$table->datetime('viewed_date')->nullable();
|
||||
$table->datetime('opened_date')->nullable();
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->foreign('vendor_contact_id')->references('id')->on('vendor_contacts')->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->foreign('purchase_order_id')->references('id')->on('purchase_orders')->onDelete('cascade')->onUpdate('cascade');
|
||||
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade');
|
||||
|
||||
$table->timestamps(6);
|
||||
$table->softDeletes('deleted_at', 6);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('purchase_order_invitations');
|
||||
}
|
||||
}
|
48
public/flutter_service_worker.js
vendored
48
public/flutter_service_worker.js
vendored
@ -3,43 +3,43 @@ const MANIFEST = 'flutter-app-manifest';
|
||||
const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"main.dart.js": "208309d12730fc06c9bf9ef0bd0c2483",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"/": "90082634bee3634faebfddf446a803a2",
|
||||
"main.dart.js": "cf8b4f4a686adceb3f1085f27a463220",
|
||||
"version.json": "3afb81924daf4f751571755436069115",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"flutter.js": "0816e65a103ba8ba51b174eeeeb2cb67",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"canvaskit/canvaskit.wasm": "4b83d89d9fecbea8ca46f2f760c5a9ba",
|
||||
"canvaskit/profiling/canvaskit.wasm": "95e736ab31147d1b2c7b25f11d4c32cd",
|
||||
"canvaskit/profiling/canvaskit.js": "ae2949af4efc61d28a4a80fffa1db900",
|
||||
"canvaskit/canvaskit.js": "c2b4e5f3d7a3d82aed024e7249a78487",
|
||||
"flutter.js": "0816e65a103ba8ba51b174eeeeb2cb67",
|
||||
"/": "d60cdb0e60692160d5f962b98b88e24d",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"version.json": "3afb81924daf4f751571755436069115",
|
||||
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
||||
"assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5",
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "b62641afc9ab487008e996a5c5865e56",
|
||||
"assets/assets/images/icon.png": "090f69e23311a4b6d851b3880ae52541",
|
||||
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
|
||||
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "95db9098c58fd6db106f1116bae85a0b",
|
||||
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
|
||||
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
|
||||
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
|
||||
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
|
||||
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
|
||||
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
|
||||
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
|
||||
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
|
||||
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
|
||||
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
|
||||
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
|
||||
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
|
||||
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
|
||||
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
|
||||
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
|
||||
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
|
||||
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
|
||||
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
|
||||
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
|
||||
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
|
||||
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
|
||||
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
|
||||
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
|
||||
"assets/assets/images/icon.png": "090f69e23311a4b6d851b3880ae52541",
|
||||
"assets/assets/images/google_logo.png": "0f118259ce403274f407f5e982e681c3",
|
||||
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
|
||||
"assets/assets/images/logo_dark.png": "a233ed1d4d0f7414bf97a9a10f11fb0a",
|
||||
"assets/NOTICES": "52d7174bb068ef86545951d5bc8c5744",
|
||||
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "95db9098c58fd6db106f1116bae85a0b",
|
||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628"
|
||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40"
|
||||
};
|
||||
|
||||
// The application shell files that are downloaded before a service worker can
|
||||
|
263138
public/main.dart.js
vendored
263138
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
260422
public/main.foss.dart.js
vendored
260422
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
255260
public/main.html.dart.js
vendored
255260
public/main.html.dart.js
vendored
File diff suppressed because one or more lines are too long
260344
public/main.next.dart.js
vendored
260344
public/main.next.dart.js
vendored
File diff suppressed because one or more lines are too long
15839
public/main.profile.dart.js
vendored
15839
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -117,13 +117,20 @@
|
||||
</div>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<td align="center">
|
||||
<div class="dark-bg"
|
||||
style="background-color:#f9f9f9; border: 1px solid #c2c2c2; border-bottom: none; padding-bottom: 20px; border-top-left-radius: 3px; border-top-right-radius: 3px;">
|
||||
<img class="logo-light"
|
||||
style="margin-top: 20px; max-width: 155px; display: block; margin-left: auto; margin-right: auto; "
|
||||
src="{{ $logo ?? '' }}"
|
||||
width="155" />
|
||||
|
||||
<!--[if gte mso 9]>
|
||||
<img src="{{ $logo ?? '' }}" alt="" width="155" border="0" align="middle" style="display:block;" />
|
||||
<div style="mso-hide:all;">
|
||||
<![endif]-->
|
||||
<img class="logo-light" src="{{ $logo ?? '' }}" alt="" style="margin-top: 10px; max-width: 570px; display: block; margin-left: auto; margin-right: auto;"/>
|
||||
<!--[if gte mso 9]>
|
||||
</div>
|
||||
<![endif]-->
|
||||
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -94,19 +94,24 @@
|
||||
<div style="text-align: center;margin-top: 25px; margin-bottom: 10px;"></div>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<td align="center" cellpadding="20">
|
||||
<div
|
||||
style="border: 1px solid #c2c2c2; border-bottom: none; padding-bottom: 10px; border-top-left-radius: 3px; border-top-right-radius: 3px;">
|
||||
|
||||
<img
|
||||
style="margin-top: 40px; height: 40px; display: block; margin-left: auto; margin-right: auto;"
|
||||
alt=""
|
||||
src="{{ $logo ?? '' }}"/>
|
||||
<!--[if gte mso 9]>
|
||||
<img src="{{ $logo ?? '' }}" alt="" width="400" border="0" align="middle" style="display:block;" />
|
||||
<div style="mso-hide:all;">
|
||||
<![endif]-->
|
||||
<img src="{{ $logo ?? '' }}" alt="" style="margin-top: 40px; max-width: 155px; display: block; margin-left: auto; margin-right: auto;"/>
|
||||
<!--[if gte mso 9]>
|
||||
</div>
|
||||
<![endif]-->
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<td cellpadding="20">
|
||||
<div style="border: 1px solid #c2c2c2; border-top: none; border-bottom: none; padding: 20px; text-align: center" id="content">
|
||||
<div style="padding-top: 10px;"></div>
|
||||
|
||||
@ -120,10 +125,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td height="20">
|
||||
<div style="border: 1px solid #c2c2c2; border-top: none; border-bottom: none; padding: 20px; text-align: center" id="content"> </div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<td cellpadding="20" bgcolor="#f9f9f9">
|
||||
<div class="dark-bg dark-text-white"
|
||||
style="text-align: center; padding-top: 10px; padding-bottom: 25px; background-color: #f9f9f9; border: 1px solid #c2c2c2; border-top: none; border-bottom-color: #f9f9f9;">
|
||||
@isset($signature)
|
||||
@ -145,7 +156,7 @@
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<td bgcolor="#242424" cellpadding="20">
|
||||
<div class="dark-bg-base"
|
||||
style="padding-top: 10px;padding-bottom: 10px; background-color: #242424; border: 1px solid #c2c2c2; border-top-color: #242424; border-bottom-color: #242424;">
|
||||
@if(isset($company))
|
||||
|
@ -37,6 +37,7 @@ class PurchaseOrderTest extends TestCase
|
||||
Model::reguard();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
}
|
||||
|
||||
public function testPurchaseOrderRest()
|
||||
@ -44,18 +45,18 @@ class PurchaseOrderTest extends TestCase
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id));
|
||||
])->get('/api/v1/purchase_orders/' . $this->encodePrimaryKey($this->purchase_order->id));
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id).'/edit');
|
||||
])->get('/api/v1/purchase_orders/' . $this->encodePrimaryKey($this->purchase_order->id) . '/edit');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$credit_update = [
|
||||
$purchase_order_update = [
|
||||
'tax_name1' => 'dippy',
|
||||
];
|
||||
|
||||
@ -64,14 +65,14 @@ class PurchaseOrderTest extends TestCase
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id), $credit_update)
|
||||
])->put('/api/v1/purchase_orders/' . $this->encodePrimaryKey($this->purchase_order->id), $purchase_order_update)
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testPostNewPurchaseOrder()
|
||||
{
|
||||
$purchase_order = [
|
||||
'status_id' => 1,
|
||||
'number' => 'dfdfd',
|
||||
'discount' => 0,
|
||||
'is_amount_discount' => 1,
|
||||
'number' => '34343xx43',
|
||||
@ -91,20 +92,21 @@ class PurchaseOrderTest extends TestCase
|
||||
])->post('/api/v1/purchase_orders/', $purchase_order)
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testPurchaseOrderDelete()
|
||||
{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->delete('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id));
|
||||
])->delete('/api/v1/purchase_orders/' . $this->encodePrimaryKey($this->purchase_order->id));
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testPurchaseOrderUpdate()
|
||||
{
|
||||
$data = [
|
||||
'status_id' => 1,
|
||||
'number' => 'dfdfd',
|
||||
'discount' => 0,
|
||||
'is_amount_discount' => 1,
|
||||
'number' => '3434343',
|
||||
@ -121,14 +123,14 @@ class PurchaseOrderTest extends TestCase
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id), $data);
|
||||
])->put('/api/v1/purchase_orders/' . $this->encodePrimaryKey($this->purchase_order->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id), $data);
|
||||
])->put('/api/v1/purchase_orders/' . $this->encodePrimaryKey($this->purchase_order->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
@ -36,6 +36,8 @@ use App\Models\GroupSetting;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Product;
|
||||
use App\Models\Project;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\Quote;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\RecurringExpense;
|
||||
@ -476,6 +478,26 @@ trait MockAccountData
|
||||
$this->purchase_order->save();
|
||||
|
||||
|
||||
PurchaseOrderInvitation::factory()->create([
|
||||
'user_id' => $user_id,
|
||||
'company_id' => $this->company->id,
|
||||
'vendor_contact_id' => $vendor_contact->id,
|
||||
'purchase_order_id' => $this->purchase_order->id,
|
||||
]);
|
||||
|
||||
|
||||
|
||||
$purchase_order_invitations = PurchaseOrderInvitation::whereCompanyId($this->purchase_order->company_id)
|
||||
->wherePurchaseOrderId($this->purchase_order->id);
|
||||
|
||||
$this->purchase_order->setRelation('invitations', $purchase_order_invitations);
|
||||
|
||||
$this->purchase_order->service()->markSent();
|
||||
|
||||
$this->purchase_order->setRelation('client', $this->client);
|
||||
$this->purchase_order->setRelation('company', $this->company);
|
||||
|
||||
$this->purchase_order->save();
|
||||
|
||||
|
||||
$this->credit = CreditFactory::create($this->company->id, $user_id);
|
||||
|
Loading…
x
Reference in New Issue
Block a user