fusesell 1.2.1__tar.gz → 1.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fusesell might be problematic. Click here for more details.
- {fusesell-1.2.1 → fusesell-1.2.2}/CHANGELOG.md +9 -1
- {fusesell-1.2.1/fusesell.egg-info → fusesell-1.2.2}/PKG-INFO +1 -1
- {fusesell-1.2.1 → fusesell-1.2.2/fusesell.egg-info}/PKG-INFO +1 -1
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell.egg-info/SOURCES.txt +4 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/__init__.py +1 -1
- fusesell-1.2.2/fusesell_local/tests/conftest.py +11 -0
- fusesell-1.2.2/fusesell_local/tests/test_data_manager_products.py +74 -0
- fusesell-1.2.2/fusesell_local/tests/test_data_manager_sales_process.py +115 -0
- fusesell-1.2.2/fusesell_local/tests/test_data_manager_teams.py +133 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/utils/data_manager.py +95 -67
- {fusesell-1.2.1 → fusesell-1.2.2}/pyproject.toml +1 -1
- {fusesell-1.2.1 → fusesell-1.2.2}/LICENSE +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/MANIFEST.in +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/README.md +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell.egg-info/dependency_links.txt +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell.egg-info/entry_points.txt +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell.egg-info/requires.txt +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell.egg-info/top_level.txt +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/api.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/cli.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/config/__init__.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/config/prompts.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/config/settings.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/pipeline.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/stages/__init__.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/stages/base_stage.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/stages/data_acquisition.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/stages/data_preparation.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/stages/follow_up.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/stages/initial_outreach.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/stages/lead_scoring.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/tests/test_api.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/tests/test_cli.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/utils/__init__.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/utils/birthday_email_manager.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/utils/event_scheduler.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/utils/llm_client.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/utils/logger.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/utils/timezone_detector.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/fusesell_local/utils/validators.py +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/requirements.txt +0 -0
- {fusesell-1.2.1 → fusesell-1.2.2}/setup.cfg +0 -0
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
All notable changes to FuseSell Local will be documented in this file.
|
|
3
|
+
All notable changes to FuseSell Local will be documented in this file.
|
|
4
|
+
|
|
5
|
+
# [1.2.2] - 2025-10-21
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- LocalDataManager regression tests covering product/team CRUD flows and sales process tracking helpers.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Default team settings seeding now targets the `gs_team_*` columns, preventing initialization failures on fresh databases.
|
|
4
12
|
|
|
5
13
|
# [1.2.1] - 2025-10-21
|
|
6
14
|
|
|
@@ -25,8 +25,12 @@ fusesell_local/stages/data_preparation.py
|
|
|
25
25
|
fusesell_local/stages/follow_up.py
|
|
26
26
|
fusesell_local/stages/initial_outreach.py
|
|
27
27
|
fusesell_local/stages/lead_scoring.py
|
|
28
|
+
fusesell_local/tests/conftest.py
|
|
28
29
|
fusesell_local/tests/test_api.py
|
|
29
30
|
fusesell_local/tests/test_cli.py
|
|
31
|
+
fusesell_local/tests/test_data_manager_products.py
|
|
32
|
+
fusesell_local/tests/test_data_manager_sales_process.py
|
|
33
|
+
fusesell_local/tests/test_data_manager_teams.py
|
|
30
34
|
fusesell_local/utils/__init__.py
|
|
31
35
|
fusesell_local/utils/birthday_email_manager.py
|
|
32
36
|
fusesell_local/utils/data_manager.py
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from fusesell_local.utils.data_manager import LocalDataManager
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.fixture
|
|
7
|
+
def data_manager(tmp_path):
|
|
8
|
+
"""Provide an isolated LocalDataManager instance backed by a temporary directory."""
|
|
9
|
+
LocalDataManager._initialized_databases.clear()
|
|
10
|
+
LocalDataManager._initialization_lock = False
|
|
11
|
+
return LocalDataManager(data_dir=str(tmp_path))
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _sample_product_payload(product_id: str = "prod-001", **overrides):
|
|
5
|
+
payload = {
|
|
6
|
+
"product_id": product_id,
|
|
7
|
+
"org_id": "org-123",
|
|
8
|
+
"org_name": "FuseSell Org",
|
|
9
|
+
"project_code": "proj-001",
|
|
10
|
+
"productName": "FuseSell AI Suite",
|
|
11
|
+
"shortDescription": "AI-powered sales automation",
|
|
12
|
+
"longDescription": "Comprehensive automation platform for revenue teams.",
|
|
13
|
+
"category": "Software",
|
|
14
|
+
"subcategory": "Sales Automation",
|
|
15
|
+
"targetUsers": ["Sales reps"],
|
|
16
|
+
"keyFeatures": ["Automation", "CRM sync"],
|
|
17
|
+
"uniqueSellingPoints": ["Privacy-first"],
|
|
18
|
+
"pricing": {"monthly": 199},
|
|
19
|
+
"pricingRules": {"currency": "USD"},
|
|
20
|
+
"productWebsite": "https://fusesell.test/ai-suite",
|
|
21
|
+
}
|
|
22
|
+
payload.update(overrides)
|
|
23
|
+
return payload
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_save_and_get_product_roundtrip(data_manager):
|
|
27
|
+
payload = _sample_product_payload()
|
|
28
|
+
product_id = data_manager.save_product(payload)
|
|
29
|
+
|
|
30
|
+
assert product_id == payload["product_id"]
|
|
31
|
+
|
|
32
|
+
stored = data_manager.get_product(product_id)
|
|
33
|
+
assert stored is not None
|
|
34
|
+
assert stored["product_name"] == payload["productName"]
|
|
35
|
+
assert stored["org_id"] == payload["org_id"]
|
|
36
|
+
assert stored["key_features"] == payload["keyFeatures"]
|
|
37
|
+
assert stored["pricing"] == payload["pricing"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_update_product_merges_changes(data_manager):
|
|
41
|
+
payload = _sample_product_payload()
|
|
42
|
+
product_id = data_manager.save_product(payload)
|
|
43
|
+
|
|
44
|
+
updated = {
|
|
45
|
+
"shortDescription": "Updated messaging",
|
|
46
|
+
"pricing": {"monthly": 249, "yearly": 2490},
|
|
47
|
+
"keyFeatures": ["Automation", "Analytics"],
|
|
48
|
+
}
|
|
49
|
+
assert data_manager.update_product(product_id, updated) is True
|
|
50
|
+
|
|
51
|
+
stored = data_manager.get_product(product_id)
|
|
52
|
+
assert stored["short_description"] == updated["shortDescription"]
|
|
53
|
+
assert stored["pricing"]["monthly"] == 249
|
|
54
|
+
assert stored["key_features"] == updated["keyFeatures"]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_get_products_by_org_returns_active_products_only(data_manager):
|
|
58
|
+
active = _sample_product_payload("prod-active")
|
|
59
|
+
inactive = _sample_product_payload("prod-inactive")
|
|
60
|
+
|
|
61
|
+
data_manager.save_product(active)
|
|
62
|
+
data_manager.save_product(inactive)
|
|
63
|
+
|
|
64
|
+
# Mark one product inactive directly to exercise status filtering
|
|
65
|
+
with sqlite3.connect(data_manager.db_path) as conn:
|
|
66
|
+
conn.execute(
|
|
67
|
+
"UPDATE products SET status = 'inactive' WHERE product_id = ?",
|
|
68
|
+
(inactive["product_id"],),
|
|
69
|
+
)
|
|
70
|
+
conn.commit()
|
|
71
|
+
|
|
72
|
+
results = data_manager.get_products_by_org(active["org_id"])
|
|
73
|
+
assert len(results) == 1
|
|
74
|
+
assert results[0]["product_id"] == active["product_id"]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
def _create_task(data_manager, task_id: str, customer: str = "Acme Corp", status: str = "running"):
|
|
2
|
+
request_body = {"customer_info": customer, "org_name": "FuseSell Org"}
|
|
3
|
+
data_manager.create_task(
|
|
4
|
+
task_id=task_id,
|
|
5
|
+
plan_id="plan-456",
|
|
6
|
+
org_id="org-123",
|
|
7
|
+
request_body=request_body,
|
|
8
|
+
status=status,
|
|
9
|
+
)
|
|
10
|
+
return request_body
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_create_task_and_get_task_by_id(data_manager):
|
|
14
|
+
task_id = "task-001"
|
|
15
|
+
request_body = _create_task(data_manager, task_id)
|
|
16
|
+
|
|
17
|
+
record = data_manager.get_task_by_id(task_id)
|
|
18
|
+
assert record is not None
|
|
19
|
+
assert record["task_id"] == task_id
|
|
20
|
+
assert record["status"] == "running"
|
|
21
|
+
assert record["request_body"]["customer_info"] == request_body["customer_info"]
|
|
22
|
+
assert record["messages"] == []
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_update_task_status_tracks_runtime(data_manager):
|
|
26
|
+
task_id = "task-002"
|
|
27
|
+
_create_task(data_manager, task_id)
|
|
28
|
+
|
|
29
|
+
data_manager.update_task_status(task_id, "completed", runtime_index=4)
|
|
30
|
+
|
|
31
|
+
record = data_manager.get_task_by_id(task_id)
|
|
32
|
+
assert record["status"] == "completed"
|
|
33
|
+
assert record["current_runtime_index"] == 4
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_create_operation_and_update_status(data_manager):
|
|
37
|
+
task_id = "task-003"
|
|
38
|
+
_create_task(data_manager, task_id)
|
|
39
|
+
|
|
40
|
+
operation_id = data_manager.create_operation(
|
|
41
|
+
task_id, "gs_161_data_acquisition", runtime_index=0, chain_index=0, input_data={"step": 1}
|
|
42
|
+
)
|
|
43
|
+
operation = data_manager.get_operation(operation_id)
|
|
44
|
+
assert operation is not None
|
|
45
|
+
assert operation["execution_status"] == "running"
|
|
46
|
+
assert operation["input_data"]["step"] == 1
|
|
47
|
+
|
|
48
|
+
data_manager.update_operation_status(operation_id, "done", {"result": "ok"})
|
|
49
|
+
updated = data_manager.get_operation(operation_id)
|
|
50
|
+
assert updated["execution_status"] == "done"
|
|
51
|
+
assert updated["output_data"]["result"] == "ok"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_get_task_with_operations_returns_summary(data_manager):
|
|
55
|
+
task_id = "task-ops"
|
|
56
|
+
_create_task(data_manager, task_id)
|
|
57
|
+
|
|
58
|
+
op_a = data_manager.create_operation(
|
|
59
|
+
task_id, "gs_161_data_acquisition", runtime_index=0, chain_index=0, input_data={"stage": "acquire"}
|
|
60
|
+
)
|
|
61
|
+
op_b = data_manager.create_operation(
|
|
62
|
+
task_id, "gs_161_data_preparation", runtime_index=0, chain_index=1, input_data={"stage": "prepare"}
|
|
63
|
+
)
|
|
64
|
+
data_manager.update_operation_status(op_a, "done", {"status": "ok"})
|
|
65
|
+
data_manager.update_operation_status(op_b, "running")
|
|
66
|
+
|
|
67
|
+
record = data_manager.get_task_with_operations(task_id)
|
|
68
|
+
assert record is not None
|
|
69
|
+
assert record["task_id"] == task_id
|
|
70
|
+
assert len(record["operations"]) == 2
|
|
71
|
+
assert record["summary"]["completed_operations"] == 1
|
|
72
|
+
assert record["summary"]["running_operations"] == 1
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_find_sales_processes_by_customer(data_manager):
|
|
76
|
+
task_acme = "task-acme"
|
|
77
|
+
task_beta = "task-beta"
|
|
78
|
+
_create_task(data_manager, task_acme, customer="Acme Corp")
|
|
79
|
+
_create_task(data_manager, task_beta, customer="Beta LLC")
|
|
80
|
+
|
|
81
|
+
results = data_manager.find_sales_processes_by_customer("Acme")
|
|
82
|
+
assert len(results) == 1
|
|
83
|
+
assert results[0]["task_id"] == task_acme
|
|
84
|
+
assert results[0]["request_body"]["customer_info"] == "Acme Corp"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_list_tasks_filters_by_status(data_manager):
|
|
88
|
+
running_id = "task-running"
|
|
89
|
+
completed_id = "task-completed"
|
|
90
|
+
_create_task(data_manager, running_id, status="running")
|
|
91
|
+
_create_task(data_manager, completed_id, status="running")
|
|
92
|
+
data_manager.update_task_status(completed_id, "completed")
|
|
93
|
+
|
|
94
|
+
running_tasks = data_manager.list_tasks(status="running")
|
|
95
|
+
completed_tasks = data_manager.list_tasks(status="completed")
|
|
96
|
+
|
|
97
|
+
assert {task["task_id"] for task in running_tasks} == {running_id}
|
|
98
|
+
assert {task["task_id"] for task in completed_tasks} == {completed_id}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_get_execution_timeline_orders_by_indices(data_manager):
|
|
102
|
+
task_id = "task-timeline"
|
|
103
|
+
_create_task(data_manager, task_id)
|
|
104
|
+
|
|
105
|
+
op_first = data_manager.create_operation(
|
|
106
|
+
task_id, "gs_161_data_acquisition", runtime_index=0, chain_index=0, input_data={"order": 1}
|
|
107
|
+
)
|
|
108
|
+
op_second = data_manager.create_operation(
|
|
109
|
+
task_id, "gs_161_data_preparation", runtime_index=0, chain_index=1, input_data={"order": 2}
|
|
110
|
+
)
|
|
111
|
+
data_manager.update_operation_status(op_first, "done")
|
|
112
|
+
data_manager.update_operation_status(op_second, "done")
|
|
113
|
+
|
|
114
|
+
timeline = data_manager.get_execution_timeline(task_id)
|
|
115
|
+
assert [entry["input_data"]["order"] for entry in timeline] == [1, 2]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
def _team_payload(team_id: str = "team-001", **overrides):
|
|
2
|
+
payload = {
|
|
3
|
+
"team_id": team_id,
|
|
4
|
+
"org_id": "org-123",
|
|
5
|
+
"org_name": "FuseSell Org",
|
|
6
|
+
"plan_id": "plan-456",
|
|
7
|
+
"plan_name": "FuseSell AI",
|
|
8
|
+
"project_code": "proj-001",
|
|
9
|
+
"name": "Outbound Squad",
|
|
10
|
+
"description": "Primary outreach team",
|
|
11
|
+
"avatar": "https://fusesell.test/avatar.png",
|
|
12
|
+
}
|
|
13
|
+
payload.update(overrides)
|
|
14
|
+
return payload
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_save_and_get_team_roundtrip(data_manager):
|
|
18
|
+
payload = _team_payload()
|
|
19
|
+
data_manager.save_team(**payload)
|
|
20
|
+
|
|
21
|
+
stored = data_manager.get_team(payload["team_id"])
|
|
22
|
+
assert stored is not None
|
|
23
|
+
assert stored["team_id"] == payload["team_id"]
|
|
24
|
+
assert stored["name"] == payload["name"]
|
|
25
|
+
assert stored["plan_id"] == payload["plan_id"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_update_team_modifies_selected_fields(data_manager):
|
|
29
|
+
payload = _team_payload()
|
|
30
|
+
data_manager.save_team(**payload)
|
|
31
|
+
|
|
32
|
+
updated_name = "Expansion Squad"
|
|
33
|
+
updated_plan = "FuseSell AI Enterprise"
|
|
34
|
+
assert data_manager.update_team(
|
|
35
|
+
payload["team_id"], name=updated_name, plan_name=updated_plan
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
stored = data_manager.get_team(payload["team_id"])
|
|
39
|
+
assert stored["name"] == updated_name
|
|
40
|
+
assert stored["plan_name"] == updated_plan
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_list_teams_returns_all_for_org(data_manager):
|
|
44
|
+
first = _team_payload("team-001")
|
|
45
|
+
second = _team_payload("team-002", name="Inbound Squad")
|
|
46
|
+
data_manager.save_team(**first)
|
|
47
|
+
data_manager.save_team(**second)
|
|
48
|
+
|
|
49
|
+
teams = data_manager.list_teams(first["org_id"])
|
|
50
|
+
identifiers = {team["team_id"] for team in teams}
|
|
51
|
+
assert identifiers == {first["team_id"], second["team_id"]}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_save_and_get_team_settings_roundtrip(data_manager):
|
|
55
|
+
payload = _team_payload()
|
|
56
|
+
data_manager.save_team(**payload)
|
|
57
|
+
|
|
58
|
+
data_manager.save_team_settings(
|
|
59
|
+
team_id=payload["team_id"],
|
|
60
|
+
org_id=payload["org_id"],
|
|
61
|
+
plan_id=payload["plan_id"],
|
|
62
|
+
team_name=payload["name"],
|
|
63
|
+
gs_team_organization={"sales_regions": ["NA", "EU"]},
|
|
64
|
+
gs_team_rep=[{"name": "Alex"}],
|
|
65
|
+
gs_team_product=[{"product_id": "prod-001"}],
|
|
66
|
+
gs_team_schedule_time={"timezone": "UTC"},
|
|
67
|
+
gs_team_initial_outreach={"enabled": True},
|
|
68
|
+
gs_team_follow_up={"sequence": 2},
|
|
69
|
+
gs_team_auto_interaction={"mode": "assist"},
|
|
70
|
+
gs_team_followup_schedule_time={"window": "business_hours"},
|
|
71
|
+
gs_team_birthday_email={"enabled": False},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
settings = data_manager.get_team_settings(payload["team_id"])
|
|
75
|
+
assert settings is not None
|
|
76
|
+
assert settings["org_id"] == payload["org_id"]
|
|
77
|
+
assert settings["gs_team_organization"]["sales_regions"] == ["NA", "EU"]
|
|
78
|
+
assert settings["gs_team_rep"][0]["name"] == "Alex"
|
|
79
|
+
assert settings["gs_team_product"][0]["product_id"] == "prod-001"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_save_team_settings_updates_existing_record(data_manager):
|
|
83
|
+
payload = _team_payload()
|
|
84
|
+
data_manager.save_team(**payload)
|
|
85
|
+
|
|
86
|
+
data_manager.save_team_settings(
|
|
87
|
+
team_id=payload["team_id"],
|
|
88
|
+
org_id=payload["org_id"],
|
|
89
|
+
plan_id=payload["plan_id"],
|
|
90
|
+
team_name=payload["name"],
|
|
91
|
+
gs_team_schedule_time={"timezone": "UTC"},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
data_manager.save_team_settings(
|
|
95
|
+
team_id=payload["team_id"],
|
|
96
|
+
org_id=payload["org_id"],
|
|
97
|
+
plan_id=payload["plan_id"],
|
|
98
|
+
team_name=payload["name"],
|
|
99
|
+
gs_team_schedule_time={"timezone": "America/New_York"},
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
settings = data_manager.get_team_settings(payload["team_id"])
|
|
103
|
+
assert settings["gs_team_schedule_time"]["timezone"] == "America/New_York"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_get_products_by_team_uses_team_settings(data_manager):
|
|
107
|
+
team = _team_payload()
|
|
108
|
+
data_manager.save_team(**team)
|
|
109
|
+
|
|
110
|
+
product_payload = {
|
|
111
|
+
"product_id": "prod-001",
|
|
112
|
+
"org_id": team["org_id"],
|
|
113
|
+
"org_name": team["org_name"],
|
|
114
|
+
"project_code": team["project_code"],
|
|
115
|
+
"productName": "FuseSell Starter",
|
|
116
|
+
"shortDescription": "Entry-level automation",
|
|
117
|
+
"longDescription": "Starter bundle.",
|
|
118
|
+
"category": "Software",
|
|
119
|
+
"targetUsers": ["SMB"],
|
|
120
|
+
}
|
|
121
|
+
data_manager.save_product(product_payload)
|
|
122
|
+
|
|
123
|
+
data_manager.save_team_settings(
|
|
124
|
+
team_id=team["team_id"],
|
|
125
|
+
org_id=team["org_id"],
|
|
126
|
+
plan_id=team["plan_id"],
|
|
127
|
+
team_name=team["name"],
|
|
128
|
+
gs_team_product=[{"product_id": product_payload["product_id"]}],
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
products = data_manager.get_products_by_team(team["team_id"])
|
|
132
|
+
assert len(products) == 1
|
|
133
|
+
assert products[0]["product_id"] == product_payload["product_id"]
|
|
@@ -2135,73 +2135,101 @@ class LocalDataManager:
|
|
|
2135
2135
|
"SELECT COUNT(*) FROM team_settings WHERE org_id = 'rta'")
|
|
2136
2136
|
team_count = cursor.fetchone()[0]
|
|
2137
2137
|
|
|
2138
|
-
if team_count == 0:
|
|
2139
|
-
default_team_settings = {
|
|
2140
|
-
'id': 'team_rta_default_settings',
|
|
2141
|
-
'team_id': 'team_rta_default',
|
|
2142
|
-
'org_id': 'rta',
|
|
2143
|
-
'plan_id': '569cdcbd-cf6d-4e33-b0b2-d2f6f15a0832',
|
|
2144
|
-
'plan_name': 'FuseSell AI (v1.025)',
|
|
2145
|
-
'project_code': 'FUSESELL',
|
|
2146
|
-
'team_name': 'RTA Default Team',
|
|
2147
|
-
'
|
|
2148
|
-
'name': 'RTA',
|
|
2149
|
-
'industry': 'Technology',
|
|
2150
|
-
'website': 'https://rta.vn'
|
|
2151
|
-
}),
|
|
2152
|
-
'
|
|
2153
|
-
'name': 'Sales Team',
|
|
2154
|
-
'email': 'sales@rta.vn',
|
|
2155
|
-
'position': 'Sales Representative',
|
|
2156
|
-
'is_primary': True
|
|
2157
|
-
}]),
|
|
2158
|
-
'
|
|
2159
|
-
{'product_id': 'prod-12345678-1234-1234-1234-123456789012',
|
|
2160
|
-
|
|
2161
|
-
{'product_id': 'prod-87654321-4321-4321-4321-210987654321',
|
|
2162
|
-
|
|
2163
|
-
]),
|
|
2164
|
-
'
|
|
2165
|
-
'business_hours_start': '08:00',
|
|
2166
|
-
'business_hours_end': '20:00',
|
|
2167
|
-
'default_delay_hours': 2,
|
|
2168
|
-
'respect_weekends': True
|
|
2169
|
-
}),
|
|
2170
|
-
'
|
|
2171
|
-
'default_tone': 'professional',
|
|
2172
|
-
'approaches': [
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
'
|
|
2179
|
-
})
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2138
|
+
if team_count == 0:
|
|
2139
|
+
default_team_settings = {
|
|
2140
|
+
'id': 'team_rta_default_settings',
|
|
2141
|
+
'team_id': 'team_rta_default',
|
|
2142
|
+
'org_id': 'rta',
|
|
2143
|
+
'plan_id': '569cdcbd-cf6d-4e33-b0b2-d2f6f15a0832',
|
|
2144
|
+
'plan_name': 'FuseSell AI (v1.025)',
|
|
2145
|
+
'project_code': 'FUSESELL',
|
|
2146
|
+
'team_name': 'RTA Default Team',
|
|
2147
|
+
'gs_team_organization': json.dumps({
|
|
2148
|
+
'name': 'RTA',
|
|
2149
|
+
'industry': 'Technology',
|
|
2150
|
+
'website': 'https://rta.vn'
|
|
2151
|
+
}),
|
|
2152
|
+
'gs_team_rep': json.dumps([{
|
|
2153
|
+
'name': 'Sales Team',
|
|
2154
|
+
'email': 'sales@rta.vn',
|
|
2155
|
+
'position': 'Sales Representative',
|
|
2156
|
+
'is_primary': True
|
|
2157
|
+
}]),
|
|
2158
|
+
'gs_team_product': json.dumps([
|
|
2159
|
+
{'product_id': 'prod-12345678-1234-1234-1234-123456789012',
|
|
2160
|
+
'enabled': True, 'priority': 1},
|
|
2161
|
+
{'product_id': 'prod-87654321-4321-4321-4321-210987654321',
|
|
2162
|
+
'enabled': True, 'priority': 2}
|
|
2163
|
+
]),
|
|
2164
|
+
'gs_team_schedule_time': json.dumps({
|
|
2165
|
+
'business_hours_start': '08:00',
|
|
2166
|
+
'business_hours_end': '20:00',
|
|
2167
|
+
'default_delay_hours': 2,
|
|
2168
|
+
'respect_weekends': True
|
|
2169
|
+
}),
|
|
2170
|
+
'gs_team_initial_outreach': json.dumps({
|
|
2171
|
+
'default_tone': 'professional',
|
|
2172
|
+
'approaches': [
|
|
2173
|
+
'professional_direct',
|
|
2174
|
+
'consultative',
|
|
2175
|
+
'industry_expert',
|
|
2176
|
+
'relationship_building'
|
|
2177
|
+
],
|
|
2178
|
+
'subject_line_variations': 4
|
|
2179
|
+
}),
|
|
2180
|
+
'gs_team_follow_up': json.dumps({
|
|
2181
|
+
'max_follow_ups': 5,
|
|
2182
|
+
'default_interval_days': 3,
|
|
2183
|
+
'strategies': [
|
|
2184
|
+
'gentle_reminder',
|
|
2185
|
+
'value_add',
|
|
2186
|
+
'alternative_approach',
|
|
2187
|
+
'final_attempt',
|
|
2188
|
+
'graceful_farewell'
|
|
2189
|
+
]
|
|
2190
|
+
}),
|
|
2191
|
+
'gs_team_auto_interaction': json.dumps({
|
|
2192
|
+
'enabled': True,
|
|
2193
|
+
'handoff_threshold': 0.8,
|
|
2194
|
+
'monitoring': 'standard'
|
|
2195
|
+
}),
|
|
2196
|
+
'gs_team_followup_schedule_time': json.dumps({
|
|
2197
|
+
'timezone': 'Asia/Ho_Chi_Minh',
|
|
2198
|
+
'window': 'business_hours'
|
|
2199
|
+
}),
|
|
2200
|
+
'gs_team_birthday_email': json.dumps({
|
|
2201
|
+
'enabled': True,
|
|
2202
|
+
'template': 'birthday_2025'
|
|
2203
|
+
})
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
cursor.execute("""
|
|
2207
|
+
INSERT INTO team_settings
|
|
2208
|
+
(id, team_id, org_id, plan_id, plan_name, project_code, team_name,
|
|
2209
|
+
gs_team_organization, gs_team_rep, gs_team_product,
|
|
2210
|
+
gs_team_schedule_time, gs_team_initial_outreach, gs_team_follow_up,
|
|
2211
|
+
gs_team_auto_interaction, gs_team_followup_schedule_time, gs_team_birthday_email)
|
|
2212
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2213
|
+
""", (
|
|
2214
|
+
default_team_settings['id'],
|
|
2215
|
+
default_team_settings['team_id'],
|
|
2216
|
+
default_team_settings['org_id'],
|
|
2217
|
+
default_team_settings['plan_id'],
|
|
2218
|
+
default_team_settings['plan_name'],
|
|
2219
|
+
default_team_settings['project_code'],
|
|
2220
|
+
default_team_settings['team_name'],
|
|
2221
|
+
default_team_settings['gs_team_organization'],
|
|
2222
|
+
default_team_settings['gs_team_rep'],
|
|
2223
|
+
default_team_settings['gs_team_product'],
|
|
2224
|
+
default_team_settings['gs_team_schedule_time'],
|
|
2225
|
+
default_team_settings['gs_team_initial_outreach'],
|
|
2226
|
+
default_team_settings['gs_team_follow_up'],
|
|
2227
|
+
default_team_settings['gs_team_auto_interaction'],
|
|
2228
|
+
default_team_settings['gs_team_followup_schedule_time'],
|
|
2229
|
+
default_team_settings['gs_team_birthday_email']
|
|
2230
|
+
))
|
|
2231
|
+
|
|
2232
|
+
self.logger.debug("Initialized default team settings")
|
|
2205
2233
|
|
|
2206
2234
|
conn.commit()
|
|
2207
2235
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|