From 7cb2d275255da7a0a28df5a07e6b09d8ab9fcd5a Mon Sep 17 00:00:00 2001 From: kaioken Date: Tue, 17 Sep 2024 20:34:35 -0400 Subject: [PATCH] feat: add test --- .../Orders/DraftOrderManagementMutation.php | 48 +++--- graphql/schemas/Souk/order.graphql | 8 +- .../Inventory/Regions/Models/Regions.php | 5 + .../Orders/Actions/CreateDraftOrderAction.php | 50 +++++++ .../Orders/DataTransferObject/DraftOrder.php | 139 ++++++++++++++++++ .../Orders/DataTransferObject/OrderItem.php | 38 +++++ src/Domains/Souk/Orders/Models/Order.php | 18 +++ .../Souk/Orders/Observers/OrderObserver.php | 15 ++ tests/GraphQL/Souk/OrderTest.php | 51 +++++++ 9 files changed, 344 insertions(+), 28 deletions(-) create mode 100644 src/Domains/Souk/Orders/Actions/CreateDraftOrderAction.php create mode 100644 src/Domains/Souk/Orders/DataTransferObject/DraftOrder.php create mode 100644 src/Domains/Souk/Orders/Observers/OrderObserver.php create mode 100644 tests/GraphQL/Souk/OrderTest.php diff --git a/app/GraphQL/Souk/Mutations/Orders/DraftOrderManagementMutation.php b/app/GraphQL/Souk/Mutations/Orders/DraftOrderManagementMutation.php index cf54d65f7..0b511dfc4 100644 --- a/app/GraphQL/Souk/Mutations/Orders/DraftOrderManagementMutation.php +++ b/app/GraphQL/Souk/Mutations/Orders/DraftOrderManagementMutation.php @@ -5,37 +5,35 @@ namespace App\GraphQL\Souk\Mutations\Orders; use Kanvas\Apps\Models\Apps; -use Kanvas\Guild\Customers\Enums\ContactTypeEnum; -use Kanvas\Inventory\Variants\Models\Variants; -use Kanvas\Social\Interactions\Actions\CreateInteraction; -use Kanvas\Social\Interactions\Actions\CreateUserInteractionAction; -use Kanvas\Social\Interactions\DataTransferObject\Interaction; -use Kanvas\Social\Interactions\DataTransferObject\UserInteraction; -use Kanvas\Souk\Orders\DataTransferObject\DirectOrder; -use Kanvas\Souk\Payments\DataTransferObject\CreditCard; -use Kanvas\Souk\Payments\Providers\AuthorizeNetPaymentProcessor; +use Kanvas\Companies\Models\CompaniesBranches; +use Kanvas\Exceptions\ValidationException; +use Kanvas\Inventory\Regions\Models\Regions; +use Kanvas\Souk\Orders\Actions\CreateDraftOrderAction; +use Kanvas\Souk\Orders\DataTransferObject\DraftOrder; +use Kanvas\Souk\Orders\Models\Order; class DraftOrderManagementMutation { - public function create(mixed $root, array $request): array + public function create(mixed $root, array $request): Order { $user = auth()->user(); + $app = app(Apps::class); + $branch = app(CompaniesBranches::class); - print_R($request); - $customer = $request['input']['customer']; - $customer['contacts'] = [ - [ - 'contact' => $request['input']['email'], - 'contacts_types_id' => ContactTypeEnum::EMAIL->value, - 'weight' => 0 - ] - ]; - - print_r($customer); die(); - - - return []; - } + if ($branch->id === null) { + throw new ValidationException('Missing Location Header'); + } + + $region = Regions::getByIdFromCompanyApp($request['input']['region_id'], $branch->company, $app); + $draftOrder = DraftOrder::viaRequest( + $app, + $branch, + $user, + $region, + $request + ); + return (new CreateDraftOrderAction($draftOrder))->execute(); + } } diff --git a/graphql/schemas/Souk/order.graphql b/graphql/schemas/Souk/order.graphql index 07f5d5df6..52bc1df4d 100644 --- a/graphql/schemas/Souk/order.graphql +++ b/graphql/schemas/Souk/order.graphql @@ -93,9 +93,11 @@ input OrderInput { input DraftOrderInput { email: String! + phone: String customer: PeopleInput! - billing: CreditCardBillingInput - address: AddressInput + region_id: ID! + billing_address: CreditCardBillingInput + shipping_address: AddressInput items: [OrderLineItemInput!]! note: String metadata: Mixed @@ -119,7 +121,7 @@ extend type Mutation @guard { @field( resolver: "App\\GraphQL\\Souk\\Mutations\\Orders\\OrderManagementMutation@create" ) - createDraftOrder(input: DraftOrderInput!): Mixed! + createDraftOrder(input: DraftOrderInput!): Order! @field( resolver: "App\\GraphQL\\Souk\\Mutations\\Orders\\DraftOrderManagementMutation@create" ) diff --git a/src/Domains/Inventory/Regions/Models/Regions.php b/src/Domains/Inventory/Regions/Models/Regions.php index 15de2e422..3e4a978ae 100644 --- a/src/Domains/Inventory/Regions/Models/Regions.php +++ b/src/Domains/Inventory/Regions/Models/Regions.php @@ -44,6 +44,11 @@ public function currencies(): BelongsTo return $this->belongsTo(Currencies::class, 'currency_id'); } + public function currency(): BelongsTo + { + return $this->belongsTo(Currencies::class, 'currency_id'); + } + public function warehouses(): HasMany { return $this->hasMany(Warehouses::class, 'regions_id'); diff --git a/src/Domains/Souk/Orders/Actions/CreateDraftOrderAction.php b/src/Domains/Souk/Orders/Actions/CreateDraftOrderAction.php new file mode 100644 index 000000000..f46740d39 --- /dev/null +++ b/src/Domains/Souk/Orders/Actions/CreateDraftOrderAction.php @@ -0,0 +1,50 @@ +transaction(function () { + $order = new ModelsOrder(); + $order->apps_id = $this->orderData->app->getId(); + $order->region_id = $this->orderData->region->getId(); + $order->companies_id = $this->orderData->branch->company->getId(); + $order->people_id = $this->orderData->people->getId(); + $order->users_id = $this->orderData->user->getId(); + $order->user_email = $this->orderData->email; + $order->user_phone = $this->orderData->phone; + $order->token = null; + $order->shipping_address_id = $this->orderData?->shippingAddress?->getId() ?? null; + $order->billing_address_id = $this->orderData?->billingAddress?->getId() ?? null; + $order->total_gross_amount = $this->orderData->total; + $order->total_net_amount = $this->orderData->total - $this->orderData->taxes; + $order->shipping_price_gross_amount = $this->orderData->totalShipping; + $order->shipping_price_net_amount = $this->orderData->totalShipping; + $order->discount_amount = $this->orderData->totalDiscount; + $order->status = $this->orderData->status; + $order->fulfillment_status = 'pending'; + $order->currency = $this->orderData->currency->code; + $order->metadata = $this->orderData->metadata; + $order->payment_gateway_names = $this->orderData->paymentGatewayName; + //$order->language_code = $this->orderData->languageCode; + $order->saveOrFail(); + + $order->addItems($this->orderData->items); + + return $order; + }); + } +} diff --git a/src/Domains/Souk/Orders/DataTransferObject/DraftOrder.php b/src/Domains/Souk/Orders/DataTransferObject/DraftOrder.php new file mode 100644 index 000000000..a176a76b1 --- /dev/null +++ b/src/Domains/Souk/Orders/DataTransferObject/DraftOrder.php @@ -0,0 +1,139 @@ + $request['input']['email'], + 'contacts_types_id' => ContactTypeEnum::EMAIL->value, + 'weight' => 0, + ], + ]; + + $people = People::from([ + 'app' => $app, + 'branch' => $branch, + 'user' => $user, + 'firstname' => $customer['firstname'], + 'middlename' => $customer['middlename'] ?? null, + 'lastname' => $customer['lastname'] ?? null, + 'contacts' => Contact::collect($customer['contacts'] ?? [], DataCollection::class), + 'address' => Address::collect([], DataCollection::class), + 'id' => $data['id'] ?? 0, + 'dob' => $data['dob'] ?? null, + 'facebook_contact_id' => $data['facebook_contact_id'] ?? null, + 'google_contact_id' => $data['google_contact_id'] ?? null, + 'apple_contact_id' => $data['apple_contact_id'] ?? null, + 'linkedin_contact_id' => $data['linkedin_contact_id'] ?? null, + 'tags' => $data['tags'] ?? [], + 'custom_fields' => $data['custom_fields'] ?? [], + ]); + + $people = (new CreatePeopleAction($people))->execute(); + + $shippingAddress = ! empty($request['input']['shipping_address']['address1']) ? + $customer->addAddress(new Address( + address: $request['input']['shipping_address']['address1'] ?? '', + address_2: $request['input']['shipping_address']['address2'] ?? '', + city: $request['input']['shipping_address']['city'] ?? '', + state: $request['input']['shipping_address']['province'] ?? '', + country: $request['input']['shipping_address']['country'] ?? '', + zipcode: $request['input']['shipping_address']['zip'] ?? '' + )) + : null; + + $billingAddress = ! empty($request['input']['billing_address']['address1']) ? + $customer->addAddress(new Address( + address: $request['input']['billing_address']['address1'], + address_2: $request['input']['billing_address']['address2'], + city: $request['input']['billing_address']['city'], + state: $request['input']['billing_address']['province'], + country: $request['input']['billing_address']['country'], + zipcode: $request['input']['billing_address']['zip'] + )) + : null; + + $total = 0; + $totalTax = 0; + $totalDiscount = 0; + $totalShipping = 0; + $lineItems = []; + foreach ($request['input']['items'] as $key => $lineItem) { + $lineItems[$key] = OrderItem::viaRequest($app, $branch->company, $region, $lineItem); + $total += $lineItems[$key]->getTotal(); + $totalTax = $lineItems[$key]->getTotalTax(); + $totalDiscount = $lineItems[$key]->getTotalDiscount(); + } + + return new self( + $app, + $branch, + $region, + $user, + $request['input']['email'], + $people, + $total, + $totalTax, + $totalDiscount, + $totalShipping, + 'draft', + $region->currency, + OrderItem::collect($lineItems, DataCollection::class), + ['manual'], + $shippingAddress, + $billingAddress, + $request['input']['phone'] ?? null, + $request['input']['notes'] ?? null, + $request['input']['metadata'] ?? null, + ); + } +} diff --git a/src/Domains/Souk/Orders/DataTransferObject/OrderItem.php b/src/Domains/Souk/Orders/DataTransferObject/OrderItem.php index 059e400a4..40fc2d25a 100644 --- a/src/Domains/Souk/Orders/DataTransferObject/OrderItem.php +++ b/src/Domains/Souk/Orders/DataTransferObject/OrderItem.php @@ -4,8 +4,11 @@ namespace Kanvas\Souk\Orders\DataTransferObject; +use Baka\Contracts\AppInterface; +use Baka\Contracts\CompanyInterface; use Kanvas\Apps\Models\Apps; use Kanvas\Currencies\Models\Currencies; +use Kanvas\Inventory\Regions\Models\Regions; use Kanvas\Inventory\Variants\Models\Variants; use Spatie\LaravelData\Data; @@ -24,4 +27,39 @@ public function __construct( public readonly int $quantityShipped = 0, ) { } + + public static function viaRequest(AppInterface $app, CompanyInterface $company, Regions $region, array $request): self + { + $variant = Variants::getByIdFromCompanyApp($request['variant_id'], $company, $app); + $warehouse = $region->warehouses()->firstOrFail(); //@todo get product warehouse with stock + $price = $variant->getPrice($warehouse); + + return new self( + app: $app, + variant: $variant, + name: $variant->name, + sku: $variant->sku, + quantity: $request['quantity'], + price: $price, + tax: 0, //@todo get from region + discount: 0, + currency: $region->currency, + quantityShipped: $request['quantity_shipped'] ?? 0 + ); + } + + public function getTotal(): float + { + return $this->price * $this->quantity; + } + + public function getTotalDiscount(): float + { + return $this->discount * $this->quantity; + } + + public function getTotalTax(): float + { + return $this->tax * $this->quantity; + } } diff --git a/src/Domains/Souk/Orders/Models/Order.php b/src/Domains/Souk/Orders/Models/Order.php index 3c6d84723..a14c18740 100644 --- a/src/Domains/Souk/Orders/Models/Order.php +++ b/src/Domains/Souk/Orders/Models/Order.php @@ -7,6 +7,7 @@ use Baka\Casts\Json; use Baka\Traits\UuidTrait; use Baka\Users\Contracts\UserInterface; +use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -15,6 +16,7 @@ use Kanvas\Inventory\Regions\Models\Regions; use Kanvas\Souk\Models\BaseModel; use Kanvas\Souk\Orders\DataTransferObject\OrderItem as OrderItemDto; +use Kanvas\Souk\Orders\Observers\OrderObserver; use Kanvas\Users\Models\Users; use Kanvas\Workflow\Traits\CanUseWorkflow; use Laravel\Scout\Searchable; @@ -65,6 +67,7 @@ * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at */ +#[ObservedBy(OrderObserver::class)] class Order extends BaseModel { use UuidTrait; @@ -172,4 +175,19 @@ public function cancel(): void $this->status = 'cancelled'; $this->saveOrFail(); } + + public function generateOrderNumber(): int + { + // Lock the orders table while retrieving the last order + $lastOrder = Order::where('companies_id', $this->companies_id) + ->where('apps_id', $this->apps_id) + ->lockForUpdate() // Ensure no race conditions + ->latest('id') + ->first(); + + $lastOrderNumber = $lastOrder ? intval($lastOrder->order_number) : 0; + $newOrderNumber = $lastOrderNumber + 1; + + return $newOrderNumber; + } } diff --git a/src/Domains/Souk/Orders/Observers/OrderObserver.php b/src/Domains/Souk/Orders/Observers/OrderObserver.php new file mode 100644 index 000000000..40f9a1aa2 --- /dev/null +++ b/src/Domains/Souk/Orders/Observers/OrderObserver.php @@ -0,0 +1,15 @@ +order_number = $order->generateOrderNumber(); + } +} diff --git a/tests/GraphQL/Souk/OrderTest.php b/tests/GraphQL/Souk/OrderTest.php new file mode 100644 index 000000000..6b4cf3e52 --- /dev/null +++ b/tests/GraphQL/Souk/OrderTest.php @@ -0,0 +1,51 @@ +warehouse->region; + $company = $region->company; + $user = $company->user; + + // Prepare input data for the draft order + $data = [ + 'email' => fake()->email(), + 'region_id' => $region->getId(), + 'metadata' => hash('sha256', random_bytes(10)), + 'customer' => [ + 'firstname' => fake()->firstName(), + 'lastname' => fake()->lastName(), + ], + 'items' => [ + [ + 'variant_id' => $variantWarehouse->variant->getId(), + 'quantity' => 1, + ], + ], + ]; + + // Perform GraphQL mutation to create a draft order + $response = $this->graphQL(' + mutation createDraftOrder($input: DraftOrderInput!) { + createDraftOrder(input: $input) { + id + } + } + ', [ + 'input' => $data, + ], [], [ + 'X-Kanvas-Location' => $company->branch->uuid, + ]); + + $response->assertSuccessful(); + } +}