geekcodev/laravel-commercejson

CommerceJSON v1.0.8 integration package for Laravel 12

Maintainers

Package info

github.com/geekcodev/laravel-commercejson

pkg:composer/geekcodev/laravel-commercejson

Statistics

Installs: 30

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-05-25 12:33 UTC

This package is auto-updated.

Last update: 2026-05-25 12:33:47 UTC


README

Latest Version on Packagist Total Downloads Code Coverage GitHub Actions (main) GitHub Actions (main) License

Пакет для интеграции с CommerceJSON API v1.0.8 в Laravel 12+. Предназначен для обмена данными с системами 1С и другими ERP-системами, поддерживающими стандарт CommerceJSON.

Оглавление

Возможности

  • HTTP-клиент с поддержкой повторных запросов, идемпотентности и пагинации
  • 23 модели Eloquent с отношениями и приведением типов
  • 24 миграции базы данных с оптимизированными индексами
  • 49 Data-классов для валидации данных
  • 6 сервисов для бизнес-логики
  • 7 очередей заданий для асинхронных операций
  • 7 Artisan-команд для работы через CLI
  • 11 событий для интеграции
  • Фабрики и сидеры для тестирования
  • Полная документация

Установка

После установки пакета и публикации конфига, маршруты автоматически регистрируются в CommerceJsonServiceProvider:

// Автоматическая регистрация в boot() методе
$this->loadRoutesFrom(__DIR__.'/routes/api.php');

Готовые REST API endpoints становятся доступны по префиксу /api/commercejson:

  • /api/commercejson/products — товары
  • /api/commercejson/orders — заказы
  • /api/commercejson/categories — категории
  • /api/commercejson/offers — предложения
  • /api/commercejson/counterparties — контрагенты
composer require geekcodev/laravel-commercejson

Публикация конфигурации и миграций

# Публикация конфигурации
php artisan vendor:publish --tag=commercejson-config

# Публикация миграций
php artisan vendor:publish --tag=commercejson-migrations

# Запуск миграций
php artisan migrate

Переменные окружения

Добавьте в файл .env:

# CommerceJSON API
COMMERCEJSON_BASE_URL=https://api.your-erp.com/v1
COMMERCEJSON_AUTH_TYPE=bearer
COMMERCEJSON_AUTH_TOKEN=your-api-token
COMMERCEJSON_TIMEOUT=30
COMMERCEJSON_RETRY_ATTEMPTS=3

# Очереди (рекомендуется для production)
COMMERCEJSON_QUEUE_ENABLED=true
COMMERCEJSON_QUEUE_CONNECTION=redis

# Синхронизация
COMMERCEJSON_SYNC_SCHEDULE=0 * * * *
COMMERCEJSON_INCREMENTAL_SYNC=true

# Логирование
COMMERCEJSON_LOGGING=true
COMMERCEJSON_LOG_CHANNEL=stack
COMMERCEJSON_LOG_REQUESTS=false

Использование

Быстрый старт

use GeekCo\CommerceJson\Facades\CommerceJson;

// Получить товары
$products = CommerceJson::products()->getProducts(
    page: 1,
    limit: 100,
    categoryId: 'uuid-here'
);

// Получить заказ
$order = CommerceJson::orders()->getOrder($orderId);

// Создать заказ
$newOrder = CommerceJson::orders()->createOrder($orderData);

// Импортировать предложения (цены и остатки)
CommerceJson::offers()->importOffers($offerImportData);

Два способа работы с пакетом

1. REST API (Headless CMS) — рекомендуется для frontend

Пакет предоставляет готовые REST API endpoints. Используйте HTTP запросы из вашего frontend приложения:

// Frontend (React/Vue/Next.js) или мобильное приложение
const response = await fetch('https://your-app.test/api/commercejson/products');
const products = await response.json();

// Или через axios
const { data } = await axios.get('/api/commercejson/products');

// Создать заказ
const { data: order } = await axios.post('/api/commercejson/orders', {
  number: 'ORD-001',
  status: 'new',
  items: [...]
});

Преимущества:

  • ✅ Готовые endpoints из коробки
  • ✅ Не нужно писать контроллеры
  • ✅ Идеально для React/Vue/Next.js frontend
  • ✅ Мобильные приложения получают доступ к API
  • ✅ CQRS архитектура внутри контроллеров

2. Сервисы — для кастомной бизнес-логики

Если вам нужна кастомная логика, используйте сервисы напрямую:

use GeekCo\CommerceJson\Services\ProductService;
use GeekCo\CommerceJson\Services\OrderService;
use GeekCo\CommerceJson\Http\Client\HttpClientInterface;

class OrderController extends Controller
{
    public function __construct(
        private HttpClientInterface $http,
        private OrderService $orderService
    ) {}

    public function index()
    {
        // Чтение через HTTP API
        $orders = $this->orderService->getOrders(page: 1, limit: 100);
        return view('orders.index', compact('orders'));
    }

    public function store(OrderCreateData $data)
    {
        // Создание через HTTP API + сохранение через CommandBus
        $order = $this->orderService->createOrder($data);
        return response()->json($order);
    }
}

Тестирование

use GeekCo\CommerceJson\Tests\TestCase;
use GeekCo\CommerceJson\Http\Client\HttpClientInterface;
use Mockery;

class ProductServiceTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        $this->mockHttp = Mockery::mock(HttpClientInterface::class);
        $this->productService = new ProductService($this->mockHttp, ...);
    }

    /** @test */
    public function get_products_returns_product_list(): void
    {
        $this->mockHttp->shouldReceive('get')
            ->once()
            ->andReturn(new ResponseDto(200, [], $mockResponse, ...));
        
        $products = $this->productService->getProducts(page: 1, limit: 100);
        $this->assertCount(10, $products->products);
    }
}

Использование factory для Data-классов

// Создание тестовых данных
$productData = ProductData::factory()->from([
    'id' => 'uuid-here',
    'name' => 'Test Product',
    'code' => 'TEST-001',
    'is_active' => true,
]);

// В тестах
protected function createProductData(array $attributes = []): ProductData
{
    return ProductData::factory()->from([
        'id' => $this->createTestUuid(),
        'name' => 'Test Product',
        ...$attributes,
    ]);
}

Console-команды

# Проверка соединения
php artisan commercejson:handshake

# Полная синхронизация
php artisan commercejson:sync --full

# Инкрементальная синхронизация (изменения за последний час)
php artisan commercejson:sync --incremental

# Импорт товаров
php artisan commercejson:import-products --queue

# Импорт заказов
php artisan commercejson:import-orders --updated-after=2026-01-01T00:00:00Z

# Экспорт заказов в ERP
php artisan commercejson:export-orders --limit=50

Архитектура

Пакет использует архитектурные паттерны CQRS (Command Query Responsibility Segregation) и Repository для разделения операций чтения и записи, что обеспечивает:

  • Чёткое разделение ответственности — команды для записи, запросы для чтения
  • Тестируемость — легко мокировать зависимости через интерфейсы
  • Масштабируемость — независимое масштабирование чтения/записи
  • Поддерживаемость — понятная структура кода
  • Headless CMS — готовые REST API контроллеры и роуты из коробки

Готовые REST API endpoints

Пакет автоматически регистрирует маршруты с префиксом /api/commercejson:

Метод URI Контроллер Описание
GET /api/commercejson/products ProductController Список товаров (paginated)
GET /api/commercejson/products/{id} ProductController Получить товар по ID
POST /api/commercejson/products ProductController Создать товар
PUT/PATCH /api/commercejson/products/{id} ProductController Обновить товар
DELETE /api/commercejson/products/{id} ProductController Удалить товар
GET /api/commercejson/orders OrderController Список заказов (paginated)
GET /api/commercejson/orders/{id} OrderController Получить заказ по ID
POST /api/commercejson/orders OrderController Создать заказ
PUT/PATCH /api/commercejson/orders/{id} OrderController Обновить заказ
DELETE /api/commercejson/orders/{id} OrderController Удалить заказ
GET /api/commercejson/categories CategoryController Список категорий (paginated)
GET /api/commercejson/categories/{id} CategoryController Получить категорию по ID
POST /api/commercejson/categories CategoryController Создать категорию
PUT/PATCH /api/commercejson/categories/{id} CategoryController Обновить категорию
DELETE /api/commercejson/categories/{id} CategoryController Удалить категорию
GET /api/commercejson/offers OfferController Список предложений (paginated)
GET /api/commercejson/offers/{id} OfferController Получить предложение по ID
POST /api/commercejson/offers OfferController Создать предложение
PUT/PATCH /api/commercejson/offers/{id} OfferController Обновить предложение
DELETE /api/commercejson/offers/{id} OfferController Удалить предложение
GET /api/commercejson/counterparties CounterpartyController Список контрагентов (paginated)
GET /api/commercejson/counterparties/{id} CounterpartyController Получить контрагента по ID
POST /api/commercejson/counterparties CounterpartyController Создать контрагента
PUT/PATCH /api/commercejson/counterparties/{id} CounterpartyController Обновить контрагента
DELETE /api/commercejson/counterparties/{id} CounterpartyController Удалить контрагента

Пример запроса:

# Получить список товаров
curl -X GET "https://your-app.test/api/commercejson/products?page=1&limit=20" \
  -H "Accept: application/json"

# Создать товар
curl -X POST "https://your-app.test/api/commercejson/products" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"name": "Product 1", "code": "PROD-001", "is_active": true}'

Структура пакета

src/
├── CommerceJsonServiceProvider.php    # Регистрация сервисов
├── config/
│   └── commercejson.php               # Конфигурация
├── Http/Client/
│   ├── HttpClientInterface.php        # Интерфейс HTTP клиента
│   ├── CommerceJsonHttpClient.php     # Реализация HTTP клиента
│   ├── RetryStrategyInterface.php     # Стратегия повторных попыток
│   ├── ExponentialBackoffStrategy.php # Экспоненциальная задержка
│   └── Dto/                           # DTO для запросов/ответов
├── Commands/                          # Команды (запись)
│   ├── CommandInterface.php
│   ├── CreateProductCommand.php
│   └── ...
├── Queries/                           # Запросы (чтение)
│   ├── QueryInterface.php
│   ├── GetProductQuery.php
│   └── ...
├── Handlers/
│   ├── Commands/                      # Обработчики команд
│   │   ├── HandlerInterface.php
│   │   ├── CreateProductCommandHandler.php
│   │   └── ...
│   └── Queries/                       # Обработчики запросов
│       ├── QueryHandlerInterface.php
│       ├── GetProductQueryHandler.php
│       └── ...
├── Bus/
│   ├── CommandBusInterface.php        # Шина команд
│   └── QueryBusInterface.php          # Шина запросов
├── Repositories/                      # Репозитории
│   ├── BaseRepository.php
│   ├── ProductRepository.php
│   └── ...
├── Services/                          # Сервисы (бизнес-логика)
│   ├── ServiceInterface.php
│   ├── ProductService.php
│   ├── OrderService.php
│   ├── OfferService.php
│   ├── ClassifierService.php
│   ├── WarehouseService.php
│   └── CounterpartyService.php
├── Http/Controllers/                  # API контроллеры
│   ├── ProductController.php
│   ├── OrderController.php
│   └── ...
├── Exchange/
│   ├── Import/                        # Импорт данных
│   │   ├── ImporterInterface.php
│   │   ├── ProductImporter.php
│   │   └── ...
│   └── Export/                        # Экспорт данных
│       ├── ExporterInterface.php
│       └── OrderExporter.php
├── Jobs/                              # Очереди заданий
│   ├── Import/
│   ├── Export/
│   └── Sync/
├── Console/Commands/                  # Artisan команды
├── Events/                            # События
├── Exceptions/                        # Исключения
│   ├── SyncException.php
│   └── Http/Client/Exceptions/        # HTTP исключения
├── Models/                            # Eloquent модели (23)
├── Data/                              # Data-класси (49 DTO)
├── Enums/                             # Перечисления (11)
├── Facades/
│   └── CommerceJson.php
└── database/
    ├── migrations/                    # Миграции (24)
    ├── factories/                     # Фабрики (17)
    └── seeders/                       # Сидеры (7)

Компоненты архитектуры

1. HTTP Client Layer

use GeekCo\CommerceJson\Http\Client\HttpClientInterface;

// Внедрение зависимости
public function __construct(
    private HttpClientInterface $http
) {}

// Использование
$response = $this->http->get('/catalog/products', ['page' => 1]);
$data = $response->data; // Массив данных

2. Command/Query Bus

use GeekCo\CommerceJson\Bus\CommandBusInterface;
use GeekCo\CommerceJson\Bus\QueryBusInterface;
use GeekCo\CommerceJson\Commands\CreateProductCommand;
use GeekCo\CommerceJson\Queries\GetProductQuery;

// Команда (запись)
$command = new CreateProductCommand($productData);
$product = $this->commandBus->dispatch($command);

// Запрос (чтение)
$query = new GetProductQuery($id);
$product = $this->queryBus->ask($query);

3. Services (бизнес-логика)

use GeekCo\CommerceJson\Services\ProductService;

public function __construct(
    private ProductService $productService
) {}

// Чтение через HTTP API
$products = $this->productService->getProducts(page: 1, limit: 100);

// Запись через CommandBus
$product = $this->productService->syncProduct($productData);

4. Controllers (API endpoints)

use GeekCo\CommerceJson\Http\Controllers\ProductController;

// GET /api/commercejson/products
public function index(Request $request): JsonResponse
{
    $query = new GetProductsQuery(perPage: 15);
    $products = $this->queryBus->ask($query);
    
    return response()->json(ProductData::collect($products->items()));
}

// POST /api/commercejson/products
public function store(Request $request): JsonResponse
{
    $command = new CreateProductCommand(ProductData::from($request->all()));
    $product = $this->commandBus->dispatch($command);
    
    return response()->json(ProductData::from($product), 201);
}

5. Exceptions

use GeekCo\CommerceJson\Http\Client\Exceptions\AuthenticationException;
use GeekCo\CommerceJson\Http\Client\Exceptions\ValidationException;
use GeekCo\CommerceJson\Http\Client\Exceptions\BusinessException;
use GeekCo\CommerceJson\Http\Client\Exceptions\RateLimitException;

try {
    $this->http->post('/orders', $data);
} catch (AuthenticationException $e) {
    // 401, 403
} catch (ValidationException $e) {
    // 400 - $e->errorsAsString()
} catch (BusinessException $e) {
    // 422, 429
}

Таблицы базы данных

24 таблицы:

  • categories — категории товаров (иерархия)
  • price_types — типы цен (розница, опт, дилер)
  • warehouses — склады
  • property_definitions — свойства товаров
  • property_values — значения свойств
  • counterparties — контрагенты
  • contacts — контактная информация
  • bank_accounts — банковские счета
  • representatives — представители
  • products — каталог товаров
  • product_variants — варианты товаров
  • product_images — изображения товаров
  • offers — торговые предложения
  • offer_prices — цены предложений
  • stocks — остатки на складах
  • orders — заказы клиентов
  • order_items — позиции заказов
  • order_item_taxes — налоги позиций
  • status_history_entries — история статусов
  • custom_attributes — пользовательские атрибуты
  • signatories — подписанты документов
  • product_analogues — аналоги товаров
  • product_components — комплектующие
  • order_linked_documents — связанные документы

Тестирование

Документация по генерации большого объёма данных для нагрузочного тестирования: TESTING.md.

# Запуск всех тестов
composer test

# Unit-тесты
php vendor/bin/phpunit --testsuite=Unit

# Feature-тесты
php vendor/bin/phpunit --testsuite=Feature

# С покрытием (HTML отчёт)
composer test:coverage

# С покрытием (текстовый отчёт)
composer test:coverage-text

После генерации HTML отчёта, откройте coverage/index.html в браузере.

Покрытие кода

Текущее покрытие: Code Coverage

Минимальные требования к покрытию:

  • Services: 85%
  • Models: 80%
  • Jobs: 80%
  • Console Commands: 75%
  • Общее: 80%

См. подробную документацию в COVERAGE.md.

Документация

Доступные методы

ProductService

// Получить товары с пагинацией
$products = $productService->getProducts(
    page: 1,
    limit: 100,
    categoryId: 'uuid',
    isActive: true,
    updatedAfter: new DateTime('2026-01-01')
);

// Получить товар по ID
$product = $productService->getProduct($id);

// Импортировать товары
$result = $productService->importProducts($productsData);

// Деактивировать товар
$productService->deactivateProduct($id);

OrderService

// Получить заказы
$orders = $orderService->getOrders(
    page: 1,
    limit: 50,
    status: 'new',
    updatedAfter: new DateTime('2026-01-01')
);

// Создать заказ
$order = $orderService->createOrder($orderCreateData);

// Обновить статус заказа
$orderService->updateOrderStatus($orderId, 'confirmed');

// Экспортировать новые заказы
$exported = $orderService->exportOrders(limit: 50);

События

use GeekCo\CommerceJson\Events\ProductsImported;
use GeekCo\CommerceJson\Events\OrderImported;
use GeekCo\CommerceJson\Events\SyncCompleted;

Event::listen(ProductsImported::class, function ($event) {
    Log::info("Импортировано {$event->importedCount} товаров");
});

Event::listen(OrderImported::class, function ($event) {
    // Обработка нового заказа
});

Event::listen(SyncCompleted::class, function ($event) {
    Log::info("Синхронизация завершена за {$event->durationSeconds}с");
});

Конфигурация

// config/commercejson.php
return [
    'base_url' => env('COMMERCEJSON_BASE_URL'),
    
    'auth' => [
        'type' => env('COMMERCEJSON_AUTH_TYPE', 'bearer'),
        'token' => env('COMMERCEJSON_AUTH_TOKEN'),
    ],

    'timeout' => env('COMMERCEJSON_TIMEOUT', 30),
    'retry_attempts' => env('COMMERCEJSON_RETRY_ATTEMPTS', 3),

    'exchange' => [
        'mode' => env('COMMERCEJSON_EXCHANGE_MODE', 'auto'),
        'batch_size' => [
            'products' => 100,
            'offers' => 200,
            'orders' => 50,
        ],
        'queue' => [
            'enabled' => env('COMMERCEJSON_QUEUE_ENABLED', true),
            'connection' => env('COMMERCEJSON_QUEUE_CONNECTION', 'redis'),
        ],
    ],

    'sync' => [
        'schedule' => env('COMMERCEJSON_SYNC_SCHEDULE', '0 * * * *'),
        'incremental' => [
            'enabled' => env('COMMERCEJSON_INCREMENTAL_SYNC', true),
        ],
    ],

    'logging' => [
        'enabled' => env('COMMERCEJSON_LOGGING', true),
        'channel' => env('COMMERCEJSON_LOG_CHANNEL', 'stack'),
    ],
];

Синхронизация

Полная синхронизация

# Синхронизация всех данных
php artisan commercejson:sync --full

Инкрементальная синхронизация

# Синхронизация изменений за последний час
php artisan commercejson:sync --incremental

# Синхронизация изменений с указанной даты
php artisan commercejson:sync --incremental --since=2026-01-01T00:00:00Z

Планирование синхронизации

Добавьте в app/Console/Kernel.php:

protected function schedule(Schedule $schedule): void
{
    // Ежечасная инкрементальная синхронизация
    $schedule->command('commercejson:sync --incremental')
        ->hourly();

    // Еженедельная полная синхронизация
    $schedule->command('commercejson:sync --full')
        ->weeklyOn(0, '2:00');
}

Обработка ошибок

HTTP исключения

use GeekCo\CommerceJson\Http\Client\Exceptions\AuthenticationException;
use GeekCo\CommerceJson\Http\Client\Exceptions\ValidationException;
use GeekCo\CommerceJson\Http\Client\Exceptions\BusinessException;
use GeekCo\CommerceJson\Http\Client\Exceptions\RateLimitException;
use GeekCo\CommerceJson\Http\Client\Exceptions\ConnectionException;

try {
    $order = CommerceJson::orders()->createOrder($data);
} catch (AuthenticationException $e) {
    // 401, 403 - Неверные учётные данные
    Log::error('Ошибка авторизации: ' . $e->getMessage());
} catch (ValidationException $e) {
    // 400 - Ошибка валидации
    Log::error('Валидация: ' . $e->errorsAsString());
    foreach ($e->errors() as $error) {
        Log::error($error);
    }
} catch (BusinessException $e) {
    // 422, 429 - Бизнес-ошибка или rate limit
    Log::error('Бизнес-ошибка [' . $e->getCode() . ']: ' . $e->getMessage());
} catch (RateLimitException $e) {
    // 429 - Превышен лимит запросов
    $retryAfter = $e->retryAfter(); // секунд до повторной попытки
    $retryAt = $e->retryAt();       // DateTime для повторной попытки
    Log::warning("Превышен лимит запросов. Повтор через {$retryAfter}с");
} catch (ConnectionException $e) {
    // Ошибка соединения
    Log::error('Ошибка соединения: ' . $e->getMessage());
}

Бизнес исключения

use GeekCo\CommerceJson\Exceptions\SyncException;

try {
    CommerceJson::syncFull();
} catch (SyncException $e) {
    Log::error('Синхронизация [' . $e->getSyncType() . ']: ' . $e->getMessage());
    $lastSync = $e->getLastSyncTime(); // DateTime последней успешной синхронизации
}

Очереди заданий

use GeekCo\CommerceJson\Jobs\Import\ImportProductsJob;
use GeekCo\CommerceJson\Jobs\Import\ImportOrdersJob;
use GeekCo\CommerceJson\Jobs\Sync\SyncFullJob;

// Импорт товаров асинхронно
ImportProductsJob::dispatch(
    page: 1,
    limit: 100,
    updatedAfter: now()->subHour()
);

// Цепочка заданий
ImportProductsJob::dispatch()->chain([
    new ImportOrdersJob(),
    new \GeekCo\CommerceJson\Jobs\Export\ExportOrdersJob(),
]);

// Полная синхронизация
SyncFullJob::dispatch();

Чеклист для production

  • Опубликовать конфигурацию: php artisan vendor:publish --tag=commercejson-config
  • Опубликовать миграции: php artisan vendor:publish --tag=commercejson-migrations
  • Запустить миграции: php artisan migrate
  • Настроить worker очередей для асинхронных операций
  • Настроить планирование синхронизации в Kernel.php
  • Настроить мониторинг неудачных заданий
  • Настроить канал логирования
  • Проверить соединение: php artisan commercejson:handshake
  • Запустить начальную полную синхронизацию: php artisan commercejson:sync --full

История версий

1.0.0 (2026-04-24)

  • Начальный выпуск
  • Поддержка CommerceJSON v1.0.8
  • 24 миграции
  • 23 модели
  • 49 Data-классов
  • 6 сервисов
  • 7 очередей заданий
  • 7 console-команд
  • 11 событий
  • Полное тестовое покрытие

Лицензия

Пакет распространяется под лицензией MIT.

Поддержка

Ссылки