Bu makalede, büyük bir projede nasıl verimli bir şekilde süreç yönetimi yapabileceğimizi ele alacağız. Örnek olarak, Satış, Tedarik Zinciri ve Ön-Satış aşamalarını içeren bir akış üzerinde yoğunlaşacağız. Akış, bir BOQ ile başlayarak, Satın Alma Talebi üretmekte, envanter kontrolü yaparak, yeterli varsa Teklif oluşturma aşamasına geçmekte, yoksa Teklif Talebi (RFQ) göndererek, Tedarikçi Tekliflerini toplamakta ve nihayetinde satın alma, depolama ve teslim etme aşamalarına yönelmektedir.
<p>Her akış aşamasında bir <strong>Stage</strong> sütununa ihtiyacımız var. Bu sayede, bir <strong>BOQ</strong> açıldığında “şu an Mal Tahsis aşamasındayız” bilgisi alabiliriz. Ancak burada bir problem var: Aşama her değiştiğinde tüm akışı güncellemek zorunda mıyım? Her serviste bu akış mantığını gömmek zorunda mıyım? Bu durum büyük bir tekrar yaratır.</p>
<p>Kendime şunu sordum: Akışın herhangi bir yerinden, sadece öncekini bilip güncellemeyi geçirebilir miyim?</p>
<p>Her serviste “önceki aşama burası” diyip güncel veriyi geçersek, bu daha elegant olur.</p>
<p>Çözüm, <strong>Event-Driven Architecture (EDA)</strong> anlayışına dayanıyordu. Bir olay üretici (Producer) olmalı, doğru servise iletilmeli (Broker) ve veri iletilip servis güncellenmelidir (Consumer).</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>When Create Purchase Request
-> Fire Event (Producer)
-> Listener (Broker)
-> Finds the right service
-> BoqService (Consumer)
-> Updates BOQ with $data
<p>Akışın dinamik olarak devam etmesini istiyorsak, önceki aşamayı yanıtın bir parçası olarak döndürmeliyiz:</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>When Create RFQ
-> Fire Event (Producer)
-> Listener (Broker)
-> Finds the right service
-> PurchaseRequestService (Consumer) -> {
Update current PR with $data
Return previous step (BOQ) + new $data
Fire new event with new data
}
<p>Bu işlemlerin bir <strong>queue</strong> üzerinden çalışması gerekmektedir; çünkü senkron bir şekilde yapmak performans açısından kötü olacaktır. Güncellemenin aynı anda gerçekleşmesi gerekmiyor, bu bir tekrar eden olay halini alır ve <strong>zero point</strong>, yani <strong>BOQ</strong> olan noktaya kadar devam eder.</p>
<p>Şimdi bu mantığı koda nasıl transliter edeceğimize bakalım.</p>
<p><strong>Olay - SalesProcessEvent:</strong></p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code><?php namespace Modules\Sales\Events;
use Illuminate\Queue\SerializesModels;
use Modules\Sales\Enums\SalesProcessStepEnum;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
class SalesProcessEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/
* Create a new event instance.
*/
public function __construct(
public SalesProcessStepEnum $mainStage,
public SalesProcessStepEnum $openStage,
public array $data
) {}
}
<p><strong>Dinleyici (Broker) - SalesProcessListener:</strong></p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code><?php namespace Modules\Sales\Listeners;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Modules\Sales\Events\SalesProcessEvent;
use Modules\Sales\Strategies\Classes\SalesProcessStrategy;
class SalesProcessListener implements ShouldQueue
{
use InteractsWithQueue;
public function __construct(
private SalesProcessStrategy $salesProcessStrategy
) {}
public function handle(SalesProcessEvent $event): void
{
$mainStage = $event-?>mainStage;
$openStage = $this->salesProcessStrategy
->setService($event->openStage)
->updateStage($mainStage, $event->openStage, $event->data);
if ($openStage !== null) {
SalesProcessEvent::dispatch($mainStage, $openStage['stage'], $openStage['data']);
}
}
}
<p>Buradaki her şey <strong>SalesProcessStrategy</strong> üzerine inşa edilmiştir. Bu strateji, verilen aşamaya göre uygun servisi bulur ve aşamayı günceller. Aşama güncellenirse, dinleyici yeni bir olay tetikler.</p>
<p><strong>Strateji - SalesProcessStrategy:</strong></p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code><?php namespace Modules\Sales\Strategies\Classes;
use Modules\PreSales\Services\BoqService;
use Modules\Sales\Enums\SalesProcessStepEnum;
use Modules\PreSales\Services\QuotationService;
use Modules\Sales\Services\MaterialIssueService;
use Modules\Inventory\Services\GoodsReceiptService;
use Modules\Inventory\Services\MaterialReceiptService;
use Modules\SupplyChain\Services\PurchaseOrderService;
use Modules\Inventory\Services\MaterialDispatchService;
use Modules\PreSales\Services\SupplierQuotationService;
use Modules\SupplyChain\Services\PurchaseReceiptService;
use Modules\SupplyChain\Services\PurchaseRequestService;
use Modules\PreSales\Services\RequestForQuotationService;
use Modules\Sales\Strategies\Interface\SalesProccesInterface;
class SalesProcessStrategy
{
private ?SalesProccesInterface $service = null;
/
* Set the service based on the stage.
*/
public function setService(SalesProcessStepEnum $stage): static
{
$this-?>service = match ($stage) {
SalesProcessStepEnum::BOQ => new BoqService(),
SalesProcessStepEnum::PURCHASE_REQUEST => new PurchaseRequestService(),
SalesProcessStepEnum::RFQ => new RequestForQuotationService(),
SalesProcessStepEnum::SUPPLIER_QUOTATION => new SupplierQuotationService(),
SalesProcessStepEnum::QUOTATION => new QuotationService(),
SalesProcessStepEnum::PURCHASE_ORDER => new PurchaseOrderService(),
SalesProcessStepEnum::PURCHASE_RECEIPT => new PurchaseReceiptService(),
SalesProcessStepEnum::MATERIAL_RECEIPT => new MaterialReceiptService(),
SalesProcessStepEnum::MATERIAL_ISSUE => new MaterialIssueService(),
SalesProcessStepEnum::MATERIAL_DISPATCH => new MaterialDispatchService(),
SalesProcessStepEnum::GOODS_RECEIPT => new GoodsReceiptService(),
default => null,
};
return $this;
}
/
* Update the stage.
*/
public function updateStage(SalesProcessStepEnum $mainStage, SalesProcessStepEnum $openStage, array $data): mixed
{
if ($this->service === null) {
throw new \InvalidArgumentException("No service resolved for stage: {$openStage}, main stage: {$mainStage}");
}
return $this->service->updateStage($mainStage, $openStage, $data);
}
}
<p>Burada <strong>$mainStage</strong> en son güncellenmesi gereken durumu temsil ederken, <strong>$openStage</strong> güncellenecek servisi ifade eder. <strong>setService</strong> uygun servisi bulur ve <strong>$this</strong> döner.</p>
<p><strong>Arayüz - SalesProccesInterface:</strong></p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>
<?php namespace Modules\Sales\Strategies\Interface;
use Modules\Sales\Enums\SalesProcessStepEnum;
interface SalesProccesInterface
{
/*
- Update the stage.
*/
public function updateStage(SalesProcessStepEnum $mainStage, SalesProcessStepEnum $openStage, array $data): mixed;
public function dispatchPreviousStage(SalesProcessStepEnum $mainStage, SalesProcessStepEnum $openStage, array $data): void;
}
<p><strong>BoqService:</strong></p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code><?php namespace Modules\PreSales\Services;
use Modules\PreSales\Models\Boq;
use Modules\Sales\Enums\SalesProcessStepEnum;
use Modules\Inventory\Events\RegistrationItemCreatedEvent;
use Modules\Sales\Strategies\Interface\SalesProccesInterface;
class BoqService implements SalesProccesInterface
{
/
Update the stage.
*/
public function updateStage(SalesProcessStepEnum $mainStage, SalesProcessStepEnum $openStage, array $data): mixed
{
$boq = Boq::find($data[‘id’]);
if ($boq && SalesProcessStepEnum::upThanCurrent($boq-?>stage, $mainStage)) {
$boq->update([
‘stage’ => $mainStage,
]);
}
return null;
}
/
* Dispatch previous stage.
*/
public function dispatchPreviousStage(SalesProcessStepEnum $mainStage, SalesProcessStepEnum $openStage, array $data): void {}
}
<p>BOQ'yi sadece iki koşul sağlandığında güncelliyoruz: BOQ var ve <strong>upThanCurrent</strong> onaylıyorsa, yeni aşamanın gerçekten daha ileri olduğunu kontrol etmiş oluyoruz.</p>
<p><strong>PurchaseRequestService:</strong></p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code><?php namespace Modules\SupplyChain\Services;
use Modules\PreSales\Models\Boq;
use Modules\Sales\Events\SalesProcessEvent;
use Modules\Sales\Enums\SalesProcessStepEnum;
use Modules\SupplyChain\Models\PurchaseRequest;
use Modules\SupplyChain\Enums\PurchaseRequestTypeEnum;
use Modules\Sales\Strategies\Interface\SalesProccesInterface;
class PurchaseRequestService implements SalesProccesInterface
{
/
Create a new PurchaseRequest.
@param array $data
@return \Modules\SupplyChain\Models\PurchaseRequest
*/
public function store(array $data): PurchaseRequest
{
$purchaseRequest = PurchaseRequest::create($data);
$this-?>syncPurchaseRequestItem($purchaseRequest, $data);
$this->dispatchPreviousStage(SalesProcessStepEnum::PURCHASE_REQUEST, SalesProcessStepEnum::BOQ, [
‘id’ => $purchaseRequest->pull_id,
]);
return $purchaseRequest->fresh();
}
/
* Update the stage.
*/
public function updateStage(SalesProcessStepEnum $mainStage, SalesProcessStepEnum $openStage, array $data): mixed
{
$purchaseRequest = PurchaseRequest::find($data['id']);
if ($purchaseRequest && SalesProcessStepEnum::upThanCurrent($purchaseRequest->stage, $mainStage)) {
$purchaseRequest->update([
'stage' => $mainStage,
]);
}
if ($purchaseRequest->type == PurchaseRequestTypeEnum::PROJECT) {
return [
'stage' => SalesProcessStepEnum::BOQ,
'data' => [
'id' => $purchaseRequest?->pull_id ?? null,
]
];
}
return null;
}
/
* Dispatch previous stage.
*/
public function dispatchPreviousStage(SalesProcessStepEnum $mainStage, SalesProcessStepEnum $openStage, array $data): void
{
SalesProcessEvent::dispatch($mainStage, $openStage, $data);
}
}