diff --git a/packages/apps/tiny-erp/src/integration/export-order-to-tiny.ts b/packages/apps/tiny-erp/src/integration/export-order-to-tiny.ts index e083546e8..5ba444a21 100644 --- a/packages/apps/tiny-erp/src/integration/export-order-to-tiny.ts +++ b/packages/apps/tiny-erp/src/integration/export-order-to-tiny.ts @@ -66,12 +66,36 @@ export default async (apiDoc, queueEntry, appData, canCreateNew) => { logger.info(`${orderId} skipped with status "${tinyStatus}"`); return null; } + + if (appData.ready_for_shipping_only) { + switch (tinyStatus) { + case 'aberto': + case 'cancelado': + case 'aprovado': + case 'preparando_envio': + case 'faturado': + if (!order.fulfillment_status || order.fulfillment_status.current !== 'ready_for_shipping') { + logger.info(`${orderId} skipped with status "${tinyStatus}"`); + return null; + } + break; + default: + break; + } + } + const tinyOrder = parseOrder(order, appData); logger.info(`${orderId} ${JSON.stringify(tinyOrder)}`); return postTiny('/pedido.incluir.php', { pedido: { pedido: tinyOrder, }, + }).then((response) => { + const tinyErpOnNewOrder = global.$tinyErpOnNewOrder; + if (tinyErpOnNewOrder && typeof tinyErpOnNewOrder === 'function') { + return tinyErpOnNewOrder({ response, order, postTiny }); + } + return response; }); } diff --git a/packages/apps/tiny-erp/src/integration/helpers/format-tiny-date.ts b/packages/apps/tiny-erp/src/integration/helpers/format-tiny-date.ts index 4b66c6e3d..a079b4915 100644 --- a/packages/apps/tiny-erp/src/integration/helpers/format-tiny-date.ts +++ b/packages/apps/tiny-erp/src/integration/helpers/format-tiny-date.ts @@ -1,5 +1,5 @@ -export default (d: Date) => { - /* eslint-disable prefer-template */ +export default (date: Date) => { + const d = new Date(date.getTime() - (3 * 60 * 60 * 1000)); return d.getDate().toString().padStart(2, '0') + '/' + (d.getMonth() + 1).toString().padStart(2, '0') + '/' + d.getFullYear(); diff --git a/packages/apps/tiny-erp/src/integration/import-order-from-tiny.ts b/packages/apps/tiny-erp/src/integration/import-order-from-tiny.ts index c9757d347..5dd1374f4 100644 --- a/packages/apps/tiny-erp/src/integration/import-order-from-tiny.ts +++ b/packages/apps/tiny-erp/src/integration/import-order-from-tiny.ts @@ -91,9 +91,16 @@ export default async (apiDoc, queueEntry) => { ? { numeroEcommerce: tinyOrderNumber.substring(5) } : { numero: tinyOrderNumber }; return postTiny('/pedidos.pesquisa.php', filter).then(({ pedidos }) => { + let prop = 'numero'; + let tinyOrderNumberSearch = tinyOrderNumber; + if (filter && filter.numeroEcommerce) { + prop = 'numero_ecommerce'; + tinyOrderNumberSearch = tinyOrderNumber.substring(5); + } const tinyOrder = pedidos.find(({ pedido }) => { - return Number(tinyOrderNumber) === Number(pedido.numero); + return Number(tinyOrderNumberSearch) === Number(pedido[prop]); }); + if (tinyOrder) { return getTinyOrder(tinyOrder.pedido.id); } diff --git a/packages/apps/tiny-erp/src/integration/import-product-from-tiny.ts b/packages/apps/tiny-erp/src/integration/import-product-from-tiny.ts index 8a372538c..d11667c25 100644 --- a/packages/apps/tiny-erp/src/integration/import-product-from-tiny.ts +++ b/packages/apps/tiny-erp/src/integration/import-product-from-tiny.ts @@ -60,11 +60,19 @@ export default async (apiDoc, queueEntry, appData, canCreateNew, isHiddenQueue) } return null; } + + if (!product && tinyProduct && tipo === 'produto') { + return parseProduct(tinyProduct, tipo, true) + .then((bodyProduct) => { + return api.post('products', bodyProduct); + }); + } + if (!tinyProduct) { return null; } - return postTiny('/produto.obter.php', { id: tinyProduct.id }) + return postTiny('/produto.obter.php', { id: (tinyProduct.id || produtoSaldo.id) }) .then(({ produto }) => { let method; let endpoint; @@ -72,14 +80,14 @@ export default async (apiDoc, queueEntry, appData, canCreateNew, isHiddenQueue) if (productId) { method = 'PATCH'; endpoint = `products/${productId}`; - } else if (tipo === 'produto' || !tipo) { + } else if (tipo === 'produto' || appData.import_all_products) { method = 'POST'; endpoint = 'products'; } else { return null; } // @ts-ignore - return parseProduct(produto, method === 'POST').then((parsedProduct: Products) => { + return parseProduct(produto, tipo, method === 'POST').then((parsedProduct: Products) => { if (!Number.isNaN(quantity)) { parsedProduct.quantity = quantity >= 0 ? quantity : 0; } @@ -137,9 +145,13 @@ export default async (apiDoc, queueEntry, appData, canCreateNew, isHiddenQueue) variationId, })); const { tinyStockUpdate } = queueEntry; - if (tinyStockUpdate && isHiddenQueue && queueProductId) { - return handleTinyStock(tinyStockUpdate as any); + if (tinyStockUpdate && isHiddenQueue && (queueProductId || (product && product._id))) { + return handleTinyStock(tinyStockUpdate as any, tinyStockUpdate.produto); + } + if (tinyStockUpdate.tipo === 'produto' && !queueProductId) { + return handleTinyStock({ produto: {}, tipo: 'produto' }, tinyStockUpdate.produto); } + return postTiny('/produtos.pesquisa.php', { pesquisa: queueSku }) .then(({ produtos }) => { if (Array.isArray(produtos)) { diff --git a/packages/apps/tiny-erp/src/integration/parsers/order-from-tiny.ts b/packages/apps/tiny-erp/src/integration/parsers/order-from-tiny.ts index c184a9cfd..8159eef60 100644 --- a/packages/apps/tiny-erp/src/integration/parsers/order-from-tiny.ts +++ b/packages/apps/tiny-erp/src/integration/parsers/order-from-tiny.ts @@ -32,11 +32,23 @@ export default (tinyOrder, shippingLines) => new Promise((resolve, reject) => { postTiny('/nota.fiscal.obter.php', { id: tinyOrder.id_nota_fiscal }) .then((tinyInvoice) => { const number = String(tinyInvoice.nota_fiscal.numero); - if (number && !shippingLine.invoices.find((invoice) => invoice.number === number)) { + const indexFromInvoice = shippingLine.invoices + .findIndex((invoice) => invoice.number === number); + + if (number && !(indexFromInvoice > -1)) { shippingLine.invoices.push({ number, serial_number: String(tinyInvoice.nota_fiscal.serie), + access_key: String(tinyInvoice.nota_fiscal.chave_acesso), }); + } else if (number && (indexFromInvoice > -1)) { + Object.assign( + shippingLine.invoices[indexFromInvoice], + { + access_key: String(tinyInvoice.nota_fiscal.chave_acesso), + serial_number: String(tinyInvoice.nota_fiscal.serie), + }, + ); } partialOrder.shipping_lines = shippingLines; resolve(partialOrder); diff --git a/packages/apps/tiny-erp/src/integration/parsers/order-to-tiny.ts b/packages/apps/tiny-erp/src/integration/parsers/order-to-tiny.ts index f1ef1a406..c14a2b330 100644 --- a/packages/apps/tiny-erp/src/integration/parsers/order-to-tiny.ts +++ b/packages/apps/tiny-erp/src/integration/parsers/order-to-tiny.ts @@ -51,7 +51,10 @@ export default (order: Orders, appData) => { if (buyer.doc_number && buyer.doc_number.length <= 18) { tinyCustomer.cpf_cnpj = buyer.doc_number; } - if (buyer.inscription_number && buyer.inscription_number.length <= 18) { + if ( + buyer.inscription_number && buyer.inscription_number.length <= 18 + && buyer.inscription_type !== 'Municipal' + ) { tinyCustomer.ie = buyer.inscription_number; } if (buyer.main_email && buyer.main_email.length <= 50) { @@ -72,7 +75,7 @@ export default (order: Orders, appData) => { }; } - if (shippingAddress && billingAddress) { + if (shippingAddress) { tinyOrder.endereco_entrega = {}; parseAddress(shippingAddress, tinyOrder.endereco_entrega); if (shippingAddress.name) { @@ -83,7 +86,7 @@ export default (order: Orders, appData) => { if (order.items) { order.items.forEach((item) => { if (item.quantity) { - const itemRef = (item.sku || item._id || Math.random().toString()).substring(0, 30); + const itemRef = (item.sku || item._id || Math.random().toString()).substring(0, 60); tinyOrder.itens.push({ item: { codigo: itemRef, @@ -124,6 +127,11 @@ export default (order: Orders, appData) => { } } + const tinyErpOrderParser = global.$tinyErpOrderParser; + if (tinyErpOrderParser && typeof tinyErpOrderParser === 'function') { + tinyErpOrderParser({ tinyOrder, order }); + } + if (order.shipping_method_label) { tinyOrder.forma_frete = order.shipping_method_label; } @@ -163,9 +171,6 @@ export default (order: Orders, appData) => { if (amount.tax) { tinyOrder.valor_frete += amount.tax; } - if (amount.extra) { - tinyOrder.valor_frete += amount.extra; - } } if (amount.discount) { tinyOrder.valor_desconto = amount.discount; diff --git a/packages/apps/tiny-erp/src/integration/parsers/product-from-tiny.ts b/packages/apps/tiny-erp/src/integration/parsers/product-from-tiny.ts index 808a1a837..d3a7bdbf6 100644 --- a/packages/apps/tiny-erp/src/integration/parsers/product-from-tiny.ts +++ b/packages/apps/tiny-erp/src/integration/parsers/product-from-tiny.ts @@ -1,4 +1,4 @@ -import type { Products } from '@cloudcommerce/types'; +import type { Products, ProductSet } from '@cloudcommerce/types'; import logger from 'firebase-functions/logger'; import axios from 'axios'; import FormData from 'form-data'; @@ -12,7 +12,11 @@ const removeAccents = (str: string) => str.replace(/áàãâÁÀÃÂ/g, 'a') .replace(/úÚ/g, 'u') .replace(/çÇ/g, 'c'); -const tryImageUpload = (originImgUrl: string, product: Products) => new Promise((resolve) => { +const tryImageUpload = ( + originImgUrl: string, + product: Products, + index?: number, +) => new Promise((resolve) => { const { storeId, apiAuth: { @@ -72,26 +76,55 @@ const tryImageUpload = (originImgUrl: string, product: Products) => new Promise( }); }).then((picture) => { if (product && product.pictures) { - // @ts-ignore - product.pictures.push(picture); + if (index === 0 || index) { + // @ts-ignore + product.pictures[index] = picture; + } else { + // @ts-ignore + product.pictures.push(picture); + } } return picture; }); -export default (tinyProduct, isNew = true) => new Promise((resolve) => { +export default ( + tinyProduct, + tipo?:string, + isNew = true, +): Promise => new Promise((resolve) => { const sku = tinyProduct.codigo || String(tinyProduct.id); const name = (tinyProduct.nome || sku).trim(); - const product: Omit = { + const isProduct = tipo === 'produto'; + const getCorrectPrice = (price) => { + return Number(price) > 0 ? Number(price) : null; + }; + const product: ProductSet = { available: tinyProduct.situacao === 'A', sku, name, - cost_price: tinyProduct.preco_custo, - price: tinyProduct.preco_promocional || tinyProduct.preco, - base_price: tinyProduct.preco, - body_html: tinyProduct.descricao_complementar, + cost_price: !isProduct ? Number(tinyProduct.preco_custo) : Number(tinyProduct.precoCusto), + price: !isProduct ? Number(tinyProduct.preco_promocional || tinyProduct.preco) + : Number(getCorrectPrice(tinyProduct.precoPromocional) || tinyProduct.preco), + base_price: Number(tinyProduct.preco), + body_html: tinyProduct.descricao_complementar || tinyProduct.descricaoComplementar, }; if (isNew) { + if (tinyProduct.seo) { + if (tinyProduct.seo.slug && tinyProduct.seo.slug.length) { + product.slug = tinyProduct.seo.slug; + } + if (tinyProduct.seo.title && tinyProduct.seo.title.length) { + product.meta_title = tinyProduct.seo.title.slice(0, 254); + } + if (tinyProduct.seo.description && tinyProduct.seo.description.length) { + product.meta_description = tinyProduct.seo.description.slice(0, 999); + } + if (tinyProduct.seo.keywords && tinyProduct.seo.keywords.length) { + product.keywords = tinyProduct.seo.keywords.split(','); + } + } + product.slug = removeAccents(name.toLowerCase()) .replace(/\s+/g, '-') .replace(/[^a-z0-9-_./]/g, ''); @@ -102,8 +135,9 @@ export default (tinyProduct, isNew = true) => new Promise((resolve) => { if (tinyProduct.garantia) { product.warranty = tinyProduct.garantia; } - if (tinyProduct.unidade_por_caixa) { - product.min_quantity = Number(tinyProduct.unidade_por_caixa); + if (tinyProduct.unidade_por_caixa || tinyProduct.unidadePorCaixa) { + product.min_quantity = !isProduct ? Number(tinyProduct.unidade_por_caixa) + : Number(tinyProduct.unidadePorCaixa); } if (tinyProduct.ncm) { product.mpn = [tinyProduct.ncm]; @@ -113,12 +147,14 @@ export default (tinyProduct, isNew = true) => new Promise((resolve) => { }; if (validateGtin(tinyProduct.gtin)) { product.gtin = [tinyProduct.gtin]; - if (validateGtin(tinyProduct.gtin_embalagem)) { - product.gtin.push(tinyProduct.gtin_embalagem); + if (validateGtin(tinyProduct.gtin_embalagem || tinyProduct.gtinEmbalagem)) { + product.gtin.push(tinyProduct.gtin_embalagem || tinyProduct.gtinEmbalagem); } } - const weight = tinyProduct.peso_bruto || tinyProduct.peso_liquido; + const weight = !isProduct ? (tinyProduct.peso_bruto || tinyProduct.peso_liquido) + : (tinyProduct.pesoBruto || tinyProduct.pesoLiquido); + if (weight > 0) { product.weight = { unit: 'kg', @@ -145,38 +181,84 @@ export default (tinyProduct, isNew = true) => new Promise((resolve) => { if (isNew) { if (Array.isArray(tinyProduct.variacoes) && tinyProduct.variacoes.length) { product.variations = []; - tinyProduct.variacoes.forEach(({ variacao }) => { - const { codigo, preco, grade } = variacao; + tinyProduct.variacoes.forEach(({ variacaoObj }) => { + const variacao = !isProduct ? variacaoObj.variacao : variacaoObj; + const { + codigo, + preco, + grade, + estoqueAtual, + anexos, + } = variacao; + const gridIdFormat = (text) => { + return removeAccents(text.toLowerCase()) + .replace(/\s+/g, '_') + .replace(/[^a-z0-9_]/g, '') + .substring(0, 30) + .padStart(2, 'i'); + }; + + const specifications = {}; + const specTexts: string[] = []; + if (grade && typeof grade === 'object') { - const specifications = {}; - const specTexts: string[] = []; - Object.keys(grade).forEach((tipo) => { - if (grade[tipo]) { - const gridId = removeAccents(tipo.toLowerCase()) - .replace(/\s+/g, '_') - .replace(/[^a-z0-9_]/g, '') - .substring(0, 30) - .padStart(2, 'i'); - const spec: Record = { - text: grade[tipo], + Object.keys(grade).forEach((tipoGrade) => { + if (grade[tipoGrade]) { + const gridId = gridIdFormat(tipoGrade); + const spec = { + text: grade[tipoGrade], }; specTexts.push(spec.text); if (gridId !== 'colors') { - spec.value = removeAccents(spec.text.toLowerCase()).substring(0, 100); + Object.assign( + spec, + { + value: removeAccents(spec.text.toLowerCase()).substring(0, 100), + }, + ); } specifications[gridId] = [spec]; } }); + } else if (Array.isArray(grade)) { + grade.forEach((gd) => { + const gridId = gridIdFormat(gd.chave); + const spec = { + text: gd.valor, + }; + specTexts.push(spec.text); + if (gridId !== 'colors') { + Object.assign( + spec, + { + value: removeAccents(spec.text.toLowerCase()).substring(0, 100), + }, + ); + } + specifications[gridId] = [spec]; + }); + } + let pictureId; + if (Array.isArray(anexos) && anexos.length + && Array.isArray(tinyProduct.anexos) && tinyProduct.anexos.length) { + pictureId = tinyProduct.anexos.length; + anexos.forEach((anexo) => { + tinyProduct.anexos.push(anexo); + }); + } else if (Array.isArray(tinyProduct.anexos) && tinyProduct.anexos.length) { + pictureId = 0; + } - if (specTexts.length) { - product.variations?.push({ - _id: ecomUtils.randomObjectId(), - name: `${name} / ${specTexts.join(' / ')}`.substring(0, 100), - sku: codigo, - specifications, - price: parseFloat(preco || 0), - }); - } + if (specTexts.length) { + product.variations?.push({ + _id: ecomUtils.randomObjectId(), + name: `${name} / ${specTexts.join(' / ')}`.substring(0, 100), + sku: codigo, + specifications, + price: parseFloat(preco || 0), + quantity: estoqueAtual || 0, + picture_id: pictureId, + }); } }); } @@ -201,12 +283,33 @@ export default (tinyProduct, isNew = true) => new Promise((resolve) => { product.pictures = []; } const promises: Promise[] = []; - tinyProduct.anexos.forEach(({ anexo }) => { - if (typeof anexo === 'string' && anexo.startsWith('http')) { + tinyProduct.anexos.forEach((anexo) => { + let url; + if (anexo && anexo.anexo) { + url = anexo.anexo; + } else if (anexo.url) { + url = anexo.url; + } + + if (typeof url === 'string' && url.startsWith('http')) { promises.push(tryImageUpload(anexo, product as Products)); } }); - Promise.all(promises).then(() => resolve(product)); + Promise.all(promises).then((images) => { + if (Array.isArray(product.variations) && product.variations.length) { + product.variations.forEach((variation) => { + if (variation.picture_id) { + const variationImage = images[variation.picture_id]; + if (variationImage._id) { + variation.picture_id = variationImage._id; + } else { + delete variation.picture_id; + } + } + }); + } + return resolve(product); + }); return; } } diff --git a/packages/apps/tiny-erp/src/integration/parsers/product-to-tiny.ts b/packages/apps/tiny-erp/src/integration/parsers/product-to-tiny.ts index 1ba9021a2..8dc195100 100644 --- a/packages/apps/tiny-erp/src/integration/parsers/product-to-tiny.ts +++ b/packages/apps/tiny-erp/src/integration/parsers/product-to-tiny.ts @@ -1,7 +1,9 @@ import type { Products } from '@cloudcommerce/types'; +import config from '@cloudcommerce/firebase/lib/config'; import ecomUtils from '@ecomplus/utils'; export default async (product: Products, originalTinyProduct, appData) => { + const { metafields } = config.get(); const hasVariations = product.variations && product.variations.length; let unidade: string = originalTinyProduct?.unidade; if (!unidade) { @@ -60,10 +62,18 @@ export default async (product: Products, originalTinyProduct, appData) => { if (product.weight && product.weight.value) { tinyProduct.peso_bruto = product.weight.value; + tinyProduct.peso_liquido = product.weight.value; + + if (metafields.tinyErpProductWeigthUnit) { + product.weight.unit = metafields.tinyErpProductWeigthUnit; + } + if (product.weight.unit === 'mg') { tinyProduct.peso_bruto /= 1000000; + tinyProduct.peso_liquido /= 1000000; } else if (product.weight.unit === 'g') { tinyProduct.peso_bruto /= 1000; + tinyProduct.peso_liquido /= 1000; } } const { dimensions } = product;