Promos API

Promos API для управлСния акциями ΠΈ скидками с 8 ΠΌΠ΅Ρ‚ΠΎΠ΄Π°ΠΌΠΈ для участия Π² ΠΏΡ€ΠΎΠΌΠΎ-кампаниях Ozon.

ΠžΠ±Π·ΠΎΡ€

Promos API прСдоставляСт инструмСнты для участия Π² акциях Ozon, управлСния скидками ΠΈ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ заявок ΠΏΠΎΠΊΡƒΠΏΠ°Ρ‚Π΅Π»Π΅ΠΉ Π½Π° Π»ΡŒΠ³ΠΎΡ‚Π½Ρ‹Π΅ Ρ†Π΅Π½Ρ‹.

ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ возмоТности:

  • 🎯 ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ доступных Π°ΠΊΡ†ΠΈΠΉ Ozon
  • πŸ“‹ ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ списка Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²-ΠΊΠ°Π½Π΄ΠΈΠ΄Π°Ρ‚ΠΎΠ² для Π°ΠΊΡ†ΠΈΠΉ
  • πŸš€ Π”ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² ΠΈΠ· Π°ΠΊΡ†ΠΈΠΉ
  • πŸ’° ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° заявок ΠΏΠΎΠΊΡƒΠΏΠ°Ρ‚Π΅Π»Π΅ΠΉ Π½Π° скидки
  • πŸ“Š ΠœΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³ ΡƒΡ‡Π°ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΡ… Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²
  • ⚑ Автоматизация ΠΏΡ€ΠΎΠΌΠΎ-ΠΊΠ°ΠΌΠΏΠ°Π½ΠΈΠΉ

ДоступныС ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹

Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ акциями

getActions() - Бписок доступных Π°ΠΊΡ†ΠΈΠΉ

const actions = await promosApi.getActions();

getCandidates(request) - Π’ΠΎΠ²Π°Ρ€Ρ‹-ΠΊΠ°Π½Π΄ΠΈΠ΄Π°Ρ‚Ρ‹ для Π°ΠΊΡ†ΠΈΠΈ

const candidates = await promosApi.getCandidates({
  action_id: 12345,
  limit: 100,
  last_id: 0
});

getParticipatingProducts(request) - Π£Ρ‡Π°ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ Π² Π°ΠΊΡ†ΠΈΠΈ Ρ‚ΠΎΠ²Π°Ρ€Ρ‹

const participants = await promosApi.getParticipatingProducts({
  action_id: 12345,
  limit: 100
});

Π”ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²

activateProducts(request) - Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Ρ‚ΠΎΠ²Π°Ρ€Ρ‹ Π² Π°ΠΊΡ†ΠΈΡŽ

const activation = await promosApi.activateProducts({
  action_id: 12345,
  products: [{
    product_id: 67890,
    action_price: '999',
    stock: 50
  }]
});

deactivateProducts(request) - Π£Π΄Π°Π»ΠΈΡ‚ΡŒ Ρ‚ΠΎΠ²Π°Ρ€Ρ‹ ΠΈΠ· Π°ΠΊΡ†ΠΈΠΈ

const deactivation = await promosApi.deactivateProducts({
  action_id: 12345,
  product_ids: [67890, 11111]
});

Заявки Π½Π° скидки

getDiscountTasks(request) - Бписок заявок Π½Π° скидку

const tasks = await promosApi.getDiscountTasks({
  status: 'NEW',
  limit: 50,
  page: 1
});

approveDiscountTasks(request) - Π‘ΠΎΠ³Π»Π°ΡΠΎΠ²Π°Ρ‚ΡŒ заявки Π½Π° скидку

const approval = await promosApi.approveDiscountTasks({
  tasks: [{
    task_id: 'task_123',
    product_id: 67890,
    discount_percentage: 15
  }]
});

declineDiscountTasks(request) - ΠžΡ‚ΠΊΠ»ΠΎΠ½ΠΈΡ‚ΡŒ заявки Π½Π° скидку

const decline = await promosApi.declineDiscountTasks({
  tasks: [{
    task_id: 'task_456',
    product_id: 67890,
    decline_reason: 'Блишком большая скидка'
  }]
});

TypeScript интСрфСйсы

// ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ запросы
interface PromosGetProductsRequest {
  action_id: number;
  limit?: number;
  last_id?: number;
  page?: number;
}

interface PromosGetDiscountTasksRequest {
  status?: "NEW" | "SEEN" | "APPROVED" | "DECLINED";
  limit?: number;
  page?: number;
  product_id?: number;
}

interface PromosApproveDiscountTasksRequest {
  tasks: Array<{
    task_id: string;
    product_id: number;
    discount_percentage: number;
    action_price?: string;
  }>;
}

interface PromosDeclineDiscountTasksRequest {
  tasks: Array<{
    task_id: string;
    product_id: number;
    decline_reason: string;
  }>;
}

interface PromosActivateProductsRequest {
  action_id: number;
  products: Array<{
    product_id: number;
    action_price: string;
    stock?: number;
  }>;
}

interface PromosDeactivateProductsRequest {
  action_id: number;
  product_ids: number[];
}

// ΠžΡ‚Π²Π΅Ρ‚Ρ‹
interface PromosGetActionsResponse {
  result: Array<{
    id: number;
    title: string;
    description: string;
    date_start: string;
    date_end: string;
    status: "ACTIVE" | "UPCOMING" | "FINISHED" | "CANCELLED";
    is_participating_available: boolean;
    action_type: string;
    conditions: {
      min_discount_percentage?: number;
      max_discount_percentage?: number;
      min_action_price?: string;
      max_action_price?: string;
      categories?: number[];
      brands?: string[];
    };
    participation_info: {
      total_products: number;
      participating_products: number;
      max_products_allowed?: number;
    };
  }>;
}

interface PromosGetProductsResponse {
  result: {
    products: Array<{
      product_id: number;
      name: string;
      offer_id: string;
      sku: number;
      price: string;
      old_price?: string;
      currency_code: string;
      action_price?: string;
      stock?: number;
      is_available: boolean;
      restrictions?: Array<{
        type: string;
        message: string;
      }>;
    }>;
    has_next: boolean;
    last_id: number;
    total: number;
  };
}

interface PromosGetDiscountTasksResponse {
  result: Array<{
    task_id: string;
    product_id: number;
    product_name: string;
    offer_id: string;
    sku: number;
    current_price: string;
    desired_price: string;
    discount_percentage: number;
    status: "NEW" | "SEEN" | "APPROVED" | "DECLINED";
    customer_count: number;
    created_at: string;
    expires_at: string;
  }>;
  total: number;
  has_next: boolean;
}

interface PromosProcessDiscountTasksResponse {
  result: {
    processed_count: number;
    success_count: number;
    error_count: number;
    errors?: Array<{
      task_id: string;
      error_message: string;
    }>;
  };
}

interface PromosActivateProductsResponse {
  result: {
    results: Array<{
      product_id: number;
      is_updated: boolean;
      errors?: string[];
    }>;
  };
}

interface PromosDeactivateProductsResponse {
  result: {
    results: Array<{
      product_id: number;
      is_updated: boolean;
      errors?: string[];
    }>;
  };
}

ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования

УчастиС Π² Π°ΠΊΡ†ΠΈΠΈ Ozon

// 1. ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ списка доступных Π°ΠΊΡ†ΠΈΠΉ
const actions = await promosApi.getActions();

const suitableActions = actions.result.filter(action => 
  action.status === "ACTIVE" && 
  action.is_participating_available &&
  action.conditions.min_discount_percentage <= 20
);

// 2. Анализ Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²-ΠΊΠ°Π½Π΄ΠΈΠ΄Π°Ρ‚ΠΎΠ²
for (const action of suitableActions) {
  console.log(`\nАнализ Π°ΠΊΡ†ΠΈΠΈ: ${action.title}`);
  console.log(`ΠŸΠ΅Ρ€ΠΈΠΎΠ΄: ${action.date_start} - ${action.date_end}`);
  
  const candidates = await promosApi.getCandidates({
    action_id: action.id,
    limit: 100
  });

  const suitableCandidates = candidates.result.products.filter(product => 
    product.is_available && 
    parseInt(product.price) > 500 // минимальная Ρ†Π΅Π½Π°
  );

  console.log(`ΠŸΠΎΠ΄Ρ…ΠΎΠ΄ΡΡ‰ΠΈΡ… Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²: ${suitableCandidates.length}`);

  // 3. Π”ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² Π² Π°ΠΊΡ†ΠΈΡŽ
  if (suitableCandidates.length > 0) {
    const productsToActivate = suitableCandidates.slice(0, 10).map(product => ({
      product_id: product.product_id,
      action_price: (parseInt(product.price) * 0.85).toString(), // скидка 15%
      stock: 50
    }));

    const activationResult = await promosApi.activateProducts({
      action_id: action.id,
      products: productsToActivate
    });

    console.log(`УспСшно Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΎ: ${activationResult.result.results.filter(r => r.is_updated).length} Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²`);
  }
}

ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° заявок Π½Π° скидки

// ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Π½ΠΎΠ²Ρ‹Ρ… заявок Π½Π° скидки
const newTasks = await promosApi.getDiscountTasks({
  status: 'NEW',
  limit: 50
});

console.log(`Новых заявок Π½Π° скидки: ${newTasks.result.length}`);

// Анализ ΠΈ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° заявок
const tasksToApprove: PromosApproveDiscountTasksRequest['tasks'] = [];
const tasksToDecline: PromosDeclineDiscountTasksRequest['tasks'] = [];

newTasks.result.forEach(task => {
  const currentPrice = parseInt(task.current_price);
  const desiredPrice = parseInt(task.desired_price);
  const discountPercent = ((currentPrice - desiredPrice) / currentPrice) * 100;

  console.log(`\nЗаявка Π½Π° Ρ‚ΠΎΠ²Π°Ρ€: ${task.product_name}`);
  console.log(`ВСкущая Ρ†Π΅Π½Π°: ${task.current_price}, ТСлаСмая: ${task.desired_price}`);
  console.log(`Π‘ΠΊΠΈΠ΄ΠΊΠ°: ${discountPercent.toFixed(1)}%`);
  console.log(`ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ ΠΏΠΎΠΊΡƒΠΏΠ°Ρ‚Π΅Π»Π΅ΠΉ: ${task.customer_count}`);

  // АвтоматичСскоС принятиС Ρ€Π΅ΡˆΠ΅Π½ΠΈΠΉ
  if (discountPercent <= 15 && task.customer_count >= 5) {
    // ΠžΠ΄ΠΎΠ±Ρ€ΡΠ΅ΠΌ скидки Π΄ΠΎ 15% ΠΏΡ€ΠΈ Π½Π°Π»ΠΈΡ‡ΠΈΠΈ 5+ ΠΏΠΎΠΊΡƒΠΏΠ°Ρ‚Π΅Π»Π΅ΠΉ
    tasksToApprove.push({
      task_id: task.task_id,
      product_id: task.product_id,
      discount_percentage: Math.min(discountPercent, 15)
    });
  } else if (discountPercent > 25) {
    // ΠžΡ‚ΠΊΠ»ΠΎΠ½ΡΠ΅ΠΌ слишком большиС скидки
    tasksToDecline.push({
      task_id: task.task_id,
      product_id: task.product_id,
      decline_reason: `Блишком большая скидка: ${discountPercent.toFixed(1)}%`
    });
  }
});

// ΠŸΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠ΅ ΠΎΠ΄ΠΎΠ±Ρ€Π΅Π½ΠΈΠ΅ заявок
if (tasksToApprove.length > 0) {
  const approvalResult = await promosApi.approveDiscountTasks({
    tasks: tasksToApprove
  });
  console.log(`\nΠžΠ΄ΠΎΠ±Ρ€Π΅Π½ΠΎ заявок: ${approvalResult.result.success_count}`);
}

// ΠŸΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠ΅ ΠΎΡ‚ΠΊΠ»ΠΎΠ½Π΅Π½ΠΈΠ΅ заявок
if (tasksToDecline.length > 0) {
  const declineResult = await promosApi.declineDiscountTasks({
    tasks: tasksToDecline
  });
  console.log(`ΠžΡ‚ΠΊΠ»ΠΎΠ½Π΅Π½ΠΎ заявок: ${declineResult.result.success_count}`);
}

ΠœΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³ Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… ΠΏΡ€ΠΎΠΌΠΎ-ΠΊΠ°ΠΌΠΏΠ°Π½ΠΈΠΉ

// ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΡƒΡ‡Π°ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΡ… Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² Π²ΠΎ всСх акциях
const actions = await promosApi.getActions();
const activeActions = actions.result.filter(a => a.status === "ACTIVE");

for (const action of activeActions) {
  const participants = await promosApi.getParticipatingProducts({
    action_id: action.id,
    limit: 1000
  });

  console.log(`\nАкция: ${action.title}`);
  console.log(`УчаствуСт Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²: ${participants.result.products.length}`);
  
  // Анализ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² Π² Π°ΠΊΡ†ΠΈΠΈ
  const lowStockProducts = participants.result.products.filter(p => 
    p.stock !== undefined && p.stock < 10
  );
  
  const expensiveProducts = participants.result.products.filter(p => 
    parseFloat(p.action_price || "0") > parseFloat(p.price) * 1.1
  );

  if (lowStockProducts.length > 0) {
    console.log(`⚠️ Π’ΠΎΠ²Π°Ρ€Ρ‹ с Π½ΠΈΠ·ΠΊΠΈΠΌ остатком: ${lowStockProducts.length}`);
    
    // МоТно автоматичСски ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ Ρ‚ΠΎΠ²Π°Ρ€Ρ‹ с Π½ΡƒΠ»Π΅Π²Ρ‹ΠΌ остатком
    const zeroStockProducts = lowStockProducts
      .filter(p => p.stock === 0)
      .map(p => p.product_id);

    if (zeroStockProducts.length > 0) {
      await promosApi.deactivateProducts({
        action_id: action.id,
        product_ids: zeroStockProducts
      });
      console.log(`Π£Π΄Π°Π»Π΅Π½ΠΎ Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² Π±Π΅Π· остатка: ${zeroStockProducts.length}`);
    }
  }

  if (expensiveProducts.length > 0) {
    console.log(`πŸ’° Π’ΠΎΠ²Π°Ρ€Ρ‹ с высокой Ρ†Π΅Π½ΠΎΠΉ Π² Π°ΠΊΡ†ΠΈΠΈ: ${expensiveProducts.length}`);
  }
}

Π‘Π»ΠΎΠΆΠ½Ρ‹Π΅ сцСнарии

PromoCampaignManager - Автоматизированная систСма ΠΏΡ€ΠΎΠΌΠΎ-ΠΊΠ°ΠΌΠΏΠ°Π½ΠΈΠΉ

class PromoCampaignManager {
  constructor(private api: PromosApi) {}

  async runAutomatedPromoStrategy(): Promise<PromoStrategyResult> {
    // 1. Анализ доступных Π°ΠΊΡ†ΠΈΠΉ
    const availableActions = await this.getOptimalActions();
    
    // 2. ΠŸΠΎΠ΄Π±ΠΎΡ€ Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ Π°ΠΊΡ†ΠΈΠΈ
    const campaignPlan = await this.createCampaignPlan(availableActions);
    
    // 3. ИсполнСниС плана
    const executionResults = await this.executeCampaignPlan(campaignPlan);
    
    // 4. ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° заявок Π½Π° скидки
    const discountResults = await this.processDiscountRequests();
    
    return {
      campaigns_executed: executionResults.length,
      total_products_activated: executionResults.reduce((sum, r) => sum + r.activated_count, 0),
      discount_tasks_processed: discountResults.processed_count,
      estimated_revenue_boost: this.calculateRevenueImpact(executionResults),
      recommendations: this.generateRecommendations(executionResults)
    };
  }

  private async getOptimalActions(): Promise<OptimalAction[]> {
    const actions = await this.api.getActions();
    
    return actions.result
      .filter(action => 
        action.status === "ACTIVE" && 
        action.is_participating_available &&
        new Date(action.date_end) > new Date(Date.now() + 24 * 60 * 60 * 1000) // ΠΌΠΈΠ½ΠΈΠΌΡƒΠΌ сутки Π΄ΠΎ окончания
      )
      .map(action => ({
        ...action,
        priority: this.calculateActionPriority(action)
      }))
      .sort((a, b) => b.priority - a.priority)
      .slice(0, 5); // Ρ‚ΠΎΠΏ-5 Π°ΠΊΡ†ΠΈΠΉ
  }

  private calculateActionPriority(action: any): number {
    let score = 0;
    
    // Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ Π°ΠΊΡ†ΠΈΠΈ (Ρ‡Π΅ΠΌ дольшС, Ρ‚Π΅ΠΌ Π»ΡƒΡ‡ΡˆΠ΅)
    const daysLeft = Math.ceil((new Date(action.date_end).getTime() - Date.now()) / (24 * 60 * 60 * 1000));
    score += Math.min(daysLeft * 2, 20);
    
    // Π’ΠΈΠΏ Π°ΠΊΡ†ΠΈΠΈ (Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Ρ‚ΠΈΠΏΡ‹ Π±ΠΎΠ»Π΅Π΅ Π²Ρ‹Π³ΠΎΠ΄Π½Ρ‹Π΅)
    if (action.action_type === "FLASH_SALE") score += 15;
    if (action.action_type === "CATEGORY_PROMOTION") score += 10;
    
    // ΠžΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΡ Π½Π° скидку (Ρ‡Π΅ΠΌ мСньшС минимальная скидка, Ρ‚Π΅ΠΌ Π»ΡƒΡ‡ΡˆΠ΅)
    if (action.conditions.min_discount_percentage) {
      score += Math.max(0, 20 - action.conditions.min_discount_percentage);
    }
    
    // Π£ΠΆΠ΅ ΡƒΡ‡Π°ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ Ρ‚ΠΎΠ²Π°Ρ€Ρ‹ (Π½Π΅ пСрСполнСнная акция Π»ΡƒΡ‡ΡˆΠ΅)
    const participationRate = action.participation_info.participating_products / 
                             (action.participation_info.max_products_allowed || 1000);
    if (participationRate < 0.5) score += 10;
    
    return score;
  }

  private async createCampaignPlan(actions: OptimalAction[]): Promise<CampaignPlan[]> {
    const plans: CampaignPlan[] = [];
    
    for (const action of actions) {
      const candidates = await this.api.getCandidates({
        action_id: action.id,
        limit: 500
      });

      const selectedProducts = this.selectOptimalProducts(candidates.result.products, action);
      
      if (selectedProducts.length > 0) {
        plans.push({
          action_id: action.id,
          action_title: action.title,
          products: selectedProducts,
          expected_sales_boost: this.estimateSalesBoost(selectedProducts, action)
        });
      }
    }
    
    return plans;
  }

  private selectOptimalProducts(candidates: any[], action: any): PromoProduct[] {
    return candidates
      .filter(candidate => candidate.is_available)
      .map(candidate => ({
        product_id: candidate.product_id,
        name: candidate.name,
        current_price: parseFloat(candidate.price),
        suggested_action_price: this.calculateOptimalActionPrice(candidate, action),
        expected_margin: this.calculateExpectedMargin(candidate, action),
        priority_score: this.calculateProductPriority(candidate, action)
      }))
      .sort((a, b) => b.priority_score - a.priority_score)
      .slice(0, 50); // максимум 50 Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² Π½Π° Π°ΠΊΡ†ΠΈΡŽ
  }

  private async processDiscountRequests(): Promise<DiscountProcessResult> {
    const tasks = await this.api.getDiscountTasks({
      status: 'NEW',
      limit: 100
    });

    const decisions = this.analyzeDiscountRequests(tasks.result);
    
    // ΠŸΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠ΅ ΠΎΠ΄ΠΎΠ±Ρ€Π΅Π½ΠΈΠ΅
    if (decisions.approve.length > 0) {
      await this.api.approveDiscountTasks({ tasks: decisions.approve });
    }
    
    // ΠŸΠ°ΠΊΠ΅Ρ‚Π½ΠΎΠ΅ ΠΎΡ‚ΠΊΠ»ΠΎΠ½Π΅Π½ΠΈΠ΅
    if (decisions.decline.length > 0) {
      await this.api.declineDiscountTasks({ tasks: decisions.decline });
    }

    return {
      processed_count: decisions.approve.length + decisions.decline.length,
      approved_count: decisions.approve.length,
      declined_count: decisions.decline.length,
      total_potential_revenue: decisions.approve.reduce((sum, task) => 
        sum + (parseFloat(task.discount_percentage.toString()) * task.product_id), 0)
    };
  }
}

interface OptimalAction {
  id: number;
  title: string;
  description: string;
  date_start: string;
  date_end: string;
  status: string;
  priority: number;
}

interface CampaignPlan {
  action_id: number;
  action_title: string;
  products: PromoProduct[];
  expected_sales_boost: number;
}

interface PromoProduct {
  product_id: number;
  name: string;
  current_price: number;
  suggested_action_price: number;
  expected_margin: number;
  priority_score: number;
}

interface PromoStrategyResult {
  campaigns_executed: number;
  total_products_activated: number;
  discount_tasks_processed: number;
  estimated_revenue_boost: number;
  recommendations: string[];
}

SmartDiscountAnalyzer - Π˜Π½Ρ‚Π΅Π»Π»Π΅ΠΊΡ‚ΡƒΠ°Π»ΡŒΠ½Ρ‹ΠΉ Π°Π½Π°Π»ΠΈΠ·Π°Ρ‚ΠΎΡ€ скидок

class SmartDiscountAnalyzer {
  constructor(private api: PromosApi) {}

  async analyzeDiscountOpportunities(): Promise<DiscountAnalysisReport> {
    // ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ всСх заявок Π½Π° скидки
    const allTasks = await this.getAllDiscountTasks();
    
    // Π“Ρ€ΡƒΠΏΠΏΠΈΡ€ΠΎΠ²ΠΊΠ° ΠΏΠΎ Ρ‚ΠΎΠ²Π°Ρ€Π°ΠΌ ΠΈ Π°Π½Π°Π»ΠΈΠ· Ρ‚Ρ€Π΅Π½Π΄ΠΎΠ²
    const productAnalysis = this.groupTasksByProduct(allTasks);
    
    // Анализ сСзонности ΠΈ Ρ‚Ρ€Π΅Π½Π΄ΠΎΠ²
    const trends = this.analyzeTrends(allTasks);
    
    // ΠšΠΎΠ½ΠΊΡƒΡ€Π΅Π½Ρ‚Π½Ρ‹ΠΉ Π°Π½Π°Π»ΠΈΠ·
    const competitiveInsights = this.analyzeCompetitivePosition(allTasks);
    
    return {
      total_discount_requests: allTasks.length,
      unique_products: Object.keys(productAnalysis).length,
      average_discount_requested: this.calculateAverageDiscount(allTasks),
      trend_analysis: trends,
      competitive_insights: competitiveInsights,
      high_demand_products: this.identifyHighDemandProducts(productAnalysis),
      pricing_recommendations: this.generatePricingRecommendations(productAnalysis)
    };
  }

  private async getAllDiscountTasks(): Promise<DiscountTask[]> {
    const tasks: DiscountTask[] = [];
    let page = 1;
    let hasMore = true;

    while (hasMore) {
      const response = await this.api.getDiscountTasks({
        limit: 100,
        page: page
      });

      tasks.push(...response.result);
      hasMore = response.has_next;
      page++;

      // Rate limiting
      await new Promise(resolve => setTimeout(resolve, 100));
    }

    return tasks;
  }

  private analyzeTrends(tasks: DiscountTask[]): TrendAnalysis {
    const dailyRequests = new Map<string, number>();
    const weeklyDiscounts = new Map<number, number[]>();

    tasks.forEach(task => {
      const date = task.created_at.split('T')[0];
      dailyRequests.set(date, (dailyRequests.get(date) || 0) + 1);

      const week = Math.floor(Date.now() / (7 * 24 * 60 * 60 * 1000));
      if (!weeklyDiscounts.has(week)) {
        weeklyDiscounts.set(week, []);
      }
      weeklyDiscounts.get(week)!.push(task.discount_percentage);
    });

    return {
      peak_request_days: this.findPeakDays(dailyRequests),
      average_weekly_discount: this.calculateWeeklyAverages(weeklyDiscounts),
      seasonal_patterns: this.identifySeasonalPatterns(dailyRequests),
      growth_rate: this.calculateGrowthRate(dailyRequests)
    };
  }

  private generatePricingRecommendations(productAnalysis: Map<number, ProductDiscountInfo>): PricingRecommendation[] {
    const recommendations: PricingRecommendation[] = [];

    productAnalysis.forEach((info, productId) => {
      const avgRequestedDiscount = info.requests.reduce((sum, r) => sum + r.discount_percentage, 0) / info.requests.length;
      const totalCustomerInterest = info.requests.reduce((sum, r) => sum + r.customer_count, 0);

      if (totalCustomerInterest >= 20 && avgRequestedDiscount <= 15) {
        recommendations.push({
          product_id: productId,
          recommendation_type: "PROACTIVE_DISCOUNT",
          suggested_discount: Math.floor(avgRequestedDiscount * 0.8), // 80% ΠΎΡ‚ Π·Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Π΅ΠΌΠΎΠΉ скидки
          rationale: `Высокий интСрСс (${totalCustomerInterest} ΠΏΠΎΠΊΡƒΠΏΠ°Ρ‚Π΅Π»Π΅ΠΉ), Ρ€Π°Π·ΡƒΠΌΠ½Ρ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ скидки`,
          priority: "HIGH",
          estimated_sales_increase: Math.floor(totalCustomerInterest * 1.5)
        });
      } else if (avgRequestedDiscount > 25) {
        recommendations.push({
          product_id: productId,
          recommendation_type: "PRICE_REVIEW",
          suggested_discount: 0,
          rationale: `Блишком высокиС запросы Π½Π° скидку (${avgRequestedDiscount.toFixed(1)}%) - Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, Ρ†Π΅Π½Π° Π·Π°Π²Ρ‹ΡˆΠ΅Π½Π°`,
          priority: "MEDIUM",
          estimated_sales_increase: 0
        });
      }
    });

    return recommendations.sort((a, b) => {
      const priorityOrder = { "HIGH": 3, "MEDIUM": 2, "LOW": 1 };
      return priorityOrder[b.priority] - priorityOrder[a.priority];
    });
  }
}

interface DiscountTask {
  task_id: string;
  product_id: number;
  product_name: string;
  current_price: string;
  desired_price: string;
  discount_percentage: number;
  customer_count: number;
  created_at: string;
  status: string;
}

interface DiscountAnalysisReport {
  total_discount_requests: number;
  unique_products: number;
  average_discount_requested: number;
  trend_analysis: TrendAnalysis;
  competitive_insights: CompetitiveInsights;
  high_demand_products: HighDemandProduct[];
  pricing_recommendations: PricingRecommendation[];
}

ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок

try {
  const activationResult = await promosApi.activateProducts({
    action_id: 12345,
    products: [{
      product_id: 67890,
      action_price: '999',
      stock: 50
    }]
  });

  activationResult.result.results.forEach(result => {
    if (!result.is_updated && result.errors) {
      console.error(`Ошибки для Ρ‚ΠΎΠ²Π°Ρ€Π° ${result.product_id}:`, result.errors);
    }
  });
} catch (error) {
  if (error.response?.status === 400) {
    console.error("Ошибка Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ:", error.response.data);
  } else if (error.response?.status === 403) {
    console.error("НСдостаточно ΠΏΡ€Π°Π² для участия Π² Π°ΠΊΡ†ΠΈΠΈ");
  } else {
    console.error("НСоТиданная ошибка:", error.message);
  }
}

Π›ΡƒΡ‡ΡˆΠΈΠ΅ ΠΏΡ€Π°ΠΊΡ‚ΠΈΠΊΠΈ

БтратСгия участия Π² акциях

// Анализ ROI ΠΏΠ΅Ρ€Π΅Π΄ участиСм Π² Π°ΠΊΡ†ΠΈΠΈ
function calculatePromoROI(product: any, actionPrice: string): number {
  const currentMargin = parseFloat(product.price) * 0.3; // прСдполагаСмая ΠΌΠ°Ρ€ΠΆΠ° 30%
  const actionMargin = parseFloat(actionPrice) * 0.3;
  const expectedSalesIncrease = 2.5; // ΠΎΠΆΠΈΠ΄Π°Π΅ΠΌΡ‹ΠΉ рост ΠΏΡ€ΠΎΠ΄Π°ΠΆ Π² 2.5 Ρ€Π°Π·Π°
  
  const currentProfit = currentMargin * 10; // тСкущая ΠΏΡ€ΠΈΠ±Ρ‹Π»ΡŒ с 10 ΠΏΡ€ΠΎΠ΄Π°ΠΆ
  const actionProfit = actionMargin * 10 * expectedSalesIncrease;
  
  return (actionProfit - currentProfit) / currentProfit;
}

// АвтоматичСскоС ΡƒΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ остатками Π² акциях
async function managePromoStock(
  promosApi: PromosApi, 
  actionId: number, 
  targetStockLevel: number
): Promise<void> {
  const participants = await promosApi.getParticipatingProducts({
    action_id: actionId,
    limit: 1000
  });

  const lowStockProducts = participants.result.products.filter(p => 
    p.stock !== undefined && p.stock < targetStockLevel
  );

  if (lowStockProducts.length > 0) {
    console.log(`Π’ΠΎΠ²Π°Ρ€Ρ‹ с Π½ΠΈΠ·ΠΊΠΈΠΌ остатком Π² Π°ΠΊΡ†ΠΈΠΈ ${actionId}: ${lowStockProducts.length}`);
    
    // Π—Π΄Π΅ΡΡŒ ΠΌΠΎΠΆΠ½ΠΎ ΠΈΠ½Ρ‚Π΅Π³Ρ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ с систСмой пополнСния остатков
    // ΠΈΠ»ΠΈ Π²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎ ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ Ρ‚ΠΎΠ²Π°Ρ€Ρ‹ ΠΈΠ· Π°ΠΊΡ†ΠΈΠΈ
  }
}

ΠžΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΡ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ заявок

// Умная систСма принятия Ρ€Π΅ΡˆΠ΅Π½ΠΈΠΉ ΠΏΠΎ заявкам Π½Π° скидки
class DiscountDecisionEngine {
  private readonly rules = [
    {
      condition: (task: any) => task.discount_percentage <= 10 && task.customer_count >= 3,
      action: "APPROVE",
      priority: 1
    },
    {
      condition: (task: any) => task.discount_percentage <= 20 && task.customer_count >= 10,
      action: "APPROVE", 
      priority: 2
    },
    {
      condition: (task: any) => task.discount_percentage > 30,
      action: "DECLINE",
      priority: 1,
      reason: "Блишком большая скидка"
    }
  ];

  processTask(task: any): { action: string; reason?: string } {
    for (const rule of this.rules.sort((a, b) => a.priority - b.priority)) {
      if (rule.condition(task)) {
        return {
          action: rule.action,
          reason: rule.reason
        };
      }
    }
    return { action: "MANUAL_REVIEW" };
  }
}

Π˜Π½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΠΎΠ½Π½Ρ‹Π΅ Π·Π°ΠΌΠ΅Ρ‚ΠΊΠΈ

  • Rate Limiting: API ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ Π΄ΠΎ 500 запросов Π² ΠΌΠΈΠ½ΡƒΡ‚Ρƒ для Π°ΠΊΡ†ΠΈΠΉ
  • Batch Operations: РСкомСндуСтся ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ Ρ‚ΠΎΠ²Π°Ρ€Ρ‹ ΠΈ заявки ΠΏΠ°ΠΊΠ΅Ρ‚Π°ΠΌΠΈ
  • Real-time Updates: Бтатусы Π°ΠΊΡ†ΠΈΠΉ ΠΎΠ±Π½ΠΎΠ²Π»ΡΡŽΡ‚ΡΡ Π² Ρ€Π΅ΠΆΠΈΠΌΠ΅ Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠ³ΠΎ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ
  • Stock Synchronization: ΠžΡΡ‚Π°Ρ‚ΠΊΠΈ Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² Π² акциях Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ с основными остатками
  • Price Validation: Π¦Π΅Π½Ρ‹ Π² акциях ΠΏΡ€ΠΎΠ²Π΅Ρ€ΡΡŽΡ‚ΡΡ Π½Π° соотвСтствиС ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΌ трСбованиям
  • Action Limits: Π‘ΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‚ Π»ΠΈΠΌΠΈΡ‚Ρ‹ Π½Π° количСство Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² Π² ΠΎΠ΄Π½ΠΎΠΉ Π°ΠΊΡ†ΠΈΠΈ
  • Geographic Restrictions: НСкоторыС Π°ΠΊΡ†ΠΈΠΈ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ доступны Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π² ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹Ρ… Ρ€Π΅Π³ΠΈΠΎΠ½Π°Ρ