Merge branch 'dillon/53-voucherify-email-reminder-to-user' into 'master'

Dillon/53 voucherify email reminder to user

See merge request CIEFWorldwideSdnBhd/exchange-2.0!159
This commit is contained in:
Dillon Ngo
2024-03-18 12:01:17 +00:00
18 changed files with 389 additions and 15 deletions
@@ -0,0 +1,12 @@
<?php
namespace App\Classes\General\Interfaces;
use Illuminate\Database\Eloquent\Relations\MorphMany;
interface KeyValueInterface
{
public function attributes(): morphMany;
}
@@ -0,0 +1,70 @@
<?php
namespace App\Classes\Jobs;
use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject;
use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair;
use App\Classes\Notifications\WelcomeVoucherEmail;
use App\Models\User;
use App\Models\Voucher;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SendWelcomeVoucherEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** @var User */
private $user;
/** @var Voucher */
private $voucher;
/** @var int */
private $emailSentCount;
/**
* SendWelcomeVoucherEmail constructor.
* @param User $user
* @param Voucher $voucher
* @param int $emailSentCount
*/
public function __construct(User $user, Voucher $voucher, int $emailSentCount = 1)
{
$this->user = $user;
$this->voucher = $voucher;
$this->emailSentCount = $emailSentCount;
}
public function handle()
{
$currentDatetime = Carbon::now();
$dateToCompare = Carbon::parse($this->voucher->end_date);
if (!$this->user->hasAttribute($this->voucher->code."_EMAIL_COUNT")
&& $this->user->rewards->where('voucher_id', $this->voucher->id)->count() > 0
&& $currentDatetime->isBefore($dateToCompare))
{
//Key #1
$keyValuePairObject = new KeyValuePairObject(
$this->voucher->code."_EMAIL_COUNT",
$this->emailSentCount
);
(App()->make(CreatesKeyValuePair::class))->execute($this->user, $keyValuePairObject);
//Key #2
$keyValuePairObject = new KeyValuePairObject(
$this->voucher->code."_EMAIL_DATE_".$this->emailSentCount,
Carbon::now()
);
(App()->make(CreatesKeyValuePair::class))->execute($this->user, $keyValuePairObject);
$this->user->notify(new WelcomeVoucherEmail($this->user, $this->voucher));
}
}
}
@@ -30,6 +30,7 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use App\Classes\Modules\Segments\Services\CreatesSeasonalSegment;
use App\Classes\ValueObjects\Constants\Vouchers;
class CreateCustomerLogic extends AbstractControllerLogic
{
@@ -159,7 +160,7 @@ class CreateCustomerLogic extends AbstractControllerLogic
$this->newCustomerToVoucherifyProcessor->execute($company->id, $user, true);
$this->createVoucherProcessor->execute($user, 'WELCOME50%OFF');
$this->createVoucherProcessor->execute($user, Vouchers::WELCOME_50_PERCENT_OFF);
return $this->response($this->authenticationProcessor->execute($request, false));
@@ -9,6 +9,9 @@ use App\Classes\Modules\Accounts\Services\CompletesEmailVerificationAttempt;
use App\Classes\Modules\Accounts\Services\FetchesEmailVerificationAttempt;
use App\Classes\Modules\Accounts\Services\VerifiesUser;
use App\Classes\Modules\Accounts\Standards\Criteria\EmailVerificationActiveAttemptExists;
use App\Classes\Modules\Vouchers\Services\FetchesVoucher;
use App\Classes\Jobs\SendWelcomeVoucherEmail;
use App\Classes\ValueObjects\Constants\Vouchers;
use App\Models\UserEmailVerification;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -36,19 +39,29 @@ class UserEmailVerificationLogic extends AbstractControllerLogic
/** @var VerifiesUser */
private $verifiesUser;
/** @var SendWelcomeVoucherEmail */
private $sendWelcomeVoucherEmail;
/** @var FetchesVoucher */
private $fetchesVoucher;
/**
* UserEmailVerificationLogic constructor.
* @param EmailVerificationActiveAttemptExists $emailVerificationActiveAttemptExists
* @param CompletesEmailVerificationAttempt $completesEmailVerificationAttempt
* @param FetchesEmailVerificationAttempt $fetchesEmailVerificationAttempt
* @param VerifiesUser $verifiesUser
* @param SendWelcomeVoucherEmail $sendWelcomeVoucherEmail
* @param FetchesVoucher $fetchesVoucher
*/
public function __construct(EmailVerificationActiveAttemptExists $emailVerificationActiveAttemptExists, CompletesEmailVerificationAttempt $completesEmailVerificationAttempt, FetchesEmailVerificationAttempt $fetchesEmailVerificationAttempt, VerifiesUser $verifiesUser)
public function __construct(EmailVerificationActiveAttemptExists $emailVerificationActiveAttemptExists, CompletesEmailVerificationAttempt $completesEmailVerificationAttempt, FetchesEmailVerificationAttempt $fetchesEmailVerificationAttempt, VerifiesUser $verifiesUser, SendWelcomeVoucherEmail $sendWelcomeVoucherEmail, FetchesVoucher $fetchesVoucher)
{
$this->emailVerificationActiveAttemptExists = $emailVerificationActiveAttemptExists;
$this->completesEmailVerificationAttempt = $completesEmailVerificationAttempt;
$this->fetchesEmailVerificationAttempt = $fetchesEmailVerificationAttempt;
$this->verifiesUser = $verifiesUser;
$this->sendWelcomeVoucherEmail = $sendWelcomeVoucherEmail;
$this->fetchesVoucher = $fetchesVoucher;
}
/**
@@ -68,7 +81,17 @@ class UserEmailVerificationLogic extends AbstractControllerLogic
$this->completesEmailVerificationAttempt->execute($attempt);
$this->verifiesUser->execute($attempt->user);
$user = $attempt->user;
$this->verifiesUser->execute($user);
// if (env('SENDING_EMAIL_WELCOME_VOUCHER_ENABLED', false)){
if (app()->environment('production') && env('SENDING_EMAIL_WELCOME_VOUCHER_ENABLED', false)){
try{ //In case voucher got deleted unintentionally
$voucher = $this->fetchesVoucher->execute(['code' => Vouchers::WELCOME_50_PERCENT_OFF]);
if($voucher) $this->sendWelcomeVoucherEmail::dispatch($user, $voucher, 1);
}
catch(\Exception $e){}
}
return $this->response([]);
}
@@ -0,0 +1,44 @@
<?php
namespace App\Classes\Modules\Accounts\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class KeyValuePairObject implements DataTransferObject
{
/** @var string */
private $key;
/** @var string */
private $value;
/**
* KeyValuePairObject constructor.
* @param string $key
* @param string $value
*/
public function __construct(string $key, string $value)
{
$this->key = $key;
$this->value = $value;
}
/**
* @return string
*/
public function getKey(): string
{
return $this->key;
}
/**
* @return string
*/
public function getValue(): string
{
return $this->value;
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Classes\Modules\Accounts\Services;
use App\Classes\General\Eloquent\AbstractUpdateRelationshipRecord;
use App\Classes\General\Interfaces\KeyValueInterface;
use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject;
use App\Models\KeyValuePair;
class CreatesKeyValuePair extends AbstractUpdateRelationshipRecord
{
/**
* @param KeyValueInterface $kv
* @param KeyValuePairObject $object
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(KeyValueInterface $kv, KeyValuePairObject $object) {
$model = new KeyValuePair();
$model->key = $object->getKey();
$model->value = $object->getValue();
return $this->handler($kv->attributes(), $model);
}
}
@@ -4,9 +4,11 @@ namespace App\Classes\Modules\Vouchers\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Rewards\Services\ListsUserRewards;
use App\Classes\ValueObjects\Constants\RoleTypes;
use App\Http\Resources\UserRewardResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ListUserVouchersLogic extends AbstractControllerLogic
{
@@ -40,6 +42,11 @@ class ListUserVouchersLogic extends AbstractControllerLogic
public function logic(Request $request) : JsonResponse
{
$query = $this->listsUserRewards->execute($this->listsUserRewards->deserializeFilters($request->input('filters')));
if(in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)){
$request->merge(['isAdmin' => true]);
}
return $this->collectionResponse(UserRewardResource::collection($query));
}
@@ -0,0 +1,43 @@
<?php
namespace App\Classes\Notifications;
use App\Models\User;
use App\Models\Voucher;
use Carbon\Carbon;
use Illuminate\Notifications\Messages\MailMessage;
class WelcomeVoucherEmail extends AbstractEmail
{
/** @var User */
private $user;
/** @var Voucher */
private $voucher;
/**
* WelcomeVoucherEmail constructor.
* @param User $user
* @param Voucher $voucher
*/
public function __construct(User $user, Voucher $voucher)
{
$this->user = $user;
$this->voucher = $voucher;
}
public function toMail()
{
$this->voucher->end_date = Carbon::parse($this->voucher->end_date)->format('Y-m-d');
$mailMessage = (new MailMessage)
->subject('Welcome Voucher')
->view('emails.accounts.welcome_voucher', ['user' => $this->user, 'voucher' => $this->voucher]);
return $mailMessage;
}
}
@@ -0,0 +1,8 @@
<?php
namespace App\Classes\ValueObjects\Constants;
final class Vouchers {
public const WELCOME_50_PERCENT_OFF = 'WELCOME50%OFF';
}
@@ -0,0 +1,24 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class KeyValueBasicResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
// 'id' => $this->id,
'key' => $this->key,
'value' => $this->value,
];
}
}
@@ -2,6 +2,7 @@
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserRewardResource extends JsonResource
@@ -14,6 +15,13 @@ class UserRewardResource extends JsonResource
*/
public function toArray($request)
{
$emailReminder = null;
if ($request->has('isAdmin')) {
$keyValuePairs = $this->user->attributes()->get();
$emailReminder = KeyValueBasicResource::collection($keyValuePairs);
$this->voucher->email = $emailReminder;
}
return [
'id' => $this->id,
'user_id' => $this->user_id,
+10 -1
View File
@@ -2,6 +2,7 @@
namespace App\Http\Resources;
use ArrayObject;
use Illuminate\Http\Resources\Json\JsonResource;
class VoucherResource extends JsonResource
@@ -14,9 +15,16 @@ class VoucherResource extends JsonResource
*/
public function toArray($request)
{
$filteredRedemptions = new ArrayObject([]);
if ($request->has('filters') && str_contains($request->input('filters'), "has_active_reward")) {
$filteredRedemptions = new ArrayObject([]);
}
else{
$filteredRedemptions = $this->redemptions->filter(function ($redemption) {
return $redemption->transaction && $redemption->transaction->owner;
});
}
return [
'id' => $this->id,
'name' => $this->name,
@@ -25,7 +33,8 @@ class VoucherResource extends JsonResource
'value' => (float) $this->value,
'start_date' => $this->start_date,
'end_date' => $this->end_date,
'is_redeemed' => $filteredRedemptions->count() > 0
'is_redeemed' => $filteredRedemptions->count() > 0,
'email' => $this->email ? new KeyValueBasicResource($this->email->where('key', $this->code.'_EMAIL_COUNT')->first()) : null,
];
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class KeyValuePair extends AbstractModel
{
protected $table = 'key_value_pairs';
public function owner(): MorphTo
{
return $this->morphTo();
}
}
+20 -1
View File
@@ -2,6 +2,7 @@
namespace App\Models;
use App\Classes\General\Interfaces\KeyValueInterface;
use App\Classes\General\Interfaces\Voucherifiable;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -26,7 +27,8 @@ class User extends AbstractModel implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract,
Voucherifiable
Voucherifiable,
KeyValueInterface
{
use HasRoles, Notifiable, Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail, SoftDeletes;
@@ -101,4 +103,21 @@ class User extends AbstractModel implements
{
return $this->HasMany(UserReward::class, 'user_id', 'id');
}
public function hasAttribute(string $key, $value = null): bool
{
$query = $this->attributes()->where('key', $key);
if ($value !== null) {
$query->where('value', $value);
}
return $query->exists();
}
public function attributes(): MorphMany
{
return $this->morphMany(KeyValuePair::class, 'owner');
}
}
@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateKeyValuePairsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('key_value_pairs', function (Blueprint $table) {
$table->id();
$table->string('owner_type'); //'user', 'order', 'transaction'
$table->unsignedBigInteger('owner_id');
$table->string('key');
$table->string('value');
$table->timestamps();
$table->index(['owner_type', 'owner_id']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('key_value_pairs');
}
}
@@ -9,6 +9,10 @@
<p v-if="item.voucher.type == 'AMOUNT'">RM{{ item.voucher.value/100 }} Discount</p>
<p v-if="item.voucher.type == 'PERCENT'">{{ item.voucher.value }}% Discount</p>
</div>
<div class="card-body border-top" v-if="$store.getters.isAdmin && item.voucher.email && item.voucher.email.key === item.voucher.code + '_EMAIL_COUNT'">
<button disabled type="button" v-if="item.voucher.email && item.voucher.email.key === item.voucher.code + '_EMAIL_COUNT' && item.voucher.email.value == '1'" class="btn btn-lg btn-primary">Email Reminder #1 Sent</button>
<!-- <button type="button" v-else @click="submit()" class="btn btn-lg btn-primary">Send Email Reminder #1</button> -->
</div>
<div class="card-footer text-muted">
<p v-if="item.voucher && item.voucher.is_redeemed">
Voucher claimed
@@ -25,6 +29,9 @@
<script>
import componentHandler from '../../../general/mixins/componentHandler';
export default {
methods: {
submit(){},
},
mixins: [componentHandler]
}
</script>
@@ -0,0 +1,13 @@
@extends('emails.layout.base')
@section('content')
<img style="width: 100%;" src="https://assets.izyim.com/public/images/emails/welcome50%25off.png"/>
<p style="font-size: 0.9em">Hello from CIEF! Thanks for signing up with us. Thinking about using our RMB payment transfer services? Use code {{$voucher->code}} and get a 50% discount on your first order. Why not give it a try?</p>
<p style="font-size: 0.9em">Any questions? I'm here to help. Start here: <a href="{{env('APP_URL').'/dashboard'}}">{{env('APP_URL').'/dashboard'}}</a></p>
<p style="font-size: 0.9em">叮咚!非常感谢您在我们代付网站注册, 您是否对我们的代付服务感兴趣却还在犹豫或者在因为其他因素还没正式使用呢? 如果是首次下单, 不妨使用我们专门为新用户准备的独家优惠, 只需在首次下单时使用代码【{{$voucher->code}}】,就能享有5折的手续费折扣呢! 尝试了一次, 或许你会喜欢我们公司的服务,点击以下网址开始启用吧!</p>
<p style="font-size: 0.9em"><a href="{{env('APP_URL').'/dashboard'}}">{{env('APP_URL').'/dashboard'}}</a></p>
<p style="font-size: 0.9em">如果您有任何疑问或需要更多信息,随时联系我。我们期待着能为您提供物流以及代付服务!</span></p>
<p style="font-size: 0.9em">Thanks</span></p>
<p style="font-size: 0.9em">谢谢!</span></p>
@endsection
+7 -1
View File
@@ -791,8 +791,14 @@ Route::get('/vouchers', function () {
})->name('rewards');
Route::get('/customer/vouchers/{marking}', function ($marking) {
$id = \App\Models\Company::where('reference', '=', $marking)->first()->employees->first()->id;
$company = \App\Models\Company::where('reference', '=', $marking)->first();
if($company){
$id = $company->employees->first()->id;
return view('pages.customers.reward', ['id' => $id]);
}
else{
abort(404);
}
})->name('customer.reward');
Route::get('transaction/{id}/credit_note/download', 'Transactions\GenerateCreditNotePdfController@download')->name('transaction.credit_note.download');