diff --git a/functions/ecom.config.js b/functions/ecom.config.js index 342c6e2..6acc4c7 100644 --- a/functions/ecom.config.js +++ b/functions/ecom.config.js @@ -141,16 +141,16 @@ const app = { /** * JSON schema based fields to be configured by merchant and saved to app `data` / `hidden_data`, such as: */ - zip: { - schema: { - type: 'string', - maxLength: 9, - pattern: '^[0-9]{5}-?[0-9]{3}$', - title: 'CEP de origem' - }, - hide: true + zip: { + schema: { + type: 'string', + maxLength: 9, + pattern: '^[0-9]{5}-?[0-9]{3}$', + title: 'CEP de origem' }, - api_key: { + hide: true + }, + api_key: { schema: { type: 'string', maxLength: 600, @@ -464,6 +464,89 @@ const app = { }, hide: true }, + warehouses: { + schema: { + title: 'Armazéns (multi CD)', + description: 'Origens e destinos para cada centro de distribuição', + type: 'array', + maxItems: 30, + items: { + title: 'Centro de distribuição', + type: 'object', + required: ['code', 'zip'], + additionalProperties: false, + properties: { + code: { + type: 'string', + maxLength: 30, + pattern: '^[A-Za-z0-9-_]{2,30}$', + title: 'Código do CD' + }, + api_key: { + type: 'string', + maxLength: 600, + title: 'Api Key', + description: 'Api Key da Frete Click específica para o CD, se houver' + }, + zip: { + type: 'string', + maxLength: 9, + pattern: '^[0-9]{5}-?[0-9]{3}$', + title: 'CEP de origem', + description: 'Código postal do remetente para cálculo do frete' + }, + posting_deadline: { + title: 'Prazo de envio do CD', + type: 'object', + required: ['days'], + additionalProperties: false, + properties: { + days: { + type: 'integer', + minimum: 0, + maximum: 999999, + title: 'Número de dias', + description: 'Dias de prazo para postar os produtos após a compra' + }, + working_days: { + type: 'boolean', + default: true, + title: 'Dias úteis' + }, + after_approval: { + type: 'boolean', + default: true, + title: 'Após aprovação do pagamento' + } + } + }, + zip_range: { + title: 'Faixa de CEP atendida', + type: 'object', + required: [ + 'min', + 'max' + ], + properties: { + min: { + type: 'integer', + minimum: 10000, + maximum: 999999999, + title: 'CEP inicial' + }, + max: { + type: 'integer', + minimum: 10000, + maximum: 999999999, + title: 'CEP final' + } + } + } + } + } + }, + hide: true + }, } } @@ -474,9 +557,7 @@ const app = { const procedures = [] - - /** Uncomment and edit code above to configure `triggers` and receive respective `webhooks`: -**/ +/** Uncomment and edit code above to configure `triggers` and receive respective `webhooks`: **/ const { baseUri } = require('./__env') procedures.push({ @@ -508,8 +589,7 @@ procedures.push({ ] }) - /* You may also edit `routes/ecom/webhook.js` to treat notifications properly. - */ +/* You may also edit `routes/ecom/webhook.js` to treat notifications properly. */ exports.app = app diff --git a/functions/lib/freteclick/create-tag.js b/functions/lib/freteclick/create-tag.js index 63a891a..1f03038 100644 --- a/functions/lib/freteclick/create-tag.js +++ b/functions/lib/freteclick/create-tag.js @@ -4,9 +4,16 @@ const getOrCreateCustomer = require('./customer') const getCompanyId = require('./me') const client = require('./client') -module.exports = async (order, token, storeId, appData, appSdk) => { -// create new shipping tag with Kangu -// https://portal.kangu.com.br/docs/api/transporte/#/ +module.exports = async (order, storeId, appData, appSdk) => { + let token = appData.api_key + const shippingLine = order.shipping_lines[0] + const warehouseCode = shippingLine.warehouse_code + if (warehouseCode) { + const warehouse = appData.warehouses?.find(({ code }) => code === warehouseCode) + if (warehouse.api_key) { + token = warehouse.api_key + } + } const { peopleId, companyId @@ -15,9 +22,9 @@ module.exports = async (order, token, storeId, appData, appSdk) => { const customer = order.buyers?.[0] const address = order.shipping_lines?.[0]?.to const retrieve = { - ...(order.shipping_lines?.[0]?.from), ...appData.from, - zip: appData.zip + zip: appData.zip, + ...(order.shipping_lines?.[0]?.from) } const freteClickCustom = (order, field) => { const shippingCustom = order.shipping_lines[0] && order.shipping_lines[0].custom_fields diff --git a/functions/routes/ecom/modules/calculate-shipping.js b/functions/routes/ecom/modules/calculate-shipping.js index aab8886..f1aaa7d 100644 --- a/functions/routes/ecom/modules/calculate-shipping.js +++ b/functions/routes/ecom/modules/calculate-shipping.js @@ -38,7 +38,7 @@ exports.post = async ({ appSdk }, req, res) => { const selectedStoreId = [51316, 51317] - const token = appData.api_key + let token = appData.api_key if (!token) { // must have configured kangu doc number and token return res.status(409).send({ @@ -55,9 +55,47 @@ exports.post = async ({ appSdk }, req, res) => { } const destinationZip = params.to ? params.to.zip.replace(/\D/g, '') : '' - const originZip = params.from - ? params.from.zip.replace(/\D/g, '') - : appData.zip ? appData.zip.replace(/\D/g, '') : '' + const checkZipCode = (rule) => { + // validate rule zip range + if (destinationZip && rule.zip_range) { + const { min, max } = rule.zip_range + return Boolean((!min || destinationZip >= min) && (!max || destinationZip <= max)) + } + return true + } + + let originZip, warehouseCode + let postingDeadline = appData.posting_deadline + if (params.from) { + originZip = params.from.zip + } else if (Array.isArray(appData.warehouses) && appData.warehouses.length) { + for (let i = 0; i < appData.warehouses.length; i++) { + const warehouse = appData.warehouses[i] + if (warehouse?.zip && checkZipCode(warehouse)) { + const { code } = warehouse + if (!code) continue + if (params.items) { + const itemNotOnWarehouse = params.items.find(({ quantity, inventory }) => { + return inventory && Object.keys(inventory).length && !(inventory[code] >= quantity) + }) + if (itemNotOnWarehouse) continue + } + originZip = warehouse.zip + if (warehouse.posting_deadline?.days) { + postingDeadline = warehouse.posting_deadline + } + if (warehouse.api_key) { + token = warehouse.api_key + } + warehouseCode = code + } + } + } + + if (!originZip) { + originZip = appData.zip + } + originZip = typeof originZip === 'string' ? originZip.replace(/\D/g, '') : '' const matchService = (service, name) => { const fields = ['service_name', 'service_code'] @@ -69,18 +107,6 @@ exports.post = async ({ appSdk }, req, res) => { return true } - const checkZipCode = rule => { - // validate rule zip range - if (destinationZip && rule.zip_range) { - const { min, max } = rule.zip_range - return Boolean((!min || destinationZip >= min) && (!max || destinationZip <= max)) - } - return true - } - - const destination = destinationZip - const origin = originZip - // search for configured free shipping rule if (Array.isArray(appData.free_shipping_rules)) { for (let i = 0; i < appData.free_shipping_rules.length; i++) { @@ -102,9 +128,6 @@ exports.post = async ({ appSdk }, req, res) => { res.send(response) return } - - /* DO THE STUFF HERE TO FILL RESPONSE OBJECT WITH SHIPPING SERVICES */ - if (!originZip) { // must have configured origin zip code to continue return res.status(409).send({ @@ -177,8 +200,8 @@ exports.post = async ({ appSdk }, req, res) => { const quoteType = 'full' const body = { - destination, - origin, + destination: destinationZip, + origin: originZip, productType, productTotalPrice, quoteType, @@ -195,7 +218,8 @@ exports.post = async ({ appSdk }, req, res) => { url: '/quotes', method: 'post', token, - data: body + data: body, + timeout: (params.is_checkout_confirmation ? 8000 : 4000) }).then(({ data, status }) => { let result if (typeof data === 'string') { @@ -209,12 +233,11 @@ exports.post = async ({ appSdk }, req, res) => { }) } } else { - result = data && data.response && data.response.data && data.response.data.order && data.response.data.order.quotes + result = data?.response?.data?.order?.quotes } if (result && Number(status) === 200 && Array.isArray(result)) { // success response - console.log('Quote with success', storeId) const orderId = data.response.data.order.id let lowestPriceShipping result.forEach((freteClickService, index) => { @@ -241,7 +264,7 @@ exports.post = async ({ appSdk }, req, res) => { delivery_instructions: 'Gestão Logística via Frete Click', posting_deadline: { days: 3, - ...appData.posting_deadline + ...postingDeadline }, package: { weight: { @@ -259,13 +282,13 @@ exports.post = async ({ appSdk }, req, res) => { value: orderId } ], + warehouse_code: warehouseCode, flags: ['freteclick-ws', `freteclick-${serviceCode}`.substr(0, 20)] } if (!lowestPriceShipping || lowestPriceShipping.price > price) { lowestPriceShipping = shippingLine } - - if (shippingLine.posting_deadline && shippingLine.posting_deadline.days >= 0) { + if (shippingLine.posting_deadline?.days >= 0) { shippingLine.posting_deadline.days += parseInt(freteClickService.retrieveDeadline, 10) } diff --git a/functions/routes/ecom/webhook.js b/functions/routes/ecom/webhook.js index 51eed11..7716e3e 100644 --- a/functions/routes/ecom/webhook.js +++ b/functions/routes/ecom/webhook.js @@ -55,7 +55,7 @@ exports.post = ({ appSdk }, req, res) => { return res.send(ECHO_SKIP) } console.log(`Shipping tag for #${storeId} ${order._id}`) - return createTag(order, api_key, storeId, appData, appSdk) + return createTag(order, storeId, appData, appSdk) .then(data => { console.log(`>> Etiqueta Criada Com Sucesso #${storeId} ${resourceId}`, data) // updates metafields with the generated tag id