diff --git a/src/backend/app/DTO/TransactionDataContainer.php b/src/backend/app/DTO/TransactionDataContainer.php index 5664d4e78..5e5c1da50 100644 --- a/src/backend/app/DTO/TransactionDataContainer.php +++ b/src/backend/app/DTO/TransactionDataContainer.php @@ -105,12 +105,17 @@ private function handleAppliancePayments(Transaction $transaction): void { } if ($this->appliancePerson) { - $installments = $this->appliancePerson->rates; - $appliancePaymentService = app()->make(AppliancePaymentService::class); - $secondInstallment = $installments[1] ?? null; - $this->installmentCost = $secondInstallment ? $secondInstallment['rate_cost'] : 0; - $this->dayDifferenceBetweenTwoInstallments = - $appliancePaymentService->getDayDifferenceBetweenTwoInstallments($installments); + if ($this->appliancePerson->isEnergyService()) { + $this->installmentCost = $this->appliancePerson->price_per_day ?? 0; + $this->dayDifferenceBetweenTwoInstallments = 1; + } else { + $installments = $this->appliancePerson->rates; + $appliancePaymentService = app()->make(AppliancePaymentService::class); + $secondInstallment = $installments[1] ?? null; + $this->installmentCost = $secondInstallment ? $secondInstallment['rate_cost'] : 0; + $this->dayDifferenceBetweenTwoInstallments = + $appliancePaymentService->getDayDifferenceBetweenTwoInstallments($installments); + } } } } diff --git a/src/backend/app/Http/Controllers/AppliancePersonController.php b/src/backend/app/Http/Controllers/AppliancePersonController.php index 97a537d8c..1dbad0c67 100644 --- a/src/backend/app/Http/Controllers/AppliancePersonController.php +++ b/src/backend/app/Http/Controllers/AppliancePersonController.php @@ -8,6 +8,8 @@ use App\Models\Appliance; use App\Models\AppliancePerson; use App\Models\Person\Person; +use App\Models\Transaction\Transaction; +use App\Models\User; use App\Services\AddressesService; use App\Services\AddressGeographicalInformationService; use App\Services\AppliancePersonService; @@ -36,88 +38,34 @@ public function __construct( private ApplianceRateService $applianceRateService, ) {} - /** - * Store a newly created resource in storage. - */ public function store( Appliance $appliance, Request $request, ): ApiResource { try { - $userId = $request->input('user_id'); - $applianceId = $request->input('id'); - $personId = $request->input('person_id'); - $cost = (int) $request->input('cost'); - $installmentCount = (int) $request->input('rate'); - $downPayment = (float) $request->input('down_payment'); + $user = $this->userService->getById($request->input('user_id')); + $appliance = $this->applianceService->getById($request->input('id')); + $paymentType = $request->input('payment_type', AppliancePerson::PAYMENT_TYPE_INSTALLMENT); + $isEnergyService = $paymentType === AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE; + $downPayment = (float) $request->input('down_payment', 0); $deviceSerial = $request->input('device_serial'); - $addressData = $request->input('address'); - $user = $this->userService->getById($userId); - $appliance = $this->applianceService->getById($applianceId); - $installmentType = $request->input('rate_type'); - $points = $request->input('points'); DB::connection('tenant')->beginTransaction(); - $appliancePerson = $this->appliancePersonService->make([ - 'appliance_id' => $applianceId, - 'person_id' => $personId, - 'total_cost' => $cost, - 'rate_count' => $installmentCount, - 'down_payment' => $downPayment, - 'device_serial' => $deviceSerial, - ]); - $this->userAppliancePersonService->setAssigned($appliancePerson); - $this->userAppliancePersonService->setAssignee($user); - $this->userAppliancePersonService->assign(); - $this->appliancePersonService->save($appliancePerson); - $preferredPrice = $appliance->price; - if ($cost !== $preferredPrice) { - $this->appliancePersonService->createLogForSoldAppliance($appliancePerson, $cost, $preferredPrice); + $appliancePerson = $this->createAppliancePerson($request, $user, $paymentType); + + if (!$isEnergyService) { + $this->createInstallmentRates($appliancePerson, $appliance, $request->input('rate_type')); } - $this->applianceRateService->create($appliancePerson, $installmentType); if ($deviceSerial) { - $device = $this->deviceService->getBySerialNumber($deviceSerial); - $this->deviceService->update($device, ['person_id' => $personId]); - $appliancePerson->device_serial = $deviceSerial; - - $address = $this->addressesService->make([ - 'street' => $addressData['street'], - 'city_id' => $addressData['city_id'], - ]); - - // Attach the new address to the person rather than the device. - $this->addressesService->assignAddressToOwner($appliancePerson->person, $address); - - $geographicalInformation = $this->geographicalInformationService->make([ - 'points' => $points, - ]); - $this->addressGeographicalInformationService->setAssigned($geographicalInformation); - $this->addressGeographicalInformationService->setAssignee($address); - $this->addressGeographicalInformationService->assign(); - $this->geographicalInformationService->save($geographicalInformation); + $this->assignDevice($appliancePerson, $request); } + if ($downPayment > 0) { - $sender = isset($addressData) ? $addressData['phone'] : '-'; - $transaction = $this->cashTransactionService->createCashTransaction( - $user->id, - $downPayment, - $sender, - $deviceSerial - ); - $applianceRate = $this->applianceRateService->getDownPaymentAsApplianceRate($appliancePerson); - event(new PaymentSuccessEvent( - amount: (int) $transaction->amount, - paymentService: 'web', - paymentType: 'down payment', - sender: $transaction->sender, - paidFor: $applianceRate, - payer: $appliancePerson->person, - transaction: $transaction, - )); - event(new TransactionSuccessfulEvent($transaction)); + $this->processDownPayment($appliancePerson, $user, $downPayment, $request); } + DB::connection('tenant')->commit(); return ApiResource::make($appliancePerson); @@ -127,6 +75,91 @@ public function store( } } + private function createAppliancePerson(Request $request, ?User $user, string $paymentType): AppliancePerson { + $isEnergyService = $paymentType === AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE; + + $appliancePerson = $this->appliancePersonService->make([ + 'appliance_id' => $request->input('id'), + 'person_id' => $request->input('person_id'), + 'total_cost' => $isEnergyService ? 0 : (int) $request->input('cost'), + 'rate_count' => $isEnergyService ? 0 : (int) $request->input('rate'), + 'down_payment' => (float) $request->input('down_payment', 0), + 'device_serial' => $request->input('device_serial'), + 'payment_type' => $paymentType, + 'minimum_payable_amount' => $isEnergyService ? $request->input('minimum_payable_amount') : null, + 'price_per_day' => $isEnergyService ? $request->input('price_per_day') : null, + ]); + + $this->userAppliancePersonService->setAssigned($appliancePerson); + $this->userAppliancePersonService->setAssignee($user); + $this->userAppliancePersonService->assign(); + $this->appliancePersonService->save($appliancePerson); + + return $appliancePerson; + } + + private function createInstallmentRates(AppliancePerson $appliancePerson, Appliance $appliance, string $installmentType): void { + $cost = (int) $appliancePerson->total_cost; + $preferredPrice = $appliance->price; + + if ($cost !== $preferredPrice) { + $this->appliancePersonService->createLogForSoldAppliance($appliancePerson, $cost, $preferredPrice); + } + + $this->applianceRateService->create($appliancePerson, $installmentType); + } + + private function assignDevice(AppliancePerson $appliancePerson, Request $request): void { + $deviceSerial = $request->input('device_serial'); + $addressData = $request->input('address'); + $points = $request->input('points'); + + $device = $this->deviceService->getBySerialNumber($deviceSerial); + $this->deviceService->update($device, ['person_id' => $appliancePerson->person_id]); + + $address = $this->addressesService->make([ + 'street' => $addressData['street'], + 'city_id' => $addressData['city_id'], + ]); + + $this->addressesService->assignAddressToOwner($appliancePerson->person, $address); + + $geographicalInformation = $this->geographicalInformationService->make([ + 'points' => $points, + ]); + $this->addressGeographicalInformationService->setAssigned($geographicalInformation); + $this->addressGeographicalInformationService->setAssignee($address); + $this->addressGeographicalInformationService->assign(); + $this->geographicalInformationService->save($geographicalInformation); + } + + private function processDownPayment(AppliancePerson $appliancePerson, ?User $user, float $downPayment, Request $request): void { + $addressData = $request->input('address'); + $deviceSerial = $request->input('device_serial'); + $sender = isset($addressData) ? $addressData['phone'] : '-'; + + $transaction = $this->cashTransactionService->createCashTransaction( + $user->id, + $downPayment, + $sender, + $deviceSerial, + type: Transaction::TYPE_DOWN_PAYMENT, + ); + + $applianceRate = $this->applianceRateService->createPaidRate($appliancePerson, $downPayment); + + event(new PaymentSuccessEvent( + amount: (int) $transaction->amount, + paymentService: 'web', + paymentType: 'down payment', + sender: $transaction->sender, + paidFor: $applianceRate, + payer: $appliancePerson->person, + transaction: $transaction, + )); + event(new TransactionSuccessfulEvent($transaction)); + } + /** * Display the specified resource. */ diff --git a/src/backend/app/Http/Requests/CreateAgentSoldApplianceRequest.php b/src/backend/app/Http/Requests/CreateAgentSoldApplianceRequest.php index 40cb49bd5..6b11aaf03 100644 --- a/src/backend/app/Http/Requests/CreateAgentSoldApplianceRequest.php +++ b/src/backend/app/Http/Requests/CreateAgentSoldApplianceRequest.php @@ -20,13 +20,16 @@ public function authorize(): bool { public function rules(): array { return [ 'person_id' => ['required'], - 'down_payment' => ['required', 'numeric'], - 'tenure' => ['required', 'numeric', 'min:0'], - 'first_payment_date' => ['required'], + 'payment_type' => ['nullable', 'string', 'in:installment,energy_service'], + 'down_payment' => ['required_unless:payment_type,energy_service', 'numeric'], + 'tenure' => ['required_unless:payment_type,energy_service', 'numeric', 'min:0'], + 'first_payment_date' => ['required_unless:payment_type,energy_service'], 'agent_assigned_appliance_id' => ['required'], 'device_serial' => ['nullable', 'string'], 'address' => ['nullable', 'array'], 'points' => ['nullable', 'string'], + 'minimum_payable_amount' => ['nullable', 'integer', 'min:0'], + 'price_per_day' => ['nullable', 'integer', 'min:0'], ]; } } diff --git a/src/backend/app/Jobs/ApplianceTransactionProcessor.php b/src/backend/app/Jobs/ApplianceTransactionProcessor.php index 74b1fa19c..677c8a1bd 100644 --- a/src/backend/app/Jobs/ApplianceTransactionProcessor.php +++ b/src/backend/app/Jobs/ApplianceTransactionProcessor.php @@ -3,6 +3,7 @@ namespace App\Jobs; use App\DTO\TransactionDataContainer; +use App\Events\PaymentSuccessEvent; use App\Events\TransactionFailedEvent; use App\Events\TransactionSuccessfulEvent; use App\Exceptions\ApplianceTokenNotProcessedException; @@ -11,12 +12,12 @@ use App\Models\Appliance; use App\Models\Transaction\Transaction; use App\Services\AppliancePaymentService; +use App\Services\ApplianceRateService; use App\Utils\ApplianceInstallmentPayer; use Illuminate\Support\Facades\Log; class ApplianceTransactionProcessor extends AbstractJob { private Transaction $transaction; - protected const TYPE = 'deferred_payment'; public function __construct(int $companyId, private int $transactionId) { $this->onConnection('redis'); @@ -66,7 +67,16 @@ private function processTransactionPayment(TransactionDataContainer $container, private function initializeTransaction(): void { $this->transaction = Transaction::query()->find($this->transactionId); - $this->transaction->type = 'deferred_payment'; + + if ($this->transaction->type !== Transaction::TYPE_DOWN_PAYMENT) { + $appliancePerson = $this->transaction->paygoAppliance()->first() + ?? $this->transaction->nonPaygoAppliance()->first(); + + $this->transaction->type = ($appliancePerson && $appliancePerson->isEnergyService()) + ? Transaction::TYPE_EAAS_RATE + : Transaction::TYPE_DEFERRED_PAYMENT; + } + $this->transaction->save(); } @@ -80,13 +90,39 @@ private function initializeTransactionDataContainer(): TransactionDataContainer } private function checkForMinimumPurchaseAmount(TransactionDataContainer $container): void { - $minimumPurchaseAmount = $container->installmentCost; - if ($container->amount < $minimumPurchaseAmount) { + $minimumPurchaseAmount = $container->appliancePerson->isEnergyService() + ? ($container->appliancePerson->minimum_payable_amount ?? 0) + : $container->installmentCost; + + if ($minimumPurchaseAmount > 0 && $container->amount < $minimumPurchaseAmount) { throw new TransactionAmountNotEnoughException("Minimum purchase amount not reached for appliance with serial id:{$container->transaction->message}"); } } private function payApplianceInstallments(TransactionDataContainer $container): TransactionDataContainer { + if ($container->appliancePerson->isEnergyService()) { + $applianceRateService = resolve(ApplianceRateService::class); + $paidRate = $applianceRateService->createPaidRate($container->appliancePerson, $container->amount); + $container->paidRates = [ + ['appliance_rate_id' => $paidRate->id, 'paid' => $container->amount], + ]; + + $container->applianceInstallmentsFullFilled = false; + // Success payment event means the client will update the ui as updated + // before the token get's generated. + event(new PaymentSuccessEvent( + amount: (int) $container->amount, + paymentService: $this->transaction->original_transaction_type, + paymentType: Transaction::TYPE_EAAS_RATE, + sender: $this->transaction->sender, + paidFor: $paidRate, + payer: $container->appliancePerson->person, + transaction: $this->transaction, + )); + + return $container; + } + $applianceInstallmentPayer = resolve(ApplianceInstallmentPayer::class); $applianceInstallmentPayer->initialize($container); $applianceInstallmentPayer->payInstallmentsForDevice($container); diff --git a/src/backend/app/Jobs/TokenProcessor.php b/src/backend/app/Jobs/TokenProcessor.php index d2b0a1c10..ca5c25427 100644 --- a/src/backend/app/Jobs/TokenProcessor.php +++ b/src/backend/app/Jobs/TokenProcessor.php @@ -6,6 +6,7 @@ use App\Events\PaymentSuccessEvent; use App\Events\TransactionFailedEvent; use App\Events\TransactionSuccessfulEvent; +use App\Models\AppliancePerson; use App\Models\ApplianceRate; use App\Models\Token; use Illuminate\Bus\Queueable; @@ -74,7 +75,10 @@ private function handleExistingToken(): ?Token { private function generateToken(mixed $api): void { try { - if ($this->transactionContainer->applianceInstallmentsFullFilled) { + $isEnergyService = $this->transactionContainer->appliancePerson instanceof AppliancePerson + && $this->transactionContainer->appliancePerson->isEnergyService(); + + if (!$isEnergyService && $this->transactionContainer->applianceInstallmentsFullFilled) { $tokenData = $api->unlockDevice($this->transactionContainer); } else { $tokenData = $api->chargeDevice($this->transactionContainer); diff --git a/src/backend/app/Models/AppliancePerson.php b/src/backend/app/Models/AppliancePerson.php index b74568981..e31c5e6bc 100644 --- a/src/backend/app/Models/AppliancePerson.php +++ b/src/backend/app/Models/AppliancePerson.php @@ -28,6 +28,9 @@ * @property float|null $down_payment * @property Carbon|null $first_payment_date * @property string|null $device_serial + * @property string $payment_type + * @property int|null $minimum_payable_amount + * @property int|null $price_per_day * @property-read Appliance|null $appliance * @property-read Model $creator * @property-read Device|null $device @@ -36,11 +39,18 @@ * @property-read Collection $rates */ class AppliancePerson extends BaseModel { + public const PAYMENT_TYPE_INSTALLMENT = 'installment'; + public const PAYMENT_TYPE_ENERGY_SERVICE = 'energy_service'; + /** @var array */ protected $dispatchesEvents = [ 'created' => AppliancePersonCreated::class, ]; + public function isEnergyService(): bool { + return $this->payment_type === self::PAYMENT_TYPE_ENERGY_SERVICE; + } + /** * @return BelongsTo */ diff --git a/src/backend/app/Models/Transaction/Transaction.php b/src/backend/app/Models/Transaction/Transaction.php index 7a4d002d1..c40808c64 100644 --- a/src/backend/app/Models/Transaction/Transaction.php +++ b/src/backend/app/Models/Transaction/Transaction.php @@ -51,6 +51,11 @@ */ class Transaction extends BaseModel { public const RELATION_NAME = 'transaction'; + public const TYPE_ENERGY = 'energy'; + public const TYPE_DEFERRED_PAYMENT = 'deferred_payment'; + public const TYPE_EAAS_RATE = 'eaas_rate'; + public const TYPE_DOWN_PAYMENT = 'down_payment'; + public const TYPE_UNKNOWN = 'unknown'; public const TYPE_IMPORTED = 'imported'; /** diff --git a/src/backend/app/Plugins/Prospect/Services/ProspectPaymentTransformer.php b/src/backend/app/Plugins/Prospect/Services/ProspectPaymentTransformer.php index 9106153e3..cdd850a34 100644 --- a/src/backend/app/Plugins/Prospect/Services/ProspectPaymentTransformer.php +++ b/src/backend/app/Plugins/Prospect/Services/ProspectPaymentTransformer.php @@ -110,6 +110,8 @@ private function mapTransactionPurpose(?string $transactionType): string { return match ($transactionType) { 'energy' => 'Paygo Payment', 'deferred_payment', 'loan', 'installment' => 'Loan repayment', + 'eaas_rate' => 'Energy service payment', + 'down_payment' => 'Down payment', 'access_rate' => 'Access rate payment', default => 'Paygo Payment', }; diff --git a/src/backend/app/Services/AgentSoldApplianceService.php b/src/backend/app/Services/AgentSoldApplianceService.php index ec9e3117b..b4df590fe 100644 --- a/src/backend/app/Services/AgentSoldApplianceService.php +++ b/src/backend/app/Services/AgentSoldApplianceService.php @@ -6,6 +6,7 @@ use App\Models\Agent; use App\Models\AgentSoldAppliance; use App\Models\AppliancePerson; +use App\Models\Transaction\Transaction; use App\Services\Interfaces\IBaseService; use Carbon\Carbon; use Illuminate\Database\Eloquent\Collection; @@ -151,6 +152,10 @@ public function processSaleFromRequest(AgentSoldAppliance $agentSoldAppliance, a $assignedAppliance->appliance()->first(); $agent = $this->agentService->getById($assignedAppliance->agent_id); $deviceSerial = $requestData['device_serial'] ?? null; + $paymentType = $requestData['payment_type'] ?? AppliancePerson::PAYMENT_TYPE_INSTALLMENT; + $isEnergyService = $paymentType === AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE; + + $downPayment = $requestData['down_payment'] ?: 0; // create agent transaction $agentTransactionData = [ @@ -162,10 +167,10 @@ public function processSaleFromRequest(AgentSoldAppliance $agentSoldAppliance, a // assign agent transaction to transaction $transactionData = [ - 'amount' => $requestData['down_payment'] ?: 0, + 'amount' => $downPayment, 'sender' => 'Agent-'.$agent->id, 'message' => $deviceSerial ?? '-', - 'type' => 'deferred_payment', + 'type' => Transaction::TYPE_DOWN_PAYMENT, ]; $transaction = $this->transactionService->make($transactionData); @@ -177,12 +182,15 @@ public function processSaleFromRequest(AgentSoldAppliance $agentSoldAppliance, a // assign agent to appliance person $appliancePersonData = [ 'person_id' => $requestData['person_id'], - 'first_payment_date' => Carbon::parse($requestData['first_payment_date'])->toDateString(), - 'rate_count' => $requestData['tenure'], - 'total_cost' => $assignedAppliance->cost, - 'down_payment' => $requestData['down_payment'], + 'first_payment_date' => $isEnergyService ? null : Carbon::parse($requestData['first_payment_date'])->toDateString(), + 'rate_count' => $isEnergyService ? 0 : $requestData['tenure'], + 'total_cost' => $isEnergyService ? 0 : $assignedAppliance->cost, + 'down_payment' => $downPayment, 'appliance_id' => $assignedAppliance->appliance->id, 'device_serial' => $deviceSerial, + 'payment_type' => $paymentType, + 'minimum_payable_amount' => $isEnergyService ? ($requestData['minimum_payable_amount'] ?? null) : null, + 'price_per_day' => $isEnergyService ? ($requestData['price_per_day'] ?? null) : null, ]; $appliancePerson = $this->appliancePersonService->make($appliancePersonData); @@ -222,10 +230,13 @@ public function processSaleFromRequest(AgentSoldAppliance $agentSoldAppliance, a // initalize appliance Rates $buyer = $this->personService->getById($appliancePerson->person_id); - $this->applianceRateService->create($appliancePerson); + + if (!$isEnergyService) { + $this->applianceRateService->create($appliancePerson); + } if ($appliancePerson->down_payment > 0) { - $applianceRate = $this->applianceRateService->getDownPaymentAsApplianceRate($appliancePerson); + $applianceRate = $this->applianceRateService->createPaidRate($appliancePerson, $appliancePerson->down_payment); event(new PaymentSuccessEvent( amount: (int) $transaction->amount, paymentService: $transaction->original_transaction_type === 'cash_transaction' ? 'web' : 'agent', diff --git a/src/backend/app/Services/AppliancePaymentService.php b/src/backend/app/Services/AppliancePaymentService.php index 4e7dd9141..aeec3535f 100644 --- a/src/backend/app/Services/AppliancePaymentService.php +++ b/src/backend/app/Services/AppliancePaymentService.php @@ -51,6 +51,19 @@ public function createPaymentHistory(float $amount, AppliancePerson $buyer, Appl } public function validateAmount(AppliancePerson $applianceDetail, float $amount): void { + if ($amount <= 0) { + throw new PaymentAmountSmallerThanZero('Payment amount can not smaller than zero'); + } + + if ($applianceDetail->isEnergyService()) { + $minimumPayableAmount = $applianceDetail->minimum_payable_amount ?? 0; + if ($minimumPayableAmount > 0 && $amount < $minimumPayableAmount) { + throw new PaymentAmountSmallerThanZero("Payment amount can not be less than minimum payable amount ({$minimumPayableAmount})"); + } + + return; + } + $totalRemainingAmount = $applianceDetail->rates->sum('remaining'); $installmentCost = $applianceDetail->rates[1]['rate_cost'] ?? 0; @@ -61,10 +74,6 @@ public function validateAmount(AppliancePerson $applianceDetail, float $amount): if ($amount < $installmentCost && $amount != $totalRemainingAmount) { throw new PaymentAmountSmallerThanZero('Payment amount can not smaller than installment cost'); } - - if ($amount <= 0) { - throw new PaymentAmountSmallerThanZero('Payment amount can not smaller than zero'); - } } public function payInstallment(ApplianceRate $installment, AppliancePerson $applianceOwner, Transaction $transaction): void { diff --git a/src/backend/app/Services/ApplianceRateService.php b/src/backend/app/Services/ApplianceRateService.php index 7621eabc7..c102eafc6 100644 --- a/src/backend/app/Services/ApplianceRateService.php +++ b/src/backend/app/Services/ApplianceRateService.php @@ -3,6 +3,7 @@ namespace App\Services; use App\Events\NewLogEvent; +use App\Models\AppliancePerson; use App\Models\ApplianceRate; use App\Models\MainSettings; use Carbon\Carbon; @@ -88,15 +89,6 @@ public function create(object $appliancePerson, string $installmentType = 'month $baseTime = $appliancePerson->first_payment_date ?? date('Y-m-d'); $installment = $installmentType === 'monthly' ? 'month' : 'week'; if ($appliancePerson->down_payment > 0) { - $this->applianceRate->newQuery()->create( - [ - 'appliance_person_id' => $appliancePerson->id, - 'rate_cost' => round($appliancePerson->down_payment), - 'remaining' => 0, - 'due_date' => Carbon::parse(date('Y-m-d'))->toDateTimeString(), - 'remind' => 0, - ] - ); $appliancePerson->total_cost -= $appliancePerson->down_payment; } foreach (range(1, $appliancePerson->rate_count) as $rate) { @@ -141,6 +133,16 @@ public function getAll(?int $limit = null): Collection { throw new \Exception('Method getAll() not yet implemented.'); } + public function createPaidRate(AppliancePerson $appliancePerson, float $amount): ApplianceRate { + return $this->applianceRate->newQuery()->create([ + 'appliance_person_id' => $appliancePerson->id, + 'rate_cost' => round($amount), + 'remaining' => 0, + 'due_date' => Carbon::now()->toDateTimeString(), + 'remind' => 0, + ]); + } + public function getDownPaymentAsApplianceRate(object $appliancePerson): ?ApplianceRate { return $this->applianceRate->newQuery() ->where('appliance_person_id', $appliancePerson->id) diff --git a/src/backend/app/Services/CashTransactionService.php b/src/backend/app/Services/CashTransactionService.php index 57f815e25..d02842487 100644 --- a/src/backend/app/Services/CashTransactionService.php +++ b/src/backend/app/Services/CashTransactionService.php @@ -9,8 +9,8 @@ class CashTransactionService { public function __construct(private CashTransaction $cashTransaction, private Transaction $transaction) {} - public function createCashTransaction(int $creatorId, float $amount, string $sender, ?string $deviceSerial = null, ?int $applianceId = null): Transaction { - return DB::transaction(function () use ($creatorId, $amount, $sender, $deviceSerial, $applianceId) { + public function createCashTransaction(int $creatorId, float $amount, string $sender, ?string $deviceSerial = null, ?int $applianceId = null, string $type = Transaction::TYPE_DEFERRED_PAYMENT): Transaction { + return DB::transaction(function () use ($creatorId, $amount, $sender, $deviceSerial, $applianceId, $type) { $cashTransaction = $this->cashTransaction->newQuery()->create([ 'user_id' => $creatorId, 'status' => 1, @@ -20,7 +20,7 @@ public function createCashTransaction(int $creatorId, float $amount, string $sen 'amount' => $amount, 'sender' => $sender, 'message' => $deviceSerial ?? strval($applianceId ?? '-'), - 'type' => 'deferred_payment', + 'type' => $type, ]); $transaction->originalTransaction()->associate($cashTransaction); diff --git a/src/backend/database/migrations/tenant/2026_03_26_000000_add_payment_type_to_appliance_people_table.php b/src/backend/database/migrations/tenant/2026_03_26_000000_add_payment_type_to_appliance_people_table.php new file mode 100644 index 000000000..bdc7f5812 --- /dev/null +++ b/src/backend/database/migrations/tenant/2026_03_26_000000_add_payment_type_to_appliance_people_table.php @@ -0,0 +1,21 @@ +table('appliance_people', function (Blueprint $table) { + $table->string('payment_type', 20)->default('installment')->after('device_serial'); + $table->integer('minimum_payable_amount')->nullable()->after('payment_type'); + $table->integer('price_per_day')->nullable()->after('minimum_payable_amount'); + }); + } + + public function down(): void { + Schema::connection('tenant')->table('appliance_people', function (Blueprint $table) { + $table->dropColumn(['payment_type', 'minimum_payable_amount', 'price_per_day']); + }); + } +}; diff --git a/src/backend/database/migrations/tenant/2026_04_01_000000_add_eaas_rate_and_down_payment_to_transactions_type.php b/src/backend/database/migrations/tenant/2026_04_01_000000_add_eaas_rate_and_down_payment_to_transactions_type.php new file mode 100644 index 000000000..2b5392723 --- /dev/null +++ b/src/backend/database/migrations/tenant/2026_04_01_000000_add_eaas_rate_and_down_payment_to_transactions_type.php @@ -0,0 +1,21 @@ +statement( + "ALTER TABLE transactions MODIFY COLUMN type ENUM('energy','deferred_payment','eaas_rate','down_payment','unknown','imported','3rd party api sync') DEFAULT 'unknown'" + ); + } + + public function down(): void { + DB::connection('tenant')->statement( + "UPDATE transactions SET type = 'deferred_payment' WHERE type IN ('eaas_rate', 'down_payment')" + ); + DB::connection('tenant')->statement( + "ALTER TABLE transactions MODIFY COLUMN type ENUM('energy','deferred_payment','unknown','imported','3rd party api sync') DEFAULT 'unknown'" + ); + } +}; diff --git a/src/backend/database/seeders/OutstandingDebtsSeeder.php b/src/backend/database/seeders/OutstandingDebtsSeeder.php index 54f4693fc..bd05aec5c 100644 --- a/src/backend/database/seeders/OutstandingDebtsSeeder.php +++ b/src/backend/database/seeders/OutstandingDebtsSeeder.php @@ -405,7 +405,7 @@ private function createHistoricalPaymentTransaction(AppliancePerson $appliancePe // Create main transaction using proper polymorphic relationship $transaction = new Transaction([ 'amount' => $paymentAmount, - 'type' => 'deferred_payment', + 'type' => Transaction::TYPE_DEFERRED_PAYMENT, 'sender' => $sender, 'message' => $appliancePerson->device_serial, 'created_at' => $historicalDate, @@ -480,7 +480,7 @@ private function createPartialPaymentTransaction(AppliancePerson $appliancePerso $transaction = new Transaction([ 'amount' => $paymentAmount, - 'type' => 'deferred_payment', + 'type' => Transaction::TYPE_DEFERRED_PAYMENT, 'sender' => $sender, 'message' => $appliancePerson->device_serial, ]); @@ -530,7 +530,7 @@ private function createFullPaymentTransaction(AppliancePerson $appliancePerson, $transaction = new Transaction([ 'amount' => $paymentAmount, - 'type' => 'deferred_payment', + 'type' => Transaction::TYPE_DEFERRED_PAYMENT, 'sender' => $sender, 'message' => $appliancePerson->device_serial, ]); diff --git a/src/backend/tests/Feature/AppliancePaymentPlanEnergyAsAServiceTest.php b/src/backend/tests/Feature/AppliancePaymentPlanEnergyAsAServiceTest.php new file mode 100644 index 000000000..0ecff583e --- /dev/null +++ b/src/backend/tests/Feature/AppliancePaymentPlanEnergyAsAServiceTest.php @@ -0,0 +1,236 @@ +createTestData(); + $this->createCluster(); + $this->createMiniGrid(); + $this->createCity(); + $this->createAgentCommission(); + $this->createAgent(); + $this->createAssignedAppliances(); + } + + private function createEaaSAppliancePerson(int $minimumPayableAmount = 0): AppliancePerson { + $this->createTestData(); + $person = PersonFactory::new()->create(); + $applianceType = ApplianceTypeFactory::new()->create(['paygo_enabled' => false]); + $appliance = ApplianceFactory::new()->create(['appliance_type_id' => $applianceType->id]); + + return AppliancePersonFactory::new()->create([ + 'person_id' => $person->id, + 'appliance_id' => $appliance->id, + 'payment_type' => AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE, + 'minimum_payable_amount' => $minimumPayableAmount ?: null, + 'price_per_day' => 100, + 'total_cost' => 0, + 'rate_count' => 0, + 'first_payment_date' => null, + 'creator_type' => 'user', + 'creator_id' => $this->user->id, + ]); + } + + public function testAgentSellsEaaSApplianceSuccessfully(): void { + $this->setUpAgentEnvironment(); + $assignedAppliance = AgentAssignedAppliances::query()->first(); + + $response = $this->actingAs($this->agent)->post('/api/app/agents/appliances', [ + 'person_id' => $this->person->id, + 'agent_assigned_appliance_id' => $assignedAppliance->id, + 'payment_type' => AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE, + 'minimum_payable_amount' => 500, + 'price_per_day' => 100, + 'down_payment' => 0, + ]); + + $response->assertStatus(201); + + $this->assertDatabaseHas('appliance_people', [ + 'person_id' => $this->person->id, + 'payment_type' => AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE, + ], 'tenant'); + } + + public function testAgentEaaSSaleDoesNotRequireTenureOrFirstPaymentDate(): void { + $this->setUpAgentEnvironment(); + $assignedAppliance = AgentAssignedAppliances::query()->first(); + + $response = $this->actingAs($this->agent)->post('/api/app/agents/appliances', [ + 'person_id' => $this->person->id, + 'agent_assigned_appliance_id' => $assignedAppliance->id, + 'payment_type' => AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE, + 'down_payment' => 0, + ]); + + $response->assertStatus(201); + } + + public function testAgentEaaSSaleStoresMinimumPayableAmountAndPricePerDay(): void { + $this->setUpAgentEnvironment(); + $assignedAppliance = AgentAssignedAppliances::query()->first(); + + $response = $this->actingAs($this->agent)->post('/api/app/agents/appliances', [ + 'person_id' => $this->person->id, + 'agent_assigned_appliance_id' => $assignedAppliance->id, + 'payment_type' => AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE, + 'minimum_payable_amount' => 300, + 'price_per_day' => 50, + 'down_payment' => 0, + ]); + + $response->assertStatus(201); + + $this->assertDatabaseHas('appliance_people', [ + 'person_id' => $this->person->id, + 'payment_type' => AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE, + 'minimum_payable_amount' => 300, + 'price_per_day' => 50, + ], 'tenant'); + } + + public function testAgentEaaSSaleDoesNotCreateInstallmentRates(): void { + $this->setUpAgentEnvironment(); + $assignedAppliance = AgentAssignedAppliances::query()->first(); + + $this->actingAs($this->agent)->post('/api/app/agents/appliances', [ + 'person_id' => $this->person->id, + 'agent_assigned_appliance_id' => $assignedAppliance->id, + 'payment_type' => AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE, + 'minimum_payable_amount' => 500, + 'down_payment' => 0, + ]); + + $appliancePerson = AppliancePerson::query() + ->where('person_id', $this->person->id) + ->where('payment_type', AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE) + ->first(); + + $this->assertNotNull($appliancePerson); + $this->assertEquals(0, $appliancePerson->rates()->count()); + } + + public function testWebUserCreatesEaaSAppliancePerson(): void { + $this->createTestData(); + $person = PersonFactory::new()->create(); + $applianceType = ApplianceTypeFactory::new()->create(); + $appliance = ApplianceFactory::new()->create(['appliance_type_id' => $applianceType->id]); + + $response = $this->actingAs($this->user)->post( + sprintf('/api/appliances/person/%s/people/%s', $appliance->id, $person->id), + [ + 'id' => $appliance->id, + 'person_id' => $person->id, + 'user_id' => $this->user->id, + 'payment_type' => AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE, + 'minimum_payable_amount' => 500, + 'price_per_day' => 100, + 'down_payment' => 0, + ] + ); + + $response->assertStatus(201); + + $this->assertDatabaseHas('appliance_people', [ + 'person_id' => $person->id, + 'appliance_id' => $appliance->id, + 'payment_type' => AppliancePerson::PAYMENT_TYPE_ENERGY_SERVICE, + 'minimum_payable_amount' => 500, + 'price_per_day' => 100, + ], 'tenant'); + } + + public function testPaymentIsAcceptedForEaaSAppliance(): void { + Queue::fake(); + $appliancePerson = $this->createEaaSAppliancePerson(minimumPayableAmount: 0); + + $response = $this->actingAs($this->user)->post( + sprintf('/api/appliances/payment/%s', $appliancePerson->id), + ['amount' => 500] + ); + + $response->assertStatus(200); + $this->assertNotNull($response->json('data.transaction_id')); + } + + public function testPaymentWithMinimumAmountIsAcceptedForEaaSAppliance(): void { + Queue::fake(); + $appliancePerson = $this->createEaaSAppliancePerson(minimumPayableAmount: 300); + + $response = $this->actingAs($this->user)->post( + sprintf('/api/appliances/payment/%s', $appliancePerson->id), + ['amount' => 300] + ); + + $response->assertStatus(200); + $this->assertNotNull($response->json('data.transaction_id')); + } + + public function testPaymentBelowMinimumIsRejectedForEaaSAppliance(): void { + Queue::fake(); + $this->withoutExceptionHandling(); + $appliancePerson = $this->createEaaSAppliancePerson(minimumPayableAmount: 500); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Payment amount can not be less than minimum payable amount (500)'); + + $this->actingAs($this->user)->post( + sprintf('/api/appliances/payment/%s', $appliancePerson->id), + ['amount' => 100] + ); + } + + public function testZeroAmountPaymentIsRejectedForEaaSAppliance(): void { + Queue::fake(); + $this->withoutExceptionHandling(); + $appliancePerson = $this->createEaaSAppliancePerson(); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Payment amount can not smaller than zero'); + + $this->actingAs($this->user)->post( + sprintf('/api/appliances/payment/%s', $appliancePerson->id), + ['amount' => 0] + ); + } + + public function testGetRatesForEaaSAppliancePerson(): void { + $appliancePerson = $this->createEaaSAppliancePerson(); + + ApplianceRateFactory::new()->create([ + 'appliance_person_id' => $appliancePerson->id, + 'rate_cost' => 500, + 'remaining' => 0, + 'due_date' => now()->toDateString(), + ]); + + ApplianceRateFactory::new()->create([ + 'appliance_person_id' => $appliancePerson->id, + 'rate_cost' => 300, + 'remaining' => 0, + 'due_date' => now()->toDateString(), + ]); + + $response = $this->actingAs($this->user)->get( + sprintf('/api/appliances/person/%s/rates', $appliancePerson->id) + ); + + $response->assertStatus(200); + $this->assertCount(2, $response->json('data')); + } +} diff --git a/src/frontend/src/assets/locales/ar.json b/src/frontend/src/assets/locales/ar.json index cb837ae54..318510e3c 100644 --- a/src/frontend/src/assets/locales/ar.json +++ b/src/frontend/src/assets/locales/ar.json @@ -167,6 +167,7 @@ "componentPrice": "سعر المكون", "confirmDeleteAgent": "حذف الوكيل", "confirmDeleteAgentText": "أؤكد أن {name} سيتم حذفه", + "confirmEaas": "Are you sure to set up this appliance as Energy as a Service?", "confirmPassword": "تأكيد كلمة السر", "confirmPayment": "تأكيد الدفع", "connectedMeters": "العدادات المتصلة", @@ -201,6 +202,8 @@ "distributeThis": ".يرجى توزيع هذا على عملائك وطلب منهم إرسال رقم عدادهم لتسجيل نظام الاتصال فايبر الخاص بنا", "downPayment": "دفعة أولى", "dueDate": "أجل الدفع", + "eaasDescription": "Energy as a Service: The customer pays on-demand for energy credits. No installment plan or due dates will be created. The device will stay owned by the service provider and no unlock tokens are sent to the customer.", + "eaasRate": "معدل EaaS", "editConnectionGroup": "تم تحديث مجموعة الاتصال بنجاح | هل أنت متأكد من تغيير اسم مجموعة الاتصال هذه؟ | تعديل مجموعة الاتصال ", "editConnectionType": "تعديل نوع الاتصال", "editConnectionTypeNotify": "تم تحديث اسم نوع الاتصال بنجاح ‏ | {name}‏ هل أنت متأكد من تغيير اسم نوع الاتصال لـ", @@ -238,6 +241,7 @@ "isPrimary": "هو أساسي", "jobTitle": "اسم الوظيفة", "lastDataReceived": "آخر بيانات تم استلامها", + "lastPaidDate": "تاريخ آخر دفعة", "lastPayment": "آخر دفعة", "lastReceipts": "آخر الإيصالات", "lastSyncDate": "تاريخ آخر تحيين ", @@ -269,7 +273,7 @@ "meterTransaction": "معاملات العداد | معاملة العداد", "meterTypes": "أنواع العدادات", "miniGridMap": "خريطة المجمّع الكهربائي الصغير", - "minimumPayableAmount": "الحد الأدنى للمبلغ الواجب دفعه (الوصول للنظام لمدة 30 يومًا) | الحد الأدنى للمبلغ الواجب دفعه (الوصول للنظام لمدة 7 أيام)", + "minimumPayableAmount": "الحد الأدنى لمبلغ الدفع | الحد الأدنى لمبلغ الدفع | الحد الأدنى لمبلغ الدفع", "minimumPurchaseAmount": "الحد الأدنى لمبلغ الشراء", "minimumPurchaseAmountDescription": "تمثل هذه الخانة الحد الأدنى لسعر الشراء الذي يمكن للحريف القيام به. يمكنك تركها صفراً إذا لم ترغب في تطبيق هذه الميزة", "missingField": "خانة مفقودة", @@ -344,6 +348,7 @@ "personalInformation": "معلومات شخصية", "pleaseShareThis": ".يرجى مشاركة هذا الرابط مع حرفائك لإجراء دفوعاتهم عبر الإنترنت", "preparingChartData": "جاري إعداد بيانات الرسم البياني", + "pricePerDay": "السعر لكل يوم خدمة", "processTime": "وقت المعالجة", "processedTransactions": "المعاملات التي تمت معالجتها", "providerSpecificInformation": "معلومات خاصة بالمزود", diff --git a/src/frontend/src/assets/locales/bu.json b/src/frontend/src/assets/locales/bu.json index f13306e9a..37a86a987 100644 --- a/src/frontend/src/assets/locales/bu.json +++ b/src/frontend/src/assets/locales/bu.json @@ -167,6 +167,7 @@ "componentPrice": "အစိတ်အပိုင်း ေစျးJှ+နး်", "confirmDeleteAgent": "အေးဂျင့်ကိုဖျက်မည်", "confirmDeleteAgentText": "{name} ကိုဖျက်မည်ဟုအတည်ပြုပါ", + "confirmEaas": "Are you sure to set up this appliance as Energy as a Service?", "confirmPassword": "စကားဝှက်ကို အတည်ြပ+ပါ", "confirmPayment": "Confirm the payment", "connectedMeters": "ချတိ ်ဆက်ထားေသာ မီတာများ", @@ -201,6 +202,8 @@ "distributeThis": "Please distribute this to your customers and ask them to send their meter number to register our Viber communication system.", "downPayment": "Down Payment", "dueDate": "ေနာက်ဆံုးရက်", + "eaasDescription": "Energy as a Service: The customer pays on-demand for energy credits. No installment plan or due dates will be created. The device will stay owned by the service provider and no unlock tokens are sent to the customer.", + "eaasRate": "EaaS နှုန်း", "editConnectionGroup": "ချတိ ်ဆက်မ6အဖဲFကို တည်းြဖတ်ပါ။ |ဤချတိ ်ဆက်မ6အဖဲFအမည်ကိုေြပာင်းရန် ေသချာပါသလား။ |ချတိ ်ဆက်မ6အဖဲFကို အပ်ဒိတ်လပု ်NပီးပါNပီ။", "editConnectionType": "ချတိ ်ဆက်မ6အမျ+ိးအစားကို တည်းြဖတ်ပါ။", "editConnectionTypeNotify": " ချတိ ်ဆက်မ6အမျ+ိးအစားအမည်ကို ေအာင်ြမင်စွာ အပ်ဒိတ်လပု ် ထားသည်။ |{name}အတကွ ် ချတိ ်ဆက်မ6 အမျ+ိး အစားအမည်ကိုေြပာင်းရန် ေသချာပါသလား။ ", @@ -238,6 +241,7 @@ "isPrimary": "မူလ ြဖစ်ပါသလား", "jobTitle": "အလပု ်အကိုင်အမည်", "lastDataReceived": "Last Data Received", + "lastPaidDate": "Last Paid Date", "lastPayment": "ေနာက်ဆံုးေငေွပးေချမ6", "lastReceipts": "ေနာက်ဆံုး လက်ခံြဖတ်ပိုင်းများ", "lastSyncDate": "ေနာက်ဆံုးစင်ခ့ ်/ထပ်တြူပ+ြခင်းလပု ်သည့်ရက်စဲွ", @@ -269,7 +273,7 @@ "meterTransaction": "မီတာလဲေXြပာင်းြခင်း|မီတာလဲေXြပာင်းြခင်းများ", "meterTypes": "မီတာအမျ+ိးအစားများ", "miniGridMap": "မီနဂီရစ်ေြမပံု", - "minimumPayableAmount": "Minimum Payable Amount (access to the system for 7 days) | Minimum Payable Amount (access to the system for 30 days)", + "minimumPayableAmount": "Minimum Payment Amount | Minimum Payment Amount | Minimum Payment Amount", "minimumPurchaseAmount": "Minimum Purchase Amount", "minimumPurchaseAmountDescription": "This field represents minimum price of the purchase the customer can do. You can leave it as zero If you do not want to apply this feature", "missingField": "ေပျာက်ဆံုးေနေသာ အကွက်", @@ -344,6 +348,7 @@ "personalInformation": "Personal Information", "pleaseShareThis": "Please share this link with your customers for making their online payments.", "preparingChartData": "ဇယားေဒတာ ြပင်ဆင်ြခင်း", + "pricePerDay": "Price per Day of Service", "processTime": "လပု်ငနး်စV်အချနိ", "processedTransactions": "စီမံထားေသာ ေငလွ ဲမX 6များ", "providerSpecificInformation": "ပံ့ပိုးေပးသူ၏ သီးြခား အချက်အလက်", diff --git a/src/frontend/src/assets/locales/en.json b/src/frontend/src/assets/locales/en.json index 4c004bcd9..4d73474c2 100644 --- a/src/frontend/src/assets/locales/en.json +++ b/src/frontend/src/assets/locales/en.json @@ -167,6 +167,7 @@ "componentPrice": "Component Price", "confirmDeleteAgent": "Delete Agent", "confirmDeleteAgentText": "I confirm that {name} will be deleted", + "confirmEaas": "Are you sure to set up this appliance as Energy as a Service?", "confirmPassword": "Confirm Password", "confirmPayment": "Confirm the payment", "connectedMeters": "Connected Meters", @@ -201,6 +202,8 @@ "distributeThis": "Please distribute this to your customers and ask them to send their meter number to register our Viber communication system.", "downPayment": "Down Payment", "dueDate": "Due Date", + "eaasDescription": "Energy as a Service: The customer pays on-demand for energy credits. No installment plan or due dates will be created. The device will stay owned by the service provider and no unlock tokens are sent to the customer.", + "eaasRate": "EaaS Rate", "editConnectionGroup": "Edit Connection Group | Are you sure to changing this connection group name ? | Connection Group Updated Successfully", "editConnectionType": "Edit Connection Type", "editConnectionTypeNotify": "Connection Type Name Updated Successfully | Are you sure to change of connection type name for {name} ?", @@ -238,6 +241,7 @@ "isPrimary": "Is Primary", "jobTitle": "Job Title", "lastDataReceived": "Last Data Received", + "lastPaidDate": "Last Paid Date", "lastPayment": "Last Payment", "lastReceipts": "Last Receipts", "lastSyncDate": "Last Sync Date", @@ -269,7 +273,7 @@ "meterTransaction": "Meter Transaction | Meter Transactions", "meterTypes": "Meter Types", "miniGridMap": "MiniGrid Map", - "minimumPayableAmount": "Minimum Payable Amount (access to the system for 7 days) | Minimum Payable Amount (access to the system for 30 days)", + "minimumPayableAmount": "Minimum Payment Amount | Minimum Payment Amount | Minimum Payment Amount", "minimumPurchaseAmount": "Minimum Purchase Amount", "minimumPurchaseAmountDescription": "This field represents minimum price of the purchase the customer can do. You can leave it as zero If you do not want to apply this feature", "missingField": "Missing Field", @@ -344,6 +348,7 @@ "personalInformation": "Personal Information", "pleaseShareThis": "Please share this link with your customers for making their online payments.", "preparingChartData": "Preparing Chart Data", + "pricePerDay": "Price per Day of Service", "processTime": "Process Time", "processedTransactions": "Processed Transactions", "providerSpecificInformation": "Provider Specific Information", diff --git a/src/frontend/src/assets/locales/fr.json b/src/frontend/src/assets/locales/fr.json index b9d81b994..5fca398ed 100644 --- a/src/frontend/src/assets/locales/fr.json +++ b/src/frontend/src/assets/locales/fr.json @@ -167,6 +167,7 @@ "componentPrice": "Component Price", "confirmDeleteAgent": "Delete Agent", "confirmDeleteAgentText": "I confirm that {name} will be deleted", + "confirmEaas": "Are you sure to set up this appliance as Energy as a Service?", "confirmPassword": "Confirm Password", "confirmPayment": "Confirm the payment", "connectedMeters": "Connected Meters", @@ -201,6 +202,8 @@ "distributeThis": "Please distribute this to your customers and ask them to send their meter number to register our Viber communication system.", "downPayment": "Down Payment", "dueDate": "Due Date", + "eaasDescription": "Energy as a Service: The customer pays on-demand for energy credits. No installment plan or due dates will be created. The device will stay owned by the service provider and no unlock tokens are sent to the customer.", + "eaasRate": "Tarif EaaS", "editConnectionGroup": "Edit Connection Group | Are you sure to changing this connection group name ? | Connection Group Updated Successfully", "editConnectionType": "Edit Connection Type", "editConnectionTypeNotify": "Connection Type Name Updated Successfully | Are you sure to change of connection type name for {name} ?", @@ -238,6 +241,7 @@ "isPrimary": "Est primaire", "jobTitle": "Titre du poste", "lastDataReceived": "Last Data Received", + "lastPaidDate": "Date du dernier paiement", "lastPayment": "Last Payment", "lastReceipts": "Last Receipts", "lastSyncDate": "Last Sync Date", @@ -269,7 +273,7 @@ "meterTransaction": "Meter Transaction | Meter Transactions", "meterTypes": "Meter Types", "miniGridMap": "MiniGrid Map", - "minimumPayableAmount": "Minimum Payable Amount (access to the system for 7 days) | Minimum Payable Amount (access to the system for 30 days)", + "minimumPayableAmount": "Montant minimum de paiement | Montant minimum de paiement | Montant minimum de paiement", "minimumPurchaseAmount": "Minimum Purchase Amount", "minimumPurchaseAmountDescription": "This field represents minimum price of the purchase the customer can do. You can leave it as zero If you do not want to apply this feature", "missingField": "Missing Field", @@ -344,6 +348,7 @@ "personalInformation": "Informations personnelles", "pleaseShareThis": "Please share this link with your customers for making their online payments.", "preparingChartData": "Preparing Chart Data", + "pricePerDay": "Prix par jour de service", "processTime": "Process Time", "processedTransactions": "Processed Transactions", "providerSpecificInformation": "Provider Specific Information", diff --git a/src/frontend/src/modules/Client/Appliances/SellApplianceModal.vue b/src/frontend/src/modules/Client/Appliances/SellApplianceModal.vue index 510887398..1306f131b 100644 --- a/src/frontend/src/modules/Client/Appliances/SellApplianceModal.vue +++ b/src/frontend/src/modules/Client/Appliances/SellApplianceModal.vue @@ -307,6 +307,99 @@ + +
+
+ + + + + --{{ $tc("words.select") }}-- + + + {{ appliance.name }} + + + + {{ errors.first("energy-service-form.appliance") }} + + +
+
+ + + + +
+
+ + + + + {{ errors.first("energy-service-form.price_per_day") }} + + +
+
+ + + + + + {{ eaasMinPaymentDaysText }} + +
+
+

+ {{ $tc("phrases.eaasDescription") }} +

+
+
+