Przeglądaj źródła

同步官方api代码

chengwl 1 tydzień temu
rodzic
commit
509ce7c28c
100 zmienionych plików z 4474 dodań i 16 usunięć
  1. 21 15
      packages/Webkul/BagistoApi/config/api-platform-vendor.php
  2. 8 1
      packages/Webkul/BagistoApi/config/api-platform.php
  3. 90 0
      packages/Webkul/BagistoApi/src/Admin/Audit/AdminApiAuditContext.php
  4. 197 0
      packages/Webkul/BagistoApi/src/Admin/Audit/AdminApiAuditRecorder.php
  5. 133 0
      packages/Webkul/BagistoApi/src/Admin/Auth/AdminApiGuard.php
  6. 45 0
      packages/Webkul/BagistoApi/src/Admin/Config/acl.php
  7. 43 0
      packages/Webkul/BagistoApi/src/Admin/Config/audit.php
  8. 8 0
      packages/Webkul/BagistoApi/src/Admin/Config/auth/guards.php
  9. 24 0
      packages/Webkul/BagistoApi/src/Admin/Config/menu.php
  10. 36 0
      packages/Webkul/BagistoApi/src/Admin/Config/system.php
  11. 97 0
      packages/Webkul/BagistoApi/src/Admin/DataGrids/AuditHistoryDataGrid.php
  12. 157 0
      packages/Webkul/BagistoApi/src/Admin/DataGrids/IntegrationDataGrid.php
  13. 98 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeCreateInput.php
  14. 37 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeFamilyCreateInput.php
  15. 40 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeFamilyUpdateInput.php
  16. 21 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeMassDeleteInput.php
  17. 36 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeOptionDto.php
  18. 38 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeOptionInput.php
  19. 25 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeOptionTranslationDto.php
  20. 24 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeTranslationDto.php
  21. 106 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeUpdateInput.php
  22. 13 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCancelOrderInput.php
  23. 65 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartAddItemInput.php
  24. 39 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartAddressDto.php
  25. 20 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartCouponInput.php
  26. 59 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartItemDto.php
  27. 18 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartRemoveItemInput.php
  28. 30 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartSaveAddressInput.php
  29. 21 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartSetPaymentMethodInput.php
  30. 21 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartSetShippingMethodInput.php
  31. 25 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartUpdateItemsInput.php
  32. 19 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductCopyInput.php
  33. 46 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductCreateInput.php
  34. 38 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductCustomerGroupPriceCreateInput.php
  35. 42 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductCustomerGroupPriceUpdateInput.php
  36. 23 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductImageDeleteInput.php
  37. 36 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductImageReorderInput.php
  38. 54 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductInventoryUpdateInput.php
  39. 17 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductMassDeleteInput.php
  40. 21 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductMassUpdateStatusInput.php
  41. 187 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductRestDto.php
  42. 154 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductUpdateInput.php
  43. 21 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductVideoDeleteInput.php
  44. 80 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCategoryCreateInput.php
  45. 17 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCategoryMassDeleteInput.php
  46. 21 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCategoryMassUpdateStatusInput.php
  47. 34 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCategoryTranslationDto.php
  48. 68 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCategoryUpdateInput.php
  49. 50 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCmsPageCreateInput.php
  50. 57 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCmsPageDetailDto.php
  51. 56 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCmsPageListDto.php
  52. 17 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCmsPageMassDeleteInput.php
  53. 45 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCmsPageUpdateInput.php
  54. 64 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminConfigurationTreeField.php
  55. 43 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminConfigurationTreeItem.php
  56. 17 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminConfigurationTreeOption.php
  57. 47 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminConfigurationUpdateInput.php
  58. 19 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCreateDraftCartInput.php
  59. 121 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCreateReturnInput.php
  60. 48 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerAddressCreateInput.php
  61. 52 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerAddressUpdateInput.php
  62. 45 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerCreateInput.php
  63. 50 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerDetailDto.php
  64. 20 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGdprDownloadDataInput.php
  65. 27 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGdprProcessInput.php
  66. 25 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGdprUpdateInput.php
  67. 15 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGroupCreateInput.php
  68. 14 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGroupMassDeleteInput.php
  69. 19 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGroupUpdateInput.php
  70. 13 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerImpersonateInput.php
  71. 44 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerListDto.php
  72. 14 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerMassDeleteInput.php
  73. 18 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerMassUpdateStatusInput.php
  74. 21 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerNoteCreateInput.php
  75. 31 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerReviewDetailDto.php
  76. 29 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerReviewListDto.php
  77. 14 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerReviewMassDeleteInput.php
  78. 18 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerReviewMassUpdateStatusInput.php
  79. 17 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerReviewUpdateInput.php
  80. 46 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerUpdateInput.php
  81. 23 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminEuWithdrawalActionInput.php
  82. 37 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminEuWithdrawalDeclineInput.php
  83. 37 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminEuWithdrawalMarkRefundedInput.php
  84. 27 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminInvoiceCreateInput.php
  85. 18 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminInvoiceMassUpdateStatusInput.php
  86. 197 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminInvoiceRestDto.php
  87. 17 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminInvoiceSendDuplicateInput.php
  88. 43 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCampaignCreateInput.php
  89. 61 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCampaignRestDto.php
  90. 20 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCampaignSendInput.php
  91. 44 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCampaignUpdateInput.php
  92. 17 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCopyInput.php
  93. 32 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCouponCreateInput.php
  94. 17 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCouponDeleteInput.php
  95. 38 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCouponGenerateInput.php
  96. 18 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCouponMassDeleteInput.php
  97. 106 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCreateInput.php
  98. 17 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleMassDeleteInput.php
  99. 116 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleRestDto.php
  100. 0 0
      packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleUpdateInput.php

+ 21 - 15
packages/Webkul/BagistoApi/config/api-platform-vendor.php

@@ -15,9 +15,9 @@ use ApiPlatform\Metadata\Operation\DashPathSegmentNameGenerator;
 use ApiPlatform\Metadata\UrlGeneratorInterface;
 use Illuminate\Auth\Access\AuthorizationException;
 use Illuminate\Auth\AuthenticationException;
-use Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter;
 use Webkul\BagistoApi\Exception\InvalidInputException;
 use Webkul\BagistoApi\Exception\ValidationException;
+use Webkul\BagistoApi\Serializer\OutputOnlySnakeToCamelNameConverter;
 
 return [
     'title'       => '',
@@ -27,20 +27,17 @@ return [
 
     'routes' => [
         'domain' => null,
-        // Global middleware applied to every API Platform routes
-        // HandleInvalidInputException: Catches validation errors and returns RFC 7807 format
-        // VerifyStorefrontKey: Validates X-STOREFRONT-KEY header and rate limiting for shop APIs
-        // BagistoApiDocumentationMiddleware: Handles custom /api index and documentation pages
-        // ForceApiJson: Ensures API responses have JSON content-type
-        // CacheResponse: Using custom ApiAwareResponseCache profile that:
-        // - Excludes API routes from caching (APIs need fresh data)
-        // - Caches shop pages for performance
-        // - Only caches HTML, not JSON responses
+     
         'middleware' => [
+            'Webkul\BagistoApi\Http\Middleware\ParseMultipartFormData',
+            'Webkul\BagistoApi\Http\Middleware\NormalizeEmptyJsonBody',
             'Webkul\BagistoApi\Http\Middleware\HandleInvalidInputException',
             'Webkul\BagistoApi\Http\Middleware\SecurityHeaders',
             'Webkul\BagistoApi\Http\Middleware\LogApiRequests',
             'Webkul\BagistoApi\Http\Middleware\VerifyStorefrontKey',
+            'Webkul\BagistoApi\Http\Middleware\EnforceAdminApiAuth',
+            'Webkul\BagistoApi\Http\Middleware\ThrottleAdminApi',
+            'Webkul\BagistoApi\Http\Middleware\SetAdminApiAuditContext',
             'Webkul\BagistoApi\Http\Middleware\BagistoApiDocumentationMiddleware',
             'Webkul\BagistoApi\Http\Middleware\ForceApiJson',
             'Webkul\BagistoApi\Http\Middleware\PaginationHeaders',
@@ -51,6 +48,8 @@ return [
 
     'resources' => [
         base_path('vendor/bagisto/bagisto-api/src/Models/'),
+        base_path('vendor/bagisto/bagisto-api/src/Admin/Models/'),
+        base_path('vendor/bagisto/bagisto-api/src/Admin/Dto/'),
         base_path('vendor/bagisto/bagisto-api/src/Dto/ProductDetail/'),
         base_path('vendor/bagisto/bagisto-api/src/Dto/CustomerOrder/'),
     ],
@@ -117,7 +116,7 @@ return [
         'enabled' => true,
     ],
 
-    'name_converter' => SnakeCaseToCamelCaseNameConverter::class,
+    'name_converter' => OutputOnlySnakeToCamelNameConverter::class,
 
     'path_segment_name_generator' => DashPathSegmentNameGenerator::class,
 
@@ -130,11 +129,18 @@ return [
 
     'swagger_ui' => [
         'enabled' => true,
-        'apiKeys' => [
-            'api' => [
-                'name'   => 'Authorization',
-                'type'   => 'header',
+
+        // 'apiKeys' => [
+        //     'api' => [
+        //         'name'   => 'Authorization',
+        //         'type'   => 'header',
+        //         'scheme' => 'bearer',
+        //     ],
+        // ],
+        'http_auth' => [
+            'bearerAuth' => [
                 'scheme' => 'bearer',
+                'bearerFormat' => 'UUID',
             ],
         ],
     ],

+ 8 - 1
packages/Webkul/BagistoApi/config/api-platform.php

@@ -18,6 +18,7 @@ use Illuminate\Auth\AuthenticationException;
 use Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter;
 use Webkul\BagistoApi\Exception\InvalidInputException;
 use Webkul\BagistoApi\Exception\ValidationException;
+use Webkul\BagistoApi\Serializer\OutputOnlySnakeToCamelNameConverter;
 
 return [
     'title'       => '',
@@ -37,14 +38,20 @@ return [
         // - Caches shop pages for performance
         // - Only caches HTML, not JSON responses
         'middleware' => [
+            'Webkul\BagistoApi\Http\Middleware\ParseMultipartFormData',
+            'Webkul\BagistoApi\Http\Middleware\NormalizeEmptyJsonBody',
             'Webkul\BagistoApi\Http\Middleware\HandleInvalidInputException',
             'Webkul\BagistoApi\Http\Middleware\SecurityHeaders',
             'Webkul\BagistoApi\Http\Middleware\LogApiRequests',
             'Webkul\BagistoApi\Http\Middleware\VerifyStorefrontKey',
+            'Webkul\BagistoApi\Http\Middleware\EnforceAdminApiAuth',
+            'Webkul\BagistoApi\Http\Middleware\ThrottleAdminApi',
+            'Webkul\BagistoApi\Http\Middleware\SetAdminApiAuditContext',
             'Webkul\BagistoApi\Http\Middleware\SetLocaleChannel',
             'Webkul\BagistoApi\Http\Middleware\BagistoApiDocumentationMiddleware',
             'Webkul\BagistoApi\Http\Middleware\ForceApiJson',
             'Webkul\BagistoApi\Http\Middleware\WrapApiResponse',
+            'Webkul\BagistoApi\Http\Middleware\PaginationHeaders',
             'Spatie\ResponseCache\Middlewares\CacheResponse',
         ],
     ],
@@ -116,7 +123,7 @@ return [
         'enabled' => true,
     ],
 
-    'name_converter' => SnakeCaseToCamelCaseNameConverter::class,
+    'name_converter' => OutputOnlySnakeToCamelNameConverter::class,
 
     'path_segment_name_generator' => DashPathSegmentNameGenerator::class,
 

+ 90 - 0
packages/Webkul/BagistoApi/src/Admin/Audit/AdminApiAuditContext.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Audit;
+
+use Webkul\User\Models\Admin;
+
+/**
+ * Request-scoped holder for the "who / where" context of an admin-API write.
+ *
+ * Populated by SetAdminApiAuditContext middleware (only on admin-API write
+ * requests) and read by AdminApiAuditRecorder when Eloquent model events fire.
+ * Bound as a singleton, so it is effectively one-per-request.
+ */
+class AdminApiAuditContext
+{
+    protected bool $active = false;
+
+    public ?string $historyId = null;
+
+    public ?int $adminId = null;
+
+    public ?string $adminName = null;
+
+    public ?int $tokenId = null;
+
+    public ?string $tokenName = null;
+
+    public ?string $method = null;
+
+    public ?string $url = null;
+
+    public ?string $ipAddress = null;
+
+    public ?string $userAgent = null;
+
+    public ?string $tags = null;
+
+    /** Number of audit rows written during this request (envelope fallback). */
+    protected int $rowCount = 0;
+
+    public function activate(array $data): void
+    {
+        $this->active = true;
+        $this->historyId = $data['history_id'] ?? null;
+        $this->adminId = $data['admin_id'] ?? null;
+        $this->adminName = $data['admin_name'] ?? null;
+        $this->tokenId = $data['token_id'] ?? null;
+        $this->tokenName = $data['token_name'] ?? null;
+        $this->method = $data['method'] ?? null;
+        $this->url = $data['url'] ?? null;
+        $this->ipAddress = $data['ip_address'] ?? null;
+        $this->userAgent = $data['user_agent'] ?? null;
+        $this->tags = $data['tags'] ?? null;
+    }
+
+    public function isActive(): bool
+    {
+        return $this->active;
+    }
+
+    public function recordWritten(): void
+    {
+        $this->rowCount++;
+    }
+
+    public function rowCount(): int
+    {
+        return $this->rowCount;
+    }
+
+    /**
+     * The shared columns every audit row inherits from the request.
+     */
+    public function baseAttributes(): array
+    {
+        return [
+            'history_id' => $this->historyId,
+            'user_type' => $this->adminId ? Admin::class : null,
+            'user_id' => $this->adminId,
+            'admin_name' => $this->adminName,
+            'token_id' => $this->tokenId,
+            'token_name' => $this->tokenName,
+            'method' => $this->method,
+            'url' => $this->url,
+            'ip_address' => $this->ipAddress,
+            'user_agent' => $this->userAgent,
+            'tags' => $this->tags,
+        ];
+    }
+}

+ 197 - 0
packages/Webkul/BagistoApi/src/Admin/Audit/AdminApiAuditRecorder.php

@@ -0,0 +1,197 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Audit;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Event;
+use Webkul\BagistoApi\Admin\Models\AdminApiAudit;
+
+/**
+ * Records admin-API writes as audit rows by listening to Eloquent model events.
+ *
+ * Only acts when AdminApiAuditContext is active (i.e. an admin-API write request)
+ * — so normal web-panel changes and reads are never recorded, with zero
+ * per-model code and no Bagisto core edits.
+ *
+ * Bound as a singleton so the updating→updated diff stash survives within a
+ * request. Every write is wrapped so auditing can never break the real change.
+ */
+class AdminApiAuditRecorder
+{
+    /** Pre-save diffs keyed by spl_object_id, captured on `updating`. */
+    protected array $pending = [];
+
+    public function __construct(protected AdminApiAuditContext $context) {}
+
+    /**
+     * Register the Eloquent listeners. Called once from the service provider.
+     */
+    public function register(): void
+    {
+        Event::listen('eloquent.updating: *', [$this, 'onUpdating']);
+        Event::listen('eloquent.updated: *', [$this, 'onUpdated']);
+        Event::listen('eloquent.created: *', [$this, 'onCreated']);
+        Event::listen('eloquent.deleted: *', [$this, 'onDeleted']);
+    }
+
+    public function onUpdating(string $event, array $payload): void
+    {
+        $model = $payload[0] ?? null;
+        if (! $model instanceof Model || ! $this->shouldAudit($model)) {
+            return;
+        }
+
+        $dirty = $model->getDirty();
+        if (empty($dirty)) {
+            return;
+        }
+
+        $this->pending[spl_object_id($model)] = [
+            'old' => array_intersect_key($model->getOriginal(), $dirty),
+            'new' => $dirty,
+        ];
+    }
+
+    public function onUpdated(string $event, array $payload): void
+    {
+        $model = $payload[0] ?? null;
+        if (! $model instanceof Model) {
+            return;
+        }
+
+        $diff = $this->pending[spl_object_id($model)] ?? null;
+        unset($this->pending[spl_object_id($model)]);
+
+        if (! $diff || ! $this->shouldAudit($model)) {
+            return;
+        }
+
+        $this->persist($model, 'updated', $diff['old'], $diff['new']);
+    }
+
+    public function onCreated(string $event, array $payload): void
+    {
+        $model = $payload[0] ?? null;
+        if (! $model instanceof Model || ! $this->shouldAudit($model)) {
+            return;
+        }
+
+        $this->persist($model, 'created', null, $model->getAttributes());
+    }
+
+    public function onDeleted(string $event, array $payload): void
+    {
+        $model = $payload[0] ?? null;
+        if (! $model instanceof Model || ! $this->shouldAudit($model)) {
+            return;
+        }
+
+        $this->persist($model, 'deleted', $model->getAttributes(), null);
+    }
+
+    /**
+     * Envelope fallback — one row when a write produced no model events.
+     */
+    public function writeEnvelope(?string $auditableType, $auditableId, string $event, ?array $newValues): void
+    {
+        if (! $this->context->isActive()) {
+            return;
+        }
+
+        try {
+            AdminApiAudit::create(array_merge($this->context->baseAttributes(), [
+                'version_id' => $auditableId ? $this->nextVersion($auditableType, $auditableId) : 1,
+                'event' => $event,
+                'auditable_type' => $auditableType,
+                'auditable_id' => $auditableId,
+                'old_values' => null,
+                'new_values' => $newValues ? $this->redact($newValues) : null,
+                'created_at' => now(),
+            ]));
+
+            $this->context->recordWritten();
+        } catch (\Throwable $e) {
+            report($e);
+        }
+    }
+
+    protected function persist(Model $model, string $event, ?array $old, ?array $new): void
+    {
+        try {
+            $type = get_class($model);
+            $id = $model->getKey();
+
+            AdminApiAudit::create(array_merge($this->context->baseAttributes(), [
+                'version_id' => $this->nextVersion($type, $id),
+                'event' => $event,
+                'auditable_type' => $type,
+                'auditable_id' => $id,
+                'old_values' => $old !== null ? $this->redact($old) : null,
+                'new_values' => $new !== null ? $this->redact($new) : null,
+                'created_at' => now(),
+            ]));
+
+            $this->context->recordWritten();
+        } catch (\Throwable $e) {
+            // Auditing must never break the underlying write.
+            report($e);
+        }
+    }
+
+    protected function shouldAudit(Model $model): bool
+    {
+        if (! config('bagistoapi.audit.enabled', true)) {
+            return false;
+        }
+
+        if (! $this->context->isActive()) {
+            return false;
+        }
+
+        foreach ((array) config('bagistoapi.audit.exclude_models', []) as $excluded) {
+            if ($model instanceof $excluded) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    protected function nextVersion(?string $type, $id): int
+    {
+        if (! $type || $id === null) {
+            return 1;
+        }
+
+        try {
+            return (int) AdminApiAudit::where('auditable_type', $type)
+                ->where('auditable_id', $id)
+                ->max('version_id') + 1;
+        } catch (\Throwable) {
+            return 1;
+        }
+    }
+
+    /**
+     * Replace sensitive values with "[redacted]" (exact or "*suffix" match).
+     */
+    protected function redact(array $values): array
+    {
+        $patterns = (array) config('bagistoapi.audit.redact', []);
+
+        foreach ($values as $key => $value) {
+            foreach ($patterns as $pattern) {
+                $matches = str_starts_with($pattern, '*')
+                    ? str_ends_with((string) $key, ltrim($pattern, '*'))
+                    : $key === $pattern;
+
+                if ($matches) {
+                    $values[$key] = '[redacted]';
+                    break;
+                }
+            }
+        }
+
+        return $values;
+    }
+}

+ 133 - 0
packages/Webkul/BagistoApi/src/Admin/Auth/AdminApiGuard.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Auth;
+
+use Illuminate\Auth\GuardHelpers;
+use Illuminate\Contracts\Auth\Authenticatable;
+use Illuminate\Contracts\Auth\Guard;
+use Illuminate\Contracts\Auth\UserProvider;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+use Webkul\BagistoApi\Admin\Models\AdminPersonalAccessToken;
+
+class AdminApiGuard implements Guard
+{
+    use GuardHelpers;
+
+    protected Request $request;
+
+    protected ?AdminPersonalAccessToken $currentToken = null;
+
+    public function __construct(UserProvider $provider, Request $request)
+    {
+        $this->provider = $provider;
+        $this->request = $request;
+    }
+
+    public function user(): ?Authenticatable
+    {
+        $request = app('request') ?: $this->request;
+
+        $bearer = $request->bearerToken();
+
+        if ($this->user !== null
+            && $this->currentToken !== null
+            && $bearer !== null
+            && str_contains($bearer, '|')
+        ) {
+            [$id] = explode('|', $bearer, 2);
+
+            if (ctype_digit($id) && (int) $id === (int) $this->currentToken->id) {
+                return $this->user;
+            }
+        }
+
+        $this->user = null;
+        $this->currentToken = null;
+
+        if (! $bearer) {
+            return null;
+        }
+
+        if (! str_contains($bearer, '|')) {
+            return null;
+        }
+
+        [$id, $plain] = explode('|', $bearer, 2);
+
+        if (! ctype_digit($id) || $plain === '') {
+            return null;
+        }
+
+        $token = AdminPersonalAccessToken::find((int) $id);
+
+        if (! $token || ! $token->isUsable()) {
+            return null;
+        }
+
+        if (! hash_equals((string) $token->token, hash('sha256', $plain))) {
+            return null;
+        }
+
+        $clientIp = $this->request->ip() ?? '';
+
+        if (! $token->isIpAllowed($clientIp)) {
+            Log::warning('Admin API token denied by IP allowlist', [
+                'token_id' => $token->id,
+                'admin_id' => $token->admin_id,
+                'client_ip' => $clientIp,
+            ]);
+
+            return null;
+        }
+
+        $admin = $this->provider->retrieveById($token->admin_id);
+
+        if (! $admin) {
+            return null;
+        }
+
+        $token->forceFill(['last_used_at' => now()])->saveQuietly();
+
+        $this->currentToken = $token;
+
+        if (method_exists($admin, 'withAccessToken')) {
+            $admin->withAccessToken($token);
+        } else {
+            $admin->setAttribute('current_access_token', $token);
+        }
+
+        // Constrain the resolved admin's effective role to the token's abilities so
+        // every endpoint permission check honours the token's permission_type
+        // (custom tokens are restricted to their frozen abilities).
+        $token->applyAbilityScope($admin);
+
+        return $this->user = $admin;
+    }
+
+    public function currentAccessToken(): ?AdminPersonalAccessToken
+    {
+        $this->user();
+
+        return $this->currentToken;
+    }
+
+    public function validate(array $credentials = []): bool
+    {
+        if (empty($credentials['token'])) {
+            return false;
+        }
+
+        $request = Request::create('/', 'GET');
+        $request->headers->set('Authorization', 'Bearer '.$credentials['token']);
+
+        $previousRequest = $this->request;
+        $this->request = $request;
+
+        try {
+            return $this->user() !== null;
+        } finally {
+            $this->request = $previousRequest;
+        }
+    }
+}

+ 45 - 0
packages/Webkul/BagistoApi/src/Admin/Config/acl.php

@@ -0,0 +1,45 @@
+<?php
+
+return [
+    [
+        'key' => 'integration',
+        'name' => 'bagistoapi::app.integration.acl.title',
+        'route' => 'admin.integration.index',
+        'sort' => 10,
+    ], [
+        'key' => 'integration.create',
+        'name' => 'bagistoapi::app.integration.acl.create',
+        'route' => 'admin.integration.create',
+        'sort' => 1,
+    ], [
+        'key' => 'integration.edit',
+        'name' => 'bagistoapi::app.integration.acl.edit',
+        'route' => 'admin.integration.edit',
+        'sort' => 2,
+    ], [
+        'key' => 'integration.delete',
+        'name' => 'bagistoapi::app.integration.acl.delete',
+        'route' => 'admin.integration.destroy',
+        'sort' => 3,
+    ], [
+        'key' => 'integration.generate',
+        'name' => 'bagistoapi::app.integration.acl.generate',
+        'route' => 'admin.integration.generate',
+        'sort' => 4,
+    ], [
+        'key' => 'integration.regenerate',
+        'name' => 'bagistoapi::app.integration.acl.regenerate',
+        'route' => 'admin.integration.regenerate',
+        'sort' => 5,
+    ], [
+        'key' => 'integration.history',
+        'name' => 'bagistoapi::app.integration.history.acl.title',
+        'route' => 'admin.integration.history.index',
+        'sort' => 6,
+    ], [
+        'key' => 'integration.history.delete',
+        'name' => 'bagistoapi::app.integration.history.acl.delete',
+        'route' => 'admin.integration.history.mass_delete',
+        'sort' => 7,
+    ],
+];

+ 43 - 0
packages/Webkul/BagistoApi/src/Admin/Config/audit.php

@@ -0,0 +1,43 @@
+<?php
+
+use Webkul\BagistoApi\Admin\Models\AdminApiAudit;
+use Webkul\BagistoApi\Admin\Models\AdminPersonalAccessToken;
+
+/**
+ * Admin-API audit trail config (merged under `bagistoapi.audit`).
+ */
+return [
+    /**
+     * Master switch. When false, no admin-API change is recorded.
+     */
+    'enabled' => env('BAGISTO_API_AUDIT_ENABLED', true),
+
+    /**
+     * Field names whose values are never stored. Matching keys in
+     * old_values / new_values / payload are replaced with "[redacted]".
+     * A leading "*" matches by suffix (e.g. "*_token" matches "api_token").
+     */
+    'redact' => [
+        'password',
+        'password_confirmation',
+        'current_password',
+        'remember_token',
+        'api_token',
+        'secret',
+        '*_token',
+        '*_secret',
+    ],
+
+    /**
+     * Models that are never audited (avoids self-auditing + noise).
+     */
+    'exclude_models' => [
+        AdminApiAudit::class,
+        AdminPersonalAccessToken::class,
+    ],
+
+    /**
+     * Auto-prune cutoff for `bagisto-api:prune-audits`. null = keep forever.
+     */
+    'retention_days' => env('BAGISTO_API_AUDIT_RETENTION_DAYS', null),
+];

+ 8 - 0
packages/Webkul/BagistoApi/src/Admin/Config/auth/guards.php

@@ -0,0 +1,8 @@
+<?php
+
+return [
+    'admin-api' => [
+        'driver' => 'admin-api',
+        'provider' => 'admins',
+    ],
+];

+ 24 - 0
packages/Webkul/BagistoApi/src/Admin/Config/menu.php

@@ -0,0 +1,24 @@
+<?php
+
+return [
+    [
+        'key' => 'integration',
+        'name' => 'bagistoapi::app.integration.menu.title',
+        'route' => 'admin.integration.index',
+        'sort' => 8,
+        'icon' => 'icon-settings',
+        'icon-class' => 'settings-icon',
+    ], [
+        'key' => 'integration.tokens',
+        'name' => 'bagistoapi::app.integration.menu.tokens',
+        'route' => 'admin.integration.token.index',
+        'sort' => 1,
+        'icon' => '',
+    ], [
+        'key' => 'integration.history',
+        'name' => 'bagistoapi::app.integration.history.menu.title',
+        'route' => 'admin.integration.history.index',
+        'sort' => 2,
+        'icon' => '',
+    ],
+];

+ 36 - 0
packages/Webkul/BagistoApi/src/Admin/Config/system.php

@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * System configuration for the BagistoApi admin modules.
+ *
+ * Merged into the core `system` config so the entries appear under
+ * Admin → Configuration. The `api.integration.settings.enabled` flag controls
+ * whether the "API Integration" plugin is shown in the admin sidebar.
+ */
+return [
+    [
+        'key' => 'api',
+        'name' => 'bagistoapi::app.integration.configuration.api.title',
+        'info' => 'bagistoapi::app.integration.configuration.api.info',
+        'sort' => 6,
+    ], [
+        'key' => 'api.integration',
+        'name' => 'bagistoapi::app.integration.configuration.integration.title',
+        'info' => 'bagistoapi::app.integration.configuration.integration.info',
+        'icon' => 'settings/store.svg',
+        'sort' => 1,
+    ], [
+        'key' => 'api.integration.settings',
+        'name' => 'bagistoapi::app.integration.configuration.settings.title',
+        'info' => 'bagistoapi::app.integration.configuration.settings.info',
+        'sort' => 1,
+        'fields' => [
+            [
+                'name' => 'enabled',
+                'title' => 'bagistoapi::app.integration.configuration.settings.enable',
+                'type' => 'boolean',
+                'default' => true,
+            ],
+        ],
+    ],
+];

+ 97 - 0
packages/Webkul/BagistoApi/src/Admin/DataGrids/AuditHistoryDataGrid.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\DataGrids;
+
+use Illuminate\Support\Facades\DB;
+use Webkul\DataGrid\DataGrid;
+
+/**
+ * Integration → History datagrid: admin-API changes (who / what / when),
+ * with search, filters and a permission-gated delete.
+ */
+class AuditHistoryDataGrid extends DataGrid
+{
+    public function prepareQueryBuilder()
+    {
+        return DB::table('admin_api_audits')
+            ->select(
+                'id',
+                'event',
+                'auditable_type',
+                'auditable_id',
+                'created_at'
+            );
+    }
+
+    public function prepareColumns()
+    {
+        $this->addColumn([
+            'index' => 'id',
+            'label' => trans('bagistoapi::app.integration.history.datagrid.id'),
+            'type' => 'integer',
+            'filterable' => true,
+            'sortable' => true,
+        ]);
+
+        $this->addColumn([
+            'index' => 'event',
+            'label' => trans('bagistoapi::app.integration.history.datagrid.operation'),
+            'type' => 'string',
+            'searchable' => false,
+            'filterable' => true,
+            'filterable_type' => 'dropdown',
+            'filterable_options' => [
+                ['label' => trans('bagistoapi::app.integration.history.events.created'), 'value' => 'created'],
+                ['label' => trans('bagistoapi::app.integration.history.events.updated'), 'value' => 'updated'],
+                ['label' => trans('bagistoapi::app.integration.history.events.deleted'), 'value' => 'deleted'],
+            ],
+            'sortable' => true,
+            'closure' => fn ($row) => '<span class="label-'.($row->event === 'deleted' ? 'canceled' : ($row->event === 'created' ? 'active' : 'pending')).'">'.ucfirst((string) $row->event).'</span>',
+        ]);
+
+        $this->addColumn([
+            'index' => 'auditable_type',
+            'label' => trans('bagistoapi::app.integration.history.datagrid.resource'),
+            'type' => 'string',
+            'searchable' => true,
+            'filterable' => true,
+            'sortable' => true,
+            'closure' => function ($row) {
+                $base = $row->auditable_type ? class_basename($row->auditable_type) : '—';
+
+                return $row->auditable_id ? $base.' #'.$row->auditable_id : $base;
+            },
+        ]);
+
+        $this->addColumn([
+            'index' => 'created_at',
+            'label' => trans('bagistoapi::app.integration.history.datagrid.date'),
+            'type' => 'datetime',
+            'searchable' => false,
+            'filterable' => true,
+            'filterable_type' => 'datetime_range',
+            'sortable' => true,
+        ]);
+    }
+
+    public function prepareActions()
+    {
+        $this->addAction([
+            'icon' => 'icon-view',
+            'title' => trans('bagistoapi::app.integration.history.datagrid.view'),
+            'method' => 'GET',
+            'url' => fn ($row) => route('admin.integration.history.view', $row->id),
+        ]);
+    }
+
+    public function prepareMassActions()
+    {
+        if (bouncer()->hasPermission('integration.history.delete')) {
+            $this->addMassAction([
+                'title' => trans('bagistoapi::app.integration.history.datagrid.delete'),
+                'method' => 'POST',
+                'url' => route('admin.integration.history.mass_delete'),
+            ]);
+        }
+    }
+}

+ 157 - 0
packages/Webkul/BagistoApi/src/Admin/DataGrids/IntegrationDataGrid.php

@@ -0,0 +1,157 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\DataGrids;
+
+use Illuminate\Support\Facades\DB;
+use Webkul\DataGrid\DataGrid;
+
+class IntegrationDataGrid extends DataGrid
+{
+    public function prepareQueryBuilder()
+    {
+        $tablePrefix = DB::getTablePrefix();
+
+        return DB::table('admin_personal_access_tokens as t')
+            ->leftJoin('admins as a', 'a.id', '=', 't.admin_id')
+            ->whereIn('t.status', ['draft', 'active'])
+            ->select(
+                't.id as id',
+                't.name as name',
+                't.description as description',
+                't.token_preview as token_preview',
+                't.permission_type as permission_type',
+                't.status as status',
+                't.expires_at as expires_at',
+                't.last_used_at as last_used_at',
+                't.created_at as created_at',
+                't.updated_at as updated_at',
+                'a.name as admin_name',
+                'a.email as admin_email'
+            )
+            ->selectRaw("CASE
+                WHEN {$tablePrefix}t.token_preview IS NULL THEN '—'
+                ELSE CONCAT({$tablePrefix}t.id, '|', {$tablePrefix}t.token_preview, '...xxxx')
+            END as masked_token");
+    }
+
+    public function prepareColumns()
+    {
+        $this->addColumn([
+            'index' => 'id',
+            'label' => trans('bagistoapi::app.integration.datagrid.id'),
+            'type' => 'integer',
+            'filterable' => true,
+            'sortable' => true,
+        ]);
+
+        $this->addColumn([
+            'index' => 'name',
+            'label' => trans('bagistoapi::app.integration.datagrid.name'),
+            'type' => 'string',
+            'searchable' => true,
+            'filterable' => true,
+            'sortable' => true,
+        ]);
+
+        $this->addColumn([
+            'index' => 'admin_name',
+            'label' => trans('bagistoapi::app.integration.datagrid.admin'),
+            'type' => 'string',
+            'searchable' => true,
+            'filterable' => true,
+            'sortable' => true,
+        ]);
+
+        $this->addColumn([
+            'index' => 'masked_token',
+            'label' => trans('bagistoapi::app.integration.datagrid.token'),
+            'type' => 'string',
+            'searchable' => false,
+            'filterable' => false,
+            'sortable' => false,
+        ]);
+
+        $this->addColumn([
+            'index' => 'status',
+            'label' => trans('bagistoapi::app.integration.datagrid.status'),
+            'type' => 'string',
+            'searchable' => false,
+            'filterable' => true,
+            'filterable_type' => 'dropdown',
+            'filterable_options' => [
+                ['label' => trans('bagistoapi::app.integration.status.draft'), 'value' => 'draft'],
+                ['label' => trans('bagistoapi::app.integration.status.active'), 'value' => 'active'],
+            ],
+            'sortable' => true,
+        ]);
+
+        $this->addColumn([
+            'index' => 'permission_type',
+            'label' => trans('bagistoapi::app.integration.datagrid.permission-type'),
+            'type' => 'string',
+            'filterable' => true,
+            'filterable_type' => 'dropdown',
+            'filterable_options' => [
+                ['label' => trans('bagistoapi::app.integration.permission_type.all'), 'value' => 'all'],
+                ['label' => trans('bagistoapi::app.integration.permission_type.custom'), 'value' => 'custom'],
+                ['label' => trans('bagistoapi::app.integration.permission_type.same_as_web'), 'value' => 'same_as_web'],
+            ],
+            'sortable' => true,
+        ]);
+
+        $this->addColumn([
+            'index' => 'expires_at',
+            'label' => trans('bagistoapi::app.integration.datagrid.expires-at'),
+            'type' => 'datetime',
+            'searchable' => false,
+            'filterable' => true,
+            'filterable_type' => 'datetime_range',
+            'sortable' => true,
+        ]);
+
+        $this->addColumn([
+            'index' => 'last_used_at',
+            'label' => trans('bagistoapi::app.integration.datagrid.last-used-at'),
+            'type' => 'datetime',
+            'searchable' => false,
+            'filterable' => true,
+            'filterable_type' => 'datetime_range',
+            'sortable' => true,
+        ]);
+
+        $this->addColumn([
+            'index' => 'created_at',
+            'label' => trans('bagistoapi::app.integration.datagrid.created-at'),
+            'type' => 'datetime',
+            'searchable' => false,
+            'filterable' => true,
+            'filterable_type' => 'datetime_range',
+            'sortable' => true,
+        ]);
+    }
+
+    public function prepareActions()
+    {
+        if (bouncer()->hasPermission('integration.edit')) {
+            $this->addAction([
+                'icon' => 'icon-edit',
+                'title' => trans('bagistoapi::app.integration.datagrid.edit'),
+                'method' => 'GET',
+                'url' => function ($row) {
+                    return route('admin.integration.edit', $row->id);
+                },
+            ]);
+        }
+
+        if (bouncer()->hasPermission('integration.delete')) {
+            $this->addAction([
+                'icon' => 'icon-delete',
+                'title' => trans('bagistoapi::app.integration.datagrid.revoke'),
+                'method' => 'DELETE',
+                'url' => function ($row) {
+                    return route('admin.integration.destroy', $row->id);
+                },
+            ]);
+        }
+    }
+}

+ 98 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeCreateInput.php

@@ -0,0 +1,98 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/catalog/attributes.
+ */
+class AdminAttributeCreateInput
+{
+    #[ApiProperty(description: 'Attribute code (lowercase letters, digits, underscore only).')]
+    #[Groups(['mutation'])]
+    public ?string $code = null;
+
+    #[ApiProperty(description: 'Admin display name.')]
+    #[Groups(['mutation'])]
+    public ?string $admin_name = null;
+
+    #[ApiProperty(description: 'Attribute type (text, textarea, price, boolean, select, multiselect, checkbox, date, datetime, image, file).')]
+    #[Groups(['mutation'])]
+    public ?string $type = null;
+
+    #[ApiProperty(description: 'Swatch display type (text, color, image).')]
+    #[Groups(['mutation'])]
+    public ?string $swatch_type = null;
+
+    #[ApiProperty(description: 'Whether the attribute is required.')]
+    #[Groups(['mutation'])]
+    public ?bool $is_required = null;
+
+    #[ApiProperty(description: 'Whether the attribute value must be unique.')]
+    #[Groups(['mutation'])]
+    public ?bool $is_unique = null;
+
+    #[ApiProperty(description: 'Whether the attribute is filterable.')]
+    #[Groups(['mutation'])]
+    public ?bool $is_filterable = null;
+
+    #[ApiProperty(description: 'Whether the attribute is configurable (for variants).')]
+    #[Groups(['mutation'])]
+    public ?bool $is_configurable = null;
+
+    #[ApiProperty(description: 'Whether the attribute is visible on the storefront.')]
+    #[Groups(['mutation'])]
+    public ?bool $is_visible_on_front = null;
+
+    #[ApiProperty(description: 'Whether the attribute is comparable.')]
+    #[Groups(['mutation'])]
+    public ?bool $is_comparable = null;
+
+    #[ApiProperty(description: 'Whether the attribute value varies per locale.')]
+    #[Groups(['mutation'])]
+    public ?bool $value_per_locale = null;
+
+    #[ApiProperty(description: 'Whether the attribute value varies per channel.')]
+    #[Groups(['mutation'])]
+    public ?bool $value_per_channel = null;
+
+    #[ApiProperty(description: 'Whether WYSIWYG editor is enabled (for textarea type).')]
+    #[Groups(['mutation'])]
+    public ?bool $enable_wysiwyg = null;
+
+    #[ApiProperty(description: 'Validation rule (required, url, email, regex, etc).')]
+    #[Groups(['mutation'])]
+    public ?string $validation = null;
+
+    #[ApiProperty(description: 'Custom regex pattern (used when validation=regex).')]
+    #[Groups(['mutation'])]
+    public ?string $regex = null;
+
+    #[ApiProperty(description: 'Default value (0 or 1 for boolean type).')]
+    #[Groups(['mutation'])]
+    public ?string $default_value = null;
+
+    #[ApiProperty(description: 'Sort position.')]
+    #[Groups(['mutation'])]
+    public ?int $position = null;
+
+    /**
+     * Locale-keyed translations, e.g. {"en": {"name": "Color"}, "fr": {"name": "Couleur"}}.
+     *
+     * @var array<string, array{name: string}>|null
+     */
+    #[ApiProperty(description: 'Locale-keyed translations map.')]
+    #[Groups(['mutation'])]
+    public ?array $translations = null;
+
+    /**
+     * Array of option objects (for select/multiselect/checkbox types).
+     *
+     * @var array<int, mixed>|null
+     */
+    #[ApiProperty(description: 'Options array (for select/multiselect/checkbox types).')]
+    #[Groups(['mutation'])]
+    public ?array $options = null;
+}

+ 37 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeFamilyCreateInput.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/catalog/families.
+ *
+ * `attribute_groups` is the same nested shape AttributeFamilyRepository::create() expects:
+ *   [
+ *     ['code' => 'general', 'name' => 'General', 'column' => 1, 'position' => 1,
+ *      'custom_attributes' => [ ['id' => 1], ['id' => 2] ]],
+ *     ...
+ *   ]
+ */
+class AdminAttributeFamilyCreateInput
+{
+    #[ApiProperty(description: 'Family code — lowercase letters, digits, underscore. Must be unique.')]
+    #[Groups(['mutation'])]
+    public ?string $code = null;
+
+    #[ApiProperty(description: 'Family display name.')]
+    #[Groups(['mutation'])]
+    public ?string $name = null;
+
+    /**
+     * Attribute groups list. Each group: { code, name, column (1|2), position?, custom_attributes? }.
+     * `custom_attributes` is an array of `{ id }` or `{ code }` for each attribute in the group.
+     *
+     * @var array<int, mixed>|null
+     */
+    #[ApiProperty(description: 'Attribute groups (each with optional custom_attributes list).')]
+    #[Groups(['mutation'])]
+    public ?array $attribute_groups = null;
+}

+ 40 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeFamilyUpdateInput.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for PUT /api/admin/catalog/families/{id} (also used as deleteAdminAttributeFamily input).
+ *
+ * `attribute_groups` is keyed for repository update semantics:
+ *   - numeric keys (existing group id)  → update that group, replacing custom_attributes
+ *   - keys starting with `group_*`      → create new group
+ *   - omitted existing group ids        → group is deleted
+ * Inside `custom_attributes`, items must include `id` and `position`.
+ */
+class AdminAttributeFamilyUpdateInput
+{
+    #[ApiProperty(description: 'Family IRI, e.g. /api/admin/catalog/families/1.')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty(description: 'Family code (must be unique among other families).')]
+    #[Groups(['mutation'])]
+    public ?string $code = null;
+
+    #[ApiProperty(description: 'Family display name.')]
+    #[Groups(['mutation'])]
+    public ?string $name = null;
+
+    /**
+     * Attribute groups map. Existing groups keyed by numeric id; new groups keyed
+     * with a string `group_*`. See repository ::update() for full semantics.
+     *
+     * @var array<string|int, mixed>|null
+     */
+    #[ApiProperty(description: 'Attribute groups map (numeric keys = existing, group_* keys = new). Omitted ids are deleted.')]
+    #[Groups(['mutation'])]
+    public ?array $attribute_groups = null;
+}

+ 21 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeMassDeleteInput.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/catalog/attributes/mass-delete.
+ */
+class AdminAttributeMassDeleteInput
+{
+    /**
+     * Array of attribute IDs to delete.
+     *
+     * @var int[]|null
+     */
+    #[ApiProperty(description: 'Array of attribute IDs to delete.')]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+}

+ 36 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeOptionDto.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiResource;
+
+/**
+ * One attribute option, surfaced inline inside the options array on the
+ * attribute detail response.
+ *
+ * Annotated with empty operations / graphQlOperations so API Platform registers
+ * it as a schema type (needed for GraphQL nested-object validation) without
+ * exposing CRUD endpoints.
+ */
+#[ApiResource(
+    operations: [],
+    graphQlOperations: [],
+    normalizationContext: ['skip_null_values' => false],
+)]
+class AdminAttributeOptionDto
+{
+    public ?int $id = null;
+
+    public ?string $adminName = null;
+
+    public ?int $sortOrder = null;
+
+    public ?string $swatchValue = null;
+
+    public ?string $swatchValueUrl = null;
+
+    /**
+     * @var array<int, AdminAttributeOptionTranslationDto>|null
+     */
+    public ?array $translations = null;
+}

+ 38 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeOptionInput.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for create/update of a single attribute option.
+ *
+ * Used by:
+ *   POST   /api/admin/catalog/attributes/{attributeId}/options
+ *   PUT    /api/admin/catalog/attributes/{attributeId}/options/{optionId}
+ *   + matching GraphQL mutations.
+ */
+class AdminAttributeOptionInput
+{
+    #[ApiProperty(description: 'Option admin display name.')]
+    #[Groups(['mutation'])]
+    public ?string $admin_name = null;
+
+    #[ApiProperty(description: 'Option sort order.')]
+    #[Groups(['mutation'])]
+    public ?int $sort_order = null;
+
+    #[ApiProperty(description: 'Swatch value (hex color, image path, or text label depending on parent swatch_type).')]
+    #[Groups(['mutation'])]
+    public ?string $swatch_value = null;
+
+    /**
+     * Locale-keyed option translations, e.g. {"en": {"label": "Red"}, "fr": {"label": "Rouge"}}.
+     *
+     * @var array<string, array{label: string}>|null
+     */
+    #[ApiProperty(description: 'Locale-keyed label translations.')]
+    #[Groups(['mutation'])]
+    public ?array $translations = null;
+}

+ 25 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeOptionTranslationDto.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiResource;
+
+/**
+ * One row of attribute_option_translations, surfaced inline inside each option
+ * on the attribute detail response.
+ *
+ * Annotated with empty operations / graphQlOperations so API Platform registers
+ * it as a schema type (needed for GraphQL nested-object validation) without
+ * exposing CRUD endpoints.
+ */
+#[ApiResource(
+    operations: [],
+    graphQlOperations: [],
+    normalizationContext: ['skip_null_values' => false],
+)]
+class AdminAttributeOptionTranslationDto
+{
+    public ?string $locale = null;
+
+    public ?string $label = null;
+}

+ 24 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeTranslationDto.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiResource;
+
+/**
+ * One row of attribute_translations, surfaced inline on the attribute detail response.
+ *
+ * Annotated with empty operations / graphQlOperations so API Platform registers
+ * it as a schema type (needed for GraphQL nested-object validation) without
+ * exposing CRUD endpoints.
+ */
+#[ApiResource(
+    operations: [],
+    graphQlOperations: [],
+    normalizationContext: ['skip_null_values' => false],
+)]
+class AdminAttributeTranslationDto
+{
+    public ?string $locale = null;
+
+    public ?string $name = null;
+}

+ 106 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminAttributeUpdateInput.php

@@ -0,0 +1,106 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for PUT /api/admin/catalog/attributes/{id}.
+ *
+ * Code changes are refused — field is accepted but triggers 422 if different.
+ */
+class AdminAttributeUpdateInput
+{
+    #[ApiProperty(description: 'Attribute ID (IRI, e.g. /api/admin/catalog/attributes/12).')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty(description: 'Attribute code (must match the existing code — cannot be changed).')]
+    #[Groups(['mutation'])]
+    public ?string $code = null;
+
+    #[ApiProperty(description: 'Admin display name.')]
+    #[Groups(['mutation'])]
+    public ?string $admin_name = null;
+
+    #[ApiProperty(description: 'Attribute type.')]
+    #[Groups(['mutation'])]
+    public ?string $type = null;
+
+    #[ApiProperty(description: 'Swatch display type.')]
+    #[Groups(['mutation'])]
+    public ?string $swatch_type = null;
+
+    #[ApiProperty(description: 'Whether the attribute is required.')]
+    #[Groups(['mutation'])]
+    public ?bool $is_required = null;
+
+    #[ApiProperty(description: 'Whether the attribute value must be unique.')]
+    #[Groups(['mutation'])]
+    public ?bool $is_unique = null;
+
+    #[ApiProperty(description: 'Whether the attribute is filterable.')]
+    #[Groups(['mutation'])]
+    public ?bool $is_filterable = null;
+
+    #[ApiProperty(description: 'Whether the attribute is configurable (for variants).')]
+    #[Groups(['mutation'])]
+    public ?bool $is_configurable = null;
+
+    #[ApiProperty(description: 'Whether the attribute is visible on the storefront.')]
+    #[Groups(['mutation'])]
+    public ?bool $is_visible_on_front = null;
+
+    #[ApiProperty(description: 'Whether the attribute is comparable.')]
+    #[Groups(['mutation'])]
+    public ?bool $is_comparable = null;
+
+    #[ApiProperty(description: 'Whether the attribute value varies per locale.')]
+    #[Groups(['mutation'])]
+    public ?bool $value_per_locale = null;
+
+    #[ApiProperty(description: 'Whether the attribute value varies per channel.')]
+    #[Groups(['mutation'])]
+    public ?bool $value_per_channel = null;
+
+    #[ApiProperty(description: 'Whether WYSIWYG editor is enabled.')]
+    #[Groups(['mutation'])]
+    public ?bool $enable_wysiwyg = null;
+
+    #[ApiProperty(description: 'Validation rule.')]
+    #[Groups(['mutation'])]
+    public ?string $validation = null;
+
+    #[ApiProperty(description: 'Custom regex pattern.')]
+    #[Groups(['mutation'])]
+    public ?string $regex = null;
+
+    #[ApiProperty(description: 'Default value (0 or 1 for boolean type).')]
+    #[Groups(['mutation'])]
+    public ?string $default_value = null;
+
+    #[ApiProperty(description: 'Sort position.')]
+    #[Groups(['mutation'])]
+    public ?int $position = null;
+
+    /**
+     * Locale-keyed translations map.
+     *
+     * @var array<string, array{name: string}>|null
+     */
+    #[ApiProperty(description: 'Locale-keyed translations map.')]
+    #[Groups(['mutation'])]
+    public ?array $translations = null;
+
+    /**
+     * Options array for replacement (select/multiselect/checkbox only).
+     * Items with an "id" are updates; items without "id" are new; any existing
+     * option not included is deleted.
+     *
+     * @var array<int, mixed>|null
+     */
+    #[ApiProperty(description: 'Full replacement options array. Items with id are updated; without id are inserted; omitted ids are deleted.')]
+    #[Groups(['mutation'])]
+    public ?array $options = null;
+}

+ 13 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCancelOrderInput.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Attribute\Groups;
+
+class AdminCancelOrderInput
+{
+    #[ApiProperty(description: 'Order id to cancel.')]
+    #[Groups(['mutation'])]
+    public ?int $orderId = null;
+}

+ 65 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartAddItemInput.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input for POST /api/admin/carts/{id}/items and the GraphQL
+ * addItemAdminCart mutation.
+ *
+ * REST reads the raw request body, so it also accepts the storefront snake_case
+ * keys directly (`selected_configurable_option`, `bundle_options`,
+ * `bundle_option_qty`, `qty`, `links`). GraphQL can only send the fields
+ * declared here, so the type-specific selections are exposed as GraphQL-friendly
+ * scalars / lists (no arbitrary-key maps — those don't round-trip over GraphQL)
+ * and the processor transforms them into the map shape Cart::addProduct expects:
+ *
+ *   - configurable : selectedConfigurableOption (variant product id)
+ *   - downloadable : links (list of downloadable-link ids)
+ *   - grouped      : groupedQuantities ([{ productId, quantity }])
+ *   - bundle       : bundleOptions ([{ optionId, productIds, quantity }])
+ */
+class AdminCartAddItemInput
+{
+    #[Groups(['mutation'])]
+    public ?string $cartId = null;
+
+    #[Groups(['mutation'])]
+    public ?int $productId = null;
+
+    #[Groups(['mutation'])]
+    public ?int $quantity = null;
+
+    #[ApiProperty(description: 'Configurable products: the chosen variant product id.')]
+    #[Groups(['mutation'])]
+    public ?int $selectedConfigurableOption = null;
+
+    /**
+     * Downloadable products: ids of the downloadable links to purchase.
+     *
+     * @var array<int, int>|null
+     */
+    #[ApiProperty(description: 'Downloadable products: list of downloadable-link ids.')]
+    #[Groups(['mutation'])]
+    public ?array $links = null;
+
+    /**
+     * Grouped products: per associated-product quantities.
+     *
+     * @var array<int, array{productId: int, quantity: int}>|null
+     */
+    #[ApiProperty(description: 'Grouped products: list of { productId, quantity }.')]
+    #[Groups(['mutation'])]
+    public ?array $groupedQuantities = null;
+
+    /**
+     * Bundle products: chosen option selections.
+     *
+     * @var array<int, array{optionId: int, productIds: array<int, int>, quantity?: int}>|null
+     */
+    #[ApiProperty(description: 'Bundle products: list of { optionId, productIds, quantity }.')]
+    #[Groups(['mutation'])]
+    public ?array $bundleOptions = null;
+}

+ 39 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartAddressDto.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+/**
+ * Cart billing/shipping address embedded in the admin draft cart payload.
+ */
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class AdminCartAddressDto
+{
+    #[ApiProperty(identifier: true)]
+    public ?int $id = null;
+
+    public ?string $addressType = null;
+
+    public ?string $firstName = null;
+
+    public ?string $lastName = null;
+
+    public ?string $companyName = null;
+
+    public ?string $email = null;
+
+    /** Stored as a newline-joined string in Bagisto core; exposed as the raw value. */
+    public ?string $address = null;
+
+    public ?string $city = null;
+
+    public ?string $state = null;
+
+    public ?string $country = null;
+
+    public ?string $postcode = null;
+
+    public ?string $phone = null;
+}

+ 20 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartCouponInput.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input for POST /api/admin/carts/{id}/coupon and the GraphQL
+ * createAdminCartApplyCoupon mutation. Also accepted by the remove-coupon
+ * mutation (where `code` is ignored — the cart's currently applied coupon
+ * is removed).
+ */
+class AdminCartCouponInput
+{
+    #[Groups(['mutation'])]
+    public ?string $cartId = null;
+
+    #[Groups(['mutation'])]
+    public ?string $code = null;
+}

+ 59 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartItemDto.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+/**
+ * Single cart-item line embedded in the admin draft cart payload.
+ *
+ * Mirrors the monolith CartItemResource shape but in camelCase. `additional`
+ * carries the type-specific data the storefront add-to-cart routine recorded
+ * (super_attribute, bundle_options, selected configurable option, etc).
+ */
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class AdminCartItemDto
+{
+    #[ApiProperty(identifier: true)]
+    public ?int $id = null;
+
+    public ?int $cartId = null;
+
+    public ?int $productId = null;
+
+    public ?int $parentId = null;
+
+    public ?string $sku = null;
+
+    public ?string $type = null;
+
+    public ?string $name = null;
+
+    public ?int $quantity = null;
+
+    public ?float $price = null;
+
+    public ?string $formattedPrice = null;
+
+    public ?float $total = null;
+
+    public ?string $formattedTotal = null;
+
+    public ?float $taxAmount = null;
+
+    public ?string $formattedTaxAmount = null;
+
+    public ?float $discountAmount = null;
+
+    public ?string $formattedDiscountAmount = null;
+
+    /** Type-specific add-to-cart payload (super_attribute, bundle_options, etc). */
+    public ?array $additional = null;
+
+    /** Configurable child variant — null for non-configurable items. */
+    public ?AdminCartItemDto $child = null;
+
+    /** Bundle / grouped / configurable child rows. */
+    public array $children = [];
+}

+ 18 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartRemoveItemInput.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input for DELETE /api/admin/carts/{id}/items (with body) and the GraphQL
+ * createAdminCartRemoveItem mutation.
+ */
+class AdminCartRemoveItemInput
+{
+    #[Groups(['mutation'])]
+    public ?string $cartId = null;
+
+    #[Groups(['mutation'])]
+    public ?int $cartItemId = null;
+}

+ 30 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartSaveAddressInput.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input for POST /api/admin/carts/{id}/addresses and the GraphQL
+ * createAdminCartSaveAddress mutation.
+ *
+ * Body shape mirrors the monolith CartAddressRequest:
+ *   {
+ *     "billing":  { firstName, lastName, email, address:[...], city, ..., useForShipping?: bool },
+ *     "shipping": { firstName, ... }   // omit if billing.useForShipping = true
+ *   }
+ *
+ * The processor passes the whole body straight into Cart::saveAddresses() so
+ * any extra fields (e.g. company_name, address2) flow through verbatim.
+ */
+class AdminCartSaveAddressInput
+{
+    #[Groups(['mutation'])]
+    public ?string $cartId = null;
+
+    #[Groups(['mutation'])]
+    public ?array $billing = null;
+
+    #[Groups(['mutation'])]
+    public ?array $shipping = null;
+}

+ 21 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartSetPaymentMethodInput.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input for POST /api/admin/carts/{id}/payment-methods and the
+ * `createAdminCartSetPaymentMethod` GraphQL mutation.
+ */
+class AdminCartSetPaymentMethodInput
+{
+    #[ApiProperty(description: 'Cart ID (GraphQL only — REST takes it from the URL).')]
+    #[Groups(['mutation'])]
+    public ?int $cartId = null;
+
+    #[ApiProperty(description: 'Payment method code, e.g. "cashondelivery", "moneytransfer".')]
+    #[Groups(['mutation'])]
+    public ?string $method = null;
+}

+ 21 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartSetShippingMethodInput.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input for POST /api/admin/carts/{id}/shipping-methods and the
+ * `createAdminCartSetShippingMethod` GraphQL mutation.
+ */
+class AdminCartSetShippingMethodInput
+{
+    #[ApiProperty(description: 'Cart ID (GraphQL only — REST takes it from the URL).')]
+    #[Groups(['mutation'])]
+    public ?int $cartId = null;
+
+    #[ApiProperty(description: 'Shipping method code, e.g. "flatrate_flatrate".')]
+    #[Groups(['mutation'])]
+    public ?string $shippingMethod = null;
+}

+ 25 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCartUpdateItemsInput.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input for PUT /api/admin/carts/{id}/items and the GraphQL
+ * createAdminCartUpdateItems mutation.
+ *
+ * `qty` mirrors the monolith shape: a map of cart_item_id => new quantity.
+ *   { "qty": { "12": 3, "13": 1 } }
+ *
+ * The processor falls back to request()->all() so any additional keys
+ * Cart::updateItems() consumes are forwarded.
+ */
+class AdminCartUpdateItemsInput
+{
+    #[Groups(['mutation'])]
+    public ?string $cartId = null;
+
+    /** Map of cart_item_id => new qty. */
+    #[Groups(['mutation'])]
+    public ?array $qty = null;
+}

+ 19 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductCopyInput.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for createAdminCatalogProductCopy (GraphQL) /
+ * POST /api/admin/catalog/products/{sourceId}/copy (REST).
+ *
+ * REST reads sourceId from the URI; GraphQL reads it from this DTO.
+ */
+class AdminCatalogProductCopyInput
+{
+    #[ApiProperty(description: 'ID of the source product to copy.')]
+    #[Groups(['mutation'])]
+    public ?int $sourceId = null;
+}

+ 46 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductCreateInput.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for Admin Catalog Product step-1 create.
+ *
+ * Mirrors the Bagisto admin Create-Product wizard step 1: only sku +
+ * attribute_family_id + type are submitted (plus super_attributes when
+ * type='configurable'). Name / description / price / inventories etc.
+ * arrive in the step-2 update endpoint (Phase 5.9).
+ *
+ * Phases 5.3 — 5.8 + 5.8-booking: accepts type ∈
+ * {simple, virtual, downloadable, grouped, bundle, configurable, booking}.
+ *
+ * REST  : POST /api/admin/catalog/products
+ * GraphQL: createAdminCatalogProduct
+ */
+class AdminCatalogProductCreateInput
+{
+    #[ApiProperty(description: 'Unique product SKU (lowercase letters/digits, hyphen-separated).')]
+    #[Groups(['mutation'])]
+    public ?string $sku = null;
+
+    #[ApiProperty(description: 'Attribute family ID the new product belongs to.')]
+    #[Groups(['mutation'])]
+    public ?int $attributeFamilyId = null;
+
+    #[ApiProperty(description: 'Product type. Defaults to "simple". One of: simple, virtual, downloadable, grouped, bundle, configurable, booking.')]
+    #[Groups(['mutation'])]
+    public ?string $type = 'simple';
+
+    /**
+     * Required for type='configurable'. Map of attribute_code (or attribute_id)
+     * to a non-empty list of option_ids. Example: { "color": [1, 2], "size": [4, 5] }.
+     * The core Configurable::create generates the full Cartesian-product of variants.
+     *
+     * @var array<string|int, int[]>|null
+     */
+    #[ApiProperty(description: 'Required for type=configurable. Map of attribute code (or id) to option_ids, e.g. { "color": [1,2], "size": [4,5] }. Generates the Cartesian-product of variants on the server.')]
+    #[Groups(['mutation'])]
+    public ?array $superAttributes = null;
+}

+ 38 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductCustomerGroupPriceCreateInput.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for creating a product customer-group price row.
+ *
+ * Used by:
+ *   POST /api/admin/catalog/products/{productId}/customer-group-prices
+ *   createAdminCatalogProductCustomerGroupPrice GraphQL mutation
+ *
+ * customer_group_id = null means "applies to all customer groups".
+ */
+class AdminCatalogProductCustomerGroupPriceCreateInput
+{
+    #[ApiProperty(description: 'Product ID (forwarded by GraphQL mutations).')]
+    #[Groups(['mutation'])]
+    public ?int $productId = null;
+
+    #[ApiProperty(description: 'Minimum quantity that triggers this tier (>= 1).')]
+    #[Groups(['mutation'])]
+    public ?int $qty = null;
+
+    #[ApiProperty(description: 'fixed (flat price) or discount (percent off base price).')]
+    #[Groups(['mutation'])]
+    public ?string $value_type = null;
+
+    #[ApiProperty(description: 'Numeric price (fixed) or percent (discount).')]
+    #[Groups(['mutation'])]
+    public ?float $value = null;
+
+    #[ApiProperty(description: 'Customer group ID. null = applies to all groups.')]
+    #[Groups(['mutation'])]
+    public ?int $customer_group_id = null;
+}

+ 42 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductCustomerGroupPriceUpdateInput.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for updating a product customer-group price row.
+ *
+ * Used by:
+ *   PUT    /api/admin/catalog/products/{productId}/customer-group-prices/{id}
+ *   DELETE /api/admin/catalog/products/{productId}/customer-group-prices/{id}
+ *   updateAdminCatalogProductCustomerGroupPrice GraphQL mutation
+ *   deleteAdminCatalogProductCustomerGroupPrice GraphQL mutation
+ */
+class AdminCatalogProductCustomerGroupPriceUpdateInput
+{
+    #[ApiProperty(description: 'Product ID (forwarded by GraphQL mutations).')]
+    #[Groups(['mutation'])]
+    public ?int $productId = null;
+
+    #[ApiProperty(description: 'Customer-group-price row ID (forwarded by GraphQL mutations).')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty(description: 'Minimum quantity that triggers this tier (>= 1).')]
+    #[Groups(['mutation'])]
+    public ?int $qty = null;
+
+    #[ApiProperty(description: 'fixed (flat price) or discount (percent off base price).')]
+    #[Groups(['mutation'])]
+    public ?string $value_type = null;
+
+    #[ApiProperty(description: 'Numeric price (fixed) or percent (discount).')]
+    #[Groups(['mutation'])]
+    public ?float $value = null;
+
+    #[ApiProperty(description: 'Customer group ID. null = applies to all groups.')]
+    #[Groups(['mutation'])]
+    public ?int $customer_group_id = null;
+}

+ 23 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductImageDeleteInput.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * GraphQL input for deleting a single product image.
+ *
+ * For REST, the path itself carries the parent productId + image id, so no DTO
+ * is needed. GraphQL needs both ids on the mutation input.
+ */
+class AdminCatalogProductImageDeleteInput
+{
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $productId = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $imageId = null;
+}

+ 36 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductImageReorderInput.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * GraphQL/REST input for the reorder operation on AdminCatalogProductImage.
+ *
+ * Body shape:
+ * {
+ *   "order": [
+ *     {"id": 12, "position": 1},
+ *     {"id": 9,  "position": 2}
+ *   ]
+ * }
+ *
+ * For GraphQL, the operation also carries `productId` so the processor can scope
+ * the operation to the parent product.
+ */
+class AdminCatalogProductImageReorderInput
+{
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $productId = null;
+
+    /**
+     * Order rows.
+     *
+     * @var array<int, array{id: int, position: int}>|null
+     */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $order = null;
+}

+ 54 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductInventoryUpdateInput.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for bulk-updating a product's per-source inventory quantities.
+ *
+ * REST   : PUT  /api/admin/catalog/products/{productId}/inventories
+ *          { "inventories": { "<source_id>": <qty>, ... } }
+ *
+ * GraphQL: mutation updateAdminCatalogProductInventories(
+ *            input: { productId: Int!, inventories: Iterable! }
+ *          )
+ *
+ * `inventories` is a map of `inventory_source_id => qty`. Quantities must be
+ * non-negative integers — qty=0 zeroes-out that source, sources omitted from
+ * the map are left untouched.
+ */
+class AdminCatalogProductInventoryUpdateInput
+{
+    #[ApiProperty(description: 'Product ID — taken from the URL on REST, required on GraphQL.')]
+    #[Groups(['mutation'])]
+    public ?int $productId = null;
+
+    /**
+     * @var array<int|string,int>|null
+     */
+    #[ApiProperty(description: 'Map of inventory_source_id → quantity. Use 0 to zero-out a source.')]
+    #[Groups(['mutation'])]
+    public ?array $inventories = null;
+
+    public function getProductId(): ?int
+    {
+        return $this->productId;
+    }
+
+    public function setProductId(?int $v): void
+    {
+        $this->productId = $v;
+    }
+
+    public function getInventories(): ?array
+    {
+        return $this->inventories;
+    }
+
+    public function setInventories(?array $v): void
+    {
+        $this->inventories = $v;
+    }
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductMassDeleteInput.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/catalog/products/mass-delete.
+ */
+class AdminCatalogProductMassDeleteInput
+{
+    /** @var int[]|null */
+    #[ApiProperty(description: 'Array of product IDs to delete.')]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+}

+ 21 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductMassUpdateStatusInput.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/catalog/products/mass-update-status.
+ */
+class AdminCatalogProductMassUpdateStatusInput
+{
+    /** @var int[]|null */
+    #[ApiProperty(description: 'Array of product IDs to update.')]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+
+    #[ApiProperty(description: 'New status value (0 = disabled, 1 = enabled).')]
+    #[Groups(['mutation'])]
+    public ?int $value = null;
+}

+ 187 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductRestDto.php

@@ -0,0 +1,187 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use Webkul\BagistoApi\Admin\Dto\Concerns\AcceptsCamelCaseWrites;
+
+/**
+ * REST output for AdminCatalogProduct detail + listing. Flat shape — every
+ * nested block is a plain array/object (the historical REST payload). Snake_case
+ * props surface as camelCase via the central output converter; the providers
+ * write camelCase and AcceptsCamelCaseWrites maps them onto the snake props.
+ * Over GraphQL the same data is served as field-selectable connections / typed
+ * objects off the AdminCatalogProduct Eloquent resource (no output: DTO there).
+ */
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class AdminCatalogProductRestDto
+{
+    use AcceptsCamelCaseWrites;
+
+    #[ApiProperty(writable: false)]
+    public ?int $id = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $sku = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $name = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $type = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $status = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $price = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_price = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $special_price = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_special_price = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $special_price_from = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $special_price_to = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $quantity = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $base_image_url = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $images_count = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $category_id = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $category_name = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $channel = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $locale = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $attribute_family_id = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $attribute_family_name = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $url_key = null;
+
+    #[ApiProperty(writable: false)]
+    public ?bool $visible_individually = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $short_description = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $description = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $meta_title = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $meta_description = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $meta_keywords = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $weight = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $tax_category_id = null;
+
+    #[ApiProperty(writable: false)]
+    public ?bool $manage_stock = null;
+
+    #[ApiProperty(writable: false)]
+    public ?bool $in_stock = null;
+
+    #[ApiProperty(writable: false)]
+    public ?bool $featured = null;
+
+    #[ApiProperty(writable: false)]
+    public ?bool $new = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $created_at = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $updated_at = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $translations = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $images = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $categories = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $inventories = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $customer_group_prices = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $super_attributes = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $variants = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $bundle_options = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $linked_products = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $downloadable_links = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $downloadable_samples = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $booking_product = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $customizable_options = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $videos = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $channels = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $attributes = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $related_products = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $up_sells = null;
+
+    #[ApiProperty(writable: false)]
+    public ?array $cross_sells = null;
+
+    /** @var string[]|null */
+    #[ApiProperty(writable: false)]
+    public ?array $warnings = null;
+}

+ 154 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductUpdateInput.php

@@ -0,0 +1,154 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for Admin Catalog Product update (Phase 5.9).
+ *
+ * Pass-through shape: declares the most common scalar fields so GraphQL has
+ * a typed schema surface, plus an `extras` catch-all for custom-attribute
+ * values that vary by family. The processor merges every set property +
+ * `extras` into a single payload and forwards to ProductRepository::update.
+ *
+ * Type-specific blocks (superAttributes/variants, bundleOptions, links,
+ * downloadableLinks/downloadableSamples, booking) pass through as-is to the
+ * type instance's update().
+ *
+ * Sub-resource fields (images, inventories, customer_group_prices) are
+ * silently stripped — they have dedicated endpoints (5.11/5.12/5.13). The
+ * response includes a `_warnings` array noting which fields were dropped.
+ *
+ * REST   : PUT /api/admin/catalog/products/{id}
+ * GraphQL: updateAdminCatalogProduct
+ */
+class AdminCatalogProductUpdateInput
+{
+    /**
+     * Resource IRI — required for GraphQL delete/update routing. API Platform
+     * populates this automatically when the mutation declares `id: ID!`.
+     */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $sku = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $urlKey = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $status = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $visibleIndividually = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $guestCheckout = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $new = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $featured = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $price = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $specialPrice = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $specialPriceFrom = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $specialPriceTo = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $cost = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $weight = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $taxCategoryId = null;
+
+    /** @var int[]|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $categories = null;
+
+    /** @var int[]|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $channels = null;
+
+    /** @var array<string, mixed>|null  Locale-keyed translation map */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $translations = null;
+
+    /** @var array<string, int[]>|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $superAttributes = null;
+
+    /** @var array<int, mixed>|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $variants = null;
+
+    /** @var array<int, mixed>|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $bundleOptions = null;
+
+    /** @var array<int, mixed>|null  Grouped product links */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $links = null;
+
+    /** @var array<int, mixed>|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $downloadableLinks = null;
+
+    /** @var array<int, mixed>|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $downloadableSamples = null;
+
+    /** @var array<string, mixed>|null  Booking sub-type payload */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $booking = null;
+
+    /**
+     * Catch-all for custom-attribute values (any other field accepted by
+     * ProductRepository::update). Merged into the payload alongside the typed
+     * fields above. Use this for attributes the family defines but this DTO
+     * doesn't enumerate (e.g. `material`, `brand`, etc.).
+     *
+     * @var array<string, mixed>|null
+     */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $extras = null;
+}

+ 21 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCatalogProductVideoDeleteInput.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Attribute\Groups;
+
+class AdminCatalogProductVideoDeleteInput
+{
+    #[ApiProperty(description: 'Video IRI (delete). GraphQL delete requires the id.')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty(description: 'Parent product id.')]
+    #[Groups(['mutation'])]
+    public ?int $productId = null;
+
+    #[ApiProperty(description: 'Video id.')]
+    #[Groups(['mutation'])]
+    public ?int $videoId = null;
+}

+ 80 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCategoryCreateInput.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/catalog/categories.
+ *
+ * Mirrors Bagisto core CategoryRequest create-mode rules:
+ *   - top-level: slug (required + unique via ProductCategoryUniqueSlug)
+ *   - name (required)
+ *   - position (required int)
+ *   - attributes (required array of filterable attribute IDs)
+ *   - description (required when display_mode in description_only/products_and_description)
+ *   - optional: parent_id, display_mode, meta_title, meta_description, meta_keywords, status,
+ *               logo_path, banner_path, locale
+ */
+class AdminCategoryCreateInput
+{
+    #[ApiProperty(description: 'Category slug (unique across category translations + product slugs).')]
+    #[Groups(['mutation'])]
+    public ?string $slug = null;
+
+    #[ApiProperty(description: 'Category display name.')]
+    #[Groups(['mutation'])]
+    public ?string $name = null;
+
+    #[ApiProperty(description: 'Category description. Required when display_mode is description_only or products_and_description.')]
+    #[Groups(['mutation'])]
+    public ?string $description = null;
+
+    #[ApiProperty(description: 'Sort position among siblings.')]
+    #[Groups(['mutation'])]
+    public ?int $position = null;
+
+    /** @var array<int, int>|null */
+    #[ApiProperty(description: 'List of filterable attribute IDs to attach.')]
+    #[Groups(['mutation'])]
+    public ?array $attributes = null;
+
+    #[ApiProperty(description: 'Parent category ID. Null/omitted for a root category.')]
+    #[Groups(['mutation'])]
+    public ?int $parent_id = null;
+
+    #[ApiProperty(description: 'Display mode (products_and_description, products_only, description_only).')]
+    #[Groups(['mutation'])]
+    public ?string $display_mode = null;
+
+    #[ApiProperty(description: 'Locale code for the translation. Defaults to the requested locale.')]
+    #[Groups(['mutation'])]
+    public ?string $locale = null;
+
+    #[ApiProperty(description: 'Status (1 = enabled, 0 = disabled).')]
+    #[Groups(['mutation'])]
+    public ?int $status = null;
+
+    #[ApiProperty(description: 'SEO meta title.')]
+    #[Groups(['mutation'])]
+    public ?string $meta_title = null;
+
+    #[ApiProperty(description: 'SEO meta description.')]
+    #[Groups(['mutation'])]
+    public ?string $meta_description = null;
+
+    #[ApiProperty(description: 'SEO meta keywords.')]
+    #[Groups(['mutation'])]
+    public ?string $meta_keywords = null;
+
+    /** @var array<int, string>|null */
+    #[ApiProperty(description: 'Optional logo path(s). File-upload is not supported in v1 — accepts pre-uploaded storage paths.')]
+    #[Groups(['mutation'])]
+    public ?array $logo_path = null;
+
+    /** @var array<int, string>|null */
+    #[ApiProperty(description: 'Optional banner path(s). File-upload is not supported in v1 — accepts pre-uploaded storage paths.')]
+    #[Groups(['mutation'])]
+    public ?array $banner_path = null;
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCategoryMassDeleteInput.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/catalog/categories/mass-delete.
+ */
+class AdminCategoryMassDeleteInput
+{
+    /** @var int[]|null */
+    #[ApiProperty(description: 'Array of category IDs to delete.')]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+}

+ 21 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCategoryMassUpdateStatusInput.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/catalog/categories/mass-update-status.
+ */
+class AdminCategoryMassUpdateStatusInput
+{
+    /** @var int[]|null */
+    #[ApiProperty(description: 'Array of category IDs to update.')]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+
+    #[ApiProperty(description: 'New status value (0 = disabled, 1 = enabled).')]
+    #[Groups(['mutation'])]
+    public ?int $value = null;
+}

+ 34 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCategoryTranslationDto.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiResource;
+
+/**
+ * One row of category_translations, surfaced inline on the detail response.
+ *
+ * Annotated with empty operations / graphQlOperations so API Platform registers
+ * it as a schema type (needed for GraphQL nested-object validation) without
+ * exposing CRUD endpoints.
+ */
+#[ApiResource(
+    operations: [],
+    graphQlOperations: [],
+    normalizationContext: ['skip_null_values' => false],
+)]
+class AdminCategoryTranslationDto
+{
+    public ?string $locale = null;
+
+    public ?string $name = null;
+
+    public ?string $slug = null;
+
+    public ?string $description = null;
+
+    public ?string $metaTitle = null;
+
+    public ?string $metaDescription = null;
+
+    public ?string $metaKeywords = null;
+}

+ 68 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCategoryUpdateInput.php

@@ -0,0 +1,68 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for PUT /api/admin/catalog/categories/{id}.
+ *
+ * Update validation is LOCALE-NESTED in monolith CategoryRequest:
+ *   <locale>.slug / <locale>.name / <locale>.description are required.
+ * Plus top-level: position, attributes, parent_id, display_mode, etc.
+ *
+ * The REST/GraphQL clients send the same nested shape — the processor passes
+ * the payload through to CategoryRepository::update() which understands it.
+ */
+class AdminCategoryUpdateInput
+{
+    #[ApiProperty(description: 'Resource IRI (e.g. /api/admin/catalog/categories/12).')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty(description: 'Locale code (e.g. "en"). Determines which nested locale block is required.')]
+    #[Groups(['mutation'])]
+    public ?string $locale = null;
+
+    #[ApiProperty(description: 'Sort position among siblings.')]
+    #[Groups(['mutation'])]
+    public ?int $position = null;
+
+    /** @var array<int, int>|null */
+    #[ApiProperty(description: 'Filterable attribute IDs to sync onto the category.')]
+    #[Groups(['mutation'])]
+    public ?array $attributes = null;
+
+    #[ApiProperty(description: 'Parent category ID. Change this (with position) to move the category.')]
+    #[Groups(['mutation'])]
+    public ?int $parent_id = null;
+
+    #[ApiProperty(description: 'Display mode.')]
+    #[Groups(['mutation'])]
+    public ?string $display_mode = null;
+
+    #[ApiProperty(description: 'Status (1 = enabled, 0 = disabled).')]
+    #[Groups(['mutation'])]
+    public ?int $status = null;
+
+    /** @var array<int, string>|null */
+    #[ApiProperty(description: 'Optional logo path(s). File-upload is not supported in v1.')]
+    #[Groups(['mutation'])]
+    public ?array $logo_path = null;
+
+    /** @var array<int, string>|null */
+    #[ApiProperty(description: 'Optional banner path(s). File-upload is not supported in v1.')]
+    #[Groups(['mutation'])]
+    public ?array $banner_path = null;
+
+    /**
+     * Per-locale translation block. Keyed by locale code (e.g. "en"), with
+     * keys: slug, name, description, meta_title, meta_description, meta_keywords.
+     *
+     * @var array<string, mixed>|null
+     */
+    #[ApiProperty(description: 'Per-locale translation block: { en: { slug, name, description, ... } }.')]
+    #[Groups(['mutation'])]
+    public ?array $en = null;
+}

+ 50 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCmsPageCreateInput.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/cms/pages + createAdminCmsPage.
+ *
+ * Mirrors Bagisto core PageController::store():
+ *   - top-level: url_key (required + unique on cms_page_translations.url_key + slug regex)
+ *   - page_title (required)
+ *   - html_content (required)
+ *   - channels (required non-empty array of channel ids)
+ *   - optional: meta_title, meta_keywords, meta_description
+ *
+ * The repository broadcasts top-level translated fields to every locale.
+ */
+class AdminCmsPageCreateInput
+{
+    #[ApiProperty(description: 'URL key (slug). Must be unique across cms_page_translations and match slug regex.')]
+    #[Groups(['mutation'])]
+    public ?string $url_key = null;
+
+    #[ApiProperty(description: 'Page title.')]
+    #[Groups(['mutation'])]
+    public ?string $page_title = null;
+
+    #[ApiProperty(description: 'Page HTML content.')]
+    #[Groups(['mutation'])]
+    public ?string $html_content = null;
+
+    /** @var array<int, int>|null */
+    #[ApiProperty(description: 'Channel IDs to attach the page to.')]
+    #[Groups(['mutation'])]
+    public ?array $channels = null;
+
+    #[ApiProperty(description: 'SEO meta title.')]
+    #[Groups(['mutation'])]
+    public ?string $meta_title = null;
+
+    #[ApiProperty(description: 'SEO meta keywords.')]
+    #[Groups(['mutation'])]
+    public ?string $meta_keywords = null;
+
+    #[ApiProperty(description: 'SEO meta description.')]
+    #[Groups(['mutation'])]
+    public ?string $meta_description = null;
+}

+ 57 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCmsPageDetailDto.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiResource;
+use Webkul\BagistoApi\Admin\Dto\Concerns\AcceptsCamelCaseWrites;
+
+/**
+ * REST output DTO for the admin CMS Page detail (and create/update responses).
+ *
+ * Reproduces the exact detail shape served before the GraphQL field-selection
+ * refactor — translations as a plain array of locale blocks, channels as a
+ * plain array of { id, code, name } objects. Properties are snake_case (resolve
+ * over both transports) and surface as camelCase via the project name converter.
+ */
+#[ApiResource(
+    shortName: 'AdminCmsPageDetailDto',
+    operations: [],
+    graphQlOperations: [],
+    normalizationContext: ['skip_null_values' => false],
+)]
+class AdminCmsPageDetailDto
+{
+    use AcceptsCamelCaseWrites;
+
+    public ?int $id = null;
+
+    public ?string $url_key = null;
+
+    public ?string $page_title = null;
+
+    public ?string $html_content = null;
+
+    public ?string $meta_title = null;
+
+    public ?string $meta_keywords = null;
+
+    public ?string $meta_description = null;
+
+    public ?string $layout = null;
+
+    public ?string $preview_url = null;
+
+    public ?string $locale = null;
+
+    public ?string $channel = null;
+
+    public ?string $created_at = null;
+
+    public ?string $updated_at = null;
+
+    /** @var array<int, mixed>|null */
+    public ?array $translations = null;
+
+    /** @var array<int, mixed>|null */
+    public ?array $channels = null;
+}

+ 56 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCmsPageListDto.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiResource;
+use Webkul\BagistoApi\Admin\Dto\Concerns\AcceptsCamelCaseWrites;
+
+/**
+ * REST output DTO for the admin CMS Pages listing.
+ *
+ * Reproduces the exact list-row shape served before the GraphQL field-selection
+ * refactor. The properties are snake_case (resolve over both transports) and
+ * surface as camelCase to clients via the project name converter.
+ */
+#[ApiResource(
+    shortName: 'AdminCmsPageListDto',
+    operations: [],
+    graphQlOperations: [],
+    normalizationContext: ['skip_null_values' => false],
+)]
+class AdminCmsPageListDto
+{
+    use AcceptsCamelCaseWrites;
+
+    public ?int $id = null;
+
+    public ?string $url_key = null;
+
+    public ?string $page_title = null;
+
+    public ?string $html_content = null;
+
+    public ?string $meta_title = null;
+
+    public ?string $meta_keywords = null;
+
+    public ?string $meta_description = null;
+
+    public ?string $layout = null;
+
+    public ?string $preview_url = null;
+
+    public ?string $locale = null;
+
+    public ?string $channel = null;
+
+    public ?string $created_at = null;
+
+    public ?string $updated_at = null;
+
+    /** @var array<int, mixed>|null */
+    public ?array $translations = null;
+
+    /** @var array<int, mixed>|null */
+    public ?array $channels = null;
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCmsPageMassDeleteInput.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/cms/pages/mass-delete.
+ */
+class AdminCmsPageMassDeleteInput
+{
+    /** @var int[]|null */
+    #[ApiProperty(description: 'Array of CMS page IDs to delete.')]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+}

+ 45 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCmsPageUpdateInput.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for PUT /api/admin/cms/pages/{id} + updateAdminCmsPage.
+ *
+ * Update validation is LOCALE-NESTED in monolith PageController::update():
+ *   <locale>.url_key / <locale>.page_title / <locale>.html_content are required.
+ * Plus top-level: channels (required), locale (required).
+ *
+ * The processor reads the body from request()->all() (REST) or
+ * $context['args']['input'] (GraphQL) because the name converter does not
+ * map dynamic locale-keyed blocks onto DTO properties cleanly. The 'en'
+ * property below is a hint for the GraphQL schema — for other locales the
+ * processor falls back to the raw args.
+ */
+class AdminCmsPageUpdateInput
+{
+    #[ApiProperty(description: 'Resource IRI (e.g. /api/admin/cms/pages/12).')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty(description: 'Locale code (e.g. "en"). Determines which nested locale block is required.')]
+    #[Groups(['mutation'])]
+    public ?string $locale = null;
+
+    /** @var array<int, int>|null */
+    #[ApiProperty(description: 'Channel IDs to sync onto the page.')]
+    #[Groups(['mutation'])]
+    public ?array $channels = null;
+
+    /**
+     * English-locale block. Other locales (fr/es/...) are accepted at runtime by
+     * reading raw args/body — declared here only so the GraphQL schema is buildable.
+     *
+     * @var array<string, mixed>|null
+     */
+    #[ApiProperty(description: 'Per-locale translation block: { en: { url_key, page_title, html_content, ... } }.')]
+    #[Groups(['mutation'])]
+    public ?array $en = null;
+}

+ 64 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminConfigurationTreeField.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminConfigurationTreeField
+{
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $name = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $code = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $title = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $type = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $customView = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $default = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?bool $channelBased = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?bool $localeBased = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $validation = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $depends = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $info = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public mixed $value = null;
+
+    /**
+     * @var AdminConfigurationTreeOption[]|null
+     */
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?array $options = null;
+}

+ 43 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminConfigurationTreeItem.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminConfigurationTreeItem
+{
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $key = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $name = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $info = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $icon = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?int $sort = null;
+
+    /**
+     * @var AdminConfigurationTreeField[]|null
+     */
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?array $fields = null;
+
+    /**
+     * @var AdminConfigurationTreeItem[]|null
+     */
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?array $children = null;
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminConfigurationTreeOption.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminConfigurationTreeOption
+{
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $title = null;
+
+    #[ApiProperty(readable: true, writable: false)]
+    #[Groups(['query'])]
+    public ?string $value = null;
+}

+ 47 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminConfigurationUpdateInput.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for `POST /api/admin/configuration` and
+ * `createAdminConfigurationUpdate` GraphQL mutation.
+ *
+ * Shape:
+ *   {
+ *     "slug": "sales.order_settings",
+ *     "channel": "default",                // optional
+ *     "locale":  "en",                     // optional
+ *     "values": { "<full.dotted.code>": <string|bool|number> }
+ *   }
+ *
+ * For REST multipart uploads, `values[<code>]` is the file part; the processor
+ * forwards `request()` to `CoreConfigRepository::create()` which handles
+ * `hasFile()` natively. GraphQL rejects file-type fields (mirrors Phase 5.11).
+ */
+class AdminConfigurationUpdateInput
+{
+    #[ApiProperty(description: 'Slug of the configuration node being updated, e.g. `sales.order_settings`.')]
+    #[Groups(['mutation'])]
+    public ?string $slug = null;
+
+    #[ApiProperty(description: 'Channel code for channel-based fields.')]
+    #[Groups(['mutation'])]
+    public ?string $channel = null;
+
+    #[ApiProperty(description: 'Locale code for locale-based fields.')]
+    #[Groups(['mutation'])]
+    public ?string $locale = null;
+
+    /**
+     * Flat map: `<full.dotted.code> => <value>`. Every key must be inside the
+     * `slug` subtree (anti-scope-escape).
+     *
+     * @var array<string, mixed>|null
+     */
+    #[ApiProperty(description: 'Flat map of dotted field codes to values.')]
+    #[Groups(['mutation'])]
+    public ?array $values = null;
+}

+ 19 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCreateDraftCartInput.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input for the `createAdminDraftCart` GraphQL mutation.
+ *
+ * REST path: POST /api/admin/customers/{customerId}/draft-carts
+ * (customerId comes from the URL on REST).
+ */
+class AdminCreateDraftCartInput
+{
+    #[ApiProperty(description: 'Customer ID the draft cart will belong to.')]
+    #[Groups(['mutation'])]
+    public ?int $customerId = null;
+}

+ 121 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCreateReturnInput.php

@@ -0,0 +1,121 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCreateReturnInput
+{
+    #[ApiProperty(description: 'Order id the returned item belongs to')]
+    #[Groups(['mutation'])]
+    public ?int $order_id = null;
+
+    #[ApiProperty(description: 'Order item id to return')]
+    #[Groups(['mutation'])]
+    public ?int $order_item_id = null;
+
+    #[ApiProperty(description: 'Quantity to return; capped server-side by the returnable quantity')]
+    #[Groups(['mutation'])]
+    public ?int $rma_qty = null;
+
+    #[ApiProperty(description: 'return | cancel_items')]
+    #[Groups(['mutation'])]
+    public ?string $resolution_type = null;
+
+    #[ApiProperty(description: 'Reason id')]
+    #[Groups(['mutation'])]
+    public ?int $rma_reason_id = null;
+
+    #[ApiProperty(description: 'Free-text detail about the return')]
+    #[Groups(['mutation'])]
+    public ?string $information = null;
+
+    #[ApiProperty(description: 'Condition of the package')]
+    #[Groups(['mutation'])]
+    public ?string $package_condition = null;
+
+    #[ApiProperty(description: 'Variant product id for configurable products')]
+    #[Groups(['mutation'])]
+    public ?int $variant = null;
+
+    public function getOrder_id(): ?int
+    {
+        return $this->order_id;
+    }
+
+    public function setOrder_id(?int $v): void
+    {
+        $this->order_id = $v;
+    }
+
+    public function getOrder_item_id(): ?int
+    {
+        return $this->order_item_id;
+    }
+
+    public function setOrder_item_id(?int $v): void
+    {
+        $this->order_item_id = $v;
+    }
+
+    public function getRma_qty(): ?int
+    {
+        return $this->rma_qty;
+    }
+
+    public function setRma_qty(?int $v): void
+    {
+        $this->rma_qty = $v;
+    }
+
+    public function getResolution_type(): ?string
+    {
+        return $this->resolution_type;
+    }
+
+    public function setResolution_type(?string $v): void
+    {
+        $this->resolution_type = $v;
+    }
+
+    public function getRma_reason_id(): ?int
+    {
+        return $this->rma_reason_id;
+    }
+
+    public function setRma_reason_id(?int $v): void
+    {
+        $this->rma_reason_id = $v;
+    }
+
+    public function getInformation(): ?string
+    {
+        return $this->information;
+    }
+
+    public function setInformation(?string $v): void
+    {
+        $this->information = $v;
+    }
+
+    public function getPackage_condition(): ?string
+    {
+        return $this->package_condition;
+    }
+
+    public function setPackage_condition(?string $v): void
+    {
+        $this->package_condition = $v;
+    }
+
+    public function getVariant(): ?int
+    {
+        return $this->variant;
+    }
+
+    public function setVariant(?int $v): void
+    {
+        $this->variant = $v;
+    }
+}

+ 48 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerAddressCreateInput.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerAddressCreateInput
+{
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?int $customer_id = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $first_name = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $last_name = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $company_name = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $vat_id = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $address = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $city = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $state = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $country = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $postcode = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $email = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $phone = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?bool $default_address = null;
+}

+ 52 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerAddressUpdateInput.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerAddressUpdateInput
+{
+    #[ApiProperty(description: 'Resource IRI (e.g. /api/admin/customers/4/addresses/12).')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?int $customer_id = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $first_name = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $last_name = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $company_name = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $vat_id = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $address = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $city = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $state = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $country = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $postcode = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $email = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $phone = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?bool $default_address = null;
+}

+ 45 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerCreateInput.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerCreateInput
+{
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $first_name = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $last_name = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $email = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $phone = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $gender = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $date_of_birth = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?int $customer_group_id = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?int $channel_id = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?int $status = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?bool $subscribed_to_news_letter = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?bool $send_password = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $password = null;
+}

+ 50 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerDetailDto.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiResource;
+use Webkul\BagistoApi\Admin\Dto\Concerns\AcceptsCamelCaseWrites;
+
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class AdminCustomerDetailDto
+{
+    use AcceptsCamelCaseWrites;
+
+    public ?int $id = null;
+
+    public ?string $first_name = null;
+
+    public ?string $last_name = null;
+
+    public ?string $name = null;
+
+    public ?string $email = null;
+
+    public ?string $phone = null;
+
+    public ?string $gender = null;
+
+    public ?string $date_of_birth = null;
+
+    public ?int $channel_id = null;
+
+    public ?int $status = null;
+
+    public ?bool $subscribed_to_news_letter = null;
+
+    public ?int $is_verified = null;
+
+    public ?int $is_suspended = null;
+
+    public ?int $total_addresses = null;
+
+    public ?int $total_orders = null;
+
+    public ?float $total_amount_spent = null;
+
+    public ?string $created_at = null;
+
+    public ?string $updated_at = null;
+
+    public ?array $group = null;
+}

+ 20 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGdprDownloadDataInput.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * POST /api/admin/customers/{customerId}/gdpr-download-data +
+ * createAdminCustomerGdprDownloadData mutation input.
+ *
+ * Returns a JSON dump of every table that references the customer's id.
+ * Not bound to a GDPR request — admin can run ad-hoc on any customer.
+ */
+class AdminCustomerGdprDownloadDataInput
+{
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $customerId = null;
+}

+ 27 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGdprProcessInput.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * POST /api/admin/customers/gdpr-requests/{id}/process +
+ * createAdminCustomerGdprProcess mutation input.
+ *
+ * Approves a GDPR request and runs the destructive side effect:
+ *   - type = delete → cascade-deletes the customer
+ *   - type = update → marks request approved (admin then applies the changes manually)
+ *
+ * Status is set to "approved" on success.
+ */
+class AdminCustomerGdprProcessInput
+{
+    #[ApiProperty(description: 'GDPR request id (numeric or IRI). Required for GraphQL; REST takes it from the URL.')]
+    #[Groups(['mutation'])]
+    public ?string $requestId = null;
+
+    #[ApiProperty(description: 'Optional admin note recorded alongside the approval.')]
+    #[Groups(['mutation'])]
+    public ?string $message = null;
+}

+ 25 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGdprUpdateInput.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * PUT /api/admin/customers/gdpr-requests/{id} +
+ * updateAdminCustomerGdprRequest mutation input.
+ */
+class AdminCustomerGdprUpdateInput
+{
+    #[ApiProperty(description: 'Resource IRI (e.g. /api/admin/customers/gdpr-requests/4).')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty(description: 'pending | processing | declined | approved | revoked')]
+    #[Groups(['mutation'])]
+    public ?string $status = null;
+
+    #[ApiProperty(description: 'Optional message recorded with the status change (e.g. reason for decline). Stored in the request message field.')]
+    #[Groups(['mutation'])]
+    public ?string $message = null;
+}

+ 15 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGroupCreateInput.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerGroupCreateInput
+{
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $code = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $name = null;
+}

+ 14 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGroupMassDeleteInput.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerGroupMassDeleteInput
+{
+    /** @var int[]|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+}

+ 19 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerGroupUpdateInput.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerGroupUpdateInput
+{
+    #[ApiProperty(description: 'Resource IRI (e.g. /api/admin/customers/groups/4).')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $code = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $name = null;
+}

+ 13 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerImpersonateInput.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerImpersonateInput
+{
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $customer_id = null;
+}

+ 44 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerListDto.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiResource;
+use Webkul\BagistoApi\Admin\Dto\Concerns\AcceptsCamelCaseWrites;
+
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class AdminCustomerListDto
+{
+    use AcceptsCamelCaseWrites;
+
+    public ?int $id = null;
+
+    public ?string $first_name = null;
+
+    public ?string $last_name = null;
+
+    public ?string $name = null;
+
+    public ?string $email = null;
+
+    public ?string $phone = null;
+
+    public ?string $gender = null;
+
+    public ?string $date_of_birth = null;
+
+    public ?int $channel_id = null;
+
+    public ?int $status = null;
+
+    public ?bool $subscribed_to_news_letter = null;
+
+    public ?int $is_verified = null;
+
+    public ?int $is_suspended = null;
+
+    public ?string $created_at = null;
+
+    public ?string $updated_at = null;
+
+    public ?array $group = null;
+}

+ 14 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerMassDeleteInput.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerMassDeleteInput
+{
+    /** @var int[]|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+}

+ 18 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerMassUpdateStatusInput.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerMassUpdateStatusInput
+{
+    /** @var int[]|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $value = null;
+}

+ 21 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerNoteCreateInput.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerNoteCreateInput
+{
+    #[ApiProperty(description: 'Target customer id (path arg, also accepted in input).')]
+    #[Groups(['mutation'])]
+    public ?int $customer_id = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $note = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?bool $customer_notified = null;
+}

+ 31 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerReviewDetailDto.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class AdminCustomerReviewDetailDto
+{
+    public ?int $id = null;
+
+    public ?string $title = null;
+
+    public ?string $comment = null;
+
+    public ?int $rating = null;
+
+    public ?string $status = null;
+
+    public ?string $name = null;
+
+    public ?string $created_at = null;
+
+    public ?string $updated_at = null;
+
+    public ?array $product = null;
+
+    public ?array $customer = null;
+
+    public array $images = [];
+}

+ 29 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerReviewListDto.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class AdminCustomerReviewListDto
+{
+    public ?int $id = null;
+
+    public ?string $title = null;
+
+    public ?string $comment = null;
+
+    public ?int $rating = null;
+
+    public ?string $status = null;
+
+    public ?string $name = null;
+
+    public ?string $created_at = null;
+
+    public ?string $updated_at = null;
+
+    public ?array $product = null;
+
+    public ?array $customer = null;
+}

+ 14 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerReviewMassDeleteInput.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerReviewMassDeleteInput
+{
+    /** @var int[]|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+}

+ 18 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerReviewMassUpdateStatusInput.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerReviewMassUpdateStatusInput
+{
+    /** @var int[]|null */
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+
+    #[ApiProperty(description: 'pending|approved|disapproved')]
+    #[Groups(['mutation'])]
+    public ?string $value = null;
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerReviewUpdateInput.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerReviewUpdateInput
+{
+    #[ApiProperty(description: 'Resource IRI (e.g. /api/admin/customers/reviews/4).')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty(description: 'New status: pending|approved|disapproved.')]
+    #[Groups(['mutation'])]
+    public ?string $status = null;
+}

+ 46 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminCustomerUpdateInput.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminCustomerUpdateInput
+{
+    #[ApiProperty(description: 'Resource IRI (e.g. /api/admin/customers/4).')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $first_name = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $last_name = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $email = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $phone = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $gender = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $date_of_birth = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?int $customer_group_id = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?int $status = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?int $is_suspended = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?bool $subscribed_to_news_letter = null;
+
+    #[ApiProperty] #[Groups(['mutation'])]
+    public ?string $password = null;
+}

+ 23 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminEuWithdrawalActionInput.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminEuWithdrawalActionInput
+{
+    #[ApiProperty(description: 'Withdrawal IRI, e.g. /api/admin/eu-withdrawals/3')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    public function getId(): ?string
+    {
+        return $this->id;
+    }
+
+    public function setId(?string $v): void
+    {
+        $this->id = $v;
+    }
+}

+ 37 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminEuWithdrawalDeclineInput.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminEuWithdrawalDeclineInput
+{
+    #[ApiProperty(description: 'Withdrawal IRI, e.g. /api/admin/eu-withdrawals/3')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty(description: 'Reason the merchant contests entitlement (max 500).')]
+    #[Groups(['mutation'])]
+    public ?string $declined_reason = null;
+
+    public function getId(): ?string
+    {
+        return $this->id;
+    }
+
+    public function setId(?string $v): void
+    {
+        $this->id = $v;
+    }
+
+    public function getDeclined_reason(): ?string
+    {
+        return $this->declined_reason;
+    }
+
+    public function setDeclined_reason(?string $v): void
+    {
+        $this->declined_reason = $v;
+    }
+}

+ 37 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminEuWithdrawalMarkRefundedInput.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminEuWithdrawalMarkRefundedInput
+{
+    #[ApiProperty(description: 'Withdrawal IRI, e.g. /api/admin/eu-withdrawals/3')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty(description: 'Optional admin reference for the out-of-band refund (max 500).')]
+    #[Groups(['mutation'])]
+    public ?string $refund_note = null;
+
+    public function getId(): ?string
+    {
+        return $this->id;
+    }
+
+    public function setId(?string $v): void
+    {
+        $this->id = $v;
+    }
+
+    public function getRefund_note(): ?string
+    {
+        return $this->refund_note;
+    }
+
+    public function setRefund_note(?string $v): void
+    {
+        $this->refund_note = $v;
+    }
+}

+ 27 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminInvoiceCreateInput.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Attribute\Groups;
+
+class AdminInvoiceCreateInput
+{
+    #[ApiProperty(description: 'Order id (GraphQL only — REST takes it from the URL).')]
+    #[Groups(['mutation'])]
+    public ?int $orderId = null;
+
+    /**
+     * Array of `{ orderItemId, quantity }` objects. Typed as plain `array` so
+     * API Platform's Symfony denormalizer doesn't try to map elements onto
+     * a nested resource (which it rejects with "Nested documents are not
+     * allowed"). The processor reads each entry as an assoc/array.
+     */
+    #[ApiProperty(description: 'Items to invoice: array of { orderItemId, quantity }.')]
+    #[Groups(['mutation'])]
+    public array $items = [];
+
+    #[ApiProperty(description: 'When true, also records an order transaction for the invoice (the admin "Create Transaction" checkbox). The transaction captures the invoice amount against the order\'s payment method. Defaults to false.')]
+    #[Groups(['mutation'])]
+    public ?bool $canCreateTransaction = null;
+}

+ 18 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminInvoiceMassUpdateStatusInput.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminInvoiceMassUpdateStatusInput
+{
+    /** @var int[]|null */
+    #[ApiProperty(description: 'Array of invoice ids to update.')]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+
+    #[ApiProperty(description: 'New invoice status: pending, paid, or overdue.')]
+    #[Groups(['mutation'])]
+    public ?string $value = null;
+}

+ 197 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminInvoiceRestDto.php

@@ -0,0 +1,197 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use Webkul\BagistoApi\Admin\Dto\Concerns\AcceptsCamelCaseWrites;
+
+/**
+ * REST output for AdminInvoice (detail + listing). Snake_case props surface as
+ * camelCase via the central converter (provider writes camelCase; the trait
+ * maps it). Nested data is plain JSON — `addresses` (billing + shipping, each
+ * with `addressType`) and `items` as flat arrays; over GraphQL the same data is
+ * served as connections off the AdminInvoice Eloquent resource.
+ *
+ * IMPORTANT (the output:-DTO name-match trap, see CLAUDE.md OrderDetail notes):
+ * with `output:` set, API Platform only serialises DTO props whose names match
+ * an attribute/relation on the AdminInvoice Eloquent resource — so the address
+ * block MUST be named `addresses` (a relation), not billingAddress/shippingAddress.
+ */
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class AdminInvoiceRestDto
+{
+    use AcceptsCamelCaseWrites;
+
+    #[ApiProperty(writable: false)]
+    public ?int $id = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $increment_id = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $order_id = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $order_increment_id = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $state = null;
+
+    #[ApiProperty(writable: false)]
+    public ?bool $email_sent = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $total_qty = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $order_currency_code = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $base_currency_code = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $channel_currency_code = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $sub_total = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_sub_total = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $base_sub_total = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_base_sub_total = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $sub_total_incl_tax = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_sub_total_incl_tax = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $base_sub_total_incl_tax = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_base_sub_total_incl_tax = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $grand_total = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_grand_total = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $base_grand_total = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_base_grand_total = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $tax_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_tax_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $base_tax_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_base_tax_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $discount_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_discount_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $base_discount_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_base_discount_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $shipping_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_shipping_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $base_shipping_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_base_shipping_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $shipping_amount_incl_tax = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_shipping_amount_incl_tax = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $base_shipping_amount_incl_tax = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_base_shipping_amount_incl_tax = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $shipping_tax_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_shipping_tax_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $base_shipping_tax_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $formatted_base_shipping_tax_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $transaction_id = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $reminders = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $next_reminder_at = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $created_at = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $updated_at = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $order_status = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $order_status_label = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $order_date = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $channel_name = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $customer_name = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $customer_email = null;
+
+    /**
+     * The invoice's order — `{ id, addresses: [...] }`. Billing/shipping live on
+     * the order; over GraphQL they're `order { addresses { edges { node } } }`.
+     * The prop is named `order` (not `addresses`) to match the AdminInvoice
+     * `order()` relation — the output:-DTO name-match rule.
+     */
+    #[ApiProperty(writable: false)]
+    public ?array $order = null;
+
+    /** @var array<int, array<string, mixed>> */
+    #[ApiProperty(writable: false)]
+    public array $items = [];
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminInvoiceSendDuplicateInput.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminInvoiceSendDuplicateInput
+{
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $invoiceId = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $email = null;
+}

+ 43 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCampaignCreateInput.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/marketing/campaigns + createAdminMarketingCampaign.
+ *
+ * Mirrors Webkul\Admin\Http\Controllers\Marketing\Communications\CampaignController::store
+ * validation rules.
+ */
+class AdminMarketingCampaignCreateInput
+{
+    #[ApiProperty(description: 'Campaign display name.')]
+    #[Groups(['mutation'])]
+    public ?string $name = null;
+
+    #[ApiProperty(description: 'Email subject line.')]
+    #[Groups(['mutation'])]
+    public ?string $subject = null;
+
+    #[ApiProperty(description: 'FK marketing_templates.id (required).')]
+    #[Groups(['mutation'])]
+    public ?int $marketing_template_id = null;
+
+    #[ApiProperty(description: 'FK marketing_events.id. Required by monolith.')]
+    #[Groups(['mutation'])]
+    public ?int $marketing_event_id = null;
+
+    #[ApiProperty(description: 'FK channels.id (required).')]
+    #[Groups(['mutation'])]
+    public ?int $channel_id = null;
+
+    #[ApiProperty(description: 'FK customer_groups.id (required).')]
+    #[Groups(['mutation'])]
+    public ?int $customer_group_id = null;
+
+    #[ApiProperty(description: 'Enabled flag (0/1). Defaults to 0 when omitted.')]
+    #[Groups(['mutation'])]
+    public ?int $status = null;
+}

+ 61 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCampaignRestDto.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use Webkul\BagistoApi\Admin\Dto\Concerns\AcceptsCamelCaseWrites;
+
+/**
+ * REST output for AdminMarketingCampaign. Snake_case props surface as camelCase
+ * via the central converter.
+ *
+ * The former bare FK scalars (channelId / customerGroupId / marketingTemplateId
+ * / marketingEventId) and the resolved flat names (channelName /
+ * customerGroupCode / marketingTemplateName / marketingEventName) are REPLACED by
+ * four to-one objects: channel {id,code,name}, customer_group {id,code,name},
+ * marketing_template {id,name,status}, marketing_event {id,name,date}|null.
+ *
+ * Name-match trap: each object prop must be named `channel` / `customer_group` /
+ * `marketing_template` / `marketing_event` to match the parent Eloquent
+ * resource's belongsTo relations.
+ */
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class AdminMarketingCampaignRestDto
+{
+    use AcceptsCamelCaseWrites;
+
+    #[ApiProperty(writable: false)]
+    public ?int $id = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $name = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $subject = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $status = null;
+
+    /** @var array{id:int, code:string|null, name:string|null}|null */
+    #[ApiProperty(writable: false)]
+    public ?array $channel = null;
+
+    /** @var array{id:int, code:string|null, name:string|null}|null */
+    #[ApiProperty(writable: false)]
+    public ?array $customer_group = null;
+
+    /** @var array{id:int, name:string|null, status:string|null}|null */
+    #[ApiProperty(writable: false)]
+    public ?array $marketing_template = null;
+
+    /** @var array{id:int, name:string|null, date:string|null}|null */
+    #[ApiProperty(writable: false)]
+    public ?array $marketing_event = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $created_at = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $updated_at = null;
+}

+ 20 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCampaignSendInput.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/marketing/campaigns/{id}/send and the
+ * createAdminMarketingCampaignSend GraphQL mutation.
+ *
+ * The send action takes no body parameters — only the campaign id from
+ * the URL (REST) or campaignId field (GraphQL).
+ */
+class AdminMarketingCampaignSendInput
+{
+    #[ApiProperty(description: 'Campaign id to send. REST takes it from the URL; GraphQL takes it from this field.')]
+    #[Groups(['mutation'])]
+    public ?int $campaignId = null;
+}

+ 44 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCampaignUpdateInput.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for PUT /api/admin/marketing/campaigns/{id} and the delete mutation.
+ */
+class AdminMarketingCampaignUpdateInput
+{
+    #[ApiProperty(description: 'Resource IRI (e.g. /api/admin/marketing/campaigns/5). Used by GraphQL mutations.')]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $name = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $subject = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $marketing_template_id = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $marketing_event_id = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $channel_id = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $customer_group_id = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $status = null;
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCopyInput.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input for the Cart Rule copy mutation. REST takes the id from the URI;
+ * GraphQL passes it as `cartRuleId` (the `id` field is reserved as the IRI).
+ */
+class AdminMarketingCartRuleCopyInput
+{
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $cartRuleId = null;
+}

+ 32 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCouponCreateInput.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Create input for a single cart-rule coupon code.
+ */
+class AdminMarketingCartRuleCouponCreateInput
+{
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $cartRuleId = null;
+
+    #[ApiProperty(description: 'Coupon code (unique).')]
+    #[Groups(['mutation'])]
+    public ?string $code = null;
+
+    #[ApiProperty(description: 'Maximum total uses (null = inherit from parent rule).')]
+    #[Groups(['mutation'])]
+    public ?int $usageLimit = null;
+
+    #[ApiProperty(description: 'Maximum uses per customer.')]
+    #[Groups(['mutation'])]
+    public ?int $usagePerCustomer = null;
+
+    #[ApiProperty(description: 'Expiry date in YYYY-MM-DD format.')]
+    #[Groups(['mutation'])]
+    public ?string $expiredAt = null;
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCouponDeleteInput.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminMarketingCartRuleCouponDeleteInput
+{
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?string $id = null;
+
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $cartRuleId = null;
+}

+ 38 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCouponGenerateInput.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Bulk-generate input. Accepts both core's keys (code_length / code_format /
+ * code_prefix / code_suffix) AND the spec's friendlier names (length / format
+ * / prefix / suffix). The processor's normalize() resolves either shape.
+ */
+class AdminMarketingCartRuleCouponGenerateInput
+{
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $cartRuleId = null;
+
+    #[ApiProperty(description: 'Code length between 4 and 30.')]
+    #[Groups(['mutation'])]
+    public ?int $length = null;
+
+    #[ApiProperty(description: 'One of: alphabetic, alphanumeric, numeric.')]
+    #[Groups(['mutation'])]
+    public ?string $format = null;
+
+    #[ApiProperty(description: 'Optional prefix.')]
+    #[Groups(['mutation'])]
+    public ?string $prefix = null;
+
+    #[ApiProperty(description: 'Optional suffix.')]
+    #[Groups(['mutation'])]
+    public ?string $suffix = null;
+
+    #[ApiProperty(description: 'How many codes to generate (1-100).')]
+    #[Groups(['mutation'])]
+    public ?int $couponQty = null;
+}

+ 18 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCouponMassDeleteInput.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class AdminMarketingCartRuleCouponMassDeleteInput
+{
+    #[ApiProperty]
+    #[Groups(['mutation'])]
+    public ?int $cartRuleId = null;
+
+    /** @var array<int, int>|null */
+    #[ApiProperty(description: 'Coupon IDs to delete.')]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+}

+ 106 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleCreateInput.php

@@ -0,0 +1,106 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/marketing/cart-rules.
+ *
+ * Mirrors Bagisto admin CartRuleRequest validation.
+ */
+class AdminMarketingCartRuleCreateInput
+{
+    #[ApiProperty(description: 'Rule name.')]
+    #[Groups(['mutation'])]
+    public ?string $name = null;
+
+    #[ApiProperty(description: 'Description.')]
+    #[Groups(['mutation'])]
+    public ?string $description = null;
+
+    #[ApiProperty(description: 'Channel ids the rule applies to.')]
+    #[Groups(['mutation'])]
+    public ?array $channels = null;
+
+    #[ApiProperty(description: 'Customer-group ids the rule applies to.')]
+    #[Groups(['mutation'])]
+    public ?array $customer_groups = null;
+
+    #[ApiProperty(description: 'Start date (ISO 8601 / Y-m-d).')]
+    #[Groups(['mutation'])]
+    public ?string $starts_from = null;
+
+    #[ApiProperty(description: 'End date (ISO 8601 / Y-m-d).')]
+    #[Groups(['mutation'])]
+    public ?string $ends_till = null;
+
+    #[ApiProperty(description: 'Active flag (1 or 0).')]
+    #[Groups(['mutation'])]
+    public ?int $status = null;
+
+    #[ApiProperty(description: 'Coupon type: 1=no coupon, 2=specific coupon.')]
+    #[Groups(['mutation'])]
+    public ?int $coupon_type = null;
+
+    #[ApiProperty(description: 'Auto-generate coupon codes (only valid when coupon_type=2).')]
+    #[Groups(['mutation'])]
+    public ?int $use_auto_generation = null;
+
+    #[ApiProperty(description: 'Coupon code (required when coupon_type=2 and use_auto_generation=0).')]
+    #[Groups(['mutation'])]
+    public ?string $coupon_code = null;
+
+    #[ApiProperty(description: 'Per-customer usage cap (0 = unlimited).')]
+    #[Groups(['mutation'])]
+    public ?int $usage_per_customer = null;
+
+    #[ApiProperty(description: 'Total uses per coupon (0 = unlimited).')]
+    #[Groups(['mutation'])]
+    public ?int $uses_per_coupon = null;
+
+    #[ApiProperty(description: 'Condition combinator: 1=ALL, 0=ANY.')]
+    #[Groups(['mutation'])]
+    public ?int $condition_type = null;
+
+    #[ApiProperty(description: 'Conditions JSON tree.')]
+    #[Groups(['mutation'])]
+    public ?array $conditions = null;
+
+    #[ApiProperty(description: 'Action type: by_percent | by_fixed | cart_fixed | buy_x_get_y.')]
+    #[Groups(['mutation'])]
+    public ?string $action_type = null;
+
+    #[ApiProperty(description: 'Discount amount.')]
+    #[Groups(['mutation'])]
+    public ?float $discount_amount = null;
+
+    #[ApiProperty(description: 'Buy quantity (buy_x_get_y).')]
+    #[Groups(['mutation'])]
+    public ?int $discount_quantity = null;
+
+    #[ApiProperty(description: 'Discount step (buy_x_get_y).')]
+    #[Groups(['mutation'])]
+    public ?string $discount_step = null;
+
+    #[ApiProperty(description: 'Apply discount to shipping amount.')]
+    #[Groups(['mutation'])]
+    public ?int $apply_to_shipping = null;
+
+    #[ApiProperty(description: 'Provide free shipping.')]
+    #[Groups(['mutation'])]
+    public ?int $free_shipping = null;
+
+    #[ApiProperty(description: 'Stop processing further rules when this matches.')]
+    #[Groups(['mutation'])]
+    public ?int $end_other_rules = null;
+
+    #[ApiProperty(description: 'Use attribute conditions extension.')]
+    #[Groups(['mutation'])]
+    public ?int $uses_attribute_conditions = null;
+
+    #[ApiProperty(description: 'Sort order priority (lower = earlier).')]
+    #[Groups(['mutation'])]
+    public ?int $sort_order = null;
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleMassDeleteInput.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Input DTO for POST /api/admin/marketing/cart-rules/mass-delete.
+ */
+class AdminMarketingCartRuleMassDeleteInput
+{
+    /** @var int[]|null */
+    #[ApiProperty(description: 'Array of cart rule IDs to delete.')]
+    #[Groups(['mutation'])]
+    public ?array $indices = null;
+}

+ 116 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleRestDto.php

@@ -0,0 +1,116 @@
+<?php
+
+namespace Webkul\BagistoApi\Admin\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use Webkul\BagistoApi\Admin\Dto\Concerns\AcceptsCamelCaseWrites;
+
+/**
+ * REST output for AdminMarketingCartRule (detail + listing + copy). Snake_case
+ * props surface as camelCase via the central converter (the provider writes
+ * camelCase; the trait maps it).
+ *
+ * REST exposes the assigned channels / customer groups as flat arrays of objects
+ * (no connections) — these REPLACE the old bare int-array `channels` /
+ * `customer_groups`:
+ *   - `channels`        → array of `{id, code, name}`
+ *   - `customer_groups` → array of `{id, code, name}`  (surfaces as customerGroups)
+ *   - `conditions`      → JSON (dynamic rule rows, left as-is)
+ *   - `coupon_code`     → scalar string (primary coupon)
+ *
+ * IMPORTANT (the output:-DTO name-match trap, see CLAUDE.md): with `output:` set,
+ * API Platform only serialises DTO props whose names match an attribute/relation
+ * on the AdminMarketingCartRule Eloquent resource — so the nested blocks MUST be
+ * named `channels` / `customer_groups` (the relations) and every other prop must
+ * match a column / accessor (`coupon_code`).
+ */
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class AdminMarketingCartRuleRestDto
+{
+    use AcceptsCamelCaseWrites;
+
+    #[ApiProperty(writable: false)]
+    public ?int $id = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $name = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $description = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $starts_from = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $ends_till = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $status = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $coupon_type = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $use_auto_generation = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $usage_per_customer = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $uses_per_coupon = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $times_used = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $condition_type = null;
+
+    /** @var array<int,mixed>|null */
+    #[ApiProperty(writable: false)]
+    public ?array $conditions = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $action_type = null;
+
+    #[ApiProperty(writable: false)]
+    public ?float $discount_amount = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $discount_quantity = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $discount_step = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $apply_to_shipping = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $free_shipping = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $end_other_rules = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $uses_attribute_conditions = null;
+
+    #[ApiProperty(writable: false)]
+    public ?int $sort_order = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $coupon_code = null;
+
+    /** @var array<int,array{id:int, code:string|null, name:string|null}>|null */
+    #[ApiProperty(writable: false)]
+    public ?array $channels = null;
+
+    /** @var array<int,array{id:int, code:string|null, name:string|null}>|null */
+    #[ApiProperty(writable: false)]
+    public ?array $customer_groups = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $created_at = null;
+
+    #[ApiProperty(writable: false)]
+    public ?string $updated_at = null;
+}

+ 0 - 0
packages/Webkul/BagistoApi/src/Admin/Dto/AdminMarketingCartRuleUpdateInput.php


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików