|
|
@@ -1,86 +1,42 @@
|
|
|
-document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Admin/Products/index.styl']+'</style>')
|
|
|
+document.head.insertAdjacentHTML('beforeend','<style type="text/css">'+stylFns['app/pages/Admin/Products/index.styl']+'</style>')
|
|
|
|
|
|
PouchDB = require 'app/utils/pouch'
|
|
|
-Papa = require 'papaparse'
|
|
|
+
|
|
|
|
|
|
module.exports =
|
|
|
name: 'AdminProducts'
|
|
|
-
|
|
|
render: (new Function '_ctx', '_cache', renderFns['app/pages/Admin/Products/index.pug'])()
|
|
|
|
|
|
data: ->
|
|
|
return {
|
|
|
products: []
|
|
|
categories: []
|
|
|
- searchQuery: ''
|
|
|
- selectedCategory: ''
|
|
|
- selectedStatus: ''
|
|
|
showProductModal: false
|
|
|
showImportModal: false
|
|
|
- showCategoriesModal: false
|
|
|
- showCategoryModal: false
|
|
|
- showMassActionsModal: false
|
|
|
- showCategoryAssignModal: false
|
|
|
- showMassCategoryAssign: false
|
|
|
- showMassPriceModal: false
|
|
|
- editingProduct: null
|
|
|
- editingCategory: null
|
|
|
selectedFile: null
|
|
|
- selectedCategoriesFile: null
|
|
|
importing: false
|
|
|
- importingCategories: false
|
|
|
- importResults: null
|
|
|
- categoriesImportResults: null
|
|
|
- availableDomains: []
|
|
|
- categoriesActiveTab: 'list'
|
|
|
importProgress: 0
|
|
|
- processedCount: 0
|
|
|
- totalCount: 0
|
|
|
- uploadingImages: false
|
|
|
- newImageUrl: ''
|
|
|
- newAdditionalImageUrl: ''
|
|
|
- newAttributeKey: ''
|
|
|
- newAttributeValue: ''
|
|
|
- newTag: ''
|
|
|
-
|
|
|
- # Mass actions data
|
|
|
- selectedProducts: []
|
|
|
- selectAll: false
|
|
|
- massCategory: ''
|
|
|
- massAllCategory: ''
|
|
|
- removeExistingCategories: false
|
|
|
- massRemoveAllCategories: false
|
|
|
- priceChangeType: 'fixed'
|
|
|
- priceChangeValue: null
|
|
|
- applyToOldPrice: false
|
|
|
-
|
|
|
- productForm:
|
|
|
+ importResults: null
|
|
|
+ currentProduct: {
|
|
|
_id: ''
|
|
|
+ type: 'product'
|
|
|
name: ''
|
|
|
sku: ''
|
|
|
- category: ''
|
|
|
price: 0
|
|
|
oldPrice: 0
|
|
|
- brand: ''
|
|
|
+ category: ''
|
|
|
description: ''
|
|
|
- image: ''
|
|
|
- additionalImages: []
|
|
|
active: true
|
|
|
domains: []
|
|
|
attributes: {}
|
|
|
- tags: []
|
|
|
-
|
|
|
- categoryForm:
|
|
|
- _id: ''
|
|
|
- name: ''
|
|
|
- slug: ''
|
|
|
- description: ''
|
|
|
- parentCategory: ''
|
|
|
- sortOrder: 0
|
|
|
- image: ''
|
|
|
- icon: ''
|
|
|
- active: true
|
|
|
- domains: []
|
|
|
+ images: []
|
|
|
+ createdAt: ''
|
|
|
+ updatedAt: ''
|
|
|
+ }
|
|
|
+ searchQuery: ''
|
|
|
+ selectedCategory: ''
|
|
|
+ bulkActions: []
|
|
|
+ selectedProducts: []
|
|
|
}
|
|
|
|
|
|
computed:
|
|
|
@@ -97,1050 +53,483 @@ module.exports =
|
|
|
products = products.filter (product) =>
|
|
|
product.category == @selectedCategory
|
|
|
|
|
|
- if @selectedStatus == 'active'
|
|
|
- products = products.filter (product) => product.active
|
|
|
- else if @selectedStatus == 'inactive'
|
|
|
- products = products.filter (product) => !product.active
|
|
|
-
|
|
|
return products
|
|
|
|
|
|
- isEditing: ->
|
|
|
- @editingProduct != null
|
|
|
-
|
|
|
- attributesList: ->
|
|
|
- list = []
|
|
|
- for key, value of @productForm.attributes
|
|
|
- list.push
|
|
|
- key: key
|
|
|
- value: value
|
|
|
- return list
|
|
|
+ availableDomains: ->
|
|
|
+ @$root.currentDomainSettings?.domains or [@$root.currentDomain]
|
|
|
+
|
|
|
+ mounted: ->
|
|
|
+ @loadProducts()
|
|
|
+ @loadCategories()
|
|
|
|
|
|
methods:
|
|
|
loadProducts: ->
|
|
|
+ debug.log '📥 Загрузка товаров...'
|
|
|
PouchDB.queryView('admin', 'products', { include_docs: true })
|
|
|
- .then (result) =>
|
|
|
- @products = result.rows.map (row) -> row.doc
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка загрузки товаров:', error
|
|
|
- @showNotification 'Ошибка загрузки товаров', 'error'
|
|
|
+ .then (result) =>
|
|
|
+ @products = result.rows.map (row) -> row.doc
|
|
|
+ debug.log "✅ Загружено #{@products.length} товаров"
|
|
|
+ .catch (error) =>
|
|
|
+ debug.log '❌ Ошибка загрузки товаров:', error
|
|
|
+ @showNotification 'Ошибка загрузки товаров', 'error'
|
|
|
|
|
|
loadCategories: ->
|
|
|
+ debug.log '📥 Загрузка категорий...'
|
|
|
PouchDB.queryView('admin', 'categories', { include_docs: true })
|
|
|
- .then (result) =>
|
|
|
- @categories = result.rows.map (row) -> row.doc
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка загрузки категорий:', error
|
|
|
-
|
|
|
- loadDomains: ->
|
|
|
- PouchDB.queryView('admin', 'domain_settings', { include_docs: true })
|
|
|
- .then (result) =>
|
|
|
- @availableDomains = result.rows.map (row) -> row.doc
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка загрузки доменов:', error
|
|
|
-
|
|
|
- getCategoryName: (categoryId) ->
|
|
|
- category = @categories.find (cat) -> cat._id == categoryId
|
|
|
- category?.name or 'Без категории'
|
|
|
-
|
|
|
- getCategoryProductCount: (categoryId) ->
|
|
|
- @products.filter((product) -> product.category == categoryId).length
|
|
|
-
|
|
|
- # Управление категориями
|
|
|
- showCategoriesManager: ->
|
|
|
- @showCategoriesModal = true
|
|
|
-
|
|
|
- createCategory: ->
|
|
|
- @editingCategory = null
|
|
|
- @resetCategoryForm()
|
|
|
- @showCategoryModal = true
|
|
|
-
|
|
|
- editCategory: (category) ->
|
|
|
- @editingCategory = category
|
|
|
- @categoryForm = Object.assign {},
|
|
|
- _id: category._id
|
|
|
- name: category.name or ''
|
|
|
- slug: category.slug or ''
|
|
|
- description: category.description or ''
|
|
|
- parentCategory: category.parentCategory or ''
|
|
|
- sortOrder: category.sortOrder or 0
|
|
|
- image: category.image or ''
|
|
|
- icon: category.icon or ''
|
|
|
- active: category.active != false
|
|
|
- domains: category.domains or [window.location.hostname]
|
|
|
-
|
|
|
- @showCategoryModal = true
|
|
|
-
|
|
|
- saveCategory: ->
|
|
|
- if !@categoryForm.name
|
|
|
- @showNotification 'Введите название категории', 'error'
|
|
|
- return
|
|
|
-
|
|
|
- categoryData = Object.assign {}, @categoryForm
|
|
|
- delete categoryData._id
|
|
|
-
|
|
|
- if !categoryData.slug
|
|
|
- categoryData.slug = @generateSlug(categoryData.name)
|
|
|
-
|
|
|
- if @editingCategory
|
|
|
- # Обновление существующей категории
|
|
|
- categoryData._id = @editingCategory._id
|
|
|
- categoryData._rev = @editingCategory._rev
|
|
|
- categoryData.updatedAt = new Date().toISOString()
|
|
|
- else
|
|
|
- # Создание новой категории
|
|
|
- categoryData._id = "category:#{Date.now()}"
|
|
|
- categoryData.type = 'category'
|
|
|
- categoryData.createdAt = new Date().toISOString()
|
|
|
- categoryData.updatedAt = categoryData.createdAt
|
|
|
-
|
|
|
- PouchDB.saveToRemote(categoryData)
|
|
|
- .then (result) =>
|
|
|
- @showCategoryModal = false
|
|
|
- @resetCategoryForm()
|
|
|
- @loadCategories()
|
|
|
- @showNotification "Категория #{if @editingCategory then 'обновлена' else 'создана'}"
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка сохранения категории:', error
|
|
|
- @showNotification 'Ошибка сохранения категории', 'error'
|
|
|
-
|
|
|
- deleteCategory: (category) ->
|
|
|
- if !confirm("Удалить категорию \"#{category.name}\"? Товары в этой категории не будут удалены.")
|
|
|
- return
|
|
|
-
|
|
|
- PouchDB.getDocument(category._id)
|
|
|
- .then (doc) ->
|
|
|
- PouchDB.saveToRemote(Object.assign {}, doc, { _deleted: true })
|
|
|
- .then (result) =>
|
|
|
- @loadCategories()
|
|
|
- @showNotification 'Категория удалена'
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка удаления категории:', error
|
|
|
- @showNotification 'Ошибка удаления категории', 'error'
|
|
|
-
|
|
|
- resetCategoryForm: ->
|
|
|
- @categoryForm =
|
|
|
- _id: ''
|
|
|
- name: ''
|
|
|
- slug: ''
|
|
|
- description: ''
|
|
|
- parentCategory: ''
|
|
|
- sortOrder: 0
|
|
|
- image: ''
|
|
|
- icon: ''
|
|
|
+ .then (result) =>
|
|
|
+ @categories = result.rows.map (row) -> row.doc
|
|
|
+ debug.log "✅ Загружено #{@categories.length} категорий"
|
|
|
+ .catch (error) =>
|
|
|
+ debug.log '❌ Ошибка загрузки категорий:', error
|
|
|
+
|
|
|
+ createCategory: (categoryName) ->
|
|
|
+ categorySlug = categoryName.toLowerCase().replace(/[^a-z0-9а-яё]/g, '-').replace(/-+/g, '-')
|
|
|
+
|
|
|
+ categoryData = {
|
|
|
+ _id: "category:#{categorySlug}"
|
|
|
+ type: 'category'
|
|
|
+ name: categoryName
|
|
|
+ slug: categorySlug
|
|
|
active: true
|
|
|
- domains: [window.location.hostname]
|
|
|
-
|
|
|
- # Редактирование и создание товаров
|
|
|
- createProduct: ->
|
|
|
- @editingProduct = null
|
|
|
- @resetProductForm()
|
|
|
- @showProductModal = true
|
|
|
-
|
|
|
- editProduct: (product) ->
|
|
|
- @editingProduct = product
|
|
|
- @productForm = Object.assign {},
|
|
|
- _id: product._id
|
|
|
- name: product.name or ''
|
|
|
- sku: product.sku or ''
|
|
|
- category: product.category or ''
|
|
|
- price: product.price or 0
|
|
|
- oldPrice: product.oldPrice or 0
|
|
|
- brand: product.brand or ''
|
|
|
- description: product.description or ''
|
|
|
- image: product.image or ''
|
|
|
- additionalImages: product.additionalImages or []
|
|
|
- active: product.active != false
|
|
|
- domains: product.domains or [window.location.hostname]
|
|
|
- attributes: product.attributes or {}
|
|
|
- tags: product.tags or []
|
|
|
+ order: @categories.length
|
|
|
+ domains: @availableDomains
|
|
|
+ createdAt: new Date().toISOString()
|
|
|
+ updatedAt: new Date().toISOString()
|
|
|
+ }
|
|
|
|
|
|
- @showProductModal = true
|
|
|
-
|
|
|
- saveProduct: ->
|
|
|
- if !@productForm.name or !@productForm.sku or !@productForm.price
|
|
|
- @showNotification 'Заполните обязательные поля: название, артикул и цена', 'error'
|
|
|
- return
|
|
|
+ PouchDB.saveToRemote(categoryData)
|
|
|
+ .then (result) =>
|
|
|
+ debug.log "✅ Создана категория: #{categoryName}"
|
|
|
+ @loadCategories()
|
|
|
+ return categorySlug
|
|
|
+ .catch (error) =>
|
|
|
+ debug.log '❌ Ошибка создания категории:', error
|
|
|
+ throw error
|
|
|
+
|
|
|
+ transformProductData: (csvData, index) ->
|
|
|
+ debug.log "🔄 Преобразование данных товара #{index + 1}: #{csvData['Артикул*']}"
|
|
|
+
|
|
|
+ sku = csvData['Артикул*']?.toString().trim()
|
|
|
+ return null unless sku
|
|
|
+
|
|
|
+ # Определяем категорию
|
|
|
+ categoryName = csvData['Тип*']?.trim() or 'Другое'
|
|
|
+ debug.log "🔍 Поиск категории: #{categoryName}"
|
|
|
|
|
|
- productData = Object.assign {}, @productForm
|
|
|
- delete productData._id
|
|
|
+ # Ищем существующую категорию
|
|
|
+ existingCategory = @categories.find (cat) -> cat.name == categoryName
|
|
|
|
|
|
- if @isEditing
|
|
|
- # Обновление существующего товара
|
|
|
- productData._id = @editingProduct._id
|
|
|
- productData._rev = @editingProduct._rev
|
|
|
- productData.updatedAt = new Date().toISOString()
|
|
|
+ if existingCategory
|
|
|
+ categorySlug = existingCategory.slug
|
|
|
+ debug.log "✅ Использована существующая категория: #{categoryName}"
|
|
|
else
|
|
|
- # Создание нового товара
|
|
|
- productData._id = "product:#{@productForm.sku}"
|
|
|
- productData.type = 'product'
|
|
|
- productData.createdAt = new Date().toISOString()
|
|
|
- productData.updatedAt = productData.createdAt
|
|
|
-
|
|
|
- PouchDB.saveToRemote(productData)
|
|
|
- .then (result) =>
|
|
|
- @showProductModal = false
|
|
|
- @resetProductForm()
|
|
|
- @loadProducts()
|
|
|
- @showNotification "Товар #{if @isEditing then 'обновлен' else 'создан'}"
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка сохранения товара:', error
|
|
|
- @showNotification 'Ошибка сохранения товара', 'error'
|
|
|
-
|
|
|
- resetProductForm: ->
|
|
|
- @productForm =
|
|
|
- _id: ''
|
|
|
- name: ''
|
|
|
- sku: ''
|
|
|
- category: ''
|
|
|
- price: 0
|
|
|
- oldPrice: 0
|
|
|
- brand: ''
|
|
|
- description: ''
|
|
|
- image: ''
|
|
|
- additionalImages: []
|
|
|
+ debug.log "🆕 Создание новой категории: #{categoryName}"
|
|
|
+ categorySlug = categoryName.toLowerCase().replace(/[^a-z0-9а-яё]/g, '-').replace(/-+/g, '-')
|
|
|
+ # Категория будет создана позже в процессе импорта
|
|
|
+
|
|
|
+ # Базовые данные товара
|
|
|
+ productData = {
|
|
|
+ _id: "product:#{sku}"
|
|
|
+ type: 'product'
|
|
|
+ name: csvData['Название товара']?.trim() or "Товар #{sku}"
|
|
|
+ sku: sku
|
|
|
+ price: parseFloat(csvData['Цена, руб.*']?.replace(/\s/g, '')?.replace(',', '.') or 0)
|
|
|
+ oldPrice: parseFloat(csvData['Цена до скидки, руб.']?.replace(/\s/g, '')?.replace(',', '.') or 0)
|
|
|
+ category: categorySlug
|
|
|
+ brand: csvData['Бренд*']?.trim()
|
|
|
+ description: csvData['Аннотация']?.trim() or ''
|
|
|
active: true
|
|
|
- domains: [window.location.hostname]
|
|
|
+ domains: @availableDomains
|
|
|
attributes: {}
|
|
|
- tags: []
|
|
|
-
|
|
|
- # Управление атрибутами товара
|
|
|
- addAttribute: ->
|
|
|
- if !@newAttributeKey
|
|
|
- @showNotification 'Введите название атрибута', 'error'
|
|
|
- return
|
|
|
-
|
|
|
- if @productForm.attributes[@newAttributeKey]
|
|
|
- @showNotification 'Атрибут с таким названием уже существует', 'error'
|
|
|
- return
|
|
|
-
|
|
|
- @productForm.attributes[@newAttributeKey] = @newAttributeValue or ''
|
|
|
- @newAttributeKey = ''
|
|
|
- @newAttributeValue = ''
|
|
|
- @showNotification 'Атрибут добавлен'
|
|
|
-
|
|
|
- removeAttribute: (key) ->
|
|
|
- delete @productForm.attributes[key]
|
|
|
- @showNotification 'Атрибут удален'
|
|
|
-
|
|
|
- updateAttribute: (key, value) ->
|
|
|
- @productForm.attributes[key] = value
|
|
|
-
|
|
|
- # Управление тегами
|
|
|
- addTag: ->
|
|
|
- if !@newTag
|
|
|
- @showNotification 'Введите тег', 'error'
|
|
|
- return
|
|
|
-
|
|
|
- if @productForm.tags.includes(@newTag)
|
|
|
- @showNotification 'Такой тег уже существует', 'error'
|
|
|
- return
|
|
|
-
|
|
|
- @productForm.tags.push(@newTag)
|
|
|
- @newTag = ''
|
|
|
- @showNotification 'Тег добавлен'
|
|
|
-
|
|
|
- removeTag: (index) ->
|
|
|
- @productForm.tags.splice(index, 1)
|
|
|
- @showNotification 'Тег удален'
|
|
|
-
|
|
|
- # Загрузка изображений для редактирования
|
|
|
- uploadMainImage: ->
|
|
|
- if !@newImageUrl
|
|
|
- @showNotification 'Введите URL изображения', 'error'
|
|
|
- return
|
|
|
+ images: []
|
|
|
+ createdAt: new Date().toISOString()
|
|
|
+ updatedAt: new Date().toISOString()
|
|
|
+ }
|
|
|
|
|
|
- @uploadingImages = true
|
|
|
- productId = if @isEditing then @editingProduct._id else "product:#{@productForm.sku}"
|
|
|
+ # Дополнительные атрибуты
|
|
|
+ additionalAttributes = {}
|
|
|
+ for key, value of csvData
|
|
|
+ if value and not key in ['Артикул*', 'Название товара', 'Цена, руб.*', 'Цена до скидки, руб.', 'Тип*', 'Бренд*', 'Аннотация', 'Rich-контент JSON', 'Ссылка на главное фото', 'Ссылки на дополнительные фото']
|
|
|
+ additionalAttributes[key] = value
|
|
|
|
|
|
- @downloadAndStoreImage(@newImageUrl, productId, 'main.jpg')
|
|
|
- .then (attachmentInfo) =>
|
|
|
- @productForm.image = "/d/braer_color_shop/#{productId}/main.jpg"
|
|
|
- @newImageUrl = ''
|
|
|
- @showNotification 'Основное изображение загружено'
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка загрузки основного изображения:', error
|
|
|
- @showNotification 'Ошибка загрузки изображения', 'error'
|
|
|
- .finally =>
|
|
|
- @uploadingImages = false
|
|
|
-
|
|
|
- uploadAdditionalImage: ->
|
|
|
- if !@newAdditionalImageUrl
|
|
|
- @showNotification 'Введите URL изображения', 'error'
|
|
|
- return
|
|
|
+ productData.attributes = additionalAttributes
|
|
|
|
|
|
- @uploadingImages = true
|
|
|
- productId = if @isEditing then @editingProduct._id else "product:#{@productForm.sku}"
|
|
|
- index = @productForm.additionalImages.length
|
|
|
+ # Rich-контент
|
|
|
+ if csvData['Rich-контент JSON']
|
|
|
+ try
|
|
|
+ richContent = JSON.parse(csvData['Rich-контент JSON'])
|
|
|
+ productData.richContent = richContent
|
|
|
+ catch error
|
|
|
+ debug.log '⚠️ Ошибка парсинга Rich-контента:', error
|
|
|
|
|
|
- @downloadAndStoreImage(@newAdditionalImageUrl, productId, "additional-#{index}.jpg")
|
|
|
- .then (attachmentInfo) =>
|
|
|
- imagePath = "/d/braer_color_shop/#{productId}/additional-#{index}.jpg"
|
|
|
- @productForm.additionalImages.push(imagePath)
|
|
|
- @newAdditionalImageUrl = ''
|
|
|
- @showNotification 'Дополнительное изображение загружено'
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка загрузки дополнительного изображения:', error
|
|
|
- @showNotification 'Ошибка загрузки изображения', 'error'
|
|
|
- .finally =>
|
|
|
- @uploadingImages = false
|
|
|
-
|
|
|
- removeAdditionalImage: (index) ->
|
|
|
- @productForm.additionalImages.splice(index, 1)
|
|
|
- @showNotification 'Изображение удалено'
|
|
|
+ debug.log "✅ Данные товара полностью преобразованы: #{sku}"
|
|
|
+ return productData
|
|
|
|
|
|
- # ИСПРАВЛЕННЫЙ метод загрузки и сохранения изображения как attachment
|
|
|
downloadAndStoreImage: (imageUrl, docId, filename) ->
|
|
|
return new Promise (resolve, reject) =>
|
|
|
debug.log "🔄 Начало загрузки изображения: #{imageUrl}"
|
|
|
- debug.log "📁 Документ: #{docId}, Файл: #{filename}"
|
|
|
|
|
|
- try
|
|
|
- # Создаем XMLHttpRequest для загрузки изображения
|
|
|
- xhr = new XMLHttpRequest()
|
|
|
- xhr.open('GET', imageUrl, true)
|
|
|
- xhr.responseType = 'blob'
|
|
|
-
|
|
|
- xhr.onload = =>
|
|
|
- debug.log "📡 Статус ответа XHR: #{xhr.status}"
|
|
|
- if xhr.status == 200
|
|
|
- blob = xhr.response
|
|
|
- debug.log "✅ Blob получен:",
|
|
|
- type: blob.type
|
|
|
- size: blob.size
|
|
|
- isBlob: blob instanceof Blob
|
|
|
-
|
|
|
- # Читаем blob как ArrayBuffer
|
|
|
- reader = new FileReader()
|
|
|
-
|
|
|
- reader.onloadstart = ->
|
|
|
- debug.log "📖 Начало чтения blob как ArrayBuffer"
|
|
|
-
|
|
|
- reader.onload = (e) =>
|
|
|
- debug.log "✅ ArrayBuffer успешно прочитан"
|
|
|
- arrayBuffer = e.target.result
|
|
|
- debug.log "📊 ArrayBuffer:",
|
|
|
- byteLength: arrayBuffer.byteLength
|
|
|
- isArrayBuffer: arrayBuffer instanceof ArrayBuffer
|
|
|
-
|
|
|
- # Получаем текущий документ для правильного _rev
|
|
|
- debug.log "🔍 Получение документа #{docId} для _rev"
|
|
|
- PouchDB.getDocument(docId)
|
|
|
- .then (doc) =>
|
|
|
- debug.log "✅ Документ получен:",
|
|
|
- _id: doc._id
|
|
|
- _rev: doc._rev?.substring(0, 10) + '...'
|
|
|
-
|
|
|
- # Сохраняем как attachment в PouchDB
|
|
|
- debug.log "💾 Сохранение attachment..."
|
|
|
- return PouchDB.putAttachment(docId, filename, doc._rev, arrayBuffer, blob.type)
|
|
|
- .then (result) =>
|
|
|
- debug.log "✅ Attachment успешно сохранен:", result
|
|
|
- resolve
|
|
|
- filename: filename
|
|
|
- contentType: blob.type
|
|
|
- size: blob.size
|
|
|
- url: "/d/braer_color_shop/#{docId}/#{filename}"
|
|
|
- .catch (error) =>
|
|
|
- debug.log "❌ Ошибка при работе с документом:", error
|
|
|
- if error.status == 404
|
|
|
- debug.log "📄 Документ не найден, создаем временный"
|
|
|
- # Документа нет - создаем временный для attachment
|
|
|
- tempDoc =
|
|
|
- _id: docId
|
|
|
- type: 'product'
|
|
|
- name: 'Temp'
|
|
|
- sku: 'temp'
|
|
|
- price: 0
|
|
|
- active: false
|
|
|
- createdAt: new Date().toISOString()
|
|
|
- updatedAt: new Date().toISOString()
|
|
|
-
|
|
|
- PouchDB.saveToRemote(tempDoc)
|
|
|
- .then =>
|
|
|
- debug.log "✅ Временный документ создан"
|
|
|
- PouchDB.putAttachment(docId, filename, tempDoc._rev, arrayBuffer, blob.type)
|
|
|
- .then (result) =>
|
|
|
- debug.log "✅ Attachment сохранен во временный документ:", result
|
|
|
- resolve
|
|
|
- filename: filename
|
|
|
- contentType: blob.type
|
|
|
- size: blob.size
|
|
|
- url: "/d/braer_color_shop/#{docId}/#{filename}"
|
|
|
- .catch (err) ->
|
|
|
- debug.log "❌ Ошибка сохранения attachment во временный документ:", err
|
|
|
- reject(err)
|
|
|
- else
|
|
|
- debug.log "❌ Другая ошибка при получении документа:", error
|
|
|
- reject(error)
|
|
|
-
|
|
|
- reader.onerror = (error) =>
|
|
|
- debug.log "❌ Ошибка чтения blob:", error
|
|
|
- reject(new Error("Ошибка чтения blob: #{error}"))
|
|
|
-
|
|
|
- reader.onabort = ->
|
|
|
- debug.log "⚠️ Чтение blob прервано"
|
|
|
-
|
|
|
- debug.log "🔁 Чтение blob как ArrayBuffer..."
|
|
|
- reader.readAsArrayBuffer(blob)
|
|
|
- else
|
|
|
- errorMsg = "Ошибка загрузки изображения: #{xhr.status}"
|
|
|
- debug.log "❌ #{errorMsg}"
|
|
|
- reject(new Error(errorMsg))
|
|
|
+ # Проверяем валидность URL
|
|
|
+ unless imageUrl and imageUrl.startsWith('http')
|
|
|
+ debug.log '⚠️ Невалидный URL изображения:', imageUrl
|
|
|
+ return resolve(null)
|
|
|
+
|
|
|
+ # Создаем уникальное имя файла
|
|
|
+ fileExtension = imageUrl.split('.').pop()?.split('?')[0] or 'jpg'
|
|
|
+ uniqueFilename = "#{filename}.#{fileExtension}"
|
|
|
+
|
|
|
+ debug.log "📁 Документ: #{docId}, Файл: #{uniqueFilename}"
|
|
|
+
|
|
|
+ # Используем fetch вместо XMLHttpRequest для лучшей обработки ошибок
|
|
|
+ fetch(imageUrl)
|
|
|
+ .then (response) =>
|
|
|
+ unless response.ok
|
|
|
+ throw new Error("HTTP #{response.status}: #{response.statusText}")
|
|
|
+ return response.blob()
|
|
|
+ .then (blob) =>
|
|
|
+ debug.log "✅ Blob получен, размер: #{blob.size} байт"
|
|
|
|
|
|
- xhr.onerror = =>
|
|
|
- errorMsg = 'Ошибка сети при загрузке изображения'
|
|
|
- debug.log "❌ #{errorMsg}"
|
|
|
- reject(new Error(errorMsg))
|
|
|
+ if blob.size == 0
|
|
|
+ throw new Error('Пустой blob')
|
|
|
|
|
|
- xhr.ontimeout = =>
|
|
|
- errorMsg = 'Таймаут загрузки изображения'
|
|
|
- debug.log "❌ #{errorMsg}"
|
|
|
- reject(new Error(errorMsg))
|
|
|
+ # Читаем blob как ArrayBuffer
|
|
|
+ reader = new FileReader()
|
|
|
+ reader.onload = (event) =>
|
|
|
+ try
|
|
|
+ arrayBuffer = event.target.result
|
|
|
+ debug.log "✅ ArrayBuffer успешно прочитан, размер: #{arrayBuffer.byteLength} байт"
|
|
|
+
|
|
|
+ # Сохраняем attachment в PouchDB
|
|
|
+ PouchDB.localDb.putAttachment(
|
|
|
+ docId,
|
|
|
+ uniqueFilename,
|
|
|
+ @currentProduct._rev,
|
|
|
+ blob,
|
|
|
+ blob.type
|
|
|
+ )
|
|
|
+ .then (result) =>
|
|
|
+ debug.log "✅ Attachment сохранен: #{uniqueFilename}"
|
|
|
+ resolve({
|
|
|
+ filename: uniqueFilename
|
|
|
+ contentType: blob.type
|
|
|
+ size: blob.size
|
|
|
+ })
|
|
|
+ .catch (attachmentError) =>
|
|
|
+ debug.log "❌ Ошибка сохранения attachment:", attachmentError
|
|
|
+ reject(attachmentError)
|
|
|
+ catch readError
|
|
|
+ debug.log "❌ Ошибка чтения blob:", readError
|
|
|
+ reject(readError)
|
|
|
|
|
|
- xhr.onabort = =>
|
|
|
- debug.log "⚠️ Загрузка изображения прервана"
|
|
|
+ reader.onerror = (error) =>
|
|
|
+ debug.log "❌ Ошибка FileReader:", error
|
|
|
+ reject(error)
|
|
|
|
|
|
- debug.log "🚀 Отправка XHR запроса..."
|
|
|
- xhr.send()
|
|
|
+ reader.readAsArrayBuffer(blob)
|
|
|
+ .catch (fetchError) =>
|
|
|
+ debug.log "❌ Ошибка загрузки изображения:", fetchError
|
|
|
+ reject(fetchError)
|
|
|
+
|
|
|
+ processProductImages: (productData, csvData) ->
|
|
|
+ debug.log "🖼️ Начало обработки изображений для товара: #{productData.sku}"
|
|
|
+
|
|
|
+ imagePromises = []
|
|
|
+
|
|
|
+ # Основное изображение
|
|
|
+ mainImageUrl = csvData['Ссылка на главное фото']?.trim()
|
|
|
+ if mainImageUrl
|
|
|
+ imagePromises.push(
|
|
|
+ @downloadAndStoreImage(mainImageUrl, productData._id, 'main')
|
|
|
+ .then (imageInfo) =>
|
|
|
+ if imageInfo
|
|
|
+ productData.mainImage = imageInfo.filename
|
|
|
+ return imageInfo
|
|
|
+ return null
|
|
|
+ .catch (error) =>
|
|
|
+ debug.log "⚠️ Не удалось загрузить основное изображение:", error
|
|
|
+ return null
|
|
|
+ )
|
|
|
+
|
|
|
+ # Дополнительные изображения
|
|
|
+ additionalImages = csvData['Ссылки на дополнительные фото']
|
|
|
+ if additionalImages
|
|
|
+ # Разделяем строку по переносам и фильтруем пустые значения
|
|
|
+ imageUrls = additionalImages.split('\n')
|
|
|
+ .map((url) -> url.trim())
|
|
|
+ .filter((url) -> url and url.startsWith('http'))
|
|
|
+ .slice(0, 5) # Ограничиваем 5 изображениями
|
|
|
|
|
|
- catch error
|
|
|
- debug.log "💥 Критическая ошибка в downloadAndStoreImage:", error
|
|
|
- reject(error)
|
|
|
-
|
|
|
- # Импорт товаров из CSV
|
|
|
- onFileSelect: (event) ->
|
|
|
- @selectedFile = event.target.files[0]
|
|
|
- @importResults = null
|
|
|
- @importProgress = 0
|
|
|
- @processedCount = 0
|
|
|
+ imageUrls.forEach (imageUrl, index) =>
|
|
|
+ imagePromises.push(
|
|
|
+ @downloadAndStoreImage(imageUrl, productData._id, "additional-#{index + 1}")
|
|
|
+ .then (imageInfo) =>
|
|
|
+ if imageInfo
|
|
|
+ return imageInfo.filename
|
|
|
+ return null
|
|
|
+ .catch (error) =>
|
|
|
+ debug.log "⚠️ Не удалось загрузить дополнительное изображение:", error
|
|
|
+ return null
|
|
|
+ )
|
|
|
+
|
|
|
+ return Promise.allSettled(imagePromises)
|
|
|
+ .then (results) =>
|
|
|
+ # Фильтруем успешно загруженные изображения
|
|
|
+ successfulResults = results.filter (r) -> r.status == 'fulfilled' and r.value
|
|
|
+ additionalFilenames = successfulResults.slice(1).map (r) -> r.value?.filename
|
|
|
+ productData.additionalImages = additionalFilenames.filter (filename) -> filename
|
|
|
+ debug.log "✅ Обработано изображений: #{successfulResults.length}"
|
|
|
+ return productData
|
|
|
+ .catch (error) =>
|
|
|
+ debug.log "❌ Ошибка обработки изображений:", error
|
|
|
+ return productData
|
|
|
+
|
|
|
+ saveProduct: (productData) ->
|
|
|
+ debug.log "💾 Попытка сохранения товара: #{productData.sku}"
|
|
|
+
|
|
|
+ return new Promise (resolve, reject) =>
|
|
|
+ # Сначала пытаемся получить существующий документ для получения _rev
|
|
|
+ PouchDB.getDocument(productData._id)
|
|
|
+ .then (existingDoc) =>
|
|
|
+ debug.log "🔄 Обновление существующего товара: #{productData.sku}"
|
|
|
+ productData._rev = existingDoc._rev
|
|
|
+ productData.updatedAt = new Date().toISOString()
|
|
|
+
|
|
|
+ # Сохраняем в удаленную БД
|
|
|
+ PouchDB.saveToRemote(productData)
|
|
|
+ .then (result) =>
|
|
|
+ debug.log "✅ Товар сохранен, получение обновленной версии: #{productData.sku}"
|
|
|
+ # Получаем обновленный документ
|
|
|
+ PouchDB.getDocument(productData._id)
|
|
|
+ .then (updatedDoc) =>
|
|
|
+ debug.log "✅ Документ получен с актуальным _rev: #{updatedDoc._rev?.substring(0, 10)}..."
|
|
|
+ resolve(updatedDoc)
|
|
|
+ .catch (getError) =>
|
|
|
+ debug.log "⚠️ Не удалось получить обновленный документ:", getError
|
|
|
+ resolve(result)
|
|
|
+ .catch (saveError) =>
|
|
|
+ debug.log "❌ Ошибка сохранения товара:", saveError
|
|
|
+ reject(saveError)
|
|
|
+ .catch (getError) =>
|
|
|
+ if getError.status == 404
|
|
|
+ debug.log "🆕 Создание нового товара: #{productData.sku}"
|
|
|
+ productData.createdAt = new Date().toISOString()
|
|
|
+ productData.updatedAt = productData.createdAt
|
|
|
+
|
|
|
+ PouchDB.saveToRemote(productData)
|
|
|
+ .then (result) =>
|
|
|
+ debug.log "✅ Товар сохранен в БД: #{productData.sku}"
|
|
|
+ resolve(result)
|
|
|
+ .catch (saveError) =>
|
|
|
+ debug.log "❌ Ошибка создания товара:", saveError
|
|
|
+ reject(saveError)
|
|
|
+ else
|
|
|
+ debug.log "❌ Ошибка при получении документа:", getError
|
|
|
+ reject(getError)
|
|
|
+
|
|
|
+ readFile: (file) ->
|
|
|
+ return new Promise (resolve, reject) =>
|
|
|
+ reader = new FileReader()
|
|
|
+ reader.onload = (event) -> resolve(event.target.result)
|
|
|
+ reader.onerror = (error) -> reject(error)
|
|
|
+ reader.readAsText(file, 'UTF-8')
|
|
|
|
|
|
importProducts: ->
|
|
|
- if !@selectedFile
|
|
|
+ unless @selectedFile
|
|
|
@showNotification 'Выберите файл для импорта', 'error'
|
|
|
return
|
|
|
|
|
|
@importing = true
|
|
|
- @importResults = null
|
|
|
@importProgress = 0
|
|
|
- @processedCount = 0
|
|
|
+ @importResults = null
|
|
|
|
|
|
- reader = new FileReader()
|
|
|
- reader.onload = (e) =>
|
|
|
- try
|
|
|
- results = Papa.parse e.target.result,
|
|
|
- header: true
|
|
|
- delimiter: ';'
|
|
|
- skipEmptyLines: true
|
|
|
- encoding: 'UTF-8'
|
|
|
-
|
|
|
- products = results.data.filter (row) =>
|
|
|
- row and row['Артикул*'] and row['Название товара'] and row['Цена, руб.*']
|
|
|
-
|
|
|
- @totalCount = products.length
|
|
|
- debug.log "📊 Найдено товаров для импорта: #{@totalCount}"
|
|
|
-
|
|
|
- # Обрабатываем товары последовательно
|
|
|
- @processProductsSequentially(products)
|
|
|
- .then (results) =>
|
|
|
- successCount = results.filter((r) -> r.success).length
|
|
|
- errorCount = results.filter((r) -> !r.success).length
|
|
|
-
|
|
|
- @importResults =
|
|
|
- success: true
|
|
|
- processed: successCount
|
|
|
- errors: results.filter((r) -> !r.success).map((r) -> r.error)
|
|
|
- total: @totalCount
|
|
|
-
|
|
|
- @importing = false
|
|
|
- @loadProducts()
|
|
|
- @loadCategories()
|
|
|
- @showNotification "Импортировано #{successCount} товаров (#{errorCount} ошибок)"
|
|
|
- .catch (error) =>
|
|
|
- @importResults =
|
|
|
- success: false
|
|
|
- error: error.message
|
|
|
- processed: 0
|
|
|
- errors: [error.message]
|
|
|
-
|
|
|
- @importing = false
|
|
|
- @showNotification "Ошибка импорта: #{error.message}", 'error'
|
|
|
+ debug.log '📦 Начало импорта товаров...'
|
|
|
+
|
|
|
+ @readFile(@selectedFile)
|
|
|
+ .then (text) =>
|
|
|
+ # Парсим CSV
|
|
|
+ results = Papa.parse(text, {
|
|
|
+ header: true
|
|
|
+ delimiter: ';'
|
|
|
+ skipEmptyLines: true
|
|
|
+ encoding: 'UTF-8'
|
|
|
+ })
|
|
|
|
|
|
- catch error
|
|
|
- @importResults =
|
|
|
- success: false
|
|
|
- error: error.message
|
|
|
- processed: 0
|
|
|
- errors: [error.message]
|
|
|
-
|
|
|
- @importing = false
|
|
|
- @showNotification "Ошибка обработки файла: #{error.message}", 'error'
|
|
|
-
|
|
|
- reader.readAsText(@selectedFile, 'UTF-8')
|
|
|
-
|
|
|
- # Исправленная последовательная обработка товаров
|
|
|
- processProductsSequentially: (products) ->
|
|
|
- return new Promise (resolve, reject) =>
|
|
|
- results = []
|
|
|
- currentIndex = 0
|
|
|
+ # Фильтруем валидные строки
|
|
|
+ validProducts = results.data.filter (row, index) =>
|
|
|
+ row and row['Артикул*'] and row['Название товара'] and row['Цена, руб.*']
|
|
|
|
|
|
- processNextProduct = =>
|
|
|
- if currentIndex >= products.length
|
|
|
- debug.log "✅ Все товары обработаны. Успешно: #{results.filter((r) -> r.success).length}, Ошибок: #{results.filter((r) -> !r.success).length}"
|
|
|
- resolve(results)
|
|
|
- return
|
|
|
-
|
|
|
- product = products[currentIndex]
|
|
|
- currentIndex++
|
|
|
- debug.log "🔧 Обработка товара #{currentIndex}/#{products.length}: #{product['Название товара']?.substring(0, 50)}..."
|
|
|
-
|
|
|
- @transformProductData(product, currentIndex)
|
|
|
- .then (productData) =>
|
|
|
- debug.log "✅ Данные товара преобразованы: #{productData.sku}"
|
|
|
- # Сохраняем каждый товар в отдельный документ
|
|
|
- return @saveProductToDB(productData)
|
|
|
- .then (savedProduct) =>
|
|
|
- debug.log "✅ Товар сохранен в БД: #{savedProduct.sku}"
|
|
|
- # Затем обрабатываем изображения для сохраненного товара
|
|
|
- return @processProductImages(product, savedProduct)
|
|
|
- .then (finalProduct) =>
|
|
|
- @processedCount = currentIndex
|
|
|
- @importProgress = Math.round((currentIndex / products.length) * 100)
|
|
|
- results.push(success: true, product: finalProduct)
|
|
|
- debug.log "✅ Товар полностью обработан: #{finalProduct.sku}"
|
|
|
- processNextProduct()
|
|
|
- .catch (error) =>
|
|
|
- debug.log "❌ Ошибка обработки товара #{currentIndex}:", error
|
|
|
- @processedCount = currentIndex
|
|
|
- @importProgress = Math.round((currentIndex / products.length) * 100)
|
|
|
- results.push(success: false, error: error.message, product: product)
|
|
|
- # Продолжаем обработку следующих товаров даже при ошибке
|
|
|
- debug.log "➡️ Продолжение обработки следующих товаров..."
|
|
|
- processNextProduct()
|
|
|
+ debug.log "📊 Найдено валидных товаров: #{validProducts.length}"
|
|
|
|
|
|
- debug.log "🚀 Запуск последовательной обработки #{products.length} товаров"
|
|
|
- processNextProduct()
|
|
|
-
|
|
|
- # Сохранение товара в БД с правильной обработкой ревизий
|
|
|
- saveProductToDB: (productData) ->
|
|
|
- return new Promise (resolve, reject) =>
|
|
|
- debug.log "💾 Попытка сохранения товара: #{productData.sku}"
|
|
|
- # Сначала пытаемся получить существующий документ
|
|
|
- PouchDB.getDocument(productData._id)
|
|
|
- .then (existingDoc) =>
|
|
|
- # Документ существует - обновляем
|
|
|
- debug.log "🔄 Обновление существующего товара: #{productData.sku}"
|
|
|
- # Сохраняем только данные, без _rev (PouchDB сам обработает)
|
|
|
- updatedData = Object.assign {}, productData
|
|
|
- delete updatedData._rev
|
|
|
- updatedData.updatedAt = new Date().toISOString()
|
|
|
- return PouchDB.saveToRemote(updatedData)
|
|
|
- .catch (error) =>
|
|
|
- if error.status == 404
|
|
|
- # Документ не существует - создаем новый
|
|
|
- debug.log "🆕 Создание нового товара: #{productData.sku}"
|
|
|
- productData.createdAt = new Date().toISOString()
|
|
|
- productData.updatedAt = productData.createdAt
|
|
|
- return PouchDB.saveToRemote(productData)
|
|
|
- else
|
|
|
- throw error
|
|
|
- .then (result) =>
|
|
|
- # Получаем обновленный документ с правильным _rev
|
|
|
- debug.log "✅ Товар сохранен, получение обновленной версии: #{productData.sku}"
|
|
|
- return PouchDB.getDocument(productData._id)
|
|
|
- .then (savedDoc) =>
|
|
|
- debug.log "✅ Документ получен с актуальным _rev: #{savedDoc._rev?.substring(0, 10)}..."
|
|
|
- resolve(savedDoc)
|
|
|
- .catch (error) =>
|
|
|
- debug.log "❌ Ошибка сохранения товара #{productData.sku}:", error
|
|
|
- reject(error)
|
|
|
-
|
|
|
- # Обработка изображений после сохранения основного документа
|
|
|
- processProductImages: (product, savedProduct) ->
|
|
|
- debug.log "🖼️ Начало обработки изображений для товара: #{savedProduct.sku}"
|
|
|
- promises = []
|
|
|
-
|
|
|
- # Обработка основного изображения
|
|
|
- if product['Ссылка на главное фото*']
|
|
|
- imageUrl = product['Ссылка на главное фото*'].trim()
|
|
|
- if imageUrl
|
|
|
- debug.log "📸 Загрузка основного изображения: #{imageUrl}"
|
|
|
- promises.push @downloadAndStoreImage(imageUrl, savedProduct._id, 'main.jpg')
|
|
|
- .then (attachmentInfo) =>
|
|
|
- debug.log "✅ Основное изображение загружено, обновление товара"
|
|
|
- savedProduct.image = "/d/braer_color_shop/#{savedProduct._id}/main.jpg"
|
|
|
- return PouchDB.saveToRemote(savedProduct)
|
|
|
- .catch (error) =>
|
|
|
- debug.log "❌ Ошибка загрузки основного изображения:", error
|
|
|
- return savedProduct
|
|
|
- else
|
|
|
- debug.log "⏭️ Основное изображение отсутствует"
|
|
|
-
|
|
|
- # Обработка дополнительных изображений
|
|
|
- if product['Ссылки на дополнительные фото']
|
|
|
- additionalImages = product['Ссылки на дополнительные фото']
|
|
|
- if typeof additionalImages == 'string'
|
|
|
- imageUrls = additionalImages.split('\n').filter((url) -> url.trim())
|
|
|
- else
|
|
|
- imageUrls = []
|
|
|
+ if validProducts.length == 0
|
|
|
+ throw new Error('Не найдено валидных товаров для импорта')
|
|
|
|
|
|
- debug.log "🖼️ Найдено дополнительных изображений: #{imageUrls.length}"
|
|
|
+ # Создаем массив для обещаний
|
|
|
+ importPromises = []
|
|
|
+ processedCount = 0
|
|
|
+ errors = []
|
|
|
|
|
|
- if imageUrls.length > 0
|
|
|
- savedProduct.additionalImages = []
|
|
|
- for imageUrl, i in imageUrls.slice(0, 3)
|
|
|
- do (imageUrl, i) =>
|
|
|
- debug.log "📸 Загрузка дополнительного изображения #{i}: #{imageUrl}"
|
|
|
- promise = @downloadAndStoreImage(imageUrl.trim(), savedProduct._id, "additional-#{i}.jpg")
|
|
|
- .then (attachmentInfo) =>
|
|
|
- imagePath = "/d/braer_color_shop/#{savedProduct._id}/additional-#{i}.jpg"
|
|
|
- savedProduct.additionalImages.push(imagePath)
|
|
|
- debug.log "✅ Дополнительное изображение #{i} загружено"
|
|
|
- return savedProduct
|
|
|
- .catch (error) =>
|
|
|
- debug.log "❌ Ошибка загрузки дополнительного изображения #{i}:", error
|
|
|
- return savedProduct
|
|
|
+ # Обрабатываем каждый товар
|
|
|
+ validProducts.forEach (product, index) =>
|
|
|
+ promise = =>
|
|
|
+ debug.log "🔧 Обработка товара #{index + 1}/#{validProducts.length}: #{product['Название товара']?.substring(0, 50)}..."
|
|
|
+
|
|
|
+ try
|
|
|
+ # Преобразуем данные CSV в объект товара
|
|
|
+ productData = @transformProductData(product, index)
|
|
|
+ return Promise.resolve(null) unless productData
|
|
|
|
|
|
- promises.push(promise)
|
|
|
-
|
|
|
- if promises.length == 0
|
|
|
- debug.log "⏭️ Нет изображений для загрузки"
|
|
|
- return Promise.resolve(savedProduct)
|
|
|
-
|
|
|
- debug.log "⏳ Ожидание загрузки #{promises.length} изображений..."
|
|
|
- return Promise.all(promises)
|
|
|
- .then =>
|
|
|
- debug.log "✅ Все изображения загружены, обновление документа"
|
|
|
- # Обновляем документ с информацией об изображениях
|
|
|
- return PouchDB.saveToRemote(savedProduct)
|
|
|
- .then (result) =>
|
|
|
- debug.log "✅ Документ обновлен с информацией об изображениях"
|
|
|
- return PouchDB.getDocument(savedProduct._id)
|
|
|
- .catch (error) =>
|
|
|
- debug.log "❌ Ошибка обновления товара с изображениями:", error
|
|
|
- return savedProduct
|
|
|
-
|
|
|
- # Полное преобразование данных товара с правильной структурой
|
|
|
- transformProductData: (product, index) ->
|
|
|
- return new Promise (resolve, reject) =>
|
|
|
- try
|
|
|
- # Генерируем ID на основе артикула для постоянства
|
|
|
- sku = product['Артикул*']?.trim() or "SKU-#{Date.now()}-#{index}"
|
|
|
- productId = "product:#{sku}"
|
|
|
-
|
|
|
- debug.log "🔄 Преобразование данных товара #{index}: #{sku}"
|
|
|
+ # Обрабатываем категорию
|
|
|
+ categoryName = product['Тип*']?.trim() or 'Другое'
|
|
|
+ existingCategory = @categories.find (cat) -> cat.name == categoryName
|
|
|
+
|
|
|
+ if not existingCategory
|
|
|
+ debug.log "🏷️ Создание категории: #{categoryName}"
|
|
|
+ return @createCategory(categoryName)
|
|
|
+ .then (categorySlug) =>
|
|
|
+ productData.category = categorySlug
|
|
|
+ # Перезагружаем категории
|
|
|
+ @loadCategories()
|
|
|
+ return productData
|
|
|
+ .catch (categoryError) =>
|
|
|
+ debug.log "⚠️ Не удалось создать категорию, используется 'Другое'"
|
|
|
+ productData.category = 'drugoe'
|
|
|
+ return productData
|
|
|
+ else
|
|
|
+ return Promise.resolve(productData)
|
|
|
+ catch transformError
|
|
|
+ debug.log "❌ Ошибка преобразования товара:", transformError
|
|
|
+ errors.push("Товар #{index + 1}: #{transformError.message}")
|
|
|
+ return Promise.resolve(null)
|
|
|
+ .then (productData) =>
|
|
|
+ return null unless productData
|
|
|
+
|
|
|
+ # Обрабатываем изображения
|
|
|
+ return @processProductImages(productData, product)
|
|
|
+ .then (productWithImages) =>
|
|
|
+ # Сохраняем товар
|
|
|
+ return @saveProduct(productWithImages)
|
|
|
+ .then (savedProduct) =>
|
|
|
+ processedCount++
|
|
|
+ @importProgress = Math.round((processedCount / validProducts.length) * 100)
|
|
|
+ debug.log "✅ Обработан товар #{processedCount}/#{validProducts.length}: #{savedProduct.sku}"
|
|
|
+ return savedProduct
|
|
|
+ .catch (saveError) =>
|
|
|
+ errorMsg = "Товар #{index + 1} (#{productData.sku}): #{saveError.message}"
|
|
|
+ debug.log "❌ Ошибка обработки товара #{index + 1}:", saveError
|
|
|
+ errors.push(errorMsg)
|
|
|
+ return null
|
|
|
+
|
|
|
+ importPromises.push(promise())
|
|
|
+
|
|
|
+ # Ожидаем завершения всех операций
|
|
|
+ return Promise.allSettled(importPromises)
|
|
|
+ .then (results) =>
|
|
|
+ successfulImports = results.filter((r) -> r.status == 'fulfilled' and r.value).length
|
|
|
+ failedImports = results.filter((r) -> r.status == 'rejected').length
|
|
|
|
|
|
- # Базовые поля согласно design документам
|
|
|
- productData =
|
|
|
- _id: productId
|
|
|
- type: 'product'
|
|
|
- name: product['Название товара']?.trim() or 'Без названия'
|
|
|
- sku: sku
|
|
|
- price: @parsePrice(product['Цена, руб.*'])
|
|
|
- active: true
|
|
|
- createdAt: new Date().toISOString()
|
|
|
- updatedAt: new Date().toISOString()
|
|
|
- domains: [window.location.hostname]
|
|
|
- additionalImages: []
|
|
|
- attributes: {}
|
|
|
- tags: []
|
|
|
+ @importResults = {
|
|
|
+ success: true
|
|
|
+ processed: validProducts.length
|
|
|
+ successful: successfulImports
|
|
|
+ failed: failedImports
|
|
|
+ errors: errors
|
|
|
+ }
|
|
|
|
|
|
- # Обработка всех полей CSV в единую структуру attributes
|
|
|
- @processAllCSVFields(product, productData)
|
|
|
+ debug.log "🎉 Импорт завершен: #{successfulImports} успешно, #{failedImports} с ошибками"
|
|
|
|
|
|
- # Обработка категории
|
|
|
- @processCategory(product, productData, index)
|
|
|
- .then =>
|
|
|
- # Обработка Rich-контента
|
|
|
- @processRichContent(product, productData)
|
|
|
- debug.log "✅ Данные товара полностью преобразованы: #{productData.sku}"
|
|
|
- resolve(productData)
|
|
|
- .catch (error) =>
|
|
|
- debug.log "⚠️ Ошибка обработки товара, возвращаем частичные данные:", error
|
|
|
- # Возвращаем товар даже с ошибками обработки
|
|
|
- resolve(productData)
|
|
|
-
|
|
|
- catch error
|
|
|
- debug.log "❌ Критическая ошибка преобразования данных товара:", error
|
|
|
- reject(error)
|
|
|
-
|
|
|
- # Парсинг цены
|
|
|
- parsePrice: (priceString) ->
|
|
|
- return 0 if !priceString
|
|
|
- try
|
|
|
- # Удаляем пробелы и заменяем запятые на точки
|
|
|
- cleanPrice = priceString.toString().replace(/\s/g, '').replace(',', '.')
|
|
|
- price = parseFloat(cleanPrice)
|
|
|
- return if isNaN(price) then 0 else Math.round(price * 100) / 100
|
|
|
- catch
|
|
|
- return 0
|
|
|
-
|
|
|
- # Обработка всех полей CSV в единую структуру attributes
|
|
|
- processAllCSVFields: (product, productData) ->
|
|
|
- # Базовые поля
|
|
|
- if product['Бренд*']?.trim()
|
|
|
- productData.brand = product['Бренд*']?.trim()
|
|
|
-
|
|
|
- if product['Аннотация']?.trim()
|
|
|
- productData.description = product['Аннотация']?.trim()
|
|
|
-
|
|
|
- # Цены
|
|
|
- if product['Цена до скидки, руб.']
|
|
|
- productData.oldPrice = @parsePrice(product['Цена до скидки, руб.'])
|
|
|
-
|
|
|
- # Основные характеристики с оригинальными названиями
|
|
|
- @setAttribute productData, 'Вес товара, г', product
|
|
|
- @setAttribute productData, 'Объем, л', product
|
|
|
- @setAttribute productData, 'Страна-изготовитель', product
|
|
|
- @setAttribute productData, 'Гарантия', product
|
|
|
- @setAttribute productData, 'Цвет товара', product
|
|
|
- @setAttribute productData, 'Название цвета', product
|
|
|
- @setAttribute productData, 'Класс опасности товара*', product
|
|
|
- @setAttribute productData, 'Степень блеска покрытия', product
|
|
|
- @setAttribute productData, 'Работы', product
|
|
|
- @setAttribute productData, 'Количество товара в УЕИ', product
|
|
|
-
|
|
|
- # Технические характеристики
|
|
|
- @setAttribute productData, 'Расход, л/м2', product
|
|
|
- @setAttribute productData, 'Время высыхания, часов', product
|
|
|
- @setAttribute productData, 'Вид краски', product
|
|
|
- @setAttribute productData, 'Основа краски', product
|
|
|
- @setAttribute productData, 'Способ нанесения', product
|
|
|
- @setAttribute productData, 'Область применения состава', product
|
|
|
- @setAttribute productData, 'Назначение грунтовки', product
|
|
|
- @setAttribute productData, 'Рекомендуемое количество слоев', product
|
|
|
- @setAttribute productData, 'Расход, кг/м2', product
|
|
|
- @setAttribute productData, 'Количество компонентов', product
|
|
|
- @setAttribute productData, 'Особенности ЛКМ', product
|
|
|
- @setAttribute productData, 'Макс. температура эксплуатации, С°', product
|
|
|
- @setAttribute productData, 'Материал основания', product
|
|
|
- @setAttribute productData, 'Основа грунтовки', product
|
|
|
- @setAttribute productData, 'Форма выпуска средства', product
|
|
|
- @setAttribute productData, 'Назначение', product
|
|
|
- @setAttribute productData, 'Тип помещения', product
|
|
|
- @setAttribute productData, 'Вид выпуска товара', product
|
|
|
- @setAttribute productData, 'Тип растворителя', product
|
|
|
- @setAttribute productData, 'Эффект краски', product
|
|
|
- @setAttribute productData, 'Марка эмали', product
|
|
|
- @setAttribute productData, 'Базис', product
|
|
|
- @setAttribute productData, 'Помещение', product
|
|
|
-
|
|
|
- # Флаги и булевы значения (объединены с attributes)
|
|
|
- @setBooleanAttribute productData, 'Рассрочка', product
|
|
|
- @setBooleanAttribute productData, 'Баллы за отзывы', product
|
|
|
- @setBooleanAttribute productData, 'Возможность колеровки', product
|
|
|
- @setBooleanAttribute productData, 'Аэрозоль', product
|
|
|
- @setBooleanAttribute productData, 'Можно мыть', product
|
|
|
-
|
|
|
- # Мета-данные
|
|
|
- if product['#Хештеги']
|
|
|
- tags = product['#Хештеги']?.split('#').filter((tag) -> tag.trim()).map((tag) -> tag.trim())
|
|
|
- productData.tags = tags or []
|
|
|
+ if successfulImports > 0
|
|
|
+ @showNotification "Импортировано #{successfulImports} товаров"
|
|
|
+ @loadProducts() # Перезагружаем список
|
|
|
+ else
|
|
|
+ @showNotification 'Не удалось импортировать ни одного товара', 'error'
|
|
|
+ .catch (error) =>
|
|
|
+ debug.log '❌ Ошибка импорта:', error
|
|
|
+ @importResults = {
|
|
|
+ success: false
|
|
|
+ error: error.message
|
|
|
+ processed: 0
|
|
|
+ successful: 0
|
|
|
+ failed: 0
|
|
|
+ errors: [error.message]
|
|
|
+ }
|
|
|
+ @showNotification "Ошибка импорта: #{error.message}", 'error'
|
|
|
+ .finally =>
|
|
|
+ @importing = false
|
|
|
+ @selectedFile = null
|
|
|
|
|
|
- # Вспомогательный метод для установки атрибута
|
|
|
- setAttribute: (productData, fieldName, product) ->
|
|
|
- if product[fieldName]?.trim()
|
|
|
- # Сохраняем оригинальное название поля
|
|
|
- productData.attributes[fieldName] = product[fieldName]?.trim()
|
|
|
+ editProduct: (product) ->
|
|
|
+ @currentProduct = Object.assign({}, product)
|
|
|
+ @showProductModal = true
|
|
|
|
|
|
- # Вспомогательный метод для установки булевых атрибутов
|
|
|
- setBooleanAttribute: (productData, fieldName, product) ->
|
|
|
- if product[fieldName]
|
|
|
- value = product[fieldName]?.toLowerCase()
|
|
|
- productData.attributes[fieldName] = value == 'да'
|
|
|
+ deleteProduct: (product) ->
|
|
|
+ if confirm("Удалить товар \"#{product.name}\"?")
|
|
|
+ PouchDB.localDb.remove(product)
|
|
|
+ .then =>
|
|
|
+ PouchDB.saveToRemote(product) # Удаляем из удаленной БД
|
|
|
+ .then =>
|
|
|
+ @showNotification 'Товар удален'
|
|
|
+ @loadProducts()
|
|
|
+ .catch (error) =>
|
|
|
+ debug.log '❌ Ошибка удаления товара из удаленной БД:', error
|
|
|
+ @showNotification 'Ошибка удаления товара', 'error'
|
|
|
+ .catch (error) =>
|
|
|
+ debug.log '❌ Ошибка удаления товара:', error
|
|
|
+ @showNotification 'Ошибка удаления товара', 'error'
|
|
|
|
|
|
- # Обработка категории с проверкой дубликатов
|
|
|
- processCategory: (product, productData, index) ->
|
|
|
- return Promise.resolve() if !product['Тип*']
|
|
|
-
|
|
|
- categoryName = product['Тип*'].trim()
|
|
|
- return Promise.resolve() if !categoryName
|
|
|
-
|
|
|
- debug.log "🔍 Поиск категории: #{categoryName}"
|
|
|
+ saveProductForm: ->
|
|
|
+ unless @currentProduct.name and @currentProduct.sku and @currentProduct.price
|
|
|
+ @showNotification 'Заполните обязательные поля', 'error'
|
|
|
+ return
|
|
|
|
|
|
- # Поиск существующей категории (регистронезависимо)
|
|
|
- existingCategory = @categories.find (cat) ->
|
|
|
- cat.name?.toLowerCase() == categoryName.toLowerCase()
|
|
|
+ productData = Object.assign({}, @currentProduct)
|
|
|
|
|
|
- if existingCategory
|
|
|
- productData.category = existingCategory._id
|
|
|
- debug.log "✅ Использована существующая категория: #{categoryName}"
|
|
|
- return Promise.resolve()
|
|
|
+ if productData._id
|
|
|
+ # Обновление существующего товара
|
|
|
+ productData.updatedAt = new Date().toISOString()
|
|
|
else
|
|
|
- # Создание новой категории
|
|
|
- categoryId = "category:#{Date.now()}-#{index}"
|
|
|
- newCategory =
|
|
|
- _id: categoryId
|
|
|
- type: 'category'
|
|
|
- name: categoryName
|
|
|
- slug: @generateSlug(categoryName)
|
|
|
- sortOrder: @categories.length
|
|
|
- active: true
|
|
|
- createdAt: new Date().toISOString()
|
|
|
- updatedAt: new Date().toISOString()
|
|
|
- domains: [window.location.hostname]
|
|
|
-
|
|
|
- productData.category = categoryId
|
|
|
-
|
|
|
- debug.log "🆕 Создание новой категории: #{categoryName}"
|
|
|
- return PouchDB.saveToRemote(newCategory)
|
|
|
- .then (result) =>
|
|
|
- debug.log "✅ Создана новая категория: #{categoryName}"
|
|
|
- @categories.push(newCategory)
|
|
|
- return Promise.resolve()
|
|
|
- .catch (error) ->
|
|
|
- debug.log "❌ Ошибка создания категории #{categoryName}:", error
|
|
|
- # Продолжаем без категории
|
|
|
- return Promise.resolve()
|
|
|
-
|
|
|
- # Обработка Rich-контента JSON и преобразование в Markdown
|
|
|
- processRichContent: (product, productData) ->
|
|
|
- # Сначала пробуем Rich-контент JSON
|
|
|
- if product['Rich-контент JSON'] and product['Rich-контент JSON'].trim()
|
|
|
- try
|
|
|
- richContent = JSON.parse(product['Rich-контент JSON'])
|
|
|
- markdownDescription = @richContentToMarkdown(richContent)
|
|
|
- # Если получили Markdown, используем его как описание
|
|
|
- if markdownDescription and markdownDescription != 'Описание товара'
|
|
|
- productData.description = markdownDescription
|
|
|
- productData.attributes['Rich-контент JSON'] = product['Rich-контент JSON']
|
|
|
- return
|
|
|
- catch error
|
|
|
- debug.log "❌ Ошибка парсинга Rich-контента:", error
|
|
|
-
|
|
|
- # Если Rich-контент невалиден или отсутствует, используем аннотацию
|
|
|
- if product['Аннотация'] and product['Аннотация'].trim() and !productData.description
|
|
|
- productData.description = product['Аннотация'].trim()
|
|
|
-
|
|
|
- # Преобразование Rich-контента JSON в Markdown
|
|
|
- richContentToMarkdown: (richContent) ->
|
|
|
- return '' if !richContent
|
|
|
-
|
|
|
- try
|
|
|
- markdownParts = []
|
|
|
-
|
|
|
- if richContent.content and Array.isArray(richContent.content)
|
|
|
- for item in richContent.content
|
|
|
- markdownParts.push(@processContentItem(item))
|
|
|
-
|
|
|
- result = markdownParts.filter((part) -> part).join('\n\n')
|
|
|
- return result or 'Описание товара'
|
|
|
- catch error
|
|
|
- debug.log "❌ Ошибка преобразования Rich-контента в Markdown:", error
|
|
|
- return ''
|
|
|
-
|
|
|
- # Обработка отдельного элемента контента
|
|
|
- processContentItem: (item) ->
|
|
|
- return '' if !item
|
|
|
-
|
|
|
- switch item.widgetName
|
|
|
- when 'raTextBlock'
|
|
|
- return @processTextBlock(item)
|
|
|
- when 'raHeader'
|
|
|
- return @processHeader(item)
|
|
|
- when 'raImage'
|
|
|
- return @processImage(item)
|
|
|
- when 'raList'
|
|
|
- return @processList(item)
|
|
|
- else
|
|
|
- return ''
|
|
|
-
|
|
|
- # Обработка текстового блока
|
|
|
- processTextBlock: (item) ->
|
|
|
- textParts = []
|
|
|
-
|
|
|
- # Заголовок
|
|
|
- if item.title and item.title.items
|
|
|
- titleText = @processTextItems(item.title.items)
|
|
|
- if titleText
|
|
|
- level = item.title.size or 'size3'
|
|
|
- hashes = @getHeadingLevel(level)
|
|
|
- textParts.push("#{hashes} #{titleText}")
|
|
|
-
|
|
|
- # Основной текст
|
|
|
- if item.text and item.text.items
|
|
|
- bodyText = @processTextItems(item.text.items)
|
|
|
- if bodyText
|
|
|
- textParts.push(bodyText)
|
|
|
-
|
|
|
- return textParts.join('\n\n')
|
|
|
-
|
|
|
- # Обработка заголовка
|
|
|
- processHeader: (item) ->
|
|
|
- if item.text and item.text.items
|
|
|
- headerText = @processTextItems(item.text.items)
|
|
|
- if headerText
|
|
|
- level = item.size or 'size2'
|
|
|
- hashes = @getHeadingLevel(level)
|
|
|
- return "#{hashes} #{headerText}"
|
|
|
- return ''
|
|
|
-
|
|
|
- # Обработка изображения
|
|
|
- processImage: (item) ->
|
|
|
- if item.url
|
|
|
- altText = item.alt or 'Изображение товара'
|
|
|
- return ""
|
|
|
- return ''
|
|
|
-
|
|
|
- # Обработка списка
|
|
|
- processList: (item) ->
|
|
|
- return '' if !item.items or !Array.isArray(item.items)
|
|
|
-
|
|
|
- listItems = []
|
|
|
- for listItem in item.items
|
|
|
- if listItem.content
|
|
|
- listItems.push("- #{listItem.content}")
|
|
|
-
|
|
|
- return listItems.join('\n')
|
|
|
-
|
|
|
- # Обработка текстовых элементов
|
|
|
- processTextItems: (items) ->
|
|
|
- return '' if !items or !Array.isArray(items)
|
|
|
-
|
|
|
- textParts = []
|
|
|
- for textItem in items
|
|
|
- if textItem.type == 'text' and textItem.content
|
|
|
- content = textItem.content
|
|
|
-
|
|
|
- # Обработка форматирования
|
|
|
- if textItem.formatting
|
|
|
- if textItem.formatting.bold
|
|
|
- content = "**#{content}**"
|
|
|
- if textItem.formatting.italic
|
|
|
- content = "*#{content}*"
|
|
|
-
|
|
|
- textParts.push(content)
|
|
|
-
|
|
|
- else if textItem.type == 'br'
|
|
|
- textParts.push('\n')
|
|
|
+ # Создание нового товара
|
|
|
+ productData._id = "product:#{productData.sku}"
|
|
|
+ productData.type = 'product'
|
|
|
+ productData.createdAt = new Date().toISOString()
|
|
|
+ productData.updatedAt = productData.createdAt
|
|
|
|
|
|
- return textParts.join('')
|
|
|
+ @saveProduct(productData)
|
|
|
+ .then (result) =>
|
|
|
+ @showProductModal = false
|
|
|
+ @resetCurrentProduct()
|
|
|
+ @showNotification 'Товар сохранен'
|
|
|
+ @loadProducts()
|
|
|
+ .catch (error) =>
|
|
|
+ debug.log '❌ Ошибка сохранения товара:', error
|
|
|
+ @showNotification 'Ошибка сохранения товара', 'error'
|
|
|
+
|
|
|
+ resetCurrentProduct: ->
|
|
|
+ @currentProduct = {
|
|
|
+ _id: ''
|
|
|
+ type: 'product'
|
|
|
+ name: ''
|
|
|
+ sku: ''
|
|
|
+ price: 0
|
|
|
+ oldPrice: 0
|
|
|
+ category: ''
|
|
|
+ description: ''
|
|
|
+ active: true
|
|
|
+ domains: @availableDomains
|
|
|
+ attributes: {}
|
|
|
+ images: []
|
|
|
+ createdAt: ''
|
|
|
+ updatedAt: ''
|
|
|
+ }
|
|
|
|
|
|
- # Получение уровня заголовка Markdown
|
|
|
- getHeadingLevel: (size) ->
|
|
|
- switch size
|
|
|
- when 'size1', 'size5' then '#' # H1
|
|
|
- when 'size2', 'size4' then '##' # H2
|
|
|
- when 'size3' then '###' # H3
|
|
|
- else '##' # H2 по умолчанию
|
|
|
+ showNotification: (message, type = 'success') ->
|
|
|
+ @$root.showNotification(message, type)
|
|
|
|
|
|
- # Генерация slug
|
|
|
- generateSlug: (text) ->
|
|
|
- return '' if !text
|
|
|
- text.toLowerCase()
|
|
|
- .replace(/\s+/g, '-')
|
|
|
- .replace(/[^\w\-]+/g, '')
|
|
|
- .replace(/\-\-+/g, '-')
|
|
|
- .replace(/^-+/, '')
|
|
|
- .replace(/-+$/, '')
|
|
|
+ handleFileSelect: (event) ->
|
|
|
+ @selectedFile = event.target.files[0]
|
|
|
+ debug.log "📁 Выбран файл: #{@selectedFile?.name}"
|
|
|
|
|
|
- # Массовые действия
|
|
|
- toggleSelectAll: ->
|
|
|
- if @selectAll
|
|
|
+ toggleAllProducts: (event) ->
|
|
|
+ if event.target.checked
|
|
|
@selectedProducts = @filteredProducts.map (product) -> product._id
|
|
|
else
|
|
|
@selectedProducts = []
|
|
|
-
|
|
|
- isProductSelected: (productId) ->
|
|
|
- @selectedProducts.includes(productId)
|
|
|
-
|
|
|
- toggleProductSelection: (productId) ->
|
|
|
- if @isProductSelected(productId)
|
|
|
- @selectedProducts = @selectedProducts.filter (id) -> id != productId
|
|
|
- else
|
|
|
- @selectedProducts.push(productId)
|
|
|
-
|
|
|
- clearSelection: ->
|
|
|
- @selectedProducts = []
|
|
|
- @selectAll = false
|
|
|
-
|
|
|
- toggleProductStatus: (product) ->
|
|
|
- updatedProduct = Object.assign {}, product,
|
|
|
- active: !product.active
|
|
|
- updatedAt: new Date().toISOString()
|
|
|
-
|
|
|
- PouchDB.saveToRemote(updatedProduct)
|
|
|
- .then (result) =>
|
|
|
- @loadProducts()
|
|
|
- @showNotification "Товар #{if product.active then 'деактивирован' else 'активирован'}"
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка изменения статуса товара:', error
|
|
|
- @showNotification 'Ошибка изменения статуса товара', 'error'
|
|
|
-
|
|
|
- activateSelected: ->
|
|
|
- if @selectedProducts.length == 0
|
|
|
- @showNotification 'Выберите товары для активации', 'error'
|
|
|
- return
|
|
|
-
|
|
|
- promises = @selectedProducts.map (productId) =>
|
|
|
- product = @products.find (p) -> p._id == productId
|
|
|
- if product and !product.active
|
|
|
- updatedProduct = Object.assign {}, product,
|
|
|
- active: true
|
|
|
- updatedAt: new Date().toISOString()
|
|
|
-
|
|
|
- PouchDB.saveToRemote(updatedProduct)
|
|
|
-
|
|
|
- Promise.all(promises)
|
|
|
- .then (results) =>
|
|
|
- @loadProducts()
|
|
|
- @clearSelection()
|
|
|
- @showNotification "Активировано #{results.length} товаров"
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка активации товаров:', error
|
|
|
- @showNotification 'Ошибка активации товаров', 'error'
|
|
|
-
|
|
|
- deactivateSelected: ->
|
|
|
- if @selectedProducts.length == 0
|
|
|
- @showNotification 'Выберите товары для деактивации', 'error'
|
|
|
- return
|
|
|
-
|
|
|
- promises = @selectedProducts.map (productId) =>
|
|
|
- product = @products.find (p) -> p._id == productId
|
|
|
- if product and product.active
|
|
|
- updatedProduct = Object.assign {}, product,
|
|
|
- active: false
|
|
|
- updatedAt: new Date().toISOString()
|
|
|
-
|
|
|
- PouchDB.saveToRemote(updatedProduct)
|
|
|
-
|
|
|
- Promise.all(promises)
|
|
|
- .then (results) =>
|
|
|
- @loadProducts()
|
|
|
- @clearSelection()
|
|
|
- @showNotification "Деактивировано #{results.length} товаров"
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка деактивации товаров:', error
|
|
|
- @showNotification 'Ошибка деактивации товаров', 'error'
|
|
|
-
|
|
|
- deleteSelected: ->
|
|
|
- if @selectedProducts.length == 0
|
|
|
- @showNotification 'Выберите товары для удаления', 'error'
|
|
|
- return
|
|
|
-
|
|
|
- if !confirm("Вы уверены, что хотите удалить #{@selectedProducts.length} товаров?")
|
|
|
- return
|
|
|
-
|
|
|
- promises = @selectedProducts.map (productId) =>
|
|
|
- PouchDB.getDocument(productId)
|
|
|
- .then (doc) ->
|
|
|
- PouchDB.saveToRemote(Object.assign {}, doc, { _deleted: true })
|
|
|
-
|
|
|
- Promise.all(promises)
|
|
|
- .then (results) =>
|
|
|
- @loadProducts()
|
|
|
- @clearSelection()
|
|
|
- @showNotification "Удалено #{results.length} товаров"
|
|
|
- .catch (error) =>
|
|
|
- debug.log 'Ошибка удаления товаров:', error
|
|
|
- @showNotification 'Ошибка удаления товаров', 'error'
|
|
|
-
|
|
|
- showNotification: (message, type = 'success') ->
|
|
|
- if @$root.showNotification?
|
|
|
- @$root.showNotification(message, type)
|
|
|
- else
|
|
|
- debug.log("#{type}: #{message}")
|
|
|
-
|
|
|
- mounted: ->
|
|
|
- @loadProducts()
|
|
|
- @loadCategories()
|
|
|
- @loadDomains()
|