feat(billing): renewals, coupons, VAT numbering, billing cycles and invoice payment tracking
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
ea9a53f742
commit
25f23d541e
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Coupon extends Model
|
||||
{
|
||||
public const TYPE_PERCENT = 'percent';
|
||||
public const TYPE_FIXED = 'fixed';
|
||||
public const TYPES = [self::TYPE_PERCENT, self::TYPE_FIXED];
|
||||
|
||||
public const APPLIES_NEW = 'new';
|
||||
public const APPLIES_RENEWAL = 'renewal';
|
||||
public const APPLIES_ALL = 'all';
|
||||
public const APPLIES_TO = [self::APPLIES_NEW, self::APPLIES_RENEWAL, self::APPLIES_ALL];
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'value' => 'integer',
|
||||
'max_discount' => 'integer',
|
||||
'min_order' => 'integer',
|
||||
'usage_limit' => 'integer',
|
||||
'per_customer_limit' => 'integer',
|
||||
'product_types' => 'array',
|
||||
'product_ids' => 'array',
|
||||
'billing_cycles' => 'array',
|
||||
'starts_at' => 'datetime',
|
||||
'ends_at' => 'datetime',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
public static function normalizeCode(string $code): string
|
||||
{
|
||||
return strtoupper(trim($code));
|
||||
}
|
||||
|
||||
protected function code(): Attribute
|
||||
{
|
||||
return Attribute::set(fn (string $value) => self::normalizeCode($value));
|
||||
}
|
||||
|
||||
public function redemptions(): HasMany
|
||||
{
|
||||
return $this->hasMany(CouponRedemption::class);
|
||||
}
|
||||
|
||||
public function activeRedemptions(): HasMany
|
||||
{
|
||||
return $this->redemptions()->whereNull('released_at');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class CouponRedemption extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'amount' => 'integer',
|
||||
'released_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function coupon(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Coupon::class);
|
||||
}
|
||||
|
||||
public function customer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class);
|
||||
}
|
||||
|
||||
public function order(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Order::class);
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,10 @@ class Customer extends Authenticatable implements JWTSubject
|
||||
'birthday',
|
||||
'province_code',
|
||||
'ward_code',
|
||||
'tax_code',
|
||||
'invoice_company',
|
||||
'invoice_address',
|
||||
'invoice_email',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
+21
-1
@@ -4,11 +4,13 @@ namespace App\Models;
|
||||
|
||||
use App\Enums\Invoice\InvoiceStatus;
|
||||
use App\Models\Concerns\Markable;
|
||||
use App\Support\VatNumbering;
|
||||
use App\Traits\LogsActivity;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Invoice extends Model
|
||||
{
|
||||
@@ -23,12 +25,26 @@ class Invoice extends Model
|
||||
'due_date' => 'date',
|
||||
'paid_at' => 'datetime',
|
||||
'auto_renew' => 'boolean',
|
||||
'vat_issued_at' => 'date',
|
||||
'buyer_snapshot' => 'array',
|
||||
];
|
||||
|
||||
public static function newLookupCode(): string
|
||||
{
|
||||
return Str::upper(Str::random(10));
|
||||
}
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (Invoice $invoice) {
|
||||
$invoice->date ??= now()->toDateString();
|
||||
$invoice->lookup_code ??= static::newLookupCode();
|
||||
});
|
||||
|
||||
static::updated(function (Invoice $invoice) {
|
||||
if ($invoice->wasChanged('status')) {
|
||||
VatNumbering::issueIfAuto($invoice);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,7 +56,11 @@ class Invoice extends Model
|
||||
/** The order that originated this invoice. */
|
||||
public function clientPath(): string
|
||||
{
|
||||
return '/hoa-don/' . ($this->order?->order_num ?? $this->invoice_number);
|
||||
$path = '/hoa-don/' . ($this->order?->order_num ?? $this->invoice_number);
|
||||
|
||||
return $this->order && (int) $this->invoice_type !== 1
|
||||
? $path . '?invoice=' . $this->invoice_number
|
||||
: $path;
|
||||
}
|
||||
|
||||
/** @param iterable<Service> $services */
|
||||
|
||||
@@ -9,6 +9,7 @@ class InvoiceAttachment extends Model
|
||||
{
|
||||
public const TYPE_PAYMENT_PROOF = 'payment_proof';
|
||||
public const TYPE_DEPLOYMENT = 'deployment';
|
||||
public const TYPE_CONTRACT = 'contract';
|
||||
public const UPLOAD_RULE = 'required|file|mimes:jpg,jpeg,png,gif,webp,pdf,doc,docx,xls,xlsx|max:51200';
|
||||
|
||||
protected $table = 'invoice_attachments';
|
||||
|
||||
@@ -22,6 +22,13 @@ class Order extends Model
|
||||
'paid_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (Order $order) {
|
||||
$order->date ??= now()->toDateString();
|
||||
});
|
||||
}
|
||||
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo(Customer::class);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Concerns\Markable;
|
||||
use App\Support\ServiceExpirySync;
|
||||
use App\Traits\LogsActivity;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -25,6 +26,12 @@ class Service extends Model
|
||||
$service->type = Product::query()->where('id', $service->product_id)->value('product_type');
|
||||
}
|
||||
});
|
||||
|
||||
static::saved(function (Service $service) {
|
||||
if ($service->wasRecentlyCreated || $service->wasChanged(['expires_at', 'vm_id'])) {
|
||||
ServiceExpirySync::toVmware($service);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected $table = 'services';
|
||||
|
||||
Reference in New Issue
Block a user