feat: implement cash and stock handling traits for improved transaction management

This commit is contained in:
Yoga Pangestu 2026-08-04 21:57:38 +07:00
parent 8970c47a4a
commit d90d2261ac
21 changed files with 197 additions and 312 deletions

View File

@ -13,7 +13,7 @@
class AdminSettingsService
{
public function __construct(
private S3PresignedService $s3Service = new S3PresignedService,
private readonly S3PresignedService $s3Service,
) {}
public function getSystemData(): array

View File

@ -5,6 +5,7 @@
use App\Enums\CashTransactionType;
use App\Models\CashAccount;
use App\Models\CashTransaction;
use App\Services\Concerns\HandlesCashTransactions;
use App\Services\Concerns\RegistersMedia;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
@ -16,10 +17,10 @@
class CashAccountService
{
use RegistersMedia;
use HandlesCashTransactions, RegistersMedia;
public function __construct(
private S3PresignedService $s3Service = new S3PresignedService,
private readonly S3PresignedService $s3Service,
) {}
public function get(): ?CashAccount
@ -94,27 +95,14 @@ private function formatTransaction(CashTransaction $transaction): array
public function deposit(array $data): CashTransaction
{
$transaction = DB::transaction(function () use ($data) {
$cashAccount = CashAccount::firstOrFail();
$newBalance = $cashAccount->balance + $data['amount'];
$transaction = DB::transaction(fn () => $this->creditCash(
amount: $data['amount'],
description: $data['description'],
));
$cashAccount->update(['balance' => $newBalance]);
$transaction = CashTransaction::create([
'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(),
'amount' => $data['amount'],
'balance_after' => $newBalance,
'type' => CashTransactionType::DEPOSIT,
'description' => $data['description'],
]);
if (! empty($data['receipt_key'])) {
$this->registerMedia($transaction, $data['receipt_key'], 'receipts', ['thumb' => true], $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
}
return $transaction;
});
if (! empty($data['receipt_key'])) {
$this->registerMedia($transaction, $data['receipt_key'], 'receipts', ['thumb' => true], $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
}
NotificationService::notify(
roles: ['Owner', 'Developer', 'Direktur', 'Admin Toko'],
@ -128,33 +116,15 @@ public function deposit(array $data): CashTransaction
public function withdrawal(array $data): CashTransaction
{
$transaction = DB::transaction(function () use ($data) {
$cashAccount = CashAccount::firstOrFail();
$transaction = DB::transaction(fn () => $this->debitCash(
amount: $data['amount'],
description: $data['description'],
type: CashTransactionType::WITHDRAWAL,
));
if ($cashAccount->balance < $data['amount']) {
throw ValidationException::withMessages([
'amount' => 'Saldo tidak mencukupi.',
]);
}
$newBalance = $cashAccount->balance - $data['amount'];
$cashAccount->update(['balance' => $newBalance]);
$transaction = CashTransaction::create([
'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(),
'amount' => $data['amount'],
'balance_after' => $newBalance,
'type' => CashTransactionType::WITHDRAWAL,
'description' => $data['description'],
]);
if (! empty($data['receipt_key'])) {
$this->registerMedia($transaction, $data['receipt_key'], 'receipts', ['thumb' => true], $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
}
return $transaction;
});
if (! empty($data['receipt_key'])) {
$this->registerMedia($transaction, $data['receipt_key'], 'receipts', ['thumb' => true], $data['file_size'] ?? null, $data['file_mime_type'] ?? null);
}
NotificationService::notify(
roles: ['Owner', 'Developer', 'Direktur', 'Admin Toko'],

View File

@ -7,6 +7,7 @@
use App\Models\CashAccount;
use App\Models\CashTransaction;
use App\Models\EmployeeAdvance;
use App\Services\Concerns\HandlesCashTransactions;
use App\Services\NotificationService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
@ -15,7 +16,8 @@
class EmployeeAdvanceService
{
public function getAll(): Collection
use HandlesCashTransactions;
public function getAll(array $filters = []): Collection
{
return EmployeeAdvance::select('id', 'employee_id', 'amount', 'paid_amount', 'description', 'due_date', 'status', 'created_at')
->with(['employee.user.userProfile'])
@ -23,7 +25,7 @@ public function getAll(): Collection
->get();
}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
return EmployeeAdvance::query()
->select('id', 'employee_id', 'amount', 'paid_amount', 'description', 'due_date', 'status', 'created_at')
@ -36,30 +38,16 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
public function create(array $data): EmployeeAdvance
{
$employeeAdvance = DB::transaction(function () use ($data) {
$cashAccount = CashAccount::firstOrFail();
$employee = auth()->user()->employee;
if (! $employee) {
throw new \Exception('Anda tidak terdaftar sebagai karyawan.');
}
if ($cashAccount->balance < $data['amount']) {
throw ValidationException::withMessages([
'amount' => 'Saldo tidak mencukupi.',
]);
}
$newBalance = $cashAccount->balance - $data['amount'];
$cashAccount->update(['balance' => $newBalance]);
$cashTransaction = CashTransaction::create([
'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(),
'amount' => $data['amount'],
'balance_after' => $newBalance,
'type' => CashTransactionType::EXPENSE,
'description' => 'Kasbon: '.$data['description'],
]);
$cashTransaction = $this->debitCash(
amount: $data['amount'],
description: 'Kasbon: '.$data['description'],
);
return EmployeeAdvance::create([
'cash_transaction_id' => $cashTransaction->id,
@ -155,19 +143,10 @@ public function approve(EmployeeAdvance $employeeAdvance): EmployeeAdvance
public function pay(EmployeeAdvance $employeeAdvance): EmployeeAdvance
{
$employeeAdvance = DB::transaction(function () use ($employeeAdvance) {
$cashAccount = CashAccount::firstOrFail();
$newBalance = $cashAccount->balance + $employeeAdvance->amount;
$cashAccount->update(['balance' => $newBalance]);
$cashTransaction = CashTransaction::create([
'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(),
'amount' => $employeeAdvance->amount,
'balance_after' => $newBalance,
'type' => CashTransactionType::DEPOSIT,
'description' => 'Pembayaran kasbon: '.$employeeAdvance->description,
]);
$cashTransaction = $this->creditCash(
amount: $employeeAdvance->amount,
description: 'Pembayaran kasbon: '.$employeeAdvance->description,
);
$employeeAdvance->update([
'status' => EmployeeAdvanceStatus::PAID,

View File

@ -6,6 +6,7 @@
use App\Models\CashAccount;
use App\Models\CashTransaction;
use App\Models\Expense;
use App\Services\Concerns\HandlesCashTransactions;
use App\Services\Concerns\RegistersMedia;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
@ -17,13 +18,13 @@
class ExpenseService
{
use RegistersMedia;
use HandlesCashTransactions, RegistersMedia;
public function __construct(
private S3PresignedService $s3Service = new S3PresignedService,
private readonly S3PresignedService $s3Service,
) {}
public function getAll(): Collection
public function getAll(array $filters = []): Collection
{
return Expense::select('id', 'created_by_id', 'amount', 'description', 'created_at')
->with('createdBy.userProfile', 'media')
@ -32,7 +33,7 @@ public function getAll(): Collection
->map(fn (Expense $expense) => $this->formatExpense($expense));
}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
$paginator = Expense::query()
->select('id', 'created_by_id', 'amount', 'description', 'created_at')
@ -78,25 +79,10 @@ private function formatExpense(Expense $expense): array
public function create(array $data): Expense
{
$expense = DB::transaction(function () use ($data) {
$cashAccount = CashAccount::firstOrFail();
if ($cashAccount->balance < $data['amount']) {
throw ValidationException::withMessages([
'amount' => 'Saldo tidak mencukupi.',
]);
}
$newBalance = $cashAccount->balance - $data['amount'];
$cashAccount->update(['balance' => $newBalance]);
$cashTransaction = CashTransaction::create([
'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(),
'amount' => $data['amount'],
'balance_after' => $newBalance,
'type' => CashTransactionType::EXPENSE,
'description' => $data['description'],
]);
$cashTransaction = $this->debitCash(
amount: $data['amount'],
description: $data['description'],
);
$expense = Expense::create([
'cash_transaction_id' => $cashTransaction->id,

View File

@ -9,6 +9,7 @@
use App\Models\CashTransaction;
use App\Models\Payroll;
use App\Models\PayrollPeriod;
use App\Services\Concerns\HandlesCashTransactions;
use App\Services\NotificationService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
@ -17,7 +18,8 @@
class PayrollPeriodService
{
public function getAll(): Collection
use HandlesCashTransactions;
public function getAll(array $filters = []): Collection
{
return PayrollPeriod::select('id', 'year', 'month', 'status', 'closed_at', 'created_at')
->withCount('payrolls')
@ -35,7 +37,7 @@ public function getAll(): Collection
->get();
}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
return PayrollPeriod::query()
->select('id', 'year', 'month', 'status', 'closed_at', 'created_at')
@ -123,19 +125,10 @@ public function pay(Payroll $payroll): Payroll
}
$payroll = DB::transaction(function () use ($payroll) {
$cashAccount = CashAccount::firstOrFail();
$newBalance = $cashAccount->balance + $payroll->total_amount;
$cashAccount->update(['balance' => $newBalance]);
$cashTransaction = CashTransaction::create([
'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(),
'amount' => $payroll->total_amount,
'balance_after' => $newBalance,
'type' => CashTransactionType::DEPOSIT,
'description' => 'Pembayaran gaji karyawan',
]);
$cashTransaction = $this->creditCash(
amount: $payroll->total_amount,
description: 'Pembayaran gaji karyawan',
);
$payroll->update([
'status' => PayrollStatus::PAID,

View File

@ -16,7 +16,7 @@ class AttendanceService
use RegistersMedia;
public function __construct(
private S3PresignedService $s3Service = new S3PresignedService,
private readonly S3PresignedService $s3Service,
) {}
public function getAll(): Collection

View File

@ -19,7 +19,7 @@ class CuttingService
use RegistersMedia;
public function __construct(
private S3PresignedService $s3Service = new S3PresignedService,
private readonly S3PresignedService $s3Service,
) {}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
@ -342,36 +342,12 @@ public function update(Cutting $cutting, array $data): Cutting
'updated_at' => $now,
]);
$this->syncCuttingPhoto($cutting, $data);
$this->syncPhoto($cutting, $data);
return $cutting;
});
}
private function syncCuttingPhoto(Cutting $cutting, array $data): void
{
if (! array_key_exists('photo_key', $data)) {
return;
}
$currentKey = $cutting->getFirstMedia('photos')?->file_name;
if ($data['photo_key'] === $currentKey) {
return;
}
$cutting->clearMediaCollection('photos');
if (! empty($data['photo_key'])) {
$this->registerMedia(
model: $cutting,
s3Key: $data['photo_key'],
collectionName: 'photos',
orderColumn: 1,
);
}
}
public function delete(Cutting $cutting): bool
{
return DB::transaction(function () use ($cutting) {

View File

@ -18,7 +18,7 @@ class PurchaseService
use RegistersMedia;
public function __construct(
private S3PresignedService $s3Service = new S3PresignedService,
private readonly S3PresignedService $s3Service,
) {}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
@ -455,36 +455,12 @@ public function update(Purchase $purchase, array $data): Purchase
}
DB::table('purchase_items')->insert($itemRows);
$this->syncPurchasePhoto($purchase, $data);
$this->syncPhoto($purchase, $data);
return $purchase;
});
}
private function syncPurchasePhoto(Purchase $purchase, array $data): void
{
if (! array_key_exists('photo_key', $data)) {
return;
}
$currentKey = $purchase->getFirstMedia('photos')?->file_name;
if ($data['photo_key'] === $currentKey) {
return;
}
$purchase->clearMediaCollection('photos');
if (! empty($data['photo_key'])) {
$this->registerMedia(
model: $purchase,
s3Key: $data['photo_key'],
collectionName: 'photos',
orderColumn: 1,
);
}
}
public function delete(Purchase $purchase): bool
{
return DB::transaction(function () use ($purchase) {

View File

@ -8,6 +8,7 @@
use App\Models\ProductVariant;
use App\Models\Restock;
use App\Models\RestockItem;
use App\Services\Concerns\HasStockAdjustment;
use App\Services\Concerns\RegistersMedia;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
@ -16,15 +17,10 @@
class RestockService
{
use RegistersMedia;
private const QUALITY_STOCK_MAP = [
ProductStockQuality::GOOD->value => 'stock',
ProductStockQuality::REJECT->value => 'reject_stock',
];
use HasStockAdjustment, RegistersMedia;
public function __construct(
private S3PresignedService $s3Service = new S3PresignedService,
private readonly S3PresignedService $s3Service,
) {}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
@ -240,45 +236,4 @@ private function buildItemRows(array $items, string $stockType, $now, int &$subt
})->toArray();
}
private function applyStock(array $items, string $stockType, int $sign): void
{
foreach ($items as $item) {
$this->adjustVariantStock($item['product_variant_id'], $item['quantity'], $sign, $stockType);
}
}
private function adjustVariantStock(int $variantId, int $quantity, int $sign, string $stockType): void
{
$field = self::QUALITY_STOCK_MAP[$stockType] ?? 'stock';
if ($sign > 0) {
ProductVariant::whereKey($variantId)->increment($field, $quantity);
} else {
ProductVariant::whereKey($variantId)->decrement($field, $quantity);
}
}
private function syncPhoto(Restock $restock, array $data): void
{
if (! array_key_exists('photo_key', $data)) {
return;
}
$currentKey = $restock->getFirstMedia('photos')?->file_name;
if ($data['photo_key'] === $currentKey) {
return;
}
$restock->clearMediaCollection('photos');
if (! empty($data['photo_key'])) {
$this->registerMedia(
model: $restock,
s3Key: $data['photo_key'],
collectionName: 'photos',
orderColumn: 1,
);
}
}
}

View File

@ -13,6 +13,7 @@
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\User;
use App\Services\Concerns\HasStockAdjustment;
use App\Services\Concerns\RegistersMedia;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
@ -21,12 +22,7 @@
class TransactionService
{
use RegistersMedia;
private const QUALITY_STOCK_MAP = [
ProductStockQuality::GOOD->value => 'stock',
ProductStockQuality::REJECT->value => 'reject_stock',
];
use HasStockAdjustment, RegistersMedia;
private const SELLING_PRICE_MAP = [
PriceType::DISTRIBUTOR->value => PriceType::DISTRIBUTOR,
@ -39,7 +35,7 @@ class TransactionService
];
public function __construct(
private S3PresignedService $s3Service = new S3PresignedService,
private readonly S3PresignedService $s3Service,
) {}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
@ -378,48 +374,6 @@ private function buildItemRows(array $items, string $stockType, string $priceTyp
})->toArray();
}
private function applyStock(array $items, string $stockType, int $sign): void
{
foreach ($items as $item) {
$this->adjustVariantStock($item['product_variant_id'], $item['quantity'], $sign, $stockType);
}
}
private function adjustVariantStock(int $variantId, int $quantity, int $sign, string $stockType): void
{
$field = self::QUALITY_STOCK_MAP[$stockType] ?? 'stock';
if ($sign > 0) {
ProductVariant::whereKey($variantId)->increment($field, $quantity);
} else {
ProductVariant::whereKey($variantId)->decrement($field, $quantity);
}
}
private function syncPhoto(Order $order, array $data): void
{
if (! array_key_exists('photo_key', $data)) {
return;
}
$currentKey = $order->getFirstMedia('photos')?->file_name;
if ($data['photo_key'] === $currentKey) {
return;
}
$order->clearMediaCollection('photos');
if (! empty($data['photo_key'])) {
$this->registerMedia(
model: $order,
s3Key: $data['photo_key'],
collectionName: 'photos',
orderColumn: 1,
);
}
}
private function generateOrderNumber(): string
{
$prefix = 'TRX';

View File

@ -8,12 +8,12 @@
class CategoryService
{
public function getAll(): Collection
public function getAll(array $filters = []): Collection
{
return Category::select('id', 'name')->latest()->get();
}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
return Category::query()
->select('id', 'name')

View File

@ -8,12 +8,12 @@
class CustomerService
{
public function getAll(): Collection
public function getAll(array $filters = []): Collection
{
return Customer::select('id', 'name', 'phone_number', 'address')->latest()->get();
}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
return Customer::query()
->select('id', 'name', 'phone_number', 'address')

View File

@ -14,9 +14,9 @@
class ProductService
{
public function __construct(
private ProductVariantService $variantService = new ProductVariantService,
private S3PresignedService $s3Service = new S3PresignedService,
private StockMutationService $stockMutationService = new StockMutationService,
private readonly ProductVariantService $variantService,
private readonly S3PresignedService $s3Service,
private readonly StockMutationService $stockMutationService,
) {}
public function getAll(array $filters = []): Collection

View File

@ -16,8 +16,8 @@ class ProductVariantService
use RegistersMedia;
public function __construct(
private S3PresignedService $s3Service = new S3PresignedService,
private StockMutationService $stockMutationService = new StockMutationService,
private readonly S3PresignedService $s3Service,
private readonly StockMutationService $stockMutationService,
) {}
public function getForEdit(ProductVariant $variant): array

View File

@ -15,7 +15,7 @@ class RawMaterialService
use RegistersMedia;
public function __construct(
private S3PresignedService $s3Service = new S3PresignedService,
private readonly S3PresignedService $s3Service,
) {}
public function getAll(array $filters = []): Collection

View File

@ -14,7 +14,7 @@ class RawMaterialVariantService
use RegistersMedia;
public function __construct(
private S3PresignedService $s3Service = new S3PresignedService,
private readonly S3PresignedService $s3Service,
) {}
public function getForEdit(RawMaterialPrice $variant): array

View File

@ -8,12 +8,12 @@
class SupplierService
{
public function getAll(): Collection
public function getAll(array $filters = []): Collection
{
return Supplier::select('id', 'name', 'phone_number', 'address')->latest()->get();
}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
return Supplier::query()
->select('id', 'name', 'phone_number', 'address')

View File

@ -15,7 +15,7 @@ public function getAll(): Collection
return Role::withCount('permissions')->get();
}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
return Role::query()
->withCount('permissions')

View File

@ -0,0 +1,57 @@
<?php
namespace App\Services\Concerns;
use App\Enums\CashTransactionType;
use App\Models\CashAccount;
use App\Models\CashTransaction;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
trait HandlesCashTransactions
{
private function getCashAccount(): CashAccount
{
return CashAccount::firstOrFail();
}
private function creditCash(int $amount, string $description, CashTransactionType $type = CashTransactionType::DEPOSIT): CashTransaction
{
$cashAccount = $this->getCashAccount();
$newBalance = $cashAccount->balance + $amount;
$cashAccount->update(['balance' => $newBalance]);
return CashTransaction::create([
'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(),
'amount' => $amount,
'balance_after' => $newBalance,
'type' => $type,
'description' => $description,
]);
}
private function debitCash(int $amount, string $description, CashTransactionType $type = CashTransactionType::EXPENSE): CashTransaction
{
$cashAccount = $this->getCashAccount();
if ($cashAccount->balance < $amount) {
throw ValidationException::withMessages([
'amount' => 'Saldo tidak mencukupi.',
]);
}
$newBalance = $cashAccount->balance - $amount;
$cashAccount->update(['balance' => $newBalance]);
return CashTransaction::create([
'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(),
'amount' => $amount,
'balance_after' => $newBalance,
'type' => $type,
'description' => $description,
]);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Services\Concerns;
use App\Enums\ProductStockQuality;
use Illuminate\Database\Eloquent\Model;
trait HasStockAdjustment
{
private const QUALITY_STOCK_MAP = [
ProductStockQuality::GOOD->value => 'stock',
ProductStockQuality::REJECT->value => 'reject_stock',
];
private function adjustStock(Model $model, string $field, int $quantity, int $sign): void
{
if ($sign > 0) {
$model->increment($field, $quantity);
} else {
$model->decrement($field, $quantity);
}
}
private function adjustVariantStock(int $variantId, int $quantity, int $sign, string $stockType): void
{
$field = self::QUALITY_STOCK_MAP[$stockType] ?? 'stock';
$this->adjustStock(
model: app(\App\Models\ProductVariant::class)->newQuery()->findOrFail($variantId),
field: $field,
quantity: $quantity,
sign: $sign,
);
}
private function applyStock(array $items, string $stockType, int $sign): void
{
foreach ($items as $item) {
$this->adjustVariantStock($item['product_variant_id'], $item['quantity'], $sign, $stockType);
}
}
private function reverseStock(array $items, string $stockType, int $sign): void
{
$this->applyStock($items, $stockType, -$sign);
}
}

View File

@ -39,35 +39,27 @@ private function registerMedia(
]);
}
private function registerMediaFromBase64(
Model $model,
string $data,
string $collectionName,
string $subdirectory,
array $generatedConversions = [],
): void {
if (str_starts_with($data, 'data:image')) {
$base64 = explode(',', $data)[1];
$imageData = base64_decode($base64);
$filename = $collectionName.'_'.time().'_'.uniqid().'.jpg';
$s3Key = $subdirectory.'/'.$filename;
app('filesystem')->disk('s3')->put($s3Key, $imageData);
$mimeType = 'image/jpeg';
$fileSize = strlen($imageData);
} else {
$s3Key = $data;
$mimeType = 'image/jpeg';
$fileSize = 0;
private function syncPhoto(Model $model, array $data, string $collectionName = 'photos'): void
{
if (! array_key_exists('photo_key', $data)) {
return;
}
$this->registerMedia(
model: $model,
s3Key: $s3Key,
collectionName: $collectionName,
generatedConversions: $generatedConversions,
fileSize: $fileSize,
mimeType: $mimeType,
);
$currentKey = $model->getFirstMedia($collectionName)?->file_name;
if ($data['photo_key'] === $currentKey) {
return;
}
$model->clearMediaCollection($collectionName);
if (! empty($data['photo_key'])) {
$this->registerMedia(
model: $model,
s3Key: $data['photo_key'],
collectionName: $collectionName,
orderColumn: 1,
);
}
}
}