FBS & rFBS Marks API
FBS & rFBS Marks API
API для управления маркировкой товаров и кодами DataMatrix в схемах FBS и rFBS в OZON Seller API.
Количество методов: 13 методов
Обзор
FBS & rFBS Marks API предоставляет полный набор инструментов для работы с маркировкой:
- 📝 Управление образцами маркировки товаров
- 📋 Загрузка и валидация кодов DataMatrix
- ✅ Проверка соответствия требованиям маркировки
- 📊 Мониторинг статуса загрузки и валидации
- 🔍 Получение информации об отправлениях, требующих маркировку
Основные возможности
🎯 Система маркировки товаров
Обязательная маркировка - российское законодательство требует маркировки определенных категорий товаров кодами DataMatrix для отслеживания в системе “Честный ЗНАК”.
📝 Образцы маркировки
- Загрузка PDF-образцов правильной маркировки
- Валидация качества и соответствия образцов
- Управление библиотекой образцов для каждого товара
🔢 Коды DataMatrix
- Загрузка кодов для конкретных отправлений
- Валидация кодов в системе “Честный ЗНАК”
- Контроль соответствия количества кодов товарам
⚠️ Требования соответствия
- Коды должны быть получены из системы “Честный ЗНАК”
- Обязательная валидация перед отправкой
- Соответствие образцам маркировки
- Контроль сроков загрузки кодов
Методы API
Управление образцами маркировки
createProductExemplar()
Назначение: Загрузить образец маркировки товара
interface FbsRfbsMarksProductExemplarCreateRequest {
product_id: number;
file: string; // Base64 encoded PDF content
file_name: string;
}
getProductExemplarInfo()
Назначение: Получить информацию о загруженном образце
getProductExemplarList()
Назначение: Получить список образцов маркировки товара
deleteProductExemplar()
Назначение: Удалить образец маркировки
getProductExemplarDeleteStatus()
Назначение: Получить статус удаления образца
validateProductExemplar()
Назначение: Валидировать образец маркировки
getProductExemplarValidateStatus()
Назначение: Получить статус валидации образца
Управление кодами маркировки
uploadPostingCodes()
Назначение: Загрузить коды маркировки для отправления
interface FbsRfbsMarksPostingCodesUploadRequest {
posting_number: string;
codes: Array<{
sku: string;
gtd: string; // DataMatrix код
quantity: number;
}>;
}
getPostingCodesUploadStatus()
Назначение: Получить статус загрузки кодов маркировки
validatePostingCodes()
Назначение: Проверить коды маркировки отправления
getPostingCodesValidateStatus()
Назначение: Получить статус проверки кодов маркировки
getPostingCodesInfo()
Назначение: Получить информацию о кодах маркировки отправления
Управление отправлениями
getPostingList()
Назначение: Получить список отправлений с обязательной маркировкой
interface FbsRfbsMarksPostingListRequest {
status?: 'awaiting_codes' | 'codes_uploaded' | 'validated';
date_from?: string;
date_to?: string;
limit?: number;
offset?: number;
}
Практические примеры
Базовое использование
import { OzonSellerAPI } from 'bmad-ozon-seller-api';
const api = new OzonSellerAPI({
clientId: 'your-client-id',
apiKey: 'your-api-key'
});
// Загрузить образец маркировки
const exemplarResult = await api.fbsRfbsMarks.createProductExemplar({
product_id: 123456,
file: 'base64EncodedPdfContent',
file_name: 'marking_exemplar.pdf'
});
// Получить отправления, требующие маркировку
const postingsWithMarking = await api.fbsRfbsMarks.getPostingList({
status: 'awaiting_codes',
date_from: '2024-01-01T00:00:00Z',
date_to: '2024-01-31T23:59:59Z',
limit: 100
});
// Загрузить коды маркировки
const codesResult = await api.fbsRfbsMarks.uploadPostingCodes({
posting_number: 'FBS-123456789',
codes: [
{ sku: 'SKU123', gtd: 'marking_code_1', quantity: 1 },
{ sku: 'SKU456', gtd: 'marking_code_2', quantity: 2 }
]
});
// Валидировать коды
const validationResult = await api.fbsRfbsMarks.validatePostingCodes({
posting_number: 'FBS-123456789'
});
Продвинутые сценарии
Менеджер маркировки товаров
class ProductMarkingManager {
constructor(private api: OzonSellerAPI) {}
async setupProductMarking(productId: number, exemplarFile: Buffer): Promise<void> {
console.log(`🏷️ Настройка маркировки для товара ${productId}`);
try {
// 1. Конвертируем файл в base64
const base64File = exemplarFile.toString('base64');
const fileName = `product_${productId}_exemplar.pdf`;
// 2. Загружаем образец маркировки
const uploadResult = await this.api.fbsRfbsMarks.createProductExemplar({
product_id: productId,
file: base64File,
file_name: fileName
});
console.log(`📤 Образец загружен, задача: ${uploadResult.task_id}`);
// 3. Ждем обработки загрузки
const uploadStatus = await this.waitForTaskCompletion(
uploadResult.task_id!,
(taskId) => this.api.fbsRfbsMarks.getProductExemplarInfo({ task_id: taskId })
);
if (uploadStatus.status !== 'completed') {
throw new Error(`Ошибка загрузки образца: ${uploadStatus.error_message}`);
}
console.log(`✅ Образец успешно загружен: ${uploadStatus.exemplar_id}`);
// 4. Валидируем образец
const validationTask = await this.api.fbsRfbsMarks.validateProductExemplar({
exemplar_id: uploadStatus.exemplar_id!
});
console.log(`🔍 Запущена валидация: ${validationTask.task_id}`);
// 5. Ждем результатов валидации
const validationStatus = await this.waitForTaskCompletion(
validationTask.task_id!,
(taskId) => this.api.fbsRfbsMarks.getProductExemplarValidateStatus({ task_id: taskId })
);
// 6. Анализируем результаты валидации
if (validationStatus.is_valid) {
console.log('✅ Образец прошел валидацию');
this.logValidationDetails(validationStatus.validation_details);
} else {
console.log('❌ Образец не прошел валидацию');
this.logValidationErrors(validationStatus.validation_errors);
throw new Error('Образец маркировки не соответствует требованиям');
}
} catch (error) {
console.error('❌ Ошибка настройки маркировки:', error);
throw error;
}
}
async getProductExemplars(productId: number): Promise<any[]> {
const exemplars = await this.api.fbsRfbsMarks.getProductExemplarList({
product_id: productId,
limit: 50
});
console.log(`📚 Найдено образцов для товара ${productId}: ${exemplars.total}`);
return exemplars.exemplars?.map(exemplar => ({
id: exemplar.exemplar_id,
filename: exemplar.file_name,
status: exemplar.status,
created_at: exemplar.created_at,
is_valid: exemplar.validation_result?.is_valid
})) || [];
}
async cleanupOldExemplars(productId: number): Promise<void> {
const exemplars = await this.getProductExemplars(productId);
const oldExemplars = exemplars.filter(exemplar =>
exemplar.status === 'invalid' ||
(exemplar.created_at && new Date(exemplar.created_at) < new Date(Date.now() - 30 * 24 * 60 * 60 * 1000))
);
console.log(`🧹 Удаление ${oldExemplars.length} устаревших образцов`);
for (const exemplar of oldExemplars) {
try {
const deleteResult = await this.api.fbsRfbsMarks.deleteProductExemplar({
exemplar_id: exemplar.id
});
const deleteStatus = await this.waitForTaskCompletion(
deleteResult.task_id!,
(taskId) => this.api.fbsRfbsMarks.getProductExemplarDeleteStatus({ task_id: taskId })
);
if (deleteStatus.success) {
console.log(`✅ Удален образец: ${exemplar.filename}`);
} else {
console.log(`❌ Ошибка удаления: ${exemplar.filename}`);
}
} catch (error) {
console.log(`❌ Ошибка удаления образца ${exemplar.id}:`, error.message);
}
}
}
private logValidationDetails(details: any): void {
if (!details) return;
console.log('📋 Детали валидации:');
if (details.quality_valid !== undefined) {
console.log(` Качество изображения: ${details.quality_valid ? '✅' : '❌'}`);
}
if (details.format_valid !== undefined) {
console.log(` Формат файла: ${details.format_valid ? '✅' : '❌'}`);
}
if (details.content_valid !== undefined) {
console.log(` Содержимое маркировки: ${details.content_valid ? '✅' : '❌'}`);
}
}
private logValidationErrors(errors: any[]): void {
if (!errors || errors.length === 0) return;
console.log('❌ Ошибки валидации:');
errors.forEach((error, index) => {
console.log(` ${index + 1}. ${error.code}: ${error.message}`);
});
}
private async waitForTaskCompletion(taskId: string, statusChecker: (id: string) => Promise<any>): Promise<any> {
const maxAttempts = 30;
const delay = 2000;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const status = await statusChecker(taskId);
if (status.status === 'completed' || status.is_valid !== undefined) {
return status;
} else if (status.status === 'error') {
throw new Error(status.error_message || 'Ошибка выполнения задачи');
}
console.log(`⏳ Ожидание завершения... (${attempt + 1}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error('Превышено время ожидания выполнения задачи');
}
}
Автоматизация загрузки кодов маркировки
class MarkingCodesProcessor {
constructor(private api: OzonSellerAPI) {}
async processMarkingCodes(): Promise<void> {
console.log('🔢 Автоматическая обработка кодов маркировки');
// 1. Получаем отправления, ожидающие коды
const pendingPostings = await this.api.fbsRfbsMarks.getPostingList({
status: 'awaiting_codes',
limit: 100
});
if (!pendingPostings.postings || pendingPostings.postings.length === 0) {
console.log('✅ Нет отправлений, ожидающих коды маркировки');
return;
}
console.log(`📦 Найдено ${pendingPostings.total} отправлений, требующих коды`);
// 2. Обрабатываем каждое отправление
for (const posting of pendingPostings.postings) {
await this.processPostingCodes(posting);
// Пауза между отправлениями
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('🎉 Обработка кодов маркировки завершена');
}
private async processPostingCodes(posting: any): Promise<void> {
console.log(`\n📋 Обработка отправления: ${posting.posting_number}`);
try {
// 1. Получаем детальную информацию о требованиях к кодам
const codesInfo = await this.api.fbsRfbsMarks.getPostingCodesInfo({
posting_number: posting.posting_number
});
if (!codesInfo.marking_required) {
console.log('ℹ️ Маркировка не требуется');
return;
}
console.log(`🏷️ Требуется кодов: ${codesInfo.total_codes_required || 'неизвестно'}`);
// 2. Получаем коды из внешней системы (например, "Честный ЗНАК")
const markingCodes = await this.getMarkingCodesFromExternalSystem(codesInfo.products);
if (markingCodes.length === 0) {
console.log('⚠️ Не удалось получить коды маркировки');
return;
}
// 3. Загружаем коды
const uploadResult = await this.api.fbsRfbsMarks.uploadPostingCodes({
posting_number: posting.posting_number,
codes: markingCodes
});
console.log(`📤 Коды загружены, задача: ${uploadResult.task_id}`);
// 4. Ждем результатов загрузки
const uploadStatus = await this.waitForUploadCompletion(uploadResult.task_id!);
// 5. Анализируем результаты загрузки
if (uploadStatus.upload_result?.valid_codes > 0) {
console.log(`✅ Валидных кодов: ${uploadStatus.upload_result.valid_codes}`);
if (uploadStatus.upload_result.invalid_codes > 0) {
console.log(`⚠️ Невалидных кодов: ${uploadStatus.upload_result.invalid_codes}`);
this.logInvalidCodes(uploadStatus.upload_result.invalid_codes_details);
}
// 6. Запускаем валидацию загруженных кодов
await this.validatePostingCodes(posting.posting_number);
} else {
console.log('❌ Все коды невалидны');
this.logInvalidCodes(uploadStatus.upload_result?.invalid_codes_details);
}
} catch (error) {
console.error(`❌ Ошибка обработки ${posting.posting_number}:`, error.message);
}
}
private async validatePostingCodes(postingNumber: string): Promise<void> {
console.log('🔍 Запуск валидации кодов');
try {
const validationResult = await this.api.fbsRfbsMarks.validatePostingCodes({
posting_number: postingNumber
});
const validationStatus = await this.waitForValidationCompletion(validationResult.task_id!);
if (validationStatus.validation_result?.all_valid) {
console.log('✅ Все коды прошли валидацию');
} else {
const validPercentage = validationStatus.validation_result?.valid_percentage || 0;
console.log(`⚠️ Валидность кодов: ${validPercentage}%`);
if (validPercentage < 100) {
this.logValidationIssues(validationStatus.validation_result?.issues);
}
}
} catch (error) {
console.error('❌ Ошибка валидации кодов:', error.message);
}
}
private async getMarkingCodesFromExternalSystem(products: any[]): Promise<any[]> {
// Эмуляция получения кодов из системы "Честный ЗНАК"
const codes = [];
for (const product of products || []) {
for (let i = 0; i < (product.required_codes_count || 1); i++) {
codes.push({
sku: product.sku,
gtd: this.generateMockDataMatrixCode(),
quantity: 1
});
}
}
return codes;
}
private generateMockDataMatrixCode(): string {
// Генерация mock кода DataMatrix для демонстрации
const prefix = '01'; // GTIN prefix
const gtin = '12345678901234'; // 14-digit GTIN
const serialPrefix = '21'; // Serial number prefix
const serial = Math.random().toString(36).substring(2, 15).toUpperCase();
return `${prefix}${gtin}${serialPrefix}${serial}`;
}
private async waitForUploadCompletion(taskId: string): Promise<any> {
const maxAttempts = 20;
const delay = 1500;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const status = await this.api.fbsRfbsMarks.getPostingCodesUploadStatus({ task_id: taskId });
if (status.status === 'completed') {
return status;
} else if (status.status === 'error') {
throw new Error(status.error_message || 'Ошибка загрузки кодов');
}
console.log(`⏳ Загрузка кодов... (${attempt + 1}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error('Превышено время ожидания загрузки кодов');
}
private async waitForValidationCompletion(taskId: string): Promise<any> {
const maxAttempts = 15;
const delay = 2000;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const status = await this.api.fbsRfbsMarks.getPostingCodesValidateStatus({ task_id: taskId });
if (status.status === 'completed') {
return status;
} else if (status.status === 'error') {
throw new Error(status.error_message || 'Ошибка валидации кодов');
}
console.log(`⏳ Валидация кодов... (${attempt + 1}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error('Превышено время ожидания валидации кодов');
}
private logInvalidCodes(invalidCodesDetails: any[]): void {
if (!invalidCodesDetails || invalidCodesDetails.length === 0) return;
console.log('❌ Детали невалидных кодов:');
invalidCodesDetails.forEach((detail, index) => {
console.log(` ${index + 1}. SKU ${detail.sku}: ${detail.error_message}`);
console.log(` Код: ${detail.code}`);
});
}
private logValidationIssues(issues: any[]): void {
if (!issues || issues.length === 0) return;
console.log('⚠️ Проблемы валидации:');
issues.forEach((issue, index) => {
console.log(` ${index + 1}. ${issue.type}: ${issue.description}`);
if (issue.affected_codes) {
console.log(` Затронуто кодов: ${issue.affected_codes.length}`);
}
});
}
}
Система мониторинга маркировки
class MarkingMonitoringSystem {
constructor(private api: OzonSellerAPI) {}
async generateMarkingReport(dateFrom: string, dateTo: string): Promise<void> {
console.log(`📊 Отчет по маркировке (${dateFrom} - ${dateTo})`);
console.log('='.repeat(60));
// 1. Общая статистика по отправлениям
const allPostings = await this.api.fbsRfbsMarks.getPostingList({
date_from: dateFrom,
date_to: dateTo,
limit: 1000
});
// 2. Группировка по статусам
const statusStats = new Map();
let totalCodesRequired = 0;
let totalCodesUploaded = 0;
for (const posting of allPostings.postings || []) {
const status = posting.marking_status || 'unknown';
statusStats.set(status, (statusStats.get(status) || 0) + 1);
// Получаем детали для каждого отправления
try {
const codesInfo = await this.api.fbsRfbsMarks.getPostingCodesInfo({
posting_number: posting.posting_number
});
totalCodesRequired += codesInfo.total_codes_required || 0;
totalCodesUploaded += codesInfo.summary?.uploaded_codes || 0;
} catch (error) {
// Игнорируем ошибки для отдельных отправлений
}
// Пауза между запросами
await new Promise(resolve => setTimeout(resolve, 100));
}
// 3. Вывод статистики
console.log(`\n📦 Общая статистика:`);
console.log(`Всего отправлений с маркировкой: ${allPostings.total}`);
console.log(`Требуется кодов: ${totalCodesRequired}`);
console.log(`Загружено кодов: ${totalCodesUploaded}`);
if (totalCodesRequired > 0) {
const completionRate = (totalCodesUploaded / totalCodesRequired * 100).toFixed(1);
console.log(`Процент выполнения: ${completionRate}%`);
}
console.log(`\n📋 Распределение по статусам:`);
Array.from(statusStats.entries()).forEach(([status, count]) => {
const percentage = ((count / allPostings.total) * 100).toFixed(1);
console.log(` ${status}: ${count} (${percentage}%)`);
});
// 4. Проблемные отправления
const awaitingPostings = allPostings.postings?.filter(p =>
p.marking_status === 'awaiting_codes'
) || [];
if (awaitingPostings.length > 0) {
console.log(`\n⚠️ Отправления, ожидающие коды: ${awaitingPostings.length}`);
// Группировка по срокам
const urgentPostings = awaitingPostings.filter(p => {
if (!p.deadline) return false;
const deadline = new Date(p.deadline);
const now = new Date();
return deadline.getTime() - now.getTime() < 24 * 60 * 60 * 1000; // меньше 24 часов
});
if (urgentPostings.length > 0) {
console.log(`🚨 Срочных (меньше 24ч): ${urgentPostings.length}`);
urgentPostings.slice(0, 5).forEach(posting => {
console.log(` ${posting.posting_number} (дедлайн: ${posting.deadline})`);
});
}
}
// 5. Рекомендации
this.generateRecommendations(statusStats, totalCodesRequired, totalCodesUploaded);
}
async monitorMarkingQuality(): Promise<void> {
console.log('🔍 Мониторинг качества маркировки');
// Получаем недавно валидированные отправления
const recentPostings = await this.api.fbsRfbsMarks.getPostingList({
status: 'codes_uploaded',
date_from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
limit: 50
});
let totalValidationScore = 0;
let validatedCount = 0;
const qualityIssues = [];
for (const posting of recentPostings.postings || []) {
try {
const codesInfo = await this.api.fbsRfbsMarks.getPostingCodesInfo({
posting_number: posting.posting_number
});
if (codesInfo.validation_result) {
const validPercentage = codesInfo.validation_result.valid_percentage || 0;
totalValidationScore += validPercentage;
validatedCount++;
if (validPercentage < 100) {
qualityIssues.push({
posting_number: posting.posting_number,
valid_percentage: validPercentage,
issues: codesInfo.validation_result.issues || []
});
}
}
} catch (error) {
console.log(`Ошибка проверки ${posting.posting_number}:`, error.message);
}
}
// Анализ качества
if (validatedCount > 0) {
const avgQuality = (totalValidationScore / validatedCount).toFixed(1);
console.log(`📊 Средний процент валидности кодов: ${avgQuality}%`);
if (qualityIssues.length > 0) {
console.log(`\n⚠️ Отправления с проблемами качества: ${qualityIssues.length}`);
// Группировка проблем по типам
const issueTypes = new Map();
qualityIssues.forEach(issue => {
issue.issues.forEach(problemType => {
const type = problemType.type || 'unknown';
issueTypes.set(type, (issueTypes.get(type) || 0) + 1);
});
});
console.log('\n📋 Типы проблем:');
Array.from(issueTypes.entries()).forEach(([type, count]) => {
console.log(` ${type}: ${count} случаев`);
});
}
}
}
private generateRecommendations(
statusStats: Map<string, number>,
totalRequired: number,
totalUploaded: number
): void {
console.log('\n💡 Рекомендации:');
const completionRate = totalRequired > 0 ? (totalUploaded / totalRequired) : 0;
const awaitingCount = statusStats.get('awaiting_codes') || 0;
if (completionRate < 0.8) {
console.log('📈 Низкий процент загрузки кодов - автоматизируйте процесс');
}
if (awaitingCount > 10) {
console.log('⚠️ Много отправлений ожидает коды - проверьте интеграцию с "Честный ЗНАК"');
}
if (completionRate > 0.95) {
console.log('✅ Отличная работа с маркировкой!');
}
console.log('🔄 Настройте автоматическое получение кодов из системы "Честный ЗНАК"');
console.log('📱 Используйте уведомления для контроля дедлайнов');
console.log('📊 Регулярно анализируйте качество загружаемых кодов');
}
}
Обработка ошибок
try {
await api.fbsRfbsMarks.uploadPostingCodes({
posting_number: 'FBS-123456789',
codes: [/* ... */]
});
} catch (error) {
if (error.response?.status === 400) {
console.error('Ошибка валидации кодов:', error.response.data);
} else if (error.response?.status === 404) {
console.error('Отправление не найдено или не требует маркировки');
} else if (error.response?.status === 409) {
console.error('Коды уже загружены для этого отправления');
} else {
console.error('Неожиданная ошибка:', error.message);
}
}
Рекомендации по использованию
🎯 Подготовка образцов
- Используйте высококачественные PDF-файлы для образцов
- Убедитесь, что маркировка четко видна и читаема
- Валидируйте все загруженные образцы
- Регулярно обновляйте устаревшие образцы
🔢 Работа с кодами DataMatrix
- Получайте коды только из официальной системы “Честный ЗНАК”
- Проверяйте соответствие количества кодов товарам
- Загружайте коды заранее, не дожидаясь дедлайнов
- Всегда валидируйте коды после загрузки
📊 Мониторинг и контроль
- Отслеживайте дедлайны загрузки кодов
- Мониторьте процент валидности кодов
- Анализируйте причины невалидных кодов
- Настройте уведомления о проблемах
🚀 Автоматизация процессов
- Автоматизируйте получение кодов из “Честный ЗНАК”
- Настройте автоматическую загрузку для регулярных товаров
- Используйте batch-обработку для массовых операций
- Интегрируйте с системами управления складом
🔒 Соответствие требованиям
- Следуйте требованиям российского законодательства
- Поддерживайте актуальность форматов маркировки
- Ведите учет всех операций с кодами маркировки
- Обеспечивайте целостность данных маркировки
FBS & rFBS Marks API обеспечивает полное соответствие требованиям российского законодательства по маркировке товаров, автоматизируя процессы от загрузки образцов до валидации кодов DataMatrix.