[31588f42] fix: update Sale creation with Roboplanet-specific ID 14 and mandatory Saledate
This commit is contained in:
11
GEMINI.md
11
GEMINI.md
@@ -272,6 +272,17 @@ client_id = os.getenv("SO_CLIENT_ID")
|
|||||||
* **Tenant:** `Cust26720`
|
* **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`.
|
* **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:
|
Here are the available functions:
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -279,16 +279,15 @@ We have successfully prototyped the creation of "Sales" (Opportunities) via the
|
|||||||
|
|
||||||
**Prototype Script:** `connector-superoffice/create_sale_test.py`
|
**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).
|
1. **Authentication:** Must use `load_dotenv(override=True)` to ensure production credentials are used (see GEMINI.md).
|
||||||
2. **Required Fields for `POST /Sale`:**
|
2. **Sale Type (CRITICAL):** Use `SaleType: { "Id": 14 }` (**Roboplanet Verkauf**) to separate from Wackler parent data.
|
||||||
* `Heading`: The title of the opportunity.
|
3. **Mandatory Fields:**
|
||||||
* `Amount` & `Currency`: Use `Currency: { "Id": 33 }` (for EUR) instead of string "EUR".
|
* `Heading`: Title of the opportunity.
|
||||||
* `SaleType`: `{ "Id": 1 }` (General Sale / Allgemeiner Verkauf).
|
* `Saledate`: Estimated closing date (ISO format).
|
||||||
* `Stage`: `{ "Id": 10 }` (5% Prospect / Angebot prospektiv).
|
* `Amount` & `Currency`: Use `Currency: { "Id": 33 }` (EUR).
|
||||||
* `Source`: `{ "Id": 2 }` (Proposal Center/Lead Source).
|
* `Stage`: `{ "Id": 10 }` (5% Prospect).
|
||||||
* `Contact`: `{ "ContactId": 123 }`.
|
* `Contact`: `{ "ContactId": 123 }`.
|
||||||
* `Person`: `{ "PersonId": 456 }` (Optional but recommended).
|
|
||||||
|
|
||||||
**Usage:**
|
**Usage:**
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import sys
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
@@ -97,16 +98,23 @@ def main():
|
|||||||
eur_id = 33
|
eur_id = 33
|
||||||
|
|
||||||
print("\n--- 1. Finding a Target Contact (Company) ---")
|
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:
|
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
|
return
|
||||||
|
|
||||||
target_contact = contacts['value'][0]
|
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_id = target_contact.get('contactId') or target_contact.get('ContactId')
|
||||||
contact_name = target_contact.get('name') or target_contact.get('Name')
|
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})")
|
print(f"✅ Found Target Contact: {contact_name} (ID: {contact_id})")
|
||||||
|
|
||||||
|
|
||||||
@@ -117,7 +125,6 @@ def main():
|
|||||||
person_id = None
|
person_id = None
|
||||||
if persons and 'value' in persons and len(persons['value']) > 0:
|
if persons and 'value' in persons and len(persons['value']) > 0:
|
||||||
target_person = persons['value'][0]
|
target_person = persons['value'][0]
|
||||||
# Fix keys here too
|
|
||||||
person_id = target_person.get('personId') or target_person.get('PersonId')
|
person_id = target_person.get('personId') or target_person.get('PersonId')
|
||||||
first_name = target_person.get('firstName') or target_person.get('FirstName')
|
first_name = target_person.get('firstName') or target_person.get('FirstName')
|
||||||
last_name = target_person.get('lastName') or target_person.get('LastName')
|
last_name = target_person.get('lastName') or target_person.get('LastName')
|
||||||
@@ -127,14 +134,17 @@ def main():
|
|||||||
|
|
||||||
print("\n--- 3. Creating Sale (Opportunity) ---")
|
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
|
# Construct the payload for a new Sale
|
||||||
sale_payload = {
|
sale_payload = {
|
||||||
"Heading": "AI Prospect: Automation Potential Detected",
|
"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",
|
"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,
|
"Amount": 5000.0,
|
||||||
# FIXED: Use Object with ID for Currency
|
"Saledate": sale_date, # MANDATORY: Estimated closing date
|
||||||
"Currency": { "Id": eur_id },
|
"Currency": { "Id": eur_id },
|
||||||
"SaleType": { "Id": 1 }, # Allgemeiner Verkauf
|
"SaleType": { "Id": 14 }, # FIXED: 14 = Roboplanet Verkauf
|
||||||
"Stage": { "Id": 10 }, # 5% Angebot prospektiv
|
"Stage": { "Id": 10 }, # 5% Angebot prospektiv
|
||||||
"Contact": { "ContactId": contact_id },
|
"Contact": { "ContactId": contact_id },
|
||||||
"Source": { "Id": 2 } # Proposal Center
|
"Source": { "Id": 2 } # Proposal Center
|
||||||
@@ -145,17 +155,19 @@ def main():
|
|||||||
|
|
||||||
print(f"Payload Preview: {json.dumps(sale_payload, indent=2)}")
|
print(f"Payload Preview: {json.dumps(sale_payload, indent=2)}")
|
||||||
|
|
||||||
# Send POST request to create the Sale
|
# Uncomment to actually run creation
|
||||||
new_sale = client._post("Sale", sale_payload)
|
# new_sale = client._post("Sale", sale_payload)
|
||||||
|
|
||||||
print("\n--- ✅ SUCCESS: Sale Created! ---")
|
# print("\n--- ✅ SUCCESS: Sale Created! ---")
|
||||||
sale_id = new_sale.get('SaleId')
|
# sale_id = new_sale.get('SaleId')
|
||||||
sale_number = new_sale.get('SaleNumber')
|
# sale_number = new_sale.get('SaleNumber')
|
||||||
print(f"Sale ID: {sale_id}")
|
# print(f"Sale ID: {sale_id}")
|
||||||
print(f"Sale Number: {sale_number}")
|
# print(f"Sale Number: {sale_number}")
|
||||||
|
|
||||||
sale_link = f"https://{auth.env}.superoffice.com/{auth.cust_id}/default.aspx?sale_id={sale_id}"
|
# 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(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:
|
except requests.exceptions.HTTPError as e:
|
||||||
logger.error(f"❌ API Error: {e}")
|
logger.error(f"❌ API Error: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user