refactor: replace custom CRM with Monica fork
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s

This commit is contained in:
Kroonk
2026-05-22 16:19:55 +02:00
parent 38cc4c09ca
commit a213a1dba0
2215 changed files with 242567 additions and 6015 deletions

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Place;
use App\Models\Account\Account;
use App\Interfaces\LabelInterface;
use App\Models\ModelBindingWithContact as Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* An Address is where the contact lives (or lived).
* The actual address (street name etc…) is represented with a Place object.
*/
class Address extends Model implements LabelInterface
{
use HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* All of the relationships to be touched.
*
* @var array
*/
protected $touches = ['contact'];
protected $table = 'addresses';
/**
* Get the account record associated with the address.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the address.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the place record associated with the address.
*
* @return BelongsTo
*/
public function place()
{
return $this->belongsTo(Place::class);
}
/**
* Get the label associated with the contact.
*
* @return BelongsToMany
*/
public function labels()
{
return $this->belongsToMany(ContactFieldLabel::class);
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Account;
use App\Models\Instance\Emotion\Emotion;
use App\Models\ModelBindingWithContact as Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @property Contact $contact
*/
class Call extends Model
{
use HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that should be mutated to dates.
*
* @var array<string>
*/
protected $dates = ['called_at'];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'contact_called' => 'boolean',
];
/**
* Eager load with every call.
*/
protected $with = [
'account',
'contact',
];
/**
* Get the account record associated with the call.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the call.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the emotion records associated with the call.
*
* @return BelongsToMany
*/
public function emotions()
{
return $this->belongsToMany(Emotion::class, 'emotion_call', 'call_id', 'emotion_id')
->withPivot('account_id', 'contact_id')
->withTimestamps();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Account;
use App\Interfaces\LabelInterface;
use App\Models\ModelBindingWithContact as Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class ContactField extends Model implements LabelInterface
{
use HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* All of the relationships to be touched.
*
* @var array
*/
protected $touches = ['contact'];
/**
* Get the account record associated with the contact field.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the contact field.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the label associated with the contact.
*
* @return BelongsToMany
*/
public function labels()
{
return $this->belongsToMany(ContactFieldLabel::class);
}
/**
* Get the type associated with the contact field.
*
* @return BelongsTo
*/
public function contactFieldType()
{
return $this->belongsTo(ContactFieldType::class);
}
/**
* Scope a query to only include contact field of email type.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeEmail($query)
{
return $query->whereHas('contactFieldType', function ($query) {
$query->where('type', '=', ContactFieldType::EMAIL);
});
}
/**
* Scope a query to only include contact field of phone type.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePhone($query)
{
return $query->whereHas('contactFieldType', function ($query) {
$query->where('type', '=', ContactFieldType::PHONE);
});
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Models\Contact;
use App\Models\Account\Account;
use App\Models\ModelBinding as Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property string $label
* @property string $label_i18n
*/
class ContactFieldLabel extends Model
{
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
protected $table = 'contact_field_labels';
/** @var array<string> */
public static $standardLabels = [
'home',
'work',
'cell',
'fax',
'pager',
'main',
'other',
];
/**
* Get the account record associated with the contact field type.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Account;
use App\Models\ModelBinding as Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ContactFieldType extends Model
{
use HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
protected $table = 'contact_field_types';
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'delible' => 'boolean',
];
/**
* Email type contact field.
*
* @var string
*/
public const EMAIL = 'email';
/**
* Phone type contact field.
*
* @var string
*/
public const PHONE = 'phone';
/**
* Get the account record associated with the contact field type.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the conversations associated with the contact field type.
*
* @return HasMany
*/
public function conversations()
{
return $this->hasMany(Conversation::class);
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Account;
use App\Models\ModelBindingHasher as Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Conversation extends Model
{
use HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that should be mutated to dates.
*
* @var array<string>
*/
protected $dates = ['happened_at'];
/**
* Get the account record associated with the conversation.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the conversation.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the contact field type record associated with the conversation.
*
* @return BelongsTo
*/
public function contactFieldType()
{
return $this->belongsTo(ContactFieldType::class);
}
/**
* Get the Message records associated with the conversation.
*
* @return HasMany
*/
public function messages()
{
return $this->hasMany(Message::class);
}
}

103
app/Models/Contact/Debt.php Normal file
View File

@@ -0,0 +1,103 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Account;
use App\Traits\AmountFormatter;
use App\Models\Settings\Currency;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\ModelBindingHasherWithContact as Model;
/**
* @property Account $account
* @property Contact $contact
* @property int $amount
*
* @method static Builder due()
* @method static Builder owed()
* @method static Builder inProgress()
*/
class Debt extends Model
{
use AmountFormatter, HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* Eager load with every debt.
*/
protected $with = [
'account',
'contact',
];
/**
* Get the account record associated with the debt.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the debt.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the currency record associated with the debt.
*
* @return BelongsTo
*/
public function currency()
{
return $this->belongsTo(Currency::class);
}
/**
* Limit results to unpaid/unreceived debt.
*
* @param Builder $query
* @return Builder
*/
public function scopeInProgress(Builder $query)
{
return $query->where('status', 'inprogress');
}
/**
* Limit results to due debt.
*
* @param Builder $query
* @return Builder
*/
public function scopeDue(Builder $query)
{
return $query->where('in_debt', 'yes');
}
/**
* Limit results to owed debt.
*
* @param Builder $query
* @return Builder
*/
public function scopeOwed(Builder $query)
{
return $query->where('in_debt', 'no');
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Helpers\StorageHelper;
use App\Models\Account\Account;
use App\Models\ModelBinding as Model;
use Illuminate\Support\Facades\Storage;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
class Document extends Model
{
use HasUuid;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'documents';
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'number_of_downloads' => 'integer',
];
/**
* Get the account record associated with the document.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the document.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the download link.
*
* @return string
*/
public function getDownloadLink(): string
{
if (config('filesystems.default_visibility') === 'public') {
return asset(StorageHelper::disk(config('filesystems.default'))->url($this->new_filename));
}
return route('storage', ['file' => $this->new_filename]);
}
/**
* Gets the data-url format of the document.
*
* @return string|null
*/
public function dataUrl(): ?string
{
try {
$url = $this->new_filename;
$file = StorageHelper::disk(config('filesystems.default'))->get($url);
return sprintf('data:%s;base64,%s',
$this->mime_type,
base64_encode($file)
);
} catch (FileNotFoundException $e) {
return null;
}
}
/**
* Delete the model from the database.
*
* @return bool|null
*/
public function delete()
{
try {
Storage::disk(config('filesystems.default'))
->delete($this->new_filename);
} catch (FileNotFoundException $e) {
// continue
}
return parent::delete();
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Account;
use App\Models\ModelBinding as Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Gender extends Model
{
use HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'name',
'type',
'account_id',
];
/**
* Male type gender.
*
* @var string
*/
public const MALE = 'M';
/**
* Female type gender.
*
* @var string
*/
public const FEMALE = 'F';
/**
* Other type gender.
*
* @var string
*/
public const OTHER = 'O';
/**
* Unknown type gender.
*
* @var string
*/
public const UNKNOWN = 'U';
/**
* None type gender.
*
* @var string
*/
public const NONE = 'N';
public const LIST = ['M', 'F', 'O', 'U', 'N'];
/**
* Get the account record associated with the gender.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact records associated with the gender.
*
* @return HasMany
*/
public function contacts()
{
return $this->hasMany(Contact::class);
}
/**
* Is this gender the default account one?.
*
* @return bool
*/
public function isDefault(): bool
{
return $this->account->default_gender_id === $this->id;
}
}

155
app/Models/Contact/Gift.php Normal file
View File

@@ -0,0 +1,155 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Photo;
use App\Models\Account\Account;
use App\Traits\AmountFormatter;
use Illuminate\Database\Eloquent\Builder;
use App\Models\ModelBindingWithContact as Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @property Account $account
* @property Contact $contact
* @property Contact|null $recipient
* @property string $name
* @property string $comment
* @property string $url
* @property Contact $is_for
*
* @method static Builder offered()
* @method static Builder isIdea()
*/
class Gift extends Model
{
use AmountFormatter, HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that should be mutated to dates.
*
* @var array<string>
*/
protected $dates = [
'date',
];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
];
/**
* Get the account record associated with the gift.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the gift.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the contact record associated with the gift.
*
* @return HasOne
*/
public function recipient()
{
return $this->hasOne(Contact::class, 'id', 'is_for');
}
/**
* Get the photos record associated with the gift.
*
* @return BelongsToMany
*/
public function photos()
{
return $this->belongsToMany(Photo::class)->withTimestamps();
}
/**
* Limit results to already offered gifts.
*
* @param Builder $query
* @return Builder
*/
public function scopeOffered(Builder $query)
{
return $query->where('status', 'offered');
}
/**
* Limit results to gifts at the idea stage.
*
* @param Builder $query
* @return Builder
*/
public function scopeIsIdea(Builder $query)
{
return $query->where('status', 'idea');
}
/**
* Check whether the gift is meant for a particular member
* of the contact's family.
*
* @return bool
*/
public function hasParticularRecipient()
{
return $this->is_for !== null && $this->is_for !== 0;
}
/**
* Set the recipient for the gift.
*
* @param int $value
* @return void
*/
public function setRecipientAttribute($value): void
{
$this->attributes['is_for'] = $value;
}
/**
* Get the name of the recipient for this gift.
*
* @return string|null
*/
public function getRecipientNameAttribute(): ?string
{
if ($this->hasParticularRecipient()) {
$recipient = $this->recipient;
if (! is_null($recipient)) {
return $recipient->first_name;
}
}
return null;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Account;
use App\Models\ModelBinding as Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class LifeEvent extends Model
{
use HasUuid;
protected $table = 'life_events';
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'name',
'note',
'happened_at',
'account_id',
'contact_id',
'reminder_id',
'life_event_type_id',
'happened_at_month_unknown',
'happened_at_day_unknown',
];
/**
* The attributes that should be mutated to dates.
*
* @var array<string>
*/
protected $dates = ['happened_at'];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'happened_at_month_unknown' => 'boolean',
'happened_at_day_unknown' => 'boolean',
];
/**
* Get the account record associated with the life event.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the life event.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the life event type record associated with the life event.
*
* @return BelongsTo
*/
public function lifeEventType()
{
return $this->belongsTo(LifeEventType::class, 'life_event_type_id');
}
/**
* Get the reminder record associated with the life event.
*
* @return BelongsTo
*/
public function reminder()
{
return $this->belongsTo(Reminder::class);
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Account;
use App\Models\ModelBinding as Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class LifeEventCategory extends Model
{
use HasUuid;
protected $table = 'life_event_categories';
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'name',
'account_id',
'default_life_event_category_key',
'core_monica_data',
];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'core_monica_data' => 'boolean',
];
/**
* Get the account record associated with the life event category.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the life event type records associated with the category.
*
* @return HasMany
*/
public function lifeEventTypes()
{
return $this->hasMany(LifeEventType::class);
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Account;
use App\Models\ModelBinding as Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class LifeEventType extends Model
{
use HasUuid;
protected $table = 'life_event_types';
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'name',
'account_id',
'life_event_category_id',
'default_life_event_type_key',
'core_monica_data',
];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'core_monica_data' => 'boolean',
];
/**
* Get the account record associated with the life event type.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the life event category record associated with the life event type.
*
* @return BelongsTo
*/
public function lifeEventCategory()
{
return $this->belongsTo(LifeEventCategory::class, 'life_event_category_id');
}
/**
* Get the Life event records associated with the life event Type.
*
* @return HasMany
*/
public function lifeEvents()
{
return $this->hasMany(LifeEvent::class);
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Account;
use App\Models\ModelBindingHasher as Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Message extends Model
{
use HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that should be mutated to dates.
*
* @var array<string>
*/
protected $dates = ['written_at'];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'written_by_me' => 'boolean',
];
/**
* Get the account record associated with the message.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the message.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the Conversation records associated with the message.
*
* @return BelongsTo
*/
public function conversation()
{
return $this->belongsTo(Conversation::class);
}
}

124
app/Models/Contact/Note.php Normal file
View File

@@ -0,0 +1,124 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Helpers\DateHelper;
use App\Models\Account\Account;
use Illuminate\Database\Eloquent\Builder;
use App\Models\ModelBindingWithContact as Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property Account $account
* @property Contact $contact
* @property string $parsed_body
* @property string $body
* @property bool $is_favorited
* @property \Illuminate\Support\Carbon|null $favorited_at
*/
class Note extends Model
{
use HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'is_favorited' => 'boolean',
];
protected $dates = [
'favorited_at',
];
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'account_id',
'contact_id',
'body',
'is_favorited',
];
/**
* Eager load with every note.
*/
protected $with = [
'account',
'contact',
];
/**
* Get the account record associated with the note.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the note.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Limit notes to favorited ones.
*
* @param Builder $query
* @return Builder
*/
public function scopeFavorited(Builder $query)
{
return $query->where('is_favorited', true);
}
/**
* Get the description of a note.
*
* @return string
*/
public function getBody()
{
return $this->body;
}
/**
* Gets the activity date for this note.
*
* @return string
*/
public function getCreatedAt()
{
return DateHelper::getShortDate($this->created_at);
}
/**
* Gets the content of the activity and formats it for the email.
*
* @return string
*/
public function getContent()
{
return wordwrap($this->getBody(), 75);
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\Models\Contact;
use App\Models\Account\Account;
use App\Models\Account\Company;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Occupation extends Model
{
protected $table = 'occupations';
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'account_id',
'contact_id',
'company_id',
'title',
'description',
'salary',
'salary_unit',
'currently_works_here',
'start_date',
'end_date',
];
/**
* Valid value for salary unit.
*
* @var array
*/
public static $salaryUnits = [
'year', 'month', 'week', 'day', 'hour',
];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'start_date' => 'datetime:Y-m-d',
'end_date' => 'datetime:Y-m-d',
];
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* Get the account record associated with the occupation.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the occupation.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the company record associated with the occupation.
*
* @return BelongsTo
*/
public function company()
{
return $this->belongsTo(Company::class);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Models\Contact;
use App\Traits\HasUuid;
use App\Models\Account\Account;
use App\Models\ModelBinding as Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Pet extends Model
{
use HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* Get the account record associated with the pet.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the pet.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the contact record associated with the pet.
*
* @return BelongsTo
*/
public function petCategory()
{
return $this->belongsTo(PetCategory::class);
}
/**
* Set the name to null if it's an empty string.
*
* @param string $value
* @return void
*/
public function setNameAttribute($value)
{
$this->attributes['name'] = $value ?: null;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Models\Contact;
use Illuminate\Database\Eloquent\Model;
class PetCategory extends Model
{
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
protected $table = 'pet_categories';
/**
* Scope a query to only include pet categories that are considered `common`.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeCommon($query)
{
return $query->where('is_common', 1);
}
}

View File

@@ -0,0 +1,199 @@
<?php
namespace App\Models\Contact;
use Carbon\Carbon;
use App\Traits\HasUuid;
use App\Models\User\User;
use App\Helpers\DateHelper;
use App\Models\Account\Account;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\ModelBindingHasherWithContact as Model;
/**
* A reminder has two states: active and inactive.
* An inactive reminder is basically a one_time reminder that has already be
* sent once and has been marked inactive so we don't schedule it again.
*
* @property string $next_expected_date_human_readable
* @property string $next_expected_date
*/
class Reminder extends Model
{
use HasUuid;
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'is_birthday' => 'boolean',
'delible' => 'boolean',
'inactive' => 'boolean',
'initial_date' => 'date:Y-m-d',
];
/**
* Valid value for frequency type.
*
* @var array
*/
public static $frequencyTypes = [
'one_time', 'week', 'month', 'year',
];
/**
* Get the account record associated with the reminder.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the reminder.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Get the Reminder Outbox records associated with the account.
*
* @return HasMany
*/
public function reminderOutboxes()
{
return $this->hasMany(ReminderOutbox::class);
}
/**
* Scope a query to only include active reminders.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('inactive', false);
}
/**
* Test if this reminder is the contact's birthday reminder.
*
* @return bool
*/
public function isBirthdayReminder(): bool
{
return $this->contact !== null
&& $this->contact->birthday_reminder_id === $this->id;
}
/**
* Calculate the next expected date for this reminder.
*
* @return Carbon
*/
public function calculateNextExpectedDate($date = null)
{
if (is_null($date)) {
$date = $this->initial_date;
}
while ($date->isPast()) {
$date = DateHelper::addTimeAccordingToFrequencyType($date, $this->frequency_type, $this->frequency_number);
}
if ($date->isToday()) {
$date = DateHelper::addTimeAccordingToFrequencyType($date, $this->frequency_type, $this->frequency_number);
}
return $date;
}
/**
* Calculate the next expected date using user timezone for this reminder.
*
* @return Carbon
*/
public function calculateNextExpectedDateOnTimezone()
{
$date = $this->initial_date;
$date = Carbon::create($date->year, $date->month, $date->day, 0, 0, 0,
DateHelper::getTimezone() ?? config('app.timezone'));
return $this->calculateNextExpectedDate($date);
}
/**
* Schedule the reminder to be sent.
*
* @param User $user
* @return void
*/
public function schedule(User $user)
{
// remove any existing scheduled reminders
$this->reminderOutboxes->each->delete();
// when should we send this reminder?
$triggerDate = $this->calculateNextExpectedDate();
// schedule the reminder in the outbox, one for each user of the account
ReminderOutbox::create([
'account_id' => $this->account_id,
'reminder_id' => $this->id,
'user_id' => $user->id,
'planned_date' => $triggerDate,
'nature' => 'reminder',
]);
$this->scheduleNotifications($triggerDate, $user);
}
/**
* Create all the notifications that are supposed to be sent
* 30 and 7 days prior to the actual reminder.
*
* @param Carbon $triggerDate
* @param User $user
* @return void
*/
public function scheduleNotifications(Carbon $triggerDate, User $user)
{
$date = $triggerDate->toDateString();
$reminderRules = $this->account->reminderRules()->where('active', 1)->get();
foreach ($reminderRules as $reminderRule) {
$datePrior = Carbon::createFromFormat('Y-m-d', $date)
->subDays($reminderRule->number_of_days_before);
if ($datePrior->lessThanOrEqualTo(now())) {
continue;
}
ReminderOutbox::create([
'account_id' => $this->account_id,
'reminder_id' => $this->id,
'user_id' => $user->id,
'planned_date' => $datePrior->toDateString(),
'nature' => 'notification',
'notification_number_days_before' => $reminderRule->number_of_days_before,
]);
}
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Models\Contact;
use App\Models\User\User;
use App\Models\Account\Account;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\ModelBindingHasherWithContact as Model;
/**
* @property Account $account
* @property int $account_id
* @property Contact $contact
* @property User $user
* @property int $user_id
* @property Reminder|null $reminder
* @property int $reminder_id
* @property string $nature
* @property \Illuminate\Support\Carbon|null $planned_date
* @property int $notification_number_days_before
*/
class ReminderOutbox extends Model
{
protected $table = 'reminder_outbox';
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that should be mutated to dates.
*
* @var array<string>
*/
protected $dates = [
'planned_date',
];
/**
* Get the account record associated with the reminder.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the reminder record associated with the reminder.
*
* @return BelongsTo
*/
public function reminder()
{
return $this->belongsTo(Reminder::class);
}
/**
* Get the user record associated with the reminder.
*
* @return BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Models\Contact;
use App\Models\Account\Account;
use App\Models\ModelBinding as Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ReminderRule extends Model
{
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
protected $table = 'reminder_rules';
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'active' => 'boolean',
];
/**
* Get the account record associated with the reminder.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Models\Contact;
use App\Models\Account\Account;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'name',
'name_slug',
'account_id',
];
/**
* Get the account record associated with the tag.
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contacts record associated with the tag.
*/
public function contacts()
{
return $this->belongsToMany(Contact::class)->withPivot('account_id')->withTimestamps();
}
/**
* Get the tags with the contact count.
*/
public static function contactsCount()
{
return DB::table('contact_tag')->selectRaw('COUNT(tag_id) AS contact_count, name, tag_id AS id')
->join('tags', function ($join) {
$join->on('tags.id', '=', 'contact_tag.tag_id')
->on('tags.account_id', '=', 'contact_tag.account_id');
})
->join('contacts', function ($join) {
$join->on('contacts.id', '=', 'contact_tag.contact_id')
->on('contacts.account_id', '=', 'contact_tag.account_id');
})
->where([
'tags.account_id' => auth()->user()->account_id,
'contacts.address_book_id' => null,
])
->groupBy('tag_id')
->get()
->sortByCollator('name');
}
}

101
app/Models/Contact/Task.php Normal file
View File

@@ -0,0 +1,101 @@
<?php
namespace App\Models\Contact;
use App\Models\Account\Account;
use App\Models\ModelBinding as Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $id
* @property Account $account
* @property Contact|null $contact
* @property string $title
* @property string $description
* @property string $uuid
* @property bool $completed
* @property \Carbon\Carbon|null $completed_at
*
* @method static Builder completed()
* @method static Builder inProgress()
*/
class Task extends Model
{
/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = ['id'];
/**
* The attributes that should be mutated to dates.
*
* @var array<string>
*/
protected $dates = [
'completed_at',
'archived_at',
];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'completed' => 'boolean',
'archived' => 'boolean',
];
/**
* Eager load with every task.
*/
protected $with = [
'account',
'contact',
];
/**
* Get the account record associated with the task.
*
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Get the contact record associated with the task.
*
* @return BelongsTo
*/
public function contact()
{
return $this->belongsTo(Contact::class);
}
/**
* Limit tasks to completed ones.
*
* @param Builder $query
* @return Builder
*/
public function scopeCompleted(Builder $query)
{
return $query->where('completed', true);
}
/**
* Limit tasks to in-progress ones.
*
* @param Builder $query
* @return Builder
*/
public function scopeInProgress(Builder $query)
{
return $query->where('completed', false);
}
}