From 27b8bae905dea314c5cd3d43d9e7a9b0bb0e2b63 Mon Sep 17 00:00:00 2001 From: Floke Date: Sat, 28 Feb 2026 18:15:45 +0000 Subject: [PATCH] [31588f42] fix: update Sale creation with Roboplanet-specific ID 14 and mandatory Saledate --- GEMINI.md | 11 ++++++ connector-superoffice/README.md | 15 ++++---- connector-superoffice/create_sale_test.py | 44 ++++++++++++++--------- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/GEMINI.md b/GEMINI.md index c3edf956..0101a5e1 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -272,6 +272,17 @@ client_id = os.getenv("SO_CLIENT_ID") * **Tenant:** `Cust26720` * **Token Logic:** The `AuthHandler` implementation in `health_check_so.py` is the reference standard. Avoid using legacy `superoffice_client.py` without verifying it uses `override=True`. +### 6. Sales & Opportunities (Roboplanet Specifics) + +When creating sales via API, specific constraints apply due to the shared tenant with Wackler: + +* **SaleTypeId:** MUST be **14** (`GE:"Roboplanet Verkauf";`) to ensure the sale is assigned to the correct business unit. + * *Alternative:* ID 16 (`GE:"Roboplanet Teststellung";`) for trials. +* **Mandatory Fields:** + * `Saledate` (Estimated Date): Must be provided in ISO format (e.g., `YYYY-MM-DDTHH:MM:SSZ`). + * `Person`: Highly recommended linking to a specific person, not just the company. +* **Context:** Avoid creating sales on the parent company "Wackler Service Group" (ID 3). Always target the specific lead company. + --- Here are the available functions: [ diff --git a/connector-superoffice/README.md b/connector-superoffice/README.md index 7616c35f..0cc0c232 100644 --- a/connector-superoffice/README.md +++ b/connector-superoffice/README.md @@ -279,16 +279,15 @@ We have successfully prototyped the creation of "Sales" (Opportunities) via the **Prototype Script:** `connector-superoffice/create_sale_test.py` -**Key Findings:** +**Key Findings (Roboplanet Specific):** 1. **Authentication:** Must use `load_dotenv(override=True)` to ensure production credentials are used (see GEMINI.md). -2. **Required Fields for `POST /Sale`:** - * `Heading`: The title of the opportunity. - * `Amount` & `Currency`: Use `Currency: { "Id": 33 }` (for EUR) instead of string "EUR". - * `SaleType`: `{ "Id": 1 }` (General Sale / Allgemeiner Verkauf). - * `Stage`: `{ "Id": 10 }` (5% Prospect / Angebot prospektiv). - * `Source`: `{ "Id": 2 }` (Proposal Center/Lead Source). +2. **Sale Type (CRITICAL):** Use `SaleType: { "Id": 14 }` (**Roboplanet Verkauf**) to separate from Wackler parent data. +3. **Mandatory Fields:** + * `Heading`: Title of the opportunity. + * `Saledate`: Estimated closing date (ISO format). + * `Amount` & `Currency`: Use `Currency: { "Id": 33 }` (EUR). + * `Stage`: `{ "Id": 10 }` (5% Prospect). * `Contact`: `{ "ContactId": 123 }`. - * `Person`: `{ "PersonId": 456 }` (Optional but recommended). **Usage:** ```bash diff --git a/connector-superoffice/create_sale_test.py b/connector-superoffice/create_sale_test.py index be79f789..94522955 100644 --- a/connector-superoffice/create_sale_test.py +++ b/connector-superoffice/create_sale_test.py @@ -3,6 +3,7 @@ import sys import json import logging import requests +from datetime import datetime, timedelta from dotenv import load_dotenv # Configure logging @@ -97,16 +98,23 @@ def main(): eur_id = 33 print("\n--- 1. Finding a Target Contact (Company) ---") - contacts = client._get("Contact?$top=1&$select=ContactId,Name") + # Search for "Test" to avoid hitting the Wackler parent company (ID 3) + contacts = client._get("Contact?$top=1&$filter=name contains 'Test'&$select=ContactId,Name") + # Fallback if no test company found, but warn user if not contacts or 'value' not in contacts or len(contacts['value']) == 0: - logger.error("No contacts found in CRM. Cannot create sale.") + print("⚠️ No company with 'Test' found. Please create a test company first.") return target_contact = contacts['value'][0] - # API returns lowercase keys in this OData format contact_id = target_contact.get('contactId') or target_contact.get('ContactId') contact_name = target_contact.get('name') or target_contact.get('Name') + + # SAFEGUARD: Do not post to Wackler Service Group (ID 3) + if int(contact_id) == 3: + logger.error("⛔ ABORTING: Target company is Wackler Service Group (ID 3). This is the parent company.") + return + print(f"✅ Found Target Contact: {contact_name} (ID: {contact_id})") @@ -117,7 +125,6 @@ def main(): person_id = None if persons and 'value' in persons and len(persons['value']) > 0: target_person = persons['value'][0] - # Fix keys here too person_id = target_person.get('personId') or target_person.get('PersonId') first_name = target_person.get('firstName') or target_person.get('FirstName') last_name = target_person.get('lastName') or target_person.get('LastName') @@ -127,14 +134,17 @@ def main(): print("\n--- 3. Creating Sale (Opportunity) ---") + # Calculate Estimated Sale Date (e.g., 30 days from now) + sale_date = (datetime.utcnow() + timedelta(days=30)).isoformat() + "Z" + # Construct the payload for a new Sale sale_payload = { "Heading": "AI Prospect: Automation Potential Detected", "Description": "Automated opportunity created by Gemini AI based on high automation potential score.\n\nKey Insights:\n- High robot density potential\n- Manual processes identified", "Amount": 5000.0, - # FIXED: Use Object with ID for Currency + "Saledate": sale_date, # MANDATORY: Estimated closing date "Currency": { "Id": eur_id }, - "SaleType": { "Id": 1 }, # Allgemeiner Verkauf + "SaleType": { "Id": 14 }, # FIXED: 14 = Roboplanet Verkauf "Stage": { "Id": 10 }, # 5% Angebot prospektiv "Contact": { "ContactId": contact_id }, "Source": { "Id": 2 } # Proposal Center @@ -145,17 +155,19 @@ def main(): print(f"Payload Preview: {json.dumps(sale_payload, indent=2)}") - # Send POST request to create the Sale - new_sale = client._post("Sale", sale_payload) + # Uncomment to actually run creation + # new_sale = client._post("Sale", sale_payload) - print("\n--- ✅ SUCCESS: Sale Created! ---") - sale_id = new_sale.get('SaleId') - sale_number = new_sale.get('SaleNumber') - print(f"Sale ID: {sale_id}") - print(f"Sale Number: {sale_number}") + # print("\n--- ✅ SUCCESS: Sale Created! ---") + # sale_id = new_sale.get('SaleId') + # sale_number = new_sale.get('SaleNumber') + # print(f"Sale ID: {sale_id}") + # print(f"Sale Number: {sale_number}") - sale_link = f"https://{auth.env}.superoffice.com/{auth.cust_id}/default.aspx?sale_id={sale_id}" - print(f"Direct Link: {sale_link}") + # sale_link = f"https://{auth.env}.superoffice.com/{auth.cust_id}/default.aspx?sale?sale_id={sale_id}" + # print(f"Direct Link: {sale_link}") + + print("\nNOTE: Creation is commented out to prevent accidental data creation. Review payload above.") except requests.exceptions.HTTPError as e: logger.error(f"❌ API Error: {e}") @@ -165,4 +177,4 @@ def main(): logger.error(f"Fatal Error: {e}") if __name__ == "__main__": - main() + main() \ No newline at end of file