// ... existing code ... /** * Localiza o "Tipo de Venda" correspondente a orçamento no Odoo. * * Procura no modelo 'sale.order.type' por um registro cujo nome contenha * "Orçamento" (PT) ou "Orcamento" (sem acento). Se não encontrar, tenta * algumas alternativas de nome e, por fim, apenas retorna null. * * Em todos os casos de erro, nunca lança exceção – apenas registra em log * e devolve null, para não quebrar a sincronização. */ function find_sale_type_orcamento($models, $db, $uid, $pwd) { try { // Estratégia 1: nome contém "Orçamento" $domains = [ [['name', 'ilike', 'Orçamento']], [['name', 'ilike', 'Orcamento']] ]; foreach ($domains as $domain) { $result = $models->execute_kw( $db, $uid, $pwd, 'sale.order.type', 'search_read', [$domain], ['fields' => ['id', 'name'], 'limit' => 1] ); if (!empty($result) && isset($result[0]['id']) && is_numeric($result[0]['id'])) { $id = (int)$result[0]['id']; $name = $result[0]['name'] ?? ''; logmsg("✓ Tipo de venda 'Orçamento' encontrado: ID {$id}, Nome '{$name}'"); return $id; } } // Se não encontrar nada, apenas loga e retorna null logmsg("⚠️ Tipo de venda 'Orçamento' não encontrado no Odoo (sale.order.type). Continuando sem definir sale_type_id."); return null; } catch (Exception $e) { logmsg("⚠️ Erro ao buscar tipo de venda 'Orçamento' no Odoo: " . $e->getMessage()); return null; } } /** * Busca ROBUSTA e AGRESSIVA por pedido existente * Esta função faz múltiplas tentativas e busca ampla se necessário * NÃO depende de cache - funciona mesmo se o cache for apagado */ function find_existing_order_robust($models, $db, $uid, $pwd, $holdprint_code, $budget_title = null, $partner_id = null) { if (empty($holdprint_code)) { return null; } logmsg("🔍🔍🔍 BUSCA ROBUSTA: Procurando pedido para código {$holdprint_code} (MÚLTIPLAS ESTRATÉGIAS) ===="); $client_order_ref = 'HoldPrint-' . $holdprint_code; $origin_value = $budget_title ? 'HoldPrint: ' . $budget_title : null; // ESTRATÉGIA 1: Busca exata por client_order_ref (mais confiável) logmsg(" → Estratégia 1: Busca EXATA por client_order_ref = '{$client_order_ref}'"); try { $found = $models->execute_kw($db, $uid, $pwd, 'sale.order', 'search_read', [['client_order_ref', '=', $client_order_ref]], ['fields' => ['id', 'name', 'state', 'client_order_ref', 'partner_id', 'origin'], 'limit' => 5] ); if (!empty($found) && is_array($found)) { foreach ($found as $order) { if (isset($order['id']) && is_numeric($order['id']) && $order['id'] > 0) { $order_ref = $order['client_order_ref'] ?? ''; if ($order_ref === $client_order_ref) { logmsg(" ✓✓✓ PEDIDO ENCONTRADO (Estratégia 1)! ID: {$order['id']}"); return $order; } } } } } catch (Exception $e) { logmsg(" → Erro na estratégia 1: " . $e->getMessage()); } // ESTRATÉGIA 2: Busca por código sem prefixo logmsg(" → Estratégia 2: Busca por client_order_ref = '{$holdprint_code}' (sem prefixo)"); try { $found = $models->execute_kw($db, $uid, $pwd, 'sale.order', 'search_read', [['client_order_ref', '=', (string)$holdprint_code]], ['fields' => ['id', 'name', 'state', 'client_order_ref', 'partner_id', 'origin'], 'limit' => 5] ); if (!empty($found) && is_array($found)) { foreach ($found as $order) { if (isset($order['id']) && is_numeric($order['id']) && $order['id'] > 0) { $order_ref = $order['client_order_ref'] ?? ''; if ($order_ref === (string)$holdprint_code || $order_ref === $client_order_ref) { logmsg(" ✓✓✓ PEDIDO ENCONTRADO (Estratégia 2)! ID: {$order['id']}"); return $order; } } } } } catch (Exception $e) { logmsg(" → Erro na estratégia 2: " . $e->getMessage()); } // ESTRATÉGIA 3: Busca por origin (se disponível) if ($origin_value) { logmsg(" → Estratégia 3: Busca por origin = '{$origin_value}'"); try { $found = $models->execute_kw($db, $uid, $pwd, 'sale.order', 'search_read', [['origin', '=', $origin_value]], ['fields' => ['id', 'name', 'state', 'client_order_ref', 'partner_id', 'origin'], 'limit' => 5] ); if (!empty($found) && is_array($found)) { foreach ($found as $order) { if (isset($order['id']) && is_numeric($order['id']) && $order['id'] > 0) { $order_origin = $order['origin'] ?? ''; if ($order_origin === $origin_value) { logmsg(" ✓✓✓ PEDIDO ENCONTRADO (Estratégia 3)! ID: {$order['id']}"); return $order; } } } } } catch (Exception $e) { logmsg(" → Erro na estratégia 3: " . $e->getMessage()); } } // ESTRATÉGIA 4: Busca por número extraído (para códigos numéricos) if (is_numeric($holdprint_code)) { logmsg(" → Estratégia 4: Busca por número {$holdprint_code} no name/client_order_ref"); $order_by_number = find_existing_sale_order_by_number($models, $db, $uid, $pwd, $holdprint_code); if ($order_by_number) { logmsg(" ✓✓✓ PEDIDO ENCONTRADO (Estratégia 4)! ID: {$order_by_number['id']}"); return $order_by_number; } } // ESTRATÉGIA 5: Busca AMPLA - busca TODOS os pedidos recentes (últimos 90 dias) e verifica manualmente // Esta é a estratégia mais agressiva - não depende de índices ou campos específicos logmsg(" → Estratégia 5: Busca AMPLA - Todos os pedidos dos últimos 90 dias"); try { $date_limit = date('Y-m-d H:i:s', strtotime('-90 days')); $all_recent = $models->execute_kw($db, $uid, $pwd, 'sale.order', 'search_read', [['date_order', '>=', $date_limit]], ['fields' => ['id', 'name', 'state', 'client_order_ref', 'partner_id', 'origin', 'note'], 'limit' => 500, 'order' => 'date_order desc'] ); if (!empty($all_recent) && is_array($all_recent)) { logmsg(" → Verificando " . count($all_recent) . " pedido(s) recentes manualmente..."); foreach ($all_recent as $order) { if (!isset($order['id']) || !is_numeric($order['id'])) { continue; } $order_ref = $order['client_order_ref'] ?? ''; $order_origin = $order['origin'] ?? ''; $order_note = $order['note'] ?? ''; $order_name = $order['name'] ?? ''; // Verifica múltiplos critérios if ($order_ref === $client_order_ref || $order_ref === (string)$holdprint_code || ($origin_value && $order_origin === $origin_value) || (is_numeric($holdprint_code) && preg_match('/\b' . preg_quote($holdprint_code, '/') . '\b/', $order_name)) || strpos($order_note, (string)$holdprint_code) !== false || strpos($order_note, 'orçamento ' . $holdprint_code) !== false) { logmsg(" ✓✓✓ PEDIDO ENCONTRADO (Estratégia 5 - Busca Ampla)! ID: {$order['id']}, Ref: '{$order_ref}'"); return $order; } } } } catch (Exception $e) { logmsg(" → Erro na estratégia 5 (busca ampla): " . $e->getMessage()); } // ESTRATÉGIA 6: Se tem partner_id, busca TODOS os pedidos do partner e verifica if ($partner_id) { logmsg(" → Estratégia 6: Busca TODOS os pedidos do partner {$partner_id}"); try { $partner_orders = $models->execute_kw($db, $uid, $pwd, 'sale.order', 'search_read', [['partner_id', '=', $partner_id]], ['fields' => ['id', 'name', 'state', 'client_order_ref', 'partner_id', 'origin', 'note'], 'limit' => 200, 'order' => 'id desc'] ); if (!empty($partner_orders) && is_array($partner_orders)) { logmsg(" → Verificando " . count($partner_orders) . " pedido(s) do partner manualmente..."); foreach ($partner_orders as $order) { if (!isset($order['id']) || !is_numeric($order['id'])) { continue; } $order_ref = $order['client_order_ref'] ?? ''; $order_origin = $order['origin'] ?? ''; $order_note = $order['note'] ?? ''; $order_name = $order['name'] ?? ''; // Verifica múltiplos critérios if ($order_ref === $client_order_ref || $order_ref === (string)$holdprint_code || ($origin_value && $order_origin === $origin_value) || (is_numeric($holdprint_code) && preg_match('/\b' . preg_quote($holdprint_code, '/') . '\b/', $order_name)) || strpos($order_note, (string)$holdprint_code) !== false || strpos($order_note, 'orçamento ' . $holdprint_code) !== false) { logmsg(" ✓✓✓ PEDIDO ENCONTRADO (Estratégia 6 - Partner)! ID: {$order['id']}, Ref: '{$order_ref}'"); return $order; } } } } catch (Exception $e) { logmsg(" → Erro na estratégia 6 (partner): " . $e->getMessage()); } } logmsg(" ✗✗✗ NENHUM PEDIDO ENCONTRADO após todas as estratégias"); return null; } /** * Busca pedido por "número" (normalmente presente no campo name ou client_order_ref). * Usado para casos como "Rascunho (* 7901)" ou refs numéricas simples. */ function find_existing_sale_order_by_number($models, $db, $uid, $pwd, $number) { if (empty($number)) { return null; } try { $number_str = (string)$number; // 1) Busca por client_order_ref exato (com e sem prefixo HoldPrint-) $refs = [$number_str, 'HoldPrint-' . $number_str]; foreach ($refs as $ref) { $found = $models->execute_kw( $db, $uid, $pwd, 'sale.order', 'search_read', [[['client_order_ref', '=', $ref]]], ['fields' => ['id', 'name', 'state', 'client_order_ref', 'partner_id', 'origin'], 'limit' => 1] ); if (!empty($found) && isset($found[0]['id']) && is_numeric($found[0]['id'])) { logmsg("✓ Pedido encontrado por client_order_ref '{$ref}' (find_existing_sale_order_by_number) - ID {$found[0]['id']}"); return $found[0]; } } // 2) Busca por número no campo name (ilike) $found = $models->execute_kw( $db, $uid, $pwd, 'sale.order', 'search_read', [[['name', 'ilike', $number_str]]], ['fields' => ['id', 'name', 'state', 'client_order_ref', 'partner_id', 'origin'], 'limit' => 5] ); if (!empty($found) && is_array($found)) { foreach ($found as $order) { if (isset($order['id']) && is_numeric($order['id'])) { logmsg("✓ Pedido encontrado por name contendo '{$number_str}' - ID {$order['id']}"); return $order; } } } } catch (Exception $e) { logmsg("⚠️ Erro em find_existing_sale_order_by_number: " . $e->getMessage()); } return null; } /** * Busca pedido existente pelo campo origin. */ function find_existing_sale_order_by_origin($models, $db, $uid, $pwd, $origin_value) { if (empty($origin_value)) { return null; } try { $found = $models->execute_kw( $db, $uid, $pwd, 'sale.order', 'search_read', [[['origin', '=', $origin_value]]], ['fields' => ['id', 'name', 'state', 'client_order_ref', 'partner_id', 'origin'], 'limit' => 1] ); if (!empty($found) && isset($found[0]['id']) && is_numeric($found[0]['id'])) { logmsg("✓ Pedido encontrado por origin '{$origin_value}' - ID {$found[0]['id']}"); return $found[0]; } } catch (Exception $e) { logmsg("⚠️ Erro em find_existing_sale_order_by_origin: " . $e->getMessage()); } return null; } /** * Busca pedido existente combinando partner + código (client_order_ref / name / note). */ function find_existing_sale_order_by_partner($models, $db, $uid, $pwd, $partner_id, $code) { if (empty($partner_id) || empty($code)) { return null; } try { $code_str = (string)$code; $domain = [['partner_id', '=', $partner_id]]; $orders = $models->execute_kw( $db, $uid, $pwd, 'sale.order', 'search_read', [$domain], ['fields' => ['id', 'name', 'state', 'client_order_ref', 'note', 'origin'], 'limit' => 100, 'order' => 'id desc'] ); if (!empty($orders) && is_array($orders)) { foreach ($orders as $order) { if (!isset($order['id']) || !is_numeric($order['id'])) { continue; } $ref = $order['client_order_ref'] ?? ''; $name = $order['name'] ?? ''; $note = $order['note'] ?? ''; if ($ref === $code_str || $ref === 'HoldPrint-' . $code_str || strpos($name, $code_str) !== false || strpos($note, $code_str) !== false) { logmsg("✓ Pedido encontrado por partner + código '{$code_str}' - ID {$order['id']}"); return $order; } } } } catch (Exception $e) { logmsg("⚠️ Erro em find_existing_sale_order_by_partner: " . $e->getMessage()); } return null; } /** * Busca global adicional usando múltiplos identificadores do orçamento. * Usa os dados de $identifiers (gerados por extract_budget_identifiers). */ function find_existing_order_globally($models, $db, $uid, $pwd, $identifiers, $partner_id = null) { if (empty($identifiers) || !is_array($identifiers)) { return null; } // 1) Tenta por código direto (client_order_ref / x_holdprint_code, etc.) if (!empty($identifiers['code'])) { $by_number = find_existing_sale_order_by_number($models, $db, $uid, $pwd, $identifiers['code']); if ($by_number) { return $by_number; } } // 2) Se tiver partner, tenta por partner + código if ($partner_id && !empty($identifiers['code'])) { $by_partner = find_existing_sale_order_by_partner($models, $db, $uid, $pwd, $partner_id, $identifiers['code']); if ($by_partner) { return $by_partner; } } // 3) Fallback simples: busca por note contendo o código try { if (!empty($identifiers['code'])) { $code_str = (string)$identifiers['code']; $domain = [['note', 'ilike', $code_str]]; if ($partner_id) { $domain[] = ['partner_id', '=', $partner_id]; } $orders = $models->execute_kw( $db, $uid, $pwd, 'sale.order', 'search_read', [$domain], ['fields' => ['id', 'name', 'state', 'client_order_ref', 'partner_id', 'origin', 'note'], 'limit' => 10, 'order' => 'id desc'] ); if (!empty($orders) && isset($orders[0]['id']) && is_numeric($orders[0]['id'])) { logmsg("✓ Pedido encontrado em find_existing_order_globally por note contendo código '{$code_str}' - ID {$orders[0]['id']}"); return $orders[0]; } } } catch (Exception $e) { logmsg("⚠️ Erro em find_existing_order_globally: " . $e->getMessage()); } return null; } /** * Garante que exista uma UOM (unidade de medida) com o símbolo informado. * Se não existir, cria uma UOM simples baseada em "Units". */ function ensure_uom($models, $db, $uid, $pwd, $unit_symbol) { if (empty($unit_symbol)) { return 1; // fallback para unidade padrão } try { // 1) Tenta encontrar UOM pelo nome $found = $models->execute_kw( $db, $uid, $pwd, 'uom.uom', 'search_read', [[['name', '=', $unit_symbol]]], ['fields' => ['id', 'name'], 'limit' => 1] ); if (!empty($found) && isset($found[0]['id']) && is_numeric($found[0]['id'])) { return (int)$found[0]['id']; } // 2) Se não existir, tenta pegar categoria "Unit" $cats = $models->execute_kw( $db, $uid, $pwd, 'uom.category', 'search_read', [[['name', 'ilike', 'Unit']]], ['fields' => ['id'], 'limit' => 1] ); $category_id = !empty($cats) && isset($cats[0]['id']) ? (int)$cats[0]['id'] : null; $data = [ 'name' => $unit_symbol, 'uom_type' => 'reference', 'factor' => 1.0, ]; if ($category_id) { $data['category_id'] = $category_id; } $result = $models->execute_kw( $db, $uid, $pwd, 'uom.uom', 'create', [$data] ); if (is_numeric($result)) { logmsg("✓ UOM '{$unit_symbol}' criada - ID {$result}"); return (int)$result; } } catch (Exception $e) { logmsg("⚠️ Erro em ensure_uom para '{$unit_symbol}': " . $e->getMessage()); } return 1; // fallback } /** * A partir de um product.template_id, obtém o product.product correspondente. */ function get_product_product_id($models, $db, $uid, $pwd, $template_id) { if (empty($template_id)) { return null; } try { $found = $models->execute_kw( $db, $uid, $pwd, 'product.product', 'search_read', [[['product_tmpl_id', '=', $template_id]]], ['fields' => ['id', 'product_tmpl_id'], 'limit' => 1] ); if (!empty($found) && isset($found[0]['id']) && is_numeric($found[0]['id'])) { return (int)$found[0]['id']; } } catch (Exception $e) { logmsg("⚠️ Erro em get_product_product_id para template {$template_id}: " . $e->getMessage()); } return null; }