From ad76169b6cd608dee91b249dc286eeee775b9079 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 13 May 2019 16:18:46 +1000 Subject: [PATCH] Working on invoice payments --- app/Events/Payment/PaymentWasCreated.php | 10 +-- app/Factory/PaymentFactory.php | 4 +- app/Jobs/Invoice/ApplyPaymentToInvoice.php | 85 +++++++++++++++++++ app/Jobs/Invoice/MarkPaid.php | 78 +++++++++++++++++ .../CreatedClientActivity.php | 2 +- .../Activity/PaymentCreatedActivity.php | 65 ++++++++++++++ app/Models/Invoice.php | 5 ++ app/Models/Payment.php | 14 ++- app/Observers/PaymentObserver.php | 2 +- app/Providers/EventServiceProvider.php | 7 +- app/Repositories/ActivityRepository.php | 15 ++++ app/Repositories/InvoiceRepository.php | 4 +- .../2014_10_13_000000_create_users_table.php | 9 +- 13 files changed, 287 insertions(+), 13 deletions(-) create mode 100644 app/Jobs/Invoice/ApplyPaymentToInvoice.php create mode 100644 app/Jobs/Invoice/MarkPaid.php rename app/Listeners/{Client => Activity}/CreatedClientActivity.php (97%) create mode 100644 app/Listeners/Activity/PaymentCreatedActivity.php diff --git a/app/Events/Payment/PaymentWasCreated.php b/app/Events/Payment/PaymentWasCreated.php index da059d2ef56a..1d08c27e91d2 100644 --- a/app/Events/Payment/PaymentWasCreated.php +++ b/app/Events/Payment/PaymentWasCreated.php @@ -22,17 +22,17 @@ class PaymentWasCreated use SerializesModels; /** - * @var Payment + * @var array $data */ - public $payment; + public $data; /** * Create a new event instance. * - * @param Payment $payment + * @param array $data */ - public function __construct(Payment $payment) + public function __construct(array $data) { - $this->payment = $payment; + $this->data = $data; } } diff --git a/app/Factory/PaymentFactory.php b/app/Factory/PaymentFactory.php index fa860bfc0e6d..5a2e2ed777f0 100644 --- a/app/Factory/PaymentFactory.php +++ b/app/Factory/PaymentFactory.php @@ -14,6 +14,7 @@ namespace App\Factory; use App\DataMapper\ClientSettings; use App\DataMapper\CompanySettings; use App\Models\Payment; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Log; class PaymentFactory @@ -31,10 +32,11 @@ class PaymentFactory $payment->payment_type_id = null; $payment->is_deleted = false; $payment->amount = 0; - $payment->payment_date = null; + $payment->payment_date = Carbon::now(); $payment->transaction_reference = null; $payment->payer_id = null; $payment->invoice_id = 0; + $payment->status_id = Payment::STATUS_PENDING; return $payment; } diff --git a/app/Jobs/Invoice/ApplyPaymentToInvoice.php b/app/Jobs/Invoice/ApplyPaymentToInvoice.php new file mode 100644 index 000000000000..ad49360bd65b --- /dev/null +++ b/app/Jobs/Invoice/ApplyPaymentToInvoice.php @@ -0,0 +1,85 @@ +invoice = $invoice; + + $this->payment = $payment; + + } + + /** + * Execute the job. + * + * + * @return void + */ + public function handle() + { + + $adjustment = $this->payment->amount * -1; + + $partial = max(0, $this->invoice->partial - $this->payment->amount); + + //check if partial exists + if($this->invoice->partial > 0) + { + + //if payment amount = partial + if( $this->formatvalue($this->invoice->partial,4) == $this->formatValue($this->payment->amount,4) ) + { + $this->invoice->partial = 0; + } + + //if payment amount < partial amount + if( $this->formatvalue($this->invoice->partial,4) > $this->formatValue($this->payment->amount,4) ) + { + //set the new partial amount to the balance + $this->invoice->partial = $partial; + } + + + $this->partial_due_date = null; + $this->due_date = + } + + + $this->balance = $this->balance + $adjustment; + + + } +} diff --git a/app/Jobs/Invoice/MarkPaid.php b/app/Jobs/Invoice/MarkPaid.php new file mode 100644 index 000000000000..419aef89ab3f --- /dev/null +++ b/app/Jobs/Invoice/MarkPaid.php @@ -0,0 +1,78 @@ +invoice = $invoice; + + } + + /** + * Execute the job. + * + * + * @return void + */ + public function handle() + { + /* Create Payment */ + $payment = new PaymentFactory($this->invoice->company_id, $this->invoice->user_id); + + $payment->amount = $invoice->balance; + $payment->status_id = Payment::STATUS_COMPLETED; + $payment->client_id = $invoice->client_id; + + $payment->save(); + + /* Create a payment relationship to the invoice entity */ + $payment->invoices()->save($this->invoice); + + + /* Need to engineer the ability to pass an array of invoices to the activity handler*/ + $data = [ + 'payment_id' => $payment->id, + 'invoice_ids' => [ + $this->invoice->id + ] + ]; + + event(new PaymentWasCreated($data)); + + /* Update Invoice balance */ + ApplyPaymentToInvoice::dispatchNow($payment, $invoice); + + } +} diff --git a/app/Listeners/Client/CreatedClientActivity.php b/app/Listeners/Activity/CreatedClientActivity.php similarity index 97% rename from app/Listeners/Client/CreatedClientActivity.php rename to app/Listeners/Activity/CreatedClientActivity.php index b9a552c9cf2a..b1446234ef01 100644 --- a/app/Listeners/Client/CreatedClientActivity.php +++ b/app/Listeners/Activity/CreatedClientActivity.php @@ -9,7 +9,7 @@ * @license https://opensource.org/licenses/AAL */ -namespace App\Listeners\Client; +namespace App\Listeners\Activity; use App\Models\Activity; use App\Repositories\ActivityRepository; diff --git a/app/Listeners/Activity/PaymentCreatedActivity.php b/app/Listeners/Activity/PaymentCreatedActivity.php new file mode 100644 index 000000000000..eb6ff35569e0 --- /dev/null +++ b/app/Listeners/Activity/PaymentCreatedActivity.php @@ -0,0 +1,65 @@ +activityRepo = $activityRepo; + } + + /** + * Handle the event. + * + * @param object $event + * @return void + */ + public function handle($event) + { + $data = $event->data; + + $payment = Payment::find($data['payment_id']); + + $invoices = Invoice::find($data['invoice_ids']); + + $fields = new \stdClass; + + $fields->payment_id = $payment->id; + $fields->user_id = $payment->user_id; + $fields->company_id = $payment->company_id; + $fields->activity_type_id = Activity::CREATE_PAYMENT; + + + foreach($invoices as $invoice) //todo we may need to add additional logic if in the future we apply payments to other entity Types, not just invoices + { + $fields->invoice_id = $invoice->id; + + $this->activityRepo->save($fields, $invoice); + } + + if( count( $invoices ) == 0 ) + $this->activityRepo->save($fields, $payment); + } +} diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 26b5e84e61aa..445d19a989e4 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -75,6 +75,11 @@ class Invoice extends BaseModel return $this->morphMany(Document::class, 'documentable'); } + public function payments() + { + return $this->morphToMany(Payment::class, 'paymentable'); + } + /* ---------------- */ /* Settings getters */ /* ---------------- */ diff --git a/app/Models/Payment.php b/app/Models/Payment.php index c037559b1810..0e286348a4cd 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -19,7 +19,14 @@ class Payment extends BaseModel { use MakesHash; use Filterable; - + + const STATUS_PENDING = 1; + const STATUS_VOIDED = 2; + const STATUS_FAILED = 3; + const STATUS_COMPLETED = 4; + const STATUS_PARTIALLY_REFUNDED = 5; + const STATUS_REFUNDED = 6; + protected $guarded = [ 'id', ]; @@ -43,4 +50,9 @@ class Payment extends BaseModel { return $this->morphMany(Document::class, 'documentable'); } + + public function invoices() + { + return $this->morphedByMany(Invoice::class, 'paymentable'); + } } diff --git a/app/Observers/PaymentObserver.php b/app/Observers/PaymentObserver.php index 65c5d7bd944b..5edfc7b2ba35 100644 --- a/app/Observers/PaymentObserver.php +++ b/app/Observers/PaymentObserver.php @@ -23,7 +23,7 @@ class PaymentObserver */ public function created(Payment $payment) { - // + } /** diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index fc87a3babaff..c6cb7f8ed3cd 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -13,8 +13,10 @@ namespace App\Providers; use App\Events\Client\ClientWasCreated; use App\Events\Invoice\InvoiceWasMarkedSent; +use App\Events\Payment\PaymentWasCreated; use App\Events\User\UserCreated; -use App\Listeners\Client\CreatedClientActivity; +use App\Listeners\Activity\PaymentCreatedActivity; +use App\Listeners\Activity\CreatedClientActivity; use App\Listeners\Invoice\CreateInvoiceInvitations; use App\Listeners\SendVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; @@ -36,6 +38,9 @@ class EventServiceProvider extends ServiceProvider CreatedClientActivity::class, // 'App\Listeners\SubscriptionListener@createdClient', ], + PaymentWasCreated::class => [ + PaymentCreatedActivity::class, + ], 'App\Events\ClientWasArchived' => [ 'App\Listeners\ActivityListener@archivedClient', ], diff --git a/app/Repositories/ActivityRepository.php b/app/Repositories/ActivityRepository.php index ee90ea1ad5b0..dfe540b4b863 100644 --- a/app/Repositories/ActivityRepository.php +++ b/app/Repositories/ActivityRepository.php @@ -14,9 +14,18 @@ namespace App\Repositories; use App\Models\Activity; use App\Models\Backup; +/** + * Class for activity repository. + */ class ActivityRepository extends BaseRepository { + /** + * Save the Activity + * + * @param stdClass $fields The fields + * @param Collection $entity The entity that you wish to have backed up (typically Invoice, Quote etc etc rather than Payment) + */ public function save($fields, $entity) { $activity = new Activity(); @@ -34,6 +43,12 @@ class ActivityRepository extends BaseRepository $this->createBackup($entity, $activity); } + /** + * Creates a backup. + * + * @param Collection $entity The entity + * @param Collection $activity The activity + */ public function createBackup($entity, $activity) { $backup = new Backup(); diff --git a/app/Repositories/InvoiceRepository.php b/app/Repositories/InvoiceRepository.php index 5021fff4bdc0..b130f911b064 100644 --- a/app/Repositories/InvoiceRepository.php +++ b/app/Repositories/InvoiceRepository.php @@ -65,8 +65,8 @@ class InvoiceRepository extends BaseRepository */ public function markSent(Invoice $invoice) : ?Invoice { - - if($invoice->status_id >= Invoice::STATUS_SENT) + /* Return immediately if status is not draft*/ + if($invoice->status_id != Invoice::STATUS_DRAFT) return $invoice; $invoice->status_id = Invoice::STATUS_SENT; diff --git a/database/migrations/2014_10_13_000000_create_users_table.php b/database/migrations/2014_10_13_000000_create_users_table.php index dee1ef9f4726..0e71a6c91e91 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -665,7 +665,7 @@ class CreateUsersTable extends Migration Schema::create('payments', function ($t) { $t->increments('id'); - $t->unsignedInteger('invoice_id')->nullable()->index(); //todo handle payments where there is no invoice OR we are paying MULTIPLE invoices + //$t->unsignedInteger('invoice_id')->nullable()->index(); //todo handle payments where there is no invoice OR we are paying MULTIPLE invoices $t->unsignedInteger('company_id')->index(); $t->unsignedInteger('client_id')->index(); $t->unsignedInteger('client_contact_id')->nullable(); @@ -673,6 +673,8 @@ class CreateUsersTable extends Migration $t->unsignedInteger('user_id')->nullable(); $t->unsignedInteger('account_gateway_id')->nullable(); $t->unsignedInteger('payment_type_id')->nullable(); + $t->unsignedInteger('status_id')->index(); + $t->timestamps(6); $t->softDeletes(); @@ -693,6 +695,11 @@ class CreateUsersTable extends Migration }); + Schema::create('paymentables', function ($table) { + $table->unsignedInteger('payment_id'); + $table->unsignedInteger('paymentable_id'); + $table->string('paymentable_type'); + }); Schema::create('payment_libraries', function ($t) { $t->increments('id');