Supplier API
Supplier API
API для интеграции с поставщиками и управления счетами-фактурами.
Обзор
Supplier API предоставляет инструменты для работы с поставщиками и документооборотом. API позволяет загружать файлы счетов-фактур, создавать и обновлять документы, а также получать информацию о статусе обработки документов.
Ключевые возможности:
- Загрузка файлов счетов-фактур в различных форматах
- Создание и обновление счетов-фактур
- Получение детальной информации о документах
- Управление статусами документов
- Интеграция с системой налогового учета
Методы API
uploadInvoiceFile()
Загрузить файл счета-фактуры в систему.
import { OzonSellerAPI } from 'bmad-ozon-seller-api';
const api = new OzonSellerAPI({
clientId: 'your-client-id',
apiKey: 'your-api-key'
});
// Загрузка PDF файла счета-фактуры
const fileResult = await api.supplier.uploadInvoiceFile({
file: 'JVBERi0xLjQKJcfsj6IKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwov...', // Base64 PDF
file_name: 'invoice_2024_001.pdf',
document_type: 'invoice'
});
console.log('Загружен файл с ID:', fileResult.file_id);
console.log('Статус загрузки:', fileResult.status);
console.log('URL файла:', fileResult.file_url);
// Загрузка изображения счета
const imageResult = await api.supplier.uploadInvoiceFile({
file: '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJ...', // Base64 JPEG
file_name: 'invoice_scan_001.jpg',
document_type: 'invoice'
});
// Функция для загрузки файла из файловой системы
async function uploadInvoiceFromFile(filePath: string, fileName: string) {
const fs = require('fs');
try {
// Читаем файл и конвертируем в Base64
const fileBuffer = fs.readFileSync(filePath);
const base64File = fileBuffer.toString('base64');
const result = await api.supplier.uploadInvoiceFile({
file: base64File,
file_name: fileName,
document_type: 'invoice'
});
return result;
} catch (error) {
console.error('Ошибка при загрузке файла:', error);
throw error;
}
}
// Использование функции загрузки
const uploadResult = await uploadInvoiceFromFile('./invoices/invoice_001.pdf', 'invoice_001.pdf');
console.log('Файл загружен, ID:', uploadResult.file_id);
createOrUpdateInvoice()
Создать новый или обновить существующий счет-фактуру.
// Создание нового счета-фактуры
const newInvoice = await api.supplier.createOrUpdateInvoice({
invoice_number: 'INV-2024-001',
invoice_date: '2024-01-15',
file_id: 'uploaded_file_id_123',
supplier_info: {
name: 'ООО "Поставщик"',
inn: '7712345678',
kpp: '771234567',
address: 'г. Москва, ул. Примерная, д. 1'
},
buyer_info: {
name: 'ООО "Покупатель"',
inn: '9876543210',
address: 'г. Санкт-Петербург, пр. Покупательский, д. 2'
},
total_amount: 118000.00,
currency: 'RUB',
vat_amount: 18000.00,
vat_rate: 20,
items: [
{
sku: 'PROD-001',
name: 'Товар 1',
quantity: 100,
unit_price: 1000.00,
total_price: 100000.00,
vat_rate: 20,
vat_amount: 15000.00
}
],
additional_info: {
payment_terms: '30 дней',
delivery_terms: 'EXW Москва',
contract_number: 'CONTRACT-2024-001'
}
});
console.log('Создан счет с ID:', newInvoice.invoice?.invoice_id);
console.log('Статус:', newInvoice.invoice?.status);
// Обновление существующего счета
const updatedInvoice = await api.supplier.createOrUpdateInvoice({
invoice_id: 'existing_invoice_id_456',
invoice_number: 'INV-2024-001-CORRECTED',
total_amount: 120000.00,
vat_amount: 18000.00,
items: [
{
sku: 'PROD-001',
name: 'Товар 1 (исправленный)',
quantity: 100,
unit_price: 1020.00,
total_price: 102000.00,
vat_rate: 20,
vat_amount: 15300.00
}
],
correction_info: {
original_invoice_number: 'INV-2024-001',
correction_reason: 'Корректировка цены товара'
}
});
console.log('Обновлен счет:', updatedInvoice.invoice?.invoice_number);
getInvoice()
Получить информацию о счете-фактуре.
// Получение информации о счете
const invoiceInfo = await api.supplier.getInvoice({
invoice_id: 'invoice_123'
});
const invoice = invoiceInfo.invoice;
if (invoice) {
console.log('Номер счета:', invoice.invoice_number);
console.log('Дата:', invoice.invoice_date);
console.log('Статус:', invoice.status);
console.log('Общая сумма:', invoice.total_amount, invoice.currency);
console.log('НДС:', invoice.vat_amount);
// Информация о поставщике
if (invoice.supplier_info) {
console.log('Поставщик:', invoice.supplier_info.name);
console.log('ИНН:', invoice.supplier_info.inn);
console.log('КПП:', invoice.supplier_info.kpp);
}
// Информация о покупателе
if (invoice.buyer_info) {
console.log('Покупатель:', invoice.buyer_info.name);
console.log('ИНН покупателя:', invoice.buyer_info.inn);
}
// Детали по товарам
console.log('\nТовары:');
invoice.items?.forEach((item, index) => {
console.log(`${index + 1}. ${item.name} (${item.sku})`);
console.log(` Количество: ${item.quantity}`);
console.log(` Цена за единицу: ${item.unit_price} ${invoice.currency}`);
console.log(` Общая стоимость: ${item.total_price} ${invoice.currency}`);
console.log(` НДС: ${item.vat_rate}% (${item.vat_amount})`);
});
// Обработка различных статусов
switch (invoice.status) {
case 'pending':
console.log('📝 Счет находится на рассмотрении');
break;
case 'approved':
console.log('✅ Счет одобрен');
break;
case 'rejected':
console.log('❌ Счет отклонен');
if (invoice.rejection_reason) {
console.log('Причина отклонения:', invoice.rejection_reason);
}
break;
case 'processing':
console.log('⏳ Счет обрабатывается');
break;
case 'paid':
console.log('💰 Счет оплачен');
break;
}
// Дополнительная информация
if (invoice.additional_info) {
console.log('\nДополнительная информация:');
console.log('Условия оплаты:', invoice.additional_info.payment_terms);
console.log('Условия поставки:', invoice.additional_info.delivery_terms);
console.log('Номер договора:', invoice.additional_info.contract_number);
}
// История изменений
if (invoice.history && invoice.history.length > 0) {
console.log('\nИстория изменений:');
invoice.history.forEach(historyItem => {
console.log(`${historyItem.date}: ${historyItem.action} - ${historyItem.comment}`);
});
}
}
deleteInvoice()
Удалить ссылку на счет-фактуру.
// Удаление счета-фактуры
const deleteResult = await api.supplier.deleteInvoice({
invoice_id: 'invoice_to_delete_123'
});
if (deleteResult.result === 'success') {
console.log('✅ Счет успешно удален');
} else {
console.log('❌ Ошибка при удалении:', deleteResult.message);
}
// Функция для безопасного удаления с подтверждением
async function safeDeleteInvoice(invoiceId: string): Promise<boolean> {
try {
// Сначала получаем информацию о счете
const invoiceInfo = await api.supplier.getInvoice({ invoice_id: invoiceId });
if (!invoiceInfo.invoice) {
console.log('Счет не найден');
return false;
}
console.log(`Удаление счета: ${invoiceInfo.invoice.invoice_number}`);
console.log(`Сумма: ${invoiceInfo.invoice.total_amount} ${invoiceInfo.invoice.currency}`);
console.log(`Статус: ${invoiceInfo.invoice.status}`);
// Проверяем, можно ли удалить
if (invoiceInfo.invoice.status === 'paid') {
console.log('⚠️ Внимание: счет уже оплачен!');
// В реальном приложении здесь может быть запрос подтверждения
}
// Выполняем удаление
const result = await api.supplier.deleteInvoice({ invoice_id: invoiceId });
return result.result === 'success';
} catch (error) {
console.error('Ошибка при удалении счета:', error);
return false;
}
}
// Использование безопасного удаления
const wasDeleted = await safeDeleteInvoice('invoice_123');
console.log('Результат удаления:', wasDeleted ? 'успешно' : 'неудачно');
TypeScript Interfaces
Request Types
interface SupplierInvoiceFileUploadRequest {
/** Файл в формате Base64 */
file: string;
/** Имя файла */
file_name: string;
/** Тип документа */
document_type: 'invoice' | 'act' | 'contract' | 'other';
/** Описание файла (опционально) */
description?: string;
}
interface SupplierInvoiceCreateOrUpdateRequest {
/** ID счета для обновления (опционально для создания) */
invoice_id?: string;
/** Номер счета-фактуры */
invoice_number: string;
/** Дата счета в формате YYYY-MM-DD */
invoice_date: string;
/** ID загруженного файла */
file_id?: string;
/** Информация о поставщике */
supplier_info?: SupplierInfo;
/** Информация о покупателе */
buyer_info?: BuyerInfo;
/** Общая сумма */
total_amount: number;
/** Валюта */
currency: 'RUB' | 'USD' | 'EUR';
/** Сумма НДС */
vat_amount: number;
/** Ставка НДС */
vat_rate: number;
/** Позиции счета */
items: InvoiceItem[];
/** Дополнительная информация */
additional_info?: AdditionalInfo;
/** Информация о корректировке */
correction_info?: CorrectionInfo;
}
interface SupplierInfo {
/** Наименование поставщика */
name: string;
/** ИНН */
inn: string;
/** КПП */
kpp?: string;
/** Адрес */
address: string;
/** Банковские реквизиты */
bank_details?: BankDetails;
}
interface BuyerInfo {
/** Наименование покупателя */
name: string;
/** ИНН */
inn: string;
/** КПП */
kpp?: string;
/** Адрес */
address: string;
}
interface InvoiceItem {
/** Артикул товара */
sku: string;
/** Наименование товара */
name: string;
/** Количество */
quantity: number;
/** Единица измерения */
unit?: string;
/** Цена за единицу */
unit_price: number;
/** Общая стоимость */
total_price: number;
/** Ставка НДС */
vat_rate: number;
/** Сумма НДС */
vat_amount: number;
/** Код товара */
product_code?: string;
}
interface AdditionalInfo {
/** Условия оплаты */
payment_terms?: string;
/** Условия поставки */
delivery_terms?: string;
/** Номер договора */
contract_number?: string;
/** Номер заказа */
order_number?: string;
/** Дополнительные примечания */
notes?: string;
}
interface CorrectionInfo {
/** Номер оригинального счета */
original_invoice_number: string;
/** Причина корректировки */
correction_reason: string;
/** Дата корректировки */
correction_date?: string;
}
interface BankDetails {
/** Наименование банка */
bank_name: string;
/** БИК */
bik: string;
/** Корреспондентский счет */
correspondent_account: string;
/** Расчетный счет */
account_number: string;
}
interface SupplierInvoiceGetRequest {
/** ID счета */
invoice_id: string;
}
interface SupplierInvoiceDeleteRequest {
/** ID счета для удаления */
invoice_id: string;
/** Причина удаления */
deletion_reason?: string;
}
Response Types
interface SupplierInvoiceFileUploadResponse {
/** ID загруженного файла */
file_id: string;
/** Статус загрузки */
status: 'uploaded' | 'processing' | 'error';
/** URL файла */
file_url?: string;
/** Размер файла в байтах */
file_size?: number;
/** MIME тип файла */
mime_type?: string;
/** Сообщение об ошибке (если есть) */
error_message?: string;
}
interface SupplierInvoiceCreateOrUpdateResponse {
/** Информация о счете */
invoice?: InvoiceDetails;
/** Результат операции */
result: 'success' | 'error';
/** Сообщение о результате */
message?: string;
}
interface InvoiceDetails {
/** ID счета */
invoice_id: string;
/** Номер счета */
invoice_number: string;
/** Дата счета */
invoice_date: string;
/** Статус счета */
status: 'pending' | 'approved' | 'rejected' | 'processing' | 'paid';
/** Причина отклонения (если отклонен) */
rejection_reason?: string;
/** Информация о поставщике */
supplier_info?: SupplierInfo;
/** Информация о покупателе */
buyer_info?: BuyerInfo;
/** Общая сумма */
total_amount: number;
/** Валюта */
currency: string;
/** Сумма НДС */
vat_amount: number;
/** Ставка НДС */
vat_rate: number;
/** Позиции счета */
items?: InvoiceItem[];
/** Дополнительная информация */
additional_info?: AdditionalInfo;
/** История изменений */
history?: InvoiceHistoryItem[];
/** Дата создания */
created_at: string;
/** Дата последнего обновления */
updated_at: string;
}
interface InvoiceHistoryItem {
/** Дата изменения */
date: string;
/** Тип действия */
action: 'created' | 'updated' | 'approved' | 'rejected' | 'paid';
/** Комментарий */
comment?: string;
/** Пользователь, внесший изменение */
user?: string;
}
interface SupplierInvoiceGetResponse {
/** Информация о счете */
invoice?: InvoiceDetails;
}
interface SupplierInvoiceDeleteResponse {
/** Результат операции */
result: 'success' | 'error';
/** Сообщение о результате */
message?: string;
}
Примеры использования
Управление счетами-фактурами
class InvoiceManager {
constructor(private api: OzonSellerAPI) {}
async createInvoiceFromOrder(orderData: OrderData): Promise<string | null> {
try {
// Шаг 1: Подготавливаем данные счета на основе заказа
const invoiceData = this.prepareInvoiceData(orderData);
// Шаг 2: Загружаем файл счета (если есть)
let fileId: string | undefined;
if (orderData.invoiceFilePath) {
const uploadResult = await this.uploadInvoiceFile(orderData.invoiceFilePath);
if (uploadResult.status === 'uploaded') {
fileId = uploadResult.file_id;
}
}
// Шаг 3: Создаем счет
const createResult = await this.api.supplier.createOrUpdateInvoice({
...invoiceData,
file_id: fileId
});
if (createResult.result === 'success' && createResult.invoice) {
console.log(`Создан счет: ${createResult.invoice.invoice_number}`);
return createResult.invoice.invoice_id;
}
return null;
} catch (error) {
console.error('Ошибка при создании счета:', error);
return null;
}
}
private prepareInvoiceData(orderData: OrderData): SupplierInvoiceCreateOrUpdateRequest {
const invoiceNumber = `INV-${new Date().getFullYear()}-${orderData.orderNumber}`;
// Рассчитываем НДС
const vatRate = 20; // 20%
const totalWithoutVat = orderData.items.reduce((sum, item) =>
sum + (item.quantity * item.price), 0
);
const vatAmount = totalWithoutVat * (vatRate / 100);
const totalAmount = totalWithoutVat + vatAmount;
return {
invoice_number: invoiceNumber,
invoice_date: new Date().toISOString().split('T')[0],
supplier_info: {
name: 'ООО "Наша Компания"',
inn: '7712345678',
kpp: '771234567',
address: 'г. Москва, ул. Поставщиков, д. 1',
bank_details: {
bank_name: 'Сбербанк России',
bik: '044525225',
correspondent_account: '30101810400000000225',
account_number: '40702810400000012345'
}
},
buyer_info: {
name: orderData.buyerName,
inn: orderData.buyerInn,
address: orderData.buyerAddress
},
total_amount: totalAmount,
currency: 'RUB',
vat_amount: vatAmount,
vat_rate: vatRate,
items: orderData.items.map(item => ({
sku: item.sku,
name: item.name,
quantity: item.quantity,
unit: 'шт',
unit_price: item.price,
total_price: item.quantity * item.price,
vat_rate: vatRate,
vat_amount: (item.quantity * item.price) * (vatRate / 100)
})),
additional_info: {
payment_terms: '14 дней',
delivery_terms: 'EXW Москва',
contract_number: orderData.contractNumber,
order_number: orderData.orderNumber
}
};
}
private async uploadInvoiceFile(filePath: string): Promise<SupplierInvoiceFileUploadResponse> {
const fs = require('fs');
const path = require('path');
const fileBuffer = fs.readFileSync(filePath);
const base64File = fileBuffer.toString('base64');
const fileName = path.basename(filePath);
return this.api.supplier.uploadInvoiceFile({
file: base64File,
file_name: fileName,
document_type: 'invoice'
});
}
async getInvoiceStatus(invoiceId: string): Promise<string> {
try {
const result = await this.api.supplier.getInvoice({ invoice_id: invoiceId });
return result.invoice?.status || 'unknown';
} catch (error) {
console.error('Ошибка при получении статуса:', error);
return 'error';
}
}
async processInvoiceApproval(invoiceId: string): Promise<void> {
const invoice = await this.api.supplier.getInvoice({ invoice_id: invoiceId });
if (!invoice.invoice) {
throw new Error('Счет не найден');
}
switch (invoice.invoice.status) {
case 'approved':
console.log('✅ Счет одобрен и готов к оплате');
await this.sendApprovalNotification(invoice.invoice);
break;
case 'rejected':
console.log('❌ Счет отклонен:', invoice.invoice.rejection_reason);
await this.handleRejection(invoice.invoice);
break;
case 'pending':
console.log('⏳ Счет ожидает рассмотрения');
break;
case 'processing':
console.log('🔄 Счет обрабатывается');
break;
case 'paid':
console.log('💰 Счет оплачен');
await this.handlePayment(invoice.invoice);
break;
}
}
private async sendApprovalNotification(invoice: InvoiceDetails): Promise<void> {
// Отправка уведомления об одобрении
console.log(`Отправка уведомления об одобрении счета ${invoice.invoice_number}`);
}
private async handleRejection(invoice: InvoiceDetails): Promise<void> {
// Обработка отклонения счета
console.log(`Обработка отклонения счета ${invoice.invoice_number}`);
console.log(`Причина: ${invoice.rejection_reason}`);
// Здесь можно добавить логику для исправления и повторной отправки
}
private async handlePayment(invoice: InvoiceDetails): Promise<void> {
// Обработка оплаты
console.log(`Обработка оплаты счета ${invoice.invoice_number}`);
console.log(`Сумма: ${invoice.total_amount} ${invoice.currency}`);
}
}
interface OrderData {
orderNumber: string;
contractNumber: string;
buyerName: string;
buyerInn: string;
buyerAddress: string;
items: OrderItem[];
invoiceFilePath?: string;
}
interface OrderItem {
sku: string;
name: string;
quantity: number;
price: number;
}
// Использование менеджера счетов
const invoiceManager = new InvoiceManager(api);
// Пример создания счета из заказа
const orderData: OrderData = {
orderNumber: 'ORD-2024-001',
contractNumber: 'CONTRACT-2024-001',
buyerName: 'ООО "Покупатель"',
buyerInn: '9876543210',
buyerAddress: 'г. СПб, ул. Покупательская, д. 1',
items: [
{
sku: 'PROD-001',
name: 'Товар 1',
quantity: 10,
price: 1000.00
},
{
sku: 'PROD-002',
name: 'Товар 2',
quantity: 5,
price: 2000.00
}
],
invoiceFilePath: './documents/invoice_template.pdf'
};
const invoiceId = await invoiceManager.createInvoiceFromOrder(orderData);
if (invoiceId) {
console.log('Счет создан с ID:', invoiceId);
// Проверяем статус через некоторое время
setTimeout(async () => {
await invoiceManager.processInvoiceApproval(invoiceId);
}, 5000);
}
Массовая обработка счетов
class BulkInvoiceProcessor {
constructor(private api: OzonSellerAPI) {}
async processBulkInvoices(invoicesData: BulkInvoiceData[]): Promise<ProcessingResult[]> {
const results: ProcessingResult[] = [];
console.log(`Начинаем обработку ${invoicesData.length} счетов...`);
for (const [index, invoiceData] of invoicesData.entries()) {
console.log(`Обработка счета ${index + 1}/${invoicesData.length}: ${invoiceData.invoice_number}`);
try {
const result = await this.processInvoice(invoiceData);
results.push(result);
// Задержка для соблюдения лимитов API
await this.delay(1000);
} catch (error) {
results.push({
invoice_number: invoiceData.invoice_number,
status: 'error',
error: error.message
});
console.error(`Ошибка при обработке ${invoiceData.invoice_number}:`, error);
}
}
this.generateProcessingReport(results);
return results;
}
private async processInvoice(invoiceData: BulkInvoiceData): Promise<ProcessingResult> {
// Загрузка файла (если есть)
let fileId: string | undefined;
if (invoiceData.file_path) {
const uploadResult = await this.uploadFile(invoiceData.file_path, invoiceData.invoice_number);
if (uploadResult.status === 'uploaded') {
fileId = uploadResult.file_id;
}
}
// Создание счета
const createResult = await this.api.supplier.createOrUpdateInvoice({
invoice_number: invoiceData.invoice_number,
invoice_date: invoiceData.invoice_date,
file_id: fileId,
supplier_info: invoiceData.supplier_info,
buyer_info: invoiceData.buyer_info,
total_amount: invoiceData.total_amount,
currency: invoiceData.currency,
vat_amount: invoiceData.vat_amount,
vat_rate: invoiceData.vat_rate,
items: invoiceData.items
});
return {
invoice_number: invoiceData.invoice_number,
status: createResult.result === 'success' ? 'created' : 'error',
invoice_id: createResult.invoice?.invoice_id,
error: createResult.message
};
}
private async uploadFile(filePath: string, invoiceNumber: string): Promise<SupplierInvoiceFileUploadResponse> {
const fs = require('fs');
const path = require('path');
try {
const fileBuffer = fs.readFileSync(filePath);
const base64File = fileBuffer.toString('base64');
const fileName = `${invoiceNumber}_${path.basename(filePath)}`;
return await this.api.supplier.uploadInvoiceFile({
file: base64File,
file_name: fileName,
document_type: 'invoice'
});
} catch (error) {
throw new Error(`Не удалось загрузить файл ${filePath}: ${error.message}`);
}
}
private generateProcessingReport(results: ProcessingResult[]): void {
const successful = results.filter(r => r.status === 'created').length;
const failed = results.filter(r => r.status === 'error').length;
console.log('\n📊 ОТЧЕТ О МАССОВОЙ ОБРАБОТКЕ:');
console.log(`Всего обработано: ${results.length}`);
console.log(`Успешно создано: ${successful}`);
console.log(`Ошибок: ${failed}`);
if (failed > 0) {
console.log('\n❌ НЕУДАЧНЫЕ ОПЕРАЦИИ:');
results.filter(r => r.status === 'error').forEach(result => {
console.log(` - ${result.invoice_number}: ${result.error}`);
});
}
if (successful > 0) {
console.log('\n✅ УСПЕШНО СОЗДАННЫЕ СЧЕТА:');
results.filter(r => r.status === 'created').forEach(result => {
console.log(` - ${result.invoice_number} (ID: ${result.invoice_id})`);
});
}
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
async monitorInvoiceStatuses(invoiceIds: string[]): Promise<StatusReport[]> {
const statusReports: StatusReport[] = [];
console.log(`Проверка статусов ${invoiceIds.length} счетов...`);
for (const invoiceId of invoiceIds) {
try {
const result = await this.api.supplier.getInvoice({ invoice_id: invoiceId });
if (result.invoice) {
statusReports.push({
invoice_id: invoiceId,
invoice_number: result.invoice.invoice_number,
status: result.invoice.status,
created_at: result.invoice.created_at,
updated_at: result.invoice.updated_at
});
}
await this.delay(500);
} catch (error) {
console.error(`Ошибка при получении статуса ${invoiceId}:`, error);
}
}
this.generateStatusReport(statusReports);
return statusReports;
}
private generateStatusReport(reports: StatusReport[]): void {
const statusCounts = reports.reduce((acc, report) => {
acc[report.status] = (acc[report.status] || 0) + 1;
return acc;
}, {} as Record<string, number>);
console.log('\n📈 ОТЧЕТ О СТАТУСАХ:');
Object.entries(statusCounts).forEach(([status, count]) => {
const emoji = {
'pending': '⏳',
'approved': '✅',
'rejected': '❌',
'processing': '🔄',
'paid': '💰'
}[status] || '❓';
console.log(`${emoji} ${status}: ${count}`);
});
}
}
interface BulkInvoiceData {
invoice_number: string;
invoice_date: string;
file_path?: string;
supplier_info: SupplierInfo;
buyer_info: BuyerInfo;
total_amount: number;
currency: 'RUB' | 'USD' | 'EUR';
vat_amount: number;
vat_rate: number;
items: InvoiceItem[];
}
interface ProcessingResult {
invoice_number: string;
status: 'created' | 'error';
invoice_id?: string;
error?: string;
}
interface StatusReport {
invoice_id: string;
invoice_number: string;
status: string;
created_at: string;
updated_at: string;
}
// Использование массовой обработки
const bulkProcessor = new BulkInvoiceProcessor(api);
// Пример массового создания счетов
const bulkData: BulkInvoiceData[] = [
{
invoice_number: 'BULK-001',
invoice_date: '2024-01-15',
file_path: './invoices/bulk_001.pdf',
supplier_info: {
name: 'ООО "Поставщик"',
inn: '7712345678',
address: 'г. Москва, ул. Поставщиков, д. 1'
},
buyer_info: {
name: 'ООО "Покупатель"',
inn: '9876543210',
address: 'г. СПб, ул. Покупательская, д. 1'
},
total_amount: 118000,
currency: 'RUB',
vat_amount: 18000,
vat_rate: 20,
items: [
{
sku: 'BULK-PROD-001',
name: 'Товар для массовой обработки',
quantity: 100,
unit_price: 1000,
total_price: 100000,
vat_rate: 20,
vat_amount: 15000
}
]
}
// ... другие счета
];
const processingResults = await bulkProcessor.processBulkInvoices(bulkData);
// Мониторинг созданных счетов
const createdInvoiceIds = processingResults
.filter(r => r.status === 'created' && r.invoice_id)
.map(r => r.invoice_id!);
if (createdInvoiceIds.length > 0) {
setTimeout(async () => {
await bulkProcessor.monitorInvoiceStatuses(createdInvoiceIds);
}, 30000); // Проверяем через 30 секунд
}
Комплексные сценарии
Интегрированная система документооборота
class DocumentManagementSystem {
private api: OzonSellerAPI;
private documentTemplates: Map<string, DocumentTemplate>;
private auditLog: AuditEntry[];
constructor(api: OzonSellerAPI) {
this.api = api;
this.documentTemplates = new Map();
this.auditLog = [];
this.initializeTemplates();
}
private initializeTemplates(): void {
// Инициализация шаблонов документов
this.documentTemplates.set('standard_invoice', {
name: 'Стандартный счет-фактура',
required_fields: ['supplier_info', 'buyer_info', 'items', 'total_amount'],
vat_included: true,
currency: 'RUB'
});
this.documentTemplates.set('export_invoice', {
name: 'Экспортный счет-фактура',
required_fields: ['supplier_info', 'buyer_info', 'items', 'total_amount'],
vat_included: false,
currency: 'USD'
});
}
async processDocumentWorkflow(request: DocumentWorkflowRequest): Promise<WorkflowResult> {
console.log(`🔄 Начало обработки документа: ${request.document_type}`);
const result: WorkflowResult = {
workflow_id: `WF-${Date.now()}`,
status: 'started',
steps: [],
created_at: new Date().toISOString()
};
try {
// Шаг 1: Валидация данных
const validationResult = await this.validateDocumentData(request);
result.steps.push({
step: 'validation',
status: validationResult.valid ? 'completed' : 'failed',
message: validationResult.message,
timestamp: new Date().toISOString()
});
if (!validationResult.valid) {
result.status = 'failed';
return result;
}
// Шаг 2: Генерация документа (если нужно)
if (request.generate_file) {
const generationResult = await this.generateDocument(request);
result.steps.push({
step: 'generation',
status: generationResult.success ? 'completed' : 'failed',
message: generationResult.message,
timestamp: new Date().toISOString()
});
if (generationResult.success && generationResult.file_path) {
request.file_path = generationResult.file_path;
}
}
// Шаг 3: Загрузка файла
if (request.file_path) {
const uploadResult = await this.uploadDocumentFile(request.file_path, request);
result.steps.push({
step: 'upload',
status: uploadResult.status === 'uploaded' ? 'completed' : 'failed',
message: `File uploaded with ID: ${uploadResult.file_id}`,
timestamp: new Date().toISOString()
});
request.file_id = uploadResult.file_id;
}
// Шаг 4: Создание записи в системе
const createResult = await this.createDocumentRecord(request);
result.steps.push({
step: 'creation',
status: createResult.result === 'success' ? 'completed' : 'failed',
message: createResult.message || 'Document created successfully',
timestamp: new Date().toISOString()
});
if (createResult.result === 'success') {
result.document_id = createResult.invoice?.invoice_id;
result.status = 'completed';
// Шаг 5: Отправка уведомлений
await this.sendNotifications(request, createResult.invoice!);
result.steps.push({
step: 'notification',
status: 'completed',
message: 'Notifications sent',
timestamp: new Date().toISOString()
});
} else {
result.status = 'failed';
}
// Логирование операции
this.auditLog.push({
workflow_id: result.workflow_id,
action: 'document_workflow',
user: request.user || 'system',
timestamp: new Date().toISOString(),
details: request
});
} catch (error) {
console.error('Ошибка в workflow:', error);
result.status = 'error';
result.error = error.message;
}
console.log(`✅ Завершение обработки документа: ${result.status}`);
return result;
}
private async validateDocumentData(request: DocumentWorkflowRequest): Promise<ValidationResult> {
const template = this.documentTemplates.get(request.template_type);
if (!template) {
return {
valid: false,
message: `Unknown template type: ${request.template_type}`
};
}
// Проверяем обязательные поля
for (const field of template.required_fields) {
if (!request.invoice_data[field]) {
return {
valid: false,
message: `Missing required field: ${field}`
};
}
}
// Валидация НДС
if (template.vat_included && !request.invoice_data.vat_amount) {
return {
valid: false,
message: 'VAT amount is required for this template'
};
}
// Валидация валюты
if (template.currency && request.invoice_data.currency !== template.currency) {
return {
valid: false,
message: `Currency must be ${template.currency} for this template`
};
}
return {
valid: true,
message: 'Validation passed'
};
}
private async generateDocument(request: DocumentWorkflowRequest): Promise<GenerationResult> {
// Имитация генерации PDF документа
console.log('📄 Генерация PDF документа...');
try {
// Здесь была бы реальная генерация PDF
const fileName = `generated_${request.invoice_data.invoice_number}.pdf`;
const filePath = `./temp/${fileName}`;
// Симуляция создания файла
const fs = require('fs');
fs.writeFileSync(filePath, 'PDF content placeholder');
return {
success: true,
file_path: filePath,
message: 'Document generated successfully'
};
} catch (error) {
return {
success: false,
message: `Generation failed: ${error.message}`
};
}
}
private async uploadDocumentFile(filePath: string, request: DocumentWorkflowRequest): Promise<SupplierInvoiceFileUploadResponse> {
const fs = require('fs');
const path = require('path');
const fileBuffer = fs.readFileSync(filePath);
const base64File = fileBuffer.toString('base64');
const fileName = path.basename(filePath);
return this.api.supplier.uploadInvoiceFile({
file: base64File,
file_name: fileName,
document_type: request.document_type,
description: `Generated for workflow ${request.workflow_id || 'unknown'}`
});
}
private async createDocumentRecord(request: DocumentWorkflowRequest): Promise<SupplierInvoiceCreateOrUpdateResponse> {
return this.api.supplier.createOrUpdateInvoice({
...request.invoice_data,
file_id: request.file_id
});
}
private async sendNotifications(request: DocumentWorkflowRequest, invoice: InvoiceDetails): Promise<void> {
console.log('📧 Отправка уведомлений...');
const notifications = [
{
type: 'email',
recipient: request.notification_email || 'default@company.com',
subject: `Создан документ: ${invoice.invoice_number}`,
body: `Счет-фактура ${invoice.invoice_number} успешно создан в системе.`
},
{
type: 'webhook',
url: request.webhook_url,
data: {
event: 'document_created',
invoice_id: invoice.invoice_id,
invoice_number: invoice.invoice_number
}
}
];
for (const notification of notifications) {
try {
if (notification.type === 'webhook' && notification.url) {
// Отправка webhook
console.log(`Webhook sent to: ${notification.url}`);
} else if (notification.type === 'email') {
// Отправка email
console.log(`Email sent to: ${notification.recipient}`);
}
} catch (error) {
console.error(`Failed to send ${notification.type} notification:`, error);
}
}
}
async getWorkflowStatus(workflowId: string): Promise<WorkflowResult | null> {
// В реальном приложении это был бы запрос к базе данных
const auditEntry = this.auditLog.find(entry => entry.workflow_id === workflowId);
if (!auditEntry) {
return null;
}
// Возвращаем статус workflow
return {
workflow_id: workflowId,
status: 'completed', // Получили бы из БД
steps: [], // Получили бы из БД
created_at: auditEntry.timestamp
};
}
async generateReport(dateFrom: string, dateTo: string): Promise<ActivityReport> {
const relevantEntries = this.auditLog.filter(entry =>
entry.timestamp >= dateFrom && entry.timestamp <= dateTo
);
return {
period: `${dateFrom} - ${dateTo}`,
total_workflows: relevantEntries.length,
successful_workflows: relevantEntries.filter(e => e.details.status === 'completed').length,
failed_workflows: relevantEntries.filter(e => e.details.status === 'failed').length,
document_types: this.getDocumentTypeStats(relevantEntries),
timeline: this.getTimelineStats(relevantEntries)
};
}
private getDocumentTypeStats(entries: AuditEntry[]): Record<string, number> {
return entries.reduce((acc, entry) => {
const docType = entry.details.document_type || 'unknown';
acc[docType] = (acc[docType] || 0) + 1;
return acc;
}, {} as Record<string, number>);
}
private getTimelineStats(entries: AuditEntry[]): TimelineEntry[] {
// Группировка по дням
const dailyStats = entries.reduce((acc, entry) => {
const date = entry.timestamp.split('T')[0];
if (!acc[date]) {
acc[date] = { date, count: 0, successful: 0 };
}
acc[date].count++;
if (entry.details.status === 'completed') {
acc[date].successful++;
}
return acc;
}, {} as Record<string, TimelineEntry>);
return Object.values(dailyStats).sort((a, b) => a.date.localeCompare(b.date));
}
}
// Интерфейсы для системы документооборота
interface DocumentWorkflowRequest {
workflow_id?: string;
document_type: 'invoice' | 'act' | 'contract';
template_type: string;
invoice_data: SupplierInvoiceCreateOrUpdateRequest;
file_path?: string;
file_id?: string;
generate_file?: boolean;
notification_email?: string;
webhook_url?: string;
user?: string;
}
interface DocumentTemplate {
name: string;
required_fields: string[];
vat_included: boolean;
currency: string;
}
interface ValidationResult {
valid: boolean;
message: string;
}
interface GenerationResult {
success: boolean;
file_path?: string;
message: string;
}
interface WorkflowResult {
workflow_id: string;
status: 'started' | 'completed' | 'failed' | 'error';
steps: WorkflowStep[];
document_id?: string;
error?: string;
created_at: string;
}
interface WorkflowStep {
step: string;
status: 'completed' | 'failed' | 'skipped';
message: string;
timestamp: string;
}
interface AuditEntry {
workflow_id: string;
action: string;
user: string;
timestamp: string;
details: any;
}
interface ActivityReport {
period: string;
total_workflows: number;
successful_workflows: number;
failed_workflows: number;
document_types: Record<string, number>;
timeline: TimelineEntry[];
}
interface TimelineEntry {
date: string;
count: number;
successful: number;
}
// Использование системы документооборота
const docSystem = new DocumentManagementSystem(api);
// Пример обработки документа
const workflowRequest: DocumentWorkflowRequest = {
document_type: 'invoice',
template_type: 'standard_invoice',
invoice_data: {
invoice_number: 'WF-INV-001',
invoice_date: '2024-01-15',
supplier_info: {
name: 'ООО "Автоматизация"',
inn: '7712345678',
address: 'г. Москва, ул. Технологическая, д. 1'
},
buyer_info: {
name: 'ООО "Заказчик"',
inn: '9876543210',
address: 'г. СПб, ул. Заказная, д. 1'
},
total_amount: 118000,
currency: 'RUB',
vat_amount: 18000,
vat_rate: 20,
items: [
{
sku: 'AUTO-001',
name: 'Автоматизированная система',
quantity: 1,
unit_price: 100000,
total_price: 100000,
vat_rate: 20,
vat_amount: 15000
}
]
},
generate_file: true,
notification_email: 'notifications@company.com',
user: 'system_user'
};
const workflowResult = await docSystem.processDocumentWorkflow(workflowRequest);
if (workflowResult.status === 'completed') {
console.log('🎉 Документооборот завершен успешно!');
console.log('ID документа:', workflowResult.document_id);
// Генерация отчета
const report = await docSystem.generateReport(
new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
new Date().toISOString()
);
console.log('📊 Отчет за последние 30 дней:', report);
} else {
console.log('❌ Ошибка в документообороте:', workflowResult.error);
}
Обработка ошибок
async function safeSupplierOperation() {
try {
const result = await api.supplier.createOrUpdateInvoice({
invoice_number: 'SAFE-001',
invoice_date: '2024-01-15',
total_amount: 100000,
currency: 'RUB',
vat_amount: 15000,
vat_rate: 20,
items: []
});
return result;
} catch (error) {
if (error.code === 'INVALID_FILE_FORMAT') {
console.error('Неподдерживаемый формат файла');
} else if (error.code === 'FILE_TOO_LARGE') {
console.error('Файл превышает максимально допустимый размер');
} else if (error.code === 'INVOICE_NUMBER_EXISTS') {
console.error('Счет с таким номером уже существует');
} else if (error.code === 'INVALID_VAT_CALCULATION') {
console.error('Ошибка в расчете НДС');
} else if (error.code === 'UNAUTHORIZED') {
console.error('Недостаточно прав для операции');
} else {
console.error('Неизвестная ошибка:', error);
}
return null;
}
}
// Функция с повторными попытками
async function uploadFileWithRetry(fileData: any, maxRetries: number = 3): Promise<any> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await api.supplier.uploadInvoiceFile(fileData);
} catch (error) {
console.error(`Попытка ${attempt}/${maxRetries} не удалась:`, error.message);
if (attempt === maxRetries) {
throw new Error(`Не удалось загрузить файл после ${maxRetries} попыток`);
}
// Экспоненциальная задержка
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Лучшие практики
1. Валидация данных перед отправкой
function validateInvoiceData(data: SupplierInvoiceCreateOrUpdateRequest): string[] {
const errors: string[] = [];
if (!data.invoice_number || data.invoice_number.trim() === '') {
errors.push('Номер счета обязателен');
}
if (!data.total_amount || data.total_amount <= 0) {
errors.push('Сумма должна быть положительной');
}
if (data.items && data.items.length === 0) {
errors.push('Должна быть хотя бы одна позиция');
}
// Проверка НДС
const calculatedVat = (data.total_amount - data.vat_amount) * (data.vat_rate / 100);
if (Math.abs(calculatedVat - data.vat_amount) > 0.01) {
errors.push('Неправильный расчет НДС');
}
return errors;
}
2. Оптимизация размера файлов
function optimizeFileSize(base64File: string): string {
// Проверка размера (максимум 10MB в base64)
const maxSize = 10 * 1024 * 1024 * 4/3; // base64 увеличивает размер на 33%
if (base64File.length > maxSize) {
console.warn('Файл превышает рекомендуемый размер');
// Здесь можно добавить сжатие или предупреждение
}
return base64File;
}
3. Кэширование результатов
class InvoiceCache {
private cache = new Map<string, any>();
private readonly TTL = 5 * 60 * 1000; // 5 минут
get(key: string): any {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp > this.TTL) {
this.cache.delete(key);
return null;
}
return item.data;
}
set(key: string, data: any): void {
this.cache.set(key, {
data,
timestamp: Date.now()
});
}
}
Интеграция с другими API
Связь с Product API
async function createInvoiceFromProducts(productIds: string[]) {
// Получаем информацию о товарах
// const products = await api.product.getInfo({ product_ids: productIds });
// Создаем позиции счета на основе товаров
const items: InvoiceItem[] = productIds.map(id => ({
sku: `PROD-${id}`,
name: `Товар ${id}`,
quantity: 1,
unit_price: 1000,
total_price: 1000,
vat_rate: 20,
vat_amount: 150
}));
return api.supplier.createOrUpdateInvoice({
invoice_number: `INV-PROD-${Date.now()}`,
invoice_date: new Date().toISOString().split('T')[0],
total_amount: items.reduce((sum, item) => sum + item.total_price, 0),
currency: 'RUB',
vat_amount: items.reduce((sum, item) => sum + item.vat_amount, 0),
vat_rate: 20,
items
});
}
Интеграция с финансовой отчетностью
async function reconcileInvoicesWithPayments() {
// Получаем оплаченные счета за период
// const payments = await api.finance.getTransactionsList(...);
console.log('Сверка счетов с платежами...');
// Логика сверки документооборота с финансовыми операциями
}