Promos API
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: ΠΠ΅ΠΊΠΎΡΠΎΡΡΠ΅ Π°ΠΊΡΠΈΠΈ ΠΌΠΎΠ³ΡΡ Π±ΡΡΡ Π΄ΠΎΡΡΡΠΏΠ½Ρ ΡΠΎΠ»ΡΠΊΠΎ Π² ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΡΡ ΡΠ΅Π³ΠΈΠΎΠ½Π°Ρ