<?php

namespace Database\Seeders;

use App\Classes\Exceptions\AccessForbiddenException;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Exceptions\RequestValidationException;
use App\Classes\General\Services\GeneratesInitials;
use App\Classes\Modules\Accounts\DataTransferObjects\RegistrationObject;
use App\Classes\Modules\Accounts\Services\CreatesUser;
use App\Classes\Modules\Addresses\DataTransferObjects\AddressObject;
use App\Classes\Modules\Addresses\Services\CreatesAddress;
use App\Classes\Modules\Banks\DataTransferObjects\BankObject;
use App\Classes\Modules\Banks\Services\CreatesBank;
use App\Classes\Modules\Banks\Services\SetsBankToDefault;
use App\Classes\Modules\Bookings\DataTransferObjects\BookingObject;
use App\Classes\Modules\Bookings\Services\CreatesBooking;
use App\Classes\Modules\Bookings\Services\FetchesBookingQuotation;
use App\Classes\Modules\Bookings\Services\GeneratesBookingMarking;
use App\Classes\Modules\Companies\DataTransferObjects\CompanyObject;
use App\Classes\Modules\Companies\DataTransferObjects\EmploymentObject;
use App\Classes\Modules\Companies\Processors\AssignEmployeeProcessor;
use App\Classes\Modules\Companies\Processors\AssignSegmentProcessor;
use App\Classes\Modules\Companies\Services\CreatesCompany;
use App\Classes\Modules\Contacts\DataTransferObjects\ContactObject;
use App\Classes\Modules\Contacts\Services\CreatesContact;
use App\Classes\Modules\Currencies\DataTransferObjects\CurrencyConversionObject;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\Modules\Documents\Services\ApprovesDocument;
use App\Classes\Modules\Documents\Services\CreatesDocument;
use App\Classes\Modules\Documents\Services\CreatesFiles;
use App\Classes\Modules\Documents\Services\RejectsDocument;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\Modules\Transactions\Processors\CreateInvoiceTransactionV2Processor;
use App\Classes\Modules\Transactions\Processors\CreatePurchaseOrderTransactionProcessor;
use App\Classes\Modules\Transactions\Processors\CreateSupplierTransactionProcessor;
use App\Classes\Modules\Transactions\Services\CreatesTransaction;
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus;
use App\Classes\Modules\Wallets\DataTransferObjects\WalletObject;
use App\Classes\Modules\Wallets\Services\CreatesWallet;
use App\Classes\Modules\Wallets\Services\GeneratesWalletCode;
use App\Classes\Modules\Wallets\Services\UpdatesWalletBalance;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\BusinessType;
use App\Classes\ValueObjects\Constants\CompanyType;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\RoleTypes;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Address;
use App\Models\Company;
use App\Models\Document;
use App\Models\Group;
use App\Models\ServiceType;
use App\Models\Transaction;
use App\Models\User;
use App\Models\Wallet;
use App\Models\Booking;
use Carbon\Carbon;
use Illuminate\Database\Seeder;

use Faker\Generator as Faker;
use Mccarlosen\LaravelMpdf\Facades\LaravelMpdf;
use Mpdf\MpdfException;

class DummyDataSeeder extends Seeder
{

    /** @var Faker */
    public $faker;

    /** @var CreatesUser */
    public $createsUser;

    /** @var CreatesCompany */
    public $createsCompany;

    /** @var CreatesContact */
    public $createsContact;

    /** @var CreatesAddress */
    public $createsAddress;

    /** @var AssignEmployeeProcessor */
    public $assignEmployeeProcessor;

    /** @var CreatesDocument */
    public $createsDocument;

    /** @var CreatesFiles */
    public $createsFiles;

    /** @var GeneratesWalletCode */
    public $generatesWalletCode;

    /** @var CreatesWallet */
    public $createsWallet;

    /** @var GeneratesTransactionBillNumber */
    public $generatesTransactionBillNumber;

    /** @var CreatesTransaction */
    public $createsTransaction;

    /** @var UpdatesTransactionStatus */
    public $updatesTransactionStatus;

    /** @var UpdatesWalletBalance */
    public $updatesWalletBalance;

    /** @var createsBank  */
    public $createsBank;

    /** @var GeneratesBookingMarking  */
    public $generatesBookingMarking;

    /** @var CreatesBooking  */
    public $createsBooking;

    /** @var FetchesBookingQuotation */
    public $fetchBookingQuotation;

    /** @var ApprovesDocument */
    public $approvesDocument;

    /** @var RejectsDocument */
    public $rejectsDocument;

    /** @var CreatePurchaseOrderTransactionProcessor */
    public $createPurchaseOrderTransactionProcessor;

    /** @var CreateInvoiceTransactionV2Processor */
    public $createInvoiceTransactionProcessor;

    /** @var CreateSupplierTransactionProcessor */
    public $createSupplierTransactionProcessor;

    /** @var AssignSegmentProcessor */
    private $assignSegmentProcessor;

    /** @var SetsBankToDefault*/
    private $setsBankToDefault ;

    /**
     * @param Faker $faker
     * @param CreatesUser $createsUser
     * @param CreatesCompany $createsCompany
     * @param CreatesContact $createsContact
     * @param CreatesAddress $createsAddress
     * @param AssignEmployeeProcessor $assignEmployeeProcessor
     * @param CreatesDocument $createsDocument
     * @param CreatesFiles $createsFiles
     * @param GeneratesWalletCode $generatesWalletCode
     * @param CreatesWallet $createsWallet
     * @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
     * @param CreatesTransaction $createsTransaction
     * @param UpdatesTransactionStatus $updatesTransactionStatus
     * @param UpdatesWalletBalance $updatesWalletBalance
     * @param CreatesBank $createsBank
     * @param GeneratesBookingMarking $generatesBookingMarking
     * @param CreatesBooking $createsBooking
     * @param FetchesBookingQuotation $fetchBookingQuotation
     * @param ApprovesDocument $approvesDocument
     * @param RejectsDocument $rejectsDocument
     * @param CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor
     * @param CreateInvoiceTransactionV2Processor $createInvoiceTransactionProcessor
     * @param CreateSupplierTransactionProcessor $createSupplierTransactionProcessor
     * @param AssignSegmentProcessor $assignSegmentProcessor
     * @param SetsBankToDefault $setsBankToDefault
     */
    public function __construct(Faker $faker, CreatesUser $createsUser, CreatesCompany $createsCompany, CreatesContact $createsContact, CreatesAddress $createsAddress, AssignEmployeeProcessor $assignEmployeeProcessor, CreatesDocument $createsDocument, CreatesFiles $createsFiles, GeneratesWalletCode $generatesWalletCode, CreatesWallet $createsWallet, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatesTransaction $createsTransaction, UpdatesTransactionStatus $updatesTransactionStatus, UpdatesWalletBalance $updatesWalletBalance, CreatesBank $createsBank, GeneratesBookingMarking $generatesBookingMarking, CreatesBooking $createsBooking, FetchesBookingQuotation $fetchBookingQuotation, ApprovesDocument $approvesDocument, RejectsDocument $rejectsDocument, CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor, CreateInvoiceTransactionV2Processor $createInvoiceTransactionProcessor, CreateSupplierTransactionProcessor $createSupplierTransactionProcessor, AssignSegmentProcessor $assignSegmentProcessor, SetsBankToDefault $setsBankToDefault)
    {
        $this->faker = $faker;
        $this->createsUser = $createsUser;
        $this->createsCompany = $createsCompany;
        $this->createsContact = $createsContact;
        $this->createsAddress = $createsAddress;
        $this->assignEmployeeProcessor = $assignEmployeeProcessor;
        $this->createsDocument = $createsDocument;
        $this->createsFiles = $createsFiles;
        $this->generatesWalletCode = $generatesWalletCode;
        $this->createsWallet = $createsWallet;
        $this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
        $this->createsTransaction = $createsTransaction;
        $this->updatesTransactionStatus = $updatesTransactionStatus;
        $this->updatesWalletBalance = $updatesWalletBalance;
        $this->createsBank = $createsBank;
        $this->generatesBookingMarking = $generatesBookingMarking;
        $this->createsBooking = $createsBooking;
        $this->fetchBookingQuotation = $fetchBookingQuotation;
        $this->approvesDocument = $approvesDocument;
        $this->rejectsDocument = $rejectsDocument;
        $this->createPurchaseOrderTransactionProcessor = $createPurchaseOrderTransactionProcessor;
        $this->createInvoiceTransactionProcessor = $createInvoiceTransactionProcessor;
        $this->createSupplierTransactionProcessor = $createSupplierTransactionProcessor;
        $this->assignSegmentProcessor = $assignSegmentProcessor;
        $this->setsBankToDefault = $setsBankToDefault;
    }


    /**
     * Run the database seeds.
     *
     * @return void
     * @throws AccessForbiddenException
     * @throws MalformedRequestException
     * @throws RequestValidationException
     * @throws MpdfException
     */
    public function run()
    {
        // Local Development Default Password Hash
        $password = '123456abcabc';

        // At the moment we only have 3 different user roles:
        // RoleTypes::SUPER_ADMIN : Full access, at the moment is not attached to a company but should be in the future.
        // RoleTypes::ADMIN : Full access except for some sensitive features that require higher level of approval, at the moment is not attached to a company but should be in the future.
        // RoleTypes::USER : This is the customer, can only access their own orders only, must be attached to a company.

        // User Status
        // ApprovalStatus::PENDING_VERIFICATION : This should be the default status before the user verifies their email status, but currently this is not being implemented.
        // ApprovalStatus::APPROVED : This is the status of users with verified emails.
        // ApprovalStatus::SUSPENDED : This is the status if the users is blocked from the system, but currently this is not being implemented.


        // =============================================== //
        //              Create CIEF Entities               //
        // =============================================== //

        // create super admin
        $userObject = new RegistrationObject($this->faker->name, 'super_admin@exchange.com', $password, $password,RoleTypes::SUPER_ADMIN, ApprovalStatus::APPROVED);
        $this->createsUser->execute($userObject);

        Auth()->login(User::find(1), true);

        // create admin
        $userObject = new RegistrationObject($this->faker->name, 'admin@exchange.com', $password, $password,RoleTypes::ADMIN, ApprovalStatus::APPROVED);
        $this->createsUser->execute($userObject);

        // create CIEF
        $company_object = new CompanyObject('CIEF Worldwide Sdn Bhd', 'CIEF',CompanyType::COMPANY_BUSINESS,ApprovalStatus::APPROVED);
        /** @var Company $company */
        $company = $this->createsCompany->execute($company_object);

        // =============================================== //
        //              Create Supplier Entities           //
        // =============================================== //
        // supplier entities consist of 2 type of company module [BusinessType::FREIGHT_FORWARDER, BusinessType::FREIGHT_FORWARDER, BusinessType::WAREHOUSE]
        // in this use case we are creating 3 supplier, with each supplier having 6 company modules, 1 BusinessType::FREIGHT_FORWARDER and 5 BusinessType::WAREHOUSE. 1 warehouse for each location.

        for ($i = 1; $i <= 3; $i++) {
            $supplierName = $this->faker->company;
            $supplierReference = $this->faker->bothify('??-????');

            $company_object = new CompanyObject($supplierName, $supplierReference,BusinessType::CURRENCY_VENDOR, CompanyType::COMPANY_BUSINESS, ApprovalStatus::APPROVED);
            /** @var Company $company */
            $company = $this->createsCompany->execute($company_object);
            $bank_object = new BankObject($company->id, $this->faker->numberBetween(2, 3),
                $this->faker->company, $this->faker->name,  $this->faker->bankAccountNumber,
                $this->faker->city,  null,  null,
                2, $this->faker->company);

            $bank = $this->createsBank->execute($bank_object);
            $this->setsBankToDefault->execute($bank);

        }


        // =============================================== //
        //                 Create Customer                 //
        // =============================================== //
        // 1. create user
        // 2. create company

        // 3. Attach Employee
        // 4. create contact
        //
        // 5. create Address

        // 6. identification verification

        // =============================================== //
        //                     Wallet                      //
        // =============================================== //

        // 7. top up wallet

        // =============================================== //
        //                 Order Workflow                  //
        // =============================================== //

        // 8. create recipient bank
        // 9. create booking
        // 10. make payment (Manual, FPX, Wallet)
        // 11. approve payment (For manual payments only) * N
        // 12. create supplier order
        // 13. upload china payment proof (outsource * N)
        // 14. create purchase order (maybe outsource)
        // 15. approve purchase order ()
        // 16. generate invoice

        // generate random number of users
        for($userLoop=1; $userLoop <= 20; $userLoop++) {

            // === //
            //  1  // ========== //
            //    Create user    //
            // ================= //
            $customerName = $this->faker->name;
            $customerEmail = $this->faker->email;
            $userObject = new RegistrationObject($customerName, $customerEmail, $password, $password, RoleTypes::USER, ApprovalStatus::APPROVED);
            /** @var User $user */
            $user = $this->createsUser->execute($userObject);

            // === //
            //  2  // ========== //
            //  Create Company   //
            // ================= //

            // company reference is called marking, it is the human readable id.

            // CompanyTypes
            // CompanyType::COMPANY_BUSINESS : For SME Business Entities and requires SSM for identity verification.
            // CompanyType::PERSONAL_BUSINESS : For Personal Entities and requires IC for identity verification, and the company name will follow the customer name in this case.

            // Company Status
            // ApprovalStatus::APPROVED : This is the default status of registered company.
            // ApprovalStatus::SUSPENDED : This is the status if the company is blocked from releasing packages from warehouse due to pending verification.

            $isCompany = $this->faker->numberBetween(0, 1);
            $companyName = $isCompany ? $this->faker->company : $customerName;

            $company_object = new CompanyObject($companyName,
                mt_rand(1000, 9999).(new GeneratesInitials())->name($companyName)->length(3)->generate(),
                $isCompany ? CompanyType::COMPANY_BUSINESS : CompanyType::PERSONAL_BUSINESS,
                ApprovalStatus::APPROVED);

            /** @var Company $company */
            $company = $this->createsCompany->execute($company_object);
            $this->assignSegmentProcessor->execute($company);


            // === //
            //  3  // ===========//
            //  Attach Employee  //
            // ==================//
            // employees are attached to company modules not companies, because an employee maybe working for one or many "Departments".
            $Object = new EmploymentObject($company, $user);
            $this->assignEmployeeProcessor->execute($Object);

            // === //
            //  4  // ========== //
            //  Create Contact   //
            // ================= //
            // Contacts uses eloquent polymorphic relationship to declare its owner. and for this use case it will be attached to the company not the company module.
            $contactObject = new ContactObject($company->id, $customerName, (int) $this->faker->randomNumber(7), $customerEmail, null, 1);
            $this->createsContact->execute($contactObject);

            // === //
            //  5  // ========== //
            //  Create Address   //
            // ================= //
            // Addresses uses eloquent polymorphic relationship to declare its owner. and for this use case it will be attached to the company module.
            // an address has at least 1 contact for the PIC.
            // there is 1 type of address we use:
            // AddressType::BILLING : for the invoice billing address

            // create delivery address
            $addressObject = new AddressObject($this->faker->streetAddress, '', 1, $this->faker->numberBetween(1, 15), $this->faker->numberBetween(1, 100), $this->faker->postcode);
            /** @var Address $address */
            $address = $this->createsAddress->execute($company, $addressObject);

            // === //
            //  6  // ====================== //
            //  identification verification   //
            // ============================== //
            // please refer to company types section for more insight
            $object = new DocumentObject($isCompany ? DocumentType::SSM_REGISTRATION : DocumentType::IDENTITY_CARD, ['data:image/png;base64,[encoded data, 4548 characters]'],
                $isCompany ? $this->faker->bothify('SSM-#######') : $this->faker->bothify('############'), ApprovalStatus::APPROVED, 'identifications');
            /** @var Document $document */
            $document = $this->createsDocument->execute($company, $object);
            $this->createsFiles->execute($document, $object);

            // === //
            //  7  // ========= //
            //  top up wallet   //
            // ================ //
            // when a customer tries to top up their wallet, if the wallet doesn't already exist it will be automatically created.
            // wallet credit can be used to pay for transfer orders to enjoy better conversion rates.
            // wallet top-ups can only be performed using FPX at the moment. but super admin can manually credit or debit credit to a customer's wallet

            // The transaction table is considered the most confusing part of our database because it is being used by multiple model using a polymorphic relationship
            // and is used for many use cases in our application which is an unintended flaw, and we are looking for ways to improve it.


            // A wallet top up is TransactionType::TOP_UP, there are many types of transactions used by a wallet:
            // TransactionType::TOP_UP : represent a top-up amount to a wallet;
            // TransactionType::PAYMENT : represent payment out of the wallet;
            // TransactionType::CREDIT_NOTE : represent a manual top-up to a wallet, and can only be performed by super admin;
            // TransactionType::DEBDIT_NOTE : represent a deduction from a wallet, and can only be performed by super admin;
            // TransactionType::WITHDRAW : represent a customer withdrawing credit out of a wallet to a bank account (refund);


            // top up only some customers
            $shouldTopUp = $this->faker->numberBetween(0, 1);
            if($shouldTopUp) {
                $object = new WalletObject($company->id, 1, $this->generatesWalletCode->execute());
                /** @var Wallet $wallet */
                $wallet = $this->createsWallet->execute($object, $company);

                $amount = $this->faker->numberBetween(10, 300000);
                $billNumber = $this->generatesTransactionBillNumber->execute('TOPUP-');

                // create billplz bill using api, we will skip this part in the seed.
                $billPlzBill = $this->faker->bothify('???#####');

                $transaction_object = new TransactionObject($billNumber, TransactionType::TOP_UP, 1, $company->id, 1, PaymentMethodType::PAYMENT_GATEWAY, $amount, $amount, 1, 1, 1, 0, 0, null, ApprovalStatus::PENDING_SUBMISSION, [], $billPlzBill);

                /** @var Transaction $transaction */
                $transaction = $this->createsTransaction->execute($wallet, $transaction_object);


                // on billplz callback url
                $status = $this->faker->randomElement([ApprovalStatus::APPROVED, ApprovalStatus::REJECTED]);
                $this->updatesTransactionStatus->execute($transaction, $status);
                if($status === ApprovalStatus::APPROVED) {
                    $this->updatesWalletBalance->execute($wallet, $amount);
                }
            }

            // === //
            //  8  // ========================== //
            //  Create Recipient Bank Accounts   //
            // ================================= //
            // bank accounts are used to store bank account details, and can be used in a variety of ways
            // Bank types:
            // 1. PERSONAL : belong to the same entity
            // 2. EXTERNAL : Doesn't belong to the entity, belongs to an external entity;
            // 3. ALIPAY : : Is an external entity, but flag the type of bank as alipay e-wallet;
            //
            // here are some of the current use cases for banks in our application:
            // 1. Recipient bank (EXTERNAL) (the account the customer is requesting to transfer funds to)
            // 2. AliPay Transfer (EXTERNAL) (the account the customer is requesting to transfer funds to when bank type is ALIPAY)
            // 3. Refund bank (PERSONAL) (the account the customer is requesting his order refunds to be transferred to)
            $bank_object = new BankObject($company->id, $this->faker->numberBetween(2, 3),
                $this->faker->company, $this->faker->name,  $this->faker->bankAccountNumber,
                $this->faker->city,  null,  null,
                2, $this->faker->company);

            // todo create multiple bank accounts with multiple types
            $bank = $this->createsBank->execute($bank_object);

            // generate random number of bookings
            for($orderLoop=1; $orderLoop <= rand(1, 5); $orderLoop++) {
                echo 'booking created';
                // === //
                //  9  // ========= //
                //  Create Booking  //
                // ================ //
                // A booking is simply a transfer order to a supplier/manufacturer bank account overseas
                // to pay for goods they are buying from overseas. the booking is not proceed until the
                // customer requests to make a payment, when the customer start the payment process he
                // will receive a quote for the cost to transfer the booked amount (e.g. 100 USD) in RM

                // bookings require 2 actions from the customer to be completed
                // 1. Make Full payment **
                // 2. Provide Purchase Order (itemized list of the products they are buying)

                // ** A booking will be the sum of payments transferred to one bank account
                // but can be partially paid (e.g. 1000 USD can be paid:  $300 deposit + $700 balance)
                // A shipping label can be re-used, and each batch that arrives at the supplier warehouse
                // is referred to as a packing list. more on this later.

                // service types are configured by the super admin from the settings
                // it will include things like conversion rates, service charge, etc..
                // and can be used to place different type of transfer orders (e.g. 1 day transfer, 3 days transfer, 1688 Payment)

                // randomly selects a service type
                $service = ServiceType::inRandomOrder()->first();

                // Booking human readable id
                $reference = $this->generatesBookingMarking->execute();

                // random currency booking (CNY, USD)
                $bookedCurrency = 2;
                $bank = $company->banks()->inRandomOrder()->first();
                $object = new BookingObject($service->id, $bank->id, $reference, $this->faker->numberBetween(10, 300000), $bookedCurrency,  $bookedCurrency, 1);
                $booking = $this->createsBooking->execute($company, $object);


                // === //
                //  10  // ====== //
                //  make payment  //
                // ============== //
                // There are few type of transactions related to a booking:
                // TransactionType::PAYMENT : is used for 2 type of use cases (1. payments to transfer orders, 2. payment out of wallet) and is attached to a booking;
                // TransactionType::BILL : is to represent the payment out to CIEF currency supplier (expenses) and is attached to a transaction of type TransactionType::PAYMENT;
                // TransactionType::TRANSFER_FEE : is to represent the transfer fee charged by CIEF currency supplier is attached to a transaction type TransactionType::BILL;
                // TransactionType::REFUND : is to represent a request for refund on a payment, and  is attached to a transaction type TransactionType::PAYMENT;

                $numberOfPayments = $this->faker->numberBetween(0, 3);

                for ($paymentLoop=0; $paymentLoop <= $numberOfPayments; $paymentLoop++) {
                    echo 'payment created';
                    $shouldSubmit = $this->faker->numberBetween(0, 1);
                    $shouldApprove = $this->faker->numberBetween(0, 1);
                    if ($numberOfPayments > 1){

                        $amount = $booking->fix_amount / $numberOfPayments;

                        $conversionObject = new CurrencyConversionObject(floatval(str_replace(',', '', $amount)), $booking->convertible_currency_id, $booking->service_id,  $booking->fix_currency_id === 1 ? 0:1, PaymentMethodType::PAYMENT_METHODS['cash']);

                        $configurations = $this->fetchBookingQuotation->execute($booking->company, $conversionObject);

                        $billNumber = $this->generatesTransactionBillNumber->execute('PYMT-');

                        $object = new TransactionObject($billNumber, TransactionType::PAYMENT, 1, $booking->company->id,
                            $configurations->getConfigurations()->getBankId(), $configurations->getConversionObject()->getPaymentMethod(),
                            $configurations->getTotal(), $configurations->getForeignTotal(), 1,
                            $configurations->getConversionObject()->getCurrencyId(), $configurations->getConfigurations()->getRate(),
                            $configurations->getTax(), $configurations->getServiceCharge(), Carbon::now()->addMinutes(10), ApprovalStatus::PENDING_SUBMISSION, [], null);

                        /** @var Transaction $transaction */
                        /** @var Booking $booking */
                        $transaction = $this->createsTransaction->execute($booking, $object);

                        if($shouldSubmit || $shouldApprove) {
                            $object = new DocumentObject( DocumentType::CUSTOMER_PAYMENT_PROOF, ['data:image/png;base64,[encoded data, 4548 characters]'],
                                '', ApprovalStatus::PENDING_VERIFICATION, 'payments');

                            /** @var Transaction $transaction */
                            /** @var Document $document */
                            $document = $this->createsDocument->execute($transaction, $object);
                            $this->createsFiles->execute($document, $object);
                            $this->updatesTransactionStatus->execute($transaction, ApprovalStatus::PENDING_SUBMISSION);
                        }

                        if($shouldApprove) {
                            $this->approvesDocument->execute($document);
                            $this->updatesTransactionStatus->execute($transaction, ApprovalStatus::APPROVED);

                            $shouldReject = $this->faker->numberBetween(0, 1);
                            if($shouldReject){
                                $this->rejectsDocument->execute($document);
                                $this->updatesTransactionStatus->execute($transaction, ApprovalStatus::REJECTED);
                            }
                        }
                    }

                }

                // creating the purchase order can happen before or after the payment is made, the customer needs to fill up the list of product
                // they are buying and attaching it to the booking, a purchase order is a transaction of type TransactionType::PURCHASE_ORDER
                $billNumber = $this->generatesTransactionBillNumber->execute('PO-');


                $shouldSubmit = $this->faker->numberBetween(0, 1);
                $shouldApprove = $this->faker->numberBetween(0, 1);

                if($shouldSubmit){
                    $completeSubmission = $this->faker->numberBetween(0, 1);

                    $quantity = $this->faker->numberBetween(5, 200);
                    $unitPrice = $booking->fix_amount / $quantity;

                    $products = collect([[
                        'stockCode' => $this->faker->numerify('#####'),
                        'description' => $this->faker->text,
                        'quantity' => $completeSubmission ? $quantity : $quantity - $this->faker->numberBetween(1, 4),
                        'unit_price' => (string) round($unitPrice, 5)
                    ]]);

                    $total = $products->first()['quantity'] * (float) $products->first()['unit_price'];

                    $object = new TransactionObject($billNumber, TransactionType::PURCHASE_ORDER, $booking->company->id, 1,
                        1, PaymentMethodType::CASH,
                        $total, $total, $booking->fix_currency_id, $booking->fix_currency_id,
                        1, 0, 0, null, ApprovalStatus::PENDING_SUBMISSION, $products->toArray());

                    $transaction = $this->createPurchaseOrderTransactionProcessor->execute($booking, $object);
                }

                if($transaction->status === ApprovalStatus::PENDING_VERIFICATION) {
                    $this->updatesTransactionStatus->execute($transaction, ApprovalStatus::APPROVED);
                }



            }

            // when processing a customer order, we will place an order with one of our currency supplier which will generate a transaction type TransactionType::BILL
            // and attach it to the customer payment TransactionType::PAYMENT, and it will update the TransactionType::PAYMENT status to ApprovalStatus::COMPLETED
            $totalApprovedPayments = Transaction::where('type', TransactionType::PAYMENT)->where('status', 2)->count();
            $totalWhiteForms = round($totalApprovedPayments / $this->faker->numberBetween(2, 5));
            $perWhiteForm = $totalApprovedPayments / (round($totalWhiteForms / 2) ?: 1);

            for($orderLoop=1; $orderLoop <= ($totalWhiteForms / 2); $orderLoop++) {
                $supplier = Company::where('business_type', BusinessType::CURRENCY_VENDOR)->inRandomOrder()->first();
                $rate = $this->faker->randomFloat(5, 1.3, 1.6);
                $payments = Transaction::where('type', TransactionType::PAYMENT)->where('status', ApprovalStatus::APPROVED)->inRandomOrder()->limit($perWhiteForm)->get();

                $this->createSupplierTransactionProcessor->execute($supplier, $rate, $payments->toArray());

                $group = new Group();
                $group->save();

                $issuer = '';
                $receiver = '';
                $amount = 0;
                $original_amount = 0;
                $currency_id = 0;
                $original_currency_id = '';
                $currency_rate = '';
                $tax = 0;
                $service_charge = 0;

                foreach ($this->createSupplierTransactionProcessor->getBills() as $key => $row) {
                    $group->transactions()->sync($row->id, false);
                    $issuer = $row->issuer;
                    $receiver = $row->receiver;
                    $amount += $row->amount;
                    $original_amount += $row->original_amount;
                    $currency_id = $row->currency_id;
                    $original_currency_id = $row->original_currency_id;
                    $currency_rate = $row->currency_rate;
                    $tax += $row->tax;
                    $service_charge += $row->service_charge;
                }

                $group->issuer = $issuer;
                $group->receiver = $receiver;
                $group->reference = $this->generatesTransactionBillNumber->execute('SPO-');
                $group->amount = $amount;
                $group->original_amount = $original_amount;
                $group->currency_id = $currency_id;
                $group->original_currency_id = $original_currency_id;
                $group->currency_rate = $currency_rate;
                $group->tax = $tax;
                $group->service_charge = $service_charge;

                $group->update();

                $pdf = LaravelMpdf::loadView('pages.pdfs.currency_vendor_order', ['transactions' => $this->createSupplierTransactionProcessor->getBills(), 'transferFeeTransactions' => $this->createSupplierTransactionProcessor->getTransferTransactions(), 'supplier' => $supplier]);

                $object = new DocumentObject(
                    DocumentType::CURRENCY_VENDOR_ORDER,
                    [chunk_split('data:application/pdf;base64,'.base64_encode($pdf->output()))],
                    '',
                    ApprovalStatus::COMPLETED,
                    'currency_vendor_order'
                );

                /** @var Document $document */
                $document = $this->createsDocument->execute($group, $object);
                $this->createsFiles->execute($document, $object);


                foreach ($payments as $payment) {
                    $chinaBankSlipUploaded = $this->faker->numberBetween(0, 1);

                    if($chinaBankSlipUploaded){
                        $bill = $payment->transactions()->where('type', TransactionType::BILL)->first();

                        // when our currency supplier completes the transfer they will send us the bank slip as proof of payment, then the admin user
                        // will upload the bank slip document and attaching it to transaction type TransactionType::BILL
                        $object = new DocumentObject( DocumentType::CUSTOMER_PAYMENT_PROOF, ['data:image/png;base64,[encoded data, 4548 characters]'],
                            '', ApprovalStatus::APPROVED, 'china_bank_slip');
                        /** @var Document $document */
                        $document = $this->createsDocument->execute($bill, $object);
                        $this->createsFiles->execute($document, $object);

                        $this->updatesTransactionStatus->execute($bill, ApprovalStatus::APPROVED);

                        // the invoicing documents will be generated once they 2 conditions are met:
                        // 1. Full payment completed (completed is flagged when the china payment proof is uploaded)
                        // 2. The purchase order is filled and approved (when the purchase order is not filled for more than 2 months the system will automatically generate a random products for Purchase order to close the order)

                        // once the invoice is generated the transaction table will include 2 new transaction type TransactionType::INVOICE, TransactionType::SUPPLIER_DELIVERY
                        // and for documents will be generated and attached to the booking.
                        // once this process is complete the booking status will update to ApprovalStatus::COMPLETED
                        $this->createInvoiceTransactionProcessor->execute($booking, "", null, [
                                'generateEInvoice' => false,
                                'generateEInvoiceWithNormalInvoiceTemplate' => false,
                                'generateEInvoiceRefund' => false,
                                'bookingOriginalStatus' => ApprovalStatus::COMPLETED //cief todo: 151
                            ]
                        );
                    }
                }


            }
        }

    }

}
